1/*
2 * Copyright 2020 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#ifndef SkSVGTextPriv_DEFINED
9#define SkSVGTextPriv_DEFINED
10
11#include "modules/skshaper/include/SkShaper.h"
12#include "modules/svg/include/SkSVGRenderContext.h"
13#include "modules/svg/include/SkSVGText.h"
14#include "src/core/SkTLazy.h"
15
16#include <functional>
17#include <tuple>
18
19class SkContourMeasure;
20struct SkRSXform;
21
22// SkSVGTextContext is responsible for sequencing input text chars into "chunks".
23// A single text chunk can span multiple structural elements (<text>, <tspan>, etc),
24// and per [1] new chunks are emitted
25//
26//   a) for each top level text element (<text>, <textPath>)
27//   b) whenever a character with an explicit absolute position is encountered
28//
29// The implementation queues shaped run data until a full text chunk is resolved, at which
30// point we have enough information to perform final alignment and rendering.
31//
32// [1] https://www.w3.org/TR/SVG11/text.html#TextLayoutIntroduction
33class SkSVGTextContext final : SkShaper::RunHandler {
34public:
35    using ShapedTextCallback = std::function<void(const SkSVGRenderContext&,
36                                                  const sk_sp<SkTextBlob>&,
37                                                  const SkPaint*,
38                                                  const SkPaint*)>;
39
40    // Helper for encoding optional positional attributes.
41    class PosAttrs {
42    public:
43        // TODO: rotate
44        enum Attr : size_t {
45            kX      = 0,
46            kY      = 1,
47            kDx     = 2,
48            kDy     = 3,
49            kRotate = 4,
50        };
51
52        float  operator[](Attr a) const { return fStorage[a]; }
53        float& operator[](Attr a)       { return fStorage[a]; }
54
55        bool has(Attr a) const { return fStorage[a] != kNone; }
56        bool hasAny()    const {
57            return this->has(kX)
58                || this->has(kY)
59                || this->has(kDx)
60                || this->has(kDy)
61                || this->has(kRotate);
62        }
63
64        void setImplicitRotate(bool imp) { fImplicitRotate = imp; }
65        bool isImplicitRotate() const { return fImplicitRotate; }
66
67    private:
68        inline static constexpr auto kNone = std::numeric_limits<float>::infinity();
69
70        float fStorage[5]     = { kNone, kNone, kNone, kNone, kNone };
71        bool  fImplicitRotate = false;
72    };
73
74    // Helper for cascading position attribute resolution (x, y, dx, dy, rotate) [1]:
75    //   - each text position element can specify an arbitrary-length attribute array
76    //   - for each character, we look up a given attribute first in its local attribute array,
77    //     then in the ancestor chain (cascading/fallback) - and return the first value encountered.
78    //   - the lookup is based on character index relative to the text content subtree
79    //     (i.e. the index crosses chunk boundaries)
80    //
81    // [1] https://www.w3.org/TR/SVG11/text.html#TSpanElementXAttribute
82    class ScopedPosResolver {
83    public:
84        ScopedPosResolver(const SkSVGTextContainer&, const SkSVGLengthContext&, SkSVGTextContext*,
85                          size_t);
86
87        ScopedPosResolver(const SkSVGTextContainer&, const SkSVGLengthContext&, SkSVGTextContext*);
88
89        ~ScopedPosResolver();
90
91        PosAttrs resolve(size_t charIndex) const;
92
93    private:
94        SkSVGTextContext*         fTextContext;
95        const ScopedPosResolver*  fParent;          // parent resolver (fallback)
96        const size_t              fCharIndexOffset; // start index for the current resolver
97        const std::vector<float>  fX,
98                                  fY,
99                                  fDx,
100                                  fDy;
101        const std::vector<float>& fRotate;
102
103        // cache for the last known index with explicit positioning
104        mutable size_t           fLastPosIndex = std::numeric_limits<size_t>::max();
105
106    };
107
108    SkSVGTextContext(const SkSVGRenderContext&,
109                     const ShapedTextCallback&,
110                     const SkSVGTextPath* = nullptr);
111    ~SkSVGTextContext() override;
112
113    // Shape and queue codepoints for final alignment.
114    void shapeFragment(const SkString&, const SkSVGRenderContext&, SkSVGXmlSpace);
115
116    // Perform final adjustments and push shaped blobs to the callback.
117    void flushChunk(const SkSVGRenderContext& ctx);
118
119    const ShapedTextCallback& getCallback() const { return fCallback; }
120
121private:
122    struct PositionAdjustment {
123        SkVector offset;
124        float    rotation;
125    };
126
127    struct ShapeBuffer {
128        SkSTArray<128, char              , true> fUtf8;
129        // per-utf8-char cumulative pos adjustments
130        SkSTArray<128, PositionAdjustment, true> fUtf8PosAdjust;
131
132        void reserve(size_t size) {
133            fUtf8.reserve_back(SkToInt(size));
134            fUtf8PosAdjust.reserve_back(SkToInt(size));
135        }
136
137        void reset() {
138            fUtf8.reset();
139            fUtf8PosAdjust.reset();
140        }
141
142        void append(SkUnichar, PositionAdjustment);
143    };
144
145    struct RunRec {
146        SkFont                                font;
147        std::unique_ptr<SkPaint>              fillPaint,
148                                              strokePaint;
149        std::unique_ptr<SkGlyphID[]>          glyphs;        // filled by SkShaper
150        std::unique_ptr<SkPoint[]>            glyphPos;      // filled by SkShaper
151        std::unique_ptr<PositionAdjustment[]> glyhPosAdjust; // deferred positioning adjustments
152        size_t                                glyphCount;
153        SkVector                              advance;
154    };
155
156    // Caches path information to accelerate position lookups.
157    class PathData {
158    public:
159        PathData(const SkSVGRenderContext&, const SkSVGTextPath&);
160
161        SkMatrix getMatrixAt(float offset) const;
162
163        float length() const { return fLength; }
164
165    private:
166        std::vector<sk_sp<SkContourMeasure>> fContours;
167        float                                fLength = 0; // total path length
168    };
169
170    void shapePendingBuffer(const SkFont&);
171
172    SkRSXform computeGlyphXform(SkGlyphID, const SkFont&, const SkPoint& glyph_pos,
173                                const PositionAdjustment&) const;
174
175    // SkShaper callbacks
176    void beginLine() override {}
177    void runInfo(const RunInfo&) override {}
178    void commitRunInfo() override {}
179    Buffer runBuffer(const RunInfo& ri) override;
180    void commitRunBuffer(const RunInfo& ri) override;
181    void commitLine() override {}
182
183    // http://www.w3.org/TR/SVG11/text.html#TextLayout
184    const SkSVGRenderContext&       fRenderContext; // original render context
185    const ShapedTextCallback&       fCallback;
186    const std::unique_ptr<SkShaper> fShaper;
187    std::vector<RunRec>             fRuns;
188    const ScopedPosResolver*        fPosResolver  = nullptr;
189    std::unique_ptr<PathData>       fPathData;
190
191    // shaper state
192    ShapeBuffer                     fShapeBuffer;
193    std::vector<uint32_t>           fShapeClusterBuffer;
194
195    // chunk state
196    SkPoint                         fChunkPos     = {0,0}; // current text chunk position
197    SkVector                        fChunkAdvance = {0,0}; // cumulative advance
198    float                           fChunkAlignmentFactor; // current chunk alignment
199
200    // tracks the global text subtree char index (cross chunks).  Used for position resolution.
201    size_t                          fCurrentCharIndex = 0;
202
203    // cached for access from SkShaper callbacks.
204    SkTLazy<SkPaint>                fCurrentFill;
205    SkTLazy<SkPaint>                fCurrentStroke;
206
207    bool                            fPrevCharSpace = true; // WS filter state
208};
209
210#endif // SkSVGTextPriv_DEFINED
211