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