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