Krita Source Code Documentation
Loading...
Searching...
No Matches
KoFontRegistry.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2022 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6#include "KoFontRegistry.h"
7#include "FlakeDebug.h"
8#include "KoCssTextUtils.h"
9
10#include <QApplication>
11#include <QDebug>
12#include <QDir>
13#include <QFile>
14#include <QGlobalStatic>
15#include <QThread>
16#include <QThreadStorage>
17#include <QtGlobal>
18#include <utility>
19
20#include <optional>
21
22#include <KoResourcePaths.h>
23#include <kis_debug.h>
24
26#include "KoFFWWSConverter.h"
27#include "KoFontChangeTracker.h"
29#include <KisResourceLocator.h>
30#include FT_TRUETYPE_TABLES_H
31#include FT_FREETYPE_H
32
33
34static unsigned int firstCharUcs4(const QStringView qsv)
35{
36 if (Q_UNLIKELY(qsv.isEmpty())) {
37 return 0;
38 }
39 const QChar high = qsv.first();
40 if (Q_LIKELY(!high.isSurrogate())) {
41 return high.unicode();
42 }
43 if (Q_LIKELY(high.isHighSurrogate() && qsv.length() >= 2)) {
44 const QChar low = qsv[1];
45 if (Q_LIKELY(low.isLowSurrogate())) {
46 return QChar::surrogateToUcs4(high, low);
47 }
48 }
49 return QChar::ReplacementCharacter;
50}
51
52std::optional<KoFFWWSConverter::FontFileEntry> getFontFileEntry(const FcPattern *p) {
53
54 FcChar8 *fileValue{};
55 if (FcPatternGetString(p, FC_FILE, 0, &fileValue) != FcResultMatch) {
56 debugFlake << "Failed to get font file for" << p;
57 return {};
58 }
59 const QString fontFileName = QString::fromUtf8(reinterpret_cast<char *>(fileValue));
60
61 int indexValue{};
62 if (FcPatternGetInteger(p, FC_INDEX, 0, &indexValue) != FcResultMatch) {
63 debugFlake << "Failed to get font index for" << p << "(file:" << fontFileName << ")";
64 return {};
65 }
66
67 return {{fontFileName, indexValue}};
68}
69
71
72class Q_DECL_HIDDEN KoFontRegistry::Private
73{
74private:
78
79 struct ThreadData {
81 QHash<QString, FcPatternSP> m_patterns;
82 QHash<QString, FcFontSetSP> m_fontSets;
83 QHash<QString, FT_FaceSP> m_faces;
84 QHash<QString, QVector<KoFFWWSConverter::FontFileEntry>> m_suggestedFiles;
85 QHash<QString, KoSvgText::FontMetrics> m_fontMetrics;
87
89 : m_library(std::move(lib))
90 {
91 }
92 };
93
94 QThreadStorage<QSharedPointer<ThreadData>> m_data;
95
97 {
98 if (!m_data.hasLocalData()) {
99 FT_Library lib = nullptr;
100 FT_Error error = FT_Init_FreeType(&lib);
101 if (error) {
102 errorFlake << "Error with initializing FreeType library:" << error << "Current thread:" << QThread::currentThread()
103 << "GUI thread:" << qApp->thread();
104 } else {
105 m_data.setLocalData(QSharedPointer<ThreadData>::create(lib));
106 }
107 }
108 }
109
110public:
112 {
113 FcConfig *config = FcConfigCreate();
114 KIS_ASSERT(config && "No Fontconfig support available");
115
124 if (qgetenv("FONTCONFIG_PATH").isEmpty()) {
125 QDir appdir("/etc/fonts");
126 if (QFile::exists(appdir.absoluteFilePath("fonts.conf"))) {
127 qputenv("FONTCONFIG_PATH", QFile::encodeName(QDir::toNativeSeparators(appdir.absolutePath())));
128 } else {
129 // Otherwise use default, which is defined in src/fcinit.c , windows and macos
130 // default locations *are* defined in fontconfig's meson build system.
131 appdir = QDir(KoResourcePaths::getApplicationRoot() +"/etc/fonts");
132 if (QFile::exists(appdir.absoluteFilePath("fonts.conf"))) {
133 qputenv("FONTCONFIG_PATH", QFile::encodeName(QDir::toNativeSeparators(appdir.absolutePath())));
134 }
135 }
136 }
137 if (!FcConfigParseAndLoad(config, nullptr, FcTrue)) {
138 errorFlake << "Failed loading the Fontconfig configuration";
139 config = FcConfigGetCurrent();
140 } else {
141 FcConfigSetCurrent(config);
142 }
143 m_config.reset(config);
144
145 // Add fonts folder from resource folder.
146 const QString fontsFolder = KoResourcePaths::saveLocation("data", "/fonts/", true);
147 FcConfigAppFontAddDir(m_config.data(), reinterpret_cast<const FcChar8 *>(fontsFolder.toUtf8().data()));
148
150 FcStrList *list = FcConfigGetFontDirs(m_config.data());
151 FcStrListFirst(list);
152 FcChar8 *dirString = FcStrListNext(list);
153 QString path = QString::fromUtf8(reinterpret_cast<char *>(dirString));
154 QStringList paths;
155 while (!path.isEmpty()) {
156 paths.append(path);
157 FcStrListNext(list);
158 dirString = FcStrListNext(list);
159 path = QString::fromUtf8(reinterpret_cast<char *>(dirString));
160 }
161 paths.append(fontsFolder);
162 FcStrListDone(list);
163 changeTracker.reset(new KoFontChangeTracker(paths));
164 changeTracker->resetChangeTracker();
165
166 reloadConverter();
167 }
168
169 ~Private() = default;
170
172 {
173 if (!m_data.hasLocalData())
174 initialize();
175 return m_data.localData()->m_library;
176 }
177
178 QHash<QString, FcPatternSP> &patterns()
179 {
180 if (!m_data.hasLocalData())
181 initialize();
182 return m_data.localData()->m_patterns;
183 }
184
185 QHash<QString, FcFontSetSP> &sets()
186 {
187 if (!m_data.hasLocalData())
188 initialize();
189 return m_data.localData()->m_fontSets;
190 }
191
192 QHash<QString, FT_FaceSP> &typeFaces()
193 {
194 if (!m_data.hasLocalData())
195 initialize();
196 return m_data.localData()->m_faces;
197 }
198
200 {
201 return m_config;
202 }
203
209 if (!m_data.hasLocalData())
210 initialize();
211 if (!m_data.localData()->m_fallbackFont.data()) {
212
213 FcPatternSP p(FcPatternCreate());
214 QByteArray fallbackBuf = QString("sans-serif").toUtf8();
215 FcValue fallback;
216 fallback.type = FcTypeString;
217 fallback.u.s = reinterpret_cast<FcChar8 *>(fallbackBuf.data());
218 FcPatternAddWeak(p.data(), FC_FAMILY, fallback, true);
219 FcConfigSubstitute(nullptr, p.data(), FcMatchPattern);
220 FcDefaultSubstitute(p.data());
221 FcResult result = FcResultNoMatch;
222 FcCharSet *cs = nullptr;
223 FcFontSetSP fontSet(FcFontSort(FcConfigGetCurrent(), p.data(), FcFalse, &cs, &result));
224
225 KIS_ASSERT_X(fontSet->nfont > 0, "No fallback fonts in font registry", "Cannot load an fallback font, no fonts found");
226
227 for (int j = 0; j < fontSet->nfont; j++) {
228 if(std::optional<KoFFWWSConverter::FontFileEntry> fontFileEntry = getFontFileEntry(fontSet->fonts[j]) ) {
229 QByteArray utfData = fontFileEntry->fileName.toUtf8();
230 FT_Face f = nullptr;
231 FT_Error err = FT_New_Face(library().data(), utfData.data(), fontFileEntry->fontIndex, &f);
232 if (err == 0) {
233 m_data.localData()->m_fallbackFont.reset(f);
234 break;
235 }
236 }
237 }
238 }
239 return m_data.localData()->m_fallbackFont;
240 }
241
243 return fontFamilyConverter;
244 }
245
247 return changeTracker;
248 }
249
251 fontFamilyConverter.reset(new KoFFWWSConverter());
252 FcObjectSet *objectSet = FcObjectSetBuild(FC_FAMILY, FC_FILE, FC_INDEX, FC_LANG, FC_CHARSET, nullptr);
253 FcFontSetSP allFonts(FcFontList(m_config.data(), FcPatternCreate(), objectSet));
254
255 for (int j = 0; j < allFonts->nfont; j++) {
256 fontFamilyConverter->addFontFromPattern(allFonts->fonts[j], library());
257 }
258 fontFamilyConverter->addGenericFamily("serif");
259 fontFamilyConverter->addGenericFamily("sans-serif");
260 fontFamilyConverter->addGenericFamily("monospace");
261 fontFamilyConverter->sortIntoWWSFamilies();
262 return true;
263 }
264
265 QHash<QString, QVector<KoFFWWSConverter::FontFileEntry>> &suggestedFileNames()
266 {
267 if (!m_data.hasLocalData())
268 initialize();
269 return m_data.localData()->m_suggestedFiles;
270 }
271
272 QHash<QString, KoSvgText::FontMetrics> &fontMetrics() {
273 if (!m_data.hasLocalData())
274 initialize();
275 return m_data.localData()->m_fontMetrics;
276 }
277
279 fontFamilyConverter->debugInfo();
280 }
281
283 if (FcConfigBuildFonts(m_config.data())) {
284 reloadConverter();
286 changeTracker->resetChangeTracker();
287 }
288 }
289};
290
292 : QObject(parent)
293 , d(new Private())
294{
295 connect(d->fontChangeTracker().data(), SIGNAL(sigUpdateConfig()), this, SLOT(updateConfig()), Qt::UniqueConnection);
296}
297
300
302{
303 return s_instance;
304}
305
306QString modificationsString(KoCSSFontInfo info, quint32 xRes, quint32 yRes) {
307 QString modifications;
308 if (info.size > -1) {
309 modifications += QString::number(info.size) + ":" + QString::number(xRes) + "x" + QString::number(yRes);
310 }
311 if (info.fontSizeAdjust != 1.0) {
312 modifications += QString::number(info.fontSizeAdjust);
313 }
314 if (info.weight != 400) {
315 modifications += "weight:"+QString::number(info.weight);
316 }
317 if (info.width != 100) {
318 modifications += "width:"+QString::number(info.width);
319 }
320 if (info.slantMode != 0) {
321 modifications += "slantMode:"+QString::number(info.slantMode);
322 if (info.slantValue != 0) {
323 modifications += "val:"+QString::number(info.slantValue);
324 }
325 }
326 if (!info.axisSettings.isEmpty()) {
327 Q_FOREACH (const QString &key, info.axisSettings.keys()) {
328 modifications += "|" + key + QString::number(info.axisSettings.value(key));
329 }
330 }
331 return modifications;
332}
333
334
335
336std::vector<FT_FaceSP> KoFontRegistry::facesForCSSValues(QVector<int> &lengths,
337 KoCSSFontInfo info,
338 const QString &text,
339 quint32 xRes,
340 quint32 yRes, bool disableFontMatching,
341 const QString &language)
342{
343 QString modifications = modificationsString(info, xRes, yRes);
344
346 const QString suggestedHash = info.families.join("+") + ":" + modifications;
347 auto entry = d->suggestedFileNames().find(suggestedHash);
348 if (entry != d->suggestedFileNames().end()) {
349 candidates = entry.value();
350 } else {
351 candidates = d->converter()->candidatesForCssValues(info,
352 xRes,
353 yRes);
354 d->suggestedFileNames().insert(suggestedHash, candidates);
355 }
356
358 lengths.clear();
359
360 if (disableFontMatching && !candidates.isEmpty()) {
361 fonts.append(candidates.first());
362 lengths.append(text.size());
363 } else {
364
365 FcPatternSP p(FcPatternCreate());
366 Q_FOREACH (const QString &family, info.families) {
367 QByteArray utfData = family.toUtf8();
368 const FcChar8 *vals = reinterpret_cast<FcChar8 *>(utfData.data());
369 FcPatternAddString(p.data(), FC_FAMILY, vals);
370 }
371
372 {
373 QByteArray fallbackBuf = QString("sans-serif").toUtf8();
374 FcValue fallback;
375 fallback.type = FcTypeString;
376 fallback.u.s = reinterpret_cast<FcChar8 *>(fallbackBuf.data());
377 FcPatternAddWeak(p.data(), FC_FAMILY, fallback, true);
378 }
379
380 for (int i = 0; i < candidates.size(); i++) {
381 KoFFWWSConverter::FontFileEntry file = candidates.at(i);
382 QByteArray utfData = file.fileName.toUtf8();
383 const FcChar8 *vals = reinterpret_cast<FcChar8 *>(utfData.data());
384 FcPatternAddString(p.data(), FC_FILE, vals);
385 FcPatternAddInteger(p.data(), FC_INDEX, file.fontIndex);
386 }
387
388 if (info.slantMode == QFont::StyleItalic) {
389 FcPatternAddInteger(p.data(), FC_SLANT, FC_SLANT_ITALIC);
390 } else if (info.slantMode == QFont::StyleOblique) {
391 FcPatternAddInteger(p.data(), FC_SLANT, FC_SLANT_OBLIQUE);
392 } else {
393 FcPatternAddInteger(p.data(), FC_SLANT, FC_SLANT_ROMAN);
394 }
395 FcPatternAddInteger(p.data(), FC_WEIGHT, FcWeightFromOpenType(info.weight));
396 FcPatternAddInteger(p.data(), FC_WIDTH, info.width);
397
398 double pixelSize = info.size*(qMin(xRes, yRes)/72.0);
399 FcPatternAddDouble(p.data(), FC_PIXEL_SIZE, pixelSize);
400
401 FcConfigSubstitute(nullptr, p.data(), FcMatchPattern);
402 FcDefaultSubstitute(p.data());
403
404
405 p = [&]() {
406 const FcChar32 hash = FcPatternHash(p.data());
407 QString patternHash = info.families.join("+")+QString::number(hash);
408 // FCPatternHash breaks down when there's multiple family names that start
409 // with the same letter and are the same length (Butcherman and Babylonica, Eater and Elsie, all 4 on google fonts).
410 const auto oldPattern = d->patterns().find(patternHash);
411 if (oldPattern != d->patterns().end()) {
412 return oldPattern.value();
413 } else {
414 d->patterns().insert(patternHash, p);
415 return p;
416 }
417 }();
418
419 FcResult result = FcResultNoMatch;
420 FcCharSetSP charSet;
421 FcFontSetSP fontSet = [&]() -> FcFontSetSP {
422 const FcChar32 hash = FcPatternHash(p.data());
423 QString patternHash = info.families.join("+")+QString::number(hash);
424 const auto set = d->sets().find(patternHash);
425
426 if (set != d->sets().end()) {
427 return set.value();
428 } else {
429 FcCharSet *cs = nullptr;
430 FcFontSetSP avalue(FcFontSort(FcConfigGetCurrent(), p.data(), FcFalse, &cs, &result));
431 charSet.reset(cs);
432 d->sets().insert(patternHash, avalue);
433 return avalue;
434 }
435 }();
436
437 if (text.isEmpty()) {
438 for (int j = 0; j < fontSet->nfont; j++) {
439 if (std::optional<KoFFWWSConverter::FontFileEntry> font = getFontFileEntry(fontSet->fonts[j])) {
440 fonts.append(std::move(*font));
441 lengths.append(0);
442 break;
443 }
444 }
445 } else {
446 FcCharSet *set = nullptr;
447 QVector<int> familyValues(text.size());
448 QVector<int> fallbackMatchValues(text.size());
449 familyValues.fill(-1);
450 fallbackMatchValues.fill(-1);
451
452 // First, we're going to split up the text into graphemes. This is both
453 // because the css spec requires it, but also because of why the css
454 // spec requires it: graphemes' parts should not end up in separate
455 // runs, which they will if they get assigned different fonts,
456 // potentially breaking ligatures and emoji sequences.
458
459 // Parse over the fonts and graphemes and try to see if we can get the
460 // best match for a given grapheme.
461 for (int i = 0; i < fontSet->nfont; i++) {
462
463 double fontsize = 0.0;
464 FcBool isScalable = false;
465 FcPatternGetBool(fontSet->fonts[i], FC_SCALABLE, 0, &isScalable);
466 FcPatternGetDouble(fontSet->fonts[i], FC_PIXEL_SIZE, 0, &fontsize);
467 if (!isScalable && pixelSize != fontsize) {
468 // For some reason, FC will sometimes consider a smaller font pixel-size
469 // to be more relevant to the requested pattern than a bigger one. This
470 // skips those fonts, but it does mean that such pixel fonts would not
471 // be used for fallback.
472 continue;
473 }
474 if (FcPatternGetCharSet(fontSet->fonts[i], FC_CHARSET, 0, &set) == FcResultMatch) {
475 int index = 0;
476 Q_FOREACH (const QString &grapheme, graphemes) {
477
478 // Don't worry about matching controls directly,
479 // as they are not important to font-selection (and many
480 // fonts have no glyph entry for these)
481 if (const uint first = firstCharUcs4(grapheme); QChar::category(first) == QChar::Other_Control
482 || QChar::category(first) == QChar::Other_Format) {
483 index += grapheme.size();
484 continue;
485 }
486 int familyIndex = -1;
487 if (familyValues.at(index) == -1) {
488 int fallbackMatch = fallbackMatchValues.at(index);
489 Q_FOREACH (uint unicode, grapheme.toUcs4()) {
490 if (FcCharSetHasChar(set, unicode)) {
491 familyIndex = i;
492 if (fallbackMatch < 0) {
493 fallbackMatch = i;
494 }
495 } else {
496 familyIndex = -1;
497 break;
498 }
499 }
500 for (int k = 0; k < grapheme.size(); k++) {
501 familyValues[index + k] = familyIndex;
502 fallbackMatchValues[index + k] = fallbackMatch;
503 }
504 }
505 index += grapheme.size();
506 }
507 if (!familyValues.contains(-1)) {
508 break;
509 }
510 }
511 }
512
513 // Remove the -1 entries.
514 if (familyValues.contains(-1)) {
515 int value = -1;
516 Q_FOREACH (const int currentValue, familyValues) {
517 if (currentValue != value) {
518 value = currentValue;
519 break;
520 }
521 }
522 value = qMax(0, value);
523 for (int i = 0; i < familyValues.size(); i++) {
524 if (familyValues.at(i) < 0) {
525 if (fallbackMatchValues.at(i) < 0) {
526 familyValues[i] = value;
527 } else {
528 familyValues[i] = fallbackMatchValues.at(i);
529 }
530 } else {
531 value = familyValues.at(i);
532 }
533 }
534 }
535
536 // Get the filenames and lengths for the entries.
537 int length = 0;
538 int startIndex = 0;
539 int lastIndex = familyValues.at(0);
541 if (std::optional<KoFFWWSConverter::FontFileEntry> f = getFontFileEntry(fontSet->fonts[lastIndex])) {
542 font = std::move(*f);
543 }
544 for (int i = 0; i < familyValues.size(); i++) {
545 if (lastIndex != familyValues.at(i)) {
546 lengths.append(text.mid(startIndex, length).size());
547 fonts.append(font);
548 startIndex = i;
549 length = 0;
550 lastIndex = familyValues.at(i);
551 if (std::optional<KoFFWWSConverter::FontFileEntry> f = getFontFileEntry(fontSet->fonts[lastIndex])) {
552 font = std::move(*f);
553 }
554 }
555 length += 1;
556 }
557 if (length > 0) {
558 lengths.append(text.mid(startIndex, length).size());
559 fonts.append(font);
560 }
561 }
562 }
563
564 std::vector<FT_FaceSP> faces;
565
566 KIS_ASSERT_X(lengths.size() == fonts.size(),
567 "KoFontRegistry",
568 QString("Fonts and lengths don't have the same size. Fonts: %1. Length: %2")
569 .arg(fonts.size(), lengths.size()).toLatin1());
570
571 for (int i = 0; i < lengths.size(); i++) {
572 KoFFWWSConverter::FontFileEntry font = fonts.at(i);
573 // For some reason, FontConfig will sometimes return the wrong file index.
574 // We can in this case select the appropriate index, which should work as tcc collections share glyphs.
575 for (int j = 0; j < candidates.size(); j++) {
576 if (font.fileName == candidates.at(j).fileName) {
577 font.fontIndex = candidates.at(j).fontIndex;
578 break;
579 }
580 }
581
582 const QString fontCacheEntry = font.fileName + "#" + QString::number(font.fontIndex) + "#" + modifications;
583 auto entry = d->typeFaces().find(fontCacheEntry);
584 if (entry != d->typeFaces().end()) {
585 faces.emplace_back(entry.value());
586 } else {
587 FT_Face f = nullptr;
588 QByteArray utfData = font.fileName.toUtf8();
589 FT_Error err = FT_New_Face(d->library().data(), utfData.data(), font.fontIndex, &f);
590 if (err == 0) {
591 FT_FaceSP face(f);
592 configureFaces({face}, info.size, info.fontSizeAdjust, xRes, yRes, info.computedAxisSettings());
593 faces.emplace_back(face);
594 d->typeFaces().insert(fontCacheEntry, face);
595 } else {
596 qWarning() << "Failed to load font" << font.fileName << "in font registry, FreeType error:" << err;
597 if (faces.size() > 0) {
598 faces.emplace_back(faces.at(faces.size()-1));
599 } else {
600 const QMap<QString, qreal> axisSettings;
601 if (d->fallbackFont().data()->size->metrics.x_ppem == 0) {
602 // if the font has not been configured yet, it's ppem is set to 0, so we test that and configure it.
603 configureFaces({d->fallbackFont()}, 12, 1.0, 72, 72, axisSettings);
604 }
605
606 faces.emplace_back(d->fallbackFont());
607 }
608 }
609 }
610 }
611 if (faces.size() == 0) {
612 lengths = QVector<int>();
613 }
614
615 return faces;
616}
617
618bool KoFontRegistry::configureFaces(const std::vector<FT_FaceSP> &faces,
619 qreal size,
620 qreal fontSizeAdjust,
621 quint32 xRes,
622 quint32 yRes,
623 const QMap<QString, qreal> &axisSettings)
624{
625 int errorCode = 0;
626 const qreal ftFontUnit = 64.0;
627 const qreal finalRes = qMin(xRes, yRes);
628 const qreal scaleToPixel = finalRes / 72;
629 Q_FOREACH (const FT_FaceSP &face, faces) {
630
631 // set the charmap, by default Freetype will select a Unicode charmap, but when there's none, it doesn't set any and we need to select one manually.
632 if (!face->charmap) {
633 FT_CharMap map = nullptr;
634 for (int i = 0; i < face->num_charmaps; i++) {
635 FT_CharMap m = face->charmaps[i];
636 if (!m) {
637 continue;
638 }
639
640 if (!map) {
641 map = m;
642 continue;
643 }
644
645 switch (m->encoding) {
646 case FT_ENCODING_UNICODE:
647 map = m;
648 break;
649 case FT_ENCODING_APPLE_ROMAN:
650 case FT_ENCODING_ADOBE_LATIN_1:
651 if (!map || map->encoding != FT_ENCODING_UNICODE) {
652 map = m;
653 }
654 break;
655 case FT_ENCODING_ADOBE_CUSTOM:
656 case FT_ENCODING_MS_SYMBOL:
657 if (!map || (map->encoding != FT_ENCODING_UNICODE && map->encoding != FT_ENCODING_APPLE_ROMAN && map->encoding != FT_ENCODING_ADOBE_LATIN_1)) {
658 map = m;
659 }
660 default:
661 break;
662 }
663 }
664 FT_Set_Charmap(face.data(), map);
665 }
666
667 qreal adjustedSize = size;
668
669 if (!FT_IS_SCALABLE(face)) {
670 const qreal fontSizePixels = size * ftFontUnit * scaleToPixel;
671 qreal sizeDelta = 0;
672 int selectedIndex = -1;
673
674 for (int i = 0; i < face->num_fixed_sizes; i++) {
675 const qreal newDelta = qAbs((fontSizePixels)-face->available_sizes[i].x_ppem);
676 if (newDelta < sizeDelta || i == 0) {
677 selectedIndex = i;
678 sizeDelta = newDelta;
679 }
680 }
681
682 if (selectedIndex >= 0) {
683 if (FT_HAS_COLOR(face)) {
684 const FT_Fixed scale = static_cast<FT_Fixed>(65535. * qreal(fontSizePixels)
685 / qreal(face->available_sizes[selectedIndex].x_ppem));
686 FT_Matrix matrix;
687 matrix.xx = scale;
688 matrix.xy = 0;
689 matrix.yx = 0;
690 matrix.yy = scale;
691 FT_Vector v;
692 FT_Set_Transform(face.data(), &matrix, &v);
693 }
694 errorCode = FT_Select_Size(face.data(), selectedIndex);
695 }
696 } else {
697 errorCode = FT_Set_Char_Size(face.data(), static_cast<FT_F26Dot6>(size * ftFontUnit), 0, xRes, yRes);
698 hb_font_t_sp font(hb_ft_font_create_referenced(face.data()));
699 hb_position_t xHeight = 0;
700 hb_ot_metrics_get_position(font.data(), HB_OT_METRICS_TAG_X_HEIGHT, &xHeight);
701 if (xHeight > 0 && fontSizeAdjust > 0) {
702 qreal aspect = xHeight / (size * ftFontUnit * scaleToPixel);
703 adjustedSize = (fontSizeAdjust / aspect) * (size);
704 errorCode = FT_Set_Char_Size(face.data(),
705 static_cast<FT_F26Dot6>(adjustedSize * ftFontUnit),
706 0,
707 xRes,
708 yRes);
709 }
710 }
711
712 QMap<FT_Tag, qreal> tags;
713 Q_FOREACH (const QString &tagName, axisSettings.keys()) {
714 if (tagName.size() == 4) {
715 const QByteArray utfData = tagName.toUtf8();
716 const char *tag = utfData.data();
717 if (tagName == "opsz" && !qFuzzyCompare(adjustedSize, size)) {
726 tags.insert(FT_MAKE_TAG(tag[0], tag[1], tag[2], tag[3]), adjustedSize);
727 } else {
728 tags.insert(FT_MAKE_TAG(tag[0], tag[1], tag[2], tag[3]), axisSettings.value(tagName));
729 }
730 }
731 }
732 const FT_Tag ITALIC_TAG = FT_MAKE_TAG('i', 't', 'a', 'l');
733 const FT_Tag SLANT_TAG = FT_MAKE_TAG('s', 'l', 'n', 't');
734 const int axisMultiplier = 65535;
735 if (FT_HAS_MULTIPLE_MASTERS(face)) {
736 FT_MM_Var *amaster = nullptr;
737 FT_Get_MM_Var(face.data(), &amaster);
738 // note: this only works for opentype, as it uses
739 // tag-based-selection.
740 std::vector<FT_Fixed> designCoords(amaster->num_axis);
741 for (FT_UInt i = 0; i < amaster->num_axis; i++) {
742 FT_Var_Axis axis = amaster->axis[i];
743 designCoords[i] = axis.def;
744 if (tags.contains(axis.tag)) {
745 designCoords[i] = qBound(axis.minimum, long(tags.value(axis.tag) * axisMultiplier), axis.maximum);
746 } else if (axis.tag == SLANT_TAG
747 && !tags.contains(SLANT_TAG)
748 && tags.value(ITALIC_TAG, 0) > 0) {
749 designCoords[i] = qBound(axis.minimum, long(-11.0 * axisMultiplier), axis.maximum);
750 } else if (axis.tag == ITALIC_TAG
751 && !tags.contains(ITALIC_TAG)
752 && !qFuzzyCompare(tags.value(SLANT_TAG, 0), 0)) {
753 designCoords[i] = qBound(axis.minimum, long(1.0 * axisMultiplier), axis.maximum);
754 }
755 }
756 FT_Set_Var_Design_Coordinates(face.data(), amaster->num_axis, designCoords.data());
757 FT_Done_MM_Var(d->library().data(), amaster);
758 }
759 }
760 return (errorCode == 0);
761}
762
764{
765 return d->converter()->collectFamilies();
766}
767
768std::optional<KoFontFamilyWWSRepresentation> KoFontRegistry::representationByFamilyName(const QString &familyName) const
769{
770 return d->converter()->representationByFamilyName(familyName);
771}
772
773std::optional<QString> KoFontRegistry::wwsNameByFamilyName(const QString familyName) const
774{
775 return d->converter()->wwsNameByFamilyName(familyName);
776}
777
779{
780 d->updateConfig();
781}
782
784{
785 constexpr unsigned OS2_ITALIC = 1u << 0;
786 constexpr unsigned OS2_REGULAR = 1u << 6;
787 constexpr unsigned OS2_OBLIQUE = 1u << 9; // Is an oblique instead of an italic.
788
789 if (FT_IS_SFNT(face.data())) {
790 hb_font_t_sp hbFont(hb_ft_font_create_referenced(face.data()));
791 bool isItalic = hb_style_get_value(hbFont.data(), HB_STYLE_TAG_ITALIC) > 0;
792 bool isOblique = false;
793
794 if (FT_HAS_MULTIPLE_MASTERS(face)) {
795 isOblique = hb_style_get_value(hbFont.data(), HB_STYLE_TAG_SLANT_ANGLE) != 0;
796 } else {
797 TT_OS2 *os2Table = nullptr;
798 os2Table = (TT_OS2*)FT_Get_Sfnt_Table(face.data(), FT_SFNT_OS2);
799 if (os2Table) {
800 isItalic = os2Table->fsSelection & OS2_ITALIC;
801 isOblique = os2Table->fsSelection & OS2_OBLIQUE;
802 }
803 if (os2Table->fsSelection & OS2_REGULAR) {
804 isItalic = false;
805 isOblique = false;
806 }
807 }
808 return isItalic? isOblique? QFont::StyleOblique: QFont::StyleItalic: QFont::StyleNormal;
809 } else {
810 return face->style_flags & FT_STYLE_FLAG_ITALIC? QFont::StyleItalic: QFont::StyleNormal;
811 }
812}
813
815 const bool isHorizontal, const KoSvgText::TextRendering rendering,
816 const QString &text,
817 quint32 xRes, quint32 yRes,
818 bool disableFontMatching, const QString &language)
819{
820 const QString suggestedHash = info.families.join(",")+":"+modificationsString(info, xRes, yRes)+language;
822 auto entry = d->fontMetrics().find(suggestedHash);
823 if (entry != d->fontMetrics().end()) {
824 metrics = entry.value();
825 } else {
826 QVector<int> lengths;
827 const std::vector<FT_FaceSP> faces = KoFontRegistry::instance()->facesForCSSValues(
828 lengths,
829 info,
830 text,
831 xRes,
832 yRes,
833 disableFontMatching,
834 language);
835
836 if (faces.empty()) return KoSvgText::FontMetrics(info.size, isHorizontal);
837 metrics = KoFontRegistry::generateFontMetrics(faces.front(), isHorizontal, KoWritingSystemUtils::scriptTagForQLocaleScript(QLocale(language).script()), rendering);
838 d->fontMetrics().insert(suggestedHash, metrics);
839 }
840 return metrics;
841}
842
844{
846 hb_direction_t dir = isHorizontal? HB_DIRECTION_LTR: HB_DIRECTION_TTB;
847 QLatin1String scriptLatin1(script.toLatin1());
848 hb_script_t scriptTag = hb_script_from_string(scriptLatin1.data(), scriptLatin1.size());
849 hb_tag_t otScriptTag = HB_OT_TAG_DEFAULT_SCRIPT;
850 hb_tag_t otLangTag = HB_OT_TAG_DEFAULT_LANGUAGE;
851 uint maxCount = 1;
852 hb_ot_tags_from_script_and_language(scriptTag, nullptr, &maxCount, &otScriptTag, &maxCount, &otLangTag);
853 hb_font_t_sp font(hb_ft_font_create_referenced(face.data()));
854 hb_face_t_sp hbFace(hb_ft_face_create_referenced(face.data()));
855
856 const FT_Int32 faceLoadFlags = loadFlagsForFace(face.data(), isHorizontal, 0, rendering);
857
858 metrics.isVertical = !isHorizontal;
859 // Fontsize and advances.
860 metrics.fontSize = isHorizontal? face.data()->size->metrics.y_ppem*64.0: face.data()->size->metrics.x_ppem*64.0;
861
862 uint charIndex = FT_Get_Char_Index(face.data(), 0x20);
863 if(charIndex > 0) {
864 if (FT_Load_Glyph(face.data(), charIndex, faceLoadFlags) == FT_Err_Ok) {
865 metrics.spaceAdvance = isHorizontal? face.data()->glyph->advance.x: face.data()->glyph->advance.y;
866 } else {
867 metrics.spaceAdvance = isHorizontal? metrics.fontSize/2: metrics.fontSize;
868 }
869 } else {
870 metrics.spaceAdvance = isHorizontal? metrics.fontSize/2: metrics.fontSize;
871 }
872
873 charIndex = FT_Get_Char_Index(face.data(), '0');
874 if(charIndex > 0) {
875 if(FT_Load_Glyph(face.data(), charIndex, faceLoadFlags) == FT_Err_Ok) {
876 metrics.zeroAdvance = isHorizontal? face.data()->glyph->advance.x: face.data()->glyph->advance.y;
877 } else {
878 metrics.zeroAdvance = isHorizontal? metrics.fontSize/2: metrics.fontSize;
879 }
880 } else {
881 metrics.zeroAdvance = isHorizontal? metrics.fontSize/2: metrics.fontSize;
882 }
883
884 bool isIdeographic = false;
885 charIndex = FT_Get_Char_Index(face.data(), 0x6C34);
886 if(charIndex > 0) {
887 if (FT_Load_Glyph(face.data(), charIndex, faceLoadFlags) == FT_Err_Ok) {
888 metrics.ideographicAdvance = isHorizontal? face.data()->glyph->advance.x: face.data()->glyph->advance.y;
889 } else {
890 metrics.ideographicAdvance = metrics.fontSize;
891 }
892 isIdeographic = true;
893 } else {
894 metrics.ideographicAdvance = metrics.fontSize;
895 }
896
897 const bool useFallback = hb_version_atleast(4, 0, 0);
898
899 // metrics
900
901 QVector<hb_ot_metrics_tag_t> metricTags ({
902 HB_OT_METRICS_TAG_X_HEIGHT,
903 HB_OT_METRICS_TAG_CAP_HEIGHT,
904 HB_OT_METRICS_TAG_SUPERSCRIPT_EM_X_OFFSET,
905 HB_OT_METRICS_TAG_SUPERSCRIPT_EM_Y_OFFSET,
906 HB_OT_METRICS_TAG_SUBSCRIPT_EM_X_OFFSET,
907 HB_OT_METRICS_TAG_SUBSCRIPT_EM_Y_OFFSET,
908 HB_OT_METRICS_TAG_UNDERLINE_OFFSET,
909 HB_OT_METRICS_TAG_UNDERLINE_SIZE,
910 HB_OT_METRICS_TAG_STRIKEOUT_OFFSET,
911 HB_OT_METRICS_TAG_STRIKEOUT_SIZE,
912 });
913
914 if (isHorizontal) {
915 metricTags.append(HB_OT_METRICS_TAG_HORIZONTAL_CARET_RISE);
916 metricTags.append(HB_OT_METRICS_TAG_HORIZONTAL_CARET_RUN);
917 metricTags.append(HB_OT_METRICS_TAG_HORIZONTAL_CARET_OFFSET);
918 metrics.caretRise = 1;
919 metrics.caretRun = 0;
920 } else {
921 metricTags.append(HB_OT_METRICS_TAG_VERTICAL_CARET_RISE);
922 metricTags.append(HB_OT_METRICS_TAG_VERTICAL_CARET_RUN);
923 metricTags.append(HB_OT_METRICS_TAG_VERTICAL_CARET_OFFSET);
924 metrics.caretRise = 0;
925 metrics.caretRun = 1;
926 }
927
928 for (auto it = metricTags.begin(); it!= metricTags.end(); it++) {
929 char c[4];
930 hb_tag_to_string(*it, c);
931 const QLatin1String tagName(c, 4);
932 hb_position_t origin = 0;
933 if (useFallback) {
934 hb_ot_metrics_get_position_with_fallback(font.data(), *it, &origin);
935 metrics.setMetricsValueByTag(tagName, origin);
936 } else if (hb_ot_metrics_get_position(font.data(), *it, &origin)) {
937 metrics.setMetricsValueByTag(tagName, origin);
938 }
939 }
940
941 // Baselines.
942
943 const QVector<hb_ot_layout_baseline_tag_t> baselines ({
944 HB_OT_LAYOUT_BASELINE_TAG_ROMAN,
945 HB_OT_LAYOUT_BASELINE_TAG_HANGING,
946 HB_OT_LAYOUT_BASELINE_TAG_IDEO_FACE_BOTTOM_OR_LEFT,
947 HB_OT_LAYOUT_BASELINE_TAG_IDEO_FACE_TOP_OR_RIGHT,
948 HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_BOTTOM_OR_LEFT,
949 HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_TOP_OR_RIGHT,
950 HB_OT_LAYOUT_BASELINE_TAG_MATH
951 });
952
953 QMap<QString, qint32> baselineVals;
954
955 for (auto it = baselines.begin(); it!= baselines.end(); it++) {
956 hb_position_t origin = 0;
957 if (hb_ot_layout_get_baseline(font.data(), *it, dir, otScriptTag, otLangTag, &origin)) {
958 std::vector<char> c(4);
959 hb_tag_to_string(*it, c.data());
960 baselineVals.insert(QString::fromLatin1(c.data(), 4), origin);
961 }
962 }
963
964 //--- ascender/descender/linegap, and also ideographic em-box calculatation ---//
965
966 hb_position_t ascender = 0;
967 hb_position_t descender = 0;
968 hb_position_t lineGap = 0;
969
984 TT_OS2 *os2Table = nullptr;
985 os2Table = (TT_OS2*)FT_Get_Sfnt_Table(face.data(), FT_SFNT_OS2);
986 if (os2Table) {
987 int yscale = face.data()->size->metrics.y_scale;
988
989 ascender = FT_MulFix(os2Table->sTypoAscender, yscale);
990 descender = FT_MulFix(os2Table->sTypoDescender, yscale);
991 lineGap = FT_MulFix(os2Table->sTypoLineGap, yscale);
992 }
993
994 constexpr unsigned USE_TYPO_METRICS = 1u << 7;
995 if (!os2Table || os2Table->version == 0xFFFFU || !(os2Table->fsSelection & USE_TYPO_METRICS)) {
996 hb_position_t altAscender = 0;
997 hb_position_t altDescender = 0;
998 hb_position_t altLineGap = 0;
999 if (!hb_ot_metrics_get_position(font.data(), HB_OT_METRICS_TAG_HORIZONTAL_ASCENDER, &altAscender)) {
1000 altAscender = face.data()->ascender;
1001 }
1002 if (!hb_ot_metrics_get_position(font.data(), HB_OT_METRICS_TAG_HORIZONTAL_DESCENDER, &altDescender)) {
1003 altDescender = face.data()->descender;
1004 }
1005 if (!hb_ot_metrics_get_position(font.data(), HB_OT_METRICS_TAG_HORIZONTAL_LINE_GAP, &altLineGap)) {
1006 altLineGap = face.data()->height - (altAscender-altDescender);
1007 }
1008
1009 // Some fonts have sTypo metrics that are too small compared
1010 // to the HHEA values which make the default line height too
1011 // tight (e.g. Microsoft JhengHei, Source Han Sans), so we
1012 // compare them and take the ones that are larger.
1013 if (!os2Table || (altAscender - altDescender + altLineGap) > (ascender - descender + lineGap)) {
1014 ascender = altAscender;
1015 descender = altDescender;
1016 lineGap = altLineGap;
1017 }
1018 }
1019
1020 // Because the ideographic em-box is so important to SVG 2 vertical line calculation,
1021 // I am manually calculating it here, following
1022 // https://learn.microsoft.com/en-us/typography/opentype/spec/baselinetags#ideographic-em-box
1023 const QString ideoBottom("ideo");
1024 const QString ideoTop("idtp");
1025 const QString alphabetic("romn");
1026 const QString ideoCenter("Idce");
1027 const QString hang("hang");
1028 const QString math("math");
1029
1030 if (baselineVals.keys().contains(ideoBottom) && !baselineVals.keys().contains(ideoTop)) {
1031 baselineVals.insert(ideoTop, baselineVals.value(ideoBottom)+metrics.fontSize);
1032 } else if (!baselineVals.keys().contains(ideoBottom) && baselineVals.keys().contains(ideoTop)) {
1033 baselineVals.insert(ideoBottom, baselineVals.value(ideoTop)-metrics.fontSize);
1034 } else if (!baselineVals.keys().contains(ideoBottom) && !baselineVals.keys().contains(ideoTop)){
1035
1036 if (!isIdeographic && isHorizontal) {
1037 hb_blob_t_sp dLang(hb_ot_meta_reference_entry( hbFace.data() , HB_OT_META_TAG_DESIGN_LANGUAGES));
1038 uint length = hb_blob_get_length(dLang.data());
1039 QByteArray ba(hb_blob_get_data(dLang.data(), &length), length);
1040
1041 const QString designLang = QString::fromLatin1(ba).trimmed();
1042
1043 if (!designLang.isEmpty()) {
1044 // This assumes a font where there's design language metadata, but no water glyph.
1045 // In theory could happen with the non-han cjk fonts.
1046 const QStringList cjkScripts {
1048 KoWritingSystemUtils::scriptTagForQLocaleScript(QLocale::JapaneseScript),
1049 KoWritingSystemUtils::scriptTagForQLocaleScript(QLocale::TraditionalHanScript),
1050 KoWritingSystemUtils::scriptTagForQLocaleScript(QLocale::SimplifiedHanScript),
1051 KoWritingSystemUtils::scriptTagForQLocaleScript(QLocale::HanWithBopomofoScript),
1052 KoWritingSystemUtils::scriptTagForQLocaleScript(QLocale::BopomofoScript),
1054 KoWritingSystemUtils::scriptTagForQLocaleScript(QLocale::KatakanaScript),
1055 KoWritingSystemUtils::scriptTagForQLocaleScript(QLocale::HiraganaScript),
1056 };
1057 Q_FOREACH (const QString cjk, cjkScripts) {
1058 if (cjk.isEmpty()) continue;
1059 if (designLang.contains(cjk.trimmed())) {
1060 isIdeographic = true;
1061 break;
1062 }
1063 }
1064 }
1065 }
1066
1067 if (isIdeographic && isHorizontal) {
1068 baselineVals.insert(ideoTop, ascender);
1069 baselineVals.insert(ideoBottom, descender);
1070 if (!baselineVals.keys().contains(alphabetic)) {
1071 baselineVals.insert(alphabetic, 0);
1072 }
1073 if (!baselineVals.keys().contains(hang)) {
1074 baselineVals.insert(hang, baselineVals.value(alphabetic)+metrics.fontSize*0.6);
1075 }
1076 } else if (isHorizontal) {
1077 const qreal alphabeticMultiplier = qreal(ascender+descender)/metrics.fontSize;
1078 qint32 top = qMax(baselineVals.value(hang, metrics.capHeight), qint32(qRound(ascender*alphabeticMultiplier)));
1079 baselineVals.insert(ideoTop, top);
1080 baselineVals.insert(ideoBottom, top-metrics.fontSize);
1081 } else {
1082 baselineVals.insert(ideoTop, metrics.fontSize);
1083 baselineVals.insert(ideoBottom, 0);
1084 if (!baselineVals.keys().contains(alphabetic)) {
1085 const qreal alphabeticMultiplier = qreal(ascender+descender)/metrics.fontSize;
1086 baselineVals.insert(alphabetic, (-descender)*alphabeticMultiplier);
1087 }
1088 if (!baselineVals.keys().contains(hang)) {
1089 baselineVals.insert(hang, baselineVals.value(alphabetic)+metrics.fontSize*0.6);
1090 }
1091 }
1092 }
1093 baselineVals.insert(ideoCenter, (baselineVals.value(ideoTop) + baselineVals.value(ideoBottom))/2);
1094 if (!isHorizontal && !baselineVals.keys().contains(math)) {
1095 baselineVals.insert(math, baselineVals.value(ideoCenter));
1096 }
1097
1098 if (useFallback) {
1099 for (auto it = baselines.begin(); it!= baselines.end(); it++) {
1100 char c[4];
1101 hb_tag_to_string(*it, c);
1102 const QString tagName = QString::fromLatin1(c, 4);
1103 if (!baselineVals.keys().contains(tagName)) {
1104 hb_position_t origin = 0;
1105 hb_ot_layout_get_baseline_with_fallback(font.data(), *it, dir, otScriptTag, otLangTag, &origin);
1106 baselineVals.insert(tagName, origin);
1107 }
1108 }
1109 }
1110
1111 Q_FOREACH(const QString key, baselineVals.keys()) {
1112 metrics.setBaselineValueByTag(key, baselineVals.value(key));
1113 }
1114
1115 if (!isHorizontal) {
1116 // SVG 2.0 explicitly requires vertical ascent/descent to be tied to the ideographic em box.
1117 ascender = metrics.ideographicOverBaseline;
1118 descender = metrics.ideographicUnderBaseline;
1119
1120 // Default microsoft CJK fonts have the vertical ascent and descent be the same as the horizontal
1121 // ascent and descent, so we 'normalize' the ascender and descender to be half the total height.
1122 qreal height = ascender - descender;
1123 ascender = height*0.5;
1124 descender = -ascender;
1125 }
1126 if (ascender == 0 && descender == 0) {
1127 ascender = face->size->metrics.ascender;
1128 descender = face->size->metrics.descender;
1129 qreal height = ascender - descender;
1130 lineGap = face->size->metrics.height - height;
1131 if (!isHorizontal) {
1132 ascender = height * 0.5;
1133 descender = -ascender;
1134 }
1135 }
1136 metrics.ascender = ascender;
1137 metrics.descender = descender;
1138 metrics.lineGap = lineGap;
1139
1140 if (isHorizontal) {
1142 } else {
1143 // because we normalize, we need to add here.
1144 metrics.ascender += metrics.ideographicCenterBaseline;
1145 metrics.descender += metrics.ideographicCenterBaseline;
1147 }
1148
1149 return metrics;
1150}
1151
1152int32_t KoFontRegistry::loadFlagsForFace(FT_Face face, bool isHorizontal, int32_t loadFlags, const KoSvgText::TextRendering rendering)
1153{
1154 FT_Int32 faceLoadFlags = loadFlags;
1155
1156 if (rendering == KoSvgText::RenderingGeometricPrecision || rendering == KoSvgText::RenderingAuto) {
1157 // without load_no_hinting, the advance and offset will be rounded
1158 // to nearest pixel, which we don't want as we're using the vector
1159 // outline.
1160 faceLoadFlags |= FT_LOAD_NO_HINTING;
1161
1162 // Disable embedded bitmaps because they _do not_ follow geometric
1163 // precision, but is focused on legibility.
1164 // This does not affect bitmap-only fonts.
1165 faceLoadFlags |= FT_LOAD_NO_BITMAP;
1166 } else if (rendering == KoSvgText::RenderingOptimizeSpeed) {
1167 faceLoadFlags |= FT_LOAD_TARGET_MONO;
1168 } else {
1169 // When using hinting, sometimes the bounding box does not encompass the
1170 // drawn glyphs properly.
1171 // The default hinting works best for vertical, while the 'light'
1172 // hinting mode works best for horizontal.
1173 if (isHorizontal) {
1174 faceLoadFlags |= FT_LOAD_TARGET_LIGHT;
1175 }
1176 }
1177 if (FT_HAS_COLOR(face)) {
1178 faceLoadFlags |= FT_LOAD_COLOR;
1179 }
1180 if (!isHorizontal && FT_HAS_VERTICAL(face)) {
1181 faceLoadFlags |= FT_LOAD_VERTICAL_LAYOUT;
1182 }
1183 if (!FT_IS_SCALABLE(face)) {
1184 // This is needed for the CBDT version of Noto Color Emoji
1185 faceLoadFlags &= ~FT_LOAD_NO_BITMAP;
1186 }
1187 return faceLoadFlags;
1188}
1189
1190KoCSSFontInfo KoFontRegistry::getCssDataForPostScriptName(const QString postScriptName, QString *foundPostScriptName)
1191{
1192 KoCSSFontInfo info;
1193 FcPatternSP p(FcPatternCreate());
1194 QByteArray utfData = postScriptName.toUtf8();
1195 const FcChar8 *vals = reinterpret_cast<FcChar8 *>(utfData.data());
1196 FcPatternAddString(p.data(), FC_POSTSCRIPT_NAME, vals);
1197 FcDefaultSubstitute(p.data());
1198
1199 FcResult result = FcResultNoMatch;
1200 FcPatternSP match(FcFontMatch(FcConfigGetCurrent(), p.data(), &result));
1201 if (result != FcResultNoMatch) {
1202 FcChar8 *fileValue = nullptr;
1203 if (FcPatternGetString(match.data(), FC_FAMILY, 0, &fileValue) == FcResultMatch) {
1204 info.families << QString(reinterpret_cast<char *>(fileValue));
1205 } else {
1206 info.families << postScriptName;
1207 }
1208 if (FcPatternGetString(match.data(), FC_POSTSCRIPT_NAME, 0, &fileValue) == FcResultMatch) {
1209 *foundPostScriptName = QString(reinterpret_cast<char *>(fileValue));
1210 } else {
1211 *foundPostScriptName = postScriptName;
1212 }
1213 int value;
1214 if (FcPatternGetInteger(match.data(), FC_WEIGHT, 0, &value) == FcResultMatch) {
1215 info.weight = FcWeightToOpenType(value);
1216 }
1217 if (FcPatternGetInteger(match.data(), FC_WIDTH, 0, &value) == FcResultMatch) {
1218 info.width = value;
1219 }
1220 if (FcPatternGetInteger(match.data(), FC_SLANT, 0, &value) == FcResultMatch) {
1221 info.slantMode = value != FC_SLANT_ROMAN? value != FC_SLANT_ITALIC? QFont::StyleOblique: QFont::StyleItalic: QFont::StyleNormal;
1222 }
1223 } else {
1224 info.families << postScriptName;
1225 }
1226 return info;
1227}
1228
1230{
1231 const QByteArray utfData = path.toUtf8();
1232 const FcChar8 *vals = reinterpret_cast<const FcChar8 *>(utfData.data());
1233 bool success = false;
1234 if (FcConfigAppFontAddFile(d->config().data(), vals)) {
1235 success = d->reloadConverter();
1236 }
1237 return success;
1238}
1239
1241{
1242 const QByteArray utfData = path.toUtf8();
1243 const FcChar8 *vals = reinterpret_cast<const FcChar8 *>(utfData.data());
1244 bool success = false;
1245 if (FcConfigAppFontAddDir(d->config().data(), vals)) {
1246 success = d->reloadConverter();
1247 }
1248 return success;
1249}
qreal length(const QPointF &vec)
Definition Ellipse.cc:82
#define errorFlake
Definition FlakeDebug.h:17
#define debugFlake
Definition FlakeDebug.h:15
float value(const T *src, size_t ch)
const Params2D p
qreal v
Q_GLOBAL_STATIC(KisStoragePluginRegistry, s_instance)
const QString SLANT_TAG
constexpr unsigned OS2_OBLIQUE
Indicates that the given font is primarily a WWS family and requires no further processing.
constexpr unsigned OS2_ITALIC
const QString ITALIC_TAG
constexpr unsigned OS2_REGULAR
Is bold.
std::optional< KoFFWWSConverter::FontFileEntry > getFontFileEntry(const FcPattern *p)
QString modificationsString(KoCSSFontInfo info, quint32 xRes, quint32 yRes)
static unsigned int firstCharUcs4(const QStringView qsv)
unsigned int uint
void updateFontStorage()
This updates the "fontregistry" storage. Called when the font directories change;.
static KisResourceLocator * instance()
static QStringList textToUnicodeGraphemeClusters(const QString &text, const QString &langCode)
textToUnicodeGraphemes In letters like Å, the amount of unicode codpoints can be 1,...
The KoFFWWSConverter class This class takes fontconfig patterns and tries to sort them into a hierarc...
The KoFontChangeTracker class This class keeps track of the paths FontConfig is looking at,...
The KoFontRegistry class A wrapper around a freetype library.
bool configureFaces(const std::vector< FT_FaceSP > &faces, qreal size, qreal fontSizeAdjust, quint32 xRes, quint32 yRes, const QMap< QString, qreal > &axisSettings)
configureFaces This configures a list of faces with pointSize and variation settings.
FT_FaceSP fallbackFont()
fallbackFont
std::vector< FT_FaceSP > facesForCSSValues(QVector< int > &lengths, KoCSSFontInfo info=KoCSSFontInfo(), const QString &text="", quint32 xRes=72, quint32 yRes=72, bool disableFontMatching=false, const QString &language=QString())
facesForCSSValues This selects a font with fontconfig using the given values. If "text" is not empty ...
QSharedPointer< KoFFWWSConverter > converter() const
QSharedPointer< KoFFWWSConverter > fontFamilyConverter
QSharedPointer< KoFontChangeTracker > changeTracker
~Private()=default
QScopedPointer< Private > d
std::optional< KoFontFamilyWWSRepresentation > representationByFamilyName(const QString &familyName) const
representationByFamilyName This simplifies retrieving the representation for a given font family.
static int32_t loadFlagsForFace(FT_Face face, bool isHorizontal=true, int32_t loadFlags=0, const KoSvgText::TextRendering rendering=KoSvgText::RenderingAuto)
static QFont::Style slantMode(FT_FaceSP face)
slantMode testing the slant mode can be annoying, so this is a convenience function to return the sla...
FT_LibrarySP library()
QHash< QString, FcFontSetSP > & sets()
KoCSSFontInfo getCssDataForPostScriptName(const QString postScriptName, QString *foundPostScriptName)
std::optional< QString > wwsNameByFamilyName(const QString familyName) const
QHash< QString, QVector< KoFFWWSConverter::FontFileEntry > > & suggestedFileNames()
QSharedPointer< KoFontChangeTracker > fontChangeTracker() const
QThreadStorage< QSharedPointer< ThreadData > > m_data
QHash< QString, FcPatternSP > & patterns()
FcConfigSP m_config
QList< KoFontFamilyWWSRepresentation > collectRepresentations() const
collectRepresentations
bool addFontFilePathToRegistry(const QString &path)
addFontFilePathToRegistry This adds a font file to the registry. Right now only used by unittests.
KoSvgText::FontMetrics fontMetricsForCSSValues(KoCSSFontInfo info=KoCSSFontInfo(), const bool isHorizontal=true, const KoSvgText::TextRendering rendering=KoSvgText::RenderingAuto, const QString &text="", quint32 xRes=72, quint32 yRes=72, bool disableFontMatching=false, const QString &language=QString())
static KoFontRegistry * instance()
QHash< QString, KoSvgText::FontMetrics > & fontMetrics()
bool addFontFileDirectoryToRegistry(const QString &path)
addFontFileDirectoryToRegistry This adds a directory of font files to the registry....
static KoSvgText::FontMetrics generateFontMetrics(FT_FaceSP face, bool isHorizontal=true, QString script=QString(), const KoSvgText::TextRendering rendering=KoSvgText::RenderingAuto)
QHash< QString, FT_FaceSP > & typeFaces()
FcConfigSP config() const
KoFontRegistry(QObject *parent=nullptr)
static QString getApplicationRoot()
static QString saveLocation(const QString &type, const QString &suffix=QString(), bool create=true)
static QString scriptTagForQLocaleScript(QLocale::Script script)
static bool qFuzzyCompare(half p1, half p2)
#define KIS_ASSERT_X(cond, where, what)
Definition kis_assert.h:40
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
@ BaselineAlphabetic
Use 'romn' or the baseline for LCG scripts.
Definition KoSvgText.h:225
@ BaselineCentral
Use the center between the ideographic over and under.
Definition KoSvgText.h:231
@ RenderingGeometricPrecision
Definition KoSvgText.h:318
@ RenderingAuto
Definition KoSvgText.h:315
@ RenderingOptimizeSpeed
Definition KoSvgText.h:316
The KoCSSFontInfo class Convenience struct to make it easier to use KoFontRegistry....
QMap< QString, double > computedAxisSettings() const
QStringList families
double fontSizeAdjust
QMap< QString, double > axisSettings
QFont::Style slantMode
QHash< QString, FcPatternSP > m_patterns
QHash< QString, QVector< KoFFWWSConverter::FontFileEntry > > m_suggestedFiles
QHash< QString, FT_FaceSP > m_faces
QHash< QString, FcFontSetSP > m_fontSets
QHash< QString, KoSvgText::FontMetrics > m_fontMetrics
The FontMetrics class A class to keep track of a variety of font metrics. Note that values are in Fre...
Definition KoSvgText.h:327
qint32 ideographicCenterBaseline
default baseline for vertical, centered between over and under.
Definition KoSvgText.h:347
qint32 ideographicUnderBaseline
location of ideographic under baseline from origin, may fall back to descender.
Definition KoSvgText.h:346
void setBaselineValueByTag(const QString &tag, int32_t value)
qint32 lineGap
additional linegap between consecutive lines.
Definition KoSvgText.h:341
qint32 zeroAdvance
Advance of the character '0', CSS Unit 'ch', defaults to 0.5 em in horizontal and 1....
Definition KoSvgText.h:330
qint32 ideographicAdvance
Advance of the character '水' (U+6C34), CSS Unit ic, defaults to 1 em.
Definition KoSvgText.h:332
qint32 ideographicOverBaseline
location of ideographic over baseline from origin.
Definition KoSvgText.h:349
void setMetricsValueByTag(const QLatin1String &tag, int32_t value)
qint32 fontSize
Currently set size, CSS unit 'em'.
Definition KoSvgText.h:329
qint32 caretRun
These are only used to determine the caret slant proportion.
Definition KoSvgText.h:362
qint32 descender
distance for origin to bottom.
Definition KoSvgText.h:340
qint32 ascender
distance from origin to top.
Definition KoSvgText.h:339
qint32 capHeight
Height of capital letters, defaults to ascender.
Definition KoSvgText.h:335
void offsetMetricsToNewOrigin(const Baseline baseline)
bool isVertical
Different fontMetrics count between vertical and horizontal.
Definition KoSvgText.h:328
qint32 spaceAdvance
Advance of the character ' ', used by tabs.
Definition KoSvgText.h:331