24#include <QPainterPath>
29#include <graphemebreak.h>
35#include FT_TRUETYPE_TABLES_H
58 ,
const bool <r =
false) {
59 QMap<int, int> logicalToVisual;
60 for (
int i = 0; i < lines.size(); i++) {
61 Q_FOREACH(
const LineChunk chunk, lines.at(i).chunks) {
62 QMap<int, int> visualToLogical;
65 visualToLogical.insert(result.at(j).visualIndex, j);
67 Q_FOREACH(
const int j, visualToLogical.values()) {
68 QMap<int, int> relevant;
69 for (
int k = 0; k < cursorPos.size(); k++) {
70 if (j == cursorPos.at(k).cluster) {
71 relevant.insert(cursorPos.at(k).offset, k);
74 Q_FOREACH(
const int k, relevant.keys()) {
75 int final = result.at(j).cursorInfo.rtl? relevant.size()-1-k: k;
76 visual.append(relevant.value(
final));
81 for (
int k = 0; k < visual.size(); k++) {
82 logicalToVisual.insert(visual.at(k), logicalToVisual.size());
85 for (
int k = visual.size()-1; k > -1; k--) {
86 logicalToVisual.insert(visual.at(k), logicalToVisual.size());
92 return logicalToVisual;
98 if (locale.language() == QLocale::Japanese || locale.script() == QLocale::JapaneseScript) {
100 }
else if (locale.language() == QLocale::Korean || locale.script() == QLocale::KoreanScript || locale.script() == QLocale::HangulScript) {
102 }
else if (locale.script() == QLocale::SimplifiedChineseScript
103 || locale.script() == QLocale::TraditionalChineseScript
104 || locale.script() == QLocale::HanWithBopomofoScript
105 || locale.script() == QLocale::HanScript
106 || locale.script() == QLocale::BopomofoScript) {
113void KoSvgTextShape::Private::updateTextWrappingAreas()
116 currentTextWrappingAreas = getShapes(shapesInside, shapesSubtract, rootProperties);
122 return getShapes(shapesInside, shapesSubtract, properties);
125void KoSvgTextShape::Private::relayout()
127 clearAssociatedOutlines();
128 this->initialTextPosition = QPointF();
129 this->result.clear();
130 this->cursorPos.clear();
131 this->logicalToVisualCursorPos.clear();
133 bool disableFontMatching = this->disableFontMatching;
149 const auto getUcs4At = [](
const QString &
s,
int i) ->
char32_t {
150 const QChar high =
s.at(i);
151 if (!high.isSurrogate()) {
152 return high.unicode();
154 if (high.isHighSurrogate() &&
s.length() > i + 1) {
155 const QChar low =
s[i + 1];
156 if (low.isLowSurrogate()) {
157 return QChar::surrogateToUcs4(high, low);
163 return high.unicode();
173 bool _ignore =
false;
178 QMap<int, KoSvgText::TextSpaceCollapse> collapseModes;
180 Q_FOREACH (
const SubChunk &chunk, textChunks) {
183 int a = pos.second < 0? -1: text.size()+pos.second;
184 int b = pos.first < 0? -1: plainText.size()+pos.first;
185 QPair<int, int> newPos = QPair<int, int> (a, b);
186 clusterToOriginalString.append(newPos);
189 collapseModes.insert(text.size(), collapse);
190 text.append(chunk.
text);
194 debugFlake <<
"Laying out the following text: " << text;
204 if (text.size() > 0) {
208 if (!lang.isEmpty()) {
212 unibreakLang +=
"-strict";
215 set_linebreaks_utf16(text.utf16(),
static_cast<size_t>(text.size()), unibreakLang.toUtf8().data(), lineBreaks.data());
216 set_wordbreaks_utf16(text.utf16(),
static_cast<size_t>(text.size()), unibreakLang.toUtf8().data(), wordBreaks.data());
217 set_graphemebreaks_utf16(text.utf16(),
static_cast<size_t>(text.size()), unibreakLang.toUtf8().data(), graphemeBreaks.data());
228 for (
int i = 0; i < text.size(); i++) {
229 if (lineBreaks[i] == LINEBREAK_MUSTBREAK) {
230 text[i] = QChar::Space;
233 for (
int i=0; i < clusterToOriginalString.size(); i++) {
234 QPair<int, int> mapping = clusterToOriginalString.at(i);
235 if (mapping.first < 0) {
238 if (mapping.first < result.size()) {
239 result[mapping.first].plaintTextIndex = mapping.second;
256 bool wrapped = !(inlineSize.
isAuto && this->shapesInside.isEmpty());
257 if (!resolvedTransforms.isEmpty()) {
258 resolvedTransforms[0].xPos = 0;
259 resolvedTransforms[0].yPos = 0;
261 this->resolveTransforms(textData.childBegin(), text, result, globalIndex, isHorizontal, wrapped,
false, resolvedTransforms, collapseChars,
KoSvgTextProperties::defaultProperties(),
true);
266 if (raqm_set_text_utf16(
layout.data(), text.utf16(),
static_cast<size_t>(text.size()))) {
268 raqm_set_par_direction(
layout.data(), raqm_direction_t::RAQM_DIRECTION_TTB);
270 raqm_set_par_direction(
layout.data(), raqm_direction_t::RAQM_DIRECTION_RTL);
272 raqm_set_par_direction(
layout.data(), raqm_direction_t::RAQM_DIRECTION_LTR);
276 Q_FOREACH (
const SubChunk &chunk, textChunks) {
285 KoSvgText::HangingPunctuations hang =
296 if ((localLinebreakStrictness != linebreakStrictness && localLinebreakStrictness !=
KoSvgText::LineBreakAnywhere) || localLang != lang ) {
299 unibreakLang +=
"-strict";
301 int localLineBreakStart = qMax(0, start -1);
302 int localLineBreakEnd = qMin(text.size(), start+chunk.
text.size());
303 QVector<char> localLineBreaks(localLineBreakEnd - localLineBreakStart);
304 set_linebreaks_utf16(text.mid(localLineBreakStart, localLineBreaks.size()).utf16(),
305 static_cast<size_t>(localLineBreaks.size()),
306 unibreakLang.toUtf8().data(),
307 localLineBreaks.data());
308 for (
int i = 0; i < localLineBreaks.size(); i++) {
309 if (i < (start - localLineBreakStart))
continue;
310 if (i+localLineBreakStart > start+chunk.
text.size())
break;
311 lineBreaks[i+localLineBreakStart] = localLineBreaks[i];
319 fillColor =
b->color();
321 if (!letterSpacing.isAuto) {
322 tabInfo.extraSpacing += letterSpacing.length.value;
324 if (!wordSpacing.isAuto) {
325 tabInfo.extraSpacing += wordSpacing.length.value;
328 for (
int i = 0; i <
length; i++) {
332 QPair<bool, bool> canJustify = justify.value(start + i, QPair<bool, bool>(
false,
false));
336 if (lineBreaks[start + i] == LINEBREAK_MUSTBREAK) {
340 }
else if (lineBreaks[start + i] == LINEBREAK_ALLOWBREAK && wrap !=
KoSvgText::NoWrap) {
349 const auto isFollowedByForcedLineBreak = [&]() {
350 if (result.size() <= start + i + 1) {
354 if (lineBreaks[start + i+ 1] == LINEBREAK_MUSTBREAK) {
358 if (resolvedTransforms.at(start + i + 1).startsNewChunk()) {
364 bool forceHang =
false;
406 if (resolvedTransforms.at(start + i).startsNewChunk()) {
407 raqm_set_arbitrary_run_break(
layout.data(),
static_cast<size_t>(start + i),
true);
413 result[start + i] = cr;
427 static_cast<quint32
>(resHandler.xRes),
428 static_cast<quint32
>(resHandler.yRes),
429 disableFontMatching);
432 raqm_set_language(
layout.data(),
434 static_cast<size_t>(start),
435 static_cast<size_t>(
length));
437 Q_FOREACH (
const QString &feature, fontFeatures) {
439 raqm_add_font_feature(
layout.data(), feature.toUtf8(), feature.toUtf8().size());
442 if (!letterSpacing.isAuto) {
443 raqm_set_letter_spacing_range(
layout.data(),
444 static_cast<int>(letterSpacing.length.value * resHandler.freeTypePixel * resHandler.pointToPixelFactor(isHorizontal)),
445 static_cast<size_t>(start),
446 static_cast<size_t>(
length));
449 if (!wordSpacing.isAuto) {
450 raqm_set_word_spacing_range(
layout.data(),
451 static_cast<int>(wordSpacing.length.value * resHandler.freeTypePixel * resHandler.pointToPixelFactor(isHorizontal)),
452 static_cast<size_t>(start),
453 static_cast<size_t>(
length));
456 for (
int i = 0; i < lengths.size(); i++) {
458 const FT_FaceSP &face = faces.at(
static_cast<size_t>(i));
461 raqm_set_freetype_face(
layout.data(), face.
data());
462 raqm_set_freetype_load_flags(
layout.data(), faceLoadFlags);
465 raqm_set_freetype_face_range(
layout.data(),
467 static_cast<size_t>(start),
468 static_cast<size_t>(
length));
469 raqm_set_freetype_load_flags_range(
layout.data(),
471 static_cast<size_t>(start),
472 static_cast<size_t>(
length));
475 QHash<QChar::Script, KoSvgText::FontMetrics> metricsList;
476 for (
int j=start; j<start+
length; j++) {
477 const QChar::Script currentScript = QChar::script(getUcs4At(text, j));
478 if (!metricsList.contains(currentScript)) {
481 result[j].metrics = metricsList.value(currentScript);
482 if (fontSize < 1.0) {
483 result[j].extraFontScaling = fontSize;
488 if (text.at(j) == QChar::Tabulation) {
490 if (tabInfo.isNumber) {
492 if (result[j].metrics.spaceAdvance > 0) {
493 tabSize = (result[j].metrics.spaceAdvance + (tabInfo.extraSpacing*resHandler.freeTypePixel)) * tabInfo.value;
495 tabSize = ((result[j].metrics.fontSize/2) + (tabInfo.extraSpacing*resHandler.freeTypePixel)) * tabInfo.value;
498 tabSize = tabInfo.length.value * resHandler.freeTypePixel;
500 result[j].tabSize = tabSize;
503 result[j].fontHalfLeading = currentMetrics.
lineGap / 2;
504 result[j].fontStyle = synthesizeStyle? style.
style: QFont::StyleNormal;
514 if (!result.empty()) {
515 result[0].anchored_chunk =
true;
518 if (raqm_layout(
layout.data())) {
525 const raqm_glyph_t *glyphs = raqm_get_glyphs(
layout.data(), &count);
530 QPointF totalAdvanceFTFontCoordinates;
531 QMap<int, int> logicalToVisual;
532 this->isBidi =
false;
538 int previousGlyph = -1;
539 int previousCluster = -1;
541 for (
int i = 0; i < static_cast<int>(count); i++) {
542 raqm_glyph_t currentGlyph = glyphs[i];
543 KIS_ASSERT(currentGlyph.cluster <= INT32_MAX);
544 const int cluster =
static_cast<int>(currentGlyph.cluster);
545 if (!result[cluster].addressable) {
553 const char32_t codepoint = getUcs4At(text, cluster);
554 debugFlake <<
"glyph" << i <<
"cluster" << cluster << currentGlyph.index << codepoint;
556 charResult.
cursorInfo.
rtl = raqm_get_direction_at_index(
layout.data(), cluster) == RAQM_DIRECTION_RTL;
561 if (!this->loadGlyph(resHandler,
568 totalAdvanceFTFontCoordinates)) {
575 if (((cluster - previousCluster) > (i-previousGlyph)) && previousCluster >= 0) {
576 raqm_glyph_t previousGlyph = glyphs[i];
577 result[previousCluster].cursorInfo.offsets = getLigatureCarets(resHandler, isHorizontal, previousGlyph);
581 charResult.
cursorInfo.
offsets = getLigatureCarets(resHandler, isHorizontal, currentGlyph);
585 logicalToVisual.insert(cluster, i);
587 charResult.
middle =
false;
589 result[cluster] = charResult;
590 if (previousCluster != cluster) {
592 previousCluster = cluster;
599 int firstCluster = -1;
600 bool graphemeBreakNext =
false;
601 for (
int i = 0; i < result.size(); i++) {
602 result[i].
middle = result.at(i).visualIndex == -1;
603 if (result[i].addressable && !result.at(i).middle) {
604 if (result.at(i).plaintTextIndex > -1 && firstCluster > -1) {
605 CursorInfo info = result.at(firstCluster).cursorInfo;
609 info.
offsets.append(result.at(firstCluster).advance);
612 result[firstCluster].cursorInfo = info;
616 int fC = qMax(0, firstCluster);
617 if (text[fC].isSpace() == text[i].isSpace()) {
619 result[fC].breakType = result.at(i).breakType;
622 result[fC].lineStart = result.at(i).lineStart;
625 result[fC].lineEnd = result.at(i).lineEnd;
628 if (graphemeBreakNext && result[i].addressable && result.at(i).plaintTextIndex > -1) {
629 result[fC].cursorInfo.graphemeIndices.append(result.at(i).plaintTextIndex);
631 result[i].cssPosition = result.at(fC).cssPosition + result.at(fC).advance;
632 result[i].hidden =
true;
634 graphemeBreakNext = graphemeBreaks[i] == GRAPHEMEBREAK_BREAK;
636 int fC = qMax(0, firstCluster);
637 if (result.at(fC).cursorInfo.graphemeIndices.isEmpty() || graphemeBreakNext) {
638 result[fC].cursorInfo.graphemeIndices.append(plainText.size());
666 result.insert(dummyIndex, dummy);
667 logicalToVisual.insert(dummyIndex, dummy.
visualIndex);
678 QPointF startPos = resolvedTransforms.value(0).absolutePos() - result.value(0).dominantBaselineOffset;
679 if (!this->currentTextWrappingAreas.isEmpty()) {
680 this->lineBoxes = flowTextInShapes(rootProperties, logicalToVisual, result, this->currentTextWrappingAreas, startPos, resHandler);
682 this->lineBoxes = breakLines(rootProperties, logicalToVisual, result, startPos, resHandler);
689 if (inlineSize.
isAuto && this->shapesInside.isEmpty()) {
690 debugFlake <<
"Starting with SVG 1.1 specific portion";
693 QPointF shift = QPointF();
694 bool setAnchoredChunk =
false;
695 for (
int i = 0; i < result.size(); i++) {
696 if (result.at(i).addressable) {
708 if (setAnchoredChunk) {
710 setAnchoredChunk =
false;
714 setAnchoredChunk =
true;
719 result[i] = charResult;
724 debugFlake <<
"5. Apply ‘textLength’ attribute";
733 for (
int i = 0; i < result.size(); i++) {
734 if (result.at(i).addressable) {
746 if (charResult.
middle && i-1 >=0) {
750 result[i] = charResult;
756 applyAnchoring(result, isHorizontal, resHandler);
761 debugFlake <<
"Now Computing text-decorations";
763 this->computeTextDecorations(textData.childBegin(),
781 debugFlake <<
"Computing text-decorationsfor inline-size";
782 this->computeTextDecorations(textData.childBegin(),
799 for (
auto chunk = textChunks.begin(); chunk != textChunks.end(); chunk++) {
800 const int j = chunk->
text.size();
802 for (
int i = globalIndex; i < chunk->
associatedLeaf->finalResultIndex; i++) {
803 if (result.at(i).addressable && !result.at(i).middle) {
805 if (result.at(i).plaintTextIndex > -1) {
807 bool insertFirst =
false;
808 if (result.at(i).anchored_chunk) {
811 pos.
index = result.at(i).plaintTextIndex;
813 QPointF newOffset = result.at(i).cursorInfo.rtl? result.at(i).advance: QPointF();
814 result[i].cursorInfo.offsets.insert(0, newOffset);
815 positions.append(newOffset);
818 cursorPos.append(pos);
821 int graphemes = result.at(i).cursorInfo.graphemeIndices.size();
822 for (
int k = 0; k < graphemes; k++) {
828 pos.
index = result.at(i).cursorInfo.graphemeIndices.at(k);
829 pos.
offset = insertFirst? k+1: k;
830 cursorPos.append(pos);
831 QPointF offset = (k+1) * (result.at(i).advance/graphemes);
832 positions.append(result.at(i).cursorInfo.rtl? result.at(i).advance - offset: offset);
835 result[i].cursorInfo.graphemeIndices.insert(0, result[i].plaintTextIndex);
837 if (result.at(i).cursorInfo.offsets.size() < positions.size()) {
838 result[i].cursorInfo.offsets = positions;
843 if (!result.at(i).hidden) {
844 const QTransform tf = result.at(i).finalTransform();
845 chunk->
associatedLeaf->associatedOutline.addRect(tf.mapRect(result.at(i).inkBoundingBox));
852 if (dummyIndex > -1 && dummyIndex < result.size()) {
853 if (result.at(dummyIndex).anchored_chunk) {
856 pos.
index = result.at(dummyIndex).plaintTextIndex;
857 result[dummyIndex].plaintTextIndex -= 1;
858 result[dummyIndex].cursorInfo.offsets.insert(0, QPointF());
861 cursorPos.append(pos);
862 if (!textChunks.isEmpty()) {
863 textChunks.last().associatedLeaf->associatedOutline.addRect(result.at(dummyIndex).finalTransform().mapRect(result[dummyIndex].inkBoundingBox));
864 textChunks.last().associatedLeaf->finalResultIndex = result.size();
868 this->initialTextPosition = startPos;
869 this->plainText = plainText;
870 this->result = result;
871 this->cursorPos = cursorPos;
875void KoSvgTextShape::Private::clearAssociatedOutlines()
877 for (
auto it = textData.depthFirstTailBegin(); it != textData.depthFirstTailEnd(); it++) {
878 it->associatedOutline = QPainterPath();
879 it->textDecorations.clear();
880 it->finalResultIndex = -1;
888const QString
bidiControls =
"\u202a\u202b\u202c\u202d\u202e\u2066\u2067\u2068\u2069";
891 bool isHorizontal,
bool wrapped,
bool textInPath,
897 int index = currentIndex;
898 int j = index + numChars(currentTextElement, withControls, resolvedProps);
900 if (!currentTextElement->textPathId.isEmpty()) {
904 for (
int k = index; k < j; k++ ) {
905 if (k >= text.size()) {
910 bool softHyphen = text.at(k) == QChar::SoftHyphen;
914 if (collapsedChars[k] || (bidi && !wrapped) || softHyphen) {
915 result[k].addressable =
false;
918 if (k > 0 && text.at(k).isLowSurrogate() && text.at(k-1).isHighSurrogate()) {
920 result[k].addressable =
false;
924 if (i < local.size()) {
928 resolved[k] = newTransform;
932 resolved[k].rotate = resolved[k - 1].rotate;
941 resolveTransforms(child, text, result, currentIndex, isHorizontal,
false, textInPath, resolved, collapsedChars, props, withControls);
945 if (!currentTextElement->textPathId.isEmpty()) {
947 for (
int k = index; k < j; k++ ) {
949 if (!result[k].addressable) {
956 resolved[k].xPos = 0.0;
958 resolved[k].yPos = 0.0;
966 resolved[k].yPos.reset();
968 resolved[k].xPos.reset();
981 int &resolvedDescendentNodes,
986 int i = currentIndex;
987 int j = i + numChars(currentTextElement,
true, resolvedProps);
988 int resolvedChildren = 0;
993 applyTextLength(child, result, currentIndex, resolvedChildren, isHorizontal, props, resHandler);
998 QMap<int, int> visualToLogical;
999 if (!currentTextElement->textLength.isAuto) {
1003 for (
int k = i; k < j; k++) {
1004 if (result.at(k).addressable) {
1005 if (result.at(k).visualIndex > -1) {
1006 visualToLogical.insert(result.at(k).visualIndex, k);
1010 qreal pos = result.at(k).finalPosition.x();
1011 qreal advance = result.at(k).advance.x();
1012 if (!isHorizontal) {
1013 pos = result.at(k).finalPosition.y();
1014 advance = result.at(k).advance.y();
1017 a = qMin(pos, pos + advance);
1018 b = qMax(pos, pos + advance);
1020 a = qMin(a, qMin(pos, pos + advance));
1021 b = qMax(b, qMax(pos, pos + advance));
1023 if (!result.at(k).textLengthApplied) {
1028 n += resolvedChildren;
1030 if (!spacingAndGlyphs) {
1033 const qreal delta = currentTextElement->textLength.customValue - (
b - a);
1035 const QPointF
d = isHorizontal ? QPointF(delta / n, 0) : QPointF(0, delta / n);
1038 bool secondTextLengthApplied =
false;
1039 Q_FOREACH (
int k, visualToLogical.keys()) {
1044 if (spacingAndGlyphs) {
1045 QPointF
scale(
d.x() != 0 ? (
d.x() / cr.
advance.x()) + 1 : 1.0,
d.
y() != 0 ? (
d.
y() / cr.advance.
y()) + 1 : 1.0);
1048 bool last = spacingAndGlyphs ? false : k == visualToLogical.keys().last();
1051 shift = resHandler.
adjust(shift+
d);
1056 result[visualToLogical.value(k)] = cr;
1058 resolvedDescendentNodes += 1;
1062 int lastVisualValue = visualToLogical.keys().last();
1063 visualToLogical.clear();
1065 for (
int k = j; k < result.size(); k++) {
1066 if (result.at(k).anchored_chunk) {
1069 visualToLogical.insert(result.at(k).visualIndex, k);
1072 for (
int k = i; k > -1; k--) {
1073 visualToLogical.insert(result.at(k).visualIndex, k);
1074 if (result.at(k).anchored_chunk) {
1078 Q_FOREACH (
int k, visualToLogical.keys()) {
1079 if (k > lastVisualValue) {
1080 result[visualToLogical.value(k)].finalPosition += shift;
1093void KoSvgTextShape::Private::computeFontMetrics(
1101 const bool isHorizontal,
1102 const bool disableFontMatching)
1104 const int i = currentIndex;
1105 const int j = qMin(i + numChars(
parent,
true, parentProps), result.size());
1112 QPointF baselineShiftTotal;
1117 baselineShiftTotal = isHorizontal ? superScript : QPointF(-superScript.y(), superScript.x());
1120 baselineShiftTotal = isHorizontal ? subScript : QPointF(-subScript.y(), subScript.x());
1123 baselineShiftTotal = isHorizontal ? QPointF(0, -baselineShift.
value) : QPointF(baselineShift.
value, 0);
1125 baselineShiftTotal = resHandler.
adjust(baselineShiftTotal);
1138 static_cast<quint32
>(resHandler.
xRes),
1139 static_cast<quint32
>(resHandler.
yRes),
1140 disableFontMatching);
1155 dominantBaseline = defaultBaseline;
1159 computeFontMetrics(child, properties, metrics, dominantBaseline, result, currentIndex, resHandler, isHorizontal, disableFontMatching);
1165 baselineAdjust = parentBaseline;
1169 baselineAdjust = defaultBaseline;
1174 const QPointF shift = resHandler.
adjust(ftTf.map(isHorizontal? QPointF(0, (boxOffset)): QPointF((boxOffset), 0)));
1177 baselineShiftTotal -= shift;
1191 for (
int k = i; k < j; k++) {
1192 if (applyGlyphAlignment) {
1195 const int originOffset = result[k].metrics.valueForBaselineValue(dominantBaseline);
1196 const QPointF newOrigin = resHandler.
adjust(ftTf.map(isHorizontal? QPointF(0, originOffset): QPointF(originOffset, 0)));
1198 const QPointF uniqueShift = resHandler.
adjust(ftTf.map(isHorizontal? QPointF(0, uniqueOffset): QPointF(uniqueOffset, 0)));
1199 result[k].translateOrigin(newOrigin);
1200 result[k].metrics.offsetMetricsToNewOrigin(dominantBaseline);
1201 result[k].dominantBaselineOffset = uniqueShift;
1203 result[k].dominantBaselineOffset += shift;
1204 result[k].baselineOffset += baselineShiftTotal;
1214 const bool isHorizontal,
1218 const int i = currentIndex;
1219 const int j = qMin(i + numChars(
parent,
true, resolvedProps), result.size());
1226 handleLineBoxAlignment(child, result, lineBoxes, currentIndex, isHorizontal, properties);
1232 Q_FOREACH(
LineBox lineBox, lineBoxes) {
1235 relevantLine = lineBox;
1239 QPointF shift = QPointF();
1240 double ascent = 0.0;
1241 double descent = 0.0;
1242 for (
int k = i; k < j; k++) {
1245 calculateLineHeight(result[k], ascent, descent, isHorizontal,
true);
1250 shift -= isHorizontal? QPointF(0, ascent):QPointF(ascent, 0);
1253 shift -= isHorizontal? QPointF(0, descent):QPointF(descent, 0);
1257 for (
int k = i; k < j; k++) {
1275void KoSvgTextShape::Private::computeTextDecorations(
1278 const QMap<int, int> &logicalToVisual,
1281 qreal textPathoffset,
1291 const int i = currentIndex;
1292 const int j = qMin(i + numChars(currentTextElement,
true, resolvedProps), result.size());
1296 qreal currentTextPathOffset = textPathoffset;
1297 bool textPathSide = side;
1299 KoShape *cTextPath = KoSvgTextShape::Private::textPathByName(currentTextElement->textPathId, textPaths);
1300 currentTextPath = textPath ? textPath :
dynamic_cast<KoPathShape *
>(cTextPath);
1304 if (currentTextElement->textPathInfo.startOffsetIsPercentage) {
1306 currentTextPathOffset = currentTextPath->
outline().length() * (0.01 * currentTextElement->textPathInfo.startOffset);
1308 currentTextPathOffset = currentTextElement->textPathInfo.startOffset;
1320 computeTextDecorations(child,
1325 currentTextPathOffset,
1331 properties, textPaths
1342 QMap<TextDecoration, QPainterPath> decorationPaths =
1343 generateDecorationPaths(i, j, resHandler,
1344 result, isHorizontal, decor, style,
false, currentTextPath,
1345 currentTextPathOffset, textPathSide, newUnderlinePosH, newUnderlinePosV
1351 QPainterPath decorationPath = decorationPaths.value(type);
1352 if (!decorationPath.isEmpty()) {
1353 currentTextElement->textDecorations.insert(type, decorationPath.simplified());
1362 const qreal strokeWidth,
1364 const bool isHorizontal,
1365 const bool onTextPath,
1366 const qreal minimumDecorationThickness
1371 p.moveTo(QPointF());
1375 const int total = std::floor(
length.length() / (strokeWidth * 2));
1376 const qreal segment = qreal(
length.length() / total);
1378 for (
int i = 0; i < total; i++) {
1379 p.lineTo(
p.currentPosition() + QPointF(segment, 0));
1382 for (
int i = 0; i < total; i++) {
1383 p.lineTo(
p.currentPosition() + QPointF(0, segment));
1395 qreal linewidthOffset = qMax(strokeWidth * 1.5, minimumDecorationThickness * 2);
1397 p.addPath(
p.translated(0, linewidthOffset));
1398 pathWidth = QPointF(0, -linewidthOffset);
1400 p.addPath(
p.translated(linewidthOffset, 0));
1401 pathWidth = QPointF(linewidthOffset, 0);
1405 qreal width =
length.length();
1406 qreal height = strokeWidth * 2;
1409 p.moveTo(QPointF());
1411 for (
int i = 0; i < qFloor(width / height); i++) {
1413 p.lineTo(
p.currentPosition().x() + height, height);
1415 p.lineTo(
p.currentPosition().x() + height, 0);
1419 qreal offset = fmod(width, height);
1421 p.lineTo(width, offset);
1423 p.lineTo(width, height - offset);
1425 pathWidth = QPointF(0, -strokeWidth);
1428 if (!isHorizontal) {
1429 for (
int i = 0; i <
p.elementCount(); i++) {
1430 p.setElementPositionAt(i,
p.elementAt(i).y - (strokeWidth * 2),
p.elementAt(i).x);
1432 pathWidth = QPointF(strokeWidth, 0);
1435 return qMakePair(
p, pathWidth);
1438void KoSvgTextShape::Private::finalizeDecoration (
1439 QPainterPath decorationPath,
1440 const QPointF offset,
1441 const QPainterPathStroker &stroker,
1443 QMap<KoSvgText::TextDecoration, QPainterPath> &decorationPaths,
1445 const bool isHorizontal,
1446 const qreal currentTextPathOffset,
1447 const bool textPathSide
1449 if (currentTextPath) {
1450 QPainterPath path = currentTextPath->
outline();
1453 path = path.toReversed();
1456 decorationPath = stretchGlyphOnPath(decorationPath.translated(offset), path, isHorizontal, currentTextPathOffset, currentTextPath->
isClosedSubpath(0));
1457 decorationPaths[type].addPath(stroker.createStroke(decorationPath));
1459 decorationPaths[type].addPath(stroker.createStroke(decorationPath.translated(offset)));
1461 decorationPaths[type].setFillRule(Qt::WindingFill);
1464QMap<KoSvgText::TextDecoration, QPainterPath>
1465KoSvgTextShape::Private::generateDecorationPaths(
const int &start,
const int &end,
1468 const bool isHorizontal,
1469 const KoSvgText::TextDecorations &decor,
1471 const bool textDecorationSkipInset,
1473 const qreal currentTextPathOffset,
1474 const bool textPathSide,
1480 const qreal minimumDecorationThickness = resHandler.
pixelToPointFactor(isHorizontal);
1482 QMap<TextDecoration, QPainterPath> decorationPaths;
1484 decorationPaths.insert(DecorationUnderline, QPainterPath());
1485 decorationPaths.insert(DecorationOverline, QPainterPath());
1486 decorationPaths.insert(DecorationLineThrough, QPainterPath());
1488 QPainterPathStroker stroker;
1489 stroker.setCapStyle(Qt::FlatCap);
1490 if (style == Dotted) {
1492 pen.setStyle(Qt::DotLine);
1493 stroker.setDashPattern(pen.dashPattern());
1494 }
else if (style == Dashed) {
1496 pen.setStyle(Qt::DashLine);
1497 stroker.setDashPattern(pen.dashPattern());
1500 struct DecorationBox {
1501 QRectF decorationRect;
1503 qint32 thickness = 0;
1508 struct LineThrough {
1510 qint32 thickness = 0;
1515 DecorationBox currentBox;
1516 currentBox.start = start;
1519 for (
int k = start; k < end; k++) {
1521 const bool atEnd = k+1 >= end;
1522 if ((charResult.
anchored_chunk || atEnd) && currentBox.start < k) {
1525 for (
int l = atEnd? k: k-1; l > currentBox.start; l--) {
1526 if (!result.at(l).inkBoundingBox.isEmpty()
1527 && result.at(l).addressable
1528 && !result.at(l).hidden) {
1534 decorationBoxes.append(currentBox);
1535 currentBox = DecorationBox();
1536 currentBox.start = k;
1538 if (currentBox.start == k && (charResult.
inkBoundingBox.isEmpty()
1541 currentBox.start = k+1;
1545 qint32 lastFontSize = 0;
1546 QPointF lastBaselineOffset;
1547 QPointF lastLineThroughOffset;
1551 for (
int b = 0;
b < decorationBoxes.size();
b++) {
1552 DecorationBox currentBox = decorationBoxes.at(b);
1553 LineThrough currentLineThrough = LineThrough();
1554 lastFontSize = result.at(currentBox.start).metrics.fontSize;
1555 lastBaselineOffset = result.at(currentBox.start).totalBaselineOffset();
1557 for (
int k = currentBox.start; k <= currentBox.end; k++) {
1560 if (currentTextPath) {
1561 characterResultOnPath(charResult,
1562 currentTextPath->
outline().length(),
1563 currentTextPathOffset,
1573 const bool newLineThrough = charResult.
metrics.
fontSize != lastFontSize && !baselineIsOffset;
1574 const bool ignoreMetrics = !newLineThrough && baselineIsOffset && !currentLineThrough.line.isEmpty();
1576 if (newLineThrough) {
1577 lineThroughLines.append(currentLineThrough);
1578 currentLineThrough = LineThrough();
1585 const QPointF alphabeticOffset = isHorizontal? QPointF(0, -alphabetic): QPointF(alphabetic, 0);
1587 if (!ignoreMetrics) {
1590 lastLineThroughOffset = isHorizontal? QPointF(0, -(charResult.
metrics.
lineThroughOffset*freetypePixelsToPt)) + alphabeticOffset
1594 QPointF lastLineThroughPoint = charResult.
finalPosition + charResult.
advance + lastLineThroughOffset;
1596 if (ignoreMetrics) {
1597 QPointF lastP = currentLineThrough.line.last();
1599 lastLineThroughPoint.setY(lastP.y());
1601 lastLineThroughPoint.setX(lastP.x());
1605 if ((isHorizontal && underlinePosH == UnderlineAuto)) {
1606 QRectF bbox = charResult.
layoutBox().translated(-alphabeticOffset);
1608 currentBox.decorationRect |= bbox.translated(charResult.
finalPosition + alphabeticOffset);
1612 if (currentLineThrough.line.isEmpty()) {
1613 currentLineThrough.line.append(charResult.
finalPosition + lastLineThroughOffset);
1615 currentLineThrough.line.append(lastLineThroughPoint);
1617 decorationBoxes[
b] = currentBox;
1618 lineThroughLines.append(currentLineThrough);
1625 const bool textOnPath = (currentTextPath)?
true: false;
1627 for (
int i = 0; i < decorationBoxes.size(); i++) {
1628 DecorationBox box = decorationBoxes.at(i);
1629 if (box.underlineOffsets.size() > 0) {
1630 stroker.setWidth(qMax(minimumDecorationThickness, (box.thickness/(box.underlineOffsets.size()))*freetypePixelsToPt));
1632 stroker.setWidth(resHandler.
adjust(QPointF(0, stroker.width())).y());
1634 stroker.setWidth(resHandler.
adjust(QPointF(stroker.width(), 0)).x());
1637 stroker.setWidth(minimumDecorationThickness);
1639 QRectF
rect = box.decorationRect;
1640 if (textDecorationSkipInset) {
1641 qreal inset = stroker.width() * 0.5;
1642 rect.adjust(-inset, -inset, inset, inset);
1645 length.setP1(isHorizontal? QPointF(
rect.left(), 0): QPointF(0,
rect.top()));
1646 length.setP2(isHorizontal? QPointF(
rect.right(), 0): QPointF(0,
rect.bottom()));
1648 QPair<QPainterPath, QPointF> generatedPath =
generateDecorationPath(
length, stroker.width(), style, isHorizontal, textOnPath, minimumDecorationThickness);
1649 QPainterPath
p = generatedPath.first;
1650 QPointF pathWidth = generatedPath.second;
1652 QMap<TextDecoration, QPointF> decorationOffsets;
1654 const qreal startX =
rect.left();
1657 if (underlinePosH == UnderlineAuto) {
1659 for (
int j = 0; j < box.underlineOffsets.size(); j++) {
1660 average += box.underlineOffsets.at(j);
1662 average = average > 0? (average/box.underlineOffsets.size()): 0;
1666 const qreal startY =
rect.top();
1667 if (underlinePosV == UnderlineRight) {
1676 if (decor.testFlag(DecorationUnderline)) {
1677 finalizeDecoration(
p, decorationOffsets.value(DecorationUnderline), stroker, DecorationUnderline, decorationPaths, currentTextPath, isHorizontal, currentTextPathOffset, textPathSide);
1679 if (decor.testFlag(DecorationOverline)) {
1680 finalizeDecoration(
p, decorationOffsets.value(DecorationOverline), stroker, DecorationOverline, decorationPaths, currentTextPath, isHorizontal, currentTextPathOffset, textPathSide);
1683 if (decor.testFlag(DecorationLineThrough)) {
1684 for (
int i = 0; i < lineThroughLines.size(); i++) {
1685 LineThrough l = lineThroughLines.at(i);
1686 QPolygonF poly = l.line;
1687 if (poly.isEmpty())
continue;
1688 stroker.setWidth(qMax(minimumDecorationThickness, (l.thickness/(poly.size()))*freetypePixelsToPt));
1691 pathWidth = resHandler.
adjust(QPointF(0, stroker.width()));
1692 stroker.setWidth(pathWidth.y());
1694 pathWidth = resHandler.
adjust(QPointF(stroker.width(), 0));
1695 stroker.setWidth(pathWidth.x());
1697 qreal average = 0.0;
1698 for (
int j = 0; j < poly.size(); j++) {
1699 average += isHorizontal? poly.at(j).y(): poly.at(j).x();
1701 average /= poly.size();
1702 QLineF line = isHorizontal? QLineF(poly.first().x(), average, poly.last().x(), average)
1703 : QLineF(average, poly.first().
y(), average, poly.last().
y());
1706 QPair<QPainterPath, QPointF> generatedPath =
generateDecorationPath(line, stroker.width(), style, isHorizontal, textOnPath, minimumDecorationThickness);
1707 QPainterPath
p = generatedPath.first;
1708 finalizeDecoration(
p, line.p1() + (generatedPath.second * 0.5), stroker, DecorationLineThrough, decorationPaths, currentTextPath, isHorizontal, currentTextPathOffset, textPathSide);
1712 return decorationPaths;
1721 while (start < result.size()) {
1723 qreal shift = anchoredChunkShift(result, isHorizontal, start, end);
1725 const QPointF shiftP = resHandler.
adjust(isHorizontal ? QPointF(shift, 0) : QPointF(0, shift));
1727 for (
int j = start; j < end; j++) {
1728 result[j].finalPosition += shiftP;
1729 result[j].textPathAndAnchoringOffset += shiftP;
1735qreal KoSvgTextShape::Private::anchoredChunkShift(
const QVector<CharacterResult> &result,
const bool isHorizontal,
const int start,
int &end)
1740 for (; i < result.size(); i++) {
1741 if (!result.at(i).addressable) {
1744 if (result.at(i).anchored_chunk && i > start) {
1747 qreal pos = isHorizontal ? result.at(i).finalPosition.x() : result.at(i).finalPosition.y();
1748 qreal advance = isHorizontal ? result.at(i).advance.x() : result.at(i).advance.y();
1750 if (result.at(i).anchored_chunk) {
1751 a = qMin(pos, pos + advance);
1752 b = qMax(pos, pos + advance);
1754 a = qMin(a, qMin(pos, pos + advance));
1755 b = qMax(b, qMax(pos, pos + advance));
1772 shift = shift - (a +
b) * 0.5;
1779qreal KoSvgTextShape::Private::characterResultOnPath(
CharacterResult &cr,
1787 if (!isHorizontal) {
1792 if (mid - offset < 0 || mid - offset >
length) {
1796 if (mid - offset < -length || mid - offset > 0) {
1800 if (mid - offset < -(
length * 0.5) || mid - offset > (
length * 0.5)) {
1809 if (mid < 0 || mid >
length) {
1816QPainterPath KoSvgTextShape::Private::stretchGlyphOnPath(
const QPainterPath &glyph,
1817 const QPainterPath &path,
1822 QPainterPath
p = glyph;
1823 for (
int i = 0; i < glyph.elementCount(); i++) {
1824 qreal mid = isHorizontal ? glyph.elementAt(i).x + offset : glyph.elementAt(i).y + offset;
1825 qreal midUnbound = mid;
1828 mid += path.length();
1830 mid = fmod(mid, qreal(path.length()));
1833 mid = qBound(0.0, mid, qreal(path.length()));
1835 const qreal percent = path.percentAtLength(mid);
1836 const QPointF pos = path.pointAtPercent(percent);
1837 qreal tAngle = path.angleAtPercent(percent);
1839 tAngle = 0 - (360 - tAngle);
1841 const QPointF vectorT(qCos(qDegreesToRadians(tAngle)), -qSin(qDegreesToRadians(tAngle)));
1842 QPointF finalPos = pos;
1844 QPointF vectorN(-vectorT.y(), vectorT.x());
1845 const qreal
o = mid - (midUnbound);
1846 finalPos = pos - (
o * vectorT) + (glyph.elementAt(i).y * vectorN);
1848 QPointF vectorN(vectorT.y(), -vectorT.x());
1849 const qreal
o = mid - (midUnbound);
1850 finalPos = pos - (
o * vectorT) + (glyph.elementAt(i).x * vectorN);
1852 p.setElementPositionAt(i, finalPos.x(), finalPos.y());
1867 bool inPath =
false;
1868 bool afterPath =
false;
1869 int currentIndex = 0;
1872 int endIndex = currentIndex + numChars(textShapeElement,
true, resolvedProps);
1874 KoShape *cTextPath = KoSvgTextShape::Private::textPathByName(textShapeElement->textPathId, textPaths);
1877 QPainterPath path = shape->
outline();
1881 path = path.toReversed();
1883 qreal
length = path.length();
1886 if (textShapeElement->textPathInfo.startOffsetIsPercentage) {
1887 offset =
length * (0.01 * textShapeElement->textPathInfo.startOffset);
1889 offset = textShapeElement->textPathInfo.startOffset;
1894 const qreal percent = path.percentAtLength(offset);
1895 startPos = path.pointAtPercent(percent);
1898 for (
int i = currentIndex; i < endIndex; i++) {
1902 const qreal mid = characterResultOnPath(cr,
length, offset, isHorizontal, isClosed);
1904 auto *outlineGlyph = std::get_if<Glyph::Outline>(&cr.
glyph);
1906 if (stretch && outlineGlyph) {
1908 QPainterPath glyph = stretchGlyphOnPath(tf.map(outlineGlyph->path), path, isHorizontal, offset, isClosed);
1909 outlineGlyph->path = glyph;
1911 const qreal percent = path.percentAtLength(mid);
1912 const QPointF pos = path.pointAtPercent(percent);
1913 qreal tAngle = path.angleAtPercent(percent);
1915 tAngle = 0 - (360 - tAngle);
1917 const QPointF vectorT(qCos(qDegreesToRadians(tAngle)), -qSin(qDegreesToRadians(tAngle)));
1920 cr.
rotate -= qDegreesToRadians(tAngle);
1921 QPointF vectorN(-vectorT.y(), vectorT.x());
1922 const qreal
o = (cr.
advance.x() * 0.5);
1925 cr.
rotate -= qDegreesToRadians(tAngle + 90);
1926 QPointF vectorN(vectorT.y(), -vectorT.x());
1927 const qreal
o = (cr.
advance.y() * 0.5);
1932 if (stretch && outlineGlyph) {
1934 outlineGlyph->path = tf.inverted().map(outlineGlyph->path);
1940 pathEnd = path.pointAtPercent(1.0);
1945 pathEnd -= result.at(currentIndex).finalPosition;
1948 for (
int i = currentIndex; i < endIndex; i++) {
1960 currentIndex = endIndex;
1967 if (!it->textPathId.isEmpty()) {
1969 firstTextInPath =
true;
1976 if (childCount(it)) {
1978 result += collectSubChunks(child, currentProps, textInPath, firstTextInPath);
1992 if (!bidiOpening.isEmpty()) {
1993 chunk.
text = bidiOpening;
1996 result.append(chunk);
1997 firstTextInPath =
false;
2002 result.append(chunk);
2004 if (!bidiClosing.isEmpty()) {
2005 chunk.
text = bidiClosing;
2008 result.append(chunk);
2011 firstTextInPath =
false;
2014 if (!it->textPathId.isEmpty()) {
2016 firstTextInPath =
false;
qreal length(const QPointF &vec)
float value(const T *src, size_t ch)
@ ConditionallyHang
Only hang if no space otherwise, only measured for justification if not hanging.
@ Collapse
Collapse if first or last in line.
@ ForceHang
Force hanging at the start or end of a line, never measured for justification.
@ NoChange
Do nothing special.
const QString bidiControls
KoSvgTextShape::Private::resolveTransforms This resolves transforms and applies whitespace collapse.
static QMap< int, int > logicalToVisualCursorPositions(const QVector< CursorPos > &cursorPos, const QVector< CharacterResult > &result, const QVector< LineBox > &lines, const bool <r=false)
logicalToVisualCursorPositions Create a map that sorts the cursor positions by the visual index of th...
QPair< QPainterPath, QPointF > generateDecorationPath(const QLineF length, const qreal strokeWidth, const KoSvgText::TextDecorationStyle style, const bool isHorizontal, const bool onTextPath, const qreal minimumDecorationThickness)
QString langToLibUnibreakLang(const QString lang)
A simple solid color shape background.
static QVector< bool > collapseSpaces(QString *text, QMap< int, KoSvgText::TextSpaceCollapse > collapseMethods)
collapseSpaces Some versions of CSS-Text 'white-space' or 'text-space-collapse' will collapse or tran...
static bool IsCssWordSeparator(QString grapheme)
IsCssWordSeparator CSS has a number of characters it considers word-separators, which are used in jus...
static bool characterCanHang(QChar c, KoSvgText::HangingPunctuations hangType)
characterCanHang The function returns whether the character qualifies for 'hanging-punctuation',...
static QString getBidiClosing(KoSvgText::UnicodeBidi bidi)
getBidiClosing Returns the bidi closing string associated with the given Css unicode-bidi value.
static bool collapseLastSpace(QChar c, KoSvgText::TextSpaceCollapse collapseMethod)
collapseLastSpace Some versions of CSS-Text 'white-space' or 'text-space-collapse' will collapse the ...
static bool hangLastSpace(const QChar c, KoSvgText::TextSpaceCollapse collapseMethod, KoSvgText::TextWrap wrapMethod, bool &force, bool nextCharIsHardBreak)
hangLastSpace Some versions of CSS-Text 'white-space' or 'text-space-collapse' will hang the final sp...
static QString getBidiOpening(bool ltr, KoSvgText::UnicodeBidi bidi)
getBidiOpening Get the bidi opening string associated with the given Css unicode-bidi value and direc...
static QVector< QPair< bool, bool > > justificationOpportunities(QString text, QString langCode)
justificationOpportunities mark justification opportunities in the text. Opportunities are between ch...
std::vector< FT_FaceSP > facesForCSSValues(QVector< int > &lengths, KoCSSFontInfo info=KoCSSFontInfo(), const QString &text="", quint32 xRes=72, quint32 yRes=72, bool disableFontMatching=false, const QString &language=QString())
facesForCSSValues This selects a font with fontconfig using the given values. If "text" is not empty ...
static int32_t loadFlagsForFace(FT_Face face, bool isHorizontal=true, int32_t loadFlags=0, const KoSvgText::TextRendering rendering=KoSvgText::RenderingAuto)
static KoFontRegistry * instance()
static KoSvgText::FontMetrics generateFontMetrics(FT_FaceSP face, bool isHorizontal=true, QString script=QString(), const KoSvgText::TextRendering rendering=KoSvgText::RenderingAuto)
The position of a path point within a path shape.
bool isClosedSubpath(int subpathIndex) const
Checks if a subpath is closed.
QPainterPath outline() const override
reimplemented
int subpathCount() const
Returns the number of subpaths in the path.
QScopedPointer< Private > d
KoShapeAnchor * anchor() const
void rotate(qreal angle)
Rotate the shape (relative)
KoShapeContainer * parent() const
void scale(qreal sx, qreal sy)
Scale the shape using the zero-point which is the top-left corner.
QTransform transformation() const
Returns the shapes local transformation matrix.
QSharedDataPointer< SharedData > s
QTransform transform() const
return the current matrix that contains the rotation/scale/position of this shape
@ TextAnchorId
KoSvgText::TextAnchor.
@ InlineSizeId
KoSvgText::AutoValue.
@ UnicodeBidiId
KoSvgText::UnicodeBidi.
@ FontSynthesisItalicId
Bool.
@ DominantBaselineId
KoSvgText::Baseline.
@ BaselineShiftValueId
Double.
@ AlignmentBaselineId
KoSvgText::Baseline.
@ WordSpacingId
KoSvgText::AutoLengthPercentage.
@ LineBreakId
KoSvgText::LineBreak.
@ LetterSpacingId
KoSvgText::AutoLengthPercentage.
@ TextCollapseId
KoSvgText::TextSpaceCollapse.
@ TextDecorationStyleId
KoSvgText::TextDecorationStyle.
@ FontStyleId
KoSvgText::CssSlantData.
@ WritingModeId
KoSvgText::WritingMode.
@ DirectionId
KoSvgText::Direction.
@ TextWrapId
KoSvgText::TextWrap.
@ HangingPunctuationId
Flags, KoSvgText::HangingPunctuations.
@ FontSynthesisBoldId
Bool.
@ BaselineShiftModeId
KoSvgText::BaselineShiftMode.
@ TextDecorationPositionId
KoSvgText::TextDecorationUnderlinePosition.
@ WordBreakId
KoSvgText::WordBreak.
@ TextLanguage
a language string.
@ TextDecorationLineId
Flags, KoSvgText::TextDecorations.
QSharedPointer< KoShapeBackground > background() const
QList< PropertyId > properties() const
KoSvgText::FontMetrics metrics(const bool withResolvedLineHeight=true, const bool offsetByBaseline=false) const
metrics Return the metrics of the first available font.
QVariant property(PropertyId id, const QVariant &defaultValue=QVariant()) const
static const KoSvgTextProperties & defaultProperties()
QStringList fontFeaturesForText(int start, int length) const
fontFeaturesForText Returns a harfbuzz friendly list of opentype font-feature settings using the vari...
KoCSSFontInfo cssFontInfo() const
cssFontInfo
bool hasProperty(PropertyId id) const
KoSvgText::FontMetrics applyLineHeight(KoSvgText::FontMetrics metrics) const
applyLineHeight Calculate the linegap for the current linegap property.
QVariant propertyOrDefault(PropertyId id) const
void inheritFrom(const KoSvgTextProperties &parentProperties, bool resolve=false)
static QString scriptTagForQCharScript(QChar::Script script)
static QLocale localeFromBcp47Locale(const Bcp47Locale &locale)
ChildIterator< value_type, is_const > childBegin(const ChildIterator< value_type, is_const > &it)
int size(const Forest< T > &forest)
ChildIterator< value_type, is_const > childEnd(const ChildIterator< value_type, is_const > &it)
void calculateLineHeight(CharacterResult cr, double &ascent, double &descent, bool isHorizontal, bool compare=false)
calculateLineHeight calculate the total ascent and descent (including baseline-offset) of a charResul...
QVector< LineBox > flowTextInShapes(const KoSvgTextProperties &properties, const QMap< int, int > &logicalToVisual, QVector< CharacterResult > &result, QList< QPainterPath > shapes, QPointF &startPos, const KoSvgText::ResolutionHandler &resHandler)
QList< QPainterPath > getShapes(QList< KoShape * > shapesInside, QList< KoShape * > shapesSubtract, const KoSvgTextProperties &properties)
QVector< LineBox > breakLines(const KoSvgTextProperties &properties, const QMap< int, int > &logicalToVisual, QVector< CharacterResult > &result, QPointF startPos, const KoSvgText::ResolutionHandler &resHandler)
BaselineShiftMode
Mode of the baseline shift.
@ ShiftLineBottom
this handles css-inline-3 vertical-align:bottom. Not exposed to ui
@ ShiftSuper
Use parent font metric for 'superscript'.
@ ShiftLineTop
this handles css-inline-3 vertical-align:top. Not exposed to ui
@ ShiftLengthPercentage
Css Length Percentage, percentage is lh.
@ ShiftSub
Use parent font metric for 'subscript'.
@ LineBreakStrict
Use strict method, language specific.
@ LineBreakAnywhere
Break between any typographic clusters.
TextAnchor
Where the text is anchored for SVG 1.1 text and 'inline-size'.
@ AnchorEnd
Anchor right for LTR, left for RTL.
@ AnchorStart
Anchor left for LTR, right for RTL.
TextDecorationStyle
Style of the text-decoration.
@ Double
Draw two lines. Ex: =====.
@ Wavy
Draw a wavy line. We currently make a zigzag, ex: ^^^^^.
OverflowWrap
What to do with words that cannot be broken, but still overflow.
TextDecorationUnderlinePosition
Which location to choose for the underline.
WordBreak
Whether to break words.
@ WordBreakBreakAll
Always break inside words.
@ WordBreakKeepAll
Never break inside words.
@ LengthAdjustSpacingAndGlyphs
Stretches the glyphs as well.
TextWrap
Part of "white-space", in practice we only support wrap and nowrap.
@ NoWrap
Do not do any text wrapping.
TextDecoration
Flags for text-decoration, for underline, overline and strikethrough.
Direction
Base direction used by Bidi algorithm.
Baseline
Baseline values used by dominant-baseline and baseline-align.
@ BaselineAlphabetic
Use 'romn' or the baseline for LCG scripts.
@ BaselineNoChange
Use parent baseline table.
@ BaselineCentral
Use the center between the ideographic over and under.
@ RenderingGeometricPrecision
@ RenderingOptimizeLegibility
@ HangForce
Whether to force hanging stops or commas.
@ HangLast
Hang closing brackets and quotes.
@ HangFirst
Hang opening brackets and quotes.
@ HangEnd
Hang stops and commas. Force/Allow is a separate boolean.
@ BreakSpaces
Same as preserve, except each white space and wordseperate is breakable.
LineEdgeBehaviour lineStart
QPointF totalBaselineOffset() const
QRectF inkBoundingBox
The bounds of the drawn glyph. Different from the bounds the charresult takes up in the layout,...
KoSvgText::TextAnchor anchor
qreal scaledDescent
Descender, in pt.
QPointF finalPosition
the final position, taking into account both CSS and SVG positioning considerations.
KoSvgText::FontMetrics metrics
Fontmetrics for current font, in Freetype scanline coordinates.
bool justifyAfter
Justification Opportunity follows this character.
KoSvgText::Direction direction
bool justifyBefore
Justification Opportunity precedes this character.
bool anchored_chunk
whether this is the start of a new chunk.
LineEdgeBehaviour lineEnd
void scaleCharacterResult(qreal xScale, qreal yScale)
scaleCharacterResult convenience function to scale the whole character result.
qreal scaledAscent
Ascender, in pt.
QPointF textPathAndAnchoringOffset
Offset caused by textPath and anchoring.
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.
QRectF layoutBox() const
layoutBox
QPointF textLengthOffset
offset caused by textLength
QTransform finalTransform() const
QLineF caret
Caret for this characterResult.
bool rtl
Whether the current glyph is right-to-left, as opposed to the markup.
QVector< int > graphemeIndices
The text-string indices of graphemes starting here, starting grapheme is not present.
QColor color
Which color the current position has.
QVector< QPointF > offsets
The advance offsets for each grapheme index.
int cluster
Which character result this position belongs in.
bool synthetic
Whether this position was inserted to have a visual indicator.
int index
Which grapheme this position belongs with.
int offset
Which offset this position belongs with.
void mergeInParentTransformation(const CharTransformation &t)
When style is oblique, a custom slant value can be specified for variable fonts.
The FontMetrics class A class to keep track of a variety of font metrics. Note that values are in Fre...
QPair< qint32, qint32 > subScriptOffset
subscript baseline height, defaults to 1/5th em below alphabetic.
qint32 lineThroughThickness
strikethrough thickness, from font.
qint32 underlineThickness
underline thickness from font.
qint32 lineThroughOffset
offset of strike-through from alphabetic baseline.
qint32 lineGap
additional linegap between consecutive lines.
qint32 underlineOffset
underline offset from alphabetic, positive.
qint32 fontSize
Currently set size, CSS unit 'em'.
int valueForBaselineValue(Baseline baseline) const
QPair< qint32, qint32 > superScriptOffset
superscript baseline height, defaults to 2/3rd above alphabetic.
The ResolutionHandler class.
qreal freeTypePixelToPointFactor(const bool x=true) const
qreal pixelToPointFactor(const bool x=true) const
QTransform freeTypeToPointTransform() const
QPointF adjustWithOffset(const QPointF point, const QPointF offset) const
QPointF adjust(const QPointF point) const
Adjusts the point to rounded pixel values, based on whether roundToPixelHorizontal or roundToPixelVer...
TextDecorationUnderlinePosition verticalPosition
TextDecorationUnderlinePosition horizontalPosition
QPointF baselineTop
Used to identify the top of the line for baseline-alignment.
QVector< LineChunk > chunks
QPointF baselineBottom
Used to identify the bottom of the line for baseline-alignment.
QVector< int > chunkIndices
charResult indices that belong to this chunk.
KisForest< KoSvgTextContentElement >::child_iterator associatedLeaf
QSharedPointer< KoShapeBackground > bg
KoSvgTextProperties inheritedProps
QVector< QPair< int, int > > newToOldPositions
For transformed strings, we need to know which.