Krita Source Code Documentation
Loading...
Searching...
No Matches
KoSvgTextShape_p_glyphs.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2017 Dmitry Kazakov <dimula73@gmail.com>
3 * SPDX-FileCopyrightText: 2022 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8#include "KoSvgTextShape.h"
9#include "KoSvgTextShape_p.h"
10
11#include "KisTofuGlyph.h"
13
14#include <FlakeDebug.h>
15#include <KoPathShape.h>
16
17#include <kis_global.h>
18
19#include <QPainterPath>
20#include <QtMath>
21
22#include <utility>
23#include <variant>
24
25#include <ft2build.h>
26#include FT_FREETYPE_H
27#include FT_COLOR_H
28#include FT_BITMAP_H
29#include FT_OUTLINE_H
30#include FT_TRUETYPE_TABLES_H
31
32#include <hb.h>
33#include <hb-ft.h>
34
35#include <raqm.h>
36
37
38static QPainterPath convertFromFreeTypeOutline(FT_GlyphSlotRec *glyphSlot);
39static QImage convertFromFreeTypeBitmap(FT_GlyphSlotRec *glyphSlot);
40
41static QString glyphFormatToStr(const FT_Glyph_Format _v)
42{
43 const unsigned int v = _v;
44 QString s;
45 s += QChar((v >> 24) & 0xFF);
46 s += QChar((v >> 16) & 0xFF);
47 s += QChar((v >> 8) & 0xFF);
48 s += QChar((v >> 0) & 0xFF);
49 return s;
50}
51
61static void
62emboldenGlyphIfNeeded(const FT_Face ftface, const CharacterResult &charResult, int *x_advance, int *y_advance)
63{
64 constexpr int WEIGHT_SEMIBOLD = 600;
65 if (charResult.fontWeight >= WEIGHT_SEMIBOLD) {
66 // Simplest check: Bold fonts don't need to be embolden.
67 if (ftface->style_flags & FT_STYLE_FLAG_BOLD) {
68 return;
69 }
70
71 // Variable fnots also don't need to be embolden.
72 if (FT_HAS_MULTIPLE_MASTERS(ftface)) {
73 return;
74 }
75
76 // Some heavy weight classes don't cause FT_STYLE_FLAG_BOLD to be set,
77 // so we have to check the OS/2 and STAT table for its weight class to be sure.
78 hb_font_t_sp hbFont(hb_ft_font_create_referenced(ftface));
79 if (hb_style_get_value(hbFont.data(), HB_STYLE_TAG_WEIGHT) >= WEIGHT_SEMIBOLD) {
80 return;
81 }
82
83 // This code is somewhat inspired by Firefox.
84 FT_Pos strength =
85 FT_MulFix(ftface->units_per_EM, ftface->size->metrics.y_scale) / 48;
86
87 if (ftface->glyph->format == FT_GLYPH_FORMAT_BITMAP) {
88 // This is similar to what FT_GlyphSlot_Embolden does.
89
90 // Round down to full pixel.
91 strength &= ~63;
92 if (strength == 0) {
93 // ... but it has to be at least one pixel.
94 strength = 64;
95 }
96
97 FT_GlyphSlot_Own_Bitmap(ftface->glyph);
98
99 // Embolden less vertically than horizontally. Especially if
100 // strength is only 1px, don't embolden vertically at all.
101 // Otherwise it makes the glyph way too heavy, especially for
102 // CJK glyphs in small sizes.
103 const FT_Pos strengthY = strength - 64;
104 FT_Bitmap_Embolden(ftface->glyph->library, &ftface->glyph->bitmap, strength, strengthY);
105
106 if (x_advance && *x_advance != 0) {
107 *x_advance += strength;
108 }
109 if (y_advance && *y_advance != 0) {
110 *y_advance -= strengthY;
111 }
112 } else {
113 FT_Outline_Embolden(&ftface->glyph->outline, strength);
114
115 if (x_advance && *x_advance != 0) {
116 *x_advance += strength;
117 }
118 if (y_advance && *y_advance != 0) {
119 *y_advance -= strength;
120 }
121 }
122 }
123}
124
125bool faceIsItalic(const FT_Face face) {
126 hb_font_t_sp hbFont(hb_ft_font_create_referenced(face));
127 bool isItalic = hb_style_get_value(hbFont.data(), HB_STYLE_TAG_ITALIC) > 0;
128 bool isOblique = false;
129
130 if (FT_HAS_MULTIPLE_MASTERS(face)) {
131 isOblique = hb_style_get_value(hbFont.data(), HB_STYLE_TAG_SLANT_ANGLE) != 0;
132 }
133
134 return isItalic || isOblique;
135}
136
147static std::pair<QTransform, QTransform> calcOutlineGlyphTransform(const QTransform &ftTF,
148 const raqm_glyph_t &currentGlyph,
149 const CharacterResult &charResult,
150 const bool isHorizontal)
151{
152 QTransform outlineGlyphTf = QTransform::fromTranslate(currentGlyph.x_offset, currentGlyph.y_offset);
153 QTransform glyphObliqueTf;
154
155 // Check whether we need to synthesize italic by shearing the glyph:
156 if (charResult.fontStyle != QFont::StyleNormal && !faceIsItalic(currentGlyph.ftface)) {
157 // CSS Fonts Module Level 4, 2.4. Font style: the font-style property:
158 // For `oblique`, "lack of an <angle> represents 14deg".
159 constexpr double SLANT_14DEG = 0.24932800284318069162403993780486;
160 if (isHorizontal) {
161 glyphObliqueTf.shear(SLANT_14DEG, 0);
162 } else {
163 // For vertical mode, CSSWG says:
164 // - Skew around the centre
165 // - Right-side down and left-side up
166 // https://github.com/w3c/csswg-drafts/issues/2869
167 glyphObliqueTf.shear(0, -SLANT_14DEG);
168 }
169 outlineGlyphTf *= glyphObliqueTf;
170 }
171 outlineGlyphTf *= ftTF;
172 return {outlineGlyphTf, glyphObliqueTf};
173}
174
180{
181public:
189 ColorLayersLoader(FT_Face face, FT_UInt baseGlyph)
190 : m_face(face)
191 , m_baseGlyph(baseGlyph)
192 {
193 const unsigned short paletteIndex = 0;
194 if (FT_Palette_Select(m_face, paletteIndex, &m_palette) != 0) {
195 m_palette = nullptr;
196 }
198 }
199
203 operator bool() const
204 {
205 return m_haveLayers && m_palette;
206 }
207
217 std::tuple<QPainterPath, QBrush, bool>
218 layer(const CharacterResult &charResult, const FT_Int32 faceLoadFlags, int *x_advance, int *y_advance)
219 {
220 QBrush layerColor;
221 bool isForeGroundColor = false;
222
223 if (m_layerColorIndex == 0xFFFF) {
224 layerColor = Qt::black;
225 isForeGroundColor = true;
226 } else {
227 const FT_Color color = m_palette[m_layerColorIndex];
228 layerColor = QColor(color.red, color.green, color.blue, color.alpha);
229 }
230 if (const FT_Error err = FT_Load_Glyph(m_face, m_layerGlyphIndex, faceLoadFlags)) {
231 warnFlake << "Failed to load glyph, freetype error" << err;
232 return {};
233 }
234 if (m_face->glyph->format == FT_GLYPH_FORMAT_OUTLINE) {
235 // Check whether we need to synthesize bold by emboldening the glyph:
236 emboldenGlyphIfNeeded(m_face, charResult, x_advance, y_advance);
237
238 const QPainterPath p = convertFromFreeTypeOutline(m_face->glyph);
239 return {p, layerColor, isForeGroundColor};
240 } else {
241 warnFlake << "Unsupported glyph format" << glyphFormatToStr(m_face->glyph->format) << "in glyph layers";
242 return {};
243 }
244 }
245
252 bool moveNext()
253 {
255 FT_Get_Color_Glyph_Layer(m_face, m_baseGlyph, &m_layerGlyphIndex, &m_layerColorIndex, &m_iterator);
256 return m_haveLayers;
257 }
258
259private:
262 FT_LayerIterator m_iterator{};
263 FT_Color *m_palette{};
264 FT_Face m_face;
265 FT_UInt m_baseGlyph;
267};
268
269
277// NOLINTNEXTLINE(readability-function-cognitive-complexity)
278std::pair<QTransform, qreal> KoSvgTextShape::Private::loadGlyphOnly(const QTransform &ftTF,
279 const FT_Int32 faceLoadFlags,
280 const bool isHorizontal,
281 raqm_glyph_t &currentGlyph,
282 CharacterResult &charResult,
283 const KoSvgText::TextRendering rendering)
284{
287 QTransform glyphObliqueTf;
288
290 qreal bitmapScale = 1.0;
291
292 if (currentGlyph.index == 0) {
293 // Missing glyph -- don't try to load the glyph from the font, we will
294 // draw a tofu block instead.
295 debugFlake << "Missing glyph";
296 return {glyphObliqueTf, bitmapScale};
297 }
298
299 // Try to retrieve CPAL/COLR v0 color layers, this should be preferred over
300 // other glyph formats. Doing this first also allows us to skip loading the
301 // default outline glyph.
302 if (ColorLayersLoader loader{currentGlyph.ftface, currentGlyph.index}) {
303 Glyph::ColorLayers *colorGlyph = std::get_if<Glyph::ColorLayers>(&charResult.glyph);
304 if (!colorGlyph) {
305 if (!std::holds_alternative<std::monostate>(charResult.glyph)) {
306 warnFlake << "Glyph contains other type than ColorLayers:" << charResult.glyph.index();
307 }
308 colorGlyph = &charResult.glyph.emplace<Glyph::ColorLayers>();
309 }
310
312 QTransform outlineGlyphTf;
313
314 // Calculate the transforms
315 std::tie(outlineGlyphTf, glyphObliqueTf) =
316 calcOutlineGlyphTransform(ftTF, currentGlyph, charResult, isHorizontal);
317
318 const int orig_x_advance = currentGlyph.x_advance;
319 const int orig_y_advance = currentGlyph.y_advance;
320 int new_x_advance{};
321 int new_y_advance{};
322 do {
323 new_x_advance = orig_x_advance;
324 new_y_advance = orig_y_advance;
325 QPainterPath p;
326 QBrush layerColor;
327 bool isForeGroundColor = false;
328 std::tie(p, layerColor, isForeGroundColor) = loader.layer(charResult, faceLoadFlags, &new_x_advance, &new_y_advance);
329 if (!p.isEmpty()) {
330 p = outlineGlyphTf.map(p);
331 if (charResult.visualIndex > -1) {
332 // This is for glyph clusters, i.e. complex emoji. Do it
333 // like how we handle unicode combining marks.
334 p = p.translated(charResult.advance);
335 }
336 colorGlyph->paths.append(p);
337 colorGlyph->colors.append(layerColor);
338 colorGlyph->replaceWithForeGroundColor.append(isForeGroundColor);
339 }
340 } while (loader.moveNext());
341 currentGlyph.x_advance = new_x_advance;
342 currentGlyph.y_advance = new_y_advance;
343 } else {
344 if (const FT_Error err = FT_Load_Glyph(currentGlyph.ftface, currentGlyph.index, faceLoadFlags)) {
345 warnFlake << "Failed to load glyph, freetype error" << err;
346 return {glyphObliqueTf, bitmapScale};
347 }
348
349 // Check whether we need to synthesize bold by emboldening the glyph:
350 emboldenGlyphIfNeeded(currentGlyph.ftface, charResult, &currentGlyph.x_advance, &currentGlyph.y_advance);
351
352 if (currentGlyph.ftface->glyph->format == FT_GLYPH_FORMAT_OUTLINE) {
353 Glyph::Outline _discard;
354 Glyph::Outline *outlineGlyph = std::get_if<Glyph::Outline>(&charResult.glyph);
355 if (!outlineGlyph) {
356 if (std::holds_alternative<Glyph::ColorLayers>(charResult.glyph)) {
357 // Special case: possibly an empty glyph in the middle of a
358 // combining color glyph, just discard the resulting path.
359 outlineGlyph = &_discard;
360 } else if (!std::holds_alternative<std::monostate>(charResult.glyph)) {
361 warnFlake << "Glyph contains other type than Outline:" << charResult.glyph.index();
362 }
363 if (!outlineGlyph) {
364 outlineGlyph = &charResult.glyph.emplace<Glyph::Outline>();
365 }
366 }
367
369 QTransform outlineGlyphTf;
370
371 // Calculate the transforms
372 std::tie(outlineGlyphTf, glyphObliqueTf) =
373 calcOutlineGlyphTransform(ftTF, currentGlyph, charResult, isHorizontal);
374
375 QPainterPath glyph = convertFromFreeTypeOutline(currentGlyph.ftface->glyph);
376 glyph = outlineGlyphTf.map(glyph);
377
378 if (charResult.visualIndex > -1) {
379 // this is for glyph clusters, unicode combining marks are always
380 // added. we could have these as separate paths, but there's no real
381 // purpose, and the svg standard prefers 'ligatures' to be treated
382 // as a single glyph. It simplifies things for us in any case.
383 outlineGlyph->path.addPath(glyph.translated(charResult.advance));
384 } else {
385 outlineGlyph->path = glyph;
386 }
387 } else {
388 QTransform bitmapTf;
389
390 if (currentGlyph.ftface->glyph->format == FT_GLYPH_FORMAT_BITMAP) {
391 if (FT_HAS_COLOR(currentGlyph.ftface)) {
392 // This applies the transform for CBDT bitmaps (e.g. Noto
393 // Color Emoji) that was set in KoFontRegistry::configureFaces
394 FT_Matrix matrix;
395 FT_Vector delta;
396 FT_Get_Transform(currentGlyph.ftface, &matrix, &delta);
397 constexpr qreal FACTOR_16 = 1.0 / 65536.0;
398 bitmapTf.setMatrix(matrix.xx * FACTOR_16, matrix.xy * FACTOR_16, 0, matrix.yx * FACTOR_16, matrix.yy * FACTOR_16, 0, 0, 0, 1);
399 KIS_SAFE_ASSERT_RECOVER_NOOP(bitmapTf.m11() == bitmapTf.m22());
400 bitmapScale = bitmapTf.m11();
401 QPointF anchor(-currentGlyph.ftface->glyph->bitmap_left, currentGlyph.ftface->glyph->bitmap_top);
402 bitmapTf = QTransform::fromTranslate(-anchor.x(), -anchor.y()) * bitmapTf
403 * QTransform::fromTranslate(anchor.x(), anchor.y());
404 }
405 } else if (currentGlyph.ftface->glyph->format == FT_GLYPH_FORMAT_SVG) {
406 debugFlake << "Unsupported glyph format" << glyphFormatToStr(currentGlyph.ftface->glyph->format);
407 return {glyphObliqueTf, bitmapScale};
408 } else {
409 debugFlake << "Unsupported glyph format" << glyphFormatToStr(currentGlyph.ftface->glyph->format)
410 << "asking freetype to render it for us";
411 FT_Render_Mode mode = FT_LOAD_TARGET_MODE(faceLoadFlags);
412 if (mode == FT_RENDER_MODE_NORMAL && (faceLoadFlags & FT_LOAD_MONOCHROME)) {
413 mode = FT_RENDER_MODE_MONO;
414 }
415 if (const FT_Error err = FT_Render_Glyph(currentGlyph.ftface->glyph, mode)) {
416 warnFlake << "Failed to render glyph, freetype error" << err;
417 return {glyphObliqueTf, bitmapScale};
418 }
419 }
420
421 Glyph::Bitmap *bitmapGlyph = std::get_if<Glyph::Bitmap>(&charResult.glyph);
422 if (!bitmapGlyph) {
423 if (!std::holds_alternative<std::monostate>(charResult.glyph)) {
424 warnFlake << "Glyph contains other type than Bitmap:" << charResult.glyph.index();
425 }
426 bitmapGlyph = &charResult.glyph.emplace<Glyph::Bitmap>();
427 }
428
429 QImage image = convertFromFreeTypeBitmap(currentGlyph.ftface->glyph);
430 bitmapGlyph->images.append(image);
431
432 // Check whether we need to synthesize italic by shearing the glyph:
433 if (charResult.fontStyle != QFont::StyleNormal
434 && !(currentGlyph.ftface->style_flags & FT_STYLE_FLAG_ITALIC)) {
435 // Since we are dealing with a bitmap glyph, we'll just use a nice
436 // round floating point number.
437 constexpr double SLANT_BITMAP = 0.25;
438 QTransform shearTf;
439 QPoint shearAt;
440 if (isHorizontal) {
441 shearTf.shear(-SLANT_BITMAP, 0);
442 glyphObliqueTf.shear(SLANT_BITMAP, 0);
443 shearAt = QPoint(0, currentGlyph.ftface->glyph->bitmap_top);
444 } else {
445 shearTf.shear(0, SLANT_BITMAP);
446 glyphObliqueTf.shear(0, -SLANT_BITMAP);
447 shearAt = QPoint(image.width() / 2, 0);
448 }
449 // We need to shear around the baseline, hence the translation.
450 bitmapTf = (QTransform::fromTranslate(-shearAt.x(), -shearAt.y()) * shearTf
451 * QTransform::fromTranslate(shearAt.x(), shearAt.y())) * bitmapTf;
452 }
453
454 if (!bitmapTf.isIdentity()) {
455 const QSize srcSize = image.size();
456 bitmapGlyph->images.replace(bitmapGlyph->images.size()-1, std::move(image).transformed(
457 bitmapTf,
458 rendering == KoSvgText::RenderingOptimizeSpeed ? Qt::FastTransformation : Qt::SmoothTransformation));
459
460 // This does the same as `QImage::trueMatrix` to get the image
461 // offset after transforming.
462 const QPoint offset = bitmapTf.mapRect(QRectF({0, 0}, srcSize)).toAlignedRect().topLeft();
463 currentGlyph.ftface->glyph->bitmap_left += offset.x();
464 currentGlyph.ftface->glyph->bitmap_top -= offset.y();
465 }
466 }
467 }
468 return {glyphObliqueTf, bitmapScale};
469}
470
476// NOLINTNEXTLINE(readability-function-cognitive-complexity)
477bool KoSvgTextShape::Private::loadGlyph(const KoSvgText::ResolutionHandler &resHandler,
478 const FT_Int32 faceLoadFlags,
479 const bool isHorizontal,
480 const char32_t firstCodepoint,
481 const KoSvgText::TextRendering rendering,
482 raqm_glyph_t &currentGlyph,
483 CharacterResult &charResult,
484 QPointF &totalAdvanceFTFontCoordinates)
485{
486 const QTransform ftTF = resHandler.freeTypeToPointTransform();
487
490 QTransform glyphObliqueTf;
491
493 qreal bitmapScale = 1.0;
494
495 // Try to load the glyph
496 std::tie(glyphObliqueTf, bitmapScale) = loadGlyphOnly(ftTF, faceLoadFlags, isHorizontal, currentGlyph, charResult, rendering);
497
498 if (charResult.visualIndex == -1) {
499 CursorInfo cursorInfo = charResult.cursorInfo;
500 qreal lineHeight = (charResult.metrics.ascender-charResult.metrics.descender) * bitmapScale;
501 qreal descender = charResult.metrics.descender * bitmapScale;
502 qint32 offset = charResult.metrics.caretOffset;
503 if (isHorizontal) {
504 qreal slope = 0;
505 if (charResult.metrics.caretRun != 0 && charResult.metrics.caretRise !=0) {
506 slope = double(charResult.metrics.caretRun)/double(charResult.metrics.caretRise);
507 if (offset == 0) {
508 offset = descender * slope;
509 }
510 }
511 QLineF caret(QPointF(), QPointF(lineHeight*slope, lineHeight));
512 caret.translate(-QPointF(-offset, -descender));
513 cursorInfo.caret = ftTF.map(glyphObliqueTf.map(caret));
514
515 } else {
516 qreal slope = 0;
517 if (charResult.metrics.caretRun != 0 && charResult.metrics.caretRise !=0) {
518 slope = double(charResult.metrics.caretRise)/double(charResult.metrics.caretRun);
519 if (offset == 0) {
520 offset = descender * slope;
521 }
522 }
523 QLineF caret(QPointF(), QPointF(lineHeight, lineHeight*slope));
524 caret.translate(-QPointF(-descender, -offset));
525 cursorInfo.caret = ftTF.map(glyphObliqueTf.map(caret));
526 }
527
528 charResult.cursorInfo = cursorInfo;
529 }
530
531 {
532 QPointF advance(currentGlyph.x_advance, currentGlyph.y_advance);
533 if (charResult.tabSize) {
534 charResult.glyph.emplace<Glyph::Outline>();
535 }
536
537 if (std::holds_alternative<std::monostate>(charResult.glyph)) {
538 // For whatever reason we don't have a glyph for this char. Draw a
539 // tofu block for it.
540 const auto height = ftTF.map(QPointF(currentGlyph.ftface->size->metrics.height, 0)).x() * 0.6;
541 QPainterPath glyph = KisTofuGlyph::create(firstCodepoint, height);
542 if (isHorizontal) {
543 glyph.translate(0, -height);
544 const qreal newAdvance =
545 ftTF.inverted().map(QPointF(glyph.boundingRect().width() + height * (1. / 15.), 0)).x();
546 if (newAdvance > advance.x()) {
547 advance.setX(newAdvance);
548 }
549 } else {
550 glyph.translate(glyph.boundingRect().width() * -0.5, (ftTF.map(advance).y() - height) * 0.5);
551 }
552 charResult.glyph.emplace<Glyph::Outline>(Glyph::Outline{glyph});
553 }
554
555 charResult.advance += ftTF.map(advance);
556
557 Glyph::Bitmap *const bitmapGlyph = std::get_if<Glyph::Bitmap>(&charResult.glyph);
558
559 if (bitmapGlyph) {
560 const int width = bitmapGlyph->images.last().width();
561 const int height = bitmapGlyph->images.last().height();
562 const int left = currentGlyph.ftface->glyph->bitmap_left;
563 const int top = currentGlyph.ftface->glyph->bitmap_top - height;
564 QRect bboxPixel(left, top, width, height);
565 if (!isHorizontal) {
566 bboxPixel.moveLeft(-(bboxPixel.width() / 2));
567 bboxPixel.moveTop(-bboxPixel.height());
568 }
569 QRectF drawRect = ftTF.mapRect(QRectF(bboxPixel.topLeft() * resHandler.freeTypePixel, bboxPixel.size() * resHandler.freeTypePixel));
570 drawRect.translate(charResult.advance - ftTF.map(advance));
571 bitmapGlyph->drawRects.append(drawRect);
572 }
573
574 QRectF bbox;
575 if (isHorizontal) {
576 bbox = QRectF(0,
577 charResult.metrics.descender * bitmapScale,
578 ftTF.inverted().map(charResult.advance).x(),
579 (charResult.metrics.ascender - charResult.metrics.descender) * bitmapScale);
580
581 } else {
582 bbox = QRectF(charResult.metrics.descender * bitmapScale,
583 0,
584 (charResult.metrics.ascender - charResult.metrics.descender) * bitmapScale,
585 ftTF.inverted().map(charResult.advance).y());
586 }
587 bbox = glyphObliqueTf.mapRect(bbox);
588 charResult.isHorizontal = isHorizontal;
589 QRectF scaledBBox = resHandler.adjust(ftTF.mapRect(bbox));
590 charResult.scaledHalfLeading = resHandler.adjust(ftTF.map(QPointF(charResult.fontHalfLeading, charResult.fontHalfLeading))).x();
591 charResult.scaledAscent = isHorizontal? scaledBBox.top(): scaledBBox.right();
592 charResult.scaledDescent = isHorizontal? scaledBBox.bottom(): scaledBBox.left();
593
594 if (charResult.tabSize) {
595 charResult.tabSize = ftTF.map(QPointF(*charResult.tabSize, *charResult.tabSize)).x();
596 }
597
598 if(!qFuzzyCompare(bitmapScale, 1.0)) {
599 charResult.metrics.scaleBaselines(bitmapScale);
600 }
601 if (charResult.extraFontScaling < 1.0) {
602 charResult.scaleCharacterResult(charResult.extraFontScaling, charResult.extraFontScaling);
603 }
604
605 if (bitmapGlyph) {
606 charResult.inkBoundingBox |= bitmapGlyph->drawRects.last();
607 } else if (const auto *outlineGlyph = std::get_if<Glyph::Outline>(&charResult.glyph)) {
608 charResult.inkBoundingBox |= outlineGlyph->path.boundingRect();
609 } else if (const auto *colorGlyph = std::get_if<Glyph::ColorLayers>(&charResult.glyph)) {
610 Q_FOREACH (const QPainterPath &p, colorGlyph->paths) {
611 charResult.inkBoundingBox |= p.boundingRect();
612 }
613 } else if (!std::holds_alternative<std::monostate>(charResult.glyph)) {
614 warnFlake << "Unhandled glyph type" << charResult.glyph.index();
615 }
616 totalAdvanceFTFontCoordinates += advance;
617 charResult.cssPosition = ftTF.map(totalAdvanceFTFontCoordinates) - charResult.advance;
618 }
619 return true;
620}
621
622QVector<QPointF> KoSvgTextShape::Private::getLigatureCarets(const KoSvgText::ResolutionHandler &resHandler, const bool isHorizontal, raqm_glyph_t &currentGlyph)
623{
624 QVector<QPointF> positions;
625 const QTransform ftTF = resHandler.freeTypeToPointTransform();
626 // Ligature caret list only uses direction to determine whether horizontal or vertical.
627 hb_direction_t dir = isHorizontal? HB_DIRECTION_LTR: HB_DIRECTION_TTB;
628 hb_font_t_sp font(hb_ft_font_create_referenced(currentGlyph.ftface));
629
630 uint numCarets = hb_ot_layout_get_ligature_carets(font.data(), dir, currentGlyph.index, 0, nullptr, nullptr);
631 for (uint i=0; i<numCarets; i++) {
632 hb_position_t caretPos = 0;
633 uint caretCount = 1;
634 hb_ot_layout_get_ligature_carets(font.data(), dir, currentGlyph.index, 0, &caretCount, &caretPos);
635 QPointF pos = isHorizontal? QPointF(caretPos, 0): QPointF(0, caretPos);
636 positions.append(ftTF.map(pos));
637 }
638 return positions;
639}
640
641// NOLINTNEXTLINE(readability-function-cognitive-complexity)
642static QPainterPath convertFromFreeTypeOutline(FT_GlyphSlotRec *glyphSlot)
643{
644 QPointF cp = QPointF();
645 // convert the outline to a painter path
646 // This is taken from qfontengine_ft.cpp.
647 QPainterPath glyph;
648 glyph.setFillRule(Qt::WindingFill);
649 int i = 0;
650 for (int j = 0; j < glyphSlot->outline.n_contours; ++j) {
651 int last_point = glyphSlot->outline.contours[j];
652 // qDebug() << "contour:" << i << "to" << last_point;
653 QPointF start = QPointF(glyphSlot->outline.points[i].x, glyphSlot->outline.points[i].y);
654 if (!(glyphSlot->outline.tags[i] & 1)) { // start point is not on curve:
655 if (!(glyphSlot->outline.tags[last_point] & 1)) { // end point is not on curve:
656 // qDebug() << " start and end point are not on curve";
657 start = (QPointF(glyphSlot->outline.points[last_point].x, glyphSlot->outline.points[last_point].y) + start) / 2.0;
658 } else {
659 // qDebug() << " end point is on curve, start is not";
660 start = QPointF(glyphSlot->outline.points[last_point].x, glyphSlot->outline.points[last_point].y);
661 }
662 --i; // to use original start point as control point below
663 }
664 start += cp;
665 // qDebug() << " start at" << start;
666 glyph.moveTo(start);
667 std::array<QPointF, 4> curve;
668 curve[0] = start;
669 size_t n = 1;
670 while (i < last_point) {
671 ++i;
672 curve.at(n) = cp + QPointF(glyphSlot->outline.points[i].x, glyphSlot->outline.points[i].y);
673 // qDebug() << " " << i << c[n] << "tag =" <<
674 // (int)g->outline.tags[i]
675 // << ": on curve =" << (bool)(g->outline.tags[i]
676 // & 1);
677 ++n;
678 switch (glyphSlot->outline.tags[i] & 3) {
679 case 2:
680 // cubic bezier element
681 if (n < 4)
682 continue;
683 curve[3] = (curve[3] + curve[2]) / 2;
684 --i;
685 break;
686 case 0:
687 // quadratic bezier element
688 if (n < 3)
689 continue;
690 curve[3] = (curve[1] + curve[2]) / 2;
691 curve[2] = (2 * curve[1] + curve[3]) / 3;
692 curve[1] = (2 * curve[1] + curve[0]) / 3;
693 --i;
694 break;
695 case 1:
696 case 3:
697 if (n == 2) {
698 // qDebug() << " lineTo" << c[1];
699 glyph.lineTo(curve[1]);
700 curve[0] = curve[1];
701 n = 1;
702 continue;
703 } else if (n == 3) {
704 curve[3] = curve[2];
705 curve[2] = (2 * curve[1] + curve[3]) / 3;
706 curve[1] = (2 * curve[1] + curve[0]) / 3;
707 }
708 break;
709 }
710 // qDebug() << " cubicTo" << c[1] << c[2] << c[3];
711 glyph.cubicTo(curve[1], curve[2], curve[3]);
712 curve[0] = curve[3];
713 n = 1;
714 }
715 if (n == 1) {
716 // qDebug() << " closeSubpath";
717 glyph.closeSubpath();
718 } else {
719 curve[3] = start;
720 if (n == 2) {
721 curve[2] = (2 * curve[1] + curve[3]) / 3;
722 curve[1] = (2 * curve[1] + curve[0]) / 3;
723 }
724 // qDebug() << " close cubicTo" << c[1] << c[2] << c[3];
725 glyph.cubicTo(curve[1], curve[2], curve[3]);
726 }
727 ++i;
728 }
729 return glyph;
730}
731
732static QImage convertFromFreeTypeBitmap(FT_GlyphSlotRec *glyphSlot)
733{
734 KIS_ASSERT(glyphSlot->bitmap.width <= INT32_MAX);
735 KIS_ASSERT(glyphSlot->bitmap.rows <= INT32_MAX);
736 QImage img;
737 const int height = static_cast<int>(glyphSlot->bitmap.rows);
738 const QSize size(static_cast<int>(glyphSlot->bitmap.width), height);
739
740 if (glyphSlot->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) {
741 img = QImage(size, QImage::Format_Mono);
742 uchar *src = glyphSlot->bitmap.buffer;
743 KIS_ASSERT(glyphSlot->bitmap.pitch >= 0);
744 for (int y = 0; y < height; y++) {
745 memcpy(img.scanLine(y), src, static_cast<size_t>(glyphSlot->bitmap.pitch));
746 src += glyphSlot->bitmap.pitch;
747 }
748 } else if (glyphSlot->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) {
749 img = QImage(size, QImage::Format_Grayscale8);
750 uchar *src = glyphSlot->bitmap.buffer;
751 KIS_ASSERT(glyphSlot->bitmap.pitch >= 0);
752 for (int y = 0; y < height; y++) {
753 memcpy(img.scanLine(y), src, static_cast<size_t>(glyphSlot->bitmap.pitch));
754 src += glyphSlot->bitmap.pitch;
755 }
756 } else if (glyphSlot->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) {
757 img = QImage(size, QImage::Format_ARGB32_Premultiplied);
758 const uint8_t *src = glyphSlot->bitmap.buffer;
759 for (int y = 0; y < height; y++) {
760 auto *argb = reinterpret_cast<QRgb *>(img.scanLine(y));
761 for (unsigned int x = 0; x < glyphSlot->bitmap.width; x++) {
762 argb[x] = qRgba(src[2], src[1], src[0], src[3]);
763 src += 4;
764 }
765 }
766 }
767
768 return img;
769}
#define warnFlake
Definition FlakeDebug.h:16
#define debugFlake
Definition FlakeDebug.h:15
const Params2D p
qreal v
unsigned int uint
static QPainterPath convertFromFreeTypeOutline(FT_GlyphSlotRec *glyphSlot)
bool faceIsItalic(const FT_Face face)
static QImage convertFromFreeTypeBitmap(FT_GlyphSlotRec *glyphSlot)
static std::pair< QTransform, QTransform > calcOutlineGlyphTransform(const QTransform &ftTF, const raqm_glyph_t &currentGlyph, const CharacterResult &charResult, const bool isHorizontal)
Calculate the transformation matrices for an outline glyph, taking synthesized italic into account.
static void emboldenGlyphIfNeeded(const FT_Face ftface, const CharacterResult &charResult, int *x_advance, int *y_advance)
Embolden a glyph (synthesize bold) if the font does not have native bold.
static QString glyphFormatToStr(const FT_Glyph_Format _v)
Helper class to load CPAL/COLR v0 color layers, functionally based off the sample code in the freetyp...
ColorLayersLoader(FT_Face face, FT_UInt baseGlyph)
Construct a ColorLayersLoader object. The first color layer is selected if there are any.
bool moveNext()
Move to the next glyph layer.
std::tuple< QPainterPath, QBrush, bool > layer(const CharacterResult &charResult, const FT_Int32 faceLoadFlags, int *x_advance, int *y_advance)
Load the current glyph layer.
virtual QSizeF size() const
Get the size of the shape in pt.
Definition KoShape.cpp:740
KoShapeAnchor * anchor() const
static bool qFuzzyCompare(half p1, half p2)
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
unsigned int QRgb
QPainterPath create(const char32_t codepoint, double height)
Creates a tofu missing glyph indicator representing the provided Unicode codepoint.
@ RenderingOptimizeSpeed
Definition KoSvgText.h:316
QRectF inkBoundingBox
The bounds of the drawn glyph. Different from the bounds the charresult takes up in the layout,...
qreal scaledDescent
Descender, in pt.
Glyph::Variant glyph
KoSvgText::FontMetrics metrics
Fontmetrics for current font, in Freetype scanline coordinates.
std::optional< qreal > tabSize
If present, this is a tab and it should align to multiples of this tabSize value.
qreal extraFontScaling
Freetype doesn't allow us to scale below 1pt, so we need to do an extra transformation in these cases...
void scaleCharacterResult(qreal xScale, qreal yScale)
scaleCharacterResult convenience function to scale the whole character result.
qreal scaledAscent
Ascender, in pt.
bool isHorizontal
Whether the current glyph lays out horizontal or vertical. Currently same as paragraph,...
QPointF cssPosition
the position in accordance with the CSS specs, as opossed to the SVG spec.
QFont::Style fontStyle
qreal scaledHalfLeading
Leading for both sides, can be either negative or positive, in pt.
qreal fontHalfLeading
Leading for both sides, can be either negative or positive.
QLineF caret
Caret for this characterResult.
QVector< QImage > images
QVector< QRectF > drawRects
QVector< bool > replaceWithForeGroundColor
QVector< QPainterPath > paths
QVector< QBrush > colors
QPainterPath path
void scaleBaselines(const qreal multiplier)
qint32 caretRun
These are only used to determine the caret slant proportion.
Definition KoSvgText.h:362
qint32 descender
distance for origin to bottom.
Definition KoSvgText.h:340
qint32 ascender
distance from origin to top.
Definition KoSvgText.h:339
The ResolutionHandler class.
Definition KoSvgText.h:1084
QTransform freeTypeToPointTransform() const
QPointF adjust(const QPointF point) const
Adjusts the point to rounded pixel values, based on whether roundToPixelHorizontal or roundToPixelVer...