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();
222 fCurrentReversed = reverse;
223 fCurrentPath =
fPath;
234 return fCurrentMeasure ? fCurrentMeasure->
length() : 0;
241 if (!fCurrentMeasure) {
245 const auto path_len = fCurrentMeasure->
length();
256 if (fCurrentClosed) {
257 distance = std::fmod(distance, path_len);
259 distance += path_len;
261 SkASSERT(0 <= distance && distance <= path_len);
266 if (!fCurrentMeasure->
getPosTan(distance, &
pos, &tan)) {
274 const auto underflow = std::min(0.0f, distance),
275 overflow = std::max(0.0f, distance - path_len);
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 =
397 ParseDefault<size_t>((*jpath)[
"m"], std::numeric_limits<size_t>::max());
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))
442 , fLogger(
std::move(logger))
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];
597 if (frag.fIsWhitespace) {
600 fMaps.
fWordsMap.push_back({word_start, i - word_start, word_advance, word_ascent});
608 word_advance = word_ascent = 0;
611 word_advance += frag.fAdvance;
612 word_ascent = std::min(word_ascent, frag.fAscent);
615 if (frag.fLineIndex != line) {
616 SkASSERT(frag.fLineIndex == line + 1);
617 fMaps.
fLinesMap.push_back({line_start, i - line_start, line_advance, line_ascent});
618 line = frag.fLineIndex;
620 line_advance = line_ascent = 0;
623 line_advance += frag.fAdvance;
624 line_ascent = std::min(line_ascent, frag.fAscent);
627 if (i > word_start) {
628 fMaps.
fWordsMap.push_back({word_start, i - word_start, word_advance, word_ascent});
631 if (i > line_start) {
632 fMaps.
fLinesMap.push_back({line_start, i - line_start, line_advance, line_ascent});
637 fText.fCurrentValue =
txt;
641uint32_t TextAdapter::shaperFlags()
const {
649 if (!fAnimators.empty() || fPathInfo || fText->fMaxLines || fText->fDecorator) {
653 if (fRequiresAnchorPoint || fText->fDecorator) {
657 if (fText->fDecorator) {
664void TextAdapter::reshape() {
667 static constexpr float kMinSize = 0.1f,
669 const Shaper::TextDesc text_desc = {
671 SkTPin(fText->fTextSize, kMinSize, kMaxSize),
672 SkTPin(fText->fMinTextSize, kMinSize, kMaxSize),
673 SkTPin(fText->fMaxTextSize, kMinSize, kMaxSize),
682 fText->fCapitalization,
685 fText->fLocale.isEmpty() ? nullptr : fText->fLocale.c_str(),
686 fText->fFontFamily.isEmpty() ? nullptr : fText->fFontFamily.c_str(),
688 auto shape_result =
Shaper::Shape(fText->fText, text_desc, fText->fBox, fFontMgr,
692 if (shape_result.fFragments.empty() && fText->fText.size() > 0) {
694 fText->fText.c_str());
702 if (shape_result.fMissingGlyphCount > 0) {
704 shape_result.fMissingGlyphCount,
705 fText->fText.c_str());
712 fTextShapingScale = shape_result.fScale;
723 box_color->setStrokeWidth(1);
724 box_color->setAntiAlias(
true);
728 bounds_color->setStrokeWidth(1);
729 bounds_color->setAntiAlias(
true);
732 std::move(box_color)));
734 std::move(bounds_color)));
739 path_color->setStrokeWidth(1);
740 path_color->setAntiAlias(
true);
744 std::move(path_color)));
752 if (fText->fDecorator) {
753 decorator_node = sk_make_sp<GlyphDecoratorNode>(fText->fDecorator, fTextShapingScale);
754 container = decorator_node.
get();
759 for (
size_t i = 0; i < shape_result.fFragments.size(); ++i) {
760 this->addFragment(shape_result.fFragments[i], container);
763 if (decorator_node) {
764 decorator_node->updateFragmentData(fFragments);
765 fRoot->
addChild(std::move(decorator_node));
768 if (!fAnimators.empty() || fPathInfo) {
770 this->buildDomainMaps(shape_result);
775 if (!fText->fHasFill && !fText->fHasStroke) {
779 if (fText.hasChanged()) {
783 if (fFragments.empty()) {
789 fPathInfo->updateContourData();
799 buf.resize(fFragments.size(), { seed_props, 0 });
802 for (
const auto& animator : fAnimators) {
803 animator->modulateProps(fMaps, buf);
807 switch (fAnchorPointGrouping) {
809 case AnchorPointGrouping::kWord: grouping_domain = &fMaps.
fWordsMap;
break;
810 case AnchorPointGrouping::kLine: grouping_domain = &fMaps.
fLinesMap;
break;
815 size_t grouping_span_index = 0;
816 SkV2 current_line_offset = { 0, 0 };
820 SkV2 total_spacing = {0,0};
821 float total_tracking = 0;
824 if (fRequiresLineAdjustments && line_span.fCount) {
825 for (
size_t i = line_span.fOffset; i < line_span.fOffset + line_span.fCount; ++i) {
826 const auto& props = buf[i].props;
827 total_spacing += props.line_spacing;
828 total_tracking += props.tracking;
833 total_tracking -= 0.5f * (buf[line_span.fOffset].props.tracking +
834 buf[line_span.fOffset + line_span.fCount - 1].props.tracking);
837 return std::make_tuple(total_spacing, total_tracking);
841 for (
const auto& line_span : fMaps.
fLinesMap) {
842 const auto [line_spacing, line_tracking] = compute_linewide_props(buf, line_span);
843 const auto align_offset = -line_tracking * align_factor(fText->fHAlign);
846 if (&line_span != &fMaps.
fLinesMap.front() && line_span.fCount) {
849 current_line_offset += line_spacing / line_span.fCount;
852 float tracking_acc = 0;
853 for (
size_t i = line_span.fOffset; i < line_span.fOffset + line_span.fCount; ++i) {
855 if (grouping_domain && i >= (*grouping_domain)[grouping_span_index].fOffset +
856 (*grouping_domain)[grouping_span_index].fCount) {
857 grouping_span_index += 1;
858 SkASSERT(i < (*grouping_domain)[grouping_span_index].fOffset +
859 (*grouping_domain)[grouping_span_index].fCount);
862 const auto& props = buf[i].props;
863 const auto& frag = fFragments[i];
872 const auto track_before = i > line_span.fOffset
873 ? props.tracking * 0.5f : 0.0f,
874 track_after = i < line_span.fOffset + line_span.fCount - 1
875 ? props.tracking * 0.5f : 0.0f;
877 const auto frag_offset = current_line_offset +
878 SkV2{align_offset + tracking_acc + track_before, 0};
880 tracking_acc += track_before + track_after;
882 this->pushPropsToFragment(props, frag, frag_offset, fGroupingAlignment * .01f,
883 grouping_domain ? &(*grouping_domain)[grouping_span_index]
889SkV2 TextAdapter::fragmentAnchorPoint(
const FragmentRec& rec,
890 const SkV2& grouping_alignment,
905 auto make_box = [](
const SkPoint&
pos,
float advance,
float ascent) {
913 auto anchor_box = [&]() ->
SkRect {
914 switch (fAnchorPointGrouping) {
915 case AnchorPointGrouping::kCharacter:
917 return make_box(rec.fOrigin, rec.fAdvance, rec.fAscent);
918 case AnchorPointGrouping::kWord:
920 case AnchorPointGrouping::kLine: {
923 const auto& first_span_fragment = fFragments[grouping_span->
fOffset];
924 return make_box(first_span_fragment.fOrigin,
928 case AnchorPointGrouping::kAll:
935 const auto ab = anchor_box();
938 const auto ap =
SkV2 {
ab.centerX() +
ab.width() * 0.5f * grouping_alignment.
x,
939 ab.centerY() +
ab.height() * 0.5f * grouping_alignment.
y };
942 return ap -
SkV2 { rec.fOrigin.fX, rec.fOrigin.fY };
945SkM44 TextAdapter::fragmentMatrix(
const TextAnimator::ResolvedProps& props,
946 const FragmentRec& rec,
const SkV2& frag_offset)
const {
948 props.position.
x + rec.fOrigin.fX + frag_offset.
x,
949 props.position.y + rec.fOrigin.fY + frag_offset.
y,
958 const auto align_offset =
959 align_factor(fText->fHAlign)*(fPathInfo->pathLength() - fText->fBox.width());
970 const auto rel_pos =
SkV2{
pos.
x,
pos.
y} -
SkV2{fText->fBox.fLeft, fText->fBox.fTop};
971 const auto path_distance = rel_pos.x + align_offset;
973 return fPathInfo->getMatrix(path_distance, fText->fHAlign)
977void TextAdapter::pushPropsToFragment(
const TextAnimator::ResolvedProps& props,
978 const FragmentRec& rec,
979 const SkV2& frag_offset,
980 const SkV2& grouping_alignment,
981 const TextAnimator::DomainSpan* grouping_span)
const {
982 const auto anchor_point = this->fragmentAnchorPoint(rec, grouping_alignment, grouping_span);
984 rec.fMatrixNode->setMatrix(
985 this->fragmentMatrix(props, rec, anchor_point + frag_offset)
989 *
SkM44::Scale(props.scale.x, props.scale.y, props.scale.z)
992 const auto scale_alpha = [](
SkColor c,
float o) {
996 if (rec.fFillColorNode) {
997 rec.fFillColorNode->setColor(scale_alpha(props.fill_color, props.opacity));
999 if (rec.fStrokeColorNode) {
1000 rec.fStrokeColorNode->setColor(scale_alpha(props.stroke_color, props.opacity));
1001 rec.fStrokeColorNode->setStrokeWidth(props.stroke_width * fTextShapingScale);
static constexpr SkColor SkColorSetA(SkColor c, U8CPU a)
#define SkColorGetA(color)
static std::unique_ptr< SkEncoder > Make(SkWStream *dst, const SkPixmap *src, const SkYUVAPixmaps *srcYUVA, const SkColorSpace *srcYUVAColorSpace, const SkJpegEncoder::Options &options)
#define SkDegreesToRadians(degrees)
#define SkScalarRoundToInt(x)
SK_API SkString static SkString SkStringPrintf()
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 > &)
@ kTrackFragmentAdvanceAscent
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 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