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 
19 class SkContourMeasure;
20 struct 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
33 class SkSVGTextContext final : SkShaper::RunHandler {
34 public:
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 
has(Attr a)55         bool has(Attr a) const { return fStorage[a] != kNone; }
hasAny()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 
setImplicitRotate(bool imp)64         void setImplicitRotate(bool imp) { fImplicitRotate = imp; }
isImplicitRotate()65         bool isImplicitRotate() const { return fImplicitRotate; }
66 
67     private:
68         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 
getCallback()119     const ShapedTextCallback& getCallback() const { return fCallback; }
120 
121 private:
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 
reserveShapeBuffer132         void reserve(size_t size) {
133             fUtf8.reserve_back(SkToInt(size));
134             fUtf8PosAdjust.reserve_back(SkToInt(size));
135         }
136 
resetShapeBuffer137         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 
length()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
beginLine()176     void beginLine() override {}
runInfo(const RunInfo &)177     void runInfo(const RunInfo&) override {}
commitRunInfo()178     void commitRunInfo() override {}
179     Buffer runBuffer(const RunInfo& ri) override;
180     void commitRunBuffer(const RunInfo& ri) override;
commitLine()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