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 */