Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
37 changes: 26 additions & 11 deletions src/EPPlus.Export.Pdf/ExcelPdf.cs
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ private void AddContent(PdfPageLayout pageLayout, PdfPage page)
!(t is PdfCellLayout cc && (cc.IsHeading || cc.IsPrintTitle)) &&
!(t is PdfCellContentLayout ccl && (ccl.IsHeaderFooter || ccl.IsHeading || ccl.IsPrintTitle || ccl.IsAddedContent)) &&
!(t is PdfCellBorderLayout cbl && cbl.IsPrintTitle)).ToList();
var headerFooterLayouts = pageLayout.ChildObjects.OfType<PdfCellContentLayout>().Where(t => t.IsHeaderFooter);
//var headerFooterLayouts = pageLayout.ChildObjects.OfType<PdfCellContentLayout>().Where(t => t.IsHeaderFooter);
var AdditionalContent = pageLayout.ChildObjects.OfType<PdfCellContentLayout>().Where(t => t.IsAddedContent && !(pageSettings.AdditionalContent != "null"));
var headingLayouts = pageLayout.ChildObjects.Where(t => (t is PdfCellLayout cl && cl.IsHeading) || (t is PdfCellContentLayout ccl && ccl.IsHeading));
var printTitleLayouts = pageLayout.ChildObjects.Where(t => (t is PdfCellLayout pl && pl.IsPrintTitle) || (t is PdfCellContentLayout pcl && pcl.IsPrintTitle) || (t is PdfCellBorderLayout pbl && pbl.IsPrintTitle));
Expand Down Expand Up @@ -246,17 +246,32 @@ private void AddContent(PdfPageLayout pageLayout, PdfPage page)
contentStream.AddCommand("Q");
}
//Add header and footer.
foreach (var hf in headerFooterLayouts)
foreach (var part in pageLayout.HeaderFooterParts)
{
contentStream.AddCellContentLayout(hf, _dictionaries, pageSettings);
}
//Add images
foreach (PdfImageLayout image in pageLayout.ChildObjects.OfType<PdfImageLayout>())
{
if (!image.IsHeaderFooter) continue;
var imageResource = _dictionaries.AddImage(image.ImageBytes);
contentStream.AddImage(imageResource.Label, image.LocalPosition.X, image.LocalPosition.Y, image.Size.X, image.Size.Y);
if (PdfImageXObject.ProducesSoftMask(image.ImageBytes)) page.HasTransparency = true;
bool scaleHf = pageLayout.ScaleHeaderFooterWithDocument;
if (scaleHf)
{
contentStream.AddCommand("q");
contentStream.AddContentScale(contentScale, part.ScalePivot.X, part.ScalePivot.Y);
}

foreach (var item in part.Items)
{
switch (item)
{
case PdfCellContentLayout text:
contentStream.AddCellContentLayout(text, _dictionaries, pageSettings);
break;
case PdfImageLayout image:
var imageResource = _dictionaries.AddImage(image.ImageBytes);
contentStream.AddImage(imageResource.Label, image.LocalPosition.X, image.LocalPosition.Y, image.Size.X, image.Size.Y);
if (PdfImageXObject.ProducesSoftMask(image.ImageBytes)) page.HasTransparency = true;
break;
}
}

if (scaleHf)
contentStream.AddCommand("Q");
}
//Add print titles
contentStream.AddCommand("q");
Expand Down
2 changes: 2 additions & 0 deletions src/EPPlus.Export.Pdf/Layout/PdfCellContentLayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ internal class PdfCellContentLayout : Transform
public TextLineCollection TextLines;
public double LeftTextSpillLength = 0d;
public double RightTextSpillLength = 0d;
public double ScaleAnchorX;
public double ScaleAnchorY;
private double bottomMargin = 3.5d; //Guessed number
private double rightMargin = 1.4d; //I guessed this one too..

Expand Down
24 changes: 24 additions & 0 deletions src/EPPlus.Export.Pdf/Layout/PdfHeaderFooterPart.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*************************************************************************************************
Required Notice: Copyright (C) EPPlus Software AB.
This software is licensed under PolyForm Noncommercial License 1.0.0
and may only be used for noncommercial purposes
https://polyformproject.org/licenses/noncommercial/1.0.0/

A commercial license to use this software can be purchased at https://epplussoftware.com
*************************************************************************************************
Date Author Change
*************************************************************************************************
27/11/2025 EPPlus Software AB EPPlus 9
*************************************************************************************************/
using EPPlus.Graphics;
using EPPlus.Graphics.Geometry;
using System.Collections.Generic;

namespace EPPlus.Export.Pdf.Layout
{
internal sealed class PdfHeaderFooterPart
{
public Vector2 ScalePivot;
public readonly List<Transform> Items = new List<Transform>();
}
}
2 changes: 2 additions & 0 deletions src/EPPlus.Export.Pdf/Layout/PdfPageLayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ internal class PdfPageLayout : Transform
public double PrintTitleWidth;
public double PrintTitleHeight;
public bool isCommentsPage = false;
public bool ScaleHeaderFooterWithDocument;
internal List<PdfHeaderFooterPart> HeaderFooterParts = new List<PdfHeaderFooterPart>();
internal PdfPageSettings Settings;
public PdfPageLayout(double x, double y, double width, double height)
: base(x, y, width, height)
Expand Down
2 changes: 2 additions & 0 deletions src/EPPlus.Export.Pdf/Settings/PdfPageSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ public PdfScaling Scaling
private double _resolvedFitScale = 1d;
internal void SetResolvedFitScale(double s) { _resolvedFitScale = s; }

internal bool ScaleHeaderWithDocument { get; set; }

internal double ContentScale
{
get
Expand Down
2 changes: 1 addition & 1 deletion src/EPPlus/ExcelHeaderFooter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ public bool AlignWithMargins
{
get
{
return GetXmlNodeBool(alignWithMarginsPath);
return GetXmlNodeBool(alignWithMarginsPath, true);
}
set
{
Expand Down
95 changes: 65 additions & 30 deletions src/EPPlus/Export/PdfExport/Layout/PdfLayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Date Author Change
using EPPlus.Fonts.OpenType.Integration;
using EPPlus.Fonts.OpenType.Integration.DataHolders;
using EPPlus.Graphics;
using EPPlus.Graphics.Geometry;
using EPPlus.Graphics.Units;
using OfficeOpenXml.Drawing;
using OfficeOpenXml.Export.PdfExport.Data;
Expand Down Expand Up @@ -244,82 +245,86 @@ internal static Transform GetCatalog(int firstPageNumber, PdfDictionaries dictio
}
if (page.HeaderFooters != null)
{
pageLayout.ScaleHeaderFooterWithDocument = page.HeaderFooters.ScaleWithDocument;
var hfType = page.HeaderFooters.GetPageType(physicalPageIndex);
var leftH = page.HeaderFooters.Get(hfType, HeaderFooterSection.Header, HeaderFooterAlignment.Left);
bool alignHf = page.HeaderFooters.AlignWithMargins;
var (hfBoxLeft, hfBoxWidth) = HeaderFooterBox(pageSettings, alignHf);
if (leftH != null && !leftH.HasImage && ShapeHeaderFooterText(pageSettings, dictionaries, leftH, displayedPageNumber, totalPages))
{
var ascent = leftH.Content.TextLines[0].LargestAscent;
var hfx = pageSettings.Margins.LeftPu;
var hfx = hfBoxLeft;
var hfy = pageSettings.PageSize.HeightPu - pageSettings.Margins.HeaderPu - ascent;
var text = new PdfCellContentLayout(pageSettings, dictionaries, leftH, hfx, hfy, 0, 0);
text.ScaleAnchorX = hfx; // left edge
text.ScaleAnchorY = hfy + ascent;
text.Name = "LeftHeader";
text.IsHeaderFooter = true;
text.GidsAndCharMap(dictionaries);
pageLayout.AddChild(text);
AddHeaderFooterTextPart(pageLayout, dictionaries, text, HeaderFooterSection.Header, HeaderFooterAlignment.Left, pageSettings, alignHf);
}
var centerH = page.HeaderFooters.Get(hfType, HeaderFooterSection.Header, HeaderFooterAlignment.Center);
if (centerH != null && !centerH.HasImage && ShapeHeaderFooterText(pageSettings, dictionaries, centerH, displayedPageNumber, totalPages))
{
var ascent = centerH.Content.TextLines[0].LargestAscent;
var hfx = pageSettings.Margins.LeftPu;
var hfx = hfBoxLeft;
var hfy = pageSettings.PageSize.HeightPu - pageSettings.Margins.HeaderPu - ascent;
var hfWidth = pageSettings.PageSize.WidthPu - pageSettings.Margins.LeftPu - pageSettings.Margins.RightPu;
var hfWidth = hfBoxWidth;
var text = new PdfCellContentLayout(pageSettings, dictionaries, centerH, hfx, hfy, hfWidth, 0);
text.ScaleAnchorX = hfx + hfWidth / 2d;
text.ScaleAnchorY = hfy + ascent;
text.Name = "CenterHeader";
text.IsHeaderFooter = true;
text.GidsAndCharMap(dictionaries);
pageLayout.AddChild(text);
AddHeaderFooterTextPart(pageLayout, dictionaries, text, HeaderFooterSection.Header, HeaderFooterAlignment.Center, pageSettings, alignHf);
}
var rightH = page.HeaderFooters.Get(hfType, HeaderFooterSection.Header, HeaderFooterAlignment.Right);
if (rightH != null && !rightH.HasImage && ShapeHeaderFooterText(pageSettings, dictionaries, rightH, displayedPageNumber, totalPages))
{
var ascent = rightH.Content.TextLines[0].LargestAscent;
var hfx = pageSettings.PageSize.WidthPu - pageSettings.Margins.RightPu;
var hfx = hfBoxLeft + hfBoxWidth;
var hfy = pageSettings.PageSize.HeightPu - pageSettings.Margins.HeaderPu - ascent;
var text = new PdfCellContentLayout(pageSettings, dictionaries, rightH, hfx, hfy, 0, 0);
text.ScaleAnchorX = hfx;
text.ScaleAnchorY = hfy + ascent;
text.Name = "RightHeader";
text.IsHeaderFooter = true;
text.GidsAndCharMap(dictionaries);
pageLayout.AddChild(text);
AddHeaderFooterTextPart(pageLayout, dictionaries, text, HeaderFooterSection.Header, HeaderFooterAlignment.Right, pageSettings, alignHf);
}
var leftF = page.HeaderFooters.Get(hfType, HeaderFooterSection.Footer, HeaderFooterAlignment.Left);
if (leftF != null && !leftF.HasImage && ShapeHeaderFooterText(pageSettings, dictionaries, leftF, displayedPageNumber, totalPages))
{
int last = leftF.Content.TextLines.Count - 1;
var descent = leftF.Content.TextLines[last].LargestDescent;
var hfx = pageSettings.Margins.LeftPu;
var hfx = hfBoxLeft;
var hfy = pageSettings.Margins.FooterPu + descent;
var text = new PdfCellContentLayout(pageSettings, dictionaries, leftF, hfx, hfy, 0, 0);
text.ScaleAnchorX = hfx;
text.ScaleAnchorY = hfy - descent;
text.Name = "LeftFooter";
text.IsHeaderFooter = true;
text.GidsAndCharMap(dictionaries);
pageLayout.AddChild(text);
AddHeaderFooterTextPart(pageLayout, dictionaries, text, HeaderFooterSection.Footer, HeaderFooterAlignment.Left, pageSettings, alignHf);
}
var centerF = page.HeaderFooters.Get(hfType, HeaderFooterSection.Footer, HeaderFooterAlignment.Center);
if (centerF != null && !centerF.HasImage && ShapeHeaderFooterText(pageSettings, dictionaries, centerF, displayedPageNumber, totalPages))
{
int last = centerF.Content.TextLines.Count - 1;
var descent = centerF.Content.TextLines[last].LargestDescent;
var hfx = pageSettings.PageSize.WidthPu / 2d;
var hfx = hfBoxLeft;
var hfy = pageSettings.Margins.FooterPu + descent;
var text = new PdfCellContentLayout(pageSettings, dictionaries, centerF, hfx, hfy, 0, 0);
var hfWidth = hfBoxWidth;
var text = new PdfCellContentLayout(pageSettings, dictionaries, centerF, hfx, hfy, hfWidth, 0);
text.ScaleAnchorX = hfx + hfWidth / 2d;
text.ScaleAnchorY = hfy - descent;
text.Name = "CenterFooter";
text.IsHeaderFooter = true;
text.GidsAndCharMap(dictionaries);
pageLayout.AddChild(text);
AddHeaderFooterTextPart(pageLayout, dictionaries, text, HeaderFooterSection.Footer, HeaderFooterAlignment.Center, pageSettings, alignHf);
}
var rightF = page.HeaderFooters.Get(hfType, HeaderFooterSection.Footer, HeaderFooterAlignment.Right);
if (rightF != null && !rightF.HasImage && ShapeHeaderFooterText(pageSettings, dictionaries, rightF, displayedPageNumber, totalPages))
{
int last = rightF.Content.TextLines.Count - 1;
var descent = rightF.Content.TextLines[last].LargestDescent;
var hfx = pageSettings.PageSize.WidthPu - pageSettings.Margins.RightPu;
var hfx = hfBoxLeft + hfBoxWidth;
var hfy = pageSettings.Margins.FooterPu + descent;
var text = new PdfCellContentLayout(pageSettings, dictionaries, rightF, hfx, hfy, 0, 0);
text.ScaleAnchorX = hfx;
text.ScaleAnchorY = hfy - descent;
text.Name = "RightFooter";
text.IsHeaderFooter = true;
text.GidsAndCharMap(dictionaries);
pageLayout.AddChild(text);
AddHeaderFooterTextPart(pageLayout, dictionaries, text, HeaderFooterSection.Footer, HeaderFooterAlignment.Right, pageSettings, alignHf);
}
double hfContentLeft = pageSettings.Margins.LeftPu;
double hfContentWidth = pageSettings.PageSize.WidthPu - pageSettings.Margins.LeftPu - pageSettings.Margins.RightPu;
Expand Down Expand Up @@ -372,17 +377,17 @@ internal static Transform GetCatalog(int firstPageNumber, PdfDictionaries dictio
? pageSettings.PageSize.HeightPu - pageSettings.Margins.HeaderPu - ascent
: pageSettings.Margins.FooterPu + descent;
double textY = System.Math.Min(naturalBaseline, imgBottom + descent);

var part = new PdfHeaderFooterPart { ScalePivot = HeaderFooterPivot(hfSection, hfAlign, pageSettings, alignHf) };
double cursor = runStart;
if (beforeRun != null)
{
pageLayout.AddChild(PlaceHeaderFooterRun(pageSettings, dictionaries, beforeRun, cursor, textY));
part.Items.Add(PlaceHeaderFooterRun(pageSettings, dictionaries, beforeRun, cursor, textY));
}
cursor += beforeWidth;

if (canEmbed)
{
pageLayout.AddChild(new PdfImageLayout(cursor, imgTop, imgW, imgH)
part.Items.Add(new PdfImageLayout(cursor, imgTop, imgW, imgH)
{
ImageBytes = hf.ImageBytes,
IsHeaderFooter = true,
Expand All @@ -392,8 +397,10 @@ internal static Transform GetCatalog(int firstPageNumber, PdfDictionaries dictio
cursor += imgW;
if (afterRun != null)
{
pageLayout.AddChild(PlaceHeaderFooterRun(pageSettings, dictionaries, afterRun, cursor, textY));
part.Items.Add(PlaceHeaderFooterRun(pageSettings, dictionaries, afterRun, cursor, textY));
}
if (part.Items.Count > 0)
pageLayout.HeaderFooterParts.Add(part);
}
}
}
Expand Down Expand Up @@ -1746,6 +1753,34 @@ private static bool ShapeHeaderFooterText(PdfPageSettings pageSettings, PdfDicti
return hf.Content != null && hf.Content.TextLines != null && hf.Content.TextLines.Count > 0;
}

private static Vector2 HeaderFooterPivot(HeaderFooterSection section, HeaderFooterAlignment align, PdfPageSettings ps, bool alignWithMargins)
{
var (left, width) = HeaderFooterBox(ps, alignWithMargins);
Comment thread
swmal marked this conversation as resolved.
double x = align == HeaderFooterAlignment.Left ? left
: align == HeaderFooterAlignment.Right ? left + width
: left + width / 2d;
double y = section == HeaderFooterSection.Header
? ps.PageSize.HeightPu - ps.Margins.HeaderPu
: ps.Margins.FooterPu;
return new Vector2(x, y);
}
private static (double Left, double Width) HeaderFooterBox(PdfPageSettings ps, bool alignWithMargins)
{
if (alignWithMargins)
return (ps.Margins.LeftPu, ps.PageSize.WidthPu - ps.Margins.LeftPu - ps.Margins.RightPu);

double d = UnitConversion.MmToPoints(19.05);
return (d, ps.PageSize.WidthPu - 2d * d);
}
private static void AddHeaderFooterTextPart(PdfPageLayout pageLayout, PdfDictionaries dictionaries, PdfCellContentLayout text, HeaderFooterSection section, HeaderFooterAlignment align, PdfPageSettings ps, bool alignWithMargins)
{
text.IsHeaderFooter = true;
text.GidsAndCharMap(dictionaries);
var part = new PdfHeaderFooterPart { ScalePivot = HeaderFooterPivot(section, align, ps, alignWithMargins) };
part.Items.Add(text);
pageLayout.HeaderFooterParts.Add(part);
}

private static void ApplyPageNumbers(PdfHeaderFooter hf, int pageNumber, int totalPages)
{
if (hf == null) return;
Expand Down
Loading
Loading