Render invoice-editor <img> elements on their own line
Playwright Tests / test (push) Canceled after 0s

<img> is a nominally inline HTML element, but TinyMCE content places images
between paragraphs of text; without a forced line break before/after, an
image could render packed against adjacent text in the PDF. Insert a line
break ahead of the image (unless one is already there) and always after it.
Also fixes the img handler's early-return path to still apply the inline
size cap (MaxInlineImageWidth/Height) when only one of width/height was
resized, matching the aspect-ratio-locked branch.

Fuchs/wwwroot/web/tools.js: cosmetic gulp rebuild diff (leading blank line),
no behavior change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-10 22:20:44 +02:00
co-authored by Claude Sonnet 5
parent 5fa7a2b50f
commit 52b2811b43
2 changed files with 20 additions and 8 deletions
+17 -6
View File
@@ -122,7 +122,9 @@ namespace MigraDoc.Extensions.Html
// else (including any remote URL the caller didn't fetch/vet) is silently dropped, same // else (including any remote URL the caller didn't fetch/vet) is silently dropped, same
// as every other unsupported element. Size mirrors what the editor shows (the width/height // as every other unsupported element. Size mirrors what the editor shows (the width/height
// TinyMCE writes onto the <img> from its resize handles), capped so a stray huge size from // TinyMCE writes onto the <img> from its resize handles), capped so a stray huge size from
// hand-edited HTML can't blow out the invoice table layout. // hand-edited HTML can't blow out the invoice table layout. Always rendered alone on its
// own line — surrounded by line breaks rather than left inline with adjacent text — even
// though <img> is nominally an inline HTML element.
_handlers["img"] = (n, p) => _handlers["img"] = (n, p) =>
{ {
string src = n.GetAttributeValue("src", ""); string src = n.GetAttributeValue("src", "");
@@ -130,6 +132,11 @@ namespace MigraDoc.Extensions.Html
return p; return p;
var para = GetPara(p); var para = GetPara(p);
if (para == null) return p; if (para == null) return p;
var existing = para.Elements.Cast<DocumentObject>().ToList();
bool lastIsLineBreak = existing.Count > 0 && existing[^1] is Character { SymbolName: SymbolName.LineBreak };
if (existing.Count > 0 && !lastIsLineBreak) para.AddLineBreak();
var img = para.AddImage(source); var img = para.AddImage(source);
img.LockAspectRatio = true; img.LockAspectRatio = true;
@@ -152,13 +159,17 @@ namespace MigraDoc.Extensions.Html
img.Width = Unit.FromCentimeter(img.Width.Centimeter * scale); img.Width = Unit.FromCentimeter(img.Width.Centimeter * scale);
img.Height = Unit.FromCentimeter(img.Height.Centimeter * scale); img.Height = Unit.FromCentimeter(img.Height.Centimeter * scale);
} }
return para;
} }
if (wPx is > 0) img.Width = PxToUnit(wPx.Value); else
else if (hPx is > 0) img.Height = PxToUnit(hPx.Value); {
if (wPx is > 0) img.Width = PxToUnit(wPx.Value);
else if (hPx is > 0) img.Height = PxToUnit(hPx.Value);
if (img.Width > MaxInlineImageWidth) { img.LockAspectRatio = true; img.Width = MaxInlineImageWidth; } if (img.Width > MaxInlineImageWidth) { img.LockAspectRatio = true; img.Width = MaxInlineImageWidth; }
if (img.Height > MaxInlineImageHeight) { img.LockAspectRatio = true; img.Height = MaxInlineImageHeight; } if (img.Height > MaxInlineImageHeight) { img.LockAspectRatio = true; img.Height = MaxInlineImageHeight; }
}
para.AddLineBreak();
return para; return para;
}; };
File diff suppressed because one or more lines are too long