]> git.lyx.org Git - lyx.git/blob - src/frontends/qt/GuiFontLoader.cpp
Merge branch 'killqt4'
[lyx.git] / src / frontends / qt / GuiFontLoader.cpp
1 /**
2  * \file FontLoader.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Asger Alstrup
7  * \author John Levon
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "GuiFontLoader.h"
15
16 #include "FontLoader.h"
17 #include "Font.h"
18 #include "GuiFontMetrics.h"
19 #include "qt_helpers.h"
20
21 #include "LyXRC.h"
22
23 #include "support/debug.h"
24 #include "support/filetools.h"
25 #include "support/gettext.h"
26 #include "support/lassert.h"
27 #include "support/lstrings.h"
28 #include "support/Systemcall.h"
29 #include "support/Package.h"
30 #include "support/os.h"
31
32 #include "GuiApplication.h"
33
34 #include <QFontInfo>
35 #include <QFontDatabase>
36
37 using namespace std;
38 using namespace lyx::support;
39
40 QString const math_fonts[] = {"cmex10", "cmmi10", "cmr10", "cmsy10",
41         "dsrom10", "esint10", "eufm10", "msam10", "msbm10", "rsfs10",
42         "stmary10", "wasy10"};
43 int const num_math_fonts = sizeof(math_fonts) / sizeof(*math_fonts);
44
45 namespace lyx {
46
47 namespace frontend {
48
49 /**
50  * Matches Fonts against
51  * actual QFont instances, and also caches metrics.
52  */
53 class GuiFontInfo
54 {
55 public:
56         GuiFontInfo(FontInfo const & f);
57
58         /// The font instance
59         QFont font;
60         /// Metrics on the font
61         GuiFontMetrics metrics;
62 };
63
64 namespace {
65
66 struct SymbolFont {
67         FontFamily lyx_family;
68         QString family;
69 };
70
71 SymbolFont symbol_fonts[] = {
72         { SYMBOL_FAMILY,"symbol"},
73         { CMR_FAMILY,   "cmr10"},
74         { CMSY_FAMILY,  "cmsy10"},
75         { CMM_FAMILY,   "cmmi10"},
76         { CMEX_FAMILY,  "cmex10"},
77         { MSA_FAMILY,   "msam10"},
78         { MSB_FAMILY,   "msbm10"},
79         { DS_FAMILY,    "dsrom10"},
80         { EUFRAK_FAMILY,"eufm10"},
81         { RSFS_FAMILY,  "rsfs10"},
82         { STMARY_FAMILY,"stmary10"},
83         { WASY_FAMILY,  "wasy10"},
84         { ESINT_FAMILY, "esint10"}
85 };
86
87 size_t const nr_symbol_fonts = sizeof(symbol_fonts) / sizeof(symbol_fonts[0]);
88
89 /// BUTT ugly !
90 static GuiFontInfo *
91 fontinfo_[NUM_FAMILIES][NUM_SERIES][NUM_SHAPE][NUM_SIZE][NUM_STYLE];
92
93
94 // returns a reference to the pointer type (GuiFontInfo *) in the
95 // fontinfo_ table.
96 GuiFontInfo * & fontinfo_ptr(FontInfo const & f)
97 {
98         // The display font and the text font are the same
99         size_t const style = (f.style() == DISPLAY_STYLE) ? TEXT_STYLE : f.style();
100         return fontinfo_[f.family()][f.series()][f.realShape()][f.size()][style];
101 }
102
103
104 // Get font info (font + metrics) for the given LyX font.
105 // if not cached, create it.
106 GuiFontInfo & fontinfo(FontInfo const & f)
107 {
108     bool const fontIsRealized =
109             (f.family() < NUM_FAMILIES) &&
110             (f.series() < NUM_SERIES) &&
111             (f.realShape() < NUM_SHAPE) &&
112             (f.size() < NUM_SIZE);
113     if (!fontIsRealized) {
114         // We can reset the font to something sensible in release mode.
115         LYXERR0("Unrealized font!" << f);
116         LATTEST(false);
117         FontInfo f2 = f;
118         f2.realize(sane_font);
119         GuiFontInfo * & fi = fontinfo_ptr(f2);
120         if (!fi)
121             fi = new GuiFontInfo(f2);
122         return *fi;
123     }
124     GuiFontInfo * & fi = fontinfo_ptr(f);
125         if (!fi)
126                 fi = new GuiFontInfo(f);
127         return *fi;
128 }
129
130
131 QString symbolFamily(FontFamily family)
132 {
133         for (size_t i = 0; i < nr_symbol_fonts; ++i) {
134                 if (family == symbol_fonts[i].lyx_family)
135                         return symbol_fonts[i].family;
136         }
137         return QString();
138 }
139
140
141 #if 0
142 bool isSymbolFamily(FontFamily family)
143 {
144         return family >= SYMBOL_FAMILY && family <= ESINT_FAMILY;
145 }
146 #endif
147
148
149 static bool isChosenFont(QFont & font, QString const & family,
150                          QString const & style)
151 {
152         // QFontInfo won't find a font that has only a few glyphs at unusual
153         // positions, e.g. the original esint10 font.
154         // The workaround is to add dummy glyphs at least at all ASCII
155         // positions.
156         QFontInfo fi(font);
157
158         LYXERR_NOPOS(Debug::FONT, "got: " << fi.family());
159
160         if (fi.family().contains(family)
161             && (style.isEmpty() || fi.styleName().contains(style))) {
162                 LYXERR_NOENDL(Debug::FONT, " got it ");
163                 return true;
164         }
165
166         return false;
167 }
168
169
170 QFont symbolFont(QString const & family, bool * ok)
171 {
172         LYXERR_NOENDL(Debug::FONT, "Looking for font family " << family << " ... ");
173         QString upper = family;
174         upper[0] = family[0].toUpper();
175
176         QFont font;
177         if (guiApp->platformName() == "xcb"
178             || guiApp->platformName().contains("wayland")) {
179                 // On *nix we have to also specify the foundry to be able to
180                 // discriminate our fonts when the texlive fonts are managed by
181                 // fontconfig. Unfortunately, doing the same on Windows breaks things.
182                 font.setFamily(family + QLatin1String(" [LyEd]"));
183         } else {
184                 font.setFamily(family);
185         }
186         font.setStyleStrategy(QFont::NoFontMerging);
187         font.setStyleName("LyX");
188
189         if (isChosenFont(font, family, "LyX")) {
190                 LYXERR_NOPOS(Debug::FONT, "lyx!");
191                 *ok = true;
192                 return font;
193         }
194
195         LYXERR_NOENDL(Debug::FONT, "Trying normal " << family << " ... ");
196         font.setStyleName(QString());
197
198         if (isChosenFont(font, family, QString())) {
199                 LYXERR_NOPOS(Debug::FONT, "normal!");
200                 *ok = true;
201                 return font;
202         }
203
204         LYXERR_NOENDL(Debug::FONT, "Trying " << upper << " ... ");
205         font.setFamily(upper);
206
207         if (isChosenFont(font, upper, QString())) {
208                 LYXERR_NOPOS(Debug::FONT, "upper!");
209                 *ok = true;
210                 return font;
211         }
212
213         LYXERR_NOPOS(Debug::FONT, " FAILED :-(");
214         *ok = false;
215         return font;
216 }
217
218 } // namespace
219
220
221 FontLoader::FontLoader()
222 {
223         QString const fonts_dir =
224                 toqstr(addPath(package().system_support().absFileName(), "fonts"));
225
226         for (int i = 0 ; i < num_math_fonts; ++i) {
227                 QString const font_file = fonts_dir + math_fonts[i] + ".ttf";
228                 int fontID = QFontDatabase::addApplicationFont(font_file);
229
230                 LYXERR(Debug::FONT, "Adding font " << font_file
231                                     << (fontID < 0 ? " FAIL" : " OK"));
232         }
233
234         for (int i1 = 0; i1 < NUM_FAMILIES; ++i1)
235                 for (int i2 = 0; i2 < NUM_SERIES; ++i2)
236                         for (int i3 = 0; i3 < NUM_SHAPE; ++i3)
237                                 for (int i4 = 0; i4 < NUM_SIZE; ++i4)
238                                         for (int i5 = 0; i5 < NUM_STYLE; ++i5)
239                                                 fontinfo_[i1][i2][i3][i4][i5] = 0;
240 }
241
242
243 void FontLoader::update()
244 {
245         for (int i1 = 0; i1 < NUM_FAMILIES; ++i1)
246                 for (int i2 = 0; i2 < NUM_SERIES; ++i2)
247                         for (int i3 = 0; i3 < NUM_SHAPE; ++i3)
248                                 for (int i4 = 0; i4 < NUM_SIZE; ++i4)
249                                         for (int i5 = 0; i5 < NUM_STYLE; ++i5) {
250                                         delete fontinfo_[i1][i2][i3][i4][i5];
251                                         fontinfo_[i1][i2][i3][i4][i5] = 0;
252                                 }
253 }
254
255
256 FontLoader::~FontLoader()
257 {
258         update();
259 }
260
261 /////////////////////////////////////////////////
262
263 namespace {
264
265 QString makeFontName(QString const & family, QString const & foundry)
266 {
267         QString res = family;
268         if (!foundry.isEmpty())
269                 res += " [" + foundry + ']';
270         return res;
271 }
272
273
274 QFont makeQFont(FontInfo const & f)
275 {
276         QFont font;
277         QString const pat = symbolFamily(f.family());
278         if (!pat.isEmpty()) {
279                 bool ok;
280                 font = symbolFont(pat, &ok);
281         } else {
282                 switch (f.family()) {
283                 case ROMAN_FAMILY: {
284                         QString family = makeFontName(toqstr(lyxrc.roman_font_name),
285                                 toqstr(lyxrc.roman_font_foundry));
286                         font.setFamily(family);
287                         break;
288                 }
289                 case SANS_FAMILY:
290                         font.setFamily(makeFontName(toqstr(lyxrc.sans_font_name),
291                                                     toqstr(lyxrc.sans_font_foundry)));
292                         break;
293                 case TYPEWRITER_FAMILY:
294                         font.setFamily(makeFontName(toqstr(lyxrc.typewriter_font_name),
295                                                     toqstr(lyxrc.typewriter_font_foundry)));
296                         break;
297                 default:
298                         break;
299                 }
300         }
301
302         switch (f.series()) {
303                 case MEDIUM_SERIES:
304                         font.setWeight(QFont::Normal);
305                         break;
306                 case BOLD_SERIES:
307                         font.setWeight(QFont::Bold);
308                         break;
309                 default:
310                         break;
311         }
312
313         switch (f.realShape()) {
314                 case ITALIC_SHAPE:
315                         font.setStyle(QFont::StyleItalic);
316                         break;
317                 case SLANTED_SHAPE:
318                         font.setStyle(QFont::StyleOblique);
319                         break;
320                 case SMALLCAPS_SHAPE:
321                         font.setCapitalization(QFont::SmallCaps);
322                         break;
323                 default:
324                         break;
325         }
326
327         LYXERR(Debug::FONT, "Font '" << f.stateText()
328                 << "' matched by\n" << font.family());
329
330         // Is this an exact match?
331         if (font.exactMatch())
332                 LYXERR(Debug::FONT, "This font is an exact match");
333         else
334                 LYXERR(Debug::FONT, "This font is NOT an exact match");
335
336         font.setPointSizeF(f.realSize() * lyxrc.currentZoom / 100.0);
337
338         LYXERR(Debug::FONT, "The font has size: " << font.pointSizeF());
339
340         return font;
341 }
342
343 } // namespace
344
345
346 GuiFontInfo::GuiFontInfo(FontInfo const & f)
347         : font(makeQFont(f)), metrics(font)
348 {}
349
350
351 bool FontLoader::available(FontInfo const & f)
352 {
353         // FIXME THREAD
354         static vector<int> cache_set(NUM_FAMILIES, false);
355         static vector<int> cache(NUM_FAMILIES, false);
356
357         FontFamily family = f.family();
358 #ifdef Q_OS_MAC
359         // Apple ships a font name "Symbol", which has more or less the same
360         // glyphs as the original PostScript Symbol font, but it uses a different
361         // encoding (see https://en.wikipedia.org/wiki/Symbol_(typeface)#cite_note-2).
362         // Since we expect the font specific encoding of the original
363         // PostScript Symbol font, we can't use the one provided on OS X.
364         // See also the discussion in bug 7954.
365         if (f.family() == SYMBOL_FAMILY)
366                 return false;
367 #endif
368         if (cache_set[family])
369                 return cache[family];
370         cache_set[family] = true;
371
372         QString const pat = symbolFamily(family);
373         if (pat.isEmpty())
374                 // We don't care about non-symbol fonts
375                 return false;
376
377         bool ok;
378         symbolFont(pat, &ok);
379         if (!ok)
380                 return false;
381
382         cache[family] = true;
383         return true;
384 }
385
386
387 bool FontLoader::canBeDisplayed(char_type c)
388 {
389         // bug 8493: with Qt5, characters with codepoints 9, 10, 12 and 13
390         // are rendered as spaces. We handle that in our TeX fonts by making
391         // copies of the glyphs elsewhere, but this may trigger if we are
392         // unlucky.
393         if (c == 0x0009 || c == 0x000a || c == 0x000c || c == 0x000d)
394                 return false;
395         return true;
396 }
397
398
399 FontMetrics const & FontLoader::metrics(FontInfo const & f)
400 {
401         return fontinfo(f).metrics;
402 }
403
404
405 GuiFontMetrics const & getFontMetrics(FontInfo const & f)
406 {
407         return fontinfo(f).metrics;
408 }
409
410
411 QFont const & getFont(FontInfo const & f)
412 {
413         return fontinfo(f).font;
414 }
415
416 } // namespace frontend
417 } // namespace lyx