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).
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;
852 hb_ot_tags_from_script_and_language(scriptTag, nullptr, &maxCount, &otScriptTag, &maxCount, &otLangTag);
855
857
859
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 {
868 }
869 } else {
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 {
879 }
880 } else {
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) {
889 } else {
891 }
892 isIdeographic = true;
893 } else {
895 }
896
897 const bool useFallback = hb_version_atleast(4, 0, 0);
898
899
900
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);
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);
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);
936 } else if (hb_ot_metrics_get_position(font.data(), *it, &origin)) {
938 }
939 }
940
941
942
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
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
1010
1011
1012
1013 if (!os2Table || (altAscender - altDescender + altLineGap) > (ascender - descender + lineGap)) {
1014 ascender = altAscender;
1015 descender = altDescender;
1016 lineGap = altLineGap;
1017 }
1018 }
1019
1020
1021
1022
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
1045
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()) {
1113 }
1114
1115 if (!isHorizontal) {
1116
1119
1120
1121
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 }
1139
1140 if (isHorizontal) {
1142 } else {
1143
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.
@ BaselineCentral
Use the center between the ideographic over and under.
qint32 ideographicCenterBaseline
default baseline for vertical, centered between over and under.
qint32 ideographicUnderBaseline
location of ideographic under baseline from origin, may fall back to descender.
void setBaselineValueByTag(const QString &tag, int32_t value)
qint32 lineGap
additional linegap between consecutive lines.
qint32 zeroAdvance
Advance of the character '0', CSS Unit 'ch', defaults to 0.5 em in horizontal and 1....
qint32 ideographicAdvance
Advance of the character 'æ°´' (U+6C34), CSS Unit ic, defaults to 1 em.
qint32 ideographicOverBaseline
location of ideographic over baseline from origin.
void setMetricsValueByTag(const QLatin1String &tag, int32_t value)
qint32 fontSize
Currently set size, CSS unit 'em'.
qint32 caretRun
These are only used to determine the caret slant proportion.
qint32 descender
distance for origin to bottom.
qint32 ascender
distance from origin to top.
qint32 capHeight
Height of capital letters, defaults to ascender.
void offsetMetricsToNewOrigin(const Baseline baseline)
bool isVertical
Different fontMetrics count between vertical and horizontal.
qint32 spaceAdvance
Advance of the character ' ', used by tabs.