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
32 changes: 32 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,38 @@ jobs:
name: test-results
path: '**/test-results.trx'

docx-font-tests:
runs-on: windows-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'

- name: Verify required DOCX fonts
shell: pwsh
run: |
$fontDirectory = Join-Path $env:WINDIR 'Fonts'
foreach ($font in @('cambria.ttc', 'cambriab.ttf', 'cambriaz.ttf', 'seguisym.ttf', 'seguiemj.ttf')) {
if (-not (Test-Path -LiteralPath (Join-Path $fontDirectory $font))) {
throw "Required DOCX test font is unavailable: $font"
}
}

- name: Test DOCX rendering with system fonts
run: dotnet test tests/MiniPdf.Tests/MiniPdf.Tests.csproj --configuration Release --filter "FullyQualifiedName~Docx|FullyQualifiedName~SystemFontTheoryAttributeTests" --verbosity normal --logger "trx;LogFileName=docx-font-results.trx"

- name: Upload DOCX test results
uses: actions/upload-artifact@v4
if: always()
with:
name: docx-font-test-results
path: '**/docx-font-results.trx'

ai-security-scan:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
Expand Down
6 changes: 6 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ dotnet build --configuration Release
dotnet test tests/MiniPdf.Tests --configuration Release
```

DOCX tests that measure installed Cambria or Segoe fonts report an explicit skip
when their required fonts are unavailable. CI runs the portable suite on Ubuntu
and a separate DOCX suite on Windows, where it verifies the required fonts
before testing. Install these fonts through a licensed system/Office installation
to run the font-dependent checks locally; do not add font binaries to the repository.

### Rust

Install the stable Rust toolchain, then run:
Expand Down
18 changes: 18 additions & 0 deletions FEATURE_PARITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,24 @@ Word compatibility modes up to 14 align implicit left indents with first-cell
content; modern implicit indents continue to align the table border.
Inline images in table cells honor the paragraph's left, center, or right
alignment in both body and header/footer tables.
Cambria table cells use the same preferred-font advances for wrapping and
row-height calculation, including header/footer tables. Auto-spaced body
paragraphs do not carry the next text-line ascent into a following table.
Large first-paragraph runs advance subsequent content from their rendered
baseline rather than the smaller inherited paragraph-mark baseline.
Cell line height distinguishes explicit paragraph-mark sizing from inherited
defaults. Collapsed table borders contribute half their stroke widths to natural
row heights and text insets; center/bottom alignment uses the remaining content
area. Cambria/Calibri paragraph flow tracks font ascents across text, empty marks,
and inline-image hosts. Auto-spaced image-only paragraphs add proportional font
leading without scaling image height. Hard section starts collapse paragraph
space-before against the closing section's space-after.
Image-only paragraphs reclaim the next text ascent before body tables, while
border-only empty marks reset text-ascent tracking. Courier transitions use
Courier font metrics. Symbol cells account for the Segoe fallback's ascent and
line height when the PDF writer selects that font.
Mixed-format Cambria paragraphs measure each run's bold/italic font advances and
spaces consistently for wrapping, run positioning, alignment, and run shading.

## XLSX Rendering Gaps

Expand Down
10 changes: 8 additions & 2 deletions src/MiniPdf/DocxReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@
var xpath = dataBinding.Attribute(W + "xpath")?.Value;
var prefixMappings = dataBinding.Attribute(W + "prefixMappings")?.Value ?? "";
if (string.IsNullOrEmpty(storeItemID) || string.IsNullOrEmpty(xpath)) return null;
if (!ctx.XmlStores.TryGetValue(storeItemID, out var doc) || doc.Root == null) return null;

Check warning on line 133 in src/MiniPdf/DocxReader.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'key' in 'bool Dictionary<string, XDocument>.TryGetValue(string key, out XDocument value)'.

var nsMgr = new System.Xml.XmlNamespaceManager(new System.Xml.NameTable());
foreach (System.Text.RegularExpressions.Match m in System.Text.RegularExpressions.Regex.Matches(
Expand Down Expand Up @@ -871,7 +871,7 @@
if (string.IsNullOrWhiteSpace(style))
return values;

foreach (var declaration in style.Split(';', StringSplitOptions.RemoveEmptyEntries))

Check warning on line 874 in src/MiniPdf/DocxReader.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 's' in 'string[] NetFxPolyfills.Split(string s, char separator, StringSplitOptions options)'.
{
var separator = declaration.IndexOf(':');
if (separator <= 0)
Expand Down Expand Up @@ -912,6 +912,7 @@
float spacingAfter = -1;
bool spacingAfterExplicit = false;
bool spacingBeforeExplicit = false;
float paragraphMarkFontSize = 0;
float lineSpacing = 0;
bool lineSpacingAbsolute = false;
bool lineSpacingExact = false;
Expand Down Expand Up @@ -1183,7 +1184,10 @@
{
var sz = rPr.Element(W + "sz")?.Attribute(W + "val")?.Value;
if (float.TryParse(sz, out var s))
{
fontSize = s / 2f; // half-points to points
paragraphMarkFontSize = fontSize;
}
// Paragraph-level character spacing
var spacingEl2 = rPr.Element(W + "spacing");
if (spacingEl2 != null && int.TryParse(spacingEl2.Attribute(W + "val")?.Value, out var pcs))
Expand Down Expand Up @@ -1509,7 +1513,8 @@
ListSuff: listSuff,
ListTabStop: listTabStop,
OutlineLevel: ReadOutlineLevel(pPr),
StyleIndentFirstLine: styleIndentFirstLineForListLabel);
StyleIndentFirstLine: styleIndentFirstLineForListLabel,
ParagraphMarkFontSize: paragraphMarkFontSize);
}

private static void EmitPageFieldPlaceholder(string instruction, List<DocxRun> runs, bool bold, bool italic, float fontSize, PdfColor? color)
Expand Down Expand Up @@ -5286,7 +5291,8 @@
// not position the automatic-numbering suffix.
float? ListTabStop = null,
int OutlineLevel = -1,
float StyleIndentFirstLine = 0
float StyleIndentFirstLine = 0,
float ParagraphMarkFontSize = 0
) : DocxElement;

/// <summary>Represents a single border edge. Width=0 is a sentinel for an
Expand Down
Loading
Loading