299 quint32 yRes,
bool disableFontMatching,
300 const QString &language)
305 const QString suggestedHash = info.
families.join(
"+") +
":" + modifications;
306 auto entry =
d->suggestedFileNames().find(suggestedHash);
307 if (entry !=
d->suggestedFileNames().end()) {
308 candidates = entry.value();
310 candidates =
d->converter()->candidatesForCssValues(info,
313 d->suggestedFileNames().insert(suggestedHash, candidates);
319 if (disableFontMatching && !candidates.isEmpty()) {
320 fonts.append(candidates.first());
321 lengths.append(text.size());
325 Q_FOREACH (
const QString &family, info.
families) {
326 QByteArray utfData = family.toUtf8();
327 const FcChar8 *vals =
reinterpret_cast<FcChar8 *
>(utfData.data());
328 FcPatternAddString(
p.data(), FC_FAMILY, vals);
332 QByteArray fallbackBuf = QString(
"sans-serif").toUtf8();
334 fallback.type = FcTypeString;
335 fallback.u.s =
reinterpret_cast<FcChar8 *
>(fallbackBuf.data());
336 FcPatternAddWeak(
p.data(), FC_FAMILY, fallback,
true);
339 for (
int i = 0; i < candidates.size(); i++) {
341 QByteArray utfData = file.fileName.toUtf8();
342 const FcChar8 *vals =
reinterpret_cast<FcChar8 *
>(utfData.data());
343 FcPatternAddString(
p.data(), FC_FILE, vals);
344 FcPatternAddInteger(
p.data(), FC_INDEX, file.fontIndex);
347 if (info.
slantMode == QFont::StyleItalic) {
348 FcPatternAddInteger(
p.data(), FC_SLANT, FC_SLANT_ITALIC);
349 }
else if (info.
slantMode == QFont::StyleOblique) {
350 FcPatternAddInteger(
p.data(), FC_SLANT, FC_SLANT_OBLIQUE);
352 FcPatternAddInteger(
p.data(), FC_SLANT, FC_SLANT_ROMAN);
354 FcPatternAddInteger(
p.data(), FC_WEIGHT, FcWeightFromOpenType(info.
weight));
355 FcPatternAddInteger(
p.data(), FC_WIDTH, info.
width);
357 double pixelSize = info.
size*(qMin(xRes, yRes)/72.0);
358 FcPatternAddDouble(
p.data(), FC_PIXEL_SIZE, pixelSize);
360 FcConfigSubstitute(
nullptr,
p.data(), FcMatchPattern);
361 FcDefaultSubstitute(
p.data());
365 const FcChar32 hash = FcPatternHash(
p.data());
366 QString patternHash = info.
families.join(
"+")+QString::number(hash);
369 const auto oldPattern =
d->patterns().find(patternHash);
370 if (oldPattern !=
d->patterns().end()) {
371 return oldPattern.value();
373 d->patterns().insert(patternHash,
p);
378 FcResult result = FcResultNoMatch;
381 const FcChar32 hash = FcPatternHash(
p.data());
382 QString patternHash = info.
families.join(
"+")+QString::number(hash);
383 const auto set =
d->sets().find(patternHash);
385 if (set !=
d->sets().end()) {
388 FcCharSet *cs =
nullptr;
389 FcFontSetSP avalue(FcFontSort(FcConfigGetCurrent(),
p.data(), FcFalse, &cs, &result));
391 d->sets().insert(patternHash, avalue);
396 if (text.isEmpty()) {
397 for (
int j = 0; j < fontSet->nfont; j++) {
398 if (std::optional<KoFFWWSConverter::FontFileEntry> font =
getFontFileEntry(fontSet->fonts[j])) {
399 fonts.append(std::move(*font));
405 FcCharSet *set =
nullptr;
408 familyValues.fill(-1);
409 fallbackMatchValues.fill(-1);
420 for (
int i = 0; i < fontSet->nfont; i++) {
422 double fontsize = 0.0;
423 FcBool isScalable =
false;
424 FcPatternGetBool(fontSet->fonts[i], FC_SCALABLE, 0, &isScalable);
425 FcPatternGetDouble(fontSet->fonts[i], FC_PIXEL_SIZE, 0, &fontsize);
426 if (!isScalable && pixelSize != fontsize) {
433 if (FcPatternGetCharSet(fontSet->fonts[i], FC_CHARSET, 0, &set) == FcResultMatch) {
435 Q_FOREACH (
const QString &grapheme, graphemes) {
440 if (
const uint first =
firstCharUcs4(grapheme); QChar::category(first) == QChar::Other_Control
441 || QChar::category(first) == QChar::Other_Format) {
442 index += grapheme.size();
445 int familyIndex = -1;
446 if (familyValues.at(index) == -1) {
447 int fallbackMatch = fallbackMatchValues.at(index);
448 Q_FOREACH (
uint unicode, grapheme.toUcs4()) {
449 if (FcCharSetHasChar(set, unicode)) {
451 if (fallbackMatch < 0) {
459 for (
int k = 0; k < grapheme.size(); k++) {
460 familyValues[index + k] = familyIndex;
461 fallbackMatchValues[index + k] = fallbackMatch;
464 index += grapheme.size();
466 if (!familyValues.contains(-1)) {
473 if (familyValues.contains(-1)) {
475 Q_FOREACH (
const int currentValue, familyValues) {
476 if (currentValue !=
value) {
477 value = currentValue;
482 for (
int i = 0; i < familyValues.size(); i++) {
483 if (familyValues.at(i) < 0) {
484 if (fallbackMatchValues.at(i) < 0) {
485 familyValues[i] =
value;
487 familyValues[i] = fallbackMatchValues.at(i);
490 value = familyValues.at(i);
498 int lastIndex = familyValues.at(0);
500 if (std::optional<KoFFWWSConverter::FontFileEntry> f =
getFontFileEntry(fontSet->fonts[lastIndex])) {
501 font = std::move(*f);
503 for (
int i = 0; i < familyValues.size(); i++) {
504 if (lastIndex != familyValues.at(i)) {
505 lengths.append(text.mid(startIndex,
length).size());
509 lastIndex = familyValues.at(i);
510 if (std::optional<KoFFWWSConverter::FontFileEntry> f =
getFontFileEntry(fontSet->fonts[lastIndex])) {
511 font = std::move(*f);
517 lengths.append(text.mid(startIndex,
length).size());
523 std::vector<FT_FaceSP> faces;
527 QString(
"Fonts and lengths don't have the same size. Fonts: %1. Length: %2")
528 .arg(fonts.size(), lengths.size()).toLatin1());
530 for (
int i = 0; i < lengths.size(); i++) {
534 for (
int j = 0; j < candidates.size(); j++) {
535 if (font.
fileName == candidates.at(j).fileName) {
536 font.
fontIndex = candidates.at(j).fontIndex;
541 const QString fontCacheEntry = font.
fileName +
"#" + QString::number(font.
fontIndex) +
"#" + modifications;
542 auto entry =
d->typeFaces().find(fontCacheEntry);
543 if (entry !=
d->typeFaces().end()) {
544 faces.emplace_back(entry.value());
547 QByteArray utfData = font.
fileName.toUtf8();
548 if (FT_New_Face(
d->library().data(), utfData.data(), font.
fontIndex, &f) == 0) {
551 faces.emplace_back(face);
552 d->typeFaces().insert(fontCacheEntry, face);
562 qreal fontSizeAdjust,
565 const QMap<QString, qreal> &axisSettings)
568 const qreal ftFontUnit = 64.0;
569 const qreal finalRes = qMin(xRes, yRes);
570 const qreal scaleToPixel = finalRes / 72;
571 Q_FOREACH (
const FT_FaceSP &face, faces) {
574 if (!face->charmap) {
575 FT_CharMap map =
nullptr;
576 for (
int i = 0; i < face->num_charmaps; i++) {
577 FT_CharMap m = face->charmaps[i];
587 switch (m->encoding) {
588 case FT_ENCODING_UNICODE:
591 case FT_ENCODING_APPLE_ROMAN:
592 case FT_ENCODING_ADOBE_LATIN_1:
593 if (!map || map->encoding != FT_ENCODING_UNICODE) {
597 case FT_ENCODING_ADOBE_CUSTOM:
598 case FT_ENCODING_MS_SYMBOL:
599 if (!map || (map->encoding != FT_ENCODING_UNICODE && map->encoding != FT_ENCODING_APPLE_ROMAN && map->encoding != FT_ENCODING_ADOBE_LATIN_1)) {
606 FT_Set_Charmap(face.
data(), map);
609 qreal adjustedSize = size;
611 if (!FT_IS_SCALABLE(face)) {
612 const qreal fontSizePixels = size * ftFontUnit * scaleToPixel;
614 int selectedIndex = -1;
616 for (
int i = 0; i < face->num_fixed_sizes; i++) {
617 const qreal newDelta = qAbs((fontSizePixels)-face->available_sizes[i].x_ppem);
618 if (newDelta < sizeDelta || i == 0) {
620 sizeDelta = newDelta;
624 if (selectedIndex >= 0) {
625 if (FT_HAS_COLOR(face)) {
626 const FT_Fixed scale =
static_cast<FT_Fixed
>(65535. * qreal(fontSizePixels)
627 / qreal(face->available_sizes[selectedIndex].x_ppem));
634 FT_Set_Transform(face.
data(), &matrix, &
v);
636 errorCode = FT_Select_Size(face.
data(), selectedIndex);
639 errorCode = FT_Set_Char_Size(face.
data(),
static_cast<FT_F26Dot6
>(size * ftFontUnit), 0, xRes, yRes);
641 hb_position_t xHeight = 0;
642 hb_ot_metrics_get_position(font.
data(), HB_OT_METRICS_TAG_X_HEIGHT, &xHeight);
643 if (xHeight > 0 && fontSizeAdjust > 0) {
644 qreal aspect = xHeight / (size * ftFontUnit * scaleToPixel);
645 adjustedSize = (fontSizeAdjust / aspect) * (size);
646 errorCode = FT_Set_Char_Size(face.
data(),
647 static_cast<FT_F26Dot6
>(adjustedSize * ftFontUnit),
654 QMap<FT_Tag, qreal> tags;
655 Q_FOREACH (
const QString &tagName, axisSettings.keys()) {
656 if (tagName.size() == 4) {
657 const QByteArray utfData = tagName.toUtf8();
658 const char *tag = utfData.data();
659 if (tagName ==
"opsz" && !
qFuzzyCompare(adjustedSize, size)) {
668 tags.insert(FT_MAKE_TAG(tag[0], tag[1], tag[2], tag[3]), adjustedSize);
670 tags.insert(FT_MAKE_TAG(tag[0], tag[1], tag[2], tag[3]), axisSettings.value(tagName));
674 const FT_Tag
ITALIC_TAG = FT_MAKE_TAG(
'i',
't',
'a',
'l');
675 const FT_Tag
SLANT_TAG = FT_MAKE_TAG(
's',
'l',
'n',
't');
676 const int axisMultiplier = 65535;
677 if (FT_HAS_MULTIPLE_MASTERS(face)) {
678 FT_MM_Var *amaster =
nullptr;
679 FT_Get_MM_Var(face.
data(), &amaster);
682 std::vector<FT_Fixed> designCoords(amaster->num_axis);
683 for (FT_UInt i = 0; i < amaster->num_axis; i++) {
684 FT_Var_Axis axis = amaster->axis[i];
685 designCoords[i] = axis.def;
686 if (tags.contains(axis.tag)) {
687 designCoords[i] = qBound(axis.minimum,
long(tags.value(axis.tag) * axisMultiplier), axis.maximum);
691 designCoords[i] = qBound(axis.minimum,
long(-11.0 * axisMultiplier), axis.maximum);
694 && !qFuzzyCompare(tags.value(
SLANT_TAG, 0), 0)) {
695 designCoords[i] = qBound(axis.minimum,
long(1.0 * axisMultiplier), axis.maximum);
698 FT_Set_Var_Design_Coordinates(face.
data(), amaster->num_axis, designCoords.data());
699 FT_Done_MM_Var(
d->library().data(), amaster);
702 return (errorCode == 0);
788 hb_direction_t dir = isHorizontal? HB_DIRECTION_LTR: HB_DIRECTION_TTB;
789 QLatin1String scriptLatin1(script.toLatin1());
790 hb_script_t scriptTag = hb_script_from_string(scriptLatin1.data(), scriptLatin1.size());
791 hb_tag_t otScriptTag = HB_OT_TAG_DEFAULT_SCRIPT;
792 hb_tag_t otLangTag = HB_OT_TAG_DEFAULT_LANGUAGE;
794 hb_ot_tags_from_script_and_language(scriptTag,
nullptr, &maxCount, &otScriptTag, &maxCount, &otLangTag);
802 metrics.
fontSize = isHorizontal? face.
data()->size->metrics.y_ppem*64.0: face.
data()->size->metrics.x_ppem*64.0;
804 uint charIndex = FT_Get_Char_Index(face.
data(), 0x20);
806 if (FT_Load_Glyph(face.
data(), charIndex, faceLoadFlags) == FT_Err_Ok) {
807 metrics.
spaceAdvance = isHorizontal? face.
data()->glyph->advance.x: face.
data()->glyph->advance.y;
815 charIndex = FT_Get_Char_Index(face.
data(),
'0');
817 if(FT_Load_Glyph(face.
data(), charIndex, faceLoadFlags) == FT_Err_Ok) {
818 metrics.
zeroAdvance = isHorizontal? face.
data()->glyph->advance.x: face.
data()->glyph->advance.y;
826 bool isIdeographic =
false;
827 charIndex = FT_Get_Char_Index(face.
data(), 0x6C34);
829 if (FT_Load_Glyph(face.
data(), charIndex, faceLoadFlags) == FT_Err_Ok) {
834 isIdeographic =
true;
839 const bool useFallback = hb_version_atleast(4, 0, 0);
844 HB_OT_METRICS_TAG_X_HEIGHT,
845 HB_OT_METRICS_TAG_CAP_HEIGHT,
846 HB_OT_METRICS_TAG_SUPERSCRIPT_EM_X_OFFSET,
847 HB_OT_METRICS_TAG_SUPERSCRIPT_EM_Y_OFFSET,
848 HB_OT_METRICS_TAG_SUBSCRIPT_EM_X_OFFSET,
849 HB_OT_METRICS_TAG_SUBSCRIPT_EM_Y_OFFSET,
850 HB_OT_METRICS_TAG_UNDERLINE_OFFSET,
851 HB_OT_METRICS_TAG_UNDERLINE_SIZE,
852 HB_OT_METRICS_TAG_STRIKEOUT_OFFSET,
853 HB_OT_METRICS_TAG_STRIKEOUT_SIZE,
857 metricTags.append(HB_OT_METRICS_TAG_HORIZONTAL_CARET_RISE);
858 metricTags.append(HB_OT_METRICS_TAG_HORIZONTAL_CARET_RUN);
859 metricTags.append(HB_OT_METRICS_TAG_HORIZONTAL_CARET_OFFSET);
863 metricTags.append(HB_OT_METRICS_TAG_VERTICAL_CARET_RISE);
864 metricTags.append(HB_OT_METRICS_TAG_VERTICAL_CARET_RUN);
865 metricTags.append(HB_OT_METRICS_TAG_VERTICAL_CARET_OFFSET);
870 for (
auto it = metricTags.begin(); it!= metricTags.end(); it++) {
872 hb_tag_to_string(*it, c);
873 const QLatin1String tagName(c, 4);
874 hb_position_t origin = 0;
876 hb_ot_metrics_get_position_with_fallback(font.
data(), *it, &origin);
878 }
else if (hb_ot_metrics_get_position(font.
data(), *it, &origin)) {
886 HB_OT_LAYOUT_BASELINE_TAG_ROMAN,
887 HB_OT_LAYOUT_BASELINE_TAG_HANGING,
888 HB_OT_LAYOUT_BASELINE_TAG_IDEO_FACE_BOTTOM_OR_LEFT,
889 HB_OT_LAYOUT_BASELINE_TAG_IDEO_FACE_TOP_OR_RIGHT,
890 HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_BOTTOM_OR_LEFT,
891 HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_TOP_OR_RIGHT,
892 HB_OT_LAYOUT_BASELINE_TAG_MATH
895 QMap<QString, qint32> baselineVals;
897 for (
auto it = baselines.begin(); it!= baselines.end(); it++) {
898 hb_position_t origin = 0;
899 if (hb_ot_layout_get_baseline(font.
data(), *it, dir, otScriptTag, otLangTag, &origin)) {
900 std::vector<char> c(4);
901 hb_tag_to_string(*it, c.data());
902 baselineVals.insert(QString::fromLatin1(c.data(), 4), origin);
908 hb_position_t ascender = 0;
909 hb_position_t descender = 0;
910 hb_position_t lineGap = 0;
926 TT_OS2 *os2Table =
nullptr;
927 os2Table = (TT_OS2*)FT_Get_Sfnt_Table(face.
data(), FT_SFNT_OS2);
929 int yscale = face.
data()->size->metrics.y_scale;
931 ascender = FT_MulFix(os2Table->sTypoAscender, yscale);
932 descender = FT_MulFix(os2Table->sTypoDescender, yscale);
933 lineGap = FT_MulFix(os2Table->sTypoLineGap, yscale);
936 constexpr unsigned USE_TYPO_METRICS = 1u << 7;
937 if (!os2Table || os2Table->version == 0xFFFFU || !(os2Table->fsSelection & USE_TYPO_METRICS)) {
938 hb_position_t altAscender = 0;
939 hb_position_t altDescender = 0;
940 hb_position_t altLineGap = 0;
941 if (!hb_ot_metrics_get_position(font.
data(), HB_OT_METRICS_TAG_HORIZONTAL_ASCENDER, &altAscender)) {
942 altAscender = face.
data()->ascender;
944 if (!hb_ot_metrics_get_position(font.
data(), HB_OT_METRICS_TAG_HORIZONTAL_DESCENDER, &altDescender)) {
945 altDescender = face.
data()->descender;
947 if (!hb_ot_metrics_get_position(font.
data(), HB_OT_METRICS_TAG_HORIZONTAL_LINE_GAP, &altLineGap)) {
948 altLineGap = face.
data()->height - (altAscender-altDescender);
955 if (!os2Table || (altAscender - altDescender + altLineGap) > (ascender - descender + lineGap)) {
956 ascender = altAscender;
957 descender = altDescender;
958 lineGap = altLineGap;
965 const QString ideoBottom(
"ideo");
966 const QString ideoTop(
"idtp");
967 const QString alphabetic(
"romn");
968 const QString ideoCenter(
"Idce");
969 const QString hang(
"hang");
970 const QString math(
"math");
972 if (baselineVals.keys().contains(ideoBottom) && !baselineVals.keys().contains(ideoTop)) {
973 baselineVals.insert(ideoTop, baselineVals.value(ideoBottom)+metrics.
fontSize);
974 }
else if (!baselineVals.keys().contains(ideoBottom) && baselineVals.keys().contains(ideoTop)) {
975 baselineVals.insert(ideoBottom, baselineVals.value(ideoTop)-metrics.
fontSize);
976 }
else if (!baselineVals.keys().contains(ideoBottom) && !baselineVals.keys().contains(ideoTop)){
978 if (!isIdeographic && isHorizontal) {
979 hb_blob_t_sp dLang(hb_ot_meta_reference_entry( hbFace.
data() , HB_OT_META_TAG_DESIGN_LANGUAGES));
983 const QString designLang = QString::fromLatin1(ba).trimmed();
985 if (!designLang.isEmpty()) {
999 Q_FOREACH (
const QString cjk, cjkScripts) {
1000 if (cjk.isEmpty())
continue;
1001 if (designLang.contains(cjk.trimmed())) {
1002 isIdeographic =
true;
1009 if (isIdeographic && isHorizontal) {
1010 baselineVals.insert(ideoTop, ascender);
1011 baselineVals.insert(ideoBottom, descender);
1012 if (!baselineVals.keys().contains(alphabetic)) {
1013 baselineVals.insert(alphabetic, 0);
1015 if (!baselineVals.keys().contains(hang)) {
1016 baselineVals.insert(hang, baselineVals.value(alphabetic)+metrics.
fontSize*0.6);
1018 }
else if (isHorizontal) {
1019 const qreal alphabeticMultiplier = qreal(ascender+descender)/metrics.
fontSize;
1020 qint32 top = qMax(baselineVals.value(hang, metrics.
capHeight), qint32(qRound(ascender*alphabeticMultiplier)));
1021 baselineVals.insert(ideoTop, top);
1022 baselineVals.insert(ideoBottom, top-metrics.
fontSize);
1024 baselineVals.insert(ideoTop, metrics.
fontSize);
1025 baselineVals.insert(ideoBottom, 0);
1026 if (!baselineVals.keys().contains(alphabetic)) {
1027 const qreal alphabeticMultiplier = qreal(ascender+descender)/metrics.
fontSize;
1028 baselineVals.insert(alphabetic, (-descender)*alphabeticMultiplier);
1030 if (!baselineVals.keys().contains(hang)) {
1031 baselineVals.insert(hang, baselineVals.value(alphabetic)+metrics.
fontSize*0.6);
1035 baselineVals.insert(ideoCenter, (baselineVals.value(ideoTop) + baselineVals.value(ideoBottom))/2);
1036 if (!isHorizontal && !baselineVals.keys().contains(math)) {
1037 baselineVals.insert(math, baselineVals.value(ideoCenter));
1041 for (
auto it = baselines.begin(); it!= baselines.end(); it++) {
1043 hb_tag_to_string(*it, c);
1044 const QString tagName = QString::fromLatin1(c, 4);
1045 if (!baselineVals.keys().contains(tagName)) {
1046 hb_position_t origin = 0;
1047 hb_ot_layout_get_baseline_with_fallback(font.
data(), *it, dir, otScriptTag, otLangTag, &origin);
1048 baselineVals.insert(tagName, origin);
1053 Q_FOREACH(
const QString key, baselineVals.keys()) {
1057 if (!isHorizontal) {
1064 qreal height = ascender - descender;
1065 ascender = height*0.5;
1066 descender = -ascender;
1068 if (ascender == 0 && descender == 0) {
1069 ascender = face->size->metrics.ascender;
1070 descender = face->size->metrics.descender;
1071 qreal height = ascender - descender;
1072 lineGap = face->size->metrics.height - height;
1073 if (!isHorizontal) {
1074 ascender = height * 0.5;
1075 descender = -ascender;