| /* |
| * Copyright 2020 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #ifndef SkSVGTextPriv_DEFINED |
| #define SkSVGTextPriv_DEFINED |
| |
| #include "modules/skshaper/include/SkShaper.h" |
| #include "modules/svg/include/SkSVGRenderContext.h" |
| #include "modules/svg/include/SkSVGText.h" |
| #include "src/core/SkTLazy.h" |
| |
| #include <functional> |
| #include <tuple> |
| |
| class SkContourMeasure; |
| struct SkRSXform; |
| |
| // SkSVGTextContext is responsible for sequencing input text chars into "chunks". |
| // A single text chunk can span multiple structural elements (<text>, <tspan>, etc), |
| // and per [1] new chunks are emitted |
| // |
| // a) for each top level text element (<text>, <textPath>) |
| // b) whenever a character with an explicit absolute position is encountered |
| // |
| // The implementation queues shaped run data until a full text chunk is resolved, at which |
| // point we have enough information to perform final alignment and rendering. |
| // |
| // [1] https://www.w3.org/TR/SVG11/text.html#TextLayoutIntroduction |
| class SkSVGTextContext final : SkShaper::RunHandler { |
| public: |
| using ShapedTextCallback = std::function<void(const SkSVGRenderContext&, |
| const sk_sp<SkTextBlob>&, |
| const SkPaint*, |
| const SkPaint*)>; |
| |
| // Helper for encoding optional positional attributes. |
| class PosAttrs { |
| public: |
| // TODO: rotate |
| enum Attr : size_t { |
| kX = 0, |
| kY = 1, |
| kDx = 2, |
| kDy = 3, |
| kRotate = 4, |
| }; |
| |
| float operator[](Attr a) const { return fStorage[a]; } |
| float& operator[](Attr a) { return fStorage[a]; } |
| |
| bool has(Attr a) const { return fStorage[a] != kNone; } |
| bool hasAny() const { |
| return this->has(kX) |
| || this->has(kY) |
| || this->has(kDx) |
| || this->has(kDy) |
| || this->has(kRotate); |
| } |
| |
| void setImplicitRotate(bool imp) { fImplicitRotate = imp; } |
| bool isImplicitRotate() const { return fImplicitRotate; } |
| |
| private: |
| inline static constexpr auto kNone = std::numeric_limits<float>::infinity(); |
| |
| float fStorage[5] = { kNone, kNone, kNone, kNone, kNone }; |
| bool fImplicitRotate = false; |
| }; |
| |
| // Helper for cascading position attribute resolution (x, y, dx, dy, rotate) [1]: |
| // - each text position element can specify an arbitrary-length attribute array |
| // - for each character, we look up a given attribute first in its local attribute array, |
| // then in the ancestor chain (cascading/fallback) - and return the first value encountered. |
| // - the lookup is based on character index relative to the text content subtree |
| // (i.e. the index crosses chunk boundaries) |
| // |
| // [1] https://www.w3.org/TR/SVG11/text.html#TSpanElementXAttribute |
| class ScopedPosResolver { |
| public: |
| ScopedPosResolver(const SkSVGTextContainer&, const SkSVGLengthContext&, SkSVGTextContext*, |
| size_t); |
| |
| ScopedPosResolver(const SkSVGTextContainer&, const SkSVGLengthContext&, SkSVGTextContext*); |
| |
| ~ScopedPosResolver(); |
| |
| PosAttrs resolve(size_t charIndex) const; |
| |
| private: |
| SkSVGTextContext* fTextContext; |
| const ScopedPosResolver* fParent; // parent resolver (fallback) |
| const size_t fCharIndexOffset; // start index for the current resolver |
| const std::vector<float> fX, |
| fY, |
| fDx, |
| fDy; |
| const std::vector<float>& fRotate; |
| |
| // cache for the last known index with explicit positioning |
| mutable size_t fLastPosIndex = std::numeric_limits<size_t>::max(); |
| |
| }; |
| |
| SkSVGTextContext(const SkSVGRenderContext&, |
| const ShapedTextCallback&, |
| const SkSVGTextPath* = nullptr); |
| ~SkSVGTextContext() override; |
| |
| // Shape and queue codepoints for final alignment. |
| void shapeFragment(const SkString&, const SkSVGRenderContext&, SkSVGXmlSpace); |
| |
| // Perform final adjustments and push shaped blobs to the callback. |
| void flushChunk(const SkSVGRenderContext& ctx); |
| |
| const ShapedTextCallback& getCallback() const { return fCallback; } |
| |
| private: |
| struct PositionAdjustment { |
| SkVector offset; |
| float rotation; |
| }; |
| |
| struct ShapeBuffer { |
| SkSTArray<128, char , true> fUtf8; |
| // per-utf8-char cumulative pos adjustments |
| SkSTArray<128, PositionAdjustment, true> fUtf8PosAdjust; |
| |
| void reserve(size_t size) { |
| fUtf8.reserve_back(SkToInt(size)); |
| fUtf8PosAdjust.reserve_back(SkToInt(size)); |
| } |
| |
| void reset() { |
| fUtf8.reset(); |
| fUtf8PosAdjust.reset(); |
| } |
| |
| void append(SkUnichar, PositionAdjustment); |
| }; |
| |
| struct RunRec { |
| SkFont font; |
| std::unique_ptr<SkPaint> fillPaint, |
| strokePaint; |
| std::unique_ptr<SkGlyphID[]> glyphs; // filled by SkShaper |
| std::unique_ptr<SkPoint[]> glyphPos; // filled by SkShaper |
| std::unique_ptr<PositionAdjustment[]> glyhPosAdjust; // deferred positioning adjustments |
| size_t glyphCount; |
| SkVector advance; |
| }; |
| |
| // Caches path information to accelerate position lookups. |
| class PathData { |
| public: |
| PathData(const SkSVGRenderContext&, const SkSVGTextPath&); |
| |
| SkMatrix getMatrixAt(float offset) const; |
| |
| float length() const { return fLength; } |
| |
| private: |
| std::vector<sk_sp<SkContourMeasure>> fContours; |
| float fLength = 0; // total path length |
| }; |
| |
| void shapePendingBuffer(const SkFont&); |
| |
| SkRSXform computeGlyphXform(SkGlyphID, const SkFont&, const SkPoint& glyph_pos, |
| const PositionAdjustment&) const; |
| |
| // SkShaper callbacks |
| void beginLine() override {} |
| void runInfo(const RunInfo&) override {} |
| void commitRunInfo() override {} |
| Buffer runBuffer(const RunInfo& ri) override; |
| void commitRunBuffer(const RunInfo& ri) override; |
| void commitLine() override {} |
| |
| // http://www.w3.org/TR/SVG11/text.html#TextLayout |
| const SkSVGRenderContext& fRenderContext; // original render context |
| const ShapedTextCallback& fCallback; |
| const std::unique_ptr<SkShaper> fShaper; |
| std::vector<RunRec> fRuns; |
| const ScopedPosResolver* fPosResolver = nullptr; |
| std::unique_ptr<PathData> fPathData; |
| |
| // shaper state |
| ShapeBuffer fShapeBuffer; |
| std::vector<uint32_t> fShapeClusterBuffer; |
| |
| // chunk state |
| SkPoint fChunkPos = {0,0}; // current text chunk position |
| SkVector fChunkAdvance = {0,0}; // cumulative advance |
| float fChunkAlignmentFactor; // current chunk alignment |
| |
| // tracks the global text subtree char index (cross chunks). Used for position resolution. |
| size_t fCurrentCharIndex = 0; |
| |
| // cached for access from SkShaper callbacks. |
| SkTLazy<SkPaint> fCurrentFill; |
| SkTLazy<SkPaint> fCurrentStroke; |
| |
| bool fPrevCharSpace = true; // WS filter state |
| }; |
| |
| #endif // SkSVGTextPriv_DEFINED |