1 // -*- related-file-name: "../include/efont/afm.hh" -*-
2 
3 /* afm.{cc,hh} -- Adobe Font Metrics files
4  *
5  * Copyright (c) 1998-2019 Eddie Kohler
6  *
7  * This program is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by the Free
9  * Software Foundation; either version 2 of the License, or (at your option)
10  * any later version. This program is distributed in the hope that it will be
11  * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13  * Public License for more details.
14  */
15 
16 #ifdef HAVE_CONFIG_H
17 # include <config.h>
18 #endif
19 #include <efont/afm.hh>
20 #include <efont/afmparse.hh>
21 #include <efont/t1cs.hh>        /* for UNKDOUBLE */
22 #include <lcdf/error.hh>
23 #include <ctype.h>
24 #include <assert.h>
25 namespace Efont {
26 
AfmReader(AfmParser & parser,Metrics * afm,AfmMetricsXt * afm_xt,ErrorHandler * errh)27 AfmReader::AfmReader(AfmParser &parser, Metrics *afm, AfmMetricsXt *afm_xt,
28                      ErrorHandler *errh)
29     : _afm(afm), _afm_xt(afm_xt), _l(parser),
30       _composite_warned(false), _metrics_sets_warned(false), _y_width_warned(0)
31 {
32     _errh = errh ? errh : ErrorHandler::silent_handler();
33 }
34 
35 Metrics *
read(Slurper & slurp,ErrorHandler * errh)36 AfmReader::read(Slurper &slurp, ErrorHandler *errh)
37 {
38     AfmParser p(slurp);
39     if (!p.ok())
40         return 0;
41 
42     Metrics *afm = new Metrics;
43     AfmMetricsXt *afm_xt = new AfmMetricsXt;
44     afm->add_xt(afm_xt);
45     AfmReader reader(p, afm, afm_xt, errh);
46 
47     if (!reader.read()) {
48         delete afm;
49         return 0;
50     } else
51         return afm;
52 }
53 
54 Metrics *
read(const Filename & fn,ErrorHandler * errh)55 AfmReader::read(const Filename &fn, ErrorHandler *errh)
56 {
57     Slurper slurpy(fn);
58     return read(slurpy, errh);
59 }
60 
61 
62 void
lwarning(const char * format,...) const63 AfmReader::lwarning(const char *format, ...) const
64 {
65     va_list val;
66     va_start(val, format);
67     _errh->xmessage(_l.landmark(), ErrorHandler::e_warning, format, val);
68     va_end(val);
69 }
70 
71 void
lerror(const char * format,...) const72 AfmReader::lerror(const char *format, ...) const
73 {
74     va_list val;
75     va_start(val, format);
76     _errh->xmessage(_l.landmark(), ErrorHandler::e_error, format, val);
77     va_end(val);
78 }
79 
80 GlyphIndex
find_err(PermString name,const char *) const81 AfmReader::find_err(PermString name, const char *) const
82 {
83     GlyphIndex gi = _afm->find(name);
84     if (gi < 0)
85         lerror("character `%s' doesn't exist", name.c_str());
86     return gi;
87 }
88 
89 void
composite_warning() const90 AfmReader::composite_warning() const
91 {
92     if (!_composite_warned)
93         lwarning("composite fonts not supported");
94     _composite_warned = 1;
95 }
96 
97 void
metrics_sets_warning() const98 AfmReader::metrics_sets_warning() const
99 {
100     if (!_metrics_sets_warned)
101         lwarning("only metrics set 0 is supported");
102     _metrics_sets_warned = 1;
103 }
104 
105 void
y_width_warning() const106 AfmReader::y_width_warning() const
107 {
108     if (_y_width_warned < 40) {
109         lwarning("character has a nonzero Y width");
110         _y_width_warned++;
111         if (_y_width_warned == 40)
112             lwarning("I won't warn you again.");
113     }
114 }
115 
116 void
no_match_warning(const char * context) const117 AfmReader::no_match_warning(const char *context) const
118 {
119     // keyword() will fail (and a warning won't get printed) only if the string
120     // is all whitespace, which the spec allows
121     PermString keyword = _l.keyword();
122     if (!keyword) return;
123     if (_l.key_matched()) {
124         lwarning(context ? "bad `%s' command in %s:"
125                  : "bad `%s' command:", keyword.c_str(), context);
126         lwarning("field %d %s", _l.fail_field(), _l.message().c_str());
127     } else
128         lwarning(context ? "unknown command `%s' in %s"
129                  : "unknown command `%s'", keyword.c_str(), context);
130     _l.clear_message();
131 }
132 
133 
134 bool
read()135 AfmReader::read()
136 {
137     AfmParser &l = _l;
138     assert(_afm && _afm_xt);
139 
140     // First, read all opening comments into an array so we can print them out
141     // later.
142     PermString comment;
143     while (l.next_line()) {
144         if (l.isall("Comment %+s", &comment))
145             _afm_xt->opening_comments.push_back(comment);
146         else if (l.isall("StartFontMetrics %g", (double *)0))
147             ;
148         else {
149             l.save_line();
150             break;
151         }
152     }
153 
154     _afm->set_scale(1000);
155     unsigned invalid_lines = 0;
156     PermString s;
157     bool isbasefont;
158     int metrics_sets;
159     int direction;
160 
161     while (l.next_line())
162         switch (l.first()) {
163 
164           case 'A':
165             if (l.isall("Ascender %g", &fd( fdAscender )))
166                 break;
167             goto invalid;
168 
169           case 'C':
170             if (l.isall("Characters %d", (int *)0))
171                 break;
172             if (l.isall("CapHeight %g", &fd( fdCapHeight )))
173                 break;
174             if (l.isall("CharacterSet %+s", (PermString *) 0))
175                 break;
176             if (l.isall("CharWidth %g %g", (double *)0, (double *)0))
177                 break;
178             if (l.isall("Comment %+s", (PermString *) 0))
179                 break;
180             goto invalid;
181 
182           case 'D':
183             if (l.isall("Descender %g", &fd( fdDescender )))
184                 break;
185             goto invalid;
186 
187           case 'E':
188             if (l.isall("EncodingScheme %+s", &_afm_xt->encoding_scheme))
189                 break;
190             if (l.isall("EndDirection"))
191                 break;
192             if (l.isall("EndFontMetrics"))
193                 goto done;
194             if (l.isall("EscChar %d", (int *)0)) {
195                 composite_warning();
196                 break;
197             }
198             goto invalid;
199 
200           case 'F':
201             if (l.isall("FontName %+s", &s)) {
202                 _afm->set_font_name(s);
203                 break;
204             }
205             if (l.isall("FullName %+s", &s)) {
206                 _afm->set_full_name(s);
207                 break;
208             }
209             if (l.isall("FamilyName %+s", &s)) {
210                 _afm->set_family(s);
211                 break;
212             }
213             if (l.isall("FontBBox %g %g %g %g",
214                         &fd( fdFontBBllx ), &fd( fdFontBBlly ),
215                         &fd( fdFontBBurx ), &fd( fdFontBBury )))
216                 break;
217             goto invalid;
218 
219           case 'I':
220             if (l.isall("ItalicAngle %g", &fd( fdItalicAngle )))
221                 break;
222             if (l.isall("IsBaseFont %b", &isbasefont)) {
223                 if (isbasefont == 0)
224                     composite_warning();
225                 break;
226             }
227             if (l.isall("IsFixedV %b", (bool *)0)) {
228                 metrics_sets_warning();
229                 break;
230             }
231             if (l.isall("IsFixedPitch %b", (bool *)0))
232                 break;
233             goto invalid;
234 
235           case 'M':
236             if (l.isall("MappingScheme %d", (int *)0)) {
237                 composite_warning();
238                 break;
239             }
240             if (l.isall("MetricsSets %d", &metrics_sets)) {
241                 if (metrics_sets != 0)
242                     metrics_sets_warning();
243                 break;
244             }
245             goto invalid;
246 
247           case 'N':
248             if (l.isall("Notice %+s", &_afm_xt->notice))
249                 break;
250             goto invalid;
251 
252           case 'S':
253             if (l.isall("StartDirection %d", &direction)) {
254                 if (direction != 0)
255                     metrics_sets_warning();
256                 break;
257             }
258             if (l.isall("StartCharMetrics %d", (int *)0)) {
259                 read_char_metrics();
260                 break;
261             }
262             if (l.isall("StartKernData")) {
263                 read_kerns();
264                 break;
265             }
266             if (l.isall("StartComposites %d", (int *)0)) {
267                 read_composites();
268                 break;
269             }
270             if (l.isall("StdHW %g", &fd( fdStdHW )))
271                 break;
272             if (l.isall("StdVW %g", &fd( fdStdVW )))
273                 break;
274             if (l.isall("StartFontMetrics %g", (double *)0))
275                 break;
276             goto invalid;
277 
278           case 'U':
279             if (l.isall("UnderlinePosition %g", &fd( fdUnderlinePosition )))
280                 break;
281             if (l.isall("UnderlineThickness %g", &fd( fdUnderlineThickness )))
282                 break;
283             goto invalid;
284 
285           case 'V':
286             if (l.isall("Version %+s", &s)) {
287                 _afm->set_version(s);
288                 break;
289             }
290             if (l.isall("VVector %g %g", (double *)0, (double *)0)) {
291                 metrics_sets_warning();
292                 break;
293             }
294             goto invalid;
295 
296           case 'W':
297             if (l.isall("Weight %+s", &s)) {
298                 _afm->set_weight(s);
299                 break;
300             }
301             goto invalid;
302 
303           case 'X':
304             if (l.isall("XHeight %g", &fd( fdXHeight )))
305                 break;
306             goto invalid;
307 
308           default:
309           invalid:
310             invalid_lines++;
311             no_match_warning();
312 
313         }
314 
315   done:
316     if (invalid_lines >= l.lineno() - 10)
317         return false;
318     else
319         return true;
320 }
321 
322 
323 static Vector<PermString> ligature_left;
324 static Vector<PermString> ligature_right;
325 static Vector<PermString> ligature_result;
326 
327 void
read_char_metric_data() const328 AfmReader::read_char_metric_data() const
329 {
330     int c = -1;
331     double wx = UNKDOUBLE;
332     double bllx = UNKDOUBLE, blly = 0, burx = 0, bury = 0;
333     PermString n;
334     PermString ligright, ligresult;
335 
336     AfmParser &l = _l;
337 
338     l.is("C %d ; WX %g ; N %/s ; B %g %g %g %g ;",
339          &c, &wx, &n, &bllx, &blly, &burx, &bury);
340 
341     while (l.left()) {
342 
343         switch (l.first()) {
344 
345           case 'B':
346             if (l.is("B %g %g %g %g", &bllx, &blly, &burx, &bury))
347                 break;
348             goto invalid;
349 
350           case 'C':
351             if (l.is("C %d", &c))
352                 break;
353             if (l.is("CH <%x>", &c))
354                 break;
355             goto invalid;
356 
357           case 'E':
358             if (l.isall("EndCharMetrics"))
359                 return;
360             goto invalid;
361 
362           case 'L':
363             if (l.is("L %/s %/s", &ligright, &ligresult)) {
364                 if (!n)
365                     lerror("ligature given, but character has no name");
366                 else {
367                     ligature_left.push_back(n);
368                     ligature_right.push_back(ligright);
369                     ligature_result.push_back(ligresult);
370                 }
371                 break;
372             }
373             goto invalid;
374 
375           case 'N':
376             if (l.is("N %/s", &n))
377                 break;
378             goto invalid;
379 
380           case 'W':
381             if (l.is("WX %g", &wx) ||
382                 l.is("W0X %g", &wx))
383                 break;
384             if (l.is("W %g %g", &wx, (double *)0) ||
385                 l.is("W0 %g %g", &wx, (double *)0) ||
386                 l.is("W0Y %g", (double *)0)) {
387                 y_width_warning();
388                 break;
389             }
390             if (l.is("W1X %g", (double *)0) ||
391                 l.is("W1Y %g", (double *)0) ||
392                 l.is("W1 %g %g", (double *)0, (double *)0)) {
393                 metrics_sets_warning();
394                 break;
395             }
396             goto invalid;
397 
398           default:
399           invalid:
400             // always warn about unknown directives here!
401             no_match_warning("character metrics");
402             l.skip_until(';');
403             break;
404 
405         }
406 
407         l.is(";"); // get rid of any possible semicolon
408     }
409 
410     // create the character
411     if (!n)
412         lwarning("character without a name ignored");
413     else {
414         if (_afm->find(n) != -1)
415             lwarning("character %s defined twice", n.c_str());
416 
417         GlyphIndex gi = _afm->add_glyph(n);
418 
419         _afm->wd(gi) = wx;
420         _afm->lf(gi) = bllx;
421         _afm->rt(gi) = burx;
422         _afm->tp(gi) = bury;
423         _afm->bt(gi) = blly;
424 
425         if (c != -1)
426             _afm->set_code(gi, c);
427     }
428 }
429 
430 
431 void
read_char_metrics() const432 AfmReader::read_char_metrics() const
433 {
434     assert(!ligature_left.size());
435 
436     while (_l.next_line())
437         // Grok the whole line. Are we on a character metric data line?
438         switch (_l.first()) {
439 
440           case 'C':
441             if (isspace(_l[1]) || (_l[1] == 'H' && isspace(_l[2]))) {
442                 read_char_metric_data();
443                 break;
444             }
445             if (_l.is("Comment"))
446                 break;
447             goto invalid;
448 
449           case 'E':
450             if (_l.isall("EndCharMetrics"))
451                 goto end_char_metrics;
452             goto invalid;
453 
454           default:
455           invalid:
456             no_match_warning();
457 
458         }
459 
460   end_char_metrics:
461 
462     for (int i = 0; i < ligature_left.size(); i++) {
463         GlyphIndex leftgi = find_err(ligature_left[i], "ligature");
464         GlyphIndex rightgi = find_err(ligature_right[i], "ligature");
465         GlyphIndex resultgi = find_err(ligature_result[i], "ligature");
466         if (leftgi >= 0 && rightgi >= 0 && resultgi >= 0)
467             if (_afm->add_lig(leftgi, rightgi, resultgi))
468                 lwarning("duplicate ligature; first ignored");
469     }
470     ligature_left.clear();
471     ligature_right.clear();
472     ligature_result.clear();
473 }
474 
475 
476 void
read_kerns() const477 AfmReader::read_kerns() const
478 {
479     double kx;
480     PermString left, right;
481     GlyphIndex leftgi, rightgi;
482 
483     AfmParser &l = _l;
484     // AFM files have reversed pair programs when read.
485     _afm->pair_program()->set_reversed(true);
486 
487     while (l.next_line())
488         switch (l.first()) {
489 
490           case 'C':
491             if (l.is("Comment"))
492                 break;
493             goto invalid;
494 
495           case 'E':
496             if (l.isall("EndKernPairs"))
497                 break;
498             if (l.isall("EndKernData"))
499                 return;
500             if (l.isall("EndTrackKern"))
501                 break;
502             goto invalid;
503 
504           case 'K':
505             if (l.isall("KPX %/s %/s %g", &left, &right, &kx)) {
506                 goto validkern;
507             }
508             if (l.isall("KP %/s %/s %g %g", &left, &right, &kx, (double *)0)) {
509                 y_width_warning();
510                 goto validkern;
511             }
512             if (l.isall("KPY %/s %/s %g", &left, &right, (double *)0)) {
513                 y_width_warning();
514                 break;
515             }
516             if (l.isall("KPH <%x> <%x> %g %g", (int *)0, (int *)0,
517                         (double *)0, (double *)0)) {
518                 lwarning("KPH not supported");
519                 break;
520             }
521             goto invalid;
522 
523           validkern:
524             leftgi = find_err(left, "kern");
525             rightgi = find_err(right, "kern");
526             if (leftgi >= 0 && rightgi >= 0)
527                 // A kern with 0 amount is NOT useless!
528                 // (Because of multiple masters.)
529                 if (_afm->add_kern(leftgi, rightgi, _afm->add_kv(kx)))
530                     lwarning("duplicate kern; first pair ignored");
531             break;
532 
533           case 'S':
534             if (l.isall("StartKernPairs %d", (int *)0) ||
535                 l.isall("StartKernPairs0 %d", (int *)0))
536                 break;
537             if (l.isall("StartKernPairs1 %d", (int *)0)) {
538                 metrics_sets_warning();
539                 break;
540             }
541             if (l.isall("StartTrackKern %d", (int *)0))
542                 break;
543             goto invalid;
544 
545           case 'T':
546             if (l.isall("TrackKern %g %g %g %g %g", (double *)0, (double *)0,
547                         (double *)0, (double *)0, (double *)0))
548                 break; // FIXME: implement TrackKern
549             goto invalid;
550 
551           default:
552           invalid:
553             no_match_warning();
554             break;
555 
556         }
557 }
558 
559 
560 void
read_composites() const561 AfmReader::read_composites() const
562 {
563     while (_l.next_line())
564         switch (_l.first()) {
565 
566           case 'C':
567             if (_l.is("Comment"))
568                 break;
569             if (_l.is("CC"))
570                 break;
571             goto invalid;
572 
573           case 'E':
574             if (_l.isall("EndComposites"))
575                 return;
576             goto invalid;
577 
578           default:
579           invalid:
580             no_match_warning();
581             break;
582 
583         }
584 }
585 
586 }
587