File indexing completed on 2024-06-16 04:17:51
0001 /* 0002 * SPDX-FileCopyrightText: 2023 Alvin Wong <alvin@alvinhc.com> 0003 * 0004 * SPDX-License-Identifier: GPL-3.0-or-later 0005 */ 0006 0007 #ifndef SVG_INLINE_SIZE_HELPER_H 0008 #define SVG_INLINE_SIZE_HELPER_H 0009 0010 #include "KoSvgText.h" 0011 #include "KoSvgTextProperties.h" 0012 #include "KoSvgTextShape.h" 0013 0014 #include <optional> 0015 0016 namespace SvgInlineSizeHelper 0017 { 0018 0019 [[nodiscard]] static inline double getInlineSizePt(const KoSvgTextShape *const shape) 0020 { 0021 const KoSvgText::AutoValue inlineSizeProp = 0022 shape->textProperties().property(KoSvgTextProperties::InlineSizeId).value<KoSvgText::AutoValue>(); 0023 if (!inlineSizeProp.isAuto) { 0024 return inlineSizeProp.customValue; 0025 } 0026 return 0.0; 0027 } 0028 0029 enum class VisualAnchor { 0030 LeftOrTop, 0031 Mid, 0032 RightOrBottom, 0033 }; 0034 0035 enum class Side { 0036 LeftOrTop, 0037 RightOrBottom, 0038 }; 0039 0040 struct Q_DECL_HIDDEN InlineSizeInfo { 0041 double inlineSize; 0042 /// Baseline coord along the block-flow direction 0043 double baseline; 0044 /// Left coord (vertical mode) or top coord (horizontal mode) 0045 double left; 0046 /// Right coord (vertical mode) or bottom coord (horizontal mode) 0047 double right; 0048 /// Top coord along the block-flow direction (right for h-rl, left for h-lr) 0049 double top; 0050 /// Bottom coord along the block-flow direction (left for h-rl, right for h-lr) 0051 double bottom; 0052 /// Length of the dashes at the end. 0053 double dashesLength; 0054 VisualAnchor anchor; 0055 /// Transformation from inline-size editor (writing-mode transformation) to shape 0056 QTransform editorTransform; 0057 /// Transformation from shape local to document 0058 QTransform shapeTransform; 0059 0060 [[nodiscard]] static inline std::optional<InlineSizeInfo> fromShape(KoSvgTextShape *const shape, qreal dashesLength = 36.0) 0061 { 0062 const double inlineSize = getInlineSizePt(shape); 0063 if (inlineSize <= 0) { 0064 return {}; 0065 } 0066 KoSvgTextProperties props = shape->propertiesForPos(-1); 0067 0068 const KoSvgText::WritingMode writingMode = KoSvgText::WritingMode( 0069 props.propertyOrDefault(KoSvgTextProperties::WritingModeId).toInt()); 0070 const KoSvgText::Direction direction = 0071 KoSvgText::Direction(props.propertyOrDefault(KoSvgTextProperties::DirectionId).toInt()); 0072 const KoSvgText::TextAnchor textAnchor = 0073 KoSvgText::TextAnchor(props.propertyOrDefault(KoSvgTextProperties::TextAnchorId).toInt()); 0074 0075 VisualAnchor anchor{}; 0076 switch (textAnchor) { 0077 case KoSvgText::TextAnchor::AnchorStart: 0078 default: 0079 if (direction == KoSvgText::Direction::DirectionLeftToRight) { 0080 anchor = VisualAnchor::LeftOrTop; 0081 } else { 0082 anchor = VisualAnchor::RightOrBottom; 0083 } 0084 break; 0085 case KoSvgText::TextAnchor::AnchorMiddle: 0086 anchor = VisualAnchor::Mid; 0087 break; 0088 case KoSvgText::TextAnchor::AnchorEnd: 0089 if (direction == KoSvgText::Direction::DirectionLeftToRight) { 0090 anchor = VisualAnchor::RightOrBottom; 0091 } else { 0092 anchor = VisualAnchor::LeftOrTop; 0093 } 0094 break; 0095 } 0096 const double xPos = shape->initialTextPosition().x(); 0097 const double yPos = shape->initialTextPosition().y(); 0098 0099 const double baseline = yPos; 0100 double left{}; 0101 double right{}; 0102 double top{}; 0103 double bottom{}; 0104 switch (anchor) { 0105 case VisualAnchor::LeftOrTop: 0106 left = xPos; 0107 right = xPos + inlineSize; 0108 break; 0109 case VisualAnchor::Mid: 0110 left = xPos - inlineSize * 0.5; 0111 right = xPos + inlineSize * 0.5; 0112 break; 0113 case VisualAnchor::RightOrBottom: 0114 left = xPos - inlineSize; 0115 right = xPos; 0116 break; 0117 }; 0118 0119 // We piggyback on the shape transformation that we already need to 0120 // deal with to also handle the different orientations of writing-mode. 0121 // We default to the "default caret size" when the textShape (and thus outline) is empty. 0122 QLineF caret; 0123 QColor c; 0124 shape->cursorForPos(0, caret, c); 0125 const QRectF outline = shape->outlineRect().isEmpty()? QRectF(caret.p2(), caret.p1()): shape->outlineRect(); 0126 QTransform editorTransform; 0127 switch (writingMode) { 0128 case KoSvgText::WritingMode::HorizontalTB: 0129 default: 0130 top = outline.top(); 0131 bottom = outline.bottom(); 0132 break; 0133 case KoSvgText::WritingMode::VerticalRL: 0134 editorTransform.rotate(90.0); 0135 top = -outline.right(); 0136 bottom = -outline.left(); 0137 break; 0138 case KoSvgText::WritingMode::VerticalLR: 0139 editorTransform.rotate(-90.0); 0140 editorTransform.scale(-1.0, 1.0); 0141 top = outline.left(); 0142 bottom = outline.right(); 0143 break; 0144 } 0145 0146 QLineF scale = editorTransform.map(QLineF(0, 0, 0, 1.0)); 0147 scale = shape->absoluteTransformation().inverted().map(scale); 0148 scale = editorTransform.inverted().map(scale); 0149 0150 InlineSizeInfo ret{inlineSize, 0151 baseline, 0152 left, 0153 right, 0154 top, 0155 bottom, 0156 dashesLength * scale.length(), 0157 anchor, 0158 editorTransform, 0159 shape->absoluteTransformation()}; 0160 return {ret}; 0161 } 0162 0163 private: 0164 [[nodiscard]] inline QLineF leftLineRaw() const 0165 { 0166 return {left, top, left, bottom}; 0167 } 0168 0169 [[nodiscard]] inline QLineF rightLineRaw() const 0170 { 0171 return {right, top, right, bottom}; 0172 } 0173 0174 [[nodiscard]] inline QRectF boundingRectRaw() const 0175 { 0176 return {QPointF(left, top), QPointF(right, bottom + dashesLength)}; 0177 } 0178 0179 [[nodiscard]] inline QLineF generateDashLine(const QLineF line, const qreal dashLength = 4.0) const 0180 { 0181 QPointF start = line.p2(); 0182 QLineF dash = line; 0183 dash.setLength(line.length() + dashLength); 0184 dash.setP1(start); 0185 return dash; 0186 } 0187 0188 public: 0189 /** 0190 * @brief Gets a shape-local line representing the first line baseline. This 0191 * always goes from left to right by the inline-base direction, then mapped 0192 * by the editor transformation. 0193 * @return QLineF 0194 */ 0195 [[nodiscard]] inline QLineF baselineLineLocal() const 0196 { 0197 return editorTransform.map(QLineF{left, baseline, right, baseline}); 0198 } 0199 0200 /** 0201 * @brief Gets a line representing the first line baseline. This always 0202 * goes from left to right by the inline-base direction, then mapped by the 0203 * editor and the shape transformation. 0204 * @return QLineF 0205 */ 0206 [[nodiscard]] inline QLineF baselineLine() const 0207 { 0208 return shapeTransform.map(baselineLineLocal()); 0209 } 0210 0211 [[nodiscard]] inline Side endLineSide() const 0212 { 0213 switch (anchor) { 0214 case VisualAnchor::LeftOrTop: 0215 case VisualAnchor::Mid: 0216 default: 0217 return Side::RightOrBottom; 0218 case VisualAnchor::RightOrBottom: 0219 return Side::LeftOrTop; 0220 } 0221 } 0222 0223 [[nodiscard]] inline QLineF endLineLocal() const 0224 { 0225 switch (endLineSide()) { 0226 case Side::LeftOrTop: 0227 return editorTransform.map(leftLineRaw()); 0228 case Side::RightOrBottom: 0229 default: 0230 return editorTransform.map(rightLineRaw()); 0231 } 0232 } 0233 0234 [[nodiscard]] inline QLineF endLineDashes() const 0235 { 0236 switch (endLineSide()) { 0237 case Side::LeftOrTop: 0238 return generateDashLine(editorTransform.map(leftLineRaw()), dashesLength); 0239 case Side::RightOrBottom: 0240 default: 0241 return generateDashLine(editorTransform.map(rightLineRaw()), dashesLength); 0242 } 0243 } 0244 0245 [[nodiscard]] inline QLineF endLine() const 0246 { 0247 return shapeTransform.map(endLineLocal()); 0248 } 0249 0250 [[nodiscard]] inline Side startLineSide() const 0251 { 0252 switch (anchor) { 0253 case VisualAnchor::LeftOrTop: 0254 case VisualAnchor::Mid: 0255 default: 0256 return Side::LeftOrTop; 0257 case VisualAnchor::RightOrBottom: 0258 return Side::RightOrBottom; 0259 } 0260 } 0261 0262 [[nodiscard]] inline QLineF startLineLocal() const 0263 { 0264 switch (endLineSide()) { 0265 case Side::LeftOrTop: 0266 return editorTransform.map(rightLineRaw()); 0267 case Side::RightOrBottom: 0268 default: 0269 return editorTransform.map(leftLineRaw()); 0270 } 0271 } 0272 0273 [[nodiscard]] inline QLineF startLineDashes() const 0274 { 0275 switch (endLineSide()) { 0276 case Side::LeftOrTop: 0277 return generateDashLine(editorTransform.map(rightLineRaw()), dashesLength); 0278 case Side::RightOrBottom: 0279 default: 0280 return generateDashLine(editorTransform.map(leftLineRaw()), dashesLength); 0281 } 0282 } 0283 0284 [[nodiscard]] inline QLineF startLine() const 0285 { 0286 return shapeTransform.map(startLineLocal()); 0287 } 0288 0289 [[nodiscard]] inline QPolygonF endLineGrabRect(double grabThreshold) const 0290 { 0291 QLineF endLine; 0292 switch (endLineSide()) { 0293 case Side::LeftOrTop: 0294 endLine = leftLineRaw(); 0295 break; 0296 case Side::RightOrBottom: 0297 default: 0298 endLine = rightLineRaw(); 0299 break; 0300 } 0301 const QRectF rect{endLine.x1() - grabThreshold, 0302 top - grabThreshold, 0303 grabThreshold * 2, 0304 bottom - top + grabThreshold * 2}; 0305 const QPolygonF poly(rect); 0306 return shapeTransform.map(editorTransform.map(poly)); 0307 } 0308 0309 [[nodiscard]] inline QPolygonF startLineGrabRect(double grabThreshold) const 0310 { 0311 QLineF startLine; 0312 switch (endLineSide()) { 0313 case Side::LeftOrTop: 0314 startLine = rightLineRaw(); 0315 break; 0316 case Side::RightOrBottom: 0317 default: 0318 startLine = leftLineRaw(); 0319 break; 0320 } 0321 const QRectF rect{startLine.x1() - grabThreshold, 0322 top - grabThreshold, 0323 grabThreshold * 2, 0324 bottom - top + grabThreshold * 2}; 0325 const QPolygonF poly(rect); 0326 return shapeTransform.map(editorTransform.map(poly)); 0327 } 0328 0329 [[nodiscard]] inline QRectF boundingRect() const 0330 { 0331 return shapeTransform.mapRect(editorTransform.mapRect(boundingRectRaw())); 0332 } 0333 }; 0334 0335 } // namespace SvgInlineSizeHelper 0336 0337 #endif /* SVG_INLINE_SIZE_HELPER_H */