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
6 changes: 3 additions & 3 deletions src/MiniPdf/DocxReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,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 132 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 @@ -828,7 +828,7 @@
if (string.IsNullOrWhiteSpace(style))
return values;

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

Check warning on line 831 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 @@ -2414,8 +2414,8 @@
return partPath.Substring(0, slashIndex) + "/_rels/" + partPath.Substring(slashIndex + 1) + ".rels";
}

private static byte[]? TryConvertMetafileToPng(byte[] sourceBytes, long widthEmu, long heightEmu,
float cropL = 0, float cropT = 0, float cropR = 0, float cropB = 0)
internal static byte[]? TryConvertMetafileToPng(byte[] sourceBytes, long widthEmu, long heightEmu,
float cropL = 0, float cropT = 0, float cropR = 0, float cropB = 0, int rasterHeight = 512)
{
if (!Compat.IsWindows())
return null;
Expand All @@ -2438,7 +2438,7 @@
? (double)widthEmu / heightEmu
: (meta.Width > 0 && meta.Height > 0 ? (double)meta.Width / meta.Height : 1.0);

var targetHeight = 512;
var targetHeight = rasterHeight;
var targetWidth = (int)Math.Round(targetHeight * aspect);
targetWidth = Compat.Clamp(targetWidth, 32, 4096);
targetHeight = Compat.Clamp(targetHeight, 32, 4096);
Expand Down
209 changes: 189 additions & 20 deletions src/MiniPdf/ExcelReader.cs

Large diffs are not rendered by default.

193 changes: 182 additions & 11 deletions src/MiniPdf/ExcelToPdfConverter.cs

Large diffs are not rendered by default.

79 changes: 79 additions & 0 deletions src/MiniPdf/PdfPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ internal sealed class PdfPage
private readonly List<PdfRectBlock> _overlayRects = [];
private readonly List<PdfLineBlock> _overlayLines = [];
private readonly List<PdfTextBlock> _overlayTexts = [];
private float? _worksheetContentTop;
private float? _worksheetContentBottom;
private bool _hasLegacyVmlImage;

/// <summary>
/// Page width in points.
Expand Down Expand Up @@ -270,6 +273,82 @@ internal PdfPage AddOverlayText(string text, float x, float y, float fontSize =
return this;
}

internal void CenterContentVertically(float printableBottom, float printableTop)
{
var bounds = new List<(float Bottom, float Top)>();
bounds.AddRange(_textBlocks.Where(block => !block.Hidden)
.Select(block => (block.Y - block.FontSize * 0.3f, block.Y + block.FontSize)));
bounds.AddRange(_imageBlocks.Select(block => (block.Y, block.Y + block.RenderHeight)));
bounds.AddRange(_rectBlocks.Select(block => (block.Y, block.Y + block.Height)));
bounds.AddRange(_ellipseBlocks.Select(block => (block.Y, block.Y + block.Height)));
bounds.AddRange(_polygonBlocks.Select(block =>
(block.Points.Min(point => point.Y), block.Points.Max(point => point.Y))));
bounds.AddRange(_lineBlocks.Select(block =>
(Math.Min(block.Y1, block.Y2), Math.Max(block.Y1, block.Y2))));
if (bounds.Count == 0)
return;

var contentBottom = bounds.Min(bound => bound.Bottom);
var contentTop = bounds.Max(bound => bound.Top);
var printableHeight = printableTop - printableBottom;
var contentHeight = contentTop - contentBottom;
if (contentHeight >= printableHeight && !_hasLegacyVmlImage
&& _worksheetContentTop is { } worksheetTop
&& _worksheetContentBottom is { } worksheetBottom)
{
contentTop = worksheetTop;
contentBottom = worksheetBottom;
contentHeight = contentTop - contentBottom;
}
if (contentHeight >= printableHeight)
return;

var offset = (printableBottom + printableTop - contentBottom - contentTop) / 2f;
if (Math.Abs(offset) < 0.01f)
return;

for (var i = 0; i < _textBlocks.Count; i++)
_textBlocks[i] = _textBlocks[i].TranslateY(offset);
for (var i = 0; i < _imageBlocks.Count; i++)
_imageBlocks[i] = _imageBlocks[i] with { Y = _imageBlocks[i].Y + offset };
for (var i = 0; i < _rectBlocks.Count; i++)
_rectBlocks[i] = _rectBlocks[i] with { Y = _rectBlocks[i].Y + offset };
for (var i = 0; i < _ellipseBlocks.Count; i++)
_ellipseBlocks[i] = _ellipseBlocks[i] with { Y = _ellipseBlocks[i].Y + offset };
for (var i = 0; i < _polygonBlocks.Count; i++)
_polygonBlocks[i] = _polygonBlocks[i] with
{
Points = _polygonBlocks[i].Points.Select(point => point with { Y = point.Y + offset }).ToList(),
Subpaths = _polygonBlocks[i].Subpaths?.Select(path =>
path.Select(point => point with { Y = point.Y + offset }).ToList()).ToList()
};
for (var i = 0; i < _lineBlocks.Count; i++)
_lineBlocks[i] = _lineBlocks[i] with
{
Y1 = _lineBlocks[i].Y1 + offset,
Y2 = _lineBlocks[i].Y2 + offset
};
}

internal void IncludeWorksheetVerticalRange(float top, float bottom)
{
_worksheetContentTop = _worksheetContentTop is { } existingTop
? Math.Max(existingTop, top)
: top;
_worksheetContentBottom = _worksheetContentBottom is { } existingBottom
? Math.Min(existingBottom, bottom)
: bottom;
}

internal float GetWorksheetVerticalCenterOffset(float printableBottom, float printableTop)
{
if (_worksheetContentTop is not { } contentTop || _worksheetContentBottom is not { } contentBottom)
return 0f;
return Math.Max(0f, (printableTop - printableBottom - (contentTop - contentBottom)) / 2f);
}

internal void MarkLegacyVmlImage() => _hasLegacyVmlImage = true;

/// <summary>
/// Adds text that automatically wraps within the specified region.
/// Text flows from top to bottom, left to right within the given bounds.
Expand Down
9 changes: 9 additions & 0 deletions src/MiniPdf/PdfTextBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,13 @@ internal PdfTextBlock(string text, float x, float y, float fontSize, PdfColor? c
UnderlineWidth = underlineWidth;
Hidden = hidden;
}

internal PdfTextBlock TranslateY(float offset)
{
var clipRect = ClipRect is { } clip
? (clip.X, clip.Y + offset, clip.Width, clip.Height)
: ((float, float, float, float)?)null;
return new PdfTextBlock(Text, X, Y + offset, FontSize, Color, clipRect, MaxWidth, Bold, Italic,
Underline, CharSpacing, WordSpacing, PreferredFontName, UnderlineWidth, Strikethrough, Hidden);
}
}
4 changes: 4 additions & 0 deletions src/MiniPdf/PdfWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2321,6 +2321,10 @@ private static string NormalizeFontName(string? name)
if (name.Contains("宋体", StringComparison.Ordinal)
|| name.Contains("simsun", StringComparison.OrdinalIgnoreCase))
return "simsun";
if (name.Contains("华文楷体", StringComparison.Ordinal)
|| name.Contains("華文楷體", StringComparison.Ordinal)
|| name.Contains("stkaiti", StringComparison.OrdinalIgnoreCase))
return "stkaiti";
if (name.Contains("楷体", StringComparison.Ordinal)
|| name.Contains("楷體", StringComparison.Ordinal)
|| name.Contains("kaiti", StringComparison.OrdinalIgnoreCase)
Expand Down
Binary file not shown.
231 changes: 118 additions & 113 deletions tests/Issue_Files/reports_xlsx/comparison_manifest.json
Original file line number Diff line number Diff line change
@@ -1,114 +1,119 @@
{
"cases": [
{
"name": "Academic Achievement Summary Table",
"case_id": "Academic Achievement Summary Table",
"format": "xlsx"
},
{
"name": "AcademicAchievement_temp",
"case_id": "AcademicAchievement_temp",
"format": "xlsx"
},
{
"name": "Business expense budget1",
"case_id": "Business expense budget1",
"format": "xlsx"
},
{
"name": "Business expenses budget2",
"case_id": "Business expenses budget2",
"format": "xlsx"
},
{
"name": "Business plan checklist with SWOT analysis1",
"case_id": "Business plan checklist with SWOT analysis1",
"format": "xlsx"
},
{
"name": "Event budget1",
"case_id": "Event budget1",
"format": "xlsx"
},
{
"name": "Expense report basic1",
"case_id": "Expense report basic1",
"format": "xlsx"
},
{
"name": "Grocery list1",
"case_id": "Grocery list1",
"format": "xlsx"
},
{
"name": "payroll-calculator_f",
"case_id": "payroll-calculator_f",
"format": "xlsx"
},
{
"name": "PO_anonymized",
"case_id": "PO_anonymized",
"format": "xlsx"
},
{
"name": "Simple invoice1",
"case_id": "Simple invoice1",
"format": "xlsx"
},
{
"name": "Small business cash flow forecast1",
"case_id": "Small business cash flow forecast1",
"format": "xlsx"
},
{
"name": "Wedding_timeline_planner1_copy",
"case_id": "Wedding_timeline_planner1_copy",
"format": "xlsx"
},
{
"name": "Weekly schedule planner1",
"case_id": "Weekly schedule planner1",
"format": "xlsx"
},
{
"name": "XlsxIssue75",
"case_id": "XlsxIssue75",
"format": "xlsx"
},
{
"name": "XlsxIssue77_MergedCellAlignment",
"case_id": "XlsxIssue77_MergedCellAlignment",
"format": "xlsx"
},
{
"name": "XlsxIssue77_Template1",
"case_id": "XlsxIssue77_Template1",
"format": "xlsx"
},
{
"name": "XlsxIssue77_Template2_Workaround",
"case_id": "XlsxIssue77_Template2_Workaround",
"format": "xlsx"
},
{
"name": "XlsxIssue81_LayoutOptions",
"case_id": "XlsxIssue81_LayoutOptions",
"format": "xlsx"
},
{
"name": "XlsxIssue82_5mb",
"case_id": "XlsxIssue82_5mb",
"format": "xlsx"
},
{
"name": "XlsxIssue82_SampleTestData5mb",
"case_id": "XlsxIssue82_SampleTestData5mb",
"format": "xlsx"
},
{
"name": "XlsxIssue82_WideTable",
"case_id": "XlsxIssue82_WideTable",
"format": "xlsx"
}
]
{
"cases": [
{
"name": "Academic Achievement Summary Table",
"case_id": "Academic Achievement Summary Table",
"format": "xlsx"
},
{
"name": "AcademicAchievement_temp",
"case_id": "AcademicAchievement_temp",
"format": "xlsx"
},
{
"name": "Business expense budget1",
"case_id": "Business expense budget1",
"format": "xlsx"
},
{
"name": "Business expenses budget2",
"case_id": "Business expenses budget2",
"format": "xlsx"
},
{
"name": "Business plan checklist with SWOT analysis1",
"case_id": "Business plan checklist with SWOT analysis1",
"format": "xlsx"
},
{
"name": "Event budget1",
"case_id": "Event budget1",
"format": "xlsx"
},
{
"name": "Expense report basic1",
"case_id": "Expense report basic1",
"format": "xlsx"
},
{
"name": "Grocery list1",
"case_id": "Grocery list1",
"format": "xlsx"
},
{
"name": "Issue202609031340",
"case_id": "Issue202609031340",
"format": "xlsx"
},
{
"name": "payroll-calculator_f",
"case_id": "payroll-calculator_f",
"format": "xlsx"
},
{
"name": "PO_anonymized",
"case_id": "PO_anonymized",
"format": "xlsx"
},
{
"name": "Simple invoice1",
"case_id": "Simple invoice1",
"format": "xlsx"
},
{
"name": "Small business cash flow forecast1",
"case_id": "Small business cash flow forecast1",
"format": "xlsx"
},
{
"name": "Wedding_timeline_planner1_copy",
"case_id": "Wedding_timeline_planner1_copy",
"format": "xlsx"
},
{
"name": "Weekly schedule planner1",
"case_id": "Weekly schedule planner1",
"format": "xlsx"
},
{
"name": "XlsxIssue75",
"case_id": "XlsxIssue75",
"format": "xlsx"
},
{
"name": "XlsxIssue77_MergedCellAlignment",
"case_id": "XlsxIssue77_MergedCellAlignment",
"format": "xlsx"
},
{
"name": "XlsxIssue77_Template1",
"case_id": "XlsxIssue77_Template1",
"format": "xlsx"
},
{
"name": "XlsxIssue77_Template2_Workaround",
"case_id": "XlsxIssue77_Template2_Workaround",
"format": "xlsx"
},
{
"name": "XlsxIssue81_LayoutOptions",
"case_id": "XlsxIssue81_LayoutOptions",
"format": "xlsx"
},
{
"name": "XlsxIssue82_5mb",
"case_id": "XlsxIssue82_5mb",
"format": "xlsx"
},
{
"name": "XlsxIssue82_SampleTestData5mb",
"case_id": "XlsxIssue82_SampleTestData5mb",
"format": "xlsx"
},
{
"name": "XlsxIssue82_WideTable",
"case_id": "XlsxIssue82_WideTable",
"format": "xlsx"
}
]
}
Loading
Loading