1 /****************************************************************************\
2 Part of the XeTeX typesetting system
3 Copyright (c) 1994-2008 by SIL International
4 Copyright (c) 2009-2014 by Jonathan Kew
5
6 SIL Author(s): Jonathan Kew
7
8 Permission is hereby granted, free of charge, to any person obtaining
9 a copy of this software and associated documentation files (the
10 "Software"), to deal in the Software without restriction, including
11 without limitation the rights to use, copy, modify, merge, publish,
12 distribute, sublicense, and/or sell copies of the Software, and to
13 permit persons to whom the Software is furnished to do so, subject to
14 the following conditions:
15
16 The above copyright notice and this permission notice shall be
17 included in all copies or substantial portions of the Software.
18
19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22 NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE
23 FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
24 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
27 Except as contained in this notice, the name of the copyright holders
28 shall not be used in advertising or otherwise to promote the sale,
29 use or other dealings in this Software without prior written
30 authorization from the copyright holders.
31 \****************************************************************************/
32
33 #include <w2c/config.h>
34
35 #include "XeTeX_web.h"
36
37 #ifdef XETEX_MAC
38 #include "XeTeXFontMgr_Mac.h"
39 #else
40 #include "XeTeXFontMgr_FC.h"
41 #endif
42 #include "XeTeXFontInst.h"
43
44 #include <hb-ot.h>
45
46 // see cpascal.h
47 #define printcstring(STR) \
48 do { \
49 const char* ch_ptr = (STR); \
50 while (*ch_ptr) \
51 zprintchar(*(ch_ptr++)); \
52 } while (0)
53
54 XeTeXFontMgr* XeTeXFontMgr::sFontManager = NULL;
55 char XeTeXFontMgr::sReqEngine = 0;
56
57 /* use our own fmax function because it seems to be missing on certain platforms
58 (solaris2.9, at least) */
59 static inline double
my_fmax(double x,double y)60 my_fmax(double x, double y)
61 {
62 return (x > y) ? x : y;
63 }
64
65 XeTeXFontMgr*
GetFontManager()66 XeTeXFontMgr::GetFontManager()
67 {
68 if (sFontManager == NULL) {
69 #ifdef XETEX_MAC
70 sFontManager = new XeTeXFontMgr_Mac;
71 #else
72 sFontManager = new XeTeXFontMgr_FC;
73 #endif
74 sFontManager->initialize();
75 }
76
77 return sFontManager;
78 }
79
80 void
Terminate()81 XeTeXFontMgr::Terminate()
82 {
83 if (sFontManager != NULL) {
84 sFontManager->terminate();
85 // we don't actually deallocate the manager, just ask it to clean up
86 // any auxiliary data such as the cocoa pool or freetype/fontconfig stuff
87 // as we still need to access font names after this is called
88 }
89 }
90
91 PlatformFontRef
findFont(const char * name,char * variant,double ptSize)92 XeTeXFontMgr::findFont(const char* name, char* variant, double ptSize)
93 // ptSize is in TeX points, or negative for 'scaled' factor
94 // "variant" string will be shortened (in-place) by removal of /B and /I if present
95 {
96 std::string nameStr(name);
97 Font* font = NULL;
98 int dsize = 100;
99 loadedfontdesignsize = 655360L;
100
101 for (int pass = 0; pass < 2; ++pass) {
102 // try full name as given
103 std::map<std::string,Font*>::iterator i = m_nameToFont.find(nameStr);
104 if (i != m_nameToFont.end()) {
105 font = i->second;
106 if (font->opSizeInfo.designSize != 0)
107 dsize = font->opSizeInfo.designSize;
108 break;
109 }
110
111 // if there's a hyphen, split there and try Family-Style
112 int hyph = nameStr.find('-');
113 if (hyph > 0 && hyph < nameStr.length() - 1) {
114 std::string family(nameStr.begin(), nameStr.begin() + hyph);
115 std::map<std::string,Family*>::iterator f = m_nameToFamily.find(family);
116 if (f != m_nameToFamily.end()) {
117 std::string style(nameStr.begin() + hyph + 1, nameStr.end());
118 i = f->second->styles->find(style);
119 if (i != f->second->styles->end()) {
120 font = i->second;
121 if (font->opSizeInfo.designSize != 0)
122 dsize = font->opSizeInfo.designSize;
123 break;
124 }
125 }
126 }
127
128 // try as PostScript name
129 i = m_psNameToFont.find(nameStr);
130 if (i != m_psNameToFont.end()) {
131 font = i->second;
132 if (font->opSizeInfo.designSize != 0)
133 dsize = font->opSizeInfo.designSize;
134 break;
135 }
136
137 // try for the name as a family name
138 std::map<std::string,Family*>::iterator f = m_nameToFamily.find(nameStr);
139
140 if (f != m_nameToFamily.end()) {
141 // look for a family member with the "regular" bit set in OS/2
142 int regFonts = 0;
143 for (i = f->second->styles->begin(); i != f->second->styles->end(); ++i)
144 if (i->second->isReg) {
145 if (regFonts == 0)
146 font = i->second;
147 ++regFonts;
148 }
149
150 // families with Ornament or similar fonts may flag those as Regular,
151 // which confuses the search above... so try some known names
152 if (font == NULL || regFonts > 1) {
153 // try for style "Regular", "Plain", "Normal", "Roman"
154 i = f->second->styles->find("Regular");
155 if (i != f->second->styles->end())
156 font = i->second;
157 else {
158 i = f->second->styles->find("Plain");
159 if (i != f->second->styles->end())
160 font = i->second;
161 else {
162 i = f->second->styles->find("Normal");
163 if (i != f->second->styles->end())
164 font = i->second;
165 else {
166 i = f->second->styles->find("Roman");
167 if (i != f->second->styles->end())
168 font = i->second;
169 }
170 }
171 }
172 }
173
174 if (font == NULL) {
175 // look through the family for the (weight, width, slant) nearest to (80, 100, 0)
176 font = bestMatchFromFamily(f->second, 80, 100, 0);
177 }
178
179 if (font != NULL)
180 break;
181 }
182
183 if (pass == 0) {
184 // didn't find it in our caches, so do a platform search (may be relatively expensive);
185 // this will update the caches with any fonts that seem to match the name given,
186 // so that the second pass might find it
187 searchForHostPlatformFonts(nameStr);
188 }
189 }
190
191 if (font == NULL)
192 return 0;
193
194 Family* parent = font->parent;
195
196 // if there are variant requests, try to apply them
197 // and delete B, I, and S=... codes from the string, just retain /engine option
198 sReqEngine = 0;
199 bool reqBold = false;
200 bool reqItal = false;
201 if (variant != NULL) {
202 std::string varString;
203 char* cp = variant;
204 while (*cp) {
205 if (strncmp(cp, "AAT", 3) == 0) {
206 sReqEngine = 'A';
207 cp += 3;
208 if (varString.length() > 0 && *(varString.end() - 1) != '/')
209 varString.append("/");
210 varString.append("AAT");
211 goto skip_to_slash;
212 }
213 if (strncmp(cp, "ICU", 3) == 0) { // for backword compatability
214 sReqEngine = 'O';
215 cp += 3;
216 if (varString.length() > 0 && *(varString.end() - 1) != '/')
217 varString.append("/");
218 varString.append("OT");
219 goto skip_to_slash;
220 }
221 if (strncmp(cp, "OT", 2) == 0) {
222 sReqEngine = 'O';
223 cp += 2;
224 if (varString.length() > 0 && *(varString.end() - 1) != '/')
225 varString.append("/");
226 varString.append("OT");
227 goto skip_to_slash;
228 }
229 if (strncmp(cp, "GR", 2) == 0) {
230 sReqEngine = 'G';
231 cp += 2;
232 if (varString.length() > 0 && *(varString.end() - 1) != '/')
233 varString.append("/");
234 varString.append("GR");
235 goto skip_to_slash;
236 }
237 if (*cp == 'S') {
238 char* start = cp;
239 ++cp;
240 if (*cp == '=')
241 ++cp;
242 ptSize = 0.0;
243 while (*cp >= '0' && *cp <= '9') {
244 ptSize = ptSize * 10 + *cp - '0';
245 ++cp;
246 }
247 if (*cp == '.') {
248 double dec = 1.0;
249 ++cp;
250 while (*cp >= '0' && *cp <= '9') {
251 dec = dec * 10.0;
252 ptSize = ptSize + (*cp - '0') / dec;
253 ++cp;
254 }
255 }
256 goto skip_to_slash;
257 }
258
259 /* if the code is "B" or "I", we skip putting it in varString */
260 while (1) {
261 if (*cp == 'B') {
262 reqBold = true;
263 ++cp;
264 continue;
265 }
266 if (*cp == 'I') {
267 reqItal = true;
268 ++cp;
269 continue;
270 }
271 break;
272 }
273
274 skip_to_slash:
275 while (*cp && *cp != '/')
276 ++cp;
277 if (*cp == '/')
278 ++cp;
279 }
280 strcpy(variant, varString.c_str());
281
282 std::map<std::string,Font*>::iterator i;
283 if (reqItal) {
284 Font* bestMatch = font;
285 if (font->slant < parent->maxSlant)
286 // try for a face with more slant
287 bestMatch = bestMatchFromFamily(parent, font->weight, font->width, parent->maxSlant);
288
289 if (bestMatch == font && font->slant > parent->minSlant)
290 // maybe the slant is negated, or maybe this was something like "Times-Italic/I"
291 bestMatch = bestMatchFromFamily(parent, font->weight, font->width, parent->minSlant);
292
293 if (parent->minWeight == parent->maxWeight && bestMatch->isBold != font->isBold) {
294 // try again using the bold flag, as we can't trust weight values
295 Font* newBest = NULL;
296 for (i = parent->styles->begin(); i != parent->styles->end(); ++i) {
297 if (i->second->isBold == font->isBold) {
298 if (newBest == NULL && i->second->isItalic != font->isItalic) {
299 newBest = i->second;
300 break;
301 }
302 }
303 }
304 if (newBest != NULL)
305 bestMatch = newBest;
306 }
307
308 if (bestMatch == font) {
309 // maybe slant values weren't present; try the style bits as a fallback
310 bestMatch = NULL;
311 for (i = parent->styles->begin(); i != parent->styles->end(); ++i) {
312 if (i->second->isItalic == !font->isItalic) {
313 if (parent->minWeight != parent->maxWeight) {
314 // weight info was available, so try to match that
315 if (bestMatch == NULL || weightAndWidthDiff(i->second, font) < weightAndWidthDiff(bestMatch, font))
316 bestMatch = i->second;
317 } else {
318 // no weight info, so try matching style bits
319 if (bestMatch == NULL && i->second->isBold == font->isBold) {
320 bestMatch = i->second;
321 break; // found a match, no need to look further as we can't distinguish!
322 }
323 }
324 }
325 }
326 }
327 if (bestMatch != NULL)
328 font = bestMatch;
329 }
330
331 if (reqBold) {
332 // try for more boldness, with the same width and slant
333 Font* bestMatch = font;
334 if (font->weight < parent->maxWeight) {
335 // try to increase weight by 1/2 x (max - min), rounding up
336 bestMatch = bestMatchFromFamily(parent,
337 font->weight + (parent->maxWeight - parent->minWeight) / 2 + 1,
338 font->width, font->slant);
339 if (parent->minSlant == parent->maxSlant) {
340 // double-check the italic flag, as we can't trust slant values
341 Font* newBest = NULL;
342 for (i = parent->styles->begin(); i != parent->styles->end(); ++i) {
343 if (i->second->isItalic == font->isItalic) {
344 if (newBest == NULL || weightAndWidthDiff(i->second, bestMatch) < weightAndWidthDiff(newBest, bestMatch))
345 newBest = i->second;
346 }
347 }
348 if (newBest != NULL)
349 bestMatch = newBest;
350 }
351 }
352 if (bestMatch == font && !font->isBold) {
353 for (i = parent->styles->begin(); i != parent->styles->end(); ++i) {
354 if (i->second->isItalic == font->isItalic && i->second->isBold) {
355 bestMatch = i->second;
356 break;
357 }
358 }
359 }
360 font = bestMatch;
361 }
362 }
363
364 // if there's optical size info, try to apply it
365 if (ptSize < 0.0)
366 ptSize = dsize / 10.0;
367 if (font != NULL && font->opSizeInfo.subFamilyID != 0 && ptSize > 0.0) {
368 ptSize = ptSize * 10.0; // convert to decipoints for comparison with the opSize values
369 double bestMismatch = my_fmax(font->opSizeInfo.minSize - ptSize, ptSize - font->opSizeInfo.maxSize);
370 if (bestMismatch > 0.0) {
371 Font* bestMatch = font;
372 for (std::map<std::string,Font*>::iterator i = parent->styles->begin(); i != parent->styles->end(); ++i) {
373 if (i->second->opSizeInfo.subFamilyID != font->opSizeInfo.subFamilyID)
374 continue;
375 double mismatch = my_fmax(i->second->opSizeInfo.minSize - ptSize, ptSize - i->second->opSizeInfo.maxSize);
376 if (mismatch < bestMismatch) {
377 bestMatch = i->second;
378 bestMismatch = mismatch;
379 }
380 if (bestMismatch <= 0.0)
381 break;
382 }
383 font = bestMatch;
384 }
385 }
386
387 if (font != NULL && font->opSizeInfo.designSize != 0)
388 loadedfontdesignsize = (font->opSizeInfo.designSize << 16L) / 10;
389
390 if (gettracingfontsstate() > 0) {
391 begindiagnostic();
392 zprintnl(' ');
393 printcstring("-> ");
394 printcstring(getPlatformFontDesc(font->fontRef).c_str());
395 zenddiagnostic(0);
396 }
397
398 return font->fontRef;
399 }
400
401 const char*
getFullName(PlatformFontRef font) const402 XeTeXFontMgr::getFullName(PlatformFontRef font) const
403 {
404 std::map<PlatformFontRef,Font*>::const_iterator i = m_platformRefToFont.find(font);
405 if (i == m_platformRefToFont.end())
406 die("internal error %d in XeTeXFontMgr", 2);
407 if (i->second->m_fullName != NULL)
408 return i->second->m_fullName->c_str();
409 else
410 return i->second->m_psName->c_str();
411 }
412
413 int
weightAndWidthDiff(const Font * a,const Font * b) const414 XeTeXFontMgr::weightAndWidthDiff(const Font* a, const Font* b) const
415 {
416 if (a->weight == 0 && a->width == 0) {
417 // assume there was no OS/2 info
418 if (a->isBold == b->isBold)
419 return 0;
420 else
421 return 10000;
422 }
423
424 int widDiff = labs(a->width - b->width);
425 if (widDiff < 10)
426 widDiff *= 50;
427
428 return labs(a->weight - b->weight) + widDiff;
429 }
430
431 int
styleDiff(const Font * a,int wt,int wd,int slant) const432 XeTeXFontMgr::styleDiff(const Font* a, int wt, int wd, int slant) const
433 {
434 int widDiff = labs(a->width - wd);
435 if (widDiff < 10)
436 widDiff *= 200;
437
438 return labs(labs(a->slant) - labs(slant)) * 2 + labs(a->weight - wt) + widDiff;
439 }
440
441 XeTeXFontMgr::Font*
bestMatchFromFamily(const Family * fam,int wt,int wd,int slant) const442 XeTeXFontMgr::bestMatchFromFamily(const Family* fam, int wt, int wd, int slant) const
443 {
444 Font* bestMatch = NULL;
445 for (std::map<std::string,Font*>::iterator s = fam->styles->begin(); s != fam->styles->end(); ++s)
446 if (bestMatch == NULL || styleDiff(s->second, wt, wd, slant) < styleDiff(bestMatch, wt, wd, slant))
447 bestMatch = s->second;
448 return bestMatch;
449 }
450
451 const XeTeXFontMgr::OpSizeRec*
getOpSize(XeTeXFont font)452 XeTeXFontMgr::getOpSize(XeTeXFont font)
453 {
454 hb_font_t* hbFont = ((XeTeXFontInst*)font)->getHbFont();
455 if (hbFont != NULL) {
456 hb_face_t* face = hb_font_get_face(hbFont);
457 OpSizeRec* pSizeRec = (OpSizeRec*) xmalloc(sizeof(OpSizeRec));
458
459 bool ok = hb_ot_layout_get_size_params(face,
460 &pSizeRec->designSize,
461 &pSizeRec->subFamilyID,
462 &pSizeRec->nameCode,
463 &pSizeRec->minSize,
464 &pSizeRec->maxSize);
465
466 if (ok)
467 return pSizeRec;
468
469 free(pSizeRec);
470 return NULL;
471 }
472
473 return NULL;
474 }
475
476 double
getDesignSize(XeTeXFont font)477 XeTeXFontMgr::getDesignSize(XeTeXFont font)
478 {
479 const OpSizeRec* pSizeRec = getOpSize(font);
480 if (pSizeRec != NULL)
481 return pSizeRec->designSize / 10.0;
482 else
483 return 10.0;
484 }
485
486 void
getOpSizeRecAndStyleFlags(Font * theFont)487 XeTeXFontMgr::getOpSizeRecAndStyleFlags(Font* theFont)
488 {
489 XeTeXFont font = createFont(theFont->fontRef, 655360);
490 XeTeXFontInst* fontInst = (XeTeXFontInst*) font;
491 if (font != 0) {
492 const OpSizeRec* pSizeRec = getOpSize(font);
493 if (pSizeRec != NULL) {
494 theFont->opSizeInfo.designSize = pSizeRec->designSize;
495 if (pSizeRec->subFamilyID == 0
496 && pSizeRec->nameCode == 0
497 && pSizeRec->minSize == 0
498 && pSizeRec->maxSize == 0)
499 goto done_size; // feature is valid, but no 'size' range
500 theFont->opSizeInfo.subFamilyID = pSizeRec->subFamilyID;
501 theFont->opSizeInfo.nameCode = pSizeRec->nameCode;
502 theFont->opSizeInfo.minSize = pSizeRec->minSize;
503 theFont->opSizeInfo.maxSize = pSizeRec->maxSize;
504 }
505 done_size:
506
507 const TT_OS2* os2Table = (TT_OS2*) fontInst->getFontTable(ft_sfnt_os2);
508 if (os2Table != NULL) {
509 theFont->weight = os2Table->usWeightClass;
510 theFont->width = os2Table->usWidthClass;
511 uint16_t sel = os2Table->fsSelection;
512 theFont->isReg = (sel & (1 << 6)) != 0;
513 theFont->isBold = (sel & (1 << 5)) != 0;
514 theFont->isItalic = (sel & (1 << 0)) != 0;
515 }
516
517 const TT_Header* headTable = (TT_Header*) fontInst->getFontTable(ft_sfnt_head);
518 if (headTable != NULL) {
519 uint16_t ms = headTable->Mac_Style;
520 if ((ms & (1 << 0)) != 0)
521 theFont->isBold = true;
522 if ((ms & (1 << 1)) != 0)
523 theFont->isItalic = true;
524 }
525
526 const TT_Postscript* postTable = (const TT_Postscript*) fontInst->getFontTable(ft_sfnt_post);
527 if (postTable != NULL) {
528 theFont->slant = (int)(1000 * (tan(Fix2D(-postTable->italicAngle) * M_PI / 180.0)));
529 }
530 deleteFont(font);
531 }
532 }
533
534 // append a name but only if it's not already in the list
535 void
appendToList(std::list<std::string> * list,const char * str)536 XeTeXFontMgr::appendToList(std::list<std::string>* list, const char* str)
537 {
538 for (std::list<std::string>::const_iterator i = list->begin(); i != list->end(); ++i)
539 if (*i == str)
540 return;
541 list->push_back(str);
542 }
543
544 // prepend a name, removing it from later in the list if present
545 void
prependToList(std::list<std::string> * list,const char * str)546 XeTeXFontMgr::prependToList(std::list<std::string>* list, const char* str)
547 {
548 for (std::list<std::string>::iterator i = list->begin(); i != list->end(); ++i)
549 if (*i == str) {
550 list->erase(i);
551 break;
552 }
553 list->push_front(str);
554 }
555
556 void
addToMaps(PlatformFontRef platformFont,const NameCollection * names)557 XeTeXFontMgr::addToMaps(PlatformFontRef platformFont, const NameCollection* names)
558 {
559 if (m_platformRefToFont.find(platformFont) != m_platformRefToFont.end())
560 return; // this font has already been cached
561
562 if (names->m_psName.length() == 0)
563 return; // can't use a font that lacks a PostScript name
564
565 if (m_psNameToFont.find(names->m_psName) != m_psNameToFont.end())
566 return; // duplicates an earlier PS name, so skip
567
568 Font* thisFont = new Font(platformFont);
569 thisFont->m_psName = new std::string(names->m_psName);
570 getOpSizeRecAndStyleFlags(thisFont);
571
572 m_psNameToFont[names->m_psName] = thisFont;
573 m_platformRefToFont[platformFont] = thisFont;
574
575 if (names->m_fullNames.size() > 0)
576 thisFont->m_fullName = new std::string(*(names->m_fullNames.begin()));
577
578 if (names->m_familyNames.size() > 0)
579 thisFont->m_familyName = new std::string(*(names->m_familyNames.begin()));
580 else
581 thisFont->m_familyName = new std::string(names->m_psName);
582
583 if (names->m_styleNames.size() > 0)
584 thisFont->m_styleName = new std::string(*(names->m_styleNames.begin()));
585 else
586 thisFont->m_styleName = new std::string;
587
588 std::list<std::string>::const_iterator i;
589 for (i = names->m_familyNames.begin(); i != names->m_familyNames.end(); ++i) {
590 std::map<std::string,Family*>::iterator iFam = m_nameToFamily.find(*i);
591 Family* family;
592 if (iFam == m_nameToFamily.end()) {
593 family = new Family;
594 m_nameToFamily[*i] = family;
595 family->minWeight = thisFont->weight;
596 family->maxWeight = thisFont->weight;
597 family->minWidth = thisFont->width;
598 family->maxWidth = thisFont->width;
599 family->minSlant = thisFont->slant;
600 family->maxSlant = thisFont->slant;
601 } else {
602 family = iFam->second;
603 if (thisFont->weight < family->minWeight)
604 family->minWeight = thisFont->weight;
605 if (thisFont->weight > family->maxWeight)
606 family->maxWeight = thisFont->weight;
607 if (thisFont->width < family->minWidth)
608 family->minWidth = thisFont->width;
609 if (thisFont->width > family->maxWidth)
610 family->maxWidth = thisFont->width;
611 if (thisFont->slant < family->minSlant)
612 family->minSlant = thisFont->slant;
613 if (thisFont->slant > family->maxSlant)
614 family->maxSlant = thisFont->slant;
615 }
616
617 if (thisFont->parent == NULL)
618 thisFont->parent = family;
619
620 // ensure all style names in the family point to thisFont
621 for (std::list<std::string>::const_iterator j = names->m_styleNames.begin(); j != names->m_styleNames.end(); ++j) {
622 std::map<std::string,Font*>::iterator iFont = family->styles->find(*j);
623 if (iFont == family->styles->end())
624 (*family->styles)[*j] = thisFont;
625 /*
626 else if (iFont->second != thisFont)
627 fprintf(stderr, "# Font name warning: ambiguous Style \"%s\" in Family \"%s\" (PSNames \"%s\" and \"%s\")\n",
628 j->c_str(), i->c_str(), iFont->second->m_psName->c_str(), thisFont->m_psName->c_str());
629 */
630 }
631 }
632
633 for (i = names->m_fullNames.begin(); i != names->m_fullNames.end(); ++i) {
634 std::map<std::string,Font*>::iterator iFont = m_nameToFont.find(*i);
635 if (iFont == m_nameToFont.end())
636 m_nameToFont[*i] = thisFont;
637 /*
638 else if (iFont->second != thisFont)
639 fprintf(stderr, "# Font name warning: ambiguous FullName \"%s\" (PSNames \"%s\" and \"%s\")\n",
640 i->c_str(), iFont->second->m_psName->c_str(), thisFont->m_psName->c_str());
641 */
642 }
643 }
644
645 void
die(const char * s,int i) const646 XeTeXFontMgr::die(const char*s, int i) const
647 {
648 fprintf(stderr, s, i);
649 fprintf(stderr, " - exiting\n");
650 exit(3);
651 }
652
653 void
terminate()654 XeTeXFontMgr::terminate()
655 {
656 }
657
658