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 1240 of file KoFontRegistry.cpp.

1241{
1242 const QByteArray utfData = path.toUtf8();
1243 const FcChar8 *vals = reinterpret_cast<const FcChar8 *>(utfData.data());
1244 bool success = false;
1245 if (FcConfigAppFontAddDir(d->config().data(), vals)) {
1246 success = d->reloadConverter();
1247 }
1248 return success;
1249}

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 1229 of file KoFontRegistry.cpp.

1230{
1231 const QByteArray utfData = path.toUtf8();
1232 const FcChar8 *vals = reinterpret_cast<const FcChar8 *>(utfData.data());
1233 bool success = false;
1234 if (FcConfigAppFontAddFile(d->config().data(), vals)) {
1235 success = d->reloadConverter();
1236 }
1237 return success;
1238}

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

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
941 // Baselines.
942
943 const QVector<hb_ot_layout_baseline_tag_t> baselines ({
944 HB_OT_LAYOUT_BASELINE_TAG_ROMAN,
945 HB_OT_LAYOUT_BASELINE_TAG_HANGING,
946 HB_OT_LAYOUT_BASELINE_TAG_IDEO_FACE_BOTTOM_OR_LEFT,
947 HB_OT_LAYOUT_BASELINE_TAG_IDEO_FACE_TOP_OR_RIGHT,
948 HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_BOTTOM_OR_LEFT,
949 HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_TOP_OR_RIGHT,
950 HB_OT_LAYOUT_BASELINE_TAG_MATH
951 });
952
953 QMap<QString, qint32> baselineVals;
954
955 for (auto it = baselines.begin(); it!= baselines.end(); it++) {
956 hb_position_t origin = 0;
957 if (hb_ot_layout_get_baseline(font.data(), *it, dir, otScriptTag, otLangTag, &origin)) {
958 std::vector<char> c(4);
959 hb_tag_to_string(*it, c.data());
960 baselineVals.insert(QString::fromLatin1(c.data(), 4), origin);
961 }
962 }
963
964 //--- ascender/descender/linegap, and also ideographic em-box calculatation ---//
965
966 hb_position_t ascender = 0;
967 hb_position_t descender = 0;
968 hb_position_t lineGap = 0;
969
984 TT_OS2 *os2Table = nullptr;
985 os2Table = (TT_OS2*)FT_Get_Sfnt_Table(face.data(), FT_SFNT_OS2);
986 if (os2Table) {
987 int yscale = face.data()->size->metrics.y_scale;
988
989 ascender = FT_MulFix(os2Table->sTypoAscender, yscale);
990 descender = FT_MulFix(os2Table->sTypoDescender, yscale);
991 lineGap = FT_MulFix(os2Table->sTypoLineGap, yscale);
992 }
993
994 constexpr unsigned USE_TYPO_METRICS = 1u << 7;
995 if (!os2Table || os2Table->version == 0xFFFFU || !(os2Table->fsSelection & USE_TYPO_METRICS)) {
996 hb_position_t altAscender = 0;
997 hb_position_t altDescender = 0;
998 hb_position_t altLineGap = 0;
999 if (!hb_ot_metrics_get_position(font.data(), HB_OT_METRICS_TAG_HORIZONTAL_ASCENDER, &altAscender)) {
1000 altAscender = face.data()->ascender;
1001 }
1002 if (!hb_ot_metrics_get_position(font.data(), HB_OT_METRICS_TAG_HORIZONTAL_DESCENDER, &altDescender)) {
1003 altDescender = face.data()->descender;
1004 }
1005 if (!hb_ot_metrics_get_position(font.data(), HB_OT_METRICS_TAG_HORIZONTAL_LINE_GAP, &altLineGap)) {
1006 altLineGap = face.data()->height - (altAscender-altDescender);
1007 }
1008
1009 // Some fonts have sTypo metrics that are too small compared
1010 // to the HHEA values which make the default line height too
1011 // tight (e.g. Microsoft JhengHei, Source Han Sans), so we
1012 // compare them and take the ones that are larger.
1013 if (!os2Table || (altAscender - altDescender + altLineGap) > (ascender - descender + lineGap)) {
1014 ascender = altAscender;
1015 descender = altDescender;
1016 lineGap = altLineGap;
1017 }
1018 }
1019
1020 // Because the ideographic em-box is so important to SVG 2 vertical line calculation,
1021 // I am manually calculating it here, following
1022 // https://learn.microsoft.com/en-us/typography/opentype/spec/baselinetags#ideographic-em-box
1023 const QString ideoBottom("ideo");
1024 const QString ideoTop("idtp");
1025 const QString alphabetic("romn");
1026 const QString ideoCenter("Idce");
1027 const QString hang("hang");
1028 const QString math("math");
1029
1030 if (baselineVals.keys().contains(ideoBottom) && !baselineVals.keys().contains(ideoTop)) {
1031 baselineVals.insert(ideoTop, baselineVals.value(ideoBottom)+metrics.fontSize);
1032 } else if (!baselineVals.keys().contains(ideoBottom) && baselineVals.keys().contains(ideoTop)) {
1033 baselineVals.insert(ideoBottom, baselineVals.value(ideoTop)-metrics.fontSize);
1034 } else if (!baselineVals.keys().contains(ideoBottom) && !baselineVals.keys().contains(ideoTop)){
1035
1036 if (!isIdeographic && isHorizontal) {
1037 hb_blob_t_sp dLang(hb_ot_meta_reference_entry( hbFace.data() , HB_OT_META_TAG_DESIGN_LANGUAGES));
1038 uint length = hb_blob_get_length(dLang.data());
1039 QByteArray ba(hb_blob_get_data(dLang.data(), &length), length);
1040
1041 const QString designLang = QString::fromLatin1(ba).trimmed();
1042
1043 if (!designLang.isEmpty()) {
1044 // This assumes a font where there's design language metadata, but no water glyph.
1045 // In theory could happen with the non-han cjk fonts.
1046 const QStringList cjkScripts {
1048 KoWritingSystemUtils::scriptTagForQLocaleScript(QLocale::JapaneseScript),
1049 KoWritingSystemUtils::scriptTagForQLocaleScript(QLocale::TraditionalHanScript),
1050 KoWritingSystemUtils::scriptTagForQLocaleScript(QLocale::SimplifiedHanScript),
1051 KoWritingSystemUtils::scriptTagForQLocaleScript(QLocale::HanWithBopomofoScript),
1052 KoWritingSystemUtils::scriptTagForQLocaleScript(QLocale::BopomofoScript),
1054 KoWritingSystemUtils::scriptTagForQLocaleScript(QLocale::KatakanaScript),
1055 KoWritingSystemUtils::scriptTagForQLocaleScript(QLocale::HiraganaScript),
1056 };
1057 Q_FOREACH (const QString cjk, cjkScripts) {
1058 if (cjk.isEmpty()) continue;
1059 if (designLang.contains(cjk.trimmed())) {
1060 isIdeographic = true;
1061 break;
1062 }
1063 }
1064 }
1065 }
1066
1067 if (isIdeographic && isHorizontal) {
1068 baselineVals.insert(ideoTop, ascender);
1069 baselineVals.insert(ideoBottom, descender);
1070 if (!baselineVals.keys().contains(alphabetic)) {
1071 baselineVals.insert(alphabetic, 0);
1072 }
1073 if (!baselineVals.keys().contains(hang)) {
1074 baselineVals.insert(hang, baselineVals.value(alphabetic)+metrics.fontSize*0.6);
1075 }
1076 } else if (isHorizontal) {
1077 const qreal alphabeticMultiplier = qreal(ascender+descender)/metrics.fontSize;
1078 qint32 top = qMax(baselineVals.value(hang, metrics.capHeight), qint32(qRound(ascender*alphabeticMultiplier)));
1079 baselineVals.insert(ideoTop, top);
1080 baselineVals.insert(ideoBottom, top-metrics.fontSize);
1081 } else {
1082 baselineVals.insert(ideoTop, metrics.fontSize);
1083 baselineVals.insert(ideoBottom, 0);
1084 if (!baselineVals.keys().contains(alphabetic)) {
1085 const qreal alphabeticMultiplier = qreal(ascender+descender)/metrics.fontSize;
1086 baselineVals.insert(alphabetic, (-descender)*alphabeticMultiplier);
1087 }
1088 if (!baselineVals.keys().contains(hang)) {
1089 baselineVals.insert(hang, baselineVals.value(alphabetic)+metrics.fontSize*0.6);
1090 }
1091 }
1092 }
1093 baselineVals.insert(ideoCenter, (baselineVals.value(ideoTop) + baselineVals.value(ideoBottom))/2);
1094 if (!isHorizontal && !baselineVals.keys().contains(math)) {
1095 baselineVals.insert(math, baselineVals.value(ideoCenter));
1096 }
1097
1098 if (useFallback) {
1099 for (auto it = baselines.begin(); it!= baselines.end(); it++) {
1100 char c[4];
1101 hb_tag_to_string(*it, c);
1102 const QString tagName = QString::fromLatin1(c, 4);
1103 if (!baselineVals.keys().contains(tagName)) {
1104 hb_position_t origin = 0;
1105 hb_ot_layout_get_baseline_with_fallback(font.data(), *it, dir, otScriptTag, otLangTag, &origin);
1106 baselineVals.insert(tagName, origin);
1107 }
1108 }
1109 }
1110
1111 Q_FOREACH(const QString key, baselineVals.keys()) {
1112 metrics.setBaselineValueByTag(key, baselineVals.value(key));
1113 }
1114
1115 if (!isHorizontal) {
1116 // SVG 2.0 explicitly requires vertical ascent/descent to be tied to the ideographic em box.
1117 ascender = metrics.ideographicOverBaseline;
1118 descender = metrics.ideographicUnderBaseline;
1119
1120 // Default microsoft CJK fonts have the vertical ascent and descent be the same as the horizontal
1121 // ascent and descent, so we 'normalize' the ascender and descender to be half the total height.
1122 qreal height = ascender - descender;
1123 ascender = height*0.5;
1124 descender = -ascender;
1125 }
1126 if (ascender == 0 && descender == 0) {
1127 ascender = face->size->metrics.ascender;
1128 descender = face->size->metrics.descender;
1129 qreal height = ascender - descender;
1130 lineGap = face->size->metrics.height - height;
1131 if (!isHorizontal) {
1132 ascender = height * 0.5;
1133 descender = -ascender;
1134 }
1135 }
1136 metrics.ascender = ascender;
1137 metrics.descender = descender;
1138 metrics.lineGap = lineGap;
1139
1140 if (isHorizontal) {
1142 } else {
1143 // because we normalize, we need to add here.
1144 metrics.ascender += metrics.ideographicCenterBaseline;
1145 metrics.descender += metrics.ideographicCenterBaseline;
1147 }
1148
1149 return metrics;
1150}
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 1190 of file KoFontRegistry.cpp.

1191{
1192 KoCSSFontInfo info;
1193 FcPatternSP p(FcPatternCreate());
1194 QByteArray utfData = postScriptName.toUtf8();
1195 const FcChar8 *vals = reinterpret_cast<FcChar8 *>(utfData.data());
1196 FcPatternAddString(p.data(), FC_POSTSCRIPT_NAME, vals);
1197 FcDefaultSubstitute(p.data());
1198
1199 FcResult result = FcResultNoMatch;
1200 FcPatternSP match(FcFontMatch(FcConfigGetCurrent(), p.data(), &result));
1201 if (result != FcResultNoMatch) {
1202 FcChar8 *fileValue = nullptr;
1203 if (FcPatternGetString(match.data(), FC_FAMILY, 0, &fileValue) == FcResultMatch) {
1204 info.families << QString(reinterpret_cast<char *>(fileValue));
1205 } else {
1206 info.families << postScriptName;
1207 }
1208 if (FcPatternGetString(match.data(), FC_POSTSCRIPT_NAME, 0, &fileValue) == FcResultMatch) {
1209 *foundPostScriptName = QString(reinterpret_cast<char *>(fileValue));
1210 } else {
1211 *foundPostScriptName = postScriptName;
1212 }
1213 int value;
1214 if (FcPatternGetInteger(match.data(), FC_WEIGHT, 0, &value) == FcResultMatch) {
1215 info.weight = FcWeightToOpenType(value);
1216 }
1217 if (FcPatternGetInteger(match.data(), FC_WIDTH, 0, &value) == FcResultMatch) {
1218 info.width = value;
1219 }
1220 if (FcPatternGetInteger(match.data(), FC_SLANT, 0, &value) == FcResultMatch) {
1221 info.slantMode = value != FC_SLANT_ROMAN? value != FC_SLANT_ITALIC? QFont::StyleOblique: QFont::StyleItalic: QFont::StyleNormal;
1222 }
1223 } else {
1224 info.families << postScriptName;
1225 }
1226 return info;
1227}
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 1152 of file KoFontRegistry.cpp.

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