diff --git a/src/EPPlus.Export.Pdf/ExcelPdf.cs b/src/EPPlus.Export.Pdf/ExcelPdf.cs index 3ec3f38ac..ebdfd3e8f 100644 --- a/src/EPPlus.Export.Pdf/ExcelPdf.cs +++ b/src/EPPlus.Export.Pdf/ExcelPdf.cs @@ -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().Where(t => t.IsHeaderFooter); + //var headerFooterLayouts = pageLayout.ChildObjects.OfType().Where(t => t.IsHeaderFooter); var AdditionalContent = pageLayout.ChildObjects.OfType().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)); @@ -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()) - { - 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"); diff --git a/src/EPPlus.Export.Pdf/Layout/PdfCellContentLayout.cs b/src/EPPlus.Export.Pdf/Layout/PdfCellContentLayout.cs index 1005c3b5d..908fe7368 100644 --- a/src/EPPlus.Export.Pdf/Layout/PdfCellContentLayout.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfCellContentLayout.cs @@ -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.. diff --git a/src/EPPlus.Export.Pdf/Layout/PdfHeaderFooterPart.cs b/src/EPPlus.Export.Pdf/Layout/PdfHeaderFooterPart.cs new file mode 100644 index 000000000..19d3da079 --- /dev/null +++ b/src/EPPlus.Export.Pdf/Layout/PdfHeaderFooterPart.cs @@ -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 Items = new List(); + } +} diff --git a/src/EPPlus.Export.Pdf/Layout/PdfPageLayout.cs b/src/EPPlus.Export.Pdf/Layout/PdfPageLayout.cs index 814ac2c49..b9f07f9ce 100644 --- a/src/EPPlus.Export.Pdf/Layout/PdfPageLayout.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfPageLayout.cs @@ -28,6 +28,8 @@ internal class PdfPageLayout : Transform public double PrintTitleWidth; public double PrintTitleHeight; public bool isCommentsPage = false; + public bool ScaleHeaderFooterWithDocument; + internal List HeaderFooterParts = new List(); internal PdfPageSettings Settings; public PdfPageLayout(double x, double y, double width, double height) : base(x, y, width, height) diff --git a/src/EPPlus.Export.Pdf/Settings/PdfPageSettings.cs b/src/EPPlus.Export.Pdf/Settings/PdfPageSettings.cs index 6f182a9b1..128297a05 100644 --- a/src/EPPlus.Export.Pdf/Settings/PdfPageSettings.cs +++ b/src/EPPlus.Export.Pdf/Settings/PdfPageSettings.cs @@ -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 diff --git a/src/EPPlus/ExcelHeaderFooter.cs b/src/EPPlus/ExcelHeaderFooter.cs index 894ecd352..0288b137d 100644 --- a/src/EPPlus/ExcelHeaderFooter.cs +++ b/src/EPPlus/ExcelHeaderFooter.cs @@ -381,7 +381,7 @@ public bool AlignWithMargins { get { - return GetXmlNodeBool(alignWithMarginsPath); + return GetXmlNodeBool(alignWithMarginsPath, true); } set { diff --git a/src/EPPlus/Export/PdfExport/Layout/PdfLayout.cs b/src/EPPlus/Export/PdfExport/Layout/PdfLayout.cs index 90e6b728a..3b899b6b5 100644 --- a/src/EPPlus/Export/PdfExport/Layout/PdfLayout.cs +++ b/src/EPPlus/Export/PdfExport/Layout/PdfLayout.cs @@ -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; @@ -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; @@ -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, @@ -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); } } } @@ -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); + 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; diff --git a/src/EPPlus/Export/PdfExport/TextMapping/PdfHeaderFooterCollection.cs b/src/EPPlus/Export/PdfExport/TextMapping/PdfHeaderFooterCollection.cs index 130d29f9d..c7204d51f 100644 --- a/src/EPPlus/Export/PdfExport/TextMapping/PdfHeaderFooterCollection.cs +++ b/src/EPPlus/Export/PdfExport/TextMapping/PdfHeaderFooterCollection.cs @@ -157,21 +157,6 @@ public PdfHeaderFooterCollection(PdfPageSettings pageSettings, PdfDictionaries d entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); PdfHeaderFooterEntries.Add(entry); } - - double hfScale = pageSettings.ContentScale; - if (ScaleWithDocument && hfScale != 1d) - { - foreach (var e in PdfHeaderFooterEntries) - { - if (e.Content?.TextFragments != null) - { - foreach (var tf in e.Content.TextFragments) - tf.Font.Size = (float)(tf.Font.Size * hfScale); - } - e.ImageWidth *= hfScale; - e.ImageHeight *= hfScale; - } - } } public PdfHeaderFooter Get(HeaderFooterType type, HeaderFooterSection section, HeaderFooterAlignment alignment)