1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /** @file
3  * TODO: insert short description here
4  *//*
5  * Authors:
6  *     fred
7  *     bulia byak <buliabyak@users.sf.net>
8  *
9  * Copyright (C) 2018 Authors
10  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
11  */
12 
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"  // only include where actually required!
15 #endif
16 
17 #ifndef PANGO_ENABLE_ENGINE
18 #define PANGO_ENABLE_ENGINE
19 #endif
20 
21 #include <unordered_map>
22 
23 #include <glibmm/i18n.h>
24 
25 #include <fontconfig/fontconfig.h>
26 
27 #include <pango/pangofc-fontmap.h>
28 #include <pango/pangoft2.h>
29 #include <pango/pango-ot.h>
30 
31 #include "io/sys.h"
32 #include "io/resource.h"
33 
34 #include "libnrtype/FontFactory.h"
35 #include "libnrtype/font-instance.h"
36 #include "libnrtype/OpenTypeUtil.h"
37 
38 # ifdef _WIN32
39 
40 #include <glibmm.h>
41 #include <windows.h>
42 
43 #endif
44 
45 typedef std::unordered_map<PangoFontDescription*, font_instance*, font_descr_hash, font_descr_equal> FaceMapType;
46 
47 // need to avoid using the size field
operator ()(PangoFontDescription * const & x) const48 size_t font_descr_hash::operator()( PangoFontDescription *const &x) const {
49     int h = 0;
50     char const *theF = sp_font_description_get_family(x);
51     h += (theF)?g_str_hash(theF):0;
52     h *= 1128467;
53     h += (int)pango_font_description_get_style(x);
54     h *= 1128467;
55     h += (int)pango_font_description_get_variant(x);
56     h *= 1128467;
57     h += (int)pango_font_description_get_weight(x);
58     h *= 1128467;
59     h += (int)pango_font_description_get_stretch(x);
60 #if PANGO_VERSION_CHECK(1,41,1)
61     char const *theV = pango_font_description_get_variations(x);
62     h *= 1128467;
63     h += (theV)?g_str_hash(theV):0;
64 #endif
65     return h;
66 }
67 
operator ()(PangoFontDescription * const & a,PangoFontDescription * const & b) const68 bool  font_descr_equal::operator()( PangoFontDescription *const&a, PangoFontDescription *const &b) const {
69     //if ( pango_font_description_equal(a,b) ) return true;
70     char const *fa = sp_font_description_get_family(a);
71     char const *fb = sp_font_description_get_family(b);
72     if ( ( fa && fb == nullptr ) || ( fb && fa == nullptr ) ) return false;
73     if ( fa && fb && strcmp(fa,fb) != 0 ) return false;
74     if ( pango_font_description_get_style(a) != pango_font_description_get_style(b) ) return false;
75     if ( pango_font_description_get_variant(a) != pango_font_description_get_variant(b) ) return false;
76     if ( pango_font_description_get_weight(a) != pango_font_description_get_weight(b) ) return false;
77     if ( pango_font_description_get_stretch(a) != pango_font_description_get_stretch(b) ) return false;
78 #if PANGO_VERSION_CHECK(1,41,1)
79     if ( g_strcmp0( pango_font_description_get_variations(a),
80                     pango_font_description_get_variations(b) ) != 0 ) return false;
81 #endif
82     return true;
83 }
84 
85 // User must free return value.
ink_font_description_from_style(SPStyle const * style)86 PangoFontDescription* ink_font_description_from_style(SPStyle const *style)
87 {
88     PangoFontDescription *descr = pango_font_description_new();
89 
90     pango_font_description_set_family(descr, style->font_family.value());
91 
92     // This duplicates Layout::EnumConversionItem... perhaps we can share code?
93     switch ( style->font_style.computed ) {
94         case SP_CSS_FONT_STYLE_ITALIC:
95             pango_font_description_set_style(descr, PANGO_STYLE_ITALIC);
96             break;
97 
98         case SP_CSS_FONT_STYLE_OBLIQUE:
99             pango_font_description_set_style(descr, PANGO_STYLE_OBLIQUE);
100             break;
101 
102         case SP_CSS_FONT_STYLE_NORMAL:
103         default:
104             pango_font_description_set_style(descr, PANGO_STYLE_NORMAL);
105             break;
106     }
107 
108     switch( style->font_weight.computed ) {
109         case SP_CSS_FONT_WEIGHT_100:
110             pango_font_description_set_weight(descr, PANGO_WEIGHT_THIN);
111             break;
112 
113         case SP_CSS_FONT_WEIGHT_200:
114             pango_font_description_set_weight(descr, PANGO_WEIGHT_ULTRALIGHT);
115             break;
116 
117         case SP_CSS_FONT_WEIGHT_300:
118             pango_font_description_set_weight(descr, PANGO_WEIGHT_LIGHT);
119             break;
120 
121         case SP_CSS_FONT_WEIGHT_400:
122         case SP_CSS_FONT_WEIGHT_NORMAL:
123             pango_font_description_set_weight(descr, PANGO_WEIGHT_NORMAL);
124             break;
125 
126         case SP_CSS_FONT_WEIGHT_500:
127             pango_font_description_set_weight(descr, PANGO_WEIGHT_MEDIUM);
128             break;
129 
130         case SP_CSS_FONT_WEIGHT_600:
131             pango_font_description_set_weight(descr, PANGO_WEIGHT_SEMIBOLD);
132             break;
133 
134         case SP_CSS_FONT_WEIGHT_700:
135         case SP_CSS_FONT_WEIGHT_BOLD:
136             pango_font_description_set_weight(descr, PANGO_WEIGHT_BOLD);
137             break;
138 
139         case SP_CSS_FONT_WEIGHT_800:
140             pango_font_description_set_weight(descr, PANGO_WEIGHT_ULTRABOLD);
141             break;
142 
143         case SP_CSS_FONT_WEIGHT_900:
144             pango_font_description_set_weight(descr, PANGO_WEIGHT_HEAVY);
145             break;
146 
147         case SP_CSS_FONT_WEIGHT_LIGHTER:
148         case SP_CSS_FONT_WEIGHT_BOLDER:
149         default:
150             g_warning("FaceFromStyle: Unrecognized font_weight.computed value");
151             pango_font_description_set_weight(descr, PANGO_WEIGHT_NORMAL);
152             break;
153     }
154     // PANGO_WIEGHT_ULTRAHEAVY not used (not CSS2)
155 
156     switch (style->font_stretch.computed) {
157         case SP_CSS_FONT_STRETCH_ULTRA_CONDENSED:
158             pango_font_description_set_stretch(descr, PANGO_STRETCH_ULTRA_CONDENSED);
159             break;
160 
161         case SP_CSS_FONT_STRETCH_EXTRA_CONDENSED:
162             pango_font_description_set_stretch(descr, PANGO_STRETCH_EXTRA_CONDENSED);
163             break;
164 
165         case SP_CSS_FONT_STRETCH_CONDENSED:
166             pango_font_description_set_stretch(descr, PANGO_STRETCH_CONDENSED);
167             break;
168 
169         case SP_CSS_FONT_STRETCH_SEMI_CONDENSED:
170             pango_font_description_set_stretch(descr, PANGO_STRETCH_SEMI_CONDENSED);
171             break;
172 
173         case SP_CSS_FONT_STRETCH_NORMAL:
174             pango_font_description_set_stretch(descr, PANGO_STRETCH_NORMAL);
175             break;
176 
177         case SP_CSS_FONT_STRETCH_SEMI_EXPANDED:
178             pango_font_description_set_stretch(descr, PANGO_STRETCH_SEMI_EXPANDED);
179             break;
180 
181         case SP_CSS_FONT_STRETCH_EXPANDED:
182             pango_font_description_set_stretch(descr, PANGO_STRETCH_EXPANDED);
183             break;
184 
185         case SP_CSS_FONT_STRETCH_EXTRA_EXPANDED:
186             pango_font_description_set_stretch(descr, PANGO_STRETCH_EXTRA_EXPANDED);
187             break;
188 
189         case SP_CSS_FONT_STRETCH_ULTRA_EXPANDED:
190             pango_font_description_set_stretch(descr, PANGO_STRETCH_ULTRA_EXPANDED);
191 
192         case SP_CSS_FONT_STRETCH_WIDER:
193         case SP_CSS_FONT_STRETCH_NARROWER:
194         default:
195             g_warning("FaceFromStyle: Unrecognized font_stretch.computed value");
196             pango_font_description_set_stretch(descr, PANGO_STRETCH_NORMAL);
197             break;
198     }
199 
200     switch ( style->font_variant.computed ) {
201         case SP_CSS_FONT_VARIANT_SMALL_CAPS:
202             pango_font_description_set_variant(descr, PANGO_VARIANT_SMALL_CAPS);
203             break;
204 
205         case SP_CSS_FONT_VARIANT_NORMAL:
206         default:
207             pango_font_description_set_variant(descr, PANGO_VARIANT_NORMAL);
208             break;
209     }
210 
211 #if PANGO_VERSION_CHECK(1,41,1)
212     // Check if not empty as Pango will add @ to string even if empty (bug in Pango?).
213     if (!style->font_variation_settings.axes.empty()) {
214         pango_font_description_set_variations(descr, style->font_variation_settings.toString().c_str());
215     }
216 #endif
217 
218     return descr;
219 }
220 
221 /////////////////// helper functions
222 
noop(...)223 static void noop(...) {}
224 //#define PANGO_DEBUG g_print
225 #define PANGO_DEBUG noop
226 
227 
228 ///////////////////// FontFactory
229 #ifndef USE_PANGO_WIN32
230 // the substitute function to tell fontconfig to enforce outline fonts
FactorySubstituteFunc(FcPattern * pattern,gpointer)231 static void FactorySubstituteFunc(FcPattern *pattern,gpointer /*data*/)
232 {
233     FcPatternAddBool(pattern, "FC_OUTLINE",FcTrue);
234     //char *fam = NULL;
235     //FcPatternGetString(pattern, "FC_FAMILY",0, &fam);
236     //printf("subst_f on %s\n",fam);
237 }
238 #endif
239 
240 
241 font_factory *font_factory::lUsine = nullptr;
242 
Default()243 font_factory *font_factory::Default()
244 {
245     if ( lUsine == nullptr ) lUsine = new font_factory;
246     return lUsine;
247 }
248 
font_factory()249 font_factory::font_factory() :
250     nbEnt(0), // Note: this "ents" cache only keeps fonts from being unreffed, does not speed up access
251     maxEnt(32),
252     ents(static_cast<font_entry*>(g_malloc(maxEnt*sizeof(font_entry)))),
253 #ifdef USE_PANGO_WIN32
254     fontServer(pango_win32_font_map_for_display()),
255     pangoFontCache(pango_win32_font_map_get_font_cache(fontServer)),
256     hScreenDC(pango_win32_get_dc()),
257 #else
258     fontServer(pango_ft2_font_map_new()),
259 #endif
260     fontContext(pango_font_map_create_context(fontServer)),
261     fontSize(512),
262     loadedPtr(new FaceMapType())
263 {
264 #ifndef USE_PANGO_WIN32
265     pango_ft2_font_map_set_resolution(PANGO_FT2_FONT_MAP(fontServer),
266                                       72, 72);
267     pango_ft2_font_map_set_default_substitute(PANGO_FT2_FONT_MAP(fontServer),
268                                               FactorySubstituteFunc,
269                                               this,
270                                               nullptr);
271 #endif
272 
273 }
274 
~font_factory()275 font_factory::~font_factory()
276 {
277     for (int i = 0;i < nbEnt;i++) ents[i].f->Unref();
278     if ( ents ) g_free(ents);
279 
280     g_object_unref(fontServer);
281 #ifdef USE_PANGO_WIN32
282     pango_win32_shutdown_display();
283 #else
284     //pango_ft2_shutdown_display();
285 #endif
286     //g_object_unref(fontContext);
287 
288     if (loadedPtr) {
289         FaceMapType* tmp = static_cast<FaceMapType*>(loadedPtr);
290         delete tmp;
291         loadedPtr = nullptr;
292     }
293 }
294 
295 
ConstructFontSpecification(PangoFontDescription * font)296 Glib::ustring font_factory::ConstructFontSpecification(PangoFontDescription *font)
297 {
298     Glib::ustring pangoString;
299 
300     g_assert(font);
301 
302     if (font) {
303         // Once the format for the font specification is decided, it must be
304         // kept.. if it is absolutely necessary to change it, the attribute
305         // it is written to needs to have a new version so the legacy files
306         // can be read.
307 
308         PangoFontDescription *copy = pango_font_description_copy(font);
309 
310         pango_font_description_unset_fields (copy, PANGO_FONT_MASK_SIZE);
311         char * copyAsString = pango_font_description_to_string(copy);
312         pangoString = copyAsString;
313         g_free(copyAsString);
314         copyAsString = nullptr;
315 
316         pango_font_description_free(copy);
317 
318     }
319 
320     return pangoString;
321 }
322 
ConstructFontSpecification(font_instance * font)323 Glib::ustring font_factory::ConstructFontSpecification(font_instance *font)
324 {
325     Glib::ustring pangoString;
326 
327     g_assert(font);
328 
329     if (font) {
330         pangoString = ConstructFontSpecification(font->descr);
331     }
332 
333     return pangoString;
334 }
335 
336 /*
337  * Wrap calls to pango_font_description_get_family
338  * and replace some of the pango font names with generic css names
339  * http://www.w3.org/TR/2008/REC-CSS2-20080411/fonts.html#generic-font-families
340  *
341  * This function should be called in place of pango_font_description_get_family()
342  */
sp_font_description_get_family(PangoFontDescription const * fontDescr)343 const char *sp_font_description_get_family(PangoFontDescription const *fontDescr) {
344 
345     static std::map<Glib::ustring, Glib::ustring> fontNameMap;
346     std::map<Glib::ustring, Glib::ustring>::iterator it;
347 
348     if (fontNameMap.empty()) {
349         fontNameMap.insert(std::make_pair("Sans", "sans-serif"));
350         fontNameMap.insert(std::make_pair("Serif", "serif"));
351         fontNameMap.insert(std::make_pair("Monospace", "monospace"));
352     }
353 
354     const char *pangoFamily = pango_font_description_get_family(fontDescr);
355 
356     if (pangoFamily && ((it = fontNameMap.find(pangoFamily)) != fontNameMap.end())) {
357         return (it->second).c_str();
358     }
359 
360     return pangoFamily;
361 }
362 
GetUIFamilyString(PangoFontDescription const * fontDescr)363 Glib::ustring font_factory::GetUIFamilyString(PangoFontDescription const *fontDescr)
364 {
365     Glib::ustring family;
366 
367     g_assert(fontDescr);
368 
369     if (fontDescr) {
370         // For now, keep it as family name taken from pango
371         const char *pangoFamily = sp_font_description_get_family(fontDescr);
372 
373         if( pangoFamily ) {
374             family = pangoFamily;
375         }
376     }
377 
378     return family;
379 }
380 
GetUIStyleString(PangoFontDescription const * fontDescr)381 Glib::ustring font_factory::GetUIStyleString(PangoFontDescription const *fontDescr)
382 {
383     Glib::ustring style;
384 
385     g_assert(fontDescr);
386 
387     if (fontDescr) {
388         PangoFontDescription *fontDescrCopy = pango_font_description_copy(fontDescr);
389 
390         pango_font_description_unset_fields(fontDescrCopy, PANGO_FONT_MASK_FAMILY);
391         pango_font_description_unset_fields(fontDescrCopy, PANGO_FONT_MASK_SIZE);
392 
393         // For now, keep it as style name taken from pango
394         char *fontDescrAsString = pango_font_description_to_string(fontDescrCopy);
395         style = fontDescrAsString;
396         g_free(fontDescrAsString);
397         fontDescrAsString = nullptr;
398         pango_font_description_free(fontDescrCopy);
399     }
400 
401     return style;
402 }
403 
404 
405 /////
406 
407 // Calculate a Style "value" based on CSS values for ordering styles.
StyleNameValue(const Glib::ustring & style)408 static int StyleNameValue( const Glib::ustring &style )
409 {
410 
411     PangoFontDescription *pfd = pango_font_description_from_string ( style.c_str() );
412     int value =
413         pango_font_description_get_weight ( pfd ) * 1000000 +
414         pango_font_description_get_style  ( pfd ) *   10000 +
415         pango_font_description_get_stretch( pfd ) *     100 +
416         pango_font_description_get_variant( pfd );
417     pango_font_description_free ( pfd );
418     return value;
419 }
420 
421 // Determines order in which styles are presented (sorted by CSS style values)
422 //static bool StyleNameCompareInternal(const StyleNames &style1, const StyleNames &style2)
423 //{
424 //   return( StyleNameValue( style1.CssName ) < StyleNameValue( style2.CssName ) );
425 //}
426 
StyleNameCompareInternalGlib(gconstpointer a,gconstpointer b)427 static gint StyleNameCompareInternalGlib(gconstpointer a, gconstpointer b)
428 {
429     return( StyleNameValue( ((StyleNames *)a)->CssName  ) <
430             StyleNameValue( ((StyleNames *)b)->CssName  ) ? -1 : 1 );
431 }
432 
ustringPairSort(std::pair<PangoFontFamily *,Glib::ustring> const & first,std::pair<PangoFontFamily *,Glib::ustring> const & second)433 static bool ustringPairSort(std::pair<PangoFontFamily*, Glib::ustring> const& first, std::pair<PangoFontFamily*, Glib::ustring> const& second)
434 {
435     // well, this looks weird.
436     return first.second < second.second;
437 }
438 
GetUIFamilies(std::vector<PangoFontFamily * > & out)439 void font_factory::GetUIFamilies(std::vector<PangoFontFamily *>& out)
440 {
441     // Gather the family names as listed by Pango
442     PangoFontFamily** families = nullptr;
443     int numFamilies = 0;
444     pango_font_map_list_families(fontServer, &families, &numFamilies);
445 
446     std::vector<std::pair<PangoFontFamily *, Glib::ustring> > sorted;
447 
448     // not size_t
449     for (int currentFamily = 0; currentFamily < numFamilies; ++currentFamily) {
450         const char* displayName = pango_font_family_get_name(families[currentFamily]);
451 
452         if (displayName == nullptr || *displayName == '\0') {
453             std::cerr << "font_factory::GetUIFamilies: Missing displayName! " << std::endl;
454             continue;
455         }
456         if (!g_utf8_validate(displayName, -1, nullptr)) {
457             // TODO: can can do anything about this or does it always indicate broken fonts that should not be used?
458             std::cerr << "font_factory::GetUIFamilies: Illegal characters in displayName. ";
459             std::cerr << "Ignoring font '" << displayName << "'" << std::endl;
460             continue;
461         }
462         sorted.emplace_back(families[currentFamily], displayName);
463     }
464 
465     std::sort(sorted.begin(), sorted.end(), ustringPairSort);
466 
467     for (auto & i : sorted) {
468         out.push_back(i.first);
469     }
470 }
471 
GetUIStyles(PangoFontFamily * in)472 GList* font_factory::GetUIStyles(PangoFontFamily * in)
473 {
474     GList* ret = nullptr;
475     // Gather the styles for this family
476     PangoFontFace** faces = nullptr;
477     int numFaces = 0;
478     if (in == nullptr) {
479         std::cerr << "font_factory::GetUIStyles(): PangoFontFamily is NULL" << std::endl;
480         return ret;
481     }
482 
483     pango_font_family_list_faces(in, &faces, &numFaces);
484 
485     for (int currentFace = 0; currentFace < numFaces; currentFace++) {
486 
487         // If the face has a name, describe it, and then use the
488         // description to get the UI family and face strings
489         const gchar* displayName = pango_font_face_get_face_name(faces[currentFace]);
490         // std::cout << "Display Name: " << displayName << std::endl;
491         if (displayName == nullptr || *displayName == '\0') {
492             std::cerr << "font_factory::GetUIStyles: Missing displayName! " << std::endl;
493             continue;
494         }
495 
496         PangoFontDescription *faceDescr = pango_font_face_describe(faces[currentFace]);
497         if (faceDescr) {
498             Glib::ustring familyUIName = GetUIFamilyString(faceDescr);
499             Glib::ustring styleUIName = GetUIStyleString(faceDescr);
500             // std::cout << "  " << familyUIName << "  styleUIName: " << styleUIName << "  displayName: " << displayName << std::endl;
501 
502             // Disable synthesized (faux) font faces except for CSS generic faces
503             if (pango_font_face_is_synthesized(faces[currentFace]) ) {
504                 if (familyUIName.compare( "sans-serif" ) != 0 &&
505                     familyUIName.compare( "serif"      ) != 0 &&
506                     familyUIName.compare( "monospace"  ) != 0 &&
507                     familyUIName.compare( "fantasy"    ) != 0 &&
508                     familyUIName.compare( "cursive"    ) != 0 ) {
509                     continue;
510                 }
511             }
512 
513             // Pango breaks the 1 to 1 mapping between Pango weights and CSS weights by
514             // adding Semi-Light (as of 1.36.7), Book (as of 1.24), and Ultra-Heavy (as of
515             // 1.24). We need to map these weights to CSS weights. Book and Ultra-Heavy
516             // are rarely used. Semi-Light (350) is problematic as it is halfway between
517             // Light (300) and Normal (400) and if care is not taken it is converted to
518             // Normal, rather than Light.
519             //
520             // Note: The ultimate solution to handling various weight in the same
521             // font family is to support the @font rules from CSS.
522             //
523             // Additional notes, helpful for debugging:
524             //   Pango's FC backend:
525             //     Weights defined in fontconfig/fontconfig.h
526             //     String equivalents in src/fcfreetype.c
527             //     Weight set from os2->usWeightClass
528             //   Use Fontforge: Element->Font Info...->OS/2->Misc->Weight Class to check font weight
529             size_t f = styleUIName.find( "Book" );
530             if( f != Glib::ustring::npos ) {
531                 styleUIName.replace( f, 4, "Normal" );
532             }
533             f = styleUIName.find( "Semi-Light" );
534             if( f != Glib::ustring::npos ) {
535                 styleUIName.replace( f, 10, "Light" );
536             }
537             f = styleUIName.find( "Ultra-Heavy" );
538             if( f != Glib::ustring::npos ) {
539                 styleUIName.replace( f, 11, "Heavy" );
540             }
541 
542             bool exists = false;
543             for(GList *temp = ret; temp; temp = temp->next) {
544                 if( ((StyleNames*)temp->data)->CssName.compare( styleUIName ) == 0 ) {
545                     exists = true;
546                     std::cerr << "Warning: Font face with same CSS values already added: "
547                               << familyUIName << " " << styleUIName
548                               << " (" << ((StyleNames*)temp->data)->DisplayName
549                               << ", " << displayName << ")" << std::endl;
550                     break;
551                 }
552             }
553 
554             if (!exists && !familyUIName.empty() && !styleUIName.empty()) {
555                 // Add the style information
556                 ret = g_list_append(ret, new StyleNames(styleUIName, displayName));
557             }
558         }
559         pango_font_description_free(faceDescr);
560     }
561     g_free(faces);
562 
563     // Sort the style lists
564     ret = g_list_sort( ret, StyleNameCompareInternalGlib );
565     return ret;
566 }
567 
568 
FaceFromStyle(SPStyle const * style)569 font_instance* font_factory::FaceFromStyle(SPStyle const *style)
570 {
571     font_instance *font = nullptr;
572 
573     g_assert(style);
574 
575     if (style) {
576 
577         //  First try to use the font specification if it is set
578         char const *val;
579         if (style->font_specification.set
580             && (val = style->font_specification.value())
581             && val[0]) {
582 
583             font = FaceFromFontSpecification(val);
584         }
585 
586         // If that failed, try using the CSS information in the style
587         if (!font) {
588             PangoFontDescription* temp_descr =
589                 ink_font_description_from_style(style);
590             font = Face(temp_descr);
591             pango_font_description_free(temp_descr);
592         }
593     }
594 
595     return font;
596 }
597 
FaceFromDescr(char const * family,char const * style)598 font_instance *font_factory::FaceFromDescr(char const *family, char const *style)
599 {
600     PangoFontDescription *temp_descr = pango_font_description_from_string(style);
601     pango_font_description_set_family(temp_descr,family);
602     font_instance *res = Face(temp_descr);
603     pango_font_description_free(temp_descr);
604     return res;
605 }
606 
FaceFromPangoString(char const * pangoString)607 font_instance* font_factory::FaceFromPangoString(char const *pangoString)
608 {
609     font_instance *fontInstance = nullptr;
610 
611     g_assert(pangoString);
612 
613     if (pangoString) {
614 
615         // Create a font description from the string - this may fail or
616         // produce unexpected results if the string does not have a good format
617         PangoFontDescription *descr = pango_font_description_from_string(pangoString);
618 
619         if (descr) {
620             if (sp_font_description_get_family(descr) != nullptr) {
621                 fontInstance = Face(descr);
622             }
623             pango_font_description_free(descr);
624         }
625     }
626 
627     return fontInstance;
628 }
629 
FaceFromFontSpecification(char const * fontSpecification)630 font_instance* font_factory::FaceFromFontSpecification(char const *fontSpecification)
631 {
632     font_instance *font = nullptr;
633 
634     g_assert(fontSpecification);
635 
636     if (fontSpecification) {
637         // How the string is used to reconstruct a font depends on how it
638         // was constructed in ConstructFontSpecification.  As it stands,
639         // the font specification is a pango-created string
640         font = FaceFromPangoString(fontSpecification);
641     }
642 
643     return font;
644 }
645 
Face(PangoFontDescription * descr,bool canFail)646 font_instance *font_factory::Face(PangoFontDescription *descr, bool canFail)
647 {
648 #ifdef USE_PANGO_WIN32
649     // damn Pango fudges the size, so we need to unfudge. See source of pango_win32_font_map_init()
650     pango_font_description_set_size(descr, (int) (fontSize*PANGO_SCALE*72/GetDeviceCaps(pango_win32_get_dc(),LOGPIXELSY))); // mandatory huge size (hinting workaround)
651 #else
652     pango_font_description_set_size(descr, (int) (fontSize*PANGO_SCALE)); // mandatory huge size (hinting workaround)
653 #endif
654 
655     font_instance *res = nullptr;
656 
657     FaceMapType& loadedFaces = *static_cast<FaceMapType*>(loadedPtr);
658     if ( loadedFaces.find(descr) == loadedFaces.end() ) {
659         // not yet loaded
660         PangoFont *nFace = nullptr;
661 
662         // workaround for bug #1025565.
663         // fonts without families blow up Pango.
664         if (sp_font_description_get_family(descr) != nullptr) {
665             nFace = pango_font_map_load_font(fontServer,fontContext,descr);
666         }
667         else {
668             g_warning("%s", _("Ignoring font without family that will crash Pango"));
669         }
670 
671         if ( nFace ) {
672             // duplicate FcPattern, the hard way
673             res = new font_instance();
674             // store the descr of the font we asked for, since this is the key where we intend
675             // to put the font_instance at in the unordered_map.  the descr of the returned
676             // pangofont may differ from what was asked, so we don't know (at this
677             // point) whether loadedFaces[that_descr] is free or not (and overwriting
678             // an entry will bring deallocation problems)
679             res->descr = pango_font_description_copy(descr);
680             res->parent = this;
681             res->InstallFace(nFace);
682             if ( res->pFont == nullptr ) {
683                 // failed to install face -> bitmap font
684                 // printf("face failed\n");
685                 res->parent = nullptr;
686                 delete res;
687                 res = nullptr;
688                 if ( canFail ) {
689                     char *tc = pango_font_description_to_string(descr);
690                     PANGO_DEBUG("falling back from %s to 'sans-serif' because InstallFace failed\n",tc);
691                     g_free(tc);
692                     pango_font_description_set_family(descr,"sans-serif");
693                     res = Face(descr,false);
694                 }
695             } else {
696                 loadedFaces[res->descr]=res;
697                 res->Ref();
698                 AddInCache(res);
699             }
700         } else {
701             // no match
702             if ( canFail ) {
703                 PANGO_DEBUG("falling back to 'sans-serif'\n");
704                 PangoFontDescription *new_descr = pango_font_description_new();
705                 pango_font_description_set_family(new_descr, "sans-serif");
706                 res = Face(new_descr, false);
707                 pango_font_description_free(new_descr);
708             } else {
709                 g_critical("Could not load any face for font '%s'.", pango_font_description_to_string(descr));
710             }
711         }
712 
713     } else {
714         // already here
715         res = loadedFaces[descr];
716         res->Ref();
717         AddInCache(res);
718     }
719     if (res) {
720         res->InitTheFace();
721     }
722     return res;
723 }
724 
725 // Not used, need to add variations if ever used.
726 // font_instance *font_factory::Face(char const *family, int variant, int style, int weight, int stretch, int /*size*/, int /*spacing*/)
727 // {
728 //     // std::cout << "font_factory::Face(family, variant, style, weight, stretch,)" << std::endl;
729 //     PangoFontDescription *temp_descr = pango_font_description_new();
730 //     pango_font_description_set_family(temp_descr,family);
731 //     pango_font_description_set_weight(temp_descr,(PangoWeight)weight);
732 //     pango_font_description_set_stretch(temp_descr,(PangoStretch)stretch);
733 //     pango_font_description_set_style(temp_descr,(PangoStyle)style);
734 //     pango_font_description_set_variant(temp_descr,(PangoVariant)variant);
735 //     font_instance *res = Face(temp_descr);
736 //     pango_font_description_free(temp_descr);
737 //     return res;
738 // }
739 
UnrefFace(font_instance * who)740 void font_factory::UnrefFace(font_instance *who)
741 {
742     if ( who ) {
743         FaceMapType& loadedFaces = *static_cast<FaceMapType*>(loadedPtr);
744 
745         if ( loadedFaces.find(who->descr) == loadedFaces.end() ) {
746             // not found
747             char *tc = pango_font_description_to_string(who->descr);
748             g_warning("unrefFace %p=%s: failed\n",who,tc);
749             g_free(tc);
750         } else {
751             loadedFaces.erase(loadedFaces.find(who->descr));
752             //            printf("unrefFace %p: success\n",who);
753         }
754     }
755 }
756 
AddInCache(font_instance * who)757 void font_factory::AddInCache(font_instance *who)
758 {
759     if ( who == nullptr ) return;
760     for (int i = 0;i < nbEnt;i++) ents[i].age *= 0.9;
761     for (int i = 0;i < nbEnt;i++) {
762         if ( ents[i].f == who ) {
763             //            printf("present\n");
764             ents[i].age += 1.0;
765             return;
766         }
767     }
768     if ( nbEnt > maxEnt ) {
769         printf("cache sur-plein?\n");
770         return;
771     }
772     who->Ref();
773     if ( nbEnt == maxEnt ) { // cache is filled, unref the oldest-accessed font in it
774         int    bi = 0;
775         double ba = ents[bi].age;
776         for (int i = 1;i < nbEnt;i++) {
777             if ( ents[i].age < ba ) {
778                 bi = i;
779                 ba = ents[bi].age;
780             }
781         }
782         ents[bi].f->Unref();
783         ents[bi]=ents[--nbEnt];
784     }
785     ents[nbEnt].f = who;
786     ents[nbEnt].age = 1.0;
787     nbEnt++;
788 }
789 
790 # ifdef _WIN32
AddFontFilesWin32(char const * directory_path)791 void font_factory::AddFontFilesWin32(char const * directory_path )
792 {
793     std::vector<const char *> allowed_ext = {"ttf","otf" };
794     std::vector<Glib::ustring> files = {};
795     Inkscape::IO::Resource::get_filenames_from_path(files,directory_path, allowed_ext,(std::vector<const char *>) {});
796     for (auto file : files) {
797         int result = AddFontResourceExA(file.c_str(),FR_PRIVATE,0);
798         if (result != 0 ) {
799             g_info("Font File: %s added sucessfully.",file.c_str());
800         } else {
801             g_warning("Font File: %s wasn't added sucessfully",file.c_str());
802         }
803     }
804 }
805 # endif
806 
AddFontsDir(char const * utf8dir)807 void font_factory::AddFontsDir(char const *utf8dir)
808 {
809 #ifdef USE_PANGO_WIN32
810     g_info("Adding additional font directories only supported for fontconfig backend.");
811 #else
812     if (!Inkscape::IO::file_test(utf8dir, G_FILE_TEST_IS_DIR)) {
813         g_warning("Fonts dir '%s' does not exist and will be ignored.", utf8dir);
814         return;
815     }
816 
817     gchar *dir;
818 # ifdef _WIN32
819     AddFontFilesWin32(utf8dir);
820     dir = g_win32_locale_filename_from_utf8(utf8dir);
821 # else
822     dir = g_filename_from_utf8(utf8dir, -1, nullptr, nullptr, nullptr);
823 # endif
824 
825     FcConfig *conf = nullptr;
826 # if PANGO_VERSION_CHECK(1,38,0)
827     conf = pango_fc_font_map_get_config(PANGO_FC_FONT_MAP(fontServer));
828 # endif
829     FcBool res = FcConfigAppFontAddDir(conf, (FcChar8 const *)dir);
830     if (res == FcTrue) {
831         g_info("Fonts dir '%s' added successfully.", utf8dir);
832 # if PANGO_VERSION_CHECK(1,38,0)
833         pango_fc_font_map_config_changed(PANGO_FC_FONT_MAP(fontServer));
834 # endif
835     } else {
836         g_warning("Could not add fonts dir '%s'.", utf8dir);
837     }
838 
839     g_free(dir);
840 #endif
841 }
842 
AddFontFile(char const * utf8file)843 void font_factory::AddFontFile(char const *utf8file)
844 {
845 #ifdef USE_PANGO_WIN32
846     g_info("Adding additional font only supported for fontconfig backend.");
847 #else
848     if (!Inkscape::IO::file_test(utf8file, G_FILE_TEST_IS_REGULAR)) {
849         g_warning("Font file '%s' does not exist and will be ignored.", utf8file);
850         return;
851     }
852 
853     gchar *file;
854 # ifdef _WIN32
855     file = g_win32_locale_filename_from_utf8(utf8file);
856 # else
857     file = g_filename_from_utf8(utf8file, -1, nullptr, nullptr, nullptr);
858 # endif
859 
860     FcConfig *conf = nullptr;
861 # if PANGO_VERSION_CHECK(1,38,0)
862     conf = pango_fc_font_map_get_config(PANGO_FC_FONT_MAP(fontServer));
863 # endif
864     FcBool res = FcConfigAppFontAddFile(conf, (FcChar8 const *)file);
865     if (res == FcTrue) {
866         g_info("Font file '%s' added successfully.", utf8file);
867 # if PANGO_VERSION_CHECK(1,38,0)
868         pango_fc_font_map_config_changed(PANGO_FC_FONT_MAP(fontServer));
869 # endif
870     } else {
871         g_warning("Could not add font file '%s'.", utf8file);
872     }
873 
874     g_free(file);
875 #endif
876 }
877 
878 /*
879   Local Variables:
880   mode:c++
881   c-file-style:"stroustrup"
882   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
883   indent-tabs-mode:nil
884   fill-column:99
885   End:
886 */
887 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
888