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);
499 hb_font_t_sp font(hb_ft_font_create_referenced(currentGlyph.ftface));
509 offset = descender * slope;
512 QLineF caret(QPointF(), QPointF(lineHeight*slope, lineHeight));
513 caret.translate(-QPointF(-offset, -descender));
514 cursorInfo.
caret = ftTF.map(glyphObliqueTf.map(caret));
518 hb_direction_t dir = HB_DIRECTION_LTR;
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;
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));
529 cursorInfo.
offsets = positions;
535 offset = descender * slope;
538 QLineF caret(QPointF(), QPointF(lineHeight, lineHeight*slope));
539 caret.translate(-QPointF(-descender, -offset));
540 cursorInfo.
caret = ftTF.map(glyphObliqueTf.map(caret));
547 QPointF advance(currentGlyph.x_advance, currentGlyph.y_advance);
552 if (std::holds_alternative<std::monostate>(charResult.
glyph)) {
555 const auto height = ftTF.map(QPointF(currentGlyph.ftface->size->metrics.height, 0)).
x() * 0.6;
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);
565 glyph.translate(glyph.boundingRect().width() * -0.5, (ftTF.map(advance).y() - height) * 0.5);
570 charResult.
advance += ftTF.map(advance);
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);
581 bboxPixel.moveLeft(-(bboxPixel.width() / 2));
582 bboxPixel.moveTop(-bboxPixel.height());
584 QRectF drawRect = ftTF.mapRect(QRectF(bboxPixel.topLeft() * resHandler.
freeTypePixel, bboxPixel.size() * resHandler.
freeTypePixel));
585 drawRect.translate(charResult.
advance - ftTF.map(advance));
593 ftTF.inverted().map(charResult.
advance).x(),
600 ftTF.inverted().map(charResult.
advance).y());
602 bbox = glyphObliqueTf.mapRect(bbox);
604 QRectF scaledBBox = resHandler.
adjust(ftTF.mapRect(bbox));
606 charResult.
scaledAscent = isHorizontal? scaledBBox.top(): scaledBBox.right();
607 charResult.
scaledDescent = isHorizontal? scaledBBox.bottom(): scaledBBox.left();
622 }
else if (
const auto *outlineGlyph = std::get_if<Glyph::Outline>(&charResult.
glyph)) {
624 }
else if (
const auto *colorGlyph = std::get_if<Glyph::ColorLayers>(&charResult.
glyph)) {
625 Q_FOREACH (
const QPainterPath &
p, colorGlyph->
paths) {
628 }
else if (!std::holds_alternative<std::monostate>(charResult.
glyph)) {
631 totalAdvanceFTFontCoordinates += advance;
632 charResult.
cssPosition = ftTF.map(totalAdvanceFTFontCoordinates) - charResult.
advance;
640 QPointF cp = QPointF();
644 glyph.setFillRule(Qt::WindingFill);
646 for (
int j = 0; j < glyphSlot->outline.n_contours; ++j) {
647 int last_point = glyphSlot->outline.contours[j];
649 QPointF start = QPointF(glyphSlot->outline.points[i].x, glyphSlot->outline.points[i].y);
650 if (!(glyphSlot->outline.tags[i] & 1)) {
651 if (!(glyphSlot->outline.tags[last_point] & 1)) {
653 start = (QPointF(glyphSlot->outline.points[last_point].x, glyphSlot->outline.points[last_point].y) + start) / 2.0;
656 start = QPointF(glyphSlot->outline.points[last_point].x, glyphSlot->outline.points[last_point].y);
663 std::array<QPointF, 4> curve;
666 while (i < last_point) {
668 curve.at(n) = cp + QPointF(glyphSlot->outline.points[i].x, glyphSlot->outline.points[i].y);
674 switch (glyphSlot->outline.tags[i] & 3) {
679 curve[3] = (curve[3] + curve[2]) / 2;
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;
695 glyph.lineTo(curve[1]);
701 curve[2] = (2 * curve[1] + curve[3]) / 3;
702 curve[1] = (2 * curve[1] + curve[0]) / 3;
707 glyph.cubicTo(curve[1], curve[2], curve[3]);
713 glyph.closeSubpath();
717 curve[2] = (2 * curve[1] + curve[3]) / 3;
718 curve[1] = (2 * curve[1] + curve[0]) / 3;
721 glyph.cubicTo(curve[1], curve[2], curve[3]);
730 KIS_ASSERT(glyphSlot->bitmap.width <= INT32_MAX);
731 KIS_ASSERT(glyphSlot->bitmap.rows <= INT32_MAX);
733 const int height =
static_cast<int>(glyphSlot->bitmap.rows);
734 const QSize
size(
static_cast<int>(glyphSlot->bitmap.width), height);
736 if (glyphSlot->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) {
737 img = QImage(
size, QImage::Format_Mono);
738 uchar *src = glyphSlot->bitmap.buffer;
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;
744 }
else if (glyphSlot->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) {
745 img = QImage(
size, QImage::Format_Grayscale8);
746 uchar *src = glyphSlot->bitmap.buffer;
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;
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]);
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< QPointF > offsets
The advance offsets for each grapheme index.
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...