1 /* main.cc -- driver for mmafm program
2  *
3  * Copyright (c) 1997-2013 Eddie Kohler
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the Free
7  * Software Foundation; either version 2 of the License, or (at your option)
8  * any later version. This program is distributed in the hope that it will be
9  * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
11  * Public License for more details.
12  */
13 
14 #ifdef HAVE_CONFIG_H
15 # include <config.h>
16 #endif
17 #include <efont/findmet.hh>
18 #include <efont/psres.hh>
19 #include <lcdf/slurper.hh>
20 #include <efont/afm.hh>
21 #include <efont/afmw.hh>
22 #include <efont/amfm.hh>
23 #include <lcdf/error.hh>
24 #include <lcdf/clp.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <errno.h>
28 #include <math.h>
29 #ifdef HAVE_CTIME
30 # include <time.h>
31 #endif
32 
33 #define WEIGHT_OPT	300
34 #define WIDTH_OPT	301
35 #define OPSIZE_OPT	302
36 #define STYLE_OPT	303
37 #define N1_OPT		304
38 #define N2_OPT		305
39 #define N3_OPT		306
40 #define N4_OPT		307
41 #define VERSION_OPT	308
42 #define HELP_OPT	309
43 #define OUTPUT_OPT	310
44 #define PRECISION_OPT	311
45 #define KERN_PREC_OPT	312
46 
47 const Clp_Option options[] = {
48   { "1", '1', N1_OPT, Clp_ValDouble, 0 },
49   { "2", '2', N2_OPT, Clp_ValDouble, 0 },
50   { "3", '3', N3_OPT, Clp_ValDouble, 0 },
51   { "4", '4', N4_OPT, Clp_ValDouble, 0 },
52   { "weight", 'w', WEIGHT_OPT, Clp_ValDouble, 0 },
53   { "width", 'W', WIDTH_OPT, Clp_ValDouble, 0 },
54   { "optical-size", 'O', OPSIZE_OPT, Clp_ValDouble, 0 },
55   { "style", 0, STYLE_OPT, Clp_ValDouble, 0 },
56   { "wt", 0, WEIGHT_OPT, Clp_ValDouble, 0 },
57   { "wd", 0, WIDTH_OPT, Clp_ValDouble, 0 },
58   { "min-kern", 'k', KERN_PREC_OPT, Clp_ValDouble, 0 },
59   { "minimum-kern", 'k', KERN_PREC_OPT, Clp_ValDouble, 0 },
60   { "kern-precision", 'k', KERN_PREC_OPT, Clp_ValDouble, 0 },
61   { "output", 'o', OUTPUT_OPT, Clp_ValString, 0 },
62   { "precision", 'p', PRECISION_OPT, Clp_ValInt, 0 },
63   { "version", 'v', VERSION_OPT, 0, 0 },
64   { "help", 'h', HELP_OPT, 0, 0 },
65 };
66 
67 using namespace Efont;
68 
69 
70 static const char *program_name;
71 static ErrorHandler *errh;
72 static AmfmMetrics *amfm;
73 
74 static Vector<PermString> ax_names;
75 static Vector<int> ax_nums;
76 static Vector<double> values;
77 
78 static void
set_design(PermString a,double v)79 set_design(PermString a, double v)
80 {
81   ax_names.push_back(a);
82   ax_nums.push_back(-1);
83   values.push_back(v);
84 }
85 
86 static void
set_design(int a,double v)87 set_design(int a, double v)
88 {
89   ax_names.push_back(PermString());
90   ax_nums.push_back(a);
91   values.push_back(v);
92 }
93 
94 static void
set_amfm(AmfmMetrics * a)95 set_amfm(AmfmMetrics *a)
96 {
97   if (a) {
98     if (amfm) errh->fatal("already read one AMFM file");
99     amfm = a;
100   }
101 }
102 
103 
104 // apply precision
105 static inline void
pround(double & v,double multiplier,double divider)106 pround(double &v, double multiplier, double divider)
107 {
108   if (KNOWN(v))
109     v = floor(v * multiplier + 0.5) * divider;
110 }
111 
112 static void
apply_precision(Metrics * m,int precision)113 apply_precision(Metrics *m, int precision)
114 {
115   if (precision < 0)
116     return;
117 
118   double multiplier = 1, divider = 1;
119   for (int i = 0; i < precision; i++)
120     multiplier *= 10, divider /= 10;
121 
122   for (int i = 0; i < m->nfd(); i++)
123     pround(m->fd(i), multiplier, divider);
124 
125   for (int i = 0; i < m->nglyphs(); i++) {
126     pround(m->wd(i), multiplier, divider);
127     pround(m->lf(i), multiplier, divider);
128     pround(m->bt(i), multiplier, divider);
129     pround(m->rt(i), multiplier, divider);
130     pround(m->tp(i), multiplier, divider);
131   }
132 
133   for (int i = 0; i < m->nkv(); i++)
134     pround(m->kv(i), multiplier, divider);
135 }
136 
137 static void
apply_kern_precision(Metrics * m,double kern_precision)138 apply_kern_precision(Metrics *m, double kern_precision)
139 {
140   if (kern_precision <= 0)
141     return;
142 
143   for (int i = 0; i < m->nkv(); i++)
144     if (fabs(m->kv(i)) < kern_precision)
145       m->kv(i) = 0;
146 }
147 
148 
149 static void
read_file(const char * fn,MetricsFinder * finder)150 read_file(const char *fn, MetricsFinder *finder)
151 {
152   Filename filename;
153   FILE *file;
154 
155   if (strcmp(fn, "-") == 0) {
156     filename = Filename("<stdin>");
157     file = stdin;
158   } else {
159     filename = Filename(fn);
160     file = filename.open_read();
161   }
162   int save_errno = errno;
163 
164   if (!file) {
165     // look for a font by name
166     AmfmMetrics *new_amfm = finder->find_amfm(fn, errh);
167     if (new_amfm) {
168       set_amfm(new_amfm);
169       return;
170     }
171     if (finder->find_metrics(fn, errh))
172       return;
173 
174     // check for instance name. don't use InstanceMetricsFinder.
175     const char *underscore = strchr(fn, '_');
176     if (underscore)
177       new_amfm = finder->find_amfm(PermString(fn, underscore - fn), errh);
178     if (!new_amfm)
179       errh->fatal("%s: %s", fn, strerror(save_errno));
180     set_amfm(new_amfm);
181 
182     int i = 0;
183     while (underscore[0] == '_' && underscore[1]) {
184       double x = strtod(underscore + 1, const_cast<char **>(&underscore));
185       set_design(i, x);
186       i++;
187     }
188     return;
189   }
190 
191   Slurper slurper(filename, file);
192   bool is_afm = false;
193   if (file != stdin) {
194     char *first_line = slurper.peek_line();
195     if (first_line)
196       is_afm = strncmp(first_line, "StartFontMetrics", 16) == 0;
197   }
198 
199   if (is_afm) {
200     Metrics *afm = AfmReader::read(slurper, errh);
201     if (afm)
202       finder->record(afm);
203   } else
204     set_amfm(AmfmReader::read(slurper, finder, errh));
205 }
206 
207 
208 static void
usage_error(const char * error_message,...)209 usage_error(const char *error_message, ...)
210 {
211   va_list val;
212   va_start(val, error_message);
213   if (!error_message)
214     errh->message("Usage: %s [OPTION | FONT]...", program_name);
215   else
216     errh->vxmessage(ErrorHandler::e_error, error_message, val);
217   errh->message("Type %s --help for more information.", program_name);
218   exit(1);
219 }
220 
221 static void
usage()222 usage()
223 {
224     FileErrorHandler uerrh(stdout);
225     uerrh.message("\
226 %<Mmafm%> creates an AFM font metrics file for a multiple master font by\n\
227 interpolating at a point you specify and writes it to the standard output.\n\
228 \n\
229 Usage: %s [OPTION | FONT]...\n\
230 \n\
231 Each FONT is either an AFM or AMFM file name, or the font name of a multiple\n\
232 master font. In the second case, mmafm will find the actual AMFM file using\n\
233 the PSRESOURCEPATH environment variable.\n\
234 \n\
235 General options:\n\
236   -o, --output=FILE             Write output to FILE.\n\
237   -h, --help                    Print this message and exit.\n\
238   -v, --version                 Print version number and warranty and exit.\n\
239 \n\
240 Interpolation settings:\n\
241   -w, --weight=N                Set weight to N.\n\
242   -W, --width=N                 Set width to N.\n\
243   -O, --optical-size=N          Set optical size to N.\n\
244       --style=N                 Set style axis to N.\n\
245   --1=N, --2=N, --3=N, --4=N    Set first (second, third, fourth) axis to N.\n\
246   -p, --precision=N             Allow N digits of fraction (default 3).\n\
247   -k, --min-kern=N              Remove kerns smaller than N (default 2).\n\
248 \n\
249 Report bugs to <ekohler@gmail.com>.\n", program_name);
250 }
251 
252 
253 int
main(int argc,char * argv[])254 main(int argc, char *argv[])
255 {
256   MetricsFinder *finder = new CacheMetricsFinder;
257 
258   PsresDatabase *psres = new PsresDatabase;
259   psres->add_psres_path(getenv("PSRESOURCEPATH"), 0, false);
260   PsresMetricsFinder *psres_finder = new PsresMetricsFinder(psres);
261   finder->add_finder(psres_finder);
262 
263   Clp_Parser *clp =
264     Clp_NewParser(argc, (const char * const *)argv, sizeof(options) / sizeof(options[0]), options);
265   program_name = Clp_ProgramName(clp);
266 
267   errh = ErrorHandler::static_initialize(new FileErrorHandler(stderr, String(program_name) + ": "));
268 
269   FILE *output_file = 0;
270   int precision = 3;
271   double kern_precision = 2.0;
272   while (1) {
273     int opt = Clp_Next(clp);
274     switch (opt) {
275 
276      case WEIGHT_OPT:
277       set_design("Weight", clp->val.d);
278       break;
279 
280      case WIDTH_OPT:
281       set_design("Width", clp->val.d);
282       break;
283 
284      case OPSIZE_OPT:
285       set_design("OpticalSize", clp->val.d);
286       break;
287 
288      case STYLE_OPT:
289       set_design("Style", clp->val.d);
290       break;
291 
292      case N1_OPT:
293      case N2_OPT:
294      case N3_OPT:
295      case N4_OPT:
296       set_design(opt - N1_OPT, clp->val.d);
297       break;
298 
299      case PRECISION_OPT:
300       precision = clp->val.i;
301       break;
302 
303      case KERN_PREC_OPT:
304       kern_precision = clp->val.d;
305       break;
306 
307      case OUTPUT_OPT:
308       if (output_file) errh->fatal("output file already specified");
309       if (strcmp(clp->vstr, "-") == 0)
310 	output_file = stdout;
311       else {
312 	output_file = fopen(clp->vstr, "wb");
313 	if (!output_file)
314 	    errh->fatal("%s: %s", clp->vstr, strerror(errno));
315       }
316       break;
317 
318      case HELP_OPT:
319       usage();
320       exit(0);
321       break;
322 
323      case VERSION_OPT:
324       printf("mmafm (LCDF typetools) %s\n", VERSION);
325       printf("Copyright (C) 1997-2013 Eddie Kohler\n\
326 This is free software; see the source for copying conditions.\n\
327 There is NO warranty, not even for merchantability or fitness for a\n\
328 particular purpose.\n");
329       exit(0);
330       break;
331 
332      case Clp_NotOption:
333       read_file(clp->vstr, finder);
334       break;
335 
336      case Clp_Done:
337       goto done;
338 
339      case Clp_BadOption:
340       usage_error(0);
341       break;
342 
343     }
344   }
345 
346  done:
347   if (!amfm) usage_error("missing font argument");
348 
349   MultipleMasterSpace *mmspace = amfm->mmspace();
350 #if MMAFM_RUN_MMPFB
351   if (!mmspace->check_intermediate()) {
352     char *buf = new char[amfm->font_name().length() + 30];
353     sprintf(buf, "mmpfb -q --amcp-info '%s'", amfm->font_name().c_str());
354 
355     FILE *f = popen(buf, "r");
356     if (f) {
357       Filename fake("<mmpfb output>");
358       Slurper slurpy(fake, f);
359       AmfmReader::add_amcp_file(slurpy, amfm, errh);
360       pclose(f);
361     }
362 
363     delete[] buf;
364   }
365 #endif
366 
367   Vector<double> design = mmspace->default_design_vector();
368   for (int i = 0; i < values.size(); i++)
369     if (ax_names[i])
370       mmspace->set_design(design, ax_names[i], values[i], errh);
371     else
372       mmspace->set_design(design, ax_nums[i], values[i], errh);
373 
374   Vector<double> weight;
375   if (!mmspace->design_to_weight(design, weight, errh)) {
376     if (!mmspace->check_intermediate()) {
377       errh->message("(I can%,t interpolate font programs with intermediate masters on my own.");
378 #if MMAFM_RUN_MMPFB
379       errh->message("I tried to run %<mmpfb --amcp-info %s%>, but it didn't work.", amfm->font_name().c_str());
380       errh->message("Maybe your PSRESOURCEPATH environment variable is not set?");
381 #endif
382       errh->fatal("See the manual page for more information.)");
383     } else
384       errh->fatal("can%,t create weight vector");
385   }
386 
387   // Need to check for case when all design coordinates are unspecified. The
388   // AMFM file contains a default WeightVector, but often NOT a default
389   // DesignVector; we don't want to generate a file with a FontName like
390   // `MyriadMM_-9.79797979e97_-9.79797979e97_' because the DesignVector
391   // components are unknown.
392   if (!KNOWN(design[0]))
393     errh->fatal("must specify %s%,s %s coordinate", amfm->font_name().c_str(),
394 		mmspace->axis_type(0).c_str());
395 
396   Metrics *m = amfm->interpolate(design, weight, errh);
397   if (m) {
398 
399     // Add a comment identifying this as interpolated by mmafm
400     if (MetricsXt *xt = m->find_xt("AFM")) {
401       AfmMetricsXt *afm_xt = (AfmMetricsXt *)xt;
402 
403 #if HAVE_CTIME
404       time_t cur_time = time(0);
405       char *time_str = ctime(&cur_time);
406       int time_len = strlen(time_str) - 1;
407       char *buf = new char[strlen(VERSION) + time_len + 100];
408       sprintf(buf, "Interpolated by mmafm-%s on %.*s.", VERSION,
409 	      time_len, time_str);
410 #else
411       char *buf = new char[strlen(VERSION) + 100];
412       sprintf(buf, "Interpolated by mmafm-%s.", VERSION);
413 #endif
414 
415       afm_xt->opening_comments.push_back(buf);
416       afm_xt->opening_comments.push_back("Mmafm is free software.  See <http://www.lcdf.org/type/>.");
417       delete[] buf;
418     }
419 
420     // round numbers if necessary
421     if (precision >= 0)
422       apply_precision(m, precision);
423     if (kern_precision > 0)
424       apply_kern_precision(m, kern_precision);
425 
426     // write the output file
427     if (!output_file)
428       output_file = stdout;
429     AfmWriter::write(m, output_file);
430 
431     return 0;
432   } else
433     return 1;
434 }
435