@@ -274,6 +274,20 @@ class FlutterVectorGraphicsListener extends VectorGraphicsCodecListener {
274274 double _textPositionY = 0 ;
275275 Float64List ? _textTransform;
276276
277+ // Pending text draws within the current SVG anchored chunk. Per the SVG
278+ // spec, `text-anchor` applies to the chunk as a whole, so we cannot
279+ // commit a paragraph to the canvas until we know the full chunk width.
280+ final List <_PendingTextDraw > _pendingChunk = < _PendingTextDraw > [];
281+ // The user-space x at which the current chunk begins (i.e. the value of
282+ // `_accumulatedTextPositionX` at the time the first paragraph in the
283+ // chunk was queued). Null when no chunk is open.
284+ double ? _chunkOriginX;
285+ // The text-anchor multiplier of the first paragraph in the chunk; used
286+ // to position the chunk as a whole.
287+ double _chunkAnchorMultiplier = 0 ;
288+ // Cumulative pen-advance within the current chunk so far.
289+ double _chunkAdvance = 0 ;
290+
277291 _PatternConfig ? _currentPattern;
278292
279293 static final Paint _emptyPaint = Paint ();
@@ -294,6 +308,7 @@ class FlutterVectorGraphicsListener extends VectorGraphicsCodecListener {
294308 PictureInfo toPicture () {
295309 assert (! _done);
296310 _done = true ;
311+ _flushPendingTextChunk ();
297312 try {
298313 return PictureInfo ._(_recorder.endRecording (), _size);
299314 } finally {
@@ -652,6 +667,15 @@ class FlutterVectorGraphicsListener extends VectorGraphicsCodecListener {
652667 @override
653668 void onUpdateTextPosition (int textPositionId) {
654669 final _TextPosition position = _textPositions[textPositionId];
670+ // Per the SVG spec, a new anchored chunk begins only when the element
671+ // establishes an explicit absolute position (i.e. an `x` or `y` on a
672+ // <text> or <tspan>). Relative `dx`/`dy` move the pen but do NOT
673+ // start a new chunk; neither does the bare per-tspan TextPosition the
674+ // parser emits when the tspan has no x/y of its own. `reset` (set on
675+ // <text> elements) likewise starts a fresh chunk.
676+ if (position.reset || position.x != null || position.y != null ) {
677+ _flushPendingTextChunk ();
678+ }
655679 if (position.reset) {
656680 _accumulatedTextPositionX = 0 ;
657681 _textPositionY = 0 ;
@@ -685,9 +709,26 @@ class FlutterVectorGraphicsListener extends VectorGraphicsCodecListener {
685709 final _TextConfig textConfig = _textConfig[textId];
686710 final double dx = _accumulatedTextPositionX ?? 0 ;
687711 final double dy = _textPositionY;
688- double paragraphWidth = 0 ;
689712
690- void draw (int paintId) {
713+ // A change in text-anchor on a continuing chunk also starts a new
714+ // anchored chunk per the SVG spec.
715+ if (_pendingChunk.isNotEmpty &&
716+ textConfig.xAnchorMultiplier != _chunkAnchorMultiplier) {
717+ _flushPendingTextChunk ();
718+ }
719+
720+ if (_pendingChunk.isEmpty) {
721+ _chunkOriginX = dx;
722+ _chunkAnchorMultiplier = textConfig.xAnchorMultiplier;
723+ _chunkAdvance = 0 ;
724+ } else {
725+ // Continuing the chunk: take the live pen position so any in-chunk
726+ // relative `dx="..."` movements applied via onUpdateTextPosition
727+ // since the last segment are accounted for in the segment's offset
728+ // within the chunk.
729+ _chunkAdvance = dx - _chunkOriginX! ;
730+ }
731+ Paragraph buildParagraph (int paintId) {
691732 final Paint paint = _paints[paintId];
692733 if (patternId != null ) {
693734 paint.shader = _patterns[patternId]! .shader;
@@ -707,37 +748,56 @@ class FlutterVectorGraphicsListener extends VectorGraphicsCodecListener {
707748 decorationColor: textConfig.decorationColor,
708749 ),
709750 );
710-
711751 builder.addText (textConfig.text);
712-
713752 final Paragraph paragraph = builder.build ();
714753 paragraph.layout (const ParagraphConstraints (width: double .infinity));
715- paragraphWidth = paragraph.maxIntrinsicWidth;
754+ return paragraph;
755+ }
756+
757+ double paragraphWidth = 0 ;
758+ if (fillId != null ) {
759+ final Paragraph p = buildParagraph (fillId);
760+ paragraphWidth = p.maxIntrinsicWidth;
761+ _pendingChunk.add (_PendingTextDraw (p, _chunkAdvance, dy, _textTransform));
762+ }
763+ if (strokeId != null ) {
764+ final Paragraph p = buildParagraph (strokeId);
765+ paragraphWidth = p.maxIntrinsicWidth;
766+ _pendingChunk.add (_PendingTextDraw (p, _chunkAdvance, dy, _textTransform));
767+ }
768+
769+ _chunkAdvance += paragraphWidth;
770+ _accumulatedTextPositionX = dx + paragraphWidth;
771+ }
716772
717- if (_textTransform != null ) {
773+ void _flushPendingTextChunk () {
774+ if (_pendingChunk.isEmpty) {
775+ return ;
776+ }
777+ final double originX = _chunkOriginX ?? 0 ;
778+ final double anchorOffset = _chunkAdvance * _chunkAnchorMultiplier;
779+ for (final _PendingTextDraw draw in _pendingChunk) {
780+ final Paragraph paragraph = draw.paragraph;
781+ if (draw.transform != null ) {
718782 _canvas.save ();
719- _canvas.transform (_textTransform ! );
783+ _canvas.transform (draw.transform ! );
720784 }
721785 _canvas.drawParagraph (
722786 paragraph,
723787 Offset (
724- dx - paragraph.maxIntrinsicWidth * textConfig.xAnchorMultiplier ,
725- dy - paragraph.alphabeticBaseline,
788+ originX + draw.offsetWithinChunk - anchorOffset ,
789+ draw. dy - paragraph.alphabeticBaseline,
726790 ),
727791 );
728792 paragraph.dispose ();
729- if (_textTransform != null ) {
793+ if (draw.transform != null ) {
730794 _canvas.restore ();
731795 }
732796 }
733-
734- if (fillId != null ) {
735- draw (fillId);
736- }
737- if (strokeId != null ) {
738- draw (strokeId);
739- }
740- _accumulatedTextPositionX = dx + paragraphWidth;
797+ _pendingChunk.clear ();
798+ _chunkOriginX = null ;
799+ _chunkAnchorMultiplier = 0 ;
800+ _chunkAdvance = 0 ;
741801 }
742802
743803 int _createImageKey (int imageId, int format) {
@@ -885,6 +945,20 @@ class _TextConfig {
885945 final Color decorationColor;
886946}
887947
948+ class _PendingTextDraw {
949+ _PendingTextDraw (
950+ this .paragraph,
951+ this .offsetWithinChunk,
952+ this .dy,
953+ this .transform,
954+ );
955+
956+ final Paragraph paragraph;
957+ final double offsetWithinChunk;
958+ final double dy;
959+ final Float64List ? transform;
960+ }
961+
888962/// An exception thrown if decoding fails.
889963///
890964/// The [originalException] is a detailed exception about what failed in
0 commit comments