Skip to content

Commit 78f026c

Browse files
committed
Support center image with margin auto #171
1 parent e6ecabf commit 78f026c

File tree

4 files changed

+54
-11
lines changed

4 files changed

+54
-11
lines changed

src/Html2OpenXml/Expressions/Image/ImageExpressionBase.cs

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
using System.Collections.Generic;
1313
using System.Linq;
1414
using DocumentFormat.OpenXml;
15-
using DocumentFormat.OpenXml.Packaging;
1615
using DocumentFormat.OpenXml.Wordprocessing;
1716

1817
using a = DocumentFormat.OpenXml.Drawing;
@@ -26,6 +25,12 @@ namespace HtmlToOpenXml.Expressions;
2625
/// </summary>
2726
abstract class ImageExpressionBase(AngleSharp.Dom.IElement node) : HtmlDomExpression
2827
{
28+
private readonly RunProperties runProperties = new();
29+
private readonly ParagraphProperties paraProperties = new();
30+
// some style attributes, such as borders, will convert this node to a framed container
31+
private bool renderAsFramed;
32+
33+
2934
/// <inheritdoc/>
3035
public override IEnumerable<OpenXmlElement> Interpret (ParsingContext context)
3136
{
@@ -35,16 +40,17 @@ public override IEnumerable<OpenXmlElement> Interpret (ParsingContext context)
3540
return [];
3641

3742
Run run = new(drawing);
38-
Border border = ComposeStyles();
39-
if (border.Val?.Equals(BorderValues.None) == false)
40-
{
41-
run.RunProperties ??= new();
42-
run.RunProperties.Border = border;
43-
}
43+
ComposeStyles();
44+
45+
if (runProperties.HasChildren)
46+
run.RunProperties = runProperties;
47+
48+
if (renderAsFramed)
49+
return [new Paragraph(paraProperties, run)];
4450
return [run];
4551
}
4652

47-
private Border ComposeStyles ()
53+
private void ComposeStyles ()
4854
{
4955
var styleAttributes = node.GetStyles();
5056
var border = new Border() { Val = BorderValues.None };
@@ -66,7 +72,22 @@ private Border ComposeStyles ()
6672
border.Size = (uint) borderWidth.ValueInPx * 4;
6773
}
6874
}
69-
return border;
75+
76+
if (border.Val?.Equals(BorderValues.None) == false)
77+
{
78+
runProperties.Border = border;
79+
}
80+
81+
// if the layout is not inline and both left and right are auto, image appears centered
82+
// https://developer.mozilla.org/en-US/docs/Web/CSS/margin-left
83+
var margin = styleAttributes.GetMargin("margin");
84+
if (margin.Left.Type == UnitMetric.Auto
85+
&& margin.Right.Type == UnitMetric.Auto
86+
&& !AngleSharpExtensions.IsInlineLayout(styleAttributes["display"]))
87+
{
88+
paraProperties.Justification = new() { Val = JustificationValues.Center };
89+
renderAsFramed = true;
90+
}
7091
}
7192

7293
/// <summary>

src/Html2OpenXml/Utilities/AngleSharpExtensions.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,4 +153,13 @@ public static string CollapseLineBreaks(this string str)
153153

154154
return new string(chars, 0, length);
155155
}
156+
157+
/// <summary>
158+
/// Determines whether the layout mode is inline vs block or flex.
159+
/// </summary>
160+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
161+
public static bool IsInlineLayout(string? displayMode)
162+
{
163+
return displayMode?.StartsWith("inline", StringComparison.OrdinalIgnoreCase) == true;
164+
}
156165
}

src/Html2OpenXml/Utilities/OpenXmlExtensions.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,9 @@
99
* IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
1010
* PARTICULAR PURPOSE.
1111
*/
12-
using System;
1312
using System.Runtime.CompilerServices;
1413
using DocumentFormat.OpenXml;
1514
using DocumentFormat.OpenXml.Wordprocessing;
16-
using DocumentFormat.OpenXml.Drawing.Wordprocessing;
1715

1816
namespace HtmlToOpenXml;
1917

test/HtmlToOpenXml.Tests/ImgTests.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,21 @@ public async Task ParseIntoDocumentPart_ReturnsImageParentedToPart (Type openXml
219219
AssertThatOpenXmlDocumentIsValid();
220220
}
221221

222+
[TestCase("block", ExpectedResult = true)]
223+
[TestCase("flex", ExpectedResult = true)]
224+
[TestCase("inline", ExpectedResult = false)]
225+
public bool CenterImg_ReturnsFramedImg(string displayMode)
226+
{
227+
var elements = converter.Parse($@"<img style=""display: {displayMode}; margin-left: auto; margin-right: auto;""
228+
src=""data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="" width=""32"" height=""32"">");
229+
230+
Assert.That(elements, Has.Count.EqualTo(1));
231+
Assert.That(elements[0], Is.TypeOf<Paragraph>());
232+
AssertIsImg(mainPart, elements[0]);
233+
return elements[0].GetFirstChild<ParagraphProperties>()?.
234+
Justification?.Val?.Value == JustificationValues.Center;
235+
}
236+
222237
private static (Drawing, ImagePart) AssertIsImg (OpenXmlPartContainer container, OpenXmlElement paragraph)
223238
{
224239
var run = paragraph.GetFirstChild<Run>();

0 commit comments

Comments
 (0)