1 | //======================================================================== |
2 | // |
3 | // CairoFontEngine.cc |
4 | // |
5 | // Copyright 2003 Glyph & Cog, LLC |
6 | // Copyright 2004 Red Hat, Inc |
7 | // |
8 | //======================================================================== |
9 | |
10 | //======================================================================== |
11 | // |
12 | // Modified under the Poppler project - http://poppler.freedesktop.org |
13 | // |
14 | // All changes made under the Poppler project to this file are licensed |
15 | // under GPL version 2 or later |
16 | // |
17 | // Copyright (C) 2005-2007 Jeff Muizelaar <jeff@infidigm.net> |
18 | // Copyright (C) 2005, 2006 Kristian Høgsberg <krh@redhat.com> |
19 | // Copyright (C) 2005 Martin Kretzschmar <martink@gnome.org> |
20 | // Copyright (C) 2005, 2009, 2012, 2013, 2015, 2017-2019, 2021, 2022 Albert Astals Cid <aacid@kde.org> |
21 | // Copyright (C) 2006, 2007, 2010, 2011 Carlos Garcia Campos <carlosgc@gnome.org> |
22 | // Copyright (C) 2007 Koji Otani <sho@bbr.jp> |
23 | // Copyright (C) 2008, 2009 Chris Wilson <chris@chris-wilson.co.uk> |
24 | // Copyright (C) 2008, 2012, 2014, 2016, 2017, 2022, 2023 Adrian Johnson <ajohnson@redneon.com> |
25 | // Copyright (C) 2009 Darren Kenny <darren.kenny@sun.com> |
26 | // Copyright (C) 2010 Suzuki Toshiya <mpsuzuki@hiroshima-u.ac.jp> |
27 | // Copyright (C) 2010 Jan Kümmel <jan+freedesktop@snorc.org> |
28 | // Copyright (C) 2012 Hib Eris <hib@hiberis.nl> |
29 | // Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de> |
30 | // Copyright (C) 2015, 2016 Jason Crain <jason@aquaticape.us> |
31 | // Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de> |
32 | // Copyright (C) 2019 Christian Persch <chpe@src.gnome.org> |
33 | // Copyright (C) 2020 Michal <sudolskym@gmail.com> |
34 | // Copyright (C) 2021, 2022 Oliver Sander <oliver.sander@tu-dresden.de> |
35 | // Copyright (C) 2022 Marcel Fabian Krüger <tex@2krueger.de> |
36 | // Copyright (C) 2023 Pablo Correa Gómez <ablocorrea@hotmail.com> |
37 | // Copyright (C) 2023 Frederic Germain <frederic.germain@gmail.com> |
38 | // Copyright (C) 2023 Ilia Kats <ilia-kats@gmx.net> |
39 | // |
40 | // To see a description of the changes please see the Changelog file that |
41 | // came with your tarball or type make ChangeLog if you are building from git |
42 | // |
43 | //======================================================================== |
44 | |
45 | #include <config.h> |
46 | |
47 | #include <cstring> |
48 | #include <fstream> |
49 | #include "CairoFontEngine.h" |
50 | #include "CairoOutputDev.h" |
51 | #include "GlobalParams.h" |
52 | #include <fofi/FoFiTrueType.h> |
53 | #include <fofi/FoFiType1C.h> |
54 | #include "goo/ft_utils.h" |
55 | #include "goo/gfile.h" |
56 | #include "Error.h" |
57 | #include "XRef.h" |
58 | #include "Gfx.h" |
59 | #include "Page.h" |
60 | |
61 | //------------------------------------------------------------------------ |
62 | // CairoFont |
63 | //------------------------------------------------------------------------ |
64 | |
65 | CairoFont::CairoFont(Ref refA, cairo_font_face_t *cairo_font_faceA, std::vector<int> &&codeToGIDA, bool substituteA, bool printingA) : ref(refA), cairo_font_face(cairo_font_faceA), substitute(substituteA), printing(printingA) |
66 | { |
67 | codeToGID = std::move(codeToGIDA); |
68 | } |
69 | |
70 | CairoFont::~CairoFont() |
71 | { |
72 | cairo_font_face_destroy(font_face: cairo_font_face); |
73 | } |
74 | |
75 | bool CairoFont::matches(Ref &other, bool printingA) |
76 | { |
77 | return (other == ref); |
78 | } |
79 | |
80 | cairo_font_face_t *CairoFont::getFontFace() |
81 | { |
82 | return cairo_font_face; |
83 | } |
84 | |
85 | unsigned long CairoFont::getGlyph(CharCode code, const Unicode *u, int uLen) |
86 | { |
87 | FT_UInt gid; |
88 | |
89 | if (code < codeToGID.size()) { |
90 | gid = (FT_UInt)codeToGID[code]; |
91 | } else { |
92 | gid = (FT_UInt)code; |
93 | } |
94 | return gid; |
95 | } |
96 | |
97 | double CairoFont::getSubstitutionCorrection(const std::shared_ptr<GfxFont> &gfxFont) |
98 | { |
99 | double w1, w2, w3; |
100 | CharCode code; |
101 | const char *name; |
102 | |
103 | // for substituted fonts: adjust the font matrix -- compare the |
104 | // width of 'm' in the original font and the substituted font |
105 | if (isSubstitute() && !gfxFont->isCIDFont()) { |
106 | for (code = 0; code < 256; ++code) { |
107 | if ((name = std::static_pointer_cast<Gfx8BitFont>(r: gfxFont)->getCharName(code)) && name[0] == 'm' && name[1] == '\0') { |
108 | break; |
109 | } |
110 | } |
111 | if (code < 256) { |
112 | w1 = std::static_pointer_cast<Gfx8BitFont>(r: gfxFont)->getWidth(c: code); |
113 | { |
114 | cairo_matrix_t m; |
115 | cairo_matrix_init_identity(matrix: &m); |
116 | cairo_font_options_t *options = cairo_font_options_create(); |
117 | cairo_font_options_set_hint_style(options, hint_style: CAIRO_HINT_STYLE_NONE); |
118 | cairo_font_options_set_hint_metrics(options, hint_metrics: CAIRO_HINT_METRICS_OFF); |
119 | cairo_scaled_font_t *scaled_font = cairo_scaled_font_create(font_face: cairo_font_face, font_matrix: &m, ctm: &m, options); |
120 | |
121 | cairo_text_extents_t extents; |
122 | cairo_scaled_font_text_extents(scaled_font, utf8: "m" , extents: &extents); |
123 | |
124 | cairo_scaled_font_destroy(scaled_font); |
125 | cairo_font_options_destroy(options); |
126 | w2 = extents.x_advance; |
127 | } |
128 | w3 = std::static_pointer_cast<Gfx8BitFont>(r: gfxFont)->getWidth(c: 0); |
129 | if (!gfxFont->isSymbolic() && w2 > 0 && w1 > w3) { |
130 | // if real font is substantially narrower than substituted |
131 | // font, reduce the font size accordingly |
132 | if (w1 > 0.01 && w1 < 0.9 * w2) { |
133 | w1 /= w2; |
134 | return w1; |
135 | } |
136 | } |
137 | } |
138 | } |
139 | return 1.0; |
140 | } |
141 | |
142 | //------------------------------------------------------------------------ |
143 | // CairoFreeTypeFont |
144 | //------------------------------------------------------------------------ |
145 | |
146 | static cairo_user_data_key_t ft_cairo_key; |
147 | |
148 | // Font resources to be freed when cairo_font_face_t is destroyed |
149 | struct FreeTypeFontResource |
150 | { |
151 | FT_Face face; |
152 | std::vector<unsigned char> font_data; |
153 | }; |
154 | |
155 | // cairo callback for when cairo_font_face_t is destroyed |
156 | static void _ft_done_face(void *closure) |
157 | { |
158 | FreeTypeFontResource *resource = (FreeTypeFontResource *)closure; |
159 | |
160 | FT_Done_Face(face: resource->face); |
161 | delete resource; |
162 | } |
163 | |
164 | CairoFreeTypeFont::CairoFreeTypeFont(Ref refA, cairo_font_face_t *cairo_font_faceA, std::vector<int> &&codeToGIDA, bool substituteA) : CairoFont(refA, cairo_font_faceA, std::move(codeToGIDA), substituteA, true) { } |
165 | |
166 | CairoFreeTypeFont::~CairoFreeTypeFont() { } |
167 | |
168 | // Create a cairo_font_face_t for the given font filename OR font data. |
169 | static std::optional<FreeTypeFontFace> createFreeTypeFontFace(FT_Library lib, const std::string &filename, std::vector<unsigned char> &&font_data) |
170 | { |
171 | FreeTypeFontResource *resource = new FreeTypeFontResource; |
172 | FreeTypeFontFace font_face; |
173 | |
174 | if (font_data.empty()) { |
175 | FT_Error err = ft_new_face_from_file(library: lib, filename_utf8: filename.c_str(), face_index: 0, aface: &resource->face); |
176 | if (err) { |
177 | delete resource; |
178 | return {}; |
179 | } |
180 | } else { |
181 | resource->font_data = std::move(font_data); |
182 | FT_Error err = FT_New_Memory_Face(library: lib, file_base: (FT_Byte *)resource->font_data.data(), file_size: resource->font_data.size(), face_index: 0, aface: &resource->face); |
183 | if (err) { |
184 | delete resource; |
185 | return {}; |
186 | } |
187 | } |
188 | |
189 | font_face.cairo_font_face = cairo_ft_font_face_create_for_ft_face(face: resource->face, FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP); |
190 | if (cairo_font_face_set_user_data(font_face: font_face.cairo_font_face, key: &ft_cairo_key, user_data: resource, destroy: _ft_done_face)) { |
191 | cairo_font_face_destroy(font_face: font_face.cairo_font_face); |
192 | _ft_done_face(closure: resource); |
193 | return {}; |
194 | } |
195 | |
196 | font_face.face = resource->face; |
197 | return font_face; |
198 | } |
199 | |
200 | // Create a cairo_font_face_t for the given font filename OR font data. First checks if external font |
201 | // is in the cache. |
202 | std::optional<FreeTypeFontFace> CairoFreeTypeFont::getFreeTypeFontFace(CairoFontEngine *fontEngine, FT_Library lib, const std::string &filename, std::vector<unsigned char> &&font_data) |
203 | { |
204 | if (font_data.empty()) { |
205 | return fontEngine->getExternalFontFace(ftlib: lib, filename); |
206 | } |
207 | |
208 | return createFreeTypeFontFace(lib, filename, font_data: std::move(font_data)); |
209 | } |
210 | |
211 | CairoFreeTypeFont *CairoFreeTypeFont::create(const std::shared_ptr<GfxFont> &gfxFont, XRef *xref, FT_Library lib, CairoFontEngine *fontEngine, bool useCIDs) |
212 | { |
213 | std::string fileName; |
214 | std::vector<unsigned char> font_data; |
215 | int i, n; |
216 | std::optional<GfxFontLoc> fontLoc; |
217 | char **enc; |
218 | const char *name; |
219 | FoFiType1C *ff1c; |
220 | std::optional<FreeTypeFontFace> font_face; |
221 | std::vector<int> codeToGID; |
222 | bool substitute = false; |
223 | |
224 | Ref ref = *gfxFont->getID(); |
225 | Ref embFontID = Ref::INVALID(); |
226 | gfxFont->getEmbeddedFontID(embID: &embFontID); |
227 | GfxFontType fontType = gfxFont->getType(); |
228 | |
229 | if (!(fontLoc = gfxFont->locateFont(xref, ps: nullptr))) { |
230 | error(category: errSyntaxError, pos: -1, msg: "Couldn't find a font for '{0:s}'" , gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)" ); |
231 | goto err2; |
232 | } |
233 | |
234 | // embedded font |
235 | if (fontLoc->locType == gfxFontLocEmbedded) { |
236 | auto fd = gfxFont->readEmbFontFile(xref); |
237 | if (!fd || fd->empty()) { |
238 | goto err2; |
239 | } |
240 | font_data = std::move(fd.value()); |
241 | |
242 | // external font |
243 | } else { // gfxFontLocExternal |
244 | fileName = fontLoc->path; |
245 | fontType = fontLoc->fontType; |
246 | substitute = true; |
247 | } |
248 | |
249 | switch (fontType) { |
250 | case fontType1: |
251 | case fontType1C: |
252 | case fontType1COT: |
253 | font_face = getFreeTypeFontFace(fontEngine, lib, filename: fileName, font_data: std::move(font_data)); |
254 | if (!font_face) { |
255 | error(category: errSyntaxError, pos: -1, msg: "could not create type1 face" ); |
256 | goto err2; |
257 | } |
258 | |
259 | enc = std::static_pointer_cast<Gfx8BitFont>(r: gfxFont)->getEncoding(); |
260 | |
261 | codeToGID.resize(new_size: 256); |
262 | for (i = 0; i < 256; ++i) { |
263 | codeToGID[i] = 0; |
264 | if ((name = enc[i])) { |
265 | codeToGID[i] = FT_Get_Name_Index(face: font_face->face, glyph_name: (char *)name); |
266 | if (codeToGID[i] == 0) { |
267 | Unicode u; |
268 | u = globalParams->mapNameToUnicodeText(charName: name); |
269 | codeToGID[i] = FT_Get_Char_Index(face: font_face->face, charcode: u); |
270 | } |
271 | if (codeToGID[i] == 0) { |
272 | name = GfxFont::getAlternateName(name); |
273 | if (name) { |
274 | codeToGID[i] = FT_Get_Name_Index(face: font_face->face, glyph_name: (char *)name); |
275 | } |
276 | } |
277 | } |
278 | } |
279 | break; |
280 | case fontCIDType2: |
281 | case fontCIDType2OT: |
282 | if (std::static_pointer_cast<GfxCIDFont>(r: gfxFont)->getCIDToGID()) { |
283 | n = std::static_pointer_cast<GfxCIDFont>(r: gfxFont)->getCIDToGIDLen(); |
284 | if (n) { |
285 | const int *src = std::static_pointer_cast<GfxCIDFont>(r: gfxFont)->getCIDToGID(); |
286 | codeToGID.reserve(n: n); |
287 | codeToGID.insert(position: codeToGID.begin(), first: src, last: src + n); |
288 | } |
289 | } else { |
290 | std::unique_ptr<FoFiTrueType> ff; |
291 | if (!font_data.empty()) { |
292 | ff = FoFiTrueType::make(fileA: font_data.data(), lenA: font_data.size()); |
293 | } else { |
294 | ff = FoFiTrueType::load(fileName: fileName.c_str()); |
295 | } |
296 | if (!ff) { |
297 | goto err2; |
298 | } |
299 | int *src = std::static_pointer_cast<GfxCIDFont>(r: gfxFont)->getCodeToGIDMap(ff: ff.get(), codeToGIDLen: &n); |
300 | codeToGID.reserve(n: n); |
301 | codeToGID.insert(position: codeToGID.begin(), first: src, last: src + n); |
302 | gfree(p: src); |
303 | } |
304 | /* Fall through */ |
305 | case fontTrueType: |
306 | case fontTrueTypeOT: { |
307 | std::unique_ptr<FoFiTrueType> ff; |
308 | if (!font_data.empty()) { |
309 | ff = FoFiTrueType::make(fileA: font_data.data(), lenA: font_data.size()); |
310 | } else { |
311 | ff = FoFiTrueType::load(fileName: fileName.c_str()); |
312 | } |
313 | if (!ff) { |
314 | error(category: errSyntaxError, pos: -1, msg: "failed to load truetype font\n" ); |
315 | goto err2; |
316 | } |
317 | /* This might be set already for the CIDType2 case */ |
318 | if (fontType == fontTrueType || fontType == fontTrueTypeOT) { |
319 | int *src = std::static_pointer_cast<Gfx8BitFont>(r: gfxFont)->getCodeToGIDMap(ff: ff.get()); |
320 | codeToGID.reserve(n: 256); |
321 | codeToGID.insert(position: codeToGID.begin(), first: src, last: src + 256); |
322 | gfree(p: src); |
323 | } |
324 | font_face = getFreeTypeFontFace(fontEngine, lib, filename: fileName, font_data: std::move(font_data)); |
325 | if (!font_face) { |
326 | error(category: errSyntaxError, pos: -1, msg: "could not create truetype face\n" ); |
327 | goto err2; |
328 | } |
329 | break; |
330 | } |
331 | case fontCIDType0: |
332 | case fontCIDType0C: |
333 | if (!useCIDs) { |
334 | if (!font_data.empty()) { |
335 | ff1c = FoFiType1C::make(fileA: font_data.data(), lenA: font_data.size()); |
336 | } else { |
337 | ff1c = FoFiType1C::load(fileName: fileName.c_str()); |
338 | } |
339 | if (ff1c) { |
340 | int *src = ff1c->getCIDToGIDMap(nCIDs: &n); |
341 | codeToGID.reserve(n: n); |
342 | codeToGID.insert(position: codeToGID.begin(), first: src, last: src + n); |
343 | gfree(p: src); |
344 | delete ff1c; |
345 | } |
346 | } |
347 | |
348 | font_face = getFreeTypeFontFace(fontEngine, lib, filename: fileName, font_data: std::move(font_data)); |
349 | if (!font_face) { |
350 | error(category: errSyntaxError, pos: -1, msg: "could not create cid face\n" ); |
351 | goto err2; |
352 | } |
353 | break; |
354 | |
355 | case fontCIDType0COT: |
356 | if (std::static_pointer_cast<GfxCIDFont>(r: gfxFont)->getCIDToGID()) { |
357 | n = std::static_pointer_cast<GfxCIDFont>(r: gfxFont)->getCIDToGIDLen(); |
358 | if (n) { |
359 | const int *src = std::static_pointer_cast<GfxCIDFont>(r: gfxFont)->getCIDToGID(); |
360 | codeToGID.reserve(n: n); |
361 | codeToGID.insert(position: codeToGID.begin(), first: src, last: src + n); |
362 | } |
363 | } |
364 | |
365 | if (codeToGID.empty()) { |
366 | if (!useCIDs) { |
367 | std::unique_ptr<FoFiTrueType> ff; |
368 | if (!font_data.empty()) { |
369 | ff = FoFiTrueType::make(fileA: font_data.data(), lenA: font_data.size()); |
370 | } else { |
371 | ff = FoFiTrueType::load(fileName: fileName.c_str()); |
372 | } |
373 | if (ff) { |
374 | if (ff->isOpenTypeCFF()) { |
375 | int *src = ff->getCIDToGIDMap(nCIDs: &n); |
376 | codeToGID.reserve(n: n); |
377 | codeToGID.insert(position: codeToGID.begin(), first: src, last: src + n); |
378 | gfree(p: src); |
379 | } |
380 | } |
381 | } |
382 | } |
383 | font_face = getFreeTypeFontFace(fontEngine, lib, filename: fileName, font_data: std::move(font_data)); |
384 | if (!font_face) { |
385 | error(category: errSyntaxError, pos: -1, msg: "could not create cid (OT) face\n" ); |
386 | goto err2; |
387 | } |
388 | break; |
389 | |
390 | default: |
391 | fprintf(stderr, format: "font type %d not handled\n" , (int)fontType); |
392 | goto err2; |
393 | break; |
394 | } |
395 | |
396 | return new CairoFreeTypeFont(ref, font_face->cairo_font_face, std::move(codeToGID), substitute); |
397 | |
398 | err2: |
399 | fprintf(stderr, format: "some font thing failed\n" ); |
400 | return nullptr; |
401 | } |
402 | |
403 | //------------------------------------------------------------------------ |
404 | // CairoType3Font |
405 | //------------------------------------------------------------------------ |
406 | |
407 | static const cairo_user_data_key_t type3_font_key = { .unused: 0 }; |
408 | |
409 | typedef struct _type3_font_info |
410 | { |
411 | _type3_font_info(const std::shared_ptr<GfxFont> &fontA, PDFDoc *docA, CairoFontEngine *fontEngineA, CairoOutputDev *outputDevA, Gfx *gfxA) : font(fontA), doc(docA), fontEngine(fontEngineA), outputDev(outputDevA), gfx(gfxA) { } |
412 | |
413 | std::shared_ptr<GfxFont> font; |
414 | PDFDoc *doc; |
415 | CairoFontEngine *fontEngine; |
416 | CairoOutputDev *outputDev; |
417 | Gfx *gfx; |
418 | } type3_font_info_t; |
419 | |
420 | static void _free_type3_font_info(void *closure) |
421 | { |
422 | type3_font_info_t *info = (type3_font_info_t *)closure; |
423 | delete info->gfx; |
424 | delete info->outputDev; |
425 | delete info; |
426 | } |
427 | |
428 | static cairo_status_t _init_type3_glyph(cairo_scaled_font_t *scaled_font, cairo_t *cr, cairo_font_extents_t *extents) |
429 | { |
430 | type3_font_info_t *info; |
431 | |
432 | info = (type3_font_info_t *)cairo_font_face_get_user_data(font_face: cairo_scaled_font_get_font_face(scaled_font), key: &type3_font_key); |
433 | const double *mat = info->font->getFontBBox(); |
434 | extents->ascent = mat[3]; /* y2 */ |
435 | extents->descent = -mat[3]; /* -y1 */ |
436 | extents->height = extents->ascent + extents->descent; |
437 | extents->max_x_advance = mat[2] - mat[1]; /* x2 - x1 */ |
438 | extents->max_y_advance = 0; |
439 | |
440 | return CAIRO_STATUS_SUCCESS; |
441 | } |
442 | |
443 | static cairo_status_t _render_type3_glyph(cairo_scaled_font_t *scaled_font, unsigned long glyph, cairo_t *cr, cairo_text_extents_t *metrics, bool color) |
444 | { |
445 | Dict *charProcs; |
446 | Object charProc; |
447 | cairo_matrix_t matrix, invert_y_axis; |
448 | const double *mat; |
449 | double wx, wy; |
450 | type3_font_info_t *info; |
451 | Gfx *gfx; |
452 | cairo_status_t status; |
453 | |
454 | info = (type3_font_info_t *)cairo_font_face_get_user_data(font_face: cairo_scaled_font_get_font_face(scaled_font), key: &type3_font_key); |
455 | |
456 | charProcs = std::static_pointer_cast<Gfx8BitFont>(r: info->font)->getCharProcs(); |
457 | if (!charProcs) { |
458 | return CAIRO_STATUS_USER_FONT_ERROR; |
459 | } |
460 | |
461 | if ((int)glyph >= charProcs->getLength()) { |
462 | return CAIRO_STATUS_USER_FONT_ERROR; |
463 | } |
464 | |
465 | mat = info->font->getFontMatrix(); |
466 | matrix.xx = mat[0]; |
467 | matrix.yx = mat[1]; |
468 | matrix.xy = mat[2]; |
469 | matrix.yy = mat[3]; |
470 | matrix.x0 = mat[4]; |
471 | matrix.y0 = mat[5]; |
472 | cairo_matrix_init_scale(matrix: &invert_y_axis, sx: 1, sy: -1); |
473 | cairo_matrix_multiply(result: &matrix, a: &matrix, b: &invert_y_axis); |
474 | cairo_transform(cr, matrix: &matrix); |
475 | |
476 | #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 18, 0) |
477 | cairo_set_source(cr, cairo_user_scaled_font_get_foreground_marker(scaled_font)); |
478 | #endif |
479 | |
480 | CairoOutputDev *output_dev = info->outputDev; |
481 | output_dev->setCairo(cr); |
482 | |
483 | gfx = info->gfx; |
484 | gfx->saveState(); |
485 | |
486 | output_dev->startDoc(docA: info->doc, fontEngine: info->fontEngine); |
487 | output_dev->startType3Render(state: gfx->getState(), xref: gfx->getXRef()); |
488 | output_dev->setType3RenderType(color ? CairoOutputDev::Type3RenderColor : CairoOutputDev::Type3RenderMask); |
489 | charProc = charProcs->getVal(i: glyph); |
490 | if (!charProc.isStream()) { |
491 | return CAIRO_STATUS_USER_FONT_ERROR; |
492 | } |
493 | Object charProcResObject = charProc.streamGetDict()->lookup(key: "Resources" ); |
494 | if (charProcResObject.isDict()) { |
495 | gfx->pushResources(resDict: charProcResObject.getDict()); |
496 | } |
497 | gfx->display(obj: &charProc); |
498 | if (charProcResObject.isDict()) { |
499 | gfx->popResources(); |
500 | } |
501 | |
502 | output_dev->getType3GlyphWidth(wx: &wx, wy: &wy); |
503 | cairo_matrix_transform_distance(matrix: &matrix, dx: &wx, dy: &wy); |
504 | metrics->x_advance = wx; |
505 | metrics->y_advance = wy; |
506 | if (output_dev->hasType3GlyphBBox()) { |
507 | double *bbox = output_dev->getType3GlyphBBox(); |
508 | |
509 | cairo_matrix_transform_point(matrix: &matrix, x: &bbox[0], y: &bbox[1]); |
510 | cairo_matrix_transform_point(matrix: &matrix, x: &bbox[2], y: &bbox[3]); |
511 | metrics->x_bearing = bbox[0]; |
512 | metrics->y_bearing = bbox[1]; |
513 | metrics->width = bbox[2] - bbox[0]; |
514 | metrics->height = bbox[3] - bbox[1]; |
515 | } |
516 | |
517 | status = CAIRO_STATUS_SUCCESS; |
518 | |
519 | // If this is a render color glyph callback but the Type 3 glyph |
520 | // specified non-color, return NOT_IMPLEMENTED. Cairo will then |
521 | // call the render non-color glyph callback. |
522 | if (color && !output_dev->type3GlyphHasColor()) { |
523 | status = CAIRO_STATUS_USER_FONT_NOT_IMPLEMENTED; |
524 | } |
525 | |
526 | return status; |
527 | } |
528 | |
529 | #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 18, 0) |
530 | static cairo_status_t _render_type3_color_glyph(cairo_scaled_font_t *scaled_font, unsigned long glyph, cairo_t *cr, cairo_text_extents_t *metrics) |
531 | { |
532 | return _render_type3_glyph(scaled_font, glyph, cr, metrics, true); |
533 | } |
534 | #endif |
535 | |
536 | static cairo_status_t _render_type3_noncolor_glyph(cairo_scaled_font_t *scaled_font, unsigned long glyph, cairo_t *cr, cairo_text_extents_t *metrics) |
537 | { |
538 | return _render_type3_glyph(scaled_font, glyph, cr, metrics, color: false); |
539 | } |
540 | |
541 | CairoType3Font *CairoType3Font::create(const std::shared_ptr<GfxFont> &gfxFont, PDFDoc *doc, CairoFontEngine *fontEngine, bool printing, XRef *xref) |
542 | { |
543 | std::vector<int> codeToGID; |
544 | char *name; |
545 | const double *mat; |
546 | |
547 | Dict *charProcs = std::static_pointer_cast<Gfx8BitFont>(r: gfxFont)->getCharProcs(); |
548 | Ref ref = *gfxFont->getID(); |
549 | cairo_font_face_t *font_face = cairo_user_font_face_create(); |
550 | cairo_user_font_face_set_init_func(font_face, init_func: _init_type3_glyph); |
551 | #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 18, 0) |
552 | // When both callbacks are set, Cairo will call the color glyph |
553 | // callback first. If that returns NOT_IMPLEMENTED, Cairo will |
554 | // then call the non-color glyph callback. |
555 | cairo_user_font_face_set_render_color_glyph_func(font_face, _render_type3_color_glyph); |
556 | #endif |
557 | cairo_user_font_face_set_render_glyph_func(font_face, render_glyph_func: _render_type3_noncolor_glyph); |
558 | |
559 | CairoOutputDev *output_dev = new CairoOutputDev(); |
560 | output_dev->setPrinting(printing); |
561 | |
562 | Dict *resDict = std::static_pointer_cast<Gfx8BitFont>(r: gfxFont)->getResources(); |
563 | mat = gfxFont->getFontBBox(); |
564 | PDFRectangle box; |
565 | box.x1 = mat[0]; |
566 | box.y1 = mat[1]; |
567 | box.x2 = mat[2]; |
568 | box.y2 = mat[3]; |
569 | Gfx *gfx = new Gfx(doc, output_dev, resDict, &box, nullptr); |
570 | |
571 | type3_font_info_t *info = new type3_font_info_t(gfxFont, doc, fontEngine, output_dev, gfx); |
572 | cairo_font_face_set_user_data(font_face, key: &type3_font_key, user_data: (void *)info, destroy: _free_type3_font_info); |
573 | |
574 | char **enc = std::static_pointer_cast<Gfx8BitFont>(r: gfxFont)->getEncoding(); |
575 | codeToGID.resize(new_size: 256); |
576 | for (int i = 0; i < 256; ++i) { |
577 | codeToGID[i] = 0; |
578 | if (charProcs && (name = enc[i])) { |
579 | for (int j = 0; j < charProcs->getLength(); j++) { |
580 | if (strcmp(s1: name, s2: charProcs->getKey(i: j)) == 0) { |
581 | codeToGID[i] = j; |
582 | } |
583 | } |
584 | } |
585 | } |
586 | |
587 | return new CairoType3Font(ref, font_face, std::move(codeToGID), printing, xref); |
588 | } |
589 | |
590 | CairoType3Font::CairoType3Font(Ref refA, cairo_font_face_t *cairo_font_faceA, std::vector<int> &&codeToGIDA, bool printingA, XRef *xref) : CairoFont(refA, cairo_font_faceA, std::move(codeToGIDA), false, printingA) { } |
591 | |
592 | CairoType3Font::~CairoType3Font() { } |
593 | |
594 | bool CairoType3Font::matches(Ref &other, bool printingA) |
595 | { |
596 | return (other == ref && printing == printingA); |
597 | } |
598 | |
599 | //------------------------------------------------------------------------ |
600 | // CairoFontEngine |
601 | //------------------------------------------------------------------------ |
602 | |
603 | std::unordered_map<std::string, FreeTypeFontFace> CairoFontEngine::fontFileCache; |
604 | std::recursive_mutex CairoFontEngine::fontFileCacheMutex; |
605 | |
606 | CairoFontEngine::CairoFontEngine(FT_Library libA) |
607 | { |
608 | lib = libA; |
609 | fontCache.reserve(n: cairoFontCacheSize); |
610 | |
611 | FT_Int major, minor, patch; |
612 | // as of FT 2.1.8, CID fonts are indexed by CID instead of GID |
613 | FT_Library_Version(library: lib, amajor: &major, aminor: &minor, apatch: &patch); |
614 | useCIDs = major > 2 || (major == 2 && (minor > 1 || (minor == 1 && patch > 7))); |
615 | } |
616 | |
617 | CairoFontEngine::~CairoFontEngine() { } |
618 | |
619 | std::shared_ptr<CairoFont> CairoFontEngine::getFont(const std::shared_ptr<GfxFont> &gfxFont, PDFDoc *doc, bool printing, XRef *xref) |
620 | { |
621 | std::scoped_lock lock(mutex); |
622 | Ref ref = *gfxFont->getID(); |
623 | std::shared_ptr<CairoFont> font; |
624 | |
625 | // Check if font is in the MRU cache, and move it to the end if it is. |
626 | for (auto it = fontCache.rbegin(); it != fontCache.rend(); ++it) { |
627 | if ((*it)->matches(other&: ref, printingA: printing)) { |
628 | font = *it; |
629 | // move it to the end |
630 | if (it != fontCache.rbegin()) { |
631 | // https://stackoverflow.com/questions/1830158/how-to-call-erase-with-a-reverse-iterator |
632 | fontCache.erase(position: std::next(x: it).base()); |
633 | fontCache.push_back(x: font); |
634 | } |
635 | return font; |
636 | } |
637 | } |
638 | |
639 | GfxFontType fontType = gfxFont->getType(); |
640 | if (fontType == fontType3) { |
641 | font = std::shared_ptr<CairoFont>(CairoType3Font::create(gfxFont, doc, fontEngine: this, printing, xref)); |
642 | } else { |
643 | font = std::shared_ptr<CairoFont>(CairoFreeTypeFont::create(gfxFont, xref, lib, fontEngine: this, useCIDs)); |
644 | } |
645 | |
646 | if (font) { |
647 | if (fontCache.size() == cairoFontCacheSize) { |
648 | fontCache.erase(position: fontCache.begin()); |
649 | } |
650 | fontCache.push_back(x: font); |
651 | } |
652 | return font; |
653 | } |
654 | |
655 | std::optional<FreeTypeFontFace> CairoFontEngine::getExternalFontFace(FT_Library ftlib, const std::string &filename) |
656 | { |
657 | std::scoped_lock lock(fontFileCacheMutex); |
658 | |
659 | auto it = fontFileCache.find(x: filename); |
660 | if (it != fontFileCache.end()) { |
661 | FreeTypeFontFace font = it->second; |
662 | cairo_font_face_reference(font_face: font.cairo_font_face); |
663 | return font; |
664 | } |
665 | |
666 | std::optional<FreeTypeFontFace> font_face = createFreeTypeFontFace(lib: ftlib, filename, font_data: {}); |
667 | if (font_face) { |
668 | cairo_font_face_reference(font_face: font_face->cairo_font_face); |
669 | fontFileCache[filename] = *font_face; |
670 | } |
671 | |
672 | it = fontFileCache.begin(); |
673 | while (it != fontFileCache.end()) { |
674 | if (cairo_font_face_get_reference_count(font_face: it->second.cairo_font_face) == 1) { |
675 | cairo_font_face_destroy(font_face: it->second.cairo_font_face); |
676 | it = fontFileCache.erase(position: it); |
677 | } else { |
678 | ++it; |
679 | } |
680 | } |
681 | |
682 | return font_face; |
683 | } |
684 | |