Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,16 @@
Gutter = (uint)pageConfig.GutterMargin
});

// Word for Mac's "Save as PDF" / "Print to PDF" pipeline does not honor
// <w:mirrorMargins/> when it is only placed in settings.xml (spec-compliant
// location). Duplicating it inside sectPr makes the export pipeline mirror
// verso pages correctly while remaining harmless for Word/Windows and
// LibreOffice, which ignore unknown sectPr children. See issue #74.
if (pageConfig.MirrorMargins)
{
sectionProps.AppendChild(new W.MirrorMargins());
}

return sectionProps;
}

Expand Down Expand Up @@ -862,7 +872,7 @@
// Adding indent equal to BorderSpace * 20 twips anchors the border at the margin boundary.
if (style.BorderSpace > 0)
{
string indentTwips = (style.BorderSpace * 20).ToString();

Check warning on line 875 in csharp-version/src/MarkdownToDocx.Core/OpenXml/OpenXmlDocumentBuilder.cs

View workflow job for this annotation

GitHub Actions / Build and Test

The behavior of 'uint.ToString()' could vary based on the current user's locale settings. Replace this call in 'OpenXmlDocumentBuilder.CreateCodeBlockParagraphProperties(CodeBlockStyle)' with a call to 'uint.ToString(IFormatProvider)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1305)

Check warning on line 875 in csharp-version/src/MarkdownToDocx.Core/OpenXml/OpenXmlDocumentBuilder.cs

View workflow job for this annotation

GitHub Actions / Build and Test

The behavior of 'uint.ToString()' could vary based on the current user's locale settings. Replace this call in 'OpenXmlDocumentBuilder.CreateCodeBlockParagraphProperties(CodeBlockStyle)' with a call to 'uint.ToString(IFormatProvider)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1305)
props.AppendChild(new Indentation { Left = indentTwips, Right = indentTwips });
}

Expand Down Expand Up @@ -1266,7 +1276,7 @@
var indentation = new Indentation { Left = style.LeftIndent };
if (hasPadding && style.PaddingSpace > 0)
{
indentation.Right = (style.PaddingSpace * 20).ToString();

Check warning on line 1279 in csharp-version/src/MarkdownToDocx.Core/OpenXml/OpenXmlDocumentBuilder.cs

View workflow job for this annotation

GitHub Actions / Build and Test

The behavior of 'uint.ToString()' could vary based on the current user's locale settings. Replace this call in 'OpenXmlDocumentBuilder.CreateQuoteParagraphProperties(QuoteStyle)' with a call to 'uint.ToString(IFormatProvider)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1305)

Check warning on line 1279 in csharp-version/src/MarkdownToDocx.Core/OpenXml/OpenXmlDocumentBuilder.cs

View workflow job for this annotation

GitHub Actions / Build and Test

The behavior of 'uint.ToString()' could vary based on the current user's locale settings. Replace this call in 'OpenXmlDocumentBuilder.CreateQuoteParagraphProperties(QuoteStyle)' with a call to 'uint.ToString(IFormatProvider)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1305)
}
props.AppendChild(indentation);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,103 @@ public void ConvertMarkdownTable_WithMirrorMargins_TableShouldUsePercentageWidth
"table width must be percentage-based to prevent margin overflow on narrow pages");
}

[Fact]
public void Build_WithMirrorMargins_ShouldEmitMirrorMarginsInBothSettingsAndSectPr()
{
// Regression test for issue #74: Word for Mac's "Save as PDF" pipeline
// does not honor <w:mirrorMargins/> when it is only placed in settings.xml.
// The element must also be duplicated inside sectPr so verso pages are
// mirrored correctly during PDF export.

// Arrange: PageLayout with MirrorMargins enabled (KDP paperback style).
var pageLayout = new PageLayoutConfig
{
Width = 14.8,
Height = 21.0,
MarginTop = 1.9,
MarginBottom = 1.9,
MarginLeft = 1.30,
MarginRight = 1.30,
MarginGutter = 1.20,
MirrorMargins = true
};

// Act
using var stream = new MemoryStream();
using (var builder = new OpenXmlDocumentBuilder(
stream,
new MarkdownToDocx.Styling.TextDirection.ConfigurableTextDirectionProvider(
new HorizontalTextProvider(), pageLayout)))
{
builder.Save();
}

// Assert: mirrorMargins present in both locations.
stream.Position = 0;
using var doc = WordprocessingDocument.Open(stream, false);

var settings = doc.MainDocumentPart!.DocumentSettingsPart!.Settings;
settings.Elements<MirrorMargins>().Should().HaveCount(1,
"spec-compliant location (CT_Settings) must keep emitting mirrorMargins");

var sectionProps = doc.MainDocumentPart.Document.Body!
.Elements<SectionProperties>().LastOrDefault();
sectionProps.Should().NotBeNull();
// mirrorMargins is not part of CT_SectPr in OOXML schema, so on
// deserialization OpenXml SDK exposes it as an OpenXmlUnknownElement
// (rather than the typed MirrorMargins class). Match by local name to
// confirm the raw XML carries it inside sectPr.
bool sectPrCarriesMirrorMargins = sectionProps!.ChildElements
.Any(e => e.LocalName == "mirrorMargins"
&& e.NamespaceUri == "http://schemas.openxmlformats.org/wordprocessingml/2006/main");
sectPrCarriesMirrorMargins.Should().BeTrue(
"Word for Mac PDF export requires mirrorMargins inside sectPr (issue #74)");
}

[Fact]
public void Build_WithoutMirrorMargins_ShouldNotEmitMirrorMarginsAnywhere()
{
// Arrange: PageLayout with MirrorMargins disabled.
var pageLayout = new PageLayoutConfig
{
Width = 14.8,
Height = 21.0,
MarginTop = 1.9,
MarginBottom = 1.9,
MarginLeft = 1.30,
MarginRight = 1.30,
MirrorMargins = false
};

// Act
using var stream = new MemoryStream();
using (var builder = new OpenXmlDocumentBuilder(
stream,
new MarkdownToDocx.Styling.TextDirection.ConfigurableTextDirectionProvider(
new HorizontalTextProvider(), pageLayout)))
{
builder.Save();
}

// Assert: no mirrorMargins emitted in either location.
stream.Position = 0;
using var doc = WordprocessingDocument.Open(stream, false);

doc.MainDocumentPart!.DocumentSettingsPart.Should().BeNull(
"settings.xml part must not be created when MirrorMargins is false");

var sectionProps = doc.MainDocumentPart.Document.Body!
.Elements<SectionProperties>().LastOrDefault();
sectionProps.Should().NotBeNull();
// Match by local name (OpenXml SDK deserializes unknown sectPr children
// as OpenXmlUnknownElement; see Build_WithMirrorMargins for context).
bool sectPrCarriesMirrorMargins = sectionProps!.ChildElements
.Any(e => e.LocalName == "mirrorMargins"
&& e.NamespaceUri == "http://schemas.openxmlformats.org/wordprocessingml/2006/main");
sectPrCarriesMirrorMargins.Should().BeFalse(
"sectPr must not carry mirrorMargins when feature is disabled");
}

public void Dispose()
{
try
Expand Down
Loading