53class InvalidationController;
57#define SHOW_LAYOUT_BOXES 0
65 explicit GlyphTextNode(Shaper::ShapedGlyphs&&
glyphs) : fGlyphs(std::move(
glyphs)) {}
67 ~GlyphTextNode()
override =
default;
69 const Shaper::ShapedGlyphs*
glyphs()
const {
return &fGlyphs; }
77 fGlyphs.draw(canvas, {0,0},
paint);
80 void onClip(
SkCanvas* canvas,
bool antiAlias)
const override {
81 canvas->
clipPath(this->asPath(), antiAlias);
84 bool onContains(
const SkPoint&
p)
const override {
85 return this->asPath().contains(
p.x(),
p.y());
88 SkPath onAsPath()
const override {
94 const Shaper::ShapedGlyphs fGlyphs;
112 : fDecorator(
std::move(decorator))
119 fFragCount = recs.size();
122 fFragInfo = std::make_unique<FragmentInfo[]>(recs.size());
124 for (
size_t i = 0;
i < recs.size(); ++
i) {
125 const auto& rec = recs[
i];
126 fFragInfo[
i] = {rec.fGlyphs, rec.fMatrixNode, rec.fAdvance};
130 fDecoratorInfo = std::make_unique<GlyphDecorator::GlyphInfo[]>(recs.size());
134 const auto child_bounds = INHERITED::onRevalidate(ic, ctm);
136 for (
size_t i = 0;
i < fFragCount; ++
i) {
137 const auto*
glyphs = fFragInfo[
i].fGlyphs;
138 fDecoratorInfo[
i].fBounds =
140 fDecoratorInfo[
i].fMatrix = sksg::TransformPriv::As<SkMatrix>(fFragInfo[
i].fMatrixNode);
142 fDecoratorInfo[
i].fCluster =
glyphs->fClusters.empty() ? 0 :
glyphs->fClusters.front();
143 fDecoratorInfo[
i].fAdvance = fFragInfo[
i].fAdvance;
153 this->INHERITED::onRender(canvas, local_ctx);
155 fDecorator->onDecorate(canvas, {
156 SkSpan(fDecoratorInfo.get(), fFragCount),
162 struct FragmentInfo {
171 std::unique_ptr<FragmentInfo[]> fFragInfo;
172 std::unique_ptr<GlyphDecorator::GlyphInfo[]> fDecoratorInfo;
175 using INHERITED =
Group;
210 if (
fPath != fCurrentPath ||
reverse != fCurrentReversed) {
220 fCurrentMeasure = iter.
next();
221 fCurrentClosed =
path.isLastContourClosed();
223 fCurrentPath =
fPath;
234 return fCurrentMeasure ? fCurrentMeasure->
length() : 0;
241 if (!fCurrentMeasure) {
245 const auto path_len = fCurrentMeasure->
length();
256 if (fCurrentClosed) {
276 pos += tan*(underflow + overflow);
293 bool fCurrentReversed =
false,
294 fCurrentClosed =
false;
353 static constexpr AnchorPointGrouping gGroupingMap[] = {
354 AnchorPointGrouping::kCharacter,
355 AnchorPointGrouping::kWord,
356 AnchorPointGrouping::kLine,
357 AnchorPointGrouping::kAll,
360 ? SkTPin<int>(ParseDefault<int>((*jm)[
"g"], 1), 1,
std::size(gGroupingMap))
364 std::move(custom_glyph_mapper),
369 adapter->bind(*abuilder, jd, adapter->fText.fCurrentValue);
371 adapter->bind(*abuilder, (*jm)[
"a"], adapter->fGroupingAlignment);
376 adapter->fAnimators.reserve(janimators->size());
380 adapter->fHasBlurAnimator |= animator->hasBlur();
381 adapter->fRequiresAnchorPoint |= animator->requiresAnchorPoint();
382 adapter->fRequiresLineAdjustments |= animator->requiresLineAdjustments();
384 adapter->fAnimators.push_back(std::move(animator));
390 const auto attach_path = [&](
const skjson::ObjectValue* jpath) -> std::unique_ptr<PathInfo> {
396 const auto mask_index =
399 if (!jmasks || mask_index >= jmasks->
size()) {
408 auto pinfo = std::make_unique<PathInfo>();
409 adapter->bind(*abuilder, (*mask)[
"pt"], &pinfo->fPath);
410 adapter->bind(*abuilder, (*jpath)[
"f"], &pinfo->fPathFMargin);
411 adapter->bind(*abuilder, (*jpath)[
"l"], &pinfo->fPathLMargin);
412 adapter->bind(*abuilder, (*jpath)[
"p"], &pinfo->fPathPerpendicular);
413 adapter->bind(*abuilder, (*jpath)[
"r"], &pinfo->fPathReverse);
423 adapter->fRequiresAnchorPoint =
true;
428 adapter->fPathInfo = attach_path((*jt)[
"p"]);
438 AnchorPointGrouping apg)
440 , fFontMgr(
std::move(fontmgr))
441 , fCustomGlyphMapper(
std::move(custom_glyph_mapper))
443 , fShapingFactory(
std::move(factory))
444 , fAnchorPointGrouping(apg)
445 , fHasBlurAnimator(
false)
446 , fRequiresAnchorPoint(
false)
447 , fRequiresLineAdjustments(
false) {}
451std::vector<sk_sp<sksg::RenderNode>>
453 std::vector<sk_sp<sksg::RenderNode>> draws;
455 if (fCustomGlyphMapper) {
456 size_t run_offset = 0;
458 for (
size_t i = 0;
i <
run.fSize; ++
i) {
459 const size_t goffset = run_offset +
i;
462 if (
auto gcomp = fCustomGlyphMapper->getGlyphComp(
run.fFont.getTypeface(), gid)) {
466 fText->fTextSize*fTextShapingScale);
473 glyphs.fGlyphIDs.erase(
glyphs.fGlyphIDs.begin() + goffset);
475 glyphs.fGlyphPos.erase(
glyphs.fGlyphPos.begin() + goffset);
476 if (!
glyphs.fClusters.empty()) {
478 glyphs.fClusters.erase(
glyphs.fClusters.begin() + goffset);
484 run_offset +=
run.fSize;
491void TextAdapter::addFragment(Shaper::Fragment& frag,
sksg::Group* container) {
503 rec.fOrigin = frag.fOrigin;
504 rec.fAdvance = frag.fAdvance;
505 rec.fAscent = frag.fAscent;
510 std::vector<sk_sp<sksg::RenderNode>> draws = this->buildGlyphCompNodes(frag.fGlyphs);
513 auto text_node = sk_make_sp<GlyphTextNode>(std::move(frag.fGlyphs));
514 rec.fGlyphs = text_node->glyphs();
516 draws.reserve(draws.size() +
517 static_cast<size_t>(fText->fHasFill) +
518 static_cast<size_t>(fText->fHasStroke));
520 SkASSERT(fText->fHasFill || fText->fHasStroke);
522 auto add_fill = [&]() {
523 if (fText->fHasFill) {
525 rec.fFillColorNode->setAntiAlias(
true);
529 auto add_stroke = [&] {
530 if (fText->fHasStroke) {
532 rec.fStrokeColorNode->setAntiAlias(
true);
534 rec.fStrokeColorNode->setStrokeWidth(fText->fStrokeWidth * fTextShapingScale);
535 rec.fStrokeColorNode->setStrokeJoin(fText->fStrokeJoin);
554 box_color->setStrokeWidth(1);
555 box_color->setAntiAlias(
true);
560 draws.shrink_to_fit();
562 auto draws_node = (draws.size() > 1)
564 : std::move(draws[0]);
566 if (fHasBlurAnimator) {
573 fFragments.push_back(std::move(rec));
576void TextAdapter::buildDomainMaps(
const Shaper::Result& shape_result) {
586 float word_advance = 0,
591 bool in_word =
false;
594 for (;
i < shape_result.fFragments.size(); ++
i) {
595 const auto& frag = shape_result.fFragments[
i];
596 const bool is_new_line = frag.fLineIndex !=
line;
598 if (frag.fIsWhitespace || is_new_line) {
602 fMaps.
fWordsMap.push_back({word_start,
i - word_start, word_advance, word_ascent});
606 if (!frag.fIsWhitespace) {
612 word_advance = word_ascent = 0;
615 word_advance += frag.fAdvance;
616 word_ascent =
std::min(word_ascent, frag.fAscent);
621 fMaps.
fLinesMap.push_back({line_start,
i - line_start, line_advance, line_ascent});
622 line = frag.fLineIndex;
624 line_advance = line_ascent = 0;
627 line_advance += frag.fAdvance;
628 line_ascent =
std::min(line_ascent, frag.fAscent);
631 if (
i > word_start) {
632 fMaps.
fWordsMap.push_back({word_start,
i - word_start, word_advance, word_ascent});
635 if (
i > line_start) {
636 fMaps.
fLinesMap.push_back({line_start,
i - line_start, line_advance, line_ascent});
641 fText.fCurrentValue =
txt;
645uint32_t TextAdapter::shaperFlags()
const {
653 if (!fAnimators.empty() || fPathInfo || fText->fMaxLines || fText->fDecorator) {
654 flags |= Shaper::Flags::kFragmentGlyphs;
657 if (fRequiresAnchorPoint || fText->fDecorator) {
658 flags |= Shaper::Flags::kTrackFragmentAdvanceAscent;
661 if (fText->fDecorator) {
662 flags |= Shaper::Flags::kClusters;
668void TextAdapter::reshape() {
671 static constexpr float kMinSize = 0.1f,
673 const Shaper::TextDesc text_desc = {
675 SkTPin(fText->fTextSize, kMinSize, kMaxSize),
676 SkTPin(fText->fMinTextSize, kMinSize, kMaxSize),
677 SkTPin(fText->fMaxTextSize, kMinSize, kMaxSize),
686 fText->fCapitalization,
689 fText->fLocale.isEmpty() ? nullptr : fText->fLocale.c_str(),
690 fText->fFontFamily.isEmpty() ? nullptr : fText->fFontFamily.c_str(),
692 auto shape_result =
Shaper::Shape(fText->fText, text_desc, fText->fBox, fFontMgr,
696 if (shape_result.fFragments.empty() && fText->fText.size() > 0) {
698 fText->fText.c_str());
706 if (shape_result.fMissingGlyphCount > 0) {
708 shape_result.fMissingGlyphCount,
709 fText->fText.c_str());
716 fTextShapingScale = shape_result.fScale;
727 box_color->setStrokeWidth(1);
728 box_color->setAntiAlias(
true);
732 bounds_color->setStrokeWidth(1);
733 bounds_color->setAntiAlias(
true);
736 std::move(box_color)));
738 std::move(bounds_color)));
743 path_color->setStrokeWidth(1);
744 path_color->setAntiAlias(
true);
748 std::move(path_color)));
756 if (fText->fDecorator) {
757 decorator_node = sk_make_sp<GlyphDecoratorNode>(fText->fDecorator, fTextShapingScale);
758 container = decorator_node.
get();
763 for (
size_t i = 0;
i < shape_result.fFragments.size(); ++
i) {
764 this->addFragment(shape_result.fFragments[
i], container);
767 if (decorator_node) {
768 decorator_node->updateFragmentData(fFragments);
769 fRoot->
addChild(std::move(decorator_node));
772 if (!fAnimators.empty() || fPathInfo) {
774 this->buildDomainMaps(shape_result);
779 if (!fText->fHasFill && !fText->fHasStroke) {
783 if (fText.hasChanged()) {
787 if (fFragments.empty()) {
793 fPathInfo->updateContourData();
803 buf.resize(fFragments.size(), { seed_props, 0 });
806 for (
const auto& animator : fAnimators) {
807 animator->modulateProps(fMaps, buf);
811 switch (fAnchorPointGrouping) {
813 case AnchorPointGrouping::kWord: grouping_domain = &fMaps.
fWordsMap;
break;
814 case AnchorPointGrouping::kLine: grouping_domain = &fMaps.
fLinesMap;
break;
819 size_t grouping_span_index = 0;
820 SkV2 current_line_offset = { 0, 0 };
824 SkV2 total_spacing = {0,0};
825 float total_tracking = 0;
828 if (fRequiresLineAdjustments && line_span.fCount) {
829 for (
size_t i = line_span.fOffset;
i < line_span.fOffset + line_span.fCount; ++
i) {
830 const auto& props = buf[
i].props;
831 total_spacing += props.line_spacing;
832 total_tracking += props.tracking;
837 total_tracking -= 0.5f * (buf[line_span.fOffset].props.tracking +
838 buf[line_span.fOffset + line_span.fCount - 1].props.tracking);
841 return std::make_tuple(total_spacing, total_tracking);
845 for (
const auto& line_span : fMaps.
fLinesMap) {
846 const auto [line_spacing, line_tracking] = compute_linewide_props(buf, line_span);
847 const auto align_offset = -line_tracking * align_factor(fText->fHAlign);
850 if (&line_span != &fMaps.
fLinesMap.front() && line_span.fCount) {
853 current_line_offset += line_spacing / line_span.fCount;
856 float tracking_acc = 0;
857 for (
size_t i = line_span.fOffset;
i < line_span.fOffset + line_span.fCount; ++
i) {
859 if (grouping_domain &&
i >= (*grouping_domain)[grouping_span_index].fOffset +
860 (*grouping_domain)[grouping_span_index].
fCount) {
861 grouping_span_index += 1;
862 SkASSERT(
i < (*grouping_domain)[grouping_span_index].fOffset +
863 (*grouping_domain)[grouping_span_index].
fCount);
866 const auto& props = buf[
i].props;
867 const auto& frag = fFragments[
i];
876 const auto track_before =
i > line_span.fOffset
877 ? props.tracking * 0.5f : 0.0f,
878 track_after =
i < line_span.fOffset + line_span.fCount - 1
879 ? props.tracking * 0.5f : 0.0f;
881 const auto frag_offset = current_line_offset +
882 SkV2{align_offset + tracking_acc + track_before, 0};
884 tracking_acc += track_before + track_after;
886 this->pushPropsToFragment(props, frag, frag_offset, fGroupingAlignment * .01f,
887 grouping_domain ? &(*grouping_domain)[grouping_span_index]
893SkV2 TextAdapter::fragmentAnchorPoint(
const FragmentRec& rec,
894 const SkV2& grouping_alignment,
917 auto anchor_box = [&]() ->
SkRect {
918 switch (fAnchorPointGrouping) {
919 case AnchorPointGrouping::kCharacter:
921 return make_box(rec.fOrigin, rec.fAdvance, rec.fAscent);
922 case AnchorPointGrouping::kWord:
924 case AnchorPointGrouping::kLine: {
927 const auto& first_span_fragment = fFragments[grouping_span->
fOffset];
928 return make_box(first_span_fragment.fOrigin,
932 case AnchorPointGrouping::kAll:
939 const auto ab = anchor_box();
942 const auto ap =
SkV2 {
ab.centerX() +
ab.width() * 0.5f * grouping_alignment.
x,
943 ab.centerY() +
ab.height() * 0.5f * grouping_alignment.
y };
946 return ap -
SkV2 { rec.fOrigin.fX, rec.fOrigin.fY };
949SkM44 TextAdapter::fragmentMatrix(
const TextAnimator::ResolvedProps& props,
950 const FragmentRec& rec,
const SkV2& frag_offset)
const {
952 props.position.
x + rec.fOrigin.fX + frag_offset.
x,
953 props.position.y + rec.fOrigin.fY + frag_offset.
y,
962 const auto align_offset =
963 align_factor(fText->fHAlign)*(fPathInfo->pathLength() - fText->fBox.width());
974 const auto rel_pos =
SkV2{
pos.
x,
pos.
y} -
SkV2{fText->fBox.fLeft, fText->fBox.fTop};
975 const auto path_distance = rel_pos.x + align_offset;
977 return fPathInfo->getMatrix(path_distance, fText->fHAlign)
981void TextAdapter::pushPropsToFragment(
const TextAnimator::ResolvedProps& props,
982 const FragmentRec& rec,
983 const SkV2& frag_offset,
984 const SkV2& grouping_alignment,
985 const TextAnimator::DomainSpan* grouping_span)
const {
986 const auto anchor_point = this->fragmentAnchorPoint(rec, grouping_alignment, grouping_span);
988 rec.fMatrixNode->setMatrix(
989 this->fragmentMatrix(props, rec, anchor_point + frag_offset)
993 *
SkM44::Scale(props.scale.x, props.scale.y, props.scale.z)
996 const auto scale_alpha = [](
SkColor c,
float o) {
1000 if (rec.fFillColorNode) {
1001 rec.fFillColorNode->setColor(scale_alpha(props.fill_color, props.opacity));
1003 if (rec.fStrokeColorNode) {
1004 rec.fStrokeColorNode->setColor(scale_alpha(props.stroke_color, props.opacity));
1005 rec.fStrokeColorNode->setStrokeWidth(props.stroke_width * fTextShapingScale);
static constexpr SkColor SkColorSetA(SkColor c, U8CPU a)
#define SkColorGetA(color)
#define SkDegreesToRadians(degrees)
#define SkScalarRoundToInt(x)
SkSpan(Container &&) -> SkSpan< std::remove_pointer_t< decltype(std::data(std::declval< Container >()))> >
SK_API SkString SkStringPrintf(const char *format,...) SK_PRINTF_LIKE(1
Creates a new string and writes into it using a printf()-style format.
static constexpr const T & SkTPin(const T &x, const T &lo, const T &hi)
constexpr size_t SkToSizeT(S x)
#define SHOW_LAYOUT_BOXES
SkMatrix getTotalMatrix() const
void clipPath(const SkPath &path, SkClipOp op, bool doAntiAlias)
sk_sp< SkContourMeasure > next()
bool getPosTan(SkScalar distance, SkPoint *position, SkVector *tangent) const
static SkM44 Rotate(SkV3 axis, SkScalar radians)
static SkM44 Translate(SkScalar x, SkScalar y, SkScalar z=0)
static SkM44 Scale(SkScalar x, SkScalar y, SkScalar z=1)
static SkMatrix Scale(SkScalar sx, SkScalar sy)
static SkMatrix Translate(SkScalar dx, SkScalar dy)
@ kStroke_Style
set to stroke geometry
SkPath & reverseAddPath(const SkPath &src)
static Result Shape(const SkString &text, const TextDesc &desc, const SkPoint &point, const sk_sp< SkFontMgr > &, const sk_sp< SkShapers::Factory > &)
void log(Logger::Level, const skjson::Value *, const char fmt[],...) const SK_PRINTF_LIKE(4
bool dispatchTextProperty(const sk_sp< TextAdapter > &, const skjson::ObjectValue *jtext) const
void updateFragmentData(const std::vector< TextAdapter::FragmentRec > &recs)
void onRender(SkCanvas *canvas, const RenderContext *ctx) const override
GlyphDecoratorNode(sk_sp< GlyphDecorator > decorator, float scale)
SkRect onRevalidate(sksg::InvalidationController *ic, const SkMatrix &ctm) override
~GlyphDecoratorNode() override=default
static sk_sp< TextAdapter > Make(const skjson::ObjectValue &, const AnimationBuilder *, sk_sp< SkFontMgr >, sk_sp< CustomFont::GlyphCompMapper >, sk_sp< Logger >, sk_sp<::SkShapers::Factory >)
void setText(const TextValue &)
std::vector< DomainSpan > DomainMap
std::vector< AnimatedPropsModulator > ModulatorBuffer
static sk_sp< TextAnimator > Make(const skjson::ObjectValue *, const AnimationBuilder *, AnimatablePropertyContainer *acontainer)
static sk_sp< BlurImageFilter > Make()
static sk_sp< Color > Make(SkColor c)
static sk_sp< Draw > Make(sk_sp< GeometryNode > geo, sk_sp< PaintNode > paint)
void addChild(sk_sp< RenderNode >)
static sk_sp< Group > Make()
static sk_sp< RenderNode > Make(sk_sp< RenderNode > child, sk_sp< ImageFilter > filter)
static sk_sp< Matrix > Make(const T &m)
const SkRect & bounds() const
static sk_sp< Path > Make()
static sk_sp< Rect > Make()
ScopedRenderContext && setIsolation(const SkRect &bounds, const SkMatrix &ctm, bool do_isolate)
FlutterSemanticsFlag flags
static float max(float r, float g, float b)
static float min(float r, float g, float b)
SK_API sk_sp< SkDocument > Make(SkWStream *dst, const SkSerialProcs *=nullptr, std::function< void(const SkPicture *)> onEndPage=nullptr)
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
static constexpr float kBlurSizeToSigma
bool Parse(const skjson::Value &, T *)
constexpr float y() const
constexpr float x() const
static constexpr SkRect MakeXYWH(float x, float y, float w, float h)
static constexpr SkRect MakeLTRB(float l, float t, float r, float b)
ScalarValue fPathPerpendicular
SkM44 getMatrix(float distance, SkTextUtils::Align alignment) const
DomainMap fNonWhitespaceMap