From b901da10106fbefbab0df64a1d2e8bfc9800a0e7 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 00:18:14 +0000 Subject: [PATCH] A figure's rows carry its picture alone Art that is the picture's own placeholder, already marked as the picture inline (how SharpMUSH lays out a lone Markdown image), carried both marks under the figure's. MXP, Pueblo and BBCode then wrote the picture twice or lost its cells, and the text beside it moved. The figure now strips pictures from its rows before marking them. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_018ztDNVgzxM8V5WKWBRs2EW --- CHANGELOG.md | 4 +++ .../Layout/FigurePictureTests.cs | 28 +++++++++++++++++++ MarkupString/Layout/Blocks/Figure.cs | 21 +++++++++++++- 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33fa256..75752d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,10 @@ follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Fixed +- **A figure whose art is the picture's own placeholder is one picture.** Art marked as the picture + inline (a Markdown image laid out as a figure) carried both marks, so MXP, Pueblo and BBCode wrote the + picture twice or dropped its cells, and the text beside it moved. A figure's rows now carry its mark + alone. - **A figure's picture reaches MXP, Pueblo and BBCode.** A `Figure` laid out as text marked its picture only for a terminal that draws pictures in its cells, so every other format wrote the art alone and an MXP client was never sent ``. Its rows (or its `[description]`) now always carry the picture diff --git a/MarkupString.Tests/Layout/FigurePictureTests.cs b/MarkupString.Tests/Layout/FigurePictureTests.cs index bd28200..6931759 100644 --- a/MarkupString.Tests/Layout/FigurePictureTests.cs +++ b/MarkupString.Tests/Layout/FigurePictureTests.cs @@ -64,6 +64,34 @@ public async Task ArtOfLineBreaksAloneIsNoArt() await Assert.That(laid.ToPlainText().TrimEnd()).IsEqualTo("[A cat]"); } + /// + /// Art that is the picture's own placeholder, marked as the picture inline, as a Markdown image laid out as + /// a figure is: still one picture, and the words beside it start where they do in the plain text. + /// + [Test] + [Arguments("mxp")] + [Arguments("pueblo")] + [Arguments("bbcode")] + public async Task ArtMarkedAsThePictureIsStillOnePicture(string format) + { + var placeholder = MarkupText.Wrap(Cat, "[image: A cat]"); + var figure = new Figure(Cat, placeholder) { Float = FigureFloat.Left, Beside = new TextBlock(MarkupText.Plain("The cat sits by the fire.")) }; + var laid = BlockLayout.Build(figure, 40); + var markupFormat = format switch { "mxp" => MarkupFormat.Mxp, "pueblo" => MarkupFormat.Pueblo, _ => MarkupFormat.BBCode }; + var tag = format switch + { + "mxp" => "", + "pueblo" => "\"A", + _ => "[img]https://example.test/img/cat.png[/img]", + }; + + var first = Lines(laid, markupFormat)[0]; + + await Assert.That(Count(first, tag)).IsEqualTo(1); + await Assert.That(first.Replace(tag, string.Empty).IndexOf("The cat", StringComparison.Ordinal)) + .IsEqualTo(laid.ToPlainText().IndexOf("The cat", StringComparison.Ordinal)); + } + /// The words beside a floated picture stay in the column they are in for a reader of the art. [Test] public async Task TextBesideAFloatedPictureStaysWhereItWas() diff --git a/MarkupString/Layout/Blocks/Figure.cs b/MarkupString/Layout/Blocks/Figure.cs index 9580ad1..cf3ea02 100644 --- a/MarkupString/Layout/Blocks/Figure.cs +++ b/MarkupString/Layout/Blocks/Figure.cs @@ -1,3 +1,4 @@ +using System.Collections.Immutable; namespace MarkupString.Layout; /// @@ -118,10 +119,28 @@ private MarkupText[] Marked(MarkupText[] rows, int columns) { var marked = new MarkupText[rows.Length]; for (var row = 0; row < rows.Length; row++) - marked[row] = MarkupText.Wrap(Image with { Row = new PictureRow(row, rows.Length, columns) }, rows[row]); + marked[row] = MarkupText.Wrap(Image with { Row = new PictureRow(row, rows.Length, columns) }, Unpictured(rows[row])); return marked; } + /// + /// without the pictures it carries. Art is often the picture's own placeholder, + /// already marked as the picture inline; under the figure's mark that would be a second picture in the + /// same cells, written twice by a format with an element for it and pushing aside what is beside it. + /// + private static MarkupText Unpictured(MarkupText text) + { + if (!text.Runs.Any(run => run.Markups.Any(markup => markup is ImageMarkup))) return text; + + var runs = ImmutableArray.CreateBuilder(text.Runs.Length); + foreach (var run in text.Runs) + { + var kept = run.Markups.Where(markup => markup is not ImageMarkup).ToArray(); + if (kept.Length > 0) runs.Add(run with { Markups = MarkupSet.Of(kept) }); + } + return new MarkupText(text.Text, runs.ToImmutable()); + } + /// public override void DrawLinear(LayoutContext context, int width, IList lines) {