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
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
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