1cb93a386Sopenharmony_ci/* 2cb93a386Sopenharmony_ci * Copyright 2019 Google Inc. 3cb93a386Sopenharmony_ci * 4cb93a386Sopenharmony_ci * Use of this source code is governed by a BSD-style license that can be 5cb93a386Sopenharmony_ci * found in the LICENSE file. 6cb93a386Sopenharmony_ci */ 7cb93a386Sopenharmony_ci 8cb93a386Sopenharmony_ci#include "modules/svg/include/SkSVGText.h" 9cb93a386Sopenharmony_ci 10cb93a386Sopenharmony_ci#include <limits> 11cb93a386Sopenharmony_ci 12cb93a386Sopenharmony_ci#include "include/core/SkCanvas.h" 13cb93a386Sopenharmony_ci#include "include/core/SkContourMeasure.h" 14cb93a386Sopenharmony_ci#include "include/core/SkFont.h" 15cb93a386Sopenharmony_ci#include "include/core/SkFontMgr.h" 16cb93a386Sopenharmony_ci#include "include/core/SkFontStyle.h" 17cb93a386Sopenharmony_ci#include "include/core/SkPathBuilder.h" 18cb93a386Sopenharmony_ci#include "include/core/SkRSXform.h" 19cb93a386Sopenharmony_ci#include "include/core/SkString.h" 20cb93a386Sopenharmony_ci#include "modules/skshaper/include/SkShaper.h" 21cb93a386Sopenharmony_ci#include "modules/svg/include/SkSVGRenderContext.h" 22cb93a386Sopenharmony_ci#include "modules/svg/include/SkSVGValue.h" 23cb93a386Sopenharmony_ci#include "modules/svg/src/SkSVGTextPriv.h" 24cb93a386Sopenharmony_ci#include "src/core/SkTextBlobPriv.h" 25cb93a386Sopenharmony_ci#include "src/utils/SkUTF.h" 26cb93a386Sopenharmony_ci 27cb93a386Sopenharmony_cinamespace { 28cb93a386Sopenharmony_ci 29cb93a386Sopenharmony_cistatic SkFont ResolveFont(const SkSVGRenderContext& ctx) { 30cb93a386Sopenharmony_ci auto weight = [](const SkSVGFontWeight& w) { 31cb93a386Sopenharmony_ci switch (w.type()) { 32cb93a386Sopenharmony_ci case SkSVGFontWeight::Type::k100: return SkFontStyle::kThin_Weight; 33cb93a386Sopenharmony_ci case SkSVGFontWeight::Type::k200: return SkFontStyle::kExtraLight_Weight; 34cb93a386Sopenharmony_ci case SkSVGFontWeight::Type::k300: return SkFontStyle::kLight_Weight; 35cb93a386Sopenharmony_ci case SkSVGFontWeight::Type::k400: return SkFontStyle::kNormal_Weight; 36cb93a386Sopenharmony_ci case SkSVGFontWeight::Type::k500: return SkFontStyle::kMedium_Weight; 37cb93a386Sopenharmony_ci case SkSVGFontWeight::Type::k600: return SkFontStyle::kSemiBold_Weight; 38cb93a386Sopenharmony_ci case SkSVGFontWeight::Type::k700: return SkFontStyle::kBold_Weight; 39cb93a386Sopenharmony_ci case SkSVGFontWeight::Type::k800: return SkFontStyle::kExtraBold_Weight; 40cb93a386Sopenharmony_ci case SkSVGFontWeight::Type::k900: return SkFontStyle::kBlack_Weight; 41cb93a386Sopenharmony_ci case SkSVGFontWeight::Type::kNormal: return SkFontStyle::kNormal_Weight; 42cb93a386Sopenharmony_ci case SkSVGFontWeight::Type::kBold: return SkFontStyle::kBold_Weight; 43cb93a386Sopenharmony_ci case SkSVGFontWeight::Type::kBolder: return SkFontStyle::kExtraBold_Weight; 44cb93a386Sopenharmony_ci case SkSVGFontWeight::Type::kLighter: return SkFontStyle::kLight_Weight; 45cb93a386Sopenharmony_ci case SkSVGFontWeight::Type::kInherit: { 46cb93a386Sopenharmony_ci SkASSERT(false); 47cb93a386Sopenharmony_ci return SkFontStyle::kNormal_Weight; 48cb93a386Sopenharmony_ci } 49cb93a386Sopenharmony_ci } 50cb93a386Sopenharmony_ci SkUNREACHABLE; 51cb93a386Sopenharmony_ci }; 52cb93a386Sopenharmony_ci 53cb93a386Sopenharmony_ci auto slant = [](const SkSVGFontStyle& s) { 54cb93a386Sopenharmony_ci switch (s.type()) { 55cb93a386Sopenharmony_ci case SkSVGFontStyle::Type::kNormal: return SkFontStyle::kUpright_Slant; 56cb93a386Sopenharmony_ci case SkSVGFontStyle::Type::kItalic: return SkFontStyle::kItalic_Slant; 57cb93a386Sopenharmony_ci case SkSVGFontStyle::Type::kOblique: return SkFontStyle::kOblique_Slant; 58cb93a386Sopenharmony_ci case SkSVGFontStyle::Type::kInherit: { 59cb93a386Sopenharmony_ci SkASSERT(false); 60cb93a386Sopenharmony_ci return SkFontStyle::kUpright_Slant; 61cb93a386Sopenharmony_ci } 62cb93a386Sopenharmony_ci } 63cb93a386Sopenharmony_ci SkUNREACHABLE; 64cb93a386Sopenharmony_ci }; 65cb93a386Sopenharmony_ci 66cb93a386Sopenharmony_ci const auto& family = ctx.presentationContext().fInherited.fFontFamily->family(); 67cb93a386Sopenharmony_ci const SkFontStyle style(weight(*ctx.presentationContext().fInherited.fFontWeight), 68cb93a386Sopenharmony_ci SkFontStyle::kNormal_Width, 69cb93a386Sopenharmony_ci slant(*ctx.presentationContext().fInherited.fFontStyle)); 70cb93a386Sopenharmony_ci 71cb93a386Sopenharmony_ci const auto size = 72cb93a386Sopenharmony_ci ctx.lengthContext().resolve(ctx.presentationContext().fInherited.fFontSize->size(), 73cb93a386Sopenharmony_ci SkSVGLengthContext::LengthType::kVertical); 74cb93a386Sopenharmony_ci 75cb93a386Sopenharmony_ci // TODO: we likely want matchFamilyStyle here, but switching away from legacyMakeTypeface 76cb93a386Sopenharmony_ci // changes all the results when using the default fontmgr. 77cb93a386Sopenharmony_ci auto tf = ctx.fontMgr()->legacyMakeTypeface(family.c_str(), style); 78cb93a386Sopenharmony_ci 79cb93a386Sopenharmony_ci SkFont font(std::move(tf), size); 80cb93a386Sopenharmony_ci font.setHinting(SkFontHinting::kNone); 81cb93a386Sopenharmony_ci font.setSubpixel(true); 82cb93a386Sopenharmony_ci font.setLinearMetrics(true); 83cb93a386Sopenharmony_ci font.setBaselineSnap(false); 84cb93a386Sopenharmony_ci font.setEdging(SkFont::Edging::kAntiAlias); 85cb93a386Sopenharmony_ci 86cb93a386Sopenharmony_ci return font; 87cb93a386Sopenharmony_ci} 88cb93a386Sopenharmony_ci 89cb93a386Sopenharmony_cistatic std::vector<float> ResolveLengths(const SkSVGLengthContext& lctx, 90cb93a386Sopenharmony_ci const std::vector<SkSVGLength>& lengths, 91cb93a386Sopenharmony_ci SkSVGLengthContext::LengthType lt) { 92cb93a386Sopenharmony_ci std::vector<float> resolved; 93cb93a386Sopenharmony_ci resolved.reserve(lengths.size()); 94cb93a386Sopenharmony_ci 95cb93a386Sopenharmony_ci for (const auto& l : lengths) { 96cb93a386Sopenharmony_ci resolved.push_back(lctx.resolve(l, lt)); 97cb93a386Sopenharmony_ci } 98cb93a386Sopenharmony_ci 99cb93a386Sopenharmony_ci return resolved; 100cb93a386Sopenharmony_ci} 101cb93a386Sopenharmony_ci 102cb93a386Sopenharmony_cistatic float ComputeAlignmentFactor(const SkSVGPresentationContext& pctx) { 103cb93a386Sopenharmony_ci switch (pctx.fInherited.fTextAnchor->type()) { 104cb93a386Sopenharmony_ci case SkSVGTextAnchor::Type::kStart : return 0.0f; 105cb93a386Sopenharmony_ci case SkSVGTextAnchor::Type::kMiddle: return -0.5f; 106cb93a386Sopenharmony_ci case SkSVGTextAnchor::Type::kEnd : return -1.0f; 107cb93a386Sopenharmony_ci case SkSVGTextAnchor::Type::kInherit: 108cb93a386Sopenharmony_ci SkASSERT(false); 109cb93a386Sopenharmony_ci return 0.0f; 110cb93a386Sopenharmony_ci } 111cb93a386Sopenharmony_ci SkUNREACHABLE; 112cb93a386Sopenharmony_ci} 113cb93a386Sopenharmony_ci 114cb93a386Sopenharmony_ci} // namespace 115cb93a386Sopenharmony_ci 116cb93a386Sopenharmony_ciSkSVGTextContext::ScopedPosResolver::ScopedPosResolver(const SkSVGTextContainer& txt, 117cb93a386Sopenharmony_ci const SkSVGLengthContext& lctx, 118cb93a386Sopenharmony_ci SkSVGTextContext* tctx, 119cb93a386Sopenharmony_ci size_t charIndexOffset) 120cb93a386Sopenharmony_ci : fTextContext(tctx) 121cb93a386Sopenharmony_ci , fParent(tctx->fPosResolver) 122cb93a386Sopenharmony_ci , fCharIndexOffset(charIndexOffset) 123cb93a386Sopenharmony_ci , fX(ResolveLengths(lctx, txt.getX(), SkSVGLengthContext::LengthType::kHorizontal)) 124cb93a386Sopenharmony_ci , fY(ResolveLengths(lctx, txt.getY(), SkSVGLengthContext::LengthType::kVertical)) 125cb93a386Sopenharmony_ci , fDx(ResolveLengths(lctx, txt.getDx(), SkSVGLengthContext::LengthType::kHorizontal)) 126cb93a386Sopenharmony_ci , fDy(ResolveLengths(lctx, txt.getDy(), SkSVGLengthContext::LengthType::kVertical)) 127cb93a386Sopenharmony_ci , fRotate(txt.getRotate()) 128cb93a386Sopenharmony_ci{ 129cb93a386Sopenharmony_ci fTextContext->fPosResolver = this; 130cb93a386Sopenharmony_ci} 131cb93a386Sopenharmony_ci 132cb93a386Sopenharmony_ciSkSVGTextContext::ScopedPosResolver::ScopedPosResolver(const SkSVGTextContainer& txt, 133cb93a386Sopenharmony_ci const SkSVGLengthContext& lctx, 134cb93a386Sopenharmony_ci SkSVGTextContext* tctx) 135cb93a386Sopenharmony_ci : ScopedPosResolver(txt, lctx, tctx, tctx->fCurrentCharIndex) {} 136cb93a386Sopenharmony_ci 137cb93a386Sopenharmony_ciSkSVGTextContext::ScopedPosResolver::~ScopedPosResolver() { 138cb93a386Sopenharmony_ci fTextContext->fPosResolver = fParent; 139cb93a386Sopenharmony_ci} 140cb93a386Sopenharmony_ci 141cb93a386Sopenharmony_ciSkSVGTextContext::PosAttrs SkSVGTextContext::ScopedPosResolver::resolve(size_t charIndex) const { 142cb93a386Sopenharmony_ci PosAttrs attrs; 143cb93a386Sopenharmony_ci 144cb93a386Sopenharmony_ci if (charIndex < fLastPosIndex) { 145cb93a386Sopenharmony_ci SkASSERT(charIndex >= fCharIndexOffset); 146cb93a386Sopenharmony_ci const auto localCharIndex = charIndex - fCharIndexOffset; 147cb93a386Sopenharmony_ci 148cb93a386Sopenharmony_ci const auto hasAllLocal = localCharIndex < fX.size() && 149cb93a386Sopenharmony_ci localCharIndex < fY.size() && 150cb93a386Sopenharmony_ci localCharIndex < fDx.size() && 151cb93a386Sopenharmony_ci localCharIndex < fDy.size() && 152cb93a386Sopenharmony_ci localCharIndex < fRotate.size(); 153cb93a386Sopenharmony_ci if (!hasAllLocal && fParent) { 154cb93a386Sopenharmony_ci attrs = fParent->resolve(charIndex); 155cb93a386Sopenharmony_ci } 156cb93a386Sopenharmony_ci 157cb93a386Sopenharmony_ci if (localCharIndex < fX.size()) { 158cb93a386Sopenharmony_ci attrs[PosAttrs::kX] = fX[localCharIndex]; 159cb93a386Sopenharmony_ci } 160cb93a386Sopenharmony_ci if (localCharIndex < fY.size()) { 161cb93a386Sopenharmony_ci attrs[PosAttrs::kY] = fY[localCharIndex]; 162cb93a386Sopenharmony_ci } 163cb93a386Sopenharmony_ci if (localCharIndex < fDx.size()) { 164cb93a386Sopenharmony_ci attrs[PosAttrs::kDx] = fDx[localCharIndex]; 165cb93a386Sopenharmony_ci } 166cb93a386Sopenharmony_ci if (localCharIndex < fDy.size()) { 167cb93a386Sopenharmony_ci attrs[PosAttrs::kDy] = fDy[localCharIndex]; 168cb93a386Sopenharmony_ci } 169cb93a386Sopenharmony_ci 170cb93a386Sopenharmony_ci // Rotation semantics are interestingly different [1]: 171cb93a386Sopenharmony_ci // 172cb93a386Sopenharmony_ci // - values are not cumulative 173cb93a386Sopenharmony_ci // - if explicit values are present at any level in the ancestor chain, those take 174cb93a386Sopenharmony_ci // precedence (closest ancestor) 175cb93a386Sopenharmony_ci // - last specified value applies to all remaining chars (closest ancestor) 176cb93a386Sopenharmony_ci // - these rules apply at node scope (not chunk scope) 177cb93a386Sopenharmony_ci // 178cb93a386Sopenharmony_ci // This means we need to discriminate between explicit rotation (rotate value provided for 179cb93a386Sopenharmony_ci // current char) and implicit rotation (ancestor has some values - but not for the requested 180cb93a386Sopenharmony_ci // char - we use the last specified value). 181cb93a386Sopenharmony_ci // 182cb93a386Sopenharmony_ci // [1] https://www.w3.org/TR/SVG11/text.html#TSpanElementRotateAttribute 183cb93a386Sopenharmony_ci if (!fRotate.empty()) { 184cb93a386Sopenharmony_ci if (localCharIndex < fRotate.size()) { 185cb93a386Sopenharmony_ci // Explicit rotation value overrides anything in the ancestor chain. 186cb93a386Sopenharmony_ci attrs[PosAttrs::kRotate] = fRotate[localCharIndex]; 187cb93a386Sopenharmony_ci attrs.setImplicitRotate(false); 188cb93a386Sopenharmony_ci } else if (!attrs.has(PosAttrs::kRotate) || attrs.isImplicitRotate()){ 189cb93a386Sopenharmony_ci // Local implicit rotation (last specified value) overrides ancestor implicit 190cb93a386Sopenharmony_ci // rotation. 191cb93a386Sopenharmony_ci attrs[PosAttrs::kRotate] = fRotate.back(); 192cb93a386Sopenharmony_ci attrs.setImplicitRotate(true); 193cb93a386Sopenharmony_ci } 194cb93a386Sopenharmony_ci } 195cb93a386Sopenharmony_ci 196cb93a386Sopenharmony_ci if (!attrs.hasAny()) { 197cb93a386Sopenharmony_ci // Once we stop producing explicit position data, there is no reason to 198cb93a386Sopenharmony_ci // continue trying for higher indices. We can suppress future lookups. 199cb93a386Sopenharmony_ci fLastPosIndex = charIndex; 200cb93a386Sopenharmony_ci } 201cb93a386Sopenharmony_ci } 202cb93a386Sopenharmony_ci 203cb93a386Sopenharmony_ci return attrs; 204cb93a386Sopenharmony_ci} 205cb93a386Sopenharmony_ci 206cb93a386Sopenharmony_civoid SkSVGTextContext::ShapeBuffer::append(SkUnichar ch, PositionAdjustment pos) { 207cb93a386Sopenharmony_ci // relative pos adjustments are cumulative 208cb93a386Sopenharmony_ci if (!fUtf8PosAdjust.empty()) { 209cb93a386Sopenharmony_ci pos.offset += fUtf8PosAdjust.back().offset; 210cb93a386Sopenharmony_ci } 211cb93a386Sopenharmony_ci 212cb93a386Sopenharmony_ci char utf8_buf[SkUTF::kMaxBytesInUTF8Sequence]; 213cb93a386Sopenharmony_ci const auto utf8_len = SkToInt(SkUTF::ToUTF8(ch, utf8_buf)); 214cb93a386Sopenharmony_ci fUtf8 .push_back_n(utf8_len, utf8_buf); 215cb93a386Sopenharmony_ci fUtf8PosAdjust.push_back_n(utf8_len, pos); 216cb93a386Sopenharmony_ci} 217cb93a386Sopenharmony_ci 218cb93a386Sopenharmony_civoid SkSVGTextContext::shapePendingBuffer(const SkFont& font) { 219cb93a386Sopenharmony_ci // TODO: directionality hints? 220cb93a386Sopenharmony_ci const auto LTR = true; 221cb93a386Sopenharmony_ci 222cb93a386Sopenharmony_ci // Initiate shaping: this will generate a series of runs via callbacks. 223cb93a386Sopenharmony_ci fShaper->shape(fShapeBuffer.fUtf8.data(), fShapeBuffer.fUtf8.size(), 224cb93a386Sopenharmony_ci font, LTR, SK_ScalarMax, this); 225cb93a386Sopenharmony_ci fShapeBuffer.reset(); 226cb93a386Sopenharmony_ci} 227cb93a386Sopenharmony_ci 228cb93a386Sopenharmony_ciSkSVGTextContext::SkSVGTextContext(const SkSVGRenderContext& ctx, const ShapedTextCallback& cb, 229cb93a386Sopenharmony_ci const SkSVGTextPath* tpath) 230cb93a386Sopenharmony_ci : fRenderContext(ctx) 231cb93a386Sopenharmony_ci , fCallback(cb) 232cb93a386Sopenharmony_ci , fShaper(SkShaper::Make(ctx.fontMgr())) 233cb93a386Sopenharmony_ci , fChunkAlignmentFactor(ComputeAlignmentFactor(ctx.presentationContext())) 234cb93a386Sopenharmony_ci{ 235cb93a386Sopenharmony_ci if (tpath) { 236cb93a386Sopenharmony_ci fPathData = std::make_unique<PathData>(ctx, *tpath); 237cb93a386Sopenharmony_ci 238cb93a386Sopenharmony_ci // https://www.w3.org/TR/SVG11/text.html#TextPathElementStartOffsetAttribute 239cb93a386Sopenharmony_ci auto resolve_offset = [this](const SkSVGLength& offset) { 240cb93a386Sopenharmony_ci if (offset.unit() != SkSVGLength::Unit::kPercentage) { 241cb93a386Sopenharmony_ci // "If a <length> other than a percentage is given, then the ‘startOffset’ 242cb93a386Sopenharmony_ci // represents a distance along the path measured in the current user coordinate 243cb93a386Sopenharmony_ci // system." 244cb93a386Sopenharmony_ci return fRenderContext.lengthContext() 245cb93a386Sopenharmony_ci .resolve(offset, SkSVGLengthContext::LengthType::kHorizontal); 246cb93a386Sopenharmony_ci } 247cb93a386Sopenharmony_ci 248cb93a386Sopenharmony_ci // "If a percentage is given, then the ‘startOffset’ represents a percentage distance 249cb93a386Sopenharmony_ci // along the entire path." 250cb93a386Sopenharmony_ci return offset.value() * fPathData->length() / 100; 251cb93a386Sopenharmony_ci }; 252cb93a386Sopenharmony_ci 253cb93a386Sopenharmony_ci // startOffset acts as an initial absolute position 254cb93a386Sopenharmony_ci fChunkPos.fX = resolve_offset(tpath->getStartOffset()); 255cb93a386Sopenharmony_ci } 256cb93a386Sopenharmony_ci} 257cb93a386Sopenharmony_ci 258cb93a386Sopenharmony_ciSkSVGTextContext::~SkSVGTextContext() { 259cb93a386Sopenharmony_ci this->flushChunk(fRenderContext); 260cb93a386Sopenharmony_ci} 261cb93a386Sopenharmony_ci 262cb93a386Sopenharmony_civoid SkSVGTextContext::shapeFragment(const SkString& txt, const SkSVGRenderContext& ctx, 263cb93a386Sopenharmony_ci SkSVGXmlSpace xs) { 264cb93a386Sopenharmony_ci // https://www.w3.org/TR/SVG11/text.html#WhiteSpace 265cb93a386Sopenharmony_ci // https://www.w3.org/TR/2008/REC-xml-20081126/#NT-S 266cb93a386Sopenharmony_ci auto filterWSDefault = [this](SkUnichar ch) -> SkUnichar { 267cb93a386Sopenharmony_ci // Remove all newline chars. 268cb93a386Sopenharmony_ci if (ch == '\n') { 269cb93a386Sopenharmony_ci return -1; 270cb93a386Sopenharmony_ci } 271cb93a386Sopenharmony_ci 272cb93a386Sopenharmony_ci // Convert tab chars to space. 273cb93a386Sopenharmony_ci if (ch == '\t') { 274cb93a386Sopenharmony_ci ch = ' '; 275cb93a386Sopenharmony_ci } 276cb93a386Sopenharmony_ci 277cb93a386Sopenharmony_ci // Consolidate contiguous space chars and strip leading spaces (fPrevCharSpace 278cb93a386Sopenharmony_ci // starts off as true). 279cb93a386Sopenharmony_ci if (fPrevCharSpace && ch == ' ') { 280cb93a386Sopenharmony_ci return -1; 281cb93a386Sopenharmony_ci } 282cb93a386Sopenharmony_ci 283cb93a386Sopenharmony_ci // TODO: Strip trailing WS? Doing this across chunks would require another buffering 284cb93a386Sopenharmony_ci // layer. In general, trailing WS should have no rendering side effects. Skipping 285cb93a386Sopenharmony_ci // for now. 286cb93a386Sopenharmony_ci return ch; 287cb93a386Sopenharmony_ci }; 288cb93a386Sopenharmony_ci auto filterWSPreserve = [](SkUnichar ch) -> SkUnichar { 289cb93a386Sopenharmony_ci // Convert newline and tab chars to space. 290cb93a386Sopenharmony_ci if (ch == '\n' || ch == '\t') { 291cb93a386Sopenharmony_ci ch = ' '; 292cb93a386Sopenharmony_ci } 293cb93a386Sopenharmony_ci return ch; 294cb93a386Sopenharmony_ci }; 295cb93a386Sopenharmony_ci 296cb93a386Sopenharmony_ci // Stash paints for access from SkShaper callbacks. 297cb93a386Sopenharmony_ci fCurrentFill = ctx.fillPaint(); 298cb93a386Sopenharmony_ci fCurrentStroke = ctx.strokePaint(); 299cb93a386Sopenharmony_ci 300cb93a386Sopenharmony_ci const auto font = ResolveFont(ctx); 301cb93a386Sopenharmony_ci fShapeBuffer.reserve(txt.size()); 302cb93a386Sopenharmony_ci 303cb93a386Sopenharmony_ci const char* ch_ptr = txt.c_str(); 304cb93a386Sopenharmony_ci const char* ch_end = ch_ptr + txt.size(); 305cb93a386Sopenharmony_ci 306cb93a386Sopenharmony_ci while (ch_ptr < ch_end) { 307cb93a386Sopenharmony_ci auto ch = SkUTF::NextUTF8(&ch_ptr, ch_end); 308cb93a386Sopenharmony_ci ch = (xs == SkSVGXmlSpace::kDefault) 309cb93a386Sopenharmony_ci ? filterWSDefault(ch) 310cb93a386Sopenharmony_ci : filterWSPreserve(ch); 311cb93a386Sopenharmony_ci 312cb93a386Sopenharmony_ci if (ch < 0) { 313cb93a386Sopenharmony_ci // invalid utf or char filtered out 314cb93a386Sopenharmony_ci continue; 315cb93a386Sopenharmony_ci } 316cb93a386Sopenharmony_ci 317cb93a386Sopenharmony_ci SkASSERT(fPosResolver); 318cb93a386Sopenharmony_ci const auto pos = fPosResolver->resolve(fCurrentCharIndex++); 319cb93a386Sopenharmony_ci 320cb93a386Sopenharmony_ci // Absolute position adjustments define a new chunk. 321cb93a386Sopenharmony_ci // (https://www.w3.org/TR/SVG11/text.html#TextLayoutIntroduction) 322cb93a386Sopenharmony_ci if (pos.has(PosAttrs::kX) || pos.has(PosAttrs::kY)) { 323cb93a386Sopenharmony_ci this->shapePendingBuffer(font); 324cb93a386Sopenharmony_ci this->flushChunk(ctx); 325cb93a386Sopenharmony_ci 326cb93a386Sopenharmony_ci // New chunk position. 327cb93a386Sopenharmony_ci if (pos.has(PosAttrs::kX)) { 328cb93a386Sopenharmony_ci fChunkPos.fX = pos[PosAttrs::kX]; 329cb93a386Sopenharmony_ci } 330cb93a386Sopenharmony_ci if (pos.has(PosAttrs::kY)) { 331cb93a386Sopenharmony_ci fChunkPos.fY = pos[PosAttrs::kY]; 332cb93a386Sopenharmony_ci } 333cb93a386Sopenharmony_ci } 334cb93a386Sopenharmony_ci 335cb93a386Sopenharmony_ci fShapeBuffer.append(ch, { 336cb93a386Sopenharmony_ci { 337cb93a386Sopenharmony_ci pos.has(PosAttrs::kDx) ? pos[PosAttrs::kDx] : 0, 338cb93a386Sopenharmony_ci pos.has(PosAttrs::kDy) ? pos[PosAttrs::kDy] : 0, 339cb93a386Sopenharmony_ci }, 340cb93a386Sopenharmony_ci pos.has(PosAttrs::kRotate) ? SkDegreesToRadians(pos[PosAttrs::kRotate]) : 0, 341cb93a386Sopenharmony_ci }); 342cb93a386Sopenharmony_ci 343cb93a386Sopenharmony_ci fPrevCharSpace = (ch == ' '); 344cb93a386Sopenharmony_ci } 345cb93a386Sopenharmony_ci 346cb93a386Sopenharmony_ci this->shapePendingBuffer(font); 347cb93a386Sopenharmony_ci 348cb93a386Sopenharmony_ci // Note: at this point we have shaped and buffered RunRecs for the current fragment. 349cb93a386Sopenharmony_ci // The active text chunk continues until an explicit or implicit flush. 350cb93a386Sopenharmony_ci} 351cb93a386Sopenharmony_ci 352cb93a386Sopenharmony_ciSkSVGTextContext::PathData::PathData(const SkSVGRenderContext& ctx, const SkSVGTextPath& tpath) 353cb93a386Sopenharmony_ci{ 354cb93a386Sopenharmony_ci const auto ref = ctx.findNodeById(tpath.getHref()); 355cb93a386Sopenharmony_ci if (!ref) { 356cb93a386Sopenharmony_ci return; 357cb93a386Sopenharmony_ci } 358cb93a386Sopenharmony_ci 359cb93a386Sopenharmony_ci SkContourMeasureIter cmi(ref->asPath(ctx), false); 360cb93a386Sopenharmony_ci while (sk_sp<SkContourMeasure> contour = cmi.next()) { 361cb93a386Sopenharmony_ci fLength += contour->length(); 362cb93a386Sopenharmony_ci fContours.push_back(std::move(contour)); 363cb93a386Sopenharmony_ci } 364cb93a386Sopenharmony_ci} 365cb93a386Sopenharmony_ci 366cb93a386Sopenharmony_ciSkMatrix SkSVGTextContext::PathData::getMatrixAt(float offset) const { 367cb93a386Sopenharmony_ci if (offset >= 0) { 368cb93a386Sopenharmony_ci for (const auto& contour : fContours) { 369cb93a386Sopenharmony_ci const auto contour_len = contour->length(); 370cb93a386Sopenharmony_ci if (offset < contour_len) { 371cb93a386Sopenharmony_ci SkMatrix m; 372cb93a386Sopenharmony_ci return contour->getMatrix(offset, &m) ? m : SkMatrix::I(); 373cb93a386Sopenharmony_ci } 374cb93a386Sopenharmony_ci offset -= contour_len; 375cb93a386Sopenharmony_ci } 376cb93a386Sopenharmony_ci } 377cb93a386Sopenharmony_ci 378cb93a386Sopenharmony_ci // Quick & dirty way to "skip" rendering of glyphs off path. 379cb93a386Sopenharmony_ci return SkMatrix::Translate(std::numeric_limits<float>::infinity(), 380cb93a386Sopenharmony_ci std::numeric_limits<float>::infinity()); 381cb93a386Sopenharmony_ci} 382cb93a386Sopenharmony_ci 383cb93a386Sopenharmony_ciSkRSXform SkSVGTextContext::computeGlyphXform(SkGlyphID glyph, const SkFont& font, 384cb93a386Sopenharmony_ci const SkPoint& glyph_pos, 385cb93a386Sopenharmony_ci const PositionAdjustment& pos_adjust) const { 386cb93a386Sopenharmony_ci SkPoint pos = fChunkPos + glyph_pos + pos_adjust.offset + fChunkAdvance * fChunkAlignmentFactor; 387cb93a386Sopenharmony_ci if (!fPathData) { 388cb93a386Sopenharmony_ci return SkRSXform::MakeFromRadians(/*scale=*/ 1, pos_adjust.rotation, pos.fX, pos.fY, 0, 0); 389cb93a386Sopenharmony_ci } 390cb93a386Sopenharmony_ci 391cb93a386Sopenharmony_ci // We're in a textPath scope, reposition the glyph on path. 392cb93a386Sopenharmony_ci // (https://www.w3.org/TR/SVG11/text.html#TextpathLayoutRules) 393cb93a386Sopenharmony_ci 394cb93a386Sopenharmony_ci // Path positioning is based on the glyph center (horizontal component). 395cb93a386Sopenharmony_ci float glyph_width; 396cb93a386Sopenharmony_ci font.getWidths(&glyph, 1, &glyph_width); 397cb93a386Sopenharmony_ci auto path_offset = pos.fX + glyph_width * .5f; 398cb93a386Sopenharmony_ci 399cb93a386Sopenharmony_ci // In addition to the path matrix, the final glyph matrix also includes: 400cb93a386Sopenharmony_ci // 401cb93a386Sopenharmony_ci // -- vertical position adjustment "dy" ("dx" is factored into path_offset) 402cb93a386Sopenharmony_ci // -- glyph origin adjustment (undoing the glyph center offset above) 403cb93a386Sopenharmony_ci // -- explicit rotation adjustment (composing with the path glyph rotation) 404cb93a386Sopenharmony_ci const auto m = fPathData->getMatrixAt(path_offset) * 405cb93a386Sopenharmony_ci SkMatrix::Translate(-glyph_width * .5f, pos_adjust.offset.fY) * 406cb93a386Sopenharmony_ci SkMatrix::RotateRad(pos_adjust.rotation); 407cb93a386Sopenharmony_ci 408cb93a386Sopenharmony_ci return SkRSXform::Make(m.getScaleX(), m.getSkewY(), m.getTranslateX(), m.getTranslateY()); 409cb93a386Sopenharmony_ci} 410cb93a386Sopenharmony_ci 411cb93a386Sopenharmony_civoid SkSVGTextContext::flushChunk(const SkSVGRenderContext& ctx) { 412cb93a386Sopenharmony_ci SkTextBlobBuilder blobBuilder; 413cb93a386Sopenharmony_ci 414cb93a386Sopenharmony_ci for (const auto& run : fRuns) { 415cb93a386Sopenharmony_ci const auto& buf = blobBuilder.allocRunRSXform(run.font, SkToInt(run.glyphCount)); 416cb93a386Sopenharmony_ci std::copy(run.glyphs.get(), run.glyphs.get() + run.glyphCount, buf.glyphs); 417cb93a386Sopenharmony_ci for (size_t i = 0; i < run.glyphCount; ++i) { 418cb93a386Sopenharmony_ci buf.xforms()[i] = this->computeGlyphXform(run.glyphs[i], 419cb93a386Sopenharmony_ci run.font, 420cb93a386Sopenharmony_ci run.glyphPos[i], 421cb93a386Sopenharmony_ci run.glyhPosAdjust[i]); 422cb93a386Sopenharmony_ci } 423cb93a386Sopenharmony_ci 424cb93a386Sopenharmony_ci fCallback(ctx, blobBuilder.make(), run.fillPaint.get(), run.strokePaint.get()); 425cb93a386Sopenharmony_ci } 426cb93a386Sopenharmony_ci 427cb93a386Sopenharmony_ci fChunkPos += fChunkAdvance; 428cb93a386Sopenharmony_ci fChunkAdvance = {0,0}; 429cb93a386Sopenharmony_ci fChunkAlignmentFactor = ComputeAlignmentFactor(ctx.presentationContext()); 430cb93a386Sopenharmony_ci 431cb93a386Sopenharmony_ci fRuns.clear(); 432cb93a386Sopenharmony_ci} 433cb93a386Sopenharmony_ci 434cb93a386Sopenharmony_ciSkShaper::RunHandler::Buffer SkSVGTextContext::runBuffer(const RunInfo& ri) { 435cb93a386Sopenharmony_ci SkASSERT(ri.glyphCount); 436cb93a386Sopenharmony_ci 437cb93a386Sopenharmony_ci fRuns.push_back({ 438cb93a386Sopenharmony_ci ri.fFont, 439cb93a386Sopenharmony_ci fCurrentFill.isValid() ? std::make_unique<SkPaint>(*fCurrentFill) : nullptr, 440cb93a386Sopenharmony_ci fCurrentStroke.isValid() ? std::make_unique<SkPaint>(*fCurrentStroke) : nullptr, 441cb93a386Sopenharmony_ci std::make_unique<SkGlyphID[] >(ri.glyphCount), 442cb93a386Sopenharmony_ci std::make_unique<SkPoint[] >(ri.glyphCount), 443cb93a386Sopenharmony_ci std::make_unique<PositionAdjustment[]>(ri.glyphCount), 444cb93a386Sopenharmony_ci ri.glyphCount, 445cb93a386Sopenharmony_ci ri.fAdvance, 446cb93a386Sopenharmony_ci }); 447cb93a386Sopenharmony_ci 448cb93a386Sopenharmony_ci // Ensure sufficient space to temporarily fetch cluster information. 449cb93a386Sopenharmony_ci fShapeClusterBuffer.resize(std::max(fShapeClusterBuffer.size(), ri.glyphCount)); 450cb93a386Sopenharmony_ci 451cb93a386Sopenharmony_ci return { 452cb93a386Sopenharmony_ci fRuns.back().glyphs.get(), 453cb93a386Sopenharmony_ci fRuns.back().glyphPos.get(), 454cb93a386Sopenharmony_ci nullptr, 455cb93a386Sopenharmony_ci fShapeClusterBuffer.data(), 456cb93a386Sopenharmony_ci fChunkAdvance, 457cb93a386Sopenharmony_ci }; 458cb93a386Sopenharmony_ci} 459cb93a386Sopenharmony_ci 460cb93a386Sopenharmony_civoid SkSVGTextContext::commitRunBuffer(const RunInfo& ri) { 461cb93a386Sopenharmony_ci const auto& current_run = fRuns.back(); 462cb93a386Sopenharmony_ci 463cb93a386Sopenharmony_ci // stash position adjustments 464cb93a386Sopenharmony_ci for (size_t i = 0; i < ri.glyphCount; ++i) { 465cb93a386Sopenharmony_ci const auto utf8_index = fShapeClusterBuffer[i]; 466cb93a386Sopenharmony_ci current_run.glyhPosAdjust[i] = fShapeBuffer.fUtf8PosAdjust[SkToInt(utf8_index)]; 467cb93a386Sopenharmony_ci } 468cb93a386Sopenharmony_ci 469cb93a386Sopenharmony_ci // Offset adjustments are cumulative - we only need to advance the current chunk 470cb93a386Sopenharmony_ci // with the last value. 471cb93a386Sopenharmony_ci fChunkAdvance += ri.fAdvance + fShapeBuffer.fUtf8PosAdjust.back().offset; 472cb93a386Sopenharmony_ci} 473cb93a386Sopenharmony_ci 474cb93a386Sopenharmony_civoid SkSVGTextFragment::renderText(const SkSVGRenderContext& ctx, SkSVGTextContext* tctx, 475cb93a386Sopenharmony_ci SkSVGXmlSpace xs) const { 476cb93a386Sopenharmony_ci // N.B.: unlike regular elements, text fragments do not establish a new OBB scope -- they 477cb93a386Sopenharmony_ci // always defer to the root <text> element for OBB resolution. 478cb93a386Sopenharmony_ci SkSVGRenderContext localContext(ctx); 479cb93a386Sopenharmony_ci 480cb93a386Sopenharmony_ci if (this->onPrepareToRender(&localContext)) { 481cb93a386Sopenharmony_ci this->onShapeText(localContext, tctx, xs); 482cb93a386Sopenharmony_ci } 483cb93a386Sopenharmony_ci} 484cb93a386Sopenharmony_ci 485cb93a386Sopenharmony_ciSkPath SkSVGTextFragment::onAsPath(const SkSVGRenderContext&) const { 486cb93a386Sopenharmony_ci // TODO 487cb93a386Sopenharmony_ci return SkPath(); 488cb93a386Sopenharmony_ci} 489cb93a386Sopenharmony_ci 490cb93a386Sopenharmony_civoid SkSVGTextContainer::appendChild(sk_sp<SkSVGNode> child) { 491cb93a386Sopenharmony_ci // Only allow text content child nodes. 492cb93a386Sopenharmony_ci switch (child->tag()) { 493cb93a386Sopenharmony_ci case SkSVGTag::kTextLiteral: 494cb93a386Sopenharmony_ci case SkSVGTag::kTextPath: 495cb93a386Sopenharmony_ci case SkSVGTag::kTSpan: 496cb93a386Sopenharmony_ci fChildren.push_back( 497cb93a386Sopenharmony_ci sk_sp<SkSVGTextFragment>(static_cast<SkSVGTextFragment*>(child.release()))); 498cb93a386Sopenharmony_ci break; 499cb93a386Sopenharmony_ci default: 500cb93a386Sopenharmony_ci break; 501cb93a386Sopenharmony_ci } 502cb93a386Sopenharmony_ci} 503cb93a386Sopenharmony_ci 504cb93a386Sopenharmony_ci 505cb93a386Sopenharmony_cistd::vector<sk_sp<SkSVGNode>> SkSVGTextContainer::getChild() { 506cb93a386Sopenharmony_ci std::vector<sk_sp<SkSVGNode>> res; 507cb93a386Sopenharmony_ci res.assign(fChildren.begin(),fChildren.end()); 508cb93a386Sopenharmony_ci return res; 509cb93a386Sopenharmony_ci} 510cb93a386Sopenharmony_ci 511cb93a386Sopenharmony_civoid SkSVGTextContainer::onShapeText(const SkSVGRenderContext& ctx, SkSVGTextContext* tctx, 512cb93a386Sopenharmony_ci SkSVGXmlSpace) const { 513cb93a386Sopenharmony_ci SkASSERT(tctx); 514cb93a386Sopenharmony_ci 515cb93a386Sopenharmony_ci const SkSVGTextContext::ScopedPosResolver resolver(*this, ctx.lengthContext(), tctx); 516cb93a386Sopenharmony_ci 517cb93a386Sopenharmony_ci for (const auto& frag : fChildren) { 518cb93a386Sopenharmony_ci // Containers always override xml:space with the local value. 519cb93a386Sopenharmony_ci frag->renderText(ctx, tctx, this->getXmlSpace()); 520cb93a386Sopenharmony_ci } 521cb93a386Sopenharmony_ci} 522cb93a386Sopenharmony_ci 523cb93a386Sopenharmony_ci// https://www.w3.org/TR/SVG11/text.html#WhiteSpace 524cb93a386Sopenharmony_citemplate <> 525cb93a386Sopenharmony_cibool SkSVGAttributeParser::parse(SkSVGXmlSpace* xs) { 526cb93a386Sopenharmony_ci static constexpr std::tuple<const char*, SkSVGXmlSpace> gXmlSpaceMap[] = { 527cb93a386Sopenharmony_ci {"default" , SkSVGXmlSpace::kDefault }, 528cb93a386Sopenharmony_ci {"preserve", SkSVGXmlSpace::kPreserve}, 529cb93a386Sopenharmony_ci }; 530cb93a386Sopenharmony_ci 531cb93a386Sopenharmony_ci return this->parseEnumMap(gXmlSpaceMap, xs) && this->parseEOSToken(); 532cb93a386Sopenharmony_ci} 533cb93a386Sopenharmony_ci 534cb93a386Sopenharmony_cibool SkSVGTextContainer::parseAndSetAttribute(const char* name, const char* value) { 535cb93a386Sopenharmony_ci return INHERITED::parseAndSetAttribute(name, value) || 536cb93a386Sopenharmony_ci this->setX(SkSVGAttributeParser::parse<std::vector<SkSVGLength>>("x", name, value)) || 537cb93a386Sopenharmony_ci this->setY(SkSVGAttributeParser::parse<std::vector<SkSVGLength>>("y", name, value)) || 538cb93a386Sopenharmony_ci this->setDx(SkSVGAttributeParser::parse<std::vector<SkSVGLength>>("dx", name, value)) || 539cb93a386Sopenharmony_ci this->setDy(SkSVGAttributeParser::parse<std::vector<SkSVGLength>>("dy", name, value)) || 540cb93a386Sopenharmony_ci this->setRotate(SkSVGAttributeParser::parse<std::vector<SkSVGNumberType>>("rotate", 541cb93a386Sopenharmony_ci name, 542cb93a386Sopenharmony_ci value)) || 543cb93a386Sopenharmony_ci this->setXmlSpace(SkSVGAttributeParser::parse<SkSVGXmlSpace>("xml:space", name, value)); 544cb93a386Sopenharmony_ci} 545cb93a386Sopenharmony_ci 546cb93a386Sopenharmony_civoid SkSVGTextLiteral::onShapeText(const SkSVGRenderContext& ctx, SkSVGTextContext* tctx, 547cb93a386Sopenharmony_ci SkSVGXmlSpace xs) const { 548cb93a386Sopenharmony_ci SkASSERT(tctx); 549cb93a386Sopenharmony_ci 550cb93a386Sopenharmony_ci tctx->shapeFragment(this->getText(), ctx, xs); 551cb93a386Sopenharmony_ci} 552cb93a386Sopenharmony_ci 553cb93a386Sopenharmony_civoid SkSVGText::onRender(const SkSVGRenderContext& ctx) const { 554cb93a386Sopenharmony_ci const SkSVGTextContext::ShapedTextCallback render_text = [](const SkSVGRenderContext& ctx, 555cb93a386Sopenharmony_ci const sk_sp<SkTextBlob>& blob, 556cb93a386Sopenharmony_ci const SkPaint* fill, 557cb93a386Sopenharmony_ci const SkPaint* stroke) { 558cb93a386Sopenharmony_ci if (fill) { 559cb93a386Sopenharmony_ci ctx.canvas()->drawTextBlob(blob, 0, 0, *fill); 560cb93a386Sopenharmony_ci } 561cb93a386Sopenharmony_ci if (stroke) { 562cb93a386Sopenharmony_ci ctx.canvas()->drawTextBlob(blob, 0, 0, *stroke); 563cb93a386Sopenharmony_ci } 564cb93a386Sopenharmony_ci }; 565cb93a386Sopenharmony_ci 566cb93a386Sopenharmony_ci // Root <text> nodes establish a text layout context. 567cb93a386Sopenharmony_ci SkSVGTextContext tctx(ctx, render_text); 568cb93a386Sopenharmony_ci 569cb93a386Sopenharmony_ci this->onShapeText(ctx, &tctx, this->getXmlSpace()); 570cb93a386Sopenharmony_ci} 571cb93a386Sopenharmony_ci 572cb93a386Sopenharmony_ciSkRect SkSVGText::onObjectBoundingBox(const SkSVGRenderContext& ctx) const { 573cb93a386Sopenharmony_ci SkRect bounds = SkRect::MakeEmpty(); 574cb93a386Sopenharmony_ci 575cb93a386Sopenharmony_ci const SkSVGTextContext::ShapedTextCallback compute_bounds = 576cb93a386Sopenharmony_ci [&bounds](const SkSVGRenderContext& ctx, const sk_sp<SkTextBlob>& blob, const SkPaint*, 577cb93a386Sopenharmony_ci const SkPaint*) { 578cb93a386Sopenharmony_ci if (!blob) { 579cb93a386Sopenharmony_ci return; 580cb93a386Sopenharmony_ci } 581cb93a386Sopenharmony_ci 582cb93a386Sopenharmony_ci SkAutoSTArray<64, SkRect> glyphBounds; 583cb93a386Sopenharmony_ci 584cb93a386Sopenharmony_ci for (SkTextBlobRunIterator it(blob.get()); !it.done(); it.next()) { 585cb93a386Sopenharmony_ci glyphBounds.reset(SkToInt(it.glyphCount())); 586cb93a386Sopenharmony_ci it.font().getBounds(it.glyphs(), it.glyphCount(), glyphBounds.get(), nullptr); 587cb93a386Sopenharmony_ci 588cb93a386Sopenharmony_ci SkASSERT(it.positioning() == SkTextBlobRunIterator::kRSXform_Positioning); 589cb93a386Sopenharmony_ci SkMatrix m; 590cb93a386Sopenharmony_ci for (uint32_t i = 0; i < it.glyphCount(); ++i) { 591cb93a386Sopenharmony_ci m.setRSXform(it.xforms()[i]); 592cb93a386Sopenharmony_ci bounds.join(m.mapRect(glyphBounds[i])); 593cb93a386Sopenharmony_ci } 594cb93a386Sopenharmony_ci } 595cb93a386Sopenharmony_ci }; 596cb93a386Sopenharmony_ci 597cb93a386Sopenharmony_ci { 598cb93a386Sopenharmony_ci SkSVGTextContext tctx(ctx, compute_bounds); 599cb93a386Sopenharmony_ci this->onShapeText(ctx, &tctx, this->getXmlSpace()); 600cb93a386Sopenharmony_ci } 601cb93a386Sopenharmony_ci 602cb93a386Sopenharmony_ci return bounds; 603cb93a386Sopenharmony_ci} 604cb93a386Sopenharmony_ci 605cb93a386Sopenharmony_ciSkPath SkSVGText::onAsPath(const SkSVGRenderContext& ctx) const { 606cb93a386Sopenharmony_ci SkPathBuilder builder; 607cb93a386Sopenharmony_ci 608cb93a386Sopenharmony_ci const SkSVGTextContext::ShapedTextCallback as_path = 609cb93a386Sopenharmony_ci [&builder](const SkSVGRenderContext& ctx, const sk_sp<SkTextBlob>& blob, const SkPaint*, 610cb93a386Sopenharmony_ci const SkPaint*) { 611cb93a386Sopenharmony_ci if (!blob) { 612cb93a386Sopenharmony_ci return; 613cb93a386Sopenharmony_ci } 614cb93a386Sopenharmony_ci 615cb93a386Sopenharmony_ci for (SkTextBlobRunIterator it(blob.get()); !it.done(); it.next()) { 616cb93a386Sopenharmony_ci struct GetPathsCtx { 617cb93a386Sopenharmony_ci SkPathBuilder& builder; 618cb93a386Sopenharmony_ci const SkRSXform* xform; 619cb93a386Sopenharmony_ci } get_paths_ctx {builder, it.xforms()}; 620cb93a386Sopenharmony_ci 621cb93a386Sopenharmony_ci it.font().getPaths(it.glyphs(), it.glyphCount(), [](const SkPath* path, 622cb93a386Sopenharmony_ci const SkMatrix& matrix, 623cb93a386Sopenharmony_ci void* raw_ctx) { 624cb93a386Sopenharmony_ci auto* get_paths_ctx = static_cast<GetPathsCtx*>(raw_ctx); 625cb93a386Sopenharmony_ci const auto& glyph_rsx = *get_paths_ctx->xform++; 626cb93a386Sopenharmony_ci 627cb93a386Sopenharmony_ci if (!path) { 628cb93a386Sopenharmony_ci return; 629cb93a386Sopenharmony_ci } 630cb93a386Sopenharmony_ci 631cb93a386Sopenharmony_ci SkMatrix glyph_matrix; 632cb93a386Sopenharmony_ci glyph_matrix.setRSXform(glyph_rsx); 633cb93a386Sopenharmony_ci glyph_matrix.preConcat(matrix); 634cb93a386Sopenharmony_ci 635cb93a386Sopenharmony_ci get_paths_ctx->builder.addPath(path->makeTransform(glyph_matrix)); 636cb93a386Sopenharmony_ci }, &get_paths_ctx); 637cb93a386Sopenharmony_ci } 638cb93a386Sopenharmony_ci }; 639cb93a386Sopenharmony_ci 640cb93a386Sopenharmony_ci { 641cb93a386Sopenharmony_ci SkSVGTextContext tctx(ctx, as_path); 642cb93a386Sopenharmony_ci this->onShapeText(ctx, &tctx, this->getXmlSpace()); 643cb93a386Sopenharmony_ci } 644cb93a386Sopenharmony_ci 645cb93a386Sopenharmony_ci auto path = builder.detach(); 646cb93a386Sopenharmony_ci this->mapToParent(&path); 647cb93a386Sopenharmony_ci 648cb93a386Sopenharmony_ci return path; 649cb93a386Sopenharmony_ci} 650cb93a386Sopenharmony_ci 651cb93a386Sopenharmony_civoid SkSVGTextPath::onShapeText(const SkSVGRenderContext& ctx, SkSVGTextContext* parent_tctx, 652cb93a386Sopenharmony_ci SkSVGXmlSpace xs) const { 653cb93a386Sopenharmony_ci SkASSERT(parent_tctx); 654cb93a386Sopenharmony_ci 655cb93a386Sopenharmony_ci // textPath nodes establish a new text layout context. 656cb93a386Sopenharmony_ci SkSVGTextContext tctx(ctx, parent_tctx->getCallback(), this); 657cb93a386Sopenharmony_ci 658cb93a386Sopenharmony_ci this->INHERITED::onShapeText(ctx, &tctx, xs); 659cb93a386Sopenharmony_ci} 660cb93a386Sopenharmony_ci 661cb93a386Sopenharmony_cibool SkSVGTextPath::parseAndSetAttribute(const char* name, const char* value) { 662cb93a386Sopenharmony_ci return INHERITED::parseAndSetAttribute(name, value) || 663cb93a386Sopenharmony_ci this->setHref(SkSVGAttributeParser::parse<SkSVGIRI>("xlink:href", name, value)) || 664cb93a386Sopenharmony_ci this->setStartOffset(SkSVGAttributeParser::parse<SkSVGLength>("startOffset", name, value)); 665cb93a386Sopenharmony_ci} 666