xref: /core/unotools/source/config/fontcfg.cxx (revision d02fde021cfca40783cb5c7e87a8ed686fec89c1)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <i18nlangtag/mslangid.hxx>
21 #include <i18nlangtag/languagetag.hxx>
22 #include <o3tl/any.hxx>
23 #include <unotools/configmgr.hxx>
24 #include <unotools/fontcfg.hxx>
25 #include <unotools/fontdefs.hxx>
26 #include <comphelper/configuration.hxx>
27 #include <comphelper/processfactory.hxx>
28 #include <comphelper/propertysequence.hxx>
29 #include <com/sun/star/uno/Any.hxx>
30 #include <com/sun/star/uno/Sequence.hxx>
31 #include <com/sun/star/configuration/theDefaultProvider.hpp>
32 #include <com/sun/star/container/XNameAccess.hpp>
33 #include <unotools/syslocale.hxx>
34 #include <rtl/ustrbuf.hxx>
35 #include <osl/diagnose.h>
36 #include <sal/log.hxx>
37 
38 #include <string.h>
39 #include <algorithm>
40 
41 using namespace utl;
42 using namespace com::sun::star::uno;
43 using namespace com::sun::star::lang;
44 using namespace com::sun::star::container;
45 using namespace com::sun::star::configuration;
46 
47 /*
48  * DefaultFontConfiguration
49  */
50 
getKeyType(DefaultFontType nKeyType)51 static OUString getKeyType( DefaultFontType nKeyType )
52 {
53     switch( nKeyType )
54     {
55     case DefaultFontType::CJK_DISPLAY: return u"CJK_DISPLAY"_ustr;
56     case DefaultFontType::CJK_HEADING: return u"CJK_HEADING"_ustr;
57     case DefaultFontType::CJK_PRESENTATION: return u"CJK_PRESENTATION"_ustr;
58     case DefaultFontType::CJK_SPREADSHEET: return u"CJK_SPREADSHEET"_ustr;
59     case DefaultFontType::CJK_TEXT: return u"CJK_TEXT"_ustr;
60     case DefaultFontType::CTL_DISPLAY: return u"CTL_DISPLAY"_ustr;
61     case DefaultFontType::CTL_HEADING: return u"CTL_HEADING"_ustr;
62     case DefaultFontType::CTL_PRESENTATION: return u"CTL_PRESENTATION"_ustr;
63     case DefaultFontType::CTL_SPREADSHEET: return u"CTL_SPREADSHEET"_ustr;
64     case DefaultFontType::CTL_TEXT: return u"CTL_TEXT"_ustr;
65     case DefaultFontType::FIXED: return u"FIXED"_ustr;
66     case DefaultFontType::LATIN_DISPLAY: return u"LATIN_DISPLAY"_ustr;
67     case DefaultFontType::LATIN_FIXED: return u"LATIN_FIXED"_ustr;
68     case DefaultFontType::LATIN_HEADING: return u"LATIN_HEADING"_ustr;
69     case DefaultFontType::LATIN_PRESENTATION: return u"LATIN_PRESENTATION"_ustr;
70     case DefaultFontType::LATIN_SPREADSHEET: return u"LATIN_SPREADSHEET"_ustr;
71     case DefaultFontType::LATIN_TEXT: return u"LATIN_TEXT"_ustr;
72     case DefaultFontType::SANS: return u"SANS"_ustr;
73     case DefaultFontType::SANS_UNICODE: return u"SANS_UNICODE"_ustr;
74     case DefaultFontType::SERIF: return u"SERIF"_ustr;
75     case DefaultFontType::SYMBOL: return u"SYMBOL"_ustr;
76     case DefaultFontType::UI_FIXED: return u"UI_FIXED"_ustr;
77     case DefaultFontType::UI_SANS: return u"UI_SANS"_ustr;
78     default:
79         OSL_FAIL( "unmatched type" );
80         return u""_ustr;
81     }
82 }
83 
get()84 DefaultFontConfiguration& DefaultFontConfiguration::get()
85 {
86     static DefaultFontConfiguration theDefaultFontConfiguration;
87     return theDefaultFontConfiguration;
88 }
89 
DefaultFontConfiguration()90 DefaultFontConfiguration::DefaultFontConfiguration()
91 {
92     if (comphelper::IsFuzzing())
93         return;
94     // create configuration hierarchical access name
95     try
96     {
97         // get service provider
98         m_xConfigProvider = theDefaultProvider::get(comphelper::getProcessComponentContext());
99         Sequence<Any> aArgs(comphelper::InitAnyPropertySequence(
100         {
101             {"nodepath", Any(u"/org.openoffice.VCL/DefaultFonts"_ustr)}
102         }));
103         m_xConfigAccess =
104             Reference< XNameAccess >(
105                 m_xConfigProvider->createInstanceWithArguments( u"com.sun.star.configuration.ConfigurationAccess"_ustr,
106                                                                 aArgs ),
107                 UNO_QUERY );
108         if( m_xConfigAccess.is() )
109         {
110             const Sequence< OUString > aLocales = m_xConfigAccess->getElementNames();
111             // fill config hash with empty interfaces
112             for( const OUString& rLocaleString : aLocales )
113             {
114                 // Feed through LanguageTag for casing.
115                 OUString aLoc( LanguageTag( rLocaleString, true).getBcp47( false));
116                 m_aConfig[ aLoc ] = LocaleAccess();
117                 m_aConfig[ aLoc ].aConfigLocaleString = rLocaleString;
118             }
119         }
120     }
121     catch (const Exception&)
122     {
123         // configuration is awry
124         m_xConfigProvider.clear();
125         m_xConfigAccess.clear();
126     }
127     SAL_INFO("unotools.config", "config provider: " << m_xConfigProvider.is()
128             << ", config access: " << m_xConfigAccess.is());
129 }
130 
~DefaultFontConfiguration()131 DefaultFontConfiguration::~DefaultFontConfiguration()
132 {
133     // release all nodes
134     m_aConfig.clear();
135     // release top node
136     m_xConfigAccess.clear();
137     // release config provider
138     m_xConfigProvider.clear();
139 }
140 
tryLocale(const OUString & rBcp47,const OUString & rType) const141 OUString DefaultFontConfiguration::tryLocale( const OUString& rBcp47, const OUString& rType ) const
142 {
143     OUString aRet;
144 
145     std::unordered_map< OUString, LocaleAccess >::const_iterator it = m_aConfig.find( rBcp47 );
146     if( it != m_aConfig.end() )
147     {
148         if( !it->second.xAccess.is() )
149         {
150             try
151             {
152                 Reference< XNameAccess > xNode;
153                 if ( m_xConfigAccess->hasByName( it->second.aConfigLocaleString ) )
154                 {
155                     Any aAny = m_xConfigAccess->getByName( it->second.aConfigLocaleString );
156                     if( aAny >>= xNode )
157                         it->second.xAccess = std::move(xNode);
158                 }
159             }
160             catch (const NoSuchElementException&)
161             {
162             }
163             catch (const WrappedTargetException&)
164             {
165             }
166         }
167         if( it->second.xAccess.is() )
168         {
169             try
170             {
171                 if ( it->second.xAccess->hasByName( rType ) )
172                 {
173                     Any aAny = it->second.xAccess->getByName( rType );
174                     aAny >>= aRet;
175                 }
176             }
177             catch (const NoSuchElementException&)
178             {
179             }
180             catch (const WrappedTargetException&)
181             {
182             }
183         }
184     }
185 
186     return aRet;
187 }
188 
getDefaultFont(const LanguageTag & rLanguageTag,DefaultFontType nType) const189 OUString DefaultFontConfiguration::getDefaultFont( const LanguageTag& rLanguageTag, DefaultFontType nType ) const
190 {
191     OUString aType = getKeyType( nType );
192     // Try the simple cases first without constructing fallbacks.
193     OUString aRet = tryLocale( rLanguageTag.getBcp47(), aType );
194     if (aRet.isEmpty())
195     {
196         if (rLanguageTag.isIsoLocale())
197         {
198             if (!rLanguageTag.getCountry().isEmpty())
199             {
200                 aRet = tryLocale( rLanguageTag.getLanguage(), aType );
201             }
202         }
203         else
204         {
205             ::std::vector< OUString > aFallbacks( rLanguageTag.getFallbackStrings( false));
206             for (const auto& rFallback : aFallbacks)
207             {
208                 aRet = tryLocale( rFallback, aType );
209                 if (!aRet.isEmpty())
210                     break;
211             }
212         }
213     }
214     if( aRet.isEmpty() )
215     {
216         aRet = tryLocale( u"en"_ustr, aType );
217     }
218     return aRet;
219 }
220 
getUserInterfaceFont(const LanguageTag & rLanguageTag) const221 OUString DefaultFontConfiguration::getUserInterfaceFont( const LanguageTag& rLanguageTag ) const
222 {
223     LanguageTag aLanguageTag( rLanguageTag);
224     if( aLanguageTag.isSystemLocale() )
225         aLanguageTag = SvtSysLocale().GetUILanguageTag();
226 
227     OUString aUIFont = getDefaultFont( aLanguageTag, DefaultFontType::UI_SANS );
228 
229     if( !aUIFont.isEmpty() )
230         return aUIFont;
231 
232     // fallback mechanism (either no configuration or no entry in configuration
233 
234     static constexpr OUStringLiteral FALLBACKFONT_UI_SANS = u"Andale Sans UI;Albany;Albany AMT;Tahoma;Arial Unicode MS;Arial;Nimbus Sans L;Bitstream Vera Sans;gnu-unifont;Interface User;Geneva;WarpSans;Dialog;Swiss;Lucida;Helvetica;Charcoal;Chicago;MS Sans Serif;Helv;Times;Times New Roman;Interface System";
235     static constexpr OUStringLiteral FALLBACKFONT_UI_SANS_LATIN2 = u"Andale Sans UI;Albany;Albany AMT;Tahoma;Arial Unicode MS;Arial;Nimbus Sans L;Luxi Sans;Bitstream Vera Sans;Interface User;Geneva;WarpSans;Dialog;Swiss;Lucida;Helvetica;Charcoal;Chicago;MS Sans Serif;Helv;Times;Times New Roman;Interface System";
236     static constexpr OUStringLiteral FALLBACKFONT_UI_SANS_ARABIC = u"Tahoma;Traditional Arabic;Simplified Arabic;Lucidasans;Lucida Sans;Supplement;Andale Sans UI;clearlyU;Interface User;Arial Unicode MS;Lucida Sans Unicode;WarpSans;Geneva;MS Sans Serif;Helv;Dialog;Albany;Lucida;Helvetica;Charcoal;Chicago;Arial;Helmet;Interface System;Sans Serif";
237     static constexpr OUStringLiteral FALLBACKFONT_UI_SANS_THAI = u"OONaksit;Tahoma;Lucidasans;Arial Unicode MS";
238     static constexpr OUStringLiteral FALLBACKFONT_UI_SANS_KOREAN = u"Noto Sans KR;Noto Sans CJK KR;Noto Serif KR;Noto Serif CJK KR;Source Han Sans KR;NanumGothic;NanumBarunGothic;NanumBarunGothic YetHangul;KoPubWorld Dotum;Malgun Gothic;Apple SD Gothic Neo;Dotum;DotumChe;Gulim;GulimChe;Batang;BatangChe;Apple Gothic;UnDotum;Baekmuk Gulim;Arial Unicode MS;Lucida Sans Unicode;gnu-unifont;Andale Sans UI";
239     static constexpr OUStringLiteral FALLBACKFONT_UI_SANS_JAPANESE = u"Noto Sans CJK JP;Noto Sans JP;Source Han Sans;Source Han Sans JP;Yu Gothic UI;Yu Gothic;YuGothic;Hiragino Sans;Hiragino Kaku Gothic ProN;Hiragino Kaku Gothic Pro;Hiragino Kaku Gothic StdN;Meiryo UI;Meiryo;IPAexGothic;IPAPGothic;IPAGothic;MS UI Gothic;MS PGothic;MS Gothic;Osaka;Unifont;gnu-unifont;Arial Unicode MS;Interface System";
240     static constexpr OUStringLiteral FALLBACKFONT_UI_SANS_CHINSIM = u"Andale Sans UI;Arial Unicode MS;ZYSong18030;AR PL SungtiL GB;AR PL KaitiM GB;SimSun;Lucida Sans Unicode;Fangsong;Hei;Song;Kai;Ming;gnu-unifont;Interface User;";
241     static constexpr OUStringLiteral FALLBACKFONT_UI_SANS_CHINTRD = u"Andale Sans UI;Arial Unicode MS;AR PL Mingti2L Big5;AR PL KaitiM Big5;Kai;PMingLiU;MingLiU;Ming;Lucida Sans Unicode;gnu-unifont;Interface User;";
242 
243     const OUString aLanguage( aLanguageTag.getLanguage());
244 
245     // optimize font list for some locales, as long as Andale Sans UI does not support them
246     if( aLanguage == "ar" || aLanguage == "he" || aLanguage == "iw"  )
247     {
248         return FALLBACKFONT_UI_SANS_ARABIC;
249     }
250     else if ( aLanguage == "th" )
251     {
252         return FALLBACKFONT_UI_SANS_THAI;
253     }
254     else if ( aLanguage == "ko" )
255     {
256         return FALLBACKFONT_UI_SANS_KOREAN;
257     }
258     else if ( aLanguage == "ja" )
259     {
260         return FALLBACKFONT_UI_SANS_JAPANESE;
261     }
262     else if( aLanguage == "cs" ||
263              aLanguage == "hu" ||
264              aLanguage == "pl" ||
265              aLanguage == "ro" ||
266              aLanguage == "rm" ||
267              aLanguage == "hr" ||
268              aLanguage == "sk" ||
269              aLanguage == "sl" ||
270              aLanguage == "sb")
271     {
272         return FALLBACKFONT_UI_SANS_LATIN2;
273     }
274     else
275     {
276         const Locale& aLocale( aLanguageTag.getLocale());
277         if (MsLangId::isTraditionalChinese(aLocale))
278             return FALLBACKFONT_UI_SANS_CHINTRD;
279         else if (MsLangId::isSimplifiedChinese(aLocale))
280             return FALLBACKFONT_UI_SANS_CHINSIM;
281     }
282 
283     return FALLBACKFONT_UI_SANS;
284 }
285 
286 /*
287  *  FontSubstConfigItem::get
288  */
289 
get()290 FontSubstConfiguration& FontSubstConfiguration::get()
291 {
292     static FontSubstConfiguration theFontSubstConfiguration;
293     return theFontSubstConfiguration;
294 }
295 
296 /*
297  *  FontSubstConfigItem::FontSubstConfigItem
298  */
299 
FontSubstConfiguration()300 FontSubstConfiguration::FontSubstConfiguration() :
301     maSubstHash( 300 ),
302     maLanguageTag(u"en"_ustr)
303 {
304     if (comphelper::IsFuzzing())
305         return;
306     try
307     {
308         // get service provider
309         const Reference< XComponentContext >& xContext( comphelper::getProcessComponentContext() );
310         // create configuration hierarchical access name
311         m_xConfigProvider = theDefaultProvider::get( xContext );
312         Sequence<Any> aArgs(comphelper::InitAnyPropertySequence(
313         {
314             {"nodepath", Any(u"/org.openoffice.VCL/FontSubstitutions"_ustr)}
315         }));
316         m_xConfigAccess =
317             Reference< XNameAccess >(
318                 m_xConfigProvider->createInstanceWithArguments( u"com.sun.star.configuration.ConfigurationAccess"_ustr,
319                                                                 aArgs ),
320                 UNO_QUERY );
321         if( m_xConfigAccess.is() )
322         {
323             const Sequence< OUString > aLocales = m_xConfigAccess->getElementNames();
324             // fill config hash with empty interfaces
325             for( const OUString& rLocaleString : aLocales )
326             {
327                 // Feed through LanguageTag for casing.
328                 OUString aLoc( LanguageTag( rLocaleString, true).getBcp47( false));
329                 m_aSubst[ aLoc ] = LocaleSubst();
330                 m_aSubst[ aLoc ].aConfigLocaleString = rLocaleString;
331             }
332         }
333     }
334     catch (const Exception&)
335     {
336         // configuration is awry
337         m_xConfigProvider.clear();
338         m_xConfigAccess.clear();
339     }
340     SAL_INFO("unotools.config", "config provider: " << m_xConfigProvider.is()
341             << ", config access: " << m_xConfigAccess.is());
342 
343     if( maLanguageTag.isSystemLocale() )
344         maLanguageTag = SvtSysLocale().GetUILanguageTag();
345 }
346 
347 /*
348  *  FontSubstConfigItem::~FontSubstConfigItem
349  */
350 
~FontSubstConfiguration()351 FontSubstConfiguration::~FontSubstConfiguration()
352 {
353     // release config access
354     m_xConfigAccess.clear();
355     // release config provider
356     m_xConfigProvider.clear();
357 }
358 
359 /*
360  *  FontSubstConfigItem::getMapName
361  */
362 
363 const char* const aImplKillLeadingList[] =
364 {
365     "microsoft",
366     "monotype",
367     "linotype",
368     "baekmuk",
369     "adobe",
370     "nimbus",
371     "zycjk",
372     "itc",
373     "sun",
374     "amt",
375     "ms",
376     "mt",
377     "cg",
378     "hg",
379     "fz",
380     "ipa",
381     "sazanami",
382     "kochi",
383     nullptr
384 };
385 
386 const char* const aImplKillTrailingList[] =
387 {
388     "microsoft",
389     "monotype",
390     "linotype",
391     "adobe",
392     "nimbus",
393     "itc",
394     "sun",
395     "amt",
396     "ms",
397     "mt",
398     "clm",
399     // Scripts, for compatibility with older versions
400     "we",
401     "cyr",
402     "tur",
403     "wt",
404     "greek",
405     "wl",
406     // CJK extensions
407     "gb",
408     "big5",
409     "pro",
410     "z01",
411     "z02",
412     "z03",
413     "z13",
414     "b01",
415     "w3x12",
416     // Old Printer Fontnames
417     "5cpi",
418     "6cpi",
419     "7cpi",
420     "8cpi",
421     "9cpi",
422     "10cpi",
423     "11cpi",
424     "12cpi",
425     "13cpi",
426     "14cpi",
427     "15cpi",
428     "16cpi",
429     "18cpi",
430     "24cpi",
431     "scale",
432     "pc",
433     nullptr
434 };
435 
436 const char* const aImplKillTrailingWithExceptionsList[] =
437 {
438     "ce", "monospace", "oldface", nullptr,
439     "ps", "caps", nullptr,
440     nullptr
441 };
442 
443 namespace {
444 
445 struct ImplFontAttrWeightSearchData
446 {
447     const char*             mpStr;
448     FontWeight              meWeight;
449 };
450 
451 }
452 
453 ImplFontAttrWeightSearchData const aImplWeightAttrSearchList[] =
454 {
455 // the attribute names are ordered by "first match wins"
456 // e.g. "semilight" should wins over "semi"
457 {   "extrablack",           WEIGHT_BLACK },
458 {   "ultrablack",           WEIGHT_BLACK },
459 {   "ultrabold",            WEIGHT_ULTRABOLD },
460 {   "semibold",             WEIGHT_SEMIBOLD },
461 {   "semilight",            WEIGHT_SEMILIGHT },
462 {   "semi",                 WEIGHT_SEMIBOLD },
463 {   "demi",                 WEIGHT_SEMIBOLD },
464 {   "black",                WEIGHT_BLACK },
465 {   "bold",                 WEIGHT_BOLD },
466 {   "heavy",                WEIGHT_BLACK },
467 {   "ultralight",           WEIGHT_ULTRALIGHT },
468 {   "light",                WEIGHT_LIGHT },
469 {   "medium",               WEIGHT_MEDIUM },
470 {   nullptr,                   WEIGHT_DONTKNOW },
471 };
472 
473 namespace {
474 
475 struct ImplFontAttrWidthSearchData
476 {
477     const char*             mpStr;
478     FontWidth               meWidth;
479 };
480 
481 }
482 
483 ImplFontAttrWidthSearchData const aImplWidthAttrSearchList[] =
484 {
485 {   "narrow",               WIDTH_CONDENSED },
486 {   "semicondensed",        WIDTH_SEMI_CONDENSED },
487 {   "ultracondensed",       WIDTH_ULTRA_CONDENSED },
488 {   "semiexpanded",         WIDTH_SEMI_EXPANDED },
489 {   "ultraexpanded",        WIDTH_ULTRA_EXPANDED },
490 {   "expanded",             WIDTH_EXPANDED },
491 {   "wide",                 WIDTH_ULTRA_EXPANDED },
492 {   "condensed",            WIDTH_CONDENSED },
493 {   "cond",                 WIDTH_CONDENSED },
494 {   "cn",                   WIDTH_CONDENSED },
495 {   nullptr,                   WIDTH_DONTKNOW },
496 };
497 
498 namespace {
499 
500 struct ImplFontAttrTypeSearchData
501 {
502     const char*             mpStr;
503     ImplFontAttrs           mnType;
504 };
505 
506 }
507 
508 ImplFontAttrTypeSearchData const aImplTypeAttrSearchList[] =
509 {
510 {   "monotype",             ImplFontAttrs::None },
511 {   "linotype",             ImplFontAttrs::None },
512 {   "titling",              ImplFontAttrs::Titling },
513 {   "capitals",             ImplFontAttrs::Capitals },
514 {   "capital",              ImplFontAttrs::Capitals },
515 {   "caps",                 ImplFontAttrs::Capitals },
516 {   "italic",               ImplFontAttrs::Italic },
517 {   "oblique",              ImplFontAttrs::Italic },
518 {   "rounded",              ImplFontAttrs::Rounded },
519 {   "outline",              ImplFontAttrs::Outline },
520 {   "shadow",               ImplFontAttrs::Shadow },
521 {   "handwriting",          ImplFontAttrs::Handwriting | ImplFontAttrs::Script },
522 {   "hand",                 ImplFontAttrs::Handwriting | ImplFontAttrs::Script },
523 {   "signet",               ImplFontAttrs::Handwriting | ImplFontAttrs::Script },
524 {   "script",               ImplFontAttrs::BrushScript | ImplFontAttrs::Script },
525 {   "calligraphy",          ImplFontAttrs::Chancery | ImplFontAttrs::Script },
526 {   "chancery",             ImplFontAttrs::Chancery | ImplFontAttrs::Script },
527 {   "corsiva",              ImplFontAttrs::Chancery | ImplFontAttrs::Script },
528 {   "gothic",               ImplFontAttrs::SansSerif | ImplFontAttrs::Gothic },
529 {   "schoolbook",           ImplFontAttrs::Serif | ImplFontAttrs::Schoolbook },
530 {   "schlbk",               ImplFontAttrs::Serif | ImplFontAttrs::Schoolbook },
531 {   "typewriter",           ImplFontAttrs::Typewriter | ImplFontAttrs::Fixed },
532 {   "lineprinter",          ImplFontAttrs::Typewriter | ImplFontAttrs::Fixed },
533 {   "monospaced",           ImplFontAttrs::Fixed },
534 {   "monospace",            ImplFontAttrs::Fixed },
535 {   "mono",                 ImplFontAttrs::Fixed },
536 {   "fixed",                ImplFontAttrs::Fixed },
537 {   "sansserif",            ImplFontAttrs::SansSerif },
538 {   "sans",                 ImplFontAttrs::SansSerif },
539 {   "swiss",                ImplFontAttrs::SansSerif },
540 {   "serif",                ImplFontAttrs::Serif },
541 {   "bright",               ImplFontAttrs::Serif },
542 {   "symbols",              ImplFontAttrs::Symbol },
543 {   "symbol",               ImplFontAttrs::Symbol },
544 {   "dingbats",             ImplFontAttrs::Symbol },
545 {   "dings",                ImplFontAttrs::Symbol },
546 {   "ding",                 ImplFontAttrs::Symbol },
547 {   "bats",                 ImplFontAttrs::Symbol },
548 {   "math",                 ImplFontAttrs::Symbol },
549 {   "oldstyle",             ImplFontAttrs::OtherStyle },
550 {   "oldface",              ImplFontAttrs::OtherStyle },
551 {   "old",                  ImplFontAttrs::OtherStyle },
552 {   "new",                  ImplFontAttrs::None },
553 {   "modern",               ImplFontAttrs::None },
554 {   "lucida",               ImplFontAttrs::None },
555 {   "regular",              ImplFontAttrs::None },
556 {   "extended",             ImplFontAttrs::None },
557 {   "extra",                ImplFontAttrs::OtherStyle },
558 {   "ext",                  ImplFontAttrs::None },
559 {   "scalable",             ImplFontAttrs::None },
560 {   "scale",                ImplFontAttrs::None },
561 {   "nimbus",               ImplFontAttrs::None },
562 {   "adobe",                ImplFontAttrs::None },
563 {   "itc",                  ImplFontAttrs::None },
564 {   "amt",                  ImplFontAttrs::None },
565 {   "mt",                   ImplFontAttrs::None },
566 {   "ms",                   ImplFontAttrs::None },
567 {   "cpi",                  ImplFontAttrs::None },
568 {   "no",                   ImplFontAttrs::None },
569 {   nullptr,                   ImplFontAttrs::None },
570 };
571 
ImplKillLeading(OUString & rName,const char * const * ppStr)572 static bool ImplKillLeading( OUString& rName, const char* const* ppStr )
573 {
574     for(; *ppStr; ++ppStr )
575     {
576         const char*         pStr = *ppStr;
577         const sal_Unicode*  pNameStr = rName.getStr();
578         while ( (*pNameStr == static_cast<sal_Unicode>(static_cast<unsigned char>(*pStr))) && *pStr )
579         {
580             pNameStr++;
581             pStr++;
582         }
583         if ( !*pStr )
584         {
585             sal_Int32 nLen = static_cast<sal_Int32>(pNameStr - rName.getStr());
586             rName = rName.copy(nLen);
587             return true;
588         }
589     }
590 
591     // special case for Baekmuk
592     // TODO: allow non-ASCII KillLeading list
593     const sal_Unicode* pNameStr = rName.getStr();
594     if( (pNameStr[0]==0xBC31) && (pNameStr[1]==0xBC35) )
595     {
596         sal_Int32 nLen = (pNameStr[2]==0x0020) ? 3 : 2;
597         rName = rName.copy(nLen);
598         return true;
599     }
600 
601     return false;
602 }
603 
ImplIsTrailing(std::u16string_view rName,const char * pStr)604 static sal_Int32 ImplIsTrailing( std::u16string_view rName, const char* pStr )
605 {
606     size_t nStrLen = strlen( pStr );
607     if( nStrLen >= rName.size() )
608         return 0;
609 
610     const sal_Unicode* pEndName = rName.data() + rName.size();
611     const sal_Unicode* pNameStr = pEndName - nStrLen;
612     do if( *(pNameStr++) != *(pStr++) )
613         return 0;
614     while( *pStr );
615 
616     return nStrLen;
617 }
618 
ImplKillTrailing(OUString & rName,const char * const * ppStr)619 static bool ImplKillTrailing( OUString& rName, const char* const* ppStr )
620 {
621     for(; *ppStr; ++ppStr )
622     {
623         sal_Int32 nTrailLen = ImplIsTrailing( rName, *ppStr );
624         if( nTrailLen )
625         {
626             rName = rName.copy(0, rName.getLength() - nTrailLen );
627             return true;
628         }
629     }
630 
631     return false;
632 }
633 
ImplKillTrailingWithExceptions(OUString & rName,const char * const * ppStr)634 static bool ImplKillTrailingWithExceptions( OUString& rName, const char* const* ppStr )
635 {
636     for(; *ppStr; ++ppStr )
637     {
638         sal_Int32 nTrailLen = ImplIsTrailing( rName, *ppStr );
639         if( nTrailLen )
640         {
641             // check string match against string exceptions
642             while( *++ppStr )
643                 if( ImplIsTrailing( rName, *ppStr ) )
644                     return false;
645 
646             rName = rName.copy(0, rName.getLength() - nTrailLen );
647             return true;
648         }
649         else
650         {
651             // skip exception strings
652             while( *++ppStr ) {}
653         }
654     }
655 
656     return false;
657 }
658 
ImplFindAndErase(OUString & rName,const char * pStr)659 static bool ImplFindAndErase( OUString& rName, const char* pStr )
660 {
661     sal_Int32 nLen = static_cast<sal_Int32>(strlen(pStr));
662     sal_Int32 nPos = rName.indexOfAsciiL(pStr, nLen );
663     if ( nPos < 0 )
664         return false;
665 
666     OUStringBuffer sBuff(rName);
667     sBuff.remove(nPos, nLen);
668     rName = sBuff.makeStringAndClear();
669     return true;
670 }
671 
getMapName(const OUString & rOrgName,OUString & rShortName,OUString & rFamilyName,FontWeight & rWeight,FontWidth & rWidth,ImplFontAttrs & rType)672 void FontSubstConfiguration::getMapName( const OUString& rOrgName, OUString& rShortName,
673                                          OUString& rFamilyName, FontWeight& rWeight,
674                                          FontWidth& rWidth, ImplFontAttrs& rType )
675 {
676     rShortName = rOrgName;
677 
678     // TODO: get rid of the crazy O(N*strlen) searches below
679     // they should be possible in O(strlen)
680 
681     // Kill leading vendor names and other unimportant data
682     ImplKillLeading( rShortName, aImplKillLeadingList );
683 
684     // Kill trailing vendor names and other unimportant data
685     ImplKillTrailing( rShortName, aImplKillTrailingList );
686     ImplKillTrailingWithExceptions( rShortName, aImplKillTrailingWithExceptionsList );
687 
688     rFamilyName = rShortName;
689 
690     // Kill attributes from the name and update the data
691     // Weight
692     const ImplFontAttrWeightSearchData* pWeightList = aImplWeightAttrSearchList;
693     while ( pWeightList->mpStr )
694     {
695         if ( ImplFindAndErase( rFamilyName, pWeightList->mpStr ) )
696         {
697             if ( (rWeight == WEIGHT_DONTKNOW) || (rWeight == WEIGHT_NORMAL) )
698                 rWeight = pWeightList->meWeight;
699             break;
700         }
701         pWeightList++;
702     }
703 
704     // Width
705     const ImplFontAttrWidthSearchData* pWidthList = aImplWidthAttrSearchList;
706     while ( pWidthList->mpStr )
707     {
708         if ( ImplFindAndErase( rFamilyName, pWidthList->mpStr ) )
709         {
710             if ( (rWidth == WIDTH_DONTKNOW) || (rWidth == WIDTH_NORMAL) )
711                 rWidth = pWidthList->meWidth;
712             break;
713         }
714         pWidthList++;
715     }
716 
717     // Type
718     rType = ImplFontAttrs::None;
719     const ImplFontAttrTypeSearchData* pTypeList = aImplTypeAttrSearchList;
720     while ( pTypeList->mpStr )
721     {
722         if ( ImplFindAndErase( rFamilyName, pTypeList->mpStr ) )
723             rType |= pTypeList->mnType;
724         pTypeList++;
725     }
726 
727     // Remove numbers
728     // TODO: also remove localized and fullwidth digits
729     sal_Int32 i = 0;
730     OUStringBuffer sBuff(rFamilyName);
731     while ( i < sBuff.getLength() )
732     {
733         sal_Unicode c = sBuff[ i ];
734         if ( (c >= 0x0030) && (c <= 0x0039) )
735             sBuff.remove(i, 1);
736         else
737             i++;
738     }
739 }
740 
741 namespace {
742 
743 struct StrictStringSort
744 {
operator ()__anonbf4f69d40411::StrictStringSort745     bool operator()( const FontNameAttr& rLeft, const FontNameAttr& rRight )
746     { return rLeft.Name.compareTo( rRight.Name ) < 0; }
747 };
748 
749 }
750 
751 // The entries in this table must match the bits in the ImplFontAttrs enum.
752 
753 const char* const pAttribNames[] =
754 {
755     "default",
756     "standard",
757     "normal",
758     "symbol",
759     "fixed",
760     "sansserif",
761     "serif",
762     "decorative",
763     "special",
764     "italic",
765     "title",
766     "capitals",
767     "cjk",
768     "cjk_jp",
769     "cjk_sc",
770     "cjk_tc",
771     "cjk_kr",
772     "ctl",
773     "nonelatin",
774     "full",
775     "outline",
776     "shadow",
777     "rounded",
778     "typewriter",
779     "script",
780     "handwriting",
781     "chancery",
782     "comic",
783     "brushscript",
784     "gothic",
785     "schoolbook",
786     "other"
787 };
788 
789 namespace {
790 
791 struct enum_convert
792 {
793     const char* pName;
794     int          nEnum;
795 };
796 
797 }
798 
799 const enum_convert pWeightNames[] =
800 {
801     { "normal", WEIGHT_NORMAL },
802     { "medium", WEIGHT_MEDIUM },
803     { "bold", WEIGHT_BOLD },
804     { "black", WEIGHT_BLACK },
805     { "semibold", WEIGHT_SEMIBOLD },
806     { "light", WEIGHT_LIGHT },
807     { "semilight", WEIGHT_SEMILIGHT },
808     { "ultrabold", WEIGHT_ULTRABOLD },
809     { "semi", WEIGHT_SEMIBOLD },
810     { "demi", WEIGHT_SEMIBOLD },
811     { "heavy", WEIGHT_BLACK },
812     { "unknown", WEIGHT_DONTKNOW },
813     { "thin", WEIGHT_THIN },
814     { "ultralight", WEIGHT_ULTRALIGHT }
815 };
816 
817 const enum_convert pWidthNames[] =
818 {
819     { "normal", WIDTH_NORMAL },
820     { "condensed", WIDTH_CONDENSED },
821     { "expanded", WIDTH_EXPANDED },
822     { "unknown", WIDTH_DONTKNOW },
823     { "ultracondensed", WIDTH_ULTRA_CONDENSED },
824     { "extracondensed", WIDTH_EXTRA_CONDENSED },
825     { "semicondensed", WIDTH_SEMI_CONDENSED },
826     { "semiexpanded", WIDTH_SEMI_EXPANDED },
827     { "extraexpanded", WIDTH_EXTRA_EXPANDED },
828     { "ultraexpanded", WIDTH_ULTRA_EXPANDED }
829 };
830 
fillSubstVector(const css::uno::Reference<XNameAccess> & rFont,const OUString & rType,std::vector<OUString> & rSubstVector) const831 void FontSubstConfiguration::fillSubstVector( const css::uno::Reference< XNameAccess >& rFont,
832                                               const OUString& rType,
833                                               std::vector< OUString >& rSubstVector ) const
834 {
835     try
836     {
837         Any aAny = rFont->getByName( rType );
838         if( auto pLine = o3tl::tryAccess<OUString>(aAny) )
839         {
840             sal_Int32 nLength = pLine->getLength();
841             if( nLength )
842             {
843                 const sal_Unicode* pStr = pLine->getStr();
844                 sal_Int32 nTokens = 0;
845                 // count tokens
846                 while( nLength-- )
847                 {
848                     if( *pStr++ == ';' )
849                         nTokens++;
850                 }
851                 rSubstVector.clear();
852                 // optimize performance, heap fragmentation
853                 rSubstVector.reserve( nTokens );
854                 sal_Int32 nIndex = 0;
855                 while( nIndex != -1 )
856                 {
857                     OUString aSubst( pLine->getToken( 0, ';', nIndex ) );
858                     if( !aSubst.isEmpty() )
859                     {
860                         auto itPair = maSubstHash.insert( aSubst );
861                         if (!itPair.second)
862                             aSubst = *itPair.first;
863                         rSubstVector.push_back( aSubst );
864                     }
865                 }
866             }
867         }
868     }
869     catch (const NoSuchElementException&)
870     {
871     }
872     catch (const WrappedTargetException&)
873     {
874     }
875 }
876 
877 // static
getSubstWeight(const css::uno::Reference<XNameAccess> & rFont,const OUString & rType)878 FontWeight FontSubstConfiguration::getSubstWeight( const css::uno::Reference< XNameAccess >& rFont,
879                                                    const OUString& rType )
880 {
881     int weight = -1;
882     try
883     {
884         Any aAny = rFont->getByName( rType );
885         if( auto pLine = o3tl::tryAccess<OUString>(aAny) )
886         {
887             if( !pLine->isEmpty() )
888             {
889                 for( weight=std::size(pWeightNames)-1; weight >= 0; weight-- )
890                     if( pLine->equalsIgnoreAsciiCaseAscii( pWeightNames[weight].pName ) )
891                         break;
892             }
893             SAL_WARN_IF(weight < 0, "unotools.config", "Error: invalid weight " << *pLine);
894         }
895     }
896     catch (const NoSuchElementException&)
897     {
898     }
899     catch (const WrappedTargetException&)
900     {
901     }
902     return static_cast<FontWeight>( weight >= 0 ? pWeightNames[weight].nEnum : WEIGHT_DONTKNOW );
903 }
904 
905 // static
getSubstWidth(const css::uno::Reference<XNameAccess> & rFont,const OUString & rType)906 FontWidth FontSubstConfiguration::getSubstWidth( const css::uno::Reference< XNameAccess >& rFont,
907                                                  const OUString& rType )
908 {
909     int width = -1;
910     try
911     {
912         Any aAny = rFont->getByName( rType );
913         if( auto pLine = o3tl::tryAccess<OUString>(aAny) )
914         {
915             if( !pLine->isEmpty() )
916             {
917                 for( width=std::size(pWidthNames)-1; width >= 0; width-- )
918                     if( pLine->equalsIgnoreAsciiCaseAscii( pWidthNames[width].pName ) )
919                         break;
920             }
921             SAL_WARN_IF( width < 0, "unotools.config", "Error: invalid width " << *pLine);
922         }
923     }
924     catch (const NoSuchElementException&)
925     {
926     }
927     catch (const WrappedTargetException&)
928     {
929     }
930     return static_cast<FontWidth>( width >= 0 ? pWidthNames[width].nEnum : WIDTH_DONTKNOW );
931 }
932 
933 // static
getSubstType(const css::uno::Reference<XNameAccess> & rFont,const OUString & rType)934 ImplFontAttrs FontSubstConfiguration::getSubstType( const css::uno::Reference< XNameAccess >& rFont,
935                                                     const OUString& rType )
936 {
937     sal_uInt32 type = 0;
938     try
939     {
940         Any aAny = rFont->getByName( rType );
941         auto pLine = o3tl::tryAccess<OUString>(aAny);
942         if( !pLine )
943             return ImplFontAttrs::None;
944         if( pLine->isEmpty() )
945             return ImplFontAttrs::None;
946         sal_Int32 nIndex = 0;
947         while( nIndex != -1 )
948         {
949             OUString aToken( pLine->getToken( 0, ',', nIndex ) );
950             for( int k = 0; k < 32; k++ )
951                 if( aToken.equalsIgnoreAsciiCaseAscii( pAttribNames[k] ) )
952                 {
953                     type |= sal_uInt32(1) << k;
954                     break;
955                 }
956         }
957         assert(((type & ~o3tl::typed_flags<ImplFontAttrs>::mask) == 0) && "invalid font attributes");
958     }
959     catch (const NoSuchElementException&)
960     {
961     }
962     catch (const WrappedTargetException&)
963     {
964     }
965 
966     return static_cast<ImplFontAttrs>(type);
967 }
968 
readLocaleSubst(const OUString & rBcp47) const969 void FontSubstConfiguration::readLocaleSubst( const OUString& rBcp47 ) const
970 {
971     std::unordered_map< OUString, LocaleSubst >::const_iterator it = m_aSubst.find( rBcp47 );
972     if( it == m_aSubst.end() )
973         return;
974 
975     if(  it->second.bConfigRead )
976         return;
977 
978     it->second.bConfigRead = true;
979     Reference< XNameAccess > xNode;
980     try
981     {
982         Any aAny = m_xConfigAccess->getByName( it->second.aConfigLocaleString );
983         aAny >>= xNode;
984     }
985     catch (const NoSuchElementException&)
986     {
987     }
988     catch (const WrappedTargetException&)
989     {
990     }
991     if( !xNode.is() )
992         return;
993 
994     const Sequence< OUString > aFonts = xNode->getElementNames();
995     int nFonts = aFonts.getLength();
996     // improve performance, heap fragmentation
997     it->second.aSubstAttributes.reserve( nFonts );
998 
999     // strings for subst retrieval, construct only once
1000     static constexpr OUStringLiteral aSubstFontsStr  ( u"SubstFonts" );
1001     static constexpr OUStringLiteral aSubstFontsMSStr( u"SubstFontsMS" );
1002     static constexpr OUStringLiteral aSubstWeightStr ( u"FontWeight" );
1003     static constexpr OUStringLiteral aSubstWidthStr  ( u"FontWidth" );
1004     static constexpr OUStringLiteral aSubstTypeStr   ( u"FontType" );
1005     for( const OUString& rFontName : aFonts )
1006     {
1007         Reference< XNameAccess > xFont;
1008         try
1009         {
1010             Any aAny = xNode->getByName( rFontName );
1011             aAny >>= xFont;
1012         }
1013         catch (const NoSuchElementException&)
1014         {
1015         }
1016         catch (const WrappedTargetException&)
1017         {
1018         }
1019         if( ! xFont.is() )
1020         {
1021             SAL_WARN("unotools.config", "did not get font attributes for " << rFontName);
1022             continue;
1023         }
1024 
1025         FontNameAttr aAttr;
1026         // read subst attributes from config
1027         aAttr.Name = rFontName;
1028         fillSubstVector( xFont, aSubstFontsStr, aAttr.Substitutions );
1029         fillSubstVector( xFont, aSubstFontsMSStr, aAttr.MSSubstitutions );
1030         aAttr.Weight = getSubstWeight( xFont, aSubstWeightStr );
1031         aAttr.Width = getSubstWidth( xFont, aSubstWidthStr );
1032         aAttr.Type = getSubstType( xFont, aSubstTypeStr );
1033 
1034         // finally insert this entry
1035         it->second.aSubstAttributes.push_back(std::move(aAttr));
1036     }
1037     std::sort( it->second.aSubstAttributes.begin(), it->second.aSubstAttributes.end(), StrictStringSort() );
1038 }
1039 
getSubstInfo(const OUString & rFontName) const1040 const FontNameAttr* FontSubstConfiguration::getSubstInfo( const OUString& rFontName ) const
1041 {
1042     if( rFontName.isEmpty() )
1043         return nullptr;
1044 
1045     // search if a  (language dep.) replacement table for the given font exists
1046     // fallback is english
1047     OUString aSearchFont( rFontName.toAsciiLowerCase() );
1048     FontNameAttr aSearchAttr;
1049     aSearchAttr.Name = aSearchFont;
1050 
1051     ::std::vector< OUString > aFallbacks( maLanguageTag.getFallbackStrings( true));
1052     if (maLanguageTag.getLanguage() != "en")
1053         aFallbacks.emplace_back("en");
1054 
1055     for (const auto& rFallback : aFallbacks)
1056     {
1057         std::unordered_map< OUString, LocaleSubst >::const_iterator lang = m_aSubst.find( rFallback );
1058         if( lang != m_aSubst.end() )
1059         {
1060             if( ! lang->second.bConfigRead )
1061                 readLocaleSubst( rFallback );
1062             // try to find an exact match
1063             // because the list is sorted this will also find fontnames of the form searchfontname*
1064             std::vector< FontNameAttr >::const_iterator it = ::std::lower_bound( lang->second.aSubstAttributes.begin(), lang->second.aSubstAttributes.end(), aSearchAttr, StrictStringSort() );
1065             if( it != lang->second.aSubstAttributes.end())
1066             {
1067                 const FontNameAttr& rFoundAttr = *it;
1068                 // a search for "abcblack" may match with an entry for "abc"
1069                 // the reverse is not a good idea (e.g. #i112731# alba->albani)
1070                 if( rFoundAttr.Name.getLength() <= aSearchFont.getLength() )
1071                     if( aSearchFont.startsWith( rFoundAttr.Name))
1072                         return &rFoundAttr;
1073             }
1074         }
1075     }
1076     return nullptr;
1077 }
1078 
1079 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1080