blob: ab898255eacaf8305bf81924d877458491f18e6c [file] [log] [blame]
/*
* 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:
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