Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
poppler-utils.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
/*
5 * Authors:
6 * Martin Owens
7 *
8 * Copyright (C) 2022 Authors
9 *
10 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
11 */
12
13#include "poppler-utils.h"
14
15#include <poppler/UTF.h>
16
17#include <2geom/affine.h>
18#include <poppler/GfxFont.h>
19#include <poppler/GfxState.h>
20#include <poppler/PDFDoc.h>
21#include <poppler/PDFDocEncoding.h>
23
28{
29 return ctmToAffine(state->getCTM());
30}
31
35Geom::Affine ctmToAffine(const double *ctm)
36{
37 if (!ctm)
38 return Geom::identity();
39 return Geom::Affine(ctm[0], ctm[1], ctm[2], ctm[3], ctm[4], ctm[5]);
40}
41
42void ctmout(const char *label, const double *ctm)
43{
44 std::cout << "C:" << label << ":" << ctm[0] << "," << ctm[1] << "," << ctm[2] << "," << ctm[3] << "," << ctm[4]
45 << "," << ctm[5] << "\n";
46}
47
48void affout(const char *label, Geom::Affine ctm)
49{
50 std::cout << "A:" << label << ":" << ctm[0] << "," << ctm[1] << "," << ctm[2] << "," << ctm[3] << "," << ctm[4]
51 << "," << ctm[5] << "\n";
52}
53
54//------------------------------------------------------------------------
55// GfxFontDict from GfxFont.cc in poppler 22.09
56//
57// Modified under the Poppler project - http://poppler.freedesktop.org
58//
59// All changes made under the Poppler project to this file are licensed
60// under GPL version 2 or later
61//
62// See poppler source code for full list of copyright holders.
63//------------------------------------------------------------------------
64
65InkFontDict::InkFontDict(XRef *xref, Ref *fontDictRef, Dict *fontDict)
66{
67 Ref r;
68
69 fonts.resize(fontDict->getLength());
70 for (std::size_t i = 0; i < fonts.size(); ++i) {
71 const Object &obj1 = fontDict->getValNF(i);
72 Object obj2 = obj1.fetch(xref);
73 if (obj2.isDict()) {
74 if (obj1.isRef()) {
75 r = obj1.getRef();
76 } else if (fontDictRef) {
77 // legal generation numbers are five digits, so we use a
78 // 6-digit number here
79 r.gen = 100000 + fontDictRef->num;
80 r.num = i;
81 } else {
82 // no indirect reference for this font, or for the containing
83 // font dict, so hash the font and use that
84 r.gen = 100000;
85 r.num = hashFontObject(&obj2);
86 }
87 // Newer poppler will require some reworking as it gives a shared ptr.
88 fonts[i] = GfxFont::makeFont(xref, fontDict->getKey(i), r, obj2.getDict());
89 if (fonts[i] && !fonts[i]->isOk()) {
90 fonts[i] = nullptr;
91 }
92 } else {
93 error(errSyntaxError, -1, "font resource is not a dictionary");
94 fonts[i] = nullptr;
95 }
96 }
97}
98
99FontPtr InkFontDict::lookup(const char *tag) const
100{
101 for (const auto &font : fonts) {
102 if (font && font->matches(tag)) {
103 return font;
104 }
105 }
106 return nullptr;
107}
108
109// FNV-1a hash
110class FNVHash
111{
112public:
113 FNVHash() { h = 2166136261U; }
114
115 void hash(char c)
116 {
117 h ^= c & 0xff;
118 h *= 16777619;
119 }
120
121 void hash(const char *p, int n)
122 {
123 int i;
124 for (i = 0; i < n; ++i) {
125 hash(p[i]);
126 }
127 }
128
129 int get31() { return (h ^ (h >> 31)) & 0x7fffffff; }
130
131private:
132 unsigned int h;
133};
134
136{
137 FNVHash h;
138
139 hashFontObject1(obj, &h);
140 return h.get31();
141}
142
143void InkFontDict::hashFontObject1(const Object *obj, FNVHash *h)
144{
145 const GooString *s;
146 const char *p;
147 double r;
148 int n, i;
149
150 switch (obj->getType()) {
151 case objBool:
152 h->hash('b');
153 h->hash(obj->getBool() ? 1 : 0);
154 break;
155 case objInt:
156 h->hash('i');
157 n = obj->getInt();
158 h->hash((char *)&n, sizeof(int));
159 break;
160 case objReal:
161 h->hash('r');
162 r = obj->getReal();
163 h->hash((char *)&r, sizeof(double));
164 break;
165 case objString:
166 h->hash('s');
167 s = obj->getString();
168 h->hash(s->c_str(), s->getLength());
169 break;
170 case objName:
171 h->hash('n');
172 p = obj->getName();
173 h->hash(p, (int)strlen(p));
174 break;
175 case objNull:
176 h->hash('z');
177 break;
178 case objArray:
179 h->hash('a');
180 n = obj->arrayGetLength();
181 h->hash((char *)&n, sizeof(int));
182 for (i = 0; i < n; ++i) {
183 const Object &obj2 = obj->arrayGetNF(i);
184 hashFontObject1(&obj2, h);
185 }
186 break;
187 case objDict:
188 h->hash('d');
189 n = obj->dictGetLength();
190 h->hash((char *)&n, sizeof(int));
191 for (i = 0; i < n; ++i) {
192 p = obj->dictGetKey(i);
193 h->hash(p, (int)strlen(p));
194 const Object &obj2 = obj->dictGetValNF(i);
195 hashFontObject1(&obj2, h);
196 }
197 break;
198 case objStream:
199 // this should never happen - streams must be indirect refs
200 break;
201 case objRef:
202 h->hash('f');
203 n = obj->getRefNum();
204 h->hash((char *)&n, sizeof(int));
205 n = obj->getRefGen();
206 h->hash((char *)&n, sizeof(int));
207 break;
208 default:
209 h->hash('u');
210 break;
211 }
212}
213
215{
216 if (!font->getName())
217 return {};
218
219 std::string tagname = font->getName()->c_str();
220 unsigned int i;
221 for (i = 0; i < tagname.size(); ++i) {
222 if (tagname[i] < 'A' || tagname[i] > 'Z') {
223 break;
224 }
225 }
226 if (i != 6 || tagname.size() <= 7 || tagname[6] != '+')
227 return tagname;
228 return tagname.substr(7);
229}
230
235{
236 // Level one parsing is taking the data from the PDF font, although this
237 // information is almost always missing. Perhaps sometimes it's not.
238 found = false;
239
240 // Style: italic, oblique, normal
241 style = font->isItalic() ? "italic" : "";
242
243 // Weight: normal, bold, etc
244 weight = "normal";
245 switch (font->getWeight()) {
246 case GfxFont::WeightNotDefined:
247 break;
248 case GfxFont::W400:
249 weight = "normal";
250 break;
251 case GfxFont::W700:
252 weight = "bold";
253 break;
254 default:
255 weight = std::to_string(font->getWeight() * 100);
256 break;
257 }
258
259 // Stretch: condensed or expanded
260 stretch = "";
261 switch (font->getStretch()) {
262 case GfxFont::UltraCondensed:
263 stretch = "ultra-condensed";
264 break;
265 case GfxFont::ExtraCondensed:
266 stretch = "extra-condensed";
267 break;
268 case GfxFont::Condensed:
269 stretch = "condensed";
270 break;
271 case GfxFont::SemiCondensed:
272 stretch = "semi-condensed";
273 break;
274 case GfxFont::Normal:
275 stretch = "normal";
276 break;
277 case GfxFont::SemiExpanded:
278 stretch = "semi-expanded";
279 break;
280 case GfxFont::Expanded:
281 stretch = "expanded";
282 break;
283 case GfxFont::ExtraExpanded:
284 stretch = "extra-expanded";
285 break;
286 case GfxFont::UltraExpanded:
287 stretch = "ultra-expanded";
288 break;
289 }
290
292 // Use this when min-poppler version is newer:
293 // name = font->getNameWithoutSubsetTag();
294
296
297 if (!desc && font->getFamily()) {
298 // Level two parsing, we break off the font description part of the name
299 // which often contains font data and use it as a pango font description.
300 std::string pdf_family = validateString(font->getFamily()->c_str());
301 std::string desc_str = pdf_family;
302 auto pos = name.find("-");
303 if (pos != std::string::npos) {
304 // Insert spaces where we see capital letters.
305 std::stringstream ret;
306 auto str = name.substr(pos + 1, name.size());
307 for (char l : str) {
308 if (l >= 'A' && l <= 'Z')
309 ret << " ";
310 ret << l;
311 }
312 desc_str = desc_str + ret.str();
313 }
314 desc = pango_font_description_from_string(desc_str.c_str());
315 if (!desc) {
316 // Sometimes it's possible to match the description string directly.
317 desc = pango_font_description_from_string(pdf_family.c_str());
318 }
319 }
320
321 if (desc) {
322 // Now we pull data out of the description.
323 auto new_family = pango_font_description_get_family(desc);
324 if (new_family && FontFactory::get().hasFontFamily(new_family)) {
325 family = new_family;
326
327 // Style from pango description
328 switch (pango_font_description_get_style(desc)) {
329 case PANGO_STYLE_ITALIC:
330 style = "italic";
331 break;
332 case PANGO_STYLE_OBLIQUE:
333 style = "oblique";
334 break;
335 }
336
337 // Weight from pango description
338 auto pw = pango_font_description_get_weight(desc);
339 if (pw != PANGO_WEIGHT_NORMAL) {
340 weight = std::to_string(pw); // Number 100-1000
341 }
342
343 // Stretch from pango description
344 switch (pango_font_description_get_stretch(desc)) {
345 case PANGO_STRETCH_ULTRA_CONDENSED:
346 stretch = "ultra-condensed";
347 break;
348 case PANGO_STRETCH_EXTRA_CONDENSED:
349 stretch = "extra-condensed";
350 break;
351 case PANGO_STRETCH_CONDENSED:
352 stretch = "condensed";
353 break;
354 case PANGO_STRETCH_SEMI_CONDENSED:
355 stretch = "semi-condensed";
356 break;
357 case PANGO_STRETCH_SEMI_EXPANDED:
358 stretch = "semi-expanded";
359 break;
360 case PANGO_STRETCH_EXPANDED:
361 stretch = "expanded";
362 break;
363 case PANGO_STRETCH_EXTRA_EXPANDED:
364 stretch = "extra-expanded";
365 break;
366 case PANGO_STRETCH_ULTRA_EXPANDED:
367 stretch = "ultra-expanded";
368 break;
369 }
370
371 // variant = TODO Convert to variant pango_font_description_get_variant(desc)
372
373 found = true;
374 // All information has been processed, don't over-write with level three.
375 return;
376 }
377 }
378
379 // Level three parsing, we take our name and attempt to match known style names
380 // Copy id-name stored in PDF and make it lower case and strip whitespaces
381 std::string source = name;
382 transform(source.begin(), source.end(), source.begin(), ::tolower);
383 source.erase(std::remove_if(source.begin(), source.end(), ::isspace), source.end());
384 auto contains = [=](const std::string &other) { return source.find(other) != std::string::npos; };
385
386 if (contains("italic") || contains("slanted")) {
387 style = "italic";
388 } else if (contains("oblique")) {
389 style = "oblique";
390 }
391
392 // Ordered by string matching pass through.
393 static std::map<std::string, std::string> weights{
394 // clang-format off
395 {"bold", "bold"},
396 {"ultrabold", "800"},
397 {"extrabold", "800"},
398 {"demibold", "600"},
399 {"semibold", "600"},
400 {"thin", "100"},
401 {"ultralight", "200"},
402 {"extralight", "200"},
403 {"light", "300"},
404 {"black", "900"},
405 {"heavy", "900"},
406 {"medium", "500"},
407 {"book", "normal"},
408 {"regular", "normal"},
409 {"roman", "normal"},
410 {"normal", "normal"},
411 // clang-format on
412 };
413 // Apply the font weight translations
414 for (auto w : weights) {
415 if (contains(w.first))
416 weight = w.second;
417 }
418
419 static std::map<std::string, std::string> stretches{
420 // clang-format off
421 {"ultracondensed", "ultra-condensed"},
422 {"extracondensed", "extra-condensed"},
423 {"semicondensed", "semi-condensed"},
424 {"condensed", "condensed"},
425 {"ultraexpanded", "ultra-expanded"},
426 {"extraexpanded", "extra-expanded"},
427 {"semiexpanded", "semi-expanded"},
428 {"expanded", "expanded"},
429 // clang-format on
430 };
431 // Apply the font weight translations
432 for (auto s : stretches) {
433 if (contains(s.first))
434 stretch = s.second;
435 }
436}
437
438/*
439 * Scan the available fonts to find the font name that best match.
440 *
441 * If nothing can be matched, returns an empty string.
442 */
443std::string FontData::getSubstitute() const
444{
445 if (found)
446 return "";
447
448 if (auto desc = FontFactory::get().parsePostscriptName(name, true)) {
449 auto new_family = pango_font_description_get_family(desc);
450 if (FontFactory::get().hasFontFamily(new_family)) {
451 return new_family;
452 }
453 }
454 return "sans";
455}
456
457/*
458 * Used to determine if any font property has changed by comparing
459 * font specifications.
460 */
461std::string FontData::getSpecification() const
462{
463 if (auto desc = FontFactory::get().parsePostscriptName(name, false)) {
464 char *copyAsString = pango_font_description_to_string(desc);
465 std::string pangoString = copyAsString;
466 g_free(copyAsString);
467 return pangoString;
468 } else {
469 return
470 family +
471 (weight == "normal" ? "" : " " + weight) +
472 (style.empty() ? "" : " " + style) +
473 (stretch.empty() ? "" : " " + stretch) +
474 (variation.empty() ? "" : " " + variation);
475 }
476}
477
478//------------------------------------------------------------------------
479// scanFonts from FontInfo.cc
480//------------------------------------------------------------------------
481
482void _getFontsRecursive(std::shared_ptr<PDFDoc> pdf_doc, Dict *resources, const FontList &fontsList,
483 std::set<int> &visitedObjects, int page)
484{
485 assert(resources);
486 auto xref = pdf_doc->getXRef();
487
488 InkFontDict *fontDict = nullptr;
489 const Object &obj1 = resources->lookupNF("Font");
490 if (obj1.isRef()) {
491 Object obj2 = obj1.fetch(xref);
492 if (obj2.isDict()) {
493 auto r = obj1.getRef();
494 fontDict = new InkFontDict(xref, &r, obj2.getDict());
495 }
496 } else if (obj1.isDict()) {
497 fontDict = new InkFontDict(xref, nullptr, obj1.getDict());
498 }
499
500 if (fontDict) {
501 for (int i = 0; i < fontDict->getNumFonts(); ++i) {
502 auto font = fontDict->getFont(i);
503 if (fontsList->find(font) == fontsList->end()) {
504 // Create new font data
505 fontsList->emplace(font, FontData(font));
506 }
507 fontsList->at(font).pages.insert(page);
508 }
509 }
510
511 // recursively scan any resource dictionaries in objects in this resource dictionary
512 const char *resTypes[] = {"XObject", "Pattern"};
513 for (const char *resType : resTypes) {
514 Object objDict = resources->lookup(resType);
515 if (!objDict.isDict())
516 continue;
517
518 for (int i = 0; i < objDict.dictGetLength(); ++i) {
519 Ref obj2Ref;
520 const Object obj2 = objDict.getDict()->getVal(i, &obj2Ref);
521 if (obj2Ref != Ref::INVALID() && !visitedObjects.insert(obj2Ref.num).second)
522 continue;
523
524 if (!obj2.isStream())
525 continue;
526
527 Ref resourcesRef;
528 const Object resObj = obj2.streamGetDict()->lookup("Resources", &resourcesRef);
529 if (resourcesRef != Ref::INVALID() && !visitedObjects.insert(resourcesRef.num).second)
530 continue;
531
532 if (resObj.isDict() && resObj.getDict() != resources) {
533 _getFontsRecursive(pdf_doc, resObj.getDict(), fontsList, visitedObjects, page);
534 }
535 }
536 }
537}
538
539FontList getPdfFonts(std::shared_ptr<PDFDoc> pdf_doc)
540{
541 auto fontsList = std::make_shared<std::map<FontPtr, FontData>>();
542 auto count = pdf_doc->getCatalog()->getNumPages();
543 std::set<int> visitedObjects;
544
545 for (auto page_num = 1; page_num <= count; page_num++) {
546 auto page = pdf_doc->getCatalog()->getPage(page_num);
547 auto resources = page->getResourceDict();
548
549 if (resources) {
550 _getFontsRecursive(pdf_doc, resources, fontsList, visitedObjects, page_num);
551 }
552 }
553 return fontsList;
554}
555
570std::string sanitizeId(std::string const &in)
571{
572 // XML allows IDs of the form [a-zA-Z_:][a-zA-Z0-9\-_\.:]* plus some UTF8 characters.
573 // Here we restrict us to the subset [a-zA-Z_][a-zA-Z0-9_]*,
574 // where "_" is used as escape character.
575 // https://www.w3.org/TR/2008/REC-xml-20081126/#id
576 // https://stackoverflow.com/questions/1077084/what-characters-are-allowed-in-dom-ids#1077111
577
578 if (in.empty()) {
579 return "_";
580 }
581 if (isalpha(in[0]) && std::find_if_not(in.begin(), in.end(), isalnum) == in.end()) {
582 [[likely]];
583 // Fast path:
584 // Input is of the form [a-zA-Z][a-zA-Z0-9]*
585 // --> Return unchanged.
586 return in;
587 }
588
589 // Slow path: Escape anything non-alphanumeric,
590 // e.g., "a bc" as a_20bc
591 std::ostringstream outStream;
592 for (char chr : in) {
593 if (isalnum(chr)) {
594 outStream.put(chr);
595 } else {
596 outStream.put('_');
597 outStream << std::hex << ((unsigned int)chr & 0xff);
598 }
599 }
600 return outStream.str();
601}
602
608std::string validateString(std::string const &in)
609{
610 if (g_utf8_validate(in.c_str(), -1, nullptr)) {
611 return in;
612 }
613 g_warning("Couldn't parse strings in the PDF, there may be errors.");
614 return "";
615}
616
620std::string getDictString(Dict *dict, const char *key)
621{
622 Object obj = dict->lookup(key);
623
624 if (!obj.isString()) {
625 return "";
626 }
627 return getString(obj.getString());
628}
629
630std::string getString(const std::unique_ptr<GooString> &value)
631{
632 return getString(value.get());
633}
634
639std::string getString(const GooString *value)
640{
641 if (value) {
642 int stringLength;
643 char *str = nullptr;
644
645 if (_POPPLER_HAS_UNICODE_BOM(value)) {
646 str = g_convert(value->getCString () + 2, value->getLength () - 2,
647 "UTF-8", "UTF-16BE", NULL, NULL, NULL);
648 } else if (_POPPLER_HAS_UNICODE_BOMLE(value)) {
649 str = g_convert(value->getCString () + 2, value->getLength () - 2,
650 "UTF-8", "UTF-16LE", NULL, NULL, NULL);
651 }
652#if POPPLER_CHECK_VERSION(25,02,0)
653 else if (auto utf16 = pdfDocEncodingToUTF16(value->toStr()); !utf16.empty()) {
654 str = g_convert(utf16.c_str(), utf16.length(), "UTF-8", "UTF-16", NULL, NULL, NULL);
655 }
656#else
657 else if (auto utf16 = pdfDocEncodingToUTF16(value->toStr(), &stringLength)) {
658 str = g_convert(utf16, stringLength, "UTF-8", "UTF-16", NULL, NULL, NULL);
659 delete[] utf16;
660 }
661#endif
662 if (str) {
663 std::string copy = str;
664 g_free(str);
665 return copy;
666 }
667 g_warning("Couldn't parse text in PDF from UTF16.");
668 }
669 return "";
670}
671
672void pdf_debug_array(const Array *array, int depth, XRef *xref)
673{
674 if (depth > 20) {
675 std::cout << "[ ... ]";
676 return;
677 }
678 std::cout << "[\n";
679 for (int i = 0; i < array->getLength(); ++i) {
680 for (int x = depth; x > -1; x--)
681 std::cout << " ";
682 std::cout << i << ": ";
683 Object obj = array->get(i);
684 pdf_debug_object(&obj, depth + 1, xref);
685 std::cout << ",\n";
686 }
687 for (int x = depth; x > 0; x--)
688 std::cout << " ";
689 std::cout << "]";
690}
691
692void pdf_debug_dict(const Dict *dict, int depth, XRef *xref)
693{
694 if (depth > 20) {
695 std::cout << "{ ... }";
696 return;
697 }
698 std::cout << "{\n";
699 for (auto j = 0; j < dict->getLength(); j++) {
700 auto key = dict->getKey(j);
701 auto val = dict->getVal(j);
702 for (int x = depth; x > -1; x--)
703 std::cout << " ";
704 std::cout << key << ": ";
705 pdf_debug_object(&val, depth + 1, xref);
706 std::cout << ",\n";
707 }
708 for (int x = depth; x > 0; x--)
709 std::cout << " ";
710 std::cout << "}";
711}
712
713void pdf_debug_object(const Object *obj, int depth, XRef *xref)
714{
715 if (obj->isRef()) {
716 std::cout << " > REF(" << obj->getRef().num << "):";
717 if (xref) {
718 auto ref = obj->fetch(xref);
719 pdf_debug_object(&ref, depth + 1, xref);
720 }
721 } else if (obj->isDict()) {
722 pdf_debug_dict(obj->getDict(), depth, xref);
723 } else if (obj->isArray()) {
724 pdf_debug_array(obj->getArray(), depth, xref);
725 } else if (obj->isString()) {
726 std::cout << " STR '" << obj->getString()->getCString() << "'";
727 } else if (obj->isName()) {
728 std::cout << " NAME '" << obj->getName() << "'";
729 } else if (obj->isBool()) {
730 std::cout << " BOOL " << (obj->getBool() ? "true" : "false");
731 } else if (obj->isNum()) {
732 std::cout << " NUM " << obj->getNum();
733 } else {
734 std::cout << " > ? " << obj->getType() << "";
735 }
736}
737
738/*
739 Local Variables:
740 mode:c++
741 c-file-style:"stroustrup"
742 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
743 indent-tabs-mode:nil
744 fill-column:99
745 End:
746*/
747// vim:filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99:
_PangoFontDescription PangoFontDescription
Definition Layout-TNG.h:44
3x3 affine transformation matrix.
uint64_t page
Definition canvas.cpp:158
std::string weight
FontData(FontPtr font)
Extract all the useful information from the GfxFont object.
std::string name
std::string getSpecification() const
std::string variation
std::string getSubstitute() const
std::string family
std::string style
std::string stretch
PangoFontDescription * parsePostscriptName(std::string const &name, bool substitute)
Use font config to parse the postscript name found in pdf/ps files and return font config family and ...
3x3 matrix representing an affine transformation.
Definition affine.h:70
FontPtr lookup(const char *tag) const
FontPtr getFont(int i) const
void hashFontObject1(const Object *obj, FNVHash *h)
std::vector< FontPtr > fonts
int hashFontObject(Object *obj)
InkFontDict(XRef *xref, Ref *fontDictRef, Dict *fontDict)
int getNumFonts() const
static FontFactory & get(Args &&... args)
Definition statics.h:153
const double w
Definition conic-4.cpp:19
double c[8][4]
TODO: insert short description here.
Glib::ustring label
Affine identity()
Create an identity matrix.
Definition affine.h:210
int n
Definition spiro.cpp:57
static cairo_user_data_key_t key
Geom::Affine stateToAffine(GfxState *state)
Get the default transformation state from the GfxState.
void ctmout(const char *label, const double *ctm)
FontList getPdfFonts(std::shared_ptr< PDFDoc > pdf_doc)
std::string getString(const std::unique_ptr< GooString > &value)
void pdf_debug_array(const Array *array, int depth, XRef *xref)
std::string getNameWithoutSubsetTag(FontPtr font)
std::string sanitizeId(std::string const &in)
Convert arbitrary string (e.g.
void pdf_debug_dict(const Dict *dict, int depth, XRef *xref)
std::string validateString(std::string const &in)
Ensure string is valid UTF8.
void _getFontsRecursive(std::shared_ptr< PDFDoc > pdf_doc, Dict *resources, const FontList &fontsList, std::set< int > &visitedObjects, int page)
std::string getDictString(Dict *dict, const char *key)
Get a string from a dictionary.
Geom::Affine ctmToAffine(const double *ctm)
Convert a transformation matrix to a lib2geom affine object.
void affout(const char *label, Geom::Affine ctm)
void pdf_debug_object(const Object *obj, int depth, XRef *xref)
PDF parsing utilities for libpoppler.
std::shared_ptr< std::map< FontPtr, FontData > > FontList
std::shared_ptr< GfxFont > FontPtr
std::string validateString(std::string const &in)
Ensure string is valid UTF8.
Ocnode ** ref
Definition quantize.cpp:32