File indexing completed on 2024-12-22 04:09:10

0001 /*
0002  *  SPDX-FileCopyrightText: 2017 Dmitry Kazakov <dimula73@gmail.com>
0003  *  SPDX-FileCopyrightText: 2022 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "KoSvgTextShape.h"
0009 #include "KoSvgTextShape_p.h"
0010 
0011 #include "KisTofuGlyph.h"
0012 #include "KoFontLibraryResourceUtils.h"
0013 
0014 #include <FlakeDebug.h>
0015 #include <KoPathShape.h>
0016 
0017 #include <kis_global.h>
0018 
0019 #include <QPainterPath>
0020 #include <QtMath>
0021 
0022 #include <utility>
0023 #include <variant>
0024 
0025 #include <ft2build.h>
0026 #include FT_FREETYPE_H
0027 #include FT_COLOR_H
0028 #include FT_BITMAP_H
0029 #include FT_OUTLINE_H
0030 #include FT_TRUETYPE_TABLES_H
0031 
0032 #include <hb.h>
0033 #include <hb-ft.h>
0034 
0035 #include <raqm.h>
0036 
0037 
0038 static QPainterPath convertFromFreeTypeOutline(FT_GlyphSlotRec *glyphSlot);
0039 static QImage convertFromFreeTypeBitmap(FT_GlyphSlotRec *glyphSlot);
0040 
0041 static QString glyphFormatToStr(const FT_Glyph_Format _v)
0042 {
0043     const unsigned int v = _v;
0044     QString s;
0045     s += (v >> 24) & 0xFF;
0046     s += (v >> 16) & 0xFF;
0047     s += (v >> 8) & 0xFF;
0048     s += (v >> 0) & 0xFF;
0049     return s;
0050 }
0051 
0052 /**
0053  * @brief Embolden a glyph (synthesize bold) if the font does not have native
0054  * bold.
0055  *
0056  * @param ftface
0057  * @param charResult
0058  * @param x_advance Pointer to the X advance to be adjusted if needed.
0059  * @param y_advance Pointer to the Y advance to be adjusted if needed.
0060  */
0061 static void
0062 emboldenGlyphIfNeeded(const FT_Face ftface, const CharacterResult &charResult, int *x_advance, int *y_advance)
0063 {
0064     constexpr int WEIGHT_SEMIBOLD = 600;
0065     if (charResult.fontWeight >= WEIGHT_SEMIBOLD) {
0066         // Simplest check: Bold fonts don't need to be embolden.
0067         if (ftface->style_flags & FT_STYLE_FLAG_BOLD) {
0068             return;
0069         }
0070 
0071         // Variable fnots also don't need to be embolden.
0072         if (FT_HAS_MULTIPLE_MASTERS(ftface)) {
0073             return;
0074         }
0075 
0076         // Some heavy weight classes don't cause FT_STYLE_FLAG_BOLD to be set,
0077         // so we have to check the OS/2 table for its weight class to be sure.
0078         if (const TT_OS2 *const os2Table = reinterpret_cast<TT_OS2 *>(FT_Get_Sfnt_Table(ftface, FT_SFNT_OS2));
0079             os2Table && os2Table->usWeightClass >= WEIGHT_SEMIBOLD) {
0080             return;
0081         }
0082 
0083         // This code is somewhat inspired by Firefox.
0084         FT_Pos strength =
0085             FT_MulFix(ftface->units_per_EM, ftface->size->metrics.y_scale) / 48;
0086 
0087         if (ftface->glyph->format == FT_GLYPH_FORMAT_BITMAP) {
0088             // This is similar to what FT_GlyphSlot_Embolden does.
0089 
0090             // Round down to full pixel.
0091             strength &= ~63;
0092             if (strength == 0) {
0093                 // ... but it has to be at least one pixel.
0094                 strength = 64;
0095             }
0096 
0097             FT_GlyphSlot_Own_Bitmap(ftface->glyph);
0098 
0099             // Embolden less vertically than horizontally. Especially if
0100             // strength is only 1px, don't embolden vertically at all.
0101             // Otherwise it makes the glyph way too heavy, especially for
0102             // CJK glyphs in small sizes.
0103             const FT_Pos strengthY = strength - 64;
0104             FT_Bitmap_Embolden(ftface->glyph->library, &ftface->glyph->bitmap, strength, strengthY);
0105 
0106             if (x_advance && *x_advance != 0) {
0107                 *x_advance += strength;
0108             }
0109             if (y_advance && *y_advance != 0) {
0110                 *y_advance -= strengthY;
0111             }
0112         } else {
0113             FT_Outline_Embolden(&ftface->glyph->outline, strength);
0114 
0115             if (x_advance && *x_advance != 0) {
0116                 *x_advance += strength;
0117             }
0118             if (y_advance && *y_advance != 0) {
0119                 *y_advance -= strength;
0120             }
0121         }
0122     }
0123 }
0124 
0125 /**
0126  * @brief Calculate the transformation matrices for an outline glyph, taking
0127  * synthesized italic into account.
0128  *
0129  * @param ftTF FT unit to 1/72 unit
0130  * @param currentGlyph
0131  * @param charResult
0132  * @param isHorizontal
0133  * @return std::pair<QTransform, QTransform> {outlineGlyphTf, glyphObliqueTf}
0134  */
0135 static std::pair<QTransform, QTransform> calcOutlineGlyphTransform(const QTransform &ftTF,
0136                                                                    const raqm_glyph_t &currentGlyph,
0137                                                                    const CharacterResult &charResult,
0138                                                                    const bool isHorizontal)
0139 {
0140     QTransform outlineGlyphTf = QTransform::fromTranslate(currentGlyph.x_offset, currentGlyph.y_offset);
0141     QTransform glyphObliqueTf;
0142 
0143     // Check whether we need to synthesize italic by shearing the glyph:
0144     if (charResult.fontStyle != QFont::StyleNormal && !(currentGlyph.ftface->style_flags & FT_STYLE_FLAG_ITALIC)) {
0145         // CSS Fonts Module Level 4, 2.4. Font style: the font-style property:
0146         // For `oblique`, "lack of an <angle> represents 14deg".
0147         constexpr double SLANT_14DEG = 0.24932800284318069162403993780486;
0148         if (isHorizontal) {
0149             glyphObliqueTf.shear(SLANT_14DEG, 0);
0150         } else {
0151             // For vertical mode, CSSWG says:
0152             // - Skew around the centre
0153             // - Right-side down and left-side up
0154             // https://github.com/w3c/csswg-drafts/issues/2869
0155             glyphObliqueTf.shear(0, -SLANT_14DEG);
0156         }
0157         outlineGlyphTf *= glyphObliqueTf;
0158     }
0159     outlineGlyphTf *= ftTF;
0160     return {outlineGlyphTf, glyphObliqueTf};
0161 }
0162 
0163 /**
0164  * @brief Helper class to load CPAL/COLR v0 color layers, functionally based
0165  * off the sample code in the freetype docs.
0166  */
0167 class ColorLayersLoader
0168 {
0169 public:
0170     /**
0171      * @brief Construct a ColorLayersLoader object. The first color layer is
0172      * selected if there are any.
0173      *
0174      * @param face
0175      * @param baseGlyph
0176      */
0177     ColorLayersLoader(FT_Face face, FT_UInt baseGlyph)
0178         : m_face(face)
0179         , m_baseGlyph(baseGlyph)
0180     {
0181         const unsigned short paletteIndex = 0;
0182         if (FT_Palette_Select(m_face, paletteIndex, &m_palette) != 0) {
0183             m_palette = nullptr;
0184         }
0185         m_haveLayers = moveNext();
0186     }
0187 
0188     /**
0189      * @brief Check whether there are color layers to be loaded.
0190      */
0191     operator bool() const
0192     {
0193         return m_haveLayers && m_palette;
0194     }
0195 
0196     /**
0197      * @brief Load the current glyph layer.
0198      *
0199      * @param charResult
0200      * @param faceLoadFlags
0201      * @param x_advance Pointer to the X advance to be adjusted if needed.
0202      * @param y_advance Pointer to the Y advance to be adjusted if needed.
0203      * @return std::tuple<QPainterPath, QBrush, bool> {glyphOutline, layerColor, isForeGroundColor}
0204      */
0205     std::tuple<QPainterPath, QBrush, bool>
0206     layer(const CharacterResult &charResult, const FT_Int32 faceLoadFlags, int *x_advance, int *y_advance)
0207     {
0208         QBrush layerColor;
0209         bool isForeGroundColor = false;
0210 
0211         if (m_layerColorIndex == 0xFFFF) {
0212             layerColor = Qt::black;
0213             isForeGroundColor = true;
0214         } else {
0215             const FT_Color color = m_palette[m_layerColorIndex];
0216             layerColor = QColor(color.red, color.green, color.blue, color.alpha);
0217         }
0218         if (const FT_Error err = FT_Load_Glyph(m_face, m_layerGlyphIndex, faceLoadFlags)) {
0219             warnFlake << "Failed to load glyph, freetype error" << err;
0220             return {};
0221         }
0222         if (m_face->glyph->format == FT_GLYPH_FORMAT_OUTLINE) {
0223             // Check whether we need to synthesize bold by emboldening the glyph:
0224             emboldenGlyphIfNeeded(m_face, charResult, x_advance, y_advance);
0225 
0226             const QPainterPath p = convertFromFreeTypeOutline(m_face->glyph);
0227             return {p, layerColor, isForeGroundColor};
0228         } else {
0229             warnFlake << "Unsupported glyph format" << glyphFormatToStr(m_face->glyph->format) << "in glyph layers";
0230             return {};
0231         }
0232     }
0233 
0234     /**
0235      * @brief Move to the next glyph layer.
0236      *
0237      * @return true if there are more layers.
0238      * @return false if there are no more layers.
0239      */
0240     bool moveNext()
0241     {
0242         m_haveLayers =
0243             FT_Get_Color_Glyph_Layer(m_face, m_baseGlyph, &m_layerGlyphIndex, &m_layerColorIndex, &m_iterator);
0244         return m_haveLayers;
0245     }
0246 
0247 private:
0248     FT_UInt m_layerGlyphIndex{};
0249     FT_UInt m_layerColorIndex{};
0250     FT_LayerIterator m_iterator{};
0251     FT_Color *m_palette{};
0252     FT_Face m_face;
0253     FT_UInt m_baseGlyph;
0254     bool m_haveLayers;
0255 };
0256 
0257 
0258 /**
0259  * @brief Load the glyph if possible. The glyph is loaded into `charResult.glyph`.
0260  *
0261  * @return std::pair<QTransform, qreal>
0262  *   - QTransform glyphObliqueTf - The matrix for Italic (oblique) synthesis.
0263  *   - qreal bitmapScale - The scaling factor for color bitmap glyphs, otherwise always 1.0
0264  */
0265 // NOLINTNEXTLINE(readability-function-cognitive-complexity)
0266 std::pair<QTransform, qreal> KoSvgTextShape::Private::loadGlyphOnly(const QTransform &ftTF,
0267                                                                     const FT_Int32 faceLoadFlags,
0268                                                                     const bool isHorizontal,
0269                                                                     raqm_glyph_t &currentGlyph,
0270                                                                     CharacterResult &charResult) const
0271 {
0272     /// The matrix for Italic (oblique) synthesis of outline glyphs, or for
0273     /// adjusting the bounding box of bitmap glyphs.
0274     QTransform glyphObliqueTf;
0275 
0276     /// The scaling factor for color bitmap glyphs, otherwise always 1.0
0277     qreal bitmapScale = 1.0;
0278 
0279     if (currentGlyph.index == 0) {
0280         // Missing glyph -- don't try to load the glyph from the font, we will
0281         // draw a tofu block instead.
0282         debugFlake << "Missing glyph";
0283         return {glyphObliqueTf, bitmapScale};
0284     }
0285 
0286     // Try to retrieve CPAL/COLR v0 color layers, this should be preferred over
0287     // other glyph formats. Doing this first also allows us to skip loading the
0288     // default outline glyph.
0289     if (ColorLayersLoader loader{currentGlyph.ftface, currentGlyph.index}) {
0290         Glyph::ColorLayers *colorGlyph = std::get_if<Glyph::ColorLayers>(&charResult.glyph);
0291         if (!colorGlyph) {
0292             if (!std::holds_alternative<std::monostate>(charResult.glyph)) {
0293                 warnFlake << "Glyph contains other type than ColorLayers:" << charResult.glyph.index();
0294             }
0295             colorGlyph = &charResult.glyph.emplace<Glyph::ColorLayers>();
0296         }
0297 
0298         /// The combined offset * italic * ftTf transform for outline glyphs.
0299         QTransform outlineGlyphTf;
0300 
0301         // Calculate the transforms
0302         std::tie(outlineGlyphTf, glyphObliqueTf) =
0303             calcOutlineGlyphTransform(ftTF, currentGlyph, charResult, isHorizontal);
0304 
0305         const int orig_x_advance = currentGlyph.x_advance;
0306         const int orig_y_advance = currentGlyph.y_advance;
0307         int new_x_advance{};
0308         int new_y_advance{};
0309         do {
0310             new_x_advance = orig_x_advance;
0311             new_y_advance = orig_y_advance;
0312             QPainterPath p;
0313             QBrush layerColor;
0314             bool isForeGroundColor = false;
0315             std::tie(p, layerColor, isForeGroundColor) = loader.layer(charResult, faceLoadFlags, &new_x_advance, &new_y_advance);
0316             if (!p.isEmpty()) {
0317                 p = outlineGlyphTf.map(p);
0318                 if (charResult.visualIndex > -1) {
0319                     // This is for glyph clusters, i.e. complex emoji. Do it
0320                     // like how we handle unicode combining marks.
0321                     p = p.translated(charResult.advance);
0322                 }
0323                 colorGlyph->paths.append(p);
0324                 colorGlyph->colors.append(layerColor);
0325                 colorGlyph->replaceWithForeGroundColor.append(isForeGroundColor);
0326             }
0327         } while (loader.moveNext());
0328         currentGlyph.x_advance = new_x_advance;
0329         currentGlyph.y_advance = new_y_advance;
0330     } else {
0331         if (const FT_Error err = FT_Load_Glyph(currentGlyph.ftface, currentGlyph.index, faceLoadFlags)) {
0332             warnFlake << "Failed to load glyph, freetype error" << err;
0333             return {glyphObliqueTf, bitmapScale};
0334         }
0335 
0336         // Check whether we need to synthesize bold by emboldening the glyph:
0337         emboldenGlyphIfNeeded(currentGlyph.ftface, charResult, &currentGlyph.x_advance, &currentGlyph.y_advance);
0338 
0339         if (currentGlyph.ftface->glyph->format == FT_GLYPH_FORMAT_OUTLINE) {
0340             Glyph::Outline _discard; ///< Storage for discarded outline, must outlive outlineGlyph
0341             Glyph::Outline *outlineGlyph = std::get_if<Glyph::Outline>(&charResult.glyph);
0342             if (!outlineGlyph) {
0343                 if (std::holds_alternative<Glyph::ColorLayers>(charResult.glyph)) {
0344                     // Special case: possibly an empty glyph in the middle of a
0345                     // combining color glyph, just discard the resulting path.
0346                     outlineGlyph = &_discard;
0347                 } else if (!std::holds_alternative<std::monostate>(charResult.glyph)) {
0348                     warnFlake << "Glyph contains other type than Outline:" << charResult.glyph.index();
0349                 }
0350                 if (!outlineGlyph) {
0351                     outlineGlyph = &charResult.glyph.emplace<Glyph::Outline>();
0352                 }
0353             }
0354 
0355             /// The combined offset * italic * ftTf transform for outline glyphs.
0356             QTransform outlineGlyphTf;
0357 
0358             // Calculate the transforms
0359             std::tie(outlineGlyphTf, glyphObliqueTf) =
0360                 calcOutlineGlyphTransform(ftTF, currentGlyph, charResult, isHorizontal);
0361 
0362             QPainterPath glyph = convertFromFreeTypeOutline(currentGlyph.ftface->glyph);
0363             glyph = outlineGlyphTf.map(glyph);
0364 
0365             if (charResult.visualIndex > -1) {
0366                 // this is for glyph clusters, unicode combining marks are always
0367                 // added. we could have these as seperate paths, but there's no real
0368                 // purpose, and the svg standard prefers 'ligatures' to be treated
0369                 // as a single glyph. It simplifies things for us in any case.
0370                 outlineGlyph->path.addPath(glyph.translated(charResult.advance));
0371             } else {
0372                 outlineGlyph->path = glyph;
0373             }
0374         } else {
0375             QTransform bitmapTf;
0376 
0377             if (currentGlyph.ftface->glyph->format == FT_GLYPH_FORMAT_BITMAP) {
0378                 if (FT_HAS_COLOR(currentGlyph.ftface)) {
0379                     // This applies the transform for CBDT bitmaps (e.g. Noto
0380                     // Color Emoji) that was set in KoFontRegistry::configureFaces
0381                     FT_Matrix matrix;
0382                     FT_Vector delta;
0383                     FT_Get_Transform(currentGlyph.ftface, &matrix, &delta);
0384                     constexpr qreal FACTOR_16 = 1.0 / 65536.0;
0385                     bitmapTf.setMatrix(matrix.xx * FACTOR_16, matrix.xy * FACTOR_16, 0, matrix.yx * FACTOR_16, matrix.yy * FACTOR_16, 0, 0, 0, 1);
0386                     KIS_SAFE_ASSERT_RECOVER_NOOP(bitmapTf.m11() == bitmapTf.m22());
0387                     bitmapScale = bitmapTf.m11();
0388                     QPointF anchor(-currentGlyph.ftface->glyph->bitmap_left, currentGlyph.ftface->glyph->bitmap_top);
0389                     bitmapTf = QTransform::fromTranslate(-anchor.x(), -anchor.y()) * bitmapTf
0390                         * QTransform::fromTranslate(anchor.x(), anchor.y());
0391                 }
0392             } else if (currentGlyph.ftface->glyph->format == FT_GLYPH_FORMAT_SVG) {
0393                 debugFlake << "Unsupported glyph format" << glyphFormatToStr(currentGlyph.ftface->glyph->format);
0394                 return {glyphObliqueTf, bitmapScale};
0395             } else {
0396                 debugFlake << "Unsupported glyph format" << glyphFormatToStr(currentGlyph.ftface->glyph->format)
0397                            << "asking freetype to render it for us";
0398                 FT_Render_Mode mode = FT_LOAD_TARGET_MODE(faceLoadFlags);
0399                 if (mode == FT_RENDER_MODE_NORMAL && (faceLoadFlags & FT_LOAD_MONOCHROME)) {
0400                     mode = FT_RENDER_MODE_MONO;
0401                 }
0402                 if (const FT_Error err = FT_Render_Glyph(currentGlyph.ftface->glyph, mode)) {
0403                     warnFlake << "Failed to render glyph, freetype error" << err;
0404                     return {glyphObliqueTf, bitmapScale};
0405                 }
0406             }
0407 
0408             Glyph::Bitmap *bitmapGlyph = std::get_if<Glyph::Bitmap>(&charResult.glyph);
0409             if (!bitmapGlyph) {
0410                 if (!std::holds_alternative<std::monostate>(charResult.glyph)) {
0411                     warnFlake << "Glyph contains other type than Bitmap:" << charResult.glyph.index();
0412                 }
0413                 bitmapGlyph = &charResult.glyph.emplace<Glyph::Bitmap>();
0414             }
0415 
0416             // TODO: Handle glyph clusters better...
0417             bitmapGlyph->image = convertFromFreeTypeBitmap(currentGlyph.ftface->glyph);
0418 
0419             // Check whether we need to synthesize italic by shearing the glyph:
0420             if (charResult.fontStyle != QFont::StyleNormal
0421                 && !(currentGlyph.ftface->style_flags & FT_STYLE_FLAG_ITALIC)) {
0422                 // Since we are dealing with a bitmap glyph, we'll just use a nice
0423                 // round floating point number.
0424                 constexpr double SLANT_BITMAP = 0.25;
0425                 QTransform shearTf;
0426                 QPoint shearAt;
0427                 if (isHorizontal) {
0428                     shearTf.shear(-SLANT_BITMAP, 0);
0429                     glyphObliqueTf.shear(SLANT_BITMAP, 0);
0430                     shearAt = QPoint(0, currentGlyph.ftface->glyph->bitmap_top);
0431                 } else {
0432                     shearTf.shear(0, SLANT_BITMAP);
0433                     glyphObliqueTf.shear(0, -SLANT_BITMAP);
0434                     shearAt = QPoint(bitmapGlyph->image.width() / 2, 0);
0435                 }
0436                 // We need to shear around the baseline, hence the translation.
0437                 bitmapTf = (QTransform::fromTranslate(-shearAt.x(), -shearAt.y()) * shearTf
0438                     * QTransform::fromTranslate(shearAt.x(), shearAt.y())) * bitmapTf;
0439             }
0440 
0441             if (!bitmapTf.isIdentity()) {
0442                 const QSize srcSize = bitmapGlyph->image.size();
0443                 bitmapGlyph->image = std::move(bitmapGlyph->image).transformed(
0444                     bitmapTf,
0445                     this->textRendering == OptimizeSpeed ? Qt::FastTransformation : Qt::SmoothTransformation);
0446 
0447                 // This does the same as `QImage::trueMatrix` to get the image
0448                 // offset after transforming.
0449                 const QPoint offset = bitmapTf.mapRect(QRectF({0, 0}, srcSize)).toAlignedRect().topLeft();
0450                 currentGlyph.ftface->glyph->bitmap_left += offset.x();
0451                 currentGlyph.ftface->glyph->bitmap_top -= offset.y();
0452             }
0453         }
0454     }
0455     return {glyphObliqueTf, bitmapScale};
0456 }
0457 
0458 /**
0459  * @brief Load the glyph if possible and set the glyph bounding box and metrics.
0460  *
0461  * @return Whether the resulting charResult is valid.
0462  */
0463 // NOLINTNEXTLINE(readability-function-cognitive-complexity)
0464 bool KoSvgTextShape::Private::loadGlyph(const QTransform &ftTF,
0465                                         const QMap<int, KoSvgText::TabSizeInfo> &tabSizeInfo,
0466                                         const FT_Int32 faceLoadFlags,
0467                                         const bool isHorizontal,
0468                                         const char32_t firstCodepoint,
0469                                         raqm_glyph_t &currentGlyph,
0470                                         CharacterResult &charResult,
0471                                         QPointF &totalAdvanceFTFontCoordinates) const
0472 {
0473     // Whenever the freetype docs talk about a 26.6 floating point unit, they
0474     // mean a 1/64 value.
0475     const qreal ftFontUnit = 64.0;
0476     const qreal ftFontUnitFactor = 1 / ftFontUnit;
0477 
0478     const int cluster = static_cast<int>(currentGlyph.cluster);
0479 
0480     QPointF spaceAdvance;
0481     if (tabSizeInfo.contains(cluster)) {
0482         FT_Load_Glyph(currentGlyph.ftface, FT_Get_Char_Index(currentGlyph.ftface, ' '), faceLoadFlags);
0483         spaceAdvance = QPointF(currentGlyph.ftface->glyph->advance.x, currentGlyph.ftface->glyph->advance.y);
0484     }
0485 
0486     /// The matrix for Italic (oblique) synthesis of outline glyphs, or for
0487     /// adjusting the bounding box of bitmap glyphs.
0488     QTransform glyphObliqueTf;
0489 
0490     /// The scaling factor for color bitmap glyphs, otherwise always 1.0
0491     qreal bitmapScale = 1.0;
0492 
0493     // Try to load the glyph
0494     std::tie(glyphObliqueTf, bitmapScale) = loadGlyphOnly(ftTF, faceLoadFlags, isHorizontal, currentGlyph, charResult);
0495 
0496     if (charResult.visualIndex == -1) {
0497         hb_font_t_up font(hb_ft_font_create_referenced(currentGlyph.ftface));
0498         CursorInfo cursorInfo = charResult.cursorInfo;
0499         qreal lineHeight = (charResult.fontAscent-charResult.fontDescent) * bitmapScale;
0500         qreal descender = charResult.fontDescent * bitmapScale;
0501         if (isHorizontal) {
0502             hb_position_t run = 0;
0503             hb_position_t rise = 1;
0504             hb_position_t offset = 0;
0505             hb_ot_metrics_get_position_with_fallback(font.data(), HB_OT_METRICS_TAG_HORIZONTAL_CARET_RUN, &run);
0506             hb_ot_metrics_get_position_with_fallback(font.data(), HB_OT_METRICS_TAG_HORIZONTAL_CARET_RISE, &rise);
0507             hb_ot_metrics_get_position_with_fallback(font.data(), HB_OT_METRICS_TAG_HORIZONTAL_CARET_OFFSET, &offset);
0508             qreal slope = 0;
0509             if (run != 0 && rise !=0) {
0510                 slope = double(run)/double(rise);
0511                 if (offset == 0) {
0512                     offset = descender * slope;
0513                 }
0514             }
0515             QLineF caret(QPointF(), QPointF(lineHeight*slope, lineHeight));
0516             caret.translate(-QPointF(-offset, -descender));
0517             cursorInfo.caret = ftTF.map(glyphObliqueTf.map(caret));
0518 
0519             QVector<QPointF> positions;
0520             // Ligature caret list only uses direction to determine whether horizontal or vertical.
0521             hb_direction_t dir = HB_DIRECTION_LTR;
0522 
0523             uint numCarets = hb_ot_layout_get_ligature_carets(font.data(), dir, currentGlyph.index, 0, nullptr, nullptr);
0524             for (uint i=0; i<numCarets; i++) {
0525                 hb_position_t caretPos = 0;
0526                 uint caretCount = 1;
0527                 hb_ot_layout_get_ligature_carets(font.data(), dir, currentGlyph.index, 0, &caretCount, &caretPos);
0528                 QPointF pos(caretPos, 0);
0529                 positions.append(ftTF.map(pos));
0530             }
0531 
0532             cursorInfo.offsets = positions;
0533         } else {
0534             hb_position_t run = 1;
0535             hb_position_t rise = 0;
0536             hb_position_t offset = 0;
0537             hb_ot_metrics_get_position_with_fallback(font.data(), HB_OT_METRICS_TAG_VERTICAL_CARET_RUN, &run);
0538             hb_ot_metrics_get_position_with_fallback(font.data(), HB_OT_METRICS_TAG_VERTICAL_CARET_RISE, &rise);
0539             hb_ot_metrics_get_position_with_fallback(font.data(), HB_OT_METRICS_TAG_VERTICAL_CARET_OFFSET, &offset);
0540             qreal slope = 0;
0541             if (run != 0 && rise !=0) {
0542                 slope = double(rise)/double(run);
0543                 if (offset == 0) {
0544                     offset = descender * slope;
0545                 }
0546             }
0547             QLineF caret(QPointF(), QPointF(lineHeight, lineHeight*slope));
0548             caret.translate(-QPointF(-descender, -offset));
0549             cursorInfo.caret = ftTF.map(glyphObliqueTf.map(caret));
0550         }
0551 
0552         charResult.cursorInfo = cursorInfo;
0553     }
0554 
0555     {
0556         QPointF advance(currentGlyph.x_advance, currentGlyph.y_advance);
0557         if (tabSizeInfo.contains(cluster)) {
0558             KoSvgText::TabSizeInfo tabSize = tabSizeInfo.value(cluster);
0559             qreal newAdvance = tabSize.value * ftFontUnit;
0560             if (tabSize.isNumber) {
0561                 QPointF extraSpacing = isHorizontal ? QPointF(tabSize.extraSpacing * ftFontUnit, 0) : QPointF(0, tabSize.extraSpacing * ftFontUnit);
0562                 advance = (spaceAdvance + extraSpacing) * tabSize.value;
0563             } else {
0564                 advance = isHorizontal ? QPointF(newAdvance, advance.y()) : QPointF(advance.x(), newAdvance);
0565             }
0566             charResult.glyph.emplace<Glyph::Outline>();
0567         }
0568 
0569         if (std::holds_alternative<std::monostate>(charResult.glyph)) {
0570             // For whatever reason we don't have a glyph for this char. Draw a
0571             // tofu block for it.
0572             const auto height = ftTF.map(QPointF(currentGlyph.ftface->size->metrics.height, 0)).x() * 0.6;
0573             QPainterPath glyph = KisTofuGlyph::create(firstCodepoint, height);
0574             if (isHorizontal) {
0575                 glyph.translate(0, -height);
0576                 const qreal newAdvance =
0577                     ftTF.inverted().map(QPointF(glyph.boundingRect().width() + height * (1. / 15.), 0)).x();
0578                 if (newAdvance > advance.x()) {
0579                     advance.setX(newAdvance);
0580                 }
0581             } else {
0582                 glyph.translate(glyph.boundingRect().width() * -0.5, (ftTF.map(advance).y() - height) * 0.5);
0583             }
0584             charResult.glyph.emplace<Glyph::Outline>(Glyph::Outline{glyph});
0585         }
0586 
0587         charResult.advance += ftTF.map(advance);
0588 
0589         Glyph::Bitmap *const bitmapGlyph = std::get_if<Glyph::Bitmap>(&charResult.glyph);
0590 
0591         if (bitmapGlyph) {
0592             const int width = bitmapGlyph->image.width();
0593             const int height = bitmapGlyph->image.height();
0594             const int left = currentGlyph.ftface->glyph->bitmap_left;
0595             const int top = currentGlyph.ftface->glyph->bitmap_top - height;
0596             QRect bboxPixel(left, top, width, height);
0597             if (!isHorizontal) {
0598                 bboxPixel.moveLeft(-(bboxPixel.width() / 2));
0599             }
0600             bitmapGlyph->drawRect = ftTF.mapRect(QRectF(bboxPixel.topLeft() * ftFontUnit, bboxPixel.size() * ftFontUnit));
0601         }
0602 
0603         QRectF bbox;
0604         if (isHorizontal) {
0605             bbox = QRectF(0,
0606                           charResult.fontDescent * bitmapScale,
0607                           ftTF.inverted().map(charResult.advance).x(),
0608                           (charResult.fontAscent - charResult.fontDescent) * bitmapScale);
0609             bbox = glyphObliqueTf.mapRect(bbox);
0610         } else {
0611             bbox = QRectF(charResult.fontDescent * bitmapScale,
0612                           0,
0613                           (charResult.fontAscent - charResult.fontDescent) * bitmapScale,
0614                           ftTF.inverted().map(charResult.advance).y());
0615             bbox = glyphObliqueTf.mapRect(bbox);
0616         }
0617         charResult.boundingBox = ftTF.mapRect(bbox);
0618         charResult.scaledHalfLeading = ftTF.map(QPointF(charResult.fontHalfLeading, charResult.fontHalfLeading)).x();
0619         charResult.scaledAscent = isHorizontal? charResult.boundingBox.top(): charResult.boundingBox.right();
0620         charResult.scaledDescent = isHorizontal? charResult.boundingBox.bottom(): charResult.boundingBox.left();
0621         if (isHorizontal) {
0622             charResult.lineHeightBox = charResult.boundingBox.adjusted(0, -charResult.scaledHalfLeading, 0, charResult.scaledHalfLeading);
0623         } else {
0624             charResult.lineHeightBox = charResult.boundingBox.adjusted(-charResult.scaledHalfLeading, 0, charResult.scaledHalfLeading, 0);
0625         }
0626 
0627         if (bitmapGlyph) {
0628             charResult.boundingBox |= bitmapGlyph->drawRect;
0629         } else if (const auto *outlineGlyph = std::get_if<Glyph::Outline>(&charResult.glyph)) {
0630             charResult.boundingBox |= outlineGlyph->path.boundingRect();
0631         } else if (const auto *colorGlyph = std::get_if<Glyph::ColorLayers>(&charResult.glyph)) {
0632             Q_FOREACH (const QPainterPath &p, colorGlyph->paths) {
0633                 charResult.boundingBox |= p.boundingRect();
0634             }
0635         } else if (!std::holds_alternative<std::monostate>(charResult.glyph)) {
0636             warnFlake << "Unhandled glyph type" << charResult.glyph.index();
0637         }
0638         totalAdvanceFTFontCoordinates += advance;
0639         charResult.cssPosition = ftTF.map(totalAdvanceFTFontCoordinates) - charResult.advance;
0640     }
0641     return true;
0642 }
0643 
0644 // NOLINTNEXTLINE(readability-function-cognitive-complexity)
0645 static QPainterPath convertFromFreeTypeOutline(FT_GlyphSlotRec *glyphSlot)
0646 {
0647     QPointF cp = QPointF();
0648     // convert the outline to a painter path
0649     // This is taken from qfontengine_ft.cpp.
0650     QPainterPath glyph;
0651     glyph.setFillRule(Qt::WindingFill);
0652     int i = 0;
0653     for (int j = 0; j < glyphSlot->outline.n_contours; ++j) {
0654         int last_point = glyphSlot->outline.contours[j];
0655         // qDebug() << "contour:" << i << "to" << last_point;
0656         QPointF start = QPointF(glyphSlot->outline.points[i].x, glyphSlot->outline.points[i].y);
0657         if (!(glyphSlot->outline.tags[i] & 1)) { // start point is not on curve:
0658             if (!(glyphSlot->outline.tags[last_point] & 1)) { // end point is not on curve:
0659                 // qDebug() << "  start and end point are not on curve";
0660                 start = (QPointF(glyphSlot->outline.points[last_point].x, glyphSlot->outline.points[last_point].y) + start) / 2.0;
0661             } else {
0662                 // qDebug() << "  end point is on curve, start is not";
0663                 start = QPointF(glyphSlot->outline.points[last_point].x, glyphSlot->outline.points[last_point].y);
0664             }
0665             --i; // to use original start point as control point below
0666         }
0667         start += cp;
0668         // qDebug() << "  start at" << start;
0669         glyph.moveTo(start);
0670         std::array<QPointF, 4> curve;
0671         curve[0] = start;
0672         size_t n = 1;
0673         while (i < last_point) {
0674             ++i;
0675             curve.at(n) = cp + QPointF(glyphSlot->outline.points[i].x, glyphSlot->outline.points[i].y);
0676             // qDebug() << "    " << i << c[n] << "tag =" <<
0677             // (int)g->outline.tags[i]
0678             //                    << ": on curve =" << (bool)(g->outline.tags[i]
0679             //                    & 1);
0680             ++n;
0681             switch (glyphSlot->outline.tags[i] & 3) {
0682             case 2:
0683                 // cubic bezier element
0684                 if (n < 4)
0685                     continue;
0686                 curve[3] = (curve[3] + curve[2]) / 2;
0687                 --i;
0688                 break;
0689             case 0:
0690                 // quadratic bezier element
0691                 if (n < 3)
0692                     continue;
0693                 curve[3] = (curve[1] + curve[2]) / 2;
0694                 curve[2] = (2 * curve[1] + curve[3]) / 3;
0695                 curve[1] = (2 * curve[1] + curve[0]) / 3;
0696                 --i;
0697                 break;
0698             case 1:
0699             case 3:
0700                 if (n == 2) {
0701                     // qDebug() << "  lineTo" << c[1];
0702                     glyph.lineTo(curve[1]);
0703                     curve[0] = curve[1];
0704                     n = 1;
0705                     continue;
0706                 } else if (n == 3) {
0707                     curve[3] = curve[2];
0708                     curve[2] = (2 * curve[1] + curve[3]) / 3;
0709                     curve[1] = (2 * curve[1] + curve[0]) / 3;
0710                 }
0711                 break;
0712             }
0713             // qDebug() << "  cubicTo" << c[1] << c[2] << c[3];
0714             glyph.cubicTo(curve[1], curve[2], curve[3]);
0715             curve[0] = curve[3];
0716             n = 1;
0717         }
0718         if (n == 1) {
0719             // qDebug() << "  closeSubpath";
0720             glyph.closeSubpath();
0721         } else {
0722             curve[3] = start;
0723             if (n == 2) {
0724                 curve[2] = (2 * curve[1] + curve[3]) / 3;
0725                 curve[1] = (2 * curve[1] + curve[0]) / 3;
0726             }
0727             // qDebug() << "  close cubicTo" << c[1] << c[2] << c[3];
0728             glyph.cubicTo(curve[1], curve[2], curve[3]);
0729         }
0730         ++i;
0731     }
0732     return glyph;
0733 }
0734 
0735 static QImage convertFromFreeTypeBitmap(FT_GlyphSlotRec *glyphSlot)
0736 {
0737     KIS_ASSERT(glyphSlot->bitmap.width <= INT32_MAX);
0738     KIS_ASSERT(glyphSlot->bitmap.rows <= INT32_MAX);
0739     QImage img;
0740     const int height = static_cast<int>(glyphSlot->bitmap.rows);
0741     const QSize size(static_cast<int>(glyphSlot->bitmap.width), height);
0742 
0743     if (glyphSlot->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) {
0744         img = QImage(size, QImage::Format_Mono);
0745         uchar *src = glyphSlot->bitmap.buffer;
0746         KIS_ASSERT(glyphSlot->bitmap.pitch >= 0);
0747         for (int y = 0; y < height; y++) {
0748             memcpy(img.scanLine(y), src, static_cast<size_t>(glyphSlot->bitmap.pitch));
0749             src += glyphSlot->bitmap.pitch;
0750         }
0751     } else if (glyphSlot->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) {
0752         img = QImage(size, QImage::Format_Grayscale8);
0753         uchar *src = glyphSlot->bitmap.buffer;
0754         KIS_ASSERT(glyphSlot->bitmap.pitch >= 0);
0755         for (int y = 0; y < height; y++) {
0756             memcpy(img.scanLine(y), src, static_cast<size_t>(glyphSlot->bitmap.pitch));
0757             src += glyphSlot->bitmap.pitch;
0758         }
0759     } else if (glyphSlot->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) {
0760         img = QImage(size, QImage::Format_ARGB32_Premultiplied);
0761         const uint8_t *src = glyphSlot->bitmap.buffer;
0762         for (int y = 0; y < height; y++) {
0763             auto *argb = reinterpret_cast<QRgb *>(img.scanLine(y));
0764             for (unsigned int x = 0; x < glyphSlot->bitmap.width; x++) {
0765                 argb[x] = qRgba(src[2], src[1], src[0], src[3]);
0766                 src += 4;
0767             }
0768         }
0769     }
0770 
0771     return img;
0772 }