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