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