Krita Source Code Documentation
Loading...
Searching...
No Matches
KoFontRegistry Class Reference

The KoFontRegistry class A wrapper around a freetype library. More...

#include <KoFontRegistry.h>

+ Inheritance diagram for KoFontRegistry:

Classes

struct  ThreadData
 

Public Member Functions

QList< KoFontFamilyWWSRepresentationcollectRepresentations () const
 collectRepresentations
 
FcConfigSP config () const
 
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.
 
QSharedPointer< KoFFWWSConverterconverter () const
 
void debugConverter ()
 
std::vector< FT_FaceSPfacesForCSSValues (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 and disableFontMatching is false, it will try to select fallback fonts as well.
 
FT_FaceSP fallbackFont ()
 fallbackFont
 
QSharedPointer< KoFontChangeTrackerfontChangeTracker () const
 
QHash< QString, KoSvgText::FontMetrics > & fontMetrics ()
 
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())
 
KoCSSFontInfo getCssDataForPostScriptName (const QString postScriptName, QString *foundPostScriptName)
 
 KoFontRegistry (QObject *parent=nullptr)
 
FT_LibrarySP library ()
 
QHash< QString, FcPatternSP > & patterns ()
 
 Private ()
 
bool reloadConverter ()
 
std::optional< KoFontFamilyWWSRepresentationrepresentationByFamilyName (const QString &familyName) const
 representationByFamilyName This simplifies retrieving the representation for a given font family.
 
QHash< QString, FcFontSetSP > & sets ()
 
QHash< QString, QVector< KoFFWWSConverter::FontFileEntry > > & suggestedFileNames ()
 
QHash< QString, FT_FaceSP > & typeFaces ()
 
void updateConfig ()
 
std::optional< QString > wwsNameByFamilyName (const QString familyName) const
 
 ~KoFontRegistry ()
 
 ~Private ()=default
 

Static Public Member Functions

static KoSvgText::FontMetrics generateFontMetrics (FT_FaceSP face, bool isHorizontal=true, QString script=QString(), const KoSvgText::TextRendering rendering=KoSvgText::RenderingAuto)
 
static KoFontRegistryinstance ()
 
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 slant mode.
 

Private Slots

void updateConfig ()
 

Private Member Functions

bool addFontFileDirectoryToRegistry (const QString &path)
 addFontFileDirectoryToRegistry This adds a directory of font files to the registry. Right now only used by unittests.
 
bool addFontFilePathToRegistry (const QString &path)
 addFontFilePathToRegistry This adds a font file to the registry. Right now only used by unittests.
 
void initialize ()
 
 Q_DISABLE_COPY (KoFontRegistry)
 
- Private Member Functions inherited from Private
 Private (KisCanvas2 *c)
 

Private Attributes

QSharedPointer< KoFontChangeTrackerchangeTracker
 
QScopedPointer< Privated
 
QSharedPointer< KoFFWWSConverterfontFamilyConverter
 
FcConfigSP m_config
 
QThreadStorage< QSharedPointer< ThreadData > > m_data
 
- Private Attributes inherited from Private
KisCanvas2canvas
 
int displayedFrame
 
int intendedFrame
 

Friends

class SvgTextCursorTest
 
class TestSvgText
 

Detailed Description

The KoFontRegistry class A wrapper around a freetype library.

This class abstract away loading freetype faces from css values. Internally it uses fontconfig to get the filename and then loads the freetype face within a mutex-lock.

It also provides a configuration function to handle all the size and variation axis values.

Definition at line 72 of file KoFontRegistry.cpp.

Constructor & Destructor Documentation

◆ ~Private()

KoFontRegistry::~Private ( )
default

◆ KoFontRegistry()

KoFontRegistry::KoFontRegistry ( QObject * parent = nullptr)

Definition at line 291 of file KoFontRegistry.cpp.

292 : QObject(parent)
293 , d(new Private())
294{
295 connect(d->fontChangeTracker().data(), SIGNAL(sigUpdateConfig()), this, SLOT(updateConfig()), Qt::UniqueConnection);
296}
QScopedPointer< Private > d

References d, and updateConfig().

◆ ~KoFontRegistry()

KoFontRegistry::~KoFontRegistry ( )

Definition at line 298 of file KoFontRegistry.cpp.

298 {
299}

Member Function Documentation

◆ addFontFileDirectoryToRegistry()

bool KoFontRegistry::addFontFileDirectoryToRegistry ( const QString & path)
private

addFontFileDirectoryToRegistry This adds a directory of font files to the registry. Right now only used by unittests.

Parameters
paththe path of the directory.
Returns
whether it was successful.

Definition at line 1247 of file KoFontRegistry.cpp.

1248{
1249 const QByteArray utfData = path.toUtf8();
1250 const FcChar8 *vals = reinterpret_cast<const FcChar8 *>(utfData.data());
1251 bool success = false;
1252 if (FcConfigAppFontAddDir(d->config().data(), vals)) {
1253 success = d->reloadConverter();
1254 }
1255 return success;
1256}

References d.

◆ addFontFilePathToRegistry()

bool KoFontRegistry::addFontFilePathToRegistry ( const QString & path)
private

addFontFilePathToRegistry This adds a font file to the registry. Right now only used by unittests.

Parameters
paththe path of the font file.
Returns
Whether adding the font file was successful.

Definition at line 1236 of file KoFontRegistry.cpp.

1237{
1238 const QByteArray utfData = path.toUtf8();
1239 const FcChar8 *vals = reinterpret_cast<const FcChar8 *>(utfData.data());
1240 bool success = false;
1241 if (FcConfigAppFontAddFile(d->config().data(), vals)) {
1242 success = d->reloadConverter();
1243 }
1244 return success;
1245}

References d.

◆ collectRepresentations()

QList< KoFontFamilyWWSRepresentation > KoFontRegistry::collectRepresentations ( ) const

collectRepresentations

Returns
a list of Width/Weight/Slant font family representations.

Definition at line 763 of file KoFontRegistry.cpp.

764{
765 return d->converter()->collectFamilies();
766}

References d.

◆ config()

FcConfigSP KoFontRegistry::config ( ) const
inline

Definition at line 199 of file KoFontRegistry.cpp.

200 {
201 return m_config;
202 }
FcConfigSP m_config

◆ configureFaces()

bool KoFontRegistry::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.

Sometimes setting the size can get quite tricky (like with bitmap fonts), so this convenience function handles all that.

Returns
whether the configuration was successful.

There's something peculiar about this: Var fonts can change their x-height depending on the optical size selected. Meaning that the x-height adjusted size could be wrong depending on the font and its x-height choices. There's no reasonable way to fix this beyond cycling, and it is questionable whether the font-size-adjust property is even meant to be that specific.

Definition at line 618 of file KoFontRegistry.cpp.

624{
625 int errorCode = 0;
626 const qreal ftFontUnit = 64.0;
627 const qreal finalRes = qMin(xRes, yRes);
628 const qreal scaleToPixel = finalRes / 72;
629 Q_FOREACH (const FT_FaceSP &face, faces) {
630
631 // set the charmap, by default Freetype will select a Unicode charmap, but when there's none, it doesn't set any and we need to select one manually.
632 if (!face->charmap) {
633 FT_CharMap map = nullptr;
634 for (int i = 0; i < face->num_charmaps; i++) {
635 FT_CharMap m = face->charmaps[i];
636 if (!m) {
637 continue;
638 }
639
640 if (!map) {
641 map = m;
642 continue;
643 }
644
645 switch (m->encoding) {
646 case FT_ENCODING_UNICODE:
647 map = m;
648 break;
649 case FT_ENCODING_APPLE_ROMAN:
650 case FT_ENCODING_ADOBE_LATIN_1:
651 if (!map || map->encoding != FT_ENCODING_UNICODE) {
652 map = m;
653 }
654 break;
655 case FT_ENCODING_ADOBE_CUSTOM:
656 case FT_ENCODING_MS_SYMBOL:
657 if (!map || (map->encoding != FT_ENCODING_UNICODE && map->encoding != FT_ENCODING_APPLE_ROMAN && map->encoding != FT_ENCODING_ADOBE_LATIN_1)) {
658 map = m;
659 }
660 default:
661 break;
662 }
663 }
664 FT_Set_Charmap(face.data(), map);
665 }
666
667 qreal adjustedSize = size;
668
669 if (!FT_IS_SCALABLE(face)) {
670 const qreal fontSizePixels = size * ftFontUnit * scaleToPixel;
671 qreal sizeDelta = 0;
672 int selectedIndex = -1;
673
674 for (int i = 0; i < face->num_fixed_sizes; i++) {
675 const qreal newDelta = qAbs((fontSizePixels)-face->available_sizes[i].x_ppem);
676 if (newDelta < sizeDelta || i == 0) {
677 selectedIndex = i;
678 sizeDelta = newDelta;
679 }
680 }
681
682 if (selectedIndex >= 0) {
683 if (FT_HAS_COLOR(face)) {
684 const FT_Fixed scale = static_cast<FT_Fixed>(65535. * qreal(fontSizePixels)
685 / qreal(face->available_sizes[selectedIndex].x_ppem));
686 FT_Matrix matrix;
687 matrix.xx = scale;
688 matrix.xy = 0;
689 matrix.yx = 0;
690 matrix.yy = scale;
691 FT_Vector v;
692 FT_Set_Transform(face.data(), &matrix, &v);
693 }
694 errorCode = FT_Select_Size(face.data(), selectedIndex);
695 }
696 } else {
697 errorCode = FT_Set_Char_Size(face.data(), static_cast<FT_F26Dot6>(size * ftFontUnit), 0, xRes, yRes);
698 hb_font_t_sp font(hb_ft_font_create_referenced(face.data()));
699 hb_position_t xHeight = 0;
700 hb_ot_metrics_get_position(font.data(), HB_OT_METRICS_TAG_X_HEIGHT, &xHeight);
701 if (xHeight > 0 && fontSizeAdjust > 0) {
702 qreal aspect = xHeight / (size * ftFontUnit * scaleToPixel);
703 adjustedSize = (fontSizeAdjust / aspect) * (size);
704 errorCode = FT_Set_Char_Size(face.data(),
705 static_cast<FT_F26Dot6>(adjustedSize * ftFontUnit),
706 0,
707 xRes,
708 yRes);
709 }
710 }
711
712 QMap<FT_Tag, qreal> tags;
713 Q_FOREACH (const QString &tagName, axisSettings.keys()) {
714 if (tagName.size() == 4) {
715 const QByteArray utfData = tagName.toUtf8();
716 const char *tag = utfData.data();
717 if (tagName == "opsz" && !qFuzzyCompare(adjustedSize, size)) {
726 tags.insert(FT_MAKE_TAG(tag[0], tag[1], tag[2], tag[3]), adjustedSize);
727 } else {
728 tags.insert(FT_MAKE_TAG(tag[0], tag[1], tag[2], tag[3]), axisSettings.value(tagName));
729 }
730 }
731 }
732 const FT_Tag ITALIC_TAG = FT_MAKE_TAG('i', 't', 'a', 'l');
733 const FT_Tag SLANT_TAG = FT_MAKE_TAG('s', 'l', 'n', 't');
734 const int axisMultiplier = 65535;
735 if (FT_HAS_MULTIPLE_MASTERS(face)) {
736 FT_MM_Var *amaster = nullptr;
737 FT_Get_MM_Var(face.data(), &amaster);
738 // note: this only works for opentype, as it uses
739 // tag-based-selection.
740 std::vector<FT_Fixed> designCoords(amaster->num_axis);
741 for (FT_UInt i = 0; i < amaster->num_axis; i++) {
742 FT_Var_Axis axis = amaster->axis[i];
743 designCoords[i] = axis.def;
744 if (tags.contains(axis.tag)) {
745 designCoords[i] = qBound(axis.minimum, long(tags.value(axis.tag) * axisMultiplier), axis.maximum);
746 } else if (axis.tag == SLANT_TAG
747 && !tags.contains(SLANT_TAG)
748 && tags.value(ITALIC_TAG, 0) > 0) {
749 designCoords[i] = qBound(axis.minimum, long(-11.0 * axisMultiplier), axis.maximum);
750 } else if (axis.tag == ITALIC_TAG
751 && !tags.contains(ITALIC_TAG)
752 && !qFuzzyCompare(tags.value(SLANT_TAG, 0), 0)) {
753 designCoords[i] = qBound(axis.minimum, long(1.0 * axisMultiplier), axis.maximum);
754 }
755 }
756 FT_Set_Var_Design_Coordinates(face.data(), amaster->num_axis, designCoords.data());
757 FT_Done_MM_Var(d->library().data(), amaster);
758 }
759 }
760 return (errorCode == 0);
761}
qreal v
const QString SLANT_TAG
const QString ITALIC_TAG
static bool qFuzzyCompare(half p1, half p2)
int size(const Forest< T > &forest)
Definition KisForest.h:1232

References d, KisLibraryResourcePointer< T, P >::data(), ITALIC_TAG, qFuzzyCompare(), SLANT_TAG, and v.

◆ converter()

QSharedPointer< KoFFWWSConverter > KoFontRegistry::converter ( ) const
inline

Definition at line 242 of file KoFontRegistry.cpp.

242 {
243 return fontFamilyConverter;
244 }
QSharedPointer< KoFFWWSConverter > fontFamilyConverter

◆ debugConverter()

void KoFontRegistry::debugConverter ( )
inline

Definition at line 278 of file KoFontRegistry.cpp.

278 {
279 fontFamilyConverter->debugInfo();
280 }

◆ facesForCSSValues()

std::vector< FT_FaceSP > KoFontRegistry::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 and disableFontMatching is false, it will try to select fallback fonts as well.

Returns
a vector of loaded FT_Faces, the "lengths" vector will be filled with the lengths of consecutive characters a face can be set on.

Definition at line 336 of file KoFontRegistry.cpp.

342{
343 QString modifications = modificationsString(info, xRes, yRes);
344
346 const QString suggestedHash = info.families.join("+") + ":" + modifications;
347 auto entry = d->suggestedFileNames().find(suggestedHash);
348 if (entry != d->suggestedFileNames().end()) {
349 candidates = entry.value();
350 } else {
351 candidates = d->converter()->candidatesForCssValues(info,
352 xRes,
353 yRes);
354 d->suggestedFileNames().insert(suggestedHash, candidates);
355 }
356
358 lengths.clear();
359
360 if (disableFontMatching && !candidates.isEmpty()) {
361 fonts.append(candidates.first());
362 lengths.append(text.size());
363 } else {
364
365 FcPatternSP p(FcPatternCreate());
366 Q_FOREACH (const QString &family, info.families) {
367 QByteArray utfData = family.toUtf8();
368 const FcChar8 *vals = reinterpret_cast<FcChar8 *>(utfData.data());
369 FcPatternAddString(p.data(), FC_FAMILY, vals);
370 }
371
372 {
373 QByteArray fallbackBuf = QString("sans-serif").toUtf8();
374 FcValue fallback;
375 fallback.type = FcTypeString;
376 fallback.u.s = reinterpret_cast<FcChar8 *>(fallbackBuf.data());
377 FcPatternAddWeak(p.data(), FC_FAMILY, fallback, true);
378 }
379
380 for (int i = 0; i < candidates.size(); i++) {
381 KoFFWWSConverter::FontFileEntry file = candidates.at(i);
382 QByteArray utfData = file.fileName.toUtf8();
383 const FcChar8 *vals = reinterpret_cast<FcChar8 *>(utfData.data());
384 FcPatternAddString(p.data(), FC_FILE, vals);
385 FcPatternAddInteger(p.data(), FC_INDEX, file.fontIndex);
386 }
387
388 if (info.slantMode == QFont::StyleItalic) {
389 FcPatternAddInteger(p.data(), FC_SLANT, FC_SLANT_ITALIC);
390 } else if (info.slantMode == QFont::StyleOblique) {
391 FcPatternAddInteger(p.data(), FC_SLANT, FC_SLANT_OBLIQUE);
392 } else {
393 FcPatternAddInteger(p.data(), FC_SLANT, FC_SLANT_ROMAN);
394 }
395 FcPatternAddInteger(p.data(), FC_WEIGHT, FcWeightFromOpenType(info.weight));
396 FcPatternAddInteger(p.data(), FC_WIDTH, info.width);
397
398 double pixelSize = info.size*(qMin(xRes, yRes)/72.0);
399 FcPatternAddDouble(p.data(), FC_PIXEL_SIZE, pixelSize);
400
401 FcConfigSubstitute(nullptr, p.data(), FcMatchPattern);
402 FcDefaultSubstitute(p.data());
403
404
405 p = [&]() {
406 const FcChar32 hash = FcPatternHash(p.data());
407 QString patternHash = info.families.join("+")+QString::number(hash);
408 // FCPatternHash breaks down when there's multiple family names that start
409 // with the same letter and are the same length (Butcherman and Babylonica, Eater and Elsie, all 4 on google fonts).
410 const auto oldPattern = d->patterns().find(patternHash);
411 if (oldPattern != d->patterns().end()) {
412 return oldPattern.value();
413 } else {
414 d->patterns().insert(patternHash, p);
415 return p;
416 }
417 }();
418
419 FcResult result = FcResultNoMatch;
420 FcCharSetSP charSet;
421 FcFontSetSP fontSet = [&]() -> FcFontSetSP {
422 const FcChar32 hash = FcPatternHash(p.data());
423 QString patternHash = info.families.join("+")+QString::number(hash);
424 const auto set = d->sets().find(patternHash);
425
426 if (set != d->sets().end()) {
427 return set.value();
428 } else {
429 FcCharSet *cs = nullptr;
430 FcFontSetSP avalue(FcFontSort(FcConfigGetCurrent(), p.data(), FcFalse, &cs, &result));
431 charSet.reset(cs);
432 d->sets().insert(patternHash, avalue);
433 return avalue;
434 }
435 }();
436
437 if (text.isEmpty()) {
438 for (int j = 0; j < fontSet->nfont; j++) {
439 if (std::optional<KoFFWWSConverter::FontFileEntry> font = getFontFileEntry(fontSet->fonts[j])) {
440 fonts.append(std::move(*font));
441 lengths.append(0);
442 break;
443 }
444 }
445 } else {
446 FcCharSet *set = nullptr;
447 QVector<int> familyValues(text.size());
448 QVector<int> fallbackMatchValues(text.size());
449 familyValues.fill(-1);
450 fallbackMatchValues.fill(-1);
451
452 // First, we're going to split up the text into graphemes. This is both
453 // because the css spec requires it, but also because of why the css
454 // spec requires it: graphemes' parts should not end up in separate
455 // runs, which they will if they get assigned different fonts,
456 // potentially breaking ligatures and emoji sequences.
458
459 // Parse over the fonts and graphemes and try to see if we can get the
460 // best match for a given grapheme.
461 for (int i = 0; i < fontSet->nfont; i++) {
462
463 double fontsize = 0.0;
464 FcBool isScalable = false;
465 FcPatternGetBool(fontSet->fonts[i], FC_SCALABLE, 0, &isScalable);
466 FcPatternGetDouble(fontSet->fonts[i], FC_PIXEL_SIZE, 0, &fontsize);
467 if (!isScalable && pixelSize != fontsize) {
468 // For some reason, FC will sometimes consider a smaller font pixel-size
469 // to be more relevant to the requested pattern than a bigger one. This
470 // skips those fonts, but it does mean that such pixel fonts would not
471 // be used for fallback.
472 continue;
473 }
474 if (FcPatternGetCharSet(fontSet->fonts[i], FC_CHARSET, 0, &set) == FcResultMatch) {
475 int index = 0;
476 Q_FOREACH (const QString &grapheme, graphemes) {
477
478 // Don't worry about matching controls directly,
479 // as they are not important to font-selection (and many
480 // fonts have no glyph entry for these)
481 if (const uint first = firstCharUcs4(grapheme); QChar::category(first) == QChar::Other_Control
482 || QChar::category(first) == QChar::Other_Format) {
483 index += grapheme.size();
484 continue;
485 }
486 int familyIndex = -1;
487 if (familyValues.at(index) == -1) {
488 int fallbackMatch = fallbackMatchValues.at(index);
489 Q_FOREACH (uint unicode, grapheme.toUcs4()) {
490 if (FcCharSetHasChar(set, unicode)) {
491 familyIndex = i;
492 if (fallbackMatch < 0) {
493 fallbackMatch = i;
494 }
495 } else {
496 familyIndex = -1;
497 break;
498 }
499 }
500 for (int k = 0; k < grapheme.size(); k++) {
501 familyValues[index + k] = familyIndex;
502 fallbackMatchValues[index + k] = fallbackMatch;
503 }
504 }
505 index += grapheme.size();
506 }
507 if (!familyValues.contains(-1)) {
508 break;
509 }
510 }
511 }
512
513 // Remove the -1 entries.
514 if (familyValues.contains(-1)) {
515 int value = -1;
516 Q_FOREACH (const int currentValue, familyValues) {
517 if (currentValue != value) {
518 value = currentValue;
519 break;
520 }
521 }
522 value = qMax(0, value);
523 for (int i = 0; i < familyValues.size(); i++) {
524 if (familyValues.at(i) < 0) {
525 if (fallbackMatchValues.at(i) < 0) {
526 familyValues[i] = value;
527 } else {
528 familyValues[i] = fallbackMatchValues.at(i);
529 }
530 } else {
531 value = familyValues.at(i);
532 }
533 }
534 }
535
536 // Get the filenames and lengths for the entries.
537 int length = 0;
538 int startIndex = 0;
539 int lastIndex = familyValues.at(0);
541 if (std::optional<KoFFWWSConverter::FontFileEntry> f = getFontFileEntry(fontSet->fonts[lastIndex])) {
542 font = std::move(*f);
543 }
544 for (int i = 0; i < familyValues.size(); i++) {
545 if (lastIndex != familyValues.at(i)) {
546 lengths.append(text.mid(startIndex, length).size());
547 fonts.append(font);
548 startIndex = i;
549 length = 0;
550 lastIndex = familyValues.at(i);
551 if (std::optional<KoFFWWSConverter::FontFileEntry> f = getFontFileEntry(fontSet->fonts[lastIndex])) {
552 font = std::move(*f);
553 }
554 }
555 length += 1;
556 }
557 if (length > 0) {
558 lengths.append(text.mid(startIndex, length).size());
559 fonts.append(font);
560 }
561 }
562 }
563
564 std::vector<FT_FaceSP> faces;
565
566 KIS_ASSERT_X(lengths.size() == fonts.size(),
567 "KoFontRegistry",
568 QString("Fonts and lengths don't have the same size. Fonts: %1. Length: %2")
569 .arg(fonts.size(), lengths.size()).toLatin1());
570
571 for (int i = 0; i < lengths.size(); i++) {
572 KoFFWWSConverter::FontFileEntry font = fonts.at(i);
573 // For some reason, FontConfig will sometimes return the wrong file index.
574 // We can in this case select the appropriate index, which should work as tcc collections share glyphs.
575 for (int j = 0; j < candidates.size(); j++) {
576 if (font.fileName == candidates.at(j).fileName) {
577 font.fontIndex = candidates.at(j).fontIndex;
578 break;
579 }
580 }
581
582 const QString fontCacheEntry = font.fileName + "#" + QString::number(font.fontIndex) + "#" + modifications;
583 auto entry = d->typeFaces().find(fontCacheEntry);
584 if (entry != d->typeFaces().end()) {
585 faces.emplace_back(entry.value());
586 } else {
587 FT_Face f = nullptr;
588 QByteArray utfData = font.fileName.toUtf8();
589 FT_Error err = FT_New_Face(d->library().data(), utfData.data(), font.fontIndex, &f);
590 if (err == 0) {
591 FT_FaceSP face(f);
592 configureFaces({face}, info.size, info.fontSizeAdjust, xRes, yRes, info.computedAxisSettings());
593 faces.emplace_back(face);
594 d->typeFaces().insert(fontCacheEntry, face);
595 } else {
596 qWarning() << "Failed to load font" << font.fileName << "in font registry, FreeType error:" << err;
597 if (faces.size() > 0) {
598 faces.emplace_back(faces.at(faces.size()-1));
599 } else {
600 const QMap<QString, qreal> axisSettings;
601 if (d->fallbackFont().data()->size->metrics.x_ppem == 0) {
602 // if the font has not been configured yet, it's ppem is set to 0, so we test that and configure it.
603 configureFaces({d->fallbackFont()}, 12, 1.0, 72, 72, axisSettings);
604 }
605
606 faces.emplace_back(d->fallbackFont());
607 }
608 }
609 }
610 }
611 if (faces.size() == 0) {
612 lengths = QVector<int>();
613 }
614
615 return faces;
616}
qreal length(const QPointF &vec)
Definition Ellipse.cc:82
float value(const T *src, size_t ch)
const Params2D p
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
static QStringList textToUnicodeGraphemeClusters(const QString &text, const QString &langCode)
textToUnicodeGraphemes In letters like Ã…, the amount of unicode codpoints can be 1,...
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.
#define KIS_ASSERT_X(cond, where, what)
Definition kis_assert.h:40
QMap< QString, double > computedAxisSettings() const
QStringList families
double fontSizeAdjust
QFont::Style slantMode

References KoCSSFontInfo::computedAxisSettings(), configureFaces(), d, KoCSSFontInfo::families, KoFFWWSConverter::FontFileEntry::fileName, firstCharUcs4(), KoFFWWSConverter::FontFileEntry::fontIndex, KoCSSFontInfo::fontSizeAdjust, getFontFileEntry(), KIS_ASSERT_X, length(), modificationsString(), p, KisLibraryResourcePointer< T, P >::reset(), KoCSSFontInfo::size, KoCSSFontInfo::slantMode, KoCssTextUtils::textToUnicodeGraphemeClusters(), value(), KoCSSFontInfo::weight, and KoCSSFontInfo::width.

◆ fallbackFont()

FT_FaceSP KoFontRegistry::fallbackFont ( )
inline

fallbackFont

Returns
a fall back font for when no other font was found.

Definition at line 208 of file KoFontRegistry.cpp.

208 {
209 if (!m_data.hasLocalData())
210 initialize();
211 if (!m_data.localData()->m_fallbackFont.data()) {
212
213 FcPatternSP p(FcPatternCreate());
214 QByteArray fallbackBuf = QString("sans-serif").toUtf8();
215 FcValue fallback;
216 fallback.type = FcTypeString;
217 fallback.u.s = reinterpret_cast<FcChar8 *>(fallbackBuf.data());
218 FcPatternAddWeak(p.data(), FC_FAMILY, fallback, true);
219 FcConfigSubstitute(nullptr, p.data(), FcMatchPattern);
220 FcDefaultSubstitute(p.data());
221 FcResult result = FcResultNoMatch;
222 FcCharSet *cs = nullptr;
223 FcFontSetSP fontSet(FcFontSort(FcConfigGetCurrent(), p.data(), FcFalse, &cs, &result));
224
225 KIS_ASSERT_X(fontSet->nfont > 0, "No fallback fonts in font registry", "Cannot load an fallback font, no fonts found");
226
227 for (int j = 0; j < fontSet->nfont; j++) {
228 if(std::optional<KoFFWWSConverter::FontFileEntry> fontFileEntry = getFontFileEntry(fontSet->fonts[j]) ) {
229 QByteArray utfData = fontFileEntry->fileName.toUtf8();
230 FT_Face f = nullptr;
231 FT_Error err = FT_New_Face(library().data(), utfData.data(), fontFileEntry->fontIndex, &f);
232 if (err == 0) {
233 m_data.localData()->m_fallbackFont.reset(f);
234 break;
235 }
236 }
237 }
238 }
239 return m_data.localData()->m_fallbackFont;
240 }
FT_LibrarySP library()
QThreadStorage< QSharedPointer< ThreadData > > m_data

References getFontFileEntry(), KIS_ASSERT_X, and p.

◆ fontChangeTracker()

QSharedPointer< KoFontChangeTracker > KoFontRegistry::fontChangeTracker ( ) const
inline

Definition at line 246 of file KoFontRegistry.cpp.

246 {
247 return changeTracker;
248 }
QSharedPointer< KoFontChangeTracker > changeTracker

◆ fontMetrics()

QHash< QString, KoSvgText::FontMetrics > & KoFontRegistry::fontMetrics ( )
inline

Definition at line 272 of file KoFontRegistry.cpp.

272 {
273 if (!m_data.hasLocalData())
274 initialize();
275 return m_data.localData()->m_fontMetrics;
276 }

◆ fontMetricsForCSSValues()

KoSvgText::FontMetrics KoFontRegistry::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() )

Definition at line 814 of file KoFontRegistry.cpp.

819{
820 const QString suggestedHash = info.families.join(",")+":"+modificationsString(info, xRes, yRes)+language;
822 auto entry = d->fontMetrics().find(suggestedHash);
823 if (entry != d->fontMetrics().end()) {
824 metrics = entry.value();
825 } else {
826 QVector<int> lengths;
827 const std::vector<FT_FaceSP> faces = KoFontRegistry::instance()->facesForCSSValues(
828 lengths,
829 info,
830 text,
831 xRes,
832 yRes,
833 disableFontMatching,
834 language);
835
836 if (faces.empty()) return KoSvgText::FontMetrics(info.size, isHorizontal);
837 metrics = KoFontRegistry::generateFontMetrics(faces.front(), isHorizontal, KoWritingSystemUtils::scriptTagForQLocaleScript(QLocale(language).script()), rendering);
838 d->fontMetrics().insert(suggestedHash, metrics);
839 }
840 return metrics;
841}
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 ...
static KoFontRegistry * instance()
static KoSvgText::FontMetrics generateFontMetrics(FT_FaceSP face, bool isHorizontal=true, QString script=QString(), const KoSvgText::TextRendering rendering=KoSvgText::RenderingAuto)
static QString scriptTagForQLocaleScript(QLocale::Script script)
The FontMetrics class A class to keep track of a variety of font metrics. Note that values are in Fre...
Definition KoSvgText.h:327

References d, facesForCSSValues(), KoCSSFontInfo::families, generateFontMetrics(), instance(), modificationsString(), KoWritingSystemUtils::scriptTagForQLocaleScript(), and KoCSSFontInfo::size.

◆ generateFontMetrics()

KoSvgText::FontMetrics KoFontRegistry::generateFontMetrics ( FT_FaceSP face,
bool isHorizontal = true,
QString script = QString(),
const KoSvgText::TextRendering rendering = KoSvgText::RenderingAuto )
static

BUG:517717, some vertical fonts have nonsense values for the caret. We can assume a multi-master font is relatively modern and doesn't have this issue.

There's 3 different definitions of the so-called vertical metrics, that is, the ascender and descender for horizontally laid out script. WinAsc & Desc, HHAE asc&desc, and OS/2... we need the last one, but harfbuzz doesn't return it unless there's a flag set in the font, which is missing in a lot of fonts that were from the transitional period, like Deja Vu Sans. Hence we need to get the OS/2 table and calculate the values manually (and fall back in various ways).

https://www.w3.org/TR/css-inline-3/#ascent-descent https://www.w3.org/TR/CSS2/visudet.html#sTypoAscender https://wiki.inkscape.org/wiki/Text_Rendering_Notes#Ascent_and_Descent

Related HB issue: https://github.com/harfbuzz/harfbuzz/issues/1920

Definition at line 843 of file KoFontRegistry.cpp.

844{
846 hb_direction_t dir = isHorizontal? HB_DIRECTION_LTR: HB_DIRECTION_TTB;
847 QLatin1String scriptLatin1(script.toLatin1());
848 hb_script_t scriptTag = hb_script_from_string(scriptLatin1.data(), scriptLatin1.size());
849 hb_tag_t otScriptTag = HB_OT_TAG_DEFAULT_SCRIPT;
850 hb_tag_t otLangTag = HB_OT_TAG_DEFAULT_LANGUAGE;
851 uint maxCount = 1;
852 hb_ot_tags_from_script_and_language(scriptTag, nullptr, &maxCount, &otScriptTag, &maxCount, &otLangTag);
853 hb_font_t_sp font(hb_ft_font_create_referenced(face.data()));
854 hb_face_t_sp hbFace(hb_ft_face_create_referenced(face.data()));
855
856 const FT_Int32 faceLoadFlags = loadFlagsForFace(face.data(), isHorizontal, 0, rendering);
857
858 metrics.isVertical = !isHorizontal;
859 // Fontsize and advances.
860 metrics.fontSize = isHorizontal? face.data()->size->metrics.y_ppem*64.0: face.data()->size->metrics.x_ppem*64.0;
861
862 uint charIndex = FT_Get_Char_Index(face.data(), 0x20);
863 if(charIndex > 0) {
864 if (FT_Load_Glyph(face.data(), charIndex, faceLoadFlags) == FT_Err_Ok) {
865 metrics.spaceAdvance = isHorizontal? face.data()->glyph->advance.x: face.data()->glyph->advance.y;
866 } else {
867 metrics.spaceAdvance = isHorizontal? metrics.fontSize/2: metrics.fontSize;
868 }
869 } else {
870 metrics.spaceAdvance = isHorizontal? metrics.fontSize/2: metrics.fontSize;
871 }
872
873 charIndex = FT_Get_Char_Index(face.data(), '0');
874 if(charIndex > 0) {
875 if(FT_Load_Glyph(face.data(), charIndex, faceLoadFlags) == FT_Err_Ok) {
876 metrics.zeroAdvance = isHorizontal? face.data()->glyph->advance.x: face.data()->glyph->advance.y;
877 } else {
878 metrics.zeroAdvance = isHorizontal? metrics.fontSize/2: metrics.fontSize;
879 }
880 } else {
881 metrics.zeroAdvance = isHorizontal? metrics.fontSize/2: metrics.fontSize;
882 }
883
884 bool isIdeographic = false;
885 charIndex = FT_Get_Char_Index(face.data(), 0x6C34);
886 if(charIndex > 0) {
887 if (FT_Load_Glyph(face.data(), charIndex, faceLoadFlags) == FT_Err_Ok) {
888 metrics.ideographicAdvance = isHorizontal? face.data()->glyph->advance.x: face.data()->glyph->advance.y;
889 } else {
890 metrics.ideographicAdvance = metrics.fontSize;
891 }
892 isIdeographic = true;
893 } else {
894 metrics.ideographicAdvance = metrics.fontSize;
895 }
896
897 const bool useFallback = hb_version_atleast(4, 0, 0);
898
899 // metrics
900
901 QVector<hb_ot_metrics_tag_t> metricTags ({
902 HB_OT_METRICS_TAG_X_HEIGHT,
903 HB_OT_METRICS_TAG_CAP_HEIGHT,
904 HB_OT_METRICS_TAG_SUPERSCRIPT_EM_X_OFFSET,
905 HB_OT_METRICS_TAG_SUPERSCRIPT_EM_Y_OFFSET,
906 HB_OT_METRICS_TAG_SUBSCRIPT_EM_X_OFFSET,
907 HB_OT_METRICS_TAG_SUBSCRIPT_EM_Y_OFFSET,
908 HB_OT_METRICS_TAG_UNDERLINE_OFFSET,
909 HB_OT_METRICS_TAG_UNDERLINE_SIZE,
910 HB_OT_METRICS_TAG_STRIKEOUT_OFFSET,
911 HB_OT_METRICS_TAG_STRIKEOUT_SIZE,
912 });
913
914 if (isHorizontal) {
915 metricTags.append(HB_OT_METRICS_TAG_HORIZONTAL_CARET_RISE);
916 metricTags.append(HB_OT_METRICS_TAG_HORIZONTAL_CARET_RUN);
917 metricTags.append(HB_OT_METRICS_TAG_HORIZONTAL_CARET_OFFSET);
918 metrics.caretRise = 1;
919 metrics.caretRun = 0;
920 } else {
921 metricTags.append(HB_OT_METRICS_TAG_VERTICAL_CARET_RISE);
922 metricTags.append(HB_OT_METRICS_TAG_VERTICAL_CARET_RUN);
923 metricTags.append(HB_OT_METRICS_TAG_VERTICAL_CARET_OFFSET);
924 metrics.caretRise = 0;
925 metrics.caretRun = 1;
926 }
927
928 for (auto it = metricTags.begin(); it!= metricTags.end(); it++) {
929 char c[4];
930 hb_tag_to_string(*it, c);
931 const QLatin1String tagName(c, 4);
932 hb_position_t origin = 0;
933 if (useFallback) {
934 hb_ot_metrics_get_position_with_fallback(font.data(), *it, &origin);
935 metrics.setMetricsValueByTag(tagName, origin);
936 } else if (hb_ot_metrics_get_position(font.data(), *it, &origin)) {
937 metrics.setMetricsValueByTag(tagName, origin);
938 }
939 }
940
943 if (!FT_HAS_MULTIPLE_MASTERS(face) && !isHorizontal) {
944 metrics.caretRise = 0;
945 metrics.caretRun = 1;
946 }
947
948 // Baselines.
949
950 const QVector<hb_ot_layout_baseline_tag_t> baselines ({
951 HB_OT_LAYOUT_BASELINE_TAG_ROMAN,
952 HB_OT_LAYOUT_BASELINE_TAG_HANGING,
953 HB_OT_LAYOUT_BASELINE_TAG_IDEO_FACE_BOTTOM_OR_LEFT,
954 HB_OT_LAYOUT_BASELINE_TAG_IDEO_FACE_TOP_OR_RIGHT,
955 HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_BOTTOM_OR_LEFT,
956 HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_TOP_OR_RIGHT,
957 HB_OT_LAYOUT_BASELINE_TAG_MATH
958 });
959
960 QMap<QString, qint32> baselineVals;
961
962 for (auto it = baselines.begin(); it!= baselines.end(); it++) {
963 hb_position_t origin = 0;
964 if (hb_ot_layout_get_baseline(font.data(), *it, dir, otScriptTag, otLangTag, &origin)) {
965 std::vector<char> c(4);
966 hb_tag_to_string(*it, c.data());
967 baselineVals.insert(QString::fromLatin1(c.data(), 4), origin);
968 }
969 }
970
971 //--- ascender/descender/linegap, and also ideographic em-box calculatation ---//
972
973 hb_position_t ascender = 0;
974 hb_position_t descender = 0;
975 hb_position_t lineGap = 0;
976
991 TT_OS2 *os2Table = nullptr;
992 os2Table = (TT_OS2*)FT_Get_Sfnt_Table(face.data(), FT_SFNT_OS2);
993 if (os2Table) {
994 int yscale = face.data()->size->metrics.y_scale;
995
996 ascender = FT_MulFix(os2Table->sTypoAscender, yscale);
997 descender = FT_MulFix(os2Table->sTypoDescender, yscale);
998 lineGap = FT_MulFix(os2Table->sTypoLineGap, yscale);
999 }
1000
1001 constexpr unsigned USE_TYPO_METRICS = 1u << 7;
1002 if (!os2Table || os2Table->version == 0xFFFFU || !(os2Table->fsSelection & USE_TYPO_METRICS)) {
1003 hb_position_t altAscender = 0;
1004 hb_position_t altDescender = 0;
1005 hb_position_t altLineGap = 0;
1006 if (!hb_ot_metrics_get_position(font.data(), HB_OT_METRICS_TAG_HORIZONTAL_ASCENDER, &altAscender)) {
1007 altAscender = face.data()->ascender;
1008 }
1009 if (!hb_ot_metrics_get_position(font.data(), HB_OT_METRICS_TAG_HORIZONTAL_DESCENDER, &altDescender)) {
1010 altDescender = face.data()->descender;
1011 }
1012 if (!hb_ot_metrics_get_position(font.data(), HB_OT_METRICS_TAG_HORIZONTAL_LINE_GAP, &altLineGap)) {
1013 altLineGap = face.data()->height - (altAscender-altDescender);
1014 }
1015
1016 // Some fonts have sTypo metrics that are too small compared
1017 // to the HHEA values which make the default line height too
1018 // tight (e.g. Microsoft JhengHei, Source Han Sans), so we
1019 // compare them and take the ones that are larger.
1020 if (!os2Table || (altAscender - altDescender + altLineGap) > (ascender - descender + lineGap)) {
1021 ascender = altAscender;
1022 descender = altDescender;
1023 lineGap = altLineGap;
1024 }
1025 }
1026
1027 // Because the ideographic em-box is so important to SVG 2 vertical line calculation,
1028 // I am manually calculating it here, following
1029 // https://learn.microsoft.com/en-us/typography/opentype/spec/baselinetags#ideographic-em-box
1030 const QString ideoBottom("ideo");
1031 const QString ideoTop("idtp");
1032 const QString alphabetic("romn");
1033 const QString ideoCenter("Idce");
1034 const QString hang("hang");
1035 const QString math("math");
1036
1037 if (baselineVals.keys().contains(ideoBottom) && !baselineVals.keys().contains(ideoTop)) {
1038 baselineVals.insert(ideoTop, baselineVals.value(ideoBottom)+metrics.fontSize);
1039 } else if (!baselineVals.keys().contains(ideoBottom) && baselineVals.keys().contains(ideoTop)) {
1040 baselineVals.insert(ideoBottom, baselineVals.value(ideoTop)-metrics.fontSize);
1041 } else if (!baselineVals.keys().contains(ideoBottom) && !baselineVals.keys().contains(ideoTop)){
1042
1043 if (!isIdeographic && isHorizontal) {
1044 hb_blob_t_sp dLang(hb_ot_meta_reference_entry( hbFace.data() , HB_OT_META_TAG_DESIGN_LANGUAGES));
1045 uint length = hb_blob_get_length(dLang.data());
1046 QByteArray ba(hb_blob_get_data(dLang.data(), &length), length);
1047
1048 const QString designLang = QString::fromLatin1(ba).trimmed();
1049
1050 if (!designLang.isEmpty()) {
1051 // This assumes a font where there's design language metadata, but no water glyph.
1052 // In theory could happen with the non-han cjk fonts.
1053 const QStringList cjkScripts {
1055 KoWritingSystemUtils::scriptTagForQLocaleScript(QLocale::JapaneseScript),
1056 KoWritingSystemUtils::scriptTagForQLocaleScript(QLocale::TraditionalHanScript),
1057 KoWritingSystemUtils::scriptTagForQLocaleScript(QLocale::SimplifiedHanScript),
1058 KoWritingSystemUtils::scriptTagForQLocaleScript(QLocale::HanWithBopomofoScript),
1059 KoWritingSystemUtils::scriptTagForQLocaleScript(QLocale::BopomofoScript),
1061 KoWritingSystemUtils::scriptTagForQLocaleScript(QLocale::KatakanaScript),
1062 KoWritingSystemUtils::scriptTagForQLocaleScript(QLocale::HiraganaScript),
1063 };
1064 Q_FOREACH (const QString cjk, cjkScripts) {
1065 if (cjk.isEmpty()) continue;
1066 if (designLang.contains(cjk.trimmed())) {
1067 isIdeographic = true;
1068 break;
1069 }
1070 }
1071 }
1072 }
1073
1074 if (isIdeographic && isHorizontal) {
1075 baselineVals.insert(ideoTop, ascender);
1076 baselineVals.insert(ideoBottom, descender);
1077 if (!baselineVals.keys().contains(alphabetic)) {
1078 baselineVals.insert(alphabetic, 0);
1079 }
1080 if (!baselineVals.keys().contains(hang)) {
1081 baselineVals.insert(hang, baselineVals.value(alphabetic)+metrics.fontSize*0.6);
1082 }
1083 } else if (isHorizontal) {
1084 const qreal alphabeticMultiplier = qreal(ascender+descender)/metrics.fontSize;
1085 qint32 top = qMax(baselineVals.value(hang, metrics.capHeight), qint32(qRound(ascender*alphabeticMultiplier)));
1086 baselineVals.insert(ideoTop, top);
1087 baselineVals.insert(ideoBottom, top-metrics.fontSize);
1088 } else {
1089 baselineVals.insert(ideoTop, metrics.fontSize);
1090 baselineVals.insert(ideoBottom, 0);
1091 if (!baselineVals.keys().contains(alphabetic)) {
1092 const qreal alphabeticMultiplier = qreal(ascender+descender)/metrics.fontSize;
1093 baselineVals.insert(alphabetic, (-descender)*alphabeticMultiplier);
1094 }
1095 if (!baselineVals.keys().contains(hang)) {
1096 baselineVals.insert(hang, baselineVals.value(alphabetic)+metrics.fontSize*0.6);
1097 }
1098 }
1099 }
1100 baselineVals.insert(ideoCenter, (baselineVals.value(ideoTop) + baselineVals.value(ideoBottom))/2);
1101 if (!isHorizontal && !baselineVals.keys().contains(math)) {
1102 baselineVals.insert(math, baselineVals.value(ideoCenter));
1103 }
1104
1105 if (useFallback) {
1106 for (auto it = baselines.begin(); it!= baselines.end(); it++) {
1107 char c[4];
1108 hb_tag_to_string(*it, c);
1109 const QString tagName = QString::fromLatin1(c, 4);
1110 if (!baselineVals.keys().contains(tagName)) {
1111 hb_position_t origin = 0;
1112 hb_ot_layout_get_baseline_with_fallback(font.data(), *it, dir, otScriptTag, otLangTag, &origin);
1113 baselineVals.insert(tagName, origin);
1114 }
1115 }
1116 }
1117
1118 Q_FOREACH(const QString key, baselineVals.keys()) {
1119 metrics.setBaselineValueByTag(key, baselineVals.value(key));
1120 }
1121
1122 if (!isHorizontal) {
1123 // SVG 2.0 explicitly requires vertical ascent/descent to be tied to the ideographic em box.
1124 ascender = metrics.ideographicOverBaseline;
1125 descender = metrics.ideographicUnderBaseline;
1126
1127 // Default microsoft CJK fonts have the vertical ascent and descent be the same as the horizontal
1128 // ascent and descent, so we 'normalize' the ascender and descender to be half the total height.
1129 qreal height = ascender - descender;
1130 ascender = height*0.5;
1131 descender = -ascender;
1132 }
1133 if (ascender == 0 && descender == 0) {
1134 ascender = face->size->metrics.ascender;
1135 descender = face->size->metrics.descender;
1136 qreal height = ascender - descender;
1137 lineGap = face->size->metrics.height - height;
1138 if (!isHorizontal) {
1139 ascender = height * 0.5;
1140 descender = -ascender;
1141 }
1142 }
1143 metrics.ascender = ascender;
1144 metrics.descender = descender;
1145 metrics.lineGap = lineGap;
1146
1147 if (isHorizontal) {
1149 } else {
1150 // because we normalize, we need to add here.
1151 metrics.ascender += metrics.ideographicCenterBaseline;
1152 metrics.descender += metrics.ideographicCenterBaseline;
1154 }
1155
1156 return metrics;
1157}
static int32_t loadFlagsForFace(FT_Face face, bool isHorizontal=true, int32_t loadFlags=0, const KoSvgText::TextRendering rendering=KoSvgText::RenderingAuto)
@ 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
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

References KoSvgText::FontMetrics::ascender, KoSvgText::BaselineAlphabetic, KoSvgText::BaselineCentral, KoSvgText::FontMetrics::capHeight, KoSvgText::FontMetrics::caretRise, KoSvgText::FontMetrics::caretRun, KisLibraryResourcePointer< T, P >::data(), KoSvgText::FontMetrics::descender, KoSvgText::FontMetrics::fontSize, KoSvgText::FontMetrics::ideographicAdvance, KoSvgText::FontMetrics::ideographicCenterBaseline, KoSvgText::FontMetrics::ideographicOverBaseline, KoSvgText::FontMetrics::ideographicUnderBaseline, KoSvgText::FontMetrics::isVertical, length(), KoSvgText::FontMetrics::lineGap, loadFlagsForFace(), KoSvgText::FontMetrics::offsetMetricsToNewOrigin(), KoWritingSystemUtils::scriptTagForQLocaleScript(), KoSvgText::FontMetrics::setBaselineValueByTag(), KoSvgText::FontMetrics::setMetricsValueByTag(), KoSvgText::FontMetrics::spaceAdvance, and KoSvgText::FontMetrics::zeroAdvance.

◆ getCssDataForPostScriptName()

KoCSSFontInfo KoFontRegistry::getCssDataForPostScriptName ( const QString postScriptName,
QString * foundPostScriptName )

Definition at line 1197 of file KoFontRegistry.cpp.

1198{
1199 KoCSSFontInfo info;
1200 FcPatternSP p(FcPatternCreate());
1201 QByteArray utfData = postScriptName.toUtf8();
1202 const FcChar8 *vals = reinterpret_cast<FcChar8 *>(utfData.data());
1203 FcPatternAddString(p.data(), FC_POSTSCRIPT_NAME, vals);
1204 FcDefaultSubstitute(p.data());
1205
1206 FcResult result = FcResultNoMatch;
1207 FcPatternSP match(FcFontMatch(FcConfigGetCurrent(), p.data(), &result));
1208 if (result != FcResultNoMatch) {
1209 FcChar8 *fileValue = nullptr;
1210 if (FcPatternGetString(match.data(), FC_FAMILY, 0, &fileValue) == FcResultMatch) {
1211 info.families << QString(reinterpret_cast<char *>(fileValue));
1212 } else {
1213 info.families << postScriptName;
1214 }
1215 if (FcPatternGetString(match.data(), FC_POSTSCRIPT_NAME, 0, &fileValue) == FcResultMatch) {
1216 *foundPostScriptName = QString(reinterpret_cast<char *>(fileValue));
1217 } else {
1218 *foundPostScriptName = postScriptName;
1219 }
1220 int value;
1221 if (FcPatternGetInteger(match.data(), FC_WEIGHT, 0, &value) == FcResultMatch) {
1222 info.weight = FcWeightToOpenType(value);
1223 }
1224 if (FcPatternGetInteger(match.data(), FC_WIDTH, 0, &value) == FcResultMatch) {
1225 info.width = value;
1226 }
1227 if (FcPatternGetInteger(match.data(), FC_SLANT, 0, &value) == FcResultMatch) {
1228 info.slantMode = value != FC_SLANT_ROMAN? value != FC_SLANT_ITALIC? QFont::StyleOblique: QFont::StyleItalic: QFont::StyleNormal;
1229 }
1230 } else {
1231 info.families << postScriptName;
1232 }
1233 return info;
1234}
The KoCSSFontInfo class Convenience struct to make it easier to use KoFontRegistry....

References KisLibraryResourcePointer< T, P >::data(), KoCSSFontInfo::families, p, KoCSSFontInfo::slantMode, value(), KoCSSFontInfo::weight, and KoCSSFontInfo::width.

◆ initialize()

void KoFontRegistry::initialize ( )
inlineprivate

Definition at line 96 of file KoFontRegistry.cpp.

97 {
98 if (!m_data.hasLocalData()) {
99 FT_Library lib = nullptr;
100 FT_Error error = FT_Init_FreeType(&lib);
101 if (error) {
102 errorFlake << "Error with initializing FreeType library:" << error << "Current thread:" << QThread::currentThread()
103 << "GUI thread:" << qApp->thread();
104 } else {
106 }
107 }
108 }
#define errorFlake
Definition FlakeDebug.h:17

References errorFlake.

◆ instance()

KoFontRegistry * KoFontRegistry::instance ( )
static

Definition at line 301 of file KoFontRegistry.cpp.

302{
303 return s_instance;
304}

◆ library()

FT_LibrarySP KoFontRegistry::library ( )
inline

Definition at line 171 of file KoFontRegistry.cpp.

172 {
173 if (!m_data.hasLocalData())
174 initialize();
175 return m_data.localData()->m_library;
176 }

◆ loadFlagsForFace()

int32_t KoFontRegistry::loadFlagsForFace ( FT_Face face,
bool isHorizontal = true,
int32_t loadFlags = 0,
const KoSvgText::TextRendering rendering = KoSvgText::RenderingAuto )
static

Definition at line 1159 of file KoFontRegistry.cpp.

1160{
1161 FT_Int32 faceLoadFlags = loadFlags;
1162
1163 if (rendering == KoSvgText::RenderingGeometricPrecision || rendering == KoSvgText::RenderingAuto) {
1164 // without load_no_hinting, the advance and offset will be rounded
1165 // to nearest pixel, which we don't want as we're using the vector
1166 // outline.
1167 faceLoadFlags |= FT_LOAD_NO_HINTING;
1168
1169 // Disable embedded bitmaps because they _do not_ follow geometric
1170 // precision, but is focused on legibility.
1171 // This does not affect bitmap-only fonts.
1172 faceLoadFlags |= FT_LOAD_NO_BITMAP;
1173 } else if (rendering == KoSvgText::RenderingOptimizeSpeed) {
1174 faceLoadFlags |= FT_LOAD_TARGET_MONO;
1175 } else {
1176 // When using hinting, sometimes the bounding box does not encompass the
1177 // drawn glyphs properly.
1178 // The default hinting works best for vertical, while the 'light'
1179 // hinting mode works best for horizontal.
1180 if (isHorizontal) {
1181 faceLoadFlags |= FT_LOAD_TARGET_LIGHT;
1182 }
1183 }
1184 if (FT_HAS_COLOR(face)) {
1185 faceLoadFlags |= FT_LOAD_COLOR;
1186 }
1187 if (!isHorizontal && FT_HAS_VERTICAL(face)) {
1188 faceLoadFlags |= FT_LOAD_VERTICAL_LAYOUT;
1189 }
1190 if (!FT_IS_SCALABLE(face)) {
1191 // This is needed for the CBDT version of Noto Color Emoji
1192 faceLoadFlags &= ~FT_LOAD_NO_BITMAP;
1193 }
1194 return faceLoadFlags;
1195}
@ RenderingGeometricPrecision
Definition KoSvgText.h:318
@ RenderingAuto
Definition KoSvgText.h:315
@ RenderingOptimizeSpeed
Definition KoSvgText.h:316

References KoSvgText::RenderingAuto, KoSvgText::RenderingGeometricPrecision, and KoSvgText::RenderingOptimizeSpeed.

◆ patterns()

QHash< QString, FcPatternSP > & KoFontRegistry::patterns ( )
inline

Definition at line 178 of file KoFontRegistry.cpp.

179 {
180 if (!m_data.hasLocalData())
181 initialize();
182 return m_data.localData()->m_patterns;
183 }

◆ Private()

KoFontRegistry::Private ( )
inline

This loads the fontconfig configured on the host Linux system.

Theoretically, it can cause some version inconsistency problems if the version of fontconfig on the host differs from the version of fontconfig shipped with Krita. But we estimate such risks as negligible.

Setup the change tracker.

Definition at line 111 of file KoFontRegistry.cpp.

112 {
113 FcConfig *config = FcConfigCreate();
114 KIS_ASSERT(config && "No Fontconfig support available");
115
124 if (qgetenv("FONTCONFIG_PATH").isEmpty()) {
125 QDir appdir("/etc/fonts");
126 if (QFile::exists(appdir.absoluteFilePath("fonts.conf"))) {
127 qputenv("FONTCONFIG_PATH", QFile::encodeName(QDir::toNativeSeparators(appdir.absolutePath())));
128 } else {
129 // Otherwise use default, which is defined in src/fcinit.c , windows and macos
130 // default locations *are* defined in fontconfig's meson build system.
131 appdir = QDir(KoResourcePaths::getApplicationRoot() +"/etc/fonts");
132 if (QFile::exists(appdir.absoluteFilePath("fonts.conf"))) {
133 qputenv("FONTCONFIG_PATH", QFile::encodeName(QDir::toNativeSeparators(appdir.absolutePath())));
134 }
135 }
136 }
137 if (!FcConfigParseAndLoad(config, nullptr, FcTrue)) {
138 errorFlake << "Failed loading the Fontconfig configuration";
139 config = FcConfigGetCurrent();
140 } else {
141 FcConfigSetCurrent(config);
142 }
144
145 // Add fonts folder from resource folder.
146 const QString fontsFolder = KoResourcePaths::saveLocation("data", "/fonts/", true);
147 FcConfigAppFontAddDir(m_config.data(), reinterpret_cast<const FcChar8 *>(fontsFolder.toUtf8().data()));
148
150 FcStrList *list = FcConfigGetFontDirs(m_config.data());
151 FcStrListFirst(list);
152 FcChar8 *dirString = FcStrListNext(list);
153 QString path = QString::fromUtf8(reinterpret_cast<char *>(dirString));
154 QStringList paths;
155 while (!path.isEmpty()) {
156 paths.append(path);
157 FcStrListNext(list);
158 dirString = FcStrListNext(list);
159 path = QString::fromUtf8(reinterpret_cast<char *>(dirString));
160 }
161 paths.append(fontsFolder);
162 FcStrListDone(list);
163 changeTracker.reset(new KoFontChangeTracker(paths));
164 changeTracker->resetChangeTracker();
165
167 }
The KoFontChangeTracker class This class keeps track of the paths FontConfig is looking at,...
FcConfigSP config() const
static QString getApplicationRoot()
static QString saveLocation(const QString &type, const QString &suffix=QString(), bool create=true)
#define KIS_ASSERT(cond)
Definition kis_assert.h:33

References KisLibraryResourcePointer< T, P >::data(), errorFlake, KoResourcePaths::getApplicationRoot(), KIS_ASSERT, KisLibraryResourcePointer< T, P >::reset(), and KoResourcePaths::saveLocation().

◆ Q_DISABLE_COPY()

KoFontRegistry::Q_DISABLE_COPY ( KoFontRegistry )
private

◆ reloadConverter()

bool KoFontRegistry::reloadConverter ( )
inline

Definition at line 250 of file KoFontRegistry.cpp.

250 {
252 FcObjectSet *objectSet = FcObjectSetBuild(FC_FAMILY, FC_FILE, FC_INDEX, FC_LANG, FC_CHARSET, nullptr);
253 FcFontSetSP allFonts(FcFontList(m_config.data(), FcPatternCreate(), objectSet));
254
255 for (int j = 0; j < allFonts->nfont; j++) {
256 fontFamilyConverter->addFontFromPattern(allFonts->fonts[j], library());
257 }
258 fontFamilyConverter->addGenericFamily("serif");
259 fontFamilyConverter->addGenericFamily("sans-serif");
260 fontFamilyConverter->addGenericFamily("monospace");
261 fontFamilyConverter->sortIntoWWSFamilies();
262 return true;
263 }
The KoFFWWSConverter class This class takes fontconfig patterns and tries to sort them into a hierarc...

References KisLibraryResourcePointer< T, P >::data().

◆ representationByFamilyName()

std::optional< KoFontFamilyWWSRepresentation > KoFontRegistry::representationByFamilyName ( const QString & familyName) const

representationByFamilyName This simplifies retrieving the representation for a given font family.

Parameters
familyName- the familyName associated with the font.
found- bool to check for success.
Returns
the font family.

Definition at line 768 of file KoFontRegistry.cpp.

769{
770 return d->converter()->representationByFamilyName(familyName);
771}

References d.

◆ sets()

QHash< QString, FcFontSetSP > & KoFontRegistry::sets ( )
inline

Definition at line 185 of file KoFontRegistry.cpp.

186 {
187 if (!m_data.hasLocalData())
188 initialize();
189 return m_data.localData()->m_fontSets;
190 }

◆ slantMode()

QFont::Style KoFontRegistry::slantMode ( FT_FaceSP face)
static

slantMode testing the slant mode can be annoying, so this is a convenience function to return the slant mode.

Parameters
facethe freetype face to test for.
Returns
the slant mode, can be normal, italic or oblique.

Is italic

Is truly regular (instead of italic or oblique)

Definition at line 783 of file KoFontRegistry.cpp.

784{
785 constexpr unsigned OS2_ITALIC = 1u << 0;
786 constexpr unsigned OS2_REGULAR = 1u << 6;
787 constexpr unsigned OS2_OBLIQUE = 1u << 9; // Is an oblique instead of an italic.
788
789 if (FT_IS_SFNT(face.data())) {
790 hb_font_t_sp hbFont(hb_ft_font_create_referenced(face.data()));
791 bool isItalic = hb_style_get_value(hbFont.data(), HB_STYLE_TAG_ITALIC) > 0;
792 bool isOblique = false;
793
794 if (FT_HAS_MULTIPLE_MASTERS(face)) {
795 isOblique = hb_style_get_value(hbFont.data(), HB_STYLE_TAG_SLANT_ANGLE) != 0;
796 } else {
797 TT_OS2 *os2Table = nullptr;
798 os2Table = (TT_OS2*)FT_Get_Sfnt_Table(face.data(), FT_SFNT_OS2);
799 if (os2Table) {
800 isItalic = os2Table->fsSelection & OS2_ITALIC;
801 isOblique = os2Table->fsSelection & OS2_OBLIQUE;
802 }
803 if (os2Table->fsSelection & OS2_REGULAR) {
804 isItalic = false;
805 isOblique = false;
806 }
807 }
808 return isItalic? isOblique? QFont::StyleOblique: QFont::StyleItalic: QFont::StyleNormal;
809 } else {
810 return face->style_flags & FT_STYLE_FLAG_ITALIC? QFont::StyleItalic: QFont::StyleNormal;
811 }
812}
constexpr unsigned OS2_OBLIQUE
Indicates that the given font is primarily a WWS family and requires no further processing.
constexpr unsigned OS2_ITALIC
constexpr unsigned OS2_REGULAR
Is bold.

References KisLibraryResourcePointer< T, P >::data(), OS2_ITALIC, OS2_OBLIQUE, and OS2_REGULAR.

◆ suggestedFileNames()

QHash< QString, QVector< KoFFWWSConverter::FontFileEntry > > & KoFontRegistry::suggestedFileNames ( )
inline

Definition at line 265 of file KoFontRegistry.cpp.

266 {
267 if (!m_data.hasLocalData())
268 initialize();
269 return m_data.localData()->m_suggestedFiles;
270 }

◆ typeFaces()

QHash< QString, FT_FaceSP > & KoFontRegistry::typeFaces ( )
inline

Definition at line 192 of file KoFontRegistry.cpp.

193 {
194 if (!m_data.hasLocalData())
195 initialize();
196 return m_data.localData()->m_faces;
197 }

◆ updateConfig() [1/2]

void KoFontRegistry::updateConfig ( )
inline

Definition at line 282 of file KoFontRegistry.cpp.

282 {
283 if (FcConfigBuildFonts(m_config.data())) {
286 changeTracker->resetChangeTracker();
287 }
288 }
void updateFontStorage()
This updates the "fontregistry" storage. Called when the font directories change;.
static KisResourceLocator * instance()

References KisLibraryResourcePointer< T, P >::data(), KisResourceLocator::instance(), and KisResourceLocator::updateFontStorage().

◆ updateConfig [2/2]

void KoFontRegistry::updateConfig ( )
privateslot

Update the config and reset the FontChangeListener.

◆ wwsNameByFamilyName()

std::optional< QString > KoFontRegistry::wwsNameByFamilyName ( const QString familyName) const

Definition at line 773 of file KoFontRegistry.cpp.

774{
775 return d->converter()->wwsNameByFamilyName(familyName);
776}

References d.

Friends And Related Symbol Documentation

◆ SvgTextCursorTest

friend class SvgTextCursorTest
friend

Definition at line 126 of file KoFontRegistry.h.

◆ TestSvgText

friend class TestSvgText
friend

Definition at line 125 of file KoFontRegistry.h.

Member Data Documentation

◆ changeTracker

QSharedPointer<KoFontChangeTracker> KoFontRegistry::changeTracker
private

Definition at line 77 of file KoFontRegistry.cpp.

◆ d

QScopedPointer<Private> KoFontRegistry::d
private

Definition at line 145 of file KoFontRegistry.h.

◆ fontFamilyConverter

QSharedPointer<KoFFWWSConverter> KoFontRegistry::fontFamilyConverter
private

Definition at line 76 of file KoFontRegistry.cpp.

◆ m_config

FcConfigSP KoFontRegistry::m_config
private

Definition at line 75 of file KoFontRegistry.cpp.

◆ m_data

QThreadStorage<QSharedPointer<ThreadData> > KoFontRegistry::m_data
private

Definition at line 94 of file KoFontRegistry.cpp.


The documentation for this class was generated from the following files: