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 ¤tGlyph, 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 ¤tGlyph, 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, ¤tGlyph.x_advance, ¤tGlyph.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 ¤tGlyph, 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 }