Skip to content

Commit 7cea1c5

Browse files
jhaygood86claude
andcommitted
Fix regressions from the CSS engine port and update stale baselines
- CssContentEngine: box.Text is a plain string post-port; fix the leftover reference to the removed SubString type. - CssUtils/CssDefaults: the new engine always expands the text-decoration shorthand into text-decoration-line/-style/-color, but the legacy property bridge only recognized "text-decoration", so underline/line-through/overline never rendered. Route the longhand through instead, matching every other migrated shorthand. - CssBox: PaintDecoration only handled a single decoration keyword; text-decoration-line can carry several space-separated values (e.g. "underline overline"), so draw each one present. - CssBox.IsClickable: was gated on the anchor lacking an "id", which wrongly treated `<a href="..." id="...">` as unclickable. Gate on "href" instead, matching :link/:visited semantics. - CssBoxImage: content: url(...) on a real <img> (not just ::before/::after) was never resolved to an image because CssBox.Content holds the literal CSS text (quotes and url() wrapper included), which ImageLoadHandler can't parse directly. Extract the URL token's data before handing it off. - Approve baselines for Anchors, Background Image, Borders, and Text where the new rendering is the spec-compliant one (confirmed against PeachPDF's current behavior for the anchor cases), and add the previously-missing baseline for the media-queries sample. Integration test suite: 16/29 -> 25/29 passing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 850aab4 commit 7cea1c5

10 files changed

Lines changed: 63 additions & 29 deletions

File tree

‎Source/HtmlRenderer/Core/CssDefaults.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ @media print {
187187
{ "padding-top", "0" },
188188
{ "page-break-inside", "auto" },
189189
{ "text-align", "" },
190-
{ "text-decoration", "" },
190+
{ "text-decoration-line", "" },
191191
{ "text-indent", "0" },
192192
{ "top", "auto" },
193193
{ "position", "static" },

‎Source/HtmlRenderer/Core/Dom/CssBox.cs‎

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -189,11 +189,13 @@ public bool IsBlock
189189
}
190190

191191
/// <summary>
192-
/// Is the css box clickable (by default only "a" element is clickable)
192+
/// Is the css box clickable (by default only "a" elements that are actual hyperlinks - i.e.
193+
/// have an "href" - are clickable; an "a" used only as a named anchor/target has no href and
194+
/// is not clickable, matching the same "href" gate CSS uses for the :link pseudo-class).
193195
/// </summary>
194196
public virtual bool IsClickable
195197
{
196-
get { return HtmlTag != null && HtmlTag.Name == HtmlConstants.A && !HtmlTag.HasAttribute("id"); }
198+
get { return HtmlTag != null && HtmlTag.Name == HtmlConstants.A && HtmlTag.HasAttribute("href"); }
197199
}
198200

199201
/// <summary>
@@ -1451,21 +1453,6 @@ protected void PaintDecoration(RGraphics g, RRect rectangle, bool isFirst, bool
14511453
if (string.IsNullOrEmpty(TextDecoration) || TextDecoration == CssConstants.None)
14521454
return;
14531455

1454-
double y = 0f;
1455-
if (TextDecoration == CssConstants.Underline)
1456-
{
1457-
y = Math.Round(rectangle.Top + ActualFont.UnderlineOffset);
1458-
}
1459-
else if (TextDecoration == CssConstants.LineThrough)
1460-
{
1461-
y = rectangle.Top + rectangle.Height / 2f;
1462-
}
1463-
else if (TextDecoration == CssConstants.Overline)
1464-
{
1465-
y = rectangle.Top;
1466-
}
1467-
y -= ActualPaddingBottom - ActualBorderBottomWidth;
1468-
14691456
double x1 = rectangle.X;
14701457
if (isFirst)
14711458
x1 += ActualPaddingLeft + ActualBorderLeftWidth;
@@ -1474,10 +1461,36 @@ protected void PaintDecoration(RGraphics g, RRect rectangle, bool isFirst, bool
14741461
if (isLast)
14751462
x2 -= ActualPaddingRight + ActualBorderRightWidth;
14761463

1464+
var bottomInset = ActualPaddingBottom - ActualBorderBottomWidth;
14771465
var pen = g.GetPen(ActualColor);
14781466
pen.Width = 1;
14791467
pen.DashStyle = RDashStyle.Solid;
1480-
g.DrawLine(pen, x1, y, x2, y);
1468+
1469+
// text-decoration-line may list several space-separated line keywords (e.g. "underline
1470+
// overline") - draw each one present rather than only the first/only value.
1471+
foreach (var line in TextDecoration.Split(' ', StringSplitOptions.RemoveEmptyEntries))
1472+
{
1473+
double y;
1474+
if (line == CssConstants.Underline)
1475+
{
1476+
y = Math.Round(rectangle.Top + ActualFont.UnderlineOffset);
1477+
}
1478+
else if (line == CssConstants.LineThrough)
1479+
{
1480+
y = rectangle.Top + rectangle.Height / 2f;
1481+
}
1482+
else if (line == CssConstants.Overline)
1483+
{
1484+
y = rectangle.Top;
1485+
}
1486+
else
1487+
{
1488+
continue;
1489+
}
1490+
1491+
y -= bottomInset;
1492+
g.DrawLine(pen, x1, y, x2, y);
1493+
}
14811494
}
14821495

14831496
/// <summary>

‎Source/HtmlRenderer/Core/Dom/CssBoxImage.cs‎

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@
1111
// "The Art of War"
1212

1313
using System;
14+
using System.Linq;
1415
using TheArtOfDev.HtmlRenderer.Adapters;
1516
using TheArtOfDev.HtmlRenderer.Adapters.Entities;
17+
using TheArtOfDev.HtmlRenderer.Core.CssEngine;
1618
using TheArtOfDev.HtmlRenderer.Core.Handlers;
19+
using TheArtOfDev.HtmlRenderer.Core.Parse;
1720
using TheArtOfDev.HtmlRenderer.Core.Utils;
1821

1922
namespace TheArtOfDev.HtmlRenderer.Core.Dom
@@ -73,7 +76,7 @@ protected override void PaintImp(RGraphics g)
7376
if (_imageLoadHandler == null)
7477
{
7578
_imageLoadHandler = new ImageLoadHandler(HtmlContainer, OnLoadImageComplete);
76-
_imageLoadHandler.LoadImage(GetAttribute("src"), HtmlTag != null ? HtmlTag.Attributes : null);
79+
_imageLoadHandler.LoadImage(GetImageSource(), HtmlTag != null ? HtmlTag.Attributes : null);
7780
}
7881

7982
var rect = CommonUtils.GetFirstValueOrDefault(Rectangles);
@@ -142,11 +145,7 @@ internal override void MeasureWordsSize(RGraphics g)
142145
if (_imageLoadHandler == null && (HtmlContainer.AvoidAsyncImagesLoading || HtmlContainer.AvoidImagesLateLoading))
143146
{
144147
_imageLoadHandler = new ImageLoadHandler(HtmlContainer, OnLoadImageComplete);
145-
146-
if (this.Content != null && this.Content != CssConstants.Normal)
147-
_imageLoadHandler.LoadImage(this.Content, HtmlTag != null ? HtmlTag.Attributes : null);
148-
else
149-
_imageLoadHandler.LoadImage(GetAttribute("src"), HtmlTag != null ? HtmlTag.Attributes : null);
148+
_imageLoadHandler.LoadImage(GetImageSource(), HtmlTag != null ? HtmlTag.Attributes : null);
150149
}
151150

152151
MeasureWordSpacing(g);
@@ -169,6 +168,28 @@ public override void Dispose()
169168

170169
#region Private methods
171170

171+
/// <summary>
172+
/// Get the image source to load: a resolved <c>content: url(...)</c> value takes precedence
173+
/// over the "src" attribute (CSS generated content replacing the element's own rendering, same
174+
/// as this engine already gives generated content priority for pseudo-elements), falling back
175+
/// to "src" when content is unset/none/normal or isn't a url().<br/>
176+
/// <see cref="CssBox.Content"/> holds the literal declared CSS text (e.g. <c>url('data:...')</c>,
177+
/// quotes and all) rather than an unwrapped source string, so the url() token's data has to be
178+
/// pulled out here - <see cref="ImageLoadHandler.LoadImage"/> only recognizes a bare path or
179+
/// "data:image..." URI, not CSS function syntax.
180+
/// </summary>
181+
private string GetImageSource()
182+
{
183+
if (!string.IsNullOrEmpty(Content) && Content != CssConstants.Normal && Content != CssConstants.None)
184+
{
185+
var token = CssValueParser.GetCssTokens(Content).FirstOrDefault();
186+
if (token is UrlToken urlToken)
187+
return urlToken.Data;
188+
}
189+
190+
return GetAttribute("src");
191+
}
192+
172193
/// <summary>
173194
/// Set error image border on the image box.
174195
/// </summary>

‎Source/HtmlRenderer/Core/Dom/CssContentEngine.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public static void ApplyContent(CssBox box)
5252
var text = Resolve(box, content);
5353
if (!string.IsNullOrEmpty(text))
5454
{
55-
box.Text = new SubString(text);
55+
box.Text = text;
5656
}
5757
}
5858

‎Source/HtmlRenderer/Core/Utils/CssUtils.cs‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ internal static class CssUtils
5050
"page-break-inside", "left", "top", "width", "max-width", "height",
5151
"background-color", "background-image", "background-position", "background-repeat",
5252
"content", "color", "display", "direction", "empty-cells", "float", "position",
53-
"line-height", "vertical-align", "text-indent", "text-align", "text-decoration",
53+
"line-height", "vertical-align", "text-indent", "text-align", "text-decoration-line",
5454
"white-space", "word-break", "visibility", "word-spacing",
5555
"font-family", "font-size", "font-style", "font-variant", "font-weight",
5656
"list-style", "list-style-position", "list-style-image", "list-style-type",
@@ -189,7 +189,7 @@ public static string GetPropertyValue(CssBox cssBox, string propName)
189189
return cssBox.TextIndent;
190190
case "text-align":
191191
return cssBox.TextAlign;
192-
case "text-decoration":
192+
case "text-decoration-line":
193193
return cssBox.TextDecoration;
194194
case "white-space":
195195
return cssBox.WhiteSpace;
@@ -378,7 +378,7 @@ public static void SetPropertyValue(CssBox cssBox, string propName, string value
378378
case "text-align":
379379
cssBox.TextAlign = value;
380380
break;
381-
case "text-decoration":
381+
case "text-decoration-line":
382382
cssBox.TextDecoration = value;
383383
break;
384384
case "white-space":
72 Bytes
Loading
33 Bytes
Loading
-144 Bytes
Loading
16.3 KB
Loading
-3.74 KB
Loading

0 commit comments

Comments
 (0)