340 quint32 yRes,
bool disableFontMatching,
341 const QString &language)
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();
351 candidates =
d->converter()->candidatesForCssValues(info,
354 d->suggestedFileNames().insert(suggestedHash, candidates);
360 if (disableFontMatching && !candidates.isEmpty()) {
361 fonts.append(candidates.first());
362 lengths.append(text.size());
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);
373 QByteArray fallbackBuf = QString(
"sans-serif").toUtf8();
375 fallback.type = FcTypeString;
376 fallback.u.s =
reinterpret_cast<FcChar8 *
>(fallbackBuf.data());
377 FcPatternAddWeak(
p.data(), FC_FAMILY, fallback,
true);
380 for (
int i = 0; i < candidates.size(); 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);
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);
393 FcPatternAddInteger(
p.data(), FC_SLANT, FC_SLANT_ROMAN);
395 FcPatternAddInteger(
p.data(), FC_WEIGHT, FcWeightFromOpenType(info.
weight));
396 FcPatternAddInteger(
p.data(), FC_WIDTH, info.
width);
398 double pixelSize = info.
size*(qMin(xRes, yRes)/72.0);
399 FcPatternAddDouble(
p.data(), FC_PIXEL_SIZE, pixelSize);
401 FcConfigSubstitute(
nullptr,
p.data(), FcMatchPattern);
402 FcDefaultSubstitute(
p.data());
406 const FcChar32 hash = FcPatternHash(
p.data());
407 QString patternHash = info.
families.join(
"+")+QString::number(hash);
410 const auto oldPattern =
d->patterns().find(patternHash);
411 if (oldPattern !=
d->patterns().end()) {
412 return oldPattern.value();
414 d->patterns().insert(patternHash,
p);
419 FcResult result = FcResultNoMatch;
422 const FcChar32 hash = FcPatternHash(
p.data());
423 QString patternHash = info.
families.join(
"+")+QString::number(hash);
424 const auto set =
d->sets().find(patternHash);
426 if (set !=
d->sets().end()) {
429 FcCharSet *cs =
nullptr;
430 FcFontSetSP avalue(FcFontSort(FcConfigGetCurrent(),
p.data(), FcFalse, &cs, &result));
432 d->sets().insert(patternHash, avalue);
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));
446 FcCharSet *set =
nullptr;
449 familyValues.fill(-1);
450 fallbackMatchValues.fill(-1);
461 for (
int i = 0; i < fontSet->nfont; i++) {
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) {
474 if (FcPatternGetCharSet(fontSet->fonts[i], FC_CHARSET, 0, &set) == FcResultMatch) {
476 Q_FOREACH (
const QString &grapheme, graphemes) {
481 if (
const uint first =
firstCharUcs4(grapheme); QChar::category(first) == QChar::Other_Control
482 || QChar::category(first) == QChar::Other_Format) {
483 index += grapheme.size();
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)) {
492 if (fallbackMatch < 0) {
500 for (
int k = 0; k < grapheme.size(); k++) {
501 familyValues[index + k] = familyIndex;
502 fallbackMatchValues[index + k] = fallbackMatch;
505 index += grapheme.size();
507 if (!familyValues.contains(-1)) {
514 if (familyValues.contains(-1)) {
516 Q_FOREACH (
const int currentValue, familyValues) {
517 if (currentValue !=
value) {
518 value = currentValue;
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;
528 familyValues[i] = fallbackMatchValues.at(i);
531 value = familyValues.at(i);
539 int lastIndex = familyValues.at(0);
541 if (std::optional<KoFFWWSConverter::FontFileEntry> f =
getFontFileEntry(fontSet->fonts[lastIndex])) {
542 font = std::move(*f);
544 for (
int i = 0; i < familyValues.size(); i++) {
545 if (lastIndex != familyValues.at(i)) {
546 lengths.append(text.mid(startIndex,
length).size());
550 lastIndex = familyValues.at(i);
551 if (std::optional<KoFFWWSConverter::FontFileEntry> f =
getFontFileEntry(fontSet->fonts[lastIndex])) {
552 font = std::move(*f);
558 lengths.append(text.mid(startIndex,
length).size());
564 std::vector<FT_FaceSP> faces;
568 QString(
"Fonts and lengths don't have the same size. Fonts: %1. Length: %2")
569 .arg(fonts.size(), lengths.size()).toLatin1());
571 for (
int i = 0; i < lengths.size(); i++) {
575 for (
int j = 0; j < candidates.size(); j++) {
576 if (font.
fileName == candidates.at(j).fileName) {
577 font.
fontIndex = candidates.at(j).fontIndex;
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());
588 QByteArray utfData = font.
fileName.toUtf8();
589 FT_Error err = FT_New_Face(
d->library().data(), utfData.data(), font.
fontIndex, &f);
593 faces.emplace_back(face);
594 d->typeFaces().insert(fontCacheEntry, face);
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));
600 const QMap<QString, qreal> axisSettings;
601 if (
d->fallbackFont().data()->size->metrics.x_ppem == 0) {
606 faces.emplace_back(
d->fallbackFont());
611 if (faces.size() == 0) {
620 qreal fontSizeAdjust,
623 const QMap<QString, qreal> &axisSettings)
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) {
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];
645 switch (m->encoding) {
646 case FT_ENCODING_UNICODE:
649 case FT_ENCODING_APPLE_ROMAN:
650 case FT_ENCODING_ADOBE_LATIN_1:
651 if (!map || map->encoding != FT_ENCODING_UNICODE) {
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)) {
664 FT_Set_Charmap(face.
data(), map);
667 qreal adjustedSize = size;
669 if (!FT_IS_SCALABLE(face)) {
670 const qreal fontSizePixels = size * ftFontUnit * scaleToPixel;
672 int selectedIndex = -1;
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) {
678 sizeDelta = newDelta;
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));
692 FT_Set_Transform(face.
data(), &matrix, &
v);
694 errorCode = FT_Select_Size(face.
data(), selectedIndex);
697 errorCode = FT_Set_Char_Size(face.
data(),
static_cast<FT_F26Dot6
>(size * ftFontUnit), 0, xRes, yRes);
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),
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);
728 tags.insert(FT_MAKE_TAG(tag[0], tag[1], tag[2], tag[3]), axisSettings.value(tagName));
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);
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);
749 designCoords[i] = qBound(axis.minimum,
long(-11.0 * axisMultiplier), axis.maximum);
752 && !qFuzzyCompare(tags.value(
SLANT_TAG, 0), 0)) {
753 designCoords[i] = qBound(axis.minimum,
long(1.0 * axisMultiplier), axis.maximum);
756 FT_Set_Var_Design_Coordinates(face.
data(), amaster->num_axis, designCoords.data());
757 FT_Done_MM_Var(
d->library().data(), amaster);
760 return (errorCode == 0);
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;
852 hb_ot_tags_from_script_and_language(scriptTag,
nullptr, &maxCount, &otScriptTag, &maxCount, &otLangTag);
860 metrics.
fontSize = isHorizontal? face.
data()->size->metrics.y_ppem*64.0: face.
data()->size->metrics.x_ppem*64.0;
862 uint charIndex = FT_Get_Char_Index(face.
data(), 0x20);
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;
873 charIndex = FT_Get_Char_Index(face.
data(),
'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;
884 bool isIdeographic =
false;
885 charIndex = FT_Get_Char_Index(face.
data(), 0x6C34);
887 if (FT_Load_Glyph(face.
data(), charIndex, faceLoadFlags) == FT_Err_Ok) {
892 isIdeographic =
true;
897 const bool useFallback = hb_version_atleast(4, 0, 0);
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,
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);
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);
928 for (
auto it = metricTags.begin(); it!= metricTags.end(); it++) {
930 hb_tag_to_string(*it, c);
931 const QLatin1String tagName(c, 4);
932 hb_position_t origin = 0;
934 hb_ot_metrics_get_position_with_fallback(font.
data(), *it, &origin);
936 }
else if (hb_ot_metrics_get_position(font.
data(), *it, &origin)) {
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
953 QMap<QString, qint32> baselineVals;
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);
966 hb_position_t ascender = 0;
967 hb_position_t descender = 0;
968 hb_position_t lineGap = 0;
984 TT_OS2 *os2Table =
nullptr;
985 os2Table = (TT_OS2*)FT_Get_Sfnt_Table(face.
data(), FT_SFNT_OS2);
987 int yscale = face.
data()->size->metrics.y_scale;
989 ascender = FT_MulFix(os2Table->sTypoAscender, yscale);
990 descender = FT_MulFix(os2Table->sTypoDescender, yscale);
991 lineGap = FT_MulFix(os2Table->sTypoLineGap, yscale);
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;
1002 if (!hb_ot_metrics_get_position(font.
data(), HB_OT_METRICS_TAG_HORIZONTAL_DESCENDER, &altDescender)) {
1003 altDescender = face.
data()->descender;
1005 if (!hb_ot_metrics_get_position(font.
data(), HB_OT_METRICS_TAG_HORIZONTAL_LINE_GAP, &altLineGap)) {
1006 altLineGap = face.
data()->height - (altAscender-altDescender);
1013 if (!os2Table || (altAscender - altDescender + altLineGap) > (ascender - descender + lineGap)) {
1014 ascender = altAscender;
1015 descender = altDescender;
1016 lineGap = altLineGap;
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");
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)){
1036 if (!isIdeographic && isHorizontal) {
1037 hb_blob_t_sp dLang(hb_ot_meta_reference_entry( hbFace.
data() , HB_OT_META_TAG_DESIGN_LANGUAGES));
1041 const QString designLang = QString::fromLatin1(ba).trimmed();
1043 if (!designLang.isEmpty()) {
1057 Q_FOREACH (
const QString cjk, cjkScripts) {
1058 if (cjk.isEmpty())
continue;
1059 if (designLang.contains(cjk.trimmed())) {
1060 isIdeographic =
true;
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);
1073 if (!baselineVals.keys().contains(hang)) {
1074 baselineVals.insert(hang, baselineVals.value(alphabetic)+metrics.
fontSize*0.6);
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);
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);
1088 if (!baselineVals.keys().contains(hang)) {
1089 baselineVals.insert(hang, baselineVals.value(alphabetic)+metrics.
fontSize*0.6);
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));
1099 for (
auto it = baselines.begin(); it!= baselines.end(); it++) {
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);
1111 Q_FOREACH(
const QString key, baselineVals.keys()) {
1115 if (!isHorizontal) {
1122 qreal height = ascender - descender;
1123 ascender = height*0.5;
1124 descender = -ascender;
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;