19#include <QPainterPath>
30#include FT_TRUETYPE_TABLES_H
43 const unsigned int v = _v;
45 s += QChar((
v >> 24) & 0xFF);
46 s += QChar((
v >> 16) & 0xFF);
47 s += QChar((
v >> 8) & 0xFF);
48 s += QChar((
v >> 0) & 0xFF);
64 constexpr int WEIGHT_SEMIBOLD = 600;
65 if (charResult.
fontWeight >= WEIGHT_SEMIBOLD) {
67 if (ftface->style_flags & FT_STYLE_FLAG_BOLD) {
72 if (FT_HAS_MULTIPLE_MASTERS(ftface)) {
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) {
85 FT_MulFix(ftface->units_per_EM, ftface->size->metrics.y_scale) / 48;
87 if (ftface->glyph->format == FT_GLYPH_FORMAT_BITMAP) {
97 FT_GlyphSlot_Own_Bitmap(ftface->glyph);
103 const FT_Pos strengthY = strength - 64;
104 FT_Bitmap_Embolden(ftface->glyph->library, &ftface->glyph->bitmap, strength, strengthY);
106 if (x_advance && *x_advance != 0) {
107 *x_advance += strength;
109 if (y_advance && *y_advance != 0) {
110 *y_advance -= strengthY;
113 FT_Outline_Embolden(&ftface->glyph->outline, strength);
115 if (x_advance && *x_advance != 0) {
116 *x_advance += strength;
118 if (y_advance && *y_advance != 0) {
119 *y_advance -= strength;
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;
130 if (FT_HAS_MULTIPLE_MASTERS(face)) {
131 isOblique = hb_style_get_value(hbFont.
data(), HB_STYLE_TAG_SLANT_ANGLE) != 0;
134 return isItalic || isOblique;
148 const raqm_glyph_t ¤tGlyph,
150 const bool isHorizontal)
152 QTransform outlineGlyphTf = QTransform::fromTranslate(currentGlyph.x_offset, currentGlyph.y_offset);
153 QTransform glyphObliqueTf;
159 constexpr double SLANT_14DEG = 0.24932800284318069162403993780486;
161 glyphObliqueTf.shear(SLANT_14DEG, 0);
167 glyphObliqueTf.shear(0, -SLANT_14DEG);
169 outlineGlyphTf *= glyphObliqueTf;
171 outlineGlyphTf *= ftTF;
172 return {outlineGlyphTf, glyphObliqueTf};
193 const unsigned short paletteIndex = 0;
203 operator bool()
const
217 std::tuple<QPainterPath, QBrush, bool>
221 bool isForeGroundColor =
false;
224 layerColor = Qt::black;
225 isForeGroundColor =
true;
228 layerColor = QColor(color.red, color.green, color.blue, color.alpha);
231 warnFlake <<
"Failed to load glyph, freetype error" << err;
234 if (
m_face->glyph->format == FT_GLYPH_FORMAT_OUTLINE) {
239 return {
p, layerColor, isForeGroundColor};
278std::pair<QTransform, qreal> KoSvgTextShape::Private::loadGlyphOnly(
const QTransform &ftTF,
279 const FT_Int32 faceLoadFlags,
280 const bool isHorizontal,
281 raqm_glyph_t ¤tGlyph,
287 QTransform glyphObliqueTf;
290 qreal bitmapScale = 1.0;
292 if (currentGlyph.index == 0) {
296 return {glyphObliqueTf, bitmapScale};
305 if (!std::holds_alternative<std::monostate>(charResult.
glyph)) {
306 warnFlake <<
"Glyph contains other type than ColorLayers:" << charResult.
glyph.index();
312 QTransform outlineGlyphTf;
315 std::tie(outlineGlyphTf, glyphObliqueTf) =
318 const int orig_x_advance = currentGlyph.x_advance;
319 const int orig_y_advance = currentGlyph.y_advance;
323 new_x_advance = orig_x_advance;
324 new_y_advance = orig_y_advance;
327 bool isForeGroundColor =
false;
328 std::tie(
p, layerColor, isForeGroundColor) = loader.layer(charResult, faceLoadFlags, &new_x_advance, &new_y_advance);
330 p = outlineGlyphTf.map(
p);
336 colorGlyph->
paths.append(
p);
337 colorGlyph->
colors.append(layerColor);
340 }
while (loader.moveNext());
341 currentGlyph.x_advance = new_x_advance;
342 currentGlyph.y_advance = new_y_advance;
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};
350 emboldenGlyphIfNeeded(currentGlyph.ftface, charResult, ¤tGlyph.x_advance, ¤tGlyph.y_advance);
352 if (currentGlyph.ftface->glyph->format == FT_GLYPH_FORMAT_OUTLINE) {
356 if (std::holds_alternative<Glyph::ColorLayers>(charResult.
glyph)) {
359 outlineGlyph = &_discard;
360 }
else if (!std::holds_alternative<std::monostate>(charResult.
glyph)) {
361 warnFlake <<
"Glyph contains other type than Outline:" << charResult.
glyph.index();
369 QTransform outlineGlyphTf;
372 std::tie(outlineGlyphTf, glyphObliqueTf) =
376 glyph = outlineGlyphTf.map(glyph);
383 outlineGlyph->
path.addPath(glyph.translated(charResult.
advance));
385 outlineGlyph->
path = glyph;
390 if (currentGlyph.ftface->glyph->format == FT_GLYPH_FORMAT_BITMAP) {
391 if (FT_HAS_COLOR(currentGlyph.ftface)) {
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);
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
405 }
else if (currentGlyph.ftface->glyph->format == FT_GLYPH_FORMAT_SVG) {
407 return {glyphObliqueTf, bitmapScale};
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;
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};
423 if (!std::holds_alternative<std::monostate>(charResult.
glyph)) {
424 warnFlake <<
"Glyph contains other type than Bitmap:" << charResult.
glyph.index();
430 bitmapGlyph->
images.append(image);
433 if (charResult.
fontStyle != QFont::StyleNormal
434 && !(currentGlyph.ftface->style_flags & FT_STYLE_FLAG_ITALIC)) {
437 constexpr double SLANT_BITMAP = 0.25;
441 shearTf.shear(-SLANT_BITMAP, 0);
442 glyphObliqueTf.shear(SLANT_BITMAP, 0);
443 shearAt = QPoint(0, currentGlyph.ftface->glyph->bitmap_top);
445 shearTf.shear(0, SLANT_BITMAP);
446 glyphObliqueTf.shear(0, -SLANT_BITMAP);
447 shearAt = QPoint(image.width() / 2, 0);
450 bitmapTf = (QTransform::fromTranslate(-shearAt.x(), -shearAt.y()) * shearTf
451 * QTransform::fromTranslate(shearAt.x(), shearAt.y())) * bitmapTf;
454 if (!bitmapTf.isIdentity()) {
455 const QSize srcSize = image.size();
456 bitmapGlyph->
images.replace(bitmapGlyph->
images.size()-1, std::move(image).transformed(
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();
468 return {glyphObliqueTf, bitmapScale};
478 const FT_Int32 faceLoadFlags,
479 const bool isHorizontal,
480 const char32_t firstCodepoint,
482 raqm_glyph_t ¤tGlyph,
484 QPointF &totalAdvanceFTFontCoordinates)
490 QTransform glyphObliqueTf;
493 qreal bitmapScale = 1.0;
496 std::tie(glyphObliqueTf, bitmapScale) = loadGlyphOnly(ftTF, faceLoadFlags, isHorizontal, currentGlyph, charResult, rendering);
508 offset = descender * slope;
511 QLineF caret(QPointF(), QPointF(lineHeight*slope, lineHeight));
512 caret.translate(-QPointF(-offset, -descender));
513 cursorInfo.
caret = ftTF.map(glyphObliqueTf.map(caret));
520 offset = descender * slope;
523 QLineF caret(QPointF(), QPointF(lineHeight, lineHeight*slope));
524 caret.translate(-QPointF(-descender, -offset));
525 cursorInfo.
caret = ftTF.map(glyphObliqueTf.map(caret));
532 QPointF advance(currentGlyph.x_advance, currentGlyph.y_advance);
537 if (std::holds_alternative<std::monostate>(charResult.
glyph)) {
540 const auto height = ftTF.map(QPointF(currentGlyph.ftface->size->metrics.height, 0)).
x() * 0.6;
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);
550 glyph.translate(glyph.boundingRect().width() * -0.5, (ftTF.map(advance).y() - height) * 0.5);
555 charResult.
advance += ftTF.map(advance);
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);
566 bboxPixel.moveLeft(-(bboxPixel.width() / 2));
567 bboxPixel.moveTop(-bboxPixel.height());
569 QRectF drawRect = ftTF.mapRect(QRectF(bboxPixel.topLeft() * resHandler.
freeTypePixel, bboxPixel.size() * resHandler.
freeTypePixel));
570 drawRect.translate(charResult.
advance - ftTF.map(advance));
578 ftTF.inverted().map(charResult.
advance).x(),
585 ftTF.inverted().map(charResult.
advance).y());
587 bbox = glyphObliqueTf.mapRect(bbox);
589 QRectF scaledBBox = resHandler.
adjust(ftTF.mapRect(bbox));
591 charResult.
scaledAscent = isHorizontal? scaledBBox.top(): scaledBBox.right();
592 charResult.
scaledDescent = isHorizontal? scaledBBox.bottom(): scaledBBox.left();
607 }
else if (
const auto *outlineGlyph = std::get_if<Glyph::Outline>(&charResult.
glyph)) {
609 }
else if (
const auto *colorGlyph = std::get_if<Glyph::ColorLayers>(&charResult.
glyph)) {
610 Q_FOREACH (
const QPainterPath &
p, colorGlyph->
paths) {
613 }
else if (!std::holds_alternative<std::monostate>(charResult.
glyph)) {
616 totalAdvanceFTFontCoordinates += advance;
617 charResult.
cssPosition = ftTF.map(totalAdvanceFTFontCoordinates) - charResult.
advance;
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));
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;
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));
644 QPointF cp = QPointF();
648 glyph.setFillRule(Qt::WindingFill);
650 for (
int j = 0; j < glyphSlot->outline.n_contours; ++j) {
651 int last_point = glyphSlot->outline.contours[j];
653 QPointF start = QPointF(glyphSlot->outline.points[i].x, glyphSlot->outline.points[i].y);
654 if (!(glyphSlot->outline.tags[i] & 1)) {
655 if (!(glyphSlot->outline.tags[last_point] & 1)) {
657 start = (QPointF(glyphSlot->outline.points[last_point].x, glyphSlot->outline.points[last_point].y) + start) / 2.0;
660 start = QPointF(glyphSlot->outline.points[last_point].x, glyphSlot->outline.points[last_point].y);
667 std::array<QPointF, 4> curve;
670 while (i < last_point) {
672 curve.at(n) = cp + QPointF(glyphSlot->outline.points[i].x, glyphSlot->outline.points[i].y);
678 switch (glyphSlot->outline.tags[i] & 3) {
683 curve[3] = (curve[3] + curve[2]) / 2;
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;
699 glyph.lineTo(curve[1]);
705 curve[2] = (2 * curve[1] + curve[3]) / 3;
706 curve[1] = (2 * curve[1] + curve[0]) / 3;
711 glyph.cubicTo(curve[1], curve[2], curve[3]);
717 glyph.closeSubpath();
721 curve[2] = (2 * curve[1] + curve[3]) / 3;
722 curve[1] = (2 * curve[1] + curve[0]) / 3;
725 glyph.cubicTo(curve[1], curve[2], curve[3]);
734 KIS_ASSERT(glyphSlot->bitmap.width <= INT32_MAX);
735 KIS_ASSERT(glyphSlot->bitmap.rows <= INT32_MAX);
737 const int height =
static_cast<int>(glyphSlot->bitmap.rows);
738 const QSize
size(
static_cast<int>(glyphSlot->bitmap.width), height);
740 if (glyphSlot->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) {
741 img = QImage(
size, QImage::Format_Mono);
742 uchar *src = glyphSlot->bitmap.buffer;
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;
748 }
else if (glyphSlot->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) {
749 img = QImage(
size, QImage::Format_Grayscale8);
750 uchar *src = glyphSlot->bitmap.buffer;
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;
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]);
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 ¤tGlyph, 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.
FT_UInt m_layerColorIndex
FT_LayerIterator m_iterator
std::tuple< QPainterPath, QBrush, bool > layer(const CharacterResult &charResult, const FT_Int32 faceLoadFlags, int *x_advance, int *y_advance)
Load the current glyph layer.
FT_UInt m_layerGlyphIndex
virtual QSizeF size() const
Get the size of the shape in pt.
KoShapeAnchor * anchor() const
static bool qFuzzyCompare(half p1, half p2)
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
QPainterPath create(const char32_t codepoint, double height)
Creates a tofu missing glyph indicator representing the provided Unicode codepoint.
QRectF inkBoundingBox
The bounds of the drawn glyph. Different from the bounds the charresult takes up in the layout,...
qreal scaledDescent
Descender, in pt.
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.
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< QRectF > drawRects
QVector< bool > replaceWithForeGroundColor
QVector< QPainterPath > paths
void scaleBaselines(const qreal multiplier)
qint32 caretRun
These are only used to determine the caret slant proportion.
qint32 descender
distance for origin to bottom.
qint32 ascender
distance from origin to top.
The ResolutionHandler class.
const qreal freeTypePixel
QTransform freeTypeToPointTransform() const
QPointF adjust(const QPointF point) const
Adjusts the point to rounded pixel values, based on whether roundToPixelHorizontal or roundToPixelVer...