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 hb_font_t_sp font(hb_ft_font_create_referenced(currentGlyph.ftface));
500 CursorInfo cursorInfo = charResult.cursorInfo;
501 qreal lineHeight = (charResult.metrics.ascender-charResult.metrics.descender) * bitmapScale;
502 qreal descender = charResult.metrics.descender * bitmapScale;
503 qint32 offset = charResult.metrics.caretOffset;
504 if (isHorizontal) {
505 qreal slope = 0;
506 if (charResult.metrics.caretRun != 0 && charResult.metrics.caretRise !=0) {
507 slope = double(charResult.metrics.caretRun)/double(charResult.metrics.caretRise);
508 if (offset == 0) {
509 offset = descender * slope;
510 }
511 }
512 QLineF caret(QPointF(), QPointF(lineHeight*slope, lineHeight));
513 caret.translate(-QPointF(-offset, -descender));
514 cursorInfo.caret = ftTF.map(glyphObliqueTf.map(caret));
515
516 QVector<QPointF> positions;
517 // Ligature caret list only uses direction to determine whether horizontal or vertical.
518 hb_direction_t dir = HB_DIRECTION_LTR;
519
520 uint numCarets = hb_ot_layout_get_ligature_carets(font.data(), dir, currentGlyph.index, 0, nullptr, nullptr);
521 for (uint i=0; i<numCarets; i++) {
522 hb_position_t caretPos = 0;
523 uint caretCount = 1;
524 hb_ot_layout_get_ligature_carets(font.data(), dir, currentGlyph.index, 0, &caretCount, &caretPos);
525 QPointF pos(caretPos, 0);
526 positions.append(ftTF.map(pos));
527 }
528
529 cursorInfo.offsets = positions;
530 } else {
531 qreal slope = 0;
532 if (charResult.metrics.caretRun != 0 && charResult.metrics.caretRise !=0) {
533 slope = double(charResult.metrics.caretRise)/double(charResult.metrics.caretRun);
534 if (offset == 0) {
535 offset = descender * slope;
536 }
537 }
538 QLineF caret(QPointF(), QPointF(lineHeight, lineHeight*slope));
539 caret.translate(-QPointF(-descender, -offset));
540 cursorInfo.caret = ftTF.map(glyphObliqueTf.map(caret));
541 }
542
543 charResult.cursorInfo = cursorInfo;
544 }
545
546 {
547 QPointF advance(currentGlyph.x_advance, currentGlyph.y_advance);
548 if (charResult.tabSize) {
549 charResult.glyph.emplace<Glyph::Outline>();
550 }
551
552 if (std::holds_alternative<std::monostate>(charResult.glyph)) {
553 // For whatever reason we don't have a glyph for this char. Draw a
554 // tofu block for it.
555 const auto height = ftTF.map(QPointF(currentGlyph.ftface->size->metrics.height, 0)).x() * 0.6;
556 QPainterPath glyph = KisTofuGlyph::create(firstCodepoint, height);
557 if (isHorizontal) {
558 glyph.translate(0, -height);
559 const qreal newAdvance =
560 ftTF.inverted().map(QPointF(glyph.boundingRect().width() + height * (1. / 15.), 0)).x();
561 if (newAdvance > advance.x()) {
562 advance.setX(newAdvance);
563 }
564 } else {
565 glyph.translate(glyph.boundingRect().width() * -0.5, (ftTF.map(advance).y() - height) * 0.5);
566 }
567 charResult.glyph.emplace<Glyph::Outline>(Glyph::Outline{glyph});
568 }
569
570 charResult.advance += ftTF.map(advance);
571
572 Glyph::Bitmap *const bitmapGlyph = std::get_if<Glyph::Bitmap>(&charResult.glyph);
573
574 if (bitmapGlyph) {
575 const int width = bitmapGlyph->images.last().width();
576 const int height = bitmapGlyph->images.last().height();
577 const int left = currentGlyph.ftface->glyph->bitmap_left;
578 const int top = currentGlyph.ftface->glyph->bitmap_top - height;
579 QRect bboxPixel(left, top, width, height);
580 if (!isHorizontal) {
581 bboxPixel.moveLeft(-(bboxPixel.width() / 2));
582 bboxPixel.moveTop(-bboxPixel.height());
583 }
584 QRectF drawRect = ftTF.mapRect(QRectF(bboxPixel.topLeft() * resHandler.freeTypePixel, bboxPixel.size() * resHandler.freeTypePixel));
585 drawRect.translate(charResult.advance - ftTF.map(advance));
586 bitmapGlyph->drawRects.append(drawRect);
587 }
588
589 QRectF bbox;
590 if (isHorizontal) {
591 bbox = QRectF(0,
592 charResult.metrics.descender * bitmapScale,
593 ftTF.inverted().map(charResult.advance).x(),
594 (charResult.metrics.ascender - charResult.metrics.descender) * bitmapScale);
595
596 } else {
597 bbox = QRectF(charResult.metrics.descender * bitmapScale,
598 0,
599 (charResult.metrics.ascender - charResult.metrics.descender) * bitmapScale,
600 ftTF.inverted().map(charResult.advance).y());
601 }
602 bbox = glyphObliqueTf.mapRect(bbox);
603 charResult.isHorizontal = isHorizontal;
604 QRectF scaledBBox = resHandler.adjust(ftTF.mapRect(bbox));
605 charResult.scaledHalfLeading = resHandler.adjust(ftTF.map(QPointF(charResult.fontHalfLeading, charResult.fontHalfLeading))).x();
606 charResult.scaledAscent = isHorizontal? scaledBBox.top(): scaledBBox.right();
607 charResult.scaledDescent = isHorizontal? scaledBBox.bottom(): scaledBBox.left();
608
609 if (charResult.tabSize) {
610 charResult.tabSize = ftTF.map(QPointF(*charResult.tabSize, *charResult.tabSize)).x();
611 }
612
613 if(!qFuzzyCompare(bitmapScale, 1.0)) {
614 charResult.metrics.scaleBaselines(bitmapScale);
615 }
616 if (charResult.extraFontScaling < 1.0) {
617 charResult.scaleCharacterResult(charResult.extraFontScaling, charResult.extraFontScaling);
618 }
619
620 if (bitmapGlyph) {
621 charResult.inkBoundingBox |= bitmapGlyph->drawRects.last();
622 } else if (const auto *outlineGlyph = std::get_if<Glyph::Outline>(&charResult.glyph)) {
623 charResult.inkBoundingBox |= outlineGlyph->path.boundingRect();
624 } else if (const auto *colorGlyph = std::get_if<Glyph::ColorLayers>(&charResult.glyph)) {
625 Q_FOREACH (const QPainterPath &p, colorGlyph->paths) {
626 charResult.inkBoundingBox |= p.boundingRect();
627 }
628 } else if (!std::holds_alternative<std::monostate>(charResult.glyph)) {
629 warnFlake << "Unhandled glyph type" << charResult.glyph.index();
630 }
631 totalAdvanceFTFontCoordinates += advance;
632 charResult.cssPosition = ftTF.map(totalAdvanceFTFontCoordinates) - charResult.advance;
633 }
634 return true;
635}
636
637// NOLINTNEXTLINE(readability-function-cognitive-complexity)
638static QPainterPath convertFromFreeTypeOutline(FT_GlyphSlotRec *glyphSlot)
639{
640 QPointF cp = QPointF();
641 // convert the outline to a painter path
642 // This is taken from qfontengine_ft.cpp.
643 QPainterPath glyph;
644 glyph.setFillRule(Qt::WindingFill);
645 int i = 0;
646 for (int j = 0; j < glyphSlot->outline.n_contours; ++j) {
647 int last_point = glyphSlot->outline.contours[j];
648 // qDebug() << "contour:" << i << "to" << last_point;
649 QPointF start = QPointF(glyphSlot->outline.points[i].x, glyphSlot->outline.points[i].y);
650 if (!(glyphSlot->outline.tags[i] & 1)) { // start point is not on curve:
651 if (!(glyphSlot->outline.tags[last_point] & 1)) { // end point is not on curve:
652 // qDebug() << " start and end point are not on curve";
653 start = (QPointF(glyphSlot->outline.points[last_point].x, glyphSlot->outline.points[last_point].y) + start) / 2.0;
654 } else {
655 // qDebug() << " end point is on curve, start is not";
656 start = QPointF(glyphSlot->outline.points[last_point].x, glyphSlot->outline.points[last_point].y);
657 }
658 --i; // to use original start point as control point below
659 }
660 start += cp;
661 // qDebug() << " start at" << start;
662 glyph.moveTo(start);
663 std::array<QPointF, 4> curve;
664 curve[0] = start;
665 size_t n = 1;
666 while (i < last_point) {
667 ++i;
668 curve.at(n) = cp + QPointF(glyphSlot->outline.points[i].x, glyphSlot->outline.points[i].y);
669 // qDebug() << " " << i << c[n] << "tag =" <<
670 // (int)g->outline.tags[i]
671 // << ": on curve =" << (bool)(g->outline.tags[i]
672 // & 1);
673 ++n;
674 switch (glyphSlot->outline.tags[i] & 3) {
675 case 2:
676 // cubic bezier element
677 if (n < 4)
678 continue;
679 curve[3] = (curve[3] + curve[2]) / 2;
680 --i;
681 break;
682 case 0:
683 // quadratic bezier element
684 if (n < 3)
685 continue;
686 curve[3] = (curve[1] + curve[2]) / 2;
687 curve[2] = (2 * curve[1] + curve[3]) / 3;
688 curve[1] = (2 * curve[1] + curve[0]) / 3;
689 --i;
690 break;
691 case 1:
692 case 3:
693 if (n == 2) {
694 // qDebug() << " lineTo" << c[1];
695 glyph.lineTo(curve[1]);
696 curve[0] = curve[1];
697 n = 1;
698 continue;
699 } else if (n == 3) {
700 curve[3] = curve[2];
701 curve[2] = (2 * curve[1] + curve[3]) / 3;
702 curve[1] = (2 * curve[1] + curve[0]) / 3;
703 }
704 break;
705 }
706 // qDebug() << " cubicTo" << c[1] << c[2] << c[3];
707 glyph.cubicTo(curve[1], curve[2], curve[3]);
708 curve[0] = curve[3];
709 n = 1;
710 }
711 if (n == 1) {
712 // qDebug() << " closeSubpath";
713 glyph.closeSubpath();
714 } else {
715 curve[3] = start;
716 if (n == 2) {
717 curve[2] = (2 * curve[1] + curve[3]) / 3;
718 curve[1] = (2 * curve[1] + curve[0]) / 3;
719 }
720 // qDebug() << " close cubicTo" << c[1] << c[2] << c[3];
721 glyph.cubicTo(curve[1], curve[2], curve[3]);
722 }
723 ++i;
724 }
725 return glyph;
726}
727
728static QImage convertFromFreeTypeBitmap(FT_GlyphSlotRec *glyphSlot)
729{
730 KIS_ASSERT(glyphSlot->bitmap.width <= INT32_MAX);
731 KIS_ASSERT(glyphSlot->bitmap.rows <= INT32_MAX);
732 QImage img;
733 const int height = static_cast<int>(glyphSlot->bitmap.rows);
734 const QSize size(static_cast<int>(glyphSlot->bitmap.width), height);
735
736 if (glyphSlot->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) {
737 img = QImage(size, QImage::Format_Mono);
738 uchar *src = glyphSlot->bitmap.buffer;
739 KIS_ASSERT(glyphSlot->bitmap.pitch >= 0);
740 for (int y = 0; y < height; y++) {
741 memcpy(img.scanLine(y), src, static_cast<size_t>(glyphSlot->bitmap.pitch));
742 src += glyphSlot->bitmap.pitch;
743 }
744 } else if (glyphSlot->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) {
745 img = QImage(size, QImage::Format_Grayscale8);
746 uchar *src = glyphSlot->bitmap.buffer;
747 KIS_ASSERT(glyphSlot->bitmap.pitch >= 0);
748 for (int y = 0; y < height; y++) {
749 memcpy(img.scanLine(y), src, static_cast<size_t>(glyphSlot->bitmap.pitch));
750 src += glyphSlot->bitmap.pitch;
751 }
752 } else if (glyphSlot->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) {
753 img = QImage(size, QImage::Format_ARGB32_Premultiplied);
754 const uint8_t *src = glyphSlot->bitmap.buffer;
755 for (int y = 0; y < height; y++) {
756 auto *argb = reinterpret_cast<QRgb *>(img.scanLine(y));
757 for (unsigned int x = 0; x < glyphSlot->bitmap.width; x++) {
758 argb[x] = qRgba(src[2], src[1], src[0], src[3]);
759 src += 4;
760 }
761 }
762 }
763
764 return img;
765}
#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:820
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< QPointF > offsets
The advance offsets for each grapheme index.
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...