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::relayout()
115 clearAssociatedOutlines();
116 this->initialTextPosition = QPointF();
117 this->result.clear();
118 this->cursorPos.clear();
119 this->logicalToVisualCursorPos.clear();
121 bool disableFontMatching = this->disableFontMatching;
137 const auto getUcs4At = [](
const QString &
s,
int i) ->
char32_t {
138 const QChar high =
s.at(i);
139 if (!high.isSurrogate()) {
140 return high.unicode();
142 if (high.isHighSurrogate() &&
s.length() > i + 1) {
143 const QChar low =
s[i + 1];
144 if (low.isLowSurrogate()) {
145 return QChar::surrogateToUcs4(high, low);
151 return high.unicode();
161 bool _ignore =
false;
166 QMap<int, KoSvgText::TextSpaceCollapse> collapseModes;
168 Q_FOREACH (
const SubChunk &chunk, textChunks) {
171 int a = pos.second < 0? -1: text.size()+pos.second;
172 int b = pos.first < 0? -1: plainText.size()+pos.first;
173 QPair<int, int> newPos = QPair<int, int> (a, b);
174 clusterToOriginalString.append(newPos);
177 collapseModes.insert(text.size(), collapse);
178 text.append(chunk.
text);
182 debugFlake <<
"Laying out the following text: " << text;
192 if (text.size() > 0) {
196 if (!lang.isEmpty()) {
200 unibreakLang +=
"-strict";
203 set_linebreaks_utf16(text.utf16(),
static_cast<size_t>(text.size()), unibreakLang.toUtf8().data(), lineBreaks.data());
204 set_wordbreaks_utf16(text.utf16(),
static_cast<size_t>(text.size()), unibreakLang.toUtf8().data(), wordBreaks.data());
205 set_graphemebreaks_utf16(text.utf16(),
static_cast<size_t>(text.size()), unibreakLang.toUtf8().data(), graphemeBreaks.data());
216 for (
int i = 0; i < text.size(); i++) {
217 if (lineBreaks[i] == LINEBREAK_MUSTBREAK) {
218 text[i] = QChar::Space;
221 for (
int i=0; i < clusterToOriginalString.size(); i++) {
222 QPair<int, int> mapping = clusterToOriginalString.at(i);
223 if (mapping.first < 0) {
226 if (mapping.first < result.size()) {
227 result[mapping.first].plaintTextIndex = mapping.second;
244 bool wrapped = !(inlineSize.
isAuto && this->shapesInside.isEmpty());
245 if (!resolvedTransforms.isEmpty()) {
246 resolvedTransforms[0].xPos = 0;
247 resolvedTransforms[0].yPos = 0;
249 this->resolveTransforms(textData.childBegin(), text, result, globalIndex, isHorizontal, wrapped,
false, resolvedTransforms, collapseChars,
KoSvgTextProperties::defaultProperties(),
true);
254 if (raqm_set_text_utf16(
layout.data(), text.utf16(),
static_cast<size_t>(text.size()))) {
256 raqm_set_par_direction(
layout.data(), raqm_direction_t::RAQM_DIRECTION_TTB);
258 raqm_set_par_direction(
layout.data(), raqm_direction_t::RAQM_DIRECTION_RTL);
260 raqm_set_par_direction(
layout.data(), raqm_direction_t::RAQM_DIRECTION_LTR);
264 Q_FOREACH (
const SubChunk &chunk, textChunks) {
273 KoSvgText::HangingPunctuations hang =
284 if ((localLinebreakStrictness != linebreakStrictness && localLinebreakStrictness !=
KoSvgText::LineBreakAnywhere) || localLang != lang ) {
287 unibreakLang +=
"-strict";
289 int localLineBreakStart = qMax(0, start -1);
290 int localLineBreakEnd = qMin(text.size(), start+chunk.
text.size());
291 QVector<char> localLineBreaks(localLineBreakEnd - localLineBreakStart);
292 set_linebreaks_utf16(text.mid(localLineBreakStart, localLineBreaks.size()).utf16(),
293 static_cast<size_t>(localLineBreaks.size()),
294 unibreakLang.toUtf8().data(),
295 localLineBreaks.data());
296 for (
int i = 0; i < localLineBreaks.size(); i++) {
297 if (i < (start - localLineBreakStart))
continue;
298 if (i+localLineBreakStart > start+chunk.
text.size())
break;
299 lineBreaks[i+localLineBreakStart] = localLineBreaks[i];
307 fillColor =
b->color();
309 if (!letterSpacing.isAuto) {
310 tabInfo.extraSpacing += letterSpacing.length.value;
312 if (!wordSpacing.isAuto) {
313 tabInfo.extraSpacing += wordSpacing.length.value;
316 for (
int i = 0; i <
length; i++) {
320 QPair<bool, bool> canJustify = justify.value(start + i, QPair<bool, bool>(
false,
false));
324 if (lineBreaks[start + i] == LINEBREAK_MUSTBREAK) {
328 }
else if (lineBreaks[start + i] == LINEBREAK_ALLOWBREAK && wrap !=
KoSvgText::NoWrap) {
337 const auto isFollowedByForcedLineBreak = [&]() {
338 if (result.size() <= start + i + 1) {
342 if (lineBreaks[start + i+ 1] == LINEBREAK_MUSTBREAK) {
346 if (resolvedTransforms.at(start + i + 1).startsNewChunk()) {
352 bool forceHang =
false;
394 if (resolvedTransforms.at(start + i).startsNewChunk()) {
395 raqm_set_arbitrary_run_break(
layout.data(),
static_cast<size_t>(start + i),
true);
401 result[start + i] = cr;
415 static_cast<quint32
>(resHandler.xRes),
416 static_cast<quint32
>(resHandler.yRes),
417 disableFontMatching);
420 raqm_set_language(
layout.data(),
422 static_cast<size_t>(start),
423 static_cast<size_t>(
length));
425 Q_FOREACH (
const QString &feature, fontFeatures) {
427 raqm_add_font_feature(
layout.data(), feature.toUtf8(), feature.toUtf8().size());
430 if (!letterSpacing.isAuto) {
431 raqm_set_letter_spacing_range(
layout.data(),
432 static_cast<int>(letterSpacing.length.value * resHandler.freeTypePixel * resHandler.pointToPixelFactor(isHorizontal)),
433 static_cast<size_t>(start),
434 static_cast<size_t>(
length));
437 if (!wordSpacing.isAuto) {
438 raqm_set_word_spacing_range(
layout.data(),
439 static_cast<int>(wordSpacing.length.value * resHandler.freeTypePixel * resHandler.pointToPixelFactor(isHorizontal)),
440 static_cast<size_t>(start),
441 static_cast<size_t>(
length));
444 for (
int i = 0; i < lengths.size(); i++) {
446 const FT_FaceSP &face = faces.at(
static_cast<size_t>(i));
449 raqm_set_freetype_face(
layout.data(), face.
data());
450 raqm_set_freetype_load_flags(
layout.data(), faceLoadFlags);
453 raqm_set_freetype_face_range(
layout.data(),
455 static_cast<size_t>(start),
456 static_cast<size_t>(
length));
457 raqm_set_freetype_load_flags_range(
layout.data(),
459 static_cast<size_t>(start),
460 static_cast<size_t>(
length));
463 QHash<QChar::Script, KoSvgText::FontMetrics> metricsList;
464 for (
int j=start; j<start+
length; j++) {
465 const QChar::Script currentScript = QChar::script(getUcs4At(text, j));
466 if (!metricsList.contains(currentScript)) {
469 result[j].metrics = metricsList.value(currentScript);
470 if (fontSize < 1.0) {
471 result[j].extraFontScaling = fontSize;
476 if (text.at(j) == QChar::Tabulation) {
478 if (tabInfo.isNumber) {
480 if (result[j].metrics.spaceAdvance > 0) {
481 tabSize = (result[j].metrics.spaceAdvance + (tabInfo.extraSpacing*resHandler.freeTypePixel)) * tabInfo.value;
483 tabSize = ((result[j].metrics.fontSize/2) + (tabInfo.extraSpacing*resHandler.freeTypePixel)) * tabInfo.value;
486 tabSize = tabInfo.length.value * resHandler.freeTypePixel;
488 result[j].tabSize = tabSize;
491 result[j].fontHalfLeading = currentMetrics.
lineGap / 2;
492 result[j].fontStyle = synthesizeStyle? style.
style: QFont::StyleNormal;
502 if (!result.empty()) {
503 result[0].anchored_chunk =
true;
506 if (raqm_layout(
layout.data())) {
513 const raqm_glyph_t *glyphs = raqm_get_glyphs(
layout.data(), &count);
518 QPointF totalAdvanceFTFontCoordinates;
519 QMap<int, int> logicalToVisual;
520 this->isBidi =
false;
525 for (
int i = 0; i < static_cast<int>(count); i++) {
526 raqm_glyph_t currentGlyph = glyphs[i];
527 KIS_ASSERT(currentGlyph.cluster <= INT32_MAX);
528 const int cluster =
static_cast<int>(currentGlyph.cluster);
529 if (!result[cluster].addressable) {
537 const char32_t codepoint = getUcs4At(text, cluster);
538 debugFlake <<
"glyph" << i <<
"cluster" << cluster << currentGlyph.index << codepoint;
540 charResult.
cursorInfo.
rtl = raqm_get_direction_at_index(
layout.data(), cluster) == RAQM_DIRECTION_RTL;
545 if (!this->loadGlyph(resHandler,
552 totalAdvanceFTFontCoordinates)) {
557 logicalToVisual.insert(cluster, i);
559 charResult.
middle =
false;
561 result[cluster] = charResult;
567 int firstCluster = -1;
568 bool graphemeBreakNext =
false;
569 for (
int i = 0; i < result.size(); i++) {
570 result[i].
middle = result.at(i).visualIndex == -1;
571 if (result[i].addressable && !result.at(i).middle) {
572 if (result.at(i).plaintTextIndex > -1 && firstCluster > -1) {
573 CursorInfo info = result.at(firstCluster).cursorInfo;
577 info.
offsets.append(result.at(firstCluster).advance);
580 result[firstCluster].cursorInfo = info;
584 int fC = qMax(0, firstCluster);
585 if (text[fC].isSpace() == text[i].isSpace()) {
587 result[fC].breakType = result.at(i).breakType;
590 result[fC].lineStart = result.at(i).lineStart;
593 result[fC].lineEnd = result.at(i).lineEnd;
596 if (graphemeBreakNext && result[i].addressable && result.at(i).plaintTextIndex > -1) {
597 result[fC].cursorInfo.graphemeIndices.append(result.at(i).plaintTextIndex);
599 result[i].cssPosition = result.at(fC).cssPosition + result.at(fC).advance;
600 result[i].hidden =
true;
602 graphemeBreakNext = graphemeBreaks[i] == GRAPHEMEBREAK_BREAK;
604 int fC = qMax(0, firstCluster);
605 if (result.at(fC).cursorInfo.graphemeIndices.isEmpty() || graphemeBreakNext) {
606 result[fC].cursorInfo.graphemeIndices.append(plainText.size());
634 result.insert(dummyIndex, dummy);
635 logicalToVisual.insert(dummyIndex, dummy.
visualIndex);
643 this->computeFontMetrics(textData.childBegin(),
KoSvgTextProperties::defaultProperties(),
KoSvgText::FontMetrics(),
KoSvgText::BaselineAuto, QPointF(), QPointF(), result, globalIndex, resHandler, isHorizontal, disableFontMatching);
646 QPointF startPos = resolvedTransforms.value(0).absolutePos() - result.value(0).dominantBaselineOffset;
647 if (!this->shapesInside.isEmpty()) {
648 QList<QPainterPath> shapes = getShapes(this->shapesInside, this->shapesSubtract, rootProperties);
649 this->lineBoxes = flowTextInShapes(rootProperties, logicalToVisual, result, shapes, startPos, resHandler);
651 this->lineBoxes = breakLines(rootProperties, logicalToVisual, result, startPos, resHandler);
658 if (inlineSize.
isAuto && this->shapesInside.isEmpty()) {
659 debugFlake <<
"Starting with SVG 1.1 specific portion";
662 QPointF shift = QPointF();
663 bool setAnchoredChunk =
false;
664 for (
int i = 0; i < result.size(); i++) {
665 if (result.at(i).addressable) {
677 if (setAnchoredChunk) {
679 setAnchoredChunk =
false;
683 setAnchoredChunk =
true;
688 result[i] = charResult;
693 debugFlake <<
"5. Apply ‘textLength’ attribute";
702 for (
int i = 0; i < result.size(); i++) {
703 if (result.at(i).addressable) {
715 if (charResult.
middle && i-1 >=0) {
719 result[i] = charResult;
725 applyAnchoring(result, isHorizontal, resHandler);
730 debugFlake <<
"Now Computing text-decorations";
732 this->computeTextDecorations(textData.childBegin(),
750 debugFlake <<
"Computing text-decorationsfor inline-size";
751 this->computeTextDecorations(textData.childBegin(),
768 for (
auto chunk = textChunks.begin(); chunk != textChunks.end(); chunk++) {
769 const int j = chunk->
text.size();
771 for (
int i = globalIndex; i < chunk->
associatedLeaf->finalResultIndex; i++) {
772 if (result.at(i).addressable && !result.at(i).middle) {
774 if (result.at(i).plaintTextIndex > -1) {
776 bool insertFirst =
false;
777 if (result.at(i).anchored_chunk) {
780 pos.
index = result.at(i).plaintTextIndex;
782 QPointF newOffset = result.at(i).cursorInfo.rtl? result.at(i).advance: QPointF();
783 result[i].cursorInfo.offsets.insert(0, newOffset);
784 positions.append(newOffset);
787 cursorPos.append(pos);
790 int graphemes = result.at(i).cursorInfo.graphemeIndices.size();
791 for (
int k = 0; k < graphemes; k++) {
797 pos.
index = result.at(i).cursorInfo.graphemeIndices.at(k);
798 pos.
offset = insertFirst? k+1: k;
799 cursorPos.append(pos);
800 QPointF offset = (k+1) * (result.at(i).advance/graphemes);
801 positions.append(result.at(i).cursorInfo.rtl? result.at(i).advance - offset: offset);
804 result[i].cursorInfo.graphemeIndices.insert(0, result[i].plaintTextIndex);
806 if (result.at(i).cursorInfo.offsets.size() < positions.size()) {
807 result[i].cursorInfo.offsets = positions;
812 if (!result.at(i).hidden) {
813 const QTransform tf = result.at(i).finalTransform();
814 chunk->
associatedLeaf->associatedOutline.addRect(tf.mapRect(result.at(i).inkBoundingBox));
821 if (dummyIndex > -1 && dummyIndex < result.size()) {
822 if (result.at(dummyIndex).anchored_chunk) {
825 pos.
index = result.at(dummyIndex).plaintTextIndex;
826 result[dummyIndex].plaintTextIndex -= 1;
827 result[dummyIndex].cursorInfo.offsets.insert(0, QPointF());
830 cursorPos.append(pos);
831 if (!textChunks.isEmpty()) {
832 textChunks.last().associatedLeaf->associatedOutline.addRect(result.at(dummyIndex).finalTransform().mapRect(result[dummyIndex].inkBoundingBox));
833 textChunks.last().associatedLeaf->finalResultIndex = result.size();
837 this->initialTextPosition = startPos;
838 this->plainText = plainText;
839 this->result = result;
840 this->cursorPos = cursorPos;
844void KoSvgTextShape::Private::clearAssociatedOutlines()
846 for (
auto it = textData.depthFirstTailBegin(); it != textData.depthFirstTailEnd(); it++) {
847 it->associatedOutline = QPainterPath();
848 it->textDecorations.clear();
849 it->finalResultIndex = -1;
857const QString
bidiControls =
"\u202a\u202b\u202c\u202d\u202e\u2066\u2067\u2068\u2069";
860 bool isHorizontal,
bool wrapped,
bool textInPath,
866 int index = currentIndex;
867 int j = index + numChars(currentTextElement, withControls, resolvedProps);
869 if (currentTextElement->textPath) {
873 for (
int k = index; k < j; k++ ) {
874 if (k >= text.size()) {
879 bool softHyphen = text.at(k) == QChar::SoftHyphen;
883 if (collapsedChars[k] || (bidi && !wrapped) || softHyphen) {
884 result[k].addressable =
false;
887 if (k > 0 && text.at(k).isLowSurrogate() && text.at(k-1).isHighSurrogate()) {
889 result[k].addressable =
false;
893 if (i < local.size()) {
897 resolved[k] = newTransform;
901 resolved[k].rotate = resolved[k - 1].rotate;
910 resolveTransforms(child, text, result, currentIndex, isHorizontal,
false, textInPath, resolved, collapsedChars, props, withControls);
914 if (currentTextElement->textPath) {
916 for (
int k = index; k < j; k++ ) {
918 if (!result[k].addressable) {
925 resolved[k].xPos = 0.0;
927 resolved[k].yPos = 0.0;
935 resolved[k].yPos.reset();
937 resolved[k].xPos.reset();
950 int &resolvedDescendentNodes,
955 int i = currentIndex;
956 int j = i + numChars(currentTextElement,
true, resolvedProps);
957 int resolvedChildren = 0;
962 applyTextLength(child, result, currentIndex, resolvedChildren, isHorizontal, props, resHandler);
967 QMap<int, int> visualToLogical;
968 if (!currentTextElement->textLength.isAuto) {
972 for (
int k = i; k < j; k++) {
973 if (result.at(k).addressable) {
974 if (result.at(k).visualIndex > -1) {
975 visualToLogical.insert(result.at(k).visualIndex, k);
979 qreal pos = result.at(k).finalPosition.x();
980 qreal advance = result.at(k).advance.x();
982 pos = result.at(k).finalPosition.y();
983 advance = result.at(k).advance.y();
986 a = qMin(pos, pos + advance);
987 b = qMax(pos, pos + advance);
989 a = qMin(a, qMin(pos, pos + advance));
990 b = qMax(b, qMax(pos, pos + advance));
992 if (!result.at(k).textLengthApplied) {
997 n += resolvedChildren;
999 if (!spacingAndGlyphs) {
1002 const qreal delta = currentTextElement->textLength.customValue - (
b - a);
1004 const QPointF
d = isHorizontal ? QPointF(delta / n, 0) : QPointF(0, delta / n);
1007 bool secondTextLengthApplied =
false;
1008 Q_FOREACH (
int k, visualToLogical.keys()) {
1012 if (spacingAndGlyphs) {
1013 QPointF
scale(
d.x() != 0 ? (
d.x() / cr.
advance.x()) + 1 : 1.0,
d.
y() != 0 ? (
d.
y() / cr.advance.
y()) + 1 : 1.0);
1016 bool last = spacingAndGlyphs ? false : k == visualToLogical.keys().last();
1019 shift = resHandler.
adjust(shift+
d);
1024 result[visualToLogical.value(k)] = cr;
1026 resolvedDescendentNodes += 1;
1030 int lastVisualValue = visualToLogical.keys().last();
1031 visualToLogical.clear();
1033 for (
int k = j; k < result.size(); k++) {
1034 if (result.at(k).anchored_chunk) {
1037 visualToLogical.insert(result.at(k).visualIndex, k);
1040 for (
int k = i; k > -1; k--) {
1041 visualToLogical.insert(result.at(k).visualIndex, k);
1042 if (result.at(k).anchored_chunk) {
1046 Q_FOREACH (
int k, visualToLogical.keys()) {
1047 if (k > lastVisualValue) {
1048 result[visualToLogical.value(k)].finalPosition += shift;
1061void KoSvgTextShape::Private::computeFontMetrics(
1066 const QPointF superScript,
1067 const QPointF subScript,
1071 const bool isHorizontal,
1072 const bool disableFontMatching)
1074 const int i = currentIndex;
1075 const int j = qMin(i + numChars(
parent,
true, parentProps), result.size());
1081 QPointF baselineShiftTotal;
1085 baselineShiftTotal = isHorizontal ? superScript : QPointF(-superScript.y(), superScript.x());
1087 baselineShiftTotal = isHorizontal ? subScript : QPointF(-subScript.y(), subScript.x());
1090 baselineShiftTotal = isHorizontal ? QPointF(0, -baselineShift.
value) : QPointF(baselineShift.
value, 0);
1092 baselineShiftTotal = resHandler.
adjust(baselineShiftTotal);
1099 static_cast<quint32
>(resHandler.
xRes),
1100 static_cast<quint32
>(resHandler.
yRes),
1101 disableFontMatching);
1116 dominantBaseline = defaultBaseline;
1125 computeFontMetrics(child, properties, metrics, dominantBaseline, newSuperScript, newSubScript, result, currentIndex, resHandler, isHorizontal, disableFontMatching);
1131 baselineAdjust = parentBaseline;
1135 baselineAdjust = defaultBaseline;
1140 const QPointF shift = resHandler.
adjust(ftTf.map(isHorizontal? QPointF(0, (boxOffset)): QPointF((boxOffset), 0)));
1143 baselineShiftTotal -= shift;
1157 for (
int k = i; k < j; k++) {
1158 if (applyGlyphAlignment) {
1161 const int originOffset = result[k].metrics.valueForBaselineValue(dominantBaseline);
1162 const QPointF newOrigin = resHandler.
adjust(ftTf.map(isHorizontal? QPointF(0, originOffset): QPointF(originOffset, 0)));
1164 const QPointF uniqueShift = resHandler.
adjust(ftTf.map(isHorizontal? QPointF(0, uniqueOffset): QPointF(uniqueOffset, 0)));
1165 result[k].translateOrigin(newOrigin);
1166 result[k].metrics.offsetMetricsToNewOrigin(dominantBaseline);
1167 result[k].dominantBaselineOffset = uniqueShift;
1169 result[k].dominantBaselineOffset += shift;
1170 result[k].baselineOffset += baselineShiftTotal;
1180 const bool isHorizontal,
1184 const int i = currentIndex;
1185 const int j = qMin(i + numChars(
parent,
true, resolvedProps), result.size());
1192 handleLineBoxAlignment(child, result, lineBoxes, currentIndex, isHorizontal, properties);
1198 Q_FOREACH(
LineBox lineBox, lineBoxes) {
1201 relevantLine = lineBox;
1205 QPointF shift = QPointF();
1206 double ascent = 0.0;
1207 double descent = 0.0;
1208 for (
int k = i; k < j; k++) {
1211 calculateLineHeight(result[k], ascent, descent, isHorizontal,
true);
1216 shift -= isHorizontal? QPointF(0, ascent):QPointF(ascent, 0);
1219 shift -= isHorizontal? QPointF(0, descent):QPointF(descent, 0);
1223 for (
int k = i; k < j; k++) {
1241void KoSvgTextShape::Private::computeTextDecorations(
1244 const QMap<int, int> &logicalToVisual,
1247 qreal textPathoffset,
1256 const int i = currentIndex;
1257 const int j = qMin(i + numChars(currentTextElement,
true, resolvedProps), result.size());
1261 qreal currentTextPathOffset = textPathoffset;
1262 bool textPathSide = side;
1264 currentTextPath = textPath ? textPath :
dynamic_cast<KoPathShape *
>(currentTextElement->textPath.data());
1266 if (currentTextElement->textPath) {
1268 if (currentTextElement->textPathInfo.startOffsetIsPercentage) {
1270 currentTextPathOffset = currentTextPath->
outline().length() * (0.01 * currentTextElement->textPathInfo.startOffset);
1272 currentTextPathOffset = currentTextElement->textPathInfo.startOffset;
1284 computeTextDecorations(child,
1289 currentTextPathOffset,
1306 QMap<TextDecoration, QPainterPath> decorationPaths =
1307 generateDecorationPaths(i, j, resHandler,
1308 result, isHorizontal, decor, style,
false, currentTextPath,
1309 currentTextPathOffset, textPathSide, newUnderlinePosH, newUnderlinePosV
1315 QPainterPath decorationPath = decorationPaths.value(type);
1316 if (!decorationPath.isEmpty()) {
1317 currentTextElement->textDecorations.insert(type, decorationPath.simplified());
1326 const qreal strokeWidth,
1328 const bool isHorizontal,
1329 const bool onTextPath,
1330 const qreal minimumDecorationThickness
1335 p.moveTo(QPointF());
1339 const int total = std::floor(
length.length() / (strokeWidth * 2));
1340 const qreal segment = qreal(
length.length() / total);
1342 for (
int i = 0; i < total; i++) {
1343 p.lineTo(
p.currentPosition() + QPointF(segment, 0));
1346 for (
int i = 0; i < total; i++) {
1347 p.lineTo(
p.currentPosition() + QPointF(0, segment));
1359 qreal linewidthOffset = qMax(strokeWidth * 1.5, minimumDecorationThickness * 2);
1361 p.addPath(
p.translated(0, linewidthOffset));
1362 pathWidth = QPointF(0, -linewidthOffset);
1364 p.addPath(
p.translated(linewidthOffset, 0));
1365 pathWidth = QPointF(linewidthOffset, 0);
1369 qreal width =
length.length();
1370 qreal height = strokeWidth * 2;
1373 p.moveTo(QPointF());
1375 for (
int i = 0; i < qFloor(width / height); i++) {
1377 p.lineTo(
p.currentPosition().x() + height, height);
1379 p.lineTo(
p.currentPosition().x() + height, 0);
1383 qreal offset = fmod(width, height);
1385 p.lineTo(width, offset);
1387 p.lineTo(width, height - offset);
1389 pathWidth = QPointF(0, -strokeWidth);
1392 if (!isHorizontal) {
1393 for (
int i = 0; i <
p.elementCount(); i++) {
1394 p.setElementPositionAt(i,
p.elementAt(i).y - (strokeWidth * 2),
p.elementAt(i).x);
1396 pathWidth = QPointF(strokeWidth, 0);
1399 return qMakePair(
p, pathWidth);
1402void KoSvgTextShape::Private::finalizeDecoration (
1403 QPainterPath decorationPath,
1404 const QPointF offset,
1405 const QPainterPathStroker &stroker,
1407 QMap<KoSvgText::TextDecoration, QPainterPath> &decorationPaths,
1409 const bool isHorizontal,
1410 const qreal currentTextPathOffset,
1411 const bool textPathSide
1413 if (currentTextPath) {
1414 QPainterPath path = currentTextPath->
outline();
1417 path = path.toReversed();
1420 decorationPath = stretchGlyphOnPath(decorationPath.translated(offset), path, isHorizontal, currentTextPathOffset, currentTextPath->
isClosedSubpath(0));
1421 decorationPaths[type].addPath(stroker.createStroke(decorationPath));
1423 decorationPaths[type].addPath(stroker.createStroke(decorationPath.translated(offset)));
1425 decorationPaths[type].setFillRule(Qt::WindingFill);
1428QMap<KoSvgText::TextDecoration, QPainterPath>
1429KoSvgTextShape::Private::generateDecorationPaths(
const int &start,
const int &end,
1432 const bool isHorizontal,
1433 const KoSvgText::TextDecorations &decor,
1435 const bool textDecorationSkipInset,
1437 const qreal currentTextPathOffset,
1438 const bool textPathSide,
1444 const qreal minimumDecorationThickness = resHandler.
pixelToPointFactor(isHorizontal);
1446 QMap<TextDecoration, QPainterPath> decorationPaths;
1448 decorationPaths.insert(DecorationUnderline, QPainterPath());
1449 decorationPaths.insert(DecorationOverline, QPainterPath());
1450 decorationPaths.insert(DecorationLineThrough, QPainterPath());
1452 QPainterPathStroker stroker;
1453 stroker.setCapStyle(Qt::FlatCap);
1454 if (style == Dotted) {
1456 pen.setStyle(Qt::DotLine);
1457 stroker.setDashPattern(pen.dashPattern());
1458 }
else if (style == Dashed) {
1460 pen.setStyle(Qt::DashLine);
1461 stroker.setDashPattern(pen.dashPattern());
1464 struct DecorationBox {
1465 QRectF decorationRect;
1467 qint32 thickness = 0;
1472 struct LineThrough {
1474 qint32 thickness = 0;
1479 DecorationBox currentBox;
1480 currentBox.start = start;
1483 for (
int k = start; k < end; k++) {
1485 const bool atEnd = k+1 >= end;
1486 if ((charResult.
anchored_chunk || atEnd) && currentBox.start < k) {
1489 for (
int l = atEnd? k: k-1; l > currentBox.start; l--) {
1490 if (!result.at(l).inkBoundingBox.isEmpty()
1491 && result.at(l).addressable
1492 && !result.at(l).hidden) {
1498 decorationBoxes.append(currentBox);
1499 currentBox = DecorationBox();
1500 currentBox.start = k;
1502 if (currentBox.start == k && (charResult.
inkBoundingBox.isEmpty()
1505 currentBox.start = k+1;
1509 qint32 lastFontSize = 0;
1510 QPointF lastBaselineOffset;
1511 QPointF lastLineThroughOffset;
1515 for (
int b = 0;
b < decorationBoxes.size();
b++) {
1516 DecorationBox currentBox = decorationBoxes.at(b);
1517 LineThrough currentLineThrough = LineThrough();
1518 lastFontSize = result.at(currentBox.start).metrics.fontSize;
1519 lastBaselineOffset = result.at(currentBox.start).totalBaselineOffset();
1521 for (
int k = currentBox.start; k <= currentBox.end; k++) {
1524 if (currentTextPath) {
1525 characterResultOnPath(charResult,
1526 currentTextPath->
outline().length(),
1527 currentTextPathOffset,
1537 const bool newLineThrough = charResult.
metrics.
fontSize != lastFontSize && !baselineIsOffset;
1538 const bool ignoreMetrics = !newLineThrough && baselineIsOffset && !currentLineThrough.line.isEmpty();
1540 if (newLineThrough) {
1541 lineThroughLines.append(currentLineThrough);
1542 currentLineThrough = LineThrough();
1549 const QPointF alphabeticOffset = isHorizontal? QPointF(0, -alphabetic): QPointF(alphabetic, 0);
1551 if (!ignoreMetrics) {
1554 lastLineThroughOffset = isHorizontal? QPointF(0, -(charResult.
metrics.
lineThroughOffset*freetypePixelsToPt)) + alphabeticOffset
1558 QPointF lastLineThroughPoint = charResult.
finalPosition + charResult.
advance + lastLineThroughOffset;
1560 if (ignoreMetrics) {
1561 QPointF lastP = currentLineThrough.line.last();
1563 lastLineThroughPoint.setY(lastP.y());
1565 lastLineThroughPoint.setX(lastP.x());
1569 if ((isHorizontal && underlinePosH == UnderlineAuto)) {
1570 QRectF bbox = charResult.
layoutBox().translated(-alphabeticOffset);
1572 currentBox.decorationRect |= bbox.translated(charResult.
finalPosition + alphabeticOffset);
1576 if (currentLineThrough.line.isEmpty()) {
1577 currentLineThrough.line.append(charResult.
finalPosition + lastLineThroughOffset);
1579 currentLineThrough.line.append(lastLineThroughPoint);
1581 decorationBoxes[
b] = currentBox;
1582 lineThroughLines.append(currentLineThrough);
1589 const bool textOnPath = (currentTextPath)?
true: false;
1591 for (
int i = 0; i < decorationBoxes.size(); i++) {
1592 DecorationBox box = decorationBoxes.at(i);
1593 if (box.underlineOffsets.size() > 0) {
1594 stroker.setWidth(qMax(minimumDecorationThickness, (box.thickness/(box.underlineOffsets.size()))*freetypePixelsToPt));
1596 stroker.setWidth(resHandler.
adjust(QPointF(0, stroker.width())).y());
1598 stroker.setWidth(resHandler.
adjust(QPointF(stroker.width(), 0)).x());
1601 stroker.setWidth(minimumDecorationThickness);
1603 QRectF
rect = box.decorationRect;
1604 if (textDecorationSkipInset) {
1605 qreal inset = stroker.width() * 0.5;
1606 rect.adjust(-inset, -inset, inset, inset);
1609 length.setP1(isHorizontal? QPointF(
rect.left(), 0): QPointF(0,
rect.top()));
1610 length.setP2(isHorizontal? QPointF(
rect.right(), 0): QPointF(0,
rect.bottom()));
1612 QPair<QPainterPath, QPointF> generatedPath =
generateDecorationPath(
length, stroker.width(), style, isHorizontal, textOnPath, minimumDecorationThickness);
1613 QPainterPath
p = generatedPath.first;
1614 QPointF pathWidth = generatedPath.second;
1616 QMap<TextDecoration, QPointF> decorationOffsets;
1618 const qreal startX =
rect.left();
1621 if (underlinePosH == UnderlineAuto) {
1623 for (
int j = 0; j < box.underlineOffsets.size(); j++) {
1624 average += box.underlineOffsets.at(j);
1626 average = average > 0? (average/box.underlineOffsets.size()): 0;
1630 const qreal startY =
rect.top();
1631 if (underlinePosV == UnderlineRight) {
1640 if (decor.testFlag(DecorationUnderline)) {
1641 finalizeDecoration(
p, decorationOffsets.value(DecorationUnderline), stroker, DecorationUnderline, decorationPaths, currentTextPath, isHorizontal, currentTextPathOffset, textPathSide);
1643 if (decor.testFlag(DecorationOverline)) {
1644 finalizeDecoration(
p, decorationOffsets.value(DecorationOverline), stroker, DecorationOverline, decorationPaths, currentTextPath, isHorizontal, currentTextPathOffset, textPathSide);
1647 if (decor.testFlag(DecorationLineThrough)) {
1648 for (
int i = 0; i < lineThroughLines.size(); i++) {
1649 LineThrough l = lineThroughLines.at(i);
1650 QPolygonF poly = l.line;
1651 if (poly.isEmpty())
continue;
1652 stroker.setWidth(qMax(minimumDecorationThickness, (l.thickness/(poly.size()))*freetypePixelsToPt));
1655 pathWidth = resHandler.
adjust(QPointF(0, stroker.width()));
1656 stroker.setWidth(pathWidth.y());
1658 pathWidth = resHandler.
adjust(QPointF(stroker.width(), 0));
1659 stroker.setWidth(pathWidth.x());
1661 qreal average = 0.0;
1662 for (
int j = 0; j < poly.size(); j++) {
1663 average += isHorizontal? poly.at(j).y(): poly.at(j).x();
1665 average /= poly.size();
1666 QLineF line = isHorizontal? QLineF(poly.first().x(), average, poly.last().x(), average)
1667 : QLineF(average, poly.first().
y(), average, poly.last().
y());
1670 QPair<QPainterPath, QPointF> generatedPath =
generateDecorationPath(line, stroker.width(), style, isHorizontal, textOnPath, minimumDecorationThickness);
1671 QPainterPath
p = generatedPath.first;
1672 finalizeDecoration(
p, line.p1() + (generatedPath.second * 0.5), stroker, DecorationLineThrough, decorationPaths, currentTextPath, isHorizontal, currentTextPathOffset, textPathSide);
1676 return decorationPaths;
1685 while (start < result.size()) {
1687 qreal shift = anchoredChunkShift(result, isHorizontal, start, end);
1689 const QPointF shiftP = resHandler.
adjust(isHorizontal ? QPointF(shift, 0) : QPointF(0, shift));
1691 for (
int j = start; j < end; j++) {
1692 result[j].finalPosition += shiftP;
1698qreal KoSvgTextShape::Private::anchoredChunkShift(
const QVector<CharacterResult> &result,
const bool isHorizontal,
const int start,
int &end)
1703 for (; i < result.size(); i++) {
1704 if (!result.at(i).addressable) {
1707 if (result.at(i).anchored_chunk && i > start) {
1710 qreal pos = isHorizontal ? result.at(i).finalPosition.x() : result.at(i).finalPosition.y();
1711 qreal advance = isHorizontal ? result.at(i).advance.x() : result.at(i).advance.y();
1713 if (result.at(i).anchored_chunk) {
1714 a = qMin(pos, pos + advance);
1715 b = qMax(pos, pos + advance);
1717 a = qMin(a, qMin(pos, pos + advance));
1718 b = qMax(b, qMax(pos, pos + advance));
1735 shift = shift - (a +
b) * 0.5;
1742qreal KoSvgTextShape::Private::characterResultOnPath(
CharacterResult &cr,
1750 if (!isHorizontal) {
1755 if (mid - offset < 0 || mid - offset >
length) {
1759 if (mid - offset < -length || mid - offset > 0) {
1763 if (mid - offset < -(
length * 0.5) || mid - offset > (
length * 0.5)) {
1772 if (mid < 0 || mid >
length) {
1779QPainterPath KoSvgTextShape::Private::stretchGlyphOnPath(
const QPainterPath &glyph,
1780 const QPainterPath &path,
1785 QPainterPath
p = glyph;
1786 for (
int i = 0; i < glyph.elementCount(); i++) {
1787 qreal mid = isHorizontal ? glyph.elementAt(i).x + offset : glyph.elementAt(i).y + offset;
1788 qreal midUnbound = mid;
1791 mid += path.length();
1793 mid = fmod(mid, qreal(path.length()));
1796 mid = qBound(0.0, mid, qreal(path.length()));
1798 const qreal percent = path.percentAtLength(mid);
1799 const QPointF pos = path.pointAtPercent(percent);
1800 qreal tAngle = path.angleAtPercent(percent);
1802 tAngle = 0 - (360 - tAngle);
1804 const QPointF vectorT(qCos(qDegreesToRadians(tAngle)), -qSin(qDegreesToRadians(tAngle)));
1805 QPointF finalPos = pos;
1807 QPointF vectorN(-vectorT.y(), vectorT.x());
1808 const qreal
o = mid - (midUnbound);
1809 finalPos = pos - (
o * vectorT) + (glyph.elementAt(i).y * vectorN);
1811 QPointF vectorN(vectorT.y(), -vectorT.x());
1812 const qreal
o = mid - (midUnbound);
1813 finalPos = pos - (
o * vectorT) + (glyph.elementAt(i).x * vectorN);
1815 p.setElementPositionAt(i, finalPos.x(), finalPos.y());
1830 bool inPath =
false;
1831 bool afterPath =
false;
1832 int currentIndex = 0;
1835 int endIndex = currentIndex + numChars(textShapeElement,
true, resolvedProps);
1839 QPainterPath path = shape->
outline();
1843 path = path.toReversed();
1845 qreal
length = path.length();
1848 if (textShapeElement->textPathInfo.startOffsetIsPercentage) {
1849 offset =
length * (0.01 * textShapeElement->textPathInfo.startOffset);
1851 offset = textShapeElement->textPathInfo.startOffset;
1856 const qreal percent = path.percentAtLength(offset);
1857 startPos = path.pointAtPercent(percent);
1860 for (
int i = currentIndex; i < endIndex; i++) {
1864 const qreal mid = characterResultOnPath(cr,
length, offset, isHorizontal, isClosed);
1866 auto *outlineGlyph = std::get_if<Glyph::Outline>(&cr.
glyph);
1868 if (stretch && outlineGlyph) {
1870 QPainterPath glyph = stretchGlyphOnPath(tf.map(outlineGlyph->path), path, isHorizontal, offset, isClosed);
1871 outlineGlyph->path = glyph;
1873 const qreal percent = path.percentAtLength(mid);
1874 const QPointF pos = path.pointAtPercent(percent);
1875 qreal tAngle = path.angleAtPercent(percent);
1877 tAngle = 0 - (360 - tAngle);
1879 const QPointF vectorT(qCos(qDegreesToRadians(tAngle)), -qSin(qDegreesToRadians(tAngle)));
1881 cr.
rotate -= qDegreesToRadians(tAngle);
1882 QPointF vectorN(-vectorT.y(), vectorT.x());
1883 const qreal
o = (cr.
advance.x() * 0.5);
1886 cr.
rotate -= qDegreesToRadians(tAngle + 90);
1887 QPointF vectorN(vectorT.y(), -vectorT.x());
1888 const qreal
o = (cr.
advance.y() * 0.5);
1892 if (stretch && outlineGlyph) {
1894 outlineGlyph->path = tf.inverted().map(outlineGlyph->path);
1900 pathEnd = path.pointAtPercent(1.0);
1905 pathEnd -= result.at(currentIndex).finalPosition;
1908 for (
int i = currentIndex; i < endIndex; i++) {
1919 currentIndex = endIndex;
1927 firstTextInPath =
true;
1934 if (childCount(it)) {
1936 result += collectSubChunks(child, currentProps, textInPath, firstTextInPath);
1950 if (!bidiOpening.isEmpty()) {
1951 chunk.
text = bidiOpening;
1954 result.append(chunk);
1955 firstTextInPath =
false;
1960 result.append(chunk);
1962 if (!bidiClosing.isEmpty()) {
1963 chunk.
text = bidiClosing;
1966 result.append(chunk);
1969 firstTextInPath =
false;
1974 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
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 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
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.