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) {