From 9a2d6764e19e829925d3169233a337ad740ac201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Wed, 27 May 2026 15:48:35 +0200 Subject: [PATCH 01/49] Added headings back --- src/EPPlus.Export.Pdf.Tests/PdfTests.cs | 2 +- .../PdfCatalog/PdfGridlinesLayout.cs | 8 +- src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs | 170 +++++++++++++++++- 3 files changed, 166 insertions(+), 14 deletions(-) diff --git a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs index e993b6e7e6..cf46495cb2 100644 --- a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs +++ b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs @@ -196,7 +196,7 @@ public void CalculatePages() range.TotalWidth = 800; range.TotalHeight = 1600; - var result = PdfLayout.GetNumberOfPages(pageSettings, pws, range); + var result = PdfLayout.GetNumberOfPages(pageSettings, pws, ref range); } [TestMethod] diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfGridlinesLayout.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfGridlinesLayout.cs index f2e8976db9..a069ad8936 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfGridlinesLayout.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfGridlinesLayout.cs @@ -41,7 +41,7 @@ public static void AddGridLines(PdfPageSettings pageSettings, Page page, PdfPage // colX[ci] = X of left edge of column ci (0-based within page). // colX[colCount] = X of right edge of last column. var colX = new double[colCount + 1]; - colX[0] = pageSettings.ContentBounds.Left; + colX[0] = pageSettings.ContentBounds.Left + page.HeadingWidth; for (int ci = 0; ci < colCount; ci++) { var cell = page.Map[page.FromRow, page.FromColumn + ci]; @@ -52,7 +52,7 @@ public static void AddGridLines(PdfPageSettings pageSettings, Page page, PdfPage // rowY[rowCount] = Y of bottom edge of last row. // Y decreases downward (PDF coordinate system used throughout GetCatalog). var rowY = new double[rowCount + 1]; - rowY[0] = pageSettings.ContentBounds.Top; + rowY[0] = pageSettings.ContentBounds.Top - page.HeadingHeight; for (int ri = 0; ri < rowCount; ri++) { @@ -63,9 +63,9 @@ public static void AddGridLines(PdfPageSettings pageSettings, Page page, PdfPage // Always computed so BorderLines is available for margin clipping regardless of // whether ShowGridLines is on. When borderOnly is true we stop here. - double frameLeft = colX[0]; + double frameLeft = pageSettings.ContentBounds.Left; //colX[0]; double frameRight = colX[colCount]; - double frameTop = rowY[0]; + double frameTop = pageSettings.ContentBounds.Top; //rowY[0]; double frameBottom = rowY[rowCount]; pageLayout.BorderLines.Add(new GridLine(frameLeft, frameTop, frameRight, frameTop)); diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs index d81a303e18..c1582cec78 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs @@ -1,8 +1,10 @@ using EPPlus.Export.Pdf.PdfLayout; using EPPlus.Export.Pdf.PdfResources; using EPPlus.Export.Pdf.PdfSettings; +using EPPlus.Fonts.OpenType.Integration; using EPPlus.Graphics; using OfficeOpenXml; +using OfficeOpenXml.Interfaces.Drawing.Text; using OfficeOpenXml.Style; using System; using System.Collections.Generic; @@ -34,6 +36,10 @@ internal struct Page public Dictionary MergedCells; public double[] RowHeights; + + public double HeadingWidth; + public double HeadingHeight; + } internal struct Pages @@ -42,6 +48,9 @@ internal struct Pages public int Width; public int Height; public bool IsCommentsPage; + public string HeadingFontName; + public float HeadingFontSize; + public ExcelFill HeadingFill; public int Count { get { return Width * Height; } @@ -85,8 +94,13 @@ internal static Transform GetCatalog(PdfPageSettings pageSettings, PdfDictionari //var drawnMergedCellsText = new HashSet(); //PdfContentLayout contentLayout = new PdfContentLayout(0d, 0d, pageSettings.ContentBounds); //pageLayout.AddChild(contentLayout); - double y = pageSettings.ContentBounds.Top; - double x = pageSettings.ContentBounds.Left; + double contentStartX = pageSettings.ContentBounds.Left + page.HeadingWidth; + double contentStartY = pageSettings.ContentBounds.Top - page.HeadingHeight; + if (pageSettings.ShowHeadings && !pdfPages[i].IsCommentsPage) + AddHeadingCells(pageSettings, dictionaries, page, pageLayout, contentStartX, contentStartY, page.HeadingWidth, page.HeadingHeight, pdfPages[i].HeadingFontName, pdfPages[i].HeadingFontSize, pdfPages[i].HeadingFill); + + double y = contentStartY;//pageSettings.ContentBounds.Top; + double x = contentStartX;//pageSettings.ContentBounds.Left; //create cells & headings if exsists for (int row = pages[j].FromRow; row <= pages[j].ToRow; row++) { @@ -174,7 +188,7 @@ internal static Transform GetCatalog(PdfPageSettings pageSettings, PdfDictionari x += map.ColumnWidth; } y -= rowHeight; - x = pageSettings.ContentBounds.Left; + x = contentStartX; //pageSettings.ContentBounds.Left; } if (page.HeaderFooters != null) @@ -359,6 +373,133 @@ private static bool HasBorder(PdfCellStyle cellStyle) private static bool HasDiagonalBorder(PdfCellStyle style) => style?.Diagonal != null && style.Diagonal.Style != ExcelBorderStyle.None; + private static void AddHeadingCells(PdfPageSettings pageSettings, PdfDictionaries dictionaries, Page page, PdfPageLayout pageLayout, double contentStartX, double contentStartY, double headingWidth, double headingHeight, string fontName, float fontSize, ExcelFill fill) + { + // An empty style gives a transparent/white fill with no border. + // TODO: replace with a proper light-grey fill once PdfCellStyle supports + // creating standalone fills without a backing ExcelFillXml object. + var headingStyle = new PdfCellStyle(); + headingStyle.xfFill = fill; + + // ── Corner cell (top-left, where the two strips intersect) ────────────────────────── + var cornerFill = new PdfCellLayout(dictionaries, headingStyle, + pageSettings.ContentBounds.Left, pageSettings.ContentBounds.Top, + headingWidth, headingHeight); + cornerFill.Name = "Heading_Corner"; + cornerFill.UpdateShadingPositionMatrix(pageSettings); + pageLayout.AddChild(cornerFill); + + // ── Column headings (one per data column, spanning the top strip) ─────────────────── + double x = contentStartX; + for (int col = page.FromColumn; col <= page.ToColumn; col++) + { + var mapCell = page.Map[page.FromRow, col]; + double colWidth = mapCell?.ColumnWidth ?? 0d; + if (colWidth == 0d) { x += colWidth; continue; } // hidden column — skip + + string colLetter = ExcelCellBase.GetColumnLetter(col); + + var colFill = new PdfCellLayout(dictionaries, headingStyle, + x, pageSettings.ContentBounds.Top, colWidth, headingHeight); + colFill.Name = "Heading_Col_" + colLetter; + colFill.UpdateShadingPositionMatrix(pageSettings); + pageLayout.AddChild(colFill); + + var colCell = CreateHeadingPdfCell(pageSettings, dictionaries, + colLetter, ExcelHorizontalAlignment.Center, + colWidth, headingHeight, fontName, fontSize); + + if (colCell.TextLines != null && colCell.TextLines.Count > 0) + { + var info = new MergedCellDrawInfo { X = x, Y = pageSettings.ContentBounds.Top, Width = colWidth, Height = headingHeight }; + var colText = new PdfCellContentLayout(pageSettings, dictionaries, colCell, info, + x, pageSettings.ContentBounds.Top, colWidth, headingHeight); + colText.Name = "Heading_Col_" + colLetter + "_Text"; + colText.GidsAndCharMap(dictionaries); + pageLayout.AddChild(colText); + } + AddHeadingCellBorder(pageLayout, x, pageSettings.ContentBounds.Top, colWidth, headingHeight); + x += colWidth; + } + // ── Row headings (one per data row, spanning the left strip) ───────────────────────── + double y = contentStartY; + for (int row = page.FromRow; row <= page.ToRow; row++) + { + double rowHeight = page.RowHeights[row - page.FromRow]; + if (rowHeight == 0d) { y -= rowHeight; continue; } // hidden row — skip + + string rowNum = row.ToString(); + + var rowFill = new PdfCellLayout(dictionaries, headingStyle, + pageSettings.ContentBounds.Left, y, headingWidth, rowHeight); + rowFill.Name = "Heading_Row_" + rowNum; + rowFill.UpdateShadingPositionMatrix(pageSettings); + pageLayout.AddChild(rowFill); + + var rowCell = CreateHeadingPdfCell(pageSettings, dictionaries, + rowNum, ExcelHorizontalAlignment.Center, + headingWidth, rowHeight, fontName, fontSize); + + if (rowCell.TextLines != null && rowCell.TextLines.Count > 0) + { + var info = new MergedCellDrawInfo { X = pageSettings.ContentBounds.Left, Y = y, Width = headingWidth, Height = rowHeight }; + var rowText = new PdfCellContentLayout(pageSettings, dictionaries, rowCell, info, + pageSettings.ContentBounds.Left, y, headingWidth, rowHeight); + rowText.Name = "Heading_Row_" + rowNum + "_Text"; + rowText.GidsAndCharMap(dictionaries); + pageLayout.AddChild(rowText); + } + AddHeadingCellBorder(pageLayout, pageSettings.ContentBounds.Left, y, headingWidth, rowHeight); + y -= rowHeight; + } + } + + private static void AddHeadingCellBorder(PdfPageLayout pageLayout, double x, double y, double width, double height) + { + double right = x + width; + double bottom = y - height; + pageLayout.BorderLines.Add(new GridLine(x, y, right, y)); // top + pageLayout.BorderLines.Add(new GridLine(x, bottom, right, bottom)); // bottom + pageLayout.BorderLines.Add(new GridLine(x, y, x, bottom)); // left + pageLayout.BorderLines.Add(new GridLine(right, y, right, bottom)); // right + } + + + private static PdfCell CreateHeadingPdfCell(PdfPageSettings pageSettings, PdfDictionaries dictionaries, string text, ExcelHorizontalAlignment hAlign, double width, double height, string fontName, float fontSize) + { + var cell = new PdfCell(); + cell.ColumnWidth = width; + cell.Width = width; + cell.Height = height; + cell.Text = text; + cell.CellStyle = new PdfCellStyle(); + cell.ContentAligmnet = new PdfCellAlignmentData(); + cell.ContentAligmnet.HorizontalAlignment = hAlign; + cell.ContentAligmnet.VerticalAlignment = ExcelVerticalAlignment.Bottom; + cell.ContentAligmnet.WrapText = false; + if (!string.IsNullOrEmpty(text)) + { + var tf = new TextFragment(); + tf.Font = new MeasurementFont + { + FontFamily = fontName, + Size = fontSize, + Style = MeasurementFontStyles.Regular + }; + tf.Text = text; + tf.RichTextOptions.Bold = false; + tf.RichTextOptions.Italic = false; + tf.RichTextOptions.UnderlineType = 12; // none + tf.RichTextOptions.StrikeType = 1; // none + cell.TextFragments = new List { tf }; + PdfTextShaper.LayoutAndShapeText(pageSettings, dictionaries, cell); + } + return cell; + } + + + + // TODO Count total pages when creating them isntead of looping them here. private static int GetTotalPages(List pdfPages) { @@ -375,18 +516,22 @@ internal static List GetPages(PdfPageSettings pageSettings, PdfWorksheet[ List PagesCollection = new List(); foreach (var pdfSheet in pdfSheets) { - foreach (var range in pdfSheet.Ranges) + for (int ri = 0; ri < pdfSheet.Ranges.Count; ri++) { - var pages = GetNumberOfPages(pageSettings, pdfSheet, range); + var range = pdfSheet.Ranges[ri]; + var pages = GetNumberOfPages(pageSettings, pdfSheet, ref range); pages = AssignRangeToPages(pageSettings, range, pages); pages = MapPage(range, pages); pages = GetHeaderFooter(range, pages, pdfSheet); pages = PrecomputeMergedCells(pageSettings, range, pages); + pages.HeadingFontName = pdfSheet.NormalStyle.Style.Font.Name; + pages.HeadingFontSize = pdfSheet.NormalStyle.Style.Font.Size; + pages.HeadingFill = pdfSheet.NormalStyle.Style.Fill; PagesCollection.Add(pages); } if (pdfSheet.CommentsAndNotes.Range != null) { - var pages = GetNumberOfPages(pageSettings, pdfSheet, pdfSheet.CommentsAndNotes); + var pages = GetNumberOfPages(pageSettings, pdfSheet, ref pdfSheet.CommentsAndNotes); pages = AssignRangeToPages(pageSettings, pdfSheet.CommentsAndNotes, pages); pages = MapPage(pdfSheet.CommentsAndNotes, pages); pages.IsCommentsPage = true; @@ -432,7 +577,7 @@ private static Page PrecomputePageMergedCells(PdfPageSettings pageSettings, PdfR } // --- Y --- // Replace the * 15d line with a sum of real row heights - double drawY = pageSettings.ContentBounds.Top; + double drawY = pageSettings.ContentBounds.Top - page.HeadingHeight; for (int r = page.FromRow; r < row; r++) { drawY -= range.RowHeights[r - range.Range._fromRow].Height; @@ -460,7 +605,7 @@ private static double[] BuildColumnXPositions(PdfPageSettings pageSettings, Page { int colCount = page.ToColumn - page.FromColumn + 1; var colX = new double[colCount]; - double x = pageSettings.ContentBounds.Left; + double x = pageSettings.ContentBounds.Left + page.HeadingWidth; for (int col = page.FromColumn; col <= page.ToColumn; col++) { colX[col - page.FromColumn] = x; @@ -470,7 +615,7 @@ private static double[] BuildColumnXPositions(PdfPageSettings pageSettings, Page return colX; } - internal static Pages GetNumberOfPages(PdfPageSettings pageSettings, PdfWorksheet pdfSheet, PdfRange range) + internal static Pages GetNumberOfPages(PdfPageSettings pageSettings, PdfWorksheet pdfSheet, ref PdfRange range) { //calculte pages needed for this range, add in col headings for width, row headings for height. THis is where we also add print headings later on. Autofit on row here too later on. var xPages = (int)Math.Max(1, Math.Ceiling(range.TotalWidth / pageSettings.ContentBounds.Width)); @@ -538,6 +683,13 @@ internal static Pages AssignRangeToPages(PdfPageSettings pageSettings, PdfRange pages.Page[i++] = new Page { FromColumn = colSeg.From, ToColumn = colSeg.To, FromRow = rowSeg.From, ToRow = rowSeg.To }; } + for (int k = 0; k < pages.Page.Length; k++) + { + var p = pages.Page[k]; + p.HeadingWidth = addedWidth; + p.HeadingHeight = addedHeight; + pages.Page[k] = p; + } pdfPages = pages; return pdfPages; } From 843bc287cb323abd61ce74e132b46a7d10870c2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Thu, 28 May 2026 10:14:10 +0200 Subject: [PATCH 02/49] fixed some issues with headings. --- src/EPPlus.Export.Pdf.Tests/PdfTests.cs | 2 +- src/EPPlus.Export.Pdf/ExcelPdf.cs | 19 +++++++++++++++++-- src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs | 6 ++++++ .../PdfLayout/PdfCatalogLayout.cs | 6 +++--- .../PdfLayout/PdfCellContentLayout.cs | 1 + .../PdfLayout/PdfCellLayout.cs | 2 +- .../PdfLayout/PdfPageLayout.cs | 3 +++ .../PdfObjects/PdfContentStream.cs | 6 +++--- 8 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs index cf46495cb2..0cb84e300b 100644 --- a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs +++ b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs @@ -171,7 +171,7 @@ public void ReadPrintAreas() pageSettings.PrintAsText = true; pageSettings.ShowGridLines = true; pageSettings.ShowHeadings = true; - PdfCatalog catlog = new PdfCatalog(pageSettings, ws, "C:\\epplustest\\pdf\\FullPageTest52.pdf"); + PdfCatalog catlog = new PdfCatalog(pageSettings, ws, "C:\\epplustest\\pdf\\FullPageTest55.pdf"); //line breaks //wrap comments diff --git a/src/EPPlus.Export.Pdf/ExcelPdf.cs b/src/EPPlus.Export.Pdf/ExcelPdf.cs index 65f34e8a1b..ac32b63a6b 100644 --- a/src/EPPlus.Export.Pdf/ExcelPdf.cs +++ b/src/EPPlus.Export.Pdf/ExcelPdf.cs @@ -184,8 +184,9 @@ private PdfObjects.PdfCatalog AddCatalog(int pagesObjectNumber) private void AddContent(Transform pageLayout, PdfPage page) { //var cells = pageLayout.ChildObjects.Where(t => t is PdfCellLayout || t is PdfCellContentLayout || t is PdfCellBorderLayout).GroupBy(t => t.Name); - var cells = pageLayout.ChildObjects.Where(t => (t is PdfCellLayout || t is PdfCellContentLayout || t is PdfCellBorderLayout) && !(t is PdfCellContentLayout cc && cc.IsHeaderFooter)).GroupBy(t => t.Name); + var cells = pageLayout.ChildObjects.Where(t => (t is PdfCellLayout || t is PdfCellContentLayout || t is PdfCellBorderLayout) && !(t is PdfCellLayout cc && cc.IsHeading) && !(t is PdfCellContentLayout ccl && (ccl.IsHeaderFooter || ccl.IsHeading))).GroupBy(t => t.Name); var headerFooterLayouts = pageLayout.ChildObjects.OfType().Where(t => t.IsHeaderFooter); + var headingLayouts = pageLayout.ChildObjects.Where(t => (t is PdfCellLayout cl && cl.IsHeading) || (t is PdfCellContentLayout ccl && ccl.IsHeading)); var contentStream = new PdfContentStream(Document.Count + 1); contentStream.AddCommand($"% {pageLayout.Name} start"); //Add clipping rectangle around page content. @@ -217,7 +218,21 @@ private void AddContent(Transform pageLayout, PdfPage page) //Close the clipping rectangle. contentStream.AddCommand("Q"); contentStream.AddCommand($"% Margin Clip End"); - if (PageSettings.ShowGridLines) + // Heading cells render outside the clip — no merged-cell content can obscure them. + foreach (var heading in headingLayouts) + { + contentStream.AddCommand($"% HEADING : {heading.Name}"); + switch (heading) + { + case PdfCellLayout layout: + contentStream.AddCellLayout(layout, GetPatternLabel(layout)); break; + case PdfCellContentLayout contentLayout: + contentStream.AddCellContentLayout(contentLayout, Dictionaries, PageSettings); break; + case PdfCellBorderLayout borderLayout: + contentStream.AddBorderLayout(borderLayout); break; + } + } + if (PageSettings.ShowGridLines || PageSettings.ShowHeadings) { contentStream.AddOuterGridBorder(pageLayout); } diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs index c1582cec78..3dc3cb1df1 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs @@ -90,6 +90,8 @@ internal static Transform GetCatalog(PdfPageSettings pageSettings, PdfDictionari var page = pages[j]; PdfPageLayout pageLayout = new PdfPageLayout(0d, 0d, 0d, 0d); pageLayout.isCommentsPage = pdfPages[i].IsCommentsPage; + pageLayout.HeadingWidth = page.HeadingWidth; + pageLayout.HeadingHeight = page.HeadingHeight; var drawnMergedCells = new HashSet(); //var drawnMergedCellsText = new HashSet(); //PdfContentLayout contentLayout = new PdfContentLayout(0d, 0d, pageSettings.ContentBounds); @@ -402,6 +404,7 @@ private static void AddHeadingCells(PdfPageSettings pageSettings, PdfDictionarie var colFill = new PdfCellLayout(dictionaries, headingStyle, x, pageSettings.ContentBounds.Top, colWidth, headingHeight); colFill.Name = "Heading_Col_" + colLetter; + colFill.IsHeading = true; colFill.UpdateShadingPositionMatrix(pageSettings); pageLayout.AddChild(colFill); @@ -415,6 +418,7 @@ private static void AddHeadingCells(PdfPageSettings pageSettings, PdfDictionarie var colText = new PdfCellContentLayout(pageSettings, dictionaries, colCell, info, x, pageSettings.ContentBounds.Top, colWidth, headingHeight); colText.Name = "Heading_Col_" + colLetter + "_Text"; + colText.IsHeading = true; colText.GidsAndCharMap(dictionaries); pageLayout.AddChild(colText); } @@ -433,6 +437,7 @@ private static void AddHeadingCells(PdfPageSettings pageSettings, PdfDictionarie var rowFill = new PdfCellLayout(dictionaries, headingStyle, pageSettings.ContentBounds.Left, y, headingWidth, rowHeight); rowFill.Name = "Heading_Row_" + rowNum; + rowFill.IsHeading = true; rowFill.UpdateShadingPositionMatrix(pageSettings); pageLayout.AddChild(rowFill); @@ -446,6 +451,7 @@ private static void AddHeadingCells(PdfPageSettings pageSettings, PdfDictionarie var rowText = new PdfCellContentLayout(pageSettings, dictionaries, rowCell, info, pageSettings.ContentBounds.Left, y, headingWidth, rowHeight); rowText.Name = "Heading_Row_" + rowNum + "_Text"; + rowText.IsHeading = true; rowText.GidsAndCharMap(dictionaries); pageLayout.AddChild(rowText); } diff --git a/src/EPPlus.Export.Pdf/PdfLayout/PdfCatalogLayout.cs b/src/EPPlus.Export.Pdf/PdfLayout/PdfCatalogLayout.cs index 20fc04c263..ff71911259 100644 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfCatalogLayout.cs +++ b/src/EPPlus.Export.Pdf/PdfLayout/PdfCatalogLayout.cs @@ -246,7 +246,7 @@ private static void AddColumnHeadings(ExcelWorksheet ws, PdfPageSettings pageSet var cell = new PdfCellLayout(dictionaries, col, cellStyle, X, content.LocalPosition.Y + content.Size.Y + height, width, height, 1, 1, 0, content); cell.Name = col.Address; cell.Z = 8; - cell.isHeading = true; + cell.IsHeading = true; cell.CellFillData.PatternStyle = ExcelFillStyle.Solid; cell.CellFillData.BackgroundColor = Color.White; //Add text @@ -294,7 +294,7 @@ private static void AddRowHeadings(ExcelWorksheet ws, PdfPageSettings pageSettin var cell = new PdfCellLayout(dictionaries, row, cellStyle, content.LocalPosition.X - width, Y, width, height, 1, 1, 0, content); cell.Name = j.ToString(); cell.Z = 8; - cell.isHeading = true; + cell.IsHeading = true; cell.CellFillData.PatternStyle = ExcelFillStyle.Solid; cell.CellFillData.BackgroundColor = Color.White; //Add text @@ -669,7 +669,7 @@ private void MoveCellToPageFromContent(PdfPageSettings pageSettings, PdfDictiona } else if (child is PdfCellLayout l) { - if (l.isHeading) continue; + if (l.IsHeading) continue; var localFromRow = l.cell._fromRow - page.FromRow; var localFromCol = l.cell._fromCol - page.FromCol; page.Map[localFromRow, localFromCol].Name = l.Name; diff --git a/src/EPPlus.Export.Pdf/PdfLayout/PdfCellContentLayout.cs b/src/EPPlus.Export.Pdf/PdfLayout/PdfCellContentLayout.cs index bacd39d9c9..5304b30f89 100644 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfCellContentLayout.cs +++ b/src/EPPlus.Export.Pdf/PdfLayout/PdfCellContentLayout.cs @@ -35,6 +35,7 @@ internal class PdfCellContentLayout : Transform public bool Clip; public Rect Clipping; public bool IsHeaderFooter; + public bool IsHeading; public PdfCellStyle CellStyle; public ExcelRangeBase cell; public TextLayoutEngine textLayoutEngine; diff --git a/src/EPPlus.Export.Pdf/PdfLayout/PdfCellLayout.cs b/src/EPPlus.Export.Pdf/PdfLayout/PdfCellLayout.cs index 02ae059543..03feeca322 100644 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfCellLayout.cs +++ b/src/EPPlus.Export.Pdf/PdfLayout/PdfCellLayout.cs @@ -33,7 +33,7 @@ internal class PdfCellLayout : Transform, IShadingLayout public double LeftTextSpillLength = 0d; public double RightTextSpillLength = 0d; public bool delete = false; - public bool isHeading = false; + public bool IsHeading = false; public PdfCellLayout(PdfDictionaries dictionaries, PdfCellStyle CellStyle, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) : base(x, y - height, width, height, scaleX, scaleY, rotation, parent) diff --git a/src/EPPlus.Export.Pdf/PdfLayout/PdfPageLayout.cs b/src/EPPlus.Export.Pdf/PdfLayout/PdfPageLayout.cs index 3c53f623b9..01896e4009 100644 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfPageLayout.cs +++ b/src/EPPlus.Export.Pdf/PdfLayout/PdfPageLayout.cs @@ -80,6 +80,9 @@ internal class PdfPageLayout : Transform public double ContentX; public double ContentHeight; + public double HeadingWidth; + public double HeadingHeight; + public bool isCommentsPage = false; public PdfPageLayout(double x, double y, double width, double height) diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs b/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs index dbdac41964..f44b5a4711 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs +++ b/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs @@ -567,10 +567,10 @@ public void AddMarginClipping(PdfPageLayout pageLayout) right = System.Math.Max(right, System.Math.Max(line.X1, line.X2)); } var pad = GridLine.Width * 4; - var x = left - pad; + var x = left + pl.HeadingWidth - pad; var y = bottom - pad; - var width = (right - left) + pad * 2; - var height = (top - bottom) + pad * 2; + var width = (right - left - pl.HeadingWidth) + pad * 2; + var height = (top - pl.HeadingHeight - bottom) + pad * 2; commands.Add($"{x.ToPdfString()} {y.ToPdfString()} {width.ToPdfString()} {height.ToPdfString()} re W n"); } From 58d80a975eb70aa018125a90d245789c73fad4eb Mon Sep 17 00:00:00 2001 From: swmal <897655+swmal@users.noreply.github.com> Date: Thu, 28 May 2026 16:56:13 +0200 Subject: [PATCH 03/49] Perf fixes --- src/EPPlus.Export.Pdf/AssemblyInfo.cs | 4 +- .../PdfCatalog/PdfCatalog.cs | 46 ++--- .../PdfCatalog/PdfTextMap.cs | 10 +- .../PdfCatalog/PdfTextShaper.cs | 160 +++++++++++++++--- .../PdfObjects/PdfContentStream.cs | 4 + .../PdfResources/PdfDictionaries.cs | 6 +- 6 files changed, 182 insertions(+), 48 deletions(-) diff --git a/src/EPPlus.Export.Pdf/AssemblyInfo.cs b/src/EPPlus.Export.Pdf/AssemblyInfo.cs index d79820d85e..adeb5b8146 100644 --- a/src/EPPlus.Export.Pdf/AssemblyInfo.cs +++ b/src/EPPlus.Export.Pdf/AssemblyInfo.cs @@ -28,4 +28,6 @@ Date Author Change // The following GUID is for the ID of the typelib if this project is exposed to COM. [assembly: Guid("60855b7d-19da-4dfa-90b2-78231d227d65")] -[assembly: InternalsVisibleTo("EPPlus.Export.Pdf.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dd3a3466a88cbf5d374fe992cec433c48022414fe96608933e8e36782001213dd31bc454dc6f962a54a3a76cfb9e03a32cd4c658ecd49d1a98709971a080ab92d5c5b65346155f8d6422db4ffbf662f78913996a9a8b78ee11ff3cda7e585208cd4468fb3201f15bbb1dfc45c120703c9d6ad495bb9de66893ae5ab5ac8f40dc")] \ No newline at end of file +[assembly: InternalsVisibleTo("EPPlus.Export.Pdf.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dd3a3466a88cbf5d374fe992cec433c48022414fe96608933e8e36782001213dd31bc454dc6f962a54a3a76cfb9e03a32cd4c658ecd49d1a98709971a080ab92d5c5b65346155f8d6422db4ffbf662f78913996a9a8b78ee11ff3cda7e585208cd4468fb3201f15bbb1dfc45c120703c9d6ad495bb9de66893ae5ab5ac8f40dc")] +// +[assembly: InternalsVisibleTo("EPPlus.PdfExportPerformance, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f5398d389a44aacee243c9e73152fae942e9ede958ebd4b06651a749e6b2c0395a66bc0fc133dce1cf83cd667fe8f761b433063e603968f12b0b17e474233410f8fe933853d2344e89526414ef65ed7236a7a5c012e30275ec30d7fb665dbe1bd3435439bec55f431b9e69e943294fd474942d6bcb431c94ef653cbeee5b8a9e")] \ No newline at end of file diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs index 052abc1a73..632e097070 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs @@ -8,7 +8,7 @@ namespace EPPlus.Export.Pdf.PdfCatalog { - internal class PdfCatalog + public class PdfCatalog { internal PdfDictionaries Dictionaries = new PdfDictionaries(); private bool AddTextForHeadings = true; @@ -61,7 +61,7 @@ public PdfCatalog(PdfPageSettings pageSettings, ExcelWorksheet worksheet, string sw.Start(); //Auto-Fit Rows - PdfCalculateRowHeight.ResizeRowHeights(pdfSheet); //call a method that does this so we can use it for comments sheet aswell! + PdfCalculateRowHeight.ResizeRowHeights(pdfSheet); sw.Stop(); var AutoFitRowTime = sw.ElapsedMilliseconds; sw.Reset(); @@ -88,7 +88,7 @@ public PdfCatalog(PdfPageSettings pageSettings, ExcelRangeBase range) ShapeTextInPdfWorksheet(pageSettings, pdfSheet); } - public PdfCellCollection GetCellCollectionFromRange(PdfPageSettings pageSettings, ExcelRangeBase range) + internal PdfCellCollection GetCellCollectionFromRange(PdfPageSettings pageSettings, ExcelRangeBase range) { PdfWorksheet pdfSheet = GetPdfWorksheet(pageSettings, range); ShapeTextInPdfWorksheet(pageSettings, pdfSheet); @@ -99,13 +99,28 @@ public PdfCellCollection GetCellCollectionFromRange(PdfPageSettings pageSettings //Create Layout Methods private Transform GetLayout(PdfPageSettings pageSettings, PdfWorksheet pdfSheet) { - PdfWorksheet[] pdfSheets = new PdfWorksheet[1]{ pdfSheet }; + PdfWorksheet[] pdfSheets = new PdfWorksheet[1] { pdfSheet }; var Layout = PdfLayout.GetLayout(pageSettings, Dictionaries, pdfSheets); return Layout; } //Shape Text Methods - private void ShapeTextInPdfWorksheet(PdfPageSettings pageSettings, PdfWorksheet pdfSheet) + internal void ShapeTextInPdfWorksheet(PdfPageSettings pageSettings, PdfWorksheet pdfSheet) + { + // Pass 1: collect text per font + IterateCells(pdfSheet, cell => PdfTextShaper.CollectText(Dictionaries, cell)); + + // Pass 2: build one provider per font + foreach (var kvp in Dictionaries.Fonts) + { + Dictionaries.ShapedProviders[kvp.Key] = kvp.Value.fontSubsetManager.CreateSubsettedProvider(); + } + + // Pass 3: shape text using the pre-built providers + IterateCells(pdfSheet, cell => PdfTextShaper.ShapeText(pageSettings, Dictionaries, cell)); + } + + private void IterateCells(PdfWorksheet pdfSheet, System.Action action) { foreach (var range in pdfSheet.Ranges) { @@ -113,29 +128,21 @@ private void ShapeTextInPdfWorksheet(PdfPageSettings pageSettings, PdfWorksheet { for (int j = range.Map.FromColumn; j <= range.Map.ToColumn; j++) { - var cell = range.Map[i, j]; - PdfTextShaper.LayoutAndShapeText(pageSettings, Dictionaries, cell); + action(range.Map[i, j]); } } } + if (pdfSheet.CommentsAndNotes.Map != null) { for (int i = pdfSheet.CommentsAndNotes.Map.FromRow; i <= pdfSheet.CommentsAndNotes.Map.ToRow; i++) { for (int j = pdfSheet.CommentsAndNotes.Map.FromColumn; j <= pdfSheet.CommentsAndNotes.Map.ToColumn; j++) { - var cell = pdfSheet.CommentsAndNotes.Map[i, j]; - PdfTextShaper.LayoutAndShapeText(pageSettings, Dictionaries, cell); + action(pdfSheet.CommentsAndNotes.Map[i, j]); } } } - //if (pdfSheet.HeaderFooters != null) - //{ - // foreach (var hf in pdfSheet.HeaderFooters.PdfHeaderFooterEntries) - // { - // PdfTextShaper.LayoutAndShapeText(pageSettings, Dictionaries, hf.Content); - // } - //} } //Collect Text Methods @@ -149,14 +156,13 @@ private PdfWorksheet[] GetPdfWorksheets(PdfPageSettings pageSettings, ExcelWorks return pdfSheets; } - private PdfWorksheet GetPdfWorksheet(PdfPageSettings pageSettings, ExcelWorksheet worksheet) + internal PdfWorksheet GetPdfWorksheet(PdfPageSettings pageSettings, ExcelWorksheet worksheet) { PdfWorksheet pdfSheet = new PdfWorksheet(); pdfSheet.Ranges = new List(); pdfSheet.Worksheet = worksheet; pdfSheet.Ranges = GetRanges(pdfSheet.Worksheet); - //pdfSheet.HeaderFooters = new PdfHeaderFooterCollection(pageSettings, Dictionaries, pdfSheet, pdfSheet.Worksheet.HeaderFooter); - if(pageSettings.ShowHeadings && AddTextForHeadings) Dictionaries.AddFont(pageSettings, pdfSheet.NormalStyle.Style.Font.Name, pdfSheet.GetSubFamilyFromNormalStyle, "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); + if (pageSettings.ShowHeadings && AddTextForHeadings) Dictionaries.AddFont(pageSettings, pdfSheet.NormalStyle.Style.Font.Name, pdfSheet.GetSubFamilyFromNormalStyle, "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); AddTextForHeadings = false; GetMaps(pageSettings, pdfSheet, pdfSheet.Ranges); GetHeaderFooter(pageSettings, pdfSheet); @@ -232,4 +238,4 @@ private void GetCommentsAndNotes(PdfPageSettings pageSettings, PdfWorksheet pdfS } } } -} +} \ No newline at end of file diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs index e21a796fe6..6d2d6a4398 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs @@ -81,7 +81,11 @@ public static PdfCellCollection SetTextMap(PdfPageSettings pageSettings, PdfDict if (!string.IsNullOrEmpty(cell.Text)) { tempMap.Text = cell.Text; - if (!cell.IsRichText) cell._rtc = new ExcelRichTextCollection(cell.Text, cell); + if(row == 1 && col == 1) + { + Console.WriteLine($"Cell.RichText.Count: {cell.RichText.Count}"); + } + if (cell._rtc == null) cell._rtc = new ExcelRichTextCollection(cell.Text, cell); //tempMap.TextFormats = GetTextFormats(pageSettings, dictionaries, cell._rtc, cellStyle); tempMap.TextFragments = GetTextFragments(pageSettings, dictionaries, cell, cell._rtc, cellStyle); } @@ -560,6 +564,10 @@ private static List GetTextFragments(PdfPageSettings pageSettings, for (int i = 0; i < RichTextCollection.Count; i++) { var rt = RichTextCollection[i]; + if (cell.Address == "A1") + { + Console.WriteLine($"[GetTextFragments] A1 rt[{i}]: Text='{rt.Text}' Bold={rt.Bold} FontName='{rt.FontName}'"); + } var textFrag = new TextFragment(); textFrag.Font = new MeasurementFont(); textFrag.Text = text == null ? rt.Text : text; diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextShaper.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextShaper.cs index 8b94e51e28..b275240c5c 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextShaper.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextShaper.cs @@ -17,17 +17,134 @@ internal static class PdfTextShaper private static Dictionary shaperCache = new Dictionary(); private static Dictionary layoutEngineCache = new Dictionary(); - public static void LayoutAndShapeText(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfCell Cell) + // Pass 1: collect text per font so FontSubsetManager can build subsets once + public static void CollectText(PdfDictionaries dictionaries, PdfCell cell) + { + if (cell == null || cell.TextFragments == null) return; + + if (cell.TextFragments.Count > 1 || (cell.TextFragments.Count == 1 && cell.TextFragments[0].Text.Contains("should"))) + { + Console.WriteLine($"[CollectText] cell={cell.Name} fragments={cell.TextFragments.Count}"); + for (int i = 0; i < cell.TextFragments.Count; i++) + { + var tf = cell.TextFragments[i]; + Console.WriteLine($" [{i}] Text='{tf.Text}' FullFontName='{tf.FullFontName}' Bold={tf.RichTextOptions.Bold}"); + } + } + + for (int i = 0; i < cell.TextFragments.Count; i++) + { + var tf = cell.TextFragments[i]; + if (!dictionaries.Fonts.ContainsKey(tf.FullFontName)) continue; + dictionaries.Fonts[tf.FullFontName].fontSubsetManager.AddText(tf.Text); + } + } + + // Pass 2: shape text using already-built providers from PdfDictionaries.ShapedProviders + public static void ShapeText(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfCell cell) { var totalTextLength = 0d; var maxLineHeight = 0d; - if (Cell.TextFragments == null) return; - Cell.ShapedTexts = new List(); - for (int i = 0; i < Cell.TextFragments.Count; i++) + if (cell == null || cell.TextFragments == null) return; + + if (cell.Name == "A1" || (cell.TextFragments.Count > 0 && cell.TextFragments[0].Text.StartsWith("This text"))) { - var tf = Cell.TextFragments[i]; - Cell.ShapedTexts.Add(new PdfShapedText()); - var st = Cell.ShapedTexts[i]; + Console.WriteLine($"[ShapeText START] cell={cell.Name} fragments={cell.TextFragments.Count}"); + for (int i = 0; i < cell.TextFragments.Count; i++) + { + Console.WriteLine($" [{i}] Text='{cell.TextFragments[i].Text}' FullFontName='{cell.TextFragments[i].FullFontName}'"); + } + } + + cell.ShapedTexts = new List(); + + for (int i = 0; i < cell.TextFragments.Count; i++) + { + var tf = cell.TextFragments[i]; + cell.ShapedTexts.Add(new PdfShapedText()); + var st = cell.ShapedTexts[i]; + + if (!dictionaries.ShapedProviders.TryGetValue(tf.FullFontName, out var provider)) + { + continue; + } + st.FontProvider = provider; + + if (!shaperCache.TryGetValue(st.FontProvider, out var shaper)) + { + shaper = new TextShaper(st.FontProvider); + shaperCache[st.FontProvider] = shaper; + } + + if (!layoutEngineCache.TryGetValue(st.FontProvider, out var layoutEngine)) + { + layoutEngine = new TextLayoutEngine(shaper); + layoutEngineCache[st.FontProvider] = layoutEngine; + } + + var options = ShapingOptions.Default; + options.ApplyPositioning = true; + options.ApplySubstitutions = true; + + var shaped = shaper.Shape(tf.Text, options); + var usedFonts = shaper.GetUsedFonts().ToList(); + //Console.WriteLine($"[ShapeText] tf.Text='{tf.Text}' FullFontName='{tf.FullFontName}' usedFonts={string.Join(",", usedFonts.Select(f => f.FullName).ToArray())}"); + var fontIdMap = new Dictionary(); + + for (byte fontId = 0; fontId < usedFonts.Count; fontId++) + { + var font = usedFonts[fontId]; + if (!dictionaries.Fonts.ContainsKey(font.FullName)) + { + int label = dictionaries.Fonts.Count > 0 + ? dictionaries.Fonts.Last().Value.labelNumber + 1 + : 1; + var fontResource = new PdfFontResource(font.FullName, font.NameTable.GetSubfamilyEnum(), label, pageSettings); + fontResource.fontData = font; + dictionaries.Fonts.Add(font.FullName, fontResource); + } + fontIdMap[fontId] = dictionaries.Fonts[font.FullName].Label; + } + + cell.TextLayoutEngine = layoutEngine; + st.ShapedText = shaped; + + totalTextLength += st.ShapedText.GetWidthInPoints((float)tf.Font.Size); + maxLineHeight = Math.Max(st.ShapedText.GetLineHeightInPoints((float)tf.Font.Size), maxLineHeight); + + st.FontIdMap = fontIdMap; + st.UsedFonts = usedFonts; + cell.TextFragments[i] = tf; + cell.ShapedTexts[i] = st; + if (cell.Name == "A1" || (cell.TextFragments.Count > 0 && cell.TextFragments[0].Text.StartsWith("This text"))) + { + Console.WriteLine($"[ShapeText END loop {i}] tf.Text='{tf.Text}' provider={(st.FontProvider != null ? "OK" : "NULL")} usedFonts={string.Join(",", st.UsedFonts.Select(f => f.FullName).ToArray())}"); + } + } + + if (cell.TextLayoutEngine != null) + { + double wrapWidth = (cell.Merged && cell.Main == null) ? cell.Width : cell.ColumnWidth; + cell.TextLines = cell.ContentAligmnet.WrapText + ? cell.TextLayoutEngine.WrapRichTextLineCollection(cell.TextFragments, wrapWidth) + : cell.TextLayoutEngine.WrapRichTextLineCollection(cell.TextFragments, double.MaxValue); + } + + cell.TotalTextLength = totalTextLength; + } + + // Kept for backwards compatibility - not used by ShapeTextInPdfWorksheet anymore + public static void LayoutAndShapeText(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfCell cell) + { + var totalTextLength = 0d; + var maxLineHeight = 0d; + if (cell.TextFragments == null) return; + cell.ShapedTexts = new List(); + for (int i = 0; i < cell.TextFragments.Count; i++) + { + var tf = cell.TextFragments[i]; + cell.ShapedTexts.Add(new PdfShapedText()); + var st = cell.ShapedTexts[i]; st.FontProvider = dictionaries.Fonts[tf.FullFontName].fontSubsetManager.CreateSubsettedProvider(); if (!shaperCache.TryGetValue(st.FontProvider, out var shaper)) @@ -55,7 +172,6 @@ public static void LayoutAndShapeText(PdfPageSettings pageSettings, PdfDictionar for (byte fontId = 0; fontId < usedFonts.Count; fontId++) { var font = usedFonts[fontId]; - if (!dictionaries.Fonts.ContainsKey(font.FullName)) { int label = 1; @@ -69,38 +185,35 @@ public static void LayoutAndShapeText(PdfPageSettings pageSettings, PdfDictionar } fontIdMap[fontId] = dictionaries.Fonts[font.FullName].Label; } - Cell.TextLayoutEngine = layoutEngine; + cell.TextLayoutEngine = layoutEngine; st.ShapedText = shaped; - var textWdith = st.ShapedText.GetWidthInPoints((float)tf.Font.Size); + var textWidth = st.ShapedText.GetWidthInPoints((float)tf.Font.Size); var textHeight = st.ShapedText.GetLineHeightInPoints((float)tf.Font.Size); - //tf.TextLength = textWdith; - //tf.TextHeight = textHeight; - totalTextLength += textWdith; + totalTextLength += textWidth; maxLineHeight = Math.Max(textHeight, maxLineHeight); st.FontIdMap = fontIdMap; st.UsedFonts = usedFonts; - Cell.TextFragments[i] = tf; - Cell.ShapedTexts[i] = st; + cell.TextFragments[i] = tf; + cell.ShapedTexts[i] = st; } - if (Cell.TextLayoutEngine != null) + if (cell.TextLayoutEngine != null) { - if (Cell.ContentAligmnet.WrapText) + if (cell.ContentAligmnet.WrapText) { - double wrapWidth = (Cell.Merged && Cell.Main == null) ? Cell.Width : Cell.ColumnWidth; - Cell.TextLines = Cell.TextLayoutEngine.WrapRichTextLineCollection(Cell.TextFragments, wrapWidth); + double wrapWidth = (cell.Merged && cell.Main == null) ? cell.Width : cell.ColumnWidth; + cell.TextLines = cell.TextLayoutEngine.WrapRichTextLineCollection(cell.TextFragments, wrapWidth); } else { - Cell.TextLines = Cell.TextLayoutEngine.WrapRichTextLineCollection(Cell.TextFragments, double.MaxValue); + cell.TextLines = cell.TextLayoutEngine.WrapRichTextLineCollection(cell.TextFragments, double.MaxValue); } } - Cell.TotalTextLength = totalTextLength; + cell.TotalTextLength = totalTextLength; } private static List GetTextFragments(List textFragments) { var fragments = new List(textFragments.Count); - foreach (var tf in textFragments) { var fragment = new TextFragment @@ -115,7 +228,6 @@ private static List GetTextFragments(List textFragme }; fragments.Add(fragment); } - return fragments; } @@ -129,4 +241,4 @@ private static MeasurementFontStyles GetMeasurementFontStyle(TextFragment tf) return style == 0 ? MeasurementFontStyles.Regular : (MeasurementFontStyles)style; } } -} +} \ No newline at end of file diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs b/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs index dbdac41964..5f3e035b7f 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs +++ b/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs @@ -194,6 +194,10 @@ public void AddText(PdfCellContentLayout cell, Vector2 position, double textRota var textLength = shapedText.ShapedText.GetWidthInPoints((float)textFormat.OriginalTextFragment.Font.Size); var color = textFormat.OriginalTextFragment.RichTextOptions.FontColor; var fontResrouce = GetFontResource(dictionaries, pageSettings, textFormat.OriginalTextFragment.Font.FontFamily, OpenTypeFonts.GetFontSubFamily(textFormat.OriginalTextFragment.Font.Style), textFormat.OriginalTextFragment.Font.Size); + if (cell.Name == "A1") + { + Console.WriteLine($"[Render A1] fragmentText='{textFormat.OriginalTextFragment.Text}' FontFamily='{textFormat.OriginalTextFragment.Font.FontFamily}' Style={textFormat.OriginalTextFragment.Font.Style} shapedTextIndex={shapedTextIndex} fontResource.Label={fontResrouce.Label}"); + } double size = textFormat.OriginalTextFragment.Font.Size; double scale = textFormat.OriginalTextFragment.Font.Size / fontResrouce.fontData.HeadTable.UnitsPerEm; Matrix3x3 textMatrix = new Matrix3x3(System.Math.Cos(rotation), System.Math.Sin(rotation), -System.Math.Sin(rotation), System.Math.Cos(rotation), position.X + lineOffsetX, position.Y + advanceY); diff --git a/src/EPPlus.Export.Pdf/PdfResources/PdfDictionaries.cs b/src/EPPlus.Export.Pdf/PdfResources/PdfDictionaries.cs index ac7e2453ab..859e3d8ba0 100644 --- a/src/EPPlus.Export.Pdf/PdfResources/PdfDictionaries.cs +++ b/src/EPPlus.Export.Pdf/PdfResources/PdfDictionaries.cs @@ -12,17 +12,19 @@ Date Author Change *************************************************************************************************/ using EPPlus.Export.Pdf.PdfLayout; using EPPlus.Export.Pdf.PdfSettings; +using EPPlus.Fonts.OpenType; using OfficeOpenXml.Interfaces.Fonts; using System.Collections.Generic; using System.Linq; namespace EPPlus.Export.Pdf.PdfResources { - internal class PdfDictionaries + public class PdfDictionaries { internal readonly Dictionary Fonts = new Dictionary(); internal readonly Dictionary Patterns = new Dictionary(); internal readonly Dictionary Shadings = new Dictionary(); + internal Dictionary ShapedProviders = new Dictionary(); public void AddFont(PdfPageSettings pageSettings, string FontName, FontSubFamily SubFamily, string Text) { @@ -39,7 +41,7 @@ public void AddFont(PdfPageSettings pageSettings, string FontName, FontSubFamily manger.AddText(Text); } - public PdfFontResource GetFont(string FontName) + internal PdfFontResource GetFont(string FontName) { if (!Fonts.ContainsKey(FontName)) { From 8bd3760454dd6a79e4fdf8145a47e5d366feb35c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Thu, 28 May 2026 19:44:17 +0200 Subject: [PATCH 04/49] print titles progress --- src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs | 103 ++++++++++++++---- src/EPPlus.Export.Pdf/PdfCatalog/PdfRange.cs | 2 + .../PdfSettings/PdfPageSettings.cs | 4 +- 3 files changed, 84 insertions(+), 25 deletions(-) diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs index 3dc3cb1df1..0a803fe5e6 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs @@ -28,6 +28,8 @@ internal struct Page public int ToColumn; public bool HasPrintTitle; + public double PrintTitleWidth; + public double PrintTitleHeight; public PdfCellCollection Map; @@ -621,9 +623,35 @@ private static double[] BuildColumnXPositions(PdfPageSettings pageSettings, Page return colX; } + private static void ComputePrintTitleDimensions(PdfPageSettings pageSettings, PdfRange range, out double titleHeight, out double titleWidth) + { + titleHeight = 0d; + titleWidth = 0d; + if (pageSettings.RowsToRepeatAtTop != null) + { + for (int r = pageSettings.RowsToRepeatAtTop._fromRow; + r <= pageSettings.RowsToRepeatAtTop._toRow; r++) + { + int idx = r - range.Range._fromRow; + if (idx >= 0 && idx < range.RowHeights.Count) + titleHeight += range.RowHeights[idx].Height; + } + } + if (pageSettings.ColumnsToRepeatAtLeft != null) + { + for (int c = pageSettings.ColumnsToRepeatAtLeft._fromCol; + c <= pageSettings.ColumnsToRepeatAtLeft._toCol; c++) + { + int idx = c - range.Range._fromCol; + if (idx >= 0 && idx < range.ColWidths.Count) + titleWidth += range.ColWidths[idx]; + } + } + } + + internal static Pages GetNumberOfPages(PdfPageSettings pageSettings, PdfWorksheet pdfSheet, ref PdfRange range) { - //calculte pages needed for this range, add in col headings for width, row headings for height. THis is where we also add print headings later on. Autofit on row here too later on. var xPages = (int)Math.Max(1, Math.Ceiling(range.TotalWidth / pageSettings.ContentBounds.Width)); var yPages = (int)Math.Max(1, Math.Ceiling(range.TotalHeight / pageSettings.ContentBounds.Height)); @@ -653,9 +681,7 @@ internal static Pages GetNumberOfPages(PdfPageSettings pageSettings, PdfWorkshee if (pdfSheet.Worksheet.Row(i).PageBreak) yPages++; } - //if (HasPrintTitles Row) - //if (HasPrintTitles Column) - + ComputePrintTitleDimensions(pageSettings, range, out range.PrintTitleHeight, out range.PrintTitleWidth); Pages p = new Pages(); p.Width = xPages; p.Height = yPages; @@ -670,32 +696,58 @@ internal static Pages AssignRangeToPages(PdfPageSettings pageSettings, PdfRange var addedWidth = pages.Width > 0 ? range.AdditionalWidth / pages.Width : 0d; var addedHeight = pages.Height > 0 ? range.AdditionalHeight / pages.Height : 0d; - var colSegments = GetColumnSegments(pageSettings, range, worksheet, addedWidth); - var rowSegments = GetRowSegments(pageSettings, range, worksheet, addedHeight); + var colSegments = GetColumnSegments(pageSettings, range, worksheet, addedWidth, range.PrintTitleWidth); + var rowSegments = GetRowSegments(pageSettings, range, worksheet, addedHeight, range.PrintTitleHeight); pages.Page = new Page[colSegments.Count * rowSegments.Count]; int i = 0; if (pageSettings.PageOrders == PageOrders.DownThenOver) { - foreach (var colSeg in colSegments) - foreach (var rowSeg in rowSegments) - pages.Page[i++] = new Page { FromColumn = colSeg.From, ToColumn = colSeg.To, FromRow = rowSeg.From, ToRow = rowSeg.To }; + //foreach (var colSeg in colSegments) + // foreach (var rowSeg in rowSegments) + // pages.Page[i++] = new Page { FromColumn = colSeg.From, ToColumn = colSeg.To, FromRow = rowSeg.From, ToRow = rowSeg.To }; + for (int ci = 0; ci < colSegments.Count; ci++) + for (int ri = 0; ri < rowSegments.Count; ri++) + pages.Page[i++] = new Page + { + FromColumn = colSegments[ci].From, + ToColumn = colSegments[ci].To, + FromRow = rowSegments[ri].From, + ToRow = rowSegments[ri].To, + HeadingWidth = addedWidth, + HeadingHeight = addedHeight, + PrintTitleWidth = ci > 0 ? range.PrintTitleWidth : 0d, + PrintTitleHeight = ri > 0 ? range.PrintTitleHeight : 0d, + }; } else //if (pageSettings.PageOrders == PageOrders.OverThenDown) { - foreach (var rowSeg in rowSegments) - foreach (var colSeg in colSegments) - pages.Page[i++] = new Page { FromColumn = colSeg.From, ToColumn = colSeg.To, FromRow = rowSeg.From, ToRow = rowSeg.To }; + //foreach (var rowSeg in rowSegments) + // foreach (var colSeg in colSegments) + // pages.Page[i++] = new Page { FromColumn = colSeg.From, ToColumn = colSeg.To, FromRow = rowSeg.From, ToRow = rowSeg.To }; + for (int ri = 0; ri < rowSegments.Count; ri++) + for (int ci = 0; ci < colSegments.Count; ci++) + pages.Page[i++] = new Page + { + FromColumn = colSegments[ci].From, + ToColumn = colSegments[ci].To, + FromRow = rowSegments[ri].From, + ToRow = rowSegments[ri].To, + HeadingWidth = addedWidth, + HeadingHeight = addedHeight, + PrintTitleWidth = ci > 0 ? range.PrintTitleWidth : 0d, + PrintTitleHeight = ri > 0 ? range.PrintTitleHeight : 0d, + }; } - for (int k = 0; k < pages.Page.Length; k++) - { - var p = pages.Page[k]; - p.HeadingWidth = addedWidth; - p.HeadingHeight = addedHeight; - pages.Page[k] = p; - } + //for (int k = 0; k < pages.Page.Length; k++) + //{ + // var p = pages.Page[k]; + // p.HeadingWidth = addedWidth; + // p.HeadingHeight = addedHeight; + // pages.Page[k] = p; + //} pdfPages = pages; return pdfPages; } @@ -707,7 +759,7 @@ private struct PageSegment public PageSegment(int from, int to) { From = from; To = to; } } - private static List GetColumnSegments(PdfPageSettings pageSettings, PdfRange range, ExcelWorksheet worksheet, double addedWidth) + private static List GetColumnSegments(PdfPageSettings pageSettings, PdfRange range, ExcelWorksheet worksheet, double addedWidth, double titleWidth) { var segments = new List(); int segStartIdx = 0; @@ -717,8 +769,11 @@ private static List GetColumnSegments(PdfPageSettings pageSettings, { int actualCol = range.Range._fromCol + col; + double effectiveAdded = addedWidth + (segments.Count == 0 ? 0d : titleWidth); + + // Content-bounds overflow: col doesn't fit, end segment before it and reprocess. - if (width + range.ColWidths[col] + addedWidth >= pageSettings.ContentBounds.Width) + if (width + range.ColWidths[col] + effectiveAdded >= pageSettings.ContentBounds.Width) { segments.Add(new PageSegment(range.Map.FromColumn + segStartIdx, range.Map.FromColumn + col - 1)); segStartIdx = col; @@ -745,7 +800,7 @@ private static List GetColumnSegments(PdfPageSettings pageSettings, return segments; } - private static List GetRowSegments(PdfPageSettings pageSettings, PdfRange range, ExcelWorksheet worksheet, double addedHeight) + private static List GetRowSegments(PdfPageSettings pageSettings, PdfRange range, ExcelWorksheet worksheet, double addedHeight, double titleHeight) { var segments = new List(); int segStartIdx = 0; @@ -755,8 +810,10 @@ private static List GetRowSegments(PdfPageSettings pageSettings, Pd { int actualRow = range.Range._fromRow + row; + double effectiveAdded = addedHeight + (segments.Count == 0 ? 0d : titleHeight); + // Content-bounds overflow: row doesn't fit, end segment before it and reprocess. - if (height + range.RowHeights[row].Height + addedHeight >= pageSettings.ContentBounds.Height) + if (height + range.RowHeights[row].Height + effectiveAdded >= pageSettings.ContentBounds.Height) { segments.Add(new PageSegment(range.Map.FromRow + segStartIdx, range.Map.FromRow + row - 1)); segStartIdx = row; diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfRange.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfRange.cs index 43937d834c..ff9ffd906a 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfRange.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfRange.cs @@ -20,6 +20,8 @@ internal struct PdfRange public double TotalWidth; public double AdditionalHeight; public double AdditionalWidth; + public double PrintTitleHeight; + public double PrintTitleWidth; public PdfRange(ExcelRangeBase range, bool extendColumns) { diff --git a/src/EPPlus.Export.Pdf/PdfSettings/PdfPageSettings.cs b/src/EPPlus.Export.Pdf/PdfSettings/PdfPageSettings.cs index 7ee46b12f1..1b63f4b5ab 100644 --- a/src/EPPlus.Export.Pdf/PdfSettings/PdfPageSettings.cs +++ b/src/EPPlus.Export.Pdf/PdfSettings/PdfPageSettings.cs @@ -94,12 +94,12 @@ public OpenTypeFontEngine FontEngine /// /// Set the range to repeat at the top of the page. /// - public ExcelRange RowsToRepeatAtTop = null; + public ExcelAddressBase RowsToRepeatAtTop = null; /// /// Set the range to repeat to the left of the page. /// - public ExcelRange ColumnsToRepeatAtLeft = null; + public ExcelAddressBase ColumnsToRepeatAtLeft = null; /// /// Set if comments and notes should be included. From d9d855f18e8ec3b34d379b820731642769026b72 Mon Sep 17 00:00:00 2001 From: swmal <897655+swmal@users.noreply.github.com> Date: Tue, 2 Jun 2026 16:03:56 +0200 Subject: [PATCH 05/49] Fix PDF rich text rendering; remove source workbook mutation in export --- src/EPPlus.Export.Pdf/ExcelPdf.cs | 244 +++++++++--------- .../PdfCatalog/PdfCatalog.cs | 122 +++++---- .../PdfCatalog/PdfTextMap.cs | 204 +++++++++------ .../PdfCatalog/PdfTextShaper.cs | 23 -- .../PdfObjects/PdfContentStream.cs | 31 +-- .../PdfResources/PdfFontResource.cs | 4 +- src/EPPlus/Style/ExcelColor.cs | 20 ++ 7 files changed, 359 insertions(+), 289 deletions(-) diff --git a/src/EPPlus.Export.Pdf/ExcelPdf.cs b/src/EPPlus.Export.Pdf/ExcelPdf.cs index 65f34e8a1b..3ce420925c 100644 --- a/src/EPPlus.Export.Pdf/ExcelPdf.cs +++ b/src/EPPlus.Export.Pdf/ExcelPdf.cs @@ -34,10 +34,12 @@ public class ExcelPdf { internal List _workheets = new List(); internal ExcelRangeBase _range; - private PdfPageSettings PageSettings; - internal List Document = new List(); + private PdfPageSettings _pageSettings; + internal List _document = new List(); internal string header = "%PDF-1.7\n"; - internal PdfDictionaries Dictionaries = new PdfDictionaries(); + internal PdfDictionaries _dictionaries = new PdfDictionaries(); + private string _debugString; + public ExcelPdf() { } @@ -50,8 +52,8 @@ public ExcelPdf() public ExcelPdf(ExcelWorksheet worksheet, PdfPageSettings pageSettings = null) { _workheets.Add(worksheet); - PageSettings = pageSettings == null ? new PdfPageSettings() : pageSettings; - PageSettings.defaultFontName = worksheet.Workbook.ThemeManager.CurrentTheme.FontScheme.MinorFont[0].Typeface; + _pageSettings = pageSettings == null ? new PdfPageSettings() : pageSettings; + _pageSettings.defaultFontName = worksheet.Workbook.ThemeManager.CurrentTheme.FontScheme.MinorFont[0].Typeface; } /// @@ -96,9 +98,9 @@ internal string GetPatternLabel(PdfCellLayout layout) if ((layout.CellFillData.PatternStyle != ExcelFillStyle.Solid && layout.CellFillData.PatternStyle != ExcelFillStyle.None) || layout.CellFillData.GradientFillData != null) { var patternName = layout.CellFillData.id; - if (Dictionaries.Patterns.ContainsKey(patternName)) + if (_dictionaries.Patterns.ContainsKey(patternName)) { - return Dictionaries.Patterns[patternName].Label; + return _dictionaries.Patterns[patternName].Label; } } return null; @@ -107,28 +109,28 @@ internal string GetPatternLabel(PdfCellLayout layout) //Add Fonts //Need to update this method a bit. We should check for all default fonts and not only courier new? Also need to check if we are allowed to embedd the font. internal void AddFontData() { - if (PageSettings.EmbeddFonts) + if (_pageSettings.EmbeddFonts) { - foreach (var font in Dictionaries.Fonts) + foreach (var font in _dictionaries.Fonts) { //font.Value.CreateGidsAndCharMaps(); - var CidSet = font.Value.GetCidSet(Document.Count + 1); - if (CidSet != null) Document.Add(CidSet); - Document.Add(font.Value.GetEmbeddedFontStreamObject(Document.Count + 1)); - Document.Add(font.Value.GetFontDescriptorObject(Document.Count + 1)); - Document.Add(font.Value.GetCIDFontObject(Document.Count + 1)); - Document.Add(font.Value.GetUnicodeCmapObject(Document.Count + 1)); - Document.Add(font.Value.GetType0FontDictObject(Document.Count + 1)); - font.Value.GetFontObject(Document.Count); + var cidSet = font.Value.GetCidSet(_document.Count + 1); + if (cidSet != null) _document.Add(cidSet); + _document.Add(font.Value.GetEmbeddedFontStreamObject(_document.Count + 1)); + _document.Add(font.Value.GetFontDescriptorObject(_document.Count + 1)); + _document.Add(font.Value.GetCIDFontObject(_document.Count + 1)); + _document.Add(font.Value.GetUnicodeCmapObject(_document.Count + 1)); + _document.Add(font.Value.GetType0FontDictObject(_document.Count + 1)); + font.Value.GetFontObject(_document.Count); } } else { - foreach (var font in Dictionaries.Fonts) + foreach (var font in _dictionaries.Fonts) { - Document.Add(font.Value.GetFontDescriptorObject(Document.Count + 1)); - Document.Add(font.Value.GetWidthsObject(Document.Count + 1)); - Document.Add(font.Value.GetFontObject(Document.Count + 1)); + _document.Add(font.Value.GetFontDescriptorObject(_document.Count + 1)); + _document.Add(font.Value.GetWidthsObject(_document.Count + 1)); + _document.Add(font.Value.GetFontObject(_document.Count + 1)); } } } @@ -136,47 +138,47 @@ internal void AddFontData() //Add Patterns internal void AddPatternData() { - foreach (var pattern in Dictionaries.Patterns) + foreach (var pattern in _dictionaries.Patterns) { - Document.Add(pattern.Value.GetPatternObject(Document.Count + 1)); + _document.Add(pattern.Value.GetPatternObject(_document.Count + 1)); } } //Add Shadings and accompanying pattern - internal void AddShadingsData(PdfDictionaries dictionaries) + internal void AddShadingsData() { - foreach (var shading in Dictionaries.Shadings) + foreach (var shading in _dictionaries.Shadings) { - Document.Add(shading.Value.GetShadingObject(Document.Count + 1)); - Document.Add(shading.Value.GetShadingPatternObject(Document.Count + 1, Document.Count)); - int label = dictionaries.Patterns.Last().Value.labelNumber + 1; + _document.Add(shading.Value.GetShadingObject(_document.Count + 1)); + _document.Add(shading.Value.GetShadingPatternObject(_document.Count + 1, _document.Count)); + int label = _dictionaries.Patterns.Last().Value.labelNumber + 1; var pr = new PdfPatternResource(label, shading.Value.CellFillData); - pr.objectNumber = Document.Count; - dictionaries.Patterns.Add(shading.Value.CellFillData.id, pr); + pr.objectNumber = _document.Count; + _dictionaries.Patterns.Add(shading.Value.CellFillData.id, pr); } } //Create Page private PdfPage AddPage(int pagesObjectNumber, List contentObjectNumbers, PdfPageSettings settings) { - var page = new PdfPage(Document.Count + 1, pagesObjectNumber, contentObjectNumbers, settings.PageSize, Dictionaries); - Document.Add(page); + var page = new PdfPage(_document.Count + 1, pagesObjectNumber, contentObjectNumbers, settings.PageSize, _dictionaries); + _document.Add(page); return page; } //Create Pages private PdfPages AddPages() { - var pages = new PdfPages(Document.Count + 1, new List{}); - Document.Add(pages); + var pages = new PdfPages(_document.Count + 1, new List { }); + _document.Add(pages); return pages; } //Create Catalog private PdfObjects.PdfCatalog AddCatalog(int pagesObjectNumber) { - var catalog = new PdfObjects.PdfCatalog(Document.Count + 1, pagesObjectNumber); - Document.Add(catalog); + var catalog = new PdfObjects.PdfCatalog(_document.Count + 1, pagesObjectNumber); + _document.Add(catalog); return catalog; } @@ -186,12 +188,12 @@ private void AddContent(Transform pageLayout, PdfPage page) //var cells = pageLayout.ChildObjects.Where(t => t is PdfCellLayout || t is PdfCellContentLayout || t is PdfCellBorderLayout).GroupBy(t => t.Name); var cells = pageLayout.ChildObjects.Where(t => (t is PdfCellLayout || t is PdfCellContentLayout || t is PdfCellBorderLayout) && !(t is PdfCellContentLayout cc && cc.IsHeaderFooter)).GroupBy(t => t.Name); var headerFooterLayouts = pageLayout.ChildObjects.OfType().Where(t => t.IsHeaderFooter); - var contentStream = new PdfContentStream(Document.Count + 1); + var contentStream = new PdfContentStream(_document.Count + 1); contentStream.AddCommand($"% {pageLayout.Name} start"); //Add clipping rectangle around page content. contentStream.AddCommand("q"); contentStream.AddMarginClipping((PdfPageLayout)pageLayout); - if (PageSettings.ShowGridLines) + if (_pageSettings.ShowGridLines) { contentStream.AddInnerGridLines(pageLayout); } @@ -206,7 +208,7 @@ private void AddContent(Transform pageLayout, PdfPage page) contentStream.AddCellLayout(layout, GetPatternLabel(layout)); break; case PdfCellContentLayout contentLayout: - contentStream.AddCellContentLayout(contentLayout, Dictionaries, PageSettings); + contentStream.AddCellContentLayout(contentLayout, _dictionaries, _pageSettings); break; case PdfCellBorderLayout borderLayout: contentStream.AddBorderLayout(borderLayout); @@ -217,16 +219,16 @@ private void AddContent(Transform pageLayout, PdfPage page) //Close the clipping rectangle. contentStream.AddCommand("Q"); contentStream.AddCommand($"% Margin Clip End"); - if (PageSettings.ShowGridLines) + if (_pageSettings.ShowGridLines) { contentStream.AddOuterGridBorder(pageLayout); } //Add header and footer. foreach (var hf in headerFooterLayouts) { - contentStream.AddCellContentLayout(hf, Dictionaries, PageSettings); + contentStream.AddCellContentLayout(hf, _dictionaries, _pageSettings); } - Document.Add(contentStream); + _document.Add(contentStream); page.contentObjectNumbers.Add(contentStream.objectNumber); contentStream.AddCommand($"% {pageLayout.Name} end"); } @@ -238,23 +240,35 @@ private void AddHeaderFooter(PdfContentStream contentStream, Transform pageLayou foreach (var hf in headerFooter) { var headerFooterLayout = hf as PdfHeaderFooterLayout; - contentStream.AddCellContentLayout(headerFooterLayout, Dictionaries, PageSettings); + contentStream.AddCellContentLayout(headerFooterLayout, _dictionaries, _pageSettings); } } //Add Info private PdfInfoObject AddInfoObject(string workBookName = "") { - var info = new PdfInfoObject(Document.Count + 1, workBookName); - Document.Add(info); + var info = new PdfInfoObject(_document.Count + 1, workBookName); + _document.Add(info); return info; } - internal void CreatePdf(PdfPageSettings pageSettings, PdfDictionaries dictionaries, Transform layout, string fileName) { - PageSettings = pageSettings; - Dictionaries = dictionaries; + using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write)) + { + CreatePdf(pageSettings, dictionaries, layout, fs); + } + + if (_pageSettings.Debug && _pageSettings.PrintAsText) + { + WriteDebugText(fileName); + } + } + + internal void CreatePdf(PdfPageSettings pageSettings, PdfDictionaries dictionaries, Transform layout, Stream stream) + { + _pageSettings = pageSettings; + _dictionaries = dictionaries; var catalog = AddCatalog(2); //Create Pages @@ -265,64 +279,45 @@ internal void CreatePdf(PdfPageSettings pageSettings, PdfDictionaries dictionari //Create Patterns AddPatternData(); //Create Shadings - AddShadingsData(dictionaries); + AddShadingsData(); //Create Page and Content for (int i = 0; i < layout.ChildObjects.Count; i++) { var pageLayout = layout.ChildObjects[i]; - var page = AddPage(2, new List(), PageSettings); + var page = AddPage(2, new List(), _pageSettings); AddContent(pageLayout, page); pages.pageObjectNumbers.Add(page.objectNumber); } var info = AddInfoObject(); - string debugString = ""; - //write to pdf - PdfCrossRefTable crossRefTable = new PdfCrossRefTable(); - //start wring pdf binary - using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write)) + + WriteDocumentToStream(stream, catalog, info); + } + + /// + /// Create the pdf from the supplied worksheet. + /// + /// The file name + public void CreatePdf(string Filename) + { + using (var fs = new FileStream(Filename, FileMode.Create, FileAccess.Write)) { - using (var bw = new BinaryWriter(fs, Encoding.ASCII)) - { - //Write header - bw.Write(Encoding.ASCII.GetBytes(header)); - debugString += header; - //Write body - foreach (var pdfobj in Document) - { - crossRefTable.AddPosition(fs.Position); - pdfobj.ToPdfBytes(bw); - debugString += pdfobj.ToPdfString(); - } - //Write CrossReference - crossRefTable.Write(bw, fs.Position, Document.Count); - debugString += crossRefTable.WriteString(Document.Count); - // Write trailer - PdfTrailer.Write(bw, Document.Count, catalog.objectNumber, info.objectNumber, crossRefTable.StartPosition); - debugString += PdfTrailer.WriteString(Document.Count, catalog.objectNumber, info.objectNumber, crossRefTable.StartPosition); - } + CreatePdf(fs); } - //Write pdf as txt for debug. - if (PageSettings.Debug && PageSettings.PrintAsText) + + if (_pageSettings.Debug && _pageSettings.PrintAsText) { - using (var fs = new FileStream(fileName + ".txt", FileMode.Create, FileAccess.Write)) - { - using (var wr = new StreamWriter(fs)) - { - wr.Write(debugString); - } - } + WriteDebugText(Filename); } } - /// - /// Create the pdf from the supplied worksheet. + /// Create the pdf from the supplied worksheet and write it to a stream. /// - /// The file name - public void CreatePdf(string Filename) + /// The stream to write the pdf to. The stream will not be closed. + public void CreatePdf(Stream stream) { //Create Catalog - var catalogLayout = new PdfCatalogLayout(_workheets[0], PageSettings, Dictionaries); + var catalogLayout = new PdfCatalogLayout(_workheets[0], _pageSettings, _dictionaries); var catalog = AddCatalog(2); //Create Pages var pagesLayout = catalogLayout.ChildObjects[0]; @@ -332,53 +327,64 @@ public void CreatePdf(string Filename) //Create Patterns AddPatternData(); //Create Shadings - AddShadingsData(Dictionaries); + AddShadingsData(); //Create Page and Content for (int i = 0; i < pagesLayout.ChildObjects.Count; i++) { var pageLayout = pagesLayout.ChildObjects[i]; - var page = AddPage(2, new List(), PageSettings); + var page = AddPage(2, new List(), _pageSettings); AddContent(pageLayout, page); pages.pageObjectNumbers.Add(page.objectNumber); } var info = AddInfoObject(_workheets[0].Workbook._package.File.Name); - string debugString = ""; - //write to pdf + + WriteDocumentToStream(stream, catalog, info); + } + + //Write the document and cross-ref/trailer to the supplied stream. + //The stream is not closed; the caller owns it. + private void WriteDocumentToStream(Stream stream, PdfObjects.PdfCatalog catalog, PdfInfoObject info) + { + _debugString = ""; PdfCrossRefTable crossRefTable = new PdfCrossRefTable(); - //start wring pdf binary - using (var fs = new FileStream(Filename, FileMode.Create, FileAccess.Write)) + + //Use a BinaryWriter without disposing it, so the underlying stream stays open for the caller. + //BinaryWriter does not own the stream when we don't dispose it; we just flush at the end. + var bw = new BinaryWriter(stream, Encoding.ASCII); + try { - using (var bw = new BinaryWriter(fs, Encoding.ASCII)) + //Write header + bw.Write(Encoding.ASCII.GetBytes(header)); + _debugString += header; + //Write body + foreach (var pdfobj in _document) { - //Write header - bw.Write(Encoding.ASCII.GetBytes(header)); - debugString += header; - //Write body - foreach (var pdfobj in Document) - { - crossRefTable.AddPosition(fs.Position); - pdfobj.ToPdfBytes(bw); - debugString += pdfobj.ToPdfString(); - } - //Write CrossReference - crossRefTable.Write(bw, fs.Position, Document.Count); - debugString += crossRefTable.WriteString(Document.Count); - // Write trailer - PdfTrailer.Write(bw, Document.Count, catalog.objectNumber, info.objectNumber, crossRefTable.StartPosition); - debugString += PdfTrailer.WriteString(Document.Count, catalog.objectNumber, info.objectNumber, crossRefTable.StartPosition); + crossRefTable.AddPosition(stream.Position); + pdfobj.ToPdfBytes(bw); + _debugString += pdfobj.ToPdfString(); } + //Write CrossReference + crossRefTable.Write(bw, stream.Position, _document.Count); + _debugString += crossRefTable.WriteString(_document.Count); + // Write trailer + PdfTrailer.Write(bw, _document.Count, catalog.objectNumber, info.objectNumber, crossRefTable.StartPosition); + _debugString += PdfTrailer.WriteString(_document.Count, catalog.objectNumber, info.objectNumber, crossRefTable.StartPosition); + } + finally + { + bw.Flush(); } - //Write pdf as txt for debug. - if (PageSettings.Debug && PageSettings.PrintAsText) + } + + private void WriteDebugText(string fileName) + { + using (var fs = new FileStream(fileName + ".txt", FileMode.Create, FileAccess.Write)) { - using (var fs = new FileStream(Filename + ".txt", FileMode.Create, FileAccess.Write)) + using (var wr = new StreamWriter(fs)) { - using ( var wr = new StreamWriter(fs)) - { - wr.Write(debugString); - } + wr.Write(_debugString); } } } } -} +} \ No newline at end of file diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs index 632e097070..75c517d086 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs @@ -2,16 +2,18 @@ using EPPlus.Export.Pdf.PdfSettings; using EPPlus.Graphics; using OfficeOpenXml; +using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; namespace EPPlus.Export.Pdf.PdfCatalog { public class PdfCatalog { - internal PdfDictionaries Dictionaries = new PdfDictionaries(); - private bool AddTextForHeadings = true; + internal PdfDictionaries _dictionaries = new PdfDictionaries(); + private bool _addTextForHeadings = true; //Constructors public PdfCatalog() { } @@ -41,45 +43,71 @@ private void HandleWorksheetCollection(PdfPageSettings pageSettings, ExcelWorksh } public PdfCatalog(PdfPageSettings pageSettings, ExcelWorksheet worksheet, string fileName) + { + BuildPdf(pageSettings, worksheet, (excelPdf, layout) => + excelPdf.CreatePdf(pageSettings, _dictionaries, layout, fileName)); + } + + public PdfCatalog(PdfPageSettings pageSettings, ExcelWorksheet worksheet, Stream stream) + { + BuildPdf(pageSettings, worksheet, (excelPdf, layout) => + excelPdf.CreatePdf(pageSettings, _dictionaries, layout, stream)); + } + + private void BuildPdf(PdfPageSettings pageSettings, ExcelWorksheet worksheet, Action writePdf) { pageSettings.defaultFontName = worksheet.Workbook.ThemeManager.CurrentTheme.FontScheme.MinorFont[0].Typeface; - Stopwatch sw = Stopwatch.StartNew(); + PdfWorksheet pdfSheet = null; + try + { + Stopwatch sw = Stopwatch.StartNew(); - //Collect Text - PdfWorksheet pdfSheet = GetPdfWorksheet(pageSettings, worksheet); - sw.Stop(); - var CollectTextTime = sw.ElapsedMilliseconds; - sw.Reset(); - sw.Start(); + //Collect Text + pdfSheet = GetPdfWorksheet(pageSettings, worksheet); + sw.Stop(); + var CollectTextTime = sw.ElapsedMilliseconds; + sw.Reset(); + sw.Start(); - //Shape Text - ShapeTextInPdfWorksheet(pageSettings, pdfSheet); - sw.Stop(); - var ShapeTextTime = sw.ElapsedMilliseconds; - sw.Reset(); - sw.Start(); - - //Auto-Fit Rows - PdfCalculateRowHeight.ResizeRowHeights(pdfSheet); - sw.Stop(); - var AutoFitRowTime = sw.ElapsedMilliseconds; - sw.Reset(); - sw.Start(); - - //Create Layout - var Layout = GetLayout(pageSettings, pdfSheet); - sw.Stop(); - var CreateLayoutTime = sw.ElapsedMilliseconds; - sw.Reset(); - sw.Start(); - - //Create Pdf - ExcelPdf excelPdf = new ExcelPdf(); - excelPdf.CreatePdf(pageSettings, Dictionaries, Layout, fileName); - sw.Stop(); - var CreatePdfTime = sw.ElapsedMilliseconds; - sw.Reset(); + //Shape Text + ShapeTextInPdfWorksheet(pageSettings, pdfSheet); + sw.Stop(); + var ShapeTextTime = sw.ElapsedMilliseconds; + sw.Reset(); + sw.Start(); + + //Auto-Fit Rows + PdfCalculateRowHeight.ResizeRowHeights(pdfSheet); + sw.Stop(); + var AutoFitRowTime = sw.ElapsedMilliseconds; + sw.Reset(); + sw.Start(); + + //Create Layout + var Layout = GetLayout(pageSettings, pdfSheet); + sw.Stop(); + var CreateLayoutTime = sw.ElapsedMilliseconds; + sw.Reset(); + sw.Start(); + + //Create Pdf + ExcelPdf excelPdf = new ExcelPdf(); + writePdf(excelPdf, Layout); + sw.Stop(); + var CreatePdfTime = sw.ElapsedMilliseconds; + sw.Reset(); + } + finally + { + //Clean up the temporary worksheet used to build the comments/notes pages, + //so the source workbook isn't permanently mutated by the PDF export. + if (pdfSheet != null && pdfSheet.CommentsAndNotesSheet != null) + { + worksheet.Workbook.Worksheets.Delete(pdfSheet.CommentsAndNotesSheet); + pdfSheet.CommentsAndNotesSheet = null; + } + } } public PdfCatalog(PdfPageSettings pageSettings, ExcelRangeBase range) @@ -100,7 +128,7 @@ internal PdfCellCollection GetCellCollectionFromRange(PdfPageSettings pageSettin private Transform GetLayout(PdfPageSettings pageSettings, PdfWorksheet pdfSheet) { PdfWorksheet[] pdfSheets = new PdfWorksheet[1] { pdfSheet }; - var Layout = PdfLayout.GetLayout(pageSettings, Dictionaries, pdfSheets); + var Layout = PdfLayout.GetLayout(pageSettings, _dictionaries, pdfSheets); return Layout; } @@ -108,16 +136,16 @@ private Transform GetLayout(PdfPageSettings pageSettings, PdfWorksheet pdfSheet) internal void ShapeTextInPdfWorksheet(PdfPageSettings pageSettings, PdfWorksheet pdfSheet) { // Pass 1: collect text per font - IterateCells(pdfSheet, cell => PdfTextShaper.CollectText(Dictionaries, cell)); + IterateCells(pdfSheet, cell => PdfTextShaper.CollectText(_dictionaries, cell)); // Pass 2: build one provider per font - foreach (var kvp in Dictionaries.Fonts) + foreach (var kvp in _dictionaries.Fonts) { - Dictionaries.ShapedProviders[kvp.Key] = kvp.Value.fontSubsetManager.CreateSubsettedProvider(); + _dictionaries.ShapedProviders[kvp.Key] = kvp.Value.fontSubsetManager.CreateSubsettedProvider(); } // Pass 3: shape text using the pre-built providers - IterateCells(pdfSheet, cell => PdfTextShaper.ShapeText(pageSettings, Dictionaries, cell)); + IterateCells(pdfSheet, cell => PdfTextShaper.ShapeText(pageSettings, _dictionaries, cell)); } private void IterateCells(PdfWorksheet pdfSheet, System.Action action) @@ -162,8 +190,8 @@ internal PdfWorksheet GetPdfWorksheet(PdfPageSettings pageSettings, ExcelWorkshe pdfSheet.Ranges = new List(); pdfSheet.Worksheet = worksheet; pdfSheet.Ranges = GetRanges(pdfSheet.Worksheet); - if (pageSettings.ShowHeadings && AddTextForHeadings) Dictionaries.AddFont(pageSettings, pdfSheet.NormalStyle.Style.Font.Name, pdfSheet.GetSubFamilyFromNormalStyle, "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); - AddTextForHeadings = false; + if (pageSettings.ShowHeadings && _addTextForHeadings) _dictionaries.AddFont(pageSettings, pdfSheet.NormalStyle.Style.Font.Name, pdfSheet.GetSubFamilyFromNormalStyle, "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); + _addTextForHeadings = false; GetMaps(pageSettings, pdfSheet, pdfSheet.Ranges); GetHeaderFooter(pageSettings, pdfSheet); GetCommentsAndNotes(pageSettings, pdfSheet); @@ -176,8 +204,8 @@ private PdfWorksheet GetPdfWorksheet(PdfPageSettings pageSettings, ExcelRangeBas pdfSheet.Ranges = new List(); pdfSheet.Worksheet = excelRange.Worksheet; pdfSheet.Ranges.Add(new PdfRange(excelRange, false)); - if (pageSettings.ShowHeadings && AddTextForHeadings) Dictionaries.AddFont(pageSettings, pdfSheet.NormalStyle.Style.Font.Name, pdfSheet.GetSubFamilyFromNormalStyle, "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); - AddTextForHeadings = false; + if (pageSettings.ShowHeadings && _addTextForHeadings) _dictionaries.AddFont(pageSettings, pdfSheet.NormalStyle.Style.Font.Name, pdfSheet.GetSubFamilyFromNormalStyle, "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); + _addTextForHeadings = false; pdfSheet.Ranges[0] = GetMaps(pageSettings, pdfSheet, pdfSheet.Ranges[0]); GetHeaderFooter(pageSettings, pdfSheet); GetCommentsAndNotes(pageSettings, pdfSheet); @@ -216,14 +244,14 @@ private void GetMaps(PdfPageSettings pageSettings, PdfWorksheet pdfSheet, List + /// Returns the resolved color as a , taking theme, tint, indexed, RGB, and auto values into account. + /// Returns if the color is not set. + /// + public Color ToColor() + { + var hex = LookupColor(); + if (string.IsNullOrEmpty(hex) || hex == "0") return Color.Empty; + + // LookupColor returns strings like "#FFRRGGBB". Strip the leading '#'. + if (hex[0] == '#') hex = hex.Substring(1); + + int argb; + if (int.TryParse(hex, System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture, out argb)) + { + return Color.FromArgb(argb); + } + return Color.Empty; + } } } From 7cc305d6781361e14e58cd0e05e2908a394bb45f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Tue, 2 Jun 2026 17:02:33 +0200 Subject: [PATCH 06/49] print titles progress --- .../PdfCatalog/PdfCatalog.cs | 70 +++++++++++++++++++ src/EPPlus.Export.Pdf/PdfCatalog/PdfCell.cs | 3 + src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs | 14 ++-- src/EPPlus.Export.Pdf/PdfCatalog/PdfRange.cs | 2 +- .../PdfCatalog/PdfWorksheet.cs | 4 ++ 5 files changed, 84 insertions(+), 9 deletions(-) diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs index 052abc1a73..53659c69dd 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs @@ -159,6 +159,7 @@ private PdfWorksheet GetPdfWorksheet(PdfPageSettings pageSettings, ExcelWorkshee if(pageSettings.ShowHeadings && AddTextForHeadings) Dictionaries.AddFont(pageSettings, pdfSheet.NormalStyle.Style.Font.Name, pdfSheet.GetSubFamilyFromNormalStyle, "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); AddTextForHeadings = false; GetMaps(pageSettings, pdfSheet, pdfSheet.Ranges); + GetPrintTitles(pageSettings, pdfSheet); GetHeaderFooter(pageSettings, pdfSheet); GetCommentsAndNotes(pageSettings, pdfSheet); return pdfSheet; @@ -173,6 +174,7 @@ private PdfWorksheet GetPdfWorksheet(PdfPageSettings pageSettings, ExcelRangeBas if (pageSettings.ShowHeadings && AddTextForHeadings) Dictionaries.AddFont(pageSettings, pdfSheet.NormalStyle.Style.Font.Name, pdfSheet.GetSubFamilyFromNormalStyle, "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); AddTextForHeadings = false; pdfSheet.Ranges[0] = GetMaps(pageSettings, pdfSheet, pdfSheet.Ranges[0]); + GetPrintTitles(pageSettings, pdfSheet); GetHeaderFooter(pageSettings, pdfSheet); GetCommentsAndNotes(pageSettings, pdfSheet); return pdfSheet; @@ -215,6 +217,74 @@ private PdfRange GetMaps(PdfPageSettings pageSettings, PdfWorksheet pdfSheet, Pd return range; } + private void GetPrintTitles(PdfPageSettings pageSettings, PdfWorksheet pdfSheet) + { + var worksheet = pdfSheet.Worksheet; + // --- Step 1: auto-detect from the worksheet's _xlnm.Print_Titles defined name --- + if (worksheet.Names.ContainsKey("_xlnm.Print_Titles")) + { + var printTitlesName = worksheet.Names["_xlnm.Print_Titles"]; + foreach (var address in printTitlesName.Addresses) + { + // A full-row reference spans every column (e.g. $1:$3 → _toCol == MaxColumns) + if (address._toCol >= ExcelPackage.MaxColumns) + { + pdfSheet.PrintTitleRowFrom = address._fromRow; + pdfSheet.PrintTitleRowTo = address._toRow; + } + // A full-column reference spans every row (e.g. $A:$B → _toRow == MaxRows) + else if (address._toRow >= ExcelPackage.MaxRows) + { + pdfSheet.PrintTitleColFrom = address._fromCol; + pdfSheet.PrintTitleColTo = address._toCol; + } + } + } + // --- Step 2: PdfPageSettings overrides take precedence over the defined name --- + if (pageSettings.RowsToRepeatAtTop != null) + { + pdfSheet.PrintTitleRowFrom = pageSettings.RowsToRepeatAtTop._fromRow; + pdfSheet.PrintTitleRowTo = pageSettings.RowsToRepeatAtTop._toRow; + } + if (pageSettings.ColumnsToRepeatAtLeft != null) + { + pdfSheet.PrintTitleColFrom = pageSettings.ColumnsToRepeatAtLeft._fromCol; + pdfSheet.PrintTitleColTo = pageSettings.ColumnsToRepeatAtLeft._toCol; + } + + // --- Step 3: mark cells so the renderer can identify them instantly --- + foreach (var range in pdfSheet.Ranges) + MarkPrintTitleCells(pdfSheet, range); + } + + private static void MarkPrintTitleCells(PdfWorksheet pdfSheet, PdfRange range) + { + var map = range.Map; + + for (int row = map.FromRow; row <= map.ToRow; row++) + { + bool isTitleRow = pdfSheet.PrintTitleRowFrom >= 0 + && row >= pdfSheet.PrintTitleRowFrom + && row <= pdfSheet.PrintTitleRowTo; + + for (int col = map.FromColumn; col <= map.ToColumn; col++) + { + bool isTitleCol = pdfSheet.PrintTitleColFrom >= 0 + && col >= pdfSheet.PrintTitleColFrom + && col <= pdfSheet.PrintTitleColTo; + + if (!isTitleRow && !isTitleCol) continue; + + var cell = map[row, col]; + if (cell == null) continue; + + if (isTitleRow) cell.IsPrintTitleRow = true; + if (isTitleCol) cell.IsPrintTitleCol = true; + } + } + } + + private void GetHeaderFooter(PdfPageSettings pageSettings, PdfWorksheet pdfSheet) { pdfSheet.HeaderFooters = new PdfHeaderFooterCollection(pageSettings, Dictionaries, pdfSheet, pdfSheet.Worksheet.HeaderFooter); diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCell.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfCell.cs index d4ded6100a..ef53e49648 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCell.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfCell.cs @@ -30,6 +30,9 @@ internal class PdfCell public PdfCell Main; public ExcelAddressBase MergedAddress; + public bool IsPrintTitleRow; + public bool IsPrintTitleCol; + //public int FromCol; diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs index 0a803fe5e6..a75ecf8f05 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs @@ -623,24 +623,22 @@ private static double[] BuildColumnXPositions(PdfPageSettings pageSettings, Page return colX; } - private static void ComputePrintTitleDimensions(PdfPageSettings pageSettings, PdfRange range, out double titleHeight, out double titleWidth) + private static void ComputePrintTitleDimensions(PdfWorksheet pdfSheet, PdfRange range, out double titleHeight, out double titleWidth) { titleHeight = 0d; titleWidth = 0d; - if (pageSettings.RowsToRepeatAtTop != null) + if (pdfSheet.PrintTitleRowFrom >= 0) { - for (int r = pageSettings.RowsToRepeatAtTop._fromRow; - r <= pageSettings.RowsToRepeatAtTop._toRow; r++) + for (int r = pdfSheet.PrintTitleRowFrom; r <= pdfSheet.PrintTitleRowTo; r++) { int idx = r - range.Range._fromRow; if (idx >= 0 && idx < range.RowHeights.Count) titleHeight += range.RowHeights[idx].Height; } } - if (pageSettings.ColumnsToRepeatAtLeft != null) + if (pdfSheet.PrintTitleColFrom >= 0) { - for (int c = pageSettings.ColumnsToRepeatAtLeft._fromCol; - c <= pageSettings.ColumnsToRepeatAtLeft._toCol; c++) + for (int c = pdfSheet.PrintTitleColFrom; c <= pdfSheet.PrintTitleColTo; c++) { int idx = c - range.Range._fromCol; if (idx >= 0 && idx < range.ColWidths.Count) @@ -681,7 +679,7 @@ internal static Pages GetNumberOfPages(PdfPageSettings pageSettings, PdfWorkshee if (pdfSheet.Worksheet.Row(i).PageBreak) yPages++; } - ComputePrintTitleDimensions(pageSettings, range, out range.PrintTitleHeight, out range.PrintTitleWidth); + ComputePrintTitleDimensions(pdfSheet, range, out range.PrintTitleHeight, out range.PrintTitleWidth); Pages p = new Pages(); p.Width = xPages; p.Height = yPages; diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfRange.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfRange.cs index ff9ffd906a..e979937ee2 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfRange.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfRange.cs @@ -22,7 +22,7 @@ internal struct PdfRange public double AdditionalWidth; public double PrintTitleHeight; public double PrintTitleWidth; - + public PdfRange(ExcelRangeBase range, bool extendColumns) { Range = range; diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfWorksheet.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfWorksheet.cs index fe9b342cd0..acf8748a20 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfWorksheet.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfWorksheet.cs @@ -21,6 +21,10 @@ internal class PdfWorksheet public PdfHeaderFooterCollection HeaderFooters = null; public double ZeroCharWidth; public int ToRow; + public int PrintTitleRowFrom = -1; + public int PrintTitleRowTo = -1; + public int PrintTitleColFrom = -1; + public int PrintTitleColTo = -1; //EPPlus references public ExcelWorksheet Worksheet { get; set; } From ca64ab0000a6067b552313c7de0dfeadc5ff9ef4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Wed, 3 Jun 2026 10:56:58 +0200 Subject: [PATCH 07/49] fixed errors --- src/EPPlus.Export.Pdf/ExcelPdf.cs | 6 +- src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs | 12 ++-- .../PdfCatalog/PdfTextMap.cs | 55 +++++++++++-------- .../PdfObjects/PdfContentStream.cs | 7 +-- 4 files changed, 45 insertions(+), 35 deletions(-) diff --git a/src/EPPlus.Export.Pdf/ExcelPdf.cs b/src/EPPlus.Export.Pdf/ExcelPdf.cs index 80fa7354c6..94aa6390b7 100644 --- a/src/EPPlus.Export.Pdf/ExcelPdf.cs +++ b/src/EPPlus.Export.Pdf/ExcelPdf.cs @@ -189,7 +189,7 @@ private void AddContent(Transform pageLayout, PdfPage page) var cells = pageLayout.ChildObjects.Where(t => (t is PdfCellLayout || t is PdfCellContentLayout || t is PdfCellBorderLayout) && !(t is PdfCellLayout cc && cc.IsHeading) && !(t is PdfCellContentLayout ccl && (ccl.IsHeaderFooter || ccl.IsHeading))).GroupBy(t => t.Name); var headerFooterLayouts = pageLayout.ChildObjects.OfType().Where(t => t.IsHeaderFooter); var headingLayouts = pageLayout.ChildObjects.Where(t => (t is PdfCellLayout cl && cl.IsHeading) || (t is PdfCellContentLayout ccl && ccl.IsHeading)); - var contentStream = new PdfContentStream(Document.Count + 1); + var contentStream = new PdfContentStream(_document.Count + 1); contentStream.AddCommand($"% {pageLayout.Name} start"); //Add clipping rectangle around page content. contentStream.AddCommand("q"); @@ -229,12 +229,12 @@ private void AddContent(Transform pageLayout, PdfPage page) case PdfCellLayout layout: contentStream.AddCellLayout(layout, GetPatternLabel(layout)); break; case PdfCellContentLayout contentLayout: - contentStream.AddCellContentLayout(contentLayout, Dictionaries, PageSettings); break; + contentStream.AddCellContentLayout(contentLayout, _dictionaries, _pageSettings); break; case PdfCellBorderLayout borderLayout: contentStream.AddBorderLayout(borderLayout); break; } } - if (PageSettings.ShowGridLines || PageSettings.ShowHeadings) + if (_pageSettings.ShowGridLines || _pageSettings.ShowHeadings) { contentStream.AddOuterGridBorder(pageLayout); } diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs index a75ecf8f05..ce2780bcd5 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs @@ -2,9 +2,11 @@ using EPPlus.Export.Pdf.PdfResources; using EPPlus.Export.Pdf.PdfSettings; using EPPlus.Fonts.OpenType.Integration; +using EPPlus.Fonts.OpenType.Integration.DataHolders; using EPPlus.Graphics; using OfficeOpenXml; using OfficeOpenXml.Interfaces.Drawing.Text; +using OfficeOpenXml.Interfaces.Fonts; using OfficeOpenXml.Style; using System; using System.Collections.Generic; @@ -488,12 +490,10 @@ private static PdfCell CreateHeadingPdfCell(PdfPageSettings pageSettings, PdfDic if (!string.IsNullOrEmpty(text)) { var tf = new TextFragment(); - tf.Font = new MeasurementFont - { - FontFamily = fontName, - Size = fontSize, - Style = MeasurementFontStyles.Regular - }; + tf.Font = new OpenTypeFontInfoBase(); + tf.Font.Family = fontName; + tf.Font.SubFamily = FontSubFamily.Regular; + tf.Font.Size = fontSize; tf.Text = text; tf.RichTextOptions.Bold = false; tf.RichTextOptions.Italic = false; diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs index e73634a767..85aaed3fa7 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs @@ -3,11 +3,14 @@ using EPPlus.Export.Pdf.PdfSettings; using EPPlus.Fonts.OpenType; using EPPlus.Fonts.OpenType.Integration; +using EPPlus.Fonts.OpenType.Integration.DataHolders; using EPPlus.Graphics.Units; using OfficeOpenXml; using OfficeOpenXml.ConditionalFormatting; using OfficeOpenXml.ConditionalFormatting.Contracts; using OfficeOpenXml.Interfaces.Drawing.Text; +using OfficeOpenXml.Interfaces.Fonts; +using OfficeOpenXml.Interfaces.RichText; using OfficeOpenXml.Style; using OfficeOpenXml.Style.HeaderFooterTextFormat; using OfficeOpenXml.Style.Table; @@ -534,10 +537,10 @@ private static List GetTextFragmentsFromRichText(PdfPageSettings p { var rt = richText[i]; var textFrag = new TextFragment(); - textFrag.Font = new MeasurementFont(); + textFrag.Font = new OpenTypeFontInfoBase(); textFrag.Text = forcedText == null ? rt.Text : forcedText; - textFrag.Font.FontFamily = rt.FontName; + textFrag.Font.Family = rt.FontName; textFrag.Font.Size = rt.Size; textFrag.RichTextOptions.Bold = rt.Bold || dxfBold; @@ -548,10 +551,10 @@ private static List GetTextFragmentsFromRichText(PdfPageSettings p textFrag.RichTextOptions.SubScript = rt.VerticalAlign == ExcelVerticalAlignmentFont.Subscript; textFrag.RichTextOptions.FontColor = rt.Color; - textFrag.Font.Style = ComputeFontStyle(textFrag); + textFrag.Font.SubFamily = ComputeFontStyle(textFrag); textFragments.Add(textFrag); - dictionaries.AddFont(pageSettings, textFrag.FullFontName, OpenTypeFonts.GetFontSubFamily(textFrag.Font.Style), textFrag.Text); + dictionaries.AddFont(pageSettings, textFrag.FullFontName, textFrag.Font.SubFamily, textFrag.Text); } return textFragments; } @@ -563,10 +566,10 @@ private static List GetTextFragmentsFromCellStyle(PdfPageSettings var textFragments = new List(1); var textFrag = new TextFragment(); - textFrag.Font = new MeasurementFont(); + textFrag.Font = new OpenTypeFontInfoBase(); textFrag.Text = forcedText == null ? cell.Text : forcedText; - textFrag.Font.FontFamily = font.Name; + textFrag.Font.Family = font.Name; textFrag.Font.Size = font.Size; textFrag.RichTextOptions.Bold = font.Bold || dxfBold; @@ -593,10 +596,10 @@ private static List GetTextFragmentsFromCellStyle(PdfPageSettings textFrag.RichTextOptions.SubScript = font.VerticalAlign == ExcelVerticalAlignmentFont.Subscript; textFrag.RichTextOptions.FontColor = font.Color.ToColor(); - textFrag.Font.Style = ComputeFontStyle(textFrag); + textFrag.Font.SubFamily = ComputeFontStyle(textFrag); textFragments.Add(textFrag); - dictionaries.AddFont(pageSettings, textFrag.FullFontName, OpenTypeFonts.GetFontSubFamily(textFrag.Font.Style), textFrag.Text); + dictionaries.AddFont(pageSettings, textFrag.FullFontName, textFrag.Font.SubFamily, textFrag.Text); return textFragments; } @@ -638,12 +641,20 @@ private static int MapUnderlineType(ExcelUnderLineType type) return 12; } - private static MeasurementFontStyles ComputeFontStyle(TextFragment textFrag) + //private static MeasurementFontStyles ComputeFontStyle(TextFragment textFrag) + //{ + // return (textFrag.RichTextOptions.Bold ? MeasurementFontStyles.Bold : 0) | + // (textFrag.RichTextOptions.Italic ? MeasurementFontStyles.Italic : 0) | + // (textFrag.RichTextOptions.UnderlineType != 12 ? MeasurementFontStyles.Underline : 0) | + // (textFrag.RichTextOptions.StrikeType > 1 ? MeasurementFontStyles.Strikeout : 0); + //} + + private static FontSubFamily ComputeFontStyle(TextFragment textFrag) { - return (textFrag.RichTextOptions.Bold ? MeasurementFontStyles.Bold : 0) | - (textFrag.RichTextOptions.Italic ? MeasurementFontStyles.Italic : 0) | - (textFrag.RichTextOptions.UnderlineType != 12 ? MeasurementFontStyles.Underline : 0) | - (textFrag.RichTextOptions.StrikeType > 1 ? MeasurementFontStyles.Strikeout : 0); + if (textFrag.RichTextOptions.Bold && textFrag.RichTextOptions.Italic) return FontSubFamily.BoldItalic; + if(textFrag.RichTextOptions.Bold) return FontSubFamily.Bold; + if (textFrag.RichTextOptions.Italic) return FontSubFamily.Italic; + return FontSubFamily.Regular; } @@ -724,9 +735,9 @@ public static PdfHeaderFooter GetTextFormats(PdfPageSettings pageSettings, PdfDi { var hf = textCollection[i]; var textFrag = new TextFragment(); - textFrag.Font = new MeasurementFont(); + textFrag.Font = new OpenTypeFontInfoBase(); - textFrag.Font.FontFamily = string.IsNullOrEmpty(hf.FontName) ? ns.Style.Font.Name : hf.FontName; + textFrag.Font.Family = string.IsNullOrEmpty(hf.FontName) ? ns.Style.Font.Name : hf.FontName; textFrag.Font.Size = hf.FontSize == null ? ns.Style.Font.Size : (float)hf.FontSize; textFrag.RichTextOptions.Bold = hf.Bold; @@ -742,10 +753,11 @@ public static PdfHeaderFooter GetTextFormats(PdfPageSettings pageSettings, PdfDi textFrag.RichTextOptions.StrikeType = hf.Striketrough ? 2 : 1; textFrag.RichTextOptions.FontColor = hf.Color; - textFrag.Font.Style = (textFrag.RichTextOptions.Bold ? MeasurementFontStyles.Bold : 0) | - (textFrag.RichTextOptions.Italic ? MeasurementFontStyles.Italic : 0) | - (textFrag.RichTextOptions.UnderlineType != 12 ? MeasurementFontStyles.Underline : 0) | - (textFrag.RichTextOptions.StrikeType > 1 ? MeasurementFontStyles.Strikeout : 0); + textFrag.Font.SubFamily = ComputeFontStyle(textFrag); + //(textFrag.RichTextOptions.Bold ? MeasurementFontStyles.Bold : 0) | + //(textFrag.RichTextOptions.Italic ? MeasurementFontStyles.Italic : 0) | + //(textFrag.RichTextOptions.UnderlineType != 12 ? MeasurementFontStyles.Underline : 0) | + //(textFrag.RichTextOptions.StrikeType > 1 ? MeasurementFontStyles.Strikeout : 0); var text = string.Empty; switch (hf.FormatCode) @@ -781,9 +793,8 @@ public static PdfHeaderFooter GetTextFormats(PdfPageSettings pageSettings, PdfDi textFrag.Text = text; textFragments.Add(textFrag); - OpenTypeFonts.GetFontSubFamily(textFrag.Font.Style); - dictionaries.AddFont(pageSettings, textFrag.FullFontName, OpenTypeFonts.GetFontSubFamily(textFrag.Font.Style), textFrag.Text); - if (NumberOfPagesIndexes.Count > 0 || PageNumberIndexes.Count > 0) dictionaries.AddFont(pageSettings, textFrag.FullFontName, OpenTypeFonts.GetFontSubFamily(textFrag.Font.Style), "1234567890"); + dictionaries.AddFont(pageSettings, textFrag.FullFontName, textFrag.Font.SubFamily, textFrag.Text); + if (NumberOfPagesIndexes.Count > 0 || PageNumberIndexes.Count > 0) dictionaries.AddFont(pageSettings, textFrag.FullFontName, textFrag.Font.SubFamily, "1234567890"); diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs b/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs index 005d11ace8..9ea27cf621 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs +++ b/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs @@ -197,10 +197,9 @@ public void AddText(PdfCellContentLayout cell, Vector2 position, double textRota var textLength = shapedText.ShapedText.GetWidthInPoints((float)richInfo.Size); var color = richInfo.FontColor; - var fontResource = GetFontResource(dictionaries, pageSettings, textFormat.OriginalTextFragment.Font.FontFamily, OpenTypeFonts.GetFontSubFamily(textFormat.OriginalTextFragment.Font.Style), textFormat.OriginalTextFragment.Font.Size); - - double scale = richInfo.Size / fontResrouce.fontData.HeadTable.UnitsPerEm; - double scale = textFormat.OriginalTextFragment.Font.Size / fontResource.fontData.HeadTable.UnitsPerEm; + var fontResource = GetFontResource(dictionaries, pageSettings, richInfo.Family, richInfo.SubFamily, richInfo.Size); + double size = richInfo.Size; + double scale = textFormat.OriginalTextFragment.RichTextOptions.Size / fontResource.fontData.HeadTable.UnitsPerEm; Matrix3x3 textMatrix = new Matrix3x3(System.Math.Cos(rotation), System.Math.Sin(rotation), -System.Math.Sin(rotation), System.Math.Cos(rotation), position.X + lineOffsetX, position.Y + advanceY); commands.Add("BT"); textMatrix = textMatrix * Matrix3x3.Translation(advanceX, 0); From 8cb5310300718ac9bb801af75cad57ed7f3944a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Wed, 3 Jun 2026 12:46:45 +0200 Subject: [PATCH 08/49] fixed wiriting negative pdf object references in font resource on page. --- src/EPPlus.Export.Pdf.Tests/PdfTests.cs | 2 +- src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs index d3285238d7..98b9734001 100644 --- a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs +++ b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs @@ -171,7 +171,7 @@ public void ReadPrintAreas() pageSettings.Debug = true; pageSettings.PrintAsText = true; pageSettings.ShowGridLines = true; - pageSettings.ShowHeadings = true; + pageSettings.ShowHeadings = false; PdfCatalog catlog = new PdfCatalog(pageSettings, ws, "C:\\epplustest\\pdf\\FullPageTest55.pdf"); //line breaks diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs b/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs index 9ea27cf621..2f9e291814 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs +++ b/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs @@ -103,7 +103,8 @@ public void AddBorderLayout(PdfCellBorderLayout cell) //Get font label //need to update this one too for same reasons as AddFontData internal PdfFontResource GetFontResource(PdfDictionaries Dictionaries, PdfPageSettings PageSettings, string fontName, FontSubFamily subFamily, double fontSize) { - if (!Dictionaries.Fonts.ContainsKey(fontName)) + var lookUpName = fontName + " " + subFamily.ToString(); + if (!Dictionaries.Fonts.ContainsKey(lookUpName)) { int label = 1; if (Dictionaries.Fonts.Count > 0) @@ -119,7 +120,7 @@ internal PdfFontResource GetFontResource(PdfDictionaries Dictionaries, PdfPageSe //Document.Add(fr.GetFontObject(Document.Count + 1)); Dictionaries.Fonts.Add(fontName, fr); } - return Dictionaries.Fonts[fontName]; + return Dictionaries.Fonts[lookUpName]; } public void AddText(PdfCellContentLayout cell, Vector2 position, double textRotation, PdfDictionaries dictionaries, PdfPageSettings pageSettings) From 45ecf688acb56efdfba55dab81733d0b63986acc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Wed, 3 Jun 2026 16:18:04 +0200 Subject: [PATCH 09/49] changed type --- src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs | 3 +-- src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs index ce2780bcd5..91667c79d0 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs @@ -5,7 +5,6 @@ using EPPlus.Fonts.OpenType.Integration.DataHolders; using EPPlus.Graphics; using OfficeOpenXml; -using OfficeOpenXml.Interfaces.Drawing.Text; using OfficeOpenXml.Interfaces.Fonts; using OfficeOpenXml.Style; using System; @@ -490,7 +489,7 @@ private static PdfCell CreateHeadingPdfCell(PdfPageSettings pageSettings, PdfDic if (!string.IsNullOrEmpty(text)) { var tf = new TextFragment(); - tf.Font = new OpenTypeFontInfoBase(); + tf.Font = new RichTextFormatSimple(); tf.Font.Family = fontName; tf.Font.SubFamily = FontSubFamily.Regular; tf.Font.Size = fontSize; diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs index 85aaed3fa7..a0aa987483 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs @@ -537,7 +537,7 @@ private static List GetTextFragmentsFromRichText(PdfPageSettings p { var rt = richText[i]; var textFrag = new TextFragment(); - textFrag.Font = new OpenTypeFontInfoBase(); + textFrag.Font = new RichTextFormatSimple(); textFrag.Text = forcedText == null ? rt.Text : forcedText; textFrag.Font.Family = rt.FontName; @@ -566,7 +566,7 @@ private static List GetTextFragmentsFromCellStyle(PdfPageSettings var textFragments = new List(1); var textFrag = new TextFragment(); - textFrag.Font = new OpenTypeFontInfoBase(); + textFrag.Font = new RichTextFormatSimple(); textFrag.Text = forcedText == null ? cell.Text : forcedText; textFrag.Font.Family = font.Name; @@ -735,7 +735,7 @@ public static PdfHeaderFooter GetTextFormats(PdfPageSettings pageSettings, PdfDi { var hf = textCollection[i]; var textFrag = new TextFragment(); - textFrag.Font = new OpenTypeFontInfoBase(); + textFrag.Font = new RichTextFormatSimple(); textFrag.Font.Family = string.IsNullOrEmpty(hf.FontName) ? ns.Style.Font.Name : hf.FontName; textFrag.Font.Size = hf.FontSize == null ? ns.Style.Font.Size : (float)hf.FontSize; From f7e22509b24260e4459934752705279a6d1f41e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Wed, 3 Jun 2026 16:22:15 +0200 Subject: [PATCH 10/49] Fixed duplication bug --- .../DataHolders/TextLineCollection.cs | 61 ++++++++++--------- .../Integration/RichText/LayoutSystem.cs | 2 +- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs index b9d7bba112..4c71afeb63 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs @@ -229,42 +229,45 @@ internal void FinalizeTextLineData(List lines) public int idxOfLargestLine { get; private set; } = -1; public List SpaceWidthsPerLine { get; private set; } = new List(); - public TextLineCollection(List lines, List originalFragments) + public TextLineCollection(List lines, List originalFragments, bool finalizeLineFragments = false) { - int lineIdx = 0; - foreach (var line in lines) + if (finalizeLineFragments) { - double largestAscent = 0; - double largestDescent = 0; - double largestFontSize = 0; - foreach (var lineFragment in line.InternalLineFragments) + int lineIdx = 0; + foreach (var line in lines) { - var frag = originalFragments[lineFragment.FragmentIndex]; - if (frag == null) continue; - largestAscent = Math.Max(frag.AscentPoints, largestAscent); - largestDescent = Math.Max(frag.DescentPoints, largestDescent); - largestFontSize = Math.Max(largestFontSize, frag.Size); - } - line.LargestAscent = largestAscent; - line.LargestDescent = largestDescent; - line.LargestFontSize = largestFontSize; + double largestAscent = 0; + double largestDescent = 0; + double largestFontSize = 0; + foreach (var lineFragment in line.InternalLineFragments) + { + var frag = originalFragments[lineFragment.FragmentIndex]; + if (frag == null) continue; + largestAscent = Math.Max(frag.AscentPoints, largestAscent); + largestDescent = Math.Max(frag.DescentPoints, largestDescent); + largestFontSize = Math.Max(largestFontSize, frag.Size); + } + line.LargestAscent = largestAscent; + line.LargestDescent = largestDescent; + line.LargestFontSize = largestFontSize; - line.FinalizeLineFragments(originalFragments); + line.FinalizeLineFragments(originalFragments); - if(line.Width > LargestWidthWithSpace) - { - LargestWidthWithSpace = line.Width; + if (line.Width > LargestWidthWithSpace) + { + LargestWidthWithSpace = line.Width; + } + if (line.GetWidthWithoutTrailingSpaces() > LargestWidthWithoutSpace) + { + LargestWidthWithoutSpace = line.GetWidthWithoutTrailingSpaces(); + idxOfLargestLine = lineIdx; + } + LargestWidthWithSpace = Math.Max(LargestWidthWithSpace, line.Width); + LargestWidthWithoutSpace = Math.Max(LargestWidthWithoutSpace, line.GetWidthWithoutTrailingSpaces()); + SpaceWidthsPerLine.Add(line.LastFontSpaceWidth); + lineIdx++; } - if(line.GetWidthWithoutTrailingSpaces() > LargestWidthWithoutSpace) - { - LargestWidthWithoutSpace = line.GetWidthWithoutTrailingSpaces(); - idxOfLargestLine = lineIdx; - } - LargestWidthWithSpace = Math.Max(LargestWidthWithSpace, line.Width); - LargestWidthWithoutSpace = Math.Max(LargestWidthWithoutSpace, line.GetWidthWithoutTrailingSpaces()); - SpaceWidthsPerLine.Add(line.LastFontSpaceWidth); - lineIdx++; } _originalFragments = originalFragments; diff --git a/src/EPPlus.Fonts.OpenType/Integration/RichText/LayoutSystem.cs b/src/EPPlus.Fonts.OpenType/Integration/RichText/LayoutSystem.cs index 8bea44ca46..65922db013 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/RichText/LayoutSystem.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/RichText/LayoutSystem.cs @@ -259,7 +259,7 @@ public TextLineCollection Wrap(double maxWidth) } } - WrappedLineCollection = new TextLineCollection(wrappedLines, InputFragments); + WrappedLineCollection = new TextLineCollection(wrappedLines, InputFragments, true); return WrappedLineCollection; } From 21c4b0645f6f5c71cd2f058388ab23c3b74c8af7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Wed, 3 Jun 2026 16:42:32 +0200 Subject: [PATCH 11/49] Changed test name --- src/EPPlus.DrawingRenderer.Tests/Chart/LineChartToSvgTests.cs | 2 +- src/EPPlus.Fonts.OpenType/OpenTypeFontEngine.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EPPlus.DrawingRenderer.Tests/Chart/LineChartToSvgTests.cs b/src/EPPlus.DrawingRenderer.Tests/Chart/LineChartToSvgTests.cs index 52eab80bd2..b7fe5272b2 100644 --- a/src/EPPlus.DrawingRenderer.Tests/Chart/LineChartToSvgTests.cs +++ b/src/EPPlus.DrawingRenderer.Tests/Chart/LineChartToSvgTests.cs @@ -129,7 +129,7 @@ public void GenerateSvgForLineCharts() } } [TestMethod] - public void GenerateSvgForLineCharts2() + public void GenerateSuperScript() { ExcelPackage.License.SetNonCommercialOrganization("EPPlus Project"); using (var p = OpenTemplatePackage("Superscript.xlsx")) diff --git a/src/EPPlus.Fonts.OpenType/OpenTypeFontEngine.cs b/src/EPPlus.Fonts.OpenType/OpenTypeFontEngine.cs index 78d3136271..511ccd1f5a 100644 --- a/src/EPPlus.Fonts.OpenType/OpenTypeFontEngine.cs +++ b/src/EPPlus.Fonts.OpenType/OpenTypeFontEngine.cs @@ -106,7 +106,7 @@ public OpenTypeFontEngine(Action configure) // Public API // ----------------------------------------------------------------------------------------- - public FontAvailability LeastRequiredAvailability { get; set; } = FontAvailability.Exact; + public FontAvailability LeastRequiredAvailability { get; set; } = FontAvailability.NotFound; bool RequireExactFoundFont { get { return LeastRequiredAvailability == FontAvailability.Exact; } } bool RequireFamilyFont{ get { return RequireExactFoundFont || LeastRequiredAvailability == FontAvailability.FamilyOnly; } } From d2c9e5cec191a4ef965a0eb676bd7e64d8aeffec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Wed, 3 Jun 2026 17:43:41 +0200 Subject: [PATCH 12/49] Reverted tint change for pdf --- .../Utils/TypeConversion/ColorConverter.cs | 47 ++++++++++++++----- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/src/EPPlus/Utils/TypeConversion/ColorConverter.cs b/src/EPPlus/Utils/TypeConversion/ColorConverter.cs index b25f4a8565..76f21ce3a1 100644 --- a/src/EPPlus/Utils/TypeConversion/ColorConverter.cs +++ b/src/EPPlus/Utils/TypeConversion/ColorConverter.cs @@ -168,23 +168,44 @@ private static int GetRgpPercentToRgb(double percentage) } internal static Color ApplyTint(Color ret, double tint) { - if (tint < 0) + if (tint == 0) { - double shade = 1+tint; - var r = (byte)Math.Round(ret.R * shade); - var g = (byte)Math.Round(ret.G * shade); - var b = (byte)Math.Round(ret.B * shade); - return Color.FromArgb(ret.A, r, g, b); + return ret; + + + + } - else if(tint > 0) + else { - double blend = 1.0 - tint; - var r = (byte)Math.Round(ret.R + (255 - ret.R) * blend); - var g = (byte)Math.Round(ret.G + (255 - ret.G) * blend); - var b = (byte)Math.Round(ret.B + (255 - ret.B) * blend); - return Color.FromArgb(ret.A, r, g, b); + ExcelDrawingRgbColor.GetHslColor(ret, out double h, out double s, out double l); + if (tint < 0) + { + l = l * (1.0 + tint); + } + else if (tint > 0) + { + l += (1 - l) * tint; + } + return ExcelDrawingHslColor.GetRgb(h, s, l); } - return ret; + //if (tint < 0) + //{ + // double shade = 1+tint; + // var r = (byte)Math.Round(ret.R * shade); + // var g = (byte)Math.Round(ret.G * shade); + // var b = (byte)Math.Round(ret.B * shade); + // return Color.FromArgb(ret.A, r, g, b); + //} + //else if(tint > 0) + //{ + // double blend = 1.0 - tint; + // var r = (byte)Math.Round(ret.R + (255 - ret.R) * blend); + // var g = (byte)Math.Round(ret.G + (255 - ret.G) * blend); + // var b = (byte)Math.Round(ret.B + (255 - ret.B) * blend); + // return Color.FromArgb(ret.A, r, g, b); + //} + //return ret; } internal static Color ApplyBlend(Color color, Color blendColor, double percent) { From e762f6607c7be9914349232fb65356dfc38ac8ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Thu, 4 Jun 2026 12:57:52 +0200 Subject: [PATCH 13/49] Fixed font dictionary setting wrong font name. --- src/EPPlus.Export.Pdf.Tests/PdfTests.cs | 3 ++- src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs | 2 +- src/EPPlus.Export.Pdf/PdfResources/PdfDictionaries.cs | 7 ++++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs index 98b9734001..3d23affefe 100644 --- a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs +++ b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs @@ -163,6 +163,7 @@ public void ReadPrintAreas() { //using var p = OpenTemplatePackage("PdfPrintAreas.xlsx"); using var p = OpenTemplatePackage("PDFTest.xlsx"); + //using var p = OpenTemplatePackage("PDFTest_old.xlsx"); //using var p = OpenTemplatePackage("DoubleBorder.xlsx"); var ws = p.Workbook.Worksheets[0]; PdfPageSettings pageSettings = new PdfPageSettings(); @@ -172,7 +173,7 @@ public void ReadPrintAreas() pageSettings.PrintAsText = true; pageSettings.ShowGridLines = true; pageSettings.ShowHeadings = false; - PdfCatalog catlog = new PdfCatalog(pageSettings, ws, "C:\\epplustest\\pdf\\FullPageTest55.pdf"); + PdfCatalog catlog = new PdfCatalog(pageSettings, ws, "C:\\epplustest\\pdf\\FullPageTest56.pdf"); //line breaks //wrap comments diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs index a0aa987483..45f9c6a60f 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs @@ -554,7 +554,7 @@ private static List GetTextFragmentsFromRichText(PdfPageSettings p textFrag.Font.SubFamily = ComputeFontStyle(textFrag); textFragments.Add(textFrag); - dictionaries.AddFont(pageSettings, textFrag.FullFontName, textFrag.Font.SubFamily, textFrag.Text); + dictionaries.AddFont(pageSettings, textFrag.Font.Family, textFrag.Font.SubFamily, textFrag.Text); } return textFragments; } diff --git a/src/EPPlus.Export.Pdf/PdfResources/PdfDictionaries.cs b/src/EPPlus.Export.Pdf/PdfResources/PdfDictionaries.cs index 859e3d8ba0..67241ef695 100644 --- a/src/EPPlus.Export.Pdf/PdfResources/PdfDictionaries.cs +++ b/src/EPPlus.Export.Pdf/PdfResources/PdfDictionaries.cs @@ -28,16 +28,17 @@ public class PdfDictionaries public void AddFont(PdfPageSettings pageSettings, string FontName, FontSubFamily SubFamily, string Text) { - if (!Fonts.ContainsKey(FontName)) + var fullFont = FontName + " " + SubFamily.ToString(); + if (!Fonts.ContainsKey(fullFont)) { int label = 1; if (Fonts.Count > 0) { label = Fonts.Last().Value.labelNumber + 1; } - Fonts.Add(FontName, new PdfFontResource(FontName, SubFamily, label, pageSettings)); + Fonts.Add(fullFont, new PdfFontResource(FontName, SubFamily, label, pageSettings)); } - var manger = Fonts[FontName].fontSubsetManager; + var manger = Fonts[fullFont].fontSubsetManager; manger.AddText(Text); } From 0db239edc4962420efe252e7e2efb13f6e5c0bb9 Mon Sep 17 00:00:00 2001 From: swmal <897655+swmal@users.noreply.github.com> Date: Thu, 4 Jun 2026 16:44:49 +0200 Subject: [PATCH 14/49] Added logging for trouble shooting --- src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs | 1 + src/EPPlus.Export.Pdf/PdfCatalog/PdfTextShaper.cs | 1 + src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs | 9 ++++++++- src/EPPlus.Fonts.OpenType/FontSubsetManager.cs | 6 ++++++ 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs index 45f9c6a60f..6d3058b991 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs @@ -793,6 +793,7 @@ public static PdfHeaderFooter GetTextFormats(PdfPageSettings pageSettings, PdfDi textFrag.Text = text; textFragments.Add(textFrag); + Console.WriteLine($"[GetTextFormats] AddFont('{textFrag.FullFontName}', subFamily={textFrag.Font.SubFamily}, text='{textFrag.Text}')"); dictionaries.AddFont(pageSettings, textFrag.FullFontName, textFrag.Font.SubFamily, textFrag.Text); if (NumberOfPagesIndexes.Count > 0 || PageNumberIndexes.Count > 0) dictionaries.AddFont(pageSettings, textFrag.FullFontName, textFrag.Font.SubFamily, "1234567890"); diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextShaper.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextShaper.cs index 11656147d2..adb5439f69 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextShaper.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextShaper.cs @@ -122,6 +122,7 @@ public static void LayoutAndShapeText(PdfPageSettings pageSettings, PdfDictionar var tf = cell.TextFragments[i]; cell.ShapedTexts.Add(new PdfShapedText()); var st = cell.ShapedTexts[i]; + Console.WriteLine($"[LayoutAndShapeText] Looking up '{tf.FullFontName}' (Family={tf.Font.Family}, SubFamily={tf.Font.SubFamily}). Found in Fonts: {dictionaries.Fonts.ContainsKey(tf.FullFontName)}"); st.FontProvider = dictionaries.Fonts[tf.FullFontName].fontSubsetManager.CreateSubsettedProvider(); if (!shaperCache.TryGetValue(st.FontProvider, out var shaper)) diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs b/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs index 2f9e291814..070eeef5ba 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs +++ b/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs @@ -186,14 +186,21 @@ public void AddText(PdfCellContentLayout cell, Vector2 position, double textRota } rtCharCount += shapedText.ShapedText.Glyphs[g].CharCount; } + var originalFragment = ((TextFragment)textFormat.OriginalTextFragment); while (glyphStart < shapedText.ShapedText.Glyphs.Length && shapedText.ShapedText.Glyphs[glyphStart].GlyphId == 0) { + Console.WriteLine($"[AddText] GlyphId==0 in shapedText {shapedTextIndex} of {cell.ShapedTexts.Count}, advancing. Cell.Text='{cell.cell?.Text}' textFormat.Text='{textFormat.Text}'"); shapedTextIndex++; + if (shapedTextIndex >= cell.ShapedTexts.Count) + { + Console.WriteLine($"[AddText] OUT OF RANGE! Original text: '{originalFragment.Text}'"); + break; // temp safety + } shapedText = cell.ShapedTexts[shapedTextIndex]; glyphStart = 0; } - var originalFragment = ((TextFragment)textFormat.OriginalTextFragment); + var richInfo = originalFragment.RichTextOptions; var textLength = shapedText.ShapedText.GetWidthInPoints((float)richInfo.Size); diff --git a/src/EPPlus.Fonts.OpenType/FontSubsetManager.cs b/src/EPPlus.Fonts.OpenType/FontSubsetManager.cs index 6e0094ff51..62eac29500 100644 --- a/src/EPPlus.Fonts.OpenType/FontSubsetManager.cs +++ b/src/EPPlus.Fonts.OpenType/FontSubsetManager.cs @@ -67,6 +67,12 @@ public void AddText(string text) ushort glyphId; _sourceProvider.TryGetGlyphFont((uint)cp, out font, out glyphId); + var fontName = font?.NameTable?.GetFullFontName() ?? "null"; + if ((char)cp == 'E' || (char)cp == 'P') + { + Console.WriteLine($"[FontSubsetManager.AddText] cp='{(char)cp}' (U+{cp:X4}) -> font='{fontName}', glyphId={glyphId}"); + } + HashSet fontCodePoints; if (!_codePointsByFont.TryGetValue(font, out fontCodePoints)) { From dca477d0bc1404bd88629c17be18a5e51afebf4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Thu, 4 Jun 2026 17:01:38 +0200 Subject: [PATCH 15/49] fixed more stuff with font dictionary adding wrong font name --- src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs | 12 ++++++------ src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs index 91667c79d0..59b115686c 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs @@ -214,7 +214,7 @@ internal static Transform GetCatalog(PdfPageSettings pageSettings, PdfDictionari pageLayout.AddChild(text); } var centerH = page.HeaderFooters.Get(hfType, HeaderFooterSection.Header, HeaderFooterAlignment.Center); - if(centerH != null) + if (centerH != null) { SubstitutePageNumbers(pageSettings, dictionaries, centerH, pageNumber, totalPages); var ascent = centerH.Content.TextLines[0].LargestAscent; @@ -228,7 +228,7 @@ internal static Transform GetCatalog(PdfPageSettings pageSettings, PdfDictionari pageLayout.AddChild(text); } var rightH = page.HeaderFooters.Get(hfType, HeaderFooterSection.Header, HeaderFooterAlignment.Right); - if(rightH != null) + if (rightH != null) { SubstitutePageNumbers(pageSettings, dictionaries, rightH, pageNumber, totalPages); var ascent = rightH.Content.TextLines[0].LargestAscent; @@ -241,7 +241,7 @@ internal static Transform GetCatalog(PdfPageSettings pageSettings, PdfDictionari pageLayout.AddChild(text); } var leftF = page.HeaderFooters.Get(hfType, HeaderFooterSection.Footer, HeaderFooterAlignment.Left); - if(leftF != null) + if (leftF != null) { SubstitutePageNumbers(pageSettings, dictionaries, leftF, pageNumber, totalPages); int last = leftF.Content.TextLines.Count - 1; @@ -255,7 +255,7 @@ internal static Transform GetCatalog(PdfPageSettings pageSettings, PdfDictionari pageLayout.AddChild(text); } var centerF = page.HeaderFooters.Get(hfType, HeaderFooterSection.Footer, HeaderFooterAlignment.Center); - if(centerF != null) + if (centerF != null) { SubstitutePageNumbers(pageSettings, dictionaries, centerF, pageNumber, totalPages); int last = centerF.Content.TextLines.Count - 1; @@ -269,7 +269,7 @@ internal static Transform GetCatalog(PdfPageSettings pageSettings, PdfDictionari pageLayout.AddChild(text); } var rightF = page.HeaderFooters.Get(hfType, HeaderFooterSection.Footer, HeaderFooterAlignment.Right); - if(rightF != null) + if (rightF != null) { SubstitutePageNumbers(pageSettings, dictionaries, rightF, pageNumber, totalPages); int last = rightF.Content.TextLines.Count - 1; @@ -886,7 +886,7 @@ private static void SubstitutePageNumbers(PdfPageSettings pageSettings, PdfDicti foreach (var idx in hf.NumberOfPagesIndexes) hf.Content.TextFragments[idx].Text = totalPages.ToString(); } - PdfTextShaper.LayoutAndShapeText(pageSettings, dictionaries, hf.Content); + PdfTextShaper.ShapeText(pageSettings, dictionaries, hf.Content); } } diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs index 6d3058b991..99c854894a 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs @@ -599,7 +599,7 @@ private static List GetTextFragmentsFromCellStyle(PdfPageSettings textFrag.Font.SubFamily = ComputeFontStyle(textFrag); textFragments.Add(textFrag); - dictionaries.AddFont(pageSettings, textFrag.FullFontName, textFrag.Font.SubFamily, textFrag.Text); + dictionaries.AddFont(pageSettings, textFrag.Font.Family, textFrag.Font.SubFamily, textFrag.Text); return textFragments; } @@ -794,8 +794,8 @@ public static PdfHeaderFooter GetTextFormats(PdfPageSettings pageSettings, PdfDi textFragments.Add(textFrag); Console.WriteLine($"[GetTextFormats] AddFont('{textFrag.FullFontName}', subFamily={textFrag.Font.SubFamily}, text='{textFrag.Text}')"); - dictionaries.AddFont(pageSettings, textFrag.FullFontName, textFrag.Font.SubFamily, textFrag.Text); - if (NumberOfPagesIndexes.Count > 0 || PageNumberIndexes.Count > 0) dictionaries.AddFont(pageSettings, textFrag.FullFontName, textFrag.Font.SubFamily, "1234567890"); + dictionaries.AddFont(pageSettings, textFrag.Font.Family, textFrag.Font.SubFamily, textFrag.Text); + if (NumberOfPagesIndexes.Count > 0 || PageNumberIndexes.Count > 0) dictionaries.AddFont(pageSettings, textFrag.Font.Family, textFrag.Font.SubFamily, "1234567890"); From f478edc7a46dd4fc6d524759ba6c0d28d36275e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Fri, 5 Jun 2026 17:04:10 +0200 Subject: [PATCH 16/49] print titles progress --- src/EPPlus.Export.Pdf.Tests/PdfTests.cs | 2 +- src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs | 1 + src/EPPlus.Export.Pdf/PdfCatalog/PdfGridlinesLayout.cs | 4 ++-- src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs | 10 ++++++---- src/EPPlus.Export.Pdf/PdfLayout/PdfPageLayout.cs | 3 +++ src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs | 6 +++--- 6 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs index 3d23affefe..cdd7577fe2 100644 --- a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs +++ b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs @@ -172,7 +172,7 @@ public void ReadPrintAreas() pageSettings.Debug = true; pageSettings.PrintAsText = true; pageSettings.ShowGridLines = true; - pageSettings.ShowHeadings = false; + pageSettings.ShowHeadings = true; PdfCatalog catlog = new PdfCatalog(pageSettings, ws, "C:\\epplustest\\pdf\\FullPageTest56.pdf"); //line breaks diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs index 8c6184973f..7bee489dda 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs @@ -330,6 +330,7 @@ private void GetCommentsAndNotes(PdfPageSettings pageSettings, PdfWorksheet pdfS { var cnPageSettings = new PdfPageSettings(); cnPageSettings.CommentsAndNotes = CommentsAndNotes.None; + cnPageSettings.ShowHeadings = false; pdfSheet.CommentsAndNotesSheet = PdfCommentsAndNotes.CreateCommentAndNotesPages(pdfSheet.CommentsAndNotesCollections, pdfSheet.Worksheet); pdfSheet.CommentsAndNotes = new PdfRange(pdfSheet.CommentsAndNotesSheet.Dimension, false); pdfSheet.CommentsAndNotes = GetMaps(cnPageSettings, pdfSheet, pdfSheet.CommentsAndNotes); diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfGridlinesLayout.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfGridlinesLayout.cs index a069ad8936..23b670f41e 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfGridlinesLayout.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfGridlinesLayout.cs @@ -41,7 +41,7 @@ public static void AddGridLines(PdfPageSettings pageSettings, Page page, PdfPage // colX[ci] = X of left edge of column ci (0-based within page). // colX[colCount] = X of right edge of last column. var colX = new double[colCount + 1]; - colX[0] = pageSettings.ContentBounds.Left + page.HeadingWidth; + colX[0] = pageSettings.ContentBounds.Left + page.HeadingWidth + page.PrintTitleWidth; for (int ci = 0; ci < colCount; ci++) { var cell = page.Map[page.FromRow, page.FromColumn + ci]; @@ -52,7 +52,7 @@ public static void AddGridLines(PdfPageSettings pageSettings, Page page, PdfPage // rowY[rowCount] = Y of bottom edge of last row. // Y decreases downward (PDF coordinate system used throughout GetCatalog). var rowY = new double[rowCount + 1]; - rowY[0] = pageSettings.ContentBounds.Top - page.HeadingHeight; + rowY[0] = pageSettings.ContentBounds.Top - page.HeadingHeight - page.PrintTitleHeight; for (int ri = 0; ri < rowCount; ri++) { diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs index 59b115686c..48d84c0add 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs @@ -95,12 +95,14 @@ internal static Transform GetCatalog(PdfPageSettings pageSettings, PdfDictionari pageLayout.isCommentsPage = pdfPages[i].IsCommentsPage; pageLayout.HeadingWidth = page.HeadingWidth; pageLayout.HeadingHeight = page.HeadingHeight; + pageLayout.PrintTitleWidth = page.PrintTitleWidth; + pageLayout.PrintTitleHeight = page.PrintTitleHeight; var drawnMergedCells = new HashSet(); //var drawnMergedCellsText = new HashSet(); //PdfContentLayout contentLayout = new PdfContentLayout(0d, 0d, pageSettings.ContentBounds); //pageLayout.AddChild(contentLayout); - double contentStartX = pageSettings.ContentBounds.Left + page.HeadingWidth; - double contentStartY = pageSettings.ContentBounds.Top - page.HeadingHeight; + double contentStartX = pageSettings.ContentBounds.Left + page.HeadingWidth + page.PrintTitleWidth; + double contentStartY = pageSettings.ContentBounds.Top - page.HeadingHeight - page.PrintTitleHeight; if (pageSettings.ShowHeadings && !pdfPages[i].IsCommentsPage) AddHeadingCells(pageSettings, dictionaries, page, pageLayout, contentStartX, contentStartY, page.HeadingWidth, page.HeadingHeight, pdfPages[i].HeadingFontName, pdfPages[i].HeadingFontSize, pdfPages[i].HeadingFill); @@ -584,7 +586,7 @@ private static Page PrecomputePageMergedCells(PdfPageSettings pageSettings, PdfR } // --- Y --- // Replace the * 15d line with a sum of real row heights - double drawY = pageSettings.ContentBounds.Top - page.HeadingHeight; + double drawY = pageSettings.ContentBounds.Top - page.HeadingHeight - page.PrintTitleHeight; for (int r = page.FromRow; r < row; r++) { drawY -= range.RowHeights[r - range.Range._fromRow].Height; @@ -612,7 +614,7 @@ private static double[] BuildColumnXPositions(PdfPageSettings pageSettings, Page { int colCount = page.ToColumn - page.FromColumn + 1; var colX = new double[colCount]; - double x = pageSettings.ContentBounds.Left + page.HeadingWidth; + double x = pageSettings.ContentBounds.Left + page.HeadingWidth + page.PrintTitleWidth; for (int col = page.FromColumn; col <= page.ToColumn; col++) { colX[col - page.FromColumn] = x; diff --git a/src/EPPlus.Export.Pdf/PdfLayout/PdfPageLayout.cs b/src/EPPlus.Export.Pdf/PdfLayout/PdfPageLayout.cs index 01896e4009..191b18156c 100644 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfPageLayout.cs +++ b/src/EPPlus.Export.Pdf/PdfLayout/PdfPageLayout.cs @@ -83,6 +83,9 @@ internal class PdfPageLayout : Transform public double HeadingWidth; public double HeadingHeight; + public double PrintTitleWidth; + public double PrintTitleHeight; + public bool isCommentsPage = false; public PdfPageLayout(double x, double y, double width, double height) diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs b/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs index 070eeef5ba..d69e53f6a6 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs +++ b/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs @@ -579,10 +579,10 @@ public void AddMarginClipping(PdfPageLayout pageLayout) right = System.Math.Max(right, System.Math.Max(line.X1, line.X2)); } var pad = GridLine.Width * 4; - var x = left + pl.HeadingWidth - pad; + var x = left + pl.HeadingWidth + pl.PrintTitleWidth - pad; var y = bottom - pad; - var width = (right - left - pl.HeadingWidth) + pad * 2; - var height = (top - pl.HeadingHeight - bottom) + pad * 2; + var width = (right - left - pl.HeadingWidth - pl.PrintTitleWidth) + pad * 2; + var height = (top - pl.HeadingHeight - pl.PrintTitleHeight - bottom) + pad * 2; commands.Add($"{x.ToPdfString()} {y.ToPdfString()} {width.ToPdfString()} {height.ToPdfString()} re W n"); } From 58c0e0fc23d16f7675746fbfd19e87df21421fcf Mon Sep 17 00:00:00 2001 From: swmal <897655+swmal@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:38:55 +0200 Subject: [PATCH 17/49] Fixed performance issue with heading rendering --- src/EPPlus.Export.Pdf.Tests/PdfTests.cs | 55 ++++++++++++- src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs | 19 ++++- .../PdfCatalog/PdfTextMap.cs | 1 - .../PdfCatalog/PdfTextShaper.cs | 80 ------------------- .../PdfObjects/PdfContentStream.cs | 4 +- .../FontSubsetManager.cs | 10 +-- 6 files changed, 79 insertions(+), 90 deletions(-) diff --git a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs index cdd7577fe2..c6d1c0ecad 100644 --- a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs +++ b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs @@ -21,6 +21,8 @@ Date Author Change using OfficeOpenXml.Interfaces.Drawing.Text; using OfficeOpenXml.Interfaces.RichText; using OfficeOpenXml.Style; +using System.Diagnostics; +using System.Drawing.Printing; namespace EPPlusTest.PDF { @@ -179,7 +181,58 @@ public void ReadPrintAreas() //wrap comments //text placement //alignment - //vertical + //vertical + } + + [TestMethod] + public void PerfTest() + { + var sw = new Stopwatch(); + Console.WriteLine("Starting..."); + sw.Start(); + using var p = OpenTemplatePackage("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx"); + Console.WriteLine($"Read package time elapsed: {sw.ElapsedMilliseconds} ms"); + sw.Restart(); + var ws = p.Workbook.Worksheets[0]; + var dun = ws.Dimension; + var pageSettings = new PdfPageSettings + { + CommentsAndNotes = CommentsAndNotes.AtEndOfSheet, + CellErrors = CellErrors.NA, + Debug = true, + PrintAsText = true, + ShowGridLines = false, + ShowHeadings = true + }; + PdfCatalog catalog = new PdfCatalog(pageSettings, ws, "C:\\epplustest\\pdf\\OutputTest1.3.pdf"); + Console.WriteLine($"workbook exported time elapsed: {sw.ElapsedMilliseconds} ms"); + } + + + [TestMethod] + // works as expected. + [DataRow("PDFTest.xlsx", "C:\\epplustest\\pdf\\FullPageTest56.pdf", 0)] + + [DataRow("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx", "C:\\epplustest\\pdf\\OutputTest1.1.pdf", 0)] + // 1. Minus signs alignment in cells differs from Excel. + // 2. Dimension seems to differ from Excel, Excel stops at row 75, EPPlus goes to row 89. + // 3. Row headings are sligthly wider in EPPlus than in Excel. + [DataRow("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx", "C:\\epplustest\\pdf\\OutputTest1.2.pdf", 1)] + public void WorkbookTests(string sourceFile, string outputPath, int wsIx) + { + using var p = OpenTemplatePackage(sourceFile); + var ws = p.Workbook.Worksheets[wsIx]; + + PdfPageSettings pageSettings = new PdfPageSettings(); + pageSettings.CommentsAndNotes = CommentsAndNotes.AtEndOfSheet; + + pageSettings.CellErrors = CellErrors.NA; + pageSettings.Debug = true; + pageSettings.PrintAsText = true; + pageSettings.ShowGridLines = true; + pageSettings.ShowHeadings = true; + + PdfCatalog catalog = new PdfCatalog(pageSettings, ws, outputPath); } diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs index 48d84c0add..1febec982e 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs @@ -9,6 +9,7 @@ using OfficeOpenXml.Style; using System; using System.Collections.Generic; +using System.Diagnostics; namespace EPPlus.Export.Pdf.PdfCatalog { @@ -62,6 +63,11 @@ public int Count internal class PdfLayout { + // [PERF] Temporary instrumentation — remove before PR + internal static long PerfHeadingCellCalls = 0; + internal static long PerfHeadingShapeTicks = 0; + internal static long PerfHeadingTotalTicks = 0; + internal static readonly System.Collections.Generic.HashSet PerfUniqueHeadingTexts = new System.Collections.Generic.HashSet(); private const double rowHeadingWith1CharWidth = 23.25d; public static Transform GetLayout(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfWorksheet[] pdfSheets) @@ -103,8 +109,14 @@ internal static Transform GetCatalog(PdfPageSettings pageSettings, PdfDictionari //pageLayout.AddChild(contentLayout); double contentStartX = pageSettings.ContentBounds.Left + page.HeadingWidth + page.PrintTitleWidth; double contentStartY = pageSettings.ContentBounds.Top - page.HeadingHeight - page.PrintTitleHeight; + // [PERF] page geometry + int perfRowCount = page.ToRow - page.FromRow + 1; + int perfColCount = page.ToColumn - page.FromColumn + 1; + long headingTicks = 0; if (pageSettings.ShowHeadings && !pdfPages[i].IsCommentsPage) + { AddHeadingCells(pageSettings, dictionaries, page, pageLayout, contentStartX, contentStartY, page.HeadingWidth, page.HeadingHeight, pdfPages[i].HeadingFontName, pdfPages[i].HeadingFontSize, pdfPages[i].HeadingFill); + } double y = contentStartY;//pageSettings.ContentBounds.Top; double x = contentStartX;//pageSettings.ContentBounds.Left; @@ -501,7 +513,12 @@ private static PdfCell CreateHeadingPdfCell(PdfPageSettings pageSettings, PdfDic tf.RichTextOptions.UnderlineType = 12; // none tf.RichTextOptions.StrikeType = 1; // none cell.TextFragments = new List { tf }; - PdfTextShaper.LayoutAndShapeText(pageSettings, dictionaries, cell); + // [PERF] + PerfHeadingCellCalls++; + PerfUniqueHeadingTexts.Add(text); + long perfT0 = System.Diagnostics.Stopwatch.GetTimestamp(); + PdfTextShaper.ShapeText(pageSettings, dictionaries, cell); + PerfHeadingShapeTicks += System.Diagnostics.Stopwatch.GetTimestamp() - perfT0; } return cell; } diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs index 99c854894a..e0a67fb548 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs @@ -793,7 +793,6 @@ public static PdfHeaderFooter GetTextFormats(PdfPageSettings pageSettings, PdfDi textFrag.Text = text; textFragments.Add(textFrag); - Console.WriteLine($"[GetTextFormats] AddFont('{textFrag.FullFontName}', subFamily={textFrag.Font.SubFamily}, text='{textFrag.Text}')"); dictionaries.AddFont(pageSettings, textFrag.Font.Family, textFrag.Font.SubFamily, textFrag.Text); if (NumberOfPagesIndexes.Count > 0 || PageNumberIndexes.Count > 0) dictionaries.AddFont(pageSettings, textFrag.Font.Family, textFrag.Font.SubFamily, "1234567890"); diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextShaper.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextShaper.cs index adb5439f69..45eb33a333 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextShaper.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextShaper.cs @@ -69,7 +69,6 @@ public static void ShapeText(PdfPageSettings pageSettings, PdfDictionaries dicti var shaped = shaper.Shape(tf.Text, options); var usedFonts = shaper.GetUsedFonts().ToList(); - //Console.WriteLine($"[ShapeText] tf.Text='{tf.Text}' FullFontName='{tf.FullFontName}' usedFonts={string.Join(",", usedFonts.Select(f => f.FullName).ToArray())}"); var fontIdMap = new Dictionary(); for (byte fontId = 0; fontId < usedFonts.Count; fontId++) @@ -109,84 +108,5 @@ public static void ShapeText(PdfPageSettings pageSettings, PdfDictionaries dicti cell.TotalTextLength = totalTextLength; } - - // Kept for backwards compatibility - not used by ShapeTextInPdfWorksheet anymore - public static void LayoutAndShapeText(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfCell cell) - { - var totalTextLength = 0d; - var maxLineHeight = 0d; - if (cell.TextFragments == null) return; - cell.ShapedTexts = new List(); - for (int i = 0; i < cell.TextFragments.Count; i++) - { - var tf = cell.TextFragments[i]; - cell.ShapedTexts.Add(new PdfShapedText()); - var st = cell.ShapedTexts[i]; - Console.WriteLine($"[LayoutAndShapeText] Looking up '{tf.FullFontName}' (Family={tf.Font.Family}, SubFamily={tf.Font.SubFamily}). Found in Fonts: {dictionaries.Fonts.ContainsKey(tf.FullFontName)}"); - st.FontProvider = dictionaries.Fonts[tf.FullFontName].fontSubsetManager.CreateSubsettedProvider(); - - if (!shaperCache.TryGetValue(st.FontProvider, out var shaper)) - { - shaper = new TextShaper(st.FontProvider); - shaperCache[st.FontProvider] = shaper; - } - - if (!layoutEngineCache.TryGetValue(st.FontProvider, out var layoutEngine)) - { - layoutEngine = new TextLayoutEngine(shaper); - layoutEngineCache[st.FontProvider] = layoutEngine; - } - - var options = ShapingOptions.Default; - options.ApplyPositioning = true; - options.ApplySubstitutions = true; - - var shaped = shaper.Shape(tf.Text, options); - var usedFonts = shaper.GetUsedFonts().ToList(); - var fontIdMap = new Dictionary(); - - var allProviderFonts = st.FontProvider.GetAllFonts().ToList(); - - for (byte fontId = 0; fontId < usedFonts.Count; fontId++) - { - var font = usedFonts[fontId]; - if (!dictionaries.Fonts.ContainsKey(font.FullName)) - { - int label = 1; - if (dictionaries.Fonts.Count > 0) - { - label = dictionaries.Fonts.Last().Value.labelNumber + 1; - } - var fontResource = new PdfFontResource(font.FullName, font.NameTable.GetSubfamilyEnum(), label, pageSettings); - fontResource.fontData = font; - dictionaries.Fonts.Add(font.FullName, fontResource); - } - fontIdMap[fontId] = dictionaries.Fonts[font.FullName].Label; - } - cell.TextLayoutEngine = layoutEngine; - st.ShapedText = shaped; - var textWidth = st.ShapedText.GetWidthInPoints((float)tf.Font.Size); - var textHeight = st.ShapedText.GetLineHeightInPoints((float)tf.Font.Size); - totalTextLength += textWidth; - maxLineHeight = Math.Max(textHeight, maxLineHeight); - st.FontIdMap = fontIdMap; - st.UsedFonts = usedFonts; - cell.TextFragments[i] = tf; - cell.ShapedTexts[i] = st; - } - if (cell.TextLayoutEngine != null) - { - if (cell.ContentAligmnet.WrapText) - { - double wrapWidth = (cell.Merged && cell.Main == null) ? cell.Width : cell.ColumnWidth; - cell.TextLines = cell.TextLayoutEngine.WrapRichTextLineCollection(cell.TextFragments, wrapWidth); - } - else - { - cell.TextLines = cell.TextLayoutEngine.WrapRichTextLineCollection(cell.TextFragments, double.MaxValue); - } - } - cell.TotalTextLength = totalTextLength; - } } } \ No newline at end of file diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs b/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs index d69e53f6a6..df27d574cd 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs +++ b/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs @@ -189,11 +189,11 @@ public void AddText(PdfCellContentLayout cell, Vector2 position, double textRota var originalFragment = ((TextFragment)textFormat.OriginalTextFragment); while (glyphStart < shapedText.ShapedText.Glyphs.Length && shapedText.ShapedText.Glyphs[glyphStart].GlyphId == 0) { - Console.WriteLine($"[AddText] GlyphId==0 in shapedText {shapedTextIndex} of {cell.ShapedTexts.Count}, advancing. Cell.Text='{cell.cell?.Text}' textFormat.Text='{textFormat.Text}'"); + //Console.WriteLine($"[AddText] GlyphId==0 in shapedText {shapedTextIndex} of {cell.ShapedTexts.Count}, advancing. Cell.Text='{cell.cell?.Text}' textFormat.Text='{textFormat.Text}'"); shapedTextIndex++; if (shapedTextIndex >= cell.ShapedTexts.Count) { - Console.WriteLine($"[AddText] OUT OF RANGE! Original text: '{originalFragment.Text}'"); + //Console.WriteLine($"[AddText] OUT OF RANGE! Original text: '{originalFragment.Text}'"); break; // temp safety } shapedText = cell.ShapedTexts[shapedTextIndex]; diff --git a/src/EPPlus.Fonts.OpenType/FontSubsetManager.cs b/src/EPPlus.Fonts.OpenType/FontSubsetManager.cs index 62eac29500..641933d282 100644 --- a/src/EPPlus.Fonts.OpenType/FontSubsetManager.cs +++ b/src/EPPlus.Fonts.OpenType/FontSubsetManager.cs @@ -67,11 +67,11 @@ public void AddText(string text) ushort glyphId; _sourceProvider.TryGetGlyphFont((uint)cp, out font, out glyphId); - var fontName = font?.NameTable?.GetFullFontName() ?? "null"; - if ((char)cp == 'E' || (char)cp == 'P') - { - Console.WriteLine($"[FontSubsetManager.AddText] cp='{(char)cp}' (U+{cp:X4}) -> font='{fontName}', glyphId={glyphId}"); - } + //var fontName = font?.NameTable?.GetFullFontName() ?? "null"; + //if ((char)cp == 'E' || (char)cp == 'P') + //{ + // Console.WriteLine($"[FontSubsetManager.AddText] cp='{(char)cp}' (U+{cp:X4}) -> font='{fontName}', glyphId={glyphId}"); + //} HashSet fontCodePoints; if (!_codePointsByFont.TryGetValue(font, out fontCodePoints)) From 7336c3acbf2d3de3849c32a907888f046bab3337 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Mon, 8 Jun 2026 16:39:33 +0200 Subject: [PATCH 18/49] print titles progress --- src/EPPlus.Export.Pdf.Tests/PdfTests.cs | 2 +- src/EPPlus.Export.Pdf/ExcelPdf.cs | 21 +- src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs | 598 +++++++++++++++--- src/EPPlus.Export.Pdf/PdfCatalog/PdfRange.cs | 4 +- .../PdfLayout/PdfCellBorderLayout.cs | 1 + .../PdfLayout/PdfCellContentLayout.cs | 1 + .../PdfLayout/PdfCellLayout.cs | 1 + .../PdfLayout/PdfPageLayout.cs | 1 + .../PdfObjects/PdfContentStream.cs | 31 + 9 files changed, 585 insertions(+), 75 deletions(-) diff --git a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs index cdd7577fe2..b62a41634a 100644 --- a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs +++ b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs @@ -173,7 +173,7 @@ public void ReadPrintAreas() pageSettings.PrintAsText = true; pageSettings.ShowGridLines = true; pageSettings.ShowHeadings = true; - PdfCatalog catlog = new PdfCatalog(pageSettings, ws, "C:\\epplustest\\pdf\\FullPageTest56.pdf"); + PdfCatalog catlog = new PdfCatalog(pageSettings, ws, "C:\\epplustest\\pdf\\FullPageTest57.pdf"); //line breaks //wrap comments diff --git a/src/EPPlus.Export.Pdf/ExcelPdf.cs b/src/EPPlus.Export.Pdf/ExcelPdf.cs index 94aa6390b7..0690942f18 100644 --- a/src/EPPlus.Export.Pdf/ExcelPdf.cs +++ b/src/EPPlus.Export.Pdf/ExcelPdf.cs @@ -186,9 +186,14 @@ private PdfObjects.PdfCatalog AddCatalog(int pagesObjectNumber) private void AddContent(Transform pageLayout, PdfPage page) { //var cells = pageLayout.ChildObjects.Where(t => t is PdfCellLayout || t is PdfCellContentLayout || t is PdfCellBorderLayout).GroupBy(t => t.Name); - var cells = pageLayout.ChildObjects.Where(t => (t is PdfCellLayout || t is PdfCellContentLayout || t is PdfCellBorderLayout) && !(t is PdfCellLayout cc && cc.IsHeading) && !(t is PdfCellContentLayout ccl && (ccl.IsHeaderFooter || ccl.IsHeading))).GroupBy(t => t.Name); + var cells = pageLayout.ChildObjects.Where(t => + (t is PdfCellLayout || t is PdfCellContentLayout || t is PdfCellBorderLayout) && + !(t is PdfCellLayout cc && (cc.IsHeading || cc.IsPrintTitle)) && + !(t is PdfCellContentLayout ccl && (ccl.IsHeaderFooter || ccl.IsHeading || ccl.IsPrintTitle)) && + !(t is PdfCellBorderLayout cbl && cbl.IsPrintTitle)).GroupBy(t => t.Name); var headerFooterLayouts = pageLayout.ChildObjects.OfType().Where(t => t.IsHeaderFooter); 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)); var contentStream = new PdfContentStream(_document.Count + 1); contentStream.AddCommand($"% {pageLayout.Name} start"); //Add clipping rectangle around page content. @@ -237,12 +242,26 @@ private void AddContent(Transform pageLayout, PdfPage page) if (_pageSettings.ShowGridLines || _pageSettings.ShowHeadings) { contentStream.AddOuterGridBorder(pageLayout); + contentStream.AddPrintTitleGridLines(pageLayout); } //Add header and footer. foreach (var hf in headerFooterLayouts) { contentStream.AddCellContentLayout(hf, _dictionaries, _pageSettings); } + foreach (var titleCell in printTitleLayouts) + { + contentStream.AddCommand($"% PRINT TITLE : {titleCell.Name}"); + switch (titleCell) + { + case PdfCellLayout layout: + contentStream.AddCellLayout(layout, GetPatternLabel(layout)); break; + case PdfCellContentLayout contentLayout: + contentStream.AddCellContentLayout(contentLayout, _dictionaries, _pageSettings); break; + case PdfCellBorderLayout borderLayout: + contentStream.AddBorderLayout(borderLayout); break; + } + } _document.Add(contentStream); page.contentObjectNumbers.Add(contentStream.objectNumber); contentStream.AddCommand($"% {pageLayout.Name} end"); diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs index 48d84c0add..6128301e52 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs @@ -21,6 +21,25 @@ internal struct MergedCellDrawInfo public double Height; } + internal struct PrintTitleCellDraw + { + public PdfCell Cell; + public double X; + public double Y; + public double Width; + public double Height; + } + + internal struct PrintTitleHeadingDraw + { + public bool IsRow; // true = row-number heading (left strip); false = column-letter (top strip) + public int Index; // original absolute row/column index — the label source + public double X; + public double Y; + public double Width; + public double Height; + } + internal struct Page { public int FromRow; @@ -37,6 +56,9 @@ internal struct Page public PdfHeaderFooterCollection HeaderFooters; public Dictionary MergedCells; + public List PrintTitleCells; + public List PrintTitleGridLines; + public List PrintTitleHeadings; public double[] RowHeights; @@ -104,7 +126,11 @@ internal static Transform GetCatalog(PdfPageSettings pageSettings, PdfDictionari double contentStartX = pageSettings.ContentBounds.Left + page.HeadingWidth + page.PrintTitleWidth; double contentStartY = pageSettings.ContentBounds.Top - page.HeadingHeight - page.PrintTitleHeight; if (pageSettings.ShowHeadings && !pdfPages[i].IsCommentsPage) + { AddHeadingCells(pageSettings, dictionaries, page, pageLayout, contentStartX, contentStartY, page.HeadingWidth, page.HeadingHeight, pdfPages[i].HeadingFontName, pdfPages[i].HeadingFontSize, pdfPages[i].HeadingFill); + AddPrintTitleHeadings(pageSettings, dictionaries, page, pageLayout, pdfPages[i].HeadingFontName, pdfPages[i].HeadingFontSize, pdfPages[i].HeadingFill); + } + AddPrintTitleCells(pageSettings, dictionaries, page, pageLayout); double y = contentStartY;//pageSettings.ContentBounds.Top; double x = contentStartX;//pageSettings.ContentBounds.Left; @@ -295,8 +321,6 @@ internal static Transform GetCatalog(PdfPageSettings pageSettings, PdfDictionari return string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase); return cmp; }); - - //Print titles pageNumber++; Catalog.AddChild(pageLayout); } @@ -380,88 +404,156 @@ private static bool HasBorder(PdfCellStyle cellStyle) private static bool HasDiagonalBorder(PdfCellStyle style) => style?.Diagonal != null && style.Diagonal.Style != ExcelBorderStyle.None; + private static void AddHeadingCell(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfPageLayout pageLayout, PdfCellStyle headingStyle, string label, double x, double y, double width, double height, string fontName, float fontSize, string namePrefix) + { + if (width == 0d || height == 0d) return; + + var fill = new PdfCellLayout(dictionaries, headingStyle, x, y, width, height); + fill.Name = namePrefix; + fill.IsHeading = true; + fill.UpdateShadingPositionMatrix(pageSettings); + pageLayout.AddChild(fill); + + var cell = CreateHeadingPdfCell(pageSettings, dictionaries, label, ExcelHorizontalAlignment.Center, width, height, fontName, fontSize); + if (cell.TextLines != null && cell.TextLines.Count > 0) + { + var info = new MergedCellDrawInfo { X = x, Y = y, Width = width, Height = height }; + var text = new PdfCellContentLayout(pageSettings, dictionaries, cell, info, x, y, width, height); + text.Name = namePrefix + "_Text"; + text.IsHeading = true; + text.GidsAndCharMap(dictionaries); + pageLayout.AddChild(text); + } + AddHeadingCellBorder(pageLayout, x, y, width, height); + } + private static void AddHeadingCells(PdfPageSettings pageSettings, PdfDictionaries dictionaries, Page page, PdfPageLayout pageLayout, double contentStartX, double contentStartY, double headingWidth, double headingHeight, string fontName, float fontSize, ExcelFill fill) { - // An empty style gives a transparent/white fill with no border. - // TODO: replace with a proper light-grey fill once PdfCellStyle supports - // creating standalone fills without a backing ExcelFillXml object. var headingStyle = new PdfCellStyle(); headingStyle.xfFill = fill; - // ── Corner cell (top-left, where the two strips intersect) ────────────────────────── var cornerFill = new PdfCellLayout(dictionaries, headingStyle, - pageSettings.ContentBounds.Left, pageSettings.ContentBounds.Top, - headingWidth, headingHeight); + pageSettings.ContentBounds.Left, pageSettings.ContentBounds.Top, headingWidth, headingHeight); cornerFill.Name = "Heading_Corner"; cornerFill.UpdateShadingPositionMatrix(pageSettings); pageLayout.AddChild(cornerFill); - // ── Column headings (one per data column, spanning the top strip) ─────────────────── double x = contentStartX; for (int col = page.FromColumn; col <= page.ToColumn; col++) { - var mapCell = page.Map[page.FromRow, col]; - double colWidth = mapCell?.ColumnWidth ?? 0d; - if (colWidth == 0d) { x += colWidth; continue; } // hidden column — skip - + double colWidth = page.Map[page.FromRow, col]?.ColumnWidth ?? 0d; + if (colWidth == 0d) { x += colWidth; continue; } string colLetter = ExcelCellBase.GetColumnLetter(col); - - var colFill = new PdfCellLayout(dictionaries, headingStyle, - x, pageSettings.ContentBounds.Top, colWidth, headingHeight); - colFill.Name = "Heading_Col_" + colLetter; - colFill.IsHeading = true; - colFill.UpdateShadingPositionMatrix(pageSettings); - pageLayout.AddChild(colFill); - - var colCell = CreateHeadingPdfCell(pageSettings, dictionaries, - colLetter, ExcelHorizontalAlignment.Center, - colWidth, headingHeight, fontName, fontSize); - - if (colCell.TextLines != null && colCell.TextLines.Count > 0) - { - var info = new MergedCellDrawInfo { X = x, Y = pageSettings.ContentBounds.Top, Width = colWidth, Height = headingHeight }; - var colText = new PdfCellContentLayout(pageSettings, dictionaries, colCell, info, - x, pageSettings.ContentBounds.Top, colWidth, headingHeight); - colText.Name = "Heading_Col_" + colLetter + "_Text"; - colText.IsHeading = true; - colText.GidsAndCharMap(dictionaries); - pageLayout.AddChild(colText); - } - AddHeadingCellBorder(pageLayout, x, pageSettings.ContentBounds.Top, colWidth, headingHeight); + AddHeadingCell(pageSettings, dictionaries, pageLayout, headingStyle, colLetter, + x, pageSettings.ContentBounds.Top, colWidth, headingHeight, fontName, fontSize, "Heading_Col_" + colLetter); x += colWidth; } - // ── Row headings (one per data row, spanning the left strip) ───────────────────────── + double y = contentStartY; for (int row = page.FromRow; row <= page.ToRow; row++) { double rowHeight = page.RowHeights[row - page.FromRow]; - if (rowHeight == 0d) { y -= rowHeight; continue; } // hidden row — skip - + if (rowHeight == 0d) { y -= rowHeight; continue; } string rowNum = row.ToString(); + AddHeadingCell(pageSettings, dictionaries, pageLayout, headingStyle, rowNum, + pageSettings.ContentBounds.Left, y, headingWidth, rowHeight, fontName, fontSize, "Heading_Row_" + rowNum); + y -= rowHeight; + } + //// An empty style gives a transparent/white fill with no border. + //// TODO: replace with a proper light-grey fill once PdfCellStyle supports + //// creating standalone fills without a backing ExcelFillXml object. + //var headingStyle = new PdfCellStyle(); + //headingStyle.xfFill = fill; + + //// ── Corner cell (top-left, where the two strips intersect) ────────────────────────── + //var cornerFill = new PdfCellLayout(dictionaries, headingStyle, + // pageSettings.ContentBounds.Left, pageSettings.ContentBounds.Top, + // headingWidth, headingHeight); + //cornerFill.Name = "Heading_Corner"; + //cornerFill.UpdateShadingPositionMatrix(pageSettings); + //pageLayout.AddChild(cornerFill); + + //// ── Column headings (one per data column, spanning the top strip) ─────────────────── + //double x = contentStartX; + //for (int col = page.FromColumn; col <= page.ToColumn; col++) + //{ + // var mapCell = page.Map[page.FromRow, col]; + // double colWidth = mapCell?.ColumnWidth ?? 0d; + // if (colWidth == 0d) { x += colWidth; continue; } // hidden column — skip + + // string colLetter = ExcelCellBase.GetColumnLetter(col); + + // var colFill = new PdfCellLayout(dictionaries, headingStyle, + // x, pageSettings.ContentBounds.Top, colWidth, headingHeight); + // colFill.Name = "Heading_Col_" + colLetter; + // colFill.IsHeading = true; + // colFill.UpdateShadingPositionMatrix(pageSettings); + // pageLayout.AddChild(colFill); + + // var colCell = CreateHeadingPdfCell(pageSettings, dictionaries, + // colLetter, ExcelHorizontalAlignment.Center, + // colWidth, headingHeight, fontName, fontSize); + + // if (colCell.TextLines != null && colCell.TextLines.Count > 0) + // { + // var info = new MergedCellDrawInfo { X = x, Y = pageSettings.ContentBounds.Top, Width = colWidth, Height = headingHeight }; + // var colText = new PdfCellContentLayout(pageSettings, dictionaries, colCell, info, + // x, pageSettings.ContentBounds.Top, colWidth, headingHeight); + // colText.Name = "Heading_Col_" + colLetter + "_Text"; + // colText.IsHeading = true; + // colText.GidsAndCharMap(dictionaries); + // pageLayout.AddChild(colText); + // } + // AddHeadingCellBorder(pageLayout, x, pageSettings.ContentBounds.Top, colWidth, headingHeight); + // x += colWidth; + //} + //// ── Row headings (one per data row, spanning the left strip) ───────────────────────── + //double y = contentStartY; + //for (int row = page.FromRow; row <= page.ToRow; row++) + //{ + // double rowHeight = page.RowHeights[row - page.FromRow]; + // if (rowHeight == 0d) { y -= rowHeight; continue; } // hidden row — skip + + // string rowNum = row.ToString(); + + // var rowFill = new PdfCellLayout(dictionaries, headingStyle, + // pageSettings.ContentBounds.Left, y, headingWidth, rowHeight); + // rowFill.Name = "Heading_Row_" + rowNum; + // rowFill.IsHeading = true; + // rowFill.UpdateShadingPositionMatrix(pageSettings); + // pageLayout.AddChild(rowFill); + + // var rowCell = CreateHeadingPdfCell(pageSettings, dictionaries, + // rowNum, ExcelHorizontalAlignment.Center, + // headingWidth, rowHeight, fontName, fontSize); + + // if (rowCell.TextLines != null && rowCell.TextLines.Count > 0) + // { + // var info = new MergedCellDrawInfo { X = pageSettings.ContentBounds.Left, Y = y, Width = headingWidth, Height = rowHeight }; + // var rowText = new PdfCellContentLayout(pageSettings, dictionaries, rowCell, info, + // pageSettings.ContentBounds.Left, y, headingWidth, rowHeight); + // rowText.Name = "Heading_Row_" + rowNum + "_Text"; + // rowText.IsHeading = true; + // rowText.GidsAndCharMap(dictionaries); + // pageLayout.AddChild(rowText); + // } + // AddHeadingCellBorder(pageLayout, pageSettings.ContentBounds.Left, y, headingWidth, rowHeight); + // y -= rowHeight; + //} + } - var rowFill = new PdfCellLayout(dictionaries, headingStyle, - pageSettings.ContentBounds.Left, y, headingWidth, rowHeight); - rowFill.Name = "Heading_Row_" + rowNum; - rowFill.IsHeading = true; - rowFill.UpdateShadingPositionMatrix(pageSettings); - pageLayout.AddChild(rowFill); + private static void AddPrintTitleHeadings(PdfPageSettings pageSettings, PdfDictionaries dictionaries, Page page, PdfPageLayout pageLayout, string fontName, float fontSize, ExcelFill fill) + { + if (page.PrintTitleHeadings == null || page.PrintTitleHeadings.Count == 0) return; - var rowCell = CreateHeadingPdfCell(pageSettings, dictionaries, - rowNum, ExcelHorizontalAlignment.Center, - headingWidth, rowHeight, fontName, fontSize); + var headingStyle = new PdfCellStyle(); + headingStyle.xfFill = fill; - if (rowCell.TextLines != null && rowCell.TextLines.Count > 0) - { - var info = new MergedCellDrawInfo { X = pageSettings.ContentBounds.Left, Y = y, Width = headingWidth, Height = rowHeight }; - var rowText = new PdfCellContentLayout(pageSettings, dictionaries, rowCell, info, - pageSettings.ContentBounds.Left, y, headingWidth, rowHeight); - rowText.Name = "Heading_Row_" + rowNum + "_Text"; - rowText.IsHeading = true; - rowText.GidsAndCharMap(dictionaries); - pageLayout.AddChild(rowText); - } - AddHeadingCellBorder(pageLayout, pageSettings.ContentBounds.Left, y, headingWidth, rowHeight); - y -= rowHeight; + foreach (var h in page.PrintTitleHeadings) + { + string label = h.IsRow ? h.Index.ToString() : ExcelCellBase.GetColumnLetter(h.Index); + string namePrefix = (h.IsRow ? "Heading_Row_" : "Heading_Col_") + label; + AddHeadingCell(pageSettings, dictionaries, pageLayout, headingStyle, label, h.X, h.Y, h.Width, h.Height, fontName, fontSize, namePrefix); } } @@ -501,12 +593,55 @@ private static PdfCell CreateHeadingPdfCell(PdfPageSettings pageSettings, PdfDic tf.RichTextOptions.UnderlineType = 12; // none tf.RichTextOptions.StrikeType = 1; // none cell.TextFragments = new List { tf }; - PdfTextShaper.LayoutAndShapeText(pageSettings, dictionaries, cell); + PdfTextShaper.ShapeText(pageSettings, dictionaries, cell); } return cell; } + private static void AddPrintTitleCells(PdfPageSettings pageSettings, PdfDictionaries dictionaries, Page page, PdfPageLayout pageLayout) + { + if (page.PrintTitleCells == null) return; + pageLayout.PrintTitleGridLines = page.PrintTitleGridLines ?? new List(); + foreach (var t in page.PrintTitleCells) + { + var map = t.Cell; + var fill = new PdfCellLayout(dictionaries, map.CellStyle, t.X, t.Y, t.Width, t.Height); + fill.Name = map.Name; + fill.IsPrintTitle = true; + fill.UpdateShadingPositionMatrix(pageSettings); + pageLayout.AddChild(fill); + + if (map.TextLines != null && map.TextLines.Count > 0) + { + var info = new MergedCellDrawInfo { X = t.X, Y = t.Y, Width = t.Width, Height = t.Height }; + var text = new PdfCellContentLayout(pageSettings, dictionaries, map, info, t.X, t.Y, t.Width, t.Height); + text.Name = map.Name; + text.IsPrintTitle = true; + text.GidsAndCharMap(dictionaries); + text.SetupClipping(t.X, t.Y, t.Width, t.Height); + pageLayout.AddChild(text); + } + + //if (map.TextLines != null && map.TextLines.Count > 0) + //{ + // var text = new PdfCellContentLayout(pageSettings, dictionaries, map, new MergedCellDrawInfo(), t.X, t.Y, t.Width, t.Height); + // text.Name = map.Name; + // text.IsPrintTitle = true; + // text.GidsAndCharMap(dictionaries); + // text.SetupClipping(t.X, t.Y, t.Width, t.Height); + // pageLayout.AddChild(text); + //} + + if (HasBorder(map.CellStyle)) + { + var border = new PdfCellBorderLayout(map.CellStyle, false, MergedCellCorners.All, new MergedCellDrawInfo(), t.X, t.Y, t.Width, t.Height); + border.Name = map.Name; + border.IsPrintTitle = true; + pageLayout.AddChild(border); + } + } + } // TODO Count total pages when creating them isntead of looping them here. @@ -533,6 +668,7 @@ internal static List GetPages(PdfPageSettings pageSettings, PdfWorksheet[ pages = MapPage(range, pages); pages = GetHeaderFooter(range, pages, pdfSheet); pages = PrecomputeMergedCells(pageSettings, range, pages); + pages = PrecomputePrintTitleCells(pageSettings, pdfSheet, range, pages); pages.HeadingFontName = pdfSheet.NormalStyle.Style.Font.Name; pages.HeadingFontSize = pdfSheet.NormalStyle.Style.Font.Size; pages.HeadingFill = pdfSheet.NormalStyle.Style.Fill; @@ -648,6 +784,321 @@ private static void ComputePrintTitleDimensions(PdfWorksheet pdfSheet, PdfRange } } + internal static Pages PrecomputePrintTitleCells(PdfPageSettings pageSettings, PdfWorksheet pdfSheet, PdfRange range, Pages pdfPages) + { + for (int i = 0; i < pdfPages.Page.Length; i++) + pdfPages.Page[i] = PrecomputePagePrintTitleCells(pageSettings, pdfSheet, range, pdfPages.Page[i]); + return pdfPages; + } + + private static Page PrecomputePagePrintTitleCells(PdfPageSettings pageSettings, PdfWorksheet pdfSheet, PdfRange range, Page page) + { + page.PrintTitleCells = new List(); + + bool topBand = page.PrintTitleHeight > 0d && pdfSheet.PrintTitleRowFrom >= 0 && page.FromRow > pdfSheet.PrintTitleRowTo; + bool leftBand = page.PrintTitleWidth > 0d && pdfSheet.PrintTitleColFrom >= 0 && page.FromColumn > pdfSheet.PrintTitleColTo; + if (!topBand && !leftBand) return page; + + // Content-column X: same origin/widths the content loop uses (step-2 origin). + var contentColX = new Dictionary(); + double cx = pageSettings.ContentBounds.Left + page.HeadingWidth + page.PrintTitleWidth; + for (int c = page.FromColumn; c <= page.ToColumn; c++) + { + contentColX[c] = cx; + cx += RangeColWidth(range, page.FromRow, c); + } + + // Content-row Y. + var contentRowY = new Dictionary(); + double cy = pageSettings.ContentBounds.Top - page.HeadingHeight - page.PrintTitleHeight; + for (int r = page.FromRow; r <= page.ToRow; r++) + { + contentRowY[r] = cy; + cy -= RangeRowHeight(range, r); + } + + // Title-column X (left band): just right of the heading gutter. + var titleColX = new Dictionary(); + if (leftBand) + { + double tx = pageSettings.ContentBounds.Left + page.HeadingWidth; + for (int c = pdfSheet.PrintTitleColFrom; c <= pdfSheet.PrintTitleColTo; c++) + { + titleColX[c] = tx; + tx += RangeColWidth(range, page.FromRow, c); + } + } + + // Title-row Y (top band): just below the heading gutter. + var titleRowY = new Dictionary(); + if (topBand) + { + double ty = pageSettings.ContentBounds.Top - page.HeadingHeight; + for (int r = pdfSheet.PrintTitleRowFrom; r <= pdfSheet.PrintTitleRowTo; r++) + { + titleRowY[r] = ty; + ty -= RangeRowHeight(range, r); + } + } + + if (topBand) + ProcessBandRegionCells(page, range, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, page.FromColumn, page.ToColumn, contentColX, titleRowY); + if (leftBand) + ProcessBandRegionCells(page, range, page.FromRow, page.ToRow, pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, titleColX, contentRowY); + if (topBand && leftBand) + ProcessBandRegionCells(page, range, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, titleColX, titleRowY); + + //// Top band: title rows × this page's content columns. + //if (topBand) + // for (int r = pdfSheet.PrintTitleRowFrom; r <= pdfSheet.PrintTitleRowTo; r++) + // for (int c = page.FromColumn; c <= page.ToColumn; c++) + // AddPrintTitleDraw(page.PrintTitleCells, range, r, c, + // contentColX[c], titleRowY[r], RangeColWidth(range, page.FromRow, c), RangeRowHeight(range, r)); + + //// Left band: this page's content rows × title columns. + //if (leftBand) + // for (int r = page.FromRow; r <= page.ToRow; r++) + // for (int c = pdfSheet.PrintTitleColFrom; c <= pdfSheet.PrintTitleColTo; c++) + // AddPrintTitleDraw(page.PrintTitleCells, range, r, c, + // titleColX[c], contentRowY[r], RangeColWidth(range, page.FromRow, c), RangeRowHeight(range, r)); + + //// Corner: title rows × title columns. + //if (topBand && leftBand) + // for (int r = pdfSheet.PrintTitleRowFrom; r <= pdfSheet.PrintTitleRowTo; r++) + // for (int c = pdfSheet.PrintTitleColFrom; c <= pdfSheet.PrintTitleColTo; c++) + // AddPrintTitleDraw(page.PrintTitleCells, range, r, c, + // titleColX[c], titleRowY[r], RangeColWidth(range, page.FromRow, c), RangeRowHeight(range, r)); + + page.PrintTitleGridLines = new List(); + if (pageSettings.ShowGridLines) + { + if (topBand) + EmitBandGrid(page.PrintTitleGridLines, range, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, page.FromColumn, page.ToColumn, + BandEdgesX(contentColX, page.FromColumn, page.ToColumn, range, page.FromRow), + BandEdgesY(titleRowY, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, range)); + if (leftBand) + EmitBandGrid(page.PrintTitleGridLines, range, page.FromRow, page.ToRow, pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, + BandEdgesX(titleColX, pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, range, page.FromRow), + BandEdgesY(contentRowY, page.FromRow, page.ToRow, range)); + if (topBand && leftBand) + EmitBandGrid(page.PrintTitleGridLines, range, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, + BandEdgesX(titleColX, pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, range, page.FromRow), + BandEdgesY(titleRowY, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, range)); + } + + // ---- band gridlines: full grid per region, rendered outside the clip ---- + //page.PrintTitleGridLines = new List(); + //if (pageSettings.ShowGridLines) + //{ + // if (topBand) + // EmitFullGrid(page.PrintTitleGridLines, + // BandEdgesX(contentColX, page.FromColumn, page.ToColumn, range, page.FromRow), + // BandEdgesY(titleRowY, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, range)); + + // if (leftBand) + // EmitFullGrid(page.PrintTitleGridLines, + // BandEdgesX(titleColX, pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, range, page.FromRow), + // BandEdgesY(contentRowY, page.FromRow, page.ToRow, range)); + + // if (topBand && leftBand) + // EmitFullGrid(page.PrintTitleGridLines, + // BandEdgesX(titleColX, pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, range, page.FromRow), + // BandEdgesY(titleRowY, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, range)); + //} + + // ---- band headings: original row numbers / column letters, in the gutter gaps ---- + page.PrintTitleHeadings = new List(); + if (pageSettings.ShowHeadings) + { + if (topBand) + for (int r = pdfSheet.PrintTitleRowFrom; r <= pdfSheet.PrintTitleRowTo; r++) + { + double h = RangeRowHeight(range, r); + if (h <= 0d) continue; + page.PrintTitleHeadings.Add(new PrintTitleHeadingDraw + { + IsRow = true, + Index = r, + X = pageSettings.ContentBounds.Left, + Y = titleRowY[r], + Width = page.HeadingWidth, + Height = h + }); + } + + if (leftBand) + for (int c = pdfSheet.PrintTitleColFrom; c <= pdfSheet.PrintTitleColTo; c++) + { + double w = RangeColWidth(range, page.FromRow, c); + if (w <= 0d) continue; + page.PrintTitleHeadings.Add(new PrintTitleHeadingDraw + { + IsRow = false, + Index = c, + X = titleColX[c], + Y = pageSettings.ContentBounds.Top, + Width = w, + Height = page.HeadingHeight + }); + } + } + + return page; + } + + private static void ProcessBandRegionCells(Page page, PdfRange range, int fromRow, int toRow, int fromCol, int toCol, Dictionary colX, Dictionary rowY) + { + var drawnMerges = new HashSet(); + for (int r = fromRow; r <= toRow; r++) + { + for (int c = fromCol; c <= toCol; c++) + { + var cell = RangeCell(range, r, c); + if (cell == null) continue; + + if (cell.Merged) + { + if (!drawnMerges.Add(cell.MergedAddress.Address)) continue; // render each merge once per region + var addr = cell.MergedAddress; + var main = cell.Main ?? cell; + + int vFromCol = Math.Max(addr._fromCol, fromCol); + int vToCol = Math.Min(addr._toCol, toCol); + int vFromRow = Math.Max(addr._fromRow, fromRow); + int vToRow = Math.Min(addr._toRow, toRow); + if (vFromCol > vToCol || vFromRow > vToRow) continue; + + double x = colX[vFromCol]; + double width = (colX[vToCol] + RangeColWidth(range, page.FromRow, vToCol)) - colX[vFromCol]; + double y = rowY[vFromRow]; + double height = (rowY[vFromRow] - rowY[vToRow]) + RangeRowHeight(range, vToRow); + if (width <= 0d || height <= 0d) continue; + + page.PrintTitleCells.Add(new PrintTitleCellDraw { Cell = main, X = x, Y = y, Width = width, Height = height }); + } + else + { + double w = RangeColWidth(range, page.FromRow, c); + double h = RangeRowHeight(range, r); + if (w <= 0d || h <= 0d) continue; + page.PrintTitleCells.Add(new PrintTitleCellDraw { Cell = cell, X = colX[c], Y = rowY[r], Width = w, Height = h }); + } + } + } + } + + private static void EmitBandGrid(List target, PdfRange range, int fromRow, int toRow, int fromCol, int toCol, double[] colX, double[] rowY) + { + int nr = toRow - fromRow + 1; + int nc = toCol - fromCol + 1; + if (nr <= 0 || nc <= 0) return; + + double top = rowY[0], bottom = rowY[nr]; + double left = colX[0], right = colX[nc]; + + // region frame (outer edges; harmless overlap with page frame / heading borders / band↔content seam) + target.Add(new GridLine(left, top, left, bottom)); + target.Add(new GridLine(right, top, right, bottom)); + target.Add(new GridLine(left, top, right, top)); + target.Add(new GridLine(left, bottom, right, bottom)); + + // interior verticals — suppress where a merge spans the gap + for (int gi = 1; gi < nc; gi++) + { + int leftCol = fromCol + gi - 1, rightCol = leftCol + 1; + double x = colX[gi]; + double? runStart = null; double runEnd = 0d; + for (int ri = 0; ri < nr; ri++) + { + int r = fromRow + ri; + if (!SameMerge(range, r, leftCol, r, rightCol)) { if (runStart == null) runStart = rowY[ri]; runEnd = rowY[ri + 1]; } + else if (runStart != null) { target.Add(new GridLine(x, runStart.Value, x, runEnd)); runStart = null; } + } + if (runStart != null) target.Add(new GridLine(x, runStart.Value, x, runEnd)); + } + + // interior horizontals — suppress where a merge spans the gap + for (int gj = 1; gj < nr; gj++) + { + int topRow = fromRow + gj - 1, bottomRow = topRow + 1; + double y = rowY[gj]; + double? runStart = null; double runEnd = 0d; + for (int ci = 0; ci < nc; ci++) + { + int c = fromCol + ci; + if (!SameMerge(range, topRow, c, bottomRow, c)) { if (runStart == null) runStart = colX[ci]; runEnd = colX[ci + 1]; } + else if (runStart != null) { target.Add(new GridLine(runStart.Value, y, runEnd, y)); runStart = null; } + } + if (runStart != null) target.Add(new GridLine(runStart.Value, y, runEnd, y)); + } + } + + private static bool SameMerge(PdfRange range, int r1, int c1, int r2, int c2) + { + var a = RangeCell(range, r1, c1); + var b = RangeCell(range, r2, c2); + if (a == null || b == null || !a.Merged || !b.Merged) return false; + return a.MergedAddress.Address == b.MergedAddress.Address; + } + + // Column edges for a band region: left edge of every column fromCol..toCol, plus the trailing right edge. + private static double[] BandEdgesX(Dictionary leftX, int fromCol, int toCol, PdfRange range, int repRow) + { + int n = toCol - fromCol + 1; + var arr = new double[n + 1]; + for (int c = fromCol, k = 0; c <= toCol; c++, k++) arr[k] = leftX[c]; + arr[n] = leftX[toCol] + RangeColWidth(range, repRow, toCol); + return arr; + } + + // Row edges for a band region: top edge of every row fromRow..toRow, plus the trailing bottom edge. + private static double[] BandEdgesY(Dictionary topY, int fromRow, int toRow, PdfRange range) + { + int n = toRow - fromRow + 1; + var arr = new double[n + 1]; + for (int r = fromRow, k = 0; r <= toRow; r++, k++) arr[k] = topY[r]; + arr[n] = topY[toRow] - RangeRowHeight(range, toRow); + return arr; + } + + private static void EmitFullGrid(List target, double[] colX, double[] rowY) + { + int nc = colX.Length - 1; + int nr = rowY.Length - 1; + if (nc < 1 || nr < 1) return; + double top = rowY[0], bottom = rowY[nr]; + double left = colX[0], right = colX[nc]; + for (int i = 0; i <= nc; i++) target.Add(new GridLine(colX[i], top, colX[i], bottom)); // verticals + for (int j = 0; j <= nr; j++) target.Add(new GridLine(left, rowY[j], right, rowY[j])); // horizontals + } + + private static void AddPrintTitleDraw(List sink, PdfRange range, int row, int col, double x, double y, double w, double h) + { + if (w <= 0d || h <= 0d) return; + var cell = RangeCell(range, row, col); + if (cell == null) return; + if (cell.Merged) return; // merged title cells: handled in a later sub-step + sink.Add(new PrintTitleCellDraw { Cell = cell, X = x, Y = y, Width = w, Height = h }); + } + + private static PdfCell RangeCell(PdfRange range, int row, int col) + { + if (row < range.Range._fromRow || row > range.Range._toRow) return null; + if (col < range.Range._fromCol || col > range.Range._toCol) return null; + return range.Map[row, col]; + } + + private static double RangeColWidth(PdfRange range, int representativeRow, int col) + { + var cell = RangeCell(range, representativeRow, col); + return cell?.ColumnWidth ?? 0d; + } + + private static double RangeRowHeight(PdfRange range, int row) + { + int i = row - range.Range._fromRow; + return (i >= 0 && i < range.RowHeights.Count) ? range.RowHeights[i].Height : 0d; + } internal static Pages GetNumberOfPages(PdfPageSettings pageSettings, PdfWorksheet pdfSheet, ref PdfRange range) { @@ -681,6 +1132,8 @@ internal static Pages GetNumberOfPages(PdfPageSettings pageSettings, PdfWorkshee yPages++; } ComputePrintTitleDimensions(pdfSheet, range, out range.PrintTitleHeight, out range.PrintTitleWidth); + range.PrintTitleRowTo = pdfSheet.PrintTitleRowFrom >= 0 ? pdfSheet.PrintTitleRowTo : -1; + range.PrintTitleColTo = pdfSheet.PrintTitleColFrom >= 0 ? pdfSheet.PrintTitleColTo : -1; Pages p = new Pages(); p.Width = xPages; p.Height = yPages; @@ -695,8 +1148,8 @@ internal static Pages AssignRangeToPages(PdfPageSettings pageSettings, PdfRange var addedWidth = pages.Width > 0 ? range.AdditionalWidth / pages.Width : 0d; var addedHeight = pages.Height > 0 ? range.AdditionalHeight / pages.Height : 0d; - var colSegments = GetColumnSegments(pageSettings, range, worksheet, addedWidth, range.PrintTitleWidth); - var rowSegments = GetRowSegments(pageSettings, range, worksheet, addedHeight, range.PrintTitleHeight); + var colSegments = GetColumnSegments(pageSettings, range, worksheet, addedWidth, range.PrintTitleWidth, range.PrintTitleColTo); + var rowSegments = GetRowSegments(pageSettings, range, worksheet, addedHeight, range.PrintTitleHeight, range.PrintTitleRowTo); pages.Page = new Page[colSegments.Count * rowSegments.Count]; int i = 0; @@ -716,8 +1169,8 @@ internal static Pages AssignRangeToPages(PdfPageSettings pageSettings, PdfRange ToRow = rowSegments[ri].To, HeadingWidth = addedWidth, HeadingHeight = addedHeight, - PrintTitleWidth = ci > 0 ? range.PrintTitleWidth : 0d, - PrintTitleHeight = ri > 0 ? range.PrintTitleHeight : 0d, + PrintTitleWidth = (range.PrintTitleColTo >= 0 && colSegments[ci].From > range.PrintTitleColTo) ? range.PrintTitleWidth : 0d, + PrintTitleHeight = (range.PrintTitleRowTo >= 0 && rowSegments[ri].From > range.PrintTitleRowTo) ? range.PrintTitleHeight : 0d, }; } else //if (pageSettings.PageOrders == PageOrders.OverThenDown) @@ -735,8 +1188,8 @@ internal static Pages AssignRangeToPages(PdfPageSettings pageSettings, PdfRange ToRow = rowSegments[ri].To, HeadingWidth = addedWidth, HeadingHeight = addedHeight, - PrintTitleWidth = ci > 0 ? range.PrintTitleWidth : 0d, - PrintTitleHeight = ri > 0 ? range.PrintTitleHeight : 0d, + PrintTitleWidth = (range.PrintTitleColTo >= 0 && colSegments[ci].From > range.PrintTitleColTo) ? range.PrintTitleWidth : 0d, + PrintTitleHeight = (range.PrintTitleRowTo >= 0 && rowSegments[ri].From > range.PrintTitleRowTo) ? range.PrintTitleHeight : 0d, }; } @@ -758,7 +1211,7 @@ private struct PageSegment public PageSegment(int from, int to) { From = from; To = to; } } - private static List GetColumnSegments(PdfPageSettings pageSettings, PdfRange range, ExcelWorksheet worksheet, double addedWidth, double titleWidth) + private static List GetColumnSegments(PdfPageSettings pageSettings, PdfRange range, ExcelWorksheet worksheet, double addedWidth, double titleWidth, int printTitleColTo) { var segments = new List(); int segStartIdx = 0; @@ -768,8 +1221,8 @@ private static List GetColumnSegments(PdfPageSettings pageSettings, { int actualCol = range.Range._fromCol + col; - double effectiveAdded = addedWidth + (segments.Count == 0 ? 0d : titleWidth); - + bool reserveTitle = titleWidth > 0d && printTitleColTo >= 0 && (range.Map.FromColumn + segStartIdx) > printTitleColTo; + double effectiveAdded = addedWidth + (reserveTitle ? titleWidth : 0d); // Content-bounds overflow: col doesn't fit, end segment before it and reprocess. if (width + range.ColWidths[col] + effectiveAdded >= pageSettings.ContentBounds.Width) @@ -799,7 +1252,7 @@ private static List GetColumnSegments(PdfPageSettings pageSettings, return segments; } - private static List GetRowSegments(PdfPageSettings pageSettings, PdfRange range, ExcelWorksheet worksheet, double addedHeight, double titleHeight) + private static List GetRowSegments(PdfPageSettings pageSettings, PdfRange range, ExcelWorksheet worksheet, double addedHeight, double titleHeight, int printTitleRowTo) { var segments = new List(); int segStartIdx = 0; @@ -809,7 +1262,8 @@ private static List GetRowSegments(PdfPageSettings pageSettings, Pd { int actualRow = range.Range._fromRow + row; - double effectiveAdded = addedHeight + (segments.Count == 0 ? 0d : titleHeight); + bool reserveTitle = titleHeight > 0d && printTitleRowTo >= 0 && (range.Map.FromRow + segStartIdx) > printTitleRowTo; + double effectiveAdded = addedHeight + (reserveTitle ? titleHeight : 0d); // Content-bounds overflow: row doesn't fit, end segment before it and reprocess. if (height + range.RowHeights[row].Height + effectiveAdded >= pageSettings.ContentBounds.Height) diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfRange.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfRange.cs index e979937ee2..c4d27bac6f 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfRange.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfRange.cs @@ -22,7 +22,9 @@ internal struct PdfRange public double AdditionalWidth; public double PrintTitleHeight; public double PrintTitleWidth; - + public int PrintTitleRowTo = -1; + public int PrintTitleColTo = -1; + public PdfRange(ExcelRangeBase range, bool extendColumns) { Range = range; diff --git a/src/EPPlus.Export.Pdf/PdfLayout/PdfCellBorderLayout.cs b/src/EPPlus.Export.Pdf/PdfLayout/PdfCellBorderLayout.cs index 9d241d160d..279fefd860 100644 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfCellBorderLayout.cs +++ b/src/EPPlus.Export.Pdf/PdfLayout/PdfCellBorderLayout.cs @@ -31,6 +31,7 @@ internal class PdfCellBorderLayout : Transform , IBorderLayout public MergedCellDrawInfo MergedCellInfo; public MergedCellCorners Corners; public ExcelRangeBase cell; + public bool IsPrintTitle; public string range; diff --git a/src/EPPlus.Export.Pdf/PdfLayout/PdfCellContentLayout.cs b/src/EPPlus.Export.Pdf/PdfLayout/PdfCellContentLayout.cs index 6150705c83..ad047ceb8d 100644 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfCellContentLayout.cs +++ b/src/EPPlus.Export.Pdf/PdfLayout/PdfCellContentLayout.cs @@ -37,6 +37,7 @@ internal class PdfCellContentLayout : Transform public Rect Clipping; public bool IsHeaderFooter; public bool IsHeading; + public bool IsPrintTitle; public PdfCellStyle CellStyle; public ExcelRangeBase cell; public TextLayoutEngine textLayoutEngine; diff --git a/src/EPPlus.Export.Pdf/PdfLayout/PdfCellLayout.cs b/src/EPPlus.Export.Pdf/PdfLayout/PdfCellLayout.cs index 6405a0fed4..33cdc79b5b 100644 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfCellLayout.cs +++ b/src/EPPlus.Export.Pdf/PdfLayout/PdfCellLayout.cs @@ -34,6 +34,7 @@ internal class PdfCellLayout : Transform, IShadingLayout public double RightTextSpillLength = 0d; public bool delete = false; public bool IsHeading = false; + public bool IsPrintTitle = false; public PdfCellLayout(PdfDictionaries dictionaries, PdfCellStyle CellStyle, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) : base(x, y - height, width, height, scaleX, scaleY, rotation, parent) diff --git a/src/EPPlus.Export.Pdf/PdfLayout/PdfPageLayout.cs b/src/EPPlus.Export.Pdf/PdfLayout/PdfPageLayout.cs index 191b18156c..553f2f1273 100644 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfPageLayout.cs +++ b/src/EPPlus.Export.Pdf/PdfLayout/PdfPageLayout.cs @@ -63,6 +63,7 @@ internal class PdfPageLayout : Transform { internal List GridLines = new List(); internal List BorderLines = new List(); + public List PrintTitleGridLines = new List(); public int FromRow = int.MaxValue; public int ToRow = 0; diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs b/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs index d69e53f6a6..62a41010b9 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs +++ b/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs @@ -516,6 +516,37 @@ public void AddInnerGridLines(Transform pageLayout) commands.Add($"% Gridlines End"); } + public void AddPrintTitleGridLines(Transform pageLayout) + { + if (pageLayout is not PdfPageLayout pl) return; + if (pl.isCommentsPage) return; + + commands.Add($"% Print Title Gridlines Start"); + commands.Add("q"); + commands.Add($"{GridLine.Width.ToPdfString()} w"); + commands.Add(Color.Black.ToFillCommand()); + foreach (var line in pl.PrintTitleGridLines) + { + string w, h; + if (line.X1 == line.X2) + { + w = GridLine.Width.ToPdfStringF4(); + h = System.Math.Abs(line.Y2 - line.Y1).ToPdfStringF4(); + } + else + { + w = System.Math.Abs(line.X2 - line.X1).ToPdfStringF4(); + h = GridLine.Width.ToPdfStringF4(); + } + var x = Math.Min(line.X1, line.X2); + var y = Math.Min(line.Y1, line.Y2); + commands.Add($"{x.ToPdfStringF4()} {y.ToPdfStringF4()} {w} {h} re"); + } + commands.Add("f"); + commands.Add("Q"); + commands.Add($"% Print Title Gridlines End"); + } + public void AddOuterGridBorder(Transform pageLayout) { if (pageLayout is not PdfPageLayout pl) return; From 4031bf50d3377c998575ad85bdf5eed8d2d5cac5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Mon, 8 Jun 2026 17:10:01 +0200 Subject: [PATCH 19/49] fixed errors from merge --- src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs | 367 +++++++++--------- 1 file changed, 174 insertions(+), 193 deletions(-) diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs index 41364da18b..cf7c0e9c97 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs @@ -85,11 +85,6 @@ public int Count internal class PdfLayout { - // [PERF] Temporary instrumentation — remove before PR - internal static long PerfHeadingCellCalls = 0; - internal static long PerfHeadingShapeTicks = 0; - internal static long PerfHeadingTotalTicks = 0; - internal static readonly System.Collections.Generic.HashSet PerfUniqueHeadingTexts = new System.Collections.Generic.HashSet(); private const double rowHeadingWith1CharWidth = 23.25d; public static Transform GetLayout(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfWorksheet[] pdfSheets) @@ -131,211 +126,206 @@ internal static Transform GetCatalog(PdfPageSettings pageSettings, PdfDictionari //pageLayout.AddChild(contentLayout); double contentStartX = pageSettings.ContentBounds.Left + page.HeadingWidth + page.PrintTitleWidth; double contentStartY = pageSettings.ContentBounds.Top - page.HeadingHeight - page.PrintTitleHeight; - // [PERF] page geometry - int perfRowCount = page.ToRow - page.FromRow + 1; - int perfColCount = page.ToColumn - page.FromColumn + 1; - long headingTicks = 0; if (pageSettings.ShowHeadings && !pdfPages[i].IsCommentsPage) { AddHeadingCells(pageSettings, dictionaries, page, pageLayout, contentStartX, contentStartY, page.HeadingWidth, page.HeadingHeight, pdfPages[i].HeadingFontName, pdfPages[i].HeadingFontSize, pdfPages[i].HeadingFill); AddPrintTitleHeadings(pageSettings, dictionaries, page, pageLayout, pdfPages[i].HeadingFontName, pdfPages[i].HeadingFontSize, pdfPages[i].HeadingFill); } AddPrintTitleCells(pageSettings, dictionaries, page, pageLayout); - } - double y = contentStartY;//pageSettings.ContentBounds.Top; - double x = contentStartX;//pageSettings.ContentBounds.Left; - //create cells & headings if exsists - for (int row = pages[j].FromRow; row <= pages[j].ToRow; row++) - { - double rowHeight = pages[j].RowHeights[row - pages[j].FromRow]; - for (int col = pages[j].FromColumn; col <= pages[j].ToColumn; col++) + double y = contentStartY;//pageSettings.ContentBounds.Top; + double x = contentStartX;//pageSettings.ContentBounds.Left; + //create cells & headings if exsists + + for (int row = pages[j].FromRow; row <= pages[j].ToRow; row++) { - var map = pages[j].Map[row, col]; - MergedCellDrawInfo info = new MergedCellDrawInfo(); - //Merged Cell - if (map.Merged) + double rowHeight = pages[j].RowHeights[row - pages[j].FromRow]; + for (int col = pages[j].FromColumn; col <= pages[j].ToColumn; col++) { - string key = map.MergedAddress.Address; - if (!drawnMergedCells.Contains(key) && - pages[j].MergedCells.TryGetValue(key, out info)) + var map = pages[j].Map[row, col]; + MergedCellDrawInfo info = new MergedCellDrawInfo(); + //Merged Cell + if (map.Merged) + { + string key = map.MergedAddress.Address; + if (!drawnMergedCells.Contains(key) && + pages[j].MergedCells.TryGetValue(key, out info)) + { + //Fill + var cellStyle = map.Main?.CellStyle ?? map.CellStyle; + var fill = new PdfCellLayout(dictionaries, cellStyle, + info.X, info.Y, info.Width, info.Height); + fill.Name = map.Name; + fill.UpdateShadingPositionMatrix(pageSettings); + pageLayout.AddChild(fill); + //Text + var sourceMap = (map.TextLines != null && map.TextLines.Count > 0) ? map : (map.Main != null && map.Main.TextLines != null && map.Main.TextLines.Count > 0) ? map.Main : null; + if (sourceMap != null) + { + var text = new PdfCellContentLayout(pageSettings, dictionaries, sourceMap, info, info.X, info.Y, info.Width, info.Height); + text.Name = map.Name; + text.GidsAndCharMap(dictionaries); + text.SetupClipping(info.X, info.Y, info.Width, info.Height); + pageLayout.AddChild(text); + } + if (map.Main != null) // map.Main != null → this is NOT the top-left cell + { + var mergeMainStyle = map.Main.CellStyle; + if (HasDiagonalBorder(mergeMainStyle)) + { + var diagBorder = new PdfCellBorderLayout( + mergeMainStyle, + isMerged: false, // use X/Y/W/H path in renderer, not info.* + corners: MergedCellCorners.All, + info: info, + x: info.X, // virtual full-merge top-left X + y: info.Y, // virtual full-merge top Y + width: info.Width, // full merge width + height: info.Height); // full merge height + diagBorder.Name = map.Name; + // Suppress edge borders — this layout exists only for the diagonal + diagBorder.BorderData.Top.BorderStyle = ExcelBorderStyle.None; + diagBorder.BorderData.Bottom.BorderStyle = ExcelBorderStyle.None; + diagBorder.BorderData.Left.BorderStyle = ExcelBorderStyle.None; + diagBorder.BorderData.Right.BorderStyle = ExcelBorderStyle.None; + pageLayout.AddChild(diagBorder); + } + } + drawnMergedCells.Add(key); + } + } + else { //Fill - var cellStyle = map.Main?.CellStyle ?? map.CellStyle; - var fill = new PdfCellLayout(dictionaries, cellStyle, - info.X, info.Y, info.Width, info.Height); - fill.Name = map.Name; + var fill = new PdfCellLayout(dictionaries, map.CellStyle, x, y, map.ColumnWidth, rowHeight); fill.UpdateShadingPositionMatrix(pageSettings); + fill.Name = map.Name; pageLayout.AddChild(fill); //Text - var sourceMap = (map.TextLines != null && map.TextLines.Count > 0) ? map : (map.Main != null && map.Main.TextLines != null && map.Main.TextLines.Count > 0) ? map.Main : null; - if (sourceMap != null) + if (map.TextLines != null && map.TextLines.Count > 0) { - var text = new PdfCellContentLayout(pageSettings, dictionaries, sourceMap, info, info.X, info.Y, info.Width, info.Height); + var text = new PdfCellContentLayout(pageSettings, dictionaries, map, info, x, y, map.ColumnWidth, rowHeight); text.Name = map.Name; text.GidsAndCharMap(dictionaries); - text.SetupClipping(info.X, info.Y, info.Width, info.Height); + if (NeedsClipping(map, pages[j], row, col)) + text.SetupClipping(x, y, map.ColumnWidth, rowHeight); pageLayout.AddChild(text); } - if (map.Main != null) // map.Main != null → this is NOT the top-left cell - { - var mergeMainStyle = map.Main.CellStyle; - if (HasDiagonalBorder(mergeMainStyle)) - { - var diagBorder = new PdfCellBorderLayout( - mergeMainStyle, - isMerged: false, // use X/Y/W/H path in renderer, not info.* - corners: MergedCellCorners.All, - info: info, - x: info.X, // virtual full-merge top-left X - y: info.Y, // virtual full-merge top Y - width: info.Width, // full merge width - height: info.Height); // full merge height - diagBorder.Name = map.Name; - // Suppress edge borders — this layout exists only for the diagonal - diagBorder.BorderData.Top.BorderStyle = ExcelBorderStyle.None; - diagBorder.BorderData.Bottom.BorderStyle = ExcelBorderStyle.None; - diagBorder.BorderData.Left.BorderStyle = ExcelBorderStyle.None; - diagBorder.BorderData.Right.BorderStyle = ExcelBorderStyle.None; - pageLayout.AddChild(diagBorder); - } - } - drawnMergedCells.Add(key); } - } - else - { - //Fill - var fill = new PdfCellLayout(dictionaries, map.CellStyle, x, y, map.ColumnWidth, rowHeight); - fill.UpdateShadingPositionMatrix(pageSettings); - fill.Name = map.Name; - pageLayout.AddChild(fill); - //Text - if (map.TextLines != null && map.TextLines.Count > 0) + //Border + var borderStyle = (map.Merged && map.Main != null) ? map.Main.CellStyle : map.CellStyle; + if (HasBorder(map.CellStyle)) { - var text = new PdfCellContentLayout(pageSettings, dictionaries, map, info, x, y, map.ColumnWidth, rowHeight); - text.Name = map.Name; - text.GidsAndCharMap(dictionaries); - if (NeedsClipping(map, pages[j], row, col)) - text.SetupClipping(x, y, map.ColumnWidth, rowHeight); - pageLayout.AddChild(text); + var border = new PdfCellBorderLayout(map.CellStyle, map.Merged, GetCorners(map.MergedAddress, row, col), info, x, y, map.ColumnWidth, rowHeight); + border.Name = map.Name; + pageLayout.AddChild(border); } + x += map.ColumnWidth; + } + y -= rowHeight; + x = contentStartX; //pageSettings.ContentBounds.Left; + } + if (page.HeaderFooters != null) + { + bool isVeryFirstPage = (i == 0 && j == 0); + var hfType = isVeryFirstPage ? HeaderFooterType.First : (pageNumber % 2 == 0 ? HeaderFooterType.Even : HeaderFooterType.Odd); + var leftH = page.HeaderFooters.Get(hfType, HeaderFooterSection.Header, HeaderFooterAlignment.Left); + if (leftH != null) + { + SubstitutePageNumbers(pageSettings, dictionaries, leftH, pageNumber, totalPages); + var ascent = leftH.Content.TextLines[0].LargestAscent; + var hfx = pageSettings.Margins.LeftPu; + var hfy = pageSettings.PageSize.HeightPu - pageSettings.Margins.HeaderPu - ascent; + var text = new PdfCellContentLayout(pageSettings, dictionaries, leftH, hfx, hfy, 0, 0); + text.Name = "LeftHeader"; + text.IsHeaderFooter = true; + text.GidsAndCharMap(dictionaries); + pageLayout.AddChild(text); + } + var centerH = page.HeaderFooters.Get(hfType, HeaderFooterSection.Header, HeaderFooterAlignment.Center); + if (centerH != null) + { + SubstitutePageNumbers(pageSettings, dictionaries, centerH, pageNumber, totalPages); + var ascent = centerH.Content.TextLines[0].LargestAscent; + var hfx = pageSettings.Margins.LeftPu; + var hfy = pageSettings.PageSize.HeightPu - pageSettings.Margins.HeaderPu - ascent; + var hfWidth = pageSettings.PageSize.WidthPu - pageSettings.Margins.LeftPu - pageSettings.Margins.RightPu; + var text = new PdfCellContentLayout(pageSettings, dictionaries, centerH, hfx, hfy, hfWidth, 0); + text.Name = "CenterHeader"; + text.IsHeaderFooter = true; + text.GidsAndCharMap(dictionaries); + pageLayout.AddChild(text); + } + var rightH = page.HeaderFooters.Get(hfType, HeaderFooterSection.Header, HeaderFooterAlignment.Right); + if (rightH != null) + { + SubstitutePageNumbers(pageSettings, dictionaries, rightH, pageNumber, totalPages); + var ascent = rightH.Content.TextLines[0].LargestAscent; + var hfx = pageSettings.PageSize.WidthPu - pageSettings.Margins.RightPu; + var hfy = pageSettings.PageSize.HeightPu - pageSettings.Margins.HeaderPu - ascent; + var text = new PdfCellContentLayout(pageSettings, dictionaries, rightH, hfx, hfy, 0, 0); + text.Name = "RightHeader"; + text.IsHeaderFooter = true; + text.GidsAndCharMap(dictionaries); + pageLayout.AddChild(text); + } + var leftF = page.HeaderFooters.Get(hfType, HeaderFooterSection.Footer, HeaderFooterAlignment.Left); + if (leftF != null) + { + SubstitutePageNumbers(pageSettings, dictionaries, leftF, pageNumber, totalPages); + int last = leftF.Content.TextLines.Count - 1; + var descent = leftF.Content.TextLines[last].LargestDescent; + var hfx = pageSettings.Margins.LeftPu; + var hfy = pageSettings.Margins.FooterPu + descent; + var text = new PdfCellContentLayout(pageSettings, dictionaries, leftF, hfx, hfy, 0, 0); + text.Name = "LeftFooter"; + text.IsHeaderFooter = true; + text.GidsAndCharMap(dictionaries); + pageLayout.AddChild(text); } - //Border - var borderStyle = (map.Merged && map.Main != null) ? map.Main.CellStyle : map.CellStyle; - if (HasBorder(map.CellStyle)) + var centerF = page.HeaderFooters.Get(hfType, HeaderFooterSection.Footer, HeaderFooterAlignment.Center); + if (centerF != null) { - var border = new PdfCellBorderLayout(map.CellStyle, map.Merged, GetCorners(map.MergedAddress, row, col), info, x, y, map.ColumnWidth, rowHeight); - border.Name = map.Name; - pageLayout.AddChild(border); + SubstitutePageNumbers(pageSettings, dictionaries, centerF, pageNumber, totalPages); + int last = centerF.Content.TextLines.Count - 1; + var descent = centerF.Content.TextLines[last].LargestDescent; + var hfx = pageSettings.PageSize.WidthPu / 2d; + var hfy = pageSettings.Margins.FooterPu + descent; + var text = new PdfCellContentLayout(pageSettings, dictionaries, centerF, hfx, hfy, 0, 0); + text.Name = "CenterFooter"; + text.IsHeaderFooter = true; + text.GidsAndCharMap(dictionaries); + pageLayout.AddChild(text); + } + var rightF = page.HeaderFooters.Get(hfType, HeaderFooterSection.Footer, HeaderFooterAlignment.Right); + if (rightF != null) + { + SubstitutePageNumbers(pageSettings, dictionaries, rightF, pageNumber, totalPages); + int last = rightF.Content.TextLines.Count - 1; + var descent = rightF.Content.TextLines[last].LargestDescent; + var hfx = pageSettings.PageSize.WidthPu - pageSettings.Margins.RightPu; + var hfy = pageSettings.Margins.FooterPu + descent; + var text = new PdfCellContentLayout(pageSettings, dictionaries, rightF, hfx, hfy, 0, 0); + text.Name = "RightFooter"; + text.IsHeaderFooter = true; + text.GidsAndCharMap(dictionaries); + pageLayout.AddChild(text); } - x += map.ColumnWidth; } - y -= rowHeight; - x = contentStartX; //pageSettings.ContentBounds.Left; - } - if (page.HeaderFooters != null) - { - bool isVeryFirstPage = (i == 0 && j == 0); - var hfType = isVeryFirstPage ? HeaderFooterType.First : (pageNumber % 2 == 0 ? HeaderFooterType.Even : HeaderFooterType.Odd); - var leftH = page.HeaderFooters.Get(hfType, HeaderFooterSection.Header, HeaderFooterAlignment.Left); - if (leftH != null) - { - SubstitutePageNumbers(pageSettings, dictionaries, leftH, pageNumber, totalPages); - var ascent = leftH.Content.TextLines[0].LargestAscent; - var hfx = pageSettings.Margins.LeftPu; - var hfy = pageSettings.PageSize.HeightPu - pageSettings.Margins.HeaderPu - ascent; - var text = new PdfCellContentLayout(pageSettings, dictionaries, leftH, hfx, hfy, 0, 0); - text.Name = "LeftHeader"; - text.IsHeaderFooter = true; - text.GidsAndCharMap(dictionaries); - pageLayout.AddChild(text); - } - var centerH = page.HeaderFooters.Get(hfType, HeaderFooterSection.Header, HeaderFooterAlignment.Center); - if (centerH != null) - { - SubstitutePageNumbers(pageSettings, dictionaries, centerH, pageNumber, totalPages); - var ascent = centerH.Content.TextLines[0].LargestAscent; - var hfx = pageSettings.Margins.LeftPu; - var hfy = pageSettings.PageSize.HeightPu - pageSettings.Margins.HeaderPu - ascent; - var hfWidth = pageSettings.PageSize.WidthPu - pageSettings.Margins.LeftPu - pageSettings.Margins.RightPu; - var text = new PdfCellContentLayout(pageSettings, dictionaries, centerH, hfx, hfy, hfWidth, 0); - text.Name = "CenterHeader"; - text.IsHeaderFooter = true; - text.GidsAndCharMap(dictionaries); - pageLayout.AddChild(text); - } - var rightH = page.HeaderFooters.Get(hfType, HeaderFooterSection.Header, HeaderFooterAlignment.Right); - if (rightH != null) - { - SubstitutePageNumbers(pageSettings, dictionaries, rightH, pageNumber, totalPages); - var ascent = rightH.Content.TextLines[0].LargestAscent; - var hfx = pageSettings.PageSize.WidthPu - pageSettings.Margins.RightPu; - var hfy = pageSettings.PageSize.HeightPu - pageSettings.Margins.HeaderPu - ascent; - var text = new PdfCellContentLayout(pageSettings, dictionaries, rightH, hfx, hfy, 0, 0); - text.Name = "RightHeader"; - text.IsHeaderFooter = true; - text.GidsAndCharMap(dictionaries); - pageLayout.AddChild(text); - } - var leftF = page.HeaderFooters.Get(hfType, HeaderFooterSection.Footer, HeaderFooterAlignment.Left); - if (leftF != null) - { - SubstitutePageNumbers(pageSettings, dictionaries, leftF, pageNumber, totalPages); - int last = leftF.Content.TextLines.Count - 1; - var descent = leftF.Content.TextLines[last].LargestDescent; - var hfx = pageSettings.Margins.LeftPu; - var hfy = pageSettings.Margins.FooterPu + descent; - var text = new PdfCellContentLayout(pageSettings, dictionaries, leftF, hfx, hfy, 0, 0); - text.Name = "LeftFooter"; - text.IsHeaderFooter = true; - text.GidsAndCharMap(dictionaries); - pageLayout.AddChild(text); - } - var centerF = page.HeaderFooters.Get(hfType, HeaderFooterSection.Footer, HeaderFooterAlignment.Center); - if (centerF != null) - { - SubstitutePageNumbers(pageSettings, dictionaries, centerF, pageNumber, totalPages); - int last = centerF.Content.TextLines.Count - 1; - var descent = centerF.Content.TextLines[last].LargestDescent; - var hfx = pageSettings.PageSize.WidthPu / 2d; - var hfy = pageSettings.Margins.FooterPu + descent; - var text = new PdfCellContentLayout(pageSettings, dictionaries, centerF, hfx, hfy, 0, 0); - text.Name = "CenterFooter"; - text.IsHeaderFooter = true; - text.GidsAndCharMap(dictionaries); - pageLayout.AddChild(text); - } - var rightF = page.HeaderFooters.Get(hfType, HeaderFooterSection.Footer, HeaderFooterAlignment.Right); - if (rightF != null) + PdfGridlinesLayout.AddGridLines(pageSettings, pages[j], pageLayout, borderOnly: !pageSettings.ShowGridLines || pdfPages[i].IsCommentsPage); + + pageLayout.ChildObjects.Sort((a, b) => { - SubstitutePageNumbers(pageSettings, dictionaries, rightF, pageNumber, totalPages); - int last = rightF.Content.TextLines.Count - 1; - var descent = rightF.Content.TextLines[last].LargestDescent; - var hfx = pageSettings.PageSize.WidthPu - pageSettings.Margins.RightPu; - var hfy = pageSettings.Margins.FooterPu + descent; - var text = new PdfCellContentLayout(pageSettings, dictionaries, rightF, hfx, hfy, 0, 0); - text.Name = "RightFooter"; - text.IsHeaderFooter = true; - text.GidsAndCharMap(dictionaries); - pageLayout.AddChild(text); - } + int cmp = a.Z.CompareTo(b.Z); + if (cmp == 0) + return string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase); + return cmp; + }); + pageNumber++; + Catalog.AddChild(pageLayout); } - - PdfGridlinesLayout.AddGridLines(pageSettings, pages[j], pageLayout, borderOnly: !pageSettings.ShowGridLines || pdfPages[i].IsCommentsPage); - - pageLayout.ChildObjects.Sort((a, b) => - { - int cmp = a.Z.CompareTo(b.Z); - if (cmp == 0) - return string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase); - return cmp; - }); - pageNumber++; - Catalog.AddChild(pageLayout); } - } return Catalog; } @@ -604,16 +594,7 @@ private static PdfCell CreateHeadingPdfCell(PdfPageSettings pageSettings, PdfDic tf.RichTextOptions.UnderlineType = 12; // none tf.RichTextOptions.StrikeType = 1; // none cell.TextFragments = new List { tf }; -<<<<<<< HEAD PdfTextShaper.ShapeText(pageSettings, dictionaries, cell); -======= - // [PERF] - PerfHeadingCellCalls++; - PerfUniqueHeadingTexts.Add(text); - long perfT0 = System.Diagnostics.Stopwatch.GetTimestamp(); - PdfTextShaper.ShapeText(pageSettings, dictionaries, cell); - PerfHeadingShapeTicks += System.Diagnostics.Stopwatch.GetTimestamp() - perfT0; ->>>>>>> 58c0e0fc23d16f7675746fbfd19e87df21421fcf } return cell; } @@ -1225,10 +1206,10 @@ internal static Pages AssignRangeToPages(PdfPageSettings pageSettings, PdfRange } private struct PageSegment - { - public int From; - public int To; - public PageSegment(int from, int to) { From = from; To = to; } + { + public int From; + public int To; + public PageSegment(int from, int to) { From = from; To = to; } } private static List GetColumnSegments(PdfPageSettings pageSettings, PdfRange range, ExcelWorksheet worksheet, double addedWidth, double titleWidth, int printTitleColTo) From 066048929d04f50ed2a4189b8da0ea8c2aabbe85 Mon Sep 17 00:00:00 2001 From: swmal <897655+swmal@users.noreply.github.com> Date: Wed, 10 Jun 2026 13:47:28 +0200 Subject: [PATCH 20/49] Unittest for pdf added --- src/EPPlus.Export.Pdf.Tests/PdfTests.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs index 4fb9b83282..29b9fc3162 100644 --- a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs +++ b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs @@ -211,17 +211,19 @@ public void PerfTest() [TestMethod] // works as expected. - [DataRow("PDFTest.xlsx", "C:\\epplustest\\pdf\\FullPageTest56.pdf", 0)] + //[DataRow("PDFTest.xlsx", "C:\\epplustest\\pdf\\FullPageTest56.pdf", "Sheet1")] - [DataRow("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx", "C:\\epplustest\\pdf\\OutputTest1.1.pdf", 0)] - // 1. Minus signs alignment in cells differs from Excel. - // 2. Dimension seems to differ from Excel, Excel stops at row 75, EPPlus goes to row 89. - // 3. Row headings are sligthly wider in EPPlus than in Excel. - [DataRow("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx", "C:\\epplustest\\pdf\\OutputTest1.2.pdf", 1)] - public void WorkbookTests(string sourceFile, string outputPath, int wsIx) + //[DataRow("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx", "C:\\epplustest\\pdf\\OutputTest1.1.pdf", "SAP Data")] + //// 1. Minus signs alignment in cells differs from Excel. + //// 2. Dimension seems to differ from Excel, Excel stops at row 75, EPPlus goes to row 89. + //// 3. Row headings are sligthly wider in EPPlus than in Excel. + //[DataRow("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx", "C:\\epplustest\\pdf\\OutputTest1.2.pdf", "Summary")] + //[DataRow("Aico WiP 120180 FBL3N for 0110 in 2025-04.xlsx", "C:\\epplustest\\pdf\\OutputTest1.4.pdf", "Technical")] + [DataRow("Aico KKS1 Variance Calculation for 0105 in 2025-04 (25_4_2025 15_43_40) .xlsx", "C:\\epplustest\\pdf\\OutputTest1.5.pdf", "Technical")] + public void WorkbookTests(string sourceFile, string outputPath, string wsName) { using var p = OpenTemplatePackage(sourceFile); - var ws = p.Workbook.Worksheets[wsIx]; + var ws = p.Workbook.Worksheets[wsName]; PdfPageSettings pageSettings = new PdfPageSettings(); pageSettings.CommentsAndNotes = CommentsAndNotes.AtEndOfSheet; From 92483265fdd9ad02ab34e44be81149d02ef8724c Mon Sep 17 00:00:00 2001 From: swmal <897655+swmal@users.noreply.github.com> Date: Wed, 10 Jun 2026 14:34:48 +0200 Subject: [PATCH 21/49] Added more unittests with improvment areas --- src/EPPlus.Export.Pdf.Tests/PdfTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs index 29b9fc3162..35c48ab99f 100644 --- a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs +++ b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs @@ -219,7 +219,8 @@ public void PerfTest() //// 3. Row headings are sligthly wider in EPPlus than in Excel. //[DataRow("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx", "C:\\epplustest\\pdf\\OutputTest1.2.pdf", "Summary")] //[DataRow("Aico WiP 120180 FBL3N for 0110 in 2025-04.xlsx", "C:\\epplustest\\pdf\\OutputTest1.4.pdf", "Technical")] - [DataRow("Aico KKS1 Variance Calculation for 0105 in 2025-04 (25_4_2025 15_43_40) .xlsx", "C:\\epplustest\\pdf\\OutputTest1.5.pdf", "Technical")] + //[DataRow("Aico KKS1 Variance Calculation for 0105 in 2025-04 (25_4_2025 15_43_40) .xlsx", "C:\\epplustest\\pdf\\OutputTest1.5.pdf", "Technical")] + [DataRow("R05.xlsx", "C:\\epplustest\\pdf\\OutputTest1.6.pdf", "R05 Arbeitseinteilung")] public void WorkbookTests(string sourceFile, string outputPath, string wsName) { using var p = OpenTemplatePackage(sourceFile); From 6be95c8f777fd651949160dda4e800fee4673ed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Wed, 10 Jun 2026 14:38:22 +0200 Subject: [PATCH 22/49] print titles fixes progress --- src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs | 223 +++++++++++++++++- 1 file changed, 219 insertions(+), 4 deletions(-) diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs index cf7c0e9c97..d8f174cbdd 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs @@ -29,6 +29,10 @@ internal struct PrintTitleCellDraw public double Y; public double Width; public double Height; + public double ClipX; // text clip (spill bound) + public double ClipY; + public double ClipWidth; + public double ClipHeight; } internal struct PrintTitleHeadingDraw @@ -41,6 +45,14 @@ internal struct PrintTitleHeadingDraw public double Height; } + internal struct SpillCellDraw + { + public PdfCell Cell; + public double X, Y, Width, Height; // source cell's true position (off-window) + public double ClipX, ClipY, ClipWidth, ClipHeight; // visible slice on this page + public bool IsPrintTitle; + } + internal struct Page { public int FromRow; @@ -60,6 +72,7 @@ internal struct Page public List PrintTitleCells; public List PrintTitleGridLines; public List PrintTitleHeadings; + public List SpillCells; public double[] RowHeights; @@ -130,6 +143,7 @@ internal static Transform GetCatalog(PdfPageSettings pageSettings, PdfDictionari { AddHeadingCells(pageSettings, dictionaries, page, pageLayout, contentStartX, contentStartY, page.HeadingWidth, page.HeadingHeight, pdfPages[i].HeadingFontName, pdfPages[i].HeadingFontSize, pdfPages[i].HeadingFill); AddPrintTitleHeadings(pageSettings, dictionaries, page, pageLayout, pdfPages[i].HeadingFontName, pdfPages[i].HeadingFontSize, pdfPages[i].HeadingFill); + AddSpillCells(pageSettings, dictionaries, page, pageLayout); } AddPrintTitleCells(pageSettings, dictionaries, page, pageLayout); @@ -599,6 +613,24 @@ private static PdfCell CreateHeadingPdfCell(PdfPageSettings pageSettings, PdfDic return cell; } + private static void AddSpillCells(PdfPageSettings pageSettings, PdfDictionaries dictionaries, Page page, PdfPageLayout pageLayout) + { + if (page.SpillCells == null) return; + foreach (var s in page.SpillCells) + { + var map = s.Cell; + if (map.TextLines == null || map.TextLines.Count == 0) continue; + if (s.ClipWidth <= 0d || s.ClipHeight <= 0d) continue; + + var text = new PdfCellContentLayout(pageSettings, dictionaries, map, new MergedCellDrawInfo(), s.X, s.Y, s.Width, s.Height); + text.Name = "Spill_" + map.Name; + text.IsPrintTitle = s.IsPrintTitle; // false → clipped content group; true → outside-clip (band) + text.GidsAndCharMap(dictionaries); + text.SetupClipping(s.ClipX, s.ClipY, s.ClipWidth, s.ClipHeight); + pageLayout.AddChild(text); + } + } + private static void AddPrintTitleCells(PdfPageSettings pageSettings, PdfDictionaries dictionaries, Page page, PdfPageLayout pageLayout) { if (page.PrintTitleCells == null) return; @@ -620,7 +652,7 @@ private static void AddPrintTitleCells(PdfPageSettings pageSettings, PdfDictiona text.Name = map.Name; text.IsPrintTitle = true; text.GidsAndCharMap(dictionaries); - text.SetupClipping(t.X, t.Y, t.Width, t.Height); + text.SetupClipping(t.ClipX, t.ClipY, t.ClipWidth, t.ClipHeight); pageLayout.AddChild(text); } @@ -669,6 +701,7 @@ internal static List GetPages(PdfPageSettings pageSettings, PdfWorksheet[ pages = MapPage(range, pages); pages = GetHeaderFooter(range, pages, pdfSheet); pages = PrecomputeMergedCells(pageSettings, range, pages); + pages = PrecomputeSpillCells(pageSettings, range, pages); pages = PrecomputePrintTitleCells(pageSettings, pdfSheet, range, pages); pages.HeadingFontName = pdfSheet.NormalStyle.Style.Font.Name; pages.HeadingFontSize = pdfSheet.NormalStyle.Style.Font.Size; @@ -944,11 +977,43 @@ private static Page PrecomputePagePrintTitleCells(PdfPageSettings pageSettings, } } + // left band: a neighbour whose text spills INTO a title column travels with the repeated column + if (leftBand) + AddIncomingSpill(page, range, page.FromRow, page.ToRow, + pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, + pageSettings.ContentBounds.Left + page.HeadingWidth, // band origin X (left edge of the title columns) + pageSettings.ContentBounds.Top - page.HeadingHeight - page.PrintTitleHeight, // content-rows origin Y + isPrintTitle: true); + + // corner: same, for the title-rows × title-columns intersection + if (topBand && leftBand) + AddIncomingSpill(page, range, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, + pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, + pageSettings.ContentBounds.Left + page.HeadingWidth, // band origin X + pageSettings.ContentBounds.Top - page.HeadingHeight, // title-rows origin Y + isPrintTitle: true); + + // repeated title-row text continues onto the next horizontal page's band + if (topBand) + AddIncomingSpill(page, range, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, page.FromColumn, page.ToColumn, + pageSettings.ContentBounds.Left + page.HeadingWidth + page.PrintTitleWidth, + pageSettings.ContentBounds.Top - page.HeadingHeight, + isPrintTitle: true); + return page; } private static void ProcessBandRegionCells(Page page, PdfRange range, int fromRow, int toRow, int fromCol, int toCol, Dictionary colX, Dictionary rowY) { + // Band-region rectangle (top-left origin). Non-merged text may spill within this rect, + // which is bounded by the band edge, so it can never overflow into the content area. + double regLeft = colX[fromCol]; + double regRight = colX[toCol] + RangeColWidth(range, page.FromRow, toCol); + double regTop = rowY[fromRow]; + double regBottom = rowY[toRow] - RangeRowHeight(range, toRow); + double regW = regRight - regLeft; + double regH = regTop - regBottom; + var drawnMerges = new HashSet(); for (int r = fromRow; r <= toRow; r++) { @@ -959,7 +1024,7 @@ private static void ProcessBandRegionCells(Page page, PdfRange range, int fromRo if (cell.Merged) { - if (!drawnMerges.Add(cell.MergedAddress.Address)) continue; // render each merge once per region + if (!drawnMerges.Add(cell.MergedAddress.Address)) continue; var addr = cell.MergedAddress; var main = cell.Main ?? cell; @@ -975,14 +1040,39 @@ private static void ProcessBandRegionCells(Page page, PdfRange range, int fromRo double height = (rowY[vFromRow] - rowY[vToRow]) + RangeRowHeight(range, vToRow); if (width <= 0d || height <= 0d) continue; - page.PrintTitleCells.Add(new PrintTitleCellDraw { Cell = main, X = x, Y = y, Width = width, Height = height }); + // merged cells don't spill — clip text to the merge itself (matches content) + page.PrintTitleCells.Add(new PrintTitleCellDraw + { + Cell = main, + X = x, + Y = y, + Width = width, + Height = height, + ClipX = x, + ClipY = y, + ClipWidth = width, + ClipHeight = height + }); } else { double w = RangeColWidth(range, page.FromRow, c); double h = RangeRowHeight(range, r); if (w <= 0d || h <= 0d) continue; - page.PrintTitleCells.Add(new PrintTitleCellDraw { Cell = cell, X = colX[c], Y = rowY[r], Width = w, Height = h }); + + // non-merged: clip to the band region so text can spill within it but not into content + page.PrintTitleCells.Add(new PrintTitleCellDraw + { + Cell = cell, + X = colX[c], + Y = rowY[r], + Width = w, + Height = h, + ClipX = regLeft, + ClipY = regTop, + ClipWidth = regW, + ClipHeight = regH + }); } } } @@ -1346,5 +1436,130 @@ private static void SubstitutePageNumbers(PdfPageSettings pageSettings, PdfDicti PdfTextShaper.ShapeText(pageSettings, dictionaries, hf.Content); } + private static void AddIncomingSpill(Page page, PdfRange range, int fromRow, int toRow, int windowFromCol, int windowToCol, double windowOriginX, double windowOriginY, bool isPrintTitle) + { + if (page.SpillCells == null) return; // initialised by the caller + + double windowRightX = windowOriginX; + for (int c = windowFromCol; c <= windowToCol; c++) windowRightX += RangeColWidth(range, page.FromRow, c); + + double rowTop = windowOriginY; + for (int r = fromRow; r <= toRow; r++) + { + double rowH = RangeRowHeight(range, r); + double y = rowTop; + rowTop -= rowH; + if (rowH <= 0d) continue; + + // spill entering from the LEFT (left/general/center) + double lx = windowOriginX; + for (int c = windowFromCol - 1; c >= range.Range._fromCol; c--) + { + double w = RangeColWidth(range, page.FromRow, c); + lx -= w; + var cell = RangeCell(range, r, c); + if (cell == null) continue; + if (cell.Merged) break; // merges don't spill, and block + if (string.IsNullOrEmpty(cell.Text)) continue; + var hal = cell.ContentAligmnet?.HorizontalAlignment ?? ExcelHorizontalAlignment.General; + double rightExtent = + (hal == ExcelHorizontalAlignment.Center) ? lx + w / 2d + cell.TotalTextLength / 2d : + (hal == ExcelHorizontalAlignment.Right) ? lx + w : + lx + cell.TotalTextLength; + if (rightExtent > windowOriginX) + { + double clipRight = FirstBlockedX(page, range, r, windowFromCol, windowToCol, windowOriginX, windowRightX, fromLeft: true); + page.SpillCells.Add(new SpillCellDraw + { + Cell = cell, + X = lx, + Y = y, + Width = w, + Height = rowH, + ClipX = windowOriginX, + ClipY = y, + ClipWidth = clipRight - windowOriginX, + ClipHeight = rowH, + IsPrintTitle = isPrintTitle + }); + } + break; + } + + // spill entering from the RIGHT (right/center) + double rx = windowRightX; + for (int c = windowToCol + 1; c <= range.Range._toCol; c++) + { + double w = RangeColWidth(range, page.FromRow, c); + var cell = RangeCell(range, r, c); + if (cell == null) { rx += w; continue; } + if (cell.Merged) break; + if (string.IsNullOrEmpty(cell.Text)) { rx += w; continue; } + var hal = cell.ContentAligmnet?.HorizontalAlignment ?? ExcelHorizontalAlignment.General; + double leftExtent = + (hal == ExcelHorizontalAlignment.Center) ? rx + w / 2d - cell.TotalTextLength / 2d : + (hal == ExcelHorizontalAlignment.Right) ? rx + w - cell.TotalTextLength : + rx; + if (leftExtent < windowRightX) + { + double clipLeft = FirstBlockedX(page, range, r, windowFromCol, windowToCol, windowOriginX, windowRightX, fromLeft: false); + page.SpillCells.Add(new SpillCellDraw + { + Cell = cell, + X = rx, + Y = y, + Width = w, + Height = rowH, + ClipX = clipLeft, + ClipY = y, + ClipWidth = windowRightX - clipLeft, + ClipHeight = rowH, + IsPrintTitle = isPrintTitle + }); + } + break; + } + } + } + + // Where spill into the window is cut off by the first non-empty (or merged) cell inside it. + private static double FirstBlockedX(Page page, PdfRange range, int row, int windowFromCol, int windowToCol, double windowOriginX, double windowRightX, bool fromLeft) + { + if (fromLeft) + { + double x = windowOriginX; + for (int c = windowFromCol; c <= windowToCol; c++) + { + var cell = RangeCell(range, row, c); + if (cell != null && (cell.Merged || !string.IsNullOrEmpty(cell.Text))) return x; + x += RangeColWidth(range, page.FromRow, c); + } + return windowRightX; + } + double rx = windowRightX; + for (int c = windowToCol; c >= windowFromCol; c--) + { + double w = RangeColWidth(range, page.FromRow, c); + rx -= w; + var cell = RangeCell(range, row, c); + if (cell != null && (cell.Merged || !string.IsNullOrEmpty(cell.Text))) return rx + w; + } + return windowOriginX; + } + + internal static Pages PrecomputeSpillCells(PdfPageSettings pageSettings, PdfRange range, Pages pdfPages) + { + for (int i = 0; i < pdfPages.Page.Length; i++) + { + var page = pdfPages.Page[i]; + page.SpillCells = new List(); + double originX = pageSettings.ContentBounds.Left + page.HeadingWidth + page.PrintTitleWidth; + double originY = pageSettings.ContentBounds.Top - page.HeadingHeight - page.PrintTitleHeight; + AddIncomingSpill(page, range, page.FromRow, page.ToRow, page.FromColumn, page.ToColumn, originX, originY, isPrintTitle: false); + pdfPages.Page[i] = page; + } + return pdfPages; + } + } } From 3fcca9ae0a95b123fa783130a62dff24c070e44b Mon Sep 17 00:00:00 2001 From: swmal <897655+swmal@users.noreply.github.com> Date: Wed, 10 Jun 2026 14:40:02 +0200 Subject: [PATCH 23/49] One more commit --- src/EPPlus.Export.Pdf.Tests/PdfTests.cs | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs index 35c48ab99f..8ff7127e8a 100644 --- a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs +++ b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs @@ -211,15 +211,22 @@ public void PerfTest() [TestMethod] // works as expected. - //[DataRow("PDFTest.xlsx", "C:\\epplustest\\pdf\\FullPageTest56.pdf", "Sheet1")] - - //[DataRow("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx", "C:\\epplustest\\pdf\\OutputTest1.1.pdf", "SAP Data")] - //// 1. Minus signs alignment in cells differs from Excel. - //// 2. Dimension seems to differ from Excel, Excel stops at row 75, EPPlus goes to row 89. - //// 3. Row headings are sligthly wider in EPPlus than in Excel. - //[DataRow("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx", "C:\\epplustest\\pdf\\OutputTest1.2.pdf", "Summary")] - //[DataRow("Aico WiP 120180 FBL3N for 0110 in 2025-04.xlsx", "C:\\epplustest\\pdf\\OutputTest1.4.pdf", "Technical")] - //[DataRow("Aico KKS1 Variance Calculation for 0105 in 2025-04 (25_4_2025 15_43_40) .xlsx", "C:\\epplustest\\pdf\\OutputTest1.5.pdf", "Technical")] + [DataRow("PDFTest.xlsx", "C:\\epplustest\\pdf\\FullPageTest56.pdf", "Sheet1")] + [DataRow("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx", "C:\\epplustest\\pdf\\OutputTest1.1.pdf", "SAP Data")] + + // Output file: OutputTest1.2.pdf + // 1. Minus signs alignment in cells differs from Excel. + // 2. Dimension seems to differ from Excel, Excel stops at row 75, EPPlus goes to row 89. + // 3. Row headings are sligthly wider in EPPlus than in Excel. + [DataRow("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx", "C:\\epplustest\\pdf\\OutputTest1.2.pdf", "Summary")] + // works as expected + [DataRow("Aico WiP 120180 FBL3N for 0110 in 2025-04.xlsx", "C:\\epplustest\\pdf\\OutputTest1.4.pdf", "Technical")] + [DataRow("Aico KKS1 Variance Calculation for 0105 in 2025-04 (25_4_2025 15_43_40) .xlsx", "C:\\epplustest\\pdf\\OutputTest1.5.pdf", "Technical")] + + // Output file: OutputTest1.6.pdf + // 1. Merged cells not working + // 2. Pattern fills looks differnt, in some cases not working + // 3. Rotation of text in cells not working (the dates). [DataRow("R05.xlsx", "C:\\epplustest\\pdf\\OutputTest1.6.pdf", "R05 Arbeitseinteilung")] public void WorkbookTests(string sourceFile, string outputPath, string wsName) { From 52ca0f3c8390f0d9175a7fb9eea8f2d126cedb94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Wed, 10 Jun 2026 17:09:05 +0200 Subject: [PATCH 24/49] progress --- src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs | 130 ++++++++++++++++-- 1 file changed, 122 insertions(+), 8 deletions(-) diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs index d8f174cbdd..6122950878 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs @@ -977,6 +977,13 @@ private static Page PrecomputePagePrintTitleCells(PdfPageSettings pageSettings, } } + // repeated title-row text continues onto the next horizontal page's band + if (topBand) + AddIncomingSpill(page, range, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, page.FromColumn, page.ToColumn, + pageSettings.ContentBounds.Left + page.HeadingWidth + page.PrintTitleWidth, + pageSettings.ContentBounds.Top - page.HeadingHeight, + isPrintTitle: true); + // left band: a neighbour whose text spills INTO a title column travels with the repeated column if (leftBand) AddIncomingSpill(page, range, page.FromRow, page.ToRow, @@ -993,13 +1000,6 @@ private static Page PrecomputePagePrintTitleCells(PdfPageSettings pageSettings, pageSettings.ContentBounds.Top - page.HeadingHeight, // title-rows origin Y isPrintTitle: true); - // repeated title-row text continues onto the next horizontal page's band - if (topBand) - AddIncomingSpill(page, range, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, page.FromColumn, page.ToColumn, - pageSettings.ContentBounds.Left + page.HeadingWidth + page.PrintTitleWidth, - pageSettings.ContentBounds.Top - page.HeadingHeight, - isPrintTitle: true); - return page; } @@ -1084,6 +1084,7 @@ private static void EmitBandGrid(List target, PdfRange range, int from int nc = toCol - fromCol + 1; if (nr <= 0 || nc <= 0) return; + var spill = BuildBandSpillMask(range, fromRow, toRow, fromCol, toCol, colX); double top = rowY[0], bottom = rowY[nr]; double left = colX[0], right = colX[nc]; @@ -1102,9 +1103,16 @@ private static void EmitBandGrid(List target, PdfRange range, int from for (int ri = 0; ri < nr; ri++) { int r = fromRow + ri; - if (!SameMerge(range, r, leftCol, r, rightCol)) { if (runStart == null) runStart = rowY[ri]; runEnd = rowY[ri + 1]; } + bool block = SameMerge(range, r, leftCol, r, rightCol) || spill[ri, gi]; + if (!block) { if (runStart == null) runStart = rowY[ri]; runEnd = rowY[ri + 1]; } else if (runStart != null) { target.Add(new GridLine(x, runStart.Value, x, runEnd)); runStart = null; } } + //for (int ri = 0; ri < nr; ri++) + //{ + // int r = fromRow + ri; + // if (!SameMerge(range, r, leftCol, r, rightCol)) { if (runStart == null) runStart = rowY[ri]; runEnd = rowY[ri + 1]; } + // else if (runStart != null) { target.Add(new GridLine(x, runStart.Value, x, runEnd)); runStart = null; } + //} if (runStart != null) target.Add(new GridLine(x, runStart.Value, x, runEnd)); } @@ -1124,6 +1132,112 @@ private static void EmitBandGrid(List target, PdfRange range, int from } } + private static bool[,] BuildBandSpillMask(PdfRange range, int fromRow, int toRow, int fromCol, int toCol, double[] colX) + { + int nr = toRow - fromRow + 1; + int nc = toCol - fromCol + 1; + var blocked = new bool[Math.Max(nr, 1), Math.Max(nc, 1)]; // [ri, g], g in 1..nc-1 + if (nr <= 0 || nc <= 1) return blocked; + int repRow = fromRow; + + for (int ri = 0; ri < nr; ri++) + { + int row = fromRow + ri; + + // (a) cells spilling within the band region + for (int ci = 0; ci < nc; ci++) + { + var cell = RangeCell(range, row, fromCol + ci); + if (cell == null || cell.Merged) continue; + if (cell.ContentAligmnet != null && cell.ContentAligmnet.WrapText) continue; + if (string.IsNullOrEmpty(cell.Text)) continue; + double spill = cell.TotalTextLength - cell.ColumnWidth; + if (spill <= 0d) continue; + var hal = cell.ContentAligmnet?.HorizontalAlignment ?? ExcelHorizontalAlignment.General; + if (hal == ExcelHorizontalAlignment.Left || hal == ExcelHorizontalAlignment.General) + BandMarkRight(range, row, ci, nc, fromCol, colX, spill, ri, blocked); + else if (hal == ExcelHorizontalAlignment.Right) + BandMarkLeft(range, row, ci, fromCol, colX, spill, ri, blocked); + else if (hal == ExcelHorizontalAlignment.Center) + { + double half = spill / 2d; + BandMarkRight(range, row, ci, nc, fromCol, colX, half, ri, blocked); + BandMarkLeft(range, row, ci, fromCol, colX, half, ri, blocked); + } + } + + // (b) spill entering from the LEFT of the region (left/general/center → spilling right in) + double lx = colX[0]; + for (int c = fromCol - 1; c >= range.Range._fromCol; c--) + { + double w = RangeColWidth(range, repRow, c); + lx -= w; + var cell = RangeCell(range, row, c); + if (cell == null) continue; + if (cell.Merged) break; + if (string.IsNullOrEmpty(cell.Text)) continue; + var hal = cell.ContentAligmnet?.HorizontalAlignment ?? ExcelHorizontalAlignment.General; + if (hal == ExcelHorizontalAlignment.Right) break; // spills away from the region + double rightExtent = (hal == ExcelHorizontalAlignment.Center) + ? lx + w / 2d + cell.TotalTextLength / 2d + : lx + cell.TotalTextLength; + for (int g = 1; g <= nc - 1; g++) + { + var blk = RangeCell(range, row, fromCol + g - 1); + if (blk != null && !string.IsNullOrEmpty(blk.Text)) break; + if (colX[g] < rightExtent) blocked[ri, g] = true; else break; + } + break; + } + + // (c) spill entering from the RIGHT of the region (right/center → spilling left in) + double rx = colX[nc]; + for (int c = toCol + 1; c <= range.Range._toCol; c++) + { + double w = RangeColWidth(range, repRow, c); + var cell = RangeCell(range, row, c); + if (cell == null) { rx += w; continue; } + if (cell.Merged) break; + if (string.IsNullOrEmpty(cell.Text)) { rx += w; continue; } + var hal = cell.ContentAligmnet?.HorizontalAlignment ?? ExcelHorizontalAlignment.General; + if (hal == ExcelHorizontalAlignment.Left || hal == ExcelHorizontalAlignment.General) break; // spills away + double leftExtent = (hal == ExcelHorizontalAlignment.Center) + ? rx + w / 2d - cell.TotalTextLength / 2d + : rx + w - cell.TotalTextLength; + for (int g = nc - 1; g >= 1; g--) + { + var blk = RangeCell(range, row, fromCol + g); + if (blk != null && !string.IsNullOrEmpty(blk.Text)) break; + if (colX[g] > leftExtent) blocked[ri, g] = true; else break; + } + break; + } + } + return blocked; + } + + private static void BandMarkRight(PdfRange range, int row, int ci, int nc, int fromCol, double[] colX, double spill, int ri, bool[,] blocked) + { + for (int g = ci + 1; g <= nc - 1; g++) + { + var rightCell = RangeCell(range, row, fromCol + g); + if (rightCell != null && !string.IsNullOrEmpty(rightCell.Text)) break; + double distToGap = colX[g] - colX[ci + 1]; + if (spill > distToGap) blocked[ri, g] = true; else break; + } + } + + private static void BandMarkLeft(PdfRange range, int row, int ci, int fromCol, double[] colX, double spill, int ri, bool[,] blocked) + { + for (int g = ci; g >= 1; g--) + { + var leftCell = RangeCell(range, row, fromCol + g - 1); + if (leftCell != null && !string.IsNullOrEmpty(leftCell.Text)) break; + double distToGap = colX[ci] - colX[g]; + if (spill > distToGap) blocked[ri, g] = true; else break; + } + } + private static bool SameMerge(PdfRange range, int r1, int c1, int r2, int c2) { var a = RangeCell(range, r1, c1); From 1d22dc2f97572020fbbc46b226cf56ae525134cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Thu, 11 Jun 2026 11:09:44 +0200 Subject: [PATCH 25/49] fixed more issues with print titles --- src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs | 108 ++++++++++++++++-- 1 file changed, 100 insertions(+), 8 deletions(-) diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs index 6122950878..b37d9b13da 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs @@ -5,6 +5,7 @@ using EPPlus.Fonts.OpenType.Integration.DataHolders; using EPPlus.Graphics; using OfficeOpenXml; +using OfficeOpenXml.ConditionalFormatting; using OfficeOpenXml.Interfaces.Fonts; using OfficeOpenXml.Style; using System; @@ -73,6 +74,7 @@ internal struct Page public List PrintTitleGridLines; public List PrintTitleHeadings; public List SpillCells; + public List PrintTitleBorders; public double[] RowHeights; @@ -666,11 +668,21 @@ private static void AddPrintTitleCells(PdfPageSettings pageSettings, PdfDictiona // pageLayout.AddChild(text); //} - if (HasBorder(map.CellStyle)) + if (!map.Merged && HasBorder(map.CellStyle)) // was: if (HasBorder(map.CellStyle)) { var border = new PdfCellBorderLayout(map.CellStyle, false, MergedCellCorners.All, new MergedCellDrawInfo(), t.X, t.Y, t.Width, t.Height); - border.Name = map.Name; border.IsPrintTitle = true; + border.Name = "PrintTitleBorder_" + map.Name; + pageLayout.AddChild(border); + } + // Per-cell borders for merged band cells — outside the margin clip like the rest of the band. + foreach (var b in page.PrintTitleBorders) + { + var sub = b.Cell; + if (!HasBorder(sub.CellStyle)) continue; + var border = new PdfCellBorderLayout(sub.CellStyle, false, MergedCellCorners.All, new MergedCellDrawInfo(), b.X, b.Y, b.Width, b.Height); + border.IsPrintTitle = true; + border.Name = "PrintTitleMergeBorder_" + sub.Name; pageLayout.AddChild(border); } } @@ -828,6 +840,7 @@ internal static Pages PrecomputePrintTitleCells(PdfPageSettings pageSettings, Pd private static Page PrecomputePagePrintTitleCells(PdfPageSettings pageSettings, PdfWorksheet pdfSheet, PdfRange range, Page page) { page.PrintTitleCells = new List(); + page.PrintTitleBorders = new List(); bool topBand = page.PrintTitleHeight > 0d && pdfSheet.PrintTitleRowFrom >= 0 && page.FromRow > pdfSheet.PrintTitleRowTo; bool leftBand = page.PrintTitleWidth > 0d && pdfSheet.PrintTitleColFrom >= 0 && page.FromColumn > pdfSheet.PrintTitleColTo; @@ -1053,6 +1066,26 @@ private static void ProcessBandRegionCells(Page page, PdfRange range, int fromRo ClipWidth = width, ClipHeight = height }); + for (int br = vFromRow; br <= vToRow; br++) + { + double bh = RangeRowHeight(range, br); + if (bh <= 0d) continue; + for (int bc = vFromCol; bc <= vToCol; bc++) + { + double bw = RangeColWidth(range, page.FromRow, bc); + if (bw <= 0d) continue; + var sub = RangeCell(range, br, bc); + if (sub == null || !HasBorder(sub.CellStyle)) continue; + page.PrintTitleBorders.Add(new PrintTitleCellDraw + { + Cell = sub, + X = colX[bc], + Y = rowY[br], + Width = bw, + Height = bh + }); + } + } } else { @@ -1088,11 +1121,16 @@ private static void EmitBandGrid(List target, PdfRange range, int from double top = rowY[0], bottom = rowY[nr]; double left = colX[0], right = colX[nc]; + EmitBandFrameH(target, range, top, fromRow, fromCol, nc, colX, CellHasTopBorder); + EmitBandFrameH(target, range, bottom, toRow, fromCol, nc, colX, CellHasBottomBorder); + EmitBandFrameV(target, range, left, fromCol, fromRow, nr, rowY, CellHasLeftBorder); + EmitBandFrameV(target, range, right, toCol, fromRow, nr, rowY, CellHasRightBorder); + // region frame (outer edges; harmless overlap with page frame / heading borders / band↔content seam) - target.Add(new GridLine(left, top, left, bottom)); - target.Add(new GridLine(right, top, right, bottom)); - target.Add(new GridLine(left, top, right, top)); - target.Add(new GridLine(left, bottom, right, bottom)); + //target.Add(new GridLine(left, top, left, bottom)); + //target.Add(new GridLine(right, top, right, bottom)); + //target.Add(new GridLine(left, top, right, top)); + //target.Add(new GridLine(left, bottom, right, bottom)); // interior verticals — suppress where a merge spans the gap for (int gi = 1; gi < nc; gi++) @@ -1103,7 +1141,10 @@ private static void EmitBandGrid(List target, PdfRange range, int from for (int ri = 0; ri < nr; ri++) { int r = fromRow + ri; - bool block = SameMerge(range, r, leftCol, r, rightCol) || spill[ri, gi]; + //bool block = SameMerge(range, r, leftCol, r, rightCol) || spill[ri, gi]; + bool block = SameMerge(range, r, leftCol, r, rightCol) || spill[ri, gi] || + CellHasRightBorder(RangeCell(range, r, leftCol)) || + CellHasLeftBorder(RangeCell(range, r, rightCol)); if (!block) { if (runStart == null) runStart = rowY[ri]; runEnd = rowY[ri + 1]; } else if (runStart != null) { target.Add(new GridLine(x, runStart.Value, x, runEnd)); runStart = null; } } @@ -1125,7 +1166,11 @@ private static void EmitBandGrid(List target, PdfRange range, int from for (int ci = 0; ci < nc; ci++) { int c = fromCol + ci; - if (!SameMerge(range, topRow, c, bottomRow, c)) { if (runStart == null) runStart = colX[ci]; runEnd = colX[ci + 1]; } + bool block = SameMerge(range, topRow, c, bottomRow, c) || + CellHasBottomBorder(RangeCell(range, topRow, c)) || + CellHasTopBorder(RangeCell(range, bottomRow, c)); + if (!block) { if (runStart == null) runStart = colX[ci]; runEnd = colX[ci + 1]; } + //if (!SameMerge(range, topRow, c, bottomRow, c)) { if (runStart == null) runStart = colX[ci]; runEnd = colX[ci + 1]; } else if (runStart != null) { target.Add(new GridLine(runStart.Value, y, runEnd, y)); runStart = null; } } if (runStart != null) target.Add(new GridLine(runStart.Value, y, runEnd, y)); @@ -1675,5 +1720,52 @@ internal static Pages PrecomputeSpillCells(PdfPageSettings pageSettings, PdfRang return pdfPages; } + private static bool CellHasRightBorder(PdfCell cell) + { + var cs = cell?.CellStyle; if (cs == null) return false; + return (cs.xfRight != null && cs.xfRight.Style != ExcelBorderStyle.None) || (cs.dxfRight?.HasValue ?? false); + } + private static bool CellHasLeftBorder(PdfCell cell) + { + var cs = cell?.CellStyle; if (cs == null) return false; + return (cs.xfLeft != null && cs.xfLeft.Style != ExcelBorderStyle.None) || (cs.dxfLeft?.HasValue ?? false); + } + private static bool CellHasTopBorder(PdfCell cell) + { + var cs = cell?.CellStyle; if (cs == null) return false; + return (cs.xfTop != null && cs.xfTop.Style != ExcelBorderStyle.None) || (cs.dxfTop?.HasValue ?? false); + } + private static bool CellHasBottomBorder(PdfCell cell) + { + var cs = cell?.CellStyle; if (cs == null) return false; + return (cs.xfBottom != null && cs.xfBottom.Style != ExcelBorderStyle.None) || (cs.dxfBottom?.HasValue ?? false); + } + + private static void EmitBandFrameH(List target, PdfRange range, double y, int row, int fromCol, int nc, double[] colX, Func hasBorder) + { + double? rs = null; double re = 0d; + for (int ci = 0; ci < nc; ci++) + { + double segL = colX[ci], segR = colX[ci + 1]; + if (segR - segL <= 0d) { if (rs != null) { target.Add(new GridLine(rs.Value, y, re, y)); rs = null; } continue; } + if (!hasBorder(RangeCell(range, row, fromCol + ci))) { if (rs == null) rs = segL; re = segR; } + else if (rs != null) { target.Add(new GridLine(rs.Value, y, re, y)); rs = null; } + } + if (rs != null) target.Add(new GridLine(rs.Value, y, re, y)); + } + + private static void EmitBandFrameV(List target, PdfRange range, double x, int col, int fromRow, int nr, double[] rowY, Func hasBorder) + { + double? rs = null; double re = 0d; + for (int ri = 0; ri < nr; ri++) + { + double segT = rowY[ri], segB = rowY[ri + 1]; + if (segT - segB <= 0d) { if (rs != null) { target.Add(new GridLine(x, rs.Value, x, re)); rs = null; } continue; } + if (!hasBorder(RangeCell(range, fromRow + ri, col))) { if (rs == null) rs = segT; re = segB; } + else if (rs != null) { target.Add(new GridLine(x, rs.Value, x, re)); rs = null; } + } + if (rs != null) target.Add(new GridLine(x, rs.Value, x, re)); + } + } } From a94c11a86a1c94a564348c2fcf8fe417552d3903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Thu, 11 Jun 2026 16:25:03 +0200 Subject: [PATCH 26/49] Made row headings less wide, changed to use DimensionByValue --- src/EPPlus.Export.Pdf.Tests/PdfTests.cs | 41 +++++++++++++++---- .../PdfCatalog/PdfCatalog.cs | 2 +- src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs | 11 ++++- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs index 8ff7127e8a..691f78c73f 100644 --- a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs +++ b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs @@ -18,6 +18,7 @@ Date Author Change using EPPlus.Fonts.OpenType; using EPPlus.Fonts.OpenType.Integration; using OfficeOpenXml; +using OfficeOpenXml.DataValidation.Formulas.Contracts; using OfficeOpenXml.Interfaces.Drawing.Text; using OfficeOpenXml.Interfaces.RichText; using OfficeOpenXml.Style; @@ -211,17 +212,17 @@ public void PerfTest() [TestMethod] // works as expected. - [DataRow("PDFTest.xlsx", "C:\\epplustest\\pdf\\FullPageTest56.pdf", "Sheet1")] - [DataRow("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx", "C:\\epplustest\\pdf\\OutputTest1.1.pdf", "SAP Data")] + //[DataRow("PDFTest.xlsx", "C:\\epplustest\\pdf\\FullPageTest56.pdf", "Sheet1")] + //[DataRow("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx", "C:\\epplustest\\pdf\\OutputTest1.1.pdf", "SAP Data")] // Output file: OutputTest1.2.pdf - // 1. Minus signs alignment in cells differs from Excel. - // 2. Dimension seems to differ from Excel, Excel stops at row 75, EPPlus goes to row 89. - // 3. Row headings are sligthly wider in EPPlus than in Excel. - [DataRow("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx", "C:\\epplustest\\pdf\\OutputTest1.2.pdf", "Summary")] + // 1. Minus signs alignment in cells differs from Excel. Comment: Currently no support for number formats. Requires implementing number formats. + // 2. Dimension seems to differ from Excel, Excel stops at row 75, EPPlus goes to row 89. Fixed + // 3. Row headings are sligthly wider in EPPlus than in Excel. Fixed + //[DataRow("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx", "C:\\epplustest\\pdf\\OutputTest1.2.pdf", "Summary")] // works as expected - [DataRow("Aico WiP 120180 FBL3N for 0110 in 2025-04.xlsx", "C:\\epplustest\\pdf\\OutputTest1.4.pdf", "Technical")] - [DataRow("Aico KKS1 Variance Calculation for 0105 in 2025-04 (25_4_2025 15_43_40) .xlsx", "C:\\epplustest\\pdf\\OutputTest1.5.pdf", "Technical")] + //[DataRow("Aico WiP 120180 FBL3N for 0110 in 2025-04.xlsx", "C:\\epplustest\\pdf\\OutputTest1.4.pdf", "Technical")] + //[DataRow("Aico KKS1 Variance Calculation for 0105 in 2025-04 (25_4_2025 15_43_40) .xlsx", "C:\\epplustest\\pdf\\OutputTest1.5.pdf", "Technical")] // Output file: OutputTest1.6.pdf // 1. Merged cells not working @@ -236,7 +237,7 @@ public void WorkbookTests(string sourceFile, string outputPath, string wsName) PdfPageSettings pageSettings = new PdfPageSettings(); pageSettings.CommentsAndNotes = CommentsAndNotes.AtEndOfSheet; - pageSettings.CellErrors = CellErrors.NA; + pageSettings.CellErrors = CellErrors.Displayed; pageSettings.Debug = true; pageSettings.PrintAsText = true; pageSettings.ShowGridLines = true; @@ -245,6 +246,28 @@ public void WorkbookTests(string sourceFile, string outputPath, string wsName) PdfCatalog catalog = new PdfCatalog(pageSettings, ws, outputPath); } + [TestMethod] + [DataRow("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx", "C:\\epplustest\\pdf\\OutputTest1.2.pdf", "Summary")] + public void TestCells(string sourceFile, string outputPath, string wsName) + { + using var p = OpenTemplatePackage(sourceFile); + var ws = p.Workbook.Worksheets[wsName]; + var dim = ws.Dimension; + var dim2 = ws.DimensionByValue; + List NotEmpty = new List(); + for (int r = 76; r <= 89; r++) + { + for (int c = 1; c <= 8; c++) + { + var cell = ws.Cells[r, c]; + if (!cell.IsEmpty(false, false, false)) + { + NotEmpty.Add(cell); + } + } + } + } + [TestMethod] public void CalculatePages() diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs index 7bee489dda..3c9481d322 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs @@ -227,7 +227,7 @@ private List GetRanges(ExcelWorksheet worksheet) } else { - var range = worksheet.Dimension; + var range = worksheet.DimensionByValue; var pdfRange = new PdfRange(range, true); pdfRange.ExtendColumns = true; ranges.Add(pdfRange); diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs index b37d9b13da..f2fa5604cd 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs @@ -100,7 +100,7 @@ public int Count internal class PdfLayout { - private const double rowHeadingWith1CharWidth = 23.25d; + private const double rowHeadingWith1CharWidth = 18d; public static Transform GetLayout(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfWorksheet[] pdfSheets) { @@ -722,11 +722,20 @@ internal static List GetPages(PdfPageSettings pageSettings, PdfWorksheet[ } if (pdfSheet.CommentsAndNotes.Range != null) { + bool savedShowHeadings = pageSettings.ShowHeadings; + pageSettings.ShowHeadings = false; var pages = GetNumberOfPages(pageSettings, pdfSheet, ref pdfSheet.CommentsAndNotes); pages = AssignRangeToPages(pageSettings, pdfSheet.CommentsAndNotes, pages); pages = MapPage(pdfSheet.CommentsAndNotes, pages); + pageSettings.ShowHeadings = savedShowHeadings; pages.IsCommentsPage = true; PagesCollection.Add(pages); + + //var pages = GetNumberOfPages(pageSettings, pdfSheet, ref pdfSheet.CommentsAndNotes); + //pages = AssignRangeToPages(pageSettings, pdfSheet.CommentsAndNotes, pages); + //pages = MapPage(pdfSheet.CommentsAndNotes, pages); + //pages.IsCommentsPage = true; + //PagesCollection.Add(pages); } } return PagesCollection; From bcfb8b2e58230c5e32ab15f318be75cae74780b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Fri, 12 Jun 2026 15:28:16 +0200 Subject: [PATCH 27/49] fixed a few issues. --- src/EPPlus.Export.Pdf.Tests/PdfTests.cs | 26 ++----------------- .../PdfCatalog/PdfCatalog.cs | 4 +-- 2 files changed, 4 insertions(+), 26 deletions(-) diff --git a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs index 691f78c73f..93e773d800 100644 --- a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs +++ b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs @@ -228,7 +228,8 @@ public void PerfTest() // 1. Merged cells not working // 2. Pattern fills looks differnt, in some cases not working // 3. Rotation of text in cells not working (the dates). - [DataRow("R05.xlsx", "C:\\epplustest\\pdf\\OutputTest1.6.pdf", "R05 Arbeitseinteilung")] + // [DataRow("R05.xlsx", "C:\\epplustest\\pdf\\OutputTest1.6.pdf", "R05 Arbeitseinteilung")] + [DataRow("R05 - Copy.xlsx", "C:\\epplustest\\pdf\\OutputTest1.6.pdf", "R05 Arbeitseinteilung")] public void WorkbookTests(string sourceFile, string outputPath, string wsName) { using var p = OpenTemplatePackage(sourceFile); @@ -246,29 +247,6 @@ public void WorkbookTests(string sourceFile, string outputPath, string wsName) PdfCatalog catalog = new PdfCatalog(pageSettings, ws, outputPath); } - [TestMethod] - [DataRow("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx", "C:\\epplustest\\pdf\\OutputTest1.2.pdf", "Summary")] - public void TestCells(string sourceFile, string outputPath, string wsName) - { - using var p = OpenTemplatePackage(sourceFile); - var ws = p.Workbook.Worksheets[wsName]; - var dim = ws.Dimension; - var dim2 = ws.DimensionByValue; - List NotEmpty = new List(); - for (int r = 76; r <= 89; r++) - { - for (int c = 1; c <= 8; c++) - { - var cell = ws.Cells[r, c]; - if (!cell.IsEmpty(false, false, false)) - { - NotEmpty.Add(cell); - } - } - } - } - - [TestMethod] public void CalculatePages() { diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs index 3c9481d322..1f7b43c73c 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs @@ -56,8 +56,8 @@ public PdfCatalog(PdfPageSettings pageSettings, ExcelWorksheet worksheet, Stream private void BuildPdf(PdfPageSettings pageSettings, ExcelWorksheet worksheet, Action writePdf) { - pageSettings.defaultFontName = worksheet.Workbook.ThemeManager.CurrentTheme.FontScheme.MinorFont[0].Typeface; - + //pageSettings.defaultFontName = worksheet.Workbook.ThemeManager.CurrentTheme.FontScheme.MinorFont[0].Typeface; + pageSettings.defaultFontName = worksheet.Workbook.ThemeManager.GetOrCreateTheme().FontScheme.MinorFont[0].Typeface; PdfWorksheet pdfSheet = null; try { From 17b5df47c277c4d67e3936b7e8a3d4b85ebb8ca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Mon, 15 Jun 2026 13:50:08 +0200 Subject: [PATCH 28/49] Fixed border rendering inside merged cells. --- src/EPPlus.Export.Pdf.Tests/PdfTests.cs | 4 ++-- src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs | 8 ++++++++ src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs | 2 +- src/EPPlus.Export.Pdf/PdfLayout/PdfCellContentLayout.cs | 2 +- src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs | 2 +- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs index 93e773d800..c8f88c6d55 100644 --- a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs +++ b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs @@ -241,8 +241,8 @@ public void WorkbookTests(string sourceFile, string outputPath, string wsName) pageSettings.CellErrors = CellErrors.Displayed; pageSettings.Debug = true; pageSettings.PrintAsText = true; - pageSettings.ShowGridLines = true; - pageSettings.ShowHeadings = true; + pageSettings.ShowGridLines = false; + pageSettings.ShowHeadings = false; PdfCatalog catalog = new PdfCatalog(pageSettings, ws, outputPath); } diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs index f2fa5604cd..095dc38731 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs @@ -234,6 +234,14 @@ internal static Transform GetCatalog(PdfPageSettings pageSettings, PdfDictionari { var border = new PdfCellBorderLayout(map.CellStyle, map.Merged, GetCorners(map.MergedAddress, row, col), info, x, y, map.ColumnWidth, rowHeight); border.Name = map.Name; + if (map.Merged && map.MergedAddress != null) + { + var addr = map.MergedAddress; + if (row != addr.Start.Row) border.BorderData.Top.BorderStyle = ExcelBorderStyle.None; + if (row != addr.End.Row) border.BorderData.Bottom.BorderStyle = ExcelBorderStyle.None; + if (col != addr.Start.Column) border.BorderData.Left.BorderStyle = ExcelBorderStyle.None; + if (col != addr.End.Column) border.BorderData.Right.BorderStyle = ExcelBorderStyle.None; + } pageLayout.AddChild(border); } x += map.ColumnWidth; diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs index e0a67fb548..0ee4b43374 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs @@ -502,7 +502,7 @@ private static PdfCellAlignmentData GetContentAlignment(ExcelRangeBase cell) contentAlignment.Indent = cell.Style.Indent; contentAlignment.WrapText = cell.Style.WrapText; contentAlignment.ShrinkToFit = cell.Style.ShrinkToFit; - contentAlignment.TextRotation = (cell.Style.TextRotation >= 90) ? ((cell.Style.TextRotation == 255) ? 0 : 90 - cell.Style.TextRotation) : cell.Style.TextRotation; + contentAlignment.TextRotation = (cell.Style.TextRotation > 90) ? ((cell.Style.TextRotation == 255) ? 0 : 90 - cell.Style.TextRotation) : cell.Style.TextRotation; contentAlignment.IsVertical = cell.Style.TextRotation == 255 ? true : false; contentAlignment.TextDirection = cell.Style.ReadingOrder; return contentAlignment; diff --git a/src/EPPlus.Export.Pdf/PdfLayout/PdfCellContentLayout.cs b/src/EPPlus.Export.Pdf/PdfLayout/PdfCellContentLayout.cs index ad047ceb8d..8175251879 100644 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfCellContentLayout.cs +++ b/src/EPPlus.Export.Pdf/PdfLayout/PdfCellContentLayout.cs @@ -214,7 +214,7 @@ public PdfCellContentLayout(ExcelRangeBase cell, PdfCellStyle CellStyle, PdfPage CellAlignmentData.Indent = cell.Style.Indent; CellAlignmentData.WrapText = cell.Style.WrapText; CellAlignmentData.ShrinkToFit = cell.Style.ShrinkToFit; - CellAlignmentData.TextRotation = (cell.Style.TextRotation >= 90) ? ((cell.Style.TextRotation == 255) ? 0 : 90 - cell.Style.TextRotation) : cell.Style.TextRotation; + CellAlignmentData.TextRotation = (cell.Style.TextRotation > 90) ? ((cell.Style.TextRotation == 255) ? 0 : 90 - cell.Style.TextRotation) : cell.Style.TextRotation; CellAlignmentData.IsVertical = cell.Style.TextRotation == 255 ? true : false; CellAlignmentData.TextDirection = cell.Style.ReadingOrder; if (!cell.IsRichText) cell._rtc = new ExcelRichTextCollection(cell.Text, cell); diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs b/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs index c9606e3677..ec02e53220 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs +++ b/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs @@ -250,7 +250,7 @@ public void AddText(PdfCellContentLayout cell, Vector2 position, double textRota commands.Add($"S"); } commands.Add(color.ToFillCommand()); - commands.Add($"{textMatrix.A.ToPdfString()} {textMatrix.B.ToPdfString()} {textMatrix.C.ToPdfString()} {textMatrix.D.ToPdfString()} {textMatrix.E.ToPdfString()} {textMatrix.F.ToPdfString()} Tm"); + commands.Add($"{textMatrix.A.ToPdfStringF4()} {textMatrix.B.ToPdfStringF4()} {textMatrix.C.ToPdfStringF4()} {textMatrix.D.ToPdfStringF4()} {textMatrix.E.ToPdfStringF4()} {textMatrix.F.ToPdfStringF4()} Tm"); // FIX: Always use fontIdMap to determine the initial font. // FontId=0 does NOT always mean "primary font" — when the text starts From 89bf83f7e34e6e144e16250443b7d92a626a36d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Mon, 15 Jun 2026 17:09:24 +0200 Subject: [PATCH 29/49] Implemented conditional formatting for fills. --- src/EPPlus.Export.Pdf.Tests/PdfTests.cs | 14 +- .../PdfCatalog/PdfTextMap.cs | 254 +++++++++--------- .../PdfLayout/PdfCellContentLayout.cs | 57 ++++ 3 files changed, 196 insertions(+), 129 deletions(-) diff --git a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs index c8f88c6d55..770597f840 100644 --- a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs +++ b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs @@ -216,19 +216,19 @@ public void PerfTest() //[DataRow("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx", "C:\\epplustest\\pdf\\OutputTest1.1.pdf", "SAP Data")] // Output file: OutputTest1.2.pdf - // 1. Minus signs alignment in cells differs from Excel. Comment: Currently no support for number formats. Requires implementing number formats. - // 2. Dimension seems to differ from Excel, Excel stops at row 75, EPPlus goes to row 89. Fixed - // 3. Row headings are sligthly wider in EPPlus than in Excel. Fixed + // 1. Minus signs alignment in cells differs from Excel. ------------------------------------------------ Comment: Currently no support for number formats. Requires implementing number formats. + // 2. Dimension seems to differ from Excel, Excel stops at row 75, EPPlus goes to row 89. --------------- Fixed + // 3. Row headings are sligthly wider in EPPlus than in Excel. ------------------------------------------ Fixed //[DataRow("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx", "C:\\epplustest\\pdf\\OutputTest1.2.pdf", "Summary")] // works as expected //[DataRow("Aico WiP 120180 FBL3N for 0110 in 2025-04.xlsx", "C:\\epplustest\\pdf\\OutputTest1.4.pdf", "Technical")] //[DataRow("Aico KKS1 Variance Calculation for 0105 in 2025-04 (25_4_2025 15_43_40) .xlsx", "C:\\epplustest\\pdf\\OutputTest1.5.pdf", "Technical")] // Output file: OutputTest1.6.pdf - // 1. Merged cells not working - // 2. Pattern fills looks differnt, in some cases not working - // 3. Rotation of text in cells not working (the dates). - // [DataRow("R05.xlsx", "C:\\epplustest\\pdf\\OutputTest1.6.pdf", "R05 Arbeitseinteilung")] + // 1. Merged cells not working ------------------------------------ Fixed. Comment Merged cells was fine, it was borders being rendered inside merged cells. + // 2. Pattern fills looks differnt, in some cases not working ----- + // 3. Rotation of text in cells not working (the dates). ---------- + // [DataRow("R05.xlsx", "C:\\epplustest\\pdf\\OutputTest1.6.pdf", "R05 Arbeitseinteilung")] [DataRow("R05 - Copy.xlsx", "C:\\epplustest\\pdf\\OutputTest1.6.pdf", "R05 Arbeitseinteilung")] public void WorkbookTests(string sourceFile, string outputPath, string wsName) { diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs index 0ee4b43374..3c51261081 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs @@ -99,6 +99,7 @@ public static PdfCellCollection SetTextMap(PdfPageSettings pageSettings, PdfDict } firstColumnRun = false; } + worksheet.ConditionalFormatting.ClearTempExportCacheForAllCFs(); pdfRange = Range; return Map; } @@ -157,14 +158,19 @@ private static void GetFillStyles(ExcelRangeBase cell, PdfCellStyle cellStyle) { //Conditional Formating var cf = cell.ConditionalFormatting.GetConditionalFormattings(); - if (cf != null && cf.Count > 1) + if (cf != null && cf.Count > 0) { // Sort ascending — priority 1 beats priority 2, etc. var ordered = cf.OrderBy(r => r.Priority); foreach (var rule in ordered) { - if (!EvaluateConditionalFormattingRule(rule, cell)) + // Use the core per-rule evaluator (the same one the HTML exporter + // calls). It correctly handles every rule type — comparisons, text, + // blanks/errors, top/bottom, above/below average, duplicate/unique, + // time periods and formula expressions — including the range-wide + // aggregates, which the previous hand-rolled evaluator stubbed out. + if (!rule.ShouldApplyToCell(cell)) { if (rule.StopIfTrue) break; // higher-priority rule fired but had no fill — stop anyway continue; @@ -173,10 +179,14 @@ private static void GetFillStyles(ExcelRangeBase cell, PdfCellStyle cellStyle) if (rule.Style?.Fill != null && rule.Style.Fill.HasValue) { cellStyle.dxfFill = rule.Style.Fill; + // xfFill must be non-null for the downstream dxf path + // (PdfCellLayout checks xfFill.IsEmpty()); the cell's own fill + // is empty here, which is exactly what selects the dxf fill. + cellStyle.xfFill = cell.Style.Fill; return; // CF fill wins — skip table and xf entirely } - if (rule.StopIfTrue) break; // rule applied but defined no fill — stop evaluating lower rules + if (rule.StopIfTrue) break; } } @@ -234,130 +244,130 @@ private static void GetFillStyles(ExcelRangeBase cell, PdfCellStyle cellStyle) } cellStyle.xfFill = cell.Style.Fill; } - private static bool EvaluateConditionalFormattingRule(IExcelConditionalFormattingRule rule, ExcelRangeBase cell) - { - // These never produce a dxf fill — skip entirely - switch (rule.Type) - { - case eExcelConditionalFormattingRuleType.TwoColorScale: - case eExcelConditionalFormattingRuleType.ThreeColorScale: - case eExcelConditionalFormattingRuleType.DataBar: - case eExcelConditionalFormattingRuleType.ThreeIconSet: - case eExcelConditionalFormattingRuleType.FourIconSet: - case eExcelConditionalFormattingRuleType.FiveIconSet: - return false; - } + //private static bool EvaluateConditionalFormattingRule(IExcelConditionalFormattingRule rule, ExcelRangeBase cell) + //{ + // // These never produce a dxf fill — skip entirely + // switch (rule.Type) + // { + // case eExcelConditionalFormattingRuleType.TwoColorScale: + // case eExcelConditionalFormattingRuleType.ThreeColorScale: + // case eExcelConditionalFormattingRuleType.DataBar: + // case eExcelConditionalFormattingRuleType.ThreeIconSet: + // case eExcelConditionalFormattingRuleType.FourIconSet: + // case eExcelConditionalFormattingRuleType.FiveIconSet: + // return false; + // } - var cellValue = cell.Value; - var cellText = cellValue?.ToString() ?? string.Empty; + // var cellValue = cell.Value; + // var cellText = cellValue?.ToString() ?? string.Empty; - switch (rule.Type) - { - // --- Comparison (CellIs) types --- - case eExcelConditionalFormattingRuleType.Equal: - case eExcelConditionalFormattingRuleType.NotEqual: - case eExcelConditionalFormattingRuleType.GreaterThan: - case eExcelConditionalFormattingRuleType.GreaterThanOrEqual: - case eExcelConditionalFormattingRuleType.LessThan: - case eExcelConditionalFormattingRuleType.LessThanOrEqual: - case eExcelConditionalFormattingRuleType.Between: - case eExcelConditionalFormattingRuleType.NotBetween: - return EvaluateCellIs(rule, cell); - - // --- Text types --- - case eExcelConditionalFormattingRuleType.ContainsText: - return cellText.Contains((rule as IExcelConditionalFormattingContainsText)?.Text ?? ""); - - case eExcelConditionalFormattingRuleType.NotContainsText: - return !cellText.Contains((rule as IExcelConditionalFormattingNotContainsText)?.Text ?? ""); - - case eExcelConditionalFormattingRuleType.NotContains: - return !cellText.Contains((rule as IExcelConditionalFormattingContainsText)?.Text ?? ""); - - case eExcelConditionalFormattingRuleType.BeginsWith: - return cellText.StartsWith((rule as IExcelConditionalFormattingBeginsWith)?.Text ?? ""); - - case eExcelConditionalFormattingRuleType.EndsWith: - return cellText.EndsWith((rule as IExcelConditionalFormattingEndsWith)?.Text ?? ""); - - // --- Blank/Error types --- - case eExcelConditionalFormattingRuleType.ContainsBlanks: - return string.IsNullOrEmpty(cellText.Trim()); - - case eExcelConditionalFormattingRuleType.NotContainsBlanks: - return !string.IsNullOrEmpty(cellText.Trim()); - - case eExcelConditionalFormattingRuleType.ContainsErrors: - return cellValue is ExcelErrorValue; - - case eExcelConditionalFormattingRuleType.NotContainsErrors: - return !(cellValue is ExcelErrorValue); - - // --- Duplicate/Unique — require range-wide scan, stub for now --- - case eExcelConditionalFormattingRuleType.DuplicateValues: - case eExcelConditionalFormattingRuleType.UniqueValues: - return false; - - // --- Average/StdDev/Top/Bottom/TimePeriod — require range-wide scan, stub for now --- - case eExcelConditionalFormattingRuleType.AboveAverage: - case eExcelConditionalFormattingRuleType.AboveOrEqualAverage: - case eExcelConditionalFormattingRuleType.BelowAverage: - case eExcelConditionalFormattingRuleType.BelowOrEqualAverage: - case eExcelConditionalFormattingRuleType.AboveStdDev: - case eExcelConditionalFormattingRuleType.BelowStdDev: - case eExcelConditionalFormattingRuleType.Top: - case eExcelConditionalFormattingRuleType.TopPercent: - case eExcelConditionalFormattingRuleType.Bottom: - case eExcelConditionalFormattingRuleType.BottomPercent: - case eExcelConditionalFormattingRuleType.Last7Days: - case eExcelConditionalFormattingRuleType.LastMonth: - case eExcelConditionalFormattingRuleType.LastWeek: - case eExcelConditionalFormattingRuleType.NextMonth: - case eExcelConditionalFormattingRuleType.NextWeek: - case eExcelConditionalFormattingRuleType.ThisMonth: - case eExcelConditionalFormattingRuleType.ThisWeek: - case eExcelConditionalFormattingRuleType.Today: - case eExcelConditionalFormattingRuleType.Tomorrow: - case eExcelConditionalFormattingRuleType.Yesterday: - return false; - - // --- Formula-based --- - case eExcelConditionalFormattingRuleType.Expression: - return false; // requires formula evaluation — future phase - - default: - return false; - } - } + // switch (rule.Type) + // { + // // --- Comparison (CellIs) types --- + // case eExcelConditionalFormattingRuleType.Equal: + // case eExcelConditionalFormattingRuleType.NotEqual: + // case eExcelConditionalFormattingRuleType.GreaterThan: + // case eExcelConditionalFormattingRuleType.GreaterThanOrEqual: + // case eExcelConditionalFormattingRuleType.LessThan: + // case eExcelConditionalFormattingRuleType.LessThanOrEqual: + // case eExcelConditionalFormattingRuleType.Between: + // case eExcelConditionalFormattingRuleType.NotBetween: + // return EvaluateCellIs(rule, cell); + + // // --- Text types --- + // case eExcelConditionalFormattingRuleType.ContainsText: + // return cellText.Contains((rule as IExcelConditionalFormattingContainsText)?.Text ?? ""); + + // case eExcelConditionalFormattingRuleType.NotContainsText: + // return !cellText.Contains((rule as IExcelConditionalFormattingNotContainsText)?.Text ?? ""); + + // case eExcelConditionalFormattingRuleType.NotContains: + // return !cellText.Contains((rule as IExcelConditionalFormattingContainsText)?.Text ?? ""); + + // case eExcelConditionalFormattingRuleType.BeginsWith: + // return cellText.StartsWith((rule as IExcelConditionalFormattingBeginsWith)?.Text ?? ""); + + // case eExcelConditionalFormattingRuleType.EndsWith: + // return cellText.EndsWith((rule as IExcelConditionalFormattingEndsWith)?.Text ?? ""); + + // // --- Blank/Error types --- + // case eExcelConditionalFormattingRuleType.ContainsBlanks: + // return string.IsNullOrEmpty(cellText.Trim()); + + // case eExcelConditionalFormattingRuleType.NotContainsBlanks: + // return !string.IsNullOrEmpty(cellText.Trim()); + + // case eExcelConditionalFormattingRuleType.ContainsErrors: + // return cellValue is ExcelErrorValue; + + // case eExcelConditionalFormattingRuleType.NotContainsErrors: + // return !(cellValue is ExcelErrorValue); + + // // --- Duplicate/Unique — require range-wide scan, stub for now --- + // case eExcelConditionalFormattingRuleType.DuplicateValues: + // case eExcelConditionalFormattingRuleType.UniqueValues: + // return false; + + // // --- Average/StdDev/Top/Bottom/TimePeriod — require range-wide scan, stub for now --- + // case eExcelConditionalFormattingRuleType.AboveAverage: + // case eExcelConditionalFormattingRuleType.AboveOrEqualAverage: + // case eExcelConditionalFormattingRuleType.BelowAverage: + // case eExcelConditionalFormattingRuleType.BelowOrEqualAverage: + // case eExcelConditionalFormattingRuleType.AboveStdDev: + // case eExcelConditionalFormattingRuleType.BelowStdDev: + // case eExcelConditionalFormattingRuleType.Top: + // case eExcelConditionalFormattingRuleType.TopPercent: + // case eExcelConditionalFormattingRuleType.Bottom: + // case eExcelConditionalFormattingRuleType.BottomPercent: + // case eExcelConditionalFormattingRuleType.Last7Days: + // case eExcelConditionalFormattingRuleType.LastMonth: + // case eExcelConditionalFormattingRuleType.LastWeek: + // case eExcelConditionalFormattingRuleType.NextMonth: + // case eExcelConditionalFormattingRuleType.NextWeek: + // case eExcelConditionalFormattingRuleType.ThisMonth: + // case eExcelConditionalFormattingRuleType.ThisWeek: + // case eExcelConditionalFormattingRuleType.Today: + // case eExcelConditionalFormattingRuleType.Tomorrow: + // case eExcelConditionalFormattingRuleType.Yesterday: + // return false; + + // // --- Formula-based --- + // case eExcelConditionalFormattingRuleType.Expression: + // return false; // requires formula evaluation — future phase + + // default: + // return false; + // } + //} - private static bool EvaluateCellIs(IExcelConditionalFormattingRule rule, ExcelRangeBase cell) - { - if (cell.Value == null) return false; - if (!double.TryParse(cell.Value.ToString(), out double cellDouble)) return false; + //private static bool EvaluateCellIs(IExcelConditionalFormattingRule rule, ExcelRangeBase cell) + //{ + // if (cell.Value == null) return false; + // if (!double.TryParse(cell.Value.ToString(), out double cellDouble)) return false; - var f1Rule = rule as IExcelConditionalFormattingWithFormula; - if (f1Rule == null || !double.TryParse(f1Rule.Formula, out double formula1)) return false; + // var f1Rule = rule as IExcelConditionalFormattingWithFormula; + // if (f1Rule == null || !double.TryParse(f1Rule.Formula, out double formula1)) return false; - switch (rule.Type) - { - case eExcelConditionalFormattingRuleType.Equal: return cellDouble == formula1; - case eExcelConditionalFormattingRuleType.NotEqual: return cellDouble != formula1; - case eExcelConditionalFormattingRuleType.GreaterThan: return cellDouble > formula1; - case eExcelConditionalFormattingRuleType.GreaterThanOrEqual: return cellDouble >= formula1; - case eExcelConditionalFormattingRuleType.LessThan: return cellDouble < formula1; - case eExcelConditionalFormattingRuleType.LessThanOrEqual: return cellDouble <= formula1; - case eExcelConditionalFormattingRuleType.Between: - case eExcelConditionalFormattingRuleType.NotBetween: - { - var f2Rule = rule as IExcelConditionalFormattingWithFormula2; - if (f2Rule == null || !double.TryParse(f2Rule.Formula2, out double formula2)) return false; - bool inRange = cellDouble >= formula1 && cellDouble <= formula2; - return rule.Type == eExcelConditionalFormattingRuleType.Between ? inRange : !inRange; - } - default: - return false; - } - } + // switch (rule.Type) + // { + // case eExcelConditionalFormattingRuleType.Equal: return cellDouble == formula1; + // case eExcelConditionalFormattingRuleType.NotEqual: return cellDouble != formula1; + // case eExcelConditionalFormattingRuleType.GreaterThan: return cellDouble > formula1; + // case eExcelConditionalFormattingRuleType.GreaterThanOrEqual: return cellDouble >= formula1; + // case eExcelConditionalFormattingRuleType.LessThan: return cellDouble < formula1; + // case eExcelConditionalFormattingRuleType.LessThanOrEqual: return cellDouble <= formula1; + // case eExcelConditionalFormattingRuleType.Between: + // case eExcelConditionalFormattingRuleType.NotBetween: + // { + // var f2Rule = rule as IExcelConditionalFormattingWithFormula2; + // if (f2Rule == null || !double.TryParse(f2Rule.Formula2, out double formula2)) return false; + // bool inRange = cellDouble >= formula1 && cellDouble <= formula2; + // return rule.Type == eExcelConditionalFormattingRuleType.Between ? inRange : !inRange; + // } + // default: + // return false; + // } + //} private static void GetBorderStyles(ExcelRangeBase cell, PdfCellStyle cellStyle, PdfCell pcell) { diff --git a/src/EPPlus.Export.Pdf/PdfLayout/PdfCellContentLayout.cs b/src/EPPlus.Export.Pdf/PdfLayout/PdfCellContentLayout.cs index 8175251879..f6e7882e9d 100644 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfCellContentLayout.cs +++ b/src/EPPlus.Export.Pdf/PdfLayout/PdfCellContentLayout.cs @@ -163,11 +163,68 @@ private Vector2 CalculatePositionFromRotation(double textLength, double x, doubl private Vector2 CalculateAlignment(string text, double textLength, double textHeight, double firstLineAscent, double lastLineAscent, double x, double y, double width, double height) { + if (CellAlignmentData.TextRotation != 0 && !CellAlignmentData.IsVertical) + { + return CalculateRotatedAlignment(textLength, textHeight, firstLineAscent, x, y, width, height); + } double newX = CalculateHorizontalAlignment(text, textLength, x, width, rightMargin); double newY = CalculateVerticalAlignment(text, textHeight, firstLineAscent, lastLineAscent, y, height, 0d); return CalculatePositionFromRotation(textLength, newX, newY); } + private Vector2 CalculateRotatedAlignment(double textLength, double textHeight, double firstLineAscent, double x, double y, double width, double height) + { + double ascent = firstLineAscent; + double descent = textHeight - firstLineAscent; + if (descent < 0d) descent = 0d; + + double theta = CellAlignmentData.TextRotation * System.Math.PI / 180.0; + double cos = System.Math.Cos(theta); + double sin = System.Math.Sin(theta); + + // Bounding box of the rotated block. Reading direction spans [0, textLength]; + // the cross (line-height) direction spans [-descent, ascent]. + double[] gx = { 0d, textLength, 0d, textLength }; + double[] gy = { ascent, ascent, -descent, -descent }; + double minX = double.MaxValue, maxX = double.MinValue, minY = double.MaxValue, maxY = double.MinValue; + for (int i = 0; i < 4; i++) + { + double ux = cos * gx[i] - sin * gy[i]; + double uy = sin * gx[i] + cos * gy[i]; + if (ux < minX) minX = ux; + if (ux > maxX) maxX = ux; + if (uy < minY) minY = uy; + if (uy > maxY) maxY = uy; + } + double blockWidth = maxX - minX; + double blockHeight = maxY - minY; + + double bx; + switch (CellAlignmentData.HorizontalAlignment) + { + case ExcelHorizontalAlignment.Left: + bx = x + rightMargin; break; + case ExcelHorizontalAlignment.Right: + bx = x + width - blockWidth - rightMargin; break; + default: // Center / General + bx = x + (width - blockWidth) / 2d; break; + } + + double by; + switch (CellAlignmentData.VerticalAlignment) + { + case ExcelVerticalAlignment.Top: + by = y + height - blockHeight - bottomMargin; break; + case ExcelVerticalAlignment.Bottom: + by = y + bottomMargin; break; + default: // Center / Justify / Distributed + by = y + (height - blockHeight) / 2d; break; + } + + // Convert the bounding-box lower-left back to the baseline origin the matrix expects. + return new Vector2(bx - minX, by - minY); + } + //private static List GetTextFragments(List textFormats) //{ // var fragments = new List(textFormats.Count); From b0d37f6f1c1b9bea0bcc92f298ba36eeb344ef73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Tue, 16 Jun 2026 16:12:33 +0200 Subject: [PATCH 30/49] Added conditional formatting for text and borders. --- .../PdfCatalog/PdfTextMap.cs | 78 ++++++++++++++++--- 1 file changed, 69 insertions(+), 9 deletions(-) diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs index 3c51261081..016b1abec6 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs @@ -1,4 +1,5 @@ -using EPPlus.Export.Pdf.PdfLayout; +using EPPlus.Export.Pdf.Pdfhelpers; +using EPPlus.Export.Pdf.PdfLayout; using EPPlus.Export.Pdf.PdfResources; using EPPlus.Export.Pdf.PdfSettings; using EPPlus.Fonts.OpenType; @@ -12,12 +13,14 @@ using OfficeOpenXml.Interfaces.Fonts; using OfficeOpenXml.Interfaces.RichText; using OfficeOpenXml.Style; +using OfficeOpenXml.Style.Dxf; using OfficeOpenXml.Style.HeaderFooterTextFormat; using OfficeOpenXml.Style.Table; using OfficeOpenXml.Table; using System; using System.Collections.Generic; using System.Diagnostics; +using System.Drawing; using System.Linq; namespace EPPlus.Export.Pdf.PdfCatalog @@ -417,11 +420,62 @@ private static void GetBorderStyles(ExcelRangeBase cell, PdfCellStyle cellStyle, cellStyle.dxfLeft = PdfCellBorderLayout.GetLeftBorderItem(cell, cellStyle.xfLeft, table, tableStyle); cellStyle.dxfRight = PdfCellBorderLayout.GetRightBorderItem(cell, cellStyle.xfRight, table, tableStyle); } + var cfBorder = GetConditionalFormattingBorder(cell); + if (cfBorder != null) + { + if (cfBorder.Top != null && cfBorder.Top.HasValue) cellStyle.dxfTop = cfBorder.Top; + if (cfBorder.Bottom != null && cfBorder.Bottom.HasValue) cellStyle.dxfBottom = cfBorder.Bottom; + if (cfBorder.Left != null && cfBorder.Left.HasValue) cellStyle.dxfLeft = cfBorder.Left; + if (cfBorder.Right != null && cfBorder.Right.HasValue) cellStyle.dxfRight = cfBorder.Right; + } } } + private static ExcelDxfBorderBase GetConditionalFormattingBorder(ExcelRangeBase cell) + { + var cf = cell.ConditionalFormatting.GetConditionalFormattings(); + if (cf != null && cf.Count > 0) + { + var ordered = cf.OrderBy(r => r.Priority); + foreach (var rule in ordered) + { + if (!rule.ShouldApplyToCell(cell)) + { + if (rule.StopIfTrue) break; + continue; + } + if (rule.Style?.Border != null && rule.Style.Border.HasValue) + { + return rule.Style.Border; + } + if (rule.StopIfTrue) break; + } + } + return null; + } + private static PdfCellStyle GetFontStyle(ExcelRangeBase cell, PdfCellStyle cellStyle) { + var cf = cell.ConditionalFormatting.GetConditionalFormattings(); + if (cf != null && cf.Count > 0) + { + // Sort ascending — priority 1 beats priority 2, etc. + var ordered = cf.OrderBy(r => r.Priority); + foreach (var rule in ordered) + { + if (!rule.ShouldApplyToCell(cell)) + { + if (rule.StopIfTrue) break; + continue; + } + if (rule.Style?.Font != null && rule.Style.Font.HasValue) + { + cellStyle.dxfFont = rule.Style.Font; + return cellStyle; // CF font wins over the table font + } + if (rule.StopIfTrue) break; + } + } var tables = cell.Worksheet.Tables.GetIntersectingRanges(cell); if (tables.Count > 0) { @@ -526,21 +580,22 @@ private static List GetTextFragments(PdfPageSettings pageSettings, { bool dxfBold, dxfItalic, dxfStrike, dxfUnderline; ExcelUnderLineType dxfUnderLineType; - ReadDxfFontOverrides(cellStyle, out dxfBold, out dxfItalic, out dxfStrike, out dxfUnderline, out dxfUnderLineType); + System.Drawing.Color? dxfColor; + ReadDxfFontOverrides(cellStyle, out dxfBold, out dxfItalic, out dxfStrike, out dxfUnderline, out dxfUnderLineType, out dxfColor); string forcedText = ResolveErrorText(pageSettings, cell); if (cell.IsRichText) { return GetTextFragmentsFromRichText(pageSettings, dictionaries, cell.RichText, forcedText, - dxfBold, dxfItalic, dxfStrike, dxfUnderline, dxfUnderLineType); + dxfBold, dxfItalic, dxfStrike, dxfUnderline, dxfUnderLineType, dxfColor); } return GetTextFragmentsFromCellStyle(pageSettings, dictionaries, cell, forcedText, - dxfBold, dxfItalic, dxfStrike, dxfUnderline, dxfUnderLineType); + dxfBold, dxfItalic, dxfStrike, dxfUnderline, dxfUnderLineType, dxfColor); } private static List GetTextFragmentsFromRichText(PdfPageSettings pageSettings, PdfDictionaries dictionaries, ExcelRichTextCollection richText, string forcedText, - bool dxfBold, bool dxfItalic, bool dxfStrike, bool dxfUnderline, ExcelUnderLineType dxfUnderLineType) + bool dxfBold, bool dxfItalic, bool dxfStrike, bool dxfUnderline, ExcelUnderLineType dxfUnderLineType, System.Drawing.Color? dxfColor) { var textFragments = new List(richText.Count); for (int i = 0; i < richText.Count; i++) @@ -559,7 +614,7 @@ private static List GetTextFragmentsFromRichText(PdfPageSettings p textFrag.RichTextOptions.StrikeType = (rt.Strike || dxfStrike) ? 2 : 1; textFrag.RichTextOptions.SuperScript = rt.VerticalAlign == ExcelVerticalAlignmentFont.Superscript; textFrag.RichTextOptions.SubScript = rt.VerticalAlign == ExcelVerticalAlignmentFont.Subscript; - textFrag.RichTextOptions.FontColor = rt.Color; + textFrag.RichTextOptions.FontColor = dxfColor ?? rt.Color; textFrag.Font.SubFamily = ComputeFontStyle(textFrag); @@ -570,7 +625,7 @@ private static List GetTextFragmentsFromRichText(PdfPageSettings p } private static List GetTextFragmentsFromCellStyle(PdfPageSettings pageSettings, PdfDictionaries dictionaries, ExcelRangeBase cell, string forcedText, - bool dxfBold, bool dxfItalic, bool dxfStrike, bool dxfUnderline, ExcelUnderLineType dxfUnderLineType) + bool dxfBold, bool dxfItalic, bool dxfStrike, bool dxfUnderline, ExcelUnderLineType dxfUnderLineType, System.Drawing.Color? dxfColor) { var font = cell.Style.Font; var textFragments = new List(1); @@ -604,7 +659,7 @@ private static List GetTextFragmentsFromCellStyle(PdfPageSettings textFrag.RichTextOptions.StrikeType = (font.Strike || dxfStrike) ? 2 : 1; textFrag.RichTextOptions.SuperScript = font.VerticalAlign == ExcelVerticalAlignmentFont.Superscript; textFrag.RichTextOptions.SubScript = font.VerticalAlign == ExcelVerticalAlignmentFont.Subscript; - textFrag.RichTextOptions.FontColor = font.Color.ToColor(); + textFrag.RichTextOptions.FontColor = = dxfColor ?? font.Color.ToColor(); textFrag.Font.SubFamily = ComputeFontStyle(textFrag); @@ -613,13 +668,14 @@ private static List GetTextFragmentsFromCellStyle(PdfPageSettings return textFragments; } - private static void ReadDxfFontOverrides(PdfCellStyle cellStyle, out bool bold, out bool italic, out bool strike, out bool underline, out ExcelUnderLineType underLineType) + private static void ReadDxfFontOverrides(PdfCellStyle cellStyle, out bool bold, out bool italic, out bool strike, out bool underline, out ExcelUnderLineType underLineType, out System.Drawing.Color? color) { bold = false; italic = false; strike = false; underline = false; underLineType = ExcelUnderLineType.None; + color = null; if (cellStyle != null && cellStyle.dxfFont != null) { bold = cellStyle.dxfFont.Bold != null ? (bool)cellStyle.dxfFont.Bold : false; @@ -627,6 +683,10 @@ private static void ReadDxfFontOverrides(PdfCellStyle cellStyle, out bool bold, strike = cellStyle.dxfFont.Strike != null ? (bool)cellStyle.dxfFont.Strike : false; underline = cellStyle.dxfFont.Underline != null; underLineType = cellStyle.dxfFont.Underline != null ? (ExcelUnderLineType)cellStyle.dxfFont.Underline : ExcelUnderLineType.None; + if (cellStyle.dxfFont.Color != null && cellStyle.dxfFont.Color.HasValue) + { + color = cellStyle.dxfFont.Color.GetColorAsColor(); + } } } From 8497e9d3398dfd9e0e978f853286c2b1f0d42e1b Mon Sep 17 00:00:00 2001 From: swmal <897655+swmal@users.noreply.github.com> Date: Tue, 16 Jun 2026 16:26:55 +0200 Subject: [PATCH 31/49] Changed unittest --- src/EPPlus.Export.Pdf.Tests/PdfTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs index 770597f840..2b4ac7cf87 100644 --- a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs +++ b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs @@ -212,7 +212,7 @@ public void PerfTest() [TestMethod] // works as expected. - //[DataRow("PDFTest.xlsx", "C:\\epplustest\\pdf\\FullPageTest56.pdf", "Sheet1")] + [DataRow("PDFTest.xlsx", "C:\\epplustest\\pdf\\FullPageTest56.pdf", "Sheet1")] //[DataRow("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx", "C:\\epplustest\\pdf\\OutputTest1.1.pdf", "SAP Data")] // Output file: OutputTest1.2.pdf From 59a569a985195485b5ee3e911a3ed8e9c28ce2b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Wed, 17 Jun 2026 15:32:59 +0200 Subject: [PATCH 32/49] Made some changes to conditional formatting so formats get applied to empty cells like excel does. --- .../PdfCatalog/PdfTextMap.cs | 2 +- .../ExcelConditionalFormattingBetween.cs | 9 +++++++-- .../ExcelConditionalFormattingGreaterThan.cs | 19 ++++++++++++------- ...ConditionalFormattingGreaterThanOrEqual.cs | 16 ++++++++++------ .../ExcelConditionalFormattingLessThan.cs | 19 +++++++++++-------- ...celConditionalFormattingLessThanOrEqual.cs | 16 ++++++++++------ .../ExcelConditionalFormattingNotBetween.cs | 9 +++++++-- 7 files changed, 58 insertions(+), 32 deletions(-) diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs index 016b1abec6..41d7586f5e 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs @@ -659,7 +659,7 @@ private static List GetTextFragmentsFromCellStyle(PdfPageSettings textFrag.RichTextOptions.StrikeType = (font.Strike || dxfStrike) ? 2 : 1; textFrag.RichTextOptions.SuperScript = font.VerticalAlign == ExcelVerticalAlignmentFont.Superscript; textFrag.RichTextOptions.SubScript = font.VerticalAlign == ExcelVerticalAlignmentFont.Subscript; - textFrag.RichTextOptions.FontColor = = dxfColor ?? font.Color.ToColor(); + textFrag.RichTextOptions.FontColor = dxfColor ?? font.Color.ToColor(); textFrag.Font.SubFamily = ComputeFontStyle(textFrag); diff --git a/src/EPPlus/ConditionalFormatting/Rules/ExcelConditionalFormattingBetween.cs b/src/EPPlus/ConditionalFormatting/Rules/ExcelConditionalFormattingBetween.cs index 856840547b..42bc6c2ff9 100644 --- a/src/EPPlus/ConditionalFormatting/Rules/ExcelConditionalFormattingBetween.cs +++ b/src/EPPlus/ConditionalFormatting/Rules/ExcelConditionalFormattingBetween.cs @@ -74,9 +74,9 @@ internal ExcelConditionalFormattingBetween( internal override bool ShouldApplyToCell(ExcelAddress address) { var cellValue = _ws.Cells[address.Address].Value; - if (cellValue != null && string.IsNullOrEmpty(Formula) == false && string.IsNullOrEmpty(Formula2) == false) + if (string.IsNullOrEmpty(Formula) == false && string.IsNullOrEmpty(Formula2) == false) { - var str = cellValue.ToString(); + var str = cellValue == null ? string.Empty : cellValue.ToString(); calculatedFormula1 = string.Format(_ws.Workbook.FormulaParserManager.Parse(GetCellFormula(address), address.FullAddress, false).ToString(), CultureInfo.InvariantCulture); calculatedFormula2 = string.Format(_ws.Workbook.FormulaParserManager.Parse(GetCellFormula(address, true), address.FullAddress, false).ToString(), CultureInfo.InvariantCulture); @@ -85,6 +85,11 @@ internal override bool ShouldApplyToCell(ExcelAddress address) var Formula2IsNum = double.TryParse(calculatedFormula2, NumberStyles.Float, CultureInfo.InvariantCulture, out double num2); var cellValueIsNum = double.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out double numCellValue); + if (cellValue == null || str.Length == 0) + { + cellValueIsNum = true; + numCellValue = 0d; + } if (Formula1IsNum && Formula2IsNum) { if (cellValueIsNum) diff --git a/src/EPPlus/ConditionalFormatting/Rules/ExcelConditionalFormattingGreaterThan.cs b/src/EPPlus/ConditionalFormatting/Rules/ExcelConditionalFormattingGreaterThan.cs index 3d563652d6..4c6b153074 100644 --- a/src/EPPlus/ConditionalFormatting/Rules/ExcelConditionalFormattingGreaterThan.cs +++ b/src/EPPlus/ConditionalFormatting/Rules/ExcelConditionalFormattingGreaterThan.cs @@ -59,21 +59,26 @@ internal ExcelConditionalFormattingGreaterThan(ExcelConditionalFormattingGreater internal override bool ShouldApplyToCell(ExcelAddress address) { var cellValue = _ws.Cells[address.Address].Value; - if (cellValue != null && string.IsNullOrEmpty(Formula) == false) + if (string.IsNullOrEmpty(Formula) == false) { calculatedFormula1 = string.Format(_ws.Workbook.FormulaParserManager.Parse(GetCellFormula(address)).ToString(), CultureInfo.InvariantCulture); if(double.TryParse(calculatedFormula1, out double result)) { - if(cellValue.IsNumeric()) + if (cellValue == null || (cellValue is string s && s.Length == 0)) + { + return 0d > result; + } + if (cellValue.IsNumeric()) { return Convert.ToDouble(cellValue) > result; } } - else - { - var compareResult = string.Compare(calculatedFormula1, cellValue.ToString(), true); - return compareResult > 0; - } + + } + else if(cellValue != null) + { + var compareResult = string.Compare(calculatedFormula1, cellValue.ToString(), true); + return compareResult > 0; } return false; diff --git a/src/EPPlus/ConditionalFormatting/Rules/ExcelConditionalFormattingGreaterThanOrEqual.cs b/src/EPPlus/ConditionalFormatting/Rules/ExcelConditionalFormattingGreaterThanOrEqual.cs index 75a1c6a910..34bfe31bbc 100644 --- a/src/EPPlus/ConditionalFormatting/Rules/ExcelConditionalFormattingGreaterThanOrEqual.cs +++ b/src/EPPlus/ConditionalFormatting/Rules/ExcelConditionalFormattingGreaterThanOrEqual.cs @@ -63,21 +63,25 @@ internal override ExcelConditionalFormattingRule Clone(ExcelWorksheet newWs = nu internal override bool ShouldApplyToCell(ExcelAddress address) { var cellValue = _ws.Cells[address.Address].Value; - if (cellValue != null && string.IsNullOrEmpty(Formula) == false) + if (string.IsNullOrEmpty(Formula) == false) { calculatedFormula1 = string.Format(_ws.Workbook.FormulaParserManager.Parse(GetCellFormula(address)).ToString(), CultureInfo.InvariantCulture); if (double.TryParse(calculatedFormula1, out double result)) { + if (cellValue == null || (cellValue is string s && s.Length == 0)) + { + return 0d >= result; + } if (cellValue.IsNumeric()) { return Convert.ToDouble(cellValue) >= result; } } - else - { - var compareResult = string.Compare(calculatedFormula1, cellValue.ToString(), true); - return compareResult >= 0; - } + } + else if(cellValue != null) + { + var compareResult = string.Compare(calculatedFormula1, cellValue.ToString(), true); + return compareResult >= 0; } return false; diff --git a/src/EPPlus/ConditionalFormatting/Rules/ExcelConditionalFormattingLessThan.cs b/src/EPPlus/ConditionalFormatting/Rules/ExcelConditionalFormattingLessThan.cs index 92ecf173e1..2bb23cd750 100644 --- a/src/EPPlus/ConditionalFormatting/Rules/ExcelConditionalFormattingLessThan.cs +++ b/src/EPPlus/ConditionalFormatting/Rules/ExcelConditionalFormattingLessThan.cs @@ -49,24 +49,27 @@ internal override ExcelConditionalFormattingRule Clone(ExcelWorksheet newWs = nu internal override bool ShouldApplyToCell(ExcelAddress address) { var cellValue = _ws.Cells[address.Address].Value; - if (cellValue != null && string.IsNullOrEmpty(Formula) == false) + if (string.IsNullOrEmpty(Formula) == false) { calculatedFormula1 = string.Format(_ws.Workbook.FormulaParserManager.Parse(GetCellFormula(address)).ToString(), CultureInfo.InvariantCulture); if (double.TryParse(calculatedFormula1, out double result)) { + if (cellValue == null || (cellValue is string s && s.Length == 0)) + { + return 0d < result; + } if (cellValue.IsNumeric()) { return Convert.ToDouble(cellValue) < result; } } - else - { - var compareResult = string.Compare(calculatedFormula1, cellValue.ToString(), true); - return compareResult < 0; - } } - - return false; + else if (cellValue != null) + { + var compareResult = string.Compare(calculatedFormula1, cellValue.ToString(), true); + return compareResult < 0; + } + return false; } } } diff --git a/src/EPPlus/ConditionalFormatting/Rules/ExcelConditionalFormattingLessThanOrEqual.cs b/src/EPPlus/ConditionalFormatting/Rules/ExcelConditionalFormattingLessThanOrEqual.cs index 993d659685..ee0b8669fb 100644 --- a/src/EPPlus/ConditionalFormatting/Rules/ExcelConditionalFormattingLessThanOrEqual.cs +++ b/src/EPPlus/ConditionalFormatting/Rules/ExcelConditionalFormattingLessThanOrEqual.cs @@ -63,21 +63,25 @@ internal override ExcelConditionalFormattingRule Clone(ExcelWorksheet newWs = nu internal override bool ShouldApplyToCell(ExcelAddress address) { var cellValue = _ws.Cells[address.Address].Value; - if (cellValue != null && string.IsNullOrEmpty(Formula) == false) + if (string.IsNullOrEmpty(Formula) == false) { calculatedFormula1 = string.Format(_ws.Workbook.FormulaParserManager.Parse(GetCellFormula(address)).ToString(), CultureInfo.InvariantCulture); if (double.TryParse(calculatedFormula1, out double result)) { + if (cellValue == null || (cellValue is string s && s.Length == 0)) + { + return 0d <= result; + } if (cellValue.IsNumeric()) { return Convert.ToDouble(cellValue) <= result; } } - else - { - var compareResult = string.Compare(calculatedFormula1, cellValue.ToString(), true); - return compareResult <= 0; - } + } + else if (cellValue != null) + { + var compareResult = string.Compare(calculatedFormula1, cellValue.ToString(), true); + return compareResult <= 0; } return false; diff --git a/src/EPPlus/ConditionalFormatting/Rules/ExcelConditionalFormattingNotBetween.cs b/src/EPPlus/ConditionalFormatting/Rules/ExcelConditionalFormattingNotBetween.cs index 1e777a9181..94d8bf8ec6 100644 --- a/src/EPPlus/ConditionalFormatting/Rules/ExcelConditionalFormattingNotBetween.cs +++ b/src/EPPlus/ConditionalFormatting/Rules/ExcelConditionalFormattingNotBetween.cs @@ -79,9 +79,9 @@ internal override ExcelConditionalFormattingRule Clone(ExcelWorksheet newWs = nu internal override bool ShouldApplyToCell(ExcelAddress address) { var cellValue = _ws.Cells[address.Address].Value; - if (cellValue != null && string.IsNullOrEmpty(Formula) == false && string.IsNullOrEmpty(Formula2) == false) + if (string.IsNullOrEmpty(Formula) == false && string.IsNullOrEmpty(Formula2) == false) { - var str = cellValue.ToString(); + var str = cellValue == null ? string.Empty : cellValue.ToString(); calculatedFormula1 = string.Format(_ws.Workbook.FormulaParserManager.Parse(GetCellFormula(address), address.FullAddress, false).ToString(), CultureInfo.InvariantCulture); calculatedFormula2 = string.Format(_ws.Workbook.FormulaParserManager.Parse(GetCellFormula(address, true), address.FullAddress, false).ToString(), CultureInfo.InvariantCulture); @@ -90,6 +90,11 @@ internal override bool ShouldApplyToCell(ExcelAddress address) var Formula2IsNum = double.TryParse(calculatedFormula2, NumberStyles.Float, CultureInfo.InvariantCulture, out double num2); var cellValueIsNum = double.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out double numCellValue); + if (cellValue == null || str.Length == 0) + { + cellValueIsNum = true; + numCellValue = 0d; + } if (Formula1IsNum && Formula2IsNum) { if (cellValueIsNum) From ec6eeb29430d737a9b996f4529e8b4a5cfa3a0d3 Mon Sep 17 00:00:00 2001 From: swmal <897655+swmal@users.noreply.github.com> Date: Wed, 17 Jun 2026 16:25:15 +0200 Subject: [PATCH 33/49] Fixed patternfills and DimensionByVisibility --- src/EPPlus.Export.Pdf.Tests/PdfTests.cs | 7 +- src/EPPlus.Export.Pdf/ExcelPdf.cs | 12 +- .../PdfCatalog/PdfCatalog.cs | 2 +- .../PdfCatalog/PdfTextMap.cs | 2 +- .../PdfLayout/PdfCellLayout.cs | 2 +- .../PdfObjects/ExcelPatternMask.cs | 307 ++++++++++++++++++ .../PdfObjects/PdfContentStream.cs | 33 +- .../PdfPatterns/PdfPatternDarkDown.cs | 34 -- .../PdfPatterns/PdfPatternDarkGray.cs | 38 --- .../PdfPatterns/PdfPatternDarkGrid.cs | 38 --- .../PdfPatterns/PdfPatternDarkHorizontal.cs | 35 -- .../PdfPatterns/PdfPatternDarkTrellis.cs | 38 --- .../PdfPatterns/PdfPatternDarkUp.cs | 34 -- .../PdfPatterns/PdfPatternDarkVertical.cs | 35 -- .../PdfPatterns/PdfPatternGray0625.cs | 38 --- .../PdfPatterns/PdfPatternGray125.cs | 38 --- .../PdfPatterns/PdfPatternLightDown.cs | 40 --- .../PdfPatterns/PdfPatternLightGray.cs | 38 --- .../PdfPatterns/PdfPatternLightGrid.cs | 36 -- .../PdfPatterns/PdfPatternLightHorizontal.cs | 35 -- .../PdfPatterns/PdfPatternLightTrellis.cs | 41 --- .../PdfPatterns/PdfPatternLightUp.cs | 41 --- .../PdfPatterns/PdfPatternLightVertical.cs | 35 -- .../PdfPatterns/PdfPatternMaskFill.cs | 91 ++++++ .../PdfPatterns/PdfPatternMediumGray.cs | 38 --- .../PdfResources/PdfPatternResource.cs | 104 +++--- src/EPPlus/ExcelWorksheet.cs | 173 ++++++++-- 27 files changed, 639 insertions(+), 726 deletions(-) create mode 100644 src/EPPlus.Export.Pdf/PdfObjects/ExcelPatternMask.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkDown.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkGray.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkGrid.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkHorizontal.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkTrellis.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkUp.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkVertical.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternGray0625.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternGray125.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternLightDown.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternLightGray.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternLightGrid.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternLightHorizontal.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternLightTrellis.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternLightUp.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternLightVertical.cs create mode 100644 src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternMaskFill.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternMediumGray.cs diff --git a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs index 2b4ac7cf87..a363d2b21f 100644 --- a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs +++ b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs @@ -212,7 +212,7 @@ public void PerfTest() [TestMethod] // works as expected. - [DataRow("PDFTest.xlsx", "C:\\epplustest\\pdf\\FullPageTest56.pdf", "Sheet1")] + //[DataRow("PDFTest.xlsx", "C:\\epplustest\\pdf\\FullPageTest56.pdf", "Sheet1")] //[DataRow("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx", "C:\\epplustest\\pdf\\OutputTest1.1.pdf", "SAP Data")] // Output file: OutputTest1.2.pdf @@ -229,11 +229,14 @@ public void PerfTest() // 2. Pattern fills looks differnt, in some cases not working ----- // 3. Rotation of text in cells not working (the dates). ---------- // [DataRow("R05.xlsx", "C:\\epplustest\\pdf\\OutputTest1.6.pdf", "R05 Arbeitseinteilung")] - [DataRow("R05 - Copy.xlsx", "C:\\epplustest\\pdf\\OutputTest1.6.pdf", "R05 Arbeitseinteilung")] + //[DataRow("R05 - Copy.xlsx", "C:\\epplustest\\pdf\\OutputTest1.6.pdf", "R05 Arbeitseinteilung")] + [DataRow("PatternStyles.xlsx", "C:\\epplustest\\pdf\\OutputTest1.8.pdf", "Sheet1")] public void WorkbookTests(string sourceFile, string outputPath, string wsName) { using var p = OpenTemplatePackage(sourceFile); var ws = p.Workbook.Worksheets[wsName]; + var d = ws.Dimension; + var d2 = ws.DimensionByValue; PdfPageSettings pageSettings = new PdfPageSettings(); pageSettings.CommentsAndNotes = CommentsAndNotes.AtEndOfSheet; diff --git a/src/EPPlus.Export.Pdf/ExcelPdf.cs b/src/EPPlus.Export.Pdf/ExcelPdf.cs index 0690942f18..51aac792af 100644 --- a/src/EPPlus.Export.Pdf/ExcelPdf.cs +++ b/src/EPPlus.Export.Pdf/ExcelPdf.cs @@ -95,10 +95,11 @@ public ExcelPdf(ExcelRangeBase Range, PdfPageSettings pageSettings = null) //Get the label to use for pattern. internal string GetPatternLabel(PdfCellLayout layout) { - if ((layout.CellFillData.PatternStyle != ExcelFillStyle.Solid && layout.CellFillData.PatternStyle != ExcelFillStyle.None) || layout.CellFillData.GradientFillData != null) + bool isPattern = (layout.CellFillData.PatternStyle != ExcelFillStyle.Solid && layout.CellFillData.PatternStyle != ExcelFillStyle.None) || layout.CellFillData.GradientFillData != null; + if (isPattern) { var patternName = layout.CellFillData.id; - if (_dictionaries.Patterns.ContainsKey(patternName)) + if (patternName != null && _dictionaries.Patterns.ContainsKey(patternName)) { return _dictionaries.Patterns[patternName].Label; } @@ -185,12 +186,13 @@ private PdfObjects.PdfCatalog AddCatalog(int pagesObjectNumber) //Create Content private void AddContent(Transform pageLayout, PdfPage page) { - //var cells = pageLayout.ChildObjects.Where(t => t is PdfCellLayout || t is PdfCellContentLayout || t is PdfCellBorderLayout).GroupBy(t => t.Name); var cells = pageLayout.ChildObjects.Where(t => - (t is PdfCellLayout || t is PdfCellContentLayout || t is PdfCellBorderLayout) && - !(t is PdfCellLayout cc && (cc.IsHeading || cc.IsPrintTitle)) && + (t is PdfCellLayout || t is PdfCellContentLayout || t is PdfCellBorderLayout) && + !(t is PdfCellLayout cc && (cc.IsHeading || cc.IsPrintTitle)) && !(t is PdfCellContentLayout ccl && (ccl.IsHeaderFooter || ccl.IsHeading || ccl.IsPrintTitle)) && !(t is PdfCellBorderLayout cbl && cbl.IsPrintTitle)).GroupBy(t => t.Name); + + var headerFooterLayouts = pageLayout.ChildObjects.OfType().Where(t => t.IsHeaderFooter); 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)); diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs index 1f7b43c73c..bae9a1d7e7 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs @@ -227,7 +227,7 @@ private List GetRanges(ExcelWorksheet worksheet) } else { - var range = worksheet.DimensionByValue; + var range = worksheet.DimensionByVisibility; var pdfRange = new PdfRange(range, true); pdfRange.ExtendColumns = true; ranges.Add(pdfRange); diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs index 016b1abec6..41d7586f5e 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs @@ -659,7 +659,7 @@ private static List GetTextFragmentsFromCellStyle(PdfPageSettings textFrag.RichTextOptions.StrikeType = (font.Strike || dxfStrike) ? 2 : 1; textFrag.RichTextOptions.SuperScript = font.VerticalAlign == ExcelVerticalAlignmentFont.Superscript; textFrag.RichTextOptions.SubScript = font.VerticalAlign == ExcelVerticalAlignmentFont.Subscript; - textFrag.RichTextOptions.FontColor = = dxfColor ?? font.Color.ToColor(); + textFrag.RichTextOptions.FontColor = dxfColor ?? font.Color.ToColor(); textFrag.Font.SubFamily = ComputeFontStyle(textFrag); diff --git a/src/EPPlus.Export.Pdf/PdfLayout/PdfCellLayout.cs b/src/EPPlus.Export.Pdf/PdfLayout/PdfCellLayout.cs index 33cdc79b5b..f8570a0b9b 100644 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfCellLayout.cs +++ b/src/EPPlus.Export.Pdf/PdfLayout/PdfCellLayout.cs @@ -114,7 +114,7 @@ public PdfCellLayout(PdfDictionaries dictionaries, PdfCellStyle CellStyle, doubl public PdfCellLayout(PdfDictionaries dictionaries, ExcelRangeBase cell, PdfCellStyle CellStyle, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) - : base(x, y-height, width, height, scaleX, scaleY, rotation, parent) + : base(x, y - height, width, height, scaleX, scaleY, rotation, parent) { if (cell != null) { diff --git a/src/EPPlus.Export.Pdf/PdfObjects/ExcelPatternMask.cs b/src/EPPlus.Export.Pdf/PdfObjects/ExcelPatternMask.cs new file mode 100644 index 0000000000..38cda15796 --- /dev/null +++ b/src/EPPlus.Export.Pdf/PdfObjects/ExcelPatternMask.cs @@ -0,0 +1,307 @@ +/************************************************************************************************* + 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 System.Collections.Generic; + +namespace EPPlus.Export.Pdf.PdfObjects.PdfPatterns +{ + /// + /// The cell fill pattern types EPPlus renders in the PDF export, matching + /// the names of OfficeOpenXml.Style.ExcelFillStyle. The pattern geometry is + /// taken from how Microsoft Excel itself rasterises each pattern when it + /// exports to PDF (an 8x8 tile), NOT from the ECMA-376 ST_Shd masks. Excel + /// and ST_Shd only overlap for a few patterns; since the goal is visual + /// parity with Excel, Excel's own output is the reference. + /// + internal enum ExcelPatternMask + { + DarkGray, + MediumGray, + LightGray, + Gray125, + Gray0625, + DarkHorizontal, + DarkVertical, + DarkDown, + DarkUp, + DarkGrid, + DarkTrellis, + LightHorizontal, + LightVertical, + LightDown, + LightUp, + LightGrid, + LightTrellis, + } + + /// + /// Reference catalog of the 8x8 cell fill pattern masks, transcribed from the + /// bitmaps Microsoft Excel produces when exporting each pattern fill to PDF. + /// + /// IMPORTANT - polarity and orientation: + /// + /// byte 1 == background == the cell fill background color. + /// byte 0 == foreground == the pattern color. + /// + /// So it is the 0 cells that get painted with the foreground/pattern color, + /// matching the convention used by the ECMA ST_Shd reference data. + /// + /// Row order matches the source bitmap exactly: row 0 is the TOP row. PDF + /// content streams have the origin at the bottom-left with y increasing + /// upward, so any comparison against rendered PDF output (or generation of a + /// PDF content stream) must mirror in y (row r maps to PDF y = 7 - r). That + /// y-flip is intentionally NOT applied here - the data is kept in bitmap + /// orientation and the flip is the responsibility of the render/diff step. + /// + internal static class ExcelPatternMaskData + { + private static readonly Dictionary _masks = BuildMasks(); + + /// + /// Gets the 8x8 reference mask for the given pattern. + /// Indexed as [row, column] with row 0 = top, matching the source bitmap. + /// + /// The pattern to look up. + /// An 8x8 matrix where 1 = background and 0 = foreground. + public static byte[,] GetMask(ExcelPatternMask pattern) + { + return _masks[pattern]; + } + + private static Dictionary BuildMasks() + { + var masks = new Dictionary(); + + // DarkGray (75 gray) + masks.Add(ExcelPatternMask.DarkGray, new byte[,] + { + { 0, 1, 0, 1, 0, 1, 0, 1 }, + { 1, 0, 1, 0, 1, 0, 1, 0 }, + { 0, 1, 0, 1, 0, 1, 0, 1 }, + { 1, 0, 1, 0, 1, 0, 1, 0 }, + { 0, 1, 0, 1, 0, 1, 0, 1 }, + { 1, 0, 1, 0, 1, 0, 1, 0 }, + { 0, 1, 0, 1, 0, 1, 0, 1 }, + { 1, 0, 1, 0, 1, 0, 1, 0 }, + }); + + // MediumGray (50 gray) + masks.Add(ExcelPatternMask.MediumGray, new byte[,] + { + { 0, 1, 1, 1, 0, 1, 1, 1 }, + { 1, 1, 0, 1, 1, 1, 0, 1 }, + { 0, 1, 1, 1, 0, 1, 1, 1 }, + { 1, 1, 0, 1, 1, 1, 0, 1 }, + { 0, 1, 1, 1, 0, 1, 1, 1 }, + { 1, 1, 0, 1, 1, 1, 0, 1 }, + { 0, 1, 1, 1, 0, 1, 1, 1 }, + { 1, 1, 0, 1, 1, 1, 0, 1 }, + }); + + // LightGray (25 gray) + masks.Add(ExcelPatternMask.LightGray, new byte[,] + { + { 0, 1, 1, 1, 0, 1, 1, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 1 }, + { 1, 1, 0, 1, 1, 1, 0, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 1 }, + { 0, 1, 1, 1, 0, 1, 1, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 1 }, + { 1, 1, 0, 1, 1, 1, 0, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 1 }, + }); + + // Gray125 (12,5 gray) + masks.Add(ExcelPatternMask.Gray125, new byte[,] + { + { 0, 1, 1, 1, 1, 1, 1, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 1 }, + { 1, 1, 1, 1, 0, 1, 1, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 1 }, + { 0, 1, 1, 1, 1, 1, 1, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 1 }, + { 1, 1, 1, 1, 0, 1, 1, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 1 }, + }); + + // Gray0625 (6,25 gray) + masks.Add(ExcelPatternMask.Gray0625, new byte[,] + { + { 0, 1, 1, 1, 1, 1, 1, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 1 }, + { 1, 1, 1, 1, 0, 1, 1, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 1 }, + }); + + // DarkHorizontal (Horizontal stripe) + masks.Add(ExcelPatternMask.DarkHorizontal, new byte[,] + { + { 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0 }, + { 1, 1, 1, 1, 1, 1, 1, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 1 }, + }); + + // DarkVertical (Vertical stripe) + masks.Add(ExcelPatternMask.DarkVertical, new byte[,] + { + { 0, 0, 0, 0, 1, 1, 1, 1 }, + { 0, 0, 0, 0, 1, 1, 1, 1 }, + { 0, 0, 0, 0, 1, 1, 1, 1 }, + { 0, 0, 0, 0, 1, 1, 1, 1 }, + { 0, 0, 0, 0, 1, 1, 1, 1 }, + { 0, 0, 0, 0, 1, 1, 1, 1 }, + { 0, 0, 0, 0, 1, 1, 1, 1 }, + { 0, 0, 0, 0, 1, 1, 1, 1 }, + }); + + // DarkDown (Reverse diagonal stripe) + masks.Add(ExcelPatternMask.DarkDown, new byte[,] + { + { 0, 0, 0, 0, 1, 1, 1, 1 }, + { 1, 0, 0, 0, 0, 1, 1, 1 }, + { 1, 1, 0, 0, 0, 0, 1, 1 }, + { 1, 1, 1, 0, 0, 0, 0, 1 }, + { 1, 1, 1, 1, 0, 0, 0, 0 }, + { 0, 1, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 1, 1, 1, 1, 0, 0 }, + { 0, 0, 0, 1, 1, 1, 1, 0 }, + }); + + // DarkUp (Diagonal stripe) + masks.Add(ExcelPatternMask.DarkUp, new byte[,] + { + { 1, 1, 0, 0, 0, 0, 1, 1 }, + { 1, 0, 0, 0, 0, 1, 1, 1 }, + { 0, 0, 0, 0, 1, 1, 1, 1 }, + { 0, 0, 0, 1, 1, 1, 1, 0 }, + { 0, 0, 1, 1, 1, 1, 0, 0 }, + { 0, 1, 1, 1, 1, 0, 0, 0 }, + { 1, 1, 1, 1, 0, 0, 0, 0 }, + { 1, 1, 1, 0, 0, 0, 0, 1 }, + }); + + // DarkGrid (Diagonal crosshatch) + masks.Add(ExcelPatternMask.DarkGrid, new byte[,] + { + { 0, 0, 0, 0, 1, 1, 1, 1 }, + { 0, 0, 0, 0, 1, 1, 1, 1 }, + { 0, 0, 0, 0, 1, 1, 1, 1 }, + { 0, 0, 0, 0, 1, 1, 1, 1 }, + { 1, 1, 1, 1, 0, 0, 0, 0 }, + { 1, 1, 1, 1, 0, 0, 0, 0 }, + { 1, 1, 1, 1, 0, 0, 0, 0 }, + { 1, 1, 1, 1, 0, 0, 0, 0 }, + }); + + // DarkTrellis (Thick diagonal crosshatch) + masks.Add(ExcelPatternMask.DarkTrellis, new byte[,] + { + { 0, 0, 0, 0, 0, 0, 0, 0 }, + { 1, 0, 0, 0, 0, 0, 0, 1 }, + { 1, 1, 0, 0, 0, 0, 1, 1 }, + { 1, 0, 0, 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 1, 1, 0, 0, 0 }, + { 0, 0, 1, 1, 1, 1, 0, 0 }, + { 0, 0, 0, 1, 1, 0, 0, 0 }, + }); + + // LightHorizontal (Thin horizontal stripe) + masks.Add(ExcelPatternMask.LightHorizontal, new byte[,] + { + { 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0 }, + { 1, 1, 1, 1, 1, 1, 1, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 1 }, + }); + + // LightVertical (Thin vertical stripe) + masks.Add(ExcelPatternMask.LightVertical, new byte[,] + { + { 0, 0, 1, 1, 1, 1, 1, 1 }, + { 0, 0, 1, 1, 1, 1, 1, 1 }, + { 0, 0, 1, 1, 1, 1, 1, 1 }, + { 0, 0, 1, 1, 1, 1, 1, 1 }, + { 0, 0, 1, 1, 1, 1, 1, 1 }, + { 0, 0, 1, 1, 1, 1, 1, 1 }, + { 0, 0, 1, 1, 1, 1, 1, 1 }, + { 0, 0, 1, 1, 1, 1, 1, 1 }, + }); + + // LightDown (Thin reverse diagonal stripe) + masks.Add(ExcelPatternMask.LightDown, new byte[,] + { + { 0, 0, 1, 1, 1, 1, 1, 1 }, + { 1, 0, 0, 1, 1, 1, 1, 1 }, + { 1, 1, 0, 0, 1, 1, 1, 1 }, + { 1, 1, 1, 0, 0, 1, 1, 1 }, + { 1, 1, 1, 1, 0, 0, 1, 1 }, + { 1, 1, 1, 1, 1, 0, 0, 1 }, + { 1, 1, 1, 1, 1, 1, 0, 0 }, + { 0, 1, 1, 1, 1, 1, 1, 0 }, + }); + + // LightUp (Thin diagonal stripe) + masks.Add(ExcelPatternMask.LightUp, new byte[,] + { + { 1, 1, 1, 0, 0, 1, 1, 1 }, + { 1, 1, 0, 0, 1, 1, 1, 1 }, + { 1, 0, 0, 1, 1, 1, 1, 1 }, + { 0, 0, 1, 1, 1, 1, 1, 1 }, + { 0, 1, 1, 1, 1, 1, 1, 0 }, + { 1, 1, 1, 1, 1, 1, 0, 0 }, + { 1, 1, 1, 1, 1, 0, 0, 1 }, + { 1, 1, 1, 1, 0, 0, 1, 1 }, + }); + + // LightGrid (Thin horizontal crosshatch) + masks.Add(ExcelPatternMask.LightGrid, new byte[,] + { + { 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 1, 1, 1, 1, 1, 1 }, + { 0, 0, 1, 1, 1, 1, 1, 1 }, + { 0, 0, 1, 1, 1, 1, 1, 1 }, + { 0, 0, 1, 1, 1, 1, 1, 1 }, + { 0, 0, 1, 1, 1, 1, 1, 1 }, + { 0, 0, 1, 1, 1, 1, 1, 1 }, + }); + + // LightTrellis (Thin diagonal crosshatch) + masks.Add(ExcelPatternMask.LightTrellis, new byte[,] + { + { 0, 0, 1, 0, 0, 1, 1, 1 }, + { 1, 0, 0, 0, 1, 1, 1, 1 }, + { 1, 0, 0, 0, 1, 1, 1, 1 }, + { 0, 0, 1, 0, 0, 1, 1, 1 }, + { 0, 1, 1, 1, 0, 0, 1, 0 }, + { 1, 1, 1, 1, 1, 0, 0, 0 }, + { 1, 1, 1, 1, 1, 0, 0, 0 }, + { 0, 1, 1, 1, 0, 0, 1, 0 }, + }); + return masks; + } + } +} \ No newline at end of file diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs b/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs index ec02e53220..e2a1f9af1b 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs +++ b/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs @@ -73,23 +73,32 @@ public void AddCellLayout(PdfCellLayout cell, string label) commands.Add("Q"); commands.Add($"% Solid Fill End: {cell.Name}"); } - else if (cell.CellFillData.BackgroundColor != Color.Empty && cell.CellFillData.PatternStyle != ExcelFillStyle.None) + else if (cell.CellFillData.PatternStyle != ExcelFillStyle.None) { - commands.Add($"% Gradient Start: {cell.Name}"); - commands.Add("q"); - commands.Add($"{GridLine.HalfWidth.ToPdfString()} w"); - commands.Add(cell.CellFillData.BackgroundColor.ToFillCommand()); - commands.Add(cell.CellFillData.BackgroundColor.ToStrokeCommand()); - commands.Add($"{cell.LocalPosition.X.ToPdfString()} {cell.LocalPosition.Y.ToPdfString()} {cell.Size.X.ToPdfString()} {cell.Size.Y.ToPdfString()} re"); - commands.Add("B"); - commands.Add("Q"); + commands.Add($"% Pattern Start: {cell.Name}"); + + // Draw the solid cell background only when one is set. The pattern + // tile already fills itself with its own background color, so the + // pattern must be rendered regardless of whether the cell has a + // separate background fill (it may be Color.Empty). + if (cell.CellFillData.BackgroundColor != Color.Empty) + { + commands.Add("q"); + commands.Add($"{GridLine.HalfWidth.ToPdfString()} w"); + commands.Add(cell.CellFillData.BackgroundColor.ToFillCommand()); + commands.Add(cell.CellFillData.BackgroundColor.ToStrokeCommand()); + commands.Add($"{cell.LocalPosition.X.ToPdfString()} {cell.LocalPosition.Y.ToPdfString()} {cell.Size.X.ToPdfString()} {cell.Size.Y.ToPdfString()} re"); + commands.Add("B"); + commands.Add("Q"); + } + commands.Add("q"); commands.Add("/Pattern cs"); commands.Add($"/{label} scn"); commands.Add($"{cell.LocalPosition.X.ToPdfString()} {cell.LocalPosition.Y.ToPdfString()} {cell.Size.X.ToPdfString()} {cell.Size.Y.ToPdfString()} re"); commands.Add("f"); commands.Add("Q"); - commands.Add($"% Gradient End: {cell.Name}"); + commands.Add($"% Pattern End: {cell.Name}"); } } @@ -200,7 +209,7 @@ public void AddText(PdfCellContentLayout cell, Vector2 position, double textRota glyphStart = 0; } - + var richInfo = originalFragment.RichTextOptions; var textLength = shapedText.ShapedText.GetWidthInPoints((float)richInfo.Size); @@ -638,4 +647,4 @@ private string FixEscapeCharacters(string text) return text.Replace(@"\", @"\\").Replace(@"(", @"\(").Replace(@")", @"\)"); } } -} +} \ No newline at end of file diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkDown.cs b/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkDown.cs deleted file mode 100644 index df5a66d5e9..0000000000 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkDown.cs +++ /dev/null @@ -1,34 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.Pdfhelpers; -using System.Drawing; -using System.Text; - -namespace EPPlus.Export.Pdf.PdfObjects.PdfPatterns -{ - internal class PdfPatternDarkDown : PdfPatternFill - { - public PdfPatternDarkDown(Color foreground, Color background) : base(foreground, background) { } - - public override string CreatePatternResource() - { - var sb = new StringBuilder(); - sb.AppendFormat($"{Foreground.ToStrokeCommand()}\n" + - $"1.4 w\n" + - $"0 11.3137 m\n" + - $"11.3137 0 l\n" + - $"S"); - return sb.ToString(); - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkGray.cs b/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkGray.cs deleted file mode 100644 index 7ca5f77ad0..0000000000 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkGray.cs +++ /dev/null @@ -1,38 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.Pdfhelpers; -using System.Drawing; -using System.Text; - -namespace EPPlus.Export.Pdf.PdfObjects.PdfPatterns -{ - internal class PdfPatternDarkGray : PdfPatternFill - { - public PdfPatternDarkGray(Color foreground, Color background) : base(foreground, background) { } - - public override string CreatePatternResource() - { - var sb = new StringBuilder(); - sb.AppendFormat($"{Background.ToFillCommand()}\n" + - $"0 0 4 2 re\n" + - $"f\n" + - $"{Foreground.ToFillCommand()}\n" + - $"0 1 1 1 re\n" + - $"f\n" + - $"{Foreground.ToFillCommand()}\n" + - $"2 0 1 1 re\n" + - $"f"); - return sb.ToString(); - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkGrid.cs b/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkGrid.cs deleted file mode 100644 index f3ebc0ffff..0000000000 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkGrid.cs +++ /dev/null @@ -1,38 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.Pdfhelpers; -using System.Drawing; -using System.Text; - -namespace EPPlus.Export.Pdf.PdfObjects.PdfPatterns -{ - internal class PdfPatternDarkGrid : PdfPatternFill - { - public PdfPatternDarkGrid(Color foreground, Color background) : base(foreground, background) { } - - public override string CreatePatternResource() - { - var sb = new StringBuilder(); - sb.AppendFormat($"{Background.ToFillCommand()}\n" + - $"0 0 2 2 re\n" + - $"f\n" + - $"{Foreground.ToFillCommand()}\n" + - $"0 0 1 1 re\n" + - $"f\n" + - $"{Foreground.ToFillCommand()}\n" + - $"1 1 1 1 re\n" + - $"f"); - return sb.ToString(); - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkHorizontal.cs b/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkHorizontal.cs deleted file mode 100644 index 1f3d5f7744..0000000000 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkHorizontal.cs +++ /dev/null @@ -1,35 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.Pdfhelpers; -using System.Drawing; -using System.Text; - -namespace EPPlus.Export.Pdf.PdfObjects.PdfPatterns -{ - internal class PdfPatternDarkHorizontal : PdfPatternFill - { - public PdfPatternDarkHorizontal(Color foreground, Color background) : base(foreground, background) { } - - public override string CreatePatternResource() - { - var sb = new StringBuilder(); - sb.AppendFormat($"{Background.ToFillCommand()}\n" + - $"0 0 1 2 re\n" + - $"f\n" + - $"{Foreground.ToFillCommand()}\n" + - $"0 0 1 1 re\n" + - $"f"); - return sb.ToString(); - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkTrellis.cs b/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkTrellis.cs deleted file mode 100644 index 0c1fcd6632..0000000000 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkTrellis.cs +++ /dev/null @@ -1,38 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.Pdfhelpers; -using System.Drawing; -using System.Text; - -namespace EPPlus.Export.Pdf.PdfObjects.PdfPatterns -{ - internal class PdfPatternDarkTrellis : PdfPatternFill - { - public PdfPatternDarkTrellis(Color foreground, Color background) : base(foreground, background) { } - - public override string CreatePatternResource() - { - var sb = new StringBuilder(); - sb.AppendFormat($"{Background.ToFillCommand()}\n" + - $"0 0 4 4 re\n" + - $"f\n" + - $"{Foreground.ToFillCommand()}\n" + - $"2 1 2 1 re\n" + - $"f\n" + - $"{Foreground.ToFillCommand()}\n" + - $"0 3 2 1 re\n" + - $"f"); - return sb.ToString(); - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkUp.cs b/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkUp.cs deleted file mode 100644 index d3567955c3..0000000000 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkUp.cs +++ /dev/null @@ -1,34 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.Pdfhelpers; -using System.Drawing; -using System.Text; - -namespace EPPlus.Export.Pdf.PdfObjects.PdfPatterns -{ - internal class PdfPatternDarkUp : PdfPatternFill - { - public PdfPatternDarkUp(Color foreground, Color background) : base(foreground, background) { } - - public override string CreatePatternResource() - { - var sb = new StringBuilder(); - sb.AppendFormat($"{Foreground.ToStrokeCommand()}\n" + - $"1.4 w\n" + - $"11.3137 0 m\n" + - $"0 11.3137 l\n" + - $"S"); - return sb.ToString(); - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkVertical.cs b/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkVertical.cs deleted file mode 100644 index 3256509c31..0000000000 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkVertical.cs +++ /dev/null @@ -1,35 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.Pdfhelpers; -using System.Drawing; -using System.Text; - -namespace EPPlus.Export.Pdf.PdfObjects.PdfPatterns -{ - internal class PdfPatternDarkVertical : PdfPatternFill - { - public PdfPatternDarkVertical(Color foreground, Color background) : base(foreground, background) { } - - public override string CreatePatternResource() - { - var sb = new StringBuilder(); - sb.AppendFormat($"{Background.ToFillCommand()}\n" + - $"0 0 2 1 re\n" + - $"f\n" + - $"{Foreground.ToFillCommand()}\n" + - $"0 0 1 1 re\n" + - $"f"); - return sb.ToString(); - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternGray0625.cs b/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternGray0625.cs deleted file mode 100644 index 4bca86e8eb..0000000000 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternGray0625.cs +++ /dev/null @@ -1,38 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.Pdfhelpers; -using System.Drawing; -using System.Text; - -namespace EPPlus.Export.Pdf.PdfObjects.PdfPatterns -{ - internal class PdfPatternGray0625 : PdfPatternFill - { - public PdfPatternGray0625(Color foreground, Color background) : base(foreground, background) { } - - public override string CreatePatternResource() - { - var sb = new StringBuilder(); - sb.AppendFormat($"{Background.ToFillCommand()}\n" + - $"0 0 8 4 re\n" + - $"f\n" + - $"{Foreground.ToFillCommand()}\n" + - $"0 3 1 1 re\n" + - $"f\n" + - $"{Foreground.ToFillCommand()}\n" + - $"4 1 1 1 re\n" + - $"f"); - return sb.ToString(); - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternGray125.cs b/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternGray125.cs deleted file mode 100644 index 6de27fec75..0000000000 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternGray125.cs +++ /dev/null @@ -1,38 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.Pdfhelpers; -using System.Drawing; -using System.Text; - -namespace EPPlus.Export.Pdf.PdfObjects.PdfPatterns -{ - internal class PdfPatternGray125 : PdfPatternFill - { - public PdfPatternGray125(Color foreground, Color background) : base(foreground, background) { } - - public override string CreatePatternResource() - { - var sb = new StringBuilder(); - sb.AppendFormat($"{Background.ToFillCommand()}\n" + - $"0 0 4 4 re\n" + - $"f\n" + - $"{Foreground.ToFillCommand()}\n" + - $"1 0 1 1 re\n" + - $"f\n" + - $"{Foreground.ToFillCommand()}\n" + - $"3 2 1 1 re\n" + - $"f"); - return sb.ToString(); - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternLightDown.cs b/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternLightDown.cs deleted file mode 100644 index b16be4bae6..0000000000 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternLightDown.cs +++ /dev/null @@ -1,40 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.Pdfhelpers; -using System.Drawing; -using System.Text; - -namespace EPPlus.Export.Pdf.PdfObjects.PdfPatterns -{ - internal class PdfPatternLightDown : PdfPatternFill - { - public PdfPatternLightDown(Color foreground, Color background) : base(foreground, background) { } - - public override string CreatePatternResource() - { - var sb = new StringBuilder(); - sb.AppendFormat($"{Background.ToFillCommand()}\n" + - $"0 0 4 4 re\n" + - $"f\n" + - $"{Foreground.ToFillCommand()}\n" + - $"0 0 3 1 re\n" + - $"0 1 2 1 re\n" + - $"3 1 1 1 re\n" + - $"0 2 1 1 re\n" + - $"2 2 2 1 re\n" + - $"1 3 3 1 re\n" + - $"f"); - return sb.ToString(); - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternLightGray.cs b/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternLightGray.cs deleted file mode 100644 index 90b6e5eb50..0000000000 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternLightGray.cs +++ /dev/null @@ -1,38 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.Pdfhelpers; -using System.Drawing; -using System.Text; - -namespace EPPlus.Export.Pdf.PdfObjects.PdfPatterns -{ - internal class PdfPatternLightGray : PdfPatternFill - { - public PdfPatternLightGray(Color foreground, Color background) : base(foreground, background) { } - - public override string CreatePatternResource() - { - var sb = new StringBuilder(); - sb.AppendFormat($"{Background.ToFillCommand()}\n" + - $"0 0 4 2 re\n" + - $"f\n" + - $"{Foreground.ToFillCommand()}\n" + - $"0 1 1 1 re\n" + - $"f\n" + - $"{Foreground.ToFillCommand()}\n" + - $"2 0 1 1 re\n" + - $"f"); - return sb.ToString(); - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternLightGrid.cs b/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternLightGrid.cs deleted file mode 100644 index 1f2f25b2a5..0000000000 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternLightGrid.cs +++ /dev/null @@ -1,36 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.Pdfhelpers; -using System.Drawing; -using System.Text; - -namespace EPPlus.Export.Pdf.PdfObjects.PdfPatterns -{ - internal class PdfPatternLightGrid : PdfPatternFill - { - public PdfPatternLightGrid(Color foreground, Color background) : base(foreground, background) { } - - public override string CreatePatternResource() - { - var sb = new StringBuilder(); - sb.AppendFormat($"{Background.ToFillCommand()}\n" + - $"0 0 4 4 re\n" + - $"f\n" + - $"{Foreground.ToFillCommand()}\n" + - $"0 0 4 1 re\n" + - $"3 0 1 4 re\n" + - $"f"); - return sb.ToString(); - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternLightHorizontal.cs b/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternLightHorizontal.cs deleted file mode 100644 index 7765f5ccf2..0000000000 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternLightHorizontal.cs +++ /dev/null @@ -1,35 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.Pdfhelpers; -using System.Drawing; -using System.Text; - -namespace EPPlus.Export.Pdf.PdfObjects.PdfPatterns -{ - internal class PdfPatternLightHorizontal : PdfPatternFill - { - public PdfPatternLightHorizontal(Color foreground, Color background) : base(foreground, background) { } - - public override string CreatePatternResource() - { - var sb = new StringBuilder(); - sb.AppendFormat($"{Background.ToFillCommand()}\n" + - $"0 0 0.5 1 re\n" + - $"f\n" + - $"{Foreground.ToFillCommand()}\n" + - $"0 0 0.5 0.5 re\n" + - $"f"); - return sb.ToString(); - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternLightTrellis.cs b/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternLightTrellis.cs deleted file mode 100644 index 19e671b051..0000000000 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternLightTrellis.cs +++ /dev/null @@ -1,41 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.Pdfhelpers; -using System.Drawing; -using System.Text; - -namespace EPPlus.Export.Pdf.PdfObjects.PdfPatterns -{ - internal class PdfPatternLightTrellis : PdfPatternFill - { - public PdfPatternLightTrellis(Color foreground, Color background) : base(foreground, background) { } - - public override string CreatePatternResource() - { - var sb = new StringBuilder(); - sb.AppendFormat($"{Background.ToFillCommand()}\n" + - $"0 0 4 4 re\n" + - $"f\n" + - $"{Foreground.ToFillCommand()}\n" + - $"0 0 3 1 re\n" + - $"1 1 1 1 re\n" + - $"3 1 1 1 re\n" + - $"0 2 1 1 re\n" + - $"2 2 2 1 re\n" + - $"1 3 1 1 re\n" + - $"3 3 1 1 re\n" + - $"f"); - return sb.ToString(); - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternLightUp.cs b/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternLightUp.cs deleted file mode 100644 index 832a62da92..0000000000 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternLightUp.cs +++ /dev/null @@ -1,41 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.Pdfhelpers; -using System.Drawing; - -using System.Text; - -namespace EPPlus.Export.Pdf.PdfObjects.PdfPatterns -{ - internal class PdfPatternLightUp : PdfPatternFill - { - public PdfPatternLightUp(Color foreground, Color background) : base(foreground, background) { } - - public override string CreatePatternResource() - { - var sb = new StringBuilder(); - sb.AppendFormat($"{Background.ToFillCommand()}\n" + - $"0 0 4 4 re\n" + - $"f\n" + - $"{Foreground.ToFillCommand()}\n" + - $"1 0 3 1 re\n" + - $"0 1 1 1 re\n" + - $"2 1 2 1 re\n" + - $"0 2 2 1 re\n" + - $"3 2 1 1 re\n" + - $"0 3 3 1 re\n" + - $"f"); - return sb.ToString(); - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternLightVertical.cs b/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternLightVertical.cs deleted file mode 100644 index 2d01d63706..0000000000 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternLightVertical.cs +++ /dev/null @@ -1,35 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.Pdfhelpers; -using System.Drawing; -using System.Text; - -namespace EPPlus.Export.Pdf.PdfObjects.PdfPatterns -{ - internal class PdfPatternLightVertical : PdfPatternFill - { - public PdfPatternLightVertical(Color foreground, Color background) : base(foreground, background) { } - - public override string CreatePatternResource() - { - var sb = new StringBuilder(); - sb.AppendFormat($"{Background.ToFillCommand()}\n" + - $"0 0 1 0.5 re\n" + - $"f\n" + - $"{Foreground.ToFillCommand()}\n" + - $"0 0 0.5 0.5 re\n" + - $"f"); - return sb.ToString(); - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternMaskFill.cs b/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternMaskFill.cs new file mode 100644 index 0000000000..01bfc4d3cc --- /dev/null +++ b/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternMaskFill.cs @@ -0,0 +1,91 @@ +/************************************************************************************************* + 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.Export.Pdf.Pdfhelpers; +using System.Drawing; +using System.Text; + +namespace EPPlus.Export.Pdf.PdfObjects.PdfPatterns +{ + /// + /// Renders a cell fill pattern from an 8x8 . + /// Replaces the former per-pattern PdfPatternFill subclasses: the geometry now + /// comes from a single verified mask catalog (taken from Excel's own PDF output) + /// instead of hand-coded rectangle coordinates. + /// + /// The whole 8x8 tile is filled with the Background color, then a rectangle is + /// drawn with the Foreground color for every foreground cell of the mask. + /// + /// The mask stores row 0 as the TOP row, while a PDF content stream has its + /// origin at the bottom-left with y increasing upward. The mask row r is + /// therefore emitted at PDF y = 7 - r (a mirror in y). Horizontally adjacent + /// foreground cells on the same row are merged into a single wider rectangle + /// to keep the content stream small. + /// + internal class PdfPatternMaskFill : PdfPatternFill + { + private readonly byte[,] _mask; + + public PdfPatternMaskFill(ExcelPatternMask pattern, Color foreground, Color background) + : base(foreground, background) + { + _mask = ExcelPatternMaskData.GetMask(pattern); + } + + public override string CreatePatternResource() + { + const int size = 8; + var sb = new StringBuilder(); + + // Fill the whole tile with the background color. + sb.Append(Background.ToFillCommand()); + sb.Append("\n0 0 "); + sb.Append(size.ToString()); + sb.Append(" "); + sb.Append(size.ToString()); + sb.Append(" re\nf\n"); + + // Draw foreground rectangles. mask value 0 == foreground. + sb.Append(Foreground.ToFillCommand()); + sb.Append("\n"); + for (int row = 0; row < size; row++) + { + int pdfY = (size - 1) - row; // mirror in y + int col = 0; + while (col < size) + { + if (_mask[row, col] == 0) + { + int start = col; + while (col < size && _mask[row, col] == 0) + { + col++; + } + int width = col - start; + sb.Append(start.ToString()); + sb.Append(" "); + sb.Append(pdfY.ToString()); + sb.Append(" "); + sb.Append(width.ToString()); + sb.Append(" 1 re\n"); + } + else + { + col++; + } + } + } + sb.Append("f"); + return sb.ToString(); + } + } +} \ No newline at end of file diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternMediumGray.cs b/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternMediumGray.cs deleted file mode 100644 index c808fc3c25..0000000000 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternMediumGray.cs +++ /dev/null @@ -1,38 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.Pdfhelpers; -using System.Drawing; -using System.Text; - -namespace EPPlus.Export.Pdf.PdfObjects.PdfPatterns -{ - internal class PdfPatternMediumGray : PdfPatternFill - { - public PdfPatternMediumGray(Color foreground, Color background) : base(foreground, background) { } - - public override string CreatePatternResource() - { - var sb = new StringBuilder(); - sb.AppendFormat($"{Background.ToFillCommand()}\n" + - $"0 0 2 2 re\n" + - $"f\n" + - $"{Foreground.ToFillCommand()}\n" + - $"1 0 1 1 re\n" + - $"f\n" + - $"{Foreground.ToFillCommand()}\n" + - $"0 1 1 1 re\n" + - $"f"); - return sb.ToString(); - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfResources/PdfPatternResource.cs b/src/EPPlus.Export.Pdf/PdfResources/PdfPatternResource.cs index f219a41c3f..68db8fcd3f 100644 --- a/src/EPPlus.Export.Pdf/PdfResources/PdfPatternResource.cs +++ b/src/EPPlus.Export.Pdf/PdfResources/PdfPatternResource.cs @@ -21,6 +21,20 @@ internal class PdfPatternResource : PdfResource internal int objectNumber; internal PdfCellFillData CellFillData; + // All mask-based patterns use an 8x8 tile in pattern space. The mask is the + // single source of geometry, so BBox and the tiling step are 8x8 for every + // pattern. + private static readonly double[] PatternBBox = new double[] { 0d, 0d, 8d, 8d }; + private const double PatternStep = 8d; + + // The 8x8 pattern space is scaled down to match the physical tile size + // Excel uses (0.75 pt for a full tile, i.e. 0.75 / 8 per pattern unit). + // The scale is positive on both axes: the renderer already mirrors the + // mask in y, so no y-flip is applied here (that would double-flip). + private const double PatternScale = 0.75d / 8d; + private static readonly double[] PatternMatrix = + new double[] { PatternScale, 0d, 0d, PatternScale, 0d, 0d }; + public PdfPatternResource(int labelNumber, PdfCellFillData cellFillData) : base("P", labelNumber) { @@ -30,47 +44,57 @@ public PdfPatternResource(int labelNumber, PdfCellFillData cellFillData) public PdfPattern GetPatternObject(int objectNumber, int version = 0) { this.objectNumber = objectNumber; - if (CellFillData.PatternStyle != ExcelFillStyle.None && CellFillData.PatternStyle != ExcelFillStyle.Solid) + + // None and Solid are not patterns; they are handled as special cases + // elsewhere in the export. + if (CellFillData.PatternStyle == ExcelFillStyle.None || + CellFillData.PatternStyle == ExcelFillStyle.Solid) + { + return null; + } + + ExcelPatternMask mask; + if (!TryGetMask(CellFillData.PatternStyle, out mask)) + { + return null; + } + + var fill = new PdfPatternMaskFill(mask, CellFillData.PatternColor, CellFillData.BackgroundColor); + var pattern = new PdfTilingPattern(objectNumber, fill, PatternBBox, PatternStep, PatternStep, version); + pattern.Matrix = PatternMatrix; + return pattern; + } + + /// + /// Maps an Excel cell fill style to the corresponding 8x8 pattern mask. + /// Returns false for styles that have no mask (e.g. None/Solid, or any + /// style not rendered as a tiling pattern). + /// + private static bool TryGetMask(ExcelFillStyle style, out ExcelPatternMask mask) + { + switch (style) { - switch (CellFillData.PatternStyle) - { - case ExcelFillStyle.DarkGray: - return new PdfTilingPattern(objectNumber, new PdfPatternDarkGray(CellFillData.PatternColor, CellFillData.BackgroundColor), [0, 0, 4, 2], 4, 2, version); - case ExcelFillStyle.MediumGray: - return new PdfTilingPattern(objectNumber, new PdfPatternMediumGray(CellFillData.PatternColor, CellFillData.BackgroundColor), [0, 0, 2, 2], 2, 2, version); - case ExcelFillStyle.LightGray: - return new PdfTilingPattern(objectNumber, new PdfPatternLightGray(CellFillData.PatternColor, CellFillData.BackgroundColor), [0, 0, 4, 2], 4, 2, version); - case ExcelFillStyle.Gray125: - return new PdfTilingPattern(objectNumber, new PdfPatternGray125(CellFillData.PatternColor, CellFillData.BackgroundColor), [0, 0, 4, 4], 4, 4, version); - case ExcelFillStyle.Gray0625: - return new PdfTilingPattern(objectNumber, new PdfPatternGray0625(CellFillData.PatternColor, CellFillData.BackgroundColor), [0, 0, 8, 4], 8, 4, version); - case ExcelFillStyle.DarkVertical: - return new PdfTilingPattern(objectNumber, new PdfPatternDarkVertical(CellFillData.PatternColor, CellFillData.BackgroundColor), [0, 0, 2, 1], 2, 1, version); - case ExcelFillStyle.DarkHorizontal: - return new PdfTilingPattern(objectNumber, new PdfPatternDarkHorizontal(CellFillData.PatternColor, CellFillData.BackgroundColor), [0, 0, 1, 2], 1, 2, version); - case ExcelFillStyle.DarkDown: - return new PdfTilingPattern(objectNumber, new PdfPatternDarkDown(CellFillData.PatternColor, CellFillData.BackgroundColor), [0, 0, 11.3137, 11.3137], 4, 4, version); - case ExcelFillStyle.DarkUp: - return new PdfTilingPattern(objectNumber, new PdfPatternDarkUp(CellFillData.PatternColor, CellFillData.BackgroundColor), [0, 0, 11.3137, 11.3137], 4, 4, version); - case ExcelFillStyle.DarkGrid: - return new PdfTilingPattern(objectNumber, new PdfPatternDarkGrid(CellFillData.PatternColor, CellFillData.BackgroundColor), [0, 0, 2, 2], 2, 2, version); - case ExcelFillStyle.DarkTrellis: - return new PdfTilingPattern(objectNumber, new PdfPatternDarkTrellis(CellFillData.PatternColor, CellFillData.BackgroundColor), [0, 0, 4, 4], 4, 4, version); - case ExcelFillStyle.LightVertical: - return new PdfTilingPattern(objectNumber, new PdfPatternLightVertical(CellFillData.PatternColor, CellFillData.BackgroundColor), [0, 0, 1, 0.5d], 1, 0.5, version); - case ExcelFillStyle.LightHorizontal: - return new PdfTilingPattern(objectNumber, new PdfPatternLightHorizontal(CellFillData.PatternColor, CellFillData.BackgroundColor), [0, 0, 0.5d, 1], 0.5d, 1, version); - case ExcelFillStyle.LightDown: - return new PdfTilingPattern(objectNumber, new PdfPatternLightDown(CellFillData.PatternColor, CellFillData.BackgroundColor), [0, 0, 4, 4], 4, 4, version); - case ExcelFillStyle.LightUp: - return new PdfTilingPattern(objectNumber, new PdfPatternLightUp(CellFillData.PatternColor, CellFillData.BackgroundColor), [0, 0, 4, 4], 4, 4, version); - case ExcelFillStyle.LightGrid: - return new PdfTilingPattern(objectNumber, new PdfPatternLightGrid(CellFillData.PatternColor, CellFillData.BackgroundColor), [0, 0, 4, 4], 4, 4, version); - case ExcelFillStyle.LightTrellis: - return new PdfTilingPattern(objectNumber, new PdfPatternLightTrellis(CellFillData.PatternColor, CellFillData.BackgroundColor), [0, 0, 4, 4], 4, 4, version); - } + case ExcelFillStyle.DarkGray: mask = ExcelPatternMask.DarkGray; return true; + case ExcelFillStyle.MediumGray: mask = ExcelPatternMask.MediumGray; return true; + case ExcelFillStyle.LightGray: mask = ExcelPatternMask.LightGray; return true; + case ExcelFillStyle.Gray125: mask = ExcelPatternMask.Gray125; return true; + case ExcelFillStyle.Gray0625: mask = ExcelPatternMask.Gray0625; return true; + case ExcelFillStyle.DarkHorizontal: mask = ExcelPatternMask.DarkHorizontal; return true; + case ExcelFillStyle.DarkVertical: mask = ExcelPatternMask.DarkVertical; return true; + case ExcelFillStyle.DarkDown: mask = ExcelPatternMask.DarkDown; return true; + case ExcelFillStyle.DarkUp: mask = ExcelPatternMask.DarkUp; return true; + case ExcelFillStyle.DarkGrid: mask = ExcelPatternMask.DarkGrid; return true; + case ExcelFillStyle.DarkTrellis: mask = ExcelPatternMask.DarkTrellis; return true; + case ExcelFillStyle.LightHorizontal: mask = ExcelPatternMask.LightHorizontal; return true; + case ExcelFillStyle.LightVertical: mask = ExcelPatternMask.LightVertical; return true; + case ExcelFillStyle.LightDown: mask = ExcelPatternMask.LightDown; return true; + case ExcelFillStyle.LightUp: mask = ExcelPatternMask.LightUp; return true; + case ExcelFillStyle.LightGrid: mask = ExcelPatternMask.LightGrid; return true; + case ExcelFillStyle.LightTrellis: mask = ExcelPatternMask.LightTrellis; return true; + default: + mask = ExcelPatternMask.DarkGray; + return false; } - return null; } } -} +} \ No newline at end of file diff --git a/src/EPPlus/ExcelWorksheet.cs b/src/EPPlus/ExcelWorksheet.cs index 027fb3393e..e4543bcc19 100644 --- a/src/EPPlus/ExcelWorksheet.cs +++ b/src/EPPlus/ExcelWorksheet.cs @@ -2996,6 +2996,20 @@ public ExcelRangeBase Dimension } } } + + /// + /// Dimension address for the worksheet for cells that has a visible difference from default cells. + /// Top left cell to Bottom right. + /// If the worksheet has no cells, null is returned + /// + public ExcelRangeBase DimensionByVisibility + { + get + { + return GetDimension(true); + } + } + /// /// Dimension address for the worksheet for cells with a value different than null. /// Top left cell to Bottom right. @@ -3005,57 +3019,152 @@ public ExcelRangeBase DimensionByValue { get { - CheckSheetTypeAndNotDisposed(); - if (_values.GetDimension(out int fr, out int fc, out int tr, out int tc)) + return GetDimension(false); + } + } + + + private ExcelRangeBase GetDimension(bool byVisibility) + { + CheckSheetTypeAndNotDisposed(); + if (_values.GetDimension(out int fr, out int fc, out int tr, out int tc)) + { + var fvc = FirstValueCell; + var lvc = LastValueCell; + // Row range comes from values only — styling never extends the height. + var fromRow = fvc._fromRow; + var toRow = lvc._toRow; + + // For a single value cell, the value-based dimension is just that cell, + // but visible styling on other columns within the same row may still + // extend the column range, so we keep going rather than early-returning + // when byVisibility is requested. + if (byVisibility == false && fvc.Address == lvc.Address) { - var fvc = FirstValueCell; - var lvc = LastValueCell; - if (fvc.Address == lvc.Address) return Cells[fvc.Address]; - var fromRow = fvc._fromRow; - var toRow = lvc._toRow; - int fromCol, toCol; + return Cells[fvc.Address]; + } - if (fvc._fromCol == fc) - { - fromCol = fvc._fromCol; - } - else + int fromCol, toCol; + + // ---- Leftmost column ---- + if (fvc._fromCol == fc) + { + fromCol = fvc._fromCol; + } + else + { + int r = fromRow, c = fc; + while (_values.NextCellByColumn(ref r, ref c, fromRow, toRow, _values.ColumnCount - 1)) { - int r = fromRow, c = fc; - while (_values.NextCellByColumn(ref r, ref c, fromRow, toRow, _values.ColumnCount - 1)) + if (_values.GetValue(r, c)._value != null) { - if (_values.GetValue(r, c)._value != null) - { - break; - } - r++; + break; } - fromCol = c; + r++; } + fromCol = c; + } - if (lvc._toCol == tc) + // ---- Rightmost column ---- + if (lvc._toCol == tc) + { + toCol = lvc._toCol; + } + else + { + int r = toRow, c = tc; + while (_values.PrevCellByColumn(ref r, ref c, fromRow, toRow, _values.ColumnCount - 1)) { - toCol = lvc._toCol; + if (_values.GetValue(r, c)._value != null) + { + break; + } + r--; } - else + toCol = c; + } + + // ---- Extend column range by visible styling (visibility mode only) ---- + // Scan the whole used column span and pull fromCol/toCol outward to + // include any column that has a visible style somewhere in the row range. + if (byVisibility) + { + for (int c = fc; c <= tc; c++) { - int r = toRow, c = tc; - while (_values.PrevCellByColumn(ref r, ref c, fromRow, toRow, _values.ColumnCount - 1)) + if (c >= fromCol && c <= toCol) + { + continue; // already inside the range + } + for (int r = fromRow; r <= toRow; r++) { - if (_values.GetValue(r, c)._value != null) + if (HasVisibleStyle(r, c)) { - break; + if (c < fromCol) fromCol = c; + if (c > toCol) toCol = c; + break; // this column qualifies; move to the next column } - r--; } - toCol = c; } + } - return Cells[Math.Min(fromRow, toRow), Math.Min(fromCol, toCol), Math.Max(fromRow, toRow), Math.Max(fromCol, toCol)]; + return Cells[System.Math.Min(fromRow, toRow), System.Math.Min(fromCol, toCol), System.Math.Max(fromRow, toRow), System.Math.Max(fromCol, toCol)]; + } + return null; + } + + /// + /// Returns true if the cell at (row, col) has a style that is visible on an + /// empty cell: a fill pattern other than None, or a border edge other than + /// None. Invisible styling (font, alignment, number format) is ignored. + /// The style id is resolved through the cell -> row -> column inheritance + /// chain so that fills/borders applied to a whole column or row are detected. + /// + private bool HasVisibleStyle(int row, int col) + { + // Resolve the effective style id, following cell -> row -> column. + int styleId = Workbook.Styles.GetStyleId(this, row, col); + if (styleId <= 0) + { + return false; // 0 == the default style, which is not visible + } + + var styles = Workbook.Styles; + if (styleId >= styles.CellXfs.Count) + { + return false; + } + var xf = styles.CellXfs[styleId]; + + // --- Fill --- + // FillId 0 is always the "None" fill (see EnsureValidFills). + if (xf.FillId > 0 && xf.FillId < styles.Fills.Count) + { + var fill = styles.Fills[xf.FillId]; + if (fill.PatternType != ExcelFillStyle.None) + { + return true; } - return null; } + + // --- Border --- + // NOTE: verify the member names against the ExcelBorderXml type in core + // EPPlus. The edges are expected to expose a Style of type ExcelBorderStyle. + if (xf.BorderId > 0 && xf.BorderId < styles.Borders.Count) + { + var border = styles.Borders[xf.BorderId]; + if (border.Top.Style != ExcelBorderStyle.None || + border.Bottom.Style != ExcelBorderStyle.None || + border.Left.Style != ExcelBorderStyle.None || + border.Right.Style != ExcelBorderStyle.None || + border.Diagonal.Style != ExcelBorderStyle.None) + { + return true; + } + } + + return false; } + /// /// The first cell with a value in the worksheet that differs from null. /// Normally this is the top-left cell, unless the worksheet is set to RightToLeft mode. From 05fcfb0860d9588f24821bfa89acb384a85fdbfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Thu, 18 Jun 2026 17:40:29 +0200 Subject: [PATCH 34/49] Moving and restructing pdf code progress. --- .../PdfCatalog/PdfCatalog.cs | 49 +- .../PdfCatalog/PdfTextMap.cs | 20 +- .../PdfDictionaries.cs | 0 .../PdfFontResource.cs | 0 .../PdfPatternResource.cs | 0 .../PdfResource.cs | 0 .../PdfShadingResource.cs | 0 .../PdfContentBounds.cs | 0 .../PdfPageData/PdfExcelPageDataLookup.cs | 0 .../PdfPageSettings.cs | 0 .../PdfPageSizes/PdfMargins.cs | 0 .../PdfPageSizes/PdfPageSize.cs | 0 .../PdfPageSizes/PdfScaling.cs | 19 +- .../Data/Dictionaries/PdfDictionaries.cs | 54 + .../Data/Dictionaries/PdfFontResource.cs | 263 +++ .../Data/Dictionaries/PdfPatternResource.cs | 100 + .../Data/Dictionaries/PdfResource.cs | 34 + .../Data/Dictionaries/PdfShadingResource.cs | 58 + .../Export/PdfExport/Data/PdfBorderData.cs | 76 + src/EPPlus/Export/PdfExport/Data/PdfCell.cs | 61 + .../PdfExport/Data/PdfCellCollection.cs | 68 + .../Export/PdfExport/Data/PdfCellStyle.cs | 51 + .../Export/PdfExport/Data/PdfFillData.cs | 50 + .../Data/PdfPageSizes/PdfContentBounds.cs | 95 + .../PdfExport/Data/PdfPageSizes/PdfMargins.cs | 62 + .../Data/PdfPageSizes/PdfPageSize.cs | 42 + .../PdfExport/Data/PdfPageSizes/PdfScaling.cs | 66 + src/EPPlus/Export/PdfExport/Data/PdfRange.cs | 45 + .../Export/PdfExport/Data/PdfWorksheet.cs | 70 + .../Export/PdfExport/Helpers/PdfColor.cs | 132 ++ .../Export/PdfExport/Helpers/PdfString.cs | 63 + .../PdfExport/Layout/MergedCellCorners.cs | 44 + .../PdfExport/Layout/PdfCellBorderLayout.cs | 592 ++++++ .../PdfExport/Layout/PdfCellContentLayout.cs | 502 +++++ .../Export/PdfExport/Layout/PdfCellLayout.cs | 332 ++++ .../PdfExport/Layout/PdfGridlinesLayout.cs | 386 ++++ .../Export/PdfExport/Layout/PdfLayout.cs | 1711 +++++++++++++++++ .../Export/PdfExport/Layout/PdfPageLayout.cs | 334 ++++ src/EPPlus/Export/PdfExport/PdfCatalog.cs | 381 ++++ .../Export/PdfExport/PdfPageSettings.cs | 290 +++ .../RowResize/PdfCalculateRowHeight.cs | 100 + .../TextMapping/PdfCommentsAndNotes.cs | 115 ++ .../PdfExport/TextMapping/PdfHeaderFooter.cs | 61 + .../TextMapping/PdfHeaderFooterCollection.cs | 163 ++ .../PdfExport/TextMapping/PdfTextMap.cs | 719 +++++++ .../PdfExport/TextShaping/PdfShapedText.cs | 28 + .../PdfExport/TextShaping/PdfTextShaper.cs | 110 ++ 47 files changed, 7327 insertions(+), 19 deletions(-) rename src/EPPlus.Export.Pdf/{PdfResources => PdfResources MOVED}/PdfDictionaries.cs (100%) rename src/EPPlus.Export.Pdf/{PdfResources => PdfResources MOVED}/PdfFontResource.cs (100%) rename src/EPPlus.Export.Pdf/{PdfResources => PdfResources MOVED}/PdfPatternResource.cs (100%) rename src/EPPlus.Export.Pdf/{PdfResources => PdfResources MOVED}/PdfResource.cs (100%) rename src/EPPlus.Export.Pdf/{PdfResources => PdfResources MOVED}/PdfShadingResource.cs (100%) rename src/EPPlus.Export.Pdf/{PdfSettings => PdfSettings MOVED}/PdfContentBounds.cs (100%) rename src/EPPlus.Export.Pdf/{PdfSettings => PdfSettings MOVED}/PdfPageData/PdfExcelPageDataLookup.cs (100%) rename src/EPPlus.Export.Pdf/{PdfSettings => PdfSettings MOVED}/PdfPageSettings.cs (100%) rename src/EPPlus.Export.Pdf/{PdfSettings => PdfSettings MOVED}/PdfPageSizes/PdfMargins.cs (100%) rename src/EPPlus.Export.Pdf/{PdfSettings => PdfSettings MOVED}/PdfPageSizes/PdfPageSize.cs (100%) rename src/EPPlus.Export.Pdf/{PdfSettings => PdfSettings MOVED}/PdfPageSizes/PdfScaling.cs (67%) create mode 100644 src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfDictionaries.cs create mode 100644 src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfFontResource.cs create mode 100644 src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfPatternResource.cs create mode 100644 src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfResource.cs create mode 100644 src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfShadingResource.cs create mode 100644 src/EPPlus/Export/PdfExport/Data/PdfBorderData.cs create mode 100644 src/EPPlus/Export/PdfExport/Data/PdfCell.cs create mode 100644 src/EPPlus/Export/PdfExport/Data/PdfCellCollection.cs create mode 100644 src/EPPlus/Export/PdfExport/Data/PdfCellStyle.cs create mode 100644 src/EPPlus/Export/PdfExport/Data/PdfFillData.cs create mode 100644 src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfContentBounds.cs create mode 100644 src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfMargins.cs create mode 100644 src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfPageSize.cs create mode 100644 src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfScaling.cs create mode 100644 src/EPPlus/Export/PdfExport/Data/PdfRange.cs create mode 100644 src/EPPlus/Export/PdfExport/Data/PdfWorksheet.cs create mode 100644 src/EPPlus/Export/PdfExport/Helpers/PdfColor.cs create mode 100644 src/EPPlus/Export/PdfExport/Helpers/PdfString.cs create mode 100644 src/EPPlus/Export/PdfExport/Layout/MergedCellCorners.cs create mode 100644 src/EPPlus/Export/PdfExport/Layout/PdfCellBorderLayout.cs create mode 100644 src/EPPlus/Export/PdfExport/Layout/PdfCellContentLayout.cs create mode 100644 src/EPPlus/Export/PdfExport/Layout/PdfCellLayout.cs create mode 100644 src/EPPlus/Export/PdfExport/Layout/PdfGridlinesLayout.cs create mode 100644 src/EPPlus/Export/PdfExport/Layout/PdfLayout.cs create mode 100644 src/EPPlus/Export/PdfExport/Layout/PdfPageLayout.cs create mode 100644 src/EPPlus/Export/PdfExport/PdfCatalog.cs create mode 100644 src/EPPlus/Export/PdfExport/PdfPageSettings.cs create mode 100644 src/EPPlus/Export/PdfExport/RowResize/PdfCalculateRowHeight.cs create mode 100644 src/EPPlus/Export/PdfExport/TextMapping/PdfCommentsAndNotes.cs create mode 100644 src/EPPlus/Export/PdfExport/TextMapping/PdfHeaderFooter.cs create mode 100644 src/EPPlus/Export/PdfExport/TextMapping/PdfHeaderFooterCollection.cs create mode 100644 src/EPPlus/Export/PdfExport/TextMapping/PdfTextMap.cs create mode 100644 src/EPPlus/Export/PdfExport/TextShaping/PdfShapedText.cs create mode 100644 src/EPPlus/Export/PdfExport/TextShaping/PdfTextShaper.cs diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs index bae9a1d7e7..d1e757ca21 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs @@ -1,4 +1,16 @@ -using EPPlus.Export.Pdf.PdfResources; +/************************************************************************************************* + 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.Export.Pdf.PdfResources; using EPPlus.Export.Pdf.PdfSettings; using EPPlus.Graphics; using OfficeOpenXml; @@ -15,8 +27,9 @@ public class PdfCatalog internal PdfDictionaries _dictionaries = new PdfDictionaries(); private bool _addTextForHeadings = true; - //Constructors - public PdfCatalog() { } + // + // CONSTRUCTORS FOR MULTIPLE WORKSHEETS AS INPUT + // public PdfCatalog(PdfPageSettings pageSettings, ExcelWorkbook workbook) { @@ -39,9 +52,16 @@ private void HandleWorksheetCollection(PdfPageSettings pageSettings, ExcelWorksh foreach (var pdfSheet in pdfSheets) { ShapeTextInPdfWorksheet(pageSettings, pdfSheet); + PdfCalculateRowHeight.ResizeRowHeights(pdfSheet); } + var Layout = GetLayout(pageSettings, pdfSheets); + //send Layout to pdf export here. } + // + // CONSTRUCTORS FOR SINGLE WORKSHEET AS INPUT + // + public PdfCatalog(PdfPageSettings pageSettings, ExcelWorksheet worksheet, string fileName) { BuildPdf(pageSettings, worksheet, (excelPdf, layout) => @@ -91,7 +111,7 @@ private void BuildPdf(PdfPageSettings pageSettings, ExcelWorksheet worksheet, Ac sw.Reset(); sw.Start(); - //Create Pdf + //Create Pdf //Done in pdf export project ExcelPdf excelPdf = new ExcelPdf(); writePdf(excelPdf, Layout); sw.Stop(); @@ -110,10 +130,17 @@ private void BuildPdf(PdfPageSettings pageSettings, ExcelWorksheet worksheet, Ac } } + // + // CONSTRUCTORS FOR RANGE AS INPUT + // + public PdfCatalog(PdfPageSettings pageSettings, ExcelRangeBase range) { PdfWorksheet pdfSheet = GetPdfWorksheet(pageSettings, range); ShapeTextInPdfWorksheet(pageSettings, pdfSheet); + PdfCalculateRowHeight.ResizeRowHeights(pdfSheet); + var Layout = GetLayout(pageSettings, pdfSheet); + //send Layout to pdf export here. } internal PdfCellCollection GetCellCollectionFromRange(PdfPageSettings pageSettings, ExcelRangeBase range) @@ -123,8 +150,18 @@ internal PdfCellCollection GetCellCollectionFromRange(PdfPageSettings pageSettin return pdfSheet.Ranges[0].Map; } + //Private Methods + + //Create Layout Methods + + private Transform GetLayout(PdfPageSettings pageSettings, PdfWorksheet[] pdfSheets) + { + var Layout = PdfLayout.GetLayout(pageSettings, _dictionaries, pdfSheets); + return Layout; + } + private Transform GetLayout(PdfPageSettings pageSettings, PdfWorksheet pdfSheet) { PdfWorksheet[] pdfSheets = new PdfWorksheet[1] { pdfSheet }; @@ -133,6 +170,7 @@ private Transform GetLayout(PdfPageSettings pageSettings, PdfWorksheet pdfSheet) } //Shape Text Methods + internal void ShapeTextInPdfWorksheet(PdfPageSettings pageSettings, PdfWorksheet pdfSheet) { // Pass 1: collect text per font @@ -227,7 +265,7 @@ private List GetRanges(ExcelWorksheet worksheet) } else { - var range = worksheet.DimensionByVisibility; + var range = worksheet.DimensionByValue; var pdfRange = new PdfRange(range, true); pdfRange.ExtendColumns = true; ranges.Add(pdfRange); @@ -318,7 +356,6 @@ private static void MarkPrintTitleCells(PdfWorksheet pdfSheet, PdfRange range) } } - private void GetHeaderFooter(PdfPageSettings pageSettings, PdfWorksheet pdfSheet) { pdfSheet.HeaderFooters = new PdfHeaderFooterCollection(pageSettings, _dictionaries, pdfSheet, pdfSheet.Worksheet.HeaderFooter); diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs index 41d7586f5e..2c64f168d0 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs @@ -1,17 +1,23 @@ -using EPPlus.Export.Pdf.Pdfhelpers; +/************************************************************************************************* + 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.Export.Pdf.PdfLayout; using EPPlus.Export.Pdf.PdfResources; using EPPlus.Export.Pdf.PdfSettings; -using EPPlus.Fonts.OpenType; using EPPlus.Fonts.OpenType.Integration; using EPPlus.Fonts.OpenType.Integration.DataHolders; using EPPlus.Graphics.Units; using OfficeOpenXml; -using OfficeOpenXml.ConditionalFormatting; -using OfficeOpenXml.ConditionalFormatting.Contracts; -using OfficeOpenXml.Interfaces.Drawing.Text; using OfficeOpenXml.Interfaces.Fonts; -using OfficeOpenXml.Interfaces.RichText; using OfficeOpenXml.Style; using OfficeOpenXml.Style.Dxf; using OfficeOpenXml.Style.HeaderFooterTextFormat; @@ -19,8 +25,6 @@ using OfficeOpenXml.Table; using System; using System.Collections.Generic; -using System.Diagnostics; -using System.Drawing; using System.Linq; namespace EPPlus.Export.Pdf.PdfCatalog diff --git a/src/EPPlus.Export.Pdf/PdfResources/PdfDictionaries.cs b/src/EPPlus.Export.Pdf/PdfResources MOVED/PdfDictionaries.cs similarity index 100% rename from src/EPPlus.Export.Pdf/PdfResources/PdfDictionaries.cs rename to src/EPPlus.Export.Pdf/PdfResources MOVED/PdfDictionaries.cs diff --git a/src/EPPlus.Export.Pdf/PdfResources/PdfFontResource.cs b/src/EPPlus.Export.Pdf/PdfResources MOVED/PdfFontResource.cs similarity index 100% rename from src/EPPlus.Export.Pdf/PdfResources/PdfFontResource.cs rename to src/EPPlus.Export.Pdf/PdfResources MOVED/PdfFontResource.cs diff --git a/src/EPPlus.Export.Pdf/PdfResources/PdfPatternResource.cs b/src/EPPlus.Export.Pdf/PdfResources MOVED/PdfPatternResource.cs similarity index 100% rename from src/EPPlus.Export.Pdf/PdfResources/PdfPatternResource.cs rename to src/EPPlus.Export.Pdf/PdfResources MOVED/PdfPatternResource.cs diff --git a/src/EPPlus.Export.Pdf/PdfResources/PdfResource.cs b/src/EPPlus.Export.Pdf/PdfResources MOVED/PdfResource.cs similarity index 100% rename from src/EPPlus.Export.Pdf/PdfResources/PdfResource.cs rename to src/EPPlus.Export.Pdf/PdfResources MOVED/PdfResource.cs diff --git a/src/EPPlus.Export.Pdf/PdfResources/PdfShadingResource.cs b/src/EPPlus.Export.Pdf/PdfResources MOVED/PdfShadingResource.cs similarity index 100% rename from src/EPPlus.Export.Pdf/PdfResources/PdfShadingResource.cs rename to src/EPPlus.Export.Pdf/PdfResources MOVED/PdfShadingResource.cs diff --git a/src/EPPlus.Export.Pdf/PdfSettings/PdfContentBounds.cs b/src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfContentBounds.cs similarity index 100% rename from src/EPPlus.Export.Pdf/PdfSettings/PdfContentBounds.cs rename to src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfContentBounds.cs diff --git a/src/EPPlus.Export.Pdf/PdfSettings/PdfPageData/PdfExcelPageDataLookup.cs b/src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfPageData/PdfExcelPageDataLookup.cs similarity index 100% rename from src/EPPlus.Export.Pdf/PdfSettings/PdfPageData/PdfExcelPageDataLookup.cs rename to src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfPageData/PdfExcelPageDataLookup.cs diff --git a/src/EPPlus.Export.Pdf/PdfSettings/PdfPageSettings.cs b/src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfPageSettings.cs similarity index 100% rename from src/EPPlus.Export.Pdf/PdfSettings/PdfPageSettings.cs rename to src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfPageSettings.cs diff --git a/src/EPPlus.Export.Pdf/PdfSettings/PdfPageSizes/PdfMargins.cs b/src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfPageSizes/PdfMargins.cs similarity index 100% rename from src/EPPlus.Export.Pdf/PdfSettings/PdfPageSizes/PdfMargins.cs rename to src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfPageSizes/PdfMargins.cs diff --git a/src/EPPlus.Export.Pdf/PdfSettings/PdfPageSizes/PdfPageSize.cs b/src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfPageSizes/PdfPageSize.cs similarity index 100% rename from src/EPPlus.Export.Pdf/PdfSettings/PdfPageSizes/PdfPageSize.cs rename to src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfPageSizes/PdfPageSize.cs diff --git a/src/EPPlus.Export.Pdf/PdfSettings/PdfPageSizes/PdfScaling.cs b/src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfPageSizes/PdfScaling.cs similarity index 67% rename from src/EPPlus.Export.Pdf/PdfSettings/PdfPageSizes/PdfScaling.cs rename to src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfPageSizes/PdfScaling.cs index 161be7c9b0..425cc74335 100644 --- a/src/EPPlus.Export.Pdf/PdfSettings/PdfPageSizes/PdfScaling.cs +++ b/src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfPageSizes/PdfScaling.cs @@ -1,4 +1,16 @@ -namespace EPPlus.Export.Pdf.PdfSettings.PdfPageSizes +/************************************************************************************************* + 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 + *************************************************************************************************/ +namespace EPPlus.Export.Pdf.PdfSettings.PdfPageSizes { public enum ScalingMode { @@ -46,12 +58,9 @@ public PdfScaling(int pagesWide, int pagesTall) PagesTall = pagesTall; } - public static PdfScaling NoScaling => new PdfScaling(1d); public static PdfScaling FitSheetToOnePage => new PdfScaling(1, 1); public static PdfScaling FitAllColumnsOnOnePage => new PdfScaling(1, 0); public static PdfScaling FitAllRowsOnOnePage => new PdfScaling(0, 1); - } - -} +} \ No newline at end of file diff --git a/src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfDictionaries.cs b/src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfDictionaries.cs new file mode 100644 index 0000000000..3407568b2c --- /dev/null +++ b/src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfDictionaries.cs @@ -0,0 +1,54 @@ +/************************************************************************************************* + 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.Fonts.OpenType; +using OfficeOpenXml.Interfaces.Fonts; +using System.Collections.Generic; +using System.Linq; + +namespace OfficeOpenXml.Export.PdfExport.Data.Dictionaries; + +internal class PdfDictionaries +{ + internal readonly Dictionary Fonts = new Dictionary(); + internal readonly Dictionary Patterns = new Dictionary(); + internal readonly Dictionary Shadings = new Dictionary(); + internal Dictionary ShapedProviders = new Dictionary(); + + public void AddFont(PdfPageSettings pageSettings, string FontName, FontSubFamily SubFamily, string Text) + { + var fullFont = FontName + " " + SubFamily.ToString(); + if (!Fonts.ContainsKey(fullFont)) + { + int label = 1; + if (Fonts.Count > 0) + { + label = Fonts.Last().Value.labelNumber + 1; + } + Fonts.Add(fullFont, new PdfFontResource(FontName, SubFamily, label, pageSettings)); + } + var manger = Fonts[fullFont].fontSubsetManager; + manger.AddText(Text); + } + + internal PdfFontResource GetFont(string FontName) + { + if (!Fonts.ContainsKey(FontName)) + { + return null; + } + return Fonts[FontName]; + } + + //this should move and be on worksheet level. + internal readonly Dictionary CommentsAndNotes = new Dictionary(); +} diff --git a/src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfFontResource.cs b/src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfFontResource.cs new file mode 100644 index 0000000000..7dbcde13a4 --- /dev/null +++ b/src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfFontResource.cs @@ -0,0 +1,263 @@ +/************************************************************************************************* + 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.Fonts.OpenType; +using EPPlus.Fonts.OpenType.Tables.Os2; +using EPPlus.Fonts.OpenType.TextShaping; +using EPPlus.Graphics; +using System; +using System.Collections.Generic; +using System.Linq; +using OfficeOpenXml.Interfaces.Fonts; +using OfficeOpenXml.Export.PdfExport.ObjectData; + +namespace OfficeOpenXml.Export.PdfExport.Data.Dictionaries; +{ + internal class PdfFontResource : PdfResource + { + internal string fontName; + internal int fontObjectNumber = -1; + internal int CIDFontObjectNumber = -1; + internal int type0FontObjectNumber = -1; + internal int unicodeCMapFontObjectNumber = -1; + internal int embedFontStreamObjectNumber = -1; + internal int fontDescObjectNumber = -1; + internal int fontWidthObjectNumber = -1; + internal int cidSetObjectNumber = -1; + internal OpenTypeFont fontData; + private OpenTypeFontEngine _fontEngine; + private int firstChar = 32; + private int lastChar = 255; + private CIDSystemInfo cidSystemInfo = null; + + internal string type0Encoding = "Identity-H"; + + internal TextShaper Shaper = null; + internal ShapedText ShapedText; + + internal HashSet Subset = new HashSet(); + internal HashSet Gids = new HashSet(); + internal Dictionary charactermappings = new Dictionary(); + + internal FontSubsetManager fontSubsetManager; + + public PdfFontResource(string fontName, FontSubFamily subFamily, int labelNumber, PdfPageSettings pageSettings) + : base("F", labelNumber) + { + this.fontName = fontName; + _fontEngine = pageSettings.FontEngine; + fontData = _fontEngine.LoadFont(fontName, subFamily); + fontSubsetManager = new FontSubsetManager(pageSettings.FontEngine, fontData); + } + + internal static OpenTypeFont GetFontData(PdfPageSettings pageSettings, string fontName, FontSubFamily subFamily) + { + return pageSettings.FontEngine.LoadFont(fontName,subFamily); + } + + //Get font data from fontResources. If font does not exsist, add it to fontResources. + //internal static OpenTypeFont GetFontResourceData(Dictionary fontResources, PdfPageSettings pageSettings, PdfTextFormat FontData) + //{ + // if (!fontResources.ContainsKey(FontData.FullFontName)) + // { + // int label = 1; + // if (fontResources.Count > 0) + // { + // label = fontResources.Last().Value.labelNumber + 1; + // } + // PdfFontResource fr = new PdfFontResource(FontData.FontName, FontData.SubFamily, label, pageSettings); + // fontResources.Add(FontData.FullFontName, fr); + // fontResources.Last().Value.fontData = GetFontData(pageSettings, FontData.FontName, FontData.SubFamily); + // } + // return fontResources[FontData.FullFontName].fontData; + //} + + //Get the Font Descriptor object to write in PDF. + internal PdfFontDescriptor GetFontDescriptorObject(int objectNumber, int version = 0) + { + int flag = 0; + if (fontData.PostTable.isFixedPitch != 0) + flag |= 1; + if (fontData.GetEnglishFontFamilyName().ToLower().Contains("serif")) + flag |= 1 << 1; + bool isSymbolic = false; + foreach (var sub in fontData.CmapTable.EncodingRecords) + { + if (sub.PlatformId == Fonts.OpenType.Tables.Cmap.Platforms.Windows) + { + if (sub.EncodingId == 0) + { + isSymbolic = true; + break; + } + if (sub.EncodingId == 1 || sub.EncodingId == 10) + { + isSymbolic = false; + break; + } + } + if (sub.PlatformId == Fonts.OpenType.Tables.Cmap.Platforms.Macintosh || sub.PlatformId == Fonts.OpenType.Tables.Cmap.Platforms.Unicode) + { + isSymbolic = true; + break; + } + } + if (isSymbolic) + flag |= 1 << 2; // Symbolic + else + flag |= 1 << 5; // Nonsymbolic + if (fontData.GetEnglishFontFamilyName().ToLower().Contains("script") || fontData.GetEnglishFontFamilyName().ToLower().Contains("cursive")) + flag |= 1 << 3; + if (fontData.PostTable.italicAngle.RawValue != 0 || (fontData.Os2Table.fsSelection & Os2Table.FsSelectionFlags.Italic) != 0) + flag |= 1 << 6; + if (((ushort)fontData.Os2Table.fsSelection & 0x100) != 0) + flag |= 1 << 16; + if (((ushort)fontData.Os2Table.fsSelection & 0x200) != 0) + flag |= 1 << 17; + if (((ushort)fontData.Os2Table.fsSelection & 0x400) != 0) + flag |= 1 << 18; + var fontBBox = new Rect(); + fontBBox.X = fontData.HeadTable.Xmin; + fontBBox.Y = fontData.HeadTable.Ymin; + fontBBox.Width = fontData.HeadTable.Xmax; + fontBBox.Height = fontData.HeadTable.Ymax; + fontDescObjectNumber = objectNumber; + return new PdfFontDescriptor + ( + objectNumber, + fontData.FullName, + flag, + fontBBox, + Convert.ToDouble(fontData.PostTable.italicAngle.FloatValue), + fontData.Os2Table.sTypoAscender, + fontData.Os2Table.sTypoDescender, + 0, + fontData.Os2Table.sCapHeight, + embedFontStreamObjectNumber, + cidSetObjectNumber, + version + ); + } + + //Get the Widths object to write in PDF. + internal PdfFontWidths GetWidthsObject(int objectNumber, int version = 0) + { + List widths = new List(); + int fallbackWidth = fontData.Os2Table.xAvgCharWidth; + var glyphMappings = fontData.CmapTable.GetPreferredSubtable().GetGlyphMappings(); + for (int c = firstChar; c <= lastChar; c++) + { + var gi = glyphMappings.GetGlyphIndex((char)c); + if (gi == 0 && c != 0) + { + int normalizedWidth = (int)System.Math.Round(fallbackWidth / (double)fontData.HeadTable.UnitsPerEm * 1000); + widths.Add(normalizedWidth); + } + else + { + var hhMetric = fontData.HmtxTable.hMetrics[gi ?? 0]; + var advanceWidth = Convert.ToInt16(hhMetric.advanceWidth); + int normalizedWidth = (int)System.Math.Round(advanceWidth / (double)fontData.HeadTable.UnitsPerEm * 1000); + widths.Add(normalizedWidth); + } + } + fontWidthObjectNumber = objectNumber; + return new PdfFontWidths(objectNumber, widths, version); + } + + //Get the Font Object to write in PDF. + internal PdfFont GetFontObject(int objectNumber, int version = 0) + { + fontObjectNumber = objectNumber; + return new PdfFont(objectNumber, fontData.FullName, PdfFontSubType.Type1, firstChar, lastChar, fontWidthObjectNumber, fontDescObjectNumber, PdfFontEncoding.WinAnsiEncoding); + } + + internal PdfCIDFont GetCIDFontObject(int objectNumber, int version = 0) + { + CIDFontObjectNumber = objectNumber; + if (cidSystemInfo == null) + { + cidSystemInfo = new CIDSystemInfo(); + } + cidSystemInfo.Registry = "Adobe"; + cidSystemInfo.Ordering = "Identity"; + cidSystemInfo.Supplement = 0; + + return new PdfCIDFont(objectNumber, fontData, Gids, CIDFontSubtype.CIDFontType2, cidSystemInfo, "Identity", fontDescObjectNumber); + } + + internal PdfType0FontDict GetType0FontDictObject(int objectNumber, int version = 0) + { + type0FontObjectNumber = objectNumber; + return new PdfType0FontDict(objectNumber, fontData.FullName, type0Encoding, CIDFontObjectNumber, unicodeCMapFontObjectNumber); + } + + internal PdfToUnicodeCMap GetUnicodeCmapObject(int objectNumber, int version = 0) + { + unicodeCMapFontObjectNumber = objectNumber; + return new PdfToUnicodeCMap(objectNumber, charactermappings); + } + private string ExtractCharactersForGlyph(ShapedGlyph glyph, string textLine) + { + var chars = new System.Text.StringBuilder(); + for (int i = 0; i < glyph.CharCount && glyph.ClusterIndex + i < textLine.Length; i++) + { + chars.Append(textLine[glyph.ClusterIndex + i]); + } + return chars.ToString(); + } + + internal PdfFontStream GetEmbeddedFontStreamObject(int objectNumber, int version = 0) + { + embedFontStreamObjectNumber = objectNumber; + return new PdfFontStream(objectNumber, fontData, version); + } + + internal PdfCidSet GetCidSet(int objectNumber, int version = 0) + { + if (!fontData.IsSubset || Gids.Count == 0) + return null; + + int maxGid = Gids.Max(); + int numBytes = (maxGid / 8) + 1; + var cidSet = new byte[numBytes]; + + foreach (var gid in Gids) + { + int byteIndex = gid / 8; + int bitIndex = 7 - (gid % 8); + cidSet[byteIndex] |= (byte)(1 << bitIndex); + } + cidSetObjectNumber = objectNumber; + return new PdfCidSet(objectNumber, cidSet, version); + } + + internal void CreateGidsAndCharMaps() + { + if (ShapedText != null) + { + foreach (var glyph in ShapedText.Glyphs) + { + Gids.Add(glyph.GlyphId); + if (!charactermappings.ContainsKey(glyph.GlyphId)) + { + var chars = ExtractCharactersForGlyph(glyph, ShapedText.OriginalText); + if (!string.IsNullOrEmpty(chars)) + { + charactermappings.Add(glyph.GlyphId, chars); + } + } + } + } + } + } +} diff --git a/src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfPatternResource.cs b/src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfPatternResource.cs new file mode 100644 index 0000000000..764f90364b --- /dev/null +++ b/src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfPatternResource.cs @@ -0,0 +1,100 @@ +/************************************************************************************************* + 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.Export.Pdf.PdfLayout; +using EPPlus.Export.Pdf.PdfObjects.PdfPatterns; +using OfficeOpenXml.Style; + +namespace OfficeOpenXml.Export.PdfExport.Data.Dictionaries; +{ + internal class PdfPatternResource : PdfResource + { + internal int objectNumber; + internal PdfCellFillData CellFillData; + + // All mask-based patterns use an 8x8 tile in pattern space. The mask is the + // single source of geometry, so BBox and the tiling step are 8x8 for every + // pattern. + private static readonly double[] PatternBBox = new double[] { 0d, 0d, 8d, 8d }; + private const double PatternStep = 8d; + + // The 8x8 pattern space is scaled down to match the physical tile size + // Excel uses (0.75 pt for a full tile, i.e. 0.75 / 8 per pattern unit). + // The scale is positive on both axes: the renderer already mirrors the + // mask in y, so no y-flip is applied here (that would double-flip). + private const double PatternScale = 0.75d / 8d; + private static readonly double[] PatternMatrix = + new double[] { PatternScale, 0d, 0d, PatternScale, 0d, 0d }; + + public PdfPatternResource(int labelNumber, PdfCellFillData cellFillData) + : base("P", labelNumber) + { + CellFillData = cellFillData; + } + + public PdfPattern GetPatternObject(int objectNumber, int version = 0) + { + this.objectNumber = objectNumber; + + // None and Solid are not patterns; they are handled as special cases + // elsewhere in the export. + if (CellFillData.PatternStyle == ExcelFillStyle.None || + CellFillData.PatternStyle == ExcelFillStyle.Solid) + { + return null; + } + + ExcelPatternMask mask; + if (!TryGetMask(CellFillData.PatternStyle, out mask)) + { + return null; + } + + var fill = new PdfPatternMaskFill(mask, CellFillData.PatternColor, CellFillData.BackgroundColor); + var pattern = new PdfTilingPattern(objectNumber, fill, PatternBBox, PatternStep, PatternStep, version); + pattern.Matrix = PatternMatrix; + return pattern; + } + + /// + /// Maps an Excel cell fill style to the corresponding 8x8 pattern mask. + /// Returns false for styles that have no mask (e.g. None/Solid, or any + /// style not rendered as a tiling pattern). + /// + private static bool TryGetMask(ExcelFillStyle style, out ExcelPatternMask mask) + { + switch (style) + { + case ExcelFillStyle.DarkGray: mask = ExcelPatternMask.DarkGray; return true; + case ExcelFillStyle.MediumGray: mask = ExcelPatternMask.MediumGray; return true; + case ExcelFillStyle.LightGray: mask = ExcelPatternMask.LightGray; return true; + case ExcelFillStyle.Gray125: mask = ExcelPatternMask.Gray125; return true; + case ExcelFillStyle.Gray0625: mask = ExcelPatternMask.Gray0625; return true; + case ExcelFillStyle.DarkHorizontal: mask = ExcelPatternMask.DarkHorizontal; return true; + case ExcelFillStyle.DarkVertical: mask = ExcelPatternMask.DarkVertical; return true; + case ExcelFillStyle.DarkDown: mask = ExcelPatternMask.DarkDown; return true; + case ExcelFillStyle.DarkUp: mask = ExcelPatternMask.DarkUp; return true; + case ExcelFillStyle.DarkGrid: mask = ExcelPatternMask.DarkGrid; return true; + case ExcelFillStyle.DarkTrellis: mask = ExcelPatternMask.DarkTrellis; return true; + case ExcelFillStyle.LightHorizontal: mask = ExcelPatternMask.LightHorizontal; return true; + case ExcelFillStyle.LightVertical: mask = ExcelPatternMask.LightVertical; return true; + case ExcelFillStyle.LightDown: mask = ExcelPatternMask.LightDown; return true; + case ExcelFillStyle.LightUp: mask = ExcelPatternMask.LightUp; return true; + case ExcelFillStyle.LightGrid: mask = ExcelPatternMask.LightGrid; return true; + case ExcelFillStyle.LightTrellis: mask = ExcelPatternMask.LightTrellis; return true; + default: + mask = ExcelPatternMask.DarkGray; + return false; + } + } + } +} \ No newline at end of file diff --git a/src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfResource.cs b/src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfResource.cs new file mode 100644 index 0000000000..be53eba9be --- /dev/null +++ b/src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfResource.cs @@ -0,0 +1,34 @@ +/************************************************************************************************* + 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 + *************************************************************************************************/ +namespace OfficeOpenXml.Export.PdfExport.Data.Dictionaries +{ + internal class PdfResource + { + internal readonly string labelPrefix; + internal int labelNumber; + + internal string Label + { + get + { + return labelPrefix + labelNumber; + } + } + + public PdfResource(string labelPrefix, int labelNumber) + { + this.labelPrefix = labelPrefix; + this.labelNumber = labelNumber; + } + } +} diff --git a/src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfShadingResource.cs b/src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfShadingResource.cs new file mode 100644 index 0000000000..1bf9f6f0f1 --- /dev/null +++ b/src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfShadingResource.cs @@ -0,0 +1,58 @@ +/************************************************************************************************* + 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 OfficeOpenXml.Style; + +namespace OfficeOpenXml.Export.PdfExport.Data.Dictionaries +{ + internal class PdfShadingResource : PdfResource + { + internal int objectNumber; + internal PdfCellFillData CellFillData; + + public PdfShadingResource(int labelNumber, PdfCellFillData cellFillData) + : base("Sh", labelNumber) + { + CellFillData = cellFillData; + } + + public PdfShading GetShadingObject(int objectNumber, int version = 0) + { + this.objectNumber = objectNumber; + if (CellFillData.GradientFillData != null) + { + if (CellFillData.GradientFillData.GradientType == ExcelFillGradientType.Linear) + { + var pas = new PdfAxialShading(objectNumber, CellFillData.GradientFillData, version); + pas.Coords = CellFillData.GradientFillData.coords; + return pas; + } + else if (CellFillData.GradientFillData.GradientType == ExcelFillGradientType.Path) + { + var prs = new PdfRadialShading(objectNumber, CellFillData.GradientFillData, version); + prs.Coords = CellFillData.GradientFillData.coords; + return prs; + } + } + return null; + } + + public PdfShadingPattern GetShadingPatternObject(int patternObjectNumber, int shadingObjectNumber, int version = 0) + { + objectNumber = shadingObjectNumber; + var shadingPattern = new PdfShadingPattern(patternObjectNumber, shadingObjectNumber); + shadingPattern.Matrix = CellFillData.GradientFillData.matrix; + return shadingPattern; + } + + } +} diff --git a/src/EPPlus/Export/PdfExport/Data/PdfBorderData.cs b/src/EPPlus/Export/PdfExport/Data/PdfBorderData.cs new file mode 100644 index 0000000000..416aeddeea --- /dev/null +++ b/src/EPPlus/Export/PdfExport/Data/PdfBorderData.cs @@ -0,0 +1,76 @@ +/************************************************************************************************* + 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 OfficeOpenXml.Style; +using System.Drawing; + +namespace OfficeOpenXml.Export.PdfExport.Data +{ + internal class PdfBorderData + { + } + internal class PdfCellBordersData + { + public PdfCellBorderData Top = new PdfCellBorderData(LineType.Top); + public PdfCellBorderData Bottom = new PdfCellBorderData(LineType.Bottom); + public PdfCellBorderData Left = new PdfCellBorderData(LineType.Left); + public PdfCellBorderData Right = new PdfCellBorderData(LineType.Right); + public PdfCellBorderData DiagonalUp = new PdfCellBorderData(LineType.DiagonalUp); + public PdfCellBorderData DiagonalDown = new PdfCellBorderData(LineType.DiagonalDown); + + public PdfCellBordersData() { } + } + + internal enum LineType + { + Top = 0, + Bottom, + Left, + Right, + DiagonalUp, + DiagonalDown + } + + internal class PdfCellBorderData + { + internal const double OuterGridLine = 1d; + internal const double Hair = 0.5d; + internal const double Thin = 0.85d; + internal const double Small = 1.1d; + internal const double Medium = 1.5d; + internal const double Thick = 2.0d; + internal const string NoDash = "[] 0 d"; + internal const string Dotted = "[0 2] 0 d"; + internal const string DashDot = "[4 2 1 2] 0 d"; + internal const string DashDotDot = "[4 2 1 2 1 2] 0 d"; + internal const string Dashed = "[4 3] 0 d"; + internal const string MediumDashDot = "[6 3 2 3] 0 d"; + internal const string MediumDashDotDot = "[6 3 2 3 2 3] 0 d"; + internal const string MediumDashed = "[6 4] 0 d"; + + public ExcelBorderStyle BorderStyle = ExcelBorderStyle.None; + public readonly LineType LineType; + public Color BorderColor = Color.Black; + public double MergedDiagonalWidth = 0d; + public double MergedDiagonalHeight = 0d; + public double Width = 0; + public double Height = 0; + public double X = 0; + public double Y = 0; + public bool IsHeading = false; + + public PdfCellBorderData(LineType LineType) + { + this.LineType = LineType; + } + } +} diff --git a/src/EPPlus/Export/PdfExport/Data/PdfCell.cs b/src/EPPlus/Export/PdfExport/Data/PdfCell.cs new file mode 100644 index 0000000000..77c65ba5f0 --- /dev/null +++ b/src/EPPlus/Export/PdfExport/Data/PdfCell.cs @@ -0,0 +1,61 @@ +/************************************************************************************************* + 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.Export.Pdf.PdfLayout; +using EPPlus.Fonts.OpenType.Integration; +using OfficeOpenXml.Export.PdfExport.TextShaping; +using OfficeOpenXml.Style; +using System.Collections.Generic; + +namespace OfficeOpenXml.Export.PdfExport.Data +{ + internal class PdfCell + { + public string Name { get; set; } + public bool Hidden; + public PdfCellStyle CellStyle; + public PdfCellAlignmentData ContentAligmnet; + public List TextFragments { get; set; } + public List ShapedTexts { get; set; } + public TextLineCollection TextLines { get; set; } + public string Text { get; set; } + + + public double TotalTextLength { get; set; } + public double ColumnWidth { get; set; } + public double Width { get; set; } + public double Height { get; set; } + + public TextLayoutEngine TextLayoutEngine { get; set; } + + public bool Merged; + public PdfCell Main; + public ExcelAddressBase MergedAddress; + + public bool IsPrintTitleRow; + public bool IsPrintTitleCol; + } + + internal class PdfCellAlignmentData + { + public ExcelHorizontalAlignment HorizontalAlignment = ExcelHorizontalAlignment.General; + public ExcelVerticalAlignment VerticalAlignment = ExcelVerticalAlignment.Bottom; + public int Indent = 0; + public bool WrapText = false; + public bool ShrinkToFit = false; + public int TextRotation = 0; + public ExcelReadingOrder TextDirection = ExcelReadingOrder.ContextDependent; + public bool IsVertical = false; + + public PdfCellAlignmentData() { } + } +} diff --git a/src/EPPlus/Export/PdfExport/Data/PdfCellCollection.cs b/src/EPPlus/Export/PdfExport/Data/PdfCellCollection.cs new file mode 100644 index 0000000000..6b39fc7d9b --- /dev/null +++ b/src/EPPlus/Export/PdfExport/Data/PdfCellCollection.cs @@ -0,0 +1,68 @@ +/************************************************************************************************* + 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 System; + +namespace OfficeOpenXml.Export.PdfExport.Data +{ + internal class PdfCellCollection + { + private PdfCell[,] Cells; + + public readonly int FromRow; + public readonly int FromColumn; + public readonly int ToRow; + public readonly int ToColumn; + + public PdfCellCollection(int fromRow, int toRow, int fromColumn, int toColumn) + { + if(fromRow > toRow) throw new ArgumentOutOfRangeException("Invalid row range. toRow must be equal or greater than fromRow"); + if(fromColumn > toColumn) throw new ArgumentOutOfRangeException("Invalid column range. toColumn must be equal or greater than fromColumn"); + FromRow = fromRow; + FromColumn = fromColumn; + ToRow = toRow; + ToColumn = toColumn; + int x = toRow - fromRow + 1; + int y = toColumn - fromColumn + 1; + + Cells = new PdfCell[x, y]; + } + + public PdfCell this[int row, int column] + { + get + { + var r = row - FromRow; + var c = column - FromColumn; + if (r < 0 || c < 0 || r >= Cells.GetLength(0) || c >= Cells.GetLength(1)) + { + return null; + } + return Cells[r, c]; + } + set + { + Cells[row - FromRow, column - FromColumn] = value; + } + } + + public PdfCell GetInternal(int row, int column) + { + return Cells[row, column]; + } + + public void SetInternal(int row, int column, PdfCell value) + { + Cells[row, column] = value; + } + } +} diff --git a/src/EPPlus/Export/PdfExport/Data/PdfCellStyle.cs b/src/EPPlus/Export/PdfExport/Data/PdfCellStyle.cs new file mode 100644 index 0000000000..f5981d231a --- /dev/null +++ b/src/EPPlus/Export/PdfExport/Data/PdfCellStyle.cs @@ -0,0 +1,51 @@ +/************************************************************************************************* + 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 OfficeOpenXml.Style; +using OfficeOpenXml.Style.Dxf; + +namespace OfficeOpenXml.Export.PdfExport.Data +{ + /// + /// Holds the styles for the cell. + /// xf styles is the base style and dxf styles is an override(sort of) that can come from tables or conditional formatting. + /// They are used in the following priority: + /// 1. xf style that is not default + /// 2. dxf style if it exsist + /// 3. deault xf style + /// + internal class PdfCellStyle + { + //Fill + internal ExcelFill xfFill { get; set; } + internal ExcelDxfFill dxfFill { get; set; } + + //Borders + internal ExcelBorderItem xfTop { get; set; } + internal ExcelBorderItem xfBottom { get; set; } + internal ExcelBorderItem xfLeft { get; set; } + internal ExcelBorderItem xfRight { get; set; } + internal bool DiagonalUp { get; set; } + internal bool DiagonalDown { get; set; } + internal ExcelBorderItem Diagonal { get; set; } + internal ExcelDxfBorderItem dxfTop { get; set; } + internal ExcelDxfBorderItem dxfBottom { get; set; } + internal ExcelDxfBorderItem dxfLeft { get; set; } + internal ExcelDxfBorderItem dxfRight { get; set; } + internal ExcelDxfBorderItem dxfHorizontal { get; set; } + internal ExcelDxfBorderItem dxfVertical { get; set; } + + //Fonts + internal ExcelFont xfFont { get; set; } + internal ExcelDxfFontBase dxfFont { get; set; } + } +} diff --git a/src/EPPlus/Export/PdfExport/Data/PdfFillData.cs b/src/EPPlus/Export/PdfExport/Data/PdfFillData.cs new file mode 100644 index 0000000000..e2be4f63a6 --- /dev/null +++ b/src/EPPlus/Export/PdfExport/Data/PdfFillData.cs @@ -0,0 +1,50 @@ +/************************************************************************************************* + 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 OfficeOpenXml.Export.PdfExport.Helpers; +using OfficeOpenXml.Style; +using System.Drawing; + +namespace OfficeOpenXml.Export.PdfExport.Data +{ + internal class PdfCellGradientFillData + { + public ExcelFillGradientType GradientType; + public Color Color1; + public Color Color2; + public Color Color3; + public double Degree; + public double Top; + public double Bottom; + public double Left; + public double Right; + public double[] matrix; + public double[] coords; + + public override string ToString() + { + return GradientType.ToString() + Color1.ToHexString() + Color2.ToHexString() + Degree + Top + Bottom + Left + Right; + } + } + + internal class PdfCellFillData + { + public string id; + public Color BackgroundColor = Color.Empty; + public ExcelFillStyle PatternStyle = ExcelFillStyle.None; + public Color PatternColor = Color.Black; + //Fill Effects + public PdfCellGradientFillData GradientFillData = null; + public bool enhanceGridLine = false; + public PdfCellFillData() { } + } +} diff --git a/src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfContentBounds.cs b/src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfContentBounds.cs new file mode 100644 index 0000000000..638c4c21c9 --- /dev/null +++ b/src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfContentBounds.cs @@ -0,0 +1,95 @@ +/************************************************************************************************* + 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; + +namespace OfficeOpenXml.Export.PdfExport.Data.PdfPageSizes +{ + internal class PdfContentBounds : Rect + { + public double HeaderY; + public double FooterY; + public double CenterHeaderX; + public double RightHeaderX; + public double CenterFooterX; + public double RightFooterX; + + private double _top; + private double _bottom; + private double _left; + private double _right; + private double _height; + private double _width; + + public override double Top + { + get { return _top; } + set { _top = value; } + } + + public override double Bottom + { + get { return _bottom; } + set { _bottom = value; } + } + + public override double Left + { + get { return _left; } + set { _left = value; } + } + + public override double Right + { + get { return _right; } + set { _right = value; } + } + + public override double Width + { + get { return _width; } + set { _width = value; } + } + + public override double Height + { + get { return _height; } + set { _height = value; } + } + + public PdfContentBounds(PdfMargins margins, PdfPageSize pageSize) + { + CalculateBounds(margins, pageSize); + } + + internal void CalculateBounds(PdfMargins margins, PdfPageSize pageSize) + { + //Content bounds rectangle + Width = pageSize.WidthPu - margins.LeftPu - margins.RightPu; + X = margins.LeftPu; + Left = X; + Right = X + Width; + Y = margins.BottomPu; + Top = pageSize.HeightPu - margins.TopPu; + Bottom = Y; + Height = Top - Bottom; + //Header Footer + var hx = Width / 3d; + FooterY = margins.FooterPu; + CenterFooterX = Left + hx; + RightFooterX = Left + hx * 2; + HeaderY = pageSize.HeightPu - margins.HeaderPu; + CenterHeaderX = Left + hx; + RightHeaderX = Left + hx * 2; + } + } +} diff --git a/src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfMargins.cs b/src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfMargins.cs new file mode 100644 index 0000000000..8ce3968fb3 --- /dev/null +++ b/src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfMargins.cs @@ -0,0 +1,62 @@ +/************************************************************************************************* + 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.Units; + +namespace OfficeOpenXml.Export.PdfExport.Data.PdfPageSizes +{ + public class PdfMargins + { + public double Header { get; set; } = 7.6; + public double Footer { get; set; } = 7.6d; + public double Top { get; set; } = 19.1d; + public double Left { get; set; } = 17.8d; + public double Right { get; set; } = 17.8d; + public double Bottom { get; set; } = 19.1d; + public double HeaderPu { get; private set; } + public double FooterPu { get; private set; } + public double TopPu { get; private set; } + public double LeftPu { get; private set; } + public double RightPu { get; private set; } + public double BottomPu { get; private set; } + + public PdfMargins() + { + TopPu = UnitConversion.MmToPoints(Top); + LeftPu = UnitConversion.MmToPoints(Left); + RightPu = UnitConversion.MmToPoints(Right); + BottomPu = UnitConversion.MmToPoints(Bottom); + HeaderPu = UnitConversion.MmToPoints(Header); + FooterPu = UnitConversion.MmToPoints(Footer); + } + + public PdfMargins(double Top, double Left, double Right, double Bottom, double Header, double Footer) + { + this.Top = Top; + this.Left = Left; + this.Right = Right; + this.Bottom = Bottom; + this.Header = Header; + this.Footer = Footer; + TopPu = UnitConversion.MmToPoints(Top); + LeftPu = UnitConversion.MmToPoints(Left); + RightPu = UnitConversion.MmToPoints(Right); + BottomPu = UnitConversion.MmToPoints(Bottom); + HeaderPu = UnitConversion.MmToPoints(Header); + FooterPu = UnitConversion.MmToPoints(Footer); + } + + public static PdfMargins Normal => new PdfMargins(); + public static PdfMargins Wide => new PdfMargins(25.4d, 25.4d, 25.4d, 25.4d, 12.7d, 12.7d); + public static PdfMargins Narrow => new PdfMargins(19.1d, 6.4d, 6.4d, 19.1d, 7.6d, 7.6d); + } +} diff --git a/src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfPageSize.cs b/src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfPageSize.cs new file mode 100644 index 0000000000..031f2d09a4 --- /dev/null +++ b/src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfPageSize.cs @@ -0,0 +1,42 @@ +/************************************************************************************************* + 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.Units; + +namespace OfficeOpenXml.Export.PdfExport.Data.PdfPageSizes +{ + public class PdfPageSize + { + public double Width { get; } + public double Height { get; } + public double WidthPu { get; } + public double HeightPu { get; } + + public PdfPageSize(double width, double height) + { + Width = width; + Height = height; + WidthPu = System.Math.Round( UnitConversion.MmToPoints(width)); + HeightPu = System.Math.Round( UnitConversion.MmToPoints(height)); + } + + public static PdfPageSize A5 => new PdfPageSize(148d, 210d); + public static PdfPageSize A4 => new PdfPageSize(210d, 297d); //(595, 842); + public static PdfPageSize A3 => new PdfPageSize(297d, 420d); //(842, 1191); + public static PdfPageSize B5 => new PdfPageSize(182d, 257d); + public static PdfPageSize B4 => new PdfPageSize(257d, 364d); + public static PdfPageSize Letter => new PdfPageSize(215.9d, 279.4d); //(612, 792); + public static PdfPageSize Legal => new PdfPageSize(215.9d, 355.6d); //(612, 1008); + public static PdfPageSize Statement => new PdfPageSize(139.7d, 215.9d); + public static PdfPageSize Executive => new PdfPageSize(184.2d, 266.7d); + } +} \ No newline at end of file diff --git a/src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfScaling.cs b/src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfScaling.cs new file mode 100644 index 0000000000..72e2d60a5f --- /dev/null +++ b/src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfScaling.cs @@ -0,0 +1,66 @@ +/************************************************************************************************* + 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 + *************************************************************************************************/ +namespace OfficeOpenXml.Export.PdfExport.Data.PdfPageSizes +{ + public enum ScalingMode + { + /// + /// Adjust size based on the normal size + /// + AdjustToNormalSize, + /// + /// Adjust size based on number of pages + /// + FitToPages + } + + public class PdfScaling + { + public ScalingMode ScalingMode { get; set; } + + /// + /// Used when is set to AdjustToNormalSize. + /// The scale factor is expressed as a multiplier, where 1.0 represents 100% (no scaling), 0.5 represents 50%, and 2.0 represents 200%. + /// + public double Scale { get; set; } = 1.0; + + /// + /// Used when is set to FitToPages. + /// Scales the content to fit to the specifed number of pages in width. + /// + public int PagesWide { get; set; } = 1; + /// + /// Used when is set to FitToPages. + /// Scales the content to fit to the specifed number of pages in height. + /// + public int PagesTall { get; set; } = 1; + + public PdfScaling(double scale) + { + ScalingMode = ScalingMode.AdjustToNormalSize; + Scale = scale; + } + + public PdfScaling(int pagesWide, int pagesTall) + { + ScalingMode = ScalingMode.FitToPages; + PagesWide = pagesWide; + PagesTall = pagesTall; + } + + public static PdfScaling NoScaling => new PdfScaling(1d); + public static PdfScaling FitSheetToOnePage => new PdfScaling(1, 1); + public static PdfScaling FitAllColumnsOnOnePage => new PdfScaling(1, 0); + public static PdfScaling FitAllRowsOnOnePage => new PdfScaling(0, 1); + } +} \ No newline at end of file diff --git a/src/EPPlus/Export/PdfExport/Data/PdfRange.cs b/src/EPPlus/Export/PdfExport/Data/PdfRange.cs new file mode 100644 index 0000000000..cddbe8b907 --- /dev/null +++ b/src/EPPlus/Export/PdfExport/Data/PdfRange.cs @@ -0,0 +1,45 @@ +/************************************************************************************************* + 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 System.Collections.Generic; + +namespace OfficeOpenXml.Export.PdfExport.Data +{ + internal struct RowHeight + { + public double Height; + public bool UsesDefaultValue; + } + + internal struct PdfRange + { + public ExcelRangeBase Range { get; set; } + public bool ExtendColumns { get; set; } + public PdfCellCollection Map { get; set; } + public List RowHeights = new List(); + public List ColWidths = new List(); + public double TotalHeight; + public double TotalWidth; + public double AdditionalHeight; + public double AdditionalWidth; + public double PrintTitleHeight; + public double PrintTitleWidth; + public int PrintTitleRowTo = -1; + public int PrintTitleColTo = -1; + + public PdfRange(ExcelRangeBase range, bool extendColumns) + { + Range = range; + ExtendColumns = extendColumns; + } + } +} diff --git a/src/EPPlus/Export/PdfExport/Data/PdfWorksheet.cs b/src/EPPlus/Export/PdfExport/Data/PdfWorksheet.cs new file mode 100644 index 0000000000..9261792bac --- /dev/null +++ b/src/EPPlus/Export/PdfExport/Data/PdfWorksheet.cs @@ -0,0 +1,70 @@ +/************************************************************************************************* + 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.Fonts.OpenType; +using EPPlus.Fonts.OpenType.TextShaping; +using OfficeOpenXml.Export.PdfExport.TextMapping; +using OfficeOpenXml.Interfaces.Fonts; +using OfficeOpenXml.Style.XmlAccess; +using System.Collections.Generic; + +namespace OfficeOpenXml.Export.PdfExport.Data +{ + internal class PdfWorksheet + { + public Dictionary CommentsAndNotesCollections = new Dictionary(); + + public List Ranges = null; //Rename this + public PdfRange CommentsAndNotes; + public PdfHeaderFooterCollection HeaderFooters = null; + public double ZeroCharWidth; + public int ToRow; + public int PrintTitleRowFrom = -1; + public int PrintTitleRowTo = -1; + public int PrintTitleColFrom = -1; + public int PrintTitleColTo = -1; + + //EPPlus references + public ExcelWorksheet Worksheet { get; set; } + public ExcelWorksheet CommentsAndNotesSheet { get; set; } + public ExcelNamedStyleXml NormalStyle { get { return Worksheet.Workbook.Styles.GetNormalStyle(); } } + public FontSubFamily GetSubFamilyFromNormalStyle //move this to a helper class or something. + { + get + { + var nsf = NormalStyle.Style.Font; + var SubFamily = FontSubFamily.Regular; + if (nsf.Bold) + { + SubFamily = FontSubFamily.Bold; + if (nsf.Italic) + { + SubFamily = FontSubFamily.BoldItalic; + } + } + else if (nsf.Italic) + { + SubFamily = FontSubFamily.Italic; + } + return SubFamily; + } + } + + public static double GetThemeFont0Width(ExcelWorksheet ws) + { + var ns = ws.Workbook.Styles.GetNormalStyle(); + TextShaper shaper = OpenTypeFonts.GetTextShaper(ns.Style.Font.Name, FontSubFamily.Regular); + var shapedText = shaper.ShapeLight("0"); + return shapedText.GetWidthInPoints(ns.Style.Font.Size); + } + } +} \ No newline at end of file diff --git a/src/EPPlus/Export/PdfExport/Helpers/PdfColor.cs b/src/EPPlus/Export/PdfExport/Helpers/PdfColor.cs new file mode 100644 index 0000000000..f26c14339f --- /dev/null +++ b/src/EPPlus/Export/PdfExport/Helpers/PdfColor.cs @@ -0,0 +1,132 @@ +/************************************************************************************************* + 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 System; +using System.Drawing; +using System.Globalization; + +namespace OfficeOpenXml.Export.PdfExport.Helpers +{ + internal static class PdfColor + { + /// + /// Returns the red component of the color as a nomralized value. + /// + /// The color to extract the component + /// Normalized value of the color component. + public static float GetR(this Color c) => c.R / 255f; + + /// + /// Returns the green component of the color as a nomralized value. + /// + /// The color to extract the component + /// Normalized value of the color component. + public static float GetG(this Color c) => c.G / 255f; + + /// + /// Returns the blue component of the color as a nomralized value. + /// + /// The color to extract the component + /// Normalized value of the color component. + public static float GetB(this Color c) => c.B / 255f; + + /// + /// Returns the alpha component of the color as a nomralized value. + /// + /// The color to extract the component + /// Normalized value of the color component. + public static float GetA(this Color c) => c.A / 255f; + + /// + /// Convert a hex string representing a color into a color. + /// + /// String with corlor in hex code. + /// Valid inputs that sets color to Color.Empty: + /// "#0" + /// "0" + /// "" + /// null + /// + /// System.Drawing.Color object. + /// Throws exception of string is in invalid format. + public static Color SetColorFromHex(string hex) + { + if (string.IsNullOrEmpty(hex) || hex == "0" || hex == "#0") + { + return Color.Empty; + + } + hex = hex.Trim().TrimStart('#'); + int R=0, G=0, B=0, A=0; + if (hex.Length == 3) + { + R = Convert.ToByte(new string(hex[0], 2), 16); + G = Convert.ToByte(new string(hex[1], 2), 16); + B = Convert.ToByte(new string(hex[2], 2), 16); + + } + else if (hex.Length == 4) + { + A = Convert.ToByte(new string(hex[0], 2), 16); + R = Convert.ToByte(new string(hex[1], 2), 16); + G = Convert.ToByte(new string(hex[2], 2), 16); + B = Convert.ToByte(new string(hex[3], 2), 16); + } + else if (hex.Length == 6) + { + R = Convert.ToByte(hex.Substring(0, 2), 16); + G = Convert.ToByte(hex.Substring(2, 2), 16); + B = Convert.ToByte(hex.Substring(4, 2), 16); + } + else if (hex.Length == 8) + { + A = Convert.ToByte(hex.Substring(0, 2), 16); + R = Convert.ToByte(hex.Substring(2, 2), 16); + G = Convert.ToByte(hex.Substring(4, 2), 16); + B = Convert.ToByte(hex.Substring(6, 2), 16); + } + else + { + throw new FormatException("Invalid hex color format."); + } + return Color.FromArgb(A, R, G, B); + } + + /// + /// Returns a string for pdf command for stroke. + /// + /// The color to use for stroke. + /// The command string for a stroke color + public static string ToStrokeCommand(this Color c) => $"{c.GetR().ToString("F", CultureInfo.InvariantCulture)} {c.GetG().ToString("F", CultureInfo.InvariantCulture)} {c.GetB().ToString("F", CultureInfo.InvariantCulture)} RG"; + + /// + /// Returns a string for pdf command for fill. + /// + /// The color to use for fill. + /// The command string for a fill color + public static string ToFillCommand(this Color c) => $"{c.GetR().ToString("F", CultureInfo.InvariantCulture)} {c.GetG().ToString("F", CultureInfo.InvariantCulture)} {c.GetB().ToString("F", CultureInfo.InvariantCulture)} rg"; + + /// + /// Get the color represented in Hex as a string. + /// + /// The color which to return the hex value as a string. + /// String represeting the hex value of the color. + public static string ToHexString(this Color c) + { + int r = (int)(c.R); + int g = (int)(c.G); + int b = (int)(c.B); + int a = (int)(c.A); + return $"#{r:X2}{g:X2}{b:X2}{a:X2}"; + } + } +} diff --git a/src/EPPlus/Export/PdfExport/Helpers/PdfString.cs b/src/EPPlus/Export/PdfExport/Helpers/PdfString.cs new file mode 100644 index 0000000000..f0dd07f222 --- /dev/null +++ b/src/EPPlus/Export/PdfExport/Helpers/PdfString.cs @@ -0,0 +1,63 @@ +/************************************************************************************************* + 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 System.Globalization; + +namespace OfficeOpenXml.Export.PdfExport.Helpers +{ + internal static class PdfString + { + /// + /// Returns the value formated for use in pdf document. + /// + /// Value to turn into a string. + /// The value repsented as a string. + internal static string ToPdfString(this double val) + { + return val.ToString(CultureInfo.InvariantCulture); + } + + /// + /// Returns the value formated for use in pdf document. + /// + /// Value to turn into a string. + /// The value repsented as a string with 4 decimals. + internal static string ToPdfStringF4(this double val) + { + return val.ToString("F4", CultureInfo.InvariantCulture); + } + + /// + /// Returns the value formated for use in pdf document. + /// + /// Value to turn into a string. + /// The value repsented as a string with no decimals. + internal static string ToPdfStringF0(this double val) + { + return val.ToString("F0", CultureInfo.InvariantCulture); + } + + //Delete this method? + public static bool IsNullOrWhiteSpace(string s) + { + if (s == null) + return true; + + for (int i = 0; i < s.Length; i++) + { + if (!char.IsWhiteSpace(s[i])) + return false; + } + return true; + } + } +} diff --git a/src/EPPlus/Export/PdfExport/Layout/MergedCellCorners.cs b/src/EPPlus/Export/PdfExport/Layout/MergedCellCorners.cs new file mode 100644 index 0000000000..39f8e54b6e --- /dev/null +++ b/src/EPPlus/Export/PdfExport/Layout/MergedCellCorners.cs @@ -0,0 +1,44 @@ +/************************************************************************************************* + 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 System; + +namespace OfficeOpenXml.Export.PdfExport.Layout +{ /// + /// Flags that describe which corners of a merged-cell boundary are owned by + /// a particular layout object on the current page. + /// + /// A corner belongs to a layout object when BOTH of its edges land on the page: + /// TopLeft – fromRow and fromCol are within the page range + /// TopRight – fromRow and toCol are within the page range + /// BottomLeft – toRow and fromCol are within the page range + /// BottomRight – toRow and toCol are within the page range + /// + /// Possible combinations: + /// None – interior cell of a large merge; all four edges are + /// clipped by page boundaries. + /// 1 flag – only one corner of the merge lands on this page. + /// 2 flags – merge is exactly 1 row tall → TopLeft|TopRight or BottomLeft|BottomRight + /// merge is exactly 1 col wide → TopLeft|BottomLeft or TopRight|BottomRight + /// All (4 flags)– non-merged cell, or merged cell fully contained on this page. + /// + [Flags] + internal enum MergedCellCorners + { + None = 0, + TopLeft = 1 << 0, // 1 + TopRight = 1 << 1, // 2 + BottomLeft = 1 << 2, // 4 + BottomRight = 1 << 3, // 8 + All = TopLeft | TopRight | BottomLeft | BottomRight + } +} diff --git a/src/EPPlus/Export/PdfExport/Layout/PdfCellBorderLayout.cs b/src/EPPlus/Export/PdfExport/Layout/PdfCellBorderLayout.cs new file mode 100644 index 0000000000..f907ee7242 --- /dev/null +++ b/src/EPPlus/Export/PdfExport/Layout/PdfCellBorderLayout.cs @@ -0,0 +1,592 @@ +/************************************************************************************************* + 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 OfficeOpenXml.Export.PdfExport.Data; +using OfficeOpenXml.Export.PdfExport.Helpers; +using OfficeOpenXml.Style; +using OfficeOpenXml.Style.Dxf; +using OfficeOpenXml.Style.Table; +using OfficeOpenXml.Table; +using System.Diagnostics; +using System.Drawing; + +namespace OfficeOpenXml.Export.PdfExport.Layout +{ + [DebuggerDisplay("Border: {Name}")] + internal class PdfCellBorderLayout : Transform + { + public PdfCellBordersData BorderData; + public bool IsMerged = false; + public MergedCellDrawInfo MergedCellInfo; + public MergedCellCorners Corners; + public ExcelRangeBase cell; + public bool IsPrintTitle; + + public string range; + + internal PdfCellStyle TableStyle; + + public PdfCellBorderLayout() { } + + public PdfCellBorderLayout(PdfCellStyle style, bool isMerged, MergedCellCorners corners, MergedCellDrawInfo info, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) + : base(x, y - height, width, height, scaleX, scaleY, rotation, parent) + { + Z = 3; + IsMerged = isMerged; + MergedCellInfo = info; + Corners = corners; + + BorderData = new PdfCellBordersData(); + + BorderData.Top.BorderStyle = style.xfTop.Style == ExcelBorderStyle.None ? ((style.dxfTop != null && style.dxfTop.HasValue) ? (ExcelBorderStyle)style.dxfTop.Style : ExcelBorderStyle.None) : style.xfTop.Style; + BorderData.Top.BorderColor = style.dxfTop != null ? PdfColor.SetColorFromHex(style.dxfTop.Color.LookupColor(style.dxfTop)) : PdfColor.SetColorFromHex(style.xfTop.Color.LookupColor(style.xfTop)); + BorderData.Top.X = LocalPosition.X; + BorderData.Top.Y = LocalPosition.Y; + BorderData.Top.Width = Size.X; + BorderData.Top.Height = Size.Y; + + BorderData.Bottom.BorderStyle = style.xfBottom.Style == ExcelBorderStyle.None ? ((style.dxfBottom != null && style.dxfBottom.HasValue) ? (ExcelBorderStyle)style.dxfBottom.Style : ExcelBorderStyle.None) : style.xfBottom.Style; + BorderData.Bottom.BorderColor = style.dxfBottom != null ? PdfColor.SetColorFromHex(style.dxfBottom.Color.LookupColor(style.dxfBottom)) : PdfColor.SetColorFromHex(style.xfBottom.Color.LookupColor(style.xfBottom)); + BorderData.Bottom.X = LocalPosition.X; + BorderData.Bottom.Y = LocalPosition.Y; + BorderData.Bottom.Width = Size.X; + BorderData.Bottom.Height = Size.Y; + + BorderData.Left.BorderStyle = style.xfLeft.Style == ExcelBorderStyle.None ? ((style.dxfLeft != null && style.dxfLeft.HasValue) ? (ExcelBorderStyle)style.dxfLeft.Style : ExcelBorderStyle.None) : style.xfLeft.Style; + BorderData.Left.BorderColor = style.dxfLeft != null ? PdfColor.SetColorFromHex(style.dxfLeft.Color.LookupColor(style.dxfLeft)) : PdfColor.SetColorFromHex(style.xfLeft.Color.LookupColor(style.xfLeft)); + BorderData.Left.X = LocalPosition.X; + BorderData.Left.Y = LocalPosition.Y; + BorderData.Left.Width = Size.X; + BorderData.Left.Height = Size.Y; + + BorderData.Right.BorderStyle = style.xfRight.Style == ExcelBorderStyle.None ? ((style.dxfRight != null && style.dxfRight.HasValue) ? (ExcelBorderStyle)style.dxfRight.Style : ExcelBorderStyle.None) : style.xfRight.Style; + BorderData.Right.BorderColor = style.dxfRight != null ? PdfColor.SetColorFromHex(style.dxfRight.Color.LookupColor(style.dxfRight)) : PdfColor.SetColorFromHex(style.xfRight.Color.LookupColor(style.xfRight)); + BorderData.Right.X = LocalPosition.X; + BorderData.Right.Y = LocalPosition.Y; + BorderData.Right.Width = Size.X; + BorderData.Right.Height = Size.Y; + + BorderData.DiagonalUp.BorderStyle = style.DiagonalUp ? style.Diagonal.Style : ExcelBorderStyle.None; + BorderData.DiagonalUp.BorderColor = style.DiagonalUp ? PdfColor.SetColorFromHex(style.Diagonal.Color.LookupColor(style.Diagonal)) : Color.Transparent; + BorderData.DiagonalUp.MergedDiagonalWidth = width; + BorderData.DiagonalUp.MergedDiagonalHeight = height; + + BorderData.DiagonalDown.BorderStyle = style.DiagonalDown ? style.Diagonal.Style : ExcelBorderStyle.None; + BorderData.DiagonalDown.BorderColor = style.DiagonalDown ? PdfColor.SetColorFromHex(style.Diagonal.Color.LookupColor(style.Diagonal)) : Color.Transparent; + BorderData.DiagonalDown.MergedDiagonalWidth = width; + BorderData.DiagonalDown.MergedDiagonalHeight = height; + } + + + + + + + public PdfCellBorderLayout(ExcelRangeBase cell, PdfCellStyle tableStyle, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) + : base(x, y-height, width, height, scaleX, scaleY, rotation, parent) + { + this.cell = cell; + this.TableStyle = tableStyle; + if (cell != null) + { + IsMerged = cell.Merge; + BorderData = new PdfCellBordersData(); + } + } + + public void InitEdgeBorders(ExcelRangeBase cell) + { + if (cell != null) + { + //BorderData.Top = new PdfCellBorderData(LineType.Top); + BorderData.Top.BorderStyle = TableStyle.xfTop.Style == ExcelBorderStyle.None ? ((TableStyle.dxfTop != null && TableStyle.dxfTop.HasValue) ? (ExcelBorderStyle)TableStyle.dxfTop.Style : ExcelBorderStyle.None ) : TableStyle.xfTop.Style; + BorderData.Top.BorderColor = TableStyle.dxfTop != null ? PdfColor.SetColorFromHex(TableStyle.dxfTop.Color.LookupColor(TableStyle.dxfTop)) : PdfColor.SetColorFromHex(cell.Style.Border.Top.Color.LookupColor(cell.Style.Border)); + BorderData.Top.X = LocalPosition.X; + BorderData.Top.Y = LocalPosition.Y; + BorderData.Top.Width = Size.X; + BorderData.Top.Height = Size.Y; + //BorderData.Bottom = new PdfCellBorderData(LineType.Bottom); + BorderData.Bottom.BorderStyle = TableStyle.xfBottom.Style == ExcelBorderStyle.None ? ((TableStyle.dxfBottom != null && TableStyle.dxfBottom.HasValue) ? (ExcelBorderStyle)TableStyle.dxfBottom.Style : ExcelBorderStyle.None) : TableStyle.xfBottom.Style; + BorderData.Bottom.BorderColor = TableStyle.dxfBottom != null ? PdfColor.SetColorFromHex(TableStyle.dxfBottom.Color.LookupColor(TableStyle.dxfBottom)) : PdfColor.SetColorFromHex(cell.Style.Border.Bottom.Color.LookupColor(cell.Style.Border)); + BorderData.Bottom.X = LocalPosition.X; + BorderData.Bottom.Y = LocalPosition.Y; + BorderData.Bottom.Width = Size.X; + BorderData.Bottom.Height = Size.Y; + //BorderData.Left = new PdfCellBorderData(LineType.Left); + BorderData.Left.BorderStyle = TableStyle.xfLeft.Style == ExcelBorderStyle.None ? ((TableStyle.dxfLeft != null && TableStyle.dxfLeft.HasValue) ? (ExcelBorderStyle)TableStyle.dxfLeft.Style : ExcelBorderStyle.None) : TableStyle.xfLeft.Style; + BorderData.Left.BorderColor = TableStyle.dxfLeft != null ? PdfColor.SetColorFromHex(TableStyle.dxfLeft.Color.LookupColor(TableStyle.dxfLeft)) : PdfColor.SetColorFromHex(cell.Style.Border.Left.Color.LookupColor(cell.Style.Border)); + BorderData.Left.X = LocalPosition.X; + BorderData.Left.Y = LocalPosition.Y; + BorderData.Left.Width = Size.X; + BorderData.Left.Height = Size.Y; + //BorderData.Right = new PdfCellBorderData(LineType.Right); + BorderData.Right.BorderStyle = TableStyle.xfRight.Style == ExcelBorderStyle.None ? ((TableStyle.dxfRight != null && TableStyle.dxfRight.HasValue) ? (ExcelBorderStyle)TableStyle.dxfRight.Style : ExcelBorderStyle.None) : TableStyle.xfRight.Style; + BorderData.Right.BorderColor = TableStyle.dxfRight != null ? PdfColor.SetColorFromHex(TableStyle.dxfRight.Color.LookupColor(TableStyle.dxfRight)) : PdfColor.SetColorFromHex(cell.Style.Border.Right.Color.LookupColor(cell.Style.Border)); + BorderData.Right.X = LocalPosition.X; + BorderData.Right.Y = LocalPosition.Y; + BorderData.Right.Width = Size.X; + BorderData.Right.Height = Size.Y; + + + ////BorderData.Top = new PdfCellBorderData(LineType.Top); + //BorderData.Top.BorderStyle = cell.Style.Border.Top.Style; + //BorderData.Top.BorderColor = PdfColor.SetColorFromHex(cell.Style.Border.Top.Color.LookupColor(cell.Style.Border)); + //BorderData.Top.X = LocalPosition.X; + //BorderData.Top.Y = LocalPosition.Y; + //BorderData.Top.Width = Size.X; + //BorderData.Top.Height = Size.Y; + ////BorderData.Bottom = new PdfCellBorderData(LineType.Bottom); + //BorderData.Bottom.BorderStyle = cell.Style.Border.Bottom.Style; + //BorderData.Bottom.BorderColor = PdfColor.SetColorFromHex(cell.Style.Border.Bottom.Color.LookupColor(cell.Style.Border)); + //BorderData.Bottom.X = LocalPosition.X; + //BorderData.Bottom.Y = LocalPosition.Y; + //BorderData.Bottom.Width = Size.X; + //BorderData.Bottom.Height = Size.Y; + ////BorderData.Left = new PdfCellBorderData(LineType.Left); + //BorderData.Left.BorderStyle = cell.Style.Border.Left.Style; + //BorderData.Left.BorderColor = PdfColor.SetColorFromHex(cell.Style.Border.Left.Color.LookupColor(cell.Style.Border)); + //BorderData.Left.X = LocalPosition.X; + //BorderData.Left.Y = LocalPosition.Y; + //BorderData.Left.Width = Size.X; + //BorderData.Left.Height = Size.Y; + ////BorderData.Right = new PdfCellBorderData(LineType.Right); + //BorderData.Right.BorderStyle = cell.Style.Border.Right.Style; + //BorderData.Right.BorderColor = PdfColor.SetColorFromHex(cell.Style.Border.Right.Color.LookupColor(cell.Style.Border)); + //BorderData.Right.X = LocalPosition.X; + //BorderData.Right.Y = LocalPosition.Y; + //BorderData.Right.Width = Size.X; + //BorderData.Right.Height = Size.Y; + } + } + + public void InitDiagonalBorders(ExcelRangeBase cell, double width, double height) + { + if (cell != null) + { + //BorderData.DiagonalUp = new PdfCellBorderData(LineType.DiagonalUp); + BorderData.DiagonalUp.BorderStyle = cell.Style.Border.DiagonalUp ? cell.Style.Border.Diagonal.Style : ExcelBorderStyle.None; + BorderData.DiagonalUp.BorderColor = PdfColor.SetColorFromHex(cell.Style.Border.Diagonal.Color.LookupColor(cell.Style.Border)); + BorderData.DiagonalUp.MergedDiagonalWidth = width; + BorderData.DiagonalUp.MergedDiagonalHeight = height; + //BorderData.DiagonalDown = new PdfCellBorderData(LineType.DiagonalDown); + BorderData.DiagonalDown.BorderStyle = cell.Style.Border.DiagonalDown ? cell.Style.Border.Diagonal.Style : ExcelBorderStyle.None; + BorderData.DiagonalDown.BorderColor = PdfColor.SetColorFromHex(cell.Style.Border.Diagonal.Color.LookupColor(cell.Style.Border)); + BorderData.DiagonalDown.MergedDiagonalWidth = width; + BorderData.DiagonalDown.MergedDiagonalHeight = height; + } + } + + public void UpdateLocalBorderPosition() + { + //if (cell.Address == FirstCellInMerge) + //{ + // BorderData.Top.X = LocalPosition.X; + // BorderData.Top.Y = LocalPosition.Y + Size.Y - BorderData.Top.Height; + //} + //else + //{ + // BorderData.Top.X = LocalPosition.X; + // BorderData.Top.Y = LocalPosition.Y; + //} + //BorderData.Bottom.X = LocalPosition.X; + //BorderData.Bottom.Y = LocalPosition.Y; + //if (cell.Address == FirstCellInMerge) + //{ + // BorderData.Left.X = LocalPosition.X; + // BorderData.Left.Y = LocalPosition.Y + Size.Y - BorderData.Left.Height; + //} + //else + //{ + // BorderData.Left.X = LocalPosition.X; + // BorderData.Left.Y = LocalPosition.Y; + //} + //BorderData.Right.X = LocalPosition.X; + //BorderData.Right.Y = LocalPosition.Y; + } + + + //Get Methods fort border styles + /* +  Whole Table +  First Column Stripe +  Second Column Stripe +  First Row Stripe +  Second Row Stripe +  Last Column +  First Column +  Header Row +  Total Row +  First Header Cell +  Last Header Cell +  First Total Cell +  Last Total Cell + */ + + + + /* + * Following methods needs to be refactored into 1 method. + * Less code is more good. + */ + public static ExcelDxfBorderItem GetTopBorderItem(ExcelRangeBase cell, ExcelBorderItem xfBorder, ExcelTable table, ExcelTableNamedStyle tableStyle) + { + var range = table.Range; + int tableRow = cell._fromRow - range._fromRow; + int tableCol = cell._fromCol - range._fromCol; + int ts = table.ShowHeader ? 1 : 0; + var top = tableRow == 0 ? tableStyle.WholeTable.Style.Border.Top : tableStyle.WholeTable.Style.Border.Horizontal; + if (table.ShowHeader && tableRow == 0) + { + if (tableStyle.HeaderRow.Style.Border.Top.HasValue) + { + top = tableStyle.HeaderRow.Style.Border.Top; + } + if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstHeaderCell.Style.Border.Top.HasValue) + { + top = tableStyle.FirstHeaderCell.Style.Border.Top; + } + if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastHeaderCell.Style.Border.Top.HasValue) + { + top = tableStyle.LastHeaderCell.Style.Border.Top; + } + } + else if (table.ShowTotal && cell._fromRow == range._toRow) + { + if (tableStyle.TotalRow.Style.Border.Top.HasValue) + { + top = tableStyle.TotalRow.Style.Border.Top; + } + if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstTotalCell.Style.Border.Top.HasValue) + { + top = tableStyle.FirstTotalCell.Style.Border.Top; + } + if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastTotalCell.Style.Border.Top.HasValue) + { + top = tableStyle.LastTotalCell.Style.Border.Top; + } + } + else + { + if (table.ShowColumnStripes &&/* tableStyle.FirstColumnStripe.Style.Border.Top.HasValue &&*/ (tableCol & 1) == 0) + { + if (cell._fromRow - ts > range._fromRow && cell._fromRow < range._toRow) + { + top = tableStyle.FirstColumnStripe.Style.Border.Horizontal; + } + else if (cell._fromRow <= range._toRow) + { + top = null; + } + else + { + top = tableStyle.FirstColumnStripe.Style.Border.Top; + } + } + if (table.ShowColumnStripes && /*tableStyle.SecondColumnStripe.Style.Border.Top.HasValue &&*/ (tableCol & 1) != 0) + { + if (cell._fromRow + ts > range._fromRow && cell._fromRow < range._toRow) + { + top = tableStyle.SecondColumnStripe.Style.Border.Horizontal; + } + else if (cell._fromRow <= range._toRow) + { + top = null; + } + else + { + top = tableStyle.SecondColumnStripe.Style.Border.Top; + } + } + if (table.ShowRowStripes && tableStyle.FirstRowStripe.Style.Border.Top.HasValue && (tableRow & 1) != 0) + { + top = tableStyle.FirstRowStripe.Style.Border.Top; + } + if (table.ShowRowStripes && tableStyle.SecondRowStripe.Style.Border.Top.HasValue && (tableRow & 1) == 0) + { + top = tableStyle.SecondRowStripe.Style.Border.Top; + } + if (table.ShowLastColumn && tableStyle.LastColumn.Style.Border.Top.HasValue && cell._fromCol == range._toCol) + { + top = tableStyle.LastColumn.Style.Border.Top; + } + if (table.ShowFirstColumn && tableStyle.FirstColumn.Style.Border.Top.HasValue && tableCol == range._toCol) + { + top = tableStyle.FirstColumn.Style.Border.Top; + } + } + return top; + } + public static ExcelDxfBorderItem GetBottomBorderItem(ExcelRangeBase cell, ExcelBorderItem xfBorder, ExcelTable table, ExcelTableNamedStyle tableStyle) + { + var range = table.Range; + int tableRow = cell._fromRow - range._fromRow; + int tableCol = cell._fromCol - range._fromCol; + var bottom = range._toRow == cell._fromRow ? tableStyle.WholeTable.Style.Border.Bottom : null; + if (table.ShowHeader && tableRow == 0) + { + if (tableStyle.HeaderRow.Style.Border.Bottom.HasValue) + { + bottom = tableStyle.HeaderRow.Style.Border.Bottom; + } + if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstHeaderCell.Style.Border.Bottom.HasValue) + { + bottom = tableStyle.FirstHeaderCell.Style.Border.Bottom; + } + if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastHeaderCell.Style.Border.Bottom.HasValue) + { + bottom = tableStyle.LastHeaderCell.Style.Border.Bottom; + } + } + else if (table.ShowTotal && cell._fromRow == range._toRow) + { + if (tableStyle.TotalRow.Style.Border.Bottom.HasValue) + { + bottom = tableStyle.TotalRow.Style.Border.Bottom; + } + if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstTotalCell.Style.Border.Bottom.HasValue) + { + bottom = tableStyle.FirstTotalCell.Style.Border.Bottom; + } + if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastTotalCell.Style.Border.Bottom.HasValue) + { + bottom = tableStyle.LastTotalCell.Style.Border.Bottom; + } + } + else + { + if (table.ShowColumnStripes && tableStyle.FirstColumnStripe.Style.Border.Bottom.HasValue && (tableCol & 1) != 0) + { + if (cell._fromRow > range._fromRow && cell._fromRow < range._toRow) + { + bottom = tableStyle.FirstColumnStripe.Style.Border.Horizontal; + } + else if (cell._fromRow < range._toRow) + { + bottom = null; + } + else + { + bottom = tableStyle.FirstColumnStripe.Style.Border.Bottom; + } + } + if (table.ShowColumnStripes && tableStyle.SecondColumnStripe.Style.Border.Bottom.HasValue && (tableCol & 1) == 0) + { + if (cell._fromRow > range._fromRow && cell._fromRow < range._toRow) + { + bottom = tableStyle.SecondColumnStripe.Style.Border.Horizontal; + } + else if (cell._fromRow < range._toRow) + { + bottom = null; + } + else + { + bottom = tableStyle.SecondColumnStripe.Style.Border.Bottom; + } + } + if (table.ShowRowStripes && tableStyle.FirstRowStripe.Style.Border.Bottom.HasValue && (tableRow & 1) != 0) + { + bottom = tableStyle.FirstRowStripe.Style.Border.Bottom; + } + if (table.ShowRowStripes && tableStyle.SecondRowStripe.Style.Border.Bottom.HasValue && (tableRow & 1) == 0) + { + bottom = tableStyle.SecondRowStripe.Style.Border.Bottom; + } + if (table.ShowLastColumn && tableStyle.LastColumn.Style.Border.Bottom.HasValue && cell._fromCol == range._toCol) + { + bottom = tableStyle.LastColumn.Style.Border.Bottom; + } + if (table.ShowFirstColumn && tableStyle.FirstColumn.Style.Border.Bottom.HasValue && tableCol == 0) + { + bottom = tableStyle.FirstColumn.Style.Border.Bottom; + } + } + return bottom; + } + public static ExcelDxfBorderItem GetLeftBorderItem(ExcelRangeBase cell, ExcelBorderItem xfBorder, ExcelTable table, ExcelTableNamedStyle tableStyle) + { + var range = table.Range; + int tableRow = cell._fromRow - range._fromRow; + int tableCol = cell._fromCol - range._fromCol; + var left = tableCol == 0 ? tableStyle.WholeTable.Style.Border.Left : tableStyle.WholeTable.Style.Border.Vertical; + if (table.ShowHeader && tableRow == 0) + { + if (tableStyle.HeaderRow.Style.Border.Left.HasValue) + { + left = tableStyle.HeaderRow.Style.Border.Left; + } + if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstHeaderCell.Style.Border.Left.HasValue) + { + left = tableStyle.FirstHeaderCell.Style.Border.Left; + } + if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastHeaderCell.Style.Border.Left.HasValue) + { + left = tableStyle.LastHeaderCell.Style.Border.Left; + } + } + else if (table.ShowTotal && cell._fromRow == range._toRow) + { + if (tableStyle.TotalRow.Style.Border.Left.HasValue) + { + left = tableStyle.TotalRow.Style.Border.Left; + } + if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstTotalCell.Style.Border.Left.HasValue) + { + left = tableStyle.FirstTotalCell.Style.Border.Left; + } + if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastTotalCell.Style.Border.Left.HasValue) + { + left = tableStyle.LastTotalCell.Style.Border.Left; + } + } + else + { + if (table.ShowColumnStripes && tableStyle.FirstColumnStripe.Style.Border.Left.HasValue && (tableCol & 1) != 0) + { + left = tableStyle.FirstColumnStripe.Style.Border.Left; + } + if (table.ShowColumnStripes && tableStyle.SecondColumnStripe.Style.Border.Left.HasValue && (tableCol & 1) == 0) + { + left = tableStyle.SecondColumnStripe.Style.Border.Left; + } + if (table.ShowRowStripes && tableStyle.FirstRowStripe.Style.Border.Left.HasValue && (tableRow & 1) != 0) + { + if (cell._fromCol > range._fromCol && cell._fromCol < range._toCol) + { + left = tableStyle.FirstRowStripe.Style.Border.Vertical; + } + else if (cell._fromCol >= range._toCol) + { + left = null; + } + else + { + left = tableStyle.FirstRowStripe.Style.Border.Left; + } + } + if (table.ShowRowStripes && tableStyle.SecondRowStripe.Style.Border.Left.HasValue && (tableRow & 1) == 0) + { + if (cell._fromCol > range._fromCol && cell._fromCol < range._toCol) + { + left = tableStyle.SecondRowStripe.Style.Border.Vertical; + } + else if (cell._fromCol >= range._toCol) + { + left = null; + } + else + { + left = tableStyle.SecondRowStripe.Style.Border.Left; + } + } + if (table.ShowLastColumn && tableStyle.LastColumn.Style.Border.Left.HasValue && cell._fromCol == range._toCol) + { + left = tableStyle.LastColumn.Style.Border.Left; + } + if (table.ShowFirstColumn && tableStyle.FirstColumn.Style.Border.Left.HasValue && tableCol == range._toCol) + { + left = tableStyle.FirstColumn.Style.Border.Left; + } + } + return left; + } + public static ExcelDxfBorderItem GetRightBorderItem(ExcelRangeBase cell, ExcelBorderItem xfBorder, ExcelTable table, ExcelTableNamedStyle tableStyle) + { + var range = table.Range; + int tableRow = cell._fromRow - range._fromRow; + int tableCol = cell._fromCol - range._fromCol; + var right = cell._fromCol == range._toCol ? tableStyle.WholeTable.Style.Border.Right : tableStyle.WholeTable.Style.Border.Vertical; + if (table.ShowHeader && tableRow == 0) + { + if (tableStyle.HeaderRow.Style.Border.Right.HasValue) + { + right = tableStyle.HeaderRow.Style.Border.Right; + } + if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstHeaderCell.Style.Border.Right.HasValue) + { + right = tableStyle.FirstHeaderCell.Style.Border.Right; + } + if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastHeaderCell.Style.Border.Right.HasValue) + { + right = tableStyle.LastHeaderCell.Style.Border.Right; + } + } + else if (table.ShowTotal && cell._fromRow == range._toRow) + { + if (tableStyle.TotalRow.Style.Border.Right.HasValue) + { + right = tableStyle.TotalRow.Style.Border.Right; + } + if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstTotalCell.Style.Border.Right.HasValue) + { + right = tableStyle.FirstTotalCell.Style.Border.Right; + } + if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastTotalCell.Style.Border.Right.HasValue) + { + right = tableStyle.LastTotalCell.Style.Border.Right; + } + } + else + { + if (table.ShowColumnStripes && tableStyle.FirstColumnStripe.Style.Border.Right.HasValue && (tableCol & 1) != 0) + { + right = tableStyle.FirstColumnStripe.Style.Border.Right; + } + if (table.ShowColumnStripes && tableStyle.SecondColumnStripe.Style.Border.Right.HasValue && (tableCol & 1) == 0) + { + right = tableStyle.SecondColumnStripe.Style.Border.Right; + } + if (table.ShowRowStripes && tableStyle.FirstRowStripe.Style.Border.Right.HasValue && (tableRow & 1) != 0) + { + if (cell._fromCol > range._fromCol && cell._fromCol < range._toCol) + { + right = tableStyle.FirstRowStripe.Style.Border.Vertical; + } + else if (cell._fromCol < range._toCol) + { + right = null; + } + else + { + right = tableStyle.FirstRowStripe.Style.Border.Right; + } + } + if (table.ShowRowStripes && tableStyle.SecondRowStripe.Style.Border.Right.HasValue && (tableRow & 1) == 0) + { + if (cell._fromCol > range._fromCol && cell._fromCol < range._toCol) + { + right = tableStyle.SecondRowStripe.Style.Border.Vertical; + } + else if (cell._fromCol < range._toCol) + { + right = null; + } + else + { + right = tableStyle.SecondRowStripe.Style.Border.Right; + } + } + if (table.ShowLastColumn && tableStyle.LastColumn.Style.Border.Right.HasValue && cell._fromCol == range._toCol) + { + right = tableStyle.LastColumn.Style.Border.Right; + } + if (table.ShowFirstColumn && tableStyle.FirstColumn.Style.Border.Right.HasValue && tableCol == range._toCol) + { + right = tableStyle.FirstColumn.Style.Border.Right; + } + } + return right; + } + } +} + diff --git a/src/EPPlus/Export/PdfExport/Layout/PdfCellContentLayout.cs b/src/EPPlus/Export/PdfExport/Layout/PdfCellContentLayout.cs new file mode 100644 index 0000000000..a1eacdd80f --- /dev/null +++ b/src/EPPlus/Export/PdfExport/Layout/PdfCellContentLayout.cs @@ -0,0 +1,502 @@ +/************************************************************************************************* + 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.Fonts.OpenType.Integration; +using EPPlus.Graphics; +using OfficeOpenXml.Export.PdfExport.Data; +using OfficeOpenXml.Export.PdfExport.Data.Dictionaries; +using OfficeOpenXml.Export.PdfExport.TextShaping; +using OfficeOpenXml.Interfaces.Fonts; +using OfficeOpenXml.Style; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Linq; +using Vector2 = EPPlus.Graphics.Geometry.Vector2; + +namespace OfficeOpenXml.Export.PdfExport.Layout +{ + [DebuggerDisplay("Content: {Name}")] + internal class PdfCellContentLayout : Transform + { + public PdfCellAlignmentData CellAlignmentData; + public bool Clip; + public Rect Clipping; + public bool IsHeaderFooter; + public bool IsHeading; + public bool IsPrintTitle; + public PdfCellStyle CellStyle; + public ExcelRangeBase cell; + public TextLayoutEngine textLayoutEngine; + //private List textFormats = new List(); + public TextLineCollection TextLines; + public List ShapedTexts { get; set; } + + private double textLength = 0; + private double textHeight = 0; + public double LeftTextSpillLength = 0d; + public double RightTextSpillLength = 0d; + private double bottomMargin = 3.5d; //Guessed number + private double rightMargin = 1.4d; //I guessed this one too.. + //internal static FontMeasurerTrueType fontMeasurerTrueType = new FontMeasurerTrueType(); + //internal static MeasurementFont font = new MeasurementFont(); + + //public List TextFormats { get => textFormats; set => textFormats = value; } + public double TextLength { get => textLength; set => textLength = value; } + public double TextHeight { get => textHeight; set => textHeight = value; } + public TextLayoutEngine TextLayoutEngine { get => textLayoutEngine; set => textLayoutEngine = value; } + + + + public PdfCellContentLayout(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfCell cell, MergedCellDrawInfo mergedCellInfo, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) + : base(x, y-height, width, height, scaleX, scaleY, rotation, parent) + { + Z = 2; + CellAlignmentData = cell.ContentAligmnet; + TextLines = cell.TextLines; + ShapedTexts = cell.ShapedTexts; + TextLayoutEngine = cell.TextLayoutEngine; + double totalTextHeight = 0d; + foreach (var line in TextLines) + { + totalTextHeight += line.LargestAscent + line.LargestDescent; + } + double firstLineAscent = TextLines[0].LargestAscent; + double lastLineAscent = TextLines[TextLines.Count - 1].LargestAscent; + LocalPosition = CalculateAlignment(cell.Text, TextLines.LineFragments[0].Width, totalTextHeight, firstLineAscent, lastLineAscent, LocalPosition.X, LocalPosition.Y, cell.Width, height); + } + + public PdfCellContentLayout(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfHeaderFooter headerFooter, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) + : base(x, y, width, height, scaleX, scaleY, rotation, parent) + { + Z = 2; + TextLines = headerFooter.Content.TextLines; + ShapedTexts = headerFooter.Content.ShapedTexts; + TextLayoutEngine = headerFooter.Content.TextLayoutEngine; + CellAlignmentData = headerFooter.Content.ContentAligmnet; + double totalTextHeight = 0d; + foreach (var line in TextLines) + { + totalTextHeight += line.LargestAscent + line.LargestDescent; + } + var newX = CalculateHorizontalAlignment(TextLines.LineFragments[0].OriginalTextFragment.Text, TextLines[0].Width, LocalPosition.X, width, 0); + LocalPosition = new Vector2 (newX, LocalPosition.Y); + } + + private double CalculateVerticalAlignment(string text, double textHeight, double firstAscent, double lastAscent, double y, double height, double padding) + { + double newY = y; + switch (CellAlignmentData.VerticalAlignment) + { + case ExcelVerticalAlignment.Top: + newY = (y + height) - padding - firstAscent; + break; + case ExcelVerticalAlignment.Center: + newY = y + (height + textHeight - firstAscent - lastAscent) / 2d; + break; + case ExcelVerticalAlignment.Bottom: + newY = y + padding + textHeight - lastAscent; + break; + } + return newY; + } + + private double CalculateHorizontalAlignment(string text, double textLength, double x, double width, double padding) + { + double newX = x; + switch (CellAlignmentData.HorizontalAlignment) + { + case ExcelHorizontalAlignment.Fill: + case ExcelHorizontalAlignment.General: + if (double.TryParse(text, out double value)) + { + newX = x + (width - textLength) - padding; + } + else + { + newX = x + padding; + } + break; + case ExcelHorizontalAlignment.Left: + newX = x + padding; + break; + case ExcelHorizontalAlignment.Center: + newX = x + (width - textLength) / 2d; + break; + case ExcelHorizontalAlignment.Right: + newX = x + (width - textLength) - padding; + break; + } + return newX; + } + + private Vector2 CalculatePositionFromRotation(double textLength, double x, double y) + { + double newX = x; + double newY = y; + if (CellAlignmentData.TextRotation < 0) + { + double rot = CellAlignmentData.TextRotation * System.Math.PI / 180.0; + newX += textLength * (1 - System.Math.Cos(rot)); + newY -= textLength * System.Math.Sin(rot); + } + else if (CellAlignmentData.TextRotation > 0) + { + double rot = CellAlignmentData.TextRotation * System.Math.PI / 180.0; + newX += textLength * (1 - System.Math.Cos(rot)); + } + return new Vector2(newX, newY); + } + + private Vector2 CalculateAlignment(string text, double textLength, double textHeight, double firstLineAscent, double lastLineAscent, double x, double y, double width, double height) + { + if (CellAlignmentData.TextRotation != 0 && !CellAlignmentData.IsVertical) + { + return CalculateRotatedAlignment(textLength, textHeight, firstLineAscent, x, y, width, height); + } + double newX = CalculateHorizontalAlignment(text, textLength, x, width, rightMargin); + double newY = CalculateVerticalAlignment(text, textHeight, firstLineAscent, lastLineAscent, y, height, 0d); + return CalculatePositionFromRotation(textLength, newX, newY); + } + + private Vector2 CalculateRotatedAlignment(double textLength, double textHeight, double firstLineAscent, double x, double y, double width, double height) + { + double ascent = firstLineAscent; + double descent = textHeight - firstLineAscent; + if (descent < 0d) descent = 0d; + + double theta = CellAlignmentData.TextRotation * System.Math.PI / 180.0; + double cos = System.Math.Cos(theta); + double sin = System.Math.Sin(theta); + + // Bounding box of the rotated block. Reading direction spans [0, textLength]; + // the cross (line-height) direction spans [-descent, ascent]. + double[] gx = { 0d, textLength, 0d, textLength }; + double[] gy = { ascent, ascent, -descent, -descent }; + double minX = double.MaxValue, maxX = double.MinValue, minY = double.MaxValue, maxY = double.MinValue; + for (int i = 0; i < 4; i++) + { + double ux = cos * gx[i] - sin * gy[i]; + double uy = sin * gx[i] + cos * gy[i]; + if (ux < minX) minX = ux; + if (ux > maxX) maxX = ux; + if (uy < minY) minY = uy; + if (uy > maxY) maxY = uy; + } + double blockWidth = maxX - minX; + double blockHeight = maxY - minY; + + double bx; + switch (CellAlignmentData.HorizontalAlignment) + { + case ExcelHorizontalAlignment.Left: + bx = x + rightMargin; break; + case ExcelHorizontalAlignment.Right: + bx = x + width - blockWidth - rightMargin; break; + default: // Center / General + bx = x + (width - blockWidth) / 2d; break; + } + + double by; + switch (CellAlignmentData.VerticalAlignment) + { + case ExcelVerticalAlignment.Top: + by = y + height - blockHeight - bottomMargin; break; + case ExcelVerticalAlignment.Bottom: + by = y + bottomMargin; break; + default: // Center / Justify / Distributed + by = y + (height - blockHeight) / 2d; break; + } + + // Convert the bounding-box lower-left back to the baseline origin the matrix expects. + return new Vector2(bx - minX, by - minY); + } + + + + + + public PdfCellContentLayout(ExcelRangeBase cell, PdfCellStyle CellStyle, PdfPageSettings pageSettings, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null, PdfDictionaries dictionaries = null) + : base(x, y, width, height, scaleX, scaleY, rotation, parent) + { + this.cell = cell; + this.CellStyle = CellStyle; + CellAlignmentData = new PdfCellAlignmentData(); + CellAlignmentData.HorizontalAlignment = cell.Style.HorizontalAlignment; + CellAlignmentData.VerticalAlignment = cell.Style.VerticalAlignment; + CellAlignmentData.Indent = cell.Style.Indent; + CellAlignmentData.WrapText = cell.Style.WrapText; + CellAlignmentData.ShrinkToFit = cell.Style.ShrinkToFit; + CellAlignmentData.TextRotation = (cell.Style.TextRotation > 90) ? ((cell.Style.TextRotation == 255) ? 0 : 90 - cell.Style.TextRotation) : cell.Style.TextRotation; + CellAlignmentData.IsVertical = cell.Style.TextRotation == 255 ? true : false; + CellAlignmentData.TextDirection = cell.Style.ReadingOrder; + if (!cell.IsRichText) cell._rtc = new ExcelRichTextCollection(cell.Text, cell); + //HandleText(pageSettings, dictionaries, x, y, width, height, CellAlignmentData.TextRotation, CellStyle); + //CalculateTextSpill(width, CellAlignmentData.TextRotation); + //LocalPosition = CalculateAlignmentPositionAndTextOffsets(cell, x, y, width, height); + Size = new Vector2(x + width - LocalPosition.X, y + height - LocalPosition.Y); + //CheckClipping(cell, x, y, width, height); + } + + public PdfCellContentLayout(string text, ExcelRangeBase cell, PdfCellStyle CellStyle, PdfPageSettings pageSettings, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null, PdfDictionaries dictionaries = null) + : base(x, y, width, height, scaleX, scaleY, rotation, parent) + { + this.cell = cell; + this.CellStyle = CellStyle; + CellAlignmentData = new PdfCellAlignmentData(); + CellAlignmentData.HorizontalAlignment = ExcelHorizontalAlignment.Center; + CellAlignmentData.VerticalAlignment = ExcelVerticalAlignment.Bottom; + cell._rtc = new ExcelRichTextCollection(text, cell); + var ns = cell.Worksheet.Workbook.Styles.GetNormalStyle(); + cell._rtc[0].FontName = ns.Style.Font.Name; + cell._rtc[0].Family = ns.Style.Font.Family; + cell._rtc[0].Size = ns.Style.Font.Size; + cell._rtc[0].Color = Color.Black; + //HandleText(pageSettings, dictionaries, x, y, width, height, CellAlignmentData.TextRotation, CellStyle); + //CalculateTextSpill(width, CellAlignmentData.TextRotation); + //LocalPosition = CalculateAlignmentPositionAndTextOffsets(cell, x, y, width, height); + Size = new Vector2(x + width - LocalPosition.X, y + height - LocalPosition.Y); + //CheckClipping(cell, x, y, width, height); + } + + public void CalculateTextSpill(double maxWidth, double rotation) + { + if (maxWidth < textLength) + { + if (rotation != 0) + { + var baseVec = new Vector2(textLength, 0); + var rad = rotation * Math.PI / 180.0d; + var rotVec = new Vector2(textLength * Math.Cos(rad), textLength * Math.Sin(rad)); + var length = Vector2.Project(rotVec, baseVec).Length; + if (length > maxWidth) + { + if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.General) + { + if (double.TryParse(cell.Value.ToString(), out double value)) + { + LeftTextSpillLength = textLength; + } + else + { + RightTextSpillLength = textLength; + } + } + else if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Left) + { + RightTextSpillLength = length; + } + else if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Right) + { + LeftTextSpillLength = length; + } + else if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Center) + { + LeftTextSpillLength = length / 2d; + RightTextSpillLength = length / 2d; + } + + } + return; + } + if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.General) + { + if (double.TryParse(cell.Value.ToString(), out double value)) + { + LeftTextSpillLength = textLength - Size.X; + } + else + { + RightTextSpillLength = textLength - Size.X; + } + } + else if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Left) + { + RightTextSpillLength = textLength - Size.X; + } + else if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Right) + { + LeftTextSpillLength = textLength - Size.X; + } + else if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Center) + { + LeftTextSpillLength = (textLength - Size.X) / 2d; + RightTextSpillLength = (textLength - Size.X) / 2d; + } + } + } + + //Calculate text position from alignment and offsets for each line of text. + public Vector2 CalculateAlignmentPositionAndTextOffsets(ExcelRangeBase cell, double cellX, double CellY, double cellWidth, double cellHeight) + { + double x = 0d; + double y = 0d; + double xOffset = 0d; + double yOffset = 0d; + switch (CellAlignmentData.HorizontalAlignment) + { + case ExcelHorizontalAlignment.Fill: + case ExcelHorizontalAlignment.General: + if (double.TryParse(cell.Value.ToString(), out double value)) + { + x = cellX + (cellWidth - textLength) - rightMargin; + } + else + { + x = cellX + rightMargin; + } + break; + case ExcelHorizontalAlignment.Left: + x = cellX + rightMargin; + break; + case ExcelHorizontalAlignment.Center: + x = cellX + (cellWidth - textLength) / 2d; + break; + case ExcelHorizontalAlignment.Right: + x = cellX + (cellWidth - textLength) - rightMargin; + break; + } + switch (CellAlignmentData.VerticalAlignment) + { + case ExcelVerticalAlignment.Top: + y = (CellY + cellHeight) - (textHeight / 2d) - bottomMargin; + break; + case ExcelVerticalAlignment.Center: + y = CellY + (cellHeight / 2d) - (textHeight / 4d); ; + break; + case ExcelVerticalAlignment.Bottom: + y = CellY + bottomMargin; + break; + } + if (CellAlignmentData.TextRotation < 0) + { + double rot = CellAlignmentData.TextRotation * System.Math.PI / 180.0; + x += textLength * (1 - System.Math.Cos(rot)); + y -= textLength * System.Math.Sin(rot); + } + else if (CellAlignmentData.TextRotation > 0) + { + double rot = CellAlignmentData.TextRotation * System.Math.PI / 180.0; + x += textLength * (1 - System.Math.Cos(rot)); + } + return new Vector2(x, y); + } + + //Check if clipping is needed. + + + //Check if clipping is needed. + + public void CheckClipping(ExcelRangeBase cell, double x, double y, double width, double height) + { + if (textLength >= width || cell.Merge) + { + if (cell.Merge || + CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Fill) + { + Clipping = new Rect() + { + X = x + rightMargin, + Y = y, + Width = width - rightMargin * 2, + Height = height + }; + } + } + } + + // Set clipping to the cell's own bounds. cellY is the top edge (same convention as the constructor). + internal void SetupClipping(double cellX, double cellY, double cellWidth, double cellHeight) + { + Clip = true; + Clipping = new Rect() + { + X = cellX + rightMargin, + Y = cellY - cellHeight, // bottom-left corner in PDF space + Width = cellWidth - rightMargin * 2, + Height = cellHeight + }; + } + + //Create clipping rectangle. + internal void CreateClippingRect(List cells) + { + if (Clip) + { + var cellName = Name.Split('_')[0]; + var pcc = cells.Where(x => x.Name.Contains(cellName)).Where(x => x is PdfCellLayout).ToList(); + if (pcc.Count > 0) + { + Clipping = new Rect() + { + X = pcc[0].LocalPosition.X + rightMargin, + Y = pcc[0].LocalPosition.Y, + Width = pcc[0].Size.X - rightMargin * 2, + Height = pcc[0].Size.Y + }; + } + } + } + + //Create clipping rectangle. + internal void CreateClippingRect(PdfCellLayout cell, double targetX) + { + //need a way to detect starting X position if text is spilled from left or right + var width = targetX - cell.LocalPosition.X; + Clipping = new Rect() + { + X = cell.LocalPosition.X + rightMargin, + Y = cell.LocalPosition.Y, + Width = width - rightMargin * 2, + Height = cell.Size.Y + }; + } + + internal void GidsAndCharMap(PdfDictionaries dictionaries) + { + foreach (var tf in ShapedTexts) + { + var usedFonts = tf.UsedFonts; + + foreach (var glyph in tf.ShapedText.Glyphs) + { + if (glyph.FontId >= usedFonts.Count) + continue; + + var font = usedFonts[glyph.FontId]; + + dictionaries.Fonts[font.FullName].Gids.Add(glyph.GlyphId); + dictionaries.Fonts[font.FullName].fontData = font; + + if (!dictionaries.Fonts[font.FullName].charactermappings.ContainsKey(glyph.GlyphId)) + { + var chars = ExtractCharactersForGlyph(glyph, tf.ShapedText.OriginalText); + if (!string.IsNullOrEmpty(chars)) + { + dictionaries.Fonts[font.FullName].charactermappings[glyph.GlyphId] = chars; + } + } + } + } + } + private string ExtractCharactersForGlyph(ShapedGlyph glyph, string textLine) + { + var chars = new System.Text.StringBuilder(); + for (int i = 0; i < glyph.CharCount && glyph.ClusterIndex + i < textLine.Length; i++) + { + chars.Append(textLine[glyph.ClusterIndex + i]); + } + return chars.ToString(); + } + } +} diff --git a/src/EPPlus/Export/PdfExport/Layout/PdfCellLayout.cs b/src/EPPlus/Export/PdfExport/Layout/PdfCellLayout.cs new file mode 100644 index 0000000000..c4b0964f80 --- /dev/null +++ b/src/EPPlus/Export/PdfExport/Layout/PdfCellLayout.cs @@ -0,0 +1,332 @@ +/************************************************************************************************* + 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 OfficeOpenXml.Export.PdfExport.Data; +using OfficeOpenXml.Export.PdfExport.Data.Dictionaries; +using OfficeOpenXml.Export.PdfExport.Helpers; +using OfficeOpenXml.Style; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Linq; + +namespace OfficeOpenXml.Export.PdfExport.Layout +{ + [DebuggerDisplay("Cell: {Name}")] + internal class PdfCellLayout : Transform + { + public ExcelRangeBase cell; + public PdfCellFillData CellFillData; + public PdfCellStyle CellStyle; + public double LeftTextSpillLength = 0d; + public double RightTextSpillLength = 0d; + public bool delete = false; + public bool IsHeading = false; + public bool IsPrintTitle = false; + + public PdfCellLayout(PdfDictionaries dictionaries, PdfCellStyle CellStyle, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) + : base(x, y - height, width, height, scaleX, scaleY, rotation, parent) + { + Z = 1; + var xfFill = CellStyle.xfFill; + var dxfFill = CellStyle.dxfFill; + CellFillData = new PdfCellFillData(); + if (dxfFill != null && xfFill.IsEmpty()) + { + CellFillData.PatternStyle = dxfFill.PatternType != null ? (ExcelFillStyle)dxfFill.PatternType : ExcelFillStyle.Solid; + if (CellFillData.PatternStyle == ExcelFillStyle.Solid) + { + CellFillData.BackgroundColor = PdfColor.SetColorFromHex(dxfFill.BackgroundColor.LookupColor()); + } + else if (CellFillData.PatternStyle != ExcelFillStyle.None) + { + CellFillData.BackgroundColor = PdfColor.SetColorFromHex(dxfFill.PatternColor.Color == null ? "#FFFFFFFF" : dxfFill.PatternColor.LookupColor()); + CellFillData.PatternColor = PdfColor.SetColorFromHex(dxfFill.BackgroundColor.LookupColor()); + CellFillData.id = AddPatternResourceData(dictionaries.Patterns, CellFillData.PatternStyle.ToString() + CellFillData.PatternColor.ToHexString() + CellFillData.BackgroundColor.ToHexString()); + } + else if (dxfFill.Gradient != null) + { + CellFillData.GradientFillData = new PdfCellGradientFillData(); + CellFillData.GradientFillData.GradientType = dxfFill.Gradient.GradientType == null ? ExcelFillGradientType.None : (ExcelFillGradientType)dxfFill.Gradient.GradientType; + CellFillData.GradientFillData.Color1 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[0].Color.LookupColor()); + CellFillData.GradientFillData.Color2 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[1].Color.LookupColor()); + CellFillData.GradientFillData.Color3 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[2].Color.LookupColor()); + CellFillData.GradientFillData.Degree = dxfFill.Gradient.Degree == null ? 0 : (double)dxfFill.Gradient.Degree; + CellFillData.GradientFillData.Top = dxfFill.Gradient.Top == null ? 0 : (double)dxfFill.Gradient.Top; + CellFillData.GradientFillData.Bottom = dxfFill.Gradient.Bottom == null ? 0 : (double)dxfFill.Gradient.Bottom; + CellFillData.GradientFillData.Left = dxfFill.Gradient.Left == null ? 0 : (double)dxfFill.Gradient.Left; + CellFillData.GradientFillData.Right = dxfFill.Gradient.Right == null ? 0 : (double)dxfFill.Gradient.Right; + CellFillData.id = CellFillData.GradientFillData.ToString() + $"_{x:F4}_{(y - height):F4}_{width:F4}_{height:F4}"; + AddShadingResourceData(dictionaries.Shadings, CellFillData.id); + } + } + else + { + if (xfFill.PatternType == ExcelFillStyle.Solid) + { + var bkgc = xfFill.BackgroundColor; + CellFillData.PatternStyle = xfFill.PatternType; + if (string.IsNullOrEmpty(bkgc.LookupColor()) && !string.IsNullOrEmpty(cell.Text)) + { + CellFillData.BackgroundColor = Color.Empty; + } + else + { + CellFillData.BackgroundColor = PdfColor.SetColorFromHex(bkgc.LookupColor()); + } + } + else if (xfFill.PatternType != ExcelFillStyle.None) + { + CellFillData.PatternStyle = xfFill.PatternType; + CellFillData.BackgroundColor = PdfColor.SetColorFromHex(xfFill.PatternColor.Rgb == null ? "#FFFFFFFF" : xfFill.PatternColor.LookupColor()); + CellFillData.PatternColor = PdfColor.SetColorFromHex(xfFill.BackgroundColor.LookupColor()); + CellFillData.id = AddPatternResourceData(dictionaries.Patterns, CellFillData.PatternStyle.ToString() + CellFillData.PatternColor.ToHexString() + CellFillData.BackgroundColor.ToHexString()); + } + else if (xfFill.HasGradient) + { + CellFillData.GradientFillData = new PdfCellGradientFillData(); + CellFillData.GradientFillData.GradientType = xfFill.Gradient.Type; + CellFillData.GradientFillData.Color1 = PdfColor.SetColorFromHex(xfFill.Gradient.Color1.LookupColor()); + CellFillData.GradientFillData.Color2 = PdfColor.SetColorFromHex(xfFill.Gradient.Color2.LookupColor()); + CellFillData.GradientFillData.Color3 = PdfColor.SetColorFromHex(xfFill.Gradient.Color3.LookupColor()); + CellFillData.GradientFillData.Degree = xfFill.Gradient.Degree; + CellFillData.GradientFillData.Top = double.IsNaN(xfFill.Gradient.Top) ? 0 : xfFill.Gradient.Top; + CellFillData.GradientFillData.Bottom = double.IsNaN(xfFill.Gradient.Bottom) ? 0 : xfFill.Gradient.Bottom; + CellFillData.GradientFillData.Left = double.IsNaN(xfFill.Gradient.Left) ? 0 : xfFill.Gradient.Left; + CellFillData.GradientFillData.Right = double.IsNaN(xfFill.Gradient.Right) ? 0 : xfFill.Gradient.Right; + CellFillData.id = CellFillData.GradientFillData.ToString() + $"_{x:F4}_{(y - height):F4}_{width:F4}_{height:F4}"; + AddShadingResourceData(dictionaries.Shadings, CellFillData.id); + } + } + } + + + public PdfCellLayout(PdfDictionaries dictionaries, ExcelRangeBase cell, PdfCellStyle CellStyle, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) + : base(x, y - height, width, height, scaleX, scaleY, rotation, parent) + { + if (cell != null) + { + this.cell = cell; + this.CellStyle = CellStyle; + var xfFill = CellStyle.xfFill; + var dxfFill = CellStyle.dxfFill; + CellFillData = new PdfCellFillData(); + if (dxfFill != null && xfFill.IsEmpty()) + { + CellFillData.PatternStyle = dxfFill.PatternType != null ? (ExcelFillStyle)dxfFill.PatternType : ExcelFillStyle.Solid; + if (CellFillData.PatternStyle == ExcelFillStyle.Solid) + { + CellFillData.BackgroundColor = PdfColor.SetColorFromHex(dxfFill.BackgroundColor.LookupColor()); + } + else if (CellFillData.PatternStyle != ExcelFillStyle.None) + { + CellFillData.BackgroundColor = PdfColor.SetColorFromHex(dxfFill.PatternColor.Color == null ? "#FFFFFFFF" : dxfFill.PatternColor.LookupColor()); + CellFillData.PatternColor = PdfColor.SetColorFromHex(dxfFill.BackgroundColor.LookupColor()); + CellFillData.id = AddPatternResourceData(dictionaries.Patterns, CellFillData.PatternStyle.ToString() + CellFillData.PatternColor.ToHexString() + CellFillData.BackgroundColor.ToHexString()); + } + else if (dxfFill.Gradient != null) + { + CellFillData.GradientFillData = new PdfCellGradientFillData(); + CellFillData.GradientFillData.GradientType = dxfFill.Gradient.GradientType == null ? ExcelFillGradientType.None : (ExcelFillGradientType)dxfFill.Gradient.GradientType; + CellFillData.GradientFillData.Color1 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[0].Color.LookupColor()); + CellFillData.GradientFillData.Color2 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[1].Color.LookupColor()); + CellFillData.GradientFillData.Color3 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[2].Color.LookupColor()); + CellFillData.GradientFillData.Degree = dxfFill.Gradient.Degree == null ? 0 : (double)dxfFill.Gradient.Degree; + CellFillData.GradientFillData.Top = dxfFill.Gradient.Top == null ? 0 : (double)dxfFill.Gradient.Top; + CellFillData.GradientFillData.Bottom = dxfFill.Gradient.Bottom == null ? 0 : (double)dxfFill.Gradient.Bottom; + CellFillData.GradientFillData.Left = dxfFill.Gradient.Left == null ? 0 : (double)dxfFill.Gradient.Left; + CellFillData.GradientFillData.Right = dxfFill.Gradient.Right == null ? 0 : (double)dxfFill.Gradient.Right; + CellFillData.id = CellFillData.GradientFillData.ToString() + $"_{x:F4}_{(y - height):F4}_{width:F4}_{height:F4}"; + AddShadingResourceData(dictionaries.Shadings, CellFillData.id); + } + } + else + { + if (xfFill.PatternType == ExcelFillStyle.Solid) + { + var bkgc = xfFill.BackgroundColor; + CellFillData.PatternStyle = xfFill.PatternType; + if (string.IsNullOrEmpty(bkgc.LookupColor()) && !string.IsNullOrEmpty(cell.Text)) + { + CellFillData.BackgroundColor = Color.Empty; + } + else + { + CellFillData.BackgroundColor = PdfColor.SetColorFromHex(bkgc.LookupColor()); + } + } + else if (xfFill.PatternType != ExcelFillStyle.None) + { + CellFillData.PatternStyle = xfFill.PatternType; + CellFillData.BackgroundColor = PdfColor.SetColorFromHex(xfFill.PatternColor.Rgb == null ? "#FFFFFFFF" : xfFill.PatternColor.LookupColor()); + CellFillData.PatternColor = PdfColor.SetColorFromHex(xfFill.BackgroundColor.LookupColor()); + CellFillData.id = AddPatternResourceData(dictionaries.Patterns, CellFillData.PatternStyle.ToString() + CellFillData.PatternColor.ToHexString() + CellFillData.BackgroundColor.ToHexString()); + } + else if (xfFill.HasGradient) + { + CellFillData.GradientFillData = new PdfCellGradientFillData(); + CellFillData.GradientFillData.GradientType = xfFill.Gradient.Type; + CellFillData.GradientFillData.Color1 = PdfColor.SetColorFromHex(xfFill.Gradient.Color1.LookupColor()); + CellFillData.GradientFillData.Color2 = PdfColor.SetColorFromHex(xfFill.Gradient.Color2.LookupColor()); + CellFillData.GradientFillData.Color3 = PdfColor.SetColorFromHex(xfFill.Gradient.Color3.LookupColor()); + CellFillData.GradientFillData.Degree = xfFill.Gradient.Degree; + CellFillData.GradientFillData.Top = double.IsNaN(xfFill.Gradient.Top) ? 0 : xfFill.Gradient.Top; + CellFillData.GradientFillData.Bottom = double.IsNaN(xfFill.Gradient.Bottom) ? 0 : xfFill.Gradient.Bottom; + CellFillData.GradientFillData.Left = double.IsNaN(xfFill.Gradient.Left) ? 0 : xfFill.Gradient.Left; + CellFillData.GradientFillData.Right = double.IsNaN(xfFill.Gradient.Right) ? 0 : xfFill.Gradient.Right; + CellFillData.id = CellFillData.GradientFillData.ToString() + $"_{x:F4}_{(y - height):F4}_{width:F4}_{height:F4}"; + AddShadingResourceData(dictionaries.Shadings, CellFillData.id); + } + } + } + } + + private string AddPatternResourceData(Dictionary patternResources, string key) + { + if (!patternResources.ContainsKey(key)) + { + int label = 1; + if (patternResources.Count > 0) + { + label = patternResources.Last().Value.labelNumber + 1; + } + var pr = new PdfPatternResource(label, CellFillData); + patternResources.Add(key, pr); + } + return key; + } + + private string AddShadingResourceData(Dictionary shadingResources, string key) + { + if (!shadingResources.ContainsKey(key)) + { + int label = 1; + if (shadingResources.Count > 0) + { + label = shadingResources.Last().Value.labelNumber + 1; + } + var pr = new PdfShadingResource(label, CellFillData); + shadingResources.Add(key, pr); + } + return key; + } + + //Adjust size and position slightly for aesthetics. + public void AdjustForGridLines() + { + Size = new Vector2(Size.X + GridLine.HalfWidth, Size.Y + GridLine.HalfWidth); + LocalPosition = new Vector2(LocalPosition.X + GridLine.FourthWidth, LocalPosition.Y + GridLine.FourthWidth); + } + + public void UpdateShadingPositionMatrix(PdfPageSettings pageSettings) + { + //LocalPosition = new Vector2(LocalPosition.X, pageSettings.PageSize.HeightPu - System.Math.Abs(LocalPosition.Y) - Size.Y); + if (CellFillData.GradientFillData != null) + { + if (CellFillData.GradientFillData.GradientType == ExcelFillGradientType.Linear) + { + switch (CellFillData.GradientFillData.Degree) + { + case 45d: + CellFillData.GradientFillData.coords = [0, 1, 1, 0]; + break; + case 90d: + CellFillData.GradientFillData.coords = [0, 1, 0, 0]; + break; + case 135d: + CellFillData.GradientFillData.coords = [1, 1, 0, 0]; + break; + case 180d: + CellFillData.GradientFillData.coords = [1, 0, 0, 0]; + break; + case 225d: + CellFillData.GradientFillData.coords = [1, 0, 0, 1]; + break; + case 270d: + CellFillData.GradientFillData.coords = [0, 0, 0, 1]; + break; + case 315d: + CellFillData.GradientFillData.coords = [0, 0, 1, 1]; + break; + case 0d: + default: + CellFillData.GradientFillData.coords = [0, 0, 1, 0]; + break; + } + CellFillData.GradientFillData.matrix = [Size.X, 0, 0, Size.Y, LocalPosition.X, LocalPosition.Y]; + } + else if (CellFillData.GradientFillData.GradientType == ExcelFillGradientType.Path) + { + double x = LocalPosition.X; + double y = LocalPosition.Y; + double width = Size.X; + double height = Size.Y; + var top = CellFillData.GradientFillData.Top; + var bottom = CellFillData.GradientFillData.Bottom; + var left = CellFillData.GradientFillData.Left; + var right = CellFillData.GradientFillData.Right; + double r = 1; + if (top == 0 && bottom == 0 && left == 0 && right == 0) + { + CellFillData.GradientFillData.coords = [0, 1, 0, 0, 1, r]; + } + else if (top == 0 && bottom == 0 && left == 1 && right == 1) + { + CellFillData.GradientFillData.coords = [1, 1, 0, 1, 1, r]; + } + else if (top == 1 && bottom == 1 && left == 0 && right == 0) + { + CellFillData.GradientFillData.coords = [0, 0, 0, 0, 0, r]; + } + else if (top == 1 && bottom == 1 && left == 1 && right == 1) + { + CellFillData.GradientFillData.coords = [1, 0, 0, 1, 0, r]; + } + else if (top == 0.5 && bottom == 0.5 && left == 0.5 && right == 0.5) + { + CellFillData.GradientFillData.coords = [0.5, 0.5, 0, 0.5, 0.5, r]; + } + CellFillData.GradientFillData.matrix = [Size.X, 0, 0, Size.Y, LocalPosition.X, LocalPosition.Y]; + //double cx = x; + //double cy = y + height; + //if (!double.IsNaN(left) && !double.IsNaN(right) && !double.IsNaN(top) && !double.IsNaN(bottom)) // bottom-right + //{ + // if (top < 1d && bottom < 1d &&left < 1d && right < 1d) + // { + // cx = x + width / 2d; + // cy = y + height / 2d; + // } + // else + // { + // cx = x + width; + // cy = y; + // } + //} + //else if (!double.IsNaN(left) && !double.IsNaN(right)) // bottom-left + //{ + // cx = x + width; + // cy = y + height; + //} + //else if (!double.IsNaN(top) && !double.IsNaN(bottom)) // top-right + //{ + // cx = x; + // cy = y; + //} + //double r = System.Math.Sqrt(width * width + height * height) / 2d; + //CellFillData.GradientFillData.coords = [cx, cy, 0, cx, cy, r]; + } + } + } + } +} \ No newline at end of file diff --git a/src/EPPlus/Export/PdfExport/Layout/PdfGridlinesLayout.cs b/src/EPPlus/Export/PdfExport/Layout/PdfGridlinesLayout.cs new file mode 100644 index 0000000000..1dee923f80 --- /dev/null +++ b/src/EPPlus/Export/PdfExport/Layout/PdfGridlinesLayout.cs @@ -0,0 +1,386 @@ +/************************************************************************************************* + 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 OfficeOpenXml.Export.PdfExport.Data; +using OfficeOpenXml.Style; + +namespace OfficeOpenXml.Export.PdfExport.Layout +{ + internal class GridLine + { + public static double Width = 0.125d; + public static double HalfWidth = Width / 2d; + public static double FourthWidth = Width / 4d; + public double X1; + public double Y1; + public double X2; + public double Y2; + + public GridLine(double x1, double y1, double x2, double y2) + { + X1 = x1; Y1 = y1; X2 = x2; Y2 = y2; + } + } + internal class PdfGridlinesLayout + { + //------------------------------------------------------------------------------------------ + // Gridline generation + // ----------------------------------------------------------------------------------------- + + /// + /// Populates .GridLines and .BorderLines for one page. + /// + /// Algorithm: + /// 1. Precompute column X positions and row Y positions from the page map / row-height array. + /// 2. Walk every cell and mark which vertical column-gaps are suppressed by text spill. + /// Spill direction depends on horizontal alignment; spill stops when a destination cell + /// has content. Multi-column spill is handled by walking until either the text runs + /// out or a non-empty cell is found. + /// 3. Emit vertical gridline segments: for each column gap, accumulate contiguous rows + /// that are not blocked by spill, a cell border, or a merged cell spanning the gap. + /// 4. Emit horizontal gridline segments: for each row gap, accumulate contiguous columns + /// that are not blocked by a cell border or a merged cell spanning the gap. + /// 5. Add the four outer-frame lines to BorderLines. + /// + public static void AddGridLines(PdfPageSettings pageSettings, Page page, PdfPageLayout pageLayout, bool borderOnly = false) + { + int rowCount = page.ToRow - page.FromRow + 1; + int colCount = page.ToColumn - page.FromColumn + 1; + + if (rowCount <= 0 || colCount <= 0) return; + + // --- 1. Position arrays --------------------------------------------------------------- + + // colX[ci] = X of left edge of column ci (0-based within page). + // colX[colCount] = X of right edge of last column. + var colX = new double[colCount + 1]; + colX[0] = pageSettings.ContentBounds.Left + page.HeadingWidth + page.PrintTitleWidth; + for (int ci = 0; ci < colCount; ci++) + { + var cell = page.Map[page.FromRow, page.FromColumn + ci]; + colX[ci + 1] = colX[ci] + (cell?.ColumnWidth ?? 0d); + } + + // rowY[ri] = Y of top edge of row ri (0-based within page). + // rowY[rowCount] = Y of bottom edge of last row. + // Y decreases downward (PDF coordinate system used throughout GetCatalog). + var rowY = new double[rowCount + 1]; + rowY[0] = pageSettings.ContentBounds.Top - page.HeadingHeight - page.PrintTitleHeight; + + for (int ri = 0; ri < rowCount; ri++) + { + rowY[ri + 1] = rowY[ri] - page.RowHeights[ri]; + } + + // --- 5. Outer frame ------------------------------------------------------------------ + // Always computed so BorderLines is available for margin clipping regardless of + // whether ShowGridLines is on. When borderOnly is true we stop here. + + double frameLeft = pageSettings.ContentBounds.Left; //colX[0]; + double frameRight = colX[colCount]; + double frameTop = pageSettings.ContentBounds.Top; //rowY[0]; + double frameBottom = rowY[rowCount]; + + pageLayout.BorderLines.Add(new GridLine(frameLeft, frameTop, frameRight, frameTop)); + pageLayout.BorderLines.Add(new GridLine(frameLeft, frameBottom, frameRight, frameBottom)); + pageLayout.BorderLines.Add(new GridLine(frameLeft, frameTop, frameLeft, frameBottom)); + pageLayout.BorderLines.Add(new GridLine(frameRight, frameTop, frameRight, frameBottom)); + + if (borderOnly) return; + + // --- 2. Text-spill mask --------------------------------------------------------------- + + // spillBlocked[ri, gi] = true means the vertical line at colX[gi+1] for row ri + // must be suppressed because cell text from this row spills across that gap. + // gi ranges 0 .. colCount-2. + var spillBlocked = new bool[rowCount, colCount - 1]; + + for (int ri = 0; ri < rowCount; ri++) + { + int row = page.FromRow + ri; + for (int ci = 0; ci < colCount; ci++) + { + int col = page.FromColumn + ci; + var cell = page.Map[row, col]; + + if (cell == null || cell.Hidden || (cell.ContentAligmnet != null && cell.ContentAligmnet.WrapText)) continue; + // Merged cells never spill — their text is clipped to the merged region. + // The vertical lines around the merge are handled by MergedAcross checks below. + if (cell.Merged) continue; + + double spill = cell.TotalTextLength - cell.ColumnWidth; + if (spill <= 0d) continue; + + var halign = cell.ContentAligmnet.HorizontalAlignment; + + // Right spill — Left / General alignment. + if (halign == ExcelHorizontalAlignment.Left || + halign == ExcelHorizontalAlignment.General) + { + MarkSpillRight(page, ri, ci, colCount, colX, spill, spillBlocked); + + } + + // Left spill — Right alignment. + else if (halign == ExcelHorizontalAlignment.Right) + { + MarkSpillLeft(page, ri, ci, colX, spill, spillBlocked); + } + // Both directions — Center alignment, half the excess each way. + else if (halign == ExcelHorizontalAlignment.Center) + { + double halfSpill = spill / 2d; + MarkSpillRight(page, ri, ci, colCount, colX, halfSpill, spillBlocked); + MarkSpillLeft(page, ri, ci, colX, halfSpill, spillBlocked); + + } + } + } + + // --- 3. Vertical gridlines (one per internal column gap) ----------------------------- + + for (int gi = 0; gi < colCount - 1; gi++) + { + double x = colX[gi + 1]; + int leftCol = page.FromColumn + gi; + int rightCol = leftCol + 1; + + double? runStart = null; + double runEnd = 0d; + + for (int ri = 0; ri < rowCount; ri++) + { + int row = page.FromRow + ri; + + // Hidden rows have zero height — no segment to draw. + if (page.RowHeights[ri] == 0d) + { + FlushVertical(pageLayout, x, ref runStart, runEnd); + continue; + } + + var leftCell = page.Map[row, leftCol]; + var rightCell = page.Map[row, rightCol]; + + bool blocked = + spillBlocked[ri, gi] || + MergedAcross(leftCell, leftCol) || // left cell's merge extends right past this gap + MergedAcross(rightCell, rightCol, fromLeft: true) || // right cell's merge came from the left + HasRightBorder(leftCell) || + HasLeftBorder(rightCell); + + if (!blocked) + { + if (runStart == null) runStart = rowY[ri]; + runEnd = rowY[ri + 1]; + } + else + { + FlushVertical(pageLayout, x, ref runStart, runEnd); + } + } + FlushVertical(pageLayout, x, ref runStart, runEnd); + } + + // --- 4. Horizontal gridlines (one per internal row gap) ------------------------------ + + for (int ri = 0; ri < rowCount - 1; ri++) + { + double y = rowY[ri + 1]; + int topRow = page.FromRow + ri; + int bottomRow = topRow + 1; + + double? runStart = null; + double runEnd = 0d; + + for (int ci = 0; ci < colCount; ci++) + { + int col = page.FromColumn + ci; + + // Hidden columns have zero width — no segment to draw. + if (colX[ci + 1] - colX[ci] == 0d) + { + FlushHorizontal(pageLayout, y, ref runStart, runEnd); + continue; + } + + var topCell = page.Map[topRow, col]; + var bottomCell = page.Map[bottomRow, col]; + + bool blocked = + MergedDown(topCell, topRow) || // top cell's merge extends down past this gap + MergedDown(bottomCell, bottomRow, fromAbove: true) || // bottom cell's merge came from above + HasBottomBorder(topCell) || + HasTopBorder(bottomCell); + + if (!blocked) + { + if (runStart == null) runStart = colX[ci]; + runEnd = colX[ci + 1]; + } + else + { + FlushHorizontal(pageLayout, y, ref runStart, runEnd); + } + } + FlushHorizontal(pageLayout, y, ref runStart, runEnd); + } + } + + // ---- Spill helpers ---------------------------------------------------------------------- + + /// + /// Marks column gaps to the right of column as spill-blocked for + /// row , until the spill budget runs out or a non-empty cell stops it. + /// + private static void MarkSpillRight( + Page page, int ri, int ci, int colCount, + double[] colX, double spill, bool[,] spillBlocked) + { + int row = page.FromRow + ri; + // Walk gaps gi = ci, ci+1, ... (gap gi lies between column gi and column gi+1). + for (int gi = ci; gi < colCount - 1; gi++) + { + // The cell immediately to the right of this gap stops the spill if it has text. + var rightCell = page.Map[row, page.FromColumn + gi + 1]; + if (rightCell != null && !string.IsNullOrEmpty(rightCell.Text)) break; + + // Distance from the right edge of the source cell to this gap. + double distToGap = colX[gi + 1] - colX[ci + 1]; + if (spill > distToGap) + spillBlocked[ri, gi] = true; + else + break; + } + } + + /// + /// Marks column gaps to the left of column as spill-blocked for + /// row , until the spill budget runs out or a non-empty cell stops it. + /// + private static void MarkSpillLeft( + Page page, int ri, int ci, + double[] colX, double spill, bool[,] spillBlocked) + { + int row = page.FromRow + ri; + // Walk gaps gi = ci-1, ci-2, ... (gap gi lies between column gi and column gi+1). + for (int gi = ci - 1; gi >= 0; gi--) + { + // The cell immediately to the left of this gap stops the spill if it has text. + var leftCell = page.Map[row, page.FromColumn + gi]; + if (leftCell != null && !string.IsNullOrEmpty(leftCell.Text)) break; + + // Distance from the left edge of the source cell to this gap. + double distToGap = colX[ci] - colX[gi + 1]; + if (spill > distToGap) + spillBlocked[ri, gi] = true; + else + break; + } + } + + // ---- Run-flush helpers ------------------------------------------------------------------ + + private static void FlushVertical(PdfPageLayout layout, double x, ref double? runStart, double runEnd) + { + if (runStart == null) return; + layout.GridLines.Add(new GridLine(x, runStart.Value, x, runEnd)); + runStart = null; + } + + private static void FlushHorizontal(PdfPageLayout layout, double y, ref double? runStart, double runEnd) + { + if (runStart == null) return; + layout.GridLines.Add(new GridLine(runStart.Value, y, runEnd, y)); + runStart = null; + } + + // ---- Merge-span helpers ----------------------------------------------------------------- + + /// + /// Returns true when is part of a merged region that spans across + /// the vertical gap adjacent to . + /// + /// = false (default): cell is the LEFT neighbour of the gap — + /// block if the merge extends to the right (toCol > col). + /// + /// + /// = true: cell is the RIGHT neighbour of the gap — + /// block if the merge started to the left (fromCol < col). + /// + /// + private static bool MergedAcross(PdfCell cell, int col, bool fromLeft = false) + { + if (cell == null || !cell.Merged || cell.MergedAddress == null) return false; + return fromLeft + ? cell.MergedAddress._fromCol < col + : cell.MergedAddress._toCol > col; + } + + /// + /// Returns true when is part of a merged region that spans across + /// the horizontal gap adjacent to . + /// + /// = false (default): cell is the TOP neighbour of the gap — + /// block if the merge extends downward (toRow > row). + /// + /// + /// = true: cell is the BOTTOM neighbour of the gap — + /// block if the merge started above (fromRow < row). + /// + /// + private static bool MergedDown(PdfCell cell, int row, bool fromAbove = false) + { + if (cell == null || !cell.Merged || cell.MergedAddress == null) return false; + return fromAbove + ? cell.MergedAddress._fromRow < row + : cell.MergedAddress._toRow > row; + } + + // ---- Border presence helpers ------------------------------------------------------------ + // For merged cells, use the main cell's style (which holds the region's outer borders). + + private static bool HasRightBorder(PdfCell cell) + { + var cs = (cell?.Merged == true && cell.Main != null) ? cell.Main.CellStyle : cell?.CellStyle; + if (cs == null) return false; + return (cs.xfRight != null && cs.xfRight.Style != ExcelBorderStyle.None) || + (cs.dxfRight?.HasValue ?? false); + + } + + private static bool HasLeftBorder(PdfCell cell) + { + var cs = (cell?.Merged == true && cell.Main != null) ? cell.Main.CellStyle : cell?.CellStyle; + if (cs == null) return false; + return (cs.xfLeft != null && cs.xfLeft.Style != ExcelBorderStyle.None) || + (cs.dxfLeft?.HasValue ?? false); + } + + private static bool HasTopBorder(PdfCell cell) + { + var cs = (cell?.Merged == true && cell.Main != null) ? cell.Main.CellStyle : cell?.CellStyle; + if (cs == null) return false; + return (cs.xfTop != null && cs.xfTop.Style != ExcelBorderStyle.None) || + (cs.dxfTop?.HasValue ?? false); + } + + private static bool HasBottomBorder(PdfCell cell) + { + var cs = (cell?.Merged == true && cell.Main != null) ? cell.Main.CellStyle : cell?.CellStyle; + if (cs == null) return false; + return (cs.xfBottom != null && cs.xfBottom.Style != ExcelBorderStyle.None) || + (cs.dxfBottom?.HasValue ?? false); + } + + } +} diff --git a/src/EPPlus/Export/PdfExport/Layout/PdfLayout.cs b/src/EPPlus/Export/PdfExport/Layout/PdfLayout.cs new file mode 100644 index 0000000000..eb1ff9f580 --- /dev/null +++ b/src/EPPlus/Export/PdfExport/Layout/PdfLayout.cs @@ -0,0 +1,1711 @@ +/************************************************************************************************* + 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.Fonts.OpenType.Integration; +using EPPlus.Fonts.OpenType.Integration.DataHolders; +using EPPlus.Graphics; +using OfficeOpenXml.Export.PdfExport.Data; +using OfficeOpenXml.Export.PdfExport.Data.Dictionaries; +using OfficeOpenXml.Export.PdfExport.TextMapping; +using OfficeOpenXml.Export.PdfExport.TextShaping; +using OfficeOpenXml.Interfaces.Fonts; +using OfficeOpenXml.Style; +using System; +using System.Collections.Generic; + +namespace OfficeOpenXml.Export.PdfExport.Layout +{ + internal struct MergedCellDrawInfo + { + public double X; + public double Y; + public double Width; + public double Height; + } + + internal struct PrintTitleCellDraw + { + public PdfCell Cell; + public double X; + public double Y; + public double Width; + public double Height; + public double ClipX; // text clip (spill bound) + public double ClipY; + public double ClipWidth; + public double ClipHeight; + } + + internal struct PrintTitleHeadingDraw + { + public bool IsRow; // true = row-number heading (left strip); false = column-letter (top strip) + public int Index; // original absolute row/column index — the label source + public double X; + public double Y; + public double Width; + public double Height; + } + + internal struct SpillCellDraw + { + public PdfCell Cell; + public double X, Y, Width, Height; // source cell's true position (off-window) + public double ClipX, ClipY, ClipWidth, ClipHeight; // visible slice on this page + public bool IsPrintTitle; + } + + internal struct Page + { + public int FromRow; + public int FromColumn; + public int ToRow; + public int ToColumn; + + public bool HasPrintTitle; + public double PrintTitleWidth; + public double PrintTitleHeight; + + public PdfCellCollection Map; + + public PdfHeaderFooterCollection HeaderFooters; + + public Dictionary MergedCells; + public List PrintTitleCells; + public List PrintTitleGridLines; + public List PrintTitleHeadings; + public List SpillCells; + public List PrintTitleBorders; + + public double[] RowHeights; + + public double HeadingWidth; + public double HeadingHeight; + + } + + internal struct Pages + { + public Page[] Page; + public int Width; + public int Height; + public bool IsCommentsPage; + public string HeadingFontName; + public float HeadingFontSize; + public ExcelFill HeadingFill; + public int Count + { + get { return Width * Height; } + } + } + + internal class PdfLayout + { + private const double rowHeadingWith1CharWidth = 18d; + + public static Transform GetLayout(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfWorksheet[] pdfSheets) + { + var PagesCollection = GetPages(pageSettings, pdfSheets); + var Catalog = GetCatalog(pageSettings, dictionaries, PagesCollection); + return Catalog; + } + + /*Header and footer notes: + * First header/footer is only used on the first worksheets first page if it exsists + * Then we use each worksheets odd and even respectively. worksheet 1 has its set of odd and even we use and worksheet 2 has it's own set we will use + * Page number does not reset and total number of pages is across all worksheets pages + * starting page number does not affect if first header/footer is used or not. + */ + + internal static Transform GetCatalog(PdfPageSettings pageSettings, PdfDictionaries dictionaries, List pdfPages) + { + Transform Catalog = new Transform(0d, 0d, 0d, 0d); + int totalPages = GetTotalPages(pdfPages); + for (int i = 0; i < pdfPages.Count; i++) + { + + var pages = pdfPages[i].Page; + int pageNumber = pageSettings.FirstPageNumber; + + for (int j = 0; j < pages.Length; j++) + { + var page = pages[j]; + PdfPageLayout pageLayout = new PdfPageLayout(0d, 0d, 0d, 0d); + pageLayout.isCommentsPage = pdfPages[i].IsCommentsPage; + pageLayout.HeadingWidth = page.HeadingWidth; + pageLayout.HeadingHeight = page.HeadingHeight; + pageLayout.PrintTitleWidth = page.PrintTitleWidth; + pageLayout.PrintTitleHeight = page.PrintTitleHeight; + var drawnMergedCells = new HashSet(); + //var drawnMergedCellsText = new HashSet(); + //PdfContentLayout contentLayout = new PdfContentLayout(0d, 0d, pageSettings.ContentBounds); + //pageLayout.AddChild(contentLayout); + double contentStartX = pageSettings.ContentBounds.Left + page.HeadingWidth + page.PrintTitleWidth; + double contentStartY = pageSettings.ContentBounds.Top - page.HeadingHeight - page.PrintTitleHeight; + if (pageSettings.ShowHeadings && !pdfPages[i].IsCommentsPage) + { + AddHeadingCells(pageSettings, dictionaries, page, pageLayout, contentStartX, contentStartY, page.HeadingWidth, page.HeadingHeight, pdfPages[i].HeadingFontName, pdfPages[i].HeadingFontSize, pdfPages[i].HeadingFill); + AddPrintTitleHeadings(pageSettings, dictionaries, page, pageLayout, pdfPages[i].HeadingFontName, pdfPages[i].HeadingFontSize, pdfPages[i].HeadingFill); + AddSpillCells(pageSettings, dictionaries, page, pageLayout); + } + AddPrintTitleCells(pageSettings, dictionaries, page, pageLayout); + + double y = contentStartY;//pageSettings.ContentBounds.Top; + double x = contentStartX;//pageSettings.ContentBounds.Left; + //create cells & headings if exsists + + for (int row = pages[j].FromRow; row <= pages[j].ToRow; row++) + { + double rowHeight = pages[j].RowHeights[row - pages[j].FromRow]; + for (int col = pages[j].FromColumn; col <= pages[j].ToColumn; col++) + { + var map = pages[j].Map[row, col]; + MergedCellDrawInfo info = new MergedCellDrawInfo(); + //Merged Cell + if (map.Merged) + { + string key = map.MergedAddress.Address; + if (!drawnMergedCells.Contains(key) && + pages[j].MergedCells.TryGetValue(key, out info)) + { + //Fill + var cellStyle = map.Main?.CellStyle ?? map.CellStyle; + var fill = new PdfCellLayout(dictionaries, cellStyle, + info.X, info.Y, info.Width, info.Height); + fill.Name = map.Name; + fill.UpdateShadingPositionMatrix(pageSettings); + pageLayout.AddChild(fill); + //Text + var sourceMap = (map.TextLines != null && map.TextLines.Count > 0) ? map : (map.Main != null && map.Main.TextLines != null && map.Main.TextLines.Count > 0) ? map.Main : null; + if (sourceMap != null) + { + var text = new PdfCellContentLayout(pageSettings, dictionaries, sourceMap, info, info.X, info.Y, info.Width, info.Height); + text.Name = map.Name; + text.GidsAndCharMap(dictionaries); + text.SetupClipping(info.X, info.Y, info.Width, info.Height); + pageLayout.AddChild(text); + } + if (map.Main != null) // map.Main != null → this is NOT the top-left cell + { + var mergeMainStyle = map.Main.CellStyle; + if (HasDiagonalBorder(mergeMainStyle)) + { + var diagBorder = new PdfCellBorderLayout( + mergeMainStyle, + isMerged: false, // use X/Y/W/H path in renderer, not info.* + corners: MergedCellCorners.All, + info: info, + x: info.X, // virtual full-merge top-left X + y: info.Y, // virtual full-merge top Y + width: info.Width, // full merge width + height: info.Height); // full merge height + diagBorder.Name = map.Name; + // Suppress edge borders — this layout exists only for the diagonal + diagBorder.BorderData.Top.BorderStyle = ExcelBorderStyle.None; + diagBorder.BorderData.Bottom.BorderStyle = ExcelBorderStyle.None; + diagBorder.BorderData.Left.BorderStyle = ExcelBorderStyle.None; + diagBorder.BorderData.Right.BorderStyle = ExcelBorderStyle.None; + pageLayout.AddChild(diagBorder); + } + } + drawnMergedCells.Add(key); + } + } + else + { + //Fill + var fill = new PdfCellLayout(dictionaries, map.CellStyle, x, y, map.ColumnWidth, rowHeight); + fill.UpdateShadingPositionMatrix(pageSettings); + fill.Name = map.Name; + pageLayout.AddChild(fill); + //Text + if (map.TextLines != null && map.TextLines.Count > 0) + { + var text = new PdfCellContentLayout(pageSettings, dictionaries, map, info, x, y, map.ColumnWidth, rowHeight); + text.Name = map.Name; + text.GidsAndCharMap(dictionaries); + if (NeedsClipping(map, pages[j], row, col)) + text.SetupClipping(x, y, map.ColumnWidth, rowHeight); + pageLayout.AddChild(text); + } + } + //Border + var borderStyle = (map.Merged && map.Main != null) ? map.Main.CellStyle : map.CellStyle; + if (HasBorder(map.CellStyle)) + { + var border = new PdfCellBorderLayout(map.CellStyle, map.Merged, GetCorners(map.MergedAddress, row, col), info, x, y, map.ColumnWidth, rowHeight); + border.Name = map.Name; + if (map.Merged && map.MergedAddress != null) + { + var addr = map.MergedAddress; + if (row != addr.Start.Row) border.BorderData.Top.BorderStyle = ExcelBorderStyle.None; + if (row != addr.End.Row) border.BorderData.Bottom.BorderStyle = ExcelBorderStyle.None; + if (col != addr.Start.Column) border.BorderData.Left.BorderStyle = ExcelBorderStyle.None; + if (col != addr.End.Column) border.BorderData.Right.BorderStyle = ExcelBorderStyle.None; + } + pageLayout.AddChild(border); + } + x += map.ColumnWidth; + } + y -= rowHeight; + x = contentStartX; //pageSettings.ContentBounds.Left; + } + if (page.HeaderFooters != null) + { + bool isVeryFirstPage = (i == 0 && j == 0); + var hfType = isVeryFirstPage ? HeaderFooterType.First : (pageNumber % 2 == 0 ? HeaderFooterType.Even : HeaderFooterType.Odd); + var leftH = page.HeaderFooters.Get(hfType, HeaderFooterSection.Header, HeaderFooterAlignment.Left); + if (leftH != null) + { + SubstitutePageNumbers(pageSettings, dictionaries, leftH, pageNumber, totalPages); + var ascent = leftH.Content.TextLines[0].LargestAscent; + var hfx = pageSettings.Margins.LeftPu; + var hfy = pageSettings.PageSize.HeightPu - pageSettings.Margins.HeaderPu - ascent; + var text = new PdfCellContentLayout(pageSettings, dictionaries, leftH, hfx, hfy, 0, 0); + text.Name = "LeftHeader"; + text.IsHeaderFooter = true; + text.GidsAndCharMap(dictionaries); + pageLayout.AddChild(text); + } + var centerH = page.HeaderFooters.Get(hfType, HeaderFooterSection.Header, HeaderFooterAlignment.Center); + if (centerH != null) + { + SubstitutePageNumbers(pageSettings, dictionaries, centerH, pageNumber, totalPages); + var ascent = centerH.Content.TextLines[0].LargestAscent; + var hfx = pageSettings.Margins.LeftPu; + var hfy = pageSettings.PageSize.HeightPu - pageSettings.Margins.HeaderPu - ascent; + var hfWidth = pageSettings.PageSize.WidthPu - pageSettings.Margins.LeftPu - pageSettings.Margins.RightPu; + var text = new PdfCellContentLayout(pageSettings, dictionaries, centerH, hfx, hfy, hfWidth, 0); + text.Name = "CenterHeader"; + text.IsHeaderFooter = true; + text.GidsAndCharMap(dictionaries); + pageLayout.AddChild(text); + } + var rightH = page.HeaderFooters.Get(hfType, HeaderFooterSection.Header, HeaderFooterAlignment.Right); + if (rightH != null) + { + SubstitutePageNumbers(pageSettings, dictionaries, rightH, pageNumber, totalPages); + var ascent = rightH.Content.TextLines[0].LargestAscent; + var hfx = pageSettings.PageSize.WidthPu - pageSettings.Margins.RightPu; + var hfy = pageSettings.PageSize.HeightPu - pageSettings.Margins.HeaderPu - ascent; + var text = new PdfCellContentLayout(pageSettings, dictionaries, rightH, hfx, hfy, 0, 0); + text.Name = "RightHeader"; + text.IsHeaderFooter = true; + text.GidsAndCharMap(dictionaries); + pageLayout.AddChild(text); + } + var leftF = page.HeaderFooters.Get(hfType, HeaderFooterSection.Footer, HeaderFooterAlignment.Left); + if (leftF != null) + { + SubstitutePageNumbers(pageSettings, dictionaries, leftF, pageNumber, totalPages); + int last = leftF.Content.TextLines.Count - 1; + var descent = leftF.Content.TextLines[last].LargestDescent; + var hfx = pageSettings.Margins.LeftPu; + var hfy = pageSettings.Margins.FooterPu + descent; + var text = new PdfCellContentLayout(pageSettings, dictionaries, leftF, hfx, hfy, 0, 0); + text.Name = "LeftFooter"; + text.IsHeaderFooter = true; + text.GidsAndCharMap(dictionaries); + pageLayout.AddChild(text); + } + var centerF = page.HeaderFooters.Get(hfType, HeaderFooterSection.Footer, HeaderFooterAlignment.Center); + if (centerF != null) + { + SubstitutePageNumbers(pageSettings, dictionaries, centerF, pageNumber, totalPages); + int last = centerF.Content.TextLines.Count - 1; + var descent = centerF.Content.TextLines[last].LargestDescent; + var hfx = pageSettings.PageSize.WidthPu / 2d; + var hfy = pageSettings.Margins.FooterPu + descent; + var text = new PdfCellContentLayout(pageSettings, dictionaries, centerF, hfx, hfy, 0, 0); + text.Name = "CenterFooter"; + text.IsHeaderFooter = true; + text.GidsAndCharMap(dictionaries); + pageLayout.AddChild(text); + } + var rightF = page.HeaderFooters.Get(hfType, HeaderFooterSection.Footer, HeaderFooterAlignment.Right); + if (rightF != null) + { + SubstitutePageNumbers(pageSettings, dictionaries, rightF, pageNumber, totalPages); + int last = rightF.Content.TextLines.Count - 1; + var descent = rightF.Content.TextLines[last].LargestDescent; + var hfx = pageSettings.PageSize.WidthPu - pageSettings.Margins.RightPu; + var hfy = pageSettings.Margins.FooterPu + descent; + var text = new PdfCellContentLayout(pageSettings, dictionaries, rightF, hfx, hfy, 0, 0); + text.Name = "RightFooter"; + text.IsHeaderFooter = true; + text.GidsAndCharMap(dictionaries); + pageLayout.AddChild(text); + } + } + + PdfGridlinesLayout.AddGridLines(pageSettings, pages[j], pageLayout, borderOnly: !pageSettings.ShowGridLines || pdfPages[i].IsCommentsPage); + + pageLayout.ChildObjects.Sort((a, b) => + { + int cmp = a.Z.CompareTo(b.Z); + if (cmp == 0) + return string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase); + return cmp; + }); + pageNumber++; + Catalog.AddChild(pageLayout); + } + } + return Catalog; + } + + static MergedCellCorners GetCorners(ExcelAddressBase addr, int row, int col) + { + if (addr == null) return MergedCellCorners.All; + MergedCellCorners result = MergedCellCorners.None; + + if (row == addr.Start.Row && col == addr.Start.Column) + result |= MergedCellCorners.TopLeft; + + if (row == addr.Start.Row && col == addr.End.Column) + result |= MergedCellCorners.TopRight; + + if (row == addr.End.Row && col == addr.Start.Column) + result |= MergedCellCorners.BottomLeft; + + if (row == addr.End.Row && col == addr.End.Column) + result |= MergedCellCorners.BottomRight; + + return result; + } + + private static bool NeedsClipping(PdfCell map, Page page, int row, int col) + { + if (map.ContentAligmnet == null) return false; + // Fill alignment always clips; WrapText is already wrapped but clip for safety. + if (map.ContentAligmnet.HorizontalAlignment == ExcelHorizontalAlignment.Fill || map.ContentAligmnet.WrapText) + return true; + if (map.TotalTextLength <= map.ColumnWidth) return false; + var halign = map.ContentAligmnet.HorizontalAlignment; + if (halign == ExcelHorizontalAlignment.Left || halign == ExcelHorizontalAlignment.General) + { + // Text spills right — clip if the right neighbour has content or we're at the page edge. + if (col >= page.ToColumn) return true; + var right = page.Map[row, col + 1]; + return right != null && !string.IsNullOrEmpty(right.Text); + } + else if (halign == ExcelHorizontalAlignment.Right) + { + // Text spills left — clip if the left neighbour has content or we're at the page edge. + if (col <= page.FromColumn) return true; + var left = page.Map[row, col - 1]; + return left != null && !string.IsNullOrEmpty(left.Text); + } + else if (halign == ExcelHorizontalAlignment.Center) + { + // Text spills both ways — clip if either neighbour blocks or we're at an edge. + bool rightBlocked = col >= page.ToColumn || (page.Map[row, col + 1] != null && !string.IsNullOrEmpty(page.Map[row, col + 1].Text)); + bool leftBlocked = col <= page.FromColumn || (page.Map[row, col - 1] != null && !string.IsNullOrEmpty(page.Map[row, col - 1].Text)); + return rightBlocked || leftBlocked; + } + return false; + } + + + + private static bool HasBorder(PdfCellStyle cellStyle) + { + if (cellStyle == null) return false; + bool hasBorders = + cellStyle.xfTop.Style != ExcelBorderStyle.None || + cellStyle.xfBottom.Style != ExcelBorderStyle.None || + cellStyle.xfLeft.Style != ExcelBorderStyle.None || + cellStyle.xfRight.Style != ExcelBorderStyle.None || + (cellStyle.dxfTop?.HasValue ?? false) || + (cellStyle.dxfBottom?.HasValue ?? false) || + (cellStyle.dxfLeft?.HasValue ?? false) || + (cellStyle.dxfRight?.HasValue ?? false) || + (cellStyle.Diagonal != null && cellStyle.Diagonal.Style != ExcelBorderStyle.None); + return hasBorders; + } + + private static bool HasDiagonalBorder(PdfCellStyle style) => style?.Diagonal != null && style.Diagonal.Style != ExcelBorderStyle.None; + + private static void AddHeadingCell(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfPageLayout pageLayout, PdfCellStyle headingStyle, string label, double x, double y, double width, double height, string fontName, float fontSize, string namePrefix) + { + if (width == 0d || height == 0d) return; + + var fill = new PdfCellLayout(dictionaries, headingStyle, x, y, width, height); + fill.Name = namePrefix; + fill.IsHeading = true; + fill.UpdateShadingPositionMatrix(pageSettings); + pageLayout.AddChild(fill); + + var cell = CreateHeadingPdfCell(pageSettings, dictionaries, label, ExcelHorizontalAlignment.Center, width, height, fontName, fontSize); + if (cell.TextLines != null && cell.TextLines.Count > 0) + { + var info = new MergedCellDrawInfo { X = x, Y = y, Width = width, Height = height }; + var text = new PdfCellContentLayout(pageSettings, dictionaries, cell, info, x, y, width, height); + text.Name = namePrefix + "_Text"; + text.IsHeading = true; + text.GidsAndCharMap(dictionaries); + pageLayout.AddChild(text); + } + AddHeadingCellBorder(pageLayout, x, y, width, height); + } + + private static void AddHeadingCells(PdfPageSettings pageSettings, PdfDictionaries dictionaries, Page page, PdfPageLayout pageLayout, double contentStartX, double contentStartY, double headingWidth, double headingHeight, string fontName, float fontSize, ExcelFill fill) + { + var headingStyle = new PdfCellStyle(); + headingStyle.xfFill = fill; + + var cornerFill = new PdfCellLayout(dictionaries, headingStyle, + pageSettings.ContentBounds.Left, pageSettings.ContentBounds.Top, headingWidth, headingHeight); + cornerFill.Name = "Heading_Corner"; + cornerFill.UpdateShadingPositionMatrix(pageSettings); + pageLayout.AddChild(cornerFill); + + double x = contentStartX; + for (int col = page.FromColumn; col <= page.ToColumn; col++) + { + double colWidth = page.Map[page.FromRow, col]?.ColumnWidth ?? 0d; + if (colWidth == 0d) { x += colWidth; continue; } + string colLetter = ExcelCellBase.GetColumnLetter(col); + AddHeadingCell(pageSettings, dictionaries, pageLayout, headingStyle, colLetter, + x, pageSettings.ContentBounds.Top, colWidth, headingHeight, fontName, fontSize, "Heading_Col_" + colLetter); + x += colWidth; + } + + double y = contentStartY; + for (int row = page.FromRow; row <= page.ToRow; row++) + { + double rowHeight = page.RowHeights[row - page.FromRow]; + if (rowHeight == 0d) { y -= rowHeight; continue; } + string rowNum = row.ToString(); + AddHeadingCell(pageSettings, dictionaries, pageLayout, headingStyle, rowNum, + pageSettings.ContentBounds.Left, y, headingWidth, rowHeight, fontName, fontSize, "Heading_Row_" + rowNum); + y -= rowHeight; + } + } + + private static void AddPrintTitleHeadings(PdfPageSettings pageSettings, PdfDictionaries dictionaries, Page page, PdfPageLayout pageLayout, string fontName, float fontSize, ExcelFill fill) + { + if (page.PrintTitleHeadings == null || page.PrintTitleHeadings.Count == 0) return; + + var headingStyle = new PdfCellStyle(); + headingStyle.xfFill = fill; + + foreach (var h in page.PrintTitleHeadings) + { + string label = h.IsRow ? h.Index.ToString() : ExcelCellBase.GetColumnLetter(h.Index); + string namePrefix = (h.IsRow ? "Heading_Row_" : "Heading_Col_") + label; + AddHeadingCell(pageSettings, dictionaries, pageLayout, headingStyle, label, h.X, h.Y, h.Width, h.Height, fontName, fontSize, namePrefix); + } + } + + private static void AddHeadingCellBorder(PdfPageLayout pageLayout, double x, double y, double width, double height) + { + double right = x + width; + double bottom = y - height; + pageLayout.BorderLines.Add(new GridLine(x, y, right, y)); // top + pageLayout.BorderLines.Add(new GridLine(x, bottom, right, bottom)); // bottom + pageLayout.BorderLines.Add(new GridLine(x, y, x, bottom)); // left + pageLayout.BorderLines.Add(new GridLine(right, y, right, bottom)); // right + } + + private static PdfCell CreateHeadingPdfCell(PdfPageSettings pageSettings, PdfDictionaries dictionaries, string text, ExcelHorizontalAlignment hAlign, double width, double height, string fontName, float fontSize) + { + var cell = new PdfCell(); + cell.ColumnWidth = width; + cell.Width = width; + cell.Height = height; + cell.Text = text; + cell.CellStyle = new PdfCellStyle(); + cell.ContentAligmnet = new PdfCellAlignmentData(); + cell.ContentAligmnet.HorizontalAlignment = hAlign; + cell.ContentAligmnet.VerticalAlignment = ExcelVerticalAlignment.Bottom; + cell.ContentAligmnet.WrapText = false; + if (!string.IsNullOrEmpty(text)) + { + var tf = new TextFragment(); + tf.Font = new RichTextFormatSimple(); + tf.Font.Family = fontName; + tf.Font.SubFamily = FontSubFamily.Regular; + tf.Font.Size = fontSize; + tf.Text = text; + tf.RichTextOptions.Bold = false; + tf.RichTextOptions.Italic = false; + tf.RichTextOptions.UnderlineType = 12; // none + tf.RichTextOptions.StrikeType = 1; // none + cell.TextFragments = new List { tf }; + PdfTextShaper.ShapeText(pageSettings, dictionaries, cell); + } + return cell; + } + + private static void AddSpillCells(PdfPageSettings pageSettings, PdfDictionaries dictionaries, Page page, PdfPageLayout pageLayout) + { + if (page.SpillCells == null) return; + foreach (var s in page.SpillCells) + { + var map = s.Cell; + if (map.TextLines == null || map.TextLines.Count == 0) continue; + if (s.ClipWidth <= 0d || s.ClipHeight <= 0d) continue; + + var text = new PdfCellContentLayout(pageSettings, dictionaries, map, new MergedCellDrawInfo(), s.X, s.Y, s.Width, s.Height); + text.Name = "Spill_" + map.Name; + text.IsPrintTitle = s.IsPrintTitle; // false → clipped content group; true → outside-clip (band) + text.GidsAndCharMap(dictionaries); + text.SetupClipping(s.ClipX, s.ClipY, s.ClipWidth, s.ClipHeight); + pageLayout.AddChild(text); + } + } + + private static void AddPrintTitleCells(PdfPageSettings pageSettings, PdfDictionaries dictionaries, Page page, PdfPageLayout pageLayout) + { + if (page.PrintTitleCells == null) return; + pageLayout.PrintTitleGridLines = page.PrintTitleGridLines ?? new List(); + foreach (var t in page.PrintTitleCells) + { + var map = t.Cell; + + var fill = new PdfCellLayout(dictionaries, map.CellStyle, t.X, t.Y, t.Width, t.Height); + fill.Name = map.Name; + fill.IsPrintTitle = true; + fill.UpdateShadingPositionMatrix(pageSettings); + pageLayout.AddChild(fill); + + if (map.TextLines != null && map.TextLines.Count > 0) + { + var info = new MergedCellDrawInfo { X = t.X, Y = t.Y, Width = t.Width, Height = t.Height }; + var text = new PdfCellContentLayout(pageSettings, dictionaries, map, info, t.X, t.Y, t.Width, t.Height); + text.Name = map.Name; + text.IsPrintTitle = true; + text.GidsAndCharMap(dictionaries); + text.SetupClipping(t.ClipX, t.ClipY, t.ClipWidth, t.ClipHeight); + pageLayout.AddChild(text); + } + + //if (map.TextLines != null && map.TextLines.Count > 0) + //{ + // var text = new PdfCellContentLayout(pageSettings, dictionaries, map, new MergedCellDrawInfo(), t.X, t.Y, t.Width, t.Height); + // text.Name = map.Name; + // text.IsPrintTitle = true; + // text.GidsAndCharMap(dictionaries); + // text.SetupClipping(t.X, t.Y, t.Width, t.Height); + // pageLayout.AddChild(text); + //} + + if (!map.Merged && HasBorder(map.CellStyle)) // was: if (HasBorder(map.CellStyle)) + { + var border = new PdfCellBorderLayout(map.CellStyle, false, MergedCellCorners.All, new MergedCellDrawInfo(), t.X, t.Y, t.Width, t.Height); + border.IsPrintTitle = true; + border.Name = "PrintTitleBorder_" + map.Name; + pageLayout.AddChild(border); + } + // Per-cell borders for merged band cells — outside the margin clip like the rest of the band. + foreach (var b in page.PrintTitleBorders) + { + var sub = b.Cell; + if (!HasBorder(sub.CellStyle)) continue; + var border = new PdfCellBorderLayout(sub.CellStyle, false, MergedCellCorners.All, new MergedCellDrawInfo(), b.X, b.Y, b.Width, b.Height); + border.IsPrintTitle = true; + border.Name = "PrintTitleMergeBorder_" + sub.Name; + pageLayout.AddChild(border); + } + } + } + + + // TODO Count total pages when creating them isntead of looping them here. + private static int GetTotalPages(List pdfPages) + { + int totalPages = 0; + for (int i = 0; i < pdfPages.Count; i++) + { + totalPages += pdfPages[i].Page.Length; + } + return totalPages; + } + + internal static List GetPages(PdfPageSettings pageSettings, PdfWorksheet[] pdfSheets) + { + List PagesCollection = new List(); + foreach (var pdfSheet in pdfSheets) + { + for (int ri = 0; ri < pdfSheet.Ranges.Count; ri++) + { + var range = pdfSheet.Ranges[ri]; + var pages = GetNumberOfPages(pageSettings, pdfSheet, ref range); + pages = AssignRangeToPages(pageSettings, range, pages); + pages = MapPage(range, pages); + pages = GetHeaderFooter(range, pages, pdfSheet); + pages = PrecomputeMergedCells(pageSettings, range, pages); + pages = PrecomputeSpillCells(pageSettings, range, pages); + pages = PrecomputePrintTitleCells(pageSettings, pdfSheet, range, pages); + pages.HeadingFontName = pdfSheet.NormalStyle.Style.Font.Name; + pages.HeadingFontSize = pdfSheet.NormalStyle.Style.Font.Size; + pages.HeadingFill = pdfSheet.NormalStyle.Style.Fill; + PagesCollection.Add(pages); + } + if (pdfSheet.CommentsAndNotes.Range != null) + { + bool savedShowHeadings = pageSettings.ShowHeadings; + pageSettings.ShowHeadings = false; + var pages = GetNumberOfPages(pageSettings, pdfSheet, ref pdfSheet.CommentsAndNotes); + pages = AssignRangeToPages(pageSettings, pdfSheet.CommentsAndNotes, pages); + pages = MapPage(pdfSheet.CommentsAndNotes, pages); + pageSettings.ShowHeadings = savedShowHeadings; + pages.IsCommentsPage = true; + PagesCollection.Add(pages); + + //var pages = GetNumberOfPages(pageSettings, pdfSheet, ref pdfSheet.CommentsAndNotes); + //pages = AssignRangeToPages(pageSettings, pdfSheet.CommentsAndNotes, pages); + //pages = MapPage(pdfSheet.CommentsAndNotes, pages); + //pages.IsCommentsPage = true; + //PagesCollection.Add(pages); + } + } + return PagesCollection; + } + + internal static Pages PrecomputeMergedCells(PdfPageSettings pageSettings, PdfRange range, Pages pdfPages) + { + for (int i = 0; i < pdfPages.Page.Length; i++) + pdfPages.Page[i] = PrecomputePageMergedCells(pageSettings, range, pdfPages.Page[i]); + return pdfPages; + } + + private static Page PrecomputePageMergedCells(PdfPageSettings pageSettings, PdfRange range, Page page) + { + page.MergedCells = new Dictionary(); + // Build a quick lookup: absolute x for each column index on this page. + // We read ColumnWidth from the first data row; widths are per-column, not per-cell. + var colX = BuildColumnXPositions(pageSettings, page); + for (int row = page.FromRow; row <= page.ToRow; row++) + { + for (int col = page.FromColumn; col <= page.ToColumn; col++) + { + var cell = page.Map[row, col]; + if (cell == null || !cell.Merged) continue; + string key = cell.MergedAddress.Address; + if (page.MergedCells.ContainsKey(key)) continue; + var addr = cell.MergedAddress; + var mainCell = cell.Main ?? cell; // Main == null means this cell IS the top-left + // --- X --- + // Start from the current column and walk left to the merge origin. + // Columns within the current page come from colX; columns that lie on + // a preceding column-page come from range.ColWidths. + double drawX = colX[col - page.FromColumn]; + for (int c = addr._fromCol; c < col; c++) + { + int rangeIdx = c - range.Range._fromCol; + if (rangeIdx >= 0 && rangeIdx < range.ColWidths.Count) + drawX -= range.ColWidths[rangeIdx]; + } + // --- Y --- + // Replace the * 15d line with a sum of real row heights + double drawY = pageSettings.ContentBounds.Top - page.HeadingHeight - page.PrintTitleHeight; + for (int r = page.FromRow; r < row; r++) + { + drawY -= range.RowHeights[r - range.Range._fromRow].Height; + } + // Existing loop — just add .Height + for (int r = addr._fromRow; r < row; r++) + { + int rangeIdx = r - range.Range._fromRow; + if (rangeIdx >= 0 && rangeIdx < range.RowHeights.Count) + drawY += range.RowHeights[rangeIdx].Height; + } + page.MergedCells[key] = new MergedCellDrawInfo + { + X = drawX, + Y = drawY, + Width = mainCell.Width, + Height = mainCell.Height + }; + } + } + return page; + } + + private static double[] BuildColumnXPositions(PdfPageSettings pageSettings, Page page) + { + int colCount = page.ToColumn - page.FromColumn + 1; + var colX = new double[colCount]; + double x = pageSettings.ContentBounds.Left + page.HeadingWidth + page.PrintTitleWidth; + for (int col = page.FromColumn; col <= page.ToColumn; col++) + { + colX[col - page.FromColumn] = x; + var cell = page.Map[page.FromRow, col]; + x += cell?.ColumnWidth ?? 0d; + } + return colX; + } + + private static void ComputePrintTitleDimensions(PdfWorksheet pdfSheet, PdfRange range, out double titleHeight, out double titleWidth) + { + titleHeight = 0d; + titleWidth = 0d; + if (pdfSheet.PrintTitleRowFrom >= 0) + { + for (int r = pdfSheet.PrintTitleRowFrom; r <= pdfSheet.PrintTitleRowTo; r++) + { + int idx = r - range.Range._fromRow; + if (idx >= 0 && idx < range.RowHeights.Count) + titleHeight += range.RowHeights[idx].Height; + } + } + if (pdfSheet.PrintTitleColFrom >= 0) + { + for (int c = pdfSheet.PrintTitleColFrom; c <= pdfSheet.PrintTitleColTo; c++) + { + int idx = c - range.Range._fromCol; + if (idx >= 0 && idx < range.ColWidths.Count) + titleWidth += range.ColWidths[idx]; + } + } + } + + internal static Pages PrecomputePrintTitleCells(PdfPageSettings pageSettings, PdfWorksheet pdfSheet, PdfRange range, Pages pdfPages) + { + for (int i = 0; i < pdfPages.Page.Length; i++) + pdfPages.Page[i] = PrecomputePagePrintTitleCells(pageSettings, pdfSheet, range, pdfPages.Page[i]); + return pdfPages; + } + + private static Page PrecomputePagePrintTitleCells(PdfPageSettings pageSettings, PdfWorksheet pdfSheet, PdfRange range, Page page) + { + page.PrintTitleCells = new List(); + page.PrintTitleBorders = new List(); + + bool topBand = page.PrintTitleHeight > 0d && pdfSheet.PrintTitleRowFrom >= 0 && page.FromRow > pdfSheet.PrintTitleRowTo; + bool leftBand = page.PrintTitleWidth > 0d && pdfSheet.PrintTitleColFrom >= 0 && page.FromColumn > pdfSheet.PrintTitleColTo; + if (!topBand && !leftBand) return page; + + // Content-column X: same origin/widths the content loop uses (step-2 origin). + var contentColX = new Dictionary(); + double cx = pageSettings.ContentBounds.Left + page.HeadingWidth + page.PrintTitleWidth; + for (int c = page.FromColumn; c <= page.ToColumn; c++) + { + contentColX[c] = cx; + cx += RangeColWidth(range, page.FromRow, c); + } + + // Content-row Y. + var contentRowY = new Dictionary(); + double cy = pageSettings.ContentBounds.Top - page.HeadingHeight - page.PrintTitleHeight; + for (int r = page.FromRow; r <= page.ToRow; r++) + { + contentRowY[r] = cy; + cy -= RangeRowHeight(range, r); + } + + // Title-column X (left band): just right of the heading gutter. + var titleColX = new Dictionary(); + if (leftBand) + { + double tx = pageSettings.ContentBounds.Left + page.HeadingWidth; + for (int c = pdfSheet.PrintTitleColFrom; c <= pdfSheet.PrintTitleColTo; c++) + { + titleColX[c] = tx; + tx += RangeColWidth(range, page.FromRow, c); + } + } + + // Title-row Y (top band): just below the heading gutter. + var titleRowY = new Dictionary(); + if (topBand) + { + double ty = pageSettings.ContentBounds.Top - page.HeadingHeight; + for (int r = pdfSheet.PrintTitleRowFrom; r <= pdfSheet.PrintTitleRowTo; r++) + { + titleRowY[r] = ty; + ty -= RangeRowHeight(range, r); + } + } + + if (topBand) + ProcessBandRegionCells(page, range, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, page.FromColumn, page.ToColumn, contentColX, titleRowY); + if (leftBand) + ProcessBandRegionCells(page, range, page.FromRow, page.ToRow, pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, titleColX, contentRowY); + if (topBand && leftBand) + ProcessBandRegionCells(page, range, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, titleColX, titleRowY); + + //// Top band: title rows × this page's content columns. + //if (topBand) + // for (int r = pdfSheet.PrintTitleRowFrom; r <= pdfSheet.PrintTitleRowTo; r++) + // for (int c = page.FromColumn; c <= page.ToColumn; c++) + // AddPrintTitleDraw(page.PrintTitleCells, range, r, c, + // contentColX[c], titleRowY[r], RangeColWidth(range, page.FromRow, c), RangeRowHeight(range, r)); + + //// Left band: this page's content rows × title columns. + //if (leftBand) + // for (int r = page.FromRow; r <= page.ToRow; r++) + // for (int c = pdfSheet.PrintTitleColFrom; c <= pdfSheet.PrintTitleColTo; c++) + // AddPrintTitleDraw(page.PrintTitleCells, range, r, c, + // titleColX[c], contentRowY[r], RangeColWidth(range, page.FromRow, c), RangeRowHeight(range, r)); + + //// Corner: title rows × title columns. + //if (topBand && leftBand) + // for (int r = pdfSheet.PrintTitleRowFrom; r <= pdfSheet.PrintTitleRowTo; r++) + // for (int c = pdfSheet.PrintTitleColFrom; c <= pdfSheet.PrintTitleColTo; c++) + // AddPrintTitleDraw(page.PrintTitleCells, range, r, c, + // titleColX[c], titleRowY[r], RangeColWidth(range, page.FromRow, c), RangeRowHeight(range, r)); + + page.PrintTitleGridLines = new List(); + if (pageSettings.ShowGridLines) + { + if (topBand) + EmitBandGrid(page.PrintTitleGridLines, range, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, page.FromColumn, page.ToColumn, + BandEdgesX(contentColX, page.FromColumn, page.ToColumn, range, page.FromRow), + BandEdgesY(titleRowY, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, range)); + if (leftBand) + EmitBandGrid(page.PrintTitleGridLines, range, page.FromRow, page.ToRow, pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, + BandEdgesX(titleColX, pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, range, page.FromRow), + BandEdgesY(contentRowY, page.FromRow, page.ToRow, range)); + if (topBand && leftBand) + EmitBandGrid(page.PrintTitleGridLines, range, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, + BandEdgesX(titleColX, pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, range, page.FromRow), + BandEdgesY(titleRowY, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, range)); + } + + // ---- band gridlines: full grid per region, rendered outside the clip ---- + //page.PrintTitleGridLines = new List(); + //if (pageSettings.ShowGridLines) + //{ + // if (topBand) + // EmitFullGrid(page.PrintTitleGridLines, + // BandEdgesX(contentColX, page.FromColumn, page.ToColumn, range, page.FromRow), + // BandEdgesY(titleRowY, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, range)); + + // if (leftBand) + // EmitFullGrid(page.PrintTitleGridLines, + // BandEdgesX(titleColX, pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, range, page.FromRow), + // BandEdgesY(contentRowY, page.FromRow, page.ToRow, range)); + + // if (topBand && leftBand) + // EmitFullGrid(page.PrintTitleGridLines, + // BandEdgesX(titleColX, pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, range, page.FromRow), + // BandEdgesY(titleRowY, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, range)); + //} + + // ---- band headings: original row numbers / column letters, in the gutter gaps ---- + page.PrintTitleHeadings = new List(); + if (pageSettings.ShowHeadings) + { + if (topBand) + for (int r = pdfSheet.PrintTitleRowFrom; r <= pdfSheet.PrintTitleRowTo; r++) + { + double h = RangeRowHeight(range, r); + if (h <= 0d) continue; + page.PrintTitleHeadings.Add(new PrintTitleHeadingDraw + { + IsRow = true, + Index = r, + X = pageSettings.ContentBounds.Left, + Y = titleRowY[r], + Width = page.HeadingWidth, + Height = h + }); + } + + if (leftBand) + for (int c = pdfSheet.PrintTitleColFrom; c <= pdfSheet.PrintTitleColTo; c++) + { + double w = RangeColWidth(range, page.FromRow, c); + if (w <= 0d) continue; + page.PrintTitleHeadings.Add(new PrintTitleHeadingDraw + { + IsRow = false, + Index = c, + X = titleColX[c], + Y = pageSettings.ContentBounds.Top, + Width = w, + Height = page.HeadingHeight + }); + } + } + + // repeated title-row text continues onto the next horizontal page's band + if (topBand) + AddIncomingSpill(page, range, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, page.FromColumn, page.ToColumn, + pageSettings.ContentBounds.Left + page.HeadingWidth + page.PrintTitleWidth, + pageSettings.ContentBounds.Top - page.HeadingHeight, + isPrintTitle: true); + + // left band: a neighbour whose text spills INTO a title column travels with the repeated column + if (leftBand) + AddIncomingSpill(page, range, page.FromRow, page.ToRow, + pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, + pageSettings.ContentBounds.Left + page.HeadingWidth, // band origin X (left edge of the title columns) + pageSettings.ContentBounds.Top - page.HeadingHeight - page.PrintTitleHeight, // content-rows origin Y + isPrintTitle: true); + + // corner: same, for the title-rows × title-columns intersection + if (topBand && leftBand) + AddIncomingSpill(page, range, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, + pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, + pageSettings.ContentBounds.Left + page.HeadingWidth, // band origin X + pageSettings.ContentBounds.Top - page.HeadingHeight, // title-rows origin Y + isPrintTitle: true); + + return page; + } + + private static void ProcessBandRegionCells(Page page, PdfRange range, int fromRow, int toRow, int fromCol, int toCol, Dictionary colX, Dictionary rowY) + { + // Band-region rectangle (top-left origin). Non-merged text may spill within this rect, + // which is bounded by the band edge, so it can never overflow into the content area. + double regLeft = colX[fromCol]; + double regRight = colX[toCol] + RangeColWidth(range, page.FromRow, toCol); + double regTop = rowY[fromRow]; + double regBottom = rowY[toRow] - RangeRowHeight(range, toRow); + double regW = regRight - regLeft; + double regH = regTop - regBottom; + + var drawnMerges = new HashSet(); + for (int r = fromRow; r <= toRow; r++) + { + for (int c = fromCol; c <= toCol; c++) + { + var cell = RangeCell(range, r, c); + if (cell == null) continue; + + if (cell.Merged) + { + if (!drawnMerges.Add(cell.MergedAddress.Address)) continue; + var addr = cell.MergedAddress; + var main = cell.Main ?? cell; + + int vFromCol = Math.Max(addr._fromCol, fromCol); + int vToCol = Math.Min(addr._toCol, toCol); + int vFromRow = Math.Max(addr._fromRow, fromRow); + int vToRow = Math.Min(addr._toRow, toRow); + if (vFromCol > vToCol || vFromRow > vToRow) continue; + + double x = colX[vFromCol]; + double width = (colX[vToCol] + RangeColWidth(range, page.FromRow, vToCol)) - colX[vFromCol]; + double y = rowY[vFromRow]; + double height = (rowY[vFromRow] - rowY[vToRow]) + RangeRowHeight(range, vToRow); + if (width <= 0d || height <= 0d) continue; + + // merged cells don't spill — clip text to the merge itself (matches content) + page.PrintTitleCells.Add(new PrintTitleCellDraw + { + Cell = main, + X = x, + Y = y, + Width = width, + Height = height, + ClipX = x, + ClipY = y, + ClipWidth = width, + ClipHeight = height + }); + for (int br = vFromRow; br <= vToRow; br++) + { + double bh = RangeRowHeight(range, br); + if (bh <= 0d) continue; + for (int bc = vFromCol; bc <= vToCol; bc++) + { + double bw = RangeColWidth(range, page.FromRow, bc); + if (bw <= 0d) continue; + var sub = RangeCell(range, br, bc); + if (sub == null || !HasBorder(sub.CellStyle)) continue; + page.PrintTitleBorders.Add(new PrintTitleCellDraw + { + Cell = sub, + X = colX[bc], + Y = rowY[br], + Width = bw, + Height = bh + }); + } + } + } + else + { + double w = RangeColWidth(range, page.FromRow, c); + double h = RangeRowHeight(range, r); + if (w <= 0d || h <= 0d) continue; + + // non-merged: clip to the band region so text can spill within it but not into content + page.PrintTitleCells.Add(new PrintTitleCellDraw + { + Cell = cell, + X = colX[c], + Y = rowY[r], + Width = w, + Height = h, + ClipX = regLeft, + ClipY = regTop, + ClipWidth = regW, + ClipHeight = regH + }); + } + } + } + } + + private static void EmitBandGrid(List target, PdfRange range, int fromRow, int toRow, int fromCol, int toCol, double[] colX, double[] rowY) + { + int nr = toRow - fromRow + 1; + int nc = toCol - fromCol + 1; + if (nr <= 0 || nc <= 0) return; + + var spill = BuildBandSpillMask(range, fromRow, toRow, fromCol, toCol, colX); + double top = rowY[0], bottom = rowY[nr]; + double left = colX[0], right = colX[nc]; + + EmitBandFrameH(target, range, top, fromRow, fromCol, nc, colX, CellHasTopBorder); + EmitBandFrameH(target, range, bottom, toRow, fromCol, nc, colX, CellHasBottomBorder); + EmitBandFrameV(target, range, left, fromCol, fromRow, nr, rowY, CellHasLeftBorder); + EmitBandFrameV(target, range, right, toCol, fromRow, nr, rowY, CellHasRightBorder); + + // region frame (outer edges; harmless overlap with page frame / heading borders / band↔content seam) + //target.Add(new GridLine(left, top, left, bottom)); + //target.Add(new GridLine(right, top, right, bottom)); + //target.Add(new GridLine(left, top, right, top)); + //target.Add(new GridLine(left, bottom, right, bottom)); + + // interior verticals — suppress where a merge spans the gap + for (int gi = 1; gi < nc; gi++) + { + int leftCol = fromCol + gi - 1, rightCol = leftCol + 1; + double x = colX[gi]; + double? runStart = null; double runEnd = 0d; + for (int ri = 0; ri < nr; ri++) + { + int r = fromRow + ri; + //bool block = SameMerge(range, r, leftCol, r, rightCol) || spill[ri, gi]; + bool block = SameMerge(range, r, leftCol, r, rightCol) || spill[ri, gi] || + CellHasRightBorder(RangeCell(range, r, leftCol)) || + CellHasLeftBorder(RangeCell(range, r, rightCol)); + if (!block) { if (runStart == null) runStart = rowY[ri]; runEnd = rowY[ri + 1]; } + else if (runStart != null) { target.Add(new GridLine(x, runStart.Value, x, runEnd)); runStart = null; } + } + //for (int ri = 0; ri < nr; ri++) + //{ + // int r = fromRow + ri; + // if (!SameMerge(range, r, leftCol, r, rightCol)) { if (runStart == null) runStart = rowY[ri]; runEnd = rowY[ri + 1]; } + // else if (runStart != null) { target.Add(new GridLine(x, runStart.Value, x, runEnd)); runStart = null; } + //} + if (runStart != null) target.Add(new GridLine(x, runStart.Value, x, runEnd)); + } + + // interior horizontals — suppress where a merge spans the gap + for (int gj = 1; gj < nr; gj++) + { + int topRow = fromRow + gj - 1, bottomRow = topRow + 1; + double y = rowY[gj]; + double? runStart = null; double runEnd = 0d; + for (int ci = 0; ci < nc; ci++) + { + int c = fromCol + ci; + bool block = SameMerge(range, topRow, c, bottomRow, c) || + CellHasBottomBorder(RangeCell(range, topRow, c)) || + CellHasTopBorder(RangeCell(range, bottomRow, c)); + if (!block) { if (runStart == null) runStart = colX[ci]; runEnd = colX[ci + 1]; } + //if (!SameMerge(range, topRow, c, bottomRow, c)) { if (runStart == null) runStart = colX[ci]; runEnd = colX[ci + 1]; } + else if (runStart != null) { target.Add(new GridLine(runStart.Value, y, runEnd, y)); runStart = null; } + } + if (runStart != null) target.Add(new GridLine(runStart.Value, y, runEnd, y)); + } + } + + private static bool[,] BuildBandSpillMask(PdfRange range, int fromRow, int toRow, int fromCol, int toCol, double[] colX) + { + int nr = toRow - fromRow + 1; + int nc = toCol - fromCol + 1; + var blocked = new bool[Math.Max(nr, 1), Math.Max(nc, 1)]; // [ri, g], g in 1..nc-1 + if (nr <= 0 || nc <= 1) return blocked; + int repRow = fromRow; + + for (int ri = 0; ri < nr; ri++) + { + int row = fromRow + ri; + + // (a) cells spilling within the band region + for (int ci = 0; ci < nc; ci++) + { + var cell = RangeCell(range, row, fromCol + ci); + if (cell == null || cell.Merged) continue; + if (cell.ContentAligmnet != null && cell.ContentAligmnet.WrapText) continue; + if (string.IsNullOrEmpty(cell.Text)) continue; + double spill = cell.TotalTextLength - cell.ColumnWidth; + if (spill <= 0d) continue; + var hal = cell.ContentAligmnet?.HorizontalAlignment ?? ExcelHorizontalAlignment.General; + if (hal == ExcelHorizontalAlignment.Left || hal == ExcelHorizontalAlignment.General) + BandMarkRight(range, row, ci, nc, fromCol, colX, spill, ri, blocked); + else if (hal == ExcelHorizontalAlignment.Right) + BandMarkLeft(range, row, ci, fromCol, colX, spill, ri, blocked); + else if (hal == ExcelHorizontalAlignment.Center) + { + double half = spill / 2d; + BandMarkRight(range, row, ci, nc, fromCol, colX, half, ri, blocked); + BandMarkLeft(range, row, ci, fromCol, colX, half, ri, blocked); + } + } + + // (b) spill entering from the LEFT of the region (left/general/center → spilling right in) + double lx = colX[0]; + for (int c = fromCol - 1; c >= range.Range._fromCol; c--) + { + double w = RangeColWidth(range, repRow, c); + lx -= w; + var cell = RangeCell(range, row, c); + if (cell == null) continue; + if (cell.Merged) break; + if (string.IsNullOrEmpty(cell.Text)) continue; + var hal = cell.ContentAligmnet?.HorizontalAlignment ?? ExcelHorizontalAlignment.General; + if (hal == ExcelHorizontalAlignment.Right) break; // spills away from the region + double rightExtent = (hal == ExcelHorizontalAlignment.Center) + ? lx + w / 2d + cell.TotalTextLength / 2d + : lx + cell.TotalTextLength; + for (int g = 1; g <= nc - 1; g++) + { + var blk = RangeCell(range, row, fromCol + g - 1); + if (blk != null && !string.IsNullOrEmpty(blk.Text)) break; + if (colX[g] < rightExtent) blocked[ri, g] = true; else break; + } + break; + } + + // (c) spill entering from the RIGHT of the region (right/center → spilling left in) + double rx = colX[nc]; + for (int c = toCol + 1; c <= range.Range._toCol; c++) + { + double w = RangeColWidth(range, repRow, c); + var cell = RangeCell(range, row, c); + if (cell == null) { rx += w; continue; } + if (cell.Merged) break; + if (string.IsNullOrEmpty(cell.Text)) { rx += w; continue; } + var hal = cell.ContentAligmnet?.HorizontalAlignment ?? ExcelHorizontalAlignment.General; + if (hal == ExcelHorizontalAlignment.Left || hal == ExcelHorizontalAlignment.General) break; // spills away + double leftExtent = (hal == ExcelHorizontalAlignment.Center) + ? rx + w / 2d - cell.TotalTextLength / 2d + : rx + w - cell.TotalTextLength; + for (int g = nc - 1; g >= 1; g--) + { + var blk = RangeCell(range, row, fromCol + g); + if (blk != null && !string.IsNullOrEmpty(blk.Text)) break; + if (colX[g] > leftExtent) blocked[ri, g] = true; else break; + } + break; + } + } + return blocked; + } + + private static void BandMarkRight(PdfRange range, int row, int ci, int nc, int fromCol, double[] colX, double spill, int ri, bool[,] blocked) + { + for (int g = ci + 1; g <= nc - 1; g++) + { + var rightCell = RangeCell(range, row, fromCol + g); + if (rightCell != null && !string.IsNullOrEmpty(rightCell.Text)) break; + double distToGap = colX[g] - colX[ci + 1]; + if (spill > distToGap) blocked[ri, g] = true; else break; + } + } + + private static void BandMarkLeft(PdfRange range, int row, int ci, int fromCol, double[] colX, double spill, int ri, bool[,] blocked) + { + for (int g = ci; g >= 1; g--) + { + var leftCell = RangeCell(range, row, fromCol + g - 1); + if (leftCell != null && !string.IsNullOrEmpty(leftCell.Text)) break; + double distToGap = colX[ci] - colX[g]; + if (spill > distToGap) blocked[ri, g] = true; else break; + } + } + + private static bool SameMerge(PdfRange range, int r1, int c1, int r2, int c2) + { + var a = RangeCell(range, r1, c1); + var b = RangeCell(range, r2, c2); + if (a == null || b == null || !a.Merged || !b.Merged) return false; + return a.MergedAddress.Address == b.MergedAddress.Address; + } + + // Column edges for a band region: left edge of every column fromCol..toCol, plus the trailing right edge. + private static double[] BandEdgesX(Dictionary leftX, int fromCol, int toCol, PdfRange range, int repRow) + { + int n = toCol - fromCol + 1; + var arr = new double[n + 1]; + for (int c = fromCol, k = 0; c <= toCol; c++, k++) arr[k] = leftX[c]; + arr[n] = leftX[toCol] + RangeColWidth(range, repRow, toCol); + return arr; + } + + // Row edges for a band region: top edge of every row fromRow..toRow, plus the trailing bottom edge. + private static double[] BandEdgesY(Dictionary topY, int fromRow, int toRow, PdfRange range) + { + int n = toRow - fromRow + 1; + var arr = new double[n + 1]; + for (int r = fromRow, k = 0; r <= toRow; r++, k++) arr[k] = topY[r]; + arr[n] = topY[toRow] - RangeRowHeight(range, toRow); + return arr; + } + + private static void EmitFullGrid(List target, double[] colX, double[] rowY) + { + int nc = colX.Length - 1; + int nr = rowY.Length - 1; + if (nc < 1 || nr < 1) return; + double top = rowY[0], bottom = rowY[nr]; + double left = colX[0], right = colX[nc]; + for (int i = 0; i <= nc; i++) target.Add(new GridLine(colX[i], top, colX[i], bottom)); // verticals + for (int j = 0; j <= nr; j++) target.Add(new GridLine(left, rowY[j], right, rowY[j])); // horizontals + } + + private static void AddPrintTitleDraw(List sink, PdfRange range, int row, int col, double x, double y, double w, double h) + { + if (w <= 0d || h <= 0d) return; + var cell = RangeCell(range, row, col); + if (cell == null) return; + if (cell.Merged) return; // merged title cells: handled in a later sub-step + sink.Add(new PrintTitleCellDraw { Cell = cell, X = x, Y = y, Width = w, Height = h }); + } + + private static PdfCell RangeCell(PdfRange range, int row, int col) + { + if (row < range.Range._fromRow || row > range.Range._toRow) return null; + if (col < range.Range._fromCol || col > range.Range._toCol) return null; + return range.Map[row, col]; + } + + private static double RangeColWidth(PdfRange range, int representativeRow, int col) + { + var cell = RangeCell(range, representativeRow, col); + return cell?.ColumnWidth ?? 0d; + } + + private static double RangeRowHeight(PdfRange range, int row) + { + int i = row - range.Range._fromRow; + return (i >= 0 && i < range.RowHeights.Count) ? range.RowHeights[i].Height : 0d; + } + + internal static Pages GetNumberOfPages(PdfPageSettings pageSettings, PdfWorksheet pdfSheet, ref PdfRange range) + { + var xPages = (int)Math.Max(1, Math.Ceiling(range.TotalWidth / pageSettings.ContentBounds.Width)); + var yPages = (int)Math.Max(1, Math.Ceiling(range.TotalHeight / pageSettings.ContentBounds.Height)); + + if (pageSettings.ShowHeadings) + { + int prev = 0; + do + { + prev = xPages; + range.AdditionalWidth = xPages * ((rowHeadingWith1CharWidth - pdfSheet.ZeroCharWidth) + (Math.Abs(pdfSheet.ToRow).ToString().Length * pdfSheet.ZeroCharWidth)); + xPages = (int)Math.Max(1, Math.Ceiling((range.TotalWidth + range.AdditionalWidth) / pageSettings.ContentBounds.Width)); + } while (prev != xPages); + do + { + prev = yPages; + range.AdditionalHeight = yPages * pdfSheet.Worksheet.DefaultRowHeight; + yPages = (int)Math.Max(1, Math.Ceiling((range.TotalHeight + range.AdditionalHeight) / pageSettings.ContentBounds.Height)); + } while (prev != yPages); + } + for (int i = range.Range._fromCol; i <= range.Range._toCol; i++) + { + if (pdfSheet.Worksheet.Column(i).PageBreak) + xPages++; + } + for (int i = range.Range._fromRow; i <= range.Range._toRow; i++) + { + if (pdfSheet.Worksheet.Row(i).PageBreak) + yPages++; + } + ComputePrintTitleDimensions(pdfSheet, range, out range.PrintTitleHeight, out range.PrintTitleWidth); + range.PrintTitleRowTo = pdfSheet.PrintTitleRowFrom >= 0 ? pdfSheet.PrintTitleRowTo : -1; + range.PrintTitleColTo = pdfSheet.PrintTitleColFrom >= 0 ? pdfSheet.PrintTitleColTo : -1; + Pages p = new Pages(); + p.Width = xPages; + p.Height = yPages; + p.Page = null; + return p; + } + + internal static Pages AssignRangeToPages(PdfPageSettings pageSettings, PdfRange range, Pages pdfPages) + { + var pages = pdfPages; + var worksheet = range.Range.Worksheet; + var addedWidth = pages.Width > 0 ? range.AdditionalWidth / pages.Width : 0d; + var addedHeight = pages.Height > 0 ? range.AdditionalHeight / pages.Height : 0d; + + var colSegments = GetColumnSegments(pageSettings, range, worksheet, addedWidth, range.PrintTitleWidth, range.PrintTitleColTo); + var rowSegments = GetRowSegments(pageSettings, range, worksheet, addedHeight, range.PrintTitleHeight, range.PrintTitleRowTo); + + pages.Page = new Page[colSegments.Count * rowSegments.Count]; + int i = 0; + + if (pageSettings.PageOrders == PageOrders.DownThenOver) + { + //foreach (var colSeg in colSegments) + // foreach (var rowSeg in rowSegments) + // pages.Page[i++] = new Page { FromColumn = colSeg.From, ToColumn = colSeg.To, FromRow = rowSeg.From, ToRow = rowSeg.To }; + for (int ci = 0; ci < colSegments.Count; ci++) + for (int ri = 0; ri < rowSegments.Count; ri++) + pages.Page[i++] = new Page + { + FromColumn = colSegments[ci].From, + ToColumn = colSegments[ci].To, + FromRow = rowSegments[ri].From, + ToRow = rowSegments[ri].To, + HeadingWidth = addedWidth, + HeadingHeight = addedHeight, + PrintTitleWidth = (range.PrintTitleColTo >= 0 && colSegments[ci].From > range.PrintTitleColTo) ? range.PrintTitleWidth : 0d, + PrintTitleHeight = (range.PrintTitleRowTo >= 0 && rowSegments[ri].From > range.PrintTitleRowTo) ? range.PrintTitleHeight : 0d, + }; + } + else //if (pageSettings.PageOrders == PageOrders.OverThenDown) + { + //foreach (var rowSeg in rowSegments) + // foreach (var colSeg in colSegments) + // pages.Page[i++] = new Page { FromColumn = colSeg.From, ToColumn = colSeg.To, FromRow = rowSeg.From, ToRow = rowSeg.To }; + for (int ri = 0; ri < rowSegments.Count; ri++) + for (int ci = 0; ci < colSegments.Count; ci++) + pages.Page[i++] = new Page + { + FromColumn = colSegments[ci].From, + ToColumn = colSegments[ci].To, + FromRow = rowSegments[ri].From, + ToRow = rowSegments[ri].To, + HeadingWidth = addedWidth, + HeadingHeight = addedHeight, + PrintTitleWidth = (range.PrintTitleColTo >= 0 && colSegments[ci].From > range.PrintTitleColTo) ? range.PrintTitleWidth : 0d, + PrintTitleHeight = (range.PrintTitleRowTo >= 0 && rowSegments[ri].From > range.PrintTitleRowTo) ? range.PrintTitleHeight : 0d, + }; + } + + //for (int k = 0; k < pages.Page.Length; k++) + //{ + // var p = pages.Page[k]; + // p.HeadingWidth = addedWidth; + // p.HeadingHeight = addedHeight; + // pages.Page[k] = p; + //} + pdfPages = pages; + return pdfPages; + } + + private struct PageSegment + { + public int From; + public int To; + public PageSegment(int from, int to) { From = from; To = to; } + } + + private static List GetColumnSegments(PdfPageSettings pageSettings, PdfRange range, ExcelWorksheet worksheet, double addedWidth, double titleWidth, int printTitleColTo) + { + var segments = new List(); + int segStartIdx = 0; + double width = 0d; + + for (int col = 0; col < range.ColWidths.Count; col++) + { + int actualCol = range.Range._fromCol + col; + + bool reserveTitle = titleWidth > 0d && printTitleColTo >= 0 && (range.Map.FromColumn + segStartIdx) > printTitleColTo; + double effectiveAdded = addedWidth + (reserveTitle ? titleWidth : 0d); + + // Content-bounds overflow: col doesn't fit, end segment before it and reprocess. + if (width + range.ColWidths[col] + effectiveAdded >= pageSettings.ContentBounds.Width) + { + segments.Add(new PageSegment(range.Map.FromColumn + segStartIdx, range.Map.FromColumn + col - 1)); + segStartIdx = col; + width = 0d; + col--; // reprocess this col as the first col of the next segment + continue; + } + + width += range.ColWidths[col]; + + // Explicit page break: col is included on this page, next segment starts after it. + if (worksheet.Column(actualCol).PageBreak) + { + segments.Add(new PageSegment(range.Map.FromColumn + segStartIdx, range.Map.FromColumn + col)); + segStartIdx = col + 1; + width = 0d; + } + } + + // Remaining cols form the last segment. + if (segStartIdx < range.ColWidths.Count) + segments.Add(new PageSegment(range.Map.FromColumn + segStartIdx, range.Map.FromColumn + range.ColWidths.Count - 1)); + + return segments; + } + + private static List GetRowSegments(PdfPageSettings pageSettings, PdfRange range, ExcelWorksheet worksheet, double addedHeight, double titleHeight, int printTitleRowTo) + { + var segments = new List(); + int segStartIdx = 0; + double height = 0d; + + for (int row = 0; row < range.RowHeights.Count; row++) + { + int actualRow = range.Range._fromRow + row; + + bool reserveTitle = titleHeight > 0d && printTitleRowTo >= 0 && (range.Map.FromRow + segStartIdx) > printTitleRowTo; + double effectiveAdded = addedHeight + (reserveTitle ? titleHeight : 0d); + + // Content-bounds overflow: row doesn't fit, end segment before it and reprocess. + if (height + range.RowHeights[row].Height + effectiveAdded >= pageSettings.ContentBounds.Height) + { + segments.Add(new PageSegment(range.Map.FromRow + segStartIdx, range.Map.FromRow + row - 1)); + segStartIdx = row; + height = 0d; + row--; // reprocess this row as the first row of the next segment + continue; + } + + height += range.RowHeights[row].Height; + + // Explicit page break: row is included on this page, next segment starts after it. + if (worksheet.Row(actualRow).PageBreak) + { + segments.Add(new PageSegment(range.Map.FromRow + segStartIdx, range.Map.FromRow + row)); + segStartIdx = row + 1; + height = 0d; + } + } + + // Remaining rows form the last segment. + if (segStartIdx < range.RowHeights.Count) + segments.Add(new PageSegment(range.Map.FromRow + segStartIdx, range.Map.FromRow + range.RowHeights.Count - 1)); + + return segments; + } + + internal static Pages MapPage(PdfRange range, Pages pdfPages) + { + var pages = pdfPages; + for (int i = 0; i < pdfPages.Page.Length; i++) + { + var page = pdfPages.Page[i]; + page.Map = new PdfCellCollection(page.FromRow, page.ToRow, page.FromColumn, page.ToColumn); + page.RowHeights = new double[page.ToRow - page.FromRow + 1]; + for (int row = page.FromRow; row <= page.ToRow; row++) + { + int rangeIdx = row - range.Range._fromRow; + page.RowHeights[row - page.FromRow] = range.RowHeights[rangeIdx].Height; + for (int col = page.FromColumn; col <= page.ToColumn; col++) + { + page.Map[row, col] = range.Map[row, col]; + } + } + pdfPages.Page[i] = page; + } + pdfPages = pages; + return pdfPages; + } + + private static Pages GetHeaderFooter(PdfRange range, Pages pdfPages, PdfWorksheet pdfSheet) + { + var pages = pdfPages; + for (int i = 0; i < pdfPages.Page.Length; i++) + { + var page = pdfPages.Page[i]; + page.HeaderFooters = pdfSheet.HeaderFooters; + pdfPages.Page[i] = page; + } + pdfPages = pages; + return pdfPages; + } + + private static void SubstitutePageNumbers(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfHeaderFooter hf, int pageNumber, int totalPages) + { + if (hf == null) return; + if (hf.PageNumberIndexes.Count > 0) + { + foreach (var idx in hf.PageNumberIndexes) + hf.Content.TextFragments[idx].Text = pageNumber.ToString(); + } + if (hf.NumberOfPagesIndexes.Count > 0) + { + foreach (var idx in hf.NumberOfPagesIndexes) + hf.Content.TextFragments[idx].Text = totalPages.ToString(); + } + PdfTextShaper.ShapeText(pageSettings, dictionaries, hf.Content); + } + + private static void AddIncomingSpill(Page page, PdfRange range, int fromRow, int toRow, int windowFromCol, int windowToCol, double windowOriginX, double windowOriginY, bool isPrintTitle) + { + if (page.SpillCells == null) return; // initialised by the caller + + double windowRightX = windowOriginX; + for (int c = windowFromCol; c <= windowToCol; c++) windowRightX += RangeColWidth(range, page.FromRow, c); + + double rowTop = windowOriginY; + for (int r = fromRow; r <= toRow; r++) + { + double rowH = RangeRowHeight(range, r); + double y = rowTop; + rowTop -= rowH; + if (rowH <= 0d) continue; + + // spill entering from the LEFT (left/general/center) + double lx = windowOriginX; + for (int c = windowFromCol - 1; c >= range.Range._fromCol; c--) + { + double w = RangeColWidth(range, page.FromRow, c); + lx -= w; + var cell = RangeCell(range, r, c); + if (cell == null) continue; + if (cell.Merged) break; // merges don't spill, and block + if (string.IsNullOrEmpty(cell.Text)) continue; + var hal = cell.ContentAligmnet?.HorizontalAlignment ?? ExcelHorizontalAlignment.General; + double rightExtent = + (hal == ExcelHorizontalAlignment.Center) ? lx + w / 2d + cell.TotalTextLength / 2d : + (hal == ExcelHorizontalAlignment.Right) ? lx + w : + lx + cell.TotalTextLength; + if (rightExtent > windowOriginX) + { + double clipRight = FirstBlockedX(page, range, r, windowFromCol, windowToCol, windowOriginX, windowRightX, fromLeft: true); + page.SpillCells.Add(new SpillCellDraw + { + Cell = cell, + X = lx, + Y = y, + Width = w, + Height = rowH, + ClipX = windowOriginX, + ClipY = y, + ClipWidth = clipRight - windowOriginX, + ClipHeight = rowH, + IsPrintTitle = isPrintTitle + }); + } + break; + } + + // spill entering from the RIGHT (right/center) + double rx = windowRightX; + for (int c = windowToCol + 1; c <= range.Range._toCol; c++) + { + double w = RangeColWidth(range, page.FromRow, c); + var cell = RangeCell(range, r, c); + if (cell == null) { rx += w; continue; } + if (cell.Merged) break; + if (string.IsNullOrEmpty(cell.Text)) { rx += w; continue; } + var hal = cell.ContentAligmnet?.HorizontalAlignment ?? ExcelHorizontalAlignment.General; + double leftExtent = + (hal == ExcelHorizontalAlignment.Center) ? rx + w / 2d - cell.TotalTextLength / 2d : + (hal == ExcelHorizontalAlignment.Right) ? rx + w - cell.TotalTextLength : + rx; + if (leftExtent < windowRightX) + { + double clipLeft = FirstBlockedX(page, range, r, windowFromCol, windowToCol, windowOriginX, windowRightX, fromLeft: false); + page.SpillCells.Add(new SpillCellDraw + { + Cell = cell, + X = rx, + Y = y, + Width = w, + Height = rowH, + ClipX = clipLeft, + ClipY = y, + ClipWidth = windowRightX - clipLeft, + ClipHeight = rowH, + IsPrintTitle = isPrintTitle + }); + } + break; + } + } + } + + // Where spill into the window is cut off by the first non-empty (or merged) cell inside it. + private static double FirstBlockedX(Page page, PdfRange range, int row, int windowFromCol, int windowToCol, double windowOriginX, double windowRightX, bool fromLeft) + { + if (fromLeft) + { + double x = windowOriginX; + for (int c = windowFromCol; c <= windowToCol; c++) + { + var cell = RangeCell(range, row, c); + if (cell != null && (cell.Merged || !string.IsNullOrEmpty(cell.Text))) return x; + x += RangeColWidth(range, page.FromRow, c); + } + return windowRightX; + } + double rx = windowRightX; + for (int c = windowToCol; c >= windowFromCol; c--) + { + double w = RangeColWidth(range, page.FromRow, c); + rx -= w; + var cell = RangeCell(range, row, c); + if (cell != null && (cell.Merged || !string.IsNullOrEmpty(cell.Text))) return rx + w; + } + return windowOriginX; + } + + internal static Pages PrecomputeSpillCells(PdfPageSettings pageSettings, PdfRange range, Pages pdfPages) + { + for (int i = 0; i < pdfPages.Page.Length; i++) + { + var page = pdfPages.Page[i]; + page.SpillCells = new List(); + double originX = pageSettings.ContentBounds.Left + page.HeadingWidth + page.PrintTitleWidth; + double originY = pageSettings.ContentBounds.Top - page.HeadingHeight - page.PrintTitleHeight; + AddIncomingSpill(page, range, page.FromRow, page.ToRow, page.FromColumn, page.ToColumn, originX, originY, isPrintTitle: false); + pdfPages.Page[i] = page; + } + return pdfPages; + } + + private static bool CellHasRightBorder(PdfCell cell) + { + var cs = cell?.CellStyle; if (cs == null) return false; + return (cs.xfRight != null && cs.xfRight.Style != ExcelBorderStyle.None) || (cs.dxfRight?.HasValue ?? false); + } + private static bool CellHasLeftBorder(PdfCell cell) + { + var cs = cell?.CellStyle; if (cs == null) return false; + return (cs.xfLeft != null && cs.xfLeft.Style != ExcelBorderStyle.None) || (cs.dxfLeft?.HasValue ?? false); + } + private static bool CellHasTopBorder(PdfCell cell) + { + var cs = cell?.CellStyle; if (cs == null) return false; + return (cs.xfTop != null && cs.xfTop.Style != ExcelBorderStyle.None) || (cs.dxfTop?.HasValue ?? false); + } + private static bool CellHasBottomBorder(PdfCell cell) + { + var cs = cell?.CellStyle; if (cs == null) return false; + return (cs.xfBottom != null && cs.xfBottom.Style != ExcelBorderStyle.None) || (cs.dxfBottom?.HasValue ?? false); + } + + private static void EmitBandFrameH(List target, PdfRange range, double y, int row, int fromCol, int nc, double[] colX, Func hasBorder) + { + double? rs = null; double re = 0d; + for (int ci = 0; ci < nc; ci++) + { + double segL = colX[ci], segR = colX[ci + 1]; + if (segR - segL <= 0d) { if (rs != null) { target.Add(new GridLine(rs.Value, y, re, y)); rs = null; } continue; } + if (!hasBorder(RangeCell(range, row, fromCol + ci))) { if (rs == null) rs = segL; re = segR; } + else if (rs != null) { target.Add(new GridLine(rs.Value, y, re, y)); rs = null; } + } + if (rs != null) target.Add(new GridLine(rs.Value, y, re, y)); + } + + private static void EmitBandFrameV(List target, PdfRange range, double x, int col, int fromRow, int nr, double[] rowY, Func hasBorder) + { + double? rs = null; double re = 0d; + for (int ri = 0; ri < nr; ri++) + { + double segT = rowY[ri], segB = rowY[ri + 1]; + if (segT - segB <= 0d) { if (rs != null) { target.Add(new GridLine(x, rs.Value, x, re)); rs = null; } continue; } + if (!hasBorder(RangeCell(range, fromRow + ri, col))) { if (rs == null) rs = segT; re = segB; } + else if (rs != null) { target.Add(new GridLine(x, rs.Value, x, re)); rs = null; } + } + if (rs != null) target.Add(new GridLine(x, rs.Value, x, re)); + } + + } +} diff --git a/src/EPPlus/Export/PdfExport/Layout/PdfPageLayout.cs b/src/EPPlus/Export/PdfExport/Layout/PdfPageLayout.cs new file mode 100644 index 0000000000..efb2fc30bb --- /dev/null +++ b/src/EPPlus/Export/PdfExport/Layout/PdfPageLayout.cs @@ -0,0 +1,334 @@ +/************************************************************************************************* + 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 System.Collections.Generic; +using System.Diagnostics; + +namespace OfficeOpenXml.Export.PdfExport.Layout +{ + [DebuggerDisplay("Cell: {Name}")] + internal struct PageMap + { + public string Name; + public int row; + public int col; + public PdfCellLayout cell; + public PdfCellContentLayout content; + public PdfCellBorderLayout border; + public enum CellType + { + Normal, + Merged, + } + public CellType Type; + + //Scoops up text spill from adjecent cells + public double LeftTextBucketSpill; + public double RightTextBucketSpill; + } + + internal class VerticalLineRun + { + public int Col; + public int RowStart; + public int RowEnd; + } + + internal class HorizontalLineRun + { + public int Row; + public int ColStart; + public int ColEnd; + } + + [DebuggerDisplay("{Name}")] + internal class PdfPageLayout : Transform + { + internal List GridLines = new List(); + internal List BorderLines = new List(); + public List PrintTitleGridLines = new List(); + + public int FromRow = int.MaxValue; + public int ToRow = 0; + public int FromCol = int.MaxValue; + public int ToCol = 0; + public int RowCount = 0; + public int ColCount = 0; + + public PageMap[,] Map; + + public double ContentTop; + public double ContentBottom; + public double ContentLeft; + public double ContentRight; + public double ContentX; + public double ContentHeight; + + public double HeadingWidth; + public double HeadingHeight; + + public double PrintTitleWidth; + public double PrintTitleHeight; + + public bool isCommentsPage = false; + + public PdfPageLayout(double x, double y, double width, double height) + : base(x, y, width, height) { } + + //public void AddCell(Transform cell) + //{ + // if (cell is PdfCellLayout cl) + // { + // FromRow = Math.Min(FromRow, cl.cell._fromRow); + // ToRow = Math.Max(ToRow, cl.cell._toRow); + // FromCol = Math.Min(FromCol, cl.cell._fromCol); + // ToCol = Math.Max(ToCol, cl.cell._toCol); + // } + // ChildObjects[0].AddChild(cell); + //} + + //public void CreateMap() + //{ + // RowCount = ToRow - FromRow + 1; + // ColCount = ToCol - FromCol + 1; + // Map = new PageMap[RowCount, ColCount]; + //} + + //internal void GenerateGridLines() + //{ + // HashSet xCoords = new HashSet(); + // HashSet yCoords = new HashSet(); + // // Collect all unique X and Y coordinates. + // var cells = ChildObjects.Where(x => x is PdfCellLayout).ToList(); + // foreach (var c in cells) + // { + // if (c is PdfMergedCellLayout) continue; + // yCoords.Add(c.LocalPosition.Y + c.Size.Y); // Top + // yCoords.Add(c.LocalPosition.Y); // Bottom + // xCoords.Add(c.LocalPosition.X); // Left + // xCoords.Add(c.LocalPosition.X + c.Size.X); // Right + // } + // double minX = cells.Where(c => c is not PdfMergedCellLayout).Min(c => c.LocalPosition.X); + // double maxX = cells.Where(c => c is not PdfMergedCellLayout).Max(c => c.LocalPosition.X + c.Size.X); + // double minY = cells.Where(c => c is not PdfMergedCellLayout).Min(c => c.LocalPosition.Y); + // double maxY = cells.Where(c => c is not PdfMergedCellLayout).Max(c => c.LocalPosition.Y + c.Size.Y); + // foreach (var x in xCoords.OrderBy(v => v)) + // { + // var line = new GridLine(x, minY, x, maxY); + // if (x == minX || x == maxX) + // BorderLines.Add(line); + // else + // GridLines.Add(line); + // } + // foreach (var y in yCoords.OrderBy(v => v)) + // { + // var line = new GridLine(minX, y, maxX, y); + // if (y == minY || y == maxY) + // BorderLines.Add(line); + // else + // GridLines.Add(line); + // } + //} + + //internal void GenerateVerticalGridLines(ExcelWorksheet ws) + //{ + // int addedColumns = PdfWorksheetLayout.AddColumnsForNonWrappedText(ws); + // bool resetStart = true; + // var startX = 0d; + // var startY = 0d; + // var endX = 0d; + // var endY = 0d; + // for (int col = FromCol+1; col <= ToCol; col++) + // { + // double length = 0; + // //string name = ""; + // //var f = ws.Cells[FromRow, col]; + // //var start = ChildObjects.Where(x => x.Name == f.Address || x.Name == f.Address + "_m" || x.Name == f.Address + "*").ToList(); + // for (int row = FromRow; row <= ToRow; row++) + // { + // var cell = ws.Cells[row, col]; + // var name = cell.Address; + // var layouts = ChildObjects.Where(x => x.Name == cell.Address || x.Name == cell.Address + "_m" || x.Name == cell.Address + "*").ToList(); + // PdfMergedCellLayout m = null; + // PdfCellLayout l = null; + // foreach (var layout in layouts) + // { + // if (layout is PdfMergedCellLayout) m = (PdfMergedCellLayout)layout; + // else if (layout is PdfCellLayout) l = (PdfCellLayout)layout; + // } + // if (l != null) + // { + // if (l.LeftTextSpillLength > 0) + // { + // length = 0; + // if (startX != 0d) + // { + // var line = new GridLine(startX, startY, endX, endY); + // GridLines.Add(line); + // resetStart = true; + // } + // } + // else + // { + // length += l.Size.Y; + // endX = l.LocalPosition.X; + // endY = l.LocalPosition.Y; + // if (resetStart) + // { + // startX = l.LocalPosition.X; + // startY = l.LocalPosition.Y + l.Size.Y; + // resetStart = false; + // } + // } + // } + // else if (m != null) + // { + // length += m.Size.Y; + // endX = m.LocalPosition.X; + // endY = m.LocalPosition.Y; + // if (resetStart) + // { + // startX = m.LocalPosition.X; + // startY = m.LocalPosition.Y + m.Size.Y; + // resetStart = false; + // } + // } + // else if (l == null && m == null) + // { + // if (length > 0) + // { + // length = 0; + // if (startX != 0d) + // { + // var line = new GridLine(startX, startY, endX, endY); + // GridLines.Add(line); + // resetStart = true; + // } + // } + // } + // } + // if (startX != 0d) + // { + // var line2 = new GridLine(startX, startY, endX, endY); + // GridLines.Add(line2); + // } + // resetStart = true; + // } + //} + + //internal void GenerateHorizontalGridLines(ExcelWorksheet ws) + //{ + // int addedColumns = PdfWorksheetLayout.AddColumnsForNonWrappedText(ws); + // bool resetStart = true; + // var startX = 0d; + // var startY = 0d; + // var endX = 0d; + // var endY = 0d; + // for (int row = FromRow+1; row <= ToRow; row++) + // { + // double length = 0; + // //string name = ""; + // //var f = ws.Cells[row, FromCol]; + // //var start = ChildObjects.Where(x => x.Name == f.Address || x.Name == f.Address + "_m" || x.Name == f.Address + "*").ToList(); + // for (int col = FromCol; col <= ToCol; col++) + // { + // var cell = ws.Cells[row, col]; + // var name = cell.Address; + // var layouts = ChildObjects.Where(x => x.Name == cell.Address || x.Name == cell.Address + "_m" || x.Name == cell.Address + "*").ToList(); + // PdfMergedCellLayout m = null; + // PdfCellLayout l = null; + // foreach (var layout in layouts) + // { + // if (layout is PdfMergedCellLayout) m = (PdfMergedCellLayout)layout; + // else if (layout is PdfCellLayout) l = (PdfCellLayout)layout; + // } + // if (l != null) + // { + // length += l.Size.X; + // endX = l.LocalPosition.X + l.Size.X; + // endY = l.LocalPosition.Y + l.Size.Y; + // if (resetStart) + // { + // startX = l.LocalPosition.X; + // startY = l.LocalPosition.Y + l.Size.Y; + // resetStart = false; + // } + // } + // else if (m != null) + // { + // length += m.Size.X; + // endX = m.LocalPosition.X + m.Size.X; + // endY = m.LocalPosition.Y + m.Size.Y; + // if (resetStart) + // { + // startX = m.LocalPosition.X; + // startY = m.LocalPosition.Y + m.Size.Y; + // resetStart = false; + // } + // } + // else if (l == null && m == null) + // { + // if (length > 0) + // { + // length = 0; + // if (startX != 0d) + // { + // var line = new GridLine(startX, startY, endX, endY); + // GridLines.Add(line); + // resetStart = true; + // } + // } + // } + // } + // if (startX != 0d) + // { + // var line2 = new GridLine(startX, startY, endX, endY); + // GridLines.Add(line2); + // } + // resetStart = true; + // } + //} + + //internal void GenerateBorderLines() + //{ + // HashSet xCoords = new HashSet(); + // HashSet yCoords = new HashSet(); + // // Collect all unique X and Y coordinates. + // var cells = ChildObjects.Where(x => x is PdfCellLayout).ToList(); + // foreach (var c in cells) + // { + // if (c is PdfMergedCellLayout) continue; + // yCoords.Add(c.LocalPosition.Y + c.Size.Y); // Top + // yCoords.Add(c.LocalPosition.Y); // Bottom + // xCoords.Add(c.LocalPosition.X); // Left + // xCoords.Add(c.LocalPosition.X + c.Size.X); // Right + // } + // double minX = cells.Where(c => c is not PdfMergedCellLayout).Min(c => c.LocalPosition.X); + // double maxX = cells.Where(c => c is not PdfMergedCellLayout).Max(c => c.LocalPosition.X + c.Size.X); + // double minY = cells.Where(c => c is not PdfMergedCellLayout).Min(c => c.LocalPosition.Y); + // double maxY = cells.Where(c => c is not PdfMergedCellLayout).Max(c => c.LocalPosition.Y + c.Size.Y); + // foreach (var x in xCoords.OrderBy(v => v)) + // { + // var line = new GridLine(x, minY, x, maxY); + // if (x == minX || x == maxX) + // BorderLines.Add(line); + // } + // foreach (var y in yCoords.OrderBy(v => v)) + // { + // var line = new GridLine(minX, y, maxX, y); + // if (y == minY || y == maxY) + // BorderLines.Add(line); + // } + //} + } +} \ No newline at end of file diff --git a/src/EPPlus/Export/PdfExport/PdfCatalog.cs b/src/EPPlus/Export/PdfExport/PdfCatalog.cs new file mode 100644 index 0000000000..92fb074165 --- /dev/null +++ b/src/EPPlus/Export/PdfExport/PdfCatalog.cs @@ -0,0 +1,381 @@ +/************************************************************************************************* + 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 OfficeOpenXml.Export.PdfExport.Data; +using OfficeOpenXml.Export.PdfExport.Data.Dictionaries; +using OfficeOpenXml.Export.PdfExport.Layout; +using OfficeOpenXml.Export.PdfExport.RowResize; +using OfficeOpenXml.Export.PdfExport.TextMapping; +using OfficeOpenXml.Export.PdfExport.TextShaping; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; + +namespace OfficeOpenXml.Export.PdfExport +{ + internal class PdfCatalog + { + internal PdfDictionaries _dictionaries = new PdfDictionaries(); + private bool _addTextForHeadings = true; + + // + // CONSTRUCTORS FOR MULTIPLE WORKSHEETS AS INPUT + // + public PdfCatalog() { } + + public PdfCatalog(PdfPageSettings pageSettings, ExcelWorkbook workbook) + { + HandleWorksheetCollection(pageSettings, workbook.Worksheets.ToArray()); + } + + public PdfCatalog(PdfPageSettings pageSettings, ExcelWorksheet[] worksheets) + { + HandleWorksheetCollection(pageSettings, worksheets); + } + + public PdfCatalog(PdfPageSettings pageSettings, List worksheets) + { + HandleWorksheetCollection(pageSettings, worksheets.ToArray()); + } + + private void HandleWorksheetCollection(PdfPageSettings pageSettings, ExcelWorksheet[] worksheets) + { + var pdfSheets = GetPdfWorksheets(pageSettings, worksheets); + foreach (var pdfSheet in pdfSheets) + { + ShapeTextInPdfWorksheet(pageSettings, pdfSheet); + PdfCalculateRowHeight.ResizeRowHeights(pdfSheet); + } + var Layout = GetLayout(pageSettings, pdfSheets); + //send Layout to pdf export here. + } + + // + // CONSTRUCTORS FOR SINGLE WORKSHEET AS INPUT + // + + public PdfCatalog(PdfPageSettings pageSettings, ExcelWorksheet worksheet, string fileName) + { + BuildPdf(pageSettings, worksheet, (excelPdf, layout) => + excelPdf.CreatePdf(pageSettings, _dictionaries, layout, fileName)); + } + + public PdfCatalog(PdfPageSettings pageSettings, ExcelWorksheet worksheet, Stream stream) + { + BuildPdf(pageSettings, worksheet, (excelPdf, layout) => + excelPdf.CreatePdf(pageSettings, _dictionaries, layout, stream)); + } + + private void BuildPdf(PdfPageSettings pageSettings, ExcelWorksheet worksheet, Action writePdf) + { + //pageSettings.defaultFontName = worksheet.Workbook.ThemeManager.CurrentTheme.FontScheme.MinorFont[0].Typeface; + pageSettings.defaultFontName = worksheet.Workbook.ThemeManager.GetOrCreateTheme().FontScheme.MinorFont[0].Typeface; + PdfWorksheet pdfSheet = null; + try + { + Stopwatch sw = Stopwatch.StartNew(); + + //Collect Text + pdfSheet = GetPdfWorksheet(pageSettings, worksheet); + sw.Stop(); + var CollectTextTime = sw.ElapsedMilliseconds; + sw.Reset(); + sw.Start(); + + //Shape Text + ShapeTextInPdfWorksheet(pageSettings, pdfSheet); + sw.Stop(); + var ShapeTextTime = sw.ElapsedMilliseconds; + sw.Reset(); + sw.Start(); + + //Auto-Fit Rows + PdfCalculateRowHeight.ResizeRowHeights(pdfSheet); + sw.Stop(); + var AutoFitRowTime = sw.ElapsedMilliseconds; + sw.Reset(); + sw.Start(); + + //Create Layout + var Layout = GetLayout(pageSettings, pdfSheet); + sw.Stop(); + var CreateLayoutTime = sw.ElapsedMilliseconds; + sw.Reset(); + sw.Start(); + + //Create Pdf //Done in pdf export project + ExcelPdf excelPdf = new ExcelPdf(); + writePdf(excelPdf, Layout); + sw.Stop(); + var CreatePdfTime = sw.ElapsedMilliseconds; + sw.Reset(); + } + finally + { + //Clean up the temporary worksheet used to build the comments/notes pages, + //so the source workbook isn't permanently mutated by the PDF export. + if (pdfSheet != null && pdfSheet.CommentsAndNotesSheet != null) + { + worksheet.Workbook.Worksheets.Delete(pdfSheet.CommentsAndNotesSheet); + pdfSheet.CommentsAndNotesSheet = null; + } + } + } + + // + // CONSTRUCTORS FOR RANGE AS INPUT + // + + public PdfCatalog(PdfPageSettings pageSettings, ExcelRangeBase range) + { + PdfWorksheet pdfSheet = GetPdfWorksheet(pageSettings, range); + ShapeTextInPdfWorksheet(pageSettings, pdfSheet); + PdfCalculateRowHeight.ResizeRowHeights(pdfSheet); + var Layout = GetLayout(pageSettings, pdfSheet); + //send Layout to pdf export here. + } + + internal PdfCellCollection GetCellCollectionFromRange(PdfPageSettings pageSettings, ExcelRangeBase range) + { + PdfWorksheet pdfSheet = GetPdfWorksheet(pageSettings, range); + ShapeTextInPdfWorksheet(pageSettings, pdfSheet); + return pdfSheet.Ranges[0].Map; + } + + + //Private Methods + + + //Create Layout Methods + + private Transform GetLayout(PdfPageSettings pageSettings, PdfWorksheet[] pdfSheets) + { + var Layout = PdfLayout.GetLayout(pageSettings, _dictionaries, pdfSheets); + return Layout; + } + + private Transform GetLayout(PdfPageSettings pageSettings, PdfWorksheet pdfSheet) + { + PdfWorksheet[] pdfSheets = new PdfWorksheet[1] { pdfSheet }; + var Layout = PdfLayout.GetLayout(pageSettings, _dictionaries, pdfSheets); + return Layout; + } + + //Shape Text Methods + + internal void ShapeTextInPdfWorksheet(PdfPageSettings pageSettings, PdfWorksheet pdfSheet) + { + // Pass 1: collect text per font + IterateCells(pdfSheet, cell => PdfTextShaper.CollectText(_dictionaries, cell)); + + // Pass 2: build one provider per font + foreach (var kvp in _dictionaries.Fonts) + { + _dictionaries.ShapedProviders[kvp.Key] = kvp.Value.fontSubsetManager.CreateSubsettedProvider(); + } + + // Pass 3: shape text using the pre-built providers + IterateCells(pdfSheet, cell => PdfTextShaper.ShapeText(pageSettings, _dictionaries, cell)); + } + + private void IterateCells(PdfWorksheet pdfSheet, System.Action action) + { + foreach (var range in pdfSheet.Ranges) + { + for (int i = range.Map.FromRow; i <= range.Map.ToRow; i++) + { + for (int j = range.Map.FromColumn; j <= range.Map.ToColumn; j++) + { + action(range.Map[i, j]); + } + } + } + + if (pdfSheet.CommentsAndNotes.Map != null) + { + for (int i = pdfSheet.CommentsAndNotes.Map.FromRow; i <= pdfSheet.CommentsAndNotes.Map.ToRow; i++) + { + for (int j = pdfSheet.CommentsAndNotes.Map.FromColumn; j <= pdfSheet.CommentsAndNotes.Map.ToColumn; j++) + { + action(pdfSheet.CommentsAndNotes.Map[i, j]); + } + } + } + } + + //Collect Text Methods + private PdfWorksheet[] GetPdfWorksheets(PdfPageSettings pageSettings, ExcelWorksheet[] worksheets) + { + PdfWorksheet[] pdfSheets = new PdfWorksheet[worksheets.Length]; + for (int i = 0; i < pdfSheets.Length; i++) + { + pdfSheets[i] = GetPdfWorksheet(pageSettings, worksheets[i]); + } + return pdfSheets; + } + + internal PdfWorksheet GetPdfWorksheet(PdfPageSettings pageSettings, ExcelWorksheet worksheet) + { + PdfWorksheet pdfSheet = new PdfWorksheet(); + pdfSheet.Ranges = new List(); + pdfSheet.Worksheet = worksheet; + pdfSheet.Ranges = GetRanges(pdfSheet.Worksheet); + if (pageSettings.ShowHeadings && _addTextForHeadings) _dictionaries.AddFont(pageSettings, pdfSheet.NormalStyle.Style.Font.Name, pdfSheet.GetSubFamilyFromNormalStyle, "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); + _addTextForHeadings = false; + GetMaps(pageSettings, pdfSheet, pdfSheet.Ranges); + GetPrintTitles(pageSettings, pdfSheet); + GetHeaderFooter(pageSettings, pdfSheet); + GetCommentsAndNotes(pageSettings, pdfSheet); + return pdfSheet; + } + + private PdfWorksheet GetPdfWorksheet(PdfPageSettings pageSettings, ExcelRangeBase excelRange) + { + PdfWorksheet pdfSheet = new PdfWorksheet(); + pdfSheet.Ranges = new List(); + pdfSheet.Worksheet = excelRange.Worksheet; + pdfSheet.Ranges.Add(new PdfRange(excelRange, false)); + if (pageSettings.ShowHeadings && _addTextForHeadings) _dictionaries.AddFont(pageSettings, pdfSheet.NormalStyle.Style.Font.Name, pdfSheet.GetSubFamilyFromNormalStyle, "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); + _addTextForHeadings = false; + pdfSheet.Ranges[0] = GetMaps(pageSettings, pdfSheet, pdfSheet.Ranges[0]); + GetPrintTitles(pageSettings, pdfSheet); + GetHeaderFooter(pageSettings, pdfSheet); + GetCommentsAndNotes(pageSettings, pdfSheet); + return pdfSheet; + } + + private List GetRanges(ExcelWorksheet worksheet) + { + List ranges = new List(); + if (worksheet.Names.ContainsKey("_xlnm.Print_Area")) + { + for (int i = 0; i < worksheet.Names["_xlnm.Print_Area"].Addresses.Count; i++) + { + var range = worksheet.Cells[worksheet.Names["_xlnm.Print_Area"].Addresses[i].Address]; + ranges.Add(new PdfRange(range, false)); + } + } + else + { + var range = worksheet.DimensionByValue; + var pdfRange = new PdfRange(range, true); + pdfRange.ExtendColumns = true; + ranges.Add(pdfRange); + } + return ranges; + } + + private void GetMaps(PdfPageSettings pageSettings, PdfWorksheet pdfSheet, List ranges) + { + for (int i = 0; i < ranges.Count; i++) + { + ranges[i] = GetMaps(pageSettings, pdfSheet, ranges[i]); + } + } + + private PdfRange GetMaps(PdfPageSettings pageSettings, PdfWorksheet pdfSheet, PdfRange range) + { + var temp = range; + temp.Map = PdfTextMap.SetTextMap(pageSettings, _dictionaries, pdfSheet, ref temp); + range = temp; + return range; + } + + private void GetPrintTitles(PdfPageSettings pageSettings, PdfWorksheet pdfSheet) + { + var worksheet = pdfSheet.Worksheet; + // --- Step 1: auto-detect from the worksheet's _xlnm.Print_Titles defined name --- + if (worksheet.Names.ContainsKey("_xlnm.Print_Titles")) + { + var printTitlesName = worksheet.Names["_xlnm.Print_Titles"]; + foreach (var address in printTitlesName.Addresses) + { + // A full-row reference spans every column (e.g. $1:$3 → _toCol == MaxColumns) + if (address._toCol >= ExcelPackage.MaxColumns) + { + pdfSheet.PrintTitleRowFrom = address._fromRow; + pdfSheet.PrintTitleRowTo = address._toRow; + } + // A full-column reference spans every row (e.g. $A:$B → _toRow == MaxRows) + else if (address._toRow >= ExcelPackage.MaxRows) + { + pdfSheet.PrintTitleColFrom = address._fromCol; + pdfSheet.PrintTitleColTo = address._toCol; + } + } + } + // --- Step 2: PdfPageSettings overrides take precedence over the defined name --- + if (pageSettings.RowsToRepeatAtTop != null) + { + pdfSheet.PrintTitleRowFrom = pageSettings.RowsToRepeatAtTop._fromRow; + pdfSheet.PrintTitleRowTo = pageSettings.RowsToRepeatAtTop._toRow; + } + if (pageSettings.ColumnsToRepeatAtLeft != null) + { + pdfSheet.PrintTitleColFrom = pageSettings.ColumnsToRepeatAtLeft._fromCol; + pdfSheet.PrintTitleColTo = pageSettings.ColumnsToRepeatAtLeft._toCol; + } + + // --- Step 3: mark cells so the renderer can identify them instantly --- + foreach (var range in pdfSheet.Ranges) + MarkPrintTitleCells(pdfSheet, range); + } + + private static void MarkPrintTitleCells(PdfWorksheet pdfSheet, PdfRange range) + { + var map = range.Map; + + for (int row = map.FromRow; row <= map.ToRow; row++) + { + bool isTitleRow = pdfSheet.PrintTitleRowFrom >= 0 + && row >= pdfSheet.PrintTitleRowFrom + && row <= pdfSheet.PrintTitleRowTo; + + for (int col = map.FromColumn; col <= map.ToColumn; col++) + { + bool isTitleCol = pdfSheet.PrintTitleColFrom >= 0 + && col >= pdfSheet.PrintTitleColFrom + && col <= pdfSheet.PrintTitleColTo; + + if (!isTitleRow && !isTitleCol) continue; + + var cell = map[row, col]; + if (cell == null) continue; + + if (isTitleRow) cell.IsPrintTitleRow = true; + if (isTitleCol) cell.IsPrintTitleCol = true; + } + } + } + + private void GetHeaderFooter(PdfPageSettings pageSettings, PdfWorksheet pdfSheet) + { + pdfSheet.HeaderFooters = new PdfHeaderFooterCollection(pageSettings, _dictionaries, pdfSheet, pdfSheet.Worksheet.HeaderFooter); + } + + private void GetCommentsAndNotes(PdfPageSettings pageSettings, PdfWorksheet pdfSheet) + { + if (pageSettings.CommentsAndNotes == CommentsAndNotes.AtEndOfSheet && pdfSheet.CommentsAndNotesCollections.Count > 0) + { + var cnPageSettings = new PdfPageSettings(); + cnPageSettings.CommentsAndNotes = CommentsAndNotes.None; + cnPageSettings.ShowHeadings = false; + pdfSheet.CommentsAndNotesSheet = PdfCommentsAndNotes.CreateCommentAndNotesPages(pdfSheet.CommentsAndNotesCollections, pdfSheet.Worksheet); + pdfSheet.CommentsAndNotes = new PdfRange(pdfSheet.CommentsAndNotesSheet.Dimension, false); + pdfSheet.CommentsAndNotes = GetMaps(cnPageSettings, pdfSheet, pdfSheet.CommentsAndNotes); + } + } + } +} \ No newline at end of file diff --git a/src/EPPlus/Export/PdfExport/PdfPageSettings.cs b/src/EPPlus/Export/PdfExport/PdfPageSettings.cs new file mode 100644 index 0000000000..6f4a063930 --- /dev/null +++ b/src/EPPlus/Export/PdfExport/PdfPageSettings.cs @@ -0,0 +1,290 @@ +/************************************************************************************************* + 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 OfficeOpenXml.Export.PdfExport.Data.PdfPageSizes; +using EPPlus.Fonts.OpenType; +using System.Collections.Generic; +using System.Linq; + +namespace OfficeOpenXml.Export.PdfExport +{ + /// + /// Settings object for exporting to PDF. + /// + public class PdfPageSettings + { + private OpenTypeFontEngine _fontEngine; + /// + /// Get the current font engine that is being used by EPPlus. + /// + public OpenTypeFontEngine FontEngine + { + get + { + if(_fontEngine == null) + { + _fontEngine = new OpenTypeFontEngine(x => + { + if(FontDirectories != null && FontDirectories.Any()) + { + foreach(var dir in FontDirectories) + { + if (!System.IO.Directory.Exists(dir)) + { + throw new System.IO.DirectoryNotFoundException($"Font directory not found: {dir}"); + } + x.FontDirectories.Add(dir); + } + x.SearchSystemDirectories = SearchSystemDirectories; + + } + + }); + } + return _fontEngine; + } + } + + /// + /// Add additional folders to search for fonts. + /// + public List FontDirectories = new List(); + + /// + /// If true, epplus will look for fonts in the system directories for installed fonts. c:\windows\fonts + /// + public bool SearchSystemDirectories = true; + + /// + /// If true, subsetted fonts will be embedded into the PDF document. + /// + public bool EmbeddFonts = true; + + /// + /// The order in how to create pages. + /// + public PageOrders PageOrders = PageOrders.DownThenOver; + + /// + /// Set to true to center content on page vertically. + /// + public bool CenterOnPageVertically; + + /// + /// Set to true to center content on page horizontally. + /// + public bool CenterOnPageHorizontally; + + /// + /// Set true to show grid lines. + /// + public bool ShowGridLines = false; + + /// + /// Set true to show row and column headings. + /// + public bool ShowHeadings = false; + + /// + /// Set the range to repeat at the top of the page. + /// + public ExcelAddressBase RowsToRepeatAtTop = null; + + /// + /// Set the range to repeat to the left of the page. + /// + public ExcelAddressBase ColumnsToRepeatAtLeft = null; + + /// + /// Set if comments and notes should be included. + /// + public CommentsAndNotes CommentsAndNotes = CommentsAndNotes.None; + + /// + /// Sets how to display errors in cells. + /// + public CellErrors CellErrors = CellErrors.Displayed; + + /// + /// Set the starting page number. + /// + public int FirstPageNumber = 1; + + PdfPageSize _pageSize = PdfPageSize.A4; + /// + /// Set the size of pages. + /// + public PdfPageSize PageSize + { + get + { + return _pageSize; + } + set + { + _pageSize = new PdfPageSize(value.Height, value.Width); + if (value.Height > value.Width) + { + _orientation = Orientations.Portrait; + } + else + { + _orientation = Orientations.Landscape; + } + ContentBounds.CalculateBounds(Margins, PageSize); + } + } + + private Orientations _orientation = Orientations.Portrait; + /// + /// Set the orientation of the pages. + /// + public Orientations Orientation + { + get + { + return _orientation; + } + set + { + if (value == Orientations.Portrait) + { + if (_pageSize.Height < _pageSize.Width) + { + _pageSize = new PdfPageSize(_pageSize.Height, _pageSize.Width); + _orientation = value; + } + } + if (value == Orientations.Landscape) + { + if (_pageSize.Height > _pageSize.Width) + { + _pageSize = new PdfPageSize(_pageSize.Height, _pageSize.Width); + _orientation = value; + } + } + } + } + + private PdfMargins _margins = PdfMargins.Normal; + /// + /// Set the page margins. + /// + public PdfMargins Margins + { + get + { + return _margins; + } + set + { + _margins = value; + ContentBounds.CalculateBounds(Margins, PageSize); + } + } + + private PdfScaling _scaling = PdfScaling.NoScaling; + /// + /// Set the scaling. NOT IMPLEMENTED + /// + public PdfScaling Scaling + { + get + { + return _scaling; + } + set + { + _scaling = value; + } + } + + internal PdfContentBounds ContentBounds = new PdfContentBounds(PdfMargins.Normal, PdfPageSize.A4); + internal string defaultFontName = ""; + + //DEBUG + internal bool Debug = false; + internal bool PrintAsText = false; + } + + /// + /// Orientation of pages. + /// + public enum Orientations + { + /// + /// Portrait orientation. + /// + Portrait, + /// + /// Landscape orientation. + /// + Landscape, + } + + /// + /// Order of pages. + /// + public enum PageOrders + { + /// + /// Order Down then over. + /// + DownThenOver, + /// + /// Order Over then down. + /// + OverThenDown, + } + + /// + /// How comments will be displayed. + /// + public enum CommentsAndNotes + { + /// + /// Comments and Notes will be ignored. + /// + None, + /// + /// Comments and Notes will be displayed on a seprate page at the end. + /// + AtEndOfSheet, + /// + /// Notes will be displayed on the sheet. (Comments will not be shown.) + /// + AsDisplayedOnSheet + } + + /// + /// How errors will be displayed. + /// + public enum CellErrors + { + /// + /// Errors will be displayed + /// + Displayed, + /// + /// Errors will be not displayed + /// + Blank, + /// + /// Errors will be displayed as "--" + /// + Dashed, + /// + /// Errors will be displayed as #N/A + /// + NA, + } +} \ No newline at end of file diff --git a/src/EPPlus/Export/PdfExport/RowResize/PdfCalculateRowHeight.cs b/src/EPPlus/Export/PdfExport/RowResize/PdfCalculateRowHeight.cs new file mode 100644 index 0000000000..09d3d82092 --- /dev/null +++ b/src/EPPlus/Export/PdfExport/RowResize/PdfCalculateRowHeight.cs @@ -0,0 +1,100 @@ +/************************************************************************************************* + 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 OfficeOpenXml.Export.PdfExport.Data; + +namespace OfficeOpenXml.Export.PdfExport.RowResize +{ + internal class PdfCalculateRowHeight + { + public static void ResizeRowHeights(PdfWorksheet pdfSheet) + { + for (int r = 0; r < pdfSheet.Ranges.Count; r++) + { + var range = pdfSheet.Ranges[r]; + ResizeRange(ref range); + pdfSheet.Ranges[r] = range; + } + + if (pdfSheet.CommentsAndNotes.Map != null) + { + var cnRange = pdfSheet.CommentsAndNotes; + ResizeRange(ref cnRange); + pdfSheet.CommentsAndNotes = cnRange; + } + } + + public static void ResizeRange(ref PdfRange range) + { + double newTotalHeight = 0d; + + for (int rowIdx = 0; rowIdx < range.RowHeights.Count; rowIdx++) + { + var rowHeight = range.RowHeights[rowIdx]; + + if (rowHeight.Height == 0d) + continue; + + if (!rowHeight.UsesDefaultValue) + { + newTotalHeight += rowHeight.Height; + continue; + } + + int row = range.Range._fromRow + rowIdx; + double maxRequired = rowHeight.Height; + bool hasWrappedCell = false; + + for (int colIdx = 0; colIdx < range.ColWidths.Count; colIdx++) + { + int col = range.Range._fromCol + colIdx; + var cell = range.Map[row, col]; + + if (cell == null || cell.Hidden) + continue; + if (cell.Merged) + continue; + if (cell.ContentAligmnet.ShrinkToFit) + continue; + if (!cell.ContentAligmnet.WrapText) + continue; + if (cell.TextLines == null || cell.TextLines.Count == 0) + continue; + + hasWrappedCell = true; + double required = GetRequiredHeightFromLines(cell); + if (required > maxRequired) + maxRequired = required; + } + + if (hasWrappedCell) + { + rowHeight.Height = maxRequired; + range.RowHeights[rowIdx] = rowHeight; + } + newTotalHeight += rowHeight.Height; + } + range.TotalHeight = newTotalHeight; + + } + + private static double GetRequiredHeightFromLines(PdfCell cell) + { + double total = 0d; + foreach (var line in cell.TextLines) + { + total += line.LargestAscent + line.LargestDescent; + } + return total; + } + } +} diff --git a/src/EPPlus/Export/PdfExport/TextMapping/PdfCommentsAndNotes.cs b/src/EPPlus/Export/PdfExport/TextMapping/PdfCommentsAndNotes.cs new file mode 100644 index 0000000000..92e949b5ed --- /dev/null +++ b/src/EPPlus/Export/PdfExport/TextMapping/PdfCommentsAndNotes.cs @@ -0,0 +1,115 @@ +/************************************************************************************************* + 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 OfficeOpenXml.Style; +using OfficeOpenXml.Style.XmlAccess; +using OfficeOpenXml.ThreadedComments; +using System.Collections.Generic; + +namespace OfficeOpenXml.Export.PdfExport +{ + internal class PdfCommentsAndNotes + { + public ExcelComment Comment; + public ExcelThreadedCommentThread ThreadedComment; + public static bool HasThreadedComment = false; + + public PdfCommentsAndNotes(ExcelComment comment) + { + Comment = comment; + } + public PdfCommentsAndNotes(ExcelThreadedCommentThread tComment) + { + ThreadedComment = tComment; + } + + public static ExcelWorksheet CreateCommentAndNotesPages(Dictionary CommentsAndNotesCollections, ExcelWorksheet ws) + { + var ns = ws.Workbook.Styles.GetNormalStyle(); + var tempWS = ws.Workbook.Worksheets.Add("TemporaryWorksheetForCommentsInPdfExporterForEPPlus"); + int row = 1; + int col = 1; + tempWS.Column(col).Width = 10d; + tempWS.Column(col + 1).Width = 75d; + foreach (var commentNote in CommentsAndNotesCollections) + { + AddText(tempWS, row, col, "Cell:", true, ExcelHorizontalAlignment.Right, ExcelVerticalAlignment.Bottom, ns); + AddText(tempWS, row, col + 1, commentNote.Key, false, ExcelHorizontalAlignment.Left, ExcelVerticalAlignment.Bottom, ns); + row++; + if (commentNote.Value.ThreadedComment != null) + { + var CommentReply = "Comment:"; + foreach (var comment in commentNote.Value.ThreadedComment.Comments) + { + AddText(tempWS, row, col, CommentReply, true, ExcelHorizontalAlignment.Right, ExcelVerticalAlignment.Bottom, ns); + AddText(tempWS, row, col + 1, comment.Author.DisplayName, false, ExcelHorizontalAlignment.Left, ExcelVerticalAlignment.Bottom, ns); + row++; + AddText(tempWS, row, col + 1, comment.Text, false, ExcelHorizontalAlignment.Left, ExcelVerticalAlignment.Top, ns); + row++; + AddText(tempWS, row, col + 1, comment.DateCreated.ToString("yyyy-MM-dd HH:mm"), false, ExcelHorizontalAlignment.Left, ExcelVerticalAlignment.Bottom, ns); + row++; + CommentReply = "Reply:"; + } + } + else if (commentNote.Value.Comment != null) + { + var note = commentNote.Value.Comment; + AddText(tempWS, row, col, "Note:", true, ExcelHorizontalAlignment.Right, ExcelVerticalAlignment.Bottom, ns); + AddText(tempWS, row, col + 1, note.Author, false, ExcelHorizontalAlignment.Left, ExcelVerticalAlignment.Bottom, ns); + row++; + AddText(tempWS, row, col + 1, note.RichText, false, ExcelHorizontalAlignment.Left, ExcelVerticalAlignment.Bottom, ns); + row++; + } + row++; + } + return tempWS; + } + private static void AddText(ExcelWorksheet ws, int row, int col, string text, bool bold, ExcelHorizontalAlignment horizontalAlignment, ExcelVerticalAlignment verticalAlignment, ExcelNamedStyleXml ns) + { + var cell = ws.Cells[row, col]; + cell.RichText.Clear(); + var rt = cell.RichText.Add(text); + rt.Bold = bold; + rt.FontName = ns.Style.Font.Name; + rt.Family = ns.Style.Font.Family; + rt.Size = ns.Style.Font.Size; + cell.Style.HorizontalAlignment = horizontalAlignment; + cell.Style.VerticalAlignment = verticalAlignment; + cell.Style.WrapText = true; + } + + private static void AddText(ExcelWorksheet ws, int row, int col, ExcelRichTextCollection RichText, bool bold, ExcelHorizontalAlignment horizontalAlignment, ExcelVerticalAlignment verticalAlignment, ExcelNamedStyleXml ns) + { + var cell = ws.Cells[row, col]; + cell.RichText.Clear(); + for (int i = 1; i < RichText.Count; i++) + { + var rt = RichText[i]; + var trimmedText = rt.Text.Trim(); + var r = cell.RichText.Add(trimmedText); + r.Bold = rt.Bold; + r.Italic = rt.Italic; + r.Color = rt.Color; + r.ColorSettings = rt.ColorSettings; + r.Strike = rt.Strike; + r.UnderLine = rt.UnderLine; + r.UnderLineType = rt.UnderLineType; + r.FontName = rt.FontName; + r.Family = rt.Family; + r.Size = rt.Size; + } + cell.Style.HorizontalAlignment = horizontalAlignment; + cell.Style.VerticalAlignment = verticalAlignment; + cell.Style.WrapText = true; + } + } +} diff --git a/src/EPPlus/Export/PdfExport/TextMapping/PdfHeaderFooter.cs b/src/EPPlus/Export/PdfExport/TextMapping/PdfHeaderFooter.cs new file mode 100644 index 0000000000..e7f38c51d6 --- /dev/null +++ b/src/EPPlus/Export/PdfExport/TextMapping/PdfHeaderFooter.cs @@ -0,0 +1,61 @@ +/************************************************************************************************* + 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.Fonts.OpenType.Integration; +using OfficeOpenXml.Export.PdfExport.Data; +using System.Collections.Generic; + +namespace OfficeOpenXml.Export.PdfExport +{ + internal enum HeaderFooterType + { + First = 0, + Odd = 1, + Even = 2 + } + + internal enum HeaderFooterAlignment + { + Left = 0, + Center = 1, + Right = 2 + } + + internal enum HeaderFooterSection + { + Header = 0, + Footer = 1 + } + + internal class PdfHeaderFooter + { + public HeaderFooterType PageType; + public HeaderFooterAlignment Alignment; + public HeaderFooterSection Section; + + public PdfCell Content; + + public List NumberOfPagesIndexes = new List(); + public List PageNumberIndexes = new List(); + + public PdfHeaderFooter(List textFormats, List pageNumberIndexes, List numberOfPagesIndexes, HeaderFooterType type, HeaderFooterAlignment alignment, HeaderFooterSection section) + { + Content = new PdfCell(); + Content.TextFragments = textFormats; + PageNumberIndexes = pageNumberIndexes; + NumberOfPagesIndexes = numberOfPagesIndexes; + PageType = type; + Alignment = alignment; + Section = section; + } + } +} diff --git a/src/EPPlus/Export/PdfExport/TextMapping/PdfHeaderFooterCollection.cs b/src/EPPlus/Export/PdfExport/TextMapping/PdfHeaderFooterCollection.cs new file mode 100644 index 0000000000..064694c49e --- /dev/null +++ b/src/EPPlus/Export/PdfExport/TextMapping/PdfHeaderFooterCollection.cs @@ -0,0 +1,163 @@ +/************************************************************************************************* + 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 OfficeOpenXml.Export.PdfExport.Data; +using OfficeOpenXml.Export.PdfExport.Data.Dictionaries; +using System.Collections.Generic; +using System.Linq; + +namespace OfficeOpenXml.Export.PdfExport.TextMapping +{ + internal class PdfHeaderFooterCollection + { + public List PdfHeaderFooterEntries = new List(); + public bool ScaleWithDocument = false; + public bool AlignWithMargins = false; + + public PdfHeaderFooterCollection(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfWorksheet pdfSheet, ExcelHeaderFooter headerFooter) + { + bool differentFirst = pdfSheet.Worksheet.HeaderFooter.differentFirst; + bool differentOddEven = pdfSheet.Worksheet.HeaderFooter.differentOddEven; + bool AlignWithMargins = pdfSheet.Worksheet.HeaderFooter.AlignWithMargins; + bool ScaleWithDocument = pdfSheet.Worksheet.HeaderFooter.ScaleWithDocument; + PdfHeaderFooter entry = null; + if (differentFirst) + { + //First Header + entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.FirstHeader.LeftAligned, HeaderFooterType.First, HeaderFooterAlignment.Left, HeaderFooterSection.Header); + if (entry != null) + { + entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); + PdfHeaderFooterEntries.Add(entry); + } + entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.FirstHeader.Centered, HeaderFooterType.First, HeaderFooterAlignment.Center, HeaderFooterSection.Header); + if (entry != null) + { + entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); + PdfHeaderFooterEntries.Add(entry); + } + entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.FirstHeader.RightAligned, HeaderFooterType.First, HeaderFooterAlignment.Right, HeaderFooterSection.Header); + if (entry != null) + { + entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); + PdfHeaderFooterEntries.Add(entry); + } + //First Footer + entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.FirstFooter.LeftAligned, HeaderFooterType.First, HeaderFooterAlignment.Left, HeaderFooterSection.Footer); + if (entry != null) + { + entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); + PdfHeaderFooterEntries.Add(entry); + } + entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.FirstFooter.Centered, HeaderFooterType.First, HeaderFooterAlignment.Center, HeaderFooterSection.Footer); + if (entry != null) + { + entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); + PdfHeaderFooterEntries.Add(entry); + } + entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.FirstFooter.RightAligned, HeaderFooterType.First, HeaderFooterAlignment.Right, HeaderFooterSection.Footer); + if (entry != null) + { + entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); + PdfHeaderFooterEntries.Add(entry); + } + } + if (differentOddEven) + { + //Even Header + entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.EvenHeader.LeftAligned, HeaderFooterType.Even, HeaderFooterAlignment.Left, HeaderFooterSection.Header); + if (entry != null) + { + entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); + PdfHeaderFooterEntries.Add(entry); + } + entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.EvenHeader.Centered, HeaderFooterType.Even, HeaderFooterAlignment.Center, HeaderFooterSection.Header); + if (entry != null) + { + entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); + PdfHeaderFooterEntries.Add(entry); + } + entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.EvenHeader.RightAligned, HeaderFooterType.Even, HeaderFooterAlignment.Right, HeaderFooterSection.Header); + if (entry != null) + { + entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); + PdfHeaderFooterEntries.Add(entry); + } + //Even Footer + entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.EvenFooter.LeftAligned, HeaderFooterType.Even, HeaderFooterAlignment.Left, HeaderFooterSection.Footer); + if (entry != null) + { + entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); + PdfHeaderFooterEntries.Add(entry); + } + entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.EvenFooter.Centered, HeaderFooterType.Even, HeaderFooterAlignment.Center, HeaderFooterSection.Footer); + if (entry != null) + { + entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); + PdfHeaderFooterEntries.Add(entry); + } + entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.EvenFooter.RightAligned, HeaderFooterType.Even, HeaderFooterAlignment.Right, HeaderFooterSection.Footer); + if (entry != null) + { + entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); + PdfHeaderFooterEntries.Add(entry); + } + } + //Odd Header + entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.OddHeader.LeftAligned, HeaderFooterType.Odd, HeaderFooterAlignment.Left, HeaderFooterSection.Header); + if (entry != null) + { + entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); + PdfHeaderFooterEntries.Add(entry); + } + entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.OddHeader.Centered, HeaderFooterType.Odd, HeaderFooterAlignment.Center, HeaderFooterSection.Header); + if (entry != null) + { + entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); + PdfHeaderFooterEntries.Add(entry); + } + entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.OddHeader.RightAligned, HeaderFooterType.Odd, HeaderFooterAlignment.Right, HeaderFooterSection.Header); + if (entry != null) + { + entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); + PdfHeaderFooterEntries.Add(entry); + } + //Odd Footer + entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.OddFooter.LeftAligned, HeaderFooterType.Odd, HeaderFooterAlignment.Left, HeaderFooterSection.Footer); + if (entry != null) + { + entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); + PdfHeaderFooterEntries.Add(entry); + } + entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.OddFooter.Centered, HeaderFooterType.Odd, HeaderFooterAlignment.Center, HeaderFooterSection.Footer); + if (entry != null) + { + entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); + PdfHeaderFooterEntries.Add(entry); + } + entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.OddFooter.RightAligned, HeaderFooterType.Odd, HeaderFooterAlignment.Right, HeaderFooterSection.Footer); + if (entry != null) + { + entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); + PdfHeaderFooterEntries.Add(entry); + } + } + + public PdfHeaderFooter Get(HeaderFooterType type, HeaderFooterSection section, HeaderFooterAlignment alignment) + { + return PdfHeaderFooterEntries.FirstOrDefault(e => + e.PageType == type && + e.Section == section && + e.Alignment == alignment); + } + } +} diff --git a/src/EPPlus/Export/PdfExport/TextMapping/PdfTextMap.cs b/src/EPPlus/Export/PdfExport/TextMapping/PdfTextMap.cs new file mode 100644 index 0000000000..9712189d07 --- /dev/null +++ b/src/EPPlus/Export/PdfExport/TextMapping/PdfTextMap.cs @@ -0,0 +1,719 @@ +/************************************************************************************************* + 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.Fonts.OpenType.Integration; +using EPPlus.Fonts.OpenType.Integration.DataHolders; +using EPPlus.Graphics.Units; +using OfficeOpenXml.Export.PdfExport.Data; +using OfficeOpenXml.Export.PdfExport.Data.Dictionaries; +using OfficeOpenXml.Export.PdfExport.Layout; +using OfficeOpenXml.Interfaces.Fonts; +using OfficeOpenXml.Style; +using OfficeOpenXml.Style.Dxf; +using OfficeOpenXml.Style.HeaderFooterTextFormat; +using OfficeOpenXml.Style.Table; +using OfficeOpenXml.Table; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace OfficeOpenXml.Export.PdfExport.TextMapping +{ + internal class PdfTextMap + { + public static PdfCellCollection SetTextMap(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfWorksheet pdfSheet, ref PdfRange pdfRange) + { + var Range = pdfRange; + var worksheet = Range.Range.Worksheet; + var ZeroCharWidth = pdfSheet.ZeroCharWidth = PdfWorksheet.GetThemeFont0Width(worksheet); + int addedColumns = Range.ExtendColumns ? AddColumnsForNonWrappedText(pageSettings, worksheet, pdfSheet) : 0; + var Map = new PdfCellCollection(Range.Range._fromRow, Range.Range._toRow, Range.Range._fromCol, Range.Range._toCol + addedColumns); + pdfSheet.ToRow = pdfSheet.ToRow < Range.Range._toRow ? Range.Range._toRow : pdfSheet.ToRow; + bool firstColumnRun = true; + List checkedMergedCells = new List(); + for (int row = Range.Range._fromRow; row <= Range.Range._toRow; row++) + { + var hiddenRow = worksheet.Row(row).Hidden; + var r = (RowInternal)worksheet.GetValueInner(row, 0); + bool usesDefaultValue = false; + double height = 0; + if (r == null || r.Height < 0) + { + usesDefaultValue = true; + height = worksheet.DefaultRowHeight; + } + else + { + height = UnitConversion.ExcelRowHeightToPoints(r.Height); + } + Range.TotalHeight += hiddenRow ? 0d : height; + Range.RowHeights.Add(new RowHeight { Height = hiddenRow ? 0d : height, UsesDefaultValue = usesDefaultValue }); + for (int col = Range.Range._fromCol; col <= Range.Range._toCol + addedColumns; col++) + { + var hiddenCol = worksheet.Column(col).Hidden; + var width = UnitConversion.ExcelColumnWidthToPoints(worksheet.Column(col).Width, ZeroCharWidth); + if (firstColumnRun) + { + Range.TotalWidth += hiddenCol ? 0d : width; + Range.ColWidths.Add(hiddenCol ? 0d : width); + } + var tempMap = new PdfCell(); + tempMap.Hidden = hiddenRow || hiddenCol; + tempMap.ColumnWidth = tempMap.Width = hiddenCol ? 0d : width; + var cell = worksheet.Cells[row, col]; + tempMap.Name = cell.Address; + if (cell.Merge) + { + HandleMergedCell(pageSettings, dictionaries, cell, checkedMergedCells, Map, tempMap, pdfSheet.ZeroCharWidth); + } + var cellStyle = new PdfCellStyle(); + GetBorderStyles(cell, cellStyle, tempMap); + if (tempMap.Main == null) + { + GetFillStyles(cell, cellStyle); + GetFontStyle(cell, cellStyle); + tempMap.ContentAligmnet = GetContentAlignment(cell); + if (!string.IsNullOrEmpty(cell.Text)) + { + tempMap.Text = cell.Text; + tempMap.TextFragments = GetTextFragments(pageSettings, dictionaries, cell, cellStyle); + } + } + tempMap.CellStyle = cellStyle; + Map[row, col] = tempMap; + if (pageSettings.CommentsAndNotes != CommentsAndNotes.None) + { + if (cell.Comment != null && cell.ThreadedComment == null) + { + pdfSheet.CommentsAndNotesCollections.Add(cell.Address, new PdfCommentsAndNotes(cell.Comment)); + } + if (cell.ThreadedComment != null) + { + pdfSheet.CommentsAndNotesCollections.Add(cell.Address, new PdfCommentsAndNotes(cell.ThreadedComment)); + PdfCommentsAndNotes.HasThreadedComment = true; + } + } + } + firstColumnRun = false; + } + worksheet.ConditionalFormatting.ClearTempExportCacheForAllCFs(); + pdfRange = Range; + return Map; + } + + private static void HandleMergedCell(PdfPageSettings pageSettings, PdfDictionaries dictionaries, ExcelRange cell, List checkedMergedCells, PdfCellCollection map, PdfCell tempMap, double ZeroCharWidth) + { + var worksheet = cell.Worksheet; + string mergeAddress = worksheet.MergedCells[cell.Start.Row, cell.Start.Column]; + ExcelAddressBase address = new ExcelAddressBase(mergeAddress); + if (!checkedMergedCells.Contains(mergeAddress)) + { + double totalWidth = 0, totalHeight = 0; + for (int k = address._fromRow; k <= address._toRow; k++) + { + totalHeight += UnitConversion.ExcelRowHeightToPoints(worksheet.Row(k).Height); + } + for (int l = address._fromCol; l <= address._toCol; l++) + { + totalWidth += UnitConversion.ExcelColumnWidthToPoints(worksheet.Column(l).Width, ZeroCharWidth); + } + checkedMergedCells.Add(mergeAddress); + tempMap.Width = totalWidth; + tempMap.Height = totalHeight; + tempMap.Main = null; + } + else + { + tempMap.Main = map[address._fromRow, address._fromCol]; + if (tempMap.Main == null) + { + var main = worksheet.Cells[address._fromRow, address._fromCol]; + PdfCell mainCell = new PdfCell(); + var cellStyle = new PdfCellStyle(); + GetBorderStyles(main, cellStyle, mainCell); + GetFillStyles(main, cellStyle); + GetFontStyle(main, cellStyle); + + mainCell.ContentAligmnet = GetContentAlignment(main); + if (!string.IsNullOrEmpty(main.Text)) + { + mainCell.Text = main.Text; + mainCell.TextFragments = GetTextFragments(pageSettings, dictionaries, main, cellStyle); + } + mainCell.CellStyle = cellStyle; + tempMap.Main = mainCell; + } + } + tempMap.MergedAddress = address; + tempMap.Name = tempMap.Name + " ; " + address.ToString(); + tempMap.Merged = true; + } + + private static void GetFillStyles(ExcelRangeBase cell, PdfCellStyle cellStyle) + { + if (cell.Style.Fill.IsEmpty()) + { + //Conditional Formating + var cf = cell.ConditionalFormatting.GetConditionalFormattings(); + if (cf != null && cf.Count > 0) + { + // Sort ascending — priority 1 beats priority 2, etc. + var ordered = cf.OrderBy(r => r.Priority); + + foreach (var rule in ordered) + { + // Use the core per-rule evaluator (the same one the HTML exporter + // calls). It correctly handles every rule type — comparisons, text, + // blanks/errors, top/bottom, above/below average, duplicate/unique, + // time periods and formula expressions — including the range-wide + // aggregates, which the previous hand-rolled evaluator stubbed out. + if (!rule.ShouldApplyToCell(cell)) + { + if (rule.StopIfTrue) break; // higher-priority rule fired but had no fill — stop anyway + continue; + } + + if (rule.Style?.Fill != null && rule.Style.Fill.HasValue) + { + cellStyle.dxfFill = rule.Style.Fill; + // xfFill must be non-null for the downstream dxf path + // (PdfCellLayout checks xfFill.IsEmpty()); the cell's own fill + // is empty here, which is exactly what selects the dxf fill. + cellStyle.xfFill = cell.Style.Fill; + return; // CF fill wins — skip table and xf entirely + } + + if (rule.StopIfTrue) break; + } + } + + //Table + var tables = cell.Worksheet.Tables.GetIntersectingRanges(cell); + if (tables.Count > 0) + { + var table = tables[0].Value; + var range = table.Range; + + int tableRow = 0; + int tableCol = 0; + ExcelTableNamedStyle tableStyle; + if (table.TableStyle == TableStyles.Custom) + { + tableStyle = cell.Worksheet.Workbook.Styles.TableStyles[table.StyleName].As.TableStyle; + } + else + { + var tmpNode = table.WorkSheet.Workbook.StylesXml.CreateElement("c:tableStyle"); + tableStyle = new ExcelTableNamedStyle(cell.Worksheet.Workbook.Styles.NameSpaceManager, tmpNode, cell.Worksheet.Workbook.Styles); + tableStyle.SetFromTemplate((TableStyles)table.TableStyle); + } + tableRow = cell._fromRow - range._fromRow; + tableCol = cell._fromCol - range._fromCol; + if (table.ShowHeader && tableRow == 0) + { + cellStyle.dxfFill = tableStyle.HeaderRow.Style.Fill; + } + else if (table.ShowTotal && range._toRow == cell._fromRow) + { + cellStyle.dxfFill = tableStyle.TotalRow.Style.Fill; + } + else if (table.ShowFirstColumn && tableCol == 0) + { + cellStyle.dxfFill = tableStyle.FirstColumn.Style.Fill; + } + else if (table.ShowLastColumn && range._toCol == cell._fromCol) + { + cellStyle.dxfFill = tableStyle.LastColumn.Style.Fill; + } + else if (table.ShowRowStripes) + { + cellStyle.dxfFill = (tableRow & 1) == 0 ? tableStyle.SecondRowStripe.Style.Fill : tableStyle.FirstRowStripe.Style.Fill; + } + else if (table.ShowColumnStripes) + { + cellStyle.dxfFill = (tableCol & 1) != 0 ? tableStyle.SecondColumnStripe.Style.Fill : tableStyle.FirstColumnStripe.Style.Fill; + } + else + { + cellStyle.dxfFill = tableStyle.WholeTable.Style.Fill; + } + } + } + cellStyle.xfFill = cell.Style.Fill; + } + + private static void GetBorderStyles(ExcelRangeBase cell, PdfCellStyle cellStyle, PdfCell pcell) + { + + /* Kika på varje del av border top bottom left right + * om cell har top använd den + * om cell inte har border, gå igenom prio ordning på tabell borders och använd WholeTable om null. + * om cellein i tabellen är i mitten av tabellen använd horizontal och vertical border istället. + * I fallet top så ska om vi är i header row eller om cell fromrow är samma som table fromrow så ska top border vara top border. annar är det horizontal som gäller + * Glöm ej vertical border om fromcol är samma som tabell fromcol. + */ + if (cell != null) + { + cellStyle.xfTop = cell.Style.Border.Top; + cellStyle.xfBottom = cell.Style.Border.Bottom; + cellStyle.xfLeft = cell.Style.Border.Left; + cellStyle.xfRight = cell.Style.Border.Right; + if (pcell.Main == null) + { + cellStyle.Diagonal = cell.Style.Border.Diagonal; + cellStyle.DiagonalUp = cell.Style.Border.DiagonalUp; + cellStyle.DiagonalDown = cell.Style.Border.DiagonalDown; + } + else + { + cellStyle.Diagonal = cell.Style.Border.Diagonal; + cellStyle.DiagonalUp = false; + cellStyle.DiagonalDown = false; + } + var tables = cell.Worksheet.Tables.GetIntersectingRanges(cell); + if (tables.Count > 0) + { + var table = tables[0].Value; + ExcelTableNamedStyle tableStyle; + if (table.TableStyle == TableStyles.Custom) + { + tableStyle = cell.Worksheet.Workbook.Styles.TableStyles[table.StyleName].As.TableStyle; + } + else + { + var tmpNode = table.WorkSheet.Workbook.StylesXml.CreateElement("c:tableStyle"); + tableStyle = new ExcelTableNamedStyle(cell.Worksheet.Workbook.Styles.NameSpaceManager, tmpNode, cell.Worksheet.Workbook.Styles); + tableStyle.SetFromTemplate((TableStyles)table.TableStyle); + } + cellStyle.dxfTop = PdfCellBorderLayout.GetTopBorderItem(cell, cellStyle.xfTop, table, tableStyle); + cellStyle.dxfBottom = PdfCellBorderLayout.GetBottomBorderItem(cell, cellStyle.xfBottom, table, tableStyle); + cellStyle.dxfLeft = PdfCellBorderLayout.GetLeftBorderItem(cell, cellStyle.xfLeft, table, tableStyle); + cellStyle.dxfRight = PdfCellBorderLayout.GetRightBorderItem(cell, cellStyle.xfRight, table, tableStyle); + } + var cfBorder = GetConditionalFormattingBorder(cell); + if (cfBorder != null) + { + if (cfBorder.Top != null && cfBorder.Top.HasValue) cellStyle.dxfTop = cfBorder.Top; + if (cfBorder.Bottom != null && cfBorder.Bottom.HasValue) cellStyle.dxfBottom = cfBorder.Bottom; + if (cfBorder.Left != null && cfBorder.Left.HasValue) cellStyle.dxfLeft = cfBorder.Left; + if (cfBorder.Right != null && cfBorder.Right.HasValue) cellStyle.dxfRight = cfBorder.Right; + } + } + } + + private static ExcelDxfBorderBase GetConditionalFormattingBorder(ExcelRangeBase cell) + { + var cf = cell.ConditionalFormatting.GetConditionalFormattings(); + if (cf != null && cf.Count > 0) + { + var ordered = cf.OrderBy(r => r.Priority); + foreach (var rule in ordered) + { + if (!rule.ShouldApplyToCell(cell)) + { + if (rule.StopIfTrue) break; + continue; + } + if (rule.Style?.Border != null && rule.Style.Border.HasValue) + { + return rule.Style.Border; + } + if (rule.StopIfTrue) break; + } + } + return null; + } + + private static PdfCellStyle GetFontStyle(ExcelRangeBase cell, PdfCellStyle cellStyle) + { + var cf = cell.ConditionalFormatting.GetConditionalFormattings(); + if (cf != null && cf.Count > 0) + { + // Sort ascending — priority 1 beats priority 2, etc. + var ordered = cf.OrderBy(r => r.Priority); + foreach (var rule in ordered) + { + if (!rule.ShouldApplyToCell(cell)) + { + if (rule.StopIfTrue) break; + continue; + } + if (rule.Style?.Font != null && rule.Style.Font.HasValue) + { + cellStyle.dxfFont = rule.Style.Font; + return cellStyle; // CF font wins over the table font + } + if (rule.StopIfTrue) break; + } + } + var tables = cell.Worksheet.Tables.GetIntersectingRanges(cell); + if (tables.Count > 0) + { + var table = tables[0].Value; + var range = table.Range; + ExcelTableNamedStyle tableStyle; + if (table.TableStyle == TableStyles.Custom) + { + tableStyle = cell.Worksheet.Workbook.Styles.TableStyles[table.StyleName].As.TableStyle; + } + else + { + var tmpNode = table.WorkSheet.Workbook.StylesXml.CreateElement("c:tableStyle"); + tableStyle = new ExcelTableNamedStyle(cell.Worksheet.Workbook.Styles.NameSpaceManager, tmpNode, cell.Worksheet.Workbook.Styles); + tableStyle.SetFromTemplate((TableStyles)table.TableStyle); + } + int tableRow = cell._fromRow - range._fromRow; + int tableCol = cell._fromCol - range._fromCol; + var font = tableStyle.WholeTable.Style.Font; + if (table.ShowHeader && tableRow == 0) + { + if (tableStyle.HeaderRow.Style.Font.HasValue) + { + font = tableStyle.HeaderRow.Style.Font; + } + if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstHeaderCell.Style.Font.HasValue) + { + font = tableStyle.FirstHeaderCell.Style.Font; + } + if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastHeaderCell.Style.Font.HasValue) + { + font = tableStyle.LastHeaderCell.Style.Font; + } + } + else if (table.ShowTotal && cell._fromRow == range._toRow) + { + if (tableStyle.TotalRow.Style.Font.HasValue) + { + font = tableStyle.TotalRow.Style.Font; + } + if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstTotalCell.Style.Font.HasValue) + { + font = tableStyle.FirstTotalCell.Style.Font; + } + if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastTotalCell.Style.Font.HasValue) + { + font = tableStyle.LastTotalCell.Style.Font; + } + } + else + { + if (table.ShowColumnStripes && (tableCol & 1) == 0) + { + font = tableStyle.FirstColumnStripe.Style.Font; + } + if (table.ShowColumnStripes && tableStyle.SecondColumnStripe.Style.Border.Top.HasValue && (tableCol & 1) != 0) + { + font = tableStyle.SecondColumnStripe.Style.Font; + } + if (table.ShowRowStripes && tableStyle.FirstRowStripe.Style.Font.HasValue && (tableRow & 1) != 0) + { + font = tableStyle.FirstRowStripe.Style.Font; + } + if (table.ShowRowStripes && tableStyle.SecondRowStripe.Style.Font.HasValue && (tableRow & 1) == 0) + { + font = tableStyle.SecondRowStripe.Style.Font; + } + if (table.ShowLastColumn && tableStyle.LastColumn.Style.Font.HasValue && cell._fromCol == range._toCol) + { + font = tableStyle.LastColumn.Style.Font; + } + if (table.ShowFirstColumn && tableStyle.FirstColumn.Style.Font.HasValue && tableCol == range._toCol) + { + font = tableStyle.FirstColumn.Style.Font; + } + } + cellStyle.dxfFont = font; + } + return cellStyle; + } + + private static PdfCellAlignmentData GetContentAlignment(ExcelRangeBase cell) + { + var contentAlignment = new PdfCellAlignmentData(); + contentAlignment.HorizontalAlignment = cell.Style.HorizontalAlignment; + contentAlignment.VerticalAlignment = cell.Style.VerticalAlignment; + contentAlignment.Indent = cell.Style.Indent; + contentAlignment.WrapText = cell.Style.WrapText; + contentAlignment.ShrinkToFit = cell.Style.ShrinkToFit; + contentAlignment.TextRotation = (cell.Style.TextRotation > 90) ? ((cell.Style.TextRotation == 255) ? 0 : 90 - cell.Style.TextRotation) : cell.Style.TextRotation; + contentAlignment.IsVertical = cell.Style.TextRotation == 255 ? true : false; + contentAlignment.TextDirection = cell.Style.ReadingOrder; + return contentAlignment; + } + + internal static long subFamilyTicks; + internal static long addFontTicks; + internal static int subFamilyCount; + + private static List GetTextFragments(PdfPageSettings pageSettings, PdfDictionaries dictionaries, ExcelRangeBase cell, PdfCellStyle cellStyle) + { + bool dxfBold, dxfItalic, dxfStrike, dxfUnderline; + ExcelUnderLineType dxfUnderLineType; + System.Drawing.Color? dxfColor; + ReadDxfFontOverrides(cellStyle, out dxfBold, out dxfItalic, out dxfStrike, out dxfUnderline, out dxfUnderLineType, out dxfColor); + + string forcedText = ResolveErrorText(pageSettings, cell); + + if (cell.IsRichText) + { + return GetTextFragmentsFromRichText(pageSettings, dictionaries, cell.RichText, forcedText, + dxfBold, dxfItalic, dxfStrike, dxfUnderline, dxfUnderLineType, dxfColor); + } + return GetTextFragmentsFromCellStyle(pageSettings, dictionaries, cell, forcedText, + dxfBold, dxfItalic, dxfStrike, dxfUnderline, dxfUnderLineType, dxfColor); + } + + private static List GetTextFragmentsFromRichText(PdfPageSettings pageSettings, PdfDictionaries dictionaries, ExcelRichTextCollection richText, string forcedText, + bool dxfBold, bool dxfItalic, bool dxfStrike, bool dxfUnderline, ExcelUnderLineType dxfUnderLineType, System.Drawing.Color? dxfColor) + { + var textFragments = new List(richText.Count); + for (int i = 0; i < richText.Count; i++) + { + var rt = richText[i]; + var textFrag = new TextFragment(); + textFrag.Font = new RichTextFormatSimple(); + textFrag.Text = forcedText == null ? rt.Text : forcedText; + + textFrag.Font.Family = rt.FontName; + textFrag.Font.Size = rt.Size; + + textFrag.RichTextOptions.Bold = rt.Bold || dxfBold; + textFrag.RichTextOptions.Italic = rt.Italic || dxfItalic; + textFrag.RichTextOptions.UnderlineType = MapUnderlineType(rt.UnderLineType); + textFrag.RichTextOptions.StrikeType = (rt.Strike || dxfStrike) ? 2 : 1; + textFrag.RichTextOptions.SuperScript = rt.VerticalAlign == ExcelVerticalAlignmentFont.Superscript; + textFrag.RichTextOptions.SubScript = rt.VerticalAlign == ExcelVerticalAlignmentFont.Subscript; + textFrag.RichTextOptions.FontColor = dxfColor ?? rt.Color; + + textFrag.Font.SubFamily = ComputeFontStyle(textFrag); + + textFragments.Add(textFrag); + dictionaries.AddFont(pageSettings, textFrag.Font.Family, textFrag.Font.SubFamily, textFrag.Text); + } + return textFragments; + } + + private static List GetTextFragmentsFromCellStyle(PdfPageSettings pageSettings, PdfDictionaries dictionaries, ExcelRangeBase cell, string forcedText, + bool dxfBold, bool dxfItalic, bool dxfStrike, bool dxfUnderline, ExcelUnderLineType dxfUnderLineType, System.Drawing.Color? dxfColor) + { + var font = cell.Style.Font; + var textFragments = new List(1); + + var textFrag = new TextFragment(); + textFrag.Font = new RichTextFormatSimple(); + textFrag.Text = forcedText == null ? cell.Text : forcedText; + + textFrag.Font.Family = font.Name; + textFrag.Font.Size = font.Size; + + textFrag.RichTextOptions.Bold = font.Bold || dxfBold; + textFrag.RichTextOptions.Italic = font.Italic || dxfItalic; + + // Cell-style underline; dxf overrides only when the cell itself is not underlined. + ExcelUnderLineType underLineType; + if (font.UnderLine) + { + underLineType = font.UnderLineType == ExcelUnderLineType.None ? ExcelUnderLineType.Single : font.UnderLineType; + } + else if (dxfUnderline) + { + underLineType = dxfUnderLineType; + } + else + { + underLineType = ExcelUnderLineType.None; + } + textFrag.RichTextOptions.UnderlineType = MapUnderlineType(underLineType); + + textFrag.RichTextOptions.StrikeType = (font.Strike || dxfStrike) ? 2 : 1; + textFrag.RichTextOptions.SuperScript = font.VerticalAlign == ExcelVerticalAlignmentFont.Superscript; + textFrag.RichTextOptions.SubScript = font.VerticalAlign == ExcelVerticalAlignmentFont.Subscript; + textFrag.RichTextOptions.FontColor = dxfColor ?? font.Color.ToColor(); + + textFrag.Font.SubFamily = ComputeFontStyle(textFrag); + + textFragments.Add(textFrag); + dictionaries.AddFont(pageSettings, textFrag.Font.Family, textFrag.Font.SubFamily, textFrag.Text); + return textFragments; + } + + private static void ReadDxfFontOverrides(PdfCellStyle cellStyle, out bool bold, out bool italic, out bool strike, out bool underline, out ExcelUnderLineType underLineType, out System.Drawing.Color? color) + { + bold = false; + italic = false; + strike = false; + underline = false; + underLineType = ExcelUnderLineType.None; + color = null; + if (cellStyle != null && cellStyle.dxfFont != null) + { + bold = cellStyle.dxfFont.Bold != null ? (bool)cellStyle.dxfFont.Bold : false; + italic = cellStyle.dxfFont.Italic != null ? (bool)cellStyle.dxfFont.Italic : false; + strike = cellStyle.dxfFont.Strike != null ? (bool)cellStyle.dxfFont.Strike : false; + underline = cellStyle.dxfFont.Underline != null; + underLineType = cellStyle.dxfFont.Underline != null ? (ExcelUnderLineType)cellStyle.dxfFont.Underline : ExcelUnderLineType.None; + if (cellStyle.dxfFont.Color != null && cellStyle.dxfFont.Color.HasValue) + { + color = cellStyle.dxfFont.Color.GetColorAsColor(); + } + } + } + + private static string ResolveErrorText(PdfPageSettings pageSettings, ExcelRangeBase cell) + { + if (!ExcelErrorValue.IsErrorValue(cell.Text)) return null; + switch (pageSettings.CellErrors) + { + case CellErrors.Blank: return ""; + case CellErrors.Dashed: return "--"; + case CellErrors.NA: return "#N/A"; + case CellErrors.Displayed: + default: return null; + } + } + + private static int MapUnderlineType(ExcelUnderLineType type) + { + // 12 = none, 13 = single, 4 = double (matches existing rendering code; accounting not supported). + if (type == ExcelUnderLineType.Single) return 13; + if (type == ExcelUnderLineType.Double) return 4; + return 12; + } + + private static FontSubFamily ComputeFontStyle(TextFragment textFrag) + { + if (textFrag.RichTextOptions.Bold && textFrag.RichTextOptions.Italic) return FontSubFamily.BoldItalic; + if(textFrag.RichTextOptions.Bold) return FontSubFamily.Bold; + if (textFrag.RichTextOptions.Italic) return FontSubFamily.Italic; + return FontSubFamily.Regular; + } + + public static PdfCellAlignmentData GetAlignmentData(PdfHeaderFooter headerFooter) + { + var contentAlignment = new PdfCellAlignmentData(); + switch (headerFooter.Alignment) + { + case HeaderFooterAlignment.Left: + contentAlignment.HorizontalAlignment = ExcelHorizontalAlignment.Left; + break; + case HeaderFooterAlignment.Center: + contentAlignment.HorizontalAlignment = ExcelHorizontalAlignment.Center; + break; + case HeaderFooterAlignment.Right: + contentAlignment.HorizontalAlignment = ExcelHorizontalAlignment.Right; + break; + } + return contentAlignment; + } + + public static PdfHeaderFooter GetTextFormats(PdfPageSettings pageSettings, PdfDictionaries dictionaries, ExcelWorksheet ws, ExcelHeaderFooterTextCollection textCollection, HeaderFooterType type, HeaderFooterAlignment alignment, HeaderFooterSection section) + { + if (textCollection == null || textCollection.Count <= 1) return null; + var ns = ws.Workbook.Styles.GetNormalStyle(); + var textFragments = new List(); + List NumberOfPagesIndexes = new List(); + List PageNumberIndexes = new List(); + for (int i = 1; i < textCollection.Count; i++) + { + var hf = textCollection[i]; + var textFrag = new TextFragment(); + textFrag.Font = new RichTextFormatSimple(); + + textFrag.Font.Family = string.IsNullOrEmpty(hf.FontName) ? ns.Style.Font.Name : hf.FontName; + textFrag.Font.Size = hf.FontSize == null ? ns.Style.Font.Size : (float)hf.FontSize; + + textFrag.RichTextOptions.Bold = hf.Bold; + textFrag.RichTextOptions.Italic = hf.Italic; + //underline + //none : 12 + //single : 13 + //Double : 4 + //accouting does not exsist + textFrag.RichTextOptions.UnderlineType = 12; + textFrag.RichTextOptions.UnderlineType = hf.Underline ? 13 : textFrag.RichTextOptions.UnderlineType; + textFrag.RichTextOptions.UnderlineType = hf.DoubleUnderline ? 4 : textFrag.RichTextOptions.UnderlineType; + textFrag.RichTextOptions.StrikeType = hf.Striketrough ? 2 : 1; + textFrag.RichTextOptions.FontColor = hf.Color; + + textFrag.Font.SubFamily = ComputeFontStyle(textFrag); + //(textFrag.RichTextOptions.Bold ? MeasurementFontStyles.Bold : 0) | + //(textFrag.RichTextOptions.Italic ? MeasurementFontStyles.Italic : 0) | + //(textFrag.RichTextOptions.UnderlineType != 12 ? MeasurementFontStyles.Underline : 0) | + //(textFrag.RichTextOptions.StrikeType > 1 ? MeasurementFontStyles.Strikeout : 0); + + var text = string.Empty; + switch (hf.FormatCode) + { + case ExcelHeaderFooterFormattingCodes.SheetName: + text += ws.Name; + break; + case ExcelHeaderFooterFormattingCodes.CurrentDate: + text += DateTime.Now.ToString($"yyyy-MM-dd"); + break; + case ExcelHeaderFooterFormattingCodes.FileName: + text += ws._package.File.Name; + break; + case ExcelHeaderFooterFormattingCodes.NumberOfPages: + text += "000"; + NumberOfPagesIndexes.Add(i-1); + break; + case ExcelHeaderFooterFormattingCodes.PageNumber: + text += "000"; + PageNumberIndexes.Add(i-1); + break; + case ExcelHeaderFooterFormattingCodes.CurrentTime: + text += DateTime.Now.ToString("HH:mm"); + break; + case ExcelHeaderFooterFormattingCodes.FilePath: + text += ws._package.File.Directory.FullName + "\\"; + break; + default: + text += hf.Text; + break; + } + + textFrag.Text = text; + + textFragments.Add(textFrag); + dictionaries.AddFont(pageSettings, textFrag.Font.Family, textFrag.Font.SubFamily, textFrag.Text); + if (NumberOfPagesIndexes.Count > 0 || PageNumberIndexes.Count > 0) dictionaries.AddFont(pageSettings, textFrag.Font.Family, textFrag.Font.SubFamily, "1234567890"); + } + return new PdfHeaderFooter(textFragments, PageNumberIndexes, NumberOfPagesIndexes, type, alignment, section); + } + + /// + /// Check if we need to add additional columns to accomodate text that is not wrapped and overlaps other cell.s + /// + /// The worksheet to check. + /// The number of columns to add. + public static int AddColumnsForNonWrappedText(PdfPageSettings pageSettings, ExcelWorksheet ws, PdfWorksheet pdfSheet) + { + int columnsToAdd = 0; + var catalog = new PdfCatalog(); + var lastColumn = ws.Dimension.End.Column; + ExcelRangeBase lastColumnRange = ws.Cells[1, lastColumn, ws.Dimension.End.Row, lastColumn]; + var cc = catalog.GetCellCollectionFromRange(pageSettings, lastColumnRange); + double textLength = 0; + for (int i = cc.FromRow; i < cc.ToRow; i++) + { + textLength = cc[i, cc.FromColumn].TotalTextLength > textLength ? cc[i, cc.FromColumn].TotalTextLength : textLength; + } + double columnWidth = UnitConversion.ExcelColumnWidthToPoints(ws.Column(ws.Dimension._toCol).Width, pdfSheet.ZeroCharWidth); + while (textLength > columnWidth) + { + columnsToAdd++; + columnWidth += UnitConversion.ExcelColumnWidthToPoints(ws.Column(ws.Dimension._toCol + columnsToAdd).Width, pdfSheet.ZeroCharWidth); + } + return columnsToAdd; + } + } +} diff --git a/src/EPPlus/Export/PdfExport/TextShaping/PdfShapedText.cs b/src/EPPlus/Export/PdfExport/TextShaping/PdfShapedText.cs new file mode 100644 index 0000000000..80fed3f096 --- /dev/null +++ b/src/EPPlus/Export/PdfExport/TextShaping/PdfShapedText.cs @@ -0,0 +1,28 @@ +/************************************************************************************************* + 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.Fonts.OpenType; +using OfficeOpenXml.Interfaces.Fonts; +using System.Collections.Generic; + +namespace OfficeOpenXml.Export.PdfExport.TextShaping +{ + internal struct PdfShapedText + { + public IFontProvider FontProvider; + //sometimes a font can have other fonts for certain characters. Key as the glyph id, Value is the font label. + //public Dictionary FontIDLabel; + public Dictionary FontIdMap; + public List UsedFonts; + public ShapedText ShapedText; + } +} diff --git a/src/EPPlus/Export/PdfExport/TextShaping/PdfTextShaper.cs b/src/EPPlus/Export/PdfExport/TextShaping/PdfTextShaper.cs new file mode 100644 index 0000000000..bb8016efd7 --- /dev/null +++ b/src/EPPlus/Export/PdfExport/TextShaping/PdfTextShaper.cs @@ -0,0 +1,110 @@ +using EPPlus.Fonts.OpenType; +using EPPlus.Fonts.OpenType.Integration; +using EPPlus.Fonts.OpenType.TextShaping; +using OfficeOpenXml.Export.PdfExport.Data; +using OfficeOpenXml.Export.PdfExport.Data.Dictionaries; +using OfficeOpenXml.Interfaces.Fonts; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace OfficeOpenXml.Export.PdfExport.TextShaping +{ + internal static class PdfTextShaper + { + private static Dictionary shaperCache = new Dictionary(); + private static Dictionary layoutEngineCache = new Dictionary(); + + // Pass 1: collect text per font so FontSubsetManager can build subsets once + public static void CollectText(PdfDictionaries dictionaries, PdfCell cell) + { + if (cell == null || cell.TextFragments == null) return; + + for (int i = 0; i < cell.TextFragments.Count; i++) + { + var tf = cell.TextFragments[i]; + if (!dictionaries.Fonts.ContainsKey(tf.FullFontName)) continue; + dictionaries.Fonts[tf.FullFontName].fontSubsetManager.AddText(tf.Text); + } + } + + // Pass 2: shape text using already-built providers from PdfDictionaries.ShapedProviders + public static void ShapeText(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfCell cell) + { + var totalTextLength = 0d; + var maxLineHeight = 0d; + if (cell == null || cell.TextFragments == null) return; + + cell.ShapedTexts = new List(); + + for (int i = 0; i < cell.TextFragments.Count; i++) + { + var tf = cell.TextFragments[i]; + cell.ShapedTexts.Add(new PdfShapedText()); + var st = cell.ShapedTexts[i]; + + if (!dictionaries.ShapedProviders.TryGetValue(tf.FullFontName, out var provider)) + { + continue; + } + st.FontProvider = provider; + + if (!shaperCache.TryGetValue(st.FontProvider, out var shaper)) + { + shaper = new TextShaper(st.FontProvider); + shaperCache[st.FontProvider] = shaper; + } + + if (!layoutEngineCache.TryGetValue(st.FontProvider, out var layoutEngine)) + { + layoutEngine = new TextLayoutEngine(shaper); + layoutEngineCache[st.FontProvider] = layoutEngine; + } + + var options = ShapingOptions.Default; + options.ApplyPositioning = true; + options.ApplySubstitutions = true; + + var shaped = shaper.Shape(tf.Text, options); + var usedFonts = shaper.GetUsedFonts().ToList(); + var fontIdMap = new Dictionary(); + + for (byte fontId = 0; fontId < usedFonts.Count; fontId++) + { + var font = usedFonts[fontId]; + if (!dictionaries.Fonts.ContainsKey(font.FullName)) + { + int label = dictionaries.Fonts.Count > 0 + ? dictionaries.Fonts.Last().Value.labelNumber + 1 + : 1; + var fontResource = new PdfFontResource(font.FullName, font.NameTable.GetSubfamilyEnum(), label, pageSettings); + fontResource.fontData = font; + dictionaries.Fonts.Add(font.FullName, fontResource); + } + fontIdMap[fontId] = dictionaries.Fonts[font.FullName].Label; + } + + cell.TextLayoutEngine = layoutEngine; + st.ShapedText = shaped; + + totalTextLength += st.ShapedText.GetWidthInPoints((float)tf.Font.Size); + maxLineHeight = Math.Max(st.ShapedText.GetLineHeightInPoints((float)tf.Font.Size), maxLineHeight); + + st.FontIdMap = fontIdMap; + st.UsedFonts = usedFonts; + cell.TextFragments[i] = tf; + cell.ShapedTexts[i] = st; + } + + if (cell.TextLayoutEngine != null) + { + double wrapWidth = (cell.Merged && cell.Main == null) ? cell.Width : cell.ColumnWidth; + cell.TextLines = cell.ContentAligmnet.WrapText + ? cell.TextLayoutEngine.WrapRichTextLineCollection(cell.TextFragments, wrapWidth) + : cell.TextLayoutEngine.WrapRichTextLineCollection(cell.TextFragments, double.MaxValue); + } + + cell.TotalTextLength = totalTextLength; + } + } +} \ No newline at end of file From 949a2fd2faa0552516a3cff7f70fe7c11d98ffb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Mon, 22 Jun 2026 16:59:21 +0200 Subject: [PATCH 35/49] Moving pdf code progress. --- .../ExcelPatternMask.cs | 2 +- .../Fonts}/CIDSystemInfo.cs | 2 +- .../Fonts}/PdfCIDFont.cs | 2 +- .../Fonts}/PdfCidSet.cs | 2 +- .../Fonts}/PdfFont.cs | 2 +- .../Fonts}/PdfFontDescriptor.cs | 2 +- .../Fonts}/PdfFontStream.cs | 2 +- .../Fonts}/PdfFontWidths.cs | 2 +- .../Fonts}/PdfToUnicodeCMap.cs | 2 +- .../Fonts}/PdfType0FontDict.cs | 2 +- .../PdfExponentialInterpolationFunction.cs | 4 +- .../Functions}/PdfFunction.cs | 2 +- .../Functions}/PdfStichingFunction.cs | 4 +- .../Patterns}/PdfPattern.cs | 2 +- .../Patterns}/PdfPatternFill.cs | 2 +- .../Patterns}/PdfPatternMaskFill.cs | 4 +- .../Patterns}/PdfShadingPattern.cs | 4 +- .../Patterns}/PdfTilingPattern.cs | 4 +- .../PdfBorderRenderer.cs | 7 +- .../PdfCatalog.cs | 2 +- .../PdfContentStream.cs | 29 +- .../PdfCrossRefTable.cs | 2 +- .../PdfInfoObject.cs | 2 +- .../PdfObject.cs | 2 +- .../PdfPage.cs | 8 +- .../PdfPages.cs | 2 +- .../PdfTrailer.cs | 2 +- .../Shadings}/PdfAxialShading.cs | 8 +- .../Shadings}/PdfRadialShading.cs | 8 +- .../Shadings}/PdfShading.cs | 2 +- .../EPPlus.Export.Pdf.csproj | 3 - src/EPPlus.Export.Pdf/ExcelPdf.cs | 110 +- src/EPPlus.Export.Pdf/Helpers/EPPlusEnums.cs | 270 +++ .../{Pdfhelpers => Helpers}/PdfColor.cs | 2 +- .../{Pdfhelpers => Helpers}/PdfString.cs | 2 +- .../Layout/CellAlignmentData.cs} | 30 +- .../CellFillData.cs} | 26 +- .../Layout/CellFillGradientData.cs} | 17 +- .../Layout/MergedCellCorners.cs | 2 +- .../MergedCellDrawInfo.cs} | 16 +- .../Layout}/PdfBorderData.cs | 2 +- .../Layout/PdfCellBorderLayout.cs | 10 +- .../Layout/PdfCellContentLayout.cs | 15 +- .../Layout/PdfCellLayout.cs | 8 +- .../Layout/PdfGridlinesLayout.cs | 3 +- .../Layout/PdfPageLayout.cs | 46 +- .../PdfCatalog/MergedCellCorners.cs | 32 - .../PdfCatalog/PdfCalculateRowHeight.cs | 96 - .../PdfCatalog/PdfCatalog.cs | 377 ---- src/EPPlus.Export.Pdf/PdfCatalog/PdfCell.cs | 49 - .../PdfCatalog/PdfCellCollection.cs | 60 - .../PdfCatalog/PdfCommentsAndNotes.cs | 112 -- .../PdfCatalog/PdfGridlinesLayout.cs | 364 ---- .../PdfCatalog/PdfHeaderFooter.cs | 49 - .../PdfCatalog/PdfHeaderFooterCollection.cs | 152 -- src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs | 1788 ----------------- src/EPPlus.Export.Pdf/PdfCatalog/PdfRange.cs | 34 - .../PdfCatalog/PdfTextMap.cs | 964 --------- .../PdfCatalog/PdfTextShaper.cs | 112 -- .../PdfCatalog/PdfWorksheet.cs | 63 - .../PdfLayout/IShadingLayout.cs | 27 - .../PdfLayout/ITextLayout.cs | 21 - .../PdfLayout/PdfCatalogLayout.cs | 1081 ---------- .../PdfLayout/PdfCellBorderLayout.cs | 593 ------ .../PdfLayout/PdfCellContentLayout.cs | 675 ------- .../PdfLayout/PdfCellData.cs | 265 --- .../PdfLayout/PdfCellLayout.cs | 333 --- .../PdfLayout/PdfCellStyle.cs | 42 - .../PdfLayout/PdfCommentsLayout.cs | 152 -- .../PdfLayout/PdfContentLayout.cs | 36 - .../PdfLayout/PdfDrawingLayout.cs | 29 - .../PdfLayout/PdfHeaderFooterLayout.cs | 193 -- .../PdfLayout/PdfMergedCellLayout.cs | 45 - .../PdfLayout/PdfPageLayout.cs | 341 ---- .../PdfLayout/PdfWorksheetLayout.cs | 473 ----- .../PdfResources MOVED/PdfDictionaries.cs | 57 - .../PdfResources MOVED/PdfFontResource.cs | 265 --- .../PdfResources MOVED/PdfShadingResource.cs | 61 - .../PdfSettings MOVED/PdfContentBounds.cs | 97 - .../PdfPageData/PdfExcelPageDataLookup.cs | 59 - .../PdfSettings MOVED/PdfPageSettings.cs | 308 --- .../Pdfhelpers/PdfTextData.cs | 122 -- .../Resources}/PdfDictionaries.cs | 2 +- .../Resources}/PdfFontResource.cs | 2 +- .../PdfPatternResource.cs | 2 +- .../PdfResource.cs | 2 +- .../Resources}/PdfShadingResource.cs | 7 +- .../Settings}/PdfPageSettings.cs | 9 +- .../PdfPageSizes/PdfContentBounds.cs | 2 +- .../PdfPageSizes/PdfMargins.cs | 2 +- .../PdfPageSizes/PdfPageSize.cs | 2 +- .../PdfPageSizes/PdfScaling.cs | 2 +- .../Data/Dictionaries/PdfPatternResource.cs | 100 - .../PdfExport/Data/PdfPageSizes/PdfMargins.cs | 62 - .../Data/PdfPageSizes/PdfPageSize.cs | 42 - .../PdfExport/Data/PdfPageSizes/PdfScaling.cs | 66 - .../Export/PdfExport/Helpers/PdfColor.cs | 132 -- .../Export/PdfExport/Helpers/PdfString.cs | 63 - .../Export/PdfExport/Layout/PdfLayout.cs | 54 +- src/EPPlus/Export/PdfExport/PdfCatalog.cs | 12 +- 100 files changed, 479 insertions(+), 10293 deletions(-) rename src/EPPlus.Export.Pdf/{PdfObjects => DocumentObjects}/ExcelPatternMask.cs (99%) rename src/EPPlus.Export.Pdf/{PdfObjects/PdfFonts => DocumentObjects/Fonts}/CIDSystemInfo.cs (95%) rename src/EPPlus.Export.Pdf/{PdfObjects/PdfFonts => DocumentObjects/Fonts}/PdfCIDFont.cs (99%) rename src/EPPlus.Export.Pdf/{PdfObjects/PdfFonts => DocumentObjects/Fonts}/PdfCidSet.cs (96%) rename src/EPPlus.Export.Pdf/{PdfObjects/PdfFonts => DocumentObjects/Fonts}/PdfFont.cs (98%) rename src/EPPlus.Export.Pdf/{PdfObjects/PdfFonts => DocumentObjects/Fonts}/PdfFontDescriptor.cs (98%) rename src/EPPlus.Export.Pdf/{PdfObjects/PdfFonts => DocumentObjects/Fonts}/PdfFontStream.cs (97%) rename src/EPPlus.Export.Pdf/{PdfObjects/PdfFonts => DocumentObjects/Fonts}/PdfFontWidths.cs (99%) rename src/EPPlus.Export.Pdf/{PdfObjects/PdfFonts => DocumentObjects/Fonts}/PdfToUnicodeCMap.cs (99%) rename src/EPPlus.Export.Pdf/{PdfObjects/PdfFonts => DocumentObjects/Fonts}/PdfType0FontDict.cs (98%) rename src/EPPlus.Export.Pdf/{PdfObjects/PdfFunctions => DocumentObjects/Functions}/PdfExponentialInterpolationFunction.cs (96%) rename src/EPPlus.Export.Pdf/{PdfObjects/PdfFunctions => DocumentObjects/Functions}/PdfFunction.cs (96%) rename src/EPPlus.Export.Pdf/{PdfObjects/PdfFunctions => DocumentObjects/Functions}/PdfStichingFunction.cs (97%) rename src/EPPlus.Export.Pdf/{PdfObjects/PdfPatterns => DocumentObjects/Patterns}/PdfPattern.cs (95%) rename src/EPPlus.Export.Pdf/{PdfObjects/PdfPatterns => DocumentObjects/Patterns}/PdfPatternFill.cs (96%) rename src/EPPlus.Export.Pdf/{PdfObjects/PdfPatterns => DocumentObjects/Patterns}/PdfPatternMaskFill.cs (97%) rename src/EPPlus.Export.Pdf/{PdfObjects/PdfPatterns => DocumentObjects/Patterns}/PdfShadingPattern.cs (96%) rename src/EPPlus.Export.Pdf/{PdfObjects/PdfPatterns => DocumentObjects/Patterns}/PdfTilingPattern.cs (97%) rename src/EPPlus.Export.Pdf/{PdfObjects => DocumentObjects}/PdfBorderRenderer.cs (99%) rename src/EPPlus.Export.Pdf/{PdfObjects => DocumentObjects}/PdfCatalog.cs (97%) rename src/EPPlus.Export.Pdf/{PdfObjects => DocumentObjects}/PdfContentStream.cs (97%) rename src/EPPlus.Export.Pdf/{PdfObjects => DocumentObjects}/PdfCrossRefTable.cs (97%) rename src/EPPlus.Export.Pdf/{PdfObjects => DocumentObjects}/PdfInfoObject.cs (98%) rename src/EPPlus.Export.Pdf/{PdfObjects => DocumentObjects}/PdfObject.cs (98%) rename src/EPPlus.Export.Pdf/{PdfObjects => DocumentObjects}/PdfPage.cs (96%) rename src/EPPlus.Export.Pdf/{PdfObjects => DocumentObjects}/PdfPages.cs (97%) rename src/EPPlus.Export.Pdf/{PdfObjects => DocumentObjects}/PdfTrailer.cs (98%) rename src/EPPlus.Export.Pdf/{PdfObjects/PdfShadings => DocumentObjects/Shadings}/PdfAxialShading.cs (97%) rename src/EPPlus.Export.Pdf/{PdfObjects/PdfShadings => DocumentObjects/Shadings}/PdfRadialShading.cs (96%) rename src/EPPlus.Export.Pdf/{PdfObjects/PdfShadings => DocumentObjects/Shadings}/PdfShading.cs (98%) create mode 100644 src/EPPlus.Export.Pdf/Helpers/EPPlusEnums.cs rename src/EPPlus.Export.Pdf/{Pdfhelpers => Helpers}/PdfColor.cs (99%) rename src/EPPlus.Export.Pdf/{Pdfhelpers => Helpers}/PdfString.cs (98%) rename src/{EPPlus/Export/PdfExport/Data/Dictionaries/PdfResource.cs => EPPlus.Export.Pdf/Layout/CellAlignmentData.cs} (58%) rename src/EPPlus.Export.Pdf/{PdfLayout/PdfPagesLayout.cs => Layout/CellFillData.cs} (63%) rename src/{EPPlus/Export/PdfExport/Data/PdfFillData.cs => EPPlus.Export.Pdf/Layout/CellFillGradientData.cs} (73%) rename src/{EPPlus/Export/PdfExport => EPPlus.Export.Pdf}/Layout/MergedCellCorners.cs (97%) rename src/EPPlus.Export.Pdf/{PdfLayout/IBorderLayout.cs => Layout/MergedCellDrawInfo.cs} (75%) rename src/{EPPlus/Export/PdfExport/Data => EPPlus.Export.Pdf/Layout}/PdfBorderData.cs (98%) rename src/{EPPlus/Export/PdfExport => EPPlus.Export.Pdf}/Layout/PdfCellBorderLayout.cs (99%) rename src/{EPPlus/Export/PdfExport => EPPlus.Export.Pdf}/Layout/PdfCellContentLayout.cs (97%) rename src/{EPPlus/Export/PdfExport => EPPlus.Export.Pdf}/Layout/PdfCellLayout.cs (99%) rename src/{EPPlus/Export/PdfExport => EPPlus.Export.Pdf}/Layout/PdfGridlinesLayout.cs (99%) rename src/{EPPlus/Export/PdfExport => EPPlus.Export.Pdf}/Layout/PdfPageLayout.cs (92%) delete mode 100644 src/EPPlus.Export.Pdf/PdfCatalog/MergedCellCorners.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfCatalog/PdfCalculateRowHeight.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfCatalog/PdfCell.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfCatalog/PdfCellCollection.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfCatalog/PdfCommentsAndNotes.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfCatalog/PdfGridlinesLayout.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfCatalog/PdfHeaderFooter.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfCatalog/PdfHeaderFooterCollection.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfCatalog/PdfRange.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfCatalog/PdfTextShaper.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfCatalog/PdfWorksheet.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfLayout/IShadingLayout.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfLayout/ITextLayout.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfLayout/PdfCatalogLayout.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfLayout/PdfCellBorderLayout.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfLayout/PdfCellContentLayout.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfLayout/PdfCellData.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfLayout/PdfCellLayout.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfLayout/PdfCellStyle.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfLayout/PdfCommentsLayout.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfLayout/PdfContentLayout.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfLayout/PdfDrawingLayout.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfLayout/PdfHeaderFooterLayout.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfLayout/PdfMergedCellLayout.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfLayout/PdfPageLayout.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfLayout/PdfWorksheetLayout.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfResources MOVED/PdfDictionaries.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfResources MOVED/PdfFontResource.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfResources MOVED/PdfShadingResource.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfContentBounds.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfPageData/PdfExcelPageDataLookup.cs delete mode 100644 src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfPageSettings.cs delete mode 100644 src/EPPlus.Export.Pdf/Pdfhelpers/PdfTextData.cs rename src/{EPPlus/Export/PdfExport/Data/Dictionaries => EPPlus.Export.Pdf/Resources}/PdfDictionaries.cs (97%) rename src/{EPPlus/Export/PdfExport/Data/Dictionaries => EPPlus.Export.Pdf/Resources}/PdfFontResource.cs (99%) rename src/EPPlus.Export.Pdf/{PdfResources MOVED => Resources}/PdfPatternResource.cs (99%) rename src/EPPlus.Export.Pdf/{PdfResources MOVED => Resources}/PdfResource.cs (96%) rename src/{EPPlus/Export/PdfExport/Data/Dictionaries => EPPlus.Export.Pdf/Resources}/PdfShadingResource.cs (92%) rename src/{EPPlus/Export/PdfExport => EPPlus.Export.Pdf/Settings}/PdfPageSettings.cs (97%) rename src/{EPPlus/Export/PdfExport/Data => EPPlus.Export.Pdf/Settings}/PdfPageSizes/PdfContentBounds.cs (98%) rename src/EPPlus.Export.Pdf/{PdfSettings MOVED => Settings}/PdfPageSizes/PdfMargins.cs (98%) rename src/EPPlus.Export.Pdf/{PdfSettings MOVED => Settings}/PdfPageSizes/PdfPageSize.cs (97%) rename src/EPPlus.Export.Pdf/{PdfSettings MOVED => Settings}/PdfPageSizes/PdfScaling.cs (98%) delete mode 100644 src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfPatternResource.cs delete mode 100644 src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfMargins.cs delete mode 100644 src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfPageSize.cs delete mode 100644 src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfScaling.cs delete mode 100644 src/EPPlus/Export/PdfExport/Helpers/PdfColor.cs delete mode 100644 src/EPPlus/Export/PdfExport/Helpers/PdfString.cs diff --git a/src/EPPlus.Export.Pdf/PdfObjects/ExcelPatternMask.cs b/src/EPPlus.Export.Pdf/DocumentObjects/ExcelPatternMask.cs similarity index 99% rename from src/EPPlus.Export.Pdf/PdfObjects/ExcelPatternMask.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/ExcelPatternMask.cs index 38cda15796..4ff9734381 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/ExcelPatternMask.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/ExcelPatternMask.cs @@ -12,7 +12,7 @@ Date Author Change *************************************************************************************************/ using System.Collections.Generic; -namespace EPPlus.Export.Pdf.PdfObjects.PdfPatterns +namespace EPPlus.Export.Pdf.DocumentObjects { /// /// The cell fill pattern types EPPlus renders in the PDF export, matching diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/CIDSystemInfo.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/CIDSystemInfo.cs similarity index 95% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/CIDSystemInfo.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/Fonts/CIDSystemInfo.cs index 6d404fb541..2078bcc38e 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/CIDSystemInfo.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/CIDSystemInfo.cs @@ -11,7 +11,7 @@ Date Author Change 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -namespace EPPlus.Export.Pdf.PdfObjects.PdfFonts +namespace EPPlus.Export.Pdf.DocumentObjects.Fonts { internal class CIDSystemInfo { diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfCIDFont.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfCIDFont.cs similarity index 99% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfCIDFont.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfCIDFont.cs index 93b714006a..0ae23865e5 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfCIDFont.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfCIDFont.cs @@ -17,7 +17,7 @@ Date Author Change using System.Linq; using System.Text; -namespace EPPlus.Export.Pdf.PdfObjects.PdfFonts +namespace EPPlus.Export.Pdf.DocumentObjects.Fonts { public enum CIDFontSubtype { diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfCidSet.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfCidSet.cs similarity index 96% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfCidSet.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfCidSet.cs index 626a2e9cf3..35566d6232 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfCidSet.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfCidSet.cs @@ -12,7 +12,7 @@ Date Author Change *************************************************************************************************/ using System.IO; -namespace EPPlus.Export.Pdf.PdfObjects.PdfFonts +namespace EPPlus.Export.Pdf.DocumentObjects.Fonts { internal class PdfCidSet : PdfObject { diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfFont.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFont.cs similarity index 98% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfFont.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFont.cs index cffdcc7158..be5226a1af 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfFont.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFont.cs @@ -13,7 +13,7 @@ Date Author Change using System.IO; using System.Text; -namespace EPPlus.Export.Pdf.PdfObjects.PdfFonts +namespace EPPlus.Export.Pdf.DocumentObjects.Fonts { public enum PdfFontSubType { diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfFontDescriptor.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontDescriptor.cs similarity index 98% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfFontDescriptor.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontDescriptor.cs index 7cc4821f95..e283d19305 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfFontDescriptor.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontDescriptor.cs @@ -15,7 +15,7 @@ Date Author Change using System.IO; using System.Text; -namespace EPPlus.Export.Pdf.PdfObjects.PdfFonts +namespace EPPlus.Export.Pdf.DocumentObjects.Fonts { /// /// Font descriptor flags diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfFontStream.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontStream.cs similarity index 97% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfFontStream.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontStream.cs index 583ba2d2a8..c3112d256e 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfFontStream.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontStream.cs @@ -14,7 +14,7 @@ Date Author Change using System.IO; using System.Text; -namespace EPPlus.Export.Pdf.PdfObjects.PdfFonts +namespace EPPlus.Export.Pdf.DocumentObjects.Fonts { internal class PdfFontStream : PdfObject { diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfFontWidths.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontWidths.cs similarity index 99% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfFontWidths.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontWidths.cs index 9d7eda852c..68e95bb786 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfFontWidths.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontWidths.cs @@ -15,7 +15,7 @@ Date Author Change using System.IO; using System.Linq; -namespace EPPlus.Export.Pdf.PdfObjects.PdfFonts +namespace EPPlus.Export.Pdf.DocumentObjects.Fonts { internal class PdfFontWidths : PdfObject { diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfToUnicodeCMap.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfToUnicodeCMap.cs similarity index 99% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfToUnicodeCMap.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfToUnicodeCMap.cs index a6b7d4897e..be397dbc8d 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfToUnicodeCMap.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfToUnicodeCMap.cs @@ -15,7 +15,7 @@ Date Author Change using System.Linq; using System.Text; -namespace EPPlus.Export.Pdf.PdfObjects.PdfFonts +namespace EPPlus.Export.Pdf.DocumentObjects.Fonts { internal class PdfToUnicodeCMap : PdfObject { diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfType0FontDict.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfType0FontDict.cs similarity index 98% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfType0FontDict.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfType0FontDict.cs index 42604bc029..2fc470e056 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfType0FontDict.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfType0FontDict.cs @@ -14,7 +14,7 @@ Date Author Change using System.Linq; using System.Text; -namespace EPPlus.Export.Pdf.PdfObjects.PdfFonts +namespace EPPlus.Export.Pdf.DocumentObjects.Fonts { internal class PdfType0FontDict : PdfObject { diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfFunctions/PdfExponentialInterpolationFunction.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Functions/PdfExponentialInterpolationFunction.cs similarity index 96% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfFunctions/PdfExponentialInterpolationFunction.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/Functions/PdfExponentialInterpolationFunction.cs index 36412072a1..41b2a99944 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfFunctions/PdfExponentialInterpolationFunction.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Functions/PdfExponentialInterpolationFunction.cs @@ -10,12 +10,12 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -using EPPlus.Export.Pdf.Pdfhelpers; +using EPPlus.Export.Pdf.Helpers; using System.IO; using System.Linq; using System.Text; -namespace EPPlus.Export.Pdf.PdfObjects.PdfFunctions +namespace EPPlus.Export.Pdf.DocumentObjects.Functions { internal class PdfExponentialInterpolationFunction : PdfFunction { diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfFunctions/PdfFunction.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Functions/PdfFunction.cs similarity index 96% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfFunctions/PdfFunction.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/Functions/PdfFunction.cs index fdd99b5498..5c393efc74 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfFunctions/PdfFunction.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Functions/PdfFunction.cs @@ -10,7 +10,7 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -namespace EPPlus.Export.Pdf.PdfObjects.PdfFunctions +namespace EPPlus.Export.Pdf.DocumentObjects.Functions { internal abstract class PdfFunction : PdfObject { diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfFunctions/PdfStichingFunction.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Functions/PdfStichingFunction.cs similarity index 97% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfFunctions/PdfStichingFunction.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/Functions/PdfStichingFunction.cs index 77e20dd1a1..694caff8d6 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfFunctions/PdfStichingFunction.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Functions/PdfStichingFunction.cs @@ -10,13 +10,13 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -using EPPlus.Export.Pdf.Pdfhelpers; +using EPPlus.Export.Pdf.Helpers; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; -namespace EPPlus.Export.Pdf.PdfObjects.PdfFunctions +namespace EPPlus.Export.Pdf.DocumentObjects.Functions { internal class PdfStichingFunction : PdfFunction { diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPattern.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Patterns/PdfPattern.cs similarity index 95% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPattern.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/Patterns/PdfPattern.cs index da59bd9d0c..2706f64b02 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPattern.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Patterns/PdfPattern.cs @@ -10,7 +10,7 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -namespace EPPlus.Export.Pdf.PdfObjects.PdfPatterns +namespace EPPlus.Export.Pdf.DocumentObjects.Patterns { internal abstract class PdfPattern : PdfObject { diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternFill.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Patterns/PdfPatternFill.cs similarity index 96% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternFill.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/Patterns/PdfPatternFill.cs index 5eb7aa576b..5511bf2db0 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternFill.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Patterns/PdfPatternFill.cs @@ -13,7 +13,7 @@ Date Author Change using System.Drawing; using System.Collections.Generic; -namespace EPPlus.Export.Pdf.PdfObjects.PdfPatterns +namespace EPPlus.Export.Pdf.DocumentObjects.Patterns { internal abstract class PdfPatternFill { diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternMaskFill.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Patterns/PdfPatternMaskFill.cs similarity index 97% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternMaskFill.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/Patterns/PdfPatternMaskFill.cs index 01bfc4d3cc..d7b3445b4a 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternMaskFill.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Patterns/PdfPatternMaskFill.cs @@ -10,11 +10,11 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -using EPPlus.Export.Pdf.Pdfhelpers; +using EPPlus.Export.Pdf.Helpers; using System.Drawing; using System.Text; -namespace EPPlus.Export.Pdf.PdfObjects.PdfPatterns +namespace EPPlus.Export.Pdf.DocumentObjects.Patterns { /// /// Renders a cell fill pattern from an 8x8 . diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfShadingPattern.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Patterns/PdfShadingPattern.cs similarity index 96% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfShadingPattern.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/Patterns/PdfShadingPattern.cs index a0b2236217..8135ad90fe 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfShadingPattern.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Patterns/PdfShadingPattern.cs @@ -10,12 +10,12 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -using EPPlus.Export.Pdf.Pdfhelpers; +using EPPlus.Export.Pdf.Helpers; using System.IO; using System.Linq; using System.Text; -namespace EPPlus.Export.Pdf.PdfObjects.PdfPatterns +namespace EPPlus.Export.Pdf.DocumentObjects.Patterns { internal class PdfShadingPattern : PdfPattern { diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfTilingPattern.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Patterns/PdfTilingPattern.cs similarity index 97% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfTilingPattern.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/Patterns/PdfTilingPattern.cs index aecc69f562..5264b9aa3e 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfTilingPattern.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Patterns/PdfTilingPattern.cs @@ -10,12 +10,12 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -using EPPlus.Export.Pdf.Pdfhelpers; +using EPPlus.Export.Pdf.Helpers; using System.IO; using System.Linq; using System.Text; -namespace EPPlus.Export.Pdf.PdfObjects.PdfPatterns +namespace EPPlus.Export.Pdf.DocumentObjects.Patterns { internal class PdfTilingPattern : PdfPattern { diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfBorderRenderer.cs b/src/EPPlus.Export.Pdf/DocumentObjects/PdfBorderRenderer.cs similarity index 99% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfBorderRenderer.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/PdfBorderRenderer.cs index a87b9b14d6..0971cabcd7 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfBorderRenderer.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/PdfBorderRenderer.cs @@ -10,12 +10,11 @@ Date Author Change ************************************************************************************************* 10/07/2025 EPPlus Software AB EPPlus.Fonts.OpenType 1.0 *************************************************************************************************/ -using EPPlus.Export.Pdf.PdfCatalog; -using EPPlus.Export.Pdf.Pdfhelpers; -using EPPlus.Export.Pdf.PdfLayout; +using EPPlus.Export.Pdf.Helpers; +using EPPlus.Export.Pdf.Layout; using OfficeOpenXml.Style; -namespace EPPlus.Export.Pdf.PdfObjects +namespace EPPlus.Export.Pdf.DocumentObjects { internal class PdfBorderRenderer { diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfCatalog.cs b/src/EPPlus.Export.Pdf/DocumentObjects/PdfCatalog.cs similarity index 97% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfCatalog.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/PdfCatalog.cs index a288bbc02d..cbb0f96f61 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfCatalog.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/PdfCatalog.cs @@ -12,7 +12,7 @@ Date Author Change *************************************************************************************************/ using System.IO; -namespace EPPlus.Export.Pdf.PdfObjects +namespace EPPlus.Export.Pdf.DocumentObjects { internal class PdfCatalog : PdfObject { diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs b/src/EPPlus.Export.Pdf/DocumentObjects/PdfContentStream.cs similarity index 97% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/PdfContentStream.cs index e2a1f9af1b..5c8f6b4c22 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/PdfContentStream.cs @@ -10,16 +10,9 @@ Date Author Change ************************************************************************************************* 10/07/2025 EPPlus Software AB EPPlus.Fonts.OpenType 1.0 *************************************************************************************************/ -using EPPlus.Export.Pdf.Pdfhelpers; -using EPPlus.Export.Pdf.PdfLayout; -using EPPlus.Export.Pdf.PdfResources; -using EPPlus.Export.Pdf.PdfSettings; -using EPPlus.Fonts.OpenType; using EPPlus.Graphics; using EPPlus.Graphics.Geometry; -using EPPlus.Graphics.Geometry; using OfficeOpenXml.Interfaces.Fonts; -using OfficeOpenXml.Style; using System; using System.Collections.Generic; using System.Drawing; @@ -27,8 +20,12 @@ Date Author Change using System.Linq; using System.Text; using EPPlus.Fonts.OpenType.Integration; +using EPPlus.Export.Pdf.Layout; +using EPPlus.Export.Pdf.Helpers; +using EPPlus.Export.Pdf.Resources; +using EPPlus.Export.Pdf.Settings; -namespace EPPlus.Export.Pdf.PdfObjects +namespace EPPlus.Export.Pdf.DocumentObjects { internal class PdfContentStream : PdfObject { @@ -485,14 +482,14 @@ private void AddClipping(PdfCellContentLayout cell) commands.Add($"{cell.Clipping.X.ToPdfString()} {cell.Clipping.Y.ToPdfString()} {cell.Clipping.Width.ToPdfString()} {cell.Clipping.Height.ToPdfString()} re W n"); } - public void AddCellContentLayout(PdfHeaderFooterLayout cell, PdfDictionaries dictionaries, PdfPageSettings pageSettings) - { - commands.Add($"% HeaderFooter Start: {cell.Name}"); - commands.Add("q"); - //AddText(cell, cell.LocalPosition, 0, dictionaries, pageSettings); - commands.Add("Q"); - commands.Add($"% HeaderFooter End: {cell.Name}"); - } + //public void AddCellContentLayout(PdfHeaderFooterLayout cell, PdfDictionaries dictionaries, PdfPageSettings pageSettings) + //{ + // commands.Add($"% HeaderFooter Start: {cell.Name}"); + // commands.Add("q"); + // //AddText(cell, cell.LocalPosition, 0, dictionaries, pageSettings); + // commands.Add("Q"); + // commands.Add($"% HeaderFooter End: {cell.Name}"); + //} public void AddInnerGridLines(Transform pageLayout) { diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfCrossRefTable.cs b/src/EPPlus.Export.Pdf/DocumentObjects/PdfCrossRefTable.cs similarity index 97% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfCrossRefTable.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/PdfCrossRefTable.cs index c5e207657a..57d7ddd8c2 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfCrossRefTable.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/PdfCrossRefTable.cs @@ -14,7 +14,7 @@ Date Author Change using System.IO; using System.Text; -namespace EPPlus.Export.Pdf.PdfObjects +namespace EPPlus.Export.Pdf.DocumentObjects { internal class PdfCrossRefTable { diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfInfoObject.cs b/src/EPPlus.Export.Pdf/DocumentObjects/PdfInfoObject.cs similarity index 98% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfInfoObject.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/PdfInfoObject.cs index 3884ef623f..ad56f5081a 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfInfoObject.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/PdfInfoObject.cs @@ -14,7 +14,7 @@ Date Author Change using System.IO; using System.Text; -namespace EPPlus.Export.Pdf.PdfObjects +namespace EPPlus.Export.Pdf.DocumentObjects { internal class PdfInfoObject : PdfObject { diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfObject.cs b/src/EPPlus.Export.Pdf/DocumentObjects/PdfObject.cs similarity index 98% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfObject.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/PdfObject.cs index 06f2bce685..bb3c361503 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfObject.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/PdfObject.cs @@ -13,7 +13,7 @@ Date Author Change using System.IO; using System.Text; -namespace EPPlus.Export.Pdf.PdfObjects +namespace EPPlus.Export.Pdf.DocumentObjects { internal abstract class PdfObject { diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfPage.cs b/src/EPPlus.Export.Pdf/DocumentObjects/PdfPage.cs similarity index 96% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfPage.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/PdfPage.cs index 34f1f5d1bb..a7632d8c2f 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfPage.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/PdfPage.cs @@ -10,15 +10,15 @@ Date Author Change ************************************************************************************************* 10/07/2025 EPPlus Software AB EPPlus.Fonts.OpenType 1.0 *************************************************************************************************/ -using EPPlus.Export.Pdf.Pdfhelpers; -using EPPlus.Export.Pdf.PdfResources; -using EPPlus.Export.Pdf.PdfSettings.PdfPageSizes; +using EPPlus.Export.Pdf.Helpers; +using EPPlus.Export.Pdf.Resources; +using EPPlus.Export.Pdf.Settings.PdfPageSizes; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; -namespace EPPlus.Export.Pdf.PdfObjects +namespace EPPlus.Export.Pdf.DocumentObjects { internal class PdfPage : PdfObject { diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfPages.cs b/src/EPPlus.Export.Pdf/DocumentObjects/PdfPages.cs similarity index 97% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfPages.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/PdfPages.cs index 2fd711fc26..a2684d13f4 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfPages.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/PdfPages.cs @@ -15,7 +15,7 @@ Date Author Change using System.Linq; using System.Text; -namespace EPPlus.Export.Pdf.PdfObjects +namespace EPPlus.Export.Pdf.DocumentObjects { internal class PdfPages : PdfObject { diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfTrailer.cs b/src/EPPlus.Export.Pdf/DocumentObjects/PdfTrailer.cs similarity index 98% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfTrailer.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/PdfTrailer.cs index ab54f0ba6a..80245bbf71 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfTrailer.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/PdfTrailer.cs @@ -13,7 +13,7 @@ Date Author Change using System.IO; using System.Text; -namespace EPPlus.Export.Pdf.PdfObjects +namespace EPPlus.Export.Pdf.DocumentObjects { internal static class PdfTrailer { diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfShadings/PdfAxialShading.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Shadings/PdfAxialShading.cs similarity index 97% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfShadings/PdfAxialShading.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/Shadings/PdfAxialShading.cs index b3b9f4db19..e4fdab6884 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfShadings/PdfAxialShading.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Shadings/PdfAxialShading.cs @@ -11,14 +11,14 @@ Date Author Change 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ using System.Drawing; -using EPPlus.Export.Pdf.Pdfhelpers; -using EPPlus.Export.Pdf.PdfLayout; -using EPPlus.Export.Pdf.PdfObjects.PdfFunctions; using System.Linq; using System.Text; using System.IO; +using EPPlus.Export.Pdf.Layout; +using EPPlus.Export.Pdf.DocumentObjects.Functions; +using EPPlus.Export.Pdf.Helpers; -namespace EPPlus.Export.Pdf.PdfObjects.PdfShadings +namespace EPPlus.Export.Pdf.DocumentObjects.Shadings { internal class PdfAxialShading : PdfShading { diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfShadings/PdfRadialShading.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Shadings/PdfRadialShading.cs similarity index 96% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfShadings/PdfRadialShading.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/Shadings/PdfRadialShading.cs index 15fd887b22..6760f686b0 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfShadings/PdfRadialShading.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Shadings/PdfRadialShading.cs @@ -10,15 +10,15 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -using EPPlus.Export.Pdf.Pdfhelpers; -using EPPlus.Export.Pdf.PdfLayout; -using EPPlus.Export.Pdf.PdfObjects.PdfFunctions; +using EPPlus.Export.Pdf.DocumentObjects.Functions; +using EPPlus.Export.Pdf.Helpers; +using EPPlus.Export.Pdf.Layout; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; -namespace EPPlus.Export.Pdf.PdfObjects.PdfShadings +namespace EPPlus.Export.Pdf.DocumentObjects.Shadings { internal class PdfRadialShading : PdfShading { diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfShadings/PdfShading.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Shadings/PdfShading.cs similarity index 98% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfShadings/PdfShading.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/Shadings/PdfShading.cs index 1124724eb4..12daf26cf8 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfShadings/PdfShading.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Shadings/PdfShading.cs @@ -14,7 +14,7 @@ Date Author Change using System.IO; using System.Text; -namespace EPPlus.Export.Pdf.PdfObjects.PdfShadings +namespace EPPlus.Export.Pdf.DocumentObjects.Shadings { public enum DeviceColorSpace { diff --git a/src/EPPlus.Export.Pdf/EPPlus.Export.Pdf.csproj b/src/EPPlus.Export.Pdf/EPPlus.Export.Pdf.csproj index 7e1db7a087..9b49eee2a5 100644 --- a/src/EPPlus.Export.Pdf/EPPlus.Export.Pdf.csproj +++ b/src/EPPlus.Export.Pdf/EPPlus.Export.Pdf.csproj @@ -25,17 +25,14 @@ - - - diff --git a/src/EPPlus.Export.Pdf/ExcelPdf.cs b/src/EPPlus.Export.Pdf/ExcelPdf.cs index 51aac792af..ca409b99bb 100644 --- a/src/EPPlus.Export.Pdf/ExcelPdf.cs +++ b/src/EPPlus.Export.Pdf/ExcelPdf.cs @@ -10,90 +10,40 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -using EPPlus.Export.Pdf.PdfLayout; -using EPPlus.Export.Pdf.PdfObjects; -using EPPlus.Export.Pdf.PdfResources; -using EPPlus.Export.Pdf.PdfSettings; -using EPPlus.Fonts.OpenType; +using EPPlus.Export.Pdf.Layout; +using EPPlus.Export.Pdf.DocumentObjects; +using EPPlus.Export.Pdf.Resources; +using EPPlus.Export.Pdf.Settings; using EPPlus.Graphics; -using OfficeOpenXml; -using OfficeOpenXml.Interfaces.Fonts; using OfficeOpenXml.Style; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; -using static OfficeOpenXml.Drawing.OleObject.Structures.OleObjectDataStructures; namespace EPPlus.Export.Pdf { /// /// Class for exporting to PDF format. /// - public class ExcelPdf + internal class ExcelPdf { - internal List _workheets = new List(); - internal ExcelRangeBase _range; private PdfPageSettings _pageSettings; - internal List _document = new List(); - internal string header = "%PDF-1.7\n"; - internal PdfDictionaries _dictionaries = new PdfDictionaries(); - private string _debugString; - - public ExcelPdf() - { - } - - /// - /// Create a PDF Document from the worksheet and settings. - /// - /// The worksheet to convert to PDF Document - /// The settings object - public ExcelPdf(ExcelWorksheet worksheet, PdfPageSettings pageSettings = null) - { - _workheets.Add(worksheet); - _pageSettings = pageSettings == null ? new PdfPageSettings() : pageSettings; - _pageSettings.defaultFontName = worksheet.Workbook.ThemeManager.CurrentTheme.FontScheme.MinorFont[0].Typeface; - } - - /// - /// Create a PDF Document from the selected worksheets and settings. NOT IMPLEMENTED - /// - /// The worksheets to convert to PDF Document - /// The Settings object - public ExcelPdf(ExcelWorksheet[] worksheet, PdfPageSettings pageSettings = null) - { - //_ws = worksheet[0]; - //defaultFontName = worksheet[0].Workbook.ThemeManager.CurrentTheme.FontScheme.MinorFont[0].Typeface; - //PageSettings = pageSettings == null ? new PdfPageSettings() : pageSettings; - } + private PdfDictionaries _dictionaries; + private List _document = new List(); - /// - /// Create a PDF Document from the entire worksbook and settings.NOT IMPLEMENTED - /// - /// Workbook to convert to PDF Document - /// The settings object - public ExcelPdf(ExcelWorkbook workbook, PdfPageSettings pageSettings = null) - { - //_ws = worksheet; - //defaultFontName = worksheet.Workbook.ThemeManager.CurrentTheme.FontScheme.MinorFont[0].Typeface; - //PageSettings = pageSettings == null ? new PdfPageSettings() : pageSettings; - } + private string _debugString; - /// - /// Create a PDF Document from the selected range and settings. NOT IMPLEMENTED - /// - /// Range to convert to PDF Document - /// The settings object - public ExcelPdf(ExcelRangeBase Range, PdfPageSettings pageSettings = null) + internal static string Header { - //_range = Range; - //defaultFontName = Range.Worksheet.Workbook.ThemeManager.CurrentTheme.FontScheme.MinorFont[0].Typeface; - //PageSettings = pageSettings == null ? new PdfPageSettings() : pageSettings; + get + { + return "%PDF-1.7\n"; + } } //Get the label to use for pattern. - internal string GetPatternLabel(PdfCellLayout layout) + private string GetPatternLabel(PdfCellLayout layout) { bool isPattern = (layout.CellFillData.PatternStyle != ExcelFillStyle.Solid && layout.CellFillData.PatternStyle != ExcelFillStyle.None) || layout.CellFillData.GradientFillData != null; if (isPattern) @@ -108,7 +58,7 @@ internal string GetPatternLabel(PdfCellLayout layout) } //Add Fonts //Need to update this method a bit. We should check for all default fonts and not only courier new? Also need to check if we are allowed to embedd the font. - internal void AddFontData() + private void AddFontData() { if (_pageSettings.EmbeddFonts) { @@ -137,7 +87,7 @@ internal void AddFontData() } //Add Patterns - internal void AddPatternData() + private void AddPatternData() { foreach (var pattern in _dictionaries.Patterns) { @@ -146,7 +96,7 @@ internal void AddPatternData() } //Add Shadings and accompanying pattern - internal void AddShadingsData() + private void AddShadingsData() { foreach (var shading in _dictionaries.Shadings) { @@ -270,15 +220,15 @@ private void AddContent(Transform pageLayout, PdfPage page) } //Add Header Footer - private void AddHeaderFooter(PdfContentStream contentStream, Transform pageLayout, PdfPage page) - { - var headerFooter = pageLayout.ChildObjects.Where(t => t is PdfHeaderFooterLayout); - foreach (var hf in headerFooter) - { - var headerFooterLayout = hf as PdfHeaderFooterLayout; - contentStream.AddCellContentLayout(headerFooterLayout, _dictionaries, _pageSettings); - } - } + //private void AddHeaderFooter(PdfContentStream contentStream, Transform pageLayout, PdfPage page) + //{ + // var headerFooter = pageLayout.ChildObjects.Where(t => t is PdfHeaderFooterLayout); + // foreach (var hf in headerFooter) + // { + // var headerFooterLayout = hf as PdfHeaderFooterLayout; + // contentStream.AddCellContentLayout(headerFooterLayout, _dictionaries, _pageSettings); + // } + //} //Add Info private PdfInfoObject AddInfoObject(string workBookName = "") @@ -333,7 +283,7 @@ internal void CreatePdf(PdfPageSettings pageSettings, PdfDictionaries dictionari /// Create the pdf from the supplied worksheet. /// /// The file name - public void CreatePdf(string Filename) + internal void CreatePdf(string Filename) { using (var fs = new FileStream(Filename, FileMode.Create, FileAccess.Write)) { @@ -350,7 +300,7 @@ public void CreatePdf(string Filename) /// Create the pdf from the supplied worksheet and write it to a stream. /// /// The stream to write the pdf to. The stream will not be closed. - public void CreatePdf(Stream stream) + internal void CreatePdf(Stream stream) { //Create Catalog var catalogLayout = new PdfCatalogLayout(_workheets[0], _pageSettings, _dictionaries); @@ -390,8 +340,8 @@ private void WriteDocumentToStream(Stream stream, PdfObjects.PdfCatalog catalog, try { //Write header - bw.Write(Encoding.ASCII.GetBytes(header)); - _debugString += header; + bw.Write(Encoding.ASCII.GetBytes(Header)); + _debugString += Header; //Write body foreach (var pdfobj in _document) { diff --git a/src/EPPlus.Export.Pdf/Helpers/EPPlusEnums.cs b/src/EPPlus.Export.Pdf/Helpers/EPPlusEnums.cs new file mode 100644 index 0000000000..68e7afbef7 --- /dev/null +++ b/src/EPPlus.Export.Pdf/Helpers/EPPlusEnums.cs @@ -0,0 +1,270 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EPPlus.Export.Pdf.Helpers +{ + /// + /// Horizontal text alignment + /// + public enum ExcelHorizontalAlignment + { + /// + /// General aligned + /// + General, + /// + /// Left aligned + /// + Left, + /// + /// Center aligned + /// + Center, + /// + /// The horizontal alignment is centered across multiple cells + /// + CenterContinuous, + /// + /// Right aligned + /// + Right, + /// + /// The value of the cell should be filled across the entire width of the cell. + /// + Fill, + /// + /// Each word in each line of text inside the cell is evenly distributed across the width of the cell + /// + Distributed, + /// + /// The horizontal alignment is justified to the Left and Right for each row. + /// + Justify + } + /// + /// Vertical text alignment + /// + public enum ExcelVerticalAlignment + { + /// + /// Top aligned + /// + Top, + /// + /// Center aligned + /// + Center, + /// + /// Bottom aligned + /// + Bottom, + /// + /// Distributed. Each line of text inside the cell is evenly distributed across the height of the cell + /// + Distributed, + /// + /// Justify. Each line of text inside the cell is evenly distributed across the height of the cell + /// + Justify + } + /// + /// The reading order + /// + public enum ExcelReadingOrder + { + /// + /// Reading order is determined by the first non-whitespace character + /// + ContextDependent = 0, + /// + /// Left to Right + /// + LeftToRight = 1, + /// + /// Right to Left + /// + RightToLeft = 2 + } + /// + /// Fill pattern + /// + public enum ExcelFillStyle + { + /// + /// No fill + /// + None, + /// + /// A solid fill + /// + Solid, + /// + /// Dark gray + /// Excel name: 75% Gray + /// + DarkGray, + /// + /// Medium gray + /// Excel name: 50% Gray + /// + MediumGray, + /// + /// Light gray + /// Excel name: 25% Gray + /// + LightGray, + /// + /// Grayscale of 0.125, 1/8 + /// Excel name: 12.5% Gray + /// + Gray125, + /// + /// Grayscale of 0.0625, 1/16 + /// Excel name: 6.25% Gray + /// + Gray0625, + /// + /// Dark vertical + /// Excel name: Vertical Stripe + /// + DarkVertical, + /// + /// Dark horizontal + /// Excel name: Horizontal Stripe + /// + DarkHorizontal, + /// + /// Dark down + /// Excel name: Reverse Diagonal Stripe + /// + DarkDown, + /// + /// Dark up + /// Excel name: Diagonal Stripe + /// + DarkUp, + /// + /// Dark grid + /// Excel name: Diagonal Crosshatch + /// + DarkGrid, + /// + /// Dark trellis + /// Excel name: Thick Diagonal Crosshatch + /// + DarkTrellis, + /// + /// Light vertical + /// Excel name: Thin Vertical Stripe + /// + LightVertical, + /// + /// Light horizontal + /// Excel name: Thin Horizontal Stripe + /// + LightHorizontal, + /// + /// Light down + /// Excel name: Thin Reverse Diagonal Stripe + /// + LightDown, + /// + /// Light up + /// Excel name: Thin Diagonal Stripe + /// + LightUp, + /// + /// Light grid + /// Excel name: Thin Horizontal Crosshatch + /// + LightGrid, + /// + /// Light trellis + /// Excel name: Thin Diagonal Crosshatch + /// + LightTrellis + } + /// + /// BulletType of gradient fill + /// + public enum ExcelFillGradientType + { + /// + /// No gradient fill. + /// + None, + /// + /// Linear gradient type. Linear gradient type means that the transition from one color to the next is along a line. + /// + Linear, + /// + /// Path gradient type. Path gradient type means the that the transition from one color to the next is a rectangle, defined by coordinates. + /// + Path + } + /// + /// Border line style + /// + public enum ExcelBorderStyle + { + /// + /// No border style + /// + None, + /// + /// Hairline + /// + Hair, + /// + /// Dotted + /// + Dotted, + /// + /// Dash Dot + /// + DashDot, + /// + /// Thin single line + /// + Thin, + /// + /// Dash Dot Dot + /// + DashDotDot, + /// + /// Dashed + /// + Dashed, + /// + /// Dash Dot Dot, medium thickness + /// + MediumDashDotDot, + /// + /// Dashed, medium thickness + /// + MediumDashed, + /// + /// Dash Dot, medium thickness + /// + MediumDashDot, + /// + /// Single line, Thick + /// + Thick, + /// + /// Single line, medium thickness + /// + Medium, + /// + /// Double line + /// + Double, + /// + /// Slanted Dash Dot + /// + SlantDashDot + }; +} diff --git a/src/EPPlus.Export.Pdf/Pdfhelpers/PdfColor.cs b/src/EPPlus.Export.Pdf/Helpers/PdfColor.cs similarity index 99% rename from src/EPPlus.Export.Pdf/Pdfhelpers/PdfColor.cs rename to src/EPPlus.Export.Pdf/Helpers/PdfColor.cs index 50fcea01ed..2378b042c4 100644 --- a/src/EPPlus.Export.Pdf/Pdfhelpers/PdfColor.cs +++ b/src/EPPlus.Export.Pdf/Helpers/PdfColor.cs @@ -14,7 +14,7 @@ Date Author Change using System.Drawing; using System.Globalization; -namespace EPPlus.Export.Pdf.Pdfhelpers +namespace EPPlus.Export.Pdf.Helpers { internal static class PdfColor { diff --git a/src/EPPlus.Export.Pdf/Pdfhelpers/PdfString.cs b/src/EPPlus.Export.Pdf/Helpers/PdfString.cs similarity index 98% rename from src/EPPlus.Export.Pdf/Pdfhelpers/PdfString.cs rename to src/EPPlus.Export.Pdf/Helpers/PdfString.cs index acd3fa5876..20802e17d4 100644 --- a/src/EPPlus.Export.Pdf/Pdfhelpers/PdfString.cs +++ b/src/EPPlus.Export.Pdf/Helpers/PdfString.cs @@ -12,7 +12,7 @@ Date Author Change *************************************************************************************************/ using System.Globalization; -namespace EPPlus.Export.Pdf.Pdfhelpers +namespace EPPlus.Export.Pdf.Helpers { internal static class PdfString { diff --git a/src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfResource.cs b/src/EPPlus.Export.Pdf/Layout/CellAlignmentData.cs similarity index 58% rename from src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfResource.cs rename to src/EPPlus.Export.Pdf/Layout/CellAlignmentData.cs index be53eba9be..265b77e4c3 100644 --- a/src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfResource.cs +++ b/src/EPPlus.Export.Pdf/Layout/CellAlignmentData.cs @@ -10,25 +10,21 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -namespace OfficeOpenXml.Export.PdfExport.Data.Dictionaries +using EPPlus.Export.Pdf.Helpers + +namespace EPPlus.Export.Pdf.Layout { - internal class PdfResource + internal class PdfCellAlignmentData { - internal readonly string labelPrefix; - internal int labelNumber; - - internal string Label - { - get - { - return labelPrefix + labelNumber; - } - } + public ExcelHorizontalAlignment HorizontalAlignment = ExcelHorizontalAlignment.General; + public ExcelVerticalAlignment VerticalAlignment = ExcelVerticalAlignment.Bottom; + public int Indent = 0; + public bool WrapText = false; + public bool ShrinkToFit = false; + public int TextRotation = 0; + public ExcelReadingOrder TextDirection = ExcelReadingOrder.ContextDependent; + public bool IsVertical = false; - public PdfResource(string labelPrefix, int labelNumber) - { - this.labelPrefix = labelPrefix; - this.labelNumber = labelNumber; - } + public PdfCellAlignmentData() { } } } diff --git a/src/EPPlus.Export.Pdf/PdfLayout/PdfPagesLayout.cs b/src/EPPlus.Export.Pdf/Layout/CellFillData.cs similarity index 63% rename from src/EPPlus.Export.Pdf/PdfLayout/PdfPagesLayout.cs rename to src/EPPlus.Export.Pdf/Layout/CellFillData.cs index fac5135935..756c9bf5f3 100644 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfPagesLayout.cs +++ b/src/EPPlus.Export.Pdf/Layout/CellFillData.cs @@ -10,22 +10,20 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -using OfficeOpenXml; -using EPPlus.Graphics; +using System.Drawing; +using EPPlus.Export.Pdf.Helpers; -namespace EPPlus.Export.Pdf.PdfLayout +namespace EPPlus.Export.Pdf.Layout { - /// - /// Represents the pages object in the pdf document. - /// - internal class PdfPagesLayout : Transform + internal class PdfCellFillData { - internal ExcelRangeBase Range; - - internal PdfPagesLayout(double x, double y, double height, double width) - : base(x, y, height, width) - { - } - + public string id; + public Color BackgroundColor = Color.Empty; + public ExcelFillStyle PatternStyle = ExcelFillStyle.None; + public Color PatternColor = Color.Black; + //Fill Effects + public PdfCellGradientFillData GradientFillData = null; + public bool enhanceGridLine = false; + public PdfCellFillData() { } } } diff --git a/src/EPPlus/Export/PdfExport/Data/PdfFillData.cs b/src/EPPlus.Export.Pdf/Layout/CellFillGradientData.cs similarity index 73% rename from src/EPPlus/Export/PdfExport/Data/PdfFillData.cs rename to src/EPPlus.Export.Pdf/Layout/CellFillGradientData.cs index e2be4f63a6..bb0f4cd85a 100644 --- a/src/EPPlus/Export/PdfExport/Data/PdfFillData.cs +++ b/src/EPPlus.Export.Pdf/Layout/CellFillGradientData.cs @@ -10,11 +10,10 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -using OfficeOpenXml.Export.PdfExport.Helpers; -using OfficeOpenXml.Style; +using EPPlus.Export.Pdf.Helpers; using System.Drawing; -namespace OfficeOpenXml.Export.PdfExport.Data +namespace EPPlus.Export.Pdf.Layout { internal class PdfCellGradientFillData { @@ -35,16 +34,4 @@ public override string ToString() return GradientType.ToString() + Color1.ToHexString() + Color2.ToHexString() + Degree + Top + Bottom + Left + Right; } } - - internal class PdfCellFillData - { - public string id; - public Color BackgroundColor = Color.Empty; - public ExcelFillStyle PatternStyle = ExcelFillStyle.None; - public Color PatternColor = Color.Black; - //Fill Effects - public PdfCellGradientFillData GradientFillData = null; - public bool enhanceGridLine = false; - public PdfCellFillData() { } - } } diff --git a/src/EPPlus/Export/PdfExport/Layout/MergedCellCorners.cs b/src/EPPlus.Export.Pdf/Layout/MergedCellCorners.cs similarity index 97% rename from src/EPPlus/Export/PdfExport/Layout/MergedCellCorners.cs rename to src/EPPlus.Export.Pdf/Layout/MergedCellCorners.cs index 39f8e54b6e..8e16f18eec 100644 --- a/src/EPPlus/Export/PdfExport/Layout/MergedCellCorners.cs +++ b/src/EPPlus.Export.Pdf/Layout/MergedCellCorners.cs @@ -12,7 +12,7 @@ Date Author Change *************************************************************************************************/ using System; -namespace OfficeOpenXml.Export.PdfExport.Layout +namespace EPPlus.Export.Pdf.Layout { /// /// Flags that describe which corners of a merged-cell boundary are owned by /// a particular layout object on the current page. diff --git a/src/EPPlus.Export.Pdf/PdfLayout/IBorderLayout.cs b/src/EPPlus.Export.Pdf/Layout/MergedCellDrawInfo.cs similarity index 75% rename from src/EPPlus.Export.Pdf/PdfLayout/IBorderLayout.cs rename to src/EPPlus.Export.Pdf/Layout/MergedCellDrawInfo.cs index 1e7f190fa0..667702e8cf 100644 --- a/src/EPPlus.Export.Pdf/PdfLayout/IBorderLayout.cs +++ b/src/EPPlus.Export.Pdf/Layout/MergedCellDrawInfo.cs @@ -10,17 +10,13 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ - -namespace EPPlus.Export.Pdf.PdfLayout +namespace EPPlus.Export.Pdf.Layout { - /// - /// Interface for border objects - /// - internal interface IBorderLayout + internal struct MergedCellDrawInfo { - /// - /// Update Border positions. - /// - abstract void UpdateLocalBorderPosition(); + public double X; + public double Y; + public double Width; + public double Height; } } diff --git a/src/EPPlus/Export/PdfExport/Data/PdfBorderData.cs b/src/EPPlus.Export.Pdf/Layout/PdfBorderData.cs similarity index 98% rename from src/EPPlus/Export/PdfExport/Data/PdfBorderData.cs rename to src/EPPlus.Export.Pdf/Layout/PdfBorderData.cs index 416aeddeea..058037c47a 100644 --- a/src/EPPlus/Export/PdfExport/Data/PdfBorderData.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfBorderData.cs @@ -13,7 +13,7 @@ Date Author Change using OfficeOpenXml.Style; using System.Drawing; -namespace OfficeOpenXml.Export.PdfExport.Data +namespace EPPlus.Export.Pdf.Layout { internal class PdfBorderData { diff --git a/src/EPPlus/Export/PdfExport/Layout/PdfCellBorderLayout.cs b/src/EPPlus.Export.Pdf/Layout/PdfCellBorderLayout.cs similarity index 99% rename from src/EPPlus/Export/PdfExport/Layout/PdfCellBorderLayout.cs rename to src/EPPlus.Export.Pdf/Layout/PdfCellBorderLayout.cs index f907ee7242..f671d04c80 100644 --- a/src/EPPlus/Export/PdfExport/Layout/PdfCellBorderLayout.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfCellBorderLayout.cs @@ -10,17 +10,12 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ +using EPPlus.Export.Pdf.Helpers; using EPPlus.Graphics; -using OfficeOpenXml.Export.PdfExport.Data; -using OfficeOpenXml.Export.PdfExport.Helpers; -using OfficeOpenXml.Style; -using OfficeOpenXml.Style.Dxf; -using OfficeOpenXml.Style.Table; -using OfficeOpenXml.Table; using System.Diagnostics; using System.Drawing; -namespace OfficeOpenXml.Export.PdfExport.Layout +namespace EPPlus.Export.Pdf.Layout { [DebuggerDisplay("Border: {Name}")] internal class PdfCellBorderLayout : Transform @@ -29,7 +24,6 @@ internal class PdfCellBorderLayout : Transform public bool IsMerged = false; public MergedCellDrawInfo MergedCellInfo; public MergedCellCorners Corners; - public ExcelRangeBase cell; public bool IsPrintTitle; public string range; diff --git a/src/EPPlus/Export/PdfExport/Layout/PdfCellContentLayout.cs b/src/EPPlus.Export.Pdf/Layout/PdfCellContentLayout.cs similarity index 97% rename from src/EPPlus/Export/PdfExport/Layout/PdfCellContentLayout.cs rename to src/EPPlus.Export.Pdf/Layout/PdfCellContentLayout.cs index a1eacdd80f..56ff85d136 100644 --- a/src/EPPlus/Export/PdfExport/Layout/PdfCellContentLayout.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfCellContentLayout.cs @@ -10,10 +10,12 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ +using EPPlus.Export.Pdf.Resources; +using EPPlus.Export.Pdf.Settings; using EPPlus.Fonts.OpenType.Integration; using EPPlus.Graphics; +using OfficeOpenXml; using OfficeOpenXml.Export.PdfExport.Data; -using OfficeOpenXml.Export.PdfExport.Data.Dictionaries; using OfficeOpenXml.Export.PdfExport.TextShaping; using OfficeOpenXml.Interfaces.Fonts; using OfficeOpenXml.Style; @@ -24,7 +26,7 @@ Date Author Change using System.Linq; using Vector2 = EPPlus.Graphics.Geometry.Vector2; -namespace OfficeOpenXml.Export.PdfExport.Layout +namespace EPPlus.Export.Pdf.Layout { [DebuggerDisplay("Content: {Name}")] internal class PdfCellContentLayout : Transform @@ -48,15 +50,6 @@ internal class PdfCellContentLayout : Transform public double RightTextSpillLength = 0d; private double bottomMargin = 3.5d; //Guessed number private double rightMargin = 1.4d; //I guessed this one too.. - //internal static FontMeasurerTrueType fontMeasurerTrueType = new FontMeasurerTrueType(); - //internal static MeasurementFont font = new MeasurementFont(); - - //public List TextFormats { get => textFormats; set => textFormats = value; } - public double TextLength { get => textLength; set => textLength = value; } - public double TextHeight { get => textHeight; set => textHeight = value; } - public TextLayoutEngine TextLayoutEngine { get => textLayoutEngine; set => textLayoutEngine = value; } - - public PdfCellContentLayout(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfCell cell, MergedCellDrawInfo mergedCellInfo, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) : base(x, y-height, width, height, scaleX, scaleY, rotation, parent) diff --git a/src/EPPlus/Export/PdfExport/Layout/PdfCellLayout.cs b/src/EPPlus.Export.Pdf/Layout/PdfCellLayout.cs similarity index 99% rename from src/EPPlus/Export/PdfExport/Layout/PdfCellLayout.cs rename to src/EPPlus.Export.Pdf/Layout/PdfCellLayout.cs index c4b0964f80..83b0f98209 100644 --- a/src/EPPlus/Export/PdfExport/Layout/PdfCellLayout.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfCellLayout.cs @@ -10,18 +10,20 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ +using EPPlus.Export.Pdf.Helpers; +using EPPlus.Export.Pdf.Resources; +using EPPlus.Export.Pdf.Settings; using EPPlus.Graphics; using EPPlus.Graphics.Geometry; +using OfficeOpenXml; using OfficeOpenXml.Export.PdfExport.Data; -using OfficeOpenXml.Export.PdfExport.Data.Dictionaries; -using OfficeOpenXml.Export.PdfExport.Helpers; using OfficeOpenXml.Style; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Linq; -namespace OfficeOpenXml.Export.PdfExport.Layout +namespace EPPlus.Export.Pdf.Layout { [DebuggerDisplay("Cell: {Name}")] internal class PdfCellLayout : Transform diff --git a/src/EPPlus/Export/PdfExport/Layout/PdfGridlinesLayout.cs b/src/EPPlus.Export.Pdf/Layout/PdfGridlinesLayout.cs similarity index 99% rename from src/EPPlus/Export/PdfExport/Layout/PdfGridlinesLayout.cs rename to src/EPPlus.Export.Pdf/Layout/PdfGridlinesLayout.cs index 1dee923f80..8e128fe467 100644 --- a/src/EPPlus/Export/PdfExport/Layout/PdfGridlinesLayout.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfGridlinesLayout.cs @@ -10,10 +10,11 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ +using EPPlus.Export.Pdf.Settings; using OfficeOpenXml.Export.PdfExport.Data; using OfficeOpenXml.Style; -namespace OfficeOpenXml.Export.PdfExport.Layout +namespace EPPlus.Export.Pdf.Layout { internal class GridLine { diff --git a/src/EPPlus/Export/PdfExport/Layout/PdfPageLayout.cs b/src/EPPlus.Export.Pdf/Layout/PdfPageLayout.cs similarity index 92% rename from src/EPPlus/Export/PdfExport/Layout/PdfPageLayout.cs rename to src/EPPlus.Export.Pdf/Layout/PdfPageLayout.cs index efb2fc30bb..8bd756c30f 100644 --- a/src/EPPlus/Export/PdfExport/Layout/PdfPageLayout.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfPageLayout.cs @@ -14,7 +14,7 @@ Date Author Change using System.Collections.Generic; using System.Diagnostics; -namespace OfficeOpenXml.Export.PdfExport.Layout +namespace EPPlus.Export.Pdf.Layout { [DebuggerDisplay("Cell: {Name}")] internal struct PageMap @@ -51,6 +51,50 @@ internal class HorizontalLineRun public int ColEnd; } + internal struct Page + { + public int FromRow; + public int FromColumn; + public int ToRow; + public int ToColumn; + + public bool HasPrintTitle; + public double PrintTitleWidth; + public double PrintTitleHeight; + + public PdfCellCollection Map; + + public PdfHeaderFooterCollection HeaderFooters; + + public Dictionary MergedCells; + public List PrintTitleCells; + public List PrintTitleGridLines; + public List PrintTitleHeadings; + public List SpillCells; + public List PrintTitleBorders; + + public double[] RowHeights; + + public double HeadingWidth; + public double HeadingHeight; + + } + + internal struct Pages + { + public Page[] Page; + public int Width; + public int Height; + public bool IsCommentsPage; + public string HeadingFontName; + public float HeadingFontSize; + public ExcelFill HeadingFill; + public int Count + { + get { return Width * Height; } + } + } + [DebuggerDisplay("{Name}")] internal class PdfPageLayout : Transform { diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/MergedCellCorners.cs b/src/EPPlus.Export.Pdf/PdfCatalog/MergedCellCorners.cs deleted file mode 100644 index 62809e8044..0000000000 --- a/src/EPPlus.Export.Pdf/PdfCatalog/MergedCellCorners.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; - -namespace EPPlus.Export.Pdf.PdfCatalog -{ /// - /// Flags that describe which corners of a merged-cell boundary are owned by - /// a particular layout object on the current page. - /// - /// A corner belongs to a layout object when BOTH of its edges land on the page: - /// TopLeft – fromRow and fromCol are within the page range - /// TopRight – fromRow and toCol are within the page range - /// BottomLeft – toRow and fromCol are within the page range - /// BottomRight – toRow and toCol are within the page range - /// - /// Possible combinations: - /// None – interior cell of a large merge; all four edges are - /// clipped by page boundaries. - /// 1 flag – only one corner of the merge lands on this page. - /// 2 flags – merge is exactly 1 row tall → TopLeft|TopRight or BottomLeft|BottomRight - /// merge is exactly 1 col wide → TopLeft|BottomLeft or TopRight|BottomRight - /// All (4 flags)– non-merged cell, or merged cell fully contained on this page. - /// - [Flags] - internal enum MergedCellCorners - { - None = 0, - TopLeft = 1 << 0, // 1 - TopRight = 1 << 1, // 2 - BottomLeft = 1 << 2, // 4 - BottomRight = 1 << 3, // 8 - All = TopLeft | TopRight | BottomLeft | BottomRight - } -} diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCalculateRowHeight.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfCalculateRowHeight.cs deleted file mode 100644 index 957426be87..0000000000 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCalculateRowHeight.cs +++ /dev/null @@ -1,96 +0,0 @@ -using EPPlus.Graphics.Units; -using OfficeOpenXml.FormulaParsing.Excel.Functions.MathFunctions; -using OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace EPPlus.Export.Pdf.PdfCatalog -{ - internal class PdfCalculateRowHeight - { - public static void ResizeRowHeights(PdfWorksheet pdfSheet) - { - for (int r = 0; r < pdfSheet.Ranges.Count; r++) - { - var range = pdfSheet.Ranges[r]; - ResizeRange(ref range); - pdfSheet.Ranges[r] = range; - } - - if (pdfSheet.CommentsAndNotes.Map != null) - { - var cnRange = pdfSheet.CommentsAndNotes; - ResizeRange(ref cnRange); - pdfSheet.CommentsAndNotes = cnRange; - } - } - - - - public static void ResizeRange(ref PdfRange range) - { - double newTotalHeight = 0d; - - for (int rowIdx = 0; rowIdx < range.RowHeights.Count; rowIdx++) - { - var rowHeight = range.RowHeights[rowIdx]; - - if (rowHeight.Height == 0d) - continue; - - if (!rowHeight.UsesDefaultValue) - { - newTotalHeight += rowHeight.Height; - continue; - } - - int row = range.Range._fromRow + rowIdx; - double maxRequired = rowHeight.Height; - bool hasWrappedCell = false; - - for (int colIdx = 0; colIdx < range.ColWidths.Count; colIdx++) - { - int col = range.Range._fromCol + colIdx; - var cell = range.Map[row, col]; - - if (cell == null || cell.Hidden) - continue; - if (cell.Merged) - continue; - if (cell.ContentAligmnet.ShrinkToFit) - continue; - if (!cell.ContentAligmnet.WrapText) - continue; - if (cell.TextLines == null || cell.TextLines.Count == 0) - continue; - - hasWrappedCell = true; - double required = GetRequiredHeightFromLines(cell); - if (required > maxRequired) - maxRequired = required; - } - - if (hasWrappedCell) - { - rowHeight.Height = maxRequired; - range.RowHeights[rowIdx] = rowHeight; - } - newTotalHeight += rowHeight.Height; - } - range.TotalHeight = newTotalHeight; - - } - - private static double GetRequiredHeightFromLines(PdfCell cell) - { - double total = 0d; - foreach (var line in cell.TextLines) - { - total += line.LargestAscent + line.LargestDescent; - } - return total; - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs deleted file mode 100644 index d1e757ca21..0000000000 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs +++ /dev/null @@ -1,377 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.PdfResources; -using EPPlus.Export.Pdf.PdfSettings; -using EPPlus.Graphics; -using OfficeOpenXml; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; - -namespace EPPlus.Export.Pdf.PdfCatalog -{ - public class PdfCatalog - { - internal PdfDictionaries _dictionaries = new PdfDictionaries(); - private bool _addTextForHeadings = true; - - // - // CONSTRUCTORS FOR MULTIPLE WORKSHEETS AS INPUT - // - - public PdfCatalog(PdfPageSettings pageSettings, ExcelWorkbook workbook) - { - HandleWorksheetCollection(pageSettings, workbook.Worksheets.ToArray()); - } - - public PdfCatalog(PdfPageSettings pageSettings, ExcelWorksheet[] worksheets) - { - HandleWorksheetCollection(pageSettings, worksheets); - } - - public PdfCatalog(PdfPageSettings pageSettings, List worksheets) - { - HandleWorksheetCollection(pageSettings, worksheets.ToArray()); - } - - private void HandleWorksheetCollection(PdfPageSettings pageSettings, ExcelWorksheet[] worksheets) - { - var pdfSheets = GetPdfWorksheets(pageSettings, worksheets); - foreach (var pdfSheet in pdfSheets) - { - ShapeTextInPdfWorksheet(pageSettings, pdfSheet); - PdfCalculateRowHeight.ResizeRowHeights(pdfSheet); - } - var Layout = GetLayout(pageSettings, pdfSheets); - //send Layout to pdf export here. - } - - // - // CONSTRUCTORS FOR SINGLE WORKSHEET AS INPUT - // - - public PdfCatalog(PdfPageSettings pageSettings, ExcelWorksheet worksheet, string fileName) - { - BuildPdf(pageSettings, worksheet, (excelPdf, layout) => - excelPdf.CreatePdf(pageSettings, _dictionaries, layout, fileName)); - } - - public PdfCatalog(PdfPageSettings pageSettings, ExcelWorksheet worksheet, Stream stream) - { - BuildPdf(pageSettings, worksheet, (excelPdf, layout) => - excelPdf.CreatePdf(pageSettings, _dictionaries, layout, stream)); - } - - private void BuildPdf(PdfPageSettings pageSettings, ExcelWorksheet worksheet, Action writePdf) - { - //pageSettings.defaultFontName = worksheet.Workbook.ThemeManager.CurrentTheme.FontScheme.MinorFont[0].Typeface; - pageSettings.defaultFontName = worksheet.Workbook.ThemeManager.GetOrCreateTheme().FontScheme.MinorFont[0].Typeface; - PdfWorksheet pdfSheet = null; - try - { - Stopwatch sw = Stopwatch.StartNew(); - - //Collect Text - pdfSheet = GetPdfWorksheet(pageSettings, worksheet); - sw.Stop(); - var CollectTextTime = sw.ElapsedMilliseconds; - sw.Reset(); - sw.Start(); - - //Shape Text - ShapeTextInPdfWorksheet(pageSettings, pdfSheet); - sw.Stop(); - var ShapeTextTime = sw.ElapsedMilliseconds; - sw.Reset(); - sw.Start(); - - //Auto-Fit Rows - PdfCalculateRowHeight.ResizeRowHeights(pdfSheet); - sw.Stop(); - var AutoFitRowTime = sw.ElapsedMilliseconds; - sw.Reset(); - sw.Start(); - - //Create Layout - var Layout = GetLayout(pageSettings, pdfSheet); - sw.Stop(); - var CreateLayoutTime = sw.ElapsedMilliseconds; - sw.Reset(); - sw.Start(); - - //Create Pdf //Done in pdf export project - ExcelPdf excelPdf = new ExcelPdf(); - writePdf(excelPdf, Layout); - sw.Stop(); - var CreatePdfTime = sw.ElapsedMilliseconds; - sw.Reset(); - } - finally - { - //Clean up the temporary worksheet used to build the comments/notes pages, - //so the source workbook isn't permanently mutated by the PDF export. - if (pdfSheet != null && pdfSheet.CommentsAndNotesSheet != null) - { - worksheet.Workbook.Worksheets.Delete(pdfSheet.CommentsAndNotesSheet); - pdfSheet.CommentsAndNotesSheet = null; - } - } - } - - // - // CONSTRUCTORS FOR RANGE AS INPUT - // - - public PdfCatalog(PdfPageSettings pageSettings, ExcelRangeBase range) - { - PdfWorksheet pdfSheet = GetPdfWorksheet(pageSettings, range); - ShapeTextInPdfWorksheet(pageSettings, pdfSheet); - PdfCalculateRowHeight.ResizeRowHeights(pdfSheet); - var Layout = GetLayout(pageSettings, pdfSheet); - //send Layout to pdf export here. - } - - internal PdfCellCollection GetCellCollectionFromRange(PdfPageSettings pageSettings, ExcelRangeBase range) - { - PdfWorksheet pdfSheet = GetPdfWorksheet(pageSettings, range); - ShapeTextInPdfWorksheet(pageSettings, pdfSheet); - return pdfSheet.Ranges[0].Map; - } - - - //Private Methods - - - //Create Layout Methods - - private Transform GetLayout(PdfPageSettings pageSettings, PdfWorksheet[] pdfSheets) - { - var Layout = PdfLayout.GetLayout(pageSettings, _dictionaries, pdfSheets); - return Layout; - } - - private Transform GetLayout(PdfPageSettings pageSettings, PdfWorksheet pdfSheet) - { - PdfWorksheet[] pdfSheets = new PdfWorksheet[1] { pdfSheet }; - var Layout = PdfLayout.GetLayout(pageSettings, _dictionaries, pdfSheets); - return Layout; - } - - //Shape Text Methods - - internal void ShapeTextInPdfWorksheet(PdfPageSettings pageSettings, PdfWorksheet pdfSheet) - { - // Pass 1: collect text per font - IterateCells(pdfSheet, cell => PdfTextShaper.CollectText(_dictionaries, cell)); - - // Pass 2: build one provider per font - foreach (var kvp in _dictionaries.Fonts) - { - _dictionaries.ShapedProviders[kvp.Key] = kvp.Value.fontSubsetManager.CreateSubsettedProvider(); - } - - // Pass 3: shape text using the pre-built providers - IterateCells(pdfSheet, cell => PdfTextShaper.ShapeText(pageSettings, _dictionaries, cell)); - } - - private void IterateCells(PdfWorksheet pdfSheet, System.Action action) - { - foreach (var range in pdfSheet.Ranges) - { - for (int i = range.Map.FromRow; i <= range.Map.ToRow; i++) - { - for (int j = range.Map.FromColumn; j <= range.Map.ToColumn; j++) - { - action(range.Map[i, j]); - } - } - } - - if (pdfSheet.CommentsAndNotes.Map != null) - { - for (int i = pdfSheet.CommentsAndNotes.Map.FromRow; i <= pdfSheet.CommentsAndNotes.Map.ToRow; i++) - { - for (int j = pdfSheet.CommentsAndNotes.Map.FromColumn; j <= pdfSheet.CommentsAndNotes.Map.ToColumn; j++) - { - action(pdfSheet.CommentsAndNotes.Map[i, j]); - } - } - } - } - - //Collect Text Methods - private PdfWorksheet[] GetPdfWorksheets(PdfPageSettings pageSettings, ExcelWorksheet[] worksheets) - { - PdfWorksheet[] pdfSheets = new PdfWorksheet[worksheets.Length]; - for (int i = 0; i < pdfSheets.Length; i++) - { - pdfSheets[i] = GetPdfWorksheet(pageSettings, worksheets[i]); - } - return pdfSheets; - } - - internal PdfWorksheet GetPdfWorksheet(PdfPageSettings pageSettings, ExcelWorksheet worksheet) - { - PdfWorksheet pdfSheet = new PdfWorksheet(); - pdfSheet.Ranges = new List(); - pdfSheet.Worksheet = worksheet; - pdfSheet.Ranges = GetRanges(pdfSheet.Worksheet); - if (pageSettings.ShowHeadings && _addTextForHeadings) _dictionaries.AddFont(pageSettings, pdfSheet.NormalStyle.Style.Font.Name, pdfSheet.GetSubFamilyFromNormalStyle, "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); - _addTextForHeadings = false; - GetMaps(pageSettings, pdfSheet, pdfSheet.Ranges); - GetPrintTitles(pageSettings, pdfSheet); - GetHeaderFooter(pageSettings, pdfSheet); - GetCommentsAndNotes(pageSettings, pdfSheet); - return pdfSheet; - } - - private PdfWorksheet GetPdfWorksheet(PdfPageSettings pageSettings, ExcelRangeBase excelRange) - { - PdfWorksheet pdfSheet = new PdfWorksheet(); - pdfSheet.Ranges = new List(); - pdfSheet.Worksheet = excelRange.Worksheet; - pdfSheet.Ranges.Add(new PdfRange(excelRange, false)); - if (pageSettings.ShowHeadings && _addTextForHeadings) _dictionaries.AddFont(pageSettings, pdfSheet.NormalStyle.Style.Font.Name, pdfSheet.GetSubFamilyFromNormalStyle, "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); - _addTextForHeadings = false; - pdfSheet.Ranges[0] = GetMaps(pageSettings, pdfSheet, pdfSheet.Ranges[0]); - GetPrintTitles(pageSettings, pdfSheet); - GetHeaderFooter(pageSettings, pdfSheet); - GetCommentsAndNotes(pageSettings, pdfSheet); - return pdfSheet; - } - - private List GetRanges(ExcelWorksheet worksheet) - { - List ranges = new List(); - if (worksheet.Names.ContainsKey("_xlnm.Print_Area")) - { - for (int i = 0; i < worksheet.Names["_xlnm.Print_Area"].Addresses.Count; i++) - { - var range = worksheet.Cells[worksheet.Names["_xlnm.Print_Area"].Addresses[i].Address]; - ranges.Add(new PdfRange(range, false)); - } - } - else - { - var range = worksheet.DimensionByValue; - var pdfRange = new PdfRange(range, true); - pdfRange.ExtendColumns = true; - ranges.Add(pdfRange); - } - return ranges; - } - - private void GetMaps(PdfPageSettings pageSettings, PdfWorksheet pdfSheet, List ranges) - { - for (int i = 0; i < ranges.Count; i++) - { - ranges[i] = GetMaps(pageSettings, pdfSheet, ranges[i]); - } - } - - private PdfRange GetMaps(PdfPageSettings pageSettings, PdfWorksheet pdfSheet, PdfRange range) - { - var temp = range; - temp.Map = PdfTextMap.SetTextMap(pageSettings, _dictionaries, pdfSheet, ref temp); - range = temp; - return range; - } - - private void GetPrintTitles(PdfPageSettings pageSettings, PdfWorksheet pdfSheet) - { - var worksheet = pdfSheet.Worksheet; - // --- Step 1: auto-detect from the worksheet's _xlnm.Print_Titles defined name --- - if (worksheet.Names.ContainsKey("_xlnm.Print_Titles")) - { - var printTitlesName = worksheet.Names["_xlnm.Print_Titles"]; - foreach (var address in printTitlesName.Addresses) - { - // A full-row reference spans every column (e.g. $1:$3 → _toCol == MaxColumns) - if (address._toCol >= ExcelPackage.MaxColumns) - { - pdfSheet.PrintTitleRowFrom = address._fromRow; - pdfSheet.PrintTitleRowTo = address._toRow; - } - // A full-column reference spans every row (e.g. $A:$B → _toRow == MaxRows) - else if (address._toRow >= ExcelPackage.MaxRows) - { - pdfSheet.PrintTitleColFrom = address._fromCol; - pdfSheet.PrintTitleColTo = address._toCol; - } - } - } - // --- Step 2: PdfPageSettings overrides take precedence over the defined name --- - if (pageSettings.RowsToRepeatAtTop != null) - { - pdfSheet.PrintTitleRowFrom = pageSettings.RowsToRepeatAtTop._fromRow; - pdfSheet.PrintTitleRowTo = pageSettings.RowsToRepeatAtTop._toRow; - } - if (pageSettings.ColumnsToRepeatAtLeft != null) - { - pdfSheet.PrintTitleColFrom = pageSettings.ColumnsToRepeatAtLeft._fromCol; - pdfSheet.PrintTitleColTo = pageSettings.ColumnsToRepeatAtLeft._toCol; - } - - // --- Step 3: mark cells so the renderer can identify them instantly --- - foreach (var range in pdfSheet.Ranges) - MarkPrintTitleCells(pdfSheet, range); - } - - private static void MarkPrintTitleCells(PdfWorksheet pdfSheet, PdfRange range) - { - var map = range.Map; - - for (int row = map.FromRow; row <= map.ToRow; row++) - { - bool isTitleRow = pdfSheet.PrintTitleRowFrom >= 0 - && row >= pdfSheet.PrintTitleRowFrom - && row <= pdfSheet.PrintTitleRowTo; - - for (int col = map.FromColumn; col <= map.ToColumn; col++) - { - bool isTitleCol = pdfSheet.PrintTitleColFrom >= 0 - && col >= pdfSheet.PrintTitleColFrom - && col <= pdfSheet.PrintTitleColTo; - - if (!isTitleRow && !isTitleCol) continue; - - var cell = map[row, col]; - if (cell == null) continue; - - if (isTitleRow) cell.IsPrintTitleRow = true; - if (isTitleCol) cell.IsPrintTitleCol = true; - } - } - } - - private void GetHeaderFooter(PdfPageSettings pageSettings, PdfWorksheet pdfSheet) - { - pdfSheet.HeaderFooters = new PdfHeaderFooterCollection(pageSettings, _dictionaries, pdfSheet, pdfSheet.Worksheet.HeaderFooter); - } - - private void GetCommentsAndNotes(PdfPageSettings pageSettings, PdfWorksheet pdfSheet) - { - if (pageSettings.CommentsAndNotes == CommentsAndNotes.AtEndOfSheet && pdfSheet.CommentsAndNotesCollections.Count > 0) - { - var cnPageSettings = new PdfPageSettings(); - cnPageSettings.CommentsAndNotes = CommentsAndNotes.None; - cnPageSettings.ShowHeadings = false; - pdfSheet.CommentsAndNotesSheet = PdfCommentsAndNotes.CreateCommentAndNotesPages(pdfSheet.CommentsAndNotesCollections, pdfSheet.Worksheet); - pdfSheet.CommentsAndNotes = new PdfRange(pdfSheet.CommentsAndNotesSheet.Dimension, false); - pdfSheet.CommentsAndNotes = GetMaps(cnPageSettings, pdfSheet, pdfSheet.CommentsAndNotes); - } - } - } -} \ No newline at end of file diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCell.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfCell.cs deleted file mode 100644 index ef53e49648..0000000000 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCell.cs +++ /dev/null @@ -1,49 +0,0 @@ -using EPPlus.Export.Pdf.PdfLayout; -using EPPlus.Export.Pdf.PdfObjects.PdfFonts; -using EPPlus.Fonts.OpenType.Integration; -using EPPlus.Graphics; -using OfficeOpenXml; -using System.Collections.Generic; - -namespace EPPlus.Export.Pdf.PdfCatalog -{ - internal class PdfCell - { - public string Name { get; set; } - public bool Hidden; - public PdfCellStyle CellStyle; - public PdfCellAlignmentData ContentAligmnet; - public List TextFragments { get; set; } - public List ShapedTexts { get; set; } - public TextLineCollection TextLines { get; set; } - public string Text { get; set; } - - - public double TotalTextLength { get; set; } - public double ColumnWidth { get; set; } - public double Width { get; set; } - public double Height { get; set; } - - public TextLayoutEngine TextLayoutEngine { get; set; } - - public bool Merged; - public PdfCell Main; - public ExcelAddressBase MergedAddress; - - public bool IsPrintTitleRow; - public bool IsPrintTitleCol; - - - - //public int FromCol; - //public int ToCol; - //public int FromRow; - //public int ToRow; - //double x, y, width, height; - //string address; - //int row, col; - //Picute - //kolla på andra map och gör denna lik så att vi kan avnänada samma kod till gridlines - - } -} diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCellCollection.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfCellCollection.cs deleted file mode 100644 index 5cb2bb24fc..0000000000 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCellCollection.cs +++ /dev/null @@ -1,60 +0,0 @@ -using OfficeOpenXml.FormulaParsing.Excel.Functions.Finance; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace EPPlus.Export.Pdf.PdfCatalog -{ - internal class PdfCellCollection - { - private PdfCell[,] Cells; - - public readonly int FromRow; - public readonly int FromColumn; - public readonly int ToRow; - public readonly int ToColumn; - - public PdfCellCollection(int fromRow, int toRow, int fromColumn, int toColumn) - { - if(fromRow > toRow) throw new ArgumentOutOfRangeException("Invalid row range. toRow must be equal or greater than fromRow"); - if(fromColumn > toColumn) throw new ArgumentOutOfRangeException("Invalid column range. toColumn must be equal or greater than fromColumn"); - FromRow = fromRow; - FromColumn = fromColumn; - ToRow = toRow; - ToColumn = toColumn; - int x = toRow - fromRow + 1; - int y = toColumn - fromColumn + 1; - - Cells = new PdfCell[x, y]; - } - - public PdfCell this[int row, int column] - { - get - { - var r = row - FromRow; - var c = column - FromColumn; - if (r < 0 || c < 0 || r >= Cells.GetLength(0) || c >= Cells.GetLength(1)) - { - return null; - } - return Cells[r, c]; - } - set - { - Cells[row - FromRow, column - FromColumn] = value; - } - } - - public PdfCell GetInternal(int row, int column) - { - return Cells[row, column]; - } - - public void SetInternal(int row, int column, PdfCell value) - { - Cells[row, column] = value; - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCommentsAndNotes.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfCommentsAndNotes.cs deleted file mode 100644 index 4568d1f0e6..0000000000 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCommentsAndNotes.cs +++ /dev/null @@ -1,112 +0,0 @@ -using EPPlus.Export.Pdf.PdfLayout; -using EPPlus.Export.Pdf.PdfResources; -using EPPlus.Export.Pdf.PdfSettings; -using OfficeOpenXml; -using OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup; -using OfficeOpenXml.Style; -using OfficeOpenXml.Style.XmlAccess; -using OfficeOpenXml.ThreadedComments; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Xml.Linq; - -namespace EPPlus.Export.Pdf.PdfCatalog -{ - internal class PdfCommentsAndNotes - { - public ExcelComment Comment; - public ExcelThreadedCommentThread ThreadedComment; - public static bool HasThreadedComment = false; - - public PdfCommentsAndNotes(ExcelComment comment) - { - Comment = comment; - } - public PdfCommentsAndNotes(ExcelThreadedCommentThread tComment) - { - ThreadedComment = tComment; - } - - public static ExcelWorksheet CreateCommentAndNotesPages(Dictionary CommentsAndNotesCollections, ExcelWorksheet ws) - { - var ns = ws.Workbook.Styles.GetNormalStyle(); - var tempWS = ws.Workbook.Worksheets.Add("TemporaryWorksheetForCommentsInPdfExporterForEPPlus"); - int row = 1; - int col = 1; - tempWS.Column(col).Width = 10d; - tempWS.Column(col + 1).Width = 75d; - foreach (var commentNote in CommentsAndNotesCollections) - { - AddText(tempWS, row, col, "Cell:", true, ExcelHorizontalAlignment.Right, ExcelVerticalAlignment.Bottom, ns); - AddText(tempWS, row, col + 1, commentNote.Key, false, ExcelHorizontalAlignment.Left, ExcelVerticalAlignment.Bottom, ns); - row++; - if (commentNote.Value.ThreadedComment != null) - { - var CommentReply = "Comment:"; - foreach (var comment in commentNote.Value.ThreadedComment.Comments) - { - AddText(tempWS, row, col, CommentReply, true, ExcelHorizontalAlignment.Right, ExcelVerticalAlignment.Bottom, ns); - AddText(tempWS, row, col + 1, comment.Author.DisplayName, false, ExcelHorizontalAlignment.Left, ExcelVerticalAlignment.Bottom, ns); - row++; - AddText(tempWS, row, col + 1, comment.Text, false, ExcelHorizontalAlignment.Left, ExcelVerticalAlignment.Top, ns); - row++; - AddText(tempWS, row, col + 1, comment.DateCreated.ToString("yyyy-MM-dd HH:mm"), false, ExcelHorizontalAlignment.Left, ExcelVerticalAlignment.Bottom, ns); - row++; - CommentReply = "Reply:"; - } - } - else if (commentNote.Value.Comment != null) - { - var note = commentNote.Value.Comment; - AddText(tempWS, row, col, "Note:", true, ExcelHorizontalAlignment.Right, ExcelVerticalAlignment.Bottom, ns); - AddText(tempWS, row, col + 1, note.Author, false, ExcelHorizontalAlignment.Left, ExcelVerticalAlignment.Bottom, ns); - row++; - AddText(tempWS, row, col + 1, note.RichText, false, ExcelHorizontalAlignment.Left, ExcelVerticalAlignment.Bottom, ns); - row++; - } - row++; - } - return tempWS; - } - private static void AddText(ExcelWorksheet ws, int row, int col, string text, bool bold, ExcelHorizontalAlignment horizontalAlignment, ExcelVerticalAlignment verticalAlignment, ExcelNamedStyleXml ns) - { - var cell = ws.Cells[row, col]; - cell.RichText.Clear(); - var rt = cell.RichText.Add(text); - rt.Bold = bold; - rt.FontName = ns.Style.Font.Name; - rt.Family = ns.Style.Font.Family; - rt.Size = ns.Style.Font.Size; - cell.Style.HorizontalAlignment = horizontalAlignment; - cell.Style.VerticalAlignment = verticalAlignment; - cell.Style.WrapText = true; - } - - private static void AddText(ExcelWorksheet ws, int row, int col, ExcelRichTextCollection RichText, bool bold, ExcelHorizontalAlignment horizontalAlignment, ExcelVerticalAlignment verticalAlignment, ExcelNamedStyleXml ns) - { - var cell = ws.Cells[row, col]; - cell.RichText.Clear(); - for (int i = 1; i < RichText.Count; i++) - { - var rt = RichText[i]; - var trimmedText = rt.Text.Trim(); - var r = cell.RichText.Add(trimmedText); - r.Bold = rt.Bold; - r.Italic = rt.Italic; - r.Color = rt.Color; - r.ColorSettings = rt.ColorSettings; - r.Strike = rt.Strike; - r.UnderLine = rt.UnderLine; - r.UnderLineType = rt.UnderLineType; - r.FontName = rt.FontName; - r.Family = rt.Family; - r.Size = rt.Size; - } - cell.Style.HorizontalAlignment = horizontalAlignment; - cell.Style.VerticalAlignment = verticalAlignment; - cell.Style.WrapText = true; - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfGridlinesLayout.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfGridlinesLayout.cs deleted file mode 100644 index 23b670f41e..0000000000 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfGridlinesLayout.cs +++ /dev/null @@ -1,364 +0,0 @@ -using EPPlus.Export.Pdf.PdfLayout; -using EPPlus.Export.Pdf.PdfSettings; -using OfficeOpenXml.Style; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace EPPlus.Export.Pdf.PdfCatalog -{ - internal class PdfGridlinesLayout - { - //------------------------------------------------------------------------------------------ - // Gridline generation - // ----------------------------------------------------------------------------------------- - - /// - /// Populates .GridLines and .BorderLines for one page. - /// - /// Algorithm: - /// 1. Precompute column X positions and row Y positions from the page map / row-height array. - /// 2. Walk every cell and mark which vertical column-gaps are suppressed by text spill. - /// Spill direction depends on horizontal alignment; spill stops when a destination cell - /// has content. Multi-column spill is handled by walking until either the text runs - /// out or a non-empty cell is found. - /// 3. Emit vertical gridline segments: for each column gap, accumulate contiguous rows - /// that are not blocked by spill, a cell border, or a merged cell spanning the gap. - /// 4. Emit horizontal gridline segments: for each row gap, accumulate contiguous columns - /// that are not blocked by a cell border or a merged cell spanning the gap. - /// 5. Add the four outer-frame lines to BorderLines. - /// - public static void AddGridLines(PdfPageSettings pageSettings, Page page, PdfPageLayout pageLayout, bool borderOnly = false) - { - int rowCount = page.ToRow - page.FromRow + 1; - int colCount = page.ToColumn - page.FromColumn + 1; - - if (rowCount <= 0 || colCount <= 0) return; - - // --- 1. Position arrays --------------------------------------------------------------- - - // colX[ci] = X of left edge of column ci (0-based within page). - // colX[colCount] = X of right edge of last column. - var colX = new double[colCount + 1]; - colX[0] = pageSettings.ContentBounds.Left + page.HeadingWidth + page.PrintTitleWidth; - for (int ci = 0; ci < colCount; ci++) - { - var cell = page.Map[page.FromRow, page.FromColumn + ci]; - colX[ci + 1] = colX[ci] + (cell?.ColumnWidth ?? 0d); - } - - // rowY[ri] = Y of top edge of row ri (0-based within page). - // rowY[rowCount] = Y of bottom edge of last row. - // Y decreases downward (PDF coordinate system used throughout GetCatalog). - var rowY = new double[rowCount + 1]; - rowY[0] = pageSettings.ContentBounds.Top - page.HeadingHeight - page.PrintTitleHeight; - - for (int ri = 0; ri < rowCount; ri++) - { - rowY[ri + 1] = rowY[ri] - page.RowHeights[ri]; - } - - // --- 5. Outer frame ------------------------------------------------------------------ - // Always computed so BorderLines is available for margin clipping regardless of - // whether ShowGridLines is on. When borderOnly is true we stop here. - - double frameLeft = pageSettings.ContentBounds.Left; //colX[0]; - double frameRight = colX[colCount]; - double frameTop = pageSettings.ContentBounds.Top; //rowY[0]; - double frameBottom = rowY[rowCount]; - - pageLayout.BorderLines.Add(new GridLine(frameLeft, frameTop, frameRight, frameTop)); - pageLayout.BorderLines.Add(new GridLine(frameLeft, frameBottom, frameRight, frameBottom)); - pageLayout.BorderLines.Add(new GridLine(frameLeft, frameTop, frameLeft, frameBottom)); - pageLayout.BorderLines.Add(new GridLine(frameRight, frameTop, frameRight, frameBottom)); - - if (borderOnly) return; - - // --- 2. Text-spill mask --------------------------------------------------------------- - - // spillBlocked[ri, gi] = true means the vertical line at colX[gi+1] for row ri - // must be suppressed because cell text from this row spills across that gap. - // gi ranges 0 .. colCount-2. - var spillBlocked = new bool[rowCount, colCount - 1]; - - for (int ri = 0; ri < rowCount; ri++) - { - int row = page.FromRow + ri; - for (int ci = 0; ci < colCount; ci++) - { - int col = page.FromColumn + ci; - var cell = page.Map[row, col]; - - if (cell == null || cell.Hidden || (cell.ContentAligmnet != null && cell.ContentAligmnet.WrapText)) continue; - // Merged cells never spill — their text is clipped to the merged region. - // The vertical lines around the merge are handled by MergedAcross checks below. - if (cell.Merged) continue; - - double spill = cell.TotalTextLength - cell.ColumnWidth; - if (spill <= 0d) continue; - - var halign = cell.ContentAligmnet.HorizontalAlignment; - - // Right spill — Left / General alignment. - if (halign == ExcelHorizontalAlignment.Left || - halign == ExcelHorizontalAlignment.General) - { - MarkSpillRight(page, ri, ci, colCount, colX, spill, spillBlocked); - - } - - // Left spill — Right alignment. - else if (halign == ExcelHorizontalAlignment.Right) - { - MarkSpillLeft(page, ri, ci, colX, spill, spillBlocked); - } - // Both directions — Center alignment, half the excess each way. - else if (halign == ExcelHorizontalAlignment.Center) - { - double halfSpill = spill / 2d; - MarkSpillRight(page, ri, ci, colCount, colX, halfSpill, spillBlocked); - MarkSpillLeft(page, ri, ci, colX, halfSpill, spillBlocked); - - } - } - } - - // --- 3. Vertical gridlines (one per internal column gap) ----------------------------- - - for (int gi = 0; gi < colCount - 1; gi++) - { - double x = colX[gi + 1]; - int leftCol = page.FromColumn + gi; - int rightCol = leftCol + 1; - - double? runStart = null; - double runEnd = 0d; - - for (int ri = 0; ri < rowCount; ri++) - { - int row = page.FromRow + ri; - - // Hidden rows have zero height — no segment to draw. - if (page.RowHeights[ri] == 0d) - { - FlushVertical(pageLayout, x, ref runStart, runEnd); - continue; - } - - var leftCell = page.Map[row, leftCol]; - var rightCell = page.Map[row, rightCol]; - - bool blocked = - spillBlocked[ri, gi] || - MergedAcross(leftCell, leftCol) || // left cell's merge extends right past this gap - MergedAcross(rightCell, rightCol, fromLeft: true) || // right cell's merge came from the left - HasRightBorder(leftCell) || - HasLeftBorder(rightCell); - - if (!blocked) - { - if (runStart == null) runStart = rowY[ri]; - runEnd = rowY[ri + 1]; - } - else - { - FlushVertical(pageLayout, x, ref runStart, runEnd); - } - } - FlushVertical(pageLayout, x, ref runStart, runEnd); - } - - // --- 4. Horizontal gridlines (one per internal row gap) ------------------------------ - - for (int ri = 0; ri < rowCount - 1; ri++) - { - double y = rowY[ri + 1]; - int topRow = page.FromRow + ri; - int bottomRow = topRow + 1; - - double? runStart = null; - double runEnd = 0d; - - for (int ci = 0; ci < colCount; ci++) - { - int col = page.FromColumn + ci; - - // Hidden columns have zero width — no segment to draw. - if (colX[ci + 1] - colX[ci] == 0d) - { - FlushHorizontal(pageLayout, y, ref runStart, runEnd); - continue; - } - - var topCell = page.Map[topRow, col]; - var bottomCell = page.Map[bottomRow, col]; - - bool blocked = - MergedDown(topCell, topRow) || // top cell's merge extends down past this gap - MergedDown(bottomCell, bottomRow, fromAbove: true) || // bottom cell's merge came from above - HasBottomBorder(topCell) || - HasTopBorder(bottomCell); - - if (!blocked) - { - if (runStart == null) runStart = colX[ci]; - runEnd = colX[ci + 1]; - } - else - { - FlushHorizontal(pageLayout, y, ref runStart, runEnd); - } - } - FlushHorizontal(pageLayout, y, ref runStart, runEnd); - } - } - - // ---- Spill helpers ---------------------------------------------------------------------- - - /// - /// Marks column gaps to the right of column as spill-blocked for - /// row , until the spill budget runs out or a non-empty cell stops it. - /// - private static void MarkSpillRight( - Page page, int ri, int ci, int colCount, - double[] colX, double spill, bool[,] spillBlocked) - { - int row = page.FromRow + ri; - // Walk gaps gi = ci, ci+1, ... (gap gi lies between column gi and column gi+1). - for (int gi = ci; gi < colCount - 1; gi++) - { - // The cell immediately to the right of this gap stops the spill if it has text. - var rightCell = page.Map[row, page.FromColumn + gi + 1]; - if (rightCell != null && !string.IsNullOrEmpty(rightCell.Text)) break; - - // Distance from the right edge of the source cell to this gap. - double distToGap = colX[gi + 1] - colX[ci + 1]; - if (spill > distToGap) - spillBlocked[ri, gi] = true; - else - break; - } - } - - /// - /// Marks column gaps to the left of column as spill-blocked for - /// row , until the spill budget runs out or a non-empty cell stops it. - /// - private static void MarkSpillLeft( - Page page, int ri, int ci, - double[] colX, double spill, bool[,] spillBlocked) - { - int row = page.FromRow + ri; - // Walk gaps gi = ci-1, ci-2, ... (gap gi lies between column gi and column gi+1). - for (int gi = ci - 1; gi >= 0; gi--) - { - // The cell immediately to the left of this gap stops the spill if it has text. - var leftCell = page.Map[row, page.FromColumn + gi]; - if (leftCell != null && !string.IsNullOrEmpty(leftCell.Text)) break; - - // Distance from the left edge of the source cell to this gap. - double distToGap = colX[ci] - colX[gi + 1]; - if (spill > distToGap) - spillBlocked[ri, gi] = true; - else - break; - } - } - - // ---- Run-flush helpers ------------------------------------------------------------------ - - private static void FlushVertical(PdfPageLayout layout, double x, ref double? runStart, double runEnd) - { - if (runStart == null) return; - layout.GridLines.Add(new GridLine(x, runStart.Value, x, runEnd)); - runStart = null; - } - - private static void FlushHorizontal(PdfPageLayout layout, double y, ref double? runStart, double runEnd) - { - if (runStart == null) return; - layout.GridLines.Add(new GridLine(runStart.Value, y, runEnd, y)); - runStart = null; - } - - // ---- Merge-span helpers ----------------------------------------------------------------- - - /// - /// Returns true when is part of a merged region that spans across - /// the vertical gap adjacent to . - /// - /// = false (default): cell is the LEFT neighbour of the gap — - /// block if the merge extends to the right (toCol > col). - /// - /// - /// = true: cell is the RIGHT neighbour of the gap — - /// block if the merge started to the left (fromCol < col). - /// - /// - private static bool MergedAcross(PdfCell cell, int col, bool fromLeft = false) - { - if (cell == null || !cell.Merged || cell.MergedAddress == null) return false; - return fromLeft - ? cell.MergedAddress._fromCol < col - : cell.MergedAddress._toCol > col; - } - - /// - /// Returns true when is part of a merged region that spans across - /// the horizontal gap adjacent to . - /// - /// = false (default): cell is the TOP neighbour of the gap — - /// block if the merge extends downward (toRow > row). - /// - /// - /// = true: cell is the BOTTOM neighbour of the gap — - /// block if the merge started above (fromRow < row). - /// - /// - private static bool MergedDown(PdfCell cell, int row, bool fromAbove = false) - { - if (cell == null || !cell.Merged || cell.MergedAddress == null) return false; - return fromAbove - ? cell.MergedAddress._fromRow < row - : cell.MergedAddress._toRow > row; - } - - // ---- Border presence helpers ------------------------------------------------------------ - // For merged cells, use the main cell's style (which holds the region's outer borders). - - private static bool HasRightBorder(PdfCell cell) - { - var cs = (cell?.Merged == true && cell.Main != null) ? cell.Main.CellStyle : cell?.CellStyle; - if (cs == null) return false; - return (cs.xfRight != null && cs.xfRight.Style != ExcelBorderStyle.None) || - (cs.dxfRight?.HasValue ?? false); - - } - - private static bool HasLeftBorder(PdfCell cell) - { - var cs = (cell?.Merged == true && cell.Main != null) ? cell.Main.CellStyle : cell?.CellStyle; - if (cs == null) return false; - return (cs.xfLeft != null && cs.xfLeft.Style != ExcelBorderStyle.None) || - (cs.dxfLeft?.HasValue ?? false); - } - - private static bool HasTopBorder(PdfCell cell) - { - var cs = (cell?.Merged == true && cell.Main != null) ? cell.Main.CellStyle : cell?.CellStyle; - if (cs == null) return false; - return (cs.xfTop != null && cs.xfTop.Style != ExcelBorderStyle.None) || - (cs.dxfTop?.HasValue ?? false); - } - - private static bool HasBottomBorder(PdfCell cell) - { - var cs = (cell?.Merged == true && cell.Main != null) ? cell.Main.CellStyle : cell?.CellStyle; - if (cs == null) return false; - return (cs.xfBottom != null && cs.xfBottom.Style != ExcelBorderStyle.None) || - (cs.dxfBottom?.HasValue ?? false); - } - - } -} diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfHeaderFooter.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfHeaderFooter.cs deleted file mode 100644 index 034cdbff2d..0000000000 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfHeaderFooter.cs +++ /dev/null @@ -1,49 +0,0 @@ -using EPPlus.Export.Pdf.PdfLayout; -using EPPlus.Fonts.OpenType.Integration; -using System.Collections.Generic; - -namespace EPPlus.Export.Pdf.PdfCatalog -{ - public enum HeaderFooterType - { - First = 0, - Odd = 1, - Even = 2 - } - - public enum HeaderFooterAlignment - { - Left = 0, - Center = 1, - Right = 2 - } - - public enum HeaderFooterSection - { - Header = 0, - Footer = 1 - } - - internal class PdfHeaderFooter - { - public HeaderFooterType PageType; - public HeaderFooterAlignment Alignment; - public HeaderFooterSection Section; - - public PdfCell Content; - - public List NumberOfPagesIndexes = new List(); - public List PageNumberIndexes = new List(); - - public PdfHeaderFooter(List textFormats, List pageNumberIndexes, List numberOfPagesIndexes, HeaderFooterType type, HeaderFooterAlignment alignment, HeaderFooterSection section) - { - Content = new PdfCell(); - Content.TextFragments = textFormats; - PageNumberIndexes = pageNumberIndexes; - NumberOfPagesIndexes = numberOfPagesIndexes; - PageType = type; - Alignment = alignment; - Section = section; - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfHeaderFooterCollection.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfHeaderFooterCollection.cs deleted file mode 100644 index d21d13049b..0000000000 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfHeaderFooterCollection.cs +++ /dev/null @@ -1,152 +0,0 @@ -using EPPlus.Export.Pdf.PdfResources; -using EPPlus.Export.Pdf.PdfSettings; -using OfficeOpenXml; -using System.Collections.Generic; -using System.Linq; - -namespace EPPlus.Export.Pdf.PdfCatalog -{ - internal class PdfHeaderFooterCollection - { - public List PdfHeaderFooterEntries = new List(); - public bool ScaleWithDocument = false; - public bool AlignWithMargins = false; - - public PdfHeaderFooterCollection(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfWorksheet pdfSheet, ExcelHeaderFooter headerFooter) - { - bool differentFirst = pdfSheet.Worksheet.HeaderFooter.differentFirst; - bool differentOddEven = pdfSheet.Worksheet.HeaderFooter.differentOddEven; - bool AlignWithMargins = pdfSheet.Worksheet.HeaderFooter.AlignWithMargins; - bool ScaleWithDocument = pdfSheet.Worksheet.HeaderFooter.ScaleWithDocument; - PdfHeaderFooter entry = null; - if (differentFirst) - { - //First Header - entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.FirstHeader.LeftAligned, HeaderFooterType.First, HeaderFooterAlignment.Left, HeaderFooterSection.Header); - if (entry != null) - { - entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); - PdfHeaderFooterEntries.Add(entry); - } - entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.FirstHeader.Centered, HeaderFooterType.First, HeaderFooterAlignment.Center, HeaderFooterSection.Header); - if (entry != null) - { - entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); - PdfHeaderFooterEntries.Add(entry); - } - entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.FirstHeader.RightAligned, HeaderFooterType.First, HeaderFooterAlignment.Right, HeaderFooterSection.Header); - if (entry != null) - { - entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); - PdfHeaderFooterEntries.Add(entry); - } - //First Footer - entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.FirstFooter.LeftAligned, HeaderFooterType.First, HeaderFooterAlignment.Left, HeaderFooterSection.Footer); - if (entry != null) - { - entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); - PdfHeaderFooterEntries.Add(entry); - } - entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.FirstFooter.Centered, HeaderFooterType.First, HeaderFooterAlignment.Center, HeaderFooterSection.Footer); - if (entry != null) - { - entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); - PdfHeaderFooterEntries.Add(entry); - } - entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.FirstFooter.RightAligned, HeaderFooterType.First, HeaderFooterAlignment.Right, HeaderFooterSection.Footer); - if (entry != null) - { - entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); - PdfHeaderFooterEntries.Add(entry); - } - } - if (differentOddEven) - { - //Even Header - entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.EvenHeader.LeftAligned, HeaderFooterType.Even, HeaderFooterAlignment.Left, HeaderFooterSection.Header); - if (entry != null) - { - entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); - PdfHeaderFooterEntries.Add(entry); - } - entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.EvenHeader.Centered, HeaderFooterType.Even, HeaderFooterAlignment.Center, HeaderFooterSection.Header); - if (entry != null) - { - entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); - PdfHeaderFooterEntries.Add(entry); - } - entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.EvenHeader.RightAligned, HeaderFooterType.Even, HeaderFooterAlignment.Right, HeaderFooterSection.Header); - if (entry != null) - { - entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); - PdfHeaderFooterEntries.Add(entry); - } - //Even Footer - entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.EvenFooter.LeftAligned, HeaderFooterType.Even, HeaderFooterAlignment.Left, HeaderFooterSection.Footer); - if (entry != null) - { - entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); - PdfHeaderFooterEntries.Add(entry); - } - entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.EvenFooter.Centered, HeaderFooterType.Even, HeaderFooterAlignment.Center, HeaderFooterSection.Footer); - if (entry != null) - { - entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); - PdfHeaderFooterEntries.Add(entry); - } - entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.EvenFooter.RightAligned, HeaderFooterType.Even, HeaderFooterAlignment.Right, HeaderFooterSection.Footer); - if (entry != null) - { - entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); - PdfHeaderFooterEntries.Add(entry); - } - } - //Odd Header - entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.OddHeader.LeftAligned, HeaderFooterType.Odd, HeaderFooterAlignment.Left, HeaderFooterSection.Header); - if (entry != null) - { - entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); - PdfHeaderFooterEntries.Add(entry); - } - entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.OddHeader.Centered, HeaderFooterType.Odd, HeaderFooterAlignment.Center, HeaderFooterSection.Header); - if (entry != null) - { - entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); - PdfHeaderFooterEntries.Add(entry); - } - entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.OddHeader.RightAligned, HeaderFooterType.Odd, HeaderFooterAlignment.Right, HeaderFooterSection.Header); - if (entry != null) - { - entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); - PdfHeaderFooterEntries.Add(entry); - } - //Odd Footer - entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.OddFooter.LeftAligned, HeaderFooterType.Odd, HeaderFooterAlignment.Left, HeaderFooterSection.Footer); - if (entry != null) - { - entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); - PdfHeaderFooterEntries.Add(entry); - } - entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.OddFooter.Centered, HeaderFooterType.Odd, HeaderFooterAlignment.Center, HeaderFooterSection.Footer); - if (entry != null) - { - entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); - PdfHeaderFooterEntries.Add(entry); - } - entry = PdfTextMap.GetTextFormats(pageSettings, dictionaries, pdfSheet.Worksheet, headerFooter.OddFooter.RightAligned, HeaderFooterType.Odd, HeaderFooterAlignment.Right, HeaderFooterSection.Footer); - if (entry != null) - { - entry.Content.ContentAligmnet = PdfTextMap.GetAlignmentData(entry); - PdfHeaderFooterEntries.Add(entry); - } - } - - public PdfHeaderFooter Get(HeaderFooterType type, HeaderFooterSection section, HeaderFooterAlignment alignment) - { - return PdfHeaderFooterEntries.FirstOrDefault(e => - e.PageType == type && - e.Section == section && - e.Alignment == alignment); - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs deleted file mode 100644 index 095dc38731..0000000000 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs +++ /dev/null @@ -1,1788 +0,0 @@ -using EPPlus.Export.Pdf.PdfLayout; -using EPPlus.Export.Pdf.PdfResources; -using EPPlus.Export.Pdf.PdfSettings; -using EPPlus.Fonts.OpenType.Integration; -using EPPlus.Fonts.OpenType.Integration.DataHolders; -using EPPlus.Graphics; -using OfficeOpenXml; -using OfficeOpenXml.ConditionalFormatting; -using OfficeOpenXml.Interfaces.Fonts; -using OfficeOpenXml.Style; -using System; -using System.Collections.Generic; -using System.Diagnostics; - -namespace EPPlus.Export.Pdf.PdfCatalog -{ - - internal struct MergedCellDrawInfo - { - public double X; - public double Y; - public double Width; - public double Height; - } - - internal struct PrintTitleCellDraw - { - public PdfCell Cell; - public double X; - public double Y; - public double Width; - public double Height; - public double ClipX; // text clip (spill bound) - public double ClipY; - public double ClipWidth; - public double ClipHeight; - } - - internal struct PrintTitleHeadingDraw - { - public bool IsRow; // true = row-number heading (left strip); false = column-letter (top strip) - public int Index; // original absolute row/column index — the label source - public double X; - public double Y; - public double Width; - public double Height; - } - - internal struct SpillCellDraw - { - public PdfCell Cell; - public double X, Y, Width, Height; // source cell's true position (off-window) - public double ClipX, ClipY, ClipWidth, ClipHeight; // visible slice on this page - public bool IsPrintTitle; - } - - internal struct Page - { - public int FromRow; - public int FromColumn; - public int ToRow; - public int ToColumn; - - public bool HasPrintTitle; - public double PrintTitleWidth; - public double PrintTitleHeight; - - public PdfCellCollection Map; - - public PdfHeaderFooterCollection HeaderFooters; - - public Dictionary MergedCells; - public List PrintTitleCells; - public List PrintTitleGridLines; - public List PrintTitleHeadings; - public List SpillCells; - public List PrintTitleBorders; - - public double[] RowHeights; - - public double HeadingWidth; - public double HeadingHeight; - - } - - internal struct Pages - { - public Page[] Page; - public int Width; - public int Height; - public bool IsCommentsPage; - public string HeadingFontName; - public float HeadingFontSize; - public ExcelFill HeadingFill; - public int Count - { - get { return Width * Height; } - } - } - - internal class PdfLayout - { - private const double rowHeadingWith1CharWidth = 18d; - - public static Transform GetLayout(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfWorksheet[] pdfSheets) - { - var PagesCollection = GetPages(pageSettings, pdfSheets); - var Catalog = GetCatalog(pageSettings, dictionaries, PagesCollection); - return Catalog; - } - - /*Header and footer notes: - * First header/footer is only used on the first worksheets first page if it exsists - * Then we use each worksheets odd and even respectively. worksheet 1 has its set of odd and even we use and worksheet 2 has it's own set we will use - * Page number does not reset and total number of pages is across all worksheets pages - * starting page number does not affect if first header/footer is used or not. - */ - - internal static Transform GetCatalog(PdfPageSettings pageSettings, PdfDictionaries dictionaries, List pdfPages) - { - Transform Catalog = new Transform(0d, 0d, 0d, 0d); - int totalPages = GetTotalPages(pdfPages); - for (int i = 0; i < pdfPages.Count; i++) - { - - var pages = pdfPages[i].Page; - int pageNumber = pageSettings.FirstPageNumber; - - for (int j = 0; j < pages.Length; j++) - { - var page = pages[j]; - PdfPageLayout pageLayout = new PdfPageLayout(0d, 0d, 0d, 0d); - pageLayout.isCommentsPage = pdfPages[i].IsCommentsPage; - pageLayout.HeadingWidth = page.HeadingWidth; - pageLayout.HeadingHeight = page.HeadingHeight; - pageLayout.PrintTitleWidth = page.PrintTitleWidth; - pageLayout.PrintTitleHeight = page.PrintTitleHeight; - var drawnMergedCells = new HashSet(); - //var drawnMergedCellsText = new HashSet(); - //PdfContentLayout contentLayout = new PdfContentLayout(0d, 0d, pageSettings.ContentBounds); - //pageLayout.AddChild(contentLayout); - double contentStartX = pageSettings.ContentBounds.Left + page.HeadingWidth + page.PrintTitleWidth; - double contentStartY = pageSettings.ContentBounds.Top - page.HeadingHeight - page.PrintTitleHeight; - if (pageSettings.ShowHeadings && !pdfPages[i].IsCommentsPage) - { - AddHeadingCells(pageSettings, dictionaries, page, pageLayout, contentStartX, contentStartY, page.HeadingWidth, page.HeadingHeight, pdfPages[i].HeadingFontName, pdfPages[i].HeadingFontSize, pdfPages[i].HeadingFill); - AddPrintTitleHeadings(pageSettings, dictionaries, page, pageLayout, pdfPages[i].HeadingFontName, pdfPages[i].HeadingFontSize, pdfPages[i].HeadingFill); - AddSpillCells(pageSettings, dictionaries, page, pageLayout); - } - AddPrintTitleCells(pageSettings, dictionaries, page, pageLayout); - - double y = contentStartY;//pageSettings.ContentBounds.Top; - double x = contentStartX;//pageSettings.ContentBounds.Left; - //create cells & headings if exsists - - for (int row = pages[j].FromRow; row <= pages[j].ToRow; row++) - { - double rowHeight = pages[j].RowHeights[row - pages[j].FromRow]; - for (int col = pages[j].FromColumn; col <= pages[j].ToColumn; col++) - { - var map = pages[j].Map[row, col]; - MergedCellDrawInfo info = new MergedCellDrawInfo(); - //Merged Cell - if (map.Merged) - { - string key = map.MergedAddress.Address; - if (!drawnMergedCells.Contains(key) && - pages[j].MergedCells.TryGetValue(key, out info)) - { - //Fill - var cellStyle = map.Main?.CellStyle ?? map.CellStyle; - var fill = new PdfCellLayout(dictionaries, cellStyle, - info.X, info.Y, info.Width, info.Height); - fill.Name = map.Name; - fill.UpdateShadingPositionMatrix(pageSettings); - pageLayout.AddChild(fill); - //Text - var sourceMap = (map.TextLines != null && map.TextLines.Count > 0) ? map : (map.Main != null && map.Main.TextLines != null && map.Main.TextLines.Count > 0) ? map.Main : null; - if (sourceMap != null) - { - var text = new PdfCellContentLayout(pageSettings, dictionaries, sourceMap, info, info.X, info.Y, info.Width, info.Height); - text.Name = map.Name; - text.GidsAndCharMap(dictionaries); - text.SetupClipping(info.X, info.Y, info.Width, info.Height); - pageLayout.AddChild(text); - } - if (map.Main != null) // map.Main != null → this is NOT the top-left cell - { - var mergeMainStyle = map.Main.CellStyle; - if (HasDiagonalBorder(mergeMainStyle)) - { - var diagBorder = new PdfCellBorderLayout( - mergeMainStyle, - isMerged: false, // use X/Y/W/H path in renderer, not info.* - corners: MergedCellCorners.All, - info: info, - x: info.X, // virtual full-merge top-left X - y: info.Y, // virtual full-merge top Y - width: info.Width, // full merge width - height: info.Height); // full merge height - diagBorder.Name = map.Name; - // Suppress edge borders — this layout exists only for the diagonal - diagBorder.BorderData.Top.BorderStyle = ExcelBorderStyle.None; - diagBorder.BorderData.Bottom.BorderStyle = ExcelBorderStyle.None; - diagBorder.BorderData.Left.BorderStyle = ExcelBorderStyle.None; - diagBorder.BorderData.Right.BorderStyle = ExcelBorderStyle.None; - pageLayout.AddChild(diagBorder); - } - } - drawnMergedCells.Add(key); - } - } - else - { - //Fill - var fill = new PdfCellLayout(dictionaries, map.CellStyle, x, y, map.ColumnWidth, rowHeight); - fill.UpdateShadingPositionMatrix(pageSettings); - fill.Name = map.Name; - pageLayout.AddChild(fill); - //Text - if (map.TextLines != null && map.TextLines.Count > 0) - { - var text = new PdfCellContentLayout(pageSettings, dictionaries, map, info, x, y, map.ColumnWidth, rowHeight); - text.Name = map.Name; - text.GidsAndCharMap(dictionaries); - if (NeedsClipping(map, pages[j], row, col)) - text.SetupClipping(x, y, map.ColumnWidth, rowHeight); - pageLayout.AddChild(text); - } - } - //Border - var borderStyle = (map.Merged && map.Main != null) ? map.Main.CellStyle : map.CellStyle; - if (HasBorder(map.CellStyle)) - { - var border = new PdfCellBorderLayout(map.CellStyle, map.Merged, GetCorners(map.MergedAddress, row, col), info, x, y, map.ColumnWidth, rowHeight); - border.Name = map.Name; - if (map.Merged && map.MergedAddress != null) - { - var addr = map.MergedAddress; - if (row != addr.Start.Row) border.BorderData.Top.BorderStyle = ExcelBorderStyle.None; - if (row != addr.End.Row) border.BorderData.Bottom.BorderStyle = ExcelBorderStyle.None; - if (col != addr.Start.Column) border.BorderData.Left.BorderStyle = ExcelBorderStyle.None; - if (col != addr.End.Column) border.BorderData.Right.BorderStyle = ExcelBorderStyle.None; - } - pageLayout.AddChild(border); - } - x += map.ColumnWidth; - } - y -= rowHeight; - x = contentStartX; //pageSettings.ContentBounds.Left; - } - if (page.HeaderFooters != null) - { - bool isVeryFirstPage = (i == 0 && j == 0); - var hfType = isVeryFirstPage ? HeaderFooterType.First : (pageNumber % 2 == 0 ? HeaderFooterType.Even : HeaderFooterType.Odd); - var leftH = page.HeaderFooters.Get(hfType, HeaderFooterSection.Header, HeaderFooterAlignment.Left); - if (leftH != null) - { - SubstitutePageNumbers(pageSettings, dictionaries, leftH, pageNumber, totalPages); - var ascent = leftH.Content.TextLines[0].LargestAscent; - var hfx = pageSettings.Margins.LeftPu; - var hfy = pageSettings.PageSize.HeightPu - pageSettings.Margins.HeaderPu - ascent; - var text = new PdfCellContentLayout(pageSettings, dictionaries, leftH, hfx, hfy, 0, 0); - text.Name = "LeftHeader"; - text.IsHeaderFooter = true; - text.GidsAndCharMap(dictionaries); - pageLayout.AddChild(text); - } - var centerH = page.HeaderFooters.Get(hfType, HeaderFooterSection.Header, HeaderFooterAlignment.Center); - if (centerH != null) - { - SubstitutePageNumbers(pageSettings, dictionaries, centerH, pageNumber, totalPages); - var ascent = centerH.Content.TextLines[0].LargestAscent; - var hfx = pageSettings.Margins.LeftPu; - var hfy = pageSettings.PageSize.HeightPu - pageSettings.Margins.HeaderPu - ascent; - var hfWidth = pageSettings.PageSize.WidthPu - pageSettings.Margins.LeftPu - pageSettings.Margins.RightPu; - var text = new PdfCellContentLayout(pageSettings, dictionaries, centerH, hfx, hfy, hfWidth, 0); - text.Name = "CenterHeader"; - text.IsHeaderFooter = true; - text.GidsAndCharMap(dictionaries); - pageLayout.AddChild(text); - } - var rightH = page.HeaderFooters.Get(hfType, HeaderFooterSection.Header, HeaderFooterAlignment.Right); - if (rightH != null) - { - SubstitutePageNumbers(pageSettings, dictionaries, rightH, pageNumber, totalPages); - var ascent = rightH.Content.TextLines[0].LargestAscent; - var hfx = pageSettings.PageSize.WidthPu - pageSettings.Margins.RightPu; - var hfy = pageSettings.PageSize.HeightPu - pageSettings.Margins.HeaderPu - ascent; - var text = new PdfCellContentLayout(pageSettings, dictionaries, rightH, hfx, hfy, 0, 0); - text.Name = "RightHeader"; - text.IsHeaderFooter = true; - text.GidsAndCharMap(dictionaries); - pageLayout.AddChild(text); - } - var leftF = page.HeaderFooters.Get(hfType, HeaderFooterSection.Footer, HeaderFooterAlignment.Left); - if (leftF != null) - { - SubstitutePageNumbers(pageSettings, dictionaries, leftF, pageNumber, totalPages); - int last = leftF.Content.TextLines.Count - 1; - var descent = leftF.Content.TextLines[last].LargestDescent; - var hfx = pageSettings.Margins.LeftPu; - var hfy = pageSettings.Margins.FooterPu + descent; - var text = new PdfCellContentLayout(pageSettings, dictionaries, leftF, hfx, hfy, 0, 0); - text.Name = "LeftFooter"; - text.IsHeaderFooter = true; - text.GidsAndCharMap(dictionaries); - pageLayout.AddChild(text); - } - var centerF = page.HeaderFooters.Get(hfType, HeaderFooterSection.Footer, HeaderFooterAlignment.Center); - if (centerF != null) - { - SubstitutePageNumbers(pageSettings, dictionaries, centerF, pageNumber, totalPages); - int last = centerF.Content.TextLines.Count - 1; - var descent = centerF.Content.TextLines[last].LargestDescent; - var hfx = pageSettings.PageSize.WidthPu / 2d; - var hfy = pageSettings.Margins.FooterPu + descent; - var text = new PdfCellContentLayout(pageSettings, dictionaries, centerF, hfx, hfy, 0, 0); - text.Name = "CenterFooter"; - text.IsHeaderFooter = true; - text.GidsAndCharMap(dictionaries); - pageLayout.AddChild(text); - } - var rightF = page.HeaderFooters.Get(hfType, HeaderFooterSection.Footer, HeaderFooterAlignment.Right); - if (rightF != null) - { - SubstitutePageNumbers(pageSettings, dictionaries, rightF, pageNumber, totalPages); - int last = rightF.Content.TextLines.Count - 1; - var descent = rightF.Content.TextLines[last].LargestDescent; - var hfx = pageSettings.PageSize.WidthPu - pageSettings.Margins.RightPu; - var hfy = pageSettings.Margins.FooterPu + descent; - var text = new PdfCellContentLayout(pageSettings, dictionaries, rightF, hfx, hfy, 0, 0); - text.Name = "RightFooter"; - text.IsHeaderFooter = true; - text.GidsAndCharMap(dictionaries); - pageLayout.AddChild(text); - } - } - - PdfGridlinesLayout.AddGridLines(pageSettings, pages[j], pageLayout, borderOnly: !pageSettings.ShowGridLines || pdfPages[i].IsCommentsPage); - - pageLayout.ChildObjects.Sort((a, b) => - { - int cmp = a.Z.CompareTo(b.Z); - if (cmp == 0) - return string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase); - return cmp; - }); - pageNumber++; - Catalog.AddChild(pageLayout); - } - } - return Catalog; - } - - - - - - static MergedCellCorners GetCorners(ExcelAddressBase addr, int row, int col) - { - if (addr == null) return MergedCellCorners.All; - MergedCellCorners result = MergedCellCorners.None; - - if (row == addr.Start.Row && col == addr.Start.Column) - result |= MergedCellCorners.TopLeft; - - if (row == addr.Start.Row && col == addr.End.Column) - result |= MergedCellCorners.TopRight; - - if (row == addr.End.Row && col == addr.Start.Column) - result |= MergedCellCorners.BottomLeft; - - if (row == addr.End.Row && col == addr.End.Column) - result |= MergedCellCorners.BottomRight; - - return result; - } - - private static bool NeedsClipping(PdfCell map, Page page, int row, int col) - { - if (map.ContentAligmnet == null) return false; - // Fill alignment always clips; WrapText is already wrapped but clip for safety. - if (map.ContentAligmnet.HorizontalAlignment == ExcelHorizontalAlignment.Fill || map.ContentAligmnet.WrapText) - return true; - if (map.TotalTextLength <= map.ColumnWidth) return false; - var halign = map.ContentAligmnet.HorizontalAlignment; - if (halign == ExcelHorizontalAlignment.Left || halign == ExcelHorizontalAlignment.General) - { - // Text spills right — clip if the right neighbour has content or we're at the page edge. - if (col >= page.ToColumn) return true; - var right = page.Map[row, col + 1]; - return right != null && !string.IsNullOrEmpty(right.Text); - } - else if (halign == ExcelHorizontalAlignment.Right) - { - // Text spills left — clip if the left neighbour has content or we're at the page edge. - if (col <= page.FromColumn) return true; - var left = page.Map[row, col - 1]; - return left != null && !string.IsNullOrEmpty(left.Text); - } - else if (halign == ExcelHorizontalAlignment.Center) - { - // Text spills both ways — clip if either neighbour blocks or we're at an edge. - bool rightBlocked = col >= page.ToColumn || (page.Map[row, col + 1] != null && !string.IsNullOrEmpty(page.Map[row, col + 1].Text)); - bool leftBlocked = col <= page.FromColumn || (page.Map[row, col - 1] != null && !string.IsNullOrEmpty(page.Map[row, col - 1].Text)); - return rightBlocked || leftBlocked; - } - return false; - } - - - - private static bool HasBorder(PdfCellStyle cellStyle) - { - if (cellStyle == null) return false; - bool hasBorders = - cellStyle.xfTop.Style != ExcelBorderStyle.None || - cellStyle.xfBottom.Style != ExcelBorderStyle.None || - cellStyle.xfLeft.Style != ExcelBorderStyle.None || - cellStyle.xfRight.Style != ExcelBorderStyle.None || - (cellStyle.dxfTop?.HasValue ?? false) || - (cellStyle.dxfBottom?.HasValue ?? false) || - (cellStyle.dxfLeft?.HasValue ?? false) || - (cellStyle.dxfRight?.HasValue ?? false) || - (cellStyle.Diagonal != null && cellStyle.Diagonal.Style != ExcelBorderStyle.None); - return hasBorders; - } - - private static bool HasDiagonalBorder(PdfCellStyle style) => style?.Diagonal != null && style.Diagonal.Style != ExcelBorderStyle.None; - - private static void AddHeadingCell(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfPageLayout pageLayout, PdfCellStyle headingStyle, string label, double x, double y, double width, double height, string fontName, float fontSize, string namePrefix) - { - if (width == 0d || height == 0d) return; - - var fill = new PdfCellLayout(dictionaries, headingStyle, x, y, width, height); - fill.Name = namePrefix; - fill.IsHeading = true; - fill.UpdateShadingPositionMatrix(pageSettings); - pageLayout.AddChild(fill); - - var cell = CreateHeadingPdfCell(pageSettings, dictionaries, label, ExcelHorizontalAlignment.Center, width, height, fontName, fontSize); - if (cell.TextLines != null && cell.TextLines.Count > 0) - { - var info = new MergedCellDrawInfo { X = x, Y = y, Width = width, Height = height }; - var text = new PdfCellContentLayout(pageSettings, dictionaries, cell, info, x, y, width, height); - text.Name = namePrefix + "_Text"; - text.IsHeading = true; - text.GidsAndCharMap(dictionaries); - pageLayout.AddChild(text); - } - AddHeadingCellBorder(pageLayout, x, y, width, height); - } - - private static void AddHeadingCells(PdfPageSettings pageSettings, PdfDictionaries dictionaries, Page page, PdfPageLayout pageLayout, double contentStartX, double contentStartY, double headingWidth, double headingHeight, string fontName, float fontSize, ExcelFill fill) - { - var headingStyle = new PdfCellStyle(); - headingStyle.xfFill = fill; - - var cornerFill = new PdfCellLayout(dictionaries, headingStyle, - pageSettings.ContentBounds.Left, pageSettings.ContentBounds.Top, headingWidth, headingHeight); - cornerFill.Name = "Heading_Corner"; - cornerFill.UpdateShadingPositionMatrix(pageSettings); - pageLayout.AddChild(cornerFill); - - double x = contentStartX; - for (int col = page.FromColumn; col <= page.ToColumn; col++) - { - double colWidth = page.Map[page.FromRow, col]?.ColumnWidth ?? 0d; - if (colWidth == 0d) { x += colWidth; continue; } - string colLetter = ExcelCellBase.GetColumnLetter(col); - AddHeadingCell(pageSettings, dictionaries, pageLayout, headingStyle, colLetter, - x, pageSettings.ContentBounds.Top, colWidth, headingHeight, fontName, fontSize, "Heading_Col_" + colLetter); - x += colWidth; - } - - double y = contentStartY; - for (int row = page.FromRow; row <= page.ToRow; row++) - { - double rowHeight = page.RowHeights[row - page.FromRow]; - if (rowHeight == 0d) { y -= rowHeight; continue; } - string rowNum = row.ToString(); - AddHeadingCell(pageSettings, dictionaries, pageLayout, headingStyle, rowNum, - pageSettings.ContentBounds.Left, y, headingWidth, rowHeight, fontName, fontSize, "Heading_Row_" + rowNum); - y -= rowHeight; - } - //// An empty style gives a transparent/white fill with no border. - //// TODO: replace with a proper light-grey fill once PdfCellStyle supports - //// creating standalone fills without a backing ExcelFillXml object. - //var headingStyle = new PdfCellStyle(); - //headingStyle.xfFill = fill; - - //// ── Corner cell (top-left, where the two strips intersect) ────────────────────────── - //var cornerFill = new PdfCellLayout(dictionaries, headingStyle, - // pageSettings.ContentBounds.Left, pageSettings.ContentBounds.Top, - // headingWidth, headingHeight); - //cornerFill.Name = "Heading_Corner"; - //cornerFill.UpdateShadingPositionMatrix(pageSettings); - //pageLayout.AddChild(cornerFill); - - //// ── Column headings (one per data column, spanning the top strip) ─────────────────── - //double x = contentStartX; - //for (int col = page.FromColumn; col <= page.ToColumn; col++) - //{ - // var mapCell = page.Map[page.FromRow, col]; - // double colWidth = mapCell?.ColumnWidth ?? 0d; - // if (colWidth == 0d) { x += colWidth; continue; } // hidden column — skip - - // string colLetter = ExcelCellBase.GetColumnLetter(col); - - // var colFill = new PdfCellLayout(dictionaries, headingStyle, - // x, pageSettings.ContentBounds.Top, colWidth, headingHeight); - // colFill.Name = "Heading_Col_" + colLetter; - // colFill.IsHeading = true; - // colFill.UpdateShadingPositionMatrix(pageSettings); - // pageLayout.AddChild(colFill); - - // var colCell = CreateHeadingPdfCell(pageSettings, dictionaries, - // colLetter, ExcelHorizontalAlignment.Center, - // colWidth, headingHeight, fontName, fontSize); - - // if (colCell.TextLines != null && colCell.TextLines.Count > 0) - // { - // var info = new MergedCellDrawInfo { X = x, Y = pageSettings.ContentBounds.Top, Width = colWidth, Height = headingHeight }; - // var colText = new PdfCellContentLayout(pageSettings, dictionaries, colCell, info, - // x, pageSettings.ContentBounds.Top, colWidth, headingHeight); - // colText.Name = "Heading_Col_" + colLetter + "_Text"; - // colText.IsHeading = true; - // colText.GidsAndCharMap(dictionaries); - // pageLayout.AddChild(colText); - // } - // AddHeadingCellBorder(pageLayout, x, pageSettings.ContentBounds.Top, colWidth, headingHeight); - // x += colWidth; - //} - //// ── Row headings (one per data row, spanning the left strip) ───────────────────────── - //double y = contentStartY; - //for (int row = page.FromRow; row <= page.ToRow; row++) - //{ - // double rowHeight = page.RowHeights[row - page.FromRow]; - // if (rowHeight == 0d) { y -= rowHeight; continue; } // hidden row — skip - - // string rowNum = row.ToString(); - - // var rowFill = new PdfCellLayout(dictionaries, headingStyle, - // pageSettings.ContentBounds.Left, y, headingWidth, rowHeight); - // rowFill.Name = "Heading_Row_" + rowNum; - // rowFill.IsHeading = true; - // rowFill.UpdateShadingPositionMatrix(pageSettings); - // pageLayout.AddChild(rowFill); - - // var rowCell = CreateHeadingPdfCell(pageSettings, dictionaries, - // rowNum, ExcelHorizontalAlignment.Center, - // headingWidth, rowHeight, fontName, fontSize); - - // if (rowCell.TextLines != null && rowCell.TextLines.Count > 0) - // { - // var info = new MergedCellDrawInfo { X = pageSettings.ContentBounds.Left, Y = y, Width = headingWidth, Height = rowHeight }; - // var rowText = new PdfCellContentLayout(pageSettings, dictionaries, rowCell, info, - // pageSettings.ContentBounds.Left, y, headingWidth, rowHeight); - // rowText.Name = "Heading_Row_" + rowNum + "_Text"; - // rowText.IsHeading = true; - // rowText.GidsAndCharMap(dictionaries); - // pageLayout.AddChild(rowText); - // } - // AddHeadingCellBorder(pageLayout, pageSettings.ContentBounds.Left, y, headingWidth, rowHeight); - // y -= rowHeight; - //} - } - - private static void AddPrintTitleHeadings(PdfPageSettings pageSettings, PdfDictionaries dictionaries, Page page, PdfPageLayout pageLayout, string fontName, float fontSize, ExcelFill fill) - { - if (page.PrintTitleHeadings == null || page.PrintTitleHeadings.Count == 0) return; - - var headingStyle = new PdfCellStyle(); - headingStyle.xfFill = fill; - - foreach (var h in page.PrintTitleHeadings) - { - string label = h.IsRow ? h.Index.ToString() : ExcelCellBase.GetColumnLetter(h.Index); - string namePrefix = (h.IsRow ? "Heading_Row_" : "Heading_Col_") + label; - AddHeadingCell(pageSettings, dictionaries, pageLayout, headingStyle, label, h.X, h.Y, h.Width, h.Height, fontName, fontSize, namePrefix); - } - } - - private static void AddHeadingCellBorder(PdfPageLayout pageLayout, double x, double y, double width, double height) - { - double right = x + width; - double bottom = y - height; - pageLayout.BorderLines.Add(new GridLine(x, y, right, y)); // top - pageLayout.BorderLines.Add(new GridLine(x, bottom, right, bottom)); // bottom - pageLayout.BorderLines.Add(new GridLine(x, y, x, bottom)); // left - pageLayout.BorderLines.Add(new GridLine(right, y, right, bottom)); // right - } - - - private static PdfCell CreateHeadingPdfCell(PdfPageSettings pageSettings, PdfDictionaries dictionaries, string text, ExcelHorizontalAlignment hAlign, double width, double height, string fontName, float fontSize) - { - var cell = new PdfCell(); - cell.ColumnWidth = width; - cell.Width = width; - cell.Height = height; - cell.Text = text; - cell.CellStyle = new PdfCellStyle(); - cell.ContentAligmnet = new PdfCellAlignmentData(); - cell.ContentAligmnet.HorizontalAlignment = hAlign; - cell.ContentAligmnet.VerticalAlignment = ExcelVerticalAlignment.Bottom; - cell.ContentAligmnet.WrapText = false; - if (!string.IsNullOrEmpty(text)) - { - var tf = new TextFragment(); - tf.Font = new RichTextFormatSimple(); - tf.Font.Family = fontName; - tf.Font.SubFamily = FontSubFamily.Regular; - tf.Font.Size = fontSize; - tf.Text = text; - tf.RichTextOptions.Bold = false; - tf.RichTextOptions.Italic = false; - tf.RichTextOptions.UnderlineType = 12; // none - tf.RichTextOptions.StrikeType = 1; // none - cell.TextFragments = new List { tf }; - PdfTextShaper.ShapeText(pageSettings, dictionaries, cell); - } - return cell; - } - - private static void AddSpillCells(PdfPageSettings pageSettings, PdfDictionaries dictionaries, Page page, PdfPageLayout pageLayout) - { - if (page.SpillCells == null) return; - foreach (var s in page.SpillCells) - { - var map = s.Cell; - if (map.TextLines == null || map.TextLines.Count == 0) continue; - if (s.ClipWidth <= 0d || s.ClipHeight <= 0d) continue; - - var text = new PdfCellContentLayout(pageSettings, dictionaries, map, new MergedCellDrawInfo(), s.X, s.Y, s.Width, s.Height); - text.Name = "Spill_" + map.Name; - text.IsPrintTitle = s.IsPrintTitle; // false → clipped content group; true → outside-clip (band) - text.GidsAndCharMap(dictionaries); - text.SetupClipping(s.ClipX, s.ClipY, s.ClipWidth, s.ClipHeight); - pageLayout.AddChild(text); - } - } - - private static void AddPrintTitleCells(PdfPageSettings pageSettings, PdfDictionaries dictionaries, Page page, PdfPageLayout pageLayout) - { - if (page.PrintTitleCells == null) return; - pageLayout.PrintTitleGridLines = page.PrintTitleGridLines ?? new List(); - foreach (var t in page.PrintTitleCells) - { - var map = t.Cell; - - var fill = new PdfCellLayout(dictionaries, map.CellStyle, t.X, t.Y, t.Width, t.Height); - fill.Name = map.Name; - fill.IsPrintTitle = true; - fill.UpdateShadingPositionMatrix(pageSettings); - pageLayout.AddChild(fill); - - if (map.TextLines != null && map.TextLines.Count > 0) - { - var info = new MergedCellDrawInfo { X = t.X, Y = t.Y, Width = t.Width, Height = t.Height }; - var text = new PdfCellContentLayout(pageSettings, dictionaries, map, info, t.X, t.Y, t.Width, t.Height); - text.Name = map.Name; - text.IsPrintTitle = true; - text.GidsAndCharMap(dictionaries); - text.SetupClipping(t.ClipX, t.ClipY, t.ClipWidth, t.ClipHeight); - pageLayout.AddChild(text); - } - - //if (map.TextLines != null && map.TextLines.Count > 0) - //{ - // var text = new PdfCellContentLayout(pageSettings, dictionaries, map, new MergedCellDrawInfo(), t.X, t.Y, t.Width, t.Height); - // text.Name = map.Name; - // text.IsPrintTitle = true; - // text.GidsAndCharMap(dictionaries); - // text.SetupClipping(t.X, t.Y, t.Width, t.Height); - // pageLayout.AddChild(text); - //} - - if (!map.Merged && HasBorder(map.CellStyle)) // was: if (HasBorder(map.CellStyle)) - { - var border = new PdfCellBorderLayout(map.CellStyle, false, MergedCellCorners.All, new MergedCellDrawInfo(), t.X, t.Y, t.Width, t.Height); - border.IsPrintTitle = true; - border.Name = "PrintTitleBorder_" + map.Name; - pageLayout.AddChild(border); - } - // Per-cell borders for merged band cells — outside the margin clip like the rest of the band. - foreach (var b in page.PrintTitleBorders) - { - var sub = b.Cell; - if (!HasBorder(sub.CellStyle)) continue; - var border = new PdfCellBorderLayout(sub.CellStyle, false, MergedCellCorners.All, new MergedCellDrawInfo(), b.X, b.Y, b.Width, b.Height); - border.IsPrintTitle = true; - border.Name = "PrintTitleMergeBorder_" + sub.Name; - pageLayout.AddChild(border); - } - } - } - - - // TODO Count total pages when creating them isntead of looping them here. - private static int GetTotalPages(List pdfPages) - { - int totalPages = 0; - for (int i = 0; i < pdfPages.Count; i++) - { - totalPages += pdfPages[i].Page.Length; - } - return totalPages; - } - - internal static List GetPages(PdfPageSettings pageSettings, PdfWorksheet[] pdfSheets) - { - List PagesCollection = new List(); - foreach (var pdfSheet in pdfSheets) - { - for (int ri = 0; ri < pdfSheet.Ranges.Count; ri++) - { - var range = pdfSheet.Ranges[ri]; - var pages = GetNumberOfPages(pageSettings, pdfSheet, ref range); - pages = AssignRangeToPages(pageSettings, range, pages); - pages = MapPage(range, pages); - pages = GetHeaderFooter(range, pages, pdfSheet); - pages = PrecomputeMergedCells(pageSettings, range, pages); - pages = PrecomputeSpillCells(pageSettings, range, pages); - pages = PrecomputePrintTitleCells(pageSettings, pdfSheet, range, pages); - pages.HeadingFontName = pdfSheet.NormalStyle.Style.Font.Name; - pages.HeadingFontSize = pdfSheet.NormalStyle.Style.Font.Size; - pages.HeadingFill = pdfSheet.NormalStyle.Style.Fill; - PagesCollection.Add(pages); - } - if (pdfSheet.CommentsAndNotes.Range != null) - { - bool savedShowHeadings = pageSettings.ShowHeadings; - pageSettings.ShowHeadings = false; - var pages = GetNumberOfPages(pageSettings, pdfSheet, ref pdfSheet.CommentsAndNotes); - pages = AssignRangeToPages(pageSettings, pdfSheet.CommentsAndNotes, pages); - pages = MapPage(pdfSheet.CommentsAndNotes, pages); - pageSettings.ShowHeadings = savedShowHeadings; - pages.IsCommentsPage = true; - PagesCollection.Add(pages); - - //var pages = GetNumberOfPages(pageSettings, pdfSheet, ref pdfSheet.CommentsAndNotes); - //pages = AssignRangeToPages(pageSettings, pdfSheet.CommentsAndNotes, pages); - //pages = MapPage(pdfSheet.CommentsAndNotes, pages); - //pages.IsCommentsPage = true; - //PagesCollection.Add(pages); - } - } - return PagesCollection; - } - - internal static Pages PrecomputeMergedCells(PdfPageSettings pageSettings, PdfRange range, Pages pdfPages) - { - for (int i = 0; i < pdfPages.Page.Length; i++) - pdfPages.Page[i] = PrecomputePageMergedCells(pageSettings, range, pdfPages.Page[i]); - return pdfPages; - } - - private static Page PrecomputePageMergedCells(PdfPageSettings pageSettings, PdfRange range, Page page) - { - page.MergedCells = new Dictionary(); - // Build a quick lookup: absolute x for each column index on this page. - // We read ColumnWidth from the first data row; widths are per-column, not per-cell. - var colX = BuildColumnXPositions(pageSettings, page); - for (int row = page.FromRow; row <= page.ToRow; row++) - { - for (int col = page.FromColumn; col <= page.ToColumn; col++) - { - var cell = page.Map[row, col]; - if (cell == null || !cell.Merged) continue; - string key = cell.MergedAddress.Address; - if (page.MergedCells.ContainsKey(key)) continue; - var addr = cell.MergedAddress; - var mainCell = cell.Main ?? cell; // Main == null means this cell IS the top-left - // --- X --- - // Start from the current column and walk left to the merge origin. - // Columns within the current page come from colX; columns that lie on - // a preceding column-page come from range.ColWidths. - double drawX = colX[col - page.FromColumn]; - for (int c = addr._fromCol; c < col; c++) - { - int rangeIdx = c - range.Range._fromCol; - if (rangeIdx >= 0 && rangeIdx < range.ColWidths.Count) - drawX -= range.ColWidths[rangeIdx]; - } - // --- Y --- - // Replace the * 15d line with a sum of real row heights - double drawY = pageSettings.ContentBounds.Top - page.HeadingHeight - page.PrintTitleHeight; - for (int r = page.FromRow; r < row; r++) - { - drawY -= range.RowHeights[r - range.Range._fromRow].Height; - } - // Existing loop — just add .Height - for (int r = addr._fromRow; r < row; r++) - { - int rangeIdx = r - range.Range._fromRow; - if (rangeIdx >= 0 && rangeIdx < range.RowHeights.Count) - drawY += range.RowHeights[rangeIdx].Height; - } - page.MergedCells[key] = new MergedCellDrawInfo - { - X = drawX, - Y = drawY, - Width = mainCell.Width, - Height = mainCell.Height - }; - } - } - return page; - } - - private static double[] BuildColumnXPositions(PdfPageSettings pageSettings, Page page) - { - int colCount = page.ToColumn - page.FromColumn + 1; - var colX = new double[colCount]; - double x = pageSettings.ContentBounds.Left + page.HeadingWidth + page.PrintTitleWidth; - for (int col = page.FromColumn; col <= page.ToColumn; col++) - { - colX[col - page.FromColumn] = x; - var cell = page.Map[page.FromRow, col]; - x += cell?.ColumnWidth ?? 0d; - } - return colX; - } - - private static void ComputePrintTitleDimensions(PdfWorksheet pdfSheet, PdfRange range, out double titleHeight, out double titleWidth) - { - titleHeight = 0d; - titleWidth = 0d; - if (pdfSheet.PrintTitleRowFrom >= 0) - { - for (int r = pdfSheet.PrintTitleRowFrom; r <= pdfSheet.PrintTitleRowTo; r++) - { - int idx = r - range.Range._fromRow; - if (idx >= 0 && idx < range.RowHeights.Count) - titleHeight += range.RowHeights[idx].Height; - } - } - if (pdfSheet.PrintTitleColFrom >= 0) - { - for (int c = pdfSheet.PrintTitleColFrom; c <= pdfSheet.PrintTitleColTo; c++) - { - int idx = c - range.Range._fromCol; - if (idx >= 0 && idx < range.ColWidths.Count) - titleWidth += range.ColWidths[idx]; - } - } - } - - internal static Pages PrecomputePrintTitleCells(PdfPageSettings pageSettings, PdfWorksheet pdfSheet, PdfRange range, Pages pdfPages) - { - for (int i = 0; i < pdfPages.Page.Length; i++) - pdfPages.Page[i] = PrecomputePagePrintTitleCells(pageSettings, pdfSheet, range, pdfPages.Page[i]); - return pdfPages; - } - - private static Page PrecomputePagePrintTitleCells(PdfPageSettings pageSettings, PdfWorksheet pdfSheet, PdfRange range, Page page) - { - page.PrintTitleCells = new List(); - page.PrintTitleBorders = new List(); - - bool topBand = page.PrintTitleHeight > 0d && pdfSheet.PrintTitleRowFrom >= 0 && page.FromRow > pdfSheet.PrintTitleRowTo; - bool leftBand = page.PrintTitleWidth > 0d && pdfSheet.PrintTitleColFrom >= 0 && page.FromColumn > pdfSheet.PrintTitleColTo; - if (!topBand && !leftBand) return page; - - // Content-column X: same origin/widths the content loop uses (step-2 origin). - var contentColX = new Dictionary(); - double cx = pageSettings.ContentBounds.Left + page.HeadingWidth + page.PrintTitleWidth; - for (int c = page.FromColumn; c <= page.ToColumn; c++) - { - contentColX[c] = cx; - cx += RangeColWidth(range, page.FromRow, c); - } - - // Content-row Y. - var contentRowY = new Dictionary(); - double cy = pageSettings.ContentBounds.Top - page.HeadingHeight - page.PrintTitleHeight; - for (int r = page.FromRow; r <= page.ToRow; r++) - { - contentRowY[r] = cy; - cy -= RangeRowHeight(range, r); - } - - // Title-column X (left band): just right of the heading gutter. - var titleColX = new Dictionary(); - if (leftBand) - { - double tx = pageSettings.ContentBounds.Left + page.HeadingWidth; - for (int c = pdfSheet.PrintTitleColFrom; c <= pdfSheet.PrintTitleColTo; c++) - { - titleColX[c] = tx; - tx += RangeColWidth(range, page.FromRow, c); - } - } - - // Title-row Y (top band): just below the heading gutter. - var titleRowY = new Dictionary(); - if (topBand) - { - double ty = pageSettings.ContentBounds.Top - page.HeadingHeight; - for (int r = pdfSheet.PrintTitleRowFrom; r <= pdfSheet.PrintTitleRowTo; r++) - { - titleRowY[r] = ty; - ty -= RangeRowHeight(range, r); - } - } - - if (topBand) - ProcessBandRegionCells(page, range, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, page.FromColumn, page.ToColumn, contentColX, titleRowY); - if (leftBand) - ProcessBandRegionCells(page, range, page.FromRow, page.ToRow, pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, titleColX, contentRowY); - if (topBand && leftBand) - ProcessBandRegionCells(page, range, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, titleColX, titleRowY); - - //// Top band: title rows × this page's content columns. - //if (topBand) - // for (int r = pdfSheet.PrintTitleRowFrom; r <= pdfSheet.PrintTitleRowTo; r++) - // for (int c = page.FromColumn; c <= page.ToColumn; c++) - // AddPrintTitleDraw(page.PrintTitleCells, range, r, c, - // contentColX[c], titleRowY[r], RangeColWidth(range, page.FromRow, c), RangeRowHeight(range, r)); - - //// Left band: this page's content rows × title columns. - //if (leftBand) - // for (int r = page.FromRow; r <= page.ToRow; r++) - // for (int c = pdfSheet.PrintTitleColFrom; c <= pdfSheet.PrintTitleColTo; c++) - // AddPrintTitleDraw(page.PrintTitleCells, range, r, c, - // titleColX[c], contentRowY[r], RangeColWidth(range, page.FromRow, c), RangeRowHeight(range, r)); - - //// Corner: title rows × title columns. - //if (topBand && leftBand) - // for (int r = pdfSheet.PrintTitleRowFrom; r <= pdfSheet.PrintTitleRowTo; r++) - // for (int c = pdfSheet.PrintTitleColFrom; c <= pdfSheet.PrintTitleColTo; c++) - // AddPrintTitleDraw(page.PrintTitleCells, range, r, c, - // titleColX[c], titleRowY[r], RangeColWidth(range, page.FromRow, c), RangeRowHeight(range, r)); - - page.PrintTitleGridLines = new List(); - if (pageSettings.ShowGridLines) - { - if (topBand) - EmitBandGrid(page.PrintTitleGridLines, range, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, page.FromColumn, page.ToColumn, - BandEdgesX(contentColX, page.FromColumn, page.ToColumn, range, page.FromRow), - BandEdgesY(titleRowY, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, range)); - if (leftBand) - EmitBandGrid(page.PrintTitleGridLines, range, page.FromRow, page.ToRow, pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, - BandEdgesX(titleColX, pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, range, page.FromRow), - BandEdgesY(contentRowY, page.FromRow, page.ToRow, range)); - if (topBand && leftBand) - EmitBandGrid(page.PrintTitleGridLines, range, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, - BandEdgesX(titleColX, pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, range, page.FromRow), - BandEdgesY(titleRowY, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, range)); - } - - // ---- band gridlines: full grid per region, rendered outside the clip ---- - //page.PrintTitleGridLines = new List(); - //if (pageSettings.ShowGridLines) - //{ - // if (topBand) - // EmitFullGrid(page.PrintTitleGridLines, - // BandEdgesX(contentColX, page.FromColumn, page.ToColumn, range, page.FromRow), - // BandEdgesY(titleRowY, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, range)); - - // if (leftBand) - // EmitFullGrid(page.PrintTitleGridLines, - // BandEdgesX(titleColX, pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, range, page.FromRow), - // BandEdgesY(contentRowY, page.FromRow, page.ToRow, range)); - - // if (topBand && leftBand) - // EmitFullGrid(page.PrintTitleGridLines, - // BandEdgesX(titleColX, pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, range, page.FromRow), - // BandEdgesY(titleRowY, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, range)); - //} - - // ---- band headings: original row numbers / column letters, in the gutter gaps ---- - page.PrintTitleHeadings = new List(); - if (pageSettings.ShowHeadings) - { - if (topBand) - for (int r = pdfSheet.PrintTitleRowFrom; r <= pdfSheet.PrintTitleRowTo; r++) - { - double h = RangeRowHeight(range, r); - if (h <= 0d) continue; - page.PrintTitleHeadings.Add(new PrintTitleHeadingDraw - { - IsRow = true, - Index = r, - X = pageSettings.ContentBounds.Left, - Y = titleRowY[r], - Width = page.HeadingWidth, - Height = h - }); - } - - if (leftBand) - for (int c = pdfSheet.PrintTitleColFrom; c <= pdfSheet.PrintTitleColTo; c++) - { - double w = RangeColWidth(range, page.FromRow, c); - if (w <= 0d) continue; - page.PrintTitleHeadings.Add(new PrintTitleHeadingDraw - { - IsRow = false, - Index = c, - X = titleColX[c], - Y = pageSettings.ContentBounds.Top, - Width = w, - Height = page.HeadingHeight - }); - } - } - - // repeated title-row text continues onto the next horizontal page's band - if (topBand) - AddIncomingSpill(page, range, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, page.FromColumn, page.ToColumn, - pageSettings.ContentBounds.Left + page.HeadingWidth + page.PrintTitleWidth, - pageSettings.ContentBounds.Top - page.HeadingHeight, - isPrintTitle: true); - - // left band: a neighbour whose text spills INTO a title column travels with the repeated column - if (leftBand) - AddIncomingSpill(page, range, page.FromRow, page.ToRow, - pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, - pageSettings.ContentBounds.Left + page.HeadingWidth, // band origin X (left edge of the title columns) - pageSettings.ContentBounds.Top - page.HeadingHeight - page.PrintTitleHeight, // content-rows origin Y - isPrintTitle: true); - - // corner: same, for the title-rows × title-columns intersection - if (topBand && leftBand) - AddIncomingSpill(page, range, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, - pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, - pageSettings.ContentBounds.Left + page.HeadingWidth, // band origin X - pageSettings.ContentBounds.Top - page.HeadingHeight, // title-rows origin Y - isPrintTitle: true); - - return page; - } - - private static void ProcessBandRegionCells(Page page, PdfRange range, int fromRow, int toRow, int fromCol, int toCol, Dictionary colX, Dictionary rowY) - { - // Band-region rectangle (top-left origin). Non-merged text may spill within this rect, - // which is bounded by the band edge, so it can never overflow into the content area. - double regLeft = colX[fromCol]; - double regRight = colX[toCol] + RangeColWidth(range, page.FromRow, toCol); - double regTop = rowY[fromRow]; - double regBottom = rowY[toRow] - RangeRowHeight(range, toRow); - double regW = regRight - regLeft; - double regH = regTop - regBottom; - - var drawnMerges = new HashSet(); - for (int r = fromRow; r <= toRow; r++) - { - for (int c = fromCol; c <= toCol; c++) - { - var cell = RangeCell(range, r, c); - if (cell == null) continue; - - if (cell.Merged) - { - if (!drawnMerges.Add(cell.MergedAddress.Address)) continue; - var addr = cell.MergedAddress; - var main = cell.Main ?? cell; - - int vFromCol = Math.Max(addr._fromCol, fromCol); - int vToCol = Math.Min(addr._toCol, toCol); - int vFromRow = Math.Max(addr._fromRow, fromRow); - int vToRow = Math.Min(addr._toRow, toRow); - if (vFromCol > vToCol || vFromRow > vToRow) continue; - - double x = colX[vFromCol]; - double width = (colX[vToCol] + RangeColWidth(range, page.FromRow, vToCol)) - colX[vFromCol]; - double y = rowY[vFromRow]; - double height = (rowY[vFromRow] - rowY[vToRow]) + RangeRowHeight(range, vToRow); - if (width <= 0d || height <= 0d) continue; - - // merged cells don't spill — clip text to the merge itself (matches content) - page.PrintTitleCells.Add(new PrintTitleCellDraw - { - Cell = main, - X = x, - Y = y, - Width = width, - Height = height, - ClipX = x, - ClipY = y, - ClipWidth = width, - ClipHeight = height - }); - for (int br = vFromRow; br <= vToRow; br++) - { - double bh = RangeRowHeight(range, br); - if (bh <= 0d) continue; - for (int bc = vFromCol; bc <= vToCol; bc++) - { - double bw = RangeColWidth(range, page.FromRow, bc); - if (bw <= 0d) continue; - var sub = RangeCell(range, br, bc); - if (sub == null || !HasBorder(sub.CellStyle)) continue; - page.PrintTitleBorders.Add(new PrintTitleCellDraw - { - Cell = sub, - X = colX[bc], - Y = rowY[br], - Width = bw, - Height = bh - }); - } - } - } - else - { - double w = RangeColWidth(range, page.FromRow, c); - double h = RangeRowHeight(range, r); - if (w <= 0d || h <= 0d) continue; - - // non-merged: clip to the band region so text can spill within it but not into content - page.PrintTitleCells.Add(new PrintTitleCellDraw - { - Cell = cell, - X = colX[c], - Y = rowY[r], - Width = w, - Height = h, - ClipX = regLeft, - ClipY = regTop, - ClipWidth = regW, - ClipHeight = regH - }); - } - } - } - } - - private static void EmitBandGrid(List target, PdfRange range, int fromRow, int toRow, int fromCol, int toCol, double[] colX, double[] rowY) - { - int nr = toRow - fromRow + 1; - int nc = toCol - fromCol + 1; - if (nr <= 0 || nc <= 0) return; - - var spill = BuildBandSpillMask(range, fromRow, toRow, fromCol, toCol, colX); - double top = rowY[0], bottom = rowY[nr]; - double left = colX[0], right = colX[nc]; - - EmitBandFrameH(target, range, top, fromRow, fromCol, nc, colX, CellHasTopBorder); - EmitBandFrameH(target, range, bottom, toRow, fromCol, nc, colX, CellHasBottomBorder); - EmitBandFrameV(target, range, left, fromCol, fromRow, nr, rowY, CellHasLeftBorder); - EmitBandFrameV(target, range, right, toCol, fromRow, nr, rowY, CellHasRightBorder); - - // region frame (outer edges; harmless overlap with page frame / heading borders / band↔content seam) - //target.Add(new GridLine(left, top, left, bottom)); - //target.Add(new GridLine(right, top, right, bottom)); - //target.Add(new GridLine(left, top, right, top)); - //target.Add(new GridLine(left, bottom, right, bottom)); - - // interior verticals — suppress where a merge spans the gap - for (int gi = 1; gi < nc; gi++) - { - int leftCol = fromCol + gi - 1, rightCol = leftCol + 1; - double x = colX[gi]; - double? runStart = null; double runEnd = 0d; - for (int ri = 0; ri < nr; ri++) - { - int r = fromRow + ri; - //bool block = SameMerge(range, r, leftCol, r, rightCol) || spill[ri, gi]; - bool block = SameMerge(range, r, leftCol, r, rightCol) || spill[ri, gi] || - CellHasRightBorder(RangeCell(range, r, leftCol)) || - CellHasLeftBorder(RangeCell(range, r, rightCol)); - if (!block) { if (runStart == null) runStart = rowY[ri]; runEnd = rowY[ri + 1]; } - else if (runStart != null) { target.Add(new GridLine(x, runStart.Value, x, runEnd)); runStart = null; } - } - //for (int ri = 0; ri < nr; ri++) - //{ - // int r = fromRow + ri; - // if (!SameMerge(range, r, leftCol, r, rightCol)) { if (runStart == null) runStart = rowY[ri]; runEnd = rowY[ri + 1]; } - // else if (runStart != null) { target.Add(new GridLine(x, runStart.Value, x, runEnd)); runStart = null; } - //} - if (runStart != null) target.Add(new GridLine(x, runStart.Value, x, runEnd)); - } - - // interior horizontals — suppress where a merge spans the gap - for (int gj = 1; gj < nr; gj++) - { - int topRow = fromRow + gj - 1, bottomRow = topRow + 1; - double y = rowY[gj]; - double? runStart = null; double runEnd = 0d; - for (int ci = 0; ci < nc; ci++) - { - int c = fromCol + ci; - bool block = SameMerge(range, topRow, c, bottomRow, c) || - CellHasBottomBorder(RangeCell(range, topRow, c)) || - CellHasTopBorder(RangeCell(range, bottomRow, c)); - if (!block) { if (runStart == null) runStart = colX[ci]; runEnd = colX[ci + 1]; } - //if (!SameMerge(range, topRow, c, bottomRow, c)) { if (runStart == null) runStart = colX[ci]; runEnd = colX[ci + 1]; } - else if (runStart != null) { target.Add(new GridLine(runStart.Value, y, runEnd, y)); runStart = null; } - } - if (runStart != null) target.Add(new GridLine(runStart.Value, y, runEnd, y)); - } - } - - private static bool[,] BuildBandSpillMask(PdfRange range, int fromRow, int toRow, int fromCol, int toCol, double[] colX) - { - int nr = toRow - fromRow + 1; - int nc = toCol - fromCol + 1; - var blocked = new bool[Math.Max(nr, 1), Math.Max(nc, 1)]; // [ri, g], g in 1..nc-1 - if (nr <= 0 || nc <= 1) return blocked; - int repRow = fromRow; - - for (int ri = 0; ri < nr; ri++) - { - int row = fromRow + ri; - - // (a) cells spilling within the band region - for (int ci = 0; ci < nc; ci++) - { - var cell = RangeCell(range, row, fromCol + ci); - if (cell == null || cell.Merged) continue; - if (cell.ContentAligmnet != null && cell.ContentAligmnet.WrapText) continue; - if (string.IsNullOrEmpty(cell.Text)) continue; - double spill = cell.TotalTextLength - cell.ColumnWidth; - if (spill <= 0d) continue; - var hal = cell.ContentAligmnet?.HorizontalAlignment ?? ExcelHorizontalAlignment.General; - if (hal == ExcelHorizontalAlignment.Left || hal == ExcelHorizontalAlignment.General) - BandMarkRight(range, row, ci, nc, fromCol, colX, spill, ri, blocked); - else if (hal == ExcelHorizontalAlignment.Right) - BandMarkLeft(range, row, ci, fromCol, colX, spill, ri, blocked); - else if (hal == ExcelHorizontalAlignment.Center) - { - double half = spill / 2d; - BandMarkRight(range, row, ci, nc, fromCol, colX, half, ri, blocked); - BandMarkLeft(range, row, ci, fromCol, colX, half, ri, blocked); - } - } - - // (b) spill entering from the LEFT of the region (left/general/center → spilling right in) - double lx = colX[0]; - for (int c = fromCol - 1; c >= range.Range._fromCol; c--) - { - double w = RangeColWidth(range, repRow, c); - lx -= w; - var cell = RangeCell(range, row, c); - if (cell == null) continue; - if (cell.Merged) break; - if (string.IsNullOrEmpty(cell.Text)) continue; - var hal = cell.ContentAligmnet?.HorizontalAlignment ?? ExcelHorizontalAlignment.General; - if (hal == ExcelHorizontalAlignment.Right) break; // spills away from the region - double rightExtent = (hal == ExcelHorizontalAlignment.Center) - ? lx + w / 2d + cell.TotalTextLength / 2d - : lx + cell.TotalTextLength; - for (int g = 1; g <= nc - 1; g++) - { - var blk = RangeCell(range, row, fromCol + g - 1); - if (blk != null && !string.IsNullOrEmpty(blk.Text)) break; - if (colX[g] < rightExtent) blocked[ri, g] = true; else break; - } - break; - } - - // (c) spill entering from the RIGHT of the region (right/center → spilling left in) - double rx = colX[nc]; - for (int c = toCol + 1; c <= range.Range._toCol; c++) - { - double w = RangeColWidth(range, repRow, c); - var cell = RangeCell(range, row, c); - if (cell == null) { rx += w; continue; } - if (cell.Merged) break; - if (string.IsNullOrEmpty(cell.Text)) { rx += w; continue; } - var hal = cell.ContentAligmnet?.HorizontalAlignment ?? ExcelHorizontalAlignment.General; - if (hal == ExcelHorizontalAlignment.Left || hal == ExcelHorizontalAlignment.General) break; // spills away - double leftExtent = (hal == ExcelHorizontalAlignment.Center) - ? rx + w / 2d - cell.TotalTextLength / 2d - : rx + w - cell.TotalTextLength; - for (int g = nc - 1; g >= 1; g--) - { - var blk = RangeCell(range, row, fromCol + g); - if (blk != null && !string.IsNullOrEmpty(blk.Text)) break; - if (colX[g] > leftExtent) blocked[ri, g] = true; else break; - } - break; - } - } - return blocked; - } - - private static void BandMarkRight(PdfRange range, int row, int ci, int nc, int fromCol, double[] colX, double spill, int ri, bool[,] blocked) - { - for (int g = ci + 1; g <= nc - 1; g++) - { - var rightCell = RangeCell(range, row, fromCol + g); - if (rightCell != null && !string.IsNullOrEmpty(rightCell.Text)) break; - double distToGap = colX[g] - colX[ci + 1]; - if (spill > distToGap) blocked[ri, g] = true; else break; - } - } - - private static void BandMarkLeft(PdfRange range, int row, int ci, int fromCol, double[] colX, double spill, int ri, bool[,] blocked) - { - for (int g = ci; g >= 1; g--) - { - var leftCell = RangeCell(range, row, fromCol + g - 1); - if (leftCell != null && !string.IsNullOrEmpty(leftCell.Text)) break; - double distToGap = colX[ci] - colX[g]; - if (spill > distToGap) blocked[ri, g] = true; else break; - } - } - - private static bool SameMerge(PdfRange range, int r1, int c1, int r2, int c2) - { - var a = RangeCell(range, r1, c1); - var b = RangeCell(range, r2, c2); - if (a == null || b == null || !a.Merged || !b.Merged) return false; - return a.MergedAddress.Address == b.MergedAddress.Address; - } - - // Column edges for a band region: left edge of every column fromCol..toCol, plus the trailing right edge. - private static double[] BandEdgesX(Dictionary leftX, int fromCol, int toCol, PdfRange range, int repRow) - { - int n = toCol - fromCol + 1; - var arr = new double[n + 1]; - for (int c = fromCol, k = 0; c <= toCol; c++, k++) arr[k] = leftX[c]; - arr[n] = leftX[toCol] + RangeColWidth(range, repRow, toCol); - return arr; - } - - // Row edges for a band region: top edge of every row fromRow..toRow, plus the trailing bottom edge. - private static double[] BandEdgesY(Dictionary topY, int fromRow, int toRow, PdfRange range) - { - int n = toRow - fromRow + 1; - var arr = new double[n + 1]; - for (int r = fromRow, k = 0; r <= toRow; r++, k++) arr[k] = topY[r]; - arr[n] = topY[toRow] - RangeRowHeight(range, toRow); - return arr; - } - - private static void EmitFullGrid(List target, double[] colX, double[] rowY) - { - int nc = colX.Length - 1; - int nr = rowY.Length - 1; - if (nc < 1 || nr < 1) return; - double top = rowY[0], bottom = rowY[nr]; - double left = colX[0], right = colX[nc]; - for (int i = 0; i <= nc; i++) target.Add(new GridLine(colX[i], top, colX[i], bottom)); // verticals - for (int j = 0; j <= nr; j++) target.Add(new GridLine(left, rowY[j], right, rowY[j])); // horizontals - } - - private static void AddPrintTitleDraw(List sink, PdfRange range, int row, int col, double x, double y, double w, double h) - { - if (w <= 0d || h <= 0d) return; - var cell = RangeCell(range, row, col); - if (cell == null) return; - if (cell.Merged) return; // merged title cells: handled in a later sub-step - sink.Add(new PrintTitleCellDraw { Cell = cell, X = x, Y = y, Width = w, Height = h }); - } - - private static PdfCell RangeCell(PdfRange range, int row, int col) - { - if (row < range.Range._fromRow || row > range.Range._toRow) return null; - if (col < range.Range._fromCol || col > range.Range._toCol) return null; - return range.Map[row, col]; - } - - private static double RangeColWidth(PdfRange range, int representativeRow, int col) - { - var cell = RangeCell(range, representativeRow, col); - return cell?.ColumnWidth ?? 0d; - } - - private static double RangeRowHeight(PdfRange range, int row) - { - int i = row - range.Range._fromRow; - return (i >= 0 && i < range.RowHeights.Count) ? range.RowHeights[i].Height : 0d; - } - - internal static Pages GetNumberOfPages(PdfPageSettings pageSettings, PdfWorksheet pdfSheet, ref PdfRange range) - { - var xPages = (int)Math.Max(1, Math.Ceiling(range.TotalWidth / pageSettings.ContentBounds.Width)); - var yPages = (int)Math.Max(1, Math.Ceiling(range.TotalHeight / pageSettings.ContentBounds.Height)); - - if (pageSettings.ShowHeadings) - { - int prev = 0; - do - { - prev = xPages; - range.AdditionalWidth = xPages * ((rowHeadingWith1CharWidth - pdfSheet.ZeroCharWidth) + (Math.Abs(pdfSheet.ToRow).ToString().Length * pdfSheet.ZeroCharWidth)); - xPages = (int)Math.Max(1, Math.Ceiling((range.TotalWidth + range.AdditionalWidth) / pageSettings.ContentBounds.Width)); - } while (prev != xPages); - do - { - prev = yPages; - range.AdditionalHeight = yPages * pdfSheet.Worksheet.DefaultRowHeight; - yPages = (int)Math.Max(1, Math.Ceiling((range.TotalHeight + range.AdditionalHeight) / pageSettings.ContentBounds.Height)); - } while (prev != yPages); - } - for (int i = range.Range._fromCol; i <= range.Range._toCol; i++) - { - if (pdfSheet.Worksheet.Column(i).PageBreak) - xPages++; - } - for (int i = range.Range._fromRow; i <= range.Range._toRow; i++) - { - if (pdfSheet.Worksheet.Row(i).PageBreak) - yPages++; - } - ComputePrintTitleDimensions(pdfSheet, range, out range.PrintTitleHeight, out range.PrintTitleWidth); - range.PrintTitleRowTo = pdfSheet.PrintTitleRowFrom >= 0 ? pdfSheet.PrintTitleRowTo : -1; - range.PrintTitleColTo = pdfSheet.PrintTitleColFrom >= 0 ? pdfSheet.PrintTitleColTo : -1; - Pages p = new Pages(); - p.Width = xPages; - p.Height = yPages; - p.Page = null; - return p; - } - - internal static Pages AssignRangeToPages(PdfPageSettings pageSettings, PdfRange range, Pages pdfPages) - { - var pages = pdfPages; - var worksheet = range.Range.Worksheet; - var addedWidth = pages.Width > 0 ? range.AdditionalWidth / pages.Width : 0d; - var addedHeight = pages.Height > 0 ? range.AdditionalHeight / pages.Height : 0d; - - var colSegments = GetColumnSegments(pageSettings, range, worksheet, addedWidth, range.PrintTitleWidth, range.PrintTitleColTo); - var rowSegments = GetRowSegments(pageSettings, range, worksheet, addedHeight, range.PrintTitleHeight, range.PrintTitleRowTo); - - pages.Page = new Page[colSegments.Count * rowSegments.Count]; - int i = 0; - - if (pageSettings.PageOrders == PageOrders.DownThenOver) - { - //foreach (var colSeg in colSegments) - // foreach (var rowSeg in rowSegments) - // pages.Page[i++] = new Page { FromColumn = colSeg.From, ToColumn = colSeg.To, FromRow = rowSeg.From, ToRow = rowSeg.To }; - for (int ci = 0; ci < colSegments.Count; ci++) - for (int ri = 0; ri < rowSegments.Count; ri++) - pages.Page[i++] = new Page - { - FromColumn = colSegments[ci].From, - ToColumn = colSegments[ci].To, - FromRow = rowSegments[ri].From, - ToRow = rowSegments[ri].To, - HeadingWidth = addedWidth, - HeadingHeight = addedHeight, - PrintTitleWidth = (range.PrintTitleColTo >= 0 && colSegments[ci].From > range.PrintTitleColTo) ? range.PrintTitleWidth : 0d, - PrintTitleHeight = (range.PrintTitleRowTo >= 0 && rowSegments[ri].From > range.PrintTitleRowTo) ? range.PrintTitleHeight : 0d, - }; - } - else //if (pageSettings.PageOrders == PageOrders.OverThenDown) - { - //foreach (var rowSeg in rowSegments) - // foreach (var colSeg in colSegments) - // pages.Page[i++] = new Page { FromColumn = colSeg.From, ToColumn = colSeg.To, FromRow = rowSeg.From, ToRow = rowSeg.To }; - for (int ri = 0; ri < rowSegments.Count; ri++) - for (int ci = 0; ci < colSegments.Count; ci++) - pages.Page[i++] = new Page - { - FromColumn = colSegments[ci].From, - ToColumn = colSegments[ci].To, - FromRow = rowSegments[ri].From, - ToRow = rowSegments[ri].To, - HeadingWidth = addedWidth, - HeadingHeight = addedHeight, - PrintTitleWidth = (range.PrintTitleColTo >= 0 && colSegments[ci].From > range.PrintTitleColTo) ? range.PrintTitleWidth : 0d, - PrintTitleHeight = (range.PrintTitleRowTo >= 0 && rowSegments[ri].From > range.PrintTitleRowTo) ? range.PrintTitleHeight : 0d, - }; - } - - //for (int k = 0; k < pages.Page.Length; k++) - //{ - // var p = pages.Page[k]; - // p.HeadingWidth = addedWidth; - // p.HeadingHeight = addedHeight; - // pages.Page[k] = p; - //} - pdfPages = pages; - return pdfPages; - } - - private struct PageSegment - { - public int From; - public int To; - public PageSegment(int from, int to) { From = from; To = to; } - } - - private static List GetColumnSegments(PdfPageSettings pageSettings, PdfRange range, ExcelWorksheet worksheet, double addedWidth, double titleWidth, int printTitleColTo) - { - var segments = new List(); - int segStartIdx = 0; - double width = 0d; - - for (int col = 0; col < range.ColWidths.Count; col++) - { - int actualCol = range.Range._fromCol + col; - - bool reserveTitle = titleWidth > 0d && printTitleColTo >= 0 && (range.Map.FromColumn + segStartIdx) > printTitleColTo; - double effectiveAdded = addedWidth + (reserveTitle ? titleWidth : 0d); - - // Content-bounds overflow: col doesn't fit, end segment before it and reprocess. - if (width + range.ColWidths[col] + effectiveAdded >= pageSettings.ContentBounds.Width) - { - segments.Add(new PageSegment(range.Map.FromColumn + segStartIdx, range.Map.FromColumn + col - 1)); - segStartIdx = col; - width = 0d; - col--; // reprocess this col as the first col of the next segment - continue; - } - - width += range.ColWidths[col]; - - // Explicit page break: col is included on this page, next segment starts after it. - if (worksheet.Column(actualCol).PageBreak) - { - segments.Add(new PageSegment(range.Map.FromColumn + segStartIdx, range.Map.FromColumn + col)); - segStartIdx = col + 1; - width = 0d; - } - } - - // Remaining cols form the last segment. - if (segStartIdx < range.ColWidths.Count) - segments.Add(new PageSegment(range.Map.FromColumn + segStartIdx, range.Map.FromColumn + range.ColWidths.Count - 1)); - - return segments; - } - - private static List GetRowSegments(PdfPageSettings pageSettings, PdfRange range, ExcelWorksheet worksheet, double addedHeight, double titleHeight, int printTitleRowTo) - { - var segments = new List(); - int segStartIdx = 0; - double height = 0d; - - for (int row = 0; row < range.RowHeights.Count; row++) - { - int actualRow = range.Range._fromRow + row; - - bool reserveTitle = titleHeight > 0d && printTitleRowTo >= 0 && (range.Map.FromRow + segStartIdx) > printTitleRowTo; - double effectiveAdded = addedHeight + (reserveTitle ? titleHeight : 0d); - - // Content-bounds overflow: row doesn't fit, end segment before it and reprocess. - if (height + range.RowHeights[row].Height + effectiveAdded >= pageSettings.ContentBounds.Height) - { - segments.Add(new PageSegment(range.Map.FromRow + segStartIdx, range.Map.FromRow + row - 1)); - segStartIdx = row; - height = 0d; - row--; // reprocess this row as the first row of the next segment - continue; - } - - height += range.RowHeights[row].Height; - - // Explicit page break: row is included on this page, next segment starts after it. - if (worksheet.Row(actualRow).PageBreak) - { - segments.Add(new PageSegment(range.Map.FromRow + segStartIdx, range.Map.FromRow + row)); - segStartIdx = row + 1; - height = 0d; - } - } - - // Remaining rows form the last segment. - if (segStartIdx < range.RowHeights.Count) - segments.Add(new PageSegment(range.Map.FromRow + segStartIdx, range.Map.FromRow + range.RowHeights.Count - 1)); - - return segments; - } - - internal static Pages MapPage(PdfRange range, Pages pdfPages) - { - var pages = pdfPages; - for (int i = 0; i < pdfPages.Page.Length; i++) - { - var page = pdfPages.Page[i]; - page.Map = new PdfCellCollection(page.FromRow, page.ToRow, page.FromColumn, page.ToColumn); - page.RowHeights = new double[page.ToRow - page.FromRow + 1]; - for (int row = page.FromRow; row <= page.ToRow; row++) - { - int rangeIdx = row - range.Range._fromRow; - page.RowHeights[row - page.FromRow] = range.RowHeights[rangeIdx].Height; - for (int col = page.FromColumn; col <= page.ToColumn; col++) - { - page.Map[row, col] = range.Map[row, col]; - } - } - pdfPages.Page[i] = page; - } - pdfPages = pages; - return pdfPages; - } - - private static Pages GetHeaderFooter(PdfRange range, Pages pdfPages, PdfWorksheet pdfSheet) - { - var pages = pdfPages; - for (int i = 0; i < pdfPages.Page.Length; i++) - { - var page = pdfPages.Page[i]; - page.HeaderFooters = pdfSheet.HeaderFooters; - pdfPages.Page[i] = page; - } - pdfPages = pages; - return pdfPages; - } - - private static void SubstitutePageNumbers(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfHeaderFooter hf, int pageNumber, int totalPages) - { - if (hf == null) return; - if (hf.PageNumberIndexes.Count > 0) - { - foreach (var idx in hf.PageNumberIndexes) - hf.Content.TextFragments[idx].Text = pageNumber.ToString(); - } - if (hf.NumberOfPagesIndexes.Count > 0) - { - foreach (var idx in hf.NumberOfPagesIndexes) - hf.Content.TextFragments[idx].Text = totalPages.ToString(); - } - PdfTextShaper.ShapeText(pageSettings, dictionaries, hf.Content); - } - - private static void AddIncomingSpill(Page page, PdfRange range, int fromRow, int toRow, int windowFromCol, int windowToCol, double windowOriginX, double windowOriginY, bool isPrintTitle) - { - if (page.SpillCells == null) return; // initialised by the caller - - double windowRightX = windowOriginX; - for (int c = windowFromCol; c <= windowToCol; c++) windowRightX += RangeColWidth(range, page.FromRow, c); - - double rowTop = windowOriginY; - for (int r = fromRow; r <= toRow; r++) - { - double rowH = RangeRowHeight(range, r); - double y = rowTop; - rowTop -= rowH; - if (rowH <= 0d) continue; - - // spill entering from the LEFT (left/general/center) - double lx = windowOriginX; - for (int c = windowFromCol - 1; c >= range.Range._fromCol; c--) - { - double w = RangeColWidth(range, page.FromRow, c); - lx -= w; - var cell = RangeCell(range, r, c); - if (cell == null) continue; - if (cell.Merged) break; // merges don't spill, and block - if (string.IsNullOrEmpty(cell.Text)) continue; - var hal = cell.ContentAligmnet?.HorizontalAlignment ?? ExcelHorizontalAlignment.General; - double rightExtent = - (hal == ExcelHorizontalAlignment.Center) ? lx + w / 2d + cell.TotalTextLength / 2d : - (hal == ExcelHorizontalAlignment.Right) ? lx + w : - lx + cell.TotalTextLength; - if (rightExtent > windowOriginX) - { - double clipRight = FirstBlockedX(page, range, r, windowFromCol, windowToCol, windowOriginX, windowRightX, fromLeft: true); - page.SpillCells.Add(new SpillCellDraw - { - Cell = cell, - X = lx, - Y = y, - Width = w, - Height = rowH, - ClipX = windowOriginX, - ClipY = y, - ClipWidth = clipRight - windowOriginX, - ClipHeight = rowH, - IsPrintTitle = isPrintTitle - }); - } - break; - } - - // spill entering from the RIGHT (right/center) - double rx = windowRightX; - for (int c = windowToCol + 1; c <= range.Range._toCol; c++) - { - double w = RangeColWidth(range, page.FromRow, c); - var cell = RangeCell(range, r, c); - if (cell == null) { rx += w; continue; } - if (cell.Merged) break; - if (string.IsNullOrEmpty(cell.Text)) { rx += w; continue; } - var hal = cell.ContentAligmnet?.HorizontalAlignment ?? ExcelHorizontalAlignment.General; - double leftExtent = - (hal == ExcelHorizontalAlignment.Center) ? rx + w / 2d - cell.TotalTextLength / 2d : - (hal == ExcelHorizontalAlignment.Right) ? rx + w - cell.TotalTextLength : - rx; - if (leftExtent < windowRightX) - { - double clipLeft = FirstBlockedX(page, range, r, windowFromCol, windowToCol, windowOriginX, windowRightX, fromLeft: false); - page.SpillCells.Add(new SpillCellDraw - { - Cell = cell, - X = rx, - Y = y, - Width = w, - Height = rowH, - ClipX = clipLeft, - ClipY = y, - ClipWidth = windowRightX - clipLeft, - ClipHeight = rowH, - IsPrintTitle = isPrintTitle - }); - } - break; - } - } - } - - // Where spill into the window is cut off by the first non-empty (or merged) cell inside it. - private static double FirstBlockedX(Page page, PdfRange range, int row, int windowFromCol, int windowToCol, double windowOriginX, double windowRightX, bool fromLeft) - { - if (fromLeft) - { - double x = windowOriginX; - for (int c = windowFromCol; c <= windowToCol; c++) - { - var cell = RangeCell(range, row, c); - if (cell != null && (cell.Merged || !string.IsNullOrEmpty(cell.Text))) return x; - x += RangeColWidth(range, page.FromRow, c); - } - return windowRightX; - } - double rx = windowRightX; - for (int c = windowToCol; c >= windowFromCol; c--) - { - double w = RangeColWidth(range, page.FromRow, c); - rx -= w; - var cell = RangeCell(range, row, c); - if (cell != null && (cell.Merged || !string.IsNullOrEmpty(cell.Text))) return rx + w; - } - return windowOriginX; - } - - internal static Pages PrecomputeSpillCells(PdfPageSettings pageSettings, PdfRange range, Pages pdfPages) - { - for (int i = 0; i < pdfPages.Page.Length; i++) - { - var page = pdfPages.Page[i]; - page.SpillCells = new List(); - double originX = pageSettings.ContentBounds.Left + page.HeadingWidth + page.PrintTitleWidth; - double originY = pageSettings.ContentBounds.Top - page.HeadingHeight - page.PrintTitleHeight; - AddIncomingSpill(page, range, page.FromRow, page.ToRow, page.FromColumn, page.ToColumn, originX, originY, isPrintTitle: false); - pdfPages.Page[i] = page; - } - return pdfPages; - } - - private static bool CellHasRightBorder(PdfCell cell) - { - var cs = cell?.CellStyle; if (cs == null) return false; - return (cs.xfRight != null && cs.xfRight.Style != ExcelBorderStyle.None) || (cs.dxfRight?.HasValue ?? false); - } - private static bool CellHasLeftBorder(PdfCell cell) - { - var cs = cell?.CellStyle; if (cs == null) return false; - return (cs.xfLeft != null && cs.xfLeft.Style != ExcelBorderStyle.None) || (cs.dxfLeft?.HasValue ?? false); - } - private static bool CellHasTopBorder(PdfCell cell) - { - var cs = cell?.CellStyle; if (cs == null) return false; - return (cs.xfTop != null && cs.xfTop.Style != ExcelBorderStyle.None) || (cs.dxfTop?.HasValue ?? false); - } - private static bool CellHasBottomBorder(PdfCell cell) - { - var cs = cell?.CellStyle; if (cs == null) return false; - return (cs.xfBottom != null && cs.xfBottom.Style != ExcelBorderStyle.None) || (cs.dxfBottom?.HasValue ?? false); - } - - private static void EmitBandFrameH(List target, PdfRange range, double y, int row, int fromCol, int nc, double[] colX, Func hasBorder) - { - double? rs = null; double re = 0d; - for (int ci = 0; ci < nc; ci++) - { - double segL = colX[ci], segR = colX[ci + 1]; - if (segR - segL <= 0d) { if (rs != null) { target.Add(new GridLine(rs.Value, y, re, y)); rs = null; } continue; } - if (!hasBorder(RangeCell(range, row, fromCol + ci))) { if (rs == null) rs = segL; re = segR; } - else if (rs != null) { target.Add(new GridLine(rs.Value, y, re, y)); rs = null; } - } - if (rs != null) target.Add(new GridLine(rs.Value, y, re, y)); - } - - private static void EmitBandFrameV(List target, PdfRange range, double x, int col, int fromRow, int nr, double[] rowY, Func hasBorder) - { - double? rs = null; double re = 0d; - for (int ri = 0; ri < nr; ri++) - { - double segT = rowY[ri], segB = rowY[ri + 1]; - if (segT - segB <= 0d) { if (rs != null) { target.Add(new GridLine(x, rs.Value, x, re)); rs = null; } continue; } - if (!hasBorder(RangeCell(range, fromRow + ri, col))) { if (rs == null) rs = segT; re = segB; } - else if (rs != null) { target.Add(new GridLine(x, rs.Value, x, re)); rs = null; } - } - if (rs != null) target.Add(new GridLine(x, rs.Value, x, re)); - } - - } -} diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfRange.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfRange.cs deleted file mode 100644 index c4d27bac6f..0000000000 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfRange.cs +++ /dev/null @@ -1,34 +0,0 @@ -using OfficeOpenXml; -using System.Collections.Generic; - -namespace EPPlus.Export.Pdf.PdfCatalog -{ - internal struct RowHeight - { - public double Height; - public bool UsesDefaultValue; - } - - internal struct PdfRange - { - public ExcelRangeBase Range { get; set; } - public bool ExtendColumns { get; set; } - public PdfCellCollection Map { get; set; } - public List RowHeights = new List(); - public List ColWidths = new List(); - public double TotalHeight; - public double TotalWidth; - public double AdditionalHeight; - public double AdditionalWidth; - public double PrintTitleHeight; - public double PrintTitleWidth; - public int PrintTitleRowTo = -1; - public int PrintTitleColTo = -1; - - public PdfRange(ExcelRangeBase range, bool extendColumns) - { - Range = range; - ExtendColumns = extendColumns; - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs deleted file mode 100644 index 2c64f168d0..0000000000 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs +++ /dev/null @@ -1,964 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.PdfLayout; -using EPPlus.Export.Pdf.PdfResources; -using EPPlus.Export.Pdf.PdfSettings; -using EPPlus.Fonts.OpenType.Integration; -using EPPlus.Fonts.OpenType.Integration.DataHolders; -using EPPlus.Graphics.Units; -using OfficeOpenXml; -using OfficeOpenXml.Interfaces.Fonts; -using OfficeOpenXml.Style; -using OfficeOpenXml.Style.Dxf; -using OfficeOpenXml.Style.HeaderFooterTextFormat; -using OfficeOpenXml.Style.Table; -using OfficeOpenXml.Table; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace EPPlus.Export.Pdf.PdfCatalog -{ - internal class PdfTextMap - { - public static PdfCellCollection SetTextMap(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfWorksheet pdfSheet, ref PdfRange pdfRange) - { - var Range = pdfRange; - var worksheet = Range.Range.Worksheet; - var ZeroCharWidth = pdfSheet.ZeroCharWidth = PdfWorksheet.GetThemeFont0Width(worksheet); - int addedColumns = Range.ExtendColumns ? AddColumnsForNonWrappedText(pageSettings, worksheet, pdfSheet) : 0; - var Map = new PdfCellCollection(Range.Range._fromRow, Range.Range._toRow, Range.Range._fromCol, Range.Range._toCol + addedColumns); - pdfSheet.ToRow = pdfSheet.ToRow < Range.Range._toRow ? Range.Range._toRow : pdfSheet.ToRow; - bool firstColumnRun = true; - List checkedMergedCells = new List(); - for (int row = Range.Range._fromRow; row <= Range.Range._toRow; row++) - { - var hiddenRow = worksheet.Row(row).Hidden; - var r = (RowInternal)worksheet.GetValueInner(row, 0); - bool usesDefaultValue = false; - double height = 0; - if (r == null || r.Height < 0) - { - usesDefaultValue = true; - height = worksheet.DefaultRowHeight; - } - else - { - height = UnitConversion.ExcelRowHeightToPoints(r.Height); - } - Range.TotalHeight += hiddenRow ? 0d : height; - Range.RowHeights.Add(new RowHeight { Height = hiddenRow ? 0d : height, UsesDefaultValue = usesDefaultValue }); - for (int col = Range.Range._fromCol; col <= Range.Range._toCol + addedColumns; col++) - { - var hiddenCol = worksheet.Column(col).Hidden; - var width = UnitConversion.ExcelColumnWidthToPoints(worksheet.Column(col).Width, ZeroCharWidth); - if (firstColumnRun) - { - Range.TotalWidth += hiddenCol ? 0d : width; - Range.ColWidths.Add(hiddenCol ? 0d : width); - } - var tempMap = new PdfCell(); - tempMap.Hidden = hiddenRow || hiddenCol; - tempMap.ColumnWidth = tempMap.Width = hiddenCol ? 0d : width; - var cell = worksheet.Cells[row, col]; - tempMap.Name = cell.Address; - if (cell.Merge) - { - HandleMergedCell(pageSettings, dictionaries, cell, checkedMergedCells, Map, tempMap, pdfSheet.ZeroCharWidth); - } - var cellStyle = new PdfCellStyle(); - GetBorderStyles(cell, cellStyle, tempMap); - if (tempMap.Main == null) - { - GetFillStyles(cell, cellStyle); - GetFontStyle(cell, cellStyle); - tempMap.ContentAligmnet = GetContentAlignment(cell); - if (!string.IsNullOrEmpty(cell.Text)) - { - tempMap.Text = cell.Text; - tempMap.TextFragments = GetTextFragments(pageSettings, dictionaries, cell, cellStyle); - } - } - tempMap.CellStyle = cellStyle; - Map[row, col] = tempMap; - if (pageSettings.CommentsAndNotes != CommentsAndNotes.None) - { - if (cell.Comment != null && cell.ThreadedComment == null) - { - pdfSheet.CommentsAndNotesCollections.Add(cell.Address, new PdfCommentsAndNotes(cell.Comment)); - } - if (cell.ThreadedComment != null) - { - pdfSheet.CommentsAndNotesCollections.Add(cell.Address, new PdfCommentsAndNotes(cell.ThreadedComment)); - PdfCommentsAndNotes.HasThreadedComment = true; - } - } - } - firstColumnRun = false; - } - worksheet.ConditionalFormatting.ClearTempExportCacheForAllCFs(); - pdfRange = Range; - return Map; - } - - private static void HandleMergedCell(PdfPageSettings pageSettings, PdfDictionaries dictionaries, ExcelRange cell, List checkedMergedCells, PdfCellCollection map, PdfCell tempMap, double ZeroCharWidth) - { - var worksheet = cell.Worksheet; - string mergeAddress = worksheet.MergedCells[cell.Start.Row, cell.Start.Column]; - ExcelAddressBase address = new ExcelAddressBase(mergeAddress); - if (!checkedMergedCells.Contains(mergeAddress)) - { - double totalWidth = 0, totalHeight = 0; - for (int k = address._fromRow; k <= address._toRow; k++) - { - totalHeight += UnitConversion.ExcelRowHeightToPoints(worksheet.Row(k).Height); - } - for (int l = address._fromCol; l <= address._toCol; l++) - { - totalWidth += UnitConversion.ExcelColumnWidthToPoints(worksheet.Column(l).Width, ZeroCharWidth); - } - checkedMergedCells.Add(mergeAddress); - tempMap.Width = totalWidth; - tempMap.Height = totalHeight; - tempMap.Main = null; - } - else - { - tempMap.Main = map[address._fromRow, address._fromCol]; - if (tempMap.Main == null) - { - var main = worksheet.Cells[address._fromRow, address._fromCol]; - PdfCell mainCell = new PdfCell(); - var cellStyle = new PdfCellStyle(); - GetBorderStyles(main, cellStyle, mainCell); - GetFillStyles(main, cellStyle); - GetFontStyle(main, cellStyle); - - mainCell.ContentAligmnet = GetContentAlignment(main); - if (!string.IsNullOrEmpty(main.Text)) - { - mainCell.Text = main.Text; - mainCell.TextFragments = GetTextFragments(pageSettings, dictionaries, main, cellStyle); - } - mainCell.CellStyle = cellStyle; - tempMap.Main = mainCell; - } - } - tempMap.MergedAddress = address; - tempMap.Name = tempMap.Name + " ; " + address.ToString(); - tempMap.Merged = true; - } - - private static void GetFillStyles(ExcelRangeBase cell, PdfCellStyle cellStyle) - { - if (cell.Style.Fill.IsEmpty()) - { - //Conditional Formating - var cf = cell.ConditionalFormatting.GetConditionalFormattings(); - if (cf != null && cf.Count > 0) - { - // Sort ascending — priority 1 beats priority 2, etc. - var ordered = cf.OrderBy(r => r.Priority); - - foreach (var rule in ordered) - { - // Use the core per-rule evaluator (the same one the HTML exporter - // calls). It correctly handles every rule type — comparisons, text, - // blanks/errors, top/bottom, above/below average, duplicate/unique, - // time periods and formula expressions — including the range-wide - // aggregates, which the previous hand-rolled evaluator stubbed out. - if (!rule.ShouldApplyToCell(cell)) - { - if (rule.StopIfTrue) break; // higher-priority rule fired but had no fill — stop anyway - continue; - } - - if (rule.Style?.Fill != null && rule.Style.Fill.HasValue) - { - cellStyle.dxfFill = rule.Style.Fill; - // xfFill must be non-null for the downstream dxf path - // (PdfCellLayout checks xfFill.IsEmpty()); the cell's own fill - // is empty here, which is exactly what selects the dxf fill. - cellStyle.xfFill = cell.Style.Fill; - return; // CF fill wins — skip table and xf entirely - } - - if (rule.StopIfTrue) break; - } - } - - //Table - var tables = cell.Worksheet.Tables.GetIntersectingRanges(cell); - if (tables.Count > 0) - { - var table = tables[0].Value; - var range = table.Range; - - int tableRow = 0; - int tableCol = 0; - ExcelTableNamedStyle tableStyle; - if (table.TableStyle == TableStyles.Custom) - { - tableStyle = cell.Worksheet.Workbook.Styles.TableStyles[table.StyleName].As.TableStyle; - } - else - { - var tmpNode = table.WorkSheet.Workbook.StylesXml.CreateElement("c:tableStyle"); - tableStyle = new ExcelTableNamedStyle(cell.Worksheet.Workbook.Styles.NameSpaceManager, tmpNode, cell.Worksheet.Workbook.Styles); - tableStyle.SetFromTemplate((TableStyles)table.TableStyle); - } - tableRow = cell._fromRow - range._fromRow; - tableCol = cell._fromCol - range._fromCol; - if (table.ShowHeader && tableRow == 0) - { - cellStyle.dxfFill = tableStyle.HeaderRow.Style.Fill; - } - else if (table.ShowTotal && range._toRow == cell._fromRow) - { - cellStyle.dxfFill = tableStyle.TotalRow.Style.Fill; - } - else if (table.ShowFirstColumn && tableCol == 0) - { - cellStyle.dxfFill = tableStyle.FirstColumn.Style.Fill; - } - else if (table.ShowLastColumn && range._toCol == cell._fromCol) - { - cellStyle.dxfFill = tableStyle.LastColumn.Style.Fill; - } - else if (table.ShowRowStripes) - { - cellStyle.dxfFill = (tableRow & 1) == 0 ? tableStyle.SecondRowStripe.Style.Fill : tableStyle.FirstRowStripe.Style.Fill; - } - else if (table.ShowColumnStripes) - { - cellStyle.dxfFill = (tableCol & 1) != 0 ? tableStyle.SecondColumnStripe.Style.Fill : tableStyle.FirstColumnStripe.Style.Fill; - } - else - { - cellStyle.dxfFill = tableStyle.WholeTable.Style.Fill; - } - } - } - cellStyle.xfFill = cell.Style.Fill; - } - //private static bool EvaluateConditionalFormattingRule(IExcelConditionalFormattingRule rule, ExcelRangeBase cell) - //{ - // // These never produce a dxf fill — skip entirely - // switch (rule.Type) - // { - // case eExcelConditionalFormattingRuleType.TwoColorScale: - // case eExcelConditionalFormattingRuleType.ThreeColorScale: - // case eExcelConditionalFormattingRuleType.DataBar: - // case eExcelConditionalFormattingRuleType.ThreeIconSet: - // case eExcelConditionalFormattingRuleType.FourIconSet: - // case eExcelConditionalFormattingRuleType.FiveIconSet: - // return false; - // } - - // var cellValue = cell.Value; - // var cellText = cellValue?.ToString() ?? string.Empty; - - // switch (rule.Type) - // { - // // --- Comparison (CellIs) types --- - // case eExcelConditionalFormattingRuleType.Equal: - // case eExcelConditionalFormattingRuleType.NotEqual: - // case eExcelConditionalFormattingRuleType.GreaterThan: - // case eExcelConditionalFormattingRuleType.GreaterThanOrEqual: - // case eExcelConditionalFormattingRuleType.LessThan: - // case eExcelConditionalFormattingRuleType.LessThanOrEqual: - // case eExcelConditionalFormattingRuleType.Between: - // case eExcelConditionalFormattingRuleType.NotBetween: - // return EvaluateCellIs(rule, cell); - - // // --- Text types --- - // case eExcelConditionalFormattingRuleType.ContainsText: - // return cellText.Contains((rule as IExcelConditionalFormattingContainsText)?.Text ?? ""); - - // case eExcelConditionalFormattingRuleType.NotContainsText: - // return !cellText.Contains((rule as IExcelConditionalFormattingNotContainsText)?.Text ?? ""); - - // case eExcelConditionalFormattingRuleType.NotContains: - // return !cellText.Contains((rule as IExcelConditionalFormattingContainsText)?.Text ?? ""); - - // case eExcelConditionalFormattingRuleType.BeginsWith: - // return cellText.StartsWith((rule as IExcelConditionalFormattingBeginsWith)?.Text ?? ""); - - // case eExcelConditionalFormattingRuleType.EndsWith: - // return cellText.EndsWith((rule as IExcelConditionalFormattingEndsWith)?.Text ?? ""); - - // // --- Blank/Error types --- - // case eExcelConditionalFormattingRuleType.ContainsBlanks: - // return string.IsNullOrEmpty(cellText.Trim()); - - // case eExcelConditionalFormattingRuleType.NotContainsBlanks: - // return !string.IsNullOrEmpty(cellText.Trim()); - - // case eExcelConditionalFormattingRuleType.ContainsErrors: - // return cellValue is ExcelErrorValue; - - // case eExcelConditionalFormattingRuleType.NotContainsErrors: - // return !(cellValue is ExcelErrorValue); - - // // --- Duplicate/Unique — require range-wide scan, stub for now --- - // case eExcelConditionalFormattingRuleType.DuplicateValues: - // case eExcelConditionalFormattingRuleType.UniqueValues: - // return false; - - // // --- Average/StdDev/Top/Bottom/TimePeriod — require range-wide scan, stub for now --- - // case eExcelConditionalFormattingRuleType.AboveAverage: - // case eExcelConditionalFormattingRuleType.AboveOrEqualAverage: - // case eExcelConditionalFormattingRuleType.BelowAverage: - // case eExcelConditionalFormattingRuleType.BelowOrEqualAverage: - // case eExcelConditionalFormattingRuleType.AboveStdDev: - // case eExcelConditionalFormattingRuleType.BelowStdDev: - // case eExcelConditionalFormattingRuleType.Top: - // case eExcelConditionalFormattingRuleType.TopPercent: - // case eExcelConditionalFormattingRuleType.Bottom: - // case eExcelConditionalFormattingRuleType.BottomPercent: - // case eExcelConditionalFormattingRuleType.Last7Days: - // case eExcelConditionalFormattingRuleType.LastMonth: - // case eExcelConditionalFormattingRuleType.LastWeek: - // case eExcelConditionalFormattingRuleType.NextMonth: - // case eExcelConditionalFormattingRuleType.NextWeek: - // case eExcelConditionalFormattingRuleType.ThisMonth: - // case eExcelConditionalFormattingRuleType.ThisWeek: - // case eExcelConditionalFormattingRuleType.Today: - // case eExcelConditionalFormattingRuleType.Tomorrow: - // case eExcelConditionalFormattingRuleType.Yesterday: - // return false; - - // // --- Formula-based --- - // case eExcelConditionalFormattingRuleType.Expression: - // return false; // requires formula evaluation — future phase - - // default: - // return false; - // } - //} - - //private static bool EvaluateCellIs(IExcelConditionalFormattingRule rule, ExcelRangeBase cell) - //{ - // if (cell.Value == null) return false; - // if (!double.TryParse(cell.Value.ToString(), out double cellDouble)) return false; - - // var f1Rule = rule as IExcelConditionalFormattingWithFormula; - // if (f1Rule == null || !double.TryParse(f1Rule.Formula, out double formula1)) return false; - - // switch (rule.Type) - // { - // case eExcelConditionalFormattingRuleType.Equal: return cellDouble == formula1; - // case eExcelConditionalFormattingRuleType.NotEqual: return cellDouble != formula1; - // case eExcelConditionalFormattingRuleType.GreaterThan: return cellDouble > formula1; - // case eExcelConditionalFormattingRuleType.GreaterThanOrEqual: return cellDouble >= formula1; - // case eExcelConditionalFormattingRuleType.LessThan: return cellDouble < formula1; - // case eExcelConditionalFormattingRuleType.LessThanOrEqual: return cellDouble <= formula1; - // case eExcelConditionalFormattingRuleType.Between: - // case eExcelConditionalFormattingRuleType.NotBetween: - // { - // var f2Rule = rule as IExcelConditionalFormattingWithFormula2; - // if (f2Rule == null || !double.TryParse(f2Rule.Formula2, out double formula2)) return false; - // bool inRange = cellDouble >= formula1 && cellDouble <= formula2; - // return rule.Type == eExcelConditionalFormattingRuleType.Between ? inRange : !inRange; - // } - // default: - // return false; - // } - //} - - private static void GetBorderStyles(ExcelRangeBase cell, PdfCellStyle cellStyle, PdfCell pcell) - { - - /* Kika på varje del av border top bottom left right - * om cell har top använd den - * om cell inte har border, gå igenom prio ordning på tabell borders och använd WholeTable om null. - * om cellein i tabellen är i mitten av tabellen använd horizontal och vertical border istället. - * I fallet top så ska om vi är i header row eller om cell fromrow är samma som table fromrow så ska top border vara top border. annar är det horizontal som gäller - * Glöm ej vertical border om fromcol är samma som tabell fromcol. - */ - if (cell != null) - { - cellStyle.xfTop = cell.Style.Border.Top; - cellStyle.xfBottom = cell.Style.Border.Bottom; - cellStyle.xfLeft = cell.Style.Border.Left; - cellStyle.xfRight = cell.Style.Border.Right; - if (pcell.Main == null) - { - cellStyle.Diagonal = cell.Style.Border.Diagonal; - cellStyle.DiagonalUp = cell.Style.Border.DiagonalUp; - cellStyle.DiagonalDown = cell.Style.Border.DiagonalDown; - } - else - { - cellStyle.Diagonal = cell.Style.Border.Diagonal; - cellStyle.DiagonalUp = false; - cellStyle.DiagonalDown = false; - } - var tables = cell.Worksheet.Tables.GetIntersectingRanges(cell); - if (tables.Count > 0) - { - var table = tables[0].Value; - ExcelTableNamedStyle tableStyle; - if (table.TableStyle == TableStyles.Custom) - { - tableStyle = cell.Worksheet.Workbook.Styles.TableStyles[table.StyleName].As.TableStyle; - } - else - { - var tmpNode = table.WorkSheet.Workbook.StylesXml.CreateElement("c:tableStyle"); - tableStyle = new ExcelTableNamedStyle(cell.Worksheet.Workbook.Styles.NameSpaceManager, tmpNode, cell.Worksheet.Workbook.Styles); - tableStyle.SetFromTemplate((TableStyles)table.TableStyle); - } - cellStyle.dxfTop = PdfCellBorderLayout.GetTopBorderItem(cell, cellStyle.xfTop, table, tableStyle); - cellStyle.dxfBottom = PdfCellBorderLayout.GetBottomBorderItem(cell, cellStyle.xfBottom, table, tableStyle); - cellStyle.dxfLeft = PdfCellBorderLayout.GetLeftBorderItem(cell, cellStyle.xfLeft, table, tableStyle); - cellStyle.dxfRight = PdfCellBorderLayout.GetRightBorderItem(cell, cellStyle.xfRight, table, tableStyle); - } - var cfBorder = GetConditionalFormattingBorder(cell); - if (cfBorder != null) - { - if (cfBorder.Top != null && cfBorder.Top.HasValue) cellStyle.dxfTop = cfBorder.Top; - if (cfBorder.Bottom != null && cfBorder.Bottom.HasValue) cellStyle.dxfBottom = cfBorder.Bottom; - if (cfBorder.Left != null && cfBorder.Left.HasValue) cellStyle.dxfLeft = cfBorder.Left; - if (cfBorder.Right != null && cfBorder.Right.HasValue) cellStyle.dxfRight = cfBorder.Right; - } - } - } - - private static ExcelDxfBorderBase GetConditionalFormattingBorder(ExcelRangeBase cell) - { - var cf = cell.ConditionalFormatting.GetConditionalFormattings(); - if (cf != null && cf.Count > 0) - { - var ordered = cf.OrderBy(r => r.Priority); - foreach (var rule in ordered) - { - if (!rule.ShouldApplyToCell(cell)) - { - if (rule.StopIfTrue) break; - continue; - } - if (rule.Style?.Border != null && rule.Style.Border.HasValue) - { - return rule.Style.Border; - } - if (rule.StopIfTrue) break; - } - } - return null; - } - - private static PdfCellStyle GetFontStyle(ExcelRangeBase cell, PdfCellStyle cellStyle) - { - var cf = cell.ConditionalFormatting.GetConditionalFormattings(); - if (cf != null && cf.Count > 0) - { - // Sort ascending — priority 1 beats priority 2, etc. - var ordered = cf.OrderBy(r => r.Priority); - foreach (var rule in ordered) - { - if (!rule.ShouldApplyToCell(cell)) - { - if (rule.StopIfTrue) break; - continue; - } - if (rule.Style?.Font != null && rule.Style.Font.HasValue) - { - cellStyle.dxfFont = rule.Style.Font; - return cellStyle; // CF font wins over the table font - } - if (rule.StopIfTrue) break; - } - } - var tables = cell.Worksheet.Tables.GetIntersectingRanges(cell); - if (tables.Count > 0) - { - var table = tables[0].Value; - var range = table.Range; - ExcelTableNamedStyle tableStyle; - if (table.TableStyle == TableStyles.Custom) - { - tableStyle = cell.Worksheet.Workbook.Styles.TableStyles[table.StyleName].As.TableStyle; - } - else - { - var tmpNode = table.WorkSheet.Workbook.StylesXml.CreateElement("c:tableStyle"); - tableStyle = new ExcelTableNamedStyle(cell.Worksheet.Workbook.Styles.NameSpaceManager, tmpNode, cell.Worksheet.Workbook.Styles); - tableStyle.SetFromTemplate((TableStyles)table.TableStyle); - } - int tableRow = cell._fromRow - range._fromRow; - int tableCol = cell._fromCol - range._fromCol; - var font = tableStyle.WholeTable.Style.Font; - if (table.ShowHeader && tableRow == 0) - { - if (tableStyle.HeaderRow.Style.Font.HasValue) - { - font = tableStyle.HeaderRow.Style.Font; - } - if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstHeaderCell.Style.Font.HasValue) - { - font = tableStyle.FirstHeaderCell.Style.Font; - } - if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastHeaderCell.Style.Font.HasValue) - { - font = tableStyle.LastHeaderCell.Style.Font; - } - } - else if (table.ShowTotal && cell._fromRow == range._toRow) - { - if (tableStyle.TotalRow.Style.Font.HasValue) - { - font = tableStyle.TotalRow.Style.Font; - } - if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstTotalCell.Style.Font.HasValue) - { - font = tableStyle.FirstTotalCell.Style.Font; - } - if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastTotalCell.Style.Font.HasValue) - { - font = tableStyle.LastTotalCell.Style.Font; - } - } - else - { - if (table.ShowColumnStripes && (tableCol & 1) == 0) - { - font = tableStyle.FirstColumnStripe.Style.Font; - } - if (table.ShowColumnStripes && tableStyle.SecondColumnStripe.Style.Border.Top.HasValue && (tableCol & 1) != 0) - { - font = tableStyle.SecondColumnStripe.Style.Font; - } - if (table.ShowRowStripes && tableStyle.FirstRowStripe.Style.Font.HasValue && (tableRow & 1) != 0) - { - font = tableStyle.FirstRowStripe.Style.Font; - } - if (table.ShowRowStripes && tableStyle.SecondRowStripe.Style.Font.HasValue && (tableRow & 1) == 0) - { - font = tableStyle.SecondRowStripe.Style.Font; - } - if (table.ShowLastColumn && tableStyle.LastColumn.Style.Font.HasValue && cell._fromCol == range._toCol) - { - font = tableStyle.LastColumn.Style.Font; - } - if (table.ShowFirstColumn && tableStyle.FirstColumn.Style.Font.HasValue && tableCol == range._toCol) - { - font = tableStyle.FirstColumn.Style.Font; - } - } - cellStyle.dxfFont = font; - } - return cellStyle; - } - - - private static PdfCellAlignmentData GetContentAlignment(ExcelRangeBase cell) - { - var contentAlignment = new PdfCellAlignmentData(); - contentAlignment.HorizontalAlignment = cell.Style.HorizontalAlignment; - contentAlignment.VerticalAlignment = cell.Style.VerticalAlignment; - contentAlignment.Indent = cell.Style.Indent; - contentAlignment.WrapText = cell.Style.WrapText; - contentAlignment.ShrinkToFit = cell.Style.ShrinkToFit; - contentAlignment.TextRotation = (cell.Style.TextRotation > 90) ? ((cell.Style.TextRotation == 255) ? 0 : 90 - cell.Style.TextRotation) : cell.Style.TextRotation; - contentAlignment.IsVertical = cell.Style.TextRotation == 255 ? true : false; - contentAlignment.TextDirection = cell.Style.ReadingOrder; - return contentAlignment; - } - - internal static long subFamilyTicks; - internal static long addFontTicks; - internal static int subFamilyCount; - - private static List GetTextFragments(PdfPageSettings pageSettings, PdfDictionaries dictionaries, ExcelRangeBase cell, PdfCellStyle cellStyle) - { - bool dxfBold, dxfItalic, dxfStrike, dxfUnderline; - ExcelUnderLineType dxfUnderLineType; - System.Drawing.Color? dxfColor; - ReadDxfFontOverrides(cellStyle, out dxfBold, out dxfItalic, out dxfStrike, out dxfUnderline, out dxfUnderLineType, out dxfColor); - - string forcedText = ResolveErrorText(pageSettings, cell); - - if (cell.IsRichText) - { - return GetTextFragmentsFromRichText(pageSettings, dictionaries, cell.RichText, forcedText, - dxfBold, dxfItalic, dxfStrike, dxfUnderline, dxfUnderLineType, dxfColor); - } - return GetTextFragmentsFromCellStyle(pageSettings, dictionaries, cell, forcedText, - dxfBold, dxfItalic, dxfStrike, dxfUnderline, dxfUnderLineType, dxfColor); - } - - private static List GetTextFragmentsFromRichText(PdfPageSettings pageSettings, PdfDictionaries dictionaries, ExcelRichTextCollection richText, string forcedText, - bool dxfBold, bool dxfItalic, bool dxfStrike, bool dxfUnderline, ExcelUnderLineType dxfUnderLineType, System.Drawing.Color? dxfColor) - { - var textFragments = new List(richText.Count); - for (int i = 0; i < richText.Count; i++) - { - var rt = richText[i]; - var textFrag = new TextFragment(); - textFrag.Font = new RichTextFormatSimple(); - textFrag.Text = forcedText == null ? rt.Text : forcedText; - - textFrag.Font.Family = rt.FontName; - textFrag.Font.Size = rt.Size; - - textFrag.RichTextOptions.Bold = rt.Bold || dxfBold; - textFrag.RichTextOptions.Italic = rt.Italic || dxfItalic; - textFrag.RichTextOptions.UnderlineType = MapUnderlineType(rt.UnderLineType); - textFrag.RichTextOptions.StrikeType = (rt.Strike || dxfStrike) ? 2 : 1; - textFrag.RichTextOptions.SuperScript = rt.VerticalAlign == ExcelVerticalAlignmentFont.Superscript; - textFrag.RichTextOptions.SubScript = rt.VerticalAlign == ExcelVerticalAlignmentFont.Subscript; - textFrag.RichTextOptions.FontColor = dxfColor ?? rt.Color; - - textFrag.Font.SubFamily = ComputeFontStyle(textFrag); - - textFragments.Add(textFrag); - dictionaries.AddFont(pageSettings, textFrag.Font.Family, textFrag.Font.SubFamily, textFrag.Text); - } - return textFragments; - } - - private static List GetTextFragmentsFromCellStyle(PdfPageSettings pageSettings, PdfDictionaries dictionaries, ExcelRangeBase cell, string forcedText, - bool dxfBold, bool dxfItalic, bool dxfStrike, bool dxfUnderline, ExcelUnderLineType dxfUnderLineType, System.Drawing.Color? dxfColor) - { - var font = cell.Style.Font; - var textFragments = new List(1); - - var textFrag = new TextFragment(); - textFrag.Font = new RichTextFormatSimple(); - textFrag.Text = forcedText == null ? cell.Text : forcedText; - - textFrag.Font.Family = font.Name; - textFrag.Font.Size = font.Size; - - textFrag.RichTextOptions.Bold = font.Bold || dxfBold; - textFrag.RichTextOptions.Italic = font.Italic || dxfItalic; - - // Cell-style underline; dxf overrides only when the cell itself is not underlined. - ExcelUnderLineType underLineType; - if (font.UnderLine) - { - underLineType = font.UnderLineType == ExcelUnderLineType.None ? ExcelUnderLineType.Single : font.UnderLineType; - } - else if (dxfUnderline) - { - underLineType = dxfUnderLineType; - } - else - { - underLineType = ExcelUnderLineType.None; - } - textFrag.RichTextOptions.UnderlineType = MapUnderlineType(underLineType); - - textFrag.RichTextOptions.StrikeType = (font.Strike || dxfStrike) ? 2 : 1; - textFrag.RichTextOptions.SuperScript = font.VerticalAlign == ExcelVerticalAlignmentFont.Superscript; - textFrag.RichTextOptions.SubScript = font.VerticalAlign == ExcelVerticalAlignmentFont.Subscript; - textFrag.RichTextOptions.FontColor = dxfColor ?? font.Color.ToColor(); - - textFrag.Font.SubFamily = ComputeFontStyle(textFrag); - - textFragments.Add(textFrag); - dictionaries.AddFont(pageSettings, textFrag.Font.Family, textFrag.Font.SubFamily, textFrag.Text); - return textFragments; - } - - private static void ReadDxfFontOverrides(PdfCellStyle cellStyle, out bool bold, out bool italic, out bool strike, out bool underline, out ExcelUnderLineType underLineType, out System.Drawing.Color? color) - { - bold = false; - italic = false; - strike = false; - underline = false; - underLineType = ExcelUnderLineType.None; - color = null; - if (cellStyle != null && cellStyle.dxfFont != null) - { - bold = cellStyle.dxfFont.Bold != null ? (bool)cellStyle.dxfFont.Bold : false; - italic = cellStyle.dxfFont.Italic != null ? (bool)cellStyle.dxfFont.Italic : false; - strike = cellStyle.dxfFont.Strike != null ? (bool)cellStyle.dxfFont.Strike : false; - underline = cellStyle.dxfFont.Underline != null; - underLineType = cellStyle.dxfFont.Underline != null ? (ExcelUnderLineType)cellStyle.dxfFont.Underline : ExcelUnderLineType.None; - if (cellStyle.dxfFont.Color != null && cellStyle.dxfFont.Color.HasValue) - { - color = cellStyle.dxfFont.Color.GetColorAsColor(); - } - } - } - - private static string ResolveErrorText(PdfPageSettings pageSettings, ExcelRangeBase cell) - { - if (!ExcelErrorValue.IsErrorValue(cell.Text)) return null; - switch (pageSettings.CellErrors) - { - case CellErrors.Blank: return ""; - case CellErrors.Dashed: return "--"; - case CellErrors.NA: return "#N/A"; - case CellErrors.Displayed: - default: return null; - } - } - - private static int MapUnderlineType(ExcelUnderLineType type) - { - // 12 = none, 13 = single, 4 = double (matches existing rendering code; accounting not supported). - if (type == ExcelUnderLineType.Single) return 13; - if (type == ExcelUnderLineType.Double) return 4; - return 12; - } - - //private static MeasurementFontStyles ComputeFontStyle(TextFragment textFrag) - //{ - // return (textFrag.RichTextOptions.Bold ? MeasurementFontStyles.Bold : 0) | - // (textFrag.RichTextOptions.Italic ? MeasurementFontStyles.Italic : 0) | - // (textFrag.RichTextOptions.UnderlineType != 12 ? MeasurementFontStyles.Underline : 0) | - // (textFrag.RichTextOptions.StrikeType > 1 ? MeasurementFontStyles.Strikeout : 0); - //} - - private static FontSubFamily ComputeFontStyle(TextFragment textFrag) - { - if (textFrag.RichTextOptions.Bold && textFrag.RichTextOptions.Italic) return FontSubFamily.BoldItalic; - if(textFrag.RichTextOptions.Bold) return FontSubFamily.Bold; - if (textFrag.RichTextOptions.Italic) return FontSubFamily.Italic; - return FontSubFamily.Regular; - } - - - //private static List GetTextFormats(PdfPageSettings pageSettings, PdfDictionaries dictionaries, ExcelRichTextCollection RichTextCollection, PdfCellStyle cellStyle = null) - //{ - // var textFormats = new List(); - // bool bold = false, italic = false, underline = false, strike = false; - // ExcelUnderLineType underLineType = ExcelUnderLineType.None; - // if (cellStyle != null && cellStyle.dxfFont != null) - // { - // bold = cellStyle.dxfFont.Bold != null ? (bool)cellStyle.dxfFont.Bold : false; - // italic = cellStyle.dxfFont.Italic != null ? (bool)cellStyle.dxfFont.Italic : false; - // strike = cellStyle.dxfFont.Strike != null ? (bool)cellStyle.dxfFont.Strike : false; - // underline = cellStyle.dxfFont.Underline != null; - // underLineType = cellStyle.dxfFont.Underline != null ? (ExcelUnderLineType)cellStyle.dxfFont.Underline : ExcelUnderLineType.None; - // } - // for (int i = 0; i < RichTextCollection.Count; i++) - // { - // var rt = RichTextCollection[i]; - // var textFormat = new PdfTextFormat(); - // textFormat.Text = rt.Text; - // textFormat.FontName = rt.FontName; - // textFormat.FontFamily = rt.Family; - // textFormat.FontSize = rt.Size; - // textFormat.Bold = rt.Bold || bold; - // textFormat.Italic = rt.Italic || italic; - // textFormat.Strike = rt.Strike || strike; - // textFormat.Underline = rt.UnderLine || underline; - // textFormat.UnderlineType = rt.UnderLineType == ExcelUnderLineType.None ? underLineType : rt.UnderLineType; - // textFormat.SuperScript = rt.VerticalAlign == ExcelVerticalAlignmentFont.Superscript; - // textFormat.SubScript = rt.VerticalAlign == ExcelVerticalAlignmentFont.Subscript; - // textFormat.FontColor = rt.Color; - // textFormat.SubFamily = FontSubFamily.Regular; - // if (textFormat.Bold) - // { - // textFormat.SubFamily = FontSubFamily.Bold; - // if (textFormat.Italic) - // { - // textFormat.SubFamily = FontSubFamily.BoldItalic; - // } - // } - // else if (textFormat.Italic) - // { - // textFormat.SubFamily = FontSubFamily.Italic; - // } - // textFormats.Add(textFormat); - // dictionaries.AddFont(pageSettings, textFormat.FullFontName, textFormat.SubFamily, textFormat.Text); - // } - // return textFormats; - //} - - public static PdfCellAlignmentData GetAlignmentData(PdfHeaderFooter headerFooter) - { - var contentAlignment = new PdfCellAlignmentData(); - switch (headerFooter.Alignment) - { - case HeaderFooterAlignment.Left: - contentAlignment.HorizontalAlignment = ExcelHorizontalAlignment.Left; - break; - case HeaderFooterAlignment.Center: - contentAlignment.HorizontalAlignment = ExcelHorizontalAlignment.Center; - break; - case HeaderFooterAlignment.Right: - contentAlignment.HorizontalAlignment = ExcelHorizontalAlignment.Right; - break; - } - return contentAlignment; - } - - public static PdfHeaderFooter GetTextFormats(PdfPageSettings pageSettings, PdfDictionaries dictionaries, ExcelWorksheet ws, ExcelHeaderFooterTextCollection textCollection, HeaderFooterType type, HeaderFooterAlignment alignment, HeaderFooterSection section) - { - if (textCollection == null || textCollection.Count <= 1) return null; - var ns = ws.Workbook.Styles.GetNormalStyle(); - var textFragments = new List(); - List NumberOfPagesIndexes = new List(); - List PageNumberIndexes = new List(); - for (int i = 1; i < textCollection.Count; i++) - { - var hf = textCollection[i]; - var textFrag = new TextFragment(); - textFrag.Font = new RichTextFormatSimple(); - - textFrag.Font.Family = string.IsNullOrEmpty(hf.FontName) ? ns.Style.Font.Name : hf.FontName; - textFrag.Font.Size = hf.FontSize == null ? ns.Style.Font.Size : (float)hf.FontSize; - - textFrag.RichTextOptions.Bold = hf.Bold; - textFrag.RichTextOptions.Italic = hf.Italic; - //underline - //none : 12 - //single : 13 - //Double : 4 - //accouting does not exsist - textFrag.RichTextOptions.UnderlineType = 12; - textFrag.RichTextOptions.UnderlineType = hf.Underline ? 13 : textFrag.RichTextOptions.UnderlineType; - textFrag.RichTextOptions.UnderlineType = hf.DoubleUnderline ? 4 : textFrag.RichTextOptions.UnderlineType; - textFrag.RichTextOptions.StrikeType = hf.Striketrough ? 2 : 1; - textFrag.RichTextOptions.FontColor = hf.Color; - - textFrag.Font.SubFamily = ComputeFontStyle(textFrag); - //(textFrag.RichTextOptions.Bold ? MeasurementFontStyles.Bold : 0) | - //(textFrag.RichTextOptions.Italic ? MeasurementFontStyles.Italic : 0) | - //(textFrag.RichTextOptions.UnderlineType != 12 ? MeasurementFontStyles.Underline : 0) | - //(textFrag.RichTextOptions.StrikeType > 1 ? MeasurementFontStyles.Strikeout : 0); - - var text = string.Empty; - switch (hf.FormatCode) - { - case ExcelHeaderFooterFormattingCodes.SheetName: - text += ws.Name; - break; - case ExcelHeaderFooterFormattingCodes.CurrentDate: - text += DateTime.Now.ToString($"yyyy-MM-dd"); - break; - case ExcelHeaderFooterFormattingCodes.FileName: - text += ws._package.File.Name; - break; - case ExcelHeaderFooterFormattingCodes.NumberOfPages: - text += "000"; - NumberOfPagesIndexes.Add(i-1); - break; - case ExcelHeaderFooterFormattingCodes.PageNumber: - text += "000"; - PageNumberIndexes.Add(i-1); - break; - case ExcelHeaderFooterFormattingCodes.CurrentTime: - text += DateTime.Now.ToString("HH:mm"); - break; - case ExcelHeaderFooterFormattingCodes.FilePath: - text += ws._package.File.Directory.FullName + "\\"; - break; - default: - text += hf.Text; - break; - } - - textFrag.Text = text; - - textFragments.Add(textFrag); - dictionaries.AddFont(pageSettings, textFrag.Font.Family, textFrag.Font.SubFamily, textFrag.Text); - if (NumberOfPagesIndexes.Count > 0 || PageNumberIndexes.Count > 0) dictionaries.AddFont(pageSettings, textFrag.Font.Family, textFrag.Font.SubFamily, "1234567890"); - - - - - //var text = textCollection[i]; - //var ns = ws.Workbook.Styles.GetNormalStyle(); - //PdfTextFormat textFormat = new PdfTextFormat(); - //textFormat.FontName = string.IsNullOrEmpty(text.FontName) ? ns.Style.Font.Name : text.FontName; - //textFormat.FontSize = text.FontSize == null ? ns.Style.Font.Size : (double)text.FontSize; - //textFormat.Bold = text.Bold; - //textFormat.Italic = text.Italic; - //textFormat.Strike = text.Striketrough; - //textFormat.SubScript = text.SubScript; - //textFormat.SuperScript = text.SuperScript; - //textFormat.Underline = text.Underline; - //textFormat.UnderlineType = textFormat.Underline ? ExcelUnderLineType.Single : ExcelUnderLineType.None; - //if (text.DoubleUnderline) textFormat.Underline = true; - //textFormat.UnderlineType = text.DoubleUnderline ? ExcelUnderLineType.Double : textFormat.UnderlineType; - //textFormat.FontColor = text.Color; - //textFormat.SubFamily = FontSubFamily.Regular; - //if (textFormat.Bold) - //{ - // textFormat.SubFamily = FontSubFamily.Bold; - // if (textFormat.Italic) - // { - // textFormat.SubFamily = FontSubFamily.BoldItalic; - // } - //} - //else if (textFormat.Italic) - //{ - // textFormat.SubFamily = FontSubFamily.Italic; - //} - //switch (text.FormatCode) - //{ - // case ExcelHeaderFooterFormattingCodes.SheetName: - // textFormat.Text += ws.Name; - // break; - // case ExcelHeaderFooterFormattingCodes.CurrentDate: - // textFormat.Text += DateTime.Now.ToString($"yyyy-MM-dd"); - // break; - // case ExcelHeaderFooterFormattingCodes.FileName: - // textFormat.Text += ws._package.File.Name; - // break; - // case ExcelHeaderFooterFormattingCodes.NumberOfPages: - // case ExcelHeaderFooterFormattingCodes.PageNumber: - // textFormat.Text += "000"; - // containsPageNumber = true; - // break; - // case ExcelHeaderFooterFormattingCodes.CurrentTime: - // textFormat.Text += DateTime.Now.ToString("HH:mm"); - // break; - // case ExcelHeaderFooterFormattingCodes.FilePath: - // textFormat.Text += ws._package.File.Directory.FullName + "\\"; - // break; - // default: - // textFormat.Text += text.Text; - // break; - //} - //textFormats.Add(textFormat); - //dictionaries.AddFont(pageSettings, textFormat.FullFontName, textFormat.SubFamily, textFormat.Text); - //if (containsPageNumber) dictionaries.AddFont(pageSettings, textFormat.FullFontName, textFormat.SubFamily, "1234567890"); - } - return new PdfHeaderFooter(textFragments, PageNumberIndexes, NumberOfPagesIndexes, type, alignment, section); - } - - - /// - /// Check if we need to add additional columns to accomodate text that is not wrapped and overlaps other cell.s - /// - /// The worksheet to check. - /// The number of columns to add. - public static int AddColumnsForNonWrappedText(PdfPageSettings pageSettings, ExcelWorksheet ws, PdfWorksheet pdfSheet) - { - int columnsToAdd = 0; - var catalog = new PdfCatalog(); - var lastColumn = ws.Dimension.End.Column; - ExcelRangeBase lastColumnRange = ws.Cells[1, lastColumn, ws.Dimension.End.Row, lastColumn]; - var cc = catalog.GetCellCollectionFromRange(pageSettings, lastColumnRange); - double textLength = 0; - for (int i = cc.FromRow; i < cc.ToRow; i++) - { - textLength = cc[i, cc.FromColumn].TotalTextLength > textLength ? cc[i, cc.FromColumn].TotalTextLength : textLength; - } - double columnWidth = UnitConversion.ExcelColumnWidthToPoints(ws.Column(ws.Dimension._toCol).Width, pdfSheet.ZeroCharWidth); - while (textLength > columnWidth) - { - columnsToAdd++; - columnWidth += UnitConversion.ExcelColumnWidthToPoints(ws.Column(ws.Dimension._toCol + columnsToAdd).Width, pdfSheet.ZeroCharWidth); - } - return columnsToAdd; - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextShaper.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextShaper.cs deleted file mode 100644 index 45eb33a333..0000000000 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextShaper.cs +++ /dev/null @@ -1,112 +0,0 @@ -using EPPlus.Export.Pdf.PdfLayout; -using EPPlus.Export.Pdf.PdfResources; -using EPPlus.Export.Pdf.PdfSettings; -using EPPlus.Fonts.OpenType; -using EPPlus.Fonts.OpenType.Integration; -using EPPlus.Fonts.OpenType.TextShaping; -using OfficeOpenXml.Interfaces.Drawing.Text; -using OfficeOpenXml.Interfaces.Fonts; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace EPPlus.Export.Pdf.PdfCatalog -{ - internal static class PdfTextShaper - { - private static Dictionary shaperCache = new Dictionary(); - private static Dictionary layoutEngineCache = new Dictionary(); - - // Pass 1: collect text per font so FontSubsetManager can build subsets once - public static void CollectText(PdfDictionaries dictionaries, PdfCell cell) - { - if (cell == null || cell.TextFragments == null) return; - - for (int i = 0; i < cell.TextFragments.Count; i++) - { - var tf = cell.TextFragments[i]; - if (!dictionaries.Fonts.ContainsKey(tf.FullFontName)) continue; - dictionaries.Fonts[tf.FullFontName].fontSubsetManager.AddText(tf.Text); - } - } - - // Pass 2: shape text using already-built providers from PdfDictionaries.ShapedProviders - public static void ShapeText(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfCell cell) - { - var totalTextLength = 0d; - var maxLineHeight = 0d; - if (cell == null || cell.TextFragments == null) return; - - cell.ShapedTexts = new List(); - - for (int i = 0; i < cell.TextFragments.Count; i++) - { - var tf = cell.TextFragments[i]; - cell.ShapedTexts.Add(new PdfShapedText()); - var st = cell.ShapedTexts[i]; - - if (!dictionaries.ShapedProviders.TryGetValue(tf.FullFontName, out var provider)) - { - continue; - } - st.FontProvider = provider; - - if (!shaperCache.TryGetValue(st.FontProvider, out var shaper)) - { - shaper = new TextShaper(st.FontProvider); - shaperCache[st.FontProvider] = shaper; - } - - if (!layoutEngineCache.TryGetValue(st.FontProvider, out var layoutEngine)) - { - layoutEngine = new TextLayoutEngine(shaper); - layoutEngineCache[st.FontProvider] = layoutEngine; - } - - var options = ShapingOptions.Default; - options.ApplyPositioning = true; - options.ApplySubstitutions = true; - - var shaped = shaper.Shape(tf.Text, options); - var usedFonts = shaper.GetUsedFonts().ToList(); - var fontIdMap = new Dictionary(); - - for (byte fontId = 0; fontId < usedFonts.Count; fontId++) - { - var font = usedFonts[fontId]; - if (!dictionaries.Fonts.ContainsKey(font.FullName)) - { - int label = dictionaries.Fonts.Count > 0 - ? dictionaries.Fonts.Last().Value.labelNumber + 1 - : 1; - var fontResource = new PdfFontResource(font.FullName, font.NameTable.GetSubfamilyEnum(), label, pageSettings); - fontResource.fontData = font; - dictionaries.Fonts.Add(font.FullName, fontResource); - } - fontIdMap[fontId] = dictionaries.Fonts[font.FullName].Label; - } - - cell.TextLayoutEngine = layoutEngine; - st.ShapedText = shaped; - - totalTextLength += st.ShapedText.GetWidthInPoints((float)tf.Font.Size); - maxLineHeight = Math.Max(st.ShapedText.GetLineHeightInPoints((float)tf.Font.Size), maxLineHeight); - - st.FontIdMap = fontIdMap; - st.UsedFonts = usedFonts; - cell.TextFragments[i] = tf; - cell.ShapedTexts[i] = st; - } - - if (cell.TextLayoutEngine != null) - { - double wrapWidth = (cell.Merged && cell.Main == null) ? cell.Width : cell.ColumnWidth; - cell.TextLines = cell.ContentAligmnet.WrapText - ? cell.TextLayoutEngine.WrapRichTextLineCollection(cell.TextFragments, wrapWidth) - : cell.TextLayoutEngine.WrapRichTextLineCollection(cell.TextFragments, double.MaxValue); - } - - cell.TotalTextLength = totalTextLength; - } - } -} \ No newline at end of file diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfWorksheet.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfWorksheet.cs deleted file mode 100644 index acf8748a20..0000000000 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfWorksheet.cs +++ /dev/null @@ -1,63 +0,0 @@ -using EPPlus.Export.Pdf.PdfLayout; -using EPPlus.Fonts.OpenType; -using EPPlus.Fonts.OpenType.TextShaping; -using OfficeOpenXml; -using OfficeOpenXml.Interfaces.Drawing.Text; -using OfficeOpenXml.Interfaces.Fonts; -using OfficeOpenXml.Style.XmlAccess; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace EPPlus.Export.Pdf.PdfCatalog -{ - internal class PdfWorksheet - { - public Dictionary CommentsAndNotesCollections = new Dictionary(); - - public List Ranges = null; //Rename this - public PdfRange CommentsAndNotes; - public PdfHeaderFooterCollection HeaderFooters = null; - public double ZeroCharWidth; - public int ToRow; - public int PrintTitleRowFrom = -1; - public int PrintTitleRowTo = -1; - public int PrintTitleColFrom = -1; - public int PrintTitleColTo = -1; - - //EPPlus references - public ExcelWorksheet Worksheet { get; set; } - public ExcelWorksheet CommentsAndNotesSheet { get; set; } - public ExcelNamedStyleXml NormalStyle { get { return Worksheet.Workbook.Styles.GetNormalStyle(); } } - public FontSubFamily GetSubFamilyFromNormalStyle //move this to a helper class or something. - { - get - { - var nsf = NormalStyle.Style.Font; - var SubFamily = FontSubFamily.Regular; - if (nsf.Bold) - { - SubFamily = FontSubFamily.Bold; - if (nsf.Italic) - { - SubFamily = FontSubFamily.BoldItalic; - } - } - else if (nsf.Italic) - { - SubFamily = FontSubFamily.Italic; - } - return SubFamily; - } - } - - public static double GetThemeFont0Width(ExcelWorksheet ws) - { - var ns = ws.Workbook.Styles.GetNormalStyle(); - TextShaper shaper = OpenTypeFonts.GetTextShaper(ns.Style.Font.Name, FontSubFamily.Regular); - var shapedText = shaper.ShapeLight("0"); - return shapedText.GetWidthInPoints(ns.Style.Font.Size); - } - } -} \ No newline at end of file diff --git a/src/EPPlus.Export.Pdf/PdfLayout/IShadingLayout.cs b/src/EPPlus.Export.Pdf/PdfLayout/IShadingLayout.cs deleted file mode 100644 index 06493c9999..0000000000 --- a/src/EPPlus.Export.Pdf/PdfLayout/IShadingLayout.cs +++ /dev/null @@ -1,27 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.PdfSettings; - -namespace EPPlus.Export.Pdf.PdfLayout -{ - /// - /// Interface for shading objects. - /// - internal interface IShadingLayout - { - /// - /// Update position matrix. - /// - abstract void UpdateShadingPositionMatrix(PdfPageSettings pageSettings); - } -} diff --git a/src/EPPlus.Export.Pdf/PdfLayout/ITextLayout.cs b/src/EPPlus.Export.Pdf/PdfLayout/ITextLayout.cs deleted file mode 100644 index cd0aee83ca..0000000000 --- a/src/EPPlus.Export.Pdf/PdfLayout/ITextLayout.cs +++ /dev/null @@ -1,21 +0,0 @@ -using EPPlus.Fonts.OpenType.Integration; -using OfficeOpenXml; -using OfficeOpenXml.FormulaParsing.Excel.Functions.Text; -using System.Collections.Generic; - -namespace EPPlus.Export.Pdf.PdfLayout -{ - internal struct PdfTextFormat - { } - internal interface ITextLayout - { - public List TextFormats { get; set; } - public double TextLength { get; set; } - public double TextHeight { get; set; } - public TextLayoutEngine TextLayoutEngine{ get; set; } - - //public abstract void CalculateTextSpill(double Width, double Rotation); - //public abstract Vector2 CalculateAlignmentPositionAndTextOffsets(ExcelRangeBase cell, double x, double y, double width, double height); - //public abstract void CheckClipping(ExcelRangeBase cell, double x, double y, double width, double height); - } -} diff --git a/src/EPPlus.Export.Pdf/PdfLayout/PdfCatalogLayout.cs b/src/EPPlus.Export.Pdf/PdfLayout/PdfCatalogLayout.cs deleted file mode 100644 index e118712ff8..0000000000 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfCatalogLayout.cs +++ /dev/null @@ -1,1081 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.PdfResources; -using EPPlus.Export.Pdf.PdfSettings; -using EPPlus.Fonts.OpenType; -using EPPlus.Fonts.OpenType.Integration; -using EPPlus.Fonts.OpenType.TextShaping; -using EPPlus.Graphics; -using EPPlus.Graphics.Geometry; -using EPPlus.Graphics.Units; -using OfficeOpenXml; -using OfficeOpenXml.Interfaces.Fonts; -using OfficeOpenXml.Style; -using OfficeOpenXml.Style.HeaderFooterTextFormat; -using System; -using System.Collections.Generic; -using System.Data; -using System.Diagnostics; -using System.Drawing; -using System.Linq; - -namespace EPPlus.Export.Pdf.PdfLayout -{ - internal class PdfCatalogLayout : Transform - { - public PdfCatalogLayout(ExcelRangeBase range, PdfPageSettings pageSettings, PdfDictionaries dictionaries) - : base(0, 0, 0, 0) - { - } - - public PdfCatalogLayout(ExcelWorkbook workbook, PdfPageSettings pageSettings, PdfDictionaries dictionaries) - : base(0, 0, 0, 0) - { - } - - public PdfCatalogLayout(ExcelWorksheet worksheet, PdfPageSettings pageSettings, PdfDictionaries dictionaries) - : base(0, 0, 0, 0) - { - Stopwatch sw = Stopwatch.StartNew(); - - Name = worksheet.Name + " Catalog"; - var WorksheetLayout = AddChild(new PdfWorksheetLayout(worksheet, pageSettings, dictionaries)); - sw.Stop(); - var wsLayoutTime = sw.ElapsedMilliseconds; - sw.Reset(); - sw.Start(); - - //CreateFontSubsets(pageSettings, dictionaries.Fonts); - var PagesLayout = CreatePagesLayoutObject(); - sw.Stop(); - var PagesTime = sw.ElapsedMilliseconds; - sw.Reset(); - sw.Start(); - - CreatePageLayoutObjects(worksheet, pageSettings, dictionaries, WorksheetLayout as PdfWorksheetLayout, PagesLayout); - sw.Stop(); - var PageTime = sw.ElapsedMilliseconds; - sw.Reset(); - sw.Start(); - - //Add Page for Notes and Comments - CreatePageForLayoutComments(pageSettings, dictionaries, worksheet, PagesLayout); - sw.Stop(); - var CommentsTime = sw.ElapsedMilliseconds; - sw.Reset(); - sw.Start(); - - AddHeaderFooter(worksheet, pageSettings, dictionaries, PagesLayout); - sw.Stop(); - var HeaderFooterTime = sw.ElapsedMilliseconds; - sw.Reset(); - sw.Start(); - - PopulatePages(pageSettings, dictionaries, WorksheetLayout, PagesLayout); - sw.Stop(); - var PopulateTime = sw.ElapsedMilliseconds; - sw.Reset(); - sw.Start(); - - //AddCellsToPageLayout(WorksheetLayout, PagesLayout); - //HandleMergedCellsAndDrawings(pageSettings, dictionaries, WorksheetLayout, PagesLayout); - MoveCellToPageFromContent(pageSettings, dictionaries, PagesLayout); - sw.Stop(); - var MoveCellsTime = sw.ElapsedMilliseconds; - sw.Reset(); - sw.Start(); - - ProocessPageAndCells(pageSettings, dictionaries, PagesLayout); - sw.Stop(); - var ProcessCellsTime = sw.ElapsedMilliseconds; - sw.Reset(); - sw.Start(); - - //ConvertToPDFCoordiantes(pageSettings, PagesLayout, worksheet); - AdjustAndSort(PagesLayout, dictionaries); - RemoveChild(WorksheetLayout); - sw.Stop(); - var AdjustSortTime = sw.ElapsedMilliseconds; - sw.Reset(); - } - - //Create the pages layout. - private PdfPagesLayout CreatePagesLayoutObject() - { - var pages = new PdfPagesLayout(0, 0, 0, 0); - pages.Name = "Pages"; - AddChild(pages); - return pages; - } - - //Create page and content objects. - internal static void CreatePageLayoutObjects(ExcelWorksheet worksheet, PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfWorksheetLayout worksheetLayout, PdfPagesLayout pages, bool IsCommentPage = false) - { - - /* when calculating pages we need to take row and column headings width and height into consideration we could start our for loops and 0 and check if j==0 and add row width and then just proceed like usual and - * every time we hit a break we then add row width again. these are found in worksheet layout to use where we calculate them. - * - * Next step in populatePages? would be to add additional cells for these headings when we have a new page first row is creating new PdfCellLayout and PdfCellContentLayout. This might conflict with text shaping since we now add new text while also shaping text. - * A solution to this could be when doing the worksheet layout is to check dimensions and add A-Z, 1-9 as needed to a new font entry in dictionaries.Fonts - */ - - //Get x cooridiantes to break for new page - double rowHeading = pageSettings.ShowHeadings ? PdfWorksheetLayout.RowHeadingWidth : 0d; - List xBreaks = new List() { 0 }; - double currentWidth = 0, boundsWidth = pageSettings.ContentBounds.Width; - List columns = new List(); - int columnCount = 0; - for (int j = 1; j <= worksheet.Dimension._toCol; j++) - { - if (worksheet.Column(j).Hidden) { continue; } - var width = UnitConversion.ExcelColumnWidthToPoints(worksheet.Column(j).Width, PdfWorksheetLayout.ZeroCharWidth); - if (currentWidth + width + rowHeading >= boundsWidth) - { - xBreaks.Add(currentWidth); - columns.Add(columnCount); - columnCount = 0; - boundsWidth = currentWidth + pageSettings.ContentBounds.Width; - } - currentWidth += width; - columnCount++; - if (worksheet.Column(j).PageBreak) - { - xBreaks.Add(currentWidth); - columns.Add(columnCount); - columnCount = 0; - boundsWidth = currentWidth + pageSettings.ContentBounds.Width; - } - } - xBreaks.Add(currentWidth); - columns.Add(columnCount); - //Get y cooridiantes to break for new page - double colHeading = pageSettings.ShowHeadings ? -PdfWorksheetLayout.ColumnHeadingHeight : 0; - List yBreaks = new List() { 0d }; - double currentHeight = 0, boundsHeight = -pageSettings.ContentBounds.Height; - List rows = new List(); - int rowCount = 0; - for (int i = 1; i <= worksheet.Dimension._toRow; i++) - { - if (worksheet.Row(i).Hidden) { continue; } - var height = UnitConversion.ExcelRowHeightToPoints(worksheet.Row(i).Height); - if (currentHeight - height + colHeading <= boundsHeight) - { - yBreaks.Add(currentHeight); - rows.Add(rowCount); - rowCount = 0; - boundsHeight = currentHeight - pageSettings.ContentBounds.Height; - } - currentHeight -= height; - rowCount++; - if (worksheet.Row(i).PageBreak) - { - yBreaks.Add(currentHeight); - rows.Add(rowCount); - rowCount = 0; - boundsHeight = currentHeight - pageSettings.ContentBounds.Height; - } - } - yBreaks.Add(currentHeight); - rows.Add(rowCount); - //calculate number of pages needed based on contentBounds and worksheetLayout.Size - int horizontalPageCount = xBreaks.Count-1; //System.Math.Max(1, (int)System.Math.Ceiling(worksheetLayout.Size.X / pageSettings.ContentBounds.Width)); - int verticalPageCount = yBreaks.Count-1; //System.Math.Max(1, (int)System.Math.Ceiling(System.Math.Abs( worksheetLayout.Size.Y) / pageSettings.ContentBounds.Height)); - int totalPages = horizontalPageCount * verticalPageCount; - //Create the new pages and place them in a grid. - for (int i = 0; i < totalPages; i++) - { - int row, col; - if (pageSettings.PageOrders == PageOrders.DownThenOver) - { - col = i / verticalPageCount; - row = i % verticalPageCount; - } - else //(settings.PageOrders == PageOrders.OverThenDown) - { - col = i % horizontalPageCount; - row = i / horizontalPageCount; - } - double x = col * pageSettings.PageSize.WidthPu; - double y = row * -pageSettings.PageSize.HeightPu; - PdfPageLayout page = new PdfPageLayout(x, y-pageSettings.PageSize.HeightPu, pageSettings.PageSize.WidthPu, pageSettings.PageSize.HeightPu); - page.Name = "Page " + (i + 1); - //ContentLayout - double width = xBreaks[col + 1] - xBreaks[col]; - double height = Math.Abs( yBreaks[row] - yBreaks[row + 1]); - PdfContentLayout content = new PdfContentLayout(0, 0, width, height); - content.Name = "Content " + (i + 1); - if (pageSettings.ShowHeadings) - { - AddColumnHeadings(worksheet, pageSettings, dictionaries, content, columns[col], col, columns); - AddRowHeadings(worksheet, pageSettings, dictionaries, content, rows[row], row, rows); - } - page.AddChild(content); - page.isCommentsPage = IsCommentPage; - content.Position = new Vector2(xBreaks[col], yBreaks[row+1]); - pages.AddChild(page); - } - } - - private static void AddColumnHeadings(ExcelWorksheet ws, PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfContentLayout content, int columnsInPage, int pageColumn, List columns ) - { - int startColIndex = 1; - for (int i = 0; i < pageColumn; i++) - { - startColIndex += columns[i]; - } - double X = content.LocalPosition.X; - for (int j = startColIndex; j < startColIndex + columnsInPage; j++) - { - if (ws.Column(j).Hidden) { continue; } - string column = ExcelRangeBase.GetColumnLetter(j); - var col = ws.Cells[column]; - var width = UnitConversion.ExcelColumnWidthToPoints(ws.Column(j).Width, PdfWorksheetLayout.ZeroCharWidth); - var height = UnitConversion.ExcelRowHeightToPoints(ws.DefaultRowHeight); - var cellStyle = new PdfCellStyle(); - cellStyle.xfFill = col.Style.Fill; - var cell = new PdfCellLayout(dictionaries, col, cellStyle, X, content.LocalPosition.Y + content.Size.Y + height, width, height, 1, 1, 0, content); - cell.Name = col.Address; - cell.Z = 8; - cell.IsHeading = true; - cell.CellFillData.PatternStyle = ExcelFillStyle.Solid; - cell.CellFillData.BackgroundColor = Color.White; - //Add text - var cellContent = new PdfCellContentLayout(column, col, cellStyle, pageSettings, X, content.LocalPosition.Y + content.Size.Y, width, height, 1, 1, 0, content, dictionaries); - cellContent.Name = col.Address; - cellContent.Z = 9; - //Add border - cellStyle.xfTop = col.Style.Border.Top; - cellStyle.xfBottom = col.Style.Border.Bottom; - cellStyle.xfLeft = col.Style.Border.Left; - cellStyle.xfRight = col.Style.Border.Right; - var border = new PdfCellBorderLayout(col, cellStyle, X, content.LocalPosition.Y + content.Size.Y + height, width, height, 1, 1, 0, content); - border.Name = col.Address; - border.Z = 10; - border.InitEdgeBorders(col); - border.BorderData.Top.BorderStyle = ExcelBorderStyle.Thin; - border.BorderData.Bottom.BorderStyle = ExcelBorderStyle.Thin; - border.BorderData.Left.BorderStyle = ExcelBorderStyle.Thin; - border.BorderData.Right.BorderStyle = ExcelBorderStyle.Thin; - border.BorderData.Top.IsHeading = true; - border.BorderData.Bottom.IsHeading = true; - border.BorderData.Left.IsHeading = true; - border.BorderData.Right.IsHeading = true; - - X = X + width; - } - } - - private static void AddRowHeadings(ExcelWorksheet ws, PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfContentLayout content, int rowsInPage, int pageRow, List rows) - { - int startRowIndex = 1; - for (int i = 0; i < pageRow; i++) - { - startRowIndex += rows[i]; - } - double Y = content.LocalPosition.Y + content.Size.Y; - for (int j = startRowIndex; j < startRowIndex + rowsInPage; j++) - { - if (ws.Row(j).Hidden) { continue; } - var row = ws.Cells[j, 1]; - var height = UnitConversion.ExcelRowHeightToPoints(ws.Row(j).Height); - var width = PdfWorksheetLayout.RowHeadingWidth; - var cellStyle = new PdfCellStyle(); - cellStyle.xfFill = row.Style.Fill; - var cell = new PdfCellLayout(dictionaries, row, cellStyle, content.LocalPosition.X - width, Y, width, height, 1, 1, 0, content); - cell.Name = j.ToString(); - cell.Z = 8; - cell.IsHeading = true; - cell.CellFillData.PatternStyle = ExcelFillStyle.Solid; - cell.CellFillData.BackgroundColor = Color.White; - //Add text - var cellContent = new PdfCellContentLayout(j.ToString(), row, cellStyle, pageSettings, content.LocalPosition.X - width, Y - height, width, height, 1, 1, 0, content, dictionaries); - cellContent.Name = j.ToString(); - cellContent.Z = 9; - //Add border - cellStyle.xfTop = row.Style.Border.Top; - cellStyle.xfBottom = row.Style.Border.Bottom; - cellStyle.xfLeft = row.Style.Border.Left; - cellStyle.xfRight = row.Style.Border.Right; - var border = new PdfCellBorderLayout(row, cellStyle, content.LocalPosition.X - width, Y, width, height, 1, 1, 0, content); - border.Name = j.ToString(); - border.Z = 10; - border.InitEdgeBorders(row); - border.BorderData.Top.BorderStyle = ExcelBorderStyle.Thin; - border.BorderData.Bottom.BorderStyle = ExcelBorderStyle.Thin; - border.BorderData.Left.BorderStyle = ExcelBorderStyle.Thin; - border.BorderData.Right.BorderStyle = ExcelBorderStyle.Thin; - border.BorderData.Top.IsHeading = true; - border.BorderData.Bottom.IsHeading = true; - border.BorderData.Left.IsHeading = true; - border.BorderData.Right.IsHeading = true; - - Y = Y - height; - } - } - - //Create pages for comments and notes - private void CreatePageForLayoutComments(PdfPageSettings pageSettings, PdfDictionaries dictionaries, ExcelWorksheet ws, PdfPagesLayout pagesLayout) - { - PdfCommentsLayout.CreateCommentAndNotesPages(pageSettings, dictionaries, ws, pagesLayout); - } - - private void LayoutAndShapeCommentsAndNotes(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfPageLayout page) - { - page.ChildObjects[0].LocalPosition = new Vector2(pageSettings.Margins.LeftPu, pageSettings.PageSize.HeightPu - pageSettings.Margins.TopPu - page.ChildObjects[0].Size.Y); - var contentList = page.ChildObjects[0].ChildObjects.ToArray(); - foreach (var child in contentList) - { - page.AddChild(child); - if (child is PdfCellContentLayout ccl) - { - //LayoutAndShapeText(pageSettings, dictionaries, shaperCache, layoutEngineCache, ccl); - ccl.LocalPosition = ccl.CalculateAlignmentPositionAndTextOffsets(ccl.cell, ccl.LocalPosition.X, ccl.LocalPosition.Y, ccl.Size.X, ccl.Size.Y); - } - } - page.RemoveChild(page.ChildObjects[0]); - } - - //Internal class for storing bounds for pages - internal class PageData - { - public PdfPageLayout Page; - public Rect Bounds; // replace BoundingBox with whatever your bounds type is - - public PageData(PdfPageLayout page, Rect bounds) - { - Page = page; - Bounds = bounds; - } - } - - private Dictionary shaperCache = new Dictionary(); - private Dictionary layoutEngineCache = new Dictionary(); - - //Move cells to their overlapping pages. - private void PopulatePages(PdfPageSettings pageSettings, PdfDictionaries dictionaries, Transform WorksheetLayout, PdfPagesLayout pages) - { - var pageData = new List(); - foreach (PdfPageLayout p in pages.ChildObjects) - { - var hfs = p.ChildObjects.Where(x => x is PdfHeaderFooterLayout).ToArray(); - foreach (var hf in hfs) - { - //LayoutAndShapeText(pageSettings, dictionaries, shaperCache, layoutEngineCache, (PdfHeaderFooterLayout)hf); - } - if (p.isCommentsPage) - { - LayoutAndShapeCommentsAndNotes(pageSettings, dictionaries, p); - continue; - } - - pageData.Add(new PageData(p, p.ChildObjects[0].GetGlobalBoundingbox())); - var contents = p.ChildObjects[0].ChildObjects.Where(x => x is PdfCellContentLayout).ToArray(); - foreach (PdfCellContentLayout content in contents) - { - //LayoutAndShapeText(pageSettings, dictionaries, shaperCache, layoutEngineCache, content); - AdjustText(content.TextLength, content.TextHeight, content); - } - - } - pageData.Sort((a, b) => a.Bounds.Top.CompareTo(b.Bounds.Top)); - var transforms = new List(WorksheetLayout.ChildObjects); - foreach (var t in transforms) - { - var cellBounds = t.GetGlobalBoundingbox(); - // Pass 1: check if ANY page fully contains this transform. - // If so, we use only that page and skip all partial intersects. - PageData fullIntersectPage = null; - foreach (var pd in pageData) - { - if (pd.Page.isCommentsPage) continue; - if (pd.Bounds.Top > cellBounds.Bottom) break; - if (pd.Bounds.Bottom < cellBounds.Top) continue; - - if (IntersectsFully(pd.Bounds, cellBounds)) - { - fullIntersectPage = pd; - break; - } - } - // Pass 2: assign to pages. - foreach (var pd in pageData) - { - if (pd.Page.isCommentsPage) continue; - if (pd.Bounds.Top > cellBounds.Bottom) break; - if (pd.Bounds.Bottom < cellBounds.Top) continue; - - bool fullIntersect = fullIntersectPage != null && pd == fullIntersectPage; - bool partialIntersect = fullIntersectPage == null && !fullIntersect && Intersects(cellBounds, pd.Bounds); - - if (!fullIntersect && !partialIntersect) continue; - - var page = pd.Page; - - if (t is PdfMergedCellLayout merged) - { - if (fullIntersect) - { - page.ChildObjects[0].AddChild(merged); - break; - } - else - { - var copy = new PdfMergedCellLayout(dictionaries, merged.cell, merged.CellStyle, - merged.LocalPosition.X, merged.LocalPosition.Y + merged.Size.Y, - merged.Size.X, merged.Size.Y, - merged.LocalScale.X, merged.LocalScale.Y, - merged.LocalRotation, WorksheetLayout); - copy.Name = merged.Name; - copy.Z = merged.Z; - copy.address = merged.address; - page.ChildObjects[0].AddChild(copy); - } - } - else if (t is PdfCellLayout cell) - { - if (fullIntersect) - { - page.AddCell(cell); - break; - } - } - else if (t is PdfCellContentLayout cellContent) - { - if (fullIntersect) - { - page.AddCell(cellContent); - //LayoutAndShapeText(pageSettings, dictionaries, shaperCache, layoutEngineCache, cellContent); - AdjustText(cellContent.TextLength, cellContent.TextHeight, cellContent); - break; - } - else - { - var copy = new PdfCellContentLayout(cellContent.cell, cellContent.CellStyle, pageSettings, - cellContent.LocalPosition.X, cellContent.LocalPosition.Y, - cellContent.Size.X, cellContent.Size.Y, - cellContent.LocalScale.X, cellContent.LocalScale.Y, - cellContent.LocalRotation, WorksheetLayout, dictionaries); - copy.Name = cellContent.Name; - copy.Z = cellContent.Z; - page.ChildObjects[0].AddChild(copy); - //LayoutAndShapeText(pageSettings, dictionaries, shaperCache, layoutEngineCache, copy); - AdjustText(cellContent.TextLength, cellContent.TextHeight, cellContent); - } - } - else if (t is PdfCellBorderLayout border) - { - if (fullIntersect) - { - page.AddCell(border); - break; - } - else - { - var copy = new PdfCellBorderLayout(border.cell, null, - border.LocalPosition.X, border.LocalPosition.Y + border.Size.Y, - border.Size.X, border.Size.Y, - border.LocalScale.X, border.LocalScale.Y, - border.LocalRotation, WorksheetLayout); - copy.Name = border.Name; - copy.Z = border.Z; - copy.BorderData = border.BorderData; - copy.range = border.range; - copy.IsMerged = border.IsMerged; - page.ChildObjects[0].AddChild(copy); - } - } - else if (t is PdfDrawingLayout drawing) - { - if (fullIntersect) - break; - else - { - var copy = new PdfDrawingLayout(null, - drawing.LocalPosition.X, drawing.LocalPosition.Y, - drawing.Size.X, drawing.Size.Y); - page.ChildObjects[0].AddChild(copy); - } - } - } - } - } - - //Font handling, Text shaping and layouting wrapped text - private static void LayoutAndShapeText(PdfPageSettings pageSettings, PdfDictionaries dictionaries, Dictionary shaperCache, Dictionary layoutEngineCache, ITextLayout text) - { - //var totalTextLength = 0d; - //var maxLineHeight = 0d; - //for (int i = 0; i < text.TextFormats.Count; i++) - //{ - // var fd = text.TextFormats[i]; - // fd.FontProvider = dictionaries.Fonts[fd.FullFontName].fontSubsetManager.CreateSubsettedProvider(); - - // if (!shaperCache.TryGetValue(fd.FontProvider, out var shaper)) - // { - // shaper = new TextShaper(fd.FontProvider); - // shaperCache[fd.FontProvider] = shaper; - // } - - // if (!layoutEngineCache.TryGetValue(fd.FontProvider, out var layoutEngine)) - // { - // layoutEngine = new TextLayoutEngine(shaper); - // layoutEngineCache[fd.FontProvider] = layoutEngine; - // } - - // var options = ShapingOptions.Default; - // options.ApplyPositioning = true; - // options.ApplySubstitutions = true; - - // var shaped = shaper.Shape(fd.Text, options); - // var usedFonts = shaper.GetUsedFonts().ToList(); - // var fontIdMap = new Dictionary(); - - // var allProviderFonts = fd.FontProvider.GetAllFonts().ToList(); - - // for (byte fontId = 0; fontId < usedFonts.Count; fontId++) - // { - // var font = usedFonts[fontId]; - - // if (!dictionaries.Fonts.ContainsKey(font.FullName)) - // { - // int label = 1; - // if (dictionaries.Fonts.Count > 0) - // { - // label = dictionaries.Fonts.Last().Value.labelNumber + 1; - // } - // var fontResource = new PdfFontResource(font.FullName, font.NameTable.GetSubfamilyEnum(), label, pageSettings); - // fontResource.fontData = font; - // dictionaries.Fonts.Add(font.FullName, fontResource); - // } - // fontIdMap[fontId] = dictionaries.Fonts[font.FullName].Label; - // } - - // text.TextLayoutEngine = layoutEngine; - // fd.ShapedText = shaped; - // var textWdith = fd.ShapedText.GetWidthInPoints((float)fd.FontSize); - // var textHeight = fd.ShapedText.GetLineHeightInPoints((float)fd.FontSize); - // fd.TextLength = textWdith; - // fd.TextHeight = textHeight; - // totalTextLength += textWdith; - // maxLineHeight = Math.Max(textHeight, maxLineHeight); - // fd.FontIdMap = fontIdMap; - // fd.UsedFonts = usedFonts; - // text.TextFormats[i] = fd; - //} - //if (text is PdfCellContentLayout ccl) - //{ - // ccl.TextLength = totalTextLength; - // ccl.TextHeight = maxLineHeight; - //} - } - - private static void AdjustText(double totalTextLength, double maxLineHeight, PdfCellContentLayout ccl) - { - ccl.CalculateTextSpill(ccl.Size.X, ccl.CellAlignmentData.TextRotation); - ccl.LocalPosition = ccl.CalculateAlignmentPositionAndTextOffsets(ccl.cell, ccl.LocalPosition.X, ccl.LocalPosition.Y, ccl.Size.X, ccl.Size.Y); - ccl.CheckClipping(ccl.cell, ccl.LocalPosition.X, ccl.LocalPosition.Y, ccl.Size.X, ccl.Size.Y); - } - - //Create a map for page - private void MoveCellToPageFromContent(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfPagesLayout pages) - { - List entriesToRemove = new(); - foreach (PdfPageLayout page in pages.ChildObjects) - { - if (page.isCommentsPage) continue; - page.CreateMap(); - var hw = PdfWorksheetLayout.RowHeadingWidth; - var x = pageSettings.Margins.LeftPu + hw; - if (pageSettings.CenterOnPageHorizontally) - { - var w = pageSettings.PageSize.WidthPu - pageSettings.Margins.LeftPu - pageSettings.Margins.RightPu; - x = pageSettings.Margins.LeftPu + (w - page.ChildObjects[0].Size.X + hw) / 2; - } - var hh = PdfWorksheetLayout.ColumnHeadingHeight; - var y = pageSettings.PageSize.HeightPu - pageSettings.Margins.TopPu - page.ChildObjects[0].Size.Y + hh; - if (pageSettings.CenterOnPageVertically) - { - var h = pageSettings.PageSize.HeightPu - pageSettings.Margins.TopPu - pageSettings.Margins.BottomPu; - y = pageSettings.Margins.BottomPu + (h - page.ChildObjects[0].Size.Y - hh) / 2; - } - page.ChildObjects[0].LocalPosition = new Vector2(x, y); - page.ContentTop = page.ChildObjects[0].LocalPosition.Y + hh + page.ChildObjects[0].Size.Y; - page.ContentBottom = page.ChildObjects[0].LocalPosition.Y; - page.ContentLeft = page.ChildObjects[0].LocalPosition.X - hw; - page.ContentRight = page.ChildObjects[0].LocalPosition.X + page.ChildObjects[0].Size.X; - page.ContentHeight = page.ChildObjects[0].Size.Y + hh; - - var contentObjects = page.ChildObjects[0].ChildObjects.ToList(); - for (int i = 0; i < contentObjects.Count; i++) - { - var child = contentObjects[i]; - page.AddChild(child); - if (child is IShadingLayout iSl) - iSl.UpdateShadingPositionMatrix(pageSettings); - if (child is PdfMergedCellLayout m) - { - var localFromRow = m.address._fromRow - page.FromRow; - var localFromCol = m.address._fromCol - page.FromCol; - var localToRow = m.address._toRow - page.FromRow; - var localToCol = m.address._toCol - page.FromCol; - int colCount = 0; - int rowCount = 0; - bool remove = true; - - for (int r = localFromRow; r <= localToRow; r++) - { - if (r >= (page.ToRow - page.FromRow) + 1) break; - if (r < 0) - { - rowCount++; - continue; - } - for (int c = localFromCol; c <= localToCol; c++) - { - if (c >= (page.ToCol - page.FromCol) + 1) break; - if (c < 0) - { - colCount++; - continue; - } - page.Map[r, c].Name = ExcelRange.GetColumnLetter(m.cell._fromCol + colCount) + (m.cell._fromRow + rowCount); - page.Map[r, c].cell = m; - page.Map[r, c].Type = PageMap.CellType.Merged; - page.Map[r, c].content = contentObjects.OfType().FirstOrDefault(t => t.Name == m.Name); - if (page.Map[r, c].content != null) page.Map[r, c].content.Clip = true; - page.Map[r, c].border = contentObjects.OfType().FirstOrDefault(t => t.Name == m.Name); - page.Map[r, c].row = m.cell._fromRow + rowCount; - page.Map[r, c].col = m.cell._fromCol + colCount; - colCount++; - remove = false; - } - rowCount++; - colCount = 0; - } - if (remove) - { - entriesToRemove.Add(child.Name); - } - } - else if (child is PdfCellLayout l) - { - if (l.IsHeading) continue; - var localFromRow = l.cell._fromRow - page.FromRow; - var localFromCol = l.cell._fromCol - page.FromCol; - page.Map[localFromRow, localFromCol].Name = l.Name; - page.Map[localFromRow, localFromCol].cell = l; - page.Map[localFromRow, localFromCol].Type = PageMap.CellType.Normal; - page.Map[localFromRow, localFromCol].content = contentObjects.OfType().FirstOrDefault(t => t.Name == l.Name); - page.Map[localFromRow, localFromCol].border = contentObjects.OfType().FirstOrDefault(t => t.Name == l.Name); - page.Map[localFromRow, localFromCol].row = l.cell._fromRow; - page.Map[localFromRow, localFromCol].col = l.cell._fromCol; - page.Map[localFromRow, localFromCol].RightTextBucketSpill = page.Map[localFromRow, localFromCol].content != null ? page.Map[localFromRow, localFromCol].content.RightTextSpillLength : 0d; - page.Map[localFromRow, localFromCol].LeftTextBucketSpill = page.Map[localFromRow, localFromCol].content != null ? page.Map[localFromRow, localFromCol].content.LeftTextSpillLength : 0d; - } - } - page.RemoveChild(page.ChildObjects[0]); - foreach (var entry in entriesToRemove) - { - var cell = contentObjects.OfType().FirstOrDefault(t => t.Name == entry); - var content = contentObjects.OfType().FirstOrDefault(t => t.Name == entry); - var border = contentObjects.OfType().FirstOrDefault(t => t.Name == entry); - page.RemoveChild(content); - page.RemoveChild(border); - page.RemoveChild(cell); - } - } - } - - private void ProocessPageAndCells(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfPagesLayout pages) - { - foreach (PdfPageLayout page in pages.ChildObjects) - { - if (page.isCommentsPage) continue; - var rowCount = page.ToRow - page.FromRow + 1; - var colCount = page.ToCol - page.FromCol + 1; - - // ── Interior vertical lines ────────────────────────────────────────── - var activeVertical = new Dictionary(); - Action flushVertical = delegate (int col) - { - VerticalLineRun run; - if (activeVertical.TryGetValue(col, out run)) - { - double x = page.Map[run.RowStart, col].cell.LocalPosition.X; - double y1 = page.Map[run.RowStart, col].cell.LocalPosition.Y; - double y2 = page.Map[run.RowEnd, col].cell.LocalPosition.Y - + page.Map[run.RowEnd, col].cell.Size.Y; - page.GridLines.Add(new GridLine(x, y1, x, y2)); - activeVertical.Remove(col); - } - }; - Action addVertical = delegate (int r, int c) - { - VerticalLineRun run; - if (activeVertical.TryGetValue(c, out run)) - { - if (run.RowEnd == r - 1) - run.RowEnd = r; - else - { - flushVertical(c); - activeVertical[c] = new VerticalLineRun { Col = c, RowStart = r, RowEnd = r }; - } - } - else - { - activeVertical[c] = new VerticalLineRun { Col = c, RowStart = r, RowEnd = r }; - } - }; - - // ── Interior horizontal lines ──────────────────────────────────────── - var activeHorizontal = new Dictionary(); - Action flushHorizontal = delegate (int row) - { - HorizontalLineRun run; - if (activeHorizontal.TryGetValue(row, out run)) - { - double y = page.Map[row, run.ColStart].cell.LocalPosition.Y; - double x1 = page.Map[row, run.ColStart].cell.LocalPosition.X; - double x2 = page.Map[row, run.ColEnd].cell.LocalPosition.X - + page.Map[row, run.ColEnd].cell.Size.X; - page.GridLines.Add(new GridLine(x1, y, x2, y)); - activeHorizontal.Remove(row); - } - }; - Action AddHorizontalSegment = delegate (int r, int c) - { - HorizontalLineRun run; - if (activeHorizontal.TryGetValue(r, out run)) - { - if (run.ColEnd == c - 1) - run.ColEnd = c; - else - { - flushHorizontal(r); - activeHorizontal[r] = new HorizontalLineRun { Row = r, ColStart = c, ColEnd = c }; - } - } - else - { - activeHorizontal[r] = new HorizontalLineRun { Row = r, ColStart = c, ColEnd = c }; - } - }; - - // ── Main loop ──────────────────────────────────────────────────────── - for (int row = 0; row < rowCount; row++) - { - // Flush horizontal runs not continued into this row - foreach (int key in new List(activeHorizontal.Keys)) - flushHorizontal(key); - - // Flush vertical runs that didn't continue - foreach (int key in new List(activeVertical.Keys)) - { - if (activeVertical[key].RowEnd < row) - flushVertical(key); - } - - for (int col = 0; col < colCount; col++) - { - var cell = page.Map[row, col]; - PageMap leftCell = new PageMap(); - PageMap rightCell = new PageMap(); - if (col != 0) leftCell = page.Map[row, col - 1]; - if (col != colCount - 1) rightCell = page.Map[row, col + 1]; - - // ── Text-spill propagation ─────────────────────────────────── - if (leftCell.cell != null) - { - if (leftCell.content != null) - { - if (leftCell.content.RightTextSpillLength > 0d) - { - if (cell.content != null) - { - // Spill collides with content — clip the source and force a gridline - leftCell.content.Clip = true; - addVertical(row, col); - } - else - { - cell.RightTextBucketSpill = leftCell.content.RightTextSpillLength - cell.cell.Size.X; - } - } - } - else if (leftCell.RightTextBucketSpill > 0d) - { - if (cell.content != null) - { - // Bucket spill collides with content — walk back to find origin and clip it, force a gridline - for (int i = col - 1; i >= 0; i--) - { - var originCell = page.Map[row, i]; - if (originCell.content != null) - { - originCell.content.Clip = true; - break; - } - } - addVertical(row, col); - } - else - { - cell.RightTextBucketSpill = leftCell.RightTextBucketSpill - cell.cell.Size.X; - } - } - } - - if (cell.content != null) - { - if (cell.content.LeftTextSpillLength > 0d) - { - double spill = cell.content.LeftTextSpillLength; - for (int i = col; i > 0; i--) - { - var prevCell = page.Map[row, i - 1]; - if (prevCell.content != null) - { - // Spill collides with content — clip and force a gridline at the boundary - cell.content.Clip = true; - cell.content.CreateClippingRect(cell.cell, prevCell.cell.LocalPosition.X + prevCell.cell.Size.X); - addVertical(row, i); - break; - } - prevCell.LeftTextBucketSpill = spill - prevCell.cell.Size.X; - spill -= prevCell.cell.Size.X; - page.Map[row, i - 1] = prevCell; - if (spill <= 0d) break; - } - } - - cell.content.GidsAndCharMap(dictionaries); - } - - // ── Gridline collection ────────────────────────────────────── - - // Interior horizontal — only between two rows (skip top page edge) - if (row > 0) - { - var t = page.Map[row - 1, col]; - bool differentTop = t.cell != cell.cell; - - // Border data is only stored at the merged cell's origin row — walk up to find it - int topOriginRow = row - 1; - while (topOriginRow > 0 && page.Map[topOriginRow - 1, col].cell == t.cell) - topOriginRow--; - var topBorder = page.Map[topOriginRow, col].border; - - int cellOriginRow = row; - while (cellOriginRow > 0 && page.Map[cellOriginRow - 1, col].cell == cell.cell) - cellOriginRow--; - var cellBorder = page.Map[cellOriginRow, col].border; - - bool borderTop = (topBorder != null && topBorder.BorderData.Bottom.BorderStyle != ExcelBorderStyle.None) || - (cellBorder != null && cellBorder.BorderData.Top.BorderStyle != ExcelBorderStyle.None); - - if (differentTop && !borderTop) - AddHorizontalSegment(row - 1, col); - } - - // Interior vertical — only between two columns (skip left page edge) - if (col > 0) - { - var l = page.Map[row, col - 1]; - bool differentLeft = l.cell != cell.cell; - bool spillLeft = l.RightTextBucketSpill > 0 || cell.LeftTextBucketSpill > 0; - - // Border data is only stored at the merged cell's origin column — walk left to find it - int leftOriginCol = col - 1; - while (leftOriginCol > 0 && page.Map[row, leftOriginCol - 1].cell == l.cell) - leftOriginCol--; - var leftBorder = page.Map[row, leftOriginCol].border; - - int cellOriginCol = col; - while (cellOriginCol > 0 && page.Map[row, cellOriginCol - 1].cell == cell.cell) - cellOriginCol--; - var cellBorderV = page.Map[row, cellOriginCol].border; - - bool borderLeft = (leftBorder != null && leftBorder.BorderData.Right.BorderStyle != ExcelBorderStyle.None) || - (cellBorderV != null && cellBorderV.BorderData.Left.BorderStyle != ExcelBorderStyle.None); - - if (differentLeft && !spillLeft && !borderLeft) - addVertical(row, col); - } - - page.Map[row, col] = cell; - } - } - - // ── Final flush ────────────────────────────────────────────────────── - foreach (int key in new List(activeVertical.Keys)) - flushVertical(key); - foreach (int key in new List(activeHorizontal.Keys)) - flushHorizontal(key); - - // ── Border lines (4 outer edges, derived from the corner cells) ────── - var tl = page.Map[0, 0].cell; // bottom-left - var br = page.Map[0, colCount - 1].cell; // bottom-right - var bl = page.Map[rowCount - 1, 0].cell; // top-left - var tr = page.Map[rowCount - 1, colCount - 1].cell; // top-right - - double left = bl.LocalPosition.X; - double right = br.LocalPosition.X + br.Size.X; - double bottom = bl.LocalPosition.Y; - double top = tl.LocalPosition.Y + tl.Size.Y; - - page.BorderLines.Add(new GridLine(left, bottom, right, bottom)); // bottom - page.BorderLines.Add(new GridLine(left, top, right, top)); // top - page.BorderLines.Add(new GridLine(left, bottom, left, top)); // left - page.BorderLines.Add(new GridLine(right, bottom, right, top)); // right - page.ChildObjects.RemoveAll(x => x is PdfCellLayout && ((PdfCellLayout)x).delete == true); - } - } - - //Make final adjustments and sort children for drawing order. - private void AdjustAndSort(PdfPagesLayout pages, PdfDictionaries dictionaries) - { - foreach (PdfPageLayout page in pages.ChildObjects) - { - if (page.isCommentsPage) - { - foreach (var child in page.ChildObjects) - { - if (child is PdfCellContentLayout content) - { - content.GidsAndCharMap(dictionaries); - } - else if (child is PdfHeaderFooterLayout headerFooterLayout) - { - //headerFooterLayout.GidsAndCharMap(dictionaries); - } - } - continue; - } - //Make adjustments - foreach (var child in page.ChildObjects) - { - if (child is PdfCellLayout cellLayout) - { - cellLayout.AdjustForGridLines(); - } - else if (child is PdfCellContentLayout content) - { - content.CreateClippingRect(page.ChildObjects); - } - else if (child is PdfMergedCellLayout mergedLayout) - { - mergedLayout.AdjustForGridLines(); - } - else if (child is PdfHeaderFooterLayout headerFooterLayout) - { - //headerFooterLayout.GidsAndCharMap(dictionaries); - } - } - //Sort by Z ascending and the by Name descending - page.ChildObjects.Sort((a, b) => - { - int cmp = a.Z.CompareTo(b.Z); - if (cmp == 0) - return string.Compare(b.Name, a.Name, StringComparison.OrdinalIgnoreCase); - return cmp; - }); - } - } - - private void AddHeaderFooter(ExcelWorksheet ws, PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfPagesLayout pages) - { - int pageNumber = 1; - //loop pages and check which header it should use - for (int i = 0; i < pages.ChildObjects.Count; i++) - { - ExcelHeaderFooterTextCollection leftH = null; - ExcelHeaderFooterTextCollection centerH = null; - ExcelHeaderFooterTextCollection rightH = null; - ExcelHeaderFooterTextCollection leftF = null; - ExcelHeaderFooterTextCollection centerF = null; - ExcelHeaderFooterTextCollection rightF = null; - - if (ws.HeaderFooter.differentFirst && pageNumber == 1) - { - leftH = ws.HeaderFooter.FirstHeader.LeftAligned; - centerH = ws.HeaderFooter.FirstHeader.Centered; - rightH = ws.HeaderFooter.FirstHeader.RightAligned; - leftF = ws.HeaderFooter.FirstFooter.LeftAligned; - centerF = ws.HeaderFooter.FirstFooter.Centered; - rightF = ws.HeaderFooter.FirstFooter.RightAligned; - if (leftH.Text == "&L" && centerH.Text == "&C" && rightH.Text == "&R" && leftF.Text == "&L" && centerF.Text == "&C" && rightF.Text == "&R") - { - leftH = ws.HeaderFooter.OddHeader.LeftAligned; - centerH = ws.HeaderFooter.OddHeader.Centered; - rightH = ws.HeaderFooter.OddHeader.RightAligned; - leftF = ws.HeaderFooter.OddFooter.LeftAligned; - centerF = ws.HeaderFooter.OddFooter.Centered; - rightF = ws.HeaderFooter.OddFooter.RightAligned; - } - } - else if (ws.HeaderFooter.differentOddEven && pageNumber % 2 == 0) - { - leftH = ws.HeaderFooter.EvenHeader.LeftAligned; - centerH = ws.HeaderFooter.EvenHeader.Centered; - rightH = ws.HeaderFooter.EvenHeader.RightAligned; - leftF = ws.HeaderFooter.EvenFooter.LeftAligned; - centerF = ws.HeaderFooter.EvenFooter.Centered; - rightF = ws.HeaderFooter.EvenFooter.RightAligned; - if (leftH.Text == "&L" && centerH.Text == "&C" && rightH.Text == "&R" && leftF.Text == "&L" && centerF.Text == "&C" && rightF.Text == "&R") - { - leftH = ws.HeaderFooter.OddHeader.LeftAligned; - centerH = ws.HeaderFooter.OddHeader.Centered; - rightH = ws.HeaderFooter.OddHeader.RightAligned; - leftF = ws.HeaderFooter.OddFooter.LeftAligned; - centerF = ws.HeaderFooter.OddFooter.Centered; - rightF = ws.HeaderFooter.OddFooter.RightAligned; - } - } - else - { - leftH = ws.HeaderFooter.OddHeader.LeftAligned; - centerH = ws.HeaderFooter.OddHeader.Centered; - rightH = ws.HeaderFooter.OddHeader.RightAligned; - leftF = ws.HeaderFooter.OddFooter.LeftAligned; - centerF = ws.HeaderFooter.OddFooter.Centered; - rightF = ws.HeaderFooter.OddFooter.RightAligned; - } - var lh = (PdfHeaderFooterLayout)pages.ChildObjects[i].AddChild(new PdfHeaderFooterLayout(leftH, ws, pageSettings, dictionaries, pageNumber, pages.ChildObjects.Count)); - lh.Name = "LeftHeader"; - lh.LocalPosition = new Vector2(pageSettings.Margins.LeftPu, pageSettings.PageSize.HeightPu - pageSettings.Margins.HeaderPu); - lh.AdjustPositionByTextLength('l', 'h'); - var ch = (PdfHeaderFooterLayout)pages.ChildObjects[i].AddChild(new PdfHeaderFooterLayout(centerH, ws, pageSettings, dictionaries, pageNumber, pages.ChildObjects.Count)); - ch.Name = "CenterHeader"; - ch.LocalPosition = new Vector2(pageSettings.PageSize.WidthPu / 2d, pageSettings.PageSize.HeightPu - pageSettings.Margins.HeaderPu); - ch.AdjustPositionByTextLength('c', 'h'); - var rh = (PdfHeaderFooterLayout)pages.ChildObjects[i].AddChild(new PdfHeaderFooterLayout(rightH, ws, pageSettings, dictionaries, pageNumber, pages.ChildObjects.Count)); - rh.Name = "RightHeader"; - rh.LocalPosition = new Vector2(pageSettings.PageSize.WidthPu - pageSettings.Margins.RightPu, pageSettings.PageSize.HeightPu - pageSettings.Margins.HeaderPu); - rh.AdjustPositionByTextLength('r', 'h'); - var lf = (PdfHeaderFooterLayout)pages.ChildObjects[i].AddChild(new PdfHeaderFooterLayout(leftF, ws, pageSettings, dictionaries, pageNumber, pages.ChildObjects.Count)); - lf.Name = "LeftFooter"; - lf.LocalPosition = new Vector2(pageSettings.Margins.LeftPu, pageSettings.Margins.FooterPu); - var cf = (PdfHeaderFooterLayout)pages.ChildObjects[i].AddChild(new PdfHeaderFooterLayout(centerF, ws, pageSettings, dictionaries, pageNumber, pages.ChildObjects.Count)); - cf.Name = "CenterFooter"; - cf.LocalPosition = new Vector2(pageSettings.PageSize.WidthPu / 2d, pageSettings.Margins.FooterPu); - cf.AdjustPositionByTextLength('c', 'f'); - var rf = (PdfHeaderFooterLayout)pages.ChildObjects[i].AddChild(new PdfHeaderFooterLayout(rightF, ws, pageSettings, dictionaries, pageNumber, pages.ChildObjects.Count)); - rf.Name = "RightFooter"; - rf.LocalPosition = new Vector2(pageSettings.PageSize.WidthPu - pageSettings.Margins.RightPu, pageSettings.Margins.FooterPu); - rf.AdjustPositionByTextLength('r', 'f'); - pageNumber++; - } - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfLayout/PdfCellBorderLayout.cs b/src/EPPlus.Export.Pdf/PdfLayout/PdfCellBorderLayout.cs deleted file mode 100644 index 279fefd860..0000000000 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfCellBorderLayout.cs +++ /dev/null @@ -1,593 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.PdfCatalog; -using EPPlus.Export.Pdf.Pdfhelpers; -using EPPlus.Graphics; -using OfficeOpenXml; -using OfficeOpenXml.Style; -using OfficeOpenXml.Style.Dxf; -using OfficeOpenXml.Style.Table; -using OfficeOpenXml.Table; -using System.Diagnostics; -using System.Drawing; - -namespace EPPlus.Export.Pdf.PdfLayout -{ - [DebuggerDisplay("Border: {Name}")] - internal class PdfCellBorderLayout : Transform , IBorderLayout - { - public PdfCellBordersData BorderData; - public bool IsMerged = false; - public MergedCellDrawInfo MergedCellInfo; - public MergedCellCorners Corners; - public ExcelRangeBase cell; - public bool IsPrintTitle; - - public string range; - - internal PdfCellStyle TableStyle; - - public PdfCellBorderLayout() { } - - public PdfCellBorderLayout(PdfCellStyle style, bool isMerged, MergedCellCorners corners, MergedCellDrawInfo info, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) - : base(x, y - height, width, height, scaleX, scaleY, rotation, parent) - { - Z = 3; - IsMerged = isMerged; - MergedCellInfo = info; - Corners = corners; - - BorderData = new PdfCellBordersData(); - - BorderData.Top.BorderStyle = style.xfTop.Style == ExcelBorderStyle.None ? ((style.dxfTop != null && style.dxfTop.HasValue) ? (ExcelBorderStyle)style.dxfTop.Style : ExcelBorderStyle.None) : style.xfTop.Style; - BorderData.Top.BorderColor = style.dxfTop != null ? PdfColor.SetColorFromHex(style.dxfTop.Color.LookupColor(style.dxfTop)) : PdfColor.SetColorFromHex(style.xfTop.Color.LookupColor(style.xfTop)); - BorderData.Top.X = LocalPosition.X; - BorderData.Top.Y = LocalPosition.Y; - BorderData.Top.Width = Size.X; - BorderData.Top.Height = Size.Y; - - BorderData.Bottom.BorderStyle = style.xfBottom.Style == ExcelBorderStyle.None ? ((style.dxfBottom != null && style.dxfBottom.HasValue) ? (ExcelBorderStyle)style.dxfBottom.Style : ExcelBorderStyle.None) : style.xfBottom.Style; - BorderData.Bottom.BorderColor = style.dxfBottom != null ? PdfColor.SetColorFromHex(style.dxfBottom.Color.LookupColor(style.dxfBottom)) : PdfColor.SetColorFromHex(style.xfBottom.Color.LookupColor(style.xfBottom)); - BorderData.Bottom.X = LocalPosition.X; - BorderData.Bottom.Y = LocalPosition.Y; - BorderData.Bottom.Width = Size.X; - BorderData.Bottom.Height = Size.Y; - - BorderData.Left.BorderStyle = style.xfLeft.Style == ExcelBorderStyle.None ? ((style.dxfLeft != null && style.dxfLeft.HasValue) ? (ExcelBorderStyle)style.dxfLeft.Style : ExcelBorderStyle.None) : style.xfLeft.Style; - BorderData.Left.BorderColor = style.dxfLeft != null ? PdfColor.SetColorFromHex(style.dxfLeft.Color.LookupColor(style.dxfLeft)) : PdfColor.SetColorFromHex(style.xfLeft.Color.LookupColor(style.xfLeft)); - BorderData.Left.X = LocalPosition.X; - BorderData.Left.Y = LocalPosition.Y; - BorderData.Left.Width = Size.X; - BorderData.Left.Height = Size.Y; - - BorderData.Right.BorderStyle = style.xfRight.Style == ExcelBorderStyle.None ? ((style.dxfRight != null && style.dxfRight.HasValue) ? (ExcelBorderStyle)style.dxfRight.Style : ExcelBorderStyle.None) : style.xfRight.Style; - BorderData.Right.BorderColor = style.dxfRight != null ? PdfColor.SetColorFromHex(style.dxfRight.Color.LookupColor(style.dxfRight)) : PdfColor.SetColorFromHex(style.xfRight.Color.LookupColor(style.xfRight)); - BorderData.Right.X = LocalPosition.X; - BorderData.Right.Y = LocalPosition.Y; - BorderData.Right.Width = Size.X; - BorderData.Right.Height = Size.Y; - - BorderData.DiagonalUp.BorderStyle = style.DiagonalUp ? style.Diagonal.Style : ExcelBorderStyle.None; - BorderData.DiagonalUp.BorderColor = style.DiagonalUp ? PdfColor.SetColorFromHex(style.Diagonal.Color.LookupColor(style.Diagonal)) : Color.Transparent; - BorderData.DiagonalUp.MergedDiagonalWidth = width; - BorderData.DiagonalUp.MergedDiagonalHeight = height; - - BorderData.DiagonalDown.BorderStyle = style.DiagonalDown ? style.Diagonal.Style : ExcelBorderStyle.None; - BorderData.DiagonalDown.BorderColor = style.DiagonalDown ? PdfColor.SetColorFromHex(style.Diagonal.Color.LookupColor(style.Diagonal)) : Color.Transparent; - BorderData.DiagonalDown.MergedDiagonalWidth = width; - BorderData.DiagonalDown.MergedDiagonalHeight = height; - } - - - - - - - public PdfCellBorderLayout(ExcelRangeBase cell, PdfCellStyle tableStyle, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) - : base(x, y-height, width, height, scaleX, scaleY, rotation, parent) - { - this.cell = cell; - this.TableStyle = tableStyle; - if (cell != null) - { - IsMerged = cell.Merge; - BorderData = new PdfCellBordersData(); - } - } - - public void InitEdgeBorders(ExcelRangeBase cell) - { - if (cell != null) - { - //BorderData.Top = new PdfCellBorderData(LineType.Top); - BorderData.Top.BorderStyle = TableStyle.xfTop.Style == ExcelBorderStyle.None ? ((TableStyle.dxfTop != null && TableStyle.dxfTop.HasValue) ? (ExcelBorderStyle)TableStyle.dxfTop.Style : ExcelBorderStyle.None ) : TableStyle.xfTop.Style; - BorderData.Top.BorderColor = TableStyle.dxfTop != null ? PdfColor.SetColorFromHex(TableStyle.dxfTop.Color.LookupColor(TableStyle.dxfTop)) : PdfColor.SetColorFromHex(cell.Style.Border.Top.Color.LookupColor(cell.Style.Border)); - BorderData.Top.X = LocalPosition.X; - BorderData.Top.Y = LocalPosition.Y; - BorderData.Top.Width = Size.X; - BorderData.Top.Height = Size.Y; - //BorderData.Bottom = new PdfCellBorderData(LineType.Bottom); - BorderData.Bottom.BorderStyle = TableStyle.xfBottom.Style == ExcelBorderStyle.None ? ((TableStyle.dxfBottom != null && TableStyle.dxfBottom.HasValue) ? (ExcelBorderStyle)TableStyle.dxfBottom.Style : ExcelBorderStyle.None) : TableStyle.xfBottom.Style; - BorderData.Bottom.BorderColor = TableStyle.dxfBottom != null ? PdfColor.SetColorFromHex(TableStyle.dxfBottom.Color.LookupColor(TableStyle.dxfBottom)) : PdfColor.SetColorFromHex(cell.Style.Border.Bottom.Color.LookupColor(cell.Style.Border)); - BorderData.Bottom.X = LocalPosition.X; - BorderData.Bottom.Y = LocalPosition.Y; - BorderData.Bottom.Width = Size.X; - BorderData.Bottom.Height = Size.Y; - //BorderData.Left = new PdfCellBorderData(LineType.Left); - BorderData.Left.BorderStyle = TableStyle.xfLeft.Style == ExcelBorderStyle.None ? ((TableStyle.dxfLeft != null && TableStyle.dxfLeft.HasValue) ? (ExcelBorderStyle)TableStyle.dxfLeft.Style : ExcelBorderStyle.None) : TableStyle.xfLeft.Style; - BorderData.Left.BorderColor = TableStyle.dxfLeft != null ? PdfColor.SetColorFromHex(TableStyle.dxfLeft.Color.LookupColor(TableStyle.dxfLeft)) : PdfColor.SetColorFromHex(cell.Style.Border.Left.Color.LookupColor(cell.Style.Border)); - BorderData.Left.X = LocalPosition.X; - BorderData.Left.Y = LocalPosition.Y; - BorderData.Left.Width = Size.X; - BorderData.Left.Height = Size.Y; - //BorderData.Right = new PdfCellBorderData(LineType.Right); - BorderData.Right.BorderStyle = TableStyle.xfRight.Style == ExcelBorderStyle.None ? ((TableStyle.dxfRight != null && TableStyle.dxfRight.HasValue) ? (ExcelBorderStyle)TableStyle.dxfRight.Style : ExcelBorderStyle.None) : TableStyle.xfRight.Style; - BorderData.Right.BorderColor = TableStyle.dxfRight != null ? PdfColor.SetColorFromHex(TableStyle.dxfRight.Color.LookupColor(TableStyle.dxfRight)) : PdfColor.SetColorFromHex(cell.Style.Border.Right.Color.LookupColor(cell.Style.Border)); - BorderData.Right.X = LocalPosition.X; - BorderData.Right.Y = LocalPosition.Y; - BorderData.Right.Width = Size.X; - BorderData.Right.Height = Size.Y; - - - ////BorderData.Top = new PdfCellBorderData(LineType.Top); - //BorderData.Top.BorderStyle = cell.Style.Border.Top.Style; - //BorderData.Top.BorderColor = PdfColor.SetColorFromHex(cell.Style.Border.Top.Color.LookupColor(cell.Style.Border)); - //BorderData.Top.X = LocalPosition.X; - //BorderData.Top.Y = LocalPosition.Y; - //BorderData.Top.Width = Size.X; - //BorderData.Top.Height = Size.Y; - ////BorderData.Bottom = new PdfCellBorderData(LineType.Bottom); - //BorderData.Bottom.BorderStyle = cell.Style.Border.Bottom.Style; - //BorderData.Bottom.BorderColor = PdfColor.SetColorFromHex(cell.Style.Border.Bottom.Color.LookupColor(cell.Style.Border)); - //BorderData.Bottom.X = LocalPosition.X; - //BorderData.Bottom.Y = LocalPosition.Y; - //BorderData.Bottom.Width = Size.X; - //BorderData.Bottom.Height = Size.Y; - ////BorderData.Left = new PdfCellBorderData(LineType.Left); - //BorderData.Left.BorderStyle = cell.Style.Border.Left.Style; - //BorderData.Left.BorderColor = PdfColor.SetColorFromHex(cell.Style.Border.Left.Color.LookupColor(cell.Style.Border)); - //BorderData.Left.X = LocalPosition.X; - //BorderData.Left.Y = LocalPosition.Y; - //BorderData.Left.Width = Size.X; - //BorderData.Left.Height = Size.Y; - ////BorderData.Right = new PdfCellBorderData(LineType.Right); - //BorderData.Right.BorderStyle = cell.Style.Border.Right.Style; - //BorderData.Right.BorderColor = PdfColor.SetColorFromHex(cell.Style.Border.Right.Color.LookupColor(cell.Style.Border)); - //BorderData.Right.X = LocalPosition.X; - //BorderData.Right.Y = LocalPosition.Y; - //BorderData.Right.Width = Size.X; - //BorderData.Right.Height = Size.Y; - } - } - - public void InitDiagonalBorders(ExcelRangeBase cell, double width, double height) - { - if (cell != null) - { - //BorderData.DiagonalUp = new PdfCellBorderData(LineType.DiagonalUp); - BorderData.DiagonalUp.BorderStyle = cell.Style.Border.DiagonalUp ? cell.Style.Border.Diagonal.Style : ExcelBorderStyle.None; - BorderData.DiagonalUp.BorderColor = PdfColor.SetColorFromHex(cell.Style.Border.Diagonal.Color.LookupColor(cell.Style.Border)); - BorderData.DiagonalUp.MergedDiagonalWidth = width; - BorderData.DiagonalUp.MergedDiagonalHeight = height; - //BorderData.DiagonalDown = new PdfCellBorderData(LineType.DiagonalDown); - BorderData.DiagonalDown.BorderStyle = cell.Style.Border.DiagonalDown ? cell.Style.Border.Diagonal.Style : ExcelBorderStyle.None; - BorderData.DiagonalDown.BorderColor = PdfColor.SetColorFromHex(cell.Style.Border.Diagonal.Color.LookupColor(cell.Style.Border)); - BorderData.DiagonalDown.MergedDiagonalWidth = width; - BorderData.DiagonalDown.MergedDiagonalHeight = height; - } - } - - public void UpdateLocalBorderPosition() - { - //if (cell.Address == FirstCellInMerge) - //{ - // BorderData.Top.X = LocalPosition.X; - // BorderData.Top.Y = LocalPosition.Y + Size.Y - BorderData.Top.Height; - //} - //else - //{ - // BorderData.Top.X = LocalPosition.X; - // BorderData.Top.Y = LocalPosition.Y; - //} - //BorderData.Bottom.X = LocalPosition.X; - //BorderData.Bottom.Y = LocalPosition.Y; - //if (cell.Address == FirstCellInMerge) - //{ - // BorderData.Left.X = LocalPosition.X; - // BorderData.Left.Y = LocalPosition.Y + Size.Y - BorderData.Left.Height; - //} - //else - //{ - // BorderData.Left.X = LocalPosition.X; - // BorderData.Left.Y = LocalPosition.Y; - //} - //BorderData.Right.X = LocalPosition.X; - //BorderData.Right.Y = LocalPosition.Y; - } - - - //Get Methods fort border styles - /* -  Whole Table -  First Column Stripe -  Second Column Stripe -  First Row Stripe -  Second Row Stripe -  Last Column -  First Column -  Header Row -  Total Row -  First Header Cell -  Last Header Cell -  First Total Cell -  Last Total Cell - */ - - - - /* - * Following methods needs to be refactored into 1 method. - * Less code is more good. - */ - public static ExcelDxfBorderItem GetTopBorderItem(ExcelRangeBase cell, ExcelBorderItem xfBorder, ExcelTable table, ExcelTableNamedStyle tableStyle) - { - var range = table.Range; - int tableRow = cell._fromRow - range._fromRow; - int tableCol = cell._fromCol - range._fromCol; - int ts = table.ShowHeader ? 1 : 0; - var top = tableRow == 0 ? tableStyle.WholeTable.Style.Border.Top : tableStyle.WholeTable.Style.Border.Horizontal; - if (table.ShowHeader && tableRow == 0) - { - if (tableStyle.HeaderRow.Style.Border.Top.HasValue) - { - top = tableStyle.HeaderRow.Style.Border.Top; - } - if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstHeaderCell.Style.Border.Top.HasValue) - { - top = tableStyle.FirstHeaderCell.Style.Border.Top; - } - if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastHeaderCell.Style.Border.Top.HasValue) - { - top = tableStyle.LastHeaderCell.Style.Border.Top; - } - } - else if (table.ShowTotal && cell._fromRow == range._toRow) - { - if (tableStyle.TotalRow.Style.Border.Top.HasValue) - { - top = tableStyle.TotalRow.Style.Border.Top; - } - if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstTotalCell.Style.Border.Top.HasValue) - { - top = tableStyle.FirstTotalCell.Style.Border.Top; - } - if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastTotalCell.Style.Border.Top.HasValue) - { - top = tableStyle.LastTotalCell.Style.Border.Top; - } - } - else - { - if (table.ShowColumnStripes &&/* tableStyle.FirstColumnStripe.Style.Border.Top.HasValue &&*/ (tableCol & 1) == 0) - { - if (cell._fromRow - ts > range._fromRow && cell._fromRow < range._toRow) - { - top = tableStyle.FirstColumnStripe.Style.Border.Horizontal; - } - else if (cell._fromRow <= range._toRow) - { - top = null; - } - else - { - top = tableStyle.FirstColumnStripe.Style.Border.Top; - } - } - if (table.ShowColumnStripes && /*tableStyle.SecondColumnStripe.Style.Border.Top.HasValue &&*/ (tableCol & 1) != 0) - { - if (cell._fromRow + ts > range._fromRow && cell._fromRow < range._toRow) - { - top = tableStyle.SecondColumnStripe.Style.Border.Horizontal; - } - else if (cell._fromRow <= range._toRow) - { - top = null; - } - else - { - top = tableStyle.SecondColumnStripe.Style.Border.Top; - } - } - if (table.ShowRowStripes && tableStyle.FirstRowStripe.Style.Border.Top.HasValue && (tableRow & 1) != 0) - { - top = tableStyle.FirstRowStripe.Style.Border.Top; - } - if (table.ShowRowStripes && tableStyle.SecondRowStripe.Style.Border.Top.HasValue && (tableRow & 1) == 0) - { - top = tableStyle.SecondRowStripe.Style.Border.Top; - } - if (table.ShowLastColumn && tableStyle.LastColumn.Style.Border.Top.HasValue && cell._fromCol == range._toCol) - { - top = tableStyle.LastColumn.Style.Border.Top; - } - if (table.ShowFirstColumn && tableStyle.FirstColumn.Style.Border.Top.HasValue && tableCol == range._toCol) - { - top = tableStyle.FirstColumn.Style.Border.Top; - } - } - return top; - } - public static ExcelDxfBorderItem GetBottomBorderItem(ExcelRangeBase cell, ExcelBorderItem xfBorder, ExcelTable table, ExcelTableNamedStyle tableStyle) - { - var range = table.Range; - int tableRow = cell._fromRow - range._fromRow; - int tableCol = cell._fromCol - range._fromCol; - var bottom = range._toRow == cell._fromRow ? tableStyle.WholeTable.Style.Border.Bottom : null; - if (table.ShowHeader && tableRow == 0) - { - if (tableStyle.HeaderRow.Style.Border.Bottom.HasValue) - { - bottom = tableStyle.HeaderRow.Style.Border.Bottom; - } - if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstHeaderCell.Style.Border.Bottom.HasValue) - { - bottom = tableStyle.FirstHeaderCell.Style.Border.Bottom; - } - if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastHeaderCell.Style.Border.Bottom.HasValue) - { - bottom = tableStyle.LastHeaderCell.Style.Border.Bottom; - } - } - else if (table.ShowTotal && cell._fromRow == range._toRow) - { - if (tableStyle.TotalRow.Style.Border.Bottom.HasValue) - { - bottom = tableStyle.TotalRow.Style.Border.Bottom; - } - if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstTotalCell.Style.Border.Bottom.HasValue) - { - bottom = tableStyle.FirstTotalCell.Style.Border.Bottom; - } - if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastTotalCell.Style.Border.Bottom.HasValue) - { - bottom = tableStyle.LastTotalCell.Style.Border.Bottom; - } - } - else - { - if (table.ShowColumnStripes && tableStyle.FirstColumnStripe.Style.Border.Bottom.HasValue && (tableCol & 1) != 0) - { - if (cell._fromRow > range._fromRow && cell._fromRow < range._toRow) - { - bottom = tableStyle.FirstColumnStripe.Style.Border.Horizontal; - } - else if (cell._fromRow < range._toRow) - { - bottom = null; - } - else - { - bottom = tableStyle.FirstColumnStripe.Style.Border.Bottom; - } - } - if (table.ShowColumnStripes && tableStyle.SecondColumnStripe.Style.Border.Bottom.HasValue && (tableCol & 1) == 0) - { - if (cell._fromRow > range._fromRow && cell._fromRow < range._toRow) - { - bottom = tableStyle.SecondColumnStripe.Style.Border.Horizontal; - } - else if (cell._fromRow < range._toRow) - { - bottom = null; - } - else - { - bottom = tableStyle.SecondColumnStripe.Style.Border.Bottom; - } - } - if (table.ShowRowStripes && tableStyle.FirstRowStripe.Style.Border.Bottom.HasValue && (tableRow & 1) != 0) - { - bottom = tableStyle.FirstRowStripe.Style.Border.Bottom; - } - if (table.ShowRowStripes && tableStyle.SecondRowStripe.Style.Border.Bottom.HasValue && (tableRow & 1) == 0) - { - bottom = tableStyle.SecondRowStripe.Style.Border.Bottom; - } - if (table.ShowLastColumn && tableStyle.LastColumn.Style.Border.Bottom.HasValue && cell._fromCol == range._toCol) - { - bottom = tableStyle.LastColumn.Style.Border.Bottom; - } - if (table.ShowFirstColumn && tableStyle.FirstColumn.Style.Border.Bottom.HasValue && tableCol == 0) - { - bottom = tableStyle.FirstColumn.Style.Border.Bottom; - } - } - return bottom; - } - public static ExcelDxfBorderItem GetLeftBorderItem(ExcelRangeBase cell, ExcelBorderItem xfBorder, ExcelTable table, ExcelTableNamedStyle tableStyle) - { - var range = table.Range; - int tableRow = cell._fromRow - range._fromRow; - int tableCol = cell._fromCol - range._fromCol; - var left = tableCol == 0 ? tableStyle.WholeTable.Style.Border.Left : tableStyle.WholeTable.Style.Border.Vertical; - if (table.ShowHeader && tableRow == 0) - { - if (tableStyle.HeaderRow.Style.Border.Left.HasValue) - { - left = tableStyle.HeaderRow.Style.Border.Left; - } - if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstHeaderCell.Style.Border.Left.HasValue) - { - left = tableStyle.FirstHeaderCell.Style.Border.Left; - } - if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastHeaderCell.Style.Border.Left.HasValue) - { - left = tableStyle.LastHeaderCell.Style.Border.Left; - } - } - else if (table.ShowTotal && cell._fromRow == range._toRow) - { - if (tableStyle.TotalRow.Style.Border.Left.HasValue) - { - left = tableStyle.TotalRow.Style.Border.Left; - } - if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstTotalCell.Style.Border.Left.HasValue) - { - left = tableStyle.FirstTotalCell.Style.Border.Left; - } - if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastTotalCell.Style.Border.Left.HasValue) - { - left = tableStyle.LastTotalCell.Style.Border.Left; - } - } - else - { - if (table.ShowColumnStripes && tableStyle.FirstColumnStripe.Style.Border.Left.HasValue && (tableCol & 1) != 0) - { - left = tableStyle.FirstColumnStripe.Style.Border.Left; - } - if (table.ShowColumnStripes && tableStyle.SecondColumnStripe.Style.Border.Left.HasValue && (tableCol & 1) == 0) - { - left = tableStyle.SecondColumnStripe.Style.Border.Left; - } - if (table.ShowRowStripes && tableStyle.FirstRowStripe.Style.Border.Left.HasValue && (tableRow & 1) != 0) - { - if (cell._fromCol > range._fromCol && cell._fromCol < range._toCol) - { - left = tableStyle.FirstRowStripe.Style.Border.Vertical; - } - else if (cell._fromCol >= range._toCol) - { - left = null; - } - else - { - left = tableStyle.FirstRowStripe.Style.Border.Left; - } - } - if (table.ShowRowStripes && tableStyle.SecondRowStripe.Style.Border.Left.HasValue && (tableRow & 1) == 0) - { - if (cell._fromCol > range._fromCol && cell._fromCol < range._toCol) - { - left = tableStyle.SecondRowStripe.Style.Border.Vertical; - } - else if (cell._fromCol >= range._toCol) - { - left = null; - } - else - { - left = tableStyle.SecondRowStripe.Style.Border.Left; - } - } - if (table.ShowLastColumn && tableStyle.LastColumn.Style.Border.Left.HasValue && cell._fromCol == range._toCol) - { - left = tableStyle.LastColumn.Style.Border.Left; - } - if (table.ShowFirstColumn && tableStyle.FirstColumn.Style.Border.Left.HasValue && tableCol == range._toCol) - { - left = tableStyle.FirstColumn.Style.Border.Left; - } - } - return left; - } - public static ExcelDxfBorderItem GetRightBorderItem(ExcelRangeBase cell, ExcelBorderItem xfBorder, ExcelTable table, ExcelTableNamedStyle tableStyle) - { - var range = table.Range; - int tableRow = cell._fromRow - range._fromRow; - int tableCol = cell._fromCol - range._fromCol; - var right = cell._fromCol == range._toCol ? tableStyle.WholeTable.Style.Border.Right : tableStyle.WholeTable.Style.Border.Vertical; - if (table.ShowHeader && tableRow == 0) - { - if (tableStyle.HeaderRow.Style.Border.Right.HasValue) - { - right = tableStyle.HeaderRow.Style.Border.Right; - } - if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstHeaderCell.Style.Border.Right.HasValue) - { - right = tableStyle.FirstHeaderCell.Style.Border.Right; - } - if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastHeaderCell.Style.Border.Right.HasValue) - { - right = tableStyle.LastHeaderCell.Style.Border.Right; - } - } - else if (table.ShowTotal && cell._fromRow == range._toRow) - { - if (tableStyle.TotalRow.Style.Border.Right.HasValue) - { - right = tableStyle.TotalRow.Style.Border.Right; - } - if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstTotalCell.Style.Border.Right.HasValue) - { - right = tableStyle.FirstTotalCell.Style.Border.Right; - } - if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastTotalCell.Style.Border.Right.HasValue) - { - right = tableStyle.LastTotalCell.Style.Border.Right; - } - } - else - { - if (table.ShowColumnStripes && tableStyle.FirstColumnStripe.Style.Border.Right.HasValue && (tableCol & 1) != 0) - { - right = tableStyle.FirstColumnStripe.Style.Border.Right; - } - if (table.ShowColumnStripes && tableStyle.SecondColumnStripe.Style.Border.Right.HasValue && (tableCol & 1) == 0) - { - right = tableStyle.SecondColumnStripe.Style.Border.Right; - } - if (table.ShowRowStripes && tableStyle.FirstRowStripe.Style.Border.Right.HasValue && (tableRow & 1) != 0) - { - if (cell._fromCol > range._fromCol && cell._fromCol < range._toCol) - { - right = tableStyle.FirstRowStripe.Style.Border.Vertical; - } - else if (cell._fromCol < range._toCol) - { - right = null; - } - else - { - right = tableStyle.FirstRowStripe.Style.Border.Right; - } - } - if (table.ShowRowStripes && tableStyle.SecondRowStripe.Style.Border.Right.HasValue && (tableRow & 1) == 0) - { - if (cell._fromCol > range._fromCol && cell._fromCol < range._toCol) - { - right = tableStyle.SecondRowStripe.Style.Border.Vertical; - } - else if (cell._fromCol < range._toCol) - { - right = null; - } - else - { - right = tableStyle.SecondRowStripe.Style.Border.Right; - } - } - if (table.ShowLastColumn && tableStyle.LastColumn.Style.Border.Right.HasValue && cell._fromCol == range._toCol) - { - right = tableStyle.LastColumn.Style.Border.Right; - } - if (table.ShowFirstColumn && tableStyle.FirstColumn.Style.Border.Right.HasValue && tableCol == range._toCol) - { - right = tableStyle.FirstColumn.Style.Border.Right; - } - } - return right; - } - } -} - diff --git a/src/EPPlus.Export.Pdf/PdfLayout/PdfCellContentLayout.cs b/src/EPPlus.Export.Pdf/PdfLayout/PdfCellContentLayout.cs deleted file mode 100644 index f6e7882e9d..0000000000 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfCellContentLayout.cs +++ /dev/null @@ -1,675 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.PdfCatalog; -using EPPlus.Export.Pdf.PdfResources; -using EPPlus.Export.Pdf.PdfSettings; -using EPPlus.Fonts.OpenType.Integration; -using EPPlus.Graphics; -using OfficeOpenXml; -using OfficeOpenXml.FormulaParsing.Excel.Functions.Text; -using OfficeOpenXml.Interfaces.Fonts; -using OfficeOpenXml.Style; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Drawing; -using System.Linq; -using System.Xml.Serialization; -using Vector2 = EPPlus.Graphics.Geometry.Vector2; - -namespace EPPlus.Export.Pdf.PdfLayout -{ - [DebuggerDisplay("Content: {Name}")] - internal class PdfCellContentLayout : Transform - { - public PdfCellAlignmentData CellAlignmentData; - public bool Clip; - public Rect Clipping; - public bool IsHeaderFooter; - public bool IsHeading; - public bool IsPrintTitle; - public PdfCellStyle CellStyle; - public ExcelRangeBase cell; - public TextLayoutEngine textLayoutEngine; - //private List textFormats = new List(); - public TextLineCollection TextLines; - public List ShapedTexts { get; set; } - - private double textLength = 0; - private double textHeight = 0; - public double LeftTextSpillLength = 0d; - public double RightTextSpillLength = 0d; - private double bottomMargin = 3.5d; //Guessed number - private double rightMargin = 1.4d; //I guessed this one too.. - //internal static FontMeasurerTrueType fontMeasurerTrueType = new FontMeasurerTrueType(); - //internal static MeasurementFont font = new MeasurementFont(); - - //public List TextFormats { get => textFormats; set => textFormats = value; } - public double TextLength { get => textLength; set => textLength = value; } - public double TextHeight { get => textHeight; set => textHeight = value; } - public TextLayoutEngine TextLayoutEngine { get => textLayoutEngine; set => textLayoutEngine = value; } - - - - public PdfCellContentLayout(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfCell cell, MergedCellDrawInfo mergedCellInfo, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) - : base(x, y-height, width, height, scaleX, scaleY, rotation, parent) - { - Z = 2; - CellAlignmentData = cell.ContentAligmnet; - TextLines = cell.TextLines; - ShapedTexts = cell.ShapedTexts; - TextLayoutEngine = cell.TextLayoutEngine; - double totalTextHeight = 0d; - foreach (var line in TextLines) - { - totalTextHeight += line.LargestAscent + line.LargestDescent; - } - double firstLineAscent = TextLines[0].LargestAscent; - double lastLineAscent = TextLines[TextLines.Count - 1].LargestAscent; - LocalPosition = CalculateAlignment(cell.Text, TextLines.LineFragments[0].Width, totalTextHeight, firstLineAscent, lastLineAscent, LocalPosition.X, LocalPosition.Y, cell.Width, height); - } - - public PdfCellContentLayout(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfHeaderFooter headerFooter, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) - : base(x, y, width, height, scaleX, scaleY, rotation, parent) - { - Z = 2; - TextLines = headerFooter.Content.TextLines; - ShapedTexts = headerFooter.Content.ShapedTexts; - TextLayoutEngine = headerFooter.Content.TextLayoutEngine; - CellAlignmentData = headerFooter.Content.ContentAligmnet; - double totalTextHeight = 0d; - foreach (var line in TextLines) - { - totalTextHeight += line.LargestAscent + line.LargestDescent; - } - var newX = CalculateHorizontalAlignment(TextLines.LineFragments[0].OriginalTextFragment.Text, TextLines[0].Width, LocalPosition.X, width, 0); - LocalPosition = new Vector2 (newX, LocalPosition.Y); - } - - private double CalculateVerticalAlignment(string text, double textHeight, double firstAscent, double lastAscent, double y, double height, double padding) - { - double newY = y; - switch (CellAlignmentData.VerticalAlignment) - { - case ExcelVerticalAlignment.Top: - newY = (y + height) - padding - firstAscent; - break; - case ExcelVerticalAlignment.Center: - newY = y + (height + textHeight - firstAscent - lastAscent) / 2d; - break; - case ExcelVerticalAlignment.Bottom: - newY = y + padding + textHeight - lastAscent; - break; - } - return newY; - } - - private double CalculateHorizontalAlignment(string text, double textLength, double x, double width, double padding) - { - double newX = x; - switch (CellAlignmentData.HorizontalAlignment) - { - case ExcelHorizontalAlignment.Fill: - case ExcelHorizontalAlignment.General: - if (double.TryParse(text, out double value)) - { - newX = x + (width - textLength) - padding; - } - else - { - newX = x + padding; - } - break; - case ExcelHorizontalAlignment.Left: - newX = x + padding; - break; - case ExcelHorizontalAlignment.Center: - newX = x + (width - textLength) / 2d; - break; - case ExcelHorizontalAlignment.Right: - newX = x + (width - textLength) - padding; - break; - } - return newX; - } - - private Vector2 CalculatePositionFromRotation(double textLength, double x, double y) - { - double newX = x; - double newY = y; - if (CellAlignmentData.TextRotation < 0) - { - double rot = CellAlignmentData.TextRotation * System.Math.PI / 180.0; - newX += textLength * (1 - System.Math.Cos(rot)); - newY -= textLength * System.Math.Sin(rot); - } - else if (CellAlignmentData.TextRotation > 0) - { - double rot = CellAlignmentData.TextRotation * System.Math.PI / 180.0; - newX += textLength * (1 - System.Math.Cos(rot)); - } - return new Vector2(newX, newY); - } - - private Vector2 CalculateAlignment(string text, double textLength, double textHeight, double firstLineAscent, double lastLineAscent, double x, double y, double width, double height) - { - if (CellAlignmentData.TextRotation != 0 && !CellAlignmentData.IsVertical) - { - return CalculateRotatedAlignment(textLength, textHeight, firstLineAscent, x, y, width, height); - } - double newX = CalculateHorizontalAlignment(text, textLength, x, width, rightMargin); - double newY = CalculateVerticalAlignment(text, textHeight, firstLineAscent, lastLineAscent, y, height, 0d); - return CalculatePositionFromRotation(textLength, newX, newY); - } - - private Vector2 CalculateRotatedAlignment(double textLength, double textHeight, double firstLineAscent, double x, double y, double width, double height) - { - double ascent = firstLineAscent; - double descent = textHeight - firstLineAscent; - if (descent < 0d) descent = 0d; - - double theta = CellAlignmentData.TextRotation * System.Math.PI / 180.0; - double cos = System.Math.Cos(theta); - double sin = System.Math.Sin(theta); - - // Bounding box of the rotated block. Reading direction spans [0, textLength]; - // the cross (line-height) direction spans [-descent, ascent]. - double[] gx = { 0d, textLength, 0d, textLength }; - double[] gy = { ascent, ascent, -descent, -descent }; - double minX = double.MaxValue, maxX = double.MinValue, minY = double.MaxValue, maxY = double.MinValue; - for (int i = 0; i < 4; i++) - { - double ux = cos * gx[i] - sin * gy[i]; - double uy = sin * gx[i] + cos * gy[i]; - if (ux < minX) minX = ux; - if (ux > maxX) maxX = ux; - if (uy < minY) minY = uy; - if (uy > maxY) maxY = uy; - } - double blockWidth = maxX - minX; - double blockHeight = maxY - minY; - - double bx; - switch (CellAlignmentData.HorizontalAlignment) - { - case ExcelHorizontalAlignment.Left: - bx = x + rightMargin; break; - case ExcelHorizontalAlignment.Right: - bx = x + width - blockWidth - rightMargin; break; - default: // Center / General - bx = x + (width - blockWidth) / 2d; break; - } - - double by; - switch (CellAlignmentData.VerticalAlignment) - { - case ExcelVerticalAlignment.Top: - by = y + height - blockHeight - bottomMargin; break; - case ExcelVerticalAlignment.Bottom: - by = y + bottomMargin; break; - default: // Center / Justify / Distributed - by = y + (height - blockHeight) / 2d; break; - } - - // Convert the bounding-box lower-left back to the baseline origin the matrix expects. - return new Vector2(bx - minX, by - minY); - } - - //private static List GetTextFragments(List textFormats) - //{ - // var fragments = new List(textFormats.Count); - - // foreach (var tf in textFormats) - // { - // var fragment = new TextFragment - // { - // Text = tf.Text, - // Font = new MeasurementFont - // { - // FontFamily = tf.FontName, - // Size = (float)tf.FontSize, - // Style = GetMeasurementFontStyle(tf) - // } - // }; - // fragments.Add(fragment); - // } - - // return fragments; - //} - - //private static MeasurementFontStyles GetMeasurementFontStyle(PdfTextFormat tf) - //{ - // var style = (tf.Bold ? MeasurementFontStyles.Bold : 0) - // | (tf.Italic ? MeasurementFontStyles.Italic : 0) - // | (tf.Strike ? MeasurementFontStyles.Strikeout : 0) - // | (tf.Underline ? MeasurementFontStyles.Underline : 0); - - // return style == 0 ? MeasurementFontStyles.Regular : (MeasurementFontStyles)style; - //} - - - - - public PdfCellContentLayout(ExcelRangeBase cell, PdfCellStyle CellStyle, PdfPageSettings pageSettings, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null, PdfDictionaries dictionaries = null) - : base(x, y, width, height, scaleX, scaleY, rotation, parent) - { - this.cell = cell; - this.CellStyle = CellStyle; - CellAlignmentData = new PdfCellAlignmentData(); - CellAlignmentData.HorizontalAlignment = cell.Style.HorizontalAlignment; - CellAlignmentData.VerticalAlignment = cell.Style.VerticalAlignment; - CellAlignmentData.Indent = cell.Style.Indent; - CellAlignmentData.WrapText = cell.Style.WrapText; - CellAlignmentData.ShrinkToFit = cell.Style.ShrinkToFit; - CellAlignmentData.TextRotation = (cell.Style.TextRotation > 90) ? ((cell.Style.TextRotation == 255) ? 0 : 90 - cell.Style.TextRotation) : cell.Style.TextRotation; - CellAlignmentData.IsVertical = cell.Style.TextRotation == 255 ? true : false; - CellAlignmentData.TextDirection = cell.Style.ReadingOrder; - if (!cell.IsRichText) cell._rtc = new ExcelRichTextCollection(cell.Text, cell); - //HandleText(pageSettings, dictionaries, x, y, width, height, CellAlignmentData.TextRotation, CellStyle); - //CalculateTextSpill(width, CellAlignmentData.TextRotation); - //LocalPosition = CalculateAlignmentPositionAndTextOffsets(cell, x, y, width, height); - Size = new Vector2(x + width - LocalPosition.X, y + height - LocalPosition.Y); - //CheckClipping(cell, x, y, width, height); - } - - public PdfCellContentLayout(string text, ExcelRangeBase cell, PdfCellStyle CellStyle, PdfPageSettings pageSettings, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null, PdfDictionaries dictionaries = null) - : base(x, y, width, height, scaleX, scaleY, rotation, parent) - { - this.cell = cell; - this.CellStyle = CellStyle; - CellAlignmentData = new PdfCellAlignmentData(); - CellAlignmentData.HorizontalAlignment = ExcelHorizontalAlignment.Center; - CellAlignmentData.VerticalAlignment = ExcelVerticalAlignment.Bottom; - cell._rtc = new ExcelRichTextCollection(text, cell); - var ns = cell.Worksheet.Workbook.Styles.GetNormalStyle(); - cell._rtc[0].FontName = ns.Style.Font.Name; - cell._rtc[0].Family = ns.Style.Font.Family; - cell._rtc[0].Size = ns.Style.Font.Size; - cell._rtc[0].Color = Color.Black; - //HandleText(pageSettings, dictionaries, x, y, width, height, CellAlignmentData.TextRotation, CellStyle); - //CalculateTextSpill(width, CellAlignmentData.TextRotation); - //LocalPosition = CalculateAlignmentPositionAndTextOffsets(cell, x, y, width, height); - Size = new Vector2(x + width - LocalPosition.X, y + height - LocalPosition.Y); - //CheckClipping(cell, x, y, width, height); - } - - //private void HandleText(PdfPageSettings pageSettings, PdfDictionaries dictionaries, double x, double y, double maxWidth, double maxHeight, double rotation, PdfCellStyle CellStyle) - //{ - // bool bold = false, italic = false, underline = false, strike = false; - // ExcelUnderLineType underLineType = ExcelUnderLineType.None; - // if (CellStyle != null && CellStyle.dxfFont != null) - // { - // bold = CellStyle.dxfFont.Bold != null ? (bool)CellStyle.dxfFont.Bold : false; - // italic = CellStyle.dxfFont.Italic != null ? (bool)CellStyle.dxfFont.Italic : false; - // strike = CellStyle.dxfFont.Strike != null ? (bool)CellStyle.dxfFont.Strike : false; - // underline = CellStyle.dxfFont.Underline != null; - // underLineType = CellStyle.dxfFont.Underline != null ? (ExcelUnderLineType)CellStyle.dxfFont.Underline : ExcelUnderLineType.None; - // } - // for (int i = 0; i < cell.RichText.Count; i++) - // { - // var rt = cell.RichText[i]; - // var textformat = new PdfTextFormat(); - // textformat.Text = rt.Text; - // textformat.FontName = rt.FontName; - // textformat.FontFamily = rt.Family; - // textformat.FontSize = rt.Size; - // textformat.Bold = rt.Bold || bold; - // textformat.Italic = rt.Italic || italic; - // textformat.Strike = rt.Strike || strike; - // textformat.Underline = rt.UnderLine || underline; - // textformat.UnderlineType = rt.UnderLineType == ExcelUnderLineType.None ? underLineType : rt.UnderLineType; - // textformat.SuperScript = rt.VerticalAlign == ExcelVerticalAlignmentFont.Superscript; - // textformat.SubScript = rt.VerticalAlign == ExcelVerticalAlignmentFont.Subscript; - // textformat.FontColor = rt.Color; - // textformat.SubFamily = FontSubFamily.Regular; - // if (textformat.Bold) - // { - // textformat.SubFamily = FontSubFamily.Bold; - // if (textformat.Italic) - // { - // textformat.SubFamily = FontSubFamily.BoldItalic; - // } - // } - // else if (textformat.Italic) - // { - // textformat.SubFamily = FontSubFamily.Italic; - // } - - // this.textFormats.Add(textformat); - // if (!dictionaries.Fonts.ContainsKey(textformat.FullFontName)) - // { - // int label = 1; - // if (dictionaries.Fonts.Count > 0) - // { - // label = dictionaries.Fonts.Last().Value.labelNumber + 1; - // } - // dictionaries.Fonts.Add(textformat.FullFontName, new PdfFontResource(textformat.FontName, textformat.SubFamily, label, pageSettings)); - // } - // var manger = dictionaries.Fonts[textformat.FullFontName].fontSubsetManager; - // manger.AddText(textformat.Text); - // } - //} - - public void CalculateTextSpill(double maxWidth, double rotation) - { - if (maxWidth < textLength) - { - if (rotation != 0) - { - var baseVec = new Vector2(textLength, 0); - var rad = rotation * Math.PI / 180.0d; - var rotVec = new Vector2(textLength * Math.Cos(rad), textLength * Math.Sin(rad)); - var length = Vector2.Project(rotVec, baseVec).Length; - if (length > maxWidth) - { - if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.General) - { - if (double.TryParse(cell.Value.ToString(), out double value)) - { - LeftTextSpillLength = textLength; - } - else - { - RightTextSpillLength = textLength; - } - } - else if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Left) - { - RightTextSpillLength = length; - } - else if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Right) - { - LeftTextSpillLength = length; - } - else if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Center) - { - LeftTextSpillLength = length / 2d; - RightTextSpillLength = length / 2d; - } - - } - return; - } - if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.General) - { - if (double.TryParse(cell.Value.ToString(), out double value)) - { - LeftTextSpillLength = textLength - Size.X; - } - else - { - RightTextSpillLength = textLength - Size.X; - } - } - else if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Left) - { - RightTextSpillLength = textLength - Size.X; - } - else if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Right) - { - LeftTextSpillLength = textLength - Size.X; - } - else if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Center) - { - LeftTextSpillLength = (textLength - Size.X) / 2d; - RightTextSpillLength = (textLength - Size.X) / 2d; - } - } - } - - //Calculate text position from alignment and offsets for each line of text. - public Vector2 CalculateAlignmentPositionAndTextOffsets(ExcelRangeBase cell, double cellX, double CellY, double cellWidth, double cellHeight) - { - double x = 0d; - double y = 0d; - double xOffset = 0d; - double yOffset = 0d; - //double textLength = Lines.TextLength; - //double textHeight = Lines.TextHeight; - //double fontHeight = Lines.FontHeight; - //double lineHeight = Lines.LineHeight; - //if (CellAlignmentData.IsVertical) - //{ - //Need to place vertical text a bit better. It appears each character is monospaced when using vertical. so we should define a common glyphbox that we then use for placing and measuring vertical text. - //switch (CellAlignmentData.HorizontalAlignment) - //{ - //case ExcelHorizontalAlignment.Left: - // x = cellX + rightMargin; - // break; - //case ExcelHorizontalAlignment.Fill: - //case ExcelHorizontalAlignment.General: - //case ExcelHorizontalAlignment.Center: - // x = cellX + (cellWidth - Lines.Height) / 2d; - // break; - //case ExcelHorizontalAlignment.Right: - // x = cellX + (cellWidth - Lines.Height) - rightMargin; - // break; - //} - //switch (CellAlignmentData.VerticalAlignment) - //{ - // case ExcelVerticalAlignment.Top: - // y = (CellY + cellHeight) - (fontHeight / 2d) - bottomMargin; - // break; - // case ExcelVerticalAlignment.Center: - // y = CellY + (cellHeight / 2d) + (Lines.Lines[0].TextHeight / 4d); - // break; - // case ExcelVerticalAlignment.Bottom: - // y = CellY + (Lines.Lines[0].TextHeight - Lines.Lines[0].Words[0].Characters[0].LineHeight) + bottomMargin; - // break; - //} - //for (int i = 1; i < Lines.Lines.Count; i++) - //{ - // //xOffset += Lines.LineHeight; - // switch (CellAlignmentData.VerticalAlignment) - // { - // case ExcelVerticalAlignment.Top: - // Lines.Lines[i].Offset = 0d; - // break; - // case ExcelVerticalAlignment.Center: - // Lines.Lines[i].Offset = CellY + (cellHeight / 2d) + (Lines.Lines[i].TextHeight / 4d) - y; - // break; - // case ExcelVerticalAlignment.Bottom: - // Lines.Lines[i].Offset = (CellY + (Lines.Lines[i].TextHeight - Lines.Lines[i].Words[0].Characters[0].LineHeight) + bottomMargin) - y; - // break; - // } - //} - //} - //else - //{ - switch (CellAlignmentData.HorizontalAlignment) - { - case ExcelHorizontalAlignment.Fill: - case ExcelHorizontalAlignment.General: - if (double.TryParse(cell.Value.ToString(), out double value)) - { - x = cellX + (cellWidth - textLength) - rightMargin; - } - else - { - x = cellX + rightMargin; - } - break; - case ExcelHorizontalAlignment.Left: - x = cellX + rightMargin; - break; - case ExcelHorizontalAlignment.Center: - x = cellX + (cellWidth - textLength) / 2d; - break; - case ExcelHorizontalAlignment.Right: - x = cellX + (cellWidth - textLength) - rightMargin; - break; - } - switch (CellAlignmentData.VerticalAlignment) - { - case ExcelVerticalAlignment.Top: - y = (CellY + cellHeight) - (textHeight / 2d) - bottomMargin; - break; - case ExcelVerticalAlignment.Center: - y = CellY + (cellHeight / 2d) - (textHeight / 4d); ; - break; - case ExcelVerticalAlignment.Bottom: - y = CellY + bottomMargin; - break; - } - if (CellAlignmentData.TextRotation < 0) - { - double rot = CellAlignmentData.TextRotation * System.Math.PI / 180.0; - x += textLength * (1 - System.Math.Cos(rot)); - y -= textLength * System.Math.Sin(rot); - } - else if (CellAlignmentData.TextRotation > 0) - { - double rot = CellAlignmentData.TextRotation * System.Math.PI / 180.0; - x += textLength * (1 - System.Math.Cos(rot)); - } - //for (int i = 1; i < Lines.Lines.Count; i++) - //{ - // yOffset += Lines.LineHeight; - // switch (CellAlignmentData.HorizontalAlignment) - // { - // case ExcelHorizontalAlignment.Fill: - // case ExcelHorizontalAlignment.General: - // if (double.TryParse(cell.Value.ToString(), out double value)) - // { - // Lines.Lines[i].Offset = -Lines.Lines[i].TextLength; - // } - // else - // { - // Lines.Lines[i].Offset = 0d; - // } - // break; - // case ExcelHorizontalAlignment.Left: - // Lines.Lines[i].Offset = 0d; - // break; - // case ExcelHorizontalAlignment.Center: - // Lines.Lines[i].Offset = (cellX + (cellWidth - Lines.Lines[i].TextLength) / 2d) - x; - // break; - // case ExcelHorizontalAlignment.Right: - // Lines.Lines[i].Offset = (cellX + (cellWidth - Lines.Lines[i].TextLength) - rightMargin) - x; - // break; - // } - //} - //} - return new Vector2(x, y); - } - - //Check if clipping is needed. - - - //Check if clipping is needed. - - public void CheckClipping(ExcelRangeBase cell, double x, double y, double width, double height) - { - if (textLength >= width || cell.Merge) - { - if (cell.Merge || - CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Fill) - //CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Left && cell.Worksheet.Cells[cell._fromRow, cell._fromCol + 1].Value != null || - //CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Right && cell.Worksheet.Cells[cell._fromRow, cell._fromCol - 1 <= 0 ? 1 : cell._fromCol - 1].Value != null) - { - Clipping = new Rect() - { - X = x + rightMargin, - Y = y, - Width = width - rightMargin * 2, - Height = height - }; - } - } - } - - // Set clipping to the cell's own bounds. cellY is the top edge (same convention as the constructor). - internal void SetupClipping(double cellX, double cellY, double cellWidth, double cellHeight) - { - Clip = true; - Clipping = new Rect() - { - X = cellX + rightMargin, - Y = cellY - cellHeight, // bottom-left corner in PDF space - Width = cellWidth - rightMargin * 2, - Height = cellHeight - }; - } - - //Create clipping rectangle. - internal void CreateClippingRect(List cells) - { - if (Clip) - { - var cellName = Name.Split('_')[0]; - var pcc = cells.Where(x => x.Name.Contains(cellName)).Where(x => x is PdfCellLayout).ToList(); - if (pcc.Count > 0) - { - Clipping = new Rect() - { - X = pcc[0].LocalPosition.X + rightMargin, - Y = pcc[0].LocalPosition.Y, - Width = pcc[0].Size.X - rightMargin * 2, - Height = pcc[0].Size.Y - }; - } - } - } - - //Create clipping rectangle. - internal void CreateClippingRect(PdfCellLayout cell, double targetX) - { - //need a way to detect starting X position if text is spilled from left or right - var width = targetX - cell.LocalPosition.X; - Clipping = new Rect() - { - X = cell.LocalPosition.X + rightMargin, - Y = cell.LocalPosition.Y, - Width = width - rightMargin * 2, - Height = cell.Size.Y - }; - } - - internal void GidsAndCharMap(PdfDictionaries dictionaries) - { - foreach (var tf in ShapedTexts) - { - var usedFonts = tf.UsedFonts; - - foreach (var glyph in tf.ShapedText.Glyphs) - { - if (glyph.FontId >= usedFonts.Count) - continue; - - var font = usedFonts[glyph.FontId]; - - dictionaries.Fonts[font.FullName].Gids.Add(glyph.GlyphId); - dictionaries.Fonts[font.FullName].fontData = font; - - if (!dictionaries.Fonts[font.FullName].charactermappings.ContainsKey(glyph.GlyphId)) - { - var chars = ExtractCharactersForGlyph(glyph, tf.ShapedText.OriginalText); - if (!string.IsNullOrEmpty(chars)) - { - dictionaries.Fonts[font.FullName].charactermappings[glyph.GlyphId] = chars; - } - } - } - } - } - private string ExtractCharactersForGlyph(ShapedGlyph glyph, string textLine) - { - var chars = new System.Text.StringBuilder(); - for (int i = 0; i < glyph.CharCount && glyph.ClusterIndex + i < textLine.Length; i++) - { - chars.Append(textLine[glyph.ClusterIndex + i]); - } - return chars.ToString(); - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfLayout/PdfCellData.cs b/src/EPPlus.Export.Pdf/PdfLayout/PdfCellData.cs deleted file mode 100644 index 40ff9c39ad..0000000000 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfCellData.cs +++ /dev/null @@ -1,265 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.Pdfhelpers; -using EPPlus.Fonts.OpenType; -using EPPlus.Fonts.OpenType.Integration; -using EPPlus.Graphics; -using OfficeOpenXml; -using OfficeOpenXml.Interfaces.Fonts; -using OfficeOpenXml.Style; -using OfficeOpenXml.ThreadedComments; -using System.Collections.Generic; -using System.Drawing; - -/* - * This file could probably be trimmed a lot and remove unnessacry stuff. - */ -namespace EPPlus.Export.Pdf.PdfLayout -{ - public enum PdfWritingMode - { - HorizontalLtr, - HorizontalRtl, - VerticalTtb, // top-to-bottom - VerticalBtt, // bottom-to-top - } - - internal struct PdfShapedText - { - public IFontProvider FontProvider; - //sometimes a font can have other fonts for certain characters. Key as the glyph id, Value is the font label. - //public Dictionary FontIDLabel; - public Dictionary FontIdMap; - public List UsedFonts; - public ShapedText ShapedText; - } - - //internal struct PdfTextFormat - //{ - //} - //internal struct PdfTextFormat - //{ - // public IFontProvider FontProvider; - // //sometimes a font can have other fonts for certain characters. Key as the glyph id, Value is the font label. - // //public Dictionary FontIDLabel; - // public Dictionary FontIdMap; - // public List UsedFonts; - // public ShapedText ShapedText; - - // public string FontName; - // public int FontFamily; - // public FontSubFamily SubFamily; - // public double FontSize; - // public bool Bold; - // public bool Italic; - // public bool Strike; - // public bool SubScript; - // public bool SuperScript; - // public bool Underline; - // public ExcelUnderLineType UnderlineType; - // public string Text; - // public Color FontColor; - // public double TextLength; - // public double TextHeight; - // public string FullFontName - // { - // get - // { - // string subfam = " " + SubFamily.ToString(); - // if (SubFamily == FontSubFamily.Regular) - // subfam = ""; - // else if (SubFamily == FontSubFamily.BoldItalic) - // subfam = " Bold Italic"; - // return FontName + subfam; - // } - // } - - // //Compares stylings. - // public bool Equals(PdfTextFormat other) - // { - // if (!string.Equals(FontName, other.FontName)) - // return false; - - // if (FontFamily != other.FontFamily) - // return false; - - // if (SubFamily != other.SubFamily) - // return false; - - // if (FontSize != other.FontSize) - // return false; - - // if (Bold != other.Bold || - // Italic != other.Italic || - // Strike != other.Strike || - // SubScript != other.SubScript || - // SuperScript != other.SuperScript || - // Underline != other.Underline) - // return false; - - // if (UnderlineType != other.UnderlineType) - // return false; - - // if (!FontColor.Equals(other.FontColor)) - // return false; - - // return true; - // } - //} - - internal class GlyphPosition - { - public char Character; - public double AdvanceX; - public double AdvanceY; - public double OffsetX; - public double OffsetY; - public Rect GlyphBox; - } - - - internal class PdfCellGradientFillData - { - public ExcelFillGradientType GradientType; - public Color Color1; - public Color Color2; - public Color Color3; - public double Degree; - public double Top; - public double Bottom; - public double Left; - public double Right; - public double[] matrix; - public double[] coords; - - public override string ToString() - { - return GradientType.ToString() + Color1.ToHexString() + Color2.ToHexString() + Degree + Top + Bottom + Left + Right; - } - } - - internal class PdfCellFillData - { - public string id; - public Color BackgroundColor = Color.Empty; - public ExcelFillStyle PatternStyle = ExcelFillStyle.None; - public Color PatternColor = Color.Black; - //Fill Effects - public PdfCellGradientFillData GradientFillData = null; - public bool enhanceGridLine = false; - public PdfCellFillData() { } - } - - internal class PdfCellBordersData - { - public PdfCellBorderData Top = new PdfCellBorderData(LineType.Top); - public PdfCellBorderData Bottom = new PdfCellBorderData(LineType.Bottom); - public PdfCellBorderData Left = new PdfCellBorderData(LineType.Left); - public PdfCellBorderData Right = new PdfCellBorderData(LineType.Right); - public PdfCellBorderData DiagonalUp = new PdfCellBorderData(LineType.DiagonalUp); - public PdfCellBorderData DiagonalDown = new PdfCellBorderData(LineType.DiagonalDown); - - public PdfCellBordersData() { } - } - - internal enum LineType - { - Top = 0, - Bottom, - Left, - Right, - DiagonalUp, - DiagonalDown - } - - internal class PdfCellBorderData - { - internal const double OuterGridLine = 1d; - internal const double Hair = 0.5d; - internal const double Thin = 0.85d; - internal const double Small = 1.1d; - internal const double Medium = 1.5d; - internal const double Thick = 2.0d; - internal const string NoDash = "[] 0 d"; - internal const string Dotted = "[0 2] 0 d"; - internal const string DashDot = "[4 2 1 2] 0 d"; - internal const string DashDotDot = "[4 2 1 2 1 2] 0 d"; - internal const string Dashed = "[4 3] 0 d"; - internal const string MediumDashDot = "[6 3 2 3] 0 d"; - internal const string MediumDashDotDot = "[6 3 2 3 2 3] 0 d"; - internal const string MediumDashed = "[6 4] 0 d"; - - public ExcelBorderStyle BorderStyle = ExcelBorderStyle.None; - public readonly LineType LineType; - public Color BorderColor = Color.Black; - public double MergedDiagonalWidth = 0d; - public double MergedDiagonalHeight = 0d; - public double Width = 0; - public double Height = 0; - public double X = 0; - public double Y = 0; - public bool IsHeading = false; - - public PdfCellBorderData(LineType LineType) - { - this.LineType = LineType; - } - } - - internal class PdfCellAlignmentData - { - public ExcelHorizontalAlignment HorizontalAlignment = ExcelHorizontalAlignment.General; - public ExcelVerticalAlignment VerticalAlignment = ExcelVerticalAlignment.Bottom; - public int Indent = 0; - public bool WrapText = false; - public bool ShrinkToFit = false; - public int TextRotation = 0; - public ExcelReadingOrder TextDirection = ExcelReadingOrder.ContextDependent; - public bool IsVertical = false; - - public PdfCellAlignmentData() { } - } - - internal class GridLine - { - public static double Width = 0.125d; - public static double HalfWidth = Width / 2d; - public static double FourthWidth = Width / 4d; - public double X1; - public double Y1; - public double X2; - public double Y2; - - public GridLine(double x1, double y1, double x2, double y2) - { - X1 = x1; Y1 = y1; X2 = x2; Y2 = y2; - } - } - - internal class PdfCommentsAndNotes - { - public ExcelComment Comment; - public ExcelThreadedCommentThread ThreadedComment; - public static bool HasThreadedComment = false; - - public PdfCommentsAndNotes(ExcelComment comment) - { - Comment = comment; - } - public PdfCommentsAndNotes(ExcelThreadedCommentThread tComment) - { - ThreadedComment = tComment; - } - } - -} diff --git a/src/EPPlus.Export.Pdf/PdfLayout/PdfCellLayout.cs b/src/EPPlus.Export.Pdf/PdfLayout/PdfCellLayout.cs deleted file mode 100644 index f8570a0b9b..0000000000 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfCellLayout.cs +++ /dev/null @@ -1,333 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.Pdfhelpers; -using EPPlus.Export.Pdf.PdfResources; -using EPPlus.Export.Pdf.PdfSettings; -using EPPlus.Graphics; -using EPPlus.Graphics.Geometry; -using OfficeOpenXml; -using OfficeOpenXml.Style; -using System.Collections.Generic; -using System.Diagnostics; -using System.Drawing; -using System.Linq; - -namespace EPPlus.Export.Pdf.PdfLayout -{ - [DebuggerDisplay("Cell: {Name}")] - internal class PdfCellLayout : Transform, IShadingLayout - { - public ExcelRangeBase cell; - public PdfCellFillData CellFillData; - public PdfCellStyle CellStyle; - public double LeftTextSpillLength = 0d; - public double RightTextSpillLength = 0d; - public bool delete = false; - public bool IsHeading = false; - public bool IsPrintTitle = false; - - public PdfCellLayout(PdfDictionaries dictionaries, PdfCellStyle CellStyle, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) - : base(x, y - height, width, height, scaleX, scaleY, rotation, parent) - { - Z = 1; - var xfFill = CellStyle.xfFill; - var dxfFill = CellStyle.dxfFill; - CellFillData = new PdfCellFillData(); - if (dxfFill != null && xfFill.IsEmpty()) - { - CellFillData.PatternStyle = dxfFill.PatternType != null ? (ExcelFillStyle)dxfFill.PatternType : ExcelFillStyle.Solid; - if (CellFillData.PatternStyle == ExcelFillStyle.Solid) - { - CellFillData.BackgroundColor = PdfColor.SetColorFromHex(dxfFill.BackgroundColor.LookupColor()); - } - else if (CellFillData.PatternStyle != ExcelFillStyle.None) - { - CellFillData.BackgroundColor = PdfColor.SetColorFromHex(dxfFill.PatternColor.Color == null ? "#FFFFFFFF" : dxfFill.PatternColor.LookupColor()); - CellFillData.PatternColor = PdfColor.SetColorFromHex(dxfFill.BackgroundColor.LookupColor()); - CellFillData.id = AddPatternResourceData(dictionaries.Patterns, CellFillData.PatternStyle.ToString() + CellFillData.PatternColor.ToHexString() + CellFillData.BackgroundColor.ToHexString()); - } - else if (dxfFill.Gradient != null) - { - CellFillData.GradientFillData = new PdfCellGradientFillData(); - CellFillData.GradientFillData.GradientType = dxfFill.Gradient.GradientType == null ? ExcelFillGradientType.None : (ExcelFillGradientType)dxfFill.Gradient.GradientType; - CellFillData.GradientFillData.Color1 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[0].Color.LookupColor()); - CellFillData.GradientFillData.Color2 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[1].Color.LookupColor()); - CellFillData.GradientFillData.Color3 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[2].Color.LookupColor()); - CellFillData.GradientFillData.Degree = dxfFill.Gradient.Degree == null ? 0 : (double)dxfFill.Gradient.Degree; - CellFillData.GradientFillData.Top = dxfFill.Gradient.Top == null ? 0 : (double)dxfFill.Gradient.Top; - CellFillData.GradientFillData.Bottom = dxfFill.Gradient.Bottom == null ? 0 : (double)dxfFill.Gradient.Bottom; - CellFillData.GradientFillData.Left = dxfFill.Gradient.Left == null ? 0 : (double)dxfFill.Gradient.Left; - CellFillData.GradientFillData.Right = dxfFill.Gradient.Right == null ? 0 : (double)dxfFill.Gradient.Right; - CellFillData.id = CellFillData.GradientFillData.ToString() + $"_{x:F4}_{(y - height):F4}_{width:F4}_{height:F4}"; - AddShadingResourceData(dictionaries.Shadings, CellFillData.id); - } - } - else - { - if (xfFill.PatternType == ExcelFillStyle.Solid) - { - var bkgc = xfFill.BackgroundColor; - CellFillData.PatternStyle = xfFill.PatternType; - if (string.IsNullOrEmpty(bkgc.LookupColor()) && !string.IsNullOrEmpty(cell.Text)) - { - CellFillData.BackgroundColor = Color.Empty; - } - else - { - CellFillData.BackgroundColor = Pdfhelpers.PdfColor.SetColorFromHex(bkgc.LookupColor()); - } - } - else if (xfFill.PatternType != ExcelFillStyle.None) - { - CellFillData.PatternStyle = xfFill.PatternType; - CellFillData.BackgroundColor = PdfColor.SetColorFromHex(xfFill.PatternColor.Rgb == null ? "#FFFFFFFF" : xfFill.PatternColor.LookupColor()); - CellFillData.PatternColor = PdfColor.SetColorFromHex(xfFill.BackgroundColor.LookupColor()); - CellFillData.id = AddPatternResourceData(dictionaries.Patterns, CellFillData.PatternStyle.ToString() + CellFillData.PatternColor.ToHexString() + CellFillData.BackgroundColor.ToHexString()); - } - else if (xfFill.HasGradient) - { - CellFillData.GradientFillData = new PdfCellGradientFillData(); - CellFillData.GradientFillData.GradientType = xfFill.Gradient.Type; - CellFillData.GradientFillData.Color1 = PdfColor.SetColorFromHex(xfFill.Gradient.Color1.LookupColor()); - CellFillData.GradientFillData.Color2 = PdfColor.SetColorFromHex(xfFill.Gradient.Color2.LookupColor()); - CellFillData.GradientFillData.Color3 = PdfColor.SetColorFromHex(xfFill.Gradient.Color3.LookupColor()); - CellFillData.GradientFillData.Degree = xfFill.Gradient.Degree; - CellFillData.GradientFillData.Top = double.IsNaN(xfFill.Gradient.Top) ? 0 : xfFill.Gradient.Top; - CellFillData.GradientFillData.Bottom = double.IsNaN(xfFill.Gradient.Bottom) ? 0 : xfFill.Gradient.Bottom; - CellFillData.GradientFillData.Left = double.IsNaN(xfFill.Gradient.Left) ? 0 : xfFill.Gradient.Left; - CellFillData.GradientFillData.Right = double.IsNaN(xfFill.Gradient.Right) ? 0 : xfFill.Gradient.Right; - CellFillData.id = CellFillData.GradientFillData.ToString() + $"_{x:F4}_{(y - height):F4}_{width:F4}_{height:F4}"; - AddShadingResourceData(dictionaries.Shadings, CellFillData.id); - } - } - } - - - public PdfCellLayout(PdfDictionaries dictionaries, ExcelRangeBase cell, PdfCellStyle CellStyle, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) - : base(x, y - height, width, height, scaleX, scaleY, rotation, parent) - { - if (cell != null) - { - this.cell = cell; - this.CellStyle = CellStyle; - var xfFill = CellStyle.xfFill; - var dxfFill = CellStyle.dxfFill; - CellFillData = new PdfCellFillData(); - if (dxfFill != null && xfFill.IsEmpty()) - { - CellFillData.PatternStyle = dxfFill.PatternType != null ? (ExcelFillStyle)dxfFill.PatternType : ExcelFillStyle.Solid; - if (CellFillData.PatternStyle == ExcelFillStyle.Solid) - { - CellFillData.BackgroundColor = PdfColor.SetColorFromHex(dxfFill.BackgroundColor.LookupColor()); - } - else if (CellFillData.PatternStyle != ExcelFillStyle.None) - { - CellFillData.BackgroundColor = PdfColor.SetColorFromHex(dxfFill.PatternColor.Color == null ? "#FFFFFFFF" : dxfFill.PatternColor.LookupColor()); - CellFillData.PatternColor = PdfColor.SetColorFromHex(dxfFill.BackgroundColor.LookupColor()); - CellFillData.id = AddPatternResourceData(dictionaries.Patterns, CellFillData.PatternStyle.ToString() + CellFillData.PatternColor.ToHexString() + CellFillData.BackgroundColor.ToHexString()); - } - else if (dxfFill.Gradient != null) - { - CellFillData.GradientFillData = new PdfCellGradientFillData(); - CellFillData.GradientFillData.GradientType = dxfFill.Gradient.GradientType == null ? ExcelFillGradientType.None : (ExcelFillGradientType)dxfFill.Gradient.GradientType; - CellFillData.GradientFillData.Color1 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[0].Color.LookupColor()); - CellFillData.GradientFillData.Color2 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[1].Color.LookupColor()); - CellFillData.GradientFillData.Color3 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[2].Color.LookupColor()); - CellFillData.GradientFillData.Degree = dxfFill.Gradient.Degree == null ? 0 : (double)dxfFill.Gradient.Degree; - CellFillData.GradientFillData.Top = dxfFill.Gradient.Top == null ? 0 : (double)dxfFill.Gradient.Top; - CellFillData.GradientFillData.Bottom = dxfFill.Gradient.Bottom == null ? 0 : (double)dxfFill.Gradient.Bottom; - CellFillData.GradientFillData.Left = dxfFill.Gradient.Left == null ? 0 : (double)dxfFill.Gradient.Left; - CellFillData.GradientFillData.Right = dxfFill.Gradient.Right == null ? 0 : (double)dxfFill.Gradient.Right; - CellFillData.id = CellFillData.GradientFillData.ToString() + $"_{x:F4}_{(y - height):F4}_{width:F4}_{height:F4}"; - AddShadingResourceData(dictionaries.Shadings, CellFillData.id); - } - } - else - { - if (xfFill.PatternType == ExcelFillStyle.Solid) - { - var bkgc = xfFill.BackgroundColor; - CellFillData.PatternStyle = xfFill.PatternType; - if (string.IsNullOrEmpty(bkgc.LookupColor()) && !string.IsNullOrEmpty(cell.Text)) - { - CellFillData.BackgroundColor = Color.Empty; - } - else - { - CellFillData.BackgroundColor = Pdfhelpers.PdfColor.SetColorFromHex(bkgc.LookupColor()); - } - } - else if (xfFill.PatternType != ExcelFillStyle.None) - { - CellFillData.PatternStyle = xfFill.PatternType; - CellFillData.BackgroundColor = PdfColor.SetColorFromHex(xfFill.PatternColor.Rgb == null ? "#FFFFFFFF" : xfFill.PatternColor.LookupColor()); - CellFillData.PatternColor = PdfColor.SetColorFromHex(xfFill.BackgroundColor.LookupColor()); - CellFillData.id = AddPatternResourceData(dictionaries.Patterns, CellFillData.PatternStyle.ToString() + CellFillData.PatternColor.ToHexString() + CellFillData.BackgroundColor.ToHexString()); - } - else if (xfFill.HasGradient) - { - CellFillData.GradientFillData = new PdfCellGradientFillData(); - CellFillData.GradientFillData.GradientType = xfFill.Gradient.Type; - CellFillData.GradientFillData.Color1 = PdfColor.SetColorFromHex(xfFill.Gradient.Color1.LookupColor()); - CellFillData.GradientFillData.Color2 = PdfColor.SetColorFromHex(xfFill.Gradient.Color2.LookupColor()); - CellFillData.GradientFillData.Color3 = PdfColor.SetColorFromHex(xfFill.Gradient.Color3.LookupColor()); - CellFillData.GradientFillData.Degree = xfFill.Gradient.Degree; - CellFillData.GradientFillData.Top = double.IsNaN(xfFill.Gradient.Top) ? 0 : xfFill.Gradient.Top; - CellFillData.GradientFillData.Bottom = double.IsNaN(xfFill.Gradient.Bottom) ? 0 : xfFill.Gradient.Bottom; - CellFillData.GradientFillData.Left = double.IsNaN(xfFill.Gradient.Left) ? 0 : xfFill.Gradient.Left; - CellFillData.GradientFillData.Right = double.IsNaN(xfFill.Gradient.Right) ? 0 : xfFill.Gradient.Right; - CellFillData.id = CellFillData.GradientFillData.ToString() + $"_{x:F4}_{(y - height):F4}_{width:F4}_{height:F4}"; - AddShadingResourceData(dictionaries.Shadings, CellFillData.id); - } - } - } - } - - private string AddPatternResourceData(Dictionary patternResources, string key) - { - if (!patternResources.ContainsKey(key)) - { - int label = 1; - if (patternResources.Count > 0) - { - label = patternResources.Last().Value.labelNumber + 1; - } - var pr = new PdfPatternResource(label, CellFillData); - patternResources.Add(key, pr); - } - return key; - } - - private string AddShadingResourceData(Dictionary shadingResources, string key) - { - if (!shadingResources.ContainsKey(key)) - { - int label = 1; - if (shadingResources.Count > 0) - { - label = shadingResources.Last().Value.labelNumber + 1; - } - var pr = new PdfShadingResource(label, CellFillData); - shadingResources.Add(key, pr); - } - return key; - } - - //Adjust size and position slightly for aesthetics. - public void AdjustForGridLines() - { - Size = new Vector2(Size.X + GridLine.HalfWidth, Size.Y + GridLine.HalfWidth); - LocalPosition = new Vector2(LocalPosition.X + GridLine.FourthWidth, LocalPosition.Y + GridLine.FourthWidth); - } - - public void UpdateShadingPositionMatrix(PdfPageSettings pageSettings) - { - //LocalPosition = new Vector2(LocalPosition.X, pageSettings.PageSize.HeightPu - System.Math.Abs(LocalPosition.Y) - Size.Y); - if (CellFillData.GradientFillData != null) - { - if (CellFillData.GradientFillData.GradientType == ExcelFillGradientType.Linear) - { - switch (CellFillData.GradientFillData.Degree) - { - case 45d: - CellFillData.GradientFillData.coords = [0, 1, 1, 0]; - break; - case 90d: - CellFillData.GradientFillData.coords = [0, 1, 0, 0]; - break; - case 135d: - CellFillData.GradientFillData.coords = [1, 1, 0, 0]; - break; - case 180d: - CellFillData.GradientFillData.coords = [1, 0, 0, 0]; - break; - case 225d: - CellFillData.GradientFillData.coords = [1, 0, 0, 1]; - break; - case 270d: - CellFillData.GradientFillData.coords = [0, 0, 0, 1]; - break; - case 315d: - CellFillData.GradientFillData.coords = [0, 0, 1, 1]; - break; - case 0d: - default: - CellFillData.GradientFillData.coords = [0, 0, 1, 0]; - break; - } - CellFillData.GradientFillData.matrix = [Size.X, 0, 0, Size.Y, LocalPosition.X, LocalPosition.Y]; - } - else if (CellFillData.GradientFillData.GradientType == ExcelFillGradientType.Path) - { - double x = LocalPosition.X; - double y = LocalPosition.Y; - double width = Size.X; - double height = Size.Y; - var top = CellFillData.GradientFillData.Top; - var bottom = CellFillData.GradientFillData.Bottom; - var left = CellFillData.GradientFillData.Left; - var right = CellFillData.GradientFillData.Right; - double r = 1; - if (top == 0 && bottom == 0 && left == 0 && right == 0) - { - CellFillData.GradientFillData.coords = [0, 1, 0, 0, 1, r]; - } - else if (top == 0 && bottom == 0 && left == 1 && right == 1) - { - CellFillData.GradientFillData.coords = [1, 1, 0, 1, 1, r]; - } - else if (top == 1 && bottom == 1 && left == 0 && right == 0) - { - CellFillData.GradientFillData.coords = [0, 0, 0, 0, 0, r]; - } - else if (top == 1 && bottom == 1 && left == 1 && right == 1) - { - CellFillData.GradientFillData.coords = [1, 0, 0, 1, 0, r]; - } - else if (top == 0.5 && bottom == 0.5 && left == 0.5 && right == 0.5) - { - CellFillData.GradientFillData.coords = [0.5, 0.5, 0, 0.5, 0.5, r]; - } - CellFillData.GradientFillData.matrix = [Size.X, 0, 0, Size.Y, LocalPosition.X, LocalPosition.Y]; - //double cx = x; - //double cy = y + height; - //if (!double.IsNaN(left) && !double.IsNaN(right) && !double.IsNaN(top) && !double.IsNaN(bottom)) // bottom-right - //{ - // if (top < 1d && bottom < 1d &&left < 1d && right < 1d) - // { - // cx = x + width / 2d; - // cy = y + height / 2d; - // } - // else - // { - // cx = x + width; - // cy = y; - // } - //} - //else if (!double.IsNaN(left) && !double.IsNaN(right)) // bottom-left - //{ - // cx = x + width; - // cy = y + height; - //} - //else if (!double.IsNaN(top) && !double.IsNaN(bottom)) // top-right - //{ - // cx = x; - // cy = y; - //} - //double r = System.Math.Sqrt(width * width + height * height) / 2d; - //CellFillData.GradientFillData.coords = [cx, cy, 0, cx, cy, r]; - } - } - } - } -} \ No newline at end of file diff --git a/src/EPPlus.Export.Pdf/PdfLayout/PdfCellStyle.cs b/src/EPPlus.Export.Pdf/PdfLayout/PdfCellStyle.cs deleted file mode 100644 index d6b33582a0..0000000000 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfCellStyle.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using EPPlus.Export.Pdf.Pdfhelpers; -using OfficeOpenXml; -using OfficeOpenXml.Style; -using OfficeOpenXml.Style.Dxf; - -namespace EPPlus.Export.Pdf.PdfLayout -{ - /// - /// Holds the styles for the cell. - /// xf styles is the base style and dxf styles is an override(sort of) that can come from tables or conditional formatting. - /// They are used in the following priority: - /// 1. xf style that is not default - /// 2. dxf style if it exsist - /// 3. deault xf style - /// - internal class PdfCellStyle - { - //Fill - internal ExcelFill xfFill { get; set; } - internal ExcelDxfFill dxfFill { get; set; } - - //Borders - internal ExcelBorderItem xfTop { get; set; } - internal ExcelBorderItem xfBottom { get; set; } - internal ExcelBorderItem xfLeft { get; set; } - internal ExcelBorderItem xfRight { get; set; } - internal bool DiagonalUp { get; set; } - internal bool DiagonalDown { get; set; } - internal ExcelBorderItem Diagonal { get; set; } - internal ExcelDxfBorderItem dxfTop { get; set; } - internal ExcelDxfBorderItem dxfBottom { get; set; } - internal ExcelDxfBorderItem dxfLeft { get; set; } - internal ExcelDxfBorderItem dxfRight { get; set; } - internal ExcelDxfBorderItem dxfHorizontal { get; set; } - internal ExcelDxfBorderItem dxfVertical { get; set; } - - //Fonts - internal ExcelFont xfFont { get; set; } - internal ExcelDxfFontBase dxfFont { get; set; } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfLayout/PdfCommentsLayout.cs b/src/EPPlus.Export.Pdf/PdfLayout/PdfCommentsLayout.cs deleted file mode 100644 index 0b196b7574..0000000000 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfCommentsLayout.cs +++ /dev/null @@ -1,152 +0,0 @@ -using EPPlus.Export.Pdf.PdfResources; -using EPPlus.Export.Pdf.PdfSettings; -using EPPlus.Graphics; -using OfficeOpenXml; -using OfficeOpenXml.Style; -using OfficeOpenXml.Style.XmlAccess; -using System.Collections.Generic; -using System.Linq; -using static EPPlus.Export.Pdf.PdfLayout.PdfCatalogLayout; - -namespace EPPlus.Export.Pdf.PdfLayout -{ - internal class PdfCommentsLayout - { - - static double firstColumnWidth = 56.501953125d; - static double secondColumnWidth = 418.8896484375d; - - public static void CreateCommentAndNotesPages(PdfPageSettings pageSettings, PdfDictionaries dictionaries, ExcelWorksheet ws, PdfPagesLayout pagesLayout) - { - if (dictionaries.CommentsAndNotes.Count == 0) return; - var ns = ws.Workbook.Styles.GetNormalStyle(); - var tempWS = ws.Workbook.Worksheets.Add("TemporaryWorksheetForCommentsInPdfExporterForEPPlus"); - int row = 1; - int col = 1; - tempWS.Column(col).Width = 10d; - tempWS.Column(col + 1).Width = 75d; - foreach (var commentNote in dictionaries.CommentsAndNotes) - { - AddText(tempWS, row, col, "Cell:", true, ExcelHorizontalAlignment.Right, ExcelVerticalAlignment.Bottom, ns); - AddText(tempWS, row, col + 1, commentNote.Key, false, ExcelHorizontalAlignment.Left, ExcelVerticalAlignment.Bottom, ns); - row++; - if (commentNote.Value.ThreadedComment != null) - { - var CommentReply = "Comment:"; - foreach (var comment in commentNote.Value.ThreadedComment.Comments) - { - AddText(tempWS, row, col, CommentReply, true, ExcelHorizontalAlignment.Right, ExcelVerticalAlignment.Bottom, ns); - AddText(tempWS, row, col + 1, comment.Author.DisplayName, false, ExcelHorizontalAlignment.Left, ExcelVerticalAlignment.Bottom, ns); - row++; - AddText(tempWS, row, col + 1, comment.Text, false, ExcelHorizontalAlignment.Left, ExcelVerticalAlignment.Top, ns); - row++; - AddText(tempWS, row, col + 1, comment.DateCreated.ToString("yyyy-MM-dd HH:mm"), false, ExcelHorizontalAlignment.Left, ExcelVerticalAlignment.Bottom, ns); - row++; - CommentReply = "Reply:"; - } - } - else if (commentNote.Value.Comment != null) - { - var note = commentNote.Value.Comment; - AddText(tempWS, row, col, "Note:", true, ExcelHorizontalAlignment.Right, ExcelVerticalAlignment.Bottom, ns); - AddText(tempWS, row, col + 1, note.Author, false, ExcelHorizontalAlignment.Left, ExcelVerticalAlignment.Bottom, ns); - row++; - AddText(tempWS, row, col + 1, note.RichText, false, ExcelHorizontalAlignment.Left, ExcelVerticalAlignment.Bottom, ns); - row++; - } - row++; - } - //get worksheet layout - var setting = pageSettings; - setting.CommentsAndNotes = CommentsAndNotes.None; - var commentsLayout = new PdfWorksheetLayout(tempWS, setting, dictionaries); - var s = commentsLayout.ToHierarchyString(); - setting.ShowHeadings = false; - PdfCatalogLayout.CreatePageLayoutObjects(tempWS, setting, dictionaries, commentsLayout, pagesLayout, true); - - //populate pages - var pages = pagesLayout.ChildObjects.Where(x => ((PdfPageLayout)x).isCommentsPage).ToArray(); - var pageData = new List(); - foreach (PdfPageLayout p in pages) - { - pageData.Add(new PdfCatalogLayout.PageData(p, p.ChildObjects[0].GetGlobalBoundingbox())); - } - pageData.Sort((a, b) => a.Bounds.Top.CompareTo(b.Bounds.Top)); - var transforms = new List(commentsLayout.ChildObjects); - foreach (var t in transforms) - { - var cellBounds = t.GetGlobalBoundingbox(); - // Pass 1: check if ANY page fully contains this transform. - // If so, we use only that page and skip all partial intersects. - PageData fullIntersectPage = null; - foreach (var pd in pageData) - { - if (pd.Bounds.Top > cellBounds.Bottom) break; - if (pd.Bounds.Bottom < cellBounds.Top) continue; - - if (Transform.IntersectsFully(pd.Bounds, cellBounds)) - { - fullIntersectPage = pd; - break; - } - } - // Pass 2: assign to pages. - foreach (var pd in pageData) - { - if (pd.Bounds.Top > cellBounds.Bottom) break; - if (pd.Bounds.Bottom < cellBounds.Top) continue; - - bool fullIntersect = fullIntersectPage != null && pd == fullIntersectPage; - bool partialIntersect = fullIntersectPage == null && !fullIntersect && Transform.Intersects(cellBounds, pd.Bounds); - - if (!fullIntersect && !partialIntersect) continue; - - var page = pd.Page; - - if (t is PdfCellContentLayout cellContent) - page.ChildObjects[0].AddChild(cellContent); - } - } - ws.Workbook.Worksheets.Delete("TemporaryWorksheetForCommentsInPdfExporterForEPPlus"); - } - - private static void AddText(ExcelWorksheet ws, int row, int col, string text, bool bold, ExcelHorizontalAlignment horizontalAlignment, ExcelVerticalAlignment verticalAlignment, ExcelNamedStyleXml ns) - { - var cell = ws.Cells[row, col]; - cell.RichText.Clear(); - var rt = cell.RichText.Add(text); - rt.Bold = bold; - rt.FontName = ns.Style.Font.Name; - rt.Family = ns.Style.Font.Family; - rt.Size = ns.Style.Font.Size; - cell.Style.HorizontalAlignment = horizontalAlignment; - cell.Style.VerticalAlignment = verticalAlignment; - cell.Style.WrapText = true; - } - - private static void AddText(ExcelWorksheet ws, int row, int col, ExcelRichTextCollection RichText, bool bold, ExcelHorizontalAlignment horizontalAlignment, ExcelVerticalAlignment verticalAlignment, ExcelNamedStyleXml ns) - { - var cell = ws.Cells[row, col]; - cell.RichText.Clear(); - for (int i=1; i< RichText.Count; i++) - { - var rt= RichText[i]; - var trimmedText = rt.Text.Trim(); - var r = cell.RichText.Add(trimmedText); - r.Bold = rt.Bold; - r.Italic = rt.Italic; - r.Color = rt.Color; - r.ColorSettings = rt.ColorSettings; - r.Strike = rt.Strike; - r.UnderLine = rt.UnderLine; - r.UnderLineType = rt.UnderLineType; - r.FontName = rt.FontName; - r.Family = rt.Family; - r.Size = rt.Size; - } - cell.Style.HorizontalAlignment = horizontalAlignment; - cell.Style.VerticalAlignment = verticalAlignment; - cell.Style.WrapText = true; - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfLayout/PdfContentLayout.cs b/src/EPPlus.Export.Pdf/PdfLayout/PdfContentLayout.cs deleted file mode 100644 index 94d2a83e82..0000000000 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfContentLayout.cs +++ /dev/null @@ -1,36 +0,0 @@ -/************************************************************************************************* - 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 EPPlus.Export.Pdf.PdfSettings; - -namespace EPPlus.Export.Pdf.PdfLayout -{ - /// - /// Represents the content area for a page in the pdf document. - /// - internal class PdfContentLayout : Transform - { - public PdfContentLayout(double x, double y, PdfContentBounds bounds) - { - Position = new Vector2(x, y); - Size = new Vector2(bounds.Width, bounds.Height); - } - - public PdfContentLayout(double x, double y, double width, double height) - { - Position = new Vector2(x, y); - Size = new Vector2(width, height); - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfLayout/PdfDrawingLayout.cs b/src/EPPlus.Export.Pdf/PdfLayout/PdfDrawingLayout.cs deleted file mode 100644 index 9d89c81e2e..0000000000 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfDrawingLayout.cs +++ /dev/null @@ -1,29 +0,0 @@ -/************************************************************************************************* - 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 OfficeOpenXml.Drawing; -using EPPlus.Graphics; - - -namespace EPPlus.Export.Pdf.PdfLayout -{ - //NOT IMPLEMENTED YET. - internal class PdfDrawingLayout : Transform - { - public ExcelDrawing Drawing; - public PdfDrawingLayout(ExcelDrawing drawing, double x, double y, double width, double height) - : base(x,y,width,height) - { - Drawing = drawing; - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfLayout/PdfHeaderFooterLayout.cs b/src/EPPlus.Export.Pdf/PdfLayout/PdfHeaderFooterLayout.cs deleted file mode 100644 index a3837de2f6..0000000000 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfHeaderFooterLayout.cs +++ /dev/null @@ -1,193 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.PdfResources; -using EPPlus.Export.Pdf.PdfSettings; -using EPPlus.Fonts.OpenType; -using EPPlus.Fonts.OpenType.Integration; -using EPPlus.Fonts.OpenType.TextShaping; -using EPPlus.Graphics; -using EPPlus.Graphics.Geometry; -using OfficeOpenXml; -using OfficeOpenXml.Interfaces.Drawing.Text; -using OfficeOpenXml.Interfaces.Fonts; -using OfficeOpenXml.Style; -using OfficeOpenXml.Style.HeaderFooterTextFormat; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace EPPlus.Export.Pdf.PdfLayout -{ - internal class PdfHeaderFooterLayout : Transform - { - //private List textFormats = new List(); - private double textLength { get; set; } - private double textHeight { get; set; } - private TextLayoutEngine textLayoutEngine; - - //public List TextFormats { get => textFormats; set => textFormats = value; } - public double TextLength { get => textLength; set => textLength = value; } - public double TextHeight { get => textHeight; set => textHeight = value; } - public TextLayoutEngine TextLayoutEngine { get => textLayoutEngine; set => textLayoutEngine = value; } - - public PdfHeaderFooterLayout(ExcelHeaderFooterTextCollection textCollection, ExcelWorksheet ws, PdfPageSettings pageSettings, PdfDictionaries dictionaries, int pageNumber, int totalPages) - { - for( int i=1; i < textCollection.Count; i++) - { - var text = textCollection[i]; - var ns = ws.Workbook.Styles.GetNormalStyle(); - //PdfTextFormat textFormat = new PdfTextFormat(); - //textFormat.FontName = string.IsNullOrEmpty( text.FontName) ? ns.Style.Font.Name : text.FontName; - //textFormat.FontSize = text.FontSize == null ? ns.Style.Font.Size : (double)text.FontSize; - //textFormat.Bold = text.Bold; - //textFormat.Italic = text.Italic; - //textFormat.Strike = text.Striketrough; - //textFormat.SubScript = text.SubScript; - //textFormat.SuperScript = text.SuperScript; - //textFormat.Underline = text.Underline; - //textFormat.UnderlineType = textFormat.Underline ? ExcelUnderLineType.Single : ExcelUnderLineType.None; - //if (text.DoubleUnderline) textFormat.Underline = true; - //textFormat.UnderlineType = text.DoubleUnderline ? ExcelUnderLineType.Double : textFormat.UnderlineType; - //textFormat.FontColor = text.Color; - //textFormat.SubFamily = FontSubFamily.Regular; - //if (textFormat.Bold) - //{ - // textFormat.SubFamily = FontSubFamily.Bold; - // if (textFormat.Italic) - // { - // textFormat.SubFamily = FontSubFamily.BoldItalic; - // } - //} - //else if (textFormat.Italic) - //{ - // textFormat.SubFamily = FontSubFamily.Italic; - //} - - //switch (text.FormatCode) - //{ - // case ExcelHeaderFooterFormattingCodes.SheetName: - // textFormat.Text += ws.Name; - // break; - // case ExcelHeaderFooterFormattingCodes.CurrentDate: - // textFormat.Text += DateTime.Now.ToString($"yyyy-MM-dd"); - // break; - // case ExcelHeaderFooterFormattingCodes.FileName: - // textFormat.Text += ws._package.File.Name; - // break; - // case ExcelHeaderFooterFormattingCodes.NumberOfPages: - // textFormat.Text += totalPages.ToString(); - // break; - // case ExcelHeaderFooterFormattingCodes.PageNumber: - // textFormat.Text += pageNumber; - // break; - // case ExcelHeaderFooterFormattingCodes.CurrentTime: - // textFormat.Text += DateTime.Now.ToString("HH:mm"); - // break; - // case ExcelHeaderFooterFormattingCodes.FilePath: - // textFormat.Text += ws._package.File.Directory.FullName + "\\"; - // break; - // default: - // textFormat.Text += text.Text; - // break; - //} - ////PdfFontResource.GetFontResourceData(dictionaries.Fonts, pageSettings, textFormat); - //MeasurementFont font = new MeasurementFont(); - //font.FontFamily = textFormat.FontName; - //font.Size = (float)textFormat.FontSize; - //font.Style = ((textFormat.Bold ? MeasurementFontStyles.Bold : 0) | - // (textFormat.Italic ? MeasurementFontStyles.Italic : 0) | - // (textFormat.Strike ? MeasurementFontStyles.Strikeout : 0) | - // (textFormat.Underline ? MeasurementFontStyles.Underline : 0)) - // switch - // { - // 0 => MeasurementFontStyles.Regular, - // var s => s - // }; - - //var shaper = (TextShaper)OpenTypeFonts.GetShaperForFont(font); - //var measurer = new OpenTypeFontTextMeasurer(shaper); - - //var result = measurer.MeasureText(textFormat.Text, font); - //textFormat.TextLength = result.Width; - //textLength += result.Width; - //textFormat.TextHeight = result.Height; - //textHeight = result.Height; - //textFormats.Add(textFormat); - //if (!dictionaries.Fonts.ContainsKey(textFormat.FullFontName)) - //{ - // int label = 1; - // if (dictionaries.Fonts.Count > 0) - // { - // label = dictionaries.Fonts.Last().Value.labelNumber + 1; - // } - // dictionaries.Fonts.Add(textFormat.FullFontName, new PdfFontResource(textFormat.FontName, textFormat.SubFamily, label, pageSettings)); - //} - //var manger = dictionaries.Fonts[textFormat.FullFontName].fontSubsetManager; - //manger.AddText(textFormat.Text); - } - } - - public void AdjustPositionByTextLength(char rc, char hf) - { - if (rc == 'r') - { - LocalPosition = new Vector2(LocalPosition.X - textLength, LocalPosition.Y); - } - else if (rc == 'c') - { - LocalPosition = new Vector2(LocalPosition.X - (textLength/2d), LocalPosition.Y); - } - if (hf == 'h') - { - LocalPosition = new Vector2(LocalPosition.X, LocalPosition.Y - textHeight); - } - } - - //internal void GidsAndCharMap(PdfDictionaries dictionaries) - //{ - // foreach (var tf in TextFormats) - // { - // var usedFonts = tf.UsedFonts; - - // foreach (var glyph in tf.ShapedText.Glyphs) - // { - // if (glyph.FontId >= usedFonts.Count) - // continue; - - // var font = usedFonts[glyph.FontId]; - - // dictionaries.Fonts[font.FullName].Gids.Add(glyph.GlyphId); - // dictionaries.Fonts[font.FullName].fontData = font; - - // if (!dictionaries.Fonts[font.FullName].charactermappings.ContainsKey(glyph.GlyphId)) - // { - // var chars = ExtractCharactersForGlyph(glyph, tf.Text); - // if (!string.IsNullOrEmpty(chars)) - // { - // dictionaries.Fonts[font.FullName].charactermappings[glyph.GlyphId] = chars; - // } - // } - // } - // } - //} - //private string ExtractCharactersForGlyph(ShapedGlyph glyph, string textLine) - //{ - // var chars = new System.Text.StringBuilder(); - // for (int i = 0; i < glyph.CharCount && glyph.ClusterIndex + i < textLine.Length; i++) - // { - // chars.Append(textLine[glyph.ClusterIndex + i]); - // } - // return chars.ToString(); - //} - } -} diff --git a/src/EPPlus.Export.Pdf/PdfLayout/PdfMergedCellLayout.cs b/src/EPPlus.Export.Pdf/PdfLayout/PdfMergedCellLayout.cs deleted file mode 100644 index 1b693c2e11..0000000000 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfMergedCellLayout.cs +++ /dev/null @@ -1,45 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.PdfResources; -using EPPlus.Graphics; -using EPPlus.Graphics.Geometry; -using OfficeOpenXml; -using OfficeOpenXml.Drawing.EMF; -using OfficeOpenXml.Style; -using System.Diagnostics; -using System.Drawing; - -namespace EPPlus.Export.Pdf.PdfLayout -{ - [DebuggerDisplay("Merged: {Name}")] - internal class PdfMergedCellLayout : PdfCellLayout - { - public ExcelAddressBase address; - - public PdfMergedCellLayout(PdfDictionaries dictionaries, ExcelRangeBase cell, PdfCellStyle CellStyle, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) - : base(dictionaries, cell, CellStyle, x, y, width, height, scaleX, scaleY, rotation, parent) - { - this.cell = cell; - } - - //Slightly shift position for cleaner result after gridlines are created. - public new void AdjustForGridLines() - { - if (CellFillData.BackgroundColor.Equals(Color.White) && CellFillData.PatternStyle == ExcelFillStyle.Solid) - { - Size = new Vector2(Size.X - GridLine.Width, Size.Y - GridLine.Width); - LocalPosition = new Vector2(LocalPosition.X + GridLine.HalfWidth, LocalPosition.Y + GridLine.HalfWidth); - } - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfLayout/PdfPageLayout.cs b/src/EPPlus.Export.Pdf/PdfLayout/PdfPageLayout.cs deleted file mode 100644 index 553f2f1273..0000000000 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfPageLayout.cs +++ /dev/null @@ -1,341 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.PdfSettings; -using EPPlus.Graphics; -using OfficeOpenXml; -using OfficeOpenXml.FormulaParsing.Excel.Functions.DateAndTime; -using OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup; -using OfficeOpenXml.Table.PivotTable; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; - -namespace EPPlus.Export.Pdf.PdfLayout -{ - [DebuggerDisplay("Cell: {Name}")] - internal struct PageMap - { - public string Name; - public int row; - public int col; - public PdfCellLayout cell; - public PdfCellContentLayout content; - public PdfCellBorderLayout border; - public enum CellType - { - Normal, - Merged, - } - public CellType Type; - - //Scoops up text spill from adjecent cells - public double LeftTextBucketSpill; - public double RightTextBucketSpill; - } - - internal class VerticalLineRun - { - public int Col; - public int RowStart; - public int RowEnd; - } - - internal class HorizontalLineRun - { - public int Row; - public int ColStart; - public int ColEnd; - } - - [DebuggerDisplay("{Name}")] - internal class PdfPageLayout : Transform - { - internal List GridLines = new List(); - internal List BorderLines = new List(); - public List PrintTitleGridLines = new List(); - - public int FromRow = int.MaxValue; - public int ToRow = 0; - public int FromCol = int.MaxValue; - public int ToCol = 0; - public int RowCount = 0; - public int ColCount = 0; - - public PageMap[,] Map; - - public double ContentTop; - public double ContentBottom; - public double ContentLeft; - public double ContentRight; - public double ContentX; - public double ContentHeight; - - public double HeadingWidth; - public double HeadingHeight; - - public double PrintTitleWidth; - public double PrintTitleHeight; - - public bool isCommentsPage = false; - - public PdfPageLayout(double x, double y, double width, double height) - : base(x, y, width, height) { } - - public void AddCell(Transform cell) - { - if (cell is PdfCellLayout cl) - { - FromRow = Math.Min(FromRow, cl.cell._fromRow); - ToRow = Math.Max(ToRow, cl.cell._toRow); - FromCol = Math.Min(FromCol, cl.cell._fromCol); - ToCol = Math.Max(ToCol, cl.cell._toCol); - } - ChildObjects[0].AddChild(cell); - } - - public void CreateMap() - { - RowCount = ToRow - FromRow + 1; - ColCount = ToCol - FromCol + 1; - Map = new PageMap[RowCount, ColCount]; - } - - internal void GenerateGridLines() - { - HashSet xCoords = new HashSet(); - HashSet yCoords = new HashSet(); - // Collect all unique X and Y coordinates. - var cells = ChildObjects.Where(x => x is PdfCellLayout).ToList(); - foreach (var c in cells) - { - if (c is PdfMergedCellLayout) continue; - yCoords.Add(c.LocalPosition.Y + c.Size.Y); // Top - yCoords.Add(c.LocalPosition.Y); // Bottom - xCoords.Add(c.LocalPosition.X); // Left - xCoords.Add(c.LocalPosition.X + c.Size.X); // Right - } - double minX = cells.Where(c => c is not PdfMergedCellLayout).Min(c => c.LocalPosition.X); - double maxX = cells.Where(c => c is not PdfMergedCellLayout).Max(c => c.LocalPosition.X + c.Size.X); - double minY = cells.Where(c => c is not PdfMergedCellLayout).Min(c => c.LocalPosition.Y); - double maxY = cells.Where(c => c is not PdfMergedCellLayout).Max(c => c.LocalPosition.Y + c.Size.Y); - foreach (var x in xCoords.OrderBy(v => v)) - { - var line = new GridLine(x, minY, x, maxY); - if (x == minX || x == maxX) - BorderLines.Add(line); - else - GridLines.Add(line); - } - foreach (var y in yCoords.OrderBy(v => v)) - { - var line = new GridLine(minX, y, maxX, y); - if (y == minY || y == maxY) - BorderLines.Add(line); - else - GridLines.Add(line); - } - } - - internal void GenerateVerticalGridLines(ExcelWorksheet ws) - { - int addedColumns = PdfWorksheetLayout.AddColumnsForNonWrappedText(ws); - bool resetStart = true; - var startX = 0d; - var startY = 0d; - var endX = 0d; - var endY = 0d; - for (int col = FromCol+1; col <= ToCol; col++) - { - double length = 0; - //string name = ""; - //var f = ws.Cells[FromRow, col]; - //var start = ChildObjects.Where(x => x.Name == f.Address || x.Name == f.Address + "_m" || x.Name == f.Address + "*").ToList(); - for (int row = FromRow; row <= ToRow; row++) - { - var cell = ws.Cells[row, col]; - var name = cell.Address; - var layouts = ChildObjects.Where(x => x.Name == cell.Address || x.Name == cell.Address + "_m" || x.Name == cell.Address + "*").ToList(); - PdfMergedCellLayout m = null; - PdfCellLayout l = null; - foreach (var layout in layouts) - { - if (layout is PdfMergedCellLayout) m = (PdfMergedCellLayout)layout; - else if (layout is PdfCellLayout) l = (PdfCellLayout)layout; - } - if (l != null) - { - if (l.LeftTextSpillLength > 0) - { - length = 0; - if (startX != 0d) - { - var line = new GridLine(startX, startY, endX, endY); - GridLines.Add(line); - resetStart = true; - } - } - else - { - length += l.Size.Y; - endX = l.LocalPosition.X; - endY = l.LocalPosition.Y; - if (resetStart) - { - startX = l.LocalPosition.X; - startY = l.LocalPosition.Y + l.Size.Y; - resetStart = false; - } - } - } - else if (m != null) - { - length += m.Size.Y; - endX = m.LocalPosition.X; - endY = m.LocalPosition.Y; - if (resetStart) - { - startX = m.LocalPosition.X; - startY = m.LocalPosition.Y + m.Size.Y; - resetStart = false; - } - } - else if (l == null && m == null) - { - if (length > 0) - { - length = 0; - if (startX != 0d) - { - var line = new GridLine(startX, startY, endX, endY); - GridLines.Add(line); - resetStart = true; - } - } - } - } - if (startX != 0d) - { - var line2 = new GridLine(startX, startY, endX, endY); - GridLines.Add(line2); - } - resetStart = true; - } - } - - internal void GenerateHorizontalGridLines(ExcelWorksheet ws) - { - int addedColumns = PdfWorksheetLayout.AddColumnsForNonWrappedText(ws); - bool resetStart = true; - var startX = 0d; - var startY = 0d; - var endX = 0d; - var endY = 0d; - for (int row = FromRow+1; row <= ToRow; row++) - { - double length = 0; - //string name = ""; - //var f = ws.Cells[row, FromCol]; - //var start = ChildObjects.Where(x => x.Name == f.Address || x.Name == f.Address + "_m" || x.Name == f.Address + "*").ToList(); - for (int col = FromCol; col <= ToCol; col++) - { - var cell = ws.Cells[row, col]; - var name = cell.Address; - var layouts = ChildObjects.Where(x => x.Name == cell.Address || x.Name == cell.Address + "_m" || x.Name == cell.Address + "*").ToList(); - PdfMergedCellLayout m = null; - PdfCellLayout l = null; - foreach (var layout in layouts) - { - if (layout is PdfMergedCellLayout) m = (PdfMergedCellLayout)layout; - else if (layout is PdfCellLayout) l = (PdfCellLayout)layout; - } - if (l != null) - { - length += l.Size.X; - endX = l.LocalPosition.X + l.Size.X; - endY = l.LocalPosition.Y + l.Size.Y; - if (resetStart) - { - startX = l.LocalPosition.X; - startY = l.LocalPosition.Y + l.Size.Y; - resetStart = false; - } - } - else if (m != null) - { - length += m.Size.X; - endX = m.LocalPosition.X + m.Size.X; - endY = m.LocalPosition.Y + m.Size.Y; - if (resetStart) - { - startX = m.LocalPosition.X; - startY = m.LocalPosition.Y + m.Size.Y; - resetStart = false; - } - } - else if (l == null && m == null) - { - if (length > 0) - { - length = 0; - if (startX != 0d) - { - var line = new GridLine(startX, startY, endX, endY); - GridLines.Add(line); - resetStart = true; - } - } - } - } - if (startX != 0d) - { - var line2 = new GridLine(startX, startY, endX, endY); - GridLines.Add(line2); - } - resetStart = true; - } - } - - internal void GenerateBorderLines() - { - HashSet xCoords = new HashSet(); - HashSet yCoords = new HashSet(); - // Collect all unique X and Y coordinates. - var cells = ChildObjects.Where(x => x is PdfCellLayout).ToList(); - foreach (var c in cells) - { - if (c is PdfMergedCellLayout) continue; - yCoords.Add(c.LocalPosition.Y + c.Size.Y); // Top - yCoords.Add(c.LocalPosition.Y); // Bottom - xCoords.Add(c.LocalPosition.X); // Left - xCoords.Add(c.LocalPosition.X + c.Size.X); // Right - } - double minX = cells.Where(c => c is not PdfMergedCellLayout).Min(c => c.LocalPosition.X); - double maxX = cells.Where(c => c is not PdfMergedCellLayout).Max(c => c.LocalPosition.X + c.Size.X); - double minY = cells.Where(c => c is not PdfMergedCellLayout).Min(c => c.LocalPosition.Y); - double maxY = cells.Where(c => c is not PdfMergedCellLayout).Max(c => c.LocalPosition.Y + c.Size.Y); - foreach (var x in xCoords.OrderBy(v => v)) - { - var line = new GridLine(x, minY, x, maxY); - if (x == minX || x == maxX) - BorderLines.Add(line); - } - foreach (var y in yCoords.OrderBy(v => v)) - { - var line = new GridLine(minX, y, maxX, y); - if (y == minY || y == maxY) - BorderLines.Add(line); - } - } - } -} \ No newline at end of file diff --git a/src/EPPlus.Export.Pdf/PdfLayout/PdfWorksheetLayout.cs b/src/EPPlus.Export.Pdf/PdfLayout/PdfWorksheetLayout.cs deleted file mode 100644 index 2260250787..0000000000 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfWorksheetLayout.cs +++ /dev/null @@ -1,473 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.PdfResources; -using EPPlus.Export.Pdf.PdfSettings; -using EPPlus.Fonts.OpenType; -using EPPlus.Fonts.OpenType.Integration; -using EPPlus.Fonts.OpenType.TextShaping; -using EPPlus.Graphics; -using EPPlus.Graphics.Geometry; -using EPPlus.Graphics.Units; -using OfficeOpenXml; -using OfficeOpenXml.Drawing.Chart; -using OfficeOpenXml.Interfaces.Drawing.Text; -using OfficeOpenXml.Style; -using OfficeOpenXml.Style.Table; -using OfficeOpenXml.Table; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace EPPlus.Export.Pdf.PdfLayout -{ - internal class PdfWorksheetLayout : Transform - { - public static double ZeroCharWidth; - public static double RowHeadingWidth; //This is calculated based on the toRowDimension. row heading text is centered at bottom. font is theme default at default size. - public static double ColumnHeadingHeight; //This is calculated using themefont height and is using theme font at default size. - - public PdfWorksheetLayout(ExcelWorksheet worksheet, PdfPageSettings pageSettings, PdfDictionaries dictionaries) - { - double x = 0d, y = 0d, totalWidth = 0d; - ZeroCharWidth = GetThemeFont0Width(worksheet); - AddHeadings(worksheet, pageSettings, dictionaries); - - List checkedMergedCells = new List(); - int addedColumns = AddColumnsForNonWrappedText(worksheet); - for(int row = 1; row<= worksheet.Dimension._toRow; row++) - { - if(worksheet.Row(row).Hidden) continue; - var height = UnitConversion.ExcelRowHeightToPoints(worksheet.Row(row).Height); - x = 0d; - for (int col = 1; col <= worksheet.Dimension._toCol + addedColumns; col++) - { - if(worksheet.Column(col).Hidden) continue; - var width = UnitConversion.ExcelColumnWidthToPoints(worksheet.Column(col).Width, ZeroCharWidth); - var cell = worksheet.Cells[row, col]; - var cellStyle = new PdfCellStyle(); - GetFillStyles(cell, cellStyle); - GetBorderStyles(cell, cellStyle); - GetFontStyles(cell, cellStyle); - PdfCellBorderLayout border = HandleEdgeBorders(cell, cellStyle, cell.Address, x, y, width, height); - if (cell.Merge) - { - HandleMergedCell(worksheet, pageSettings, dictionaries, cell, cellStyle, checkedMergedCells, x, y); - } - else - { - HandleCell(pageSettings, dictionaries, cell, x, y, width, height, cellStyle); - } - if (border != null) border.InitEdgeBorders(cell); - x += width; - totalWidth = System.Math.Max( x, totalWidth); - if (pageSettings.CommentsAndNotes != CommentsAndNotes.None) - { - if (cell.Comment != null && cell.ThreadedComment == null) - { - dictionaries.CommentsAndNotes.Add(cell.Address, new PdfCommentsAndNotes(cell.Comment)); - } - if (cell.ThreadedComment != null) - { - dictionaries.CommentsAndNotes.Add(cell.Address, new PdfCommentsAndNotes(cell.ThreadedComment)); - PdfCommentsAndNotes.HasThreadedComment = true; - } - } - } - y -= height; - } - HandleDrawings(worksheet); - Size = new Vector2(totalWidth, Math.Abs(y)); - } - - /// - /// Check if we need to add additional columns to accomodate text that is not wrapped and overlaps other cell.s - /// - /// The worksheet to check. - /// The number of columns to add. - public static int AddColumnsForNonWrappedText(ExcelWorksheet ws) - { - double columnWidth = UnitConversion.ExcelColumnWidthToPoints(ws.Column(ws.Dimension._toCol).Width, ZeroCharWidth); - int columnsToAdd = 0; - - MeasurementFont font = new MeasurementFont(); - var shaper = (TextShaper)OpenTypeFonts.GetShaperForFont(font); - OpenTypeFontTextMeasurer fontMeasurerTrueType = new OpenTypeFontTextMeasurer(shaper); - - double textLength = 0; - for (int row = 1; row <= ws.Dimension._toRow; row++) - { - var cell = ws.Cells[row, ws.Dimension._toCol]; - if ((!string.IsNullOrEmpty(cell.Text) || cell.RichText.Count > 0) && !cell.Style.WrapText && !cell.Merge) - { - if (cell.IsRichText) - { - foreach (var rt in cell.RichText) - { - font.FontFamily = rt.FontName; - font.Size = (float)rt.Size; - font.Style = ((cell.Style.Font.Bold ? MeasurementFontStyles.Bold : 0) | - (cell.Style.Font.Italic ? MeasurementFontStyles.Italic : 0) | - (cell.Style.Font.Strike ? MeasurementFontStyles.Strikeout : 0) | - (cell.Style.Font.UnderLine ? MeasurementFontStyles.Underline : 0)) - switch - { - 0 => MeasurementFontStyles.Regular, - var s => s - }; - var result = fontMeasurerTrueType.MeasureText(rt.Text, font); - textLength += result.Width; - } - } - else - { - font.FontFamily = cell.Style.Font.Name; - font.Size = (float)cell.Style.Font.Size; - font.Style = ((cell.Style.Font.Bold ? MeasurementFontStyles.Bold : 0) | - (cell.Style.Font.Italic ? MeasurementFontStyles.Italic : 0) | - (cell.Style.Font.Strike ? MeasurementFontStyles.Strikeout : 0) | - (cell.Style.Font.UnderLine ? MeasurementFontStyles.Underline : 0)) - switch - { - 0 => MeasurementFontStyles.Regular, - var s => s - }; - var result = fontMeasurerTrueType.MeasureText(cell.Text, font); - textLength += result.Width; - } - //loop next col width until text is included - //increase columns to add - while (textLength > columnWidth) - { - columnsToAdd++; - columnWidth += UnitConversion.ExcelColumnWidthToPoints(ws.Column(ws.Dimension._toCol + columnsToAdd).Width, ZeroCharWidth); - } - } - } - return columnsToAdd; - } - - //Get Border styles from cell, tables and conditional formatting. TODO: Add support for Conditional formatting. - private void GetBorderStyles(ExcelRangeBase cell, PdfCellStyle cellStyle) - { - - /* Kika på varje del av border top bottom left right - * om cell har top använd den - * om cell inte har border, gå igenom prio ordning på tabell borders och använd WholeTable om null. - * om cellein i tabellen är i mitten av tabellen använd horizontal och vertical border istället. - * I fallet top så ska om vi är i header row eller om cell fromrow är samma som table fromrow så ska top border vara top border. annar är det horizontal som gäller - * Glöm ej vertical border om fromcol är samma som tabell fromcol. - */ - if (cell != null) - { - cellStyle.xfTop = cell.Style.Border.Top; - cellStyle.xfBottom = cell.Style.Border.Bottom; - cellStyle.xfLeft = cell.Style.Border.Left; - cellStyle.xfRight = cell.Style.Border.Right; - cellStyle.Diagonal= cell.Style.Border.Diagonal; - cellStyle.DiagonalUp = cell.Style.Border.DiagonalUp; - cellStyle.DiagonalDown = cell.Style.Border.DiagonalDown; - var tables = cell.Worksheet.Tables.GetIntersectingRanges(cell); - if (tables.Count > 0) - { - var table = tables[0].Value; - ExcelTableNamedStyle tableStyle; - if (table.TableStyle == TableStyles.Custom) - { - tableStyle = cell.Worksheet.Workbook.Styles.TableStyles[table.StyleName].As.TableStyle; - } - else - { - var tmpNode = table.WorkSheet.Workbook.StylesXml.CreateElement("c:tableStyle"); - tableStyle = new ExcelTableNamedStyle(cell.Worksheet.Workbook.Styles.NameSpaceManager, tmpNode, cell.Worksheet.Workbook.Styles); - tableStyle.SetFromTemplate((TableStyles)table.TableStyle); - } - cellStyle.dxfTop = PdfCellBorderLayout.GetTopBorderItem(cell, cellStyle.xfTop, table, tableStyle); - cellStyle.dxfBottom = PdfCellBorderLayout.GetBottomBorderItem(cell, cellStyle.xfBottom, table, tableStyle); - cellStyle.dxfLeft = PdfCellBorderLayout.GetLeftBorderItem(cell, cellStyle.xfLeft, table, tableStyle); - cellStyle.dxfRight = PdfCellBorderLayout.GetRightBorderItem(cell, cellStyle.xfRight, table, tableStyle); - } - } - } - - //Get Fill style from cell, tables and conditional formatting. TODO: Add support for Conditional formatting. - public void GetFillStyles( ExcelRangeBase cell, PdfCellStyle cellStyle) - { - if (cell.Style.Fill.IsEmpty()) - { - //Conditional Formating - - //Table - var tables = cell.Worksheet.Tables.GetIntersectingRanges(cell); - if (tables.Count > 0) - { - var table = tables[0].Value; - var range = table.Range; - - int tableRow = 0; - int tableCol = 0; - ExcelTableNamedStyle tableStyle; - if (table.TableStyle == TableStyles.Custom) - { - tableStyle = cell.Worksheet.Workbook.Styles.TableStyles[table.StyleName].As.TableStyle; - } - else - { - var tmpNode = table.WorkSheet.Workbook.StylesXml.CreateElement("c:tableStyle"); - tableStyle = new ExcelTableNamedStyle(cell.Worksheet.Workbook.Styles.NameSpaceManager, tmpNode, cell.Worksheet.Workbook.Styles); - tableStyle.SetFromTemplate((TableStyles)table.TableStyle); - } - tableRow = cell._fromRow - range._fromRow; - tableCol = cell._fromCol - range._fromCol; - if (table.ShowHeader && tableRow == 0) - { - cellStyle.dxfFill = tableStyle.HeaderRow.Style.Fill; - } - else if (table.ShowTotal && range._toRow == cell._fromRow) - { - cellStyle.dxfFill = tableStyle.TotalRow.Style.Fill; - } - else if (table.ShowFirstColumn && tableCol == 0) - { - cellStyle.dxfFill = tableStyle.FirstColumn.Style.Fill; - } - else if (table.ShowLastColumn && range._toCol == cell._fromCol) - { - cellStyle.dxfFill = tableStyle.LastColumn.Style.Fill; - } - else if (table.ShowRowStripes) - { - cellStyle.dxfFill = (tableRow & 1) == 0 ? tableStyle.SecondRowStripe.Style.Fill : tableStyle.FirstRowStripe.Style.Fill; - } - else if (table.ShowColumnStripes) - { - cellStyle.dxfFill = (tableCol & 1) != 0 ? tableStyle.SecondColumnStripe.Style.Fill : tableStyle.FirstColumnStripe.Style.Fill; - } - else - { - cellStyle.dxfFill = tableStyle.WholeTable.Style.Fill; - } - } - } - cellStyle.xfFill = cell.Style.Fill; - } - - //Get Font style from cell, tables and conditional formatting. TODO: Add support for Conditional formatting. - public void GetFontStyles(ExcelRangeBase cell, PdfCellStyle cellStyle) - { - var tables = cell.Worksheet.Tables.GetIntersectingRanges(cell); - if (tables.Count > 0) - { - var table = tables[0].Value; - var range = table.Range; - ExcelTableNamedStyle tableStyle; - if (table.TableStyle == TableStyles.Custom) - { - tableStyle = cell.Worksheet.Workbook.Styles.TableStyles[table.StyleName].As.TableStyle; - } - else - { - var tmpNode = table.WorkSheet.Workbook.StylesXml.CreateElement("c:tableStyle"); - tableStyle = new ExcelTableNamedStyle(cell.Worksheet.Workbook.Styles.NameSpaceManager, tmpNode, cell.Worksheet.Workbook.Styles); - tableStyle.SetFromTemplate((TableStyles)table.TableStyle); - } - int tableRow = cell._fromRow - range._fromRow; - int tableCol = cell._fromCol - range._fromCol; - var font = tableStyle.WholeTable.Style.Font; - if (table.ShowHeader && tableRow == 0) - { - if (tableStyle.HeaderRow.Style.Font.HasValue) - { - font = tableStyle.HeaderRow.Style.Font; - } - if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstHeaderCell.Style.Font.HasValue) - { - font = tableStyle.FirstHeaderCell.Style.Font; - } - if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastHeaderCell.Style.Font.HasValue) - { - font = tableStyle.LastHeaderCell.Style.Font; - } - } - else if (table.ShowTotal && cell._fromRow == range._toRow) - { - if (tableStyle.TotalRow.Style.Font.HasValue) - { - font = tableStyle.TotalRow.Style.Font; - } - if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstTotalCell.Style.Font.HasValue) - { - font = tableStyle.FirstTotalCell.Style.Font; - } - if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastTotalCell.Style.Font.HasValue) - { - font = tableStyle.LastTotalCell.Style.Font; - } - } - else - { - if (table.ShowColumnStripes && (tableCol & 1) == 0) - { - font = tableStyle.FirstColumnStripe.Style.Font; - } - if (table.ShowColumnStripes && tableStyle.SecondColumnStripe.Style.Border.Top.HasValue && (tableCol & 1) != 0) - { - font = tableStyle.SecondColumnStripe.Style.Font; - } - if (table.ShowRowStripes && tableStyle.FirstRowStripe.Style.Font.HasValue && (tableRow & 1) != 0) - { - font = tableStyle.FirstRowStripe.Style.Font; - } - if (table.ShowRowStripes && tableStyle.SecondRowStripe.Style.Font.HasValue && (tableRow & 1) == 0) - { - font = tableStyle.SecondRowStripe.Style.Font; - } - if (table.ShowLastColumn && tableStyle.LastColumn.Style.Font.HasValue && cell._fromCol == range._toCol) - { - font = tableStyle.LastColumn.Style.Font; - } - if (table.ShowFirstColumn && tableStyle.FirstColumn.Style.Font.HasValue && tableCol == range._toCol) - { - font = tableStyle.FirstColumn.Style.Font; - } - } - cellStyle.dxfFont = font; - } - } - - //Create cell. - private void HandleCell(PdfPageSettings pageSettings, PdfDictionaries dictionaries, ExcelRangeBase cell, double x, double y, double width, double height, PdfCellStyle CellStyle) - { - bool delete = cell.IsEmpty() && !cell.Worksheet.ExistsStyleInner(cell._fromRow, cell._toCol); - var cl0 = new PdfCellLayout(dictionaries, cell, CellStyle, x, y, width, height, 1, 1, 0, this); - cl0.Name = cell.Address; - cl0.Z = 1; - cl0.delete = delete; - AddCellContent(pageSettings, dictionaries, cell, CellStyle, cell.Address, x, y - height, width, height, 2); - var border = HandleDiagonalBorders(cell, CellStyle, cell.Address, x, y, width, height); - if (border != null) - { - border.InitDiagonalBorders(cell, width, height); - } - } - - //Create merged cell. - private void HandleMergedCell(ExcelWorksheet worksheet, PdfPageSettings pageSettings, PdfDictionaries dictionaries, ExcelRangeBase cell, PdfCellStyle CellStyle, List checkedMergedCells, double x, double y) - { - string mergeAddress = worksheet.MergedCells[cell.Start.Row, cell.Start.Column]; - if (!checkedMergedCells.Contains(mergeAddress)) - { - double height = 0, width = 0; - ExcelAddressBase address = new ExcelAddressBase(mergeAddress); - for (int k = address._fromRow; k <= address._toRow; k++) - { - height += UnitConversion.ExcelRowHeightToPoints(worksheet.Row(k).Height); - } - for (int l = address._fromCol; l <= address._toCol; l++) - { - width += UnitConversion.ExcelColumnWidthToPoints(worksheet.Column(l).Width, ZeroCharWidth); - } - var mergedCell = new PdfMergedCellLayout(dictionaries, worksheet.Cells[address._fromRow, address._fromCol], CellStyle, x, y, width, height); - mergedCell.Name = address.Address;//cell.Address + "_m"; - mergedCell.Z = 5; - mergedCell.address = address; - AddChild(mergedCell); - AddCellContent(pageSettings, dictionaries, cell, CellStyle, address.Address, x, y-height, width, height, 6); - checkedMergedCells.Add(mergeAddress); - var border = HandleDiagonalBorders(cell, null, address.Address, x, y, width, height); - if (border != null) - { - border.InitDiagonalBorders(cell, width, height); - border.range = address.Address; - } - } - } - - //Create content. - private void AddCellContent(PdfPageSettings pageSettings, PdfDictionaries dictionaries, ExcelRangeBase cell, PdfCellStyle CellStyle, string name, double x, double y, double width, double height, int zOrder) - { - if (!string.IsNullOrEmpty(cell.Text)) - { - var cellContent = new PdfCellContentLayout(cell, CellStyle, pageSettings, x, y, width, height, 1, 1, 0, this, dictionaries); - cellContent.Name = name;// + "_c"; - cellContent.Z = zOrder; - } - } - - //Create Edge borders. - private PdfCellBorderLayout HandleEdgeBorders(ExcelRangeBase cell, PdfCellStyle tableStyle, string name, double x, double y, double width, double height) - { - bool edges = new[] { cell.Style.Border.Top.Style, cell.Style.Border.Bottom.Style, cell.Style.Border.Left.Style, cell.Style.Border.Right.Style }.All(s => s == ExcelBorderStyle.None); - bool edges2 = new[] { tableStyle.dxfTop, tableStyle.dxfBottom, tableStyle.dxfLeft, tableStyle.dxfRight }.Any(s => s != null && s.HasValue); - if (!edges || edges2) - { - var clb0 = new PdfCellBorderLayout(cell, tableStyle, x, y, width, height, 1, 1, 0, this); - clb0.Name = name;// + "_b"; - clb0.Z = 7; - return clb0; - } - return null; - } - - //Create Diagonal borders. - private PdfCellBorderLayout HandleDiagonalBorders(ExcelRangeBase cell, PdfCellStyle TableStyle, string name, double x, double y, double width, double height) - { - bool diagonals = new[] { cell.Style.Border.Diagonal.Style }.All(s => s == ExcelBorderStyle.None); - if (!diagonals) - { - var clb0 = new PdfCellBorderLayout(cell, TableStyle, x, y, width, height, 1, 1, 0, this); - clb0.Name = name;// + "_b"; - clb0.Z = 7; - return clb0; - } - return null; - } - - //Create drawings. - private void HandleDrawings(ExcelWorksheet worksheet) - { - foreach (var drawing in worksheet.Drawings) //NOT IMPLEMENTED - { - var drawLayout = AddChild(new PdfDrawingLayout(drawing, drawing.Position.X, drawing.Position.Y, drawing._width, drawing._height)); - drawLayout.Z = 10; - drawLayout.Name = "Drawing " + drawing.Name; - } - } - - //Get the width of the character 0 from the current themes default font style. It's used to calculate width of cells. - private double GetThemeFont0Width(ExcelWorksheet ws) - { - MeasurementFont font = new MeasurementFont(); - var ns = ws.Workbook.Styles.GetNormalStyle(); - font.FontFamily = ns.Style.Font.Name; - font.Size = ns.Style.Font.Size; - font.Style = MeasurementFontStyles.Regular; - - var shaper = OpenTypeFonts.GetShaperForFont(font); - OpenTypeFontTextMeasurer fontMeasurerTrueType = new OpenTypeFontTextMeasurer(shaper); - var result = fontMeasurerTrueType.MeasureText("0", font); - return result.Width; - } - - private void AddHeadings(ExcelWorksheet ws, PdfPageSettings pageSettings, PdfDictionaries dictionaries) - { - if (!pageSettings.ShowHeadings) - { - RowHeadingWidth = 0d; - ColumnHeadingHeight = 0d; - return; - } - ColumnHeadingHeight = ws.DefaultRowHeight; //Should take font height into consideration here - RowHeadingWidth = UnitConversion.ExcelColumnWidthToPoints(4, ZeroCharWidth); //4 is an estimation. In reality we should measure width based on font width/height at the max row - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfResources MOVED/PdfDictionaries.cs b/src/EPPlus.Export.Pdf/PdfResources MOVED/PdfDictionaries.cs deleted file mode 100644 index 67241ef695..0000000000 --- a/src/EPPlus.Export.Pdf/PdfResources MOVED/PdfDictionaries.cs +++ /dev/null @@ -1,57 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.PdfLayout; -using EPPlus.Export.Pdf.PdfSettings; -using EPPlus.Fonts.OpenType; -using OfficeOpenXml.Interfaces.Fonts; -using System.Collections.Generic; -using System.Linq; - -namespace EPPlus.Export.Pdf.PdfResources -{ - public class PdfDictionaries - { - internal readonly Dictionary Fonts = new Dictionary(); - internal readonly Dictionary Patterns = new Dictionary(); - internal readonly Dictionary Shadings = new Dictionary(); - internal Dictionary ShapedProviders = new Dictionary(); - - public void AddFont(PdfPageSettings pageSettings, string FontName, FontSubFamily SubFamily, string Text) - { - var fullFont = FontName + " " + SubFamily.ToString(); - if (!Fonts.ContainsKey(fullFont)) - { - int label = 1; - if (Fonts.Count > 0) - { - label = Fonts.Last().Value.labelNumber + 1; - } - Fonts.Add(fullFont, new PdfFontResource(FontName, SubFamily, label, pageSettings)); - } - var manger = Fonts[fullFont].fontSubsetManager; - manger.AddText(Text); - } - - internal PdfFontResource GetFont(string FontName) - { - if (!Fonts.ContainsKey(FontName)) - { - return null; - } - return Fonts[FontName]; - } - - //this should move and be on worksheet level. - internal readonly Dictionary CommentsAndNotes = new Dictionary(); - } -} diff --git a/src/EPPlus.Export.Pdf/PdfResources MOVED/PdfFontResource.cs b/src/EPPlus.Export.Pdf/PdfResources MOVED/PdfFontResource.cs deleted file mode 100644 index 4a7d72e65d..0000000000 --- a/src/EPPlus.Export.Pdf/PdfResources MOVED/PdfFontResource.cs +++ /dev/null @@ -1,265 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.PdfLayout; -using EPPlus.Export.Pdf.PdfObjects.PdfFonts; -using EPPlus.Export.Pdf.PdfSettings; -using EPPlus.Fonts.OpenType; -using EPPlus.Fonts.OpenType.Tables.Os2; -using EPPlus.Fonts.OpenType.TextShaping; -using EPPlus.Graphics; -using System; -using System.Collections.Generic; -using System.Linq; -using OfficeOpenXml.Interfaces.Fonts; - -namespace EPPlus.Export.Pdf.PdfResources -{ - internal class PdfFontResource : PdfResource - { - internal string fontName; - internal int fontObjectNumber = -1; - internal int CIDFontObjectNumber = -1; - internal int type0FontObjectNumber = -1; - internal int unicodeCMapFontObjectNumber = -1; - internal int embedFontStreamObjectNumber = -1; - internal int fontDescObjectNumber = -1; - internal int fontWidthObjectNumber = -1; - internal int cidSetObjectNumber = -1; - internal OpenTypeFont fontData; - private OpenTypeFontEngine _fontEngine; - private int firstChar = 32; - private int lastChar = 255; - private CIDSystemInfo cidSystemInfo = null; - - internal string type0Encoding = "Identity-H"; - - internal TextShaper Shaper = null; - internal ShapedText ShapedText; - - internal HashSet Subset = new HashSet(); - internal HashSet Gids = new HashSet(); - internal Dictionary charactermappings = new Dictionary(); - - internal FontSubsetManager fontSubsetManager; - - public PdfFontResource(string fontName, FontSubFamily subFamily, int labelNumber, PdfPageSettings pageSettings) - : base("F", labelNumber) - { - this.fontName = fontName; - _fontEngine = pageSettings.FontEngine; - fontData = _fontEngine.LoadFont(fontName, subFamily); - fontSubsetManager = new FontSubsetManager(pageSettings.FontEngine, fontData); - } - - internal static OpenTypeFont GetFontData(PdfPageSettings pageSettings, string fontName, FontSubFamily subFamily) - { - return pageSettings.FontEngine.LoadFont(fontName,subFamily); - } - - //Get font data from fontResources. If font does not exsist, add it to fontResources. - //internal static OpenTypeFont GetFontResourceData(Dictionary fontResources, PdfPageSettings pageSettings, PdfTextFormat FontData) - //{ - // if (!fontResources.ContainsKey(FontData.FullFontName)) - // { - // int label = 1; - // if (fontResources.Count > 0) - // { - // label = fontResources.Last().Value.labelNumber + 1; - // } - // PdfFontResource fr = new PdfFontResource(FontData.FontName, FontData.SubFamily, label, pageSettings); - // fontResources.Add(FontData.FullFontName, fr); - // fontResources.Last().Value.fontData = GetFontData(pageSettings, FontData.FontName, FontData.SubFamily); - // } - // return fontResources[FontData.FullFontName].fontData; - //} - - //Get the Font Descriptor object to write in PDF. - internal PdfFontDescriptor GetFontDescriptorObject(int objectNumber, int version = 0) - { - int flag = 0; - if (fontData.PostTable.isFixedPitch != 0) - flag |= 1; - if (fontData.GetEnglishFontFamilyName().ToLower().Contains("serif")) - flag |= 1 << 1; - bool isSymbolic = false; - foreach (var sub in fontData.CmapTable.EncodingRecords) - { - if (sub.PlatformId == Fonts.OpenType.Tables.Cmap.Platforms.Windows) - { - if (sub.EncodingId == 0) - { - isSymbolic = true; - break; - } - if (sub.EncodingId == 1 || sub.EncodingId == 10) - { - isSymbolic = false; - break; - } - } - if (sub.PlatformId == Fonts.OpenType.Tables.Cmap.Platforms.Macintosh || sub.PlatformId == Fonts.OpenType.Tables.Cmap.Platforms.Unicode) - { - isSymbolic = true; - break; - } - } - if (isSymbolic) - flag |= 1 << 2; // Symbolic - else - flag |= 1 << 5; // Nonsymbolic - if (fontData.GetEnglishFontFamilyName().ToLower().Contains("script") || fontData.GetEnglishFontFamilyName().ToLower().Contains("cursive")) - flag |= 1 << 3; - if (fontData.PostTable.italicAngle.RawValue != 0 || (fontData.Os2Table.fsSelection & Os2Table.FsSelectionFlags.Italic) != 0) - flag |= 1 << 6; - if (((ushort)fontData.Os2Table.fsSelection & 0x100) != 0) - flag |= 1 << 16; - if (((ushort)fontData.Os2Table.fsSelection & 0x200) != 0) - flag |= 1 << 17; - if (((ushort)fontData.Os2Table.fsSelection & 0x400) != 0) - flag |= 1 << 18; - var fontBBox = new Rect(); - fontBBox.X = fontData.HeadTable.Xmin; - fontBBox.Y = fontData.HeadTable.Ymin; - fontBBox.Width = fontData.HeadTable.Xmax; - fontBBox.Height = fontData.HeadTable.Ymax; - fontDescObjectNumber = objectNumber; - return new PdfFontDescriptor - ( - objectNumber, - fontData.FullName, - flag, - fontBBox, - Convert.ToDouble(fontData.PostTable.italicAngle.FloatValue), - fontData.Os2Table.sTypoAscender, - fontData.Os2Table.sTypoDescender, - 0, - fontData.Os2Table.sCapHeight, - embedFontStreamObjectNumber, - cidSetObjectNumber, - version - ); - } - - //Get the Widths object to write in PDF. - internal PdfFontWidths GetWidthsObject(int objectNumber, int version = 0) - { - List widths = new List(); - int fallbackWidth = fontData.Os2Table.xAvgCharWidth; - var glyphMappings = fontData.CmapTable.GetPreferredSubtable().GetGlyphMappings(); - for (int c = firstChar; c <= lastChar; c++) - { - var gi = glyphMappings.GetGlyphIndex((char)c); - if (gi == 0 && c != 0) - { - int normalizedWidth = (int)System.Math.Round(fallbackWidth / (double)fontData.HeadTable.UnitsPerEm * 1000); - widths.Add(normalizedWidth); - } - else - { - var hhMetric = fontData.HmtxTable.hMetrics[gi ?? 0]; - var advanceWidth = Convert.ToInt16(hhMetric.advanceWidth); - int normalizedWidth = (int)System.Math.Round(advanceWidth / (double)fontData.HeadTable.UnitsPerEm * 1000); - widths.Add(normalizedWidth); - } - } - fontWidthObjectNumber = objectNumber; - return new PdfFontWidths(objectNumber, widths, version); - } - - //Get the Font Object to write in PDF. - internal PdfFont GetFontObject(int objectNumber, int version = 0) - { - fontObjectNumber = objectNumber; - return new PdfFont(objectNumber, fontData.FullName, PdfFontSubType.Type1, firstChar, lastChar, fontWidthObjectNumber, fontDescObjectNumber, PdfFontEncoding.WinAnsiEncoding); - } - - internal PdfCIDFont GetCIDFontObject(int objectNumber, int version = 0) - { - CIDFontObjectNumber = objectNumber; - if (cidSystemInfo == null) - { - cidSystemInfo = new CIDSystemInfo(); - } - cidSystemInfo.Registry = "Adobe"; - cidSystemInfo.Ordering = "Identity"; - cidSystemInfo.Supplement = 0; - - return new PdfCIDFont(objectNumber, fontData, Gids, CIDFontSubtype.CIDFontType2, cidSystemInfo, "Identity", fontDescObjectNumber); - } - - internal PdfType0FontDict GetType0FontDictObject(int objectNumber, int version = 0) - { - type0FontObjectNumber = objectNumber; - return new PdfType0FontDict(objectNumber, fontData.FullName, type0Encoding, CIDFontObjectNumber, unicodeCMapFontObjectNumber); - } - - internal PdfToUnicodeCMap GetUnicodeCmapObject(int objectNumber, int version = 0) - { - unicodeCMapFontObjectNumber = objectNumber; - return new PdfToUnicodeCMap(objectNumber, charactermappings); - } - private string ExtractCharactersForGlyph(ShapedGlyph glyph, string textLine) - { - var chars = new System.Text.StringBuilder(); - for (int i = 0; i < glyph.CharCount && glyph.ClusterIndex + i < textLine.Length; i++) - { - chars.Append(textLine[glyph.ClusterIndex + i]); - } - return chars.ToString(); - } - - internal PdfFontStream GetEmbeddedFontStreamObject(int objectNumber, int version = 0) - { - embedFontStreamObjectNumber = objectNumber; - return new PdfFontStream(objectNumber, fontData, version); - } - - internal PdfCidSet GetCidSet(int objectNumber, int version = 0) - { - if (!fontData.IsSubset || Gids.Count == 0) - return null; - - int maxGid = Gids.Max(); - int numBytes = (maxGid / 8) + 1; - var cidSet = new byte[numBytes]; - - foreach (var gid in Gids) - { - int byteIndex = gid / 8; - int bitIndex = 7 - (gid % 8); - cidSet[byteIndex] |= (byte)(1 << bitIndex); - } - cidSetObjectNumber = objectNumber; - return new PdfCidSet(objectNumber, cidSet, version); - } - - internal void CreateGidsAndCharMaps() - { - if (ShapedText != null) - { - foreach (var glyph in ShapedText.Glyphs) - { - Gids.Add(glyph.GlyphId); - if (!charactermappings.ContainsKey(glyph.GlyphId)) - { - var chars = ExtractCharactersForGlyph(glyph, ShapedText.OriginalText); - if (!string.IsNullOrEmpty(chars)) - { - charactermappings.Add(glyph.GlyphId, chars); - } - } - } - } - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfResources MOVED/PdfShadingResource.cs b/src/EPPlus.Export.Pdf/PdfResources MOVED/PdfShadingResource.cs deleted file mode 100644 index 0070438776..0000000000 --- a/src/EPPlus.Export.Pdf/PdfResources MOVED/PdfShadingResource.cs +++ /dev/null @@ -1,61 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.PdfLayout; -using EPPlus.Export.Pdf.PdfObjects.PdfPatterns; -using EPPlus.Export.Pdf.PdfObjects.PdfShadings; -using OfficeOpenXml.Style; - -namespace EPPlus.Export.Pdf.PdfResources -{ - internal class PdfShadingResource : PdfResource - { - internal int objectNumber; - internal PdfCellFillData CellFillData; - - public PdfShadingResource(int labelNumber, PdfCellFillData cellFillData) - : base("Sh", labelNumber) - { - CellFillData = cellFillData; - } - - public PdfShading GetShadingObject(int objectNumber, int version = 0) - { - this.objectNumber = objectNumber; - if (CellFillData.GradientFillData != null) - { - if (CellFillData.GradientFillData.GradientType == ExcelFillGradientType.Linear) - { - var pas = new PdfAxialShading(objectNumber, CellFillData.GradientFillData, version); - pas.Coords = CellFillData.GradientFillData.coords; - return pas; - } - else if (CellFillData.GradientFillData.GradientType == ExcelFillGradientType.Path) - { - var prs = new PdfRadialShading(objectNumber, CellFillData.GradientFillData, version); - prs.Coords = CellFillData.GradientFillData.coords; - return prs; - } - } - return null; - } - - public PdfShadingPattern GetShadingPatternObject(int patternObjectNumber, int shadingObjectNumber, int version = 0) - { - objectNumber = shadingObjectNumber; - var shadingPattern = new PdfShadingPattern(patternObjectNumber, shadingObjectNumber); - shadingPattern.Matrix = CellFillData.GradientFillData.matrix; - return shadingPattern; - } - - } -} diff --git a/src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfContentBounds.cs b/src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfContentBounds.cs deleted file mode 100644 index 8472960241..0000000000 --- a/src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfContentBounds.cs +++ /dev/null @@ -1,97 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.PdfSettings.PdfPageSizes; -using EPPlus.Graphics; - - -namespace EPPlus.Export.Pdf.PdfSettings -{ - internal class PdfContentBounds : Rect - { - public double HeaderY; - public double FooterY; - public double CenterHeaderX; - public double RightHeaderX; - public double CenterFooterX; - public double RightFooterX; - - private double _top; - private double _bottom; - private double _left; - private double _right; - private double _height; - private double _width; - - public override double Top - { - get { return _top; } - set { _top = value; } - } - - public override double Bottom - { - get { return _bottom; } - set { _bottom = value; } - } - - public override double Left - { - get { return _left; } - set { _left = value; } - } - - public override double Right - { - get { return _right; } - set { _right = value; } - } - - public override double Width - { - get { return _width; } - set { _width = value; } - } - - public override double Height - { - get { return _height; } - set { _height = value; } - } - - public PdfContentBounds(PdfMargins margins, PdfPageSize pageSize) - { - CalculateBounds(margins, pageSize); - } - - internal void CalculateBounds(PdfMargins margins, PdfPageSize pageSize) - { - //Content bounds rectangle - Width = pageSize.WidthPu - margins.LeftPu - margins.RightPu; - X = margins.LeftPu; - Left = X; - Right = X + Width; - Y = margins.BottomPu; - Top = pageSize.HeightPu - margins.TopPu; - Bottom = Y; - Height = Top - Bottom; - //Header Footer - var hx = Width / 3d; - FooterY = margins.FooterPu; - CenterFooterX = Left + hx; - RightFooterX = Left + hx * 2; - HeaderY = pageSize.HeightPu - margins.HeaderPu; - CenterHeaderX = Left + hx; - RightHeaderX = Left + hx * 2; - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfPageData/PdfExcelPageDataLookup.cs b/src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfPageData/PdfExcelPageDataLookup.cs deleted file mode 100644 index e4428a25cb..0000000000 --- a/src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfPageData/PdfExcelPageDataLookup.cs +++ /dev/null @@ -1,59 +0,0 @@ -/************************************************************************************************* - 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 System.Collections.Generic; - -namespace EPPlus.Export.Pdf.PdfSettings.PdfPageData -{ - internal class PdfExcelPageDataLookup - { - //Not to be used but kept for reference, This table has data on how many rows and columns each font/theme gives in an A4 page. - internal static Dictionary PdfExcelA4PageData = new Dictionary() - { - { "Aptos", [48, 9] }, - { "Aptos Display", [48, 9] }, - { "Aptos Narrow", [48, 9] }, - { "Arial", [53, 8] }, - { "Arial Black", [42, 7] }, - { "Arial Narrow", [53, 10] }, - { "Bookman Old Style", [53, 8] }, - { "Calibri", [50, 9] }, - { "Calibri Light", [50, 9] }, - { "Calisto MT", [53, 9] }, - { "Cambria", [53, 8] }, - { "Candara", [50, 8] }, - { "Century Gothic", [51, 8] }, - { "Century Schoolbook", [51, 8] }, - { "Consolas", [52, 8] }, - { "Constantia", [50, 9] }, - { "Corbel", [50, 9] }, - { "Courier New", [57, 8] }, - { "Franklin Gothic Book", [49, 8] }, - { "Franklin Gothic Medium", [49, 8] }, - { "Garamond", [55, 10] }, - { "Georgia", [54, 8] }, - { "Gill Sans MT", [44, 9] }, - { "Impact", [51, 9] }, - { "Liberation Serif", [53, 9] }, - { "MS Gothic", [59, 9] }, - { "Palatino Linotype", [44, 9] }, - { "Rockwell", [54, 9] }, - { "Rockwell Condensed", [52, 12] }, - { "SegoeUI", [44, 9] }, - { "Tahoma", [51, 9] }, - { "Times New Roman", [52, 9] }, - { "Trebuchet MS", [49, 9] }, - { "Tw Cen MT", [54, 8] }, - { "Verdana", [50, 8] }, - }; - } -} \ No newline at end of file diff --git a/src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfPageSettings.cs b/src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfPageSettings.cs deleted file mode 100644 index 1b63f4b5ab..0000000000 --- a/src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfPageSettings.cs +++ /dev/null @@ -1,308 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.PdfSettings.PdfPageSizes; -using EPPlus.Fonts.OpenType; -using OfficeOpenXml; -using System.Collections.Generic; -using System.Linq; - -namespace EPPlus.Export.Pdf.PdfSettings -{ - /// - /// Settings object for exporting to PDF. - /// - public class PdfPageSettings - { - private OpenTypeFontEngine _fontEngine; - public OpenTypeFontEngine FontEngine - { - get - { - if(_fontEngine == null) - { - _fontEngine = new OpenTypeFontEngine(x => - { - if(FontDirectories != null && FontDirectories.Any()) - { - foreach(var dir in FontDirectories) - { - if (!System.IO.Directory.Exists(dir)) - { - throw new System.IO.DirectoryNotFoundException($"Font directory not found: {dir}"); - } - x.FontDirectories.Add(dir); - } - x.SearchSystemDirectories = SearchSystemDirectories; - - } - - }); - } - return _fontEngine; - } - } - /// - /// Add additional folders to search for fonts. - /// - public List FontDirectories = new List(); - - /// - /// If true, epplus will look for fonts in the system directories for installed fonts. c:\windows\fonts - /// - public bool SearchSystemDirectories = true; - - /// - /// If true, subsetted fonts will be embedded into the PDF document. - /// - public bool EmbeddFonts = true; - - /// - /// The order in how to create pages. - /// - public PageOrders PageOrders = PageOrders.DownThenOver; - - /// - /// Set to true to center content on page vertically. - /// - public bool CenterOnPageVertically; - - /// - /// Set to true to center content on page horizontally. - /// - public bool CenterOnPageHorizontally; - - /// - /// Set true to show grid lines. - /// - public bool ShowGridLines = false; - - /// - /// Set true to show row and column headings. - /// - public bool ShowHeadings = false; - - /// - /// Set the range to repeat at the top of the page. - /// - public ExcelAddressBase RowsToRepeatAtTop = null; - - /// - /// Set the range to repeat to the left of the page. - /// - public ExcelAddressBase ColumnsToRepeatAtLeft = null; - - /// - /// Set if comments and notes should be included. - /// - public CommentsAndNotes CommentsAndNotes = CommentsAndNotes.None; - - /// - /// Sets how to display errors in cells. - /// - public CellErrors CellErrors = CellErrors.Displayed; - - /// - /// Set the starting page number. - /// - public int FirstPageNumber = 1; - - PdfPageSize _pageSize = PdfPageSize.A4; - /// - /// Set the size of pages. - /// - public PdfPageSize PageSize - { - get - { - return _pageSize; - } - set - { - _pageSize = new PdfPageSize(value.Height, value.Width); - if (value.Height > value.Width) - { - _orientation = Orientations.Portrait; - } - else - { - _orientation = Orientations.Landscape; - } - ContentBounds.CalculateBounds(Margins, PageSize); - } - } - - private Orientations _orientation = Orientations.Portrait; - /// - /// Set the orientation of the pages. - /// - public Orientations Orientation - { - get - { - return _orientation; - } - set - { - if (value == Orientations.Portrait) - { - if (_pageSize.Height < _pageSize.Width) - { - _pageSize = new PdfPageSize(_pageSize.Height, _pageSize.Width); - _orientation = value; - } - } - if (value == Orientations.Landscape) - { - if (_pageSize.Height > _pageSize.Width) - { - _pageSize = new PdfPageSize(_pageSize.Height, _pageSize.Width); - _orientation = value; - } - } - } - } - - private PdfMargins _margins = PdfMargins.Normal; - /// - /// Set the page margins. - /// - public PdfMargins Margins - { - get - { - return _margins; - } - set - { - _margins = value; - ContentBounds.CalculateBounds(Margins, PageSize); - } - } - - private PdfScaling _scaling = PdfScaling.NoScaling; - /// - /// Set the scaling. NOT IMPLEMENTED - /// - public PdfScaling Scaling - { - get - { - return _scaling; - } - set - { - _scaling = value; - } - } - - internal PdfContentBounds ContentBounds = new PdfContentBounds(PdfMargins.Normal, PdfPageSize.A4); - internal string defaultFontName = ""; - - //DEBUG - internal bool Debug = false; - internal bool PrintAsText = false; - } - - /// - /// Orientation of pages. - /// - public enum Orientations - { - /// - /// Portrait orientation. - /// - Portrait, - /// - /// Landscape orientation. - /// - Landscape, - } - - /// - /// Order of pages. - /// - public enum PageOrders - { - /// - /// Order Down then over. - /// - DownThenOver, - /// - /// Order Over then down. - /// - OverThenDown, - } - - public enum CommentsAndNotes - { - /// - /// Comments and Notes will be ignored. - /// - None, - /// - /// Comments and Notes will be displayed on a seprate page at the end. - /// - AtEndOfSheet, - /// - /// Notes will be displayed on the sheet. (Comments will not be shown.) - /// - AsDisplayedOnSheet - } - - public enum CellErrors - { - /// - /// Errors will be displayed - /// - Displayed, - /// - /// Errors will be not displayed - /// - Blank, - /// - /// Errors will be displayed as "--" - /// - Dashed, - /// - /// Errors will be displayed as #N/A - /// - NA, - } -} - - -/* -//Page - //Orientation - //Portrait - //Landscape - //Scaling - //First Page number - -//marigns - //Header - //Footer - //Center On Page - //Horizontal - //vertical - -//Sheet - //print grid lines - //black and white - //print cell errors - //comments and notes - //Row and column headings - //Page order - //down, then over - //over, then down -*/ \ No newline at end of file diff --git a/src/EPPlus.Export.Pdf/Pdfhelpers/PdfTextData.cs b/src/EPPlus.Export.Pdf/Pdfhelpers/PdfTextData.cs deleted file mode 100644 index cbf89df535..0000000000 --- a/src/EPPlus.Export.Pdf/Pdfhelpers/PdfTextData.cs +++ /dev/null @@ -1,122 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.PdfSettings; -using EPPlus.Fonts.OpenType; -using EPPlus.Fonts.OpenType.Tables.Cmap; -using EPPlus.Fonts.OpenType.Tables.Kern; -using OfficeOpenXml.Interfaces.Fonts; -using System; -using System.Linq; - -namespace EPPlus.Export.Pdf.Pdfhelpers -{ - internal static class PdfTextData - { - internal static OpenTypeFont GetFontData(PdfPageSettings pageSettings, string fontName, FontSubFamily subFamily) - { - return OpenTypeFonts.LoadFont(fontName, subFamily, pageSettings.FontDirectories, pageSettings.SearchSystemDirectories); - } - - internal static double MeasureFontHeight(OpenTypeFont font, double fontSize) - { - var asc = font.Os2Table.usWinAscent; - var desc = font.Os2Table.usWinDescent; - var size = fontSize; - var em = font.HeadTable.UnitsPerEm; - //var lineHeight = asc + System.Math.Abs(desc); //we should use this formula instead, but due to how layouting works we use the other one for now. Need to fix layouting stuff and the issue is when we swtich Y axis most likely. - var FontHeight = asc - desc; - var FontHeightPt = FontHeight * (size / em); - return FontHeightPt; - } - - internal static double MeasureLineHeight(OpenTypeFont font, double fontSize) - { - var asc = font.Os2Table.usWinAscent; - var desc = font.Os2Table.usWinDescent; - var lineGap = font.Os2Table.sTypoLineGap; - var size = fontSize; - var em = font.HeadTable.UnitsPerEm; - var lineHeight = asc - desc + lineGap; - var lineHeightPt = lineHeight * (size / em); - return lineHeightPt; - } - - internal static double MeasureAscent(OpenTypeFont font, double fontSize) - { - return font.Os2Table.usWinAscent * (fontSize / font.HeadTable.UnitsPerEm); - } - - internal static double MeasureDescent(OpenTypeFont font, double fontSize) - { - return font.Os2Table.usWinDescent * (fontSize / font.HeadTable.UnitsPerEm); - } - - internal static double MeasureText(string text, double fontSize, OpenTypeFont fontData) - { - double totalAdvanceWidth = 0; - ushort? lastGlyphIndex = 0; - bool firstChar = true; - var glyphMappings = fontData.CmapTable.GetPreferredSubtable().GetGlyphMappings(); - for (int i = 0; i < text.Length; i++) - { - char c = text[i]; - var gi = glyphMappings.GetGlyphIndex(c); - //var encodingRecord = fontData.CmapTable.EncodingRecords.FirstOrDefault(er => er.PlatformId == Platforms.Windows && er.EncodingId == 1); - //if (encodingRecord == null) throw new Exception("Could not find Microsoft Unicode cmap (PlatformID 3, EncodingID 1)."); - //GlyphMapping[] mappings = encodingRecord.Mappings; - //encodingRecord.CharMappingsToGlyphIndex.TryGetValue(c, out ushort gi); - int advanceWidth; - if (gi == 0 && c != 0) - { - advanceWidth = fontData.Os2Table.xAvgCharWidth; - } - else - { - var hhMetric = fontData.HmtxTable.hMetrics[gi ?? 0]; - advanceWidth = Convert.ToInt16(hhMetric.advanceWidth); - } - totalAdvanceWidth += advanceWidth; - // Kerning adjustment - if (!firstChar) - { - int kerning = GetKerningAdjustment(lastGlyphIndex ?? 0, gi ?? 0, fontData); - totalAdvanceWidth += kerning; - } - lastGlyphIndex = gi; - firstChar = false; - } - // Convert to points - return totalAdvanceWidth / fontData.HeadTable.UnitsPerEm * fontSize; - } - - private static int GetKerningAdjustment(ushort left, ushort right, OpenTypeFont fontData) - { - foreach (var subtable in fontData.KernTable.SubTables) - { - if (subtable.Format0Subtable == null) continue; - // Format 0 only - int format = subtable.coverage.RawValue >> 8; - bool isHorizontal = (subtable.coverage.RawValue & 0x1) == 1; - if (format != 0 || !isHorizontal) continue; - KerningPair[] pairs = subtable.Format0Subtable.Pairs; - if (pairs == null) continue; - for (int i = 0; i < pairs.Length; i++) - { - if (pairs[i].left == left && pairs[i].right == right) - return pairs[i].value; - } - } - return 0; - } - } -} diff --git a/src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfDictionaries.cs b/src/EPPlus.Export.Pdf/Resources/PdfDictionaries.cs similarity index 97% rename from src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfDictionaries.cs rename to src/EPPlus.Export.Pdf/Resources/PdfDictionaries.cs index 3407568b2c..9b4fc600bf 100644 --- a/src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfDictionaries.cs +++ b/src/EPPlus.Export.Pdf/Resources/PdfDictionaries.cs @@ -15,7 +15,7 @@ Date Author Change using System.Collections.Generic; using System.Linq; -namespace OfficeOpenXml.Export.PdfExport.Data.Dictionaries; +namespace EPPlus.Export.Pdf.Resources internal class PdfDictionaries { diff --git a/src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfFontResource.cs b/src/EPPlus.Export.Pdf/Resources/PdfFontResource.cs similarity index 99% rename from src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfFontResource.cs rename to src/EPPlus.Export.Pdf/Resources/PdfFontResource.cs index 7dbcde13a4..21ec1ca890 100644 --- a/src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfFontResource.cs +++ b/src/EPPlus.Export.Pdf/Resources/PdfFontResource.cs @@ -20,7 +20,7 @@ Date Author Change using OfficeOpenXml.Interfaces.Fonts; using OfficeOpenXml.Export.PdfExport.ObjectData; -namespace OfficeOpenXml.Export.PdfExport.Data.Dictionaries; +namespace EPPlus.Export.Pdf.Resources { internal class PdfFontResource : PdfResource { diff --git a/src/EPPlus.Export.Pdf/PdfResources MOVED/PdfPatternResource.cs b/src/EPPlus.Export.Pdf/Resources/PdfPatternResource.cs similarity index 99% rename from src/EPPlus.Export.Pdf/PdfResources MOVED/PdfPatternResource.cs rename to src/EPPlus.Export.Pdf/Resources/PdfPatternResource.cs index 68db8fcd3f..2cad5eed22 100644 --- a/src/EPPlus.Export.Pdf/PdfResources MOVED/PdfPatternResource.cs +++ b/src/EPPlus.Export.Pdf/Resources/PdfPatternResource.cs @@ -14,7 +14,7 @@ Date Author Change using EPPlus.Export.Pdf.PdfObjects.PdfPatterns; using OfficeOpenXml.Style; -namespace EPPlus.Export.Pdf.PdfResources +namespace EPPlus.Export.Pdf.Resources { internal class PdfPatternResource : PdfResource { diff --git a/src/EPPlus.Export.Pdf/PdfResources MOVED/PdfResource.cs b/src/EPPlus.Export.Pdf/Resources/PdfResource.cs similarity index 96% rename from src/EPPlus.Export.Pdf/PdfResources MOVED/PdfResource.cs rename to src/EPPlus.Export.Pdf/Resources/PdfResource.cs index 8bf3ff2339..4c2e5d500f 100644 --- a/src/EPPlus.Export.Pdf/PdfResources MOVED/PdfResource.cs +++ b/src/EPPlus.Export.Pdf/Resources/PdfResource.cs @@ -10,7 +10,7 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -namespace EPPlus.Export.Pdf.PdfResources +namespace EPPlus.Export.Pdf.Resources { internal class PdfResource { diff --git a/src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfShadingResource.cs b/src/EPPlus.Export.Pdf/Resources/PdfShadingResource.cs similarity index 92% rename from src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfShadingResource.cs rename to src/EPPlus.Export.Pdf/Resources/PdfShadingResource.cs index 1bf9f6f0f1..944bb91c65 100644 --- a/src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfShadingResource.cs +++ b/src/EPPlus.Export.Pdf/Resources/PdfShadingResource.cs @@ -10,9 +10,12 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -using OfficeOpenXml.Style; +using EPPlus.Export.Pdf.DocumentObjects.Patterns; +using EPPlus.Export.Pdf.DocumentObjects.Shadings; +using EPPlus.Export.Pdf.Helpers; +using EPPlus.Export.Pdf.Layout; -namespace OfficeOpenXml.Export.PdfExport.Data.Dictionaries +namespace EPPlus.Export.Pdf.Resources { internal class PdfShadingResource : PdfResource { diff --git a/src/EPPlus/Export/PdfExport/PdfPageSettings.cs b/src/EPPlus.Export.Pdf/Settings/PdfPageSettings.cs similarity index 97% rename from src/EPPlus/Export/PdfExport/PdfPageSettings.cs rename to src/EPPlus.Export.Pdf/Settings/PdfPageSettings.cs index 6f4a063930..d5fdf6486a 100644 --- a/src/EPPlus/Export/PdfExport/PdfPageSettings.cs +++ b/src/EPPlus.Export.Pdf/Settings/PdfPageSettings.cs @@ -10,12 +10,13 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -using OfficeOpenXml.Export.PdfExport.Data.PdfPageSizes; +using EPPlus.Export.Pdf.Settings.PdfPageSizes; using EPPlus.Fonts.OpenType; +using OfficeOpenXml; using System.Collections.Generic; using System.Linq; -namespace OfficeOpenXml.Export.PdfExport +namespace EPPlus.Export.Pdf.Settings { /// /// Settings object for exporting to PDF. @@ -97,12 +98,12 @@ public OpenTypeFontEngine FontEngine /// /// Set the range to repeat at the top of the page. /// - public ExcelAddressBase RowsToRepeatAtTop = null; + public string RowsToRepeatAtTop = null; /// /// Set the range to repeat to the left of the page. /// - public ExcelAddressBase ColumnsToRepeatAtLeft = null; + public string ColumnsToRepeatAtLeft = null; /// /// Set if comments and notes should be included. diff --git a/src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfContentBounds.cs b/src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfContentBounds.cs similarity index 98% rename from src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfContentBounds.cs rename to src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfContentBounds.cs index 638c4c21c9..6d05d77db5 100644 --- a/src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfContentBounds.cs +++ b/src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfContentBounds.cs @@ -12,7 +12,7 @@ Date Author Change *************************************************************************************************/ using EPPlus.Graphics; -namespace OfficeOpenXml.Export.PdfExport.Data.PdfPageSizes +namespace EPPlus.Export.Pdf.Settings.PdfPageSizes { internal class PdfContentBounds : Rect { diff --git a/src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfPageSizes/PdfMargins.cs b/src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfMargins.cs similarity index 98% rename from src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfPageSizes/PdfMargins.cs rename to src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfMargins.cs index 7a052c256c..1fee87bf8e 100644 --- a/src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfPageSizes/PdfMargins.cs +++ b/src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfMargins.cs @@ -12,7 +12,7 @@ Date Author Change *************************************************************************************************/ using EPPlus.Graphics.Units; -namespace EPPlus.Export.Pdf.PdfSettings.PdfPageSizes +namespace EPPlus.Export.Pdf.Settings.PdfPageSizes { public class PdfMargins { diff --git a/src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfPageSizes/PdfPageSize.cs b/src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfPageSize.cs similarity index 97% rename from src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfPageSizes/PdfPageSize.cs rename to src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfPageSize.cs index 47bd7ca6de..8dce84bc52 100644 --- a/src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfPageSizes/PdfPageSize.cs +++ b/src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfPageSize.cs @@ -12,7 +12,7 @@ Date Author Change *************************************************************************************************/ using EPPlus.Graphics.Units; -namespace EPPlus.Export.Pdf.PdfSettings.PdfPageSizes +namespace EPPlus.Export.Pdf.Settings.PdfPageSizes { public class PdfPageSize { diff --git a/src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfPageSizes/PdfScaling.cs b/src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfScaling.cs similarity index 98% rename from src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfPageSizes/PdfScaling.cs rename to src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfScaling.cs index 425cc74335..5d48db05c2 100644 --- a/src/EPPlus.Export.Pdf/PdfSettings MOVED/PdfPageSizes/PdfScaling.cs +++ b/src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfScaling.cs @@ -10,7 +10,7 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -namespace EPPlus.Export.Pdf.PdfSettings.PdfPageSizes +namespace EPPlus.Export.Pdf.Settings.PdfPageSizes { public enum ScalingMode { diff --git a/src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfPatternResource.cs b/src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfPatternResource.cs deleted file mode 100644 index 764f90364b..0000000000 --- a/src/EPPlus/Export/PdfExport/Data/Dictionaries/PdfPatternResource.cs +++ /dev/null @@ -1,100 +0,0 @@ -/************************************************************************************************* - 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.Export.Pdf.PdfLayout; -using EPPlus.Export.Pdf.PdfObjects.PdfPatterns; -using OfficeOpenXml.Style; - -namespace OfficeOpenXml.Export.PdfExport.Data.Dictionaries; -{ - internal class PdfPatternResource : PdfResource - { - internal int objectNumber; - internal PdfCellFillData CellFillData; - - // All mask-based patterns use an 8x8 tile in pattern space. The mask is the - // single source of geometry, so BBox and the tiling step are 8x8 for every - // pattern. - private static readonly double[] PatternBBox = new double[] { 0d, 0d, 8d, 8d }; - private const double PatternStep = 8d; - - // The 8x8 pattern space is scaled down to match the physical tile size - // Excel uses (0.75 pt for a full tile, i.e. 0.75 / 8 per pattern unit). - // The scale is positive on both axes: the renderer already mirrors the - // mask in y, so no y-flip is applied here (that would double-flip). - private const double PatternScale = 0.75d / 8d; - private static readonly double[] PatternMatrix = - new double[] { PatternScale, 0d, 0d, PatternScale, 0d, 0d }; - - public PdfPatternResource(int labelNumber, PdfCellFillData cellFillData) - : base("P", labelNumber) - { - CellFillData = cellFillData; - } - - public PdfPattern GetPatternObject(int objectNumber, int version = 0) - { - this.objectNumber = objectNumber; - - // None and Solid are not patterns; they are handled as special cases - // elsewhere in the export. - if (CellFillData.PatternStyle == ExcelFillStyle.None || - CellFillData.PatternStyle == ExcelFillStyle.Solid) - { - return null; - } - - ExcelPatternMask mask; - if (!TryGetMask(CellFillData.PatternStyle, out mask)) - { - return null; - } - - var fill = new PdfPatternMaskFill(mask, CellFillData.PatternColor, CellFillData.BackgroundColor); - var pattern = new PdfTilingPattern(objectNumber, fill, PatternBBox, PatternStep, PatternStep, version); - pattern.Matrix = PatternMatrix; - return pattern; - } - - /// - /// Maps an Excel cell fill style to the corresponding 8x8 pattern mask. - /// Returns false for styles that have no mask (e.g. None/Solid, or any - /// style not rendered as a tiling pattern). - /// - private static bool TryGetMask(ExcelFillStyle style, out ExcelPatternMask mask) - { - switch (style) - { - case ExcelFillStyle.DarkGray: mask = ExcelPatternMask.DarkGray; return true; - case ExcelFillStyle.MediumGray: mask = ExcelPatternMask.MediumGray; return true; - case ExcelFillStyle.LightGray: mask = ExcelPatternMask.LightGray; return true; - case ExcelFillStyle.Gray125: mask = ExcelPatternMask.Gray125; return true; - case ExcelFillStyle.Gray0625: mask = ExcelPatternMask.Gray0625; return true; - case ExcelFillStyle.DarkHorizontal: mask = ExcelPatternMask.DarkHorizontal; return true; - case ExcelFillStyle.DarkVertical: mask = ExcelPatternMask.DarkVertical; return true; - case ExcelFillStyle.DarkDown: mask = ExcelPatternMask.DarkDown; return true; - case ExcelFillStyle.DarkUp: mask = ExcelPatternMask.DarkUp; return true; - case ExcelFillStyle.DarkGrid: mask = ExcelPatternMask.DarkGrid; return true; - case ExcelFillStyle.DarkTrellis: mask = ExcelPatternMask.DarkTrellis; return true; - case ExcelFillStyle.LightHorizontal: mask = ExcelPatternMask.LightHorizontal; return true; - case ExcelFillStyle.LightVertical: mask = ExcelPatternMask.LightVertical; return true; - case ExcelFillStyle.LightDown: mask = ExcelPatternMask.LightDown; return true; - case ExcelFillStyle.LightUp: mask = ExcelPatternMask.LightUp; return true; - case ExcelFillStyle.LightGrid: mask = ExcelPatternMask.LightGrid; return true; - case ExcelFillStyle.LightTrellis: mask = ExcelPatternMask.LightTrellis; return true; - default: - mask = ExcelPatternMask.DarkGray; - return false; - } - } - } -} \ No newline at end of file diff --git a/src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfMargins.cs b/src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfMargins.cs deleted file mode 100644 index 8ce3968fb3..0000000000 --- a/src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfMargins.cs +++ /dev/null @@ -1,62 +0,0 @@ -/************************************************************************************************* - 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.Units; - -namespace OfficeOpenXml.Export.PdfExport.Data.PdfPageSizes -{ - public class PdfMargins - { - public double Header { get; set; } = 7.6; - public double Footer { get; set; } = 7.6d; - public double Top { get; set; } = 19.1d; - public double Left { get; set; } = 17.8d; - public double Right { get; set; } = 17.8d; - public double Bottom { get; set; } = 19.1d; - public double HeaderPu { get; private set; } - public double FooterPu { get; private set; } - public double TopPu { get; private set; } - public double LeftPu { get; private set; } - public double RightPu { get; private set; } - public double BottomPu { get; private set; } - - public PdfMargins() - { - TopPu = UnitConversion.MmToPoints(Top); - LeftPu = UnitConversion.MmToPoints(Left); - RightPu = UnitConversion.MmToPoints(Right); - BottomPu = UnitConversion.MmToPoints(Bottom); - HeaderPu = UnitConversion.MmToPoints(Header); - FooterPu = UnitConversion.MmToPoints(Footer); - } - - public PdfMargins(double Top, double Left, double Right, double Bottom, double Header, double Footer) - { - this.Top = Top; - this.Left = Left; - this.Right = Right; - this.Bottom = Bottom; - this.Header = Header; - this.Footer = Footer; - TopPu = UnitConversion.MmToPoints(Top); - LeftPu = UnitConversion.MmToPoints(Left); - RightPu = UnitConversion.MmToPoints(Right); - BottomPu = UnitConversion.MmToPoints(Bottom); - HeaderPu = UnitConversion.MmToPoints(Header); - FooterPu = UnitConversion.MmToPoints(Footer); - } - - public static PdfMargins Normal => new PdfMargins(); - public static PdfMargins Wide => new PdfMargins(25.4d, 25.4d, 25.4d, 25.4d, 12.7d, 12.7d); - public static PdfMargins Narrow => new PdfMargins(19.1d, 6.4d, 6.4d, 19.1d, 7.6d, 7.6d); - } -} diff --git a/src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfPageSize.cs b/src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfPageSize.cs deleted file mode 100644 index 031f2d09a4..0000000000 --- a/src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfPageSize.cs +++ /dev/null @@ -1,42 +0,0 @@ -/************************************************************************************************* - 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.Units; - -namespace OfficeOpenXml.Export.PdfExport.Data.PdfPageSizes -{ - public class PdfPageSize - { - public double Width { get; } - public double Height { get; } - public double WidthPu { get; } - public double HeightPu { get; } - - public PdfPageSize(double width, double height) - { - Width = width; - Height = height; - WidthPu = System.Math.Round( UnitConversion.MmToPoints(width)); - HeightPu = System.Math.Round( UnitConversion.MmToPoints(height)); - } - - public static PdfPageSize A5 => new PdfPageSize(148d, 210d); - public static PdfPageSize A4 => new PdfPageSize(210d, 297d); //(595, 842); - public static PdfPageSize A3 => new PdfPageSize(297d, 420d); //(842, 1191); - public static PdfPageSize B5 => new PdfPageSize(182d, 257d); - public static PdfPageSize B4 => new PdfPageSize(257d, 364d); - public static PdfPageSize Letter => new PdfPageSize(215.9d, 279.4d); //(612, 792); - public static PdfPageSize Legal => new PdfPageSize(215.9d, 355.6d); //(612, 1008); - public static PdfPageSize Statement => new PdfPageSize(139.7d, 215.9d); - public static PdfPageSize Executive => new PdfPageSize(184.2d, 266.7d); - } -} \ No newline at end of file diff --git a/src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfScaling.cs b/src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfScaling.cs deleted file mode 100644 index 72e2d60a5f..0000000000 --- a/src/EPPlus/Export/PdfExport/Data/PdfPageSizes/PdfScaling.cs +++ /dev/null @@ -1,66 +0,0 @@ -/************************************************************************************************* - 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 - *************************************************************************************************/ -namespace OfficeOpenXml.Export.PdfExport.Data.PdfPageSizes -{ - public enum ScalingMode - { - /// - /// Adjust size based on the normal size - /// - AdjustToNormalSize, - /// - /// Adjust size based on number of pages - /// - FitToPages - } - - public class PdfScaling - { - public ScalingMode ScalingMode { get; set; } - - /// - /// Used when is set to AdjustToNormalSize. - /// The scale factor is expressed as a multiplier, where 1.0 represents 100% (no scaling), 0.5 represents 50%, and 2.0 represents 200%. - /// - public double Scale { get; set; } = 1.0; - - /// - /// Used when is set to FitToPages. - /// Scales the content to fit to the specifed number of pages in width. - /// - public int PagesWide { get; set; } = 1; - /// - /// Used when is set to FitToPages. - /// Scales the content to fit to the specifed number of pages in height. - /// - public int PagesTall { get; set; } = 1; - - public PdfScaling(double scale) - { - ScalingMode = ScalingMode.AdjustToNormalSize; - Scale = scale; - } - - public PdfScaling(int pagesWide, int pagesTall) - { - ScalingMode = ScalingMode.FitToPages; - PagesWide = pagesWide; - PagesTall = pagesTall; - } - - public static PdfScaling NoScaling => new PdfScaling(1d); - public static PdfScaling FitSheetToOnePage => new PdfScaling(1, 1); - public static PdfScaling FitAllColumnsOnOnePage => new PdfScaling(1, 0); - public static PdfScaling FitAllRowsOnOnePage => new PdfScaling(0, 1); - } -} \ No newline at end of file diff --git a/src/EPPlus/Export/PdfExport/Helpers/PdfColor.cs b/src/EPPlus/Export/PdfExport/Helpers/PdfColor.cs deleted file mode 100644 index f26c14339f..0000000000 --- a/src/EPPlus/Export/PdfExport/Helpers/PdfColor.cs +++ /dev/null @@ -1,132 +0,0 @@ -/************************************************************************************************* - 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 System; -using System.Drawing; -using System.Globalization; - -namespace OfficeOpenXml.Export.PdfExport.Helpers -{ - internal static class PdfColor - { - /// - /// Returns the red component of the color as a nomralized value. - /// - /// The color to extract the component - /// Normalized value of the color component. - public static float GetR(this Color c) => c.R / 255f; - - /// - /// Returns the green component of the color as a nomralized value. - /// - /// The color to extract the component - /// Normalized value of the color component. - public static float GetG(this Color c) => c.G / 255f; - - /// - /// Returns the blue component of the color as a nomralized value. - /// - /// The color to extract the component - /// Normalized value of the color component. - public static float GetB(this Color c) => c.B / 255f; - - /// - /// Returns the alpha component of the color as a nomralized value. - /// - /// The color to extract the component - /// Normalized value of the color component. - public static float GetA(this Color c) => c.A / 255f; - - /// - /// Convert a hex string representing a color into a color. - /// - /// String with corlor in hex code. - /// Valid inputs that sets color to Color.Empty: - /// "#0" - /// "0" - /// "" - /// null - /// - /// System.Drawing.Color object. - /// Throws exception of string is in invalid format. - public static Color SetColorFromHex(string hex) - { - if (string.IsNullOrEmpty(hex) || hex == "0" || hex == "#0") - { - return Color.Empty; - - } - hex = hex.Trim().TrimStart('#'); - int R=0, G=0, B=0, A=0; - if (hex.Length == 3) - { - R = Convert.ToByte(new string(hex[0], 2), 16); - G = Convert.ToByte(new string(hex[1], 2), 16); - B = Convert.ToByte(new string(hex[2], 2), 16); - - } - else if (hex.Length == 4) - { - A = Convert.ToByte(new string(hex[0], 2), 16); - R = Convert.ToByte(new string(hex[1], 2), 16); - G = Convert.ToByte(new string(hex[2], 2), 16); - B = Convert.ToByte(new string(hex[3], 2), 16); - } - else if (hex.Length == 6) - { - R = Convert.ToByte(hex.Substring(0, 2), 16); - G = Convert.ToByte(hex.Substring(2, 2), 16); - B = Convert.ToByte(hex.Substring(4, 2), 16); - } - else if (hex.Length == 8) - { - A = Convert.ToByte(hex.Substring(0, 2), 16); - R = Convert.ToByte(hex.Substring(2, 2), 16); - G = Convert.ToByte(hex.Substring(4, 2), 16); - B = Convert.ToByte(hex.Substring(6, 2), 16); - } - else - { - throw new FormatException("Invalid hex color format."); - } - return Color.FromArgb(A, R, G, B); - } - - /// - /// Returns a string for pdf command for stroke. - /// - /// The color to use for stroke. - /// The command string for a stroke color - public static string ToStrokeCommand(this Color c) => $"{c.GetR().ToString("F", CultureInfo.InvariantCulture)} {c.GetG().ToString("F", CultureInfo.InvariantCulture)} {c.GetB().ToString("F", CultureInfo.InvariantCulture)} RG"; - - /// - /// Returns a string for pdf command for fill. - /// - /// The color to use for fill. - /// The command string for a fill color - public static string ToFillCommand(this Color c) => $"{c.GetR().ToString("F", CultureInfo.InvariantCulture)} {c.GetG().ToString("F", CultureInfo.InvariantCulture)} {c.GetB().ToString("F", CultureInfo.InvariantCulture)} rg"; - - /// - /// Get the color represented in Hex as a string. - /// - /// The color which to return the hex value as a string. - /// String represeting the hex value of the color. - public static string ToHexString(this Color c) - { - int r = (int)(c.R); - int g = (int)(c.G); - int b = (int)(c.B); - int a = (int)(c.A); - return $"#{r:X2}{g:X2}{b:X2}{a:X2}"; - } - } -} diff --git a/src/EPPlus/Export/PdfExport/Helpers/PdfString.cs b/src/EPPlus/Export/PdfExport/Helpers/PdfString.cs deleted file mode 100644 index f0dd07f222..0000000000 --- a/src/EPPlus/Export/PdfExport/Helpers/PdfString.cs +++ /dev/null @@ -1,63 +0,0 @@ -/************************************************************************************************* - 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 System.Globalization; - -namespace OfficeOpenXml.Export.PdfExport.Helpers -{ - internal static class PdfString - { - /// - /// Returns the value formated for use in pdf document. - /// - /// Value to turn into a string. - /// The value repsented as a string. - internal static string ToPdfString(this double val) - { - return val.ToString(CultureInfo.InvariantCulture); - } - - /// - /// Returns the value formated for use in pdf document. - /// - /// Value to turn into a string. - /// The value repsented as a string with 4 decimals. - internal static string ToPdfStringF4(this double val) - { - return val.ToString("F4", CultureInfo.InvariantCulture); - } - - /// - /// Returns the value formated for use in pdf document. - /// - /// Value to turn into a string. - /// The value repsented as a string with no decimals. - internal static string ToPdfStringF0(this double val) - { - return val.ToString("F0", CultureInfo.InvariantCulture); - } - - //Delete this method? - public static bool IsNullOrWhiteSpace(string s) - { - if (s == null) - return true; - - for (int i = 0; i < s.Length; i++) - { - if (!char.IsWhiteSpace(s[i])) - return false; - } - return true; - } - } -} diff --git a/src/EPPlus/Export/PdfExport/Layout/PdfLayout.cs b/src/EPPlus/Export/PdfExport/Layout/PdfLayout.cs index eb1ff9f580..246cba0a2f 100644 --- a/src/EPPlus/Export/PdfExport/Layout/PdfLayout.cs +++ b/src/EPPlus/Export/PdfExport/Layout/PdfLayout.cs @@ -14,24 +14,16 @@ Date Author Change using EPPlus.Fonts.OpenType.Integration.DataHolders; using EPPlus.Graphics; using OfficeOpenXml.Export.PdfExport.Data; -using OfficeOpenXml.Export.PdfExport.Data.Dictionaries; using OfficeOpenXml.Export.PdfExport.TextMapping; using OfficeOpenXml.Export.PdfExport.TextShaping; using OfficeOpenXml.Interfaces.Fonts; using OfficeOpenXml.Style; using System; using System.Collections.Generic; +using EPPlus.Export.Pdf.Settings; namespace OfficeOpenXml.Export.PdfExport.Layout { - internal struct MergedCellDrawInfo - { - public double X; - public double Y; - public double Width; - public double Height; - } - internal struct PrintTitleCellDraw { public PdfCell Cell; @@ -63,50 +55,6 @@ internal struct SpillCellDraw public bool IsPrintTitle; } - internal struct Page - { - public int FromRow; - public int FromColumn; - public int ToRow; - public int ToColumn; - - public bool HasPrintTitle; - public double PrintTitleWidth; - public double PrintTitleHeight; - - public PdfCellCollection Map; - - public PdfHeaderFooterCollection HeaderFooters; - - public Dictionary MergedCells; - public List PrintTitleCells; - public List PrintTitleGridLines; - public List PrintTitleHeadings; - public List SpillCells; - public List PrintTitleBorders; - - public double[] RowHeights; - - public double HeadingWidth; - public double HeadingHeight; - - } - - internal struct Pages - { - public Page[] Page; - public int Width; - public int Height; - public bool IsCommentsPage; - public string HeadingFontName; - public float HeadingFontSize; - public ExcelFill HeadingFill; - public int Count - { - get { return Width * Height; } - } - } - internal class PdfLayout { private const double rowHeadingWith1CharWidth = 18d; diff --git a/src/EPPlus/Export/PdfExport/PdfCatalog.cs b/src/EPPlus/Export/PdfExport/PdfCatalog.cs index 92fb074165..f1781a8e8e 100644 --- a/src/EPPlus/Export/PdfExport/PdfCatalog.cs +++ b/src/EPPlus/Export/PdfExport/PdfCatalog.cs @@ -11,8 +11,8 @@ Date Author Change 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ using EPPlus.Graphics; +using EPPlus.Export.Pdf.Settings; using OfficeOpenXml.Export.PdfExport.Data; -using OfficeOpenXml.Export.PdfExport.Data.Dictionaries; using OfficeOpenXml.Export.PdfExport.Layout; using OfficeOpenXml.Export.PdfExport.RowResize; using OfficeOpenXml.Export.PdfExport.TextMapping; @@ -319,13 +319,15 @@ private void GetPrintTitles(PdfPageSettings pageSettings, PdfWorksheet pdfSheet) // --- Step 2: PdfPageSettings overrides take precedence over the defined name --- if (pageSettings.RowsToRepeatAtTop != null) { - pdfSheet.PrintTitleRowFrom = pageSettings.RowsToRepeatAtTop._fromRow; - pdfSheet.PrintTitleRowTo = pageSettings.RowsToRepeatAtTop._toRow; + ExcelAddressBase repeatRows = new ExcelAddressBase(pageSettings.RowsToRepeatAtTop); + pdfSheet.PrintTitleRowFrom = repeatRows._fromRow; + pdfSheet.PrintTitleRowTo = repeatRows._toRow; } if (pageSettings.ColumnsToRepeatAtLeft != null) { - pdfSheet.PrintTitleColFrom = pageSettings.ColumnsToRepeatAtLeft._fromCol; - pdfSheet.PrintTitleColTo = pageSettings.ColumnsToRepeatAtLeft._toCol; + ExcelAddressBase repeatCols = new ExcelAddressBase(pageSettings.ColumnsToRepeatAtLeft); + pdfSheet.PrintTitleColFrom = repeatCols._fromCol; + pdfSheet.PrintTitleColTo = repeatCols._toCol; } // --- Step 3: mark cells so the renderer can identify them instantly --- From a6b5508a1fed8381f4ff17818e5516123582ff4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Tue, 23 Jun 2026 16:46:49 +0200 Subject: [PATCH 36/49] Moving files progress --- src/EPPlus.Export.Pdf/AssemblyInfo.cs | 6 +- .../DocumentObjects/Fonts/CIDSystemInfo.cs | 1 - .../DocumentObjects/Fonts/PdfFontWidths.cs | 153 +++-- .../DocumentObjects/PdfBorderRenderer.cs | 1 - .../EPPlus.Export.Pdf.csproj | 1 - .../{Helpers => Enums}/EPPlusEnums.cs | 2 +- src/EPPlus.Export.Pdf/ExcelPdf.cs | 2 +- .../Layout/CellAlignmentData.cs | 2 +- src/EPPlus.Export.Pdf/Layout/GridLineData.cs | 30 + src/EPPlus.Export.Pdf/Layout/PdfBorderData.cs | 2 +- src/EPPlus.Export.Pdf/Layout/PdfCell.cs | 26 + .../Layout/PdfCellBorderLayout.cs | 181 +++--- .../Layout/PdfCellContentLayout.cs | 556 +++++++++--------- src/EPPlus.Export.Pdf/Layout/PdfCellLayout.cs | 371 ++++++------ src/EPPlus.Export.Pdf/Layout/PdfPageLayout.cs | 44 -- .../Layout}/PdfShapedText.cs | 2 +- .../Resources/PdfDictionaries.cs | 52 +- .../Resources/PdfFontResource.cs | 3 +- .../Resources/PdfPatternResource.cs | 7 +- src/EPPlus/EPPlus.csproj | 1 + src/EPPlus/Export/PdfExport/Data/PageData.cs | 65 ++ src/EPPlus/Export/PdfExport/Data/PdfCell.cs | 40 +- .../PdfExport}/Layout/PdfGridlinesLayout.cs | 19 +- .../Export/PdfExport/Layout/PdfLayout.cs | 5 +- .../PdfExport/TextMapping/PdfTextMap.cs | 17 +- src/EPPlus/Properties/AssemblyInfo.cs | 1 - 26 files changed, 805 insertions(+), 785 deletions(-) rename src/EPPlus.Export.Pdf/{Helpers => Enums}/EPPlusEnums.cs (99%) create mode 100644 src/EPPlus.Export.Pdf/Layout/GridLineData.cs create mode 100644 src/EPPlus.Export.Pdf/Layout/PdfCell.cs rename src/{EPPlus/Export/PdfExport/TextShaping => EPPlus.Export.Pdf/Layout}/PdfShapedText.cs (96%) create mode 100644 src/EPPlus/Export/PdfExport/Data/PageData.cs rename src/{EPPlus.Export.Pdf => EPPlus/Export/PdfExport}/Layout/PdfGridlinesLayout.cs (97%) diff --git a/src/EPPlus.Export.Pdf/AssemblyInfo.cs b/src/EPPlus.Export.Pdf/AssemblyInfo.cs index adeb5b8146..280655fcc7 100644 --- a/src/EPPlus.Export.Pdf/AssemblyInfo.cs +++ b/src/EPPlus.Export.Pdf/AssemblyInfo.cs @@ -12,6 +12,7 @@ Date Author Change *************************************************************************************************/ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Security; // In SDK-style projects such as this one, several assembly attributes that were historically // defined in this file are now automatically added during build and populated with @@ -26,8 +27,9 @@ Date Author Change [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM. - +[assembly: AllowPartiallyTrustedCallers] [assembly: Guid("60855b7d-19da-4dfa-90b2-78231d227d65")] +[assembly: InternalsVisibleTo("EPPlus, PublicKey=00240000048000009400000006020000002400005253413100040000010001002981343969ed86fe604c56a84c61e33109424ef07bb458ff12e9533c11ea23ac8ef7e014b2a2de4ceb5f7528f963c755fe9b32f09cc35d21de94319d2a952a6e663cd46d6d98465998c77b52093d4f17cdc20ec054751244696f08afa6f4417d85267b147b73b6a3f5e9015b9dfd3dcc3328ce63df53a7c08a5544c1526ea5a5")] [assembly: InternalsVisibleTo("EPPlus.Export.Pdf.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dd3a3466a88cbf5d374fe992cec433c48022414fe96608933e8e36782001213dd31bc454dc6f962a54a3a76cfb9e03a32cd4c658ecd49d1a98709971a080ab92d5c5b65346155f8d6422db4ffbf662f78913996a9a8b78ee11ff3cda7e585208cd4468fb3201f15bbb1dfc45c120703c9d6ad495bb9de66893ae5ab5ac8f40dc")] // -[assembly: InternalsVisibleTo("EPPlus.PdfExportPerformance, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f5398d389a44aacee243c9e73152fae942e9ede958ebd4b06651a749e6b2c0395a66bc0fc133dce1cf83cd667fe8f761b433063e603968f12b0b17e474233410f8fe933853d2344e89526414ef65ed7236a7a5c012e30275ec30d7fb665dbe1bd3435439bec55f431b9e69e943294fd474942d6bcb431c94ef653cbeee5b8a9e")] \ No newline at end of file +[assembly: InternalsVisibleTo("EPPlus.PdfExportPerformance, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f5398d389a44aacee243c9e73152fae942e9ede958ebd4b06651a749e6b2c0395a66bc0fc133dce1cf83cd667fe8f761b433063e603968f12b0b17e474233410f8fe933853d2344e89526414ef65ed7236a7a5c012e30275ec30d7fb665dbe1bd3435439bec55f431b9e69e943294fd474942d6bcb431c94ef653cbeee5b8a9e")] diff --git a/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/CIDSystemInfo.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/CIDSystemInfo.cs index 2078bcc38e..49a6506dae 100644 --- a/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/CIDSystemInfo.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/CIDSystemInfo.cs @@ -10,7 +10,6 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ - namespace EPPlus.Export.Pdf.DocumentObjects.Fonts { internal class CIDSystemInfo diff --git a/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontWidths.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontWidths.cs index 68e95bb786..7fc4ee0a64 100644 --- a/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontWidths.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontWidths.cs @@ -10,7 +10,6 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -using OfficeOpenXml.Core.Worksheet.Core.Worksheet.Fonts.GenericMeasurements; using System.Collections.Generic; using System.IO; using System.Linq; @@ -27,83 +26,83 @@ public PdfFontWidths(int objectNumber, List widths, int version = 0) this.widths = widths.ToArray(); } - //can Remove this construcotr probably. - public PdfFontWidths(int objectNumber, Dictionary w, Dictionary m, int version = 0) - : base(objectNumber, version) - { - float e = 2048; - float d = 72f / 96f; - float r = e * d; - float k = 1000f; - var l = (int)(w[m[' ']] * r * k / e); - List exsist = new List(); - List no = new List(); - for (int i = 0; i <= 900; i++) - { - char c = (char)i; - if (m.ContainsKey(c)) - { - exsist.Add(c); - } - else - { - no.Add(c); - } - } - //this.widths = new int[] - //{ - // (int)((w[m[' ']]*r*k)/e), (int)((w[m['!']]*r*k)/e), (int)((w[m['"']]*r*k)/e), (int)((w[m['#']]*r*k)/e), (int)((w[m['$']]*r*k)/e), (int)((w[m['%']]*r*k)/e), (int)((w[m['&']]*r*k)/e), (int)((w[m['\'']]*r*k)/e), (int)((w[m['(']]*r*k)/e), (int)((w[m[')']]*r*k)/e), - // (int)((w[m['*']]*r*k)/e), (int)((w[m['+']]*r*k)/e), (int)((w[m[',']]*r*k)/e), (int)((w[m['-']]*r*k)/e), (int)((w[m['.']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['0']]*r*k)/e), (int)((w[m['1']]*r*k)/e), (int)((w[m['2']]*r*k)/e), (int)((w[m['3']]*r*k)/e), - // (int)((w[m['4']]*r*k)/e), (int)((w[m['5']]*r*k)/e), (int)((w[m['6']]*r*k)/e), (int)((w[m['7']]*r*k)/e), (int)((w[m['8']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m[':']]*r*k)/e), (int)((w[m[';']]*r*k)/e), (int)((w[m['<']]*r*k)/e), (int)((w[m['=']]*r*k)/e), - // (int)((w[m['>']]*r*k)/e), (int)((w[m['?']]*r*k)/e), (int)((w[m['@']]*r*k)/e), (int)((w[m['A']]*r*k)/e), (int)((w[m['B']]*r*k)/e), (int)((w[m['C']]*r*k)/e), (int)((w[m['D']]*r*k)/e), (int)((w[m['E']]*r*k)/e), (int)((w[m['F']]*r*k)/e), (int)((w[m['G']]*r*k)/e), - // (int)((w[m['H']]*r*k)/e), (int)((w[m['I']]*r*k)/e), (int)((w[m['J']]*r*k)/e), (int)((w[m['K']]*r*k)/e), (int)((w[m['L']]*r*k)/e), (int)((w[m['M']]*r*k)/e), (int)((w[m['N']]*r*k)/e), (int)((w[m['O']]*r*k)/e), (int)((w[m['P']]*r*k)/e), (int)((w[m['Q']]*r*k)/e), - // (int)((w[m['R']]*r*k)/e), (int)((w[m['S']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['U']]*r*k)/e), (int)((w[m['V']]*r*k)/e), (int)((w[m['W']]*r*k)/e), (int)((w[m['X']]*r*k)/e), (int)((w[m['Y']]*r*k)/e), (int)((w[m['Z']]*r*k)/e), (int)((w[m['[']]*r*k)/e), - // (int)((w[m['\\']]*r*k)/e), (int)((w[m[']']]*r*k)/e), (int)((w[m['^']]*r*k)/e), (int)((w[m['_']]*r*k)/e), (int)((w[m['`']]*r*k)/e), (int)((w[m['a']]*r*k)/e), (int)((w[m['b']]*r*k)/e), (int)((w[m['c']]*r*k)/e), (int)((w[m['d']]*r*k)/e), (int)((w[m['e']]*r*k)/e), - // (int)((w[m['f']]*r*k)/e), (int)((w[m['g']]*r*k)/e), (int)((w[m['h']]*r*k)/e), (int)((w[m['i']]*r*k)/e), (int)((w[m['j']]*r*k)/e), (int)((w[m['k']]*r*k)/e), (int)((w[m['l']]*r*k)/e), (int)((w[m['m']]*r*k)/e), (int)((w[m['n']]*r*k)/e), (int)((w[m['o']]*r*k)/e), - // (int)((w[m['p']]*r*k)/e), (int)((w[m['q']]*r*k)/e), (int)((w[m['r']]*r*k)/e), (int)((w[m['s']]*r*k)/e), (int)((w[m['t']]*r*k)/e), (int)((w[m['u']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['w']]*r*k)/e), (int)((w[m['x']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), - // (int)((w[m['z']]*r*k)/e), (int)((w[m['{']]*r*k)/e), (int)((w[m['|']]*r*k)/e), (int)((w[m['}']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['€']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['‚']]*r*k)/e), (int)((w[m['ƒ']]*r*k)/e), - // (int)((w[m[' ']]*r*k)/e), (int)((w[m['„']]*r*k)/e), (int)((w[m['…']]*r*k)/e), (int)((w[m['†']]*r*k)/e), (int)((w[m['‡']]*r*k)/e), (int)((w[m['ˆ']]*r*k)/e), (int)((w[m['‰']]*r*k)/e), (int)((w[m['Š']]*r*k)/e), (int)((w[m['‹']]*r*k)/e), (int)((w[m['Œ']]*r*k)/e), - // (int)((w[m[' ']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['Ž']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['‘']]*r*k)/e), (int)((w[m['’']]*r*k)/e), (int)((w[m['“']]*r*k)/e), (int)((w[m['”']]*r*k)/e), (int)((w[m['•']]*r*k)/e), - // (int)((w[m['–']]*r*k)/e), (int)((w[m['—']]*r*k)/e), (int)((w[m['˜']]*r*k)/e), (int)((w[m['™']]*r*k)/e), (int)((w[m['š']]*r*k)/e), (int)((w[m['›']]*r*k)/e), (int)((w[m['œ']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['ž']]*r*k)/e), (int)((w[m['Ÿ']]*r*k)/e), - // (int)((w[m['¡']]*r*k)/e), (int)((w[m['¢']]*r*k)/e), (int)((w[m['£']]*r*k)/e), (int)((w[m['¤']]*r*k)/e), (int)((w[m['¥']]*r*k)/e), (int)((w[m['¦']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['¨']]*r*k)/e), (int)((w[m['©']]*r*k)/e), (int)((w[m['ª']]*r*k)/e), - // (int)((w[m['«']]*r*k)/e), (int)((w[m['¬']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['¯']]*r*k)/e), (int)((w[m['°']]*r*k)/e), (int)((w[m['±']]*r*k)/e), (int)((w[m['²']]*r*k)/e), (int)((w[m['³']]*r*k)/e), (int)((w[m['´']]*r*k)/e), - // (int)((w[m['µ']]*r*k)/e), (int)((w[m['¶']]*r*k)/e), (int)((w[m['·']]*r*k)/e), (int)((w[m['¸']]*r*k)/e), (int)((w[m['¹']]*r*k)/e), (int)((w[m['º']]*r*k)/e), (int)((w[m['»']]*r*k)/e), (int)((w[m['¼']]*r*k)/e), (int)((w[m['½']]*r*k)/e), (int)((w[m['¾']]*r*k)/e), - // (int)((w[m['¿']]*r*k)/e), (int)((w[m['À']]*r*k)/e), (int)((w[m['Á']]*r*k)/e), (int)((w[m['Â']]*r*k)/e), (int)((w[m['Ã']]*r*k)/e), (int)((w[m['Ä']]*r*k)/e), (int)((w[m['Å']]*r*k)/e), (int)((w[m['Æ']]*r*k)/e), (int)((w[m['Ç']]*r*k)/e), (int)((w[m['È']]*r*k)/e), - // (int)((w[m['É']]*r*k)/e), (int)((w[m['Ê']]*r*k)/e), (int)((w[m['Ë']]*r*k)/e), (int)((w[m['Ì']]*r*k)/e), (int)((w[m['Í']]*r*k)/e), (int)((w[m['Î']]*r*k)/e), (int)((w[m['Ï']]*r*k)/e), (int)((w[m['Ð']]*r*k)/e), (int)((w[m['Ñ']]*r*k)/e), (int)((w[m['Ò']]*r*k)/e), - // (int)((w[m['Ó']]*r*k)/e), (int)((w[m['Ô']]*r*k)/e), (int)((w[m['Õ']]*r*k)/e), (int)((w[m['Ö']]*r*k)/e), (int)((w[m['×']]*r*k)/e), (int)((w[m['Ø']]*r*k)/e), (int)((w[m['Ù']]*r*k)/e), (int)((w[m['Ú']]*r*k)/e), (int)((w[m['Û']]*r*k)/e), (int)((w[m['Ü']]*r*k)/e), - // (int)((w[m['Ý']]*r*k)/e), (int)((w[m['Þ']]*r*k)/e), (int)((w[m['ß']]*r*k)/e), (int)((w[m['à']]*r*k)/e), (int)((w[m['á']]*r*k)/e), (int)((w[m['â']]*r*k)/e), (int)((w[m['ã']]*r*k)/e), (int)((w[m['ä']]*r*k)/e), (int)((w[m['å']]*r*k)/e), (int)((w[m['æ']]*r*k)/e), - // (int)((w[m['ç']]*r*k)/e), (int)((w[m['è']]*r*k)/e), (int)((w[m['é']]*r*k)/e), (int)((w[m['ê']]*r*k)/e), (int)((w[m['ë']]*r*k)/e), (int)((w[m['ì']]*r*k)/e), (int)((w[m['í']]*r*k)/e), (int)((w[m['î']]*r*k)/e), (int)((w[m['ï']]*r*k)/e), (int)((w[m['ð']]*r*k)/e), - // (int)((w[m['ñ']]*r*k)/e), (int)((w[m['ò']]*r*k)/e), (int)((w[m['ó']]*r*k)/e), (int)((w[m['ô']]*r*k)/e), (int)((w[m['õ']]*r*k)/e), (int)((w[m['ö']]*r*k)/e), (int)((w[m['÷']]*r*k)/e), (int)((w[m['ø']]*r*k)/e), (int)((w[m['ù']]*r*k)/e), (int)((w[m['ú']]*r*k)/e), - // (int)((w[m['û']]*r*k)/e), (int)((w[m['ü']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['þ']]*r*k)/e), (int)((w[m['ÿ']]*r*k)/e) - //}; - widths = new int[] - { - (int)(w[m[' ']]*r*k/e), (int)(w[m['!']]*r*k/e), (int)(w[m['"']]*r*k/e), (int)(w[m['#']]*r*k/e), (int)(w[m['$']]*r*k/e), (int)(w[m['%']]*r*k/e), (int)(w[m['&']]*r*k/e), (int)(w[m['\'']]*r*k/e), (int)(w[m['(']]*r*k/e), (int)(w[m[')']]*r*k/e), - (int)(w[m['*']]*r*k/e), (int)(w[m['+']]*r*k/e), (int)(w[m[',']]*r*k/e), (int)(w[m['-']]*r*k/e), (int)(w[m['.']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m['0']]*r*k/e), (int)(w[m['1']]*r*k/e), (int)(w[m['2']]*r*k/e), (int)(w[m['3']]*r*k/e), - (int)(w[m['4']]*r*k/e), (int)(w[m['5']]*r*k/e), (int)(w[m['6']]*r*k/e), (int)(w[m['7']]*r*k/e), (int)(w[m['8']]*r*k/e), (int)(w[m['8']]*r*k/e), (int)(w[m[':']]*r*k/e), (int)(w[m[';']]*r*k/e), (int)(w[m['<']]*r*k/e), (int)(w[m['=']]*r*k/e), - (int)(w[m['>']]*r*k/e), (int)(w[m['?']]*r*k/e), (int)(w[m['@']]*r*k/e), (int)(w[m['A']]*r*k/e), (int)(w[m['B']]*r*k/e), (int)(w[m['C']]*r*k/e), (int)(w[m['D']]*r*k/e), (int)(w[m['E']]*r*k/e), (int)(w[m['F']]*r*k/e), (int)(w[m['G']]*r*k/e), - (int)(w[m['H']]*r*k/e), (int)(w[m['I']]*r*k/e), (int)(w[m['J']]*r*k/e), (int)(w[m['K']]*r*k/e), (int)(w[m['L']]*r*k/e), (int)(w[m['M']]*r*k/e), (int)(w[m['N']]*r*k/e), (int)(w[m['O']]*r*k/e), (int)(w[m['P']]*r*k/e), (int)(w[m['Q']]*r*k/e), - (int)(w[m['R']]*r*k/e), (int)(w[m['S']]*r*k/e), (int)(w[m['W']]*r*k/e), (int)(w[m['U']]*r*k/e), (int)(w[m['V']]*r*k/e), (int)(w[m['W']]*r*k/e), (int)(w[m['X']]*r*k/e), (int)(w[m['Y']]*r*k/e), (int)(w[m['Z']]*r*k/e), (int)(w[m['[']]*r*k/e), - (int)(w[m['\\']]*r*k/e), (int)(w[m[']']]*r*k/e), (int)(w[m['^']]*r*k/e), (int)(w[m['_']]*r*k/e), (int)(w[m['`']]*r*k/e), (int)(w[m['a']]*r*k/e), (int)(w[m['b']]*r*k/e), (int)(w[m['c']]*r*k/e), (int)(w[m['d']]*r*k/e), (int)(w[m['e']]*r*k/e), - (int)(w[m['f']]*r*k/e), (int)(w[m['g']]*r*k/e), (int)(w[m['h']]*r*k/e), (int)(w[m['i']]*r*k/e), (int)(w[m['j']]*r*k/e), (int)(w[m['k']]*r*k/e), (int)(w[m['l']]*r*k/e), (int)(w[m['m']]*r*k/e), (int)(w[m['n']]*r*k/e), (int)(w[m['o']]*r*k/e), - (int)(w[m['p']]*r*k/e), (int)(w[m['q']]*r*k/e), (int)(w[m['r']]*r*k/e), (int)(w[m['s']]*r*k/e), (int)(w[m['t']]*r*k/e), (int)(w[m['u']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m['w']]*r*k/e), (int)(w[m['x']]*r*k/e), (int)(w[m[' ']]*r*k/e), - (int)(w[m['z']]*r*k/e), (int)(w[m['{']]*r*k/e), (int)(w[m['|']]*r*k/e), (int)(w[m['}']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), - (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), - (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), - (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), - (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), - (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), - (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), - (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), - (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), - (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), - (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), - (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), - (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), - (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e) - }; + ////can Remove this construcotr probably. + //public PdfFontWidths(int objectNumber, Dictionary w, Dictionary m, int version = 0) + // : base(objectNumber, version) + //{ + // float e = 2048; + // float d = 72f / 96f; + // float r = e * d; + // float k = 1000f; + // var l = (int)(w[m[' ']] * r * k / e); + // List exsist = new List(); + // List no = new List(); + // for (int i = 0; i <= 900; i++) + // { + // char c = (char)i; + // if (m.ContainsKey(c)) + // { + // exsist.Add(c); + // } + // else + // { + // no.Add(c); + // } + // } + // //this.widths = new int[] + // //{ + // // (int)((w[m[' ']]*r*k)/e), (int)((w[m['!']]*r*k)/e), (int)((w[m['"']]*r*k)/e), (int)((w[m['#']]*r*k)/e), (int)((w[m['$']]*r*k)/e), (int)((w[m['%']]*r*k)/e), (int)((w[m['&']]*r*k)/e), (int)((w[m['\'']]*r*k)/e), (int)((w[m['(']]*r*k)/e), (int)((w[m[')']]*r*k)/e), + // // (int)((w[m['*']]*r*k)/e), (int)((w[m['+']]*r*k)/e), (int)((w[m[',']]*r*k)/e), (int)((w[m['-']]*r*k)/e), (int)((w[m['.']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['0']]*r*k)/e), (int)((w[m['1']]*r*k)/e), (int)((w[m['2']]*r*k)/e), (int)((w[m['3']]*r*k)/e), + // // (int)((w[m['4']]*r*k)/e), (int)((w[m['5']]*r*k)/e), (int)((w[m['6']]*r*k)/e), (int)((w[m['7']]*r*k)/e), (int)((w[m['8']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m[':']]*r*k)/e), (int)((w[m[';']]*r*k)/e), (int)((w[m['<']]*r*k)/e), (int)((w[m['=']]*r*k)/e), + // // (int)((w[m['>']]*r*k)/e), (int)((w[m['?']]*r*k)/e), (int)((w[m['@']]*r*k)/e), (int)((w[m['A']]*r*k)/e), (int)((w[m['B']]*r*k)/e), (int)((w[m['C']]*r*k)/e), (int)((w[m['D']]*r*k)/e), (int)((w[m['E']]*r*k)/e), (int)((w[m['F']]*r*k)/e), (int)((w[m['G']]*r*k)/e), + // // (int)((w[m['H']]*r*k)/e), (int)((w[m['I']]*r*k)/e), (int)((w[m['J']]*r*k)/e), (int)((w[m['K']]*r*k)/e), (int)((w[m['L']]*r*k)/e), (int)((w[m['M']]*r*k)/e), (int)((w[m['N']]*r*k)/e), (int)((w[m['O']]*r*k)/e), (int)((w[m['P']]*r*k)/e), (int)((w[m['Q']]*r*k)/e), + // // (int)((w[m['R']]*r*k)/e), (int)((w[m['S']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['U']]*r*k)/e), (int)((w[m['V']]*r*k)/e), (int)((w[m['W']]*r*k)/e), (int)((w[m['X']]*r*k)/e), (int)((w[m['Y']]*r*k)/e), (int)((w[m['Z']]*r*k)/e), (int)((w[m['[']]*r*k)/e), + // // (int)((w[m['\\']]*r*k)/e), (int)((w[m[']']]*r*k)/e), (int)((w[m['^']]*r*k)/e), (int)((w[m['_']]*r*k)/e), (int)((w[m['`']]*r*k)/e), (int)((w[m['a']]*r*k)/e), (int)((w[m['b']]*r*k)/e), (int)((w[m['c']]*r*k)/e), (int)((w[m['d']]*r*k)/e), (int)((w[m['e']]*r*k)/e), + // // (int)((w[m['f']]*r*k)/e), (int)((w[m['g']]*r*k)/e), (int)((w[m['h']]*r*k)/e), (int)((w[m['i']]*r*k)/e), (int)((w[m['j']]*r*k)/e), (int)((w[m['k']]*r*k)/e), (int)((w[m['l']]*r*k)/e), (int)((w[m['m']]*r*k)/e), (int)((w[m['n']]*r*k)/e), (int)((w[m['o']]*r*k)/e), + // // (int)((w[m['p']]*r*k)/e), (int)((w[m['q']]*r*k)/e), (int)((w[m['r']]*r*k)/e), (int)((w[m['s']]*r*k)/e), (int)((w[m['t']]*r*k)/e), (int)((w[m['u']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['w']]*r*k)/e), (int)((w[m['x']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), + // // (int)((w[m['z']]*r*k)/e), (int)((w[m['{']]*r*k)/e), (int)((w[m['|']]*r*k)/e), (int)((w[m['}']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['€']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['‚']]*r*k)/e), (int)((w[m['ƒ']]*r*k)/e), + // // (int)((w[m[' ']]*r*k)/e), (int)((w[m['„']]*r*k)/e), (int)((w[m['…']]*r*k)/e), (int)((w[m['†']]*r*k)/e), (int)((w[m['‡']]*r*k)/e), (int)((w[m['ˆ']]*r*k)/e), (int)((w[m['‰']]*r*k)/e), (int)((w[m['Š']]*r*k)/e), (int)((w[m['‹']]*r*k)/e), (int)((w[m['Œ']]*r*k)/e), + // // (int)((w[m[' ']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['Ž']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['‘']]*r*k)/e), (int)((w[m['’']]*r*k)/e), (int)((w[m['“']]*r*k)/e), (int)((w[m['”']]*r*k)/e), (int)((w[m['•']]*r*k)/e), + // // (int)((w[m['–']]*r*k)/e), (int)((w[m['—']]*r*k)/e), (int)((w[m['˜']]*r*k)/e), (int)((w[m['™']]*r*k)/e), (int)((w[m['š']]*r*k)/e), (int)((w[m['›']]*r*k)/e), (int)((w[m['œ']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['ž']]*r*k)/e), (int)((w[m['Ÿ']]*r*k)/e), + // // (int)((w[m['¡']]*r*k)/e), (int)((w[m['¢']]*r*k)/e), (int)((w[m['£']]*r*k)/e), (int)((w[m['¤']]*r*k)/e), (int)((w[m['¥']]*r*k)/e), (int)((w[m['¦']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['¨']]*r*k)/e), (int)((w[m['©']]*r*k)/e), (int)((w[m['ª']]*r*k)/e), + // // (int)((w[m['«']]*r*k)/e), (int)((w[m['¬']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['¯']]*r*k)/e), (int)((w[m['°']]*r*k)/e), (int)((w[m['±']]*r*k)/e), (int)((w[m['²']]*r*k)/e), (int)((w[m['³']]*r*k)/e), (int)((w[m['´']]*r*k)/e), + // // (int)((w[m['µ']]*r*k)/e), (int)((w[m['¶']]*r*k)/e), (int)((w[m['·']]*r*k)/e), (int)((w[m['¸']]*r*k)/e), (int)((w[m['¹']]*r*k)/e), (int)((w[m['º']]*r*k)/e), (int)((w[m['»']]*r*k)/e), (int)((w[m['¼']]*r*k)/e), (int)((w[m['½']]*r*k)/e), (int)((w[m['¾']]*r*k)/e), + // // (int)((w[m['¿']]*r*k)/e), (int)((w[m['À']]*r*k)/e), (int)((w[m['Á']]*r*k)/e), (int)((w[m['Â']]*r*k)/e), (int)((w[m['Ã']]*r*k)/e), (int)((w[m['Ä']]*r*k)/e), (int)((w[m['Å']]*r*k)/e), (int)((w[m['Æ']]*r*k)/e), (int)((w[m['Ç']]*r*k)/e), (int)((w[m['È']]*r*k)/e), + // // (int)((w[m['É']]*r*k)/e), (int)((w[m['Ê']]*r*k)/e), (int)((w[m['Ë']]*r*k)/e), (int)((w[m['Ì']]*r*k)/e), (int)((w[m['Í']]*r*k)/e), (int)((w[m['Î']]*r*k)/e), (int)((w[m['Ï']]*r*k)/e), (int)((w[m['Ð']]*r*k)/e), (int)((w[m['Ñ']]*r*k)/e), (int)((w[m['Ò']]*r*k)/e), + // // (int)((w[m['Ó']]*r*k)/e), (int)((w[m['Ô']]*r*k)/e), (int)((w[m['Õ']]*r*k)/e), (int)((w[m['Ö']]*r*k)/e), (int)((w[m['×']]*r*k)/e), (int)((w[m['Ø']]*r*k)/e), (int)((w[m['Ù']]*r*k)/e), (int)((w[m['Ú']]*r*k)/e), (int)((w[m['Û']]*r*k)/e), (int)((w[m['Ü']]*r*k)/e), + // // (int)((w[m['Ý']]*r*k)/e), (int)((w[m['Þ']]*r*k)/e), (int)((w[m['ß']]*r*k)/e), (int)((w[m['à']]*r*k)/e), (int)((w[m['á']]*r*k)/e), (int)((w[m['â']]*r*k)/e), (int)((w[m['ã']]*r*k)/e), (int)((w[m['ä']]*r*k)/e), (int)((w[m['å']]*r*k)/e), (int)((w[m['æ']]*r*k)/e), + // // (int)((w[m['ç']]*r*k)/e), (int)((w[m['è']]*r*k)/e), (int)((w[m['é']]*r*k)/e), (int)((w[m['ê']]*r*k)/e), (int)((w[m['ë']]*r*k)/e), (int)((w[m['ì']]*r*k)/e), (int)((w[m['í']]*r*k)/e), (int)((w[m['î']]*r*k)/e), (int)((w[m['ï']]*r*k)/e), (int)((w[m['ð']]*r*k)/e), + // // (int)((w[m['ñ']]*r*k)/e), (int)((w[m['ò']]*r*k)/e), (int)((w[m['ó']]*r*k)/e), (int)((w[m['ô']]*r*k)/e), (int)((w[m['õ']]*r*k)/e), (int)((w[m['ö']]*r*k)/e), (int)((w[m['÷']]*r*k)/e), (int)((w[m['ø']]*r*k)/e), (int)((w[m['ù']]*r*k)/e), (int)((w[m['ú']]*r*k)/e), + // // (int)((w[m['û']]*r*k)/e), (int)((w[m['ü']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['þ']]*r*k)/e), (int)((w[m['ÿ']]*r*k)/e) + // //}; + // widths = new int[] + // { + // (int)(w[m[' ']]*r*k/e), (int)(w[m['!']]*r*k/e), (int)(w[m['"']]*r*k/e), (int)(w[m['#']]*r*k/e), (int)(w[m['$']]*r*k/e), (int)(w[m['%']]*r*k/e), (int)(w[m['&']]*r*k/e), (int)(w[m['\'']]*r*k/e), (int)(w[m['(']]*r*k/e), (int)(w[m[')']]*r*k/e), + // (int)(w[m['*']]*r*k/e), (int)(w[m['+']]*r*k/e), (int)(w[m[',']]*r*k/e), (int)(w[m['-']]*r*k/e), (int)(w[m['.']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m['0']]*r*k/e), (int)(w[m['1']]*r*k/e), (int)(w[m['2']]*r*k/e), (int)(w[m['3']]*r*k/e), + // (int)(w[m['4']]*r*k/e), (int)(w[m['5']]*r*k/e), (int)(w[m['6']]*r*k/e), (int)(w[m['7']]*r*k/e), (int)(w[m['8']]*r*k/e), (int)(w[m['8']]*r*k/e), (int)(w[m[':']]*r*k/e), (int)(w[m[';']]*r*k/e), (int)(w[m['<']]*r*k/e), (int)(w[m['=']]*r*k/e), + // (int)(w[m['>']]*r*k/e), (int)(w[m['?']]*r*k/e), (int)(w[m['@']]*r*k/e), (int)(w[m['A']]*r*k/e), (int)(w[m['B']]*r*k/e), (int)(w[m['C']]*r*k/e), (int)(w[m['D']]*r*k/e), (int)(w[m['E']]*r*k/e), (int)(w[m['F']]*r*k/e), (int)(w[m['G']]*r*k/e), + // (int)(w[m['H']]*r*k/e), (int)(w[m['I']]*r*k/e), (int)(w[m['J']]*r*k/e), (int)(w[m['K']]*r*k/e), (int)(w[m['L']]*r*k/e), (int)(w[m['M']]*r*k/e), (int)(w[m['N']]*r*k/e), (int)(w[m['O']]*r*k/e), (int)(w[m['P']]*r*k/e), (int)(w[m['Q']]*r*k/e), + // (int)(w[m['R']]*r*k/e), (int)(w[m['S']]*r*k/e), (int)(w[m['W']]*r*k/e), (int)(w[m['U']]*r*k/e), (int)(w[m['V']]*r*k/e), (int)(w[m['W']]*r*k/e), (int)(w[m['X']]*r*k/e), (int)(w[m['Y']]*r*k/e), (int)(w[m['Z']]*r*k/e), (int)(w[m['[']]*r*k/e), + // (int)(w[m['\\']]*r*k/e), (int)(w[m[']']]*r*k/e), (int)(w[m['^']]*r*k/e), (int)(w[m['_']]*r*k/e), (int)(w[m['`']]*r*k/e), (int)(w[m['a']]*r*k/e), (int)(w[m['b']]*r*k/e), (int)(w[m['c']]*r*k/e), (int)(w[m['d']]*r*k/e), (int)(w[m['e']]*r*k/e), + // (int)(w[m['f']]*r*k/e), (int)(w[m['g']]*r*k/e), (int)(w[m['h']]*r*k/e), (int)(w[m['i']]*r*k/e), (int)(w[m['j']]*r*k/e), (int)(w[m['k']]*r*k/e), (int)(w[m['l']]*r*k/e), (int)(w[m['m']]*r*k/e), (int)(w[m['n']]*r*k/e), (int)(w[m['o']]*r*k/e), + // (int)(w[m['p']]*r*k/e), (int)(w[m['q']]*r*k/e), (int)(w[m['r']]*r*k/e), (int)(w[m['s']]*r*k/e), (int)(w[m['t']]*r*k/e), (int)(w[m['u']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m['w']]*r*k/e), (int)(w[m['x']]*r*k/e), (int)(w[m[' ']]*r*k/e), + // (int)(w[m['z']]*r*k/e), (int)(w[m['{']]*r*k/e), (int)(w[m['|']]*r*k/e), (int)(w[m['}']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), + // (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), + // (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), + // (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), + // (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), + // (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), + // (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), + // (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), + // (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), + // (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), + // (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), + // (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), + // (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), + // (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e) + // }; - } + //} internal override string RenderDictionary() { diff --git a/src/EPPlus.Export.Pdf/DocumentObjects/PdfBorderRenderer.cs b/src/EPPlus.Export.Pdf/DocumentObjects/PdfBorderRenderer.cs index 0971cabcd7..15ee672a93 100644 --- a/src/EPPlus.Export.Pdf/DocumentObjects/PdfBorderRenderer.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/PdfBorderRenderer.cs @@ -12,7 +12,6 @@ Date Author Change *************************************************************************************************/ using EPPlus.Export.Pdf.Helpers; using EPPlus.Export.Pdf.Layout; -using OfficeOpenXml.Style; namespace EPPlus.Export.Pdf.DocumentObjects { diff --git a/src/EPPlus.Export.Pdf/EPPlus.Export.Pdf.csproj b/src/EPPlus.Export.Pdf/EPPlus.Export.Pdf.csproj index 9b49eee2a5..1aa441393c 100644 --- a/src/EPPlus.Export.Pdf/EPPlus.Export.Pdf.csproj +++ b/src/EPPlus.Export.Pdf/EPPlus.Export.Pdf.csproj @@ -16,7 +16,6 @@ - diff --git a/src/EPPlus.Export.Pdf/Helpers/EPPlusEnums.cs b/src/EPPlus.Export.Pdf/Enums/EPPlusEnums.cs similarity index 99% rename from src/EPPlus.Export.Pdf/Helpers/EPPlusEnums.cs rename to src/EPPlus.Export.Pdf/Enums/EPPlusEnums.cs index 68e7afbef7..a98efa5b4e 100644 --- a/src/EPPlus.Export.Pdf/Helpers/EPPlusEnums.cs +++ b/src/EPPlus.Export.Pdf/Enums/EPPlusEnums.cs @@ -266,5 +266,5 @@ public enum ExcelBorderStyle /// Slanted Dash Dot /// SlantDashDot - }; + } } diff --git a/src/EPPlus.Export.Pdf/ExcelPdf.cs b/src/EPPlus.Export.Pdf/ExcelPdf.cs index ca409b99bb..0db57f517b 100644 --- a/src/EPPlus.Export.Pdf/ExcelPdf.cs +++ b/src/EPPlus.Export.Pdf/ExcelPdf.cs @@ -14,8 +14,8 @@ Date Author Change using EPPlus.Export.Pdf.DocumentObjects; using EPPlus.Export.Pdf.Resources; using EPPlus.Export.Pdf.Settings; +using EPPlus.Export.Pdf.Helpers; using EPPlus.Graphics; -using OfficeOpenXml.Style; using System.Collections.Generic; using System.IO; using System.Linq; diff --git a/src/EPPlus.Export.Pdf/Layout/CellAlignmentData.cs b/src/EPPlus.Export.Pdf/Layout/CellAlignmentData.cs index 265b77e4c3..eeea904435 100644 --- a/src/EPPlus.Export.Pdf/Layout/CellAlignmentData.cs +++ b/src/EPPlus.Export.Pdf/Layout/CellAlignmentData.cs @@ -10,7 +10,7 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -using EPPlus.Export.Pdf.Helpers +using EPPlus.Export.Pdf.Helpers; namespace EPPlus.Export.Pdf.Layout { diff --git a/src/EPPlus.Export.Pdf/Layout/GridLineData.cs b/src/EPPlus.Export.Pdf/Layout/GridLineData.cs new file mode 100644 index 0000000000..fab9877ebd --- /dev/null +++ b/src/EPPlus.Export.Pdf/Layout/GridLineData.cs @@ -0,0 +1,30 @@ +/************************************************************************************************* + 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 + *************************************************************************************************/ +namespace EPPlus.Export.Pdf.Layout +{ + internal class GridLine + { + public static double Width = 0.125d; + public static double HalfWidth = Width / 2d; + public static double FourthWidth = Width / 4d; + public double X1; + public double Y1; + public double X2; + public double Y2; + + public GridLine(double x1, double y1, double x2, double y2) + { + X1 = x1; Y1 = y1; X2 = x2; Y2 = y2; + } + } +} diff --git a/src/EPPlus.Export.Pdf/Layout/PdfBorderData.cs b/src/EPPlus.Export.Pdf/Layout/PdfBorderData.cs index 058037c47a..f665b97da0 100644 --- a/src/EPPlus.Export.Pdf/Layout/PdfBorderData.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfBorderData.cs @@ -10,8 +10,8 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -using OfficeOpenXml.Style; using System.Drawing; +using EPPlus.Export.Pdf.Helpers; namespace EPPlus.Export.Pdf.Layout { diff --git a/src/EPPlus.Export.Pdf/Layout/PdfCell.cs b/src/EPPlus.Export.Pdf/Layout/PdfCell.cs new file mode 100644 index 0000000000..67d38dae19 --- /dev/null +++ b/src/EPPlus.Export.Pdf/Layout/PdfCell.cs @@ -0,0 +1,26 @@ +using EPPlus.Fonts.OpenType.Integration; +using System.Collections.Generic; + +namespace EPPlus.Export.Pdf.Layout +{ + internal class PdfCellBase + { + public string Name { get; set; } + public bool Hidden; + public PdfCellAlignmentData ContentAligmnet; + public List TextFragments { get; set; } + public List ShapedTexts { get; set; } + public TextLineCollection TextLines { get; set; } + public string Text { get; set; } + + public double TotalTextLength { get; set; } + public double ColumnWidth { get; set; } + public double Width { get; set; } + public double Height { get; set; } + + public TextLayoutEngine TextLayoutEngine { get; set; } + + public bool IsPrintTitleRow; + public bool IsPrintTitleCol; + } +} diff --git a/src/EPPlus.Export.Pdf/Layout/PdfCellBorderLayout.cs b/src/EPPlus.Export.Pdf/Layout/PdfCellBorderLayout.cs index f671d04c80..6d4fc415a7 100644 --- a/src/EPPlus.Export.Pdf/Layout/PdfCellBorderLayout.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfCellBorderLayout.cs @@ -10,6 +10,7 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ +using EPPlus.Export.Pdf.Enums; using EPPlus.Export.Pdf.Helpers; using EPPlus.Graphics; using System.Diagnostics; @@ -86,99 +87,99 @@ public PdfCellBorderLayout(PdfCellStyle style, bool isMerged, MergedCellCorners - public PdfCellBorderLayout(ExcelRangeBase cell, PdfCellStyle tableStyle, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) - : base(x, y-height, width, height, scaleX, scaleY, rotation, parent) - { - this.cell = cell; - this.TableStyle = tableStyle; - if (cell != null) - { - IsMerged = cell.Merge; - BorderData = new PdfCellBordersData(); - } - } + //public PdfCellBorderLayout(ExcelRangeBase cell, PdfCellStyle tableStyle, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) + // : base(x, y-height, width, height, scaleX, scaleY, rotation, parent) + //{ + // this.cell = cell; + // this.TableStyle = tableStyle; + // if (cell != null) + // { + // IsMerged = cell.Merge; + // BorderData = new PdfCellBordersData(); + // } + //} - public void InitEdgeBorders(ExcelRangeBase cell) - { - if (cell != null) - { - //BorderData.Top = new PdfCellBorderData(LineType.Top); - BorderData.Top.BorderStyle = TableStyle.xfTop.Style == ExcelBorderStyle.None ? ((TableStyle.dxfTop != null && TableStyle.dxfTop.HasValue) ? (ExcelBorderStyle)TableStyle.dxfTop.Style : ExcelBorderStyle.None ) : TableStyle.xfTop.Style; - BorderData.Top.BorderColor = TableStyle.dxfTop != null ? PdfColor.SetColorFromHex(TableStyle.dxfTop.Color.LookupColor(TableStyle.dxfTop)) : PdfColor.SetColorFromHex(cell.Style.Border.Top.Color.LookupColor(cell.Style.Border)); - BorderData.Top.X = LocalPosition.X; - BorderData.Top.Y = LocalPosition.Y; - BorderData.Top.Width = Size.X; - BorderData.Top.Height = Size.Y; - //BorderData.Bottom = new PdfCellBorderData(LineType.Bottom); - BorderData.Bottom.BorderStyle = TableStyle.xfBottom.Style == ExcelBorderStyle.None ? ((TableStyle.dxfBottom != null && TableStyle.dxfBottom.HasValue) ? (ExcelBorderStyle)TableStyle.dxfBottom.Style : ExcelBorderStyle.None) : TableStyle.xfBottom.Style; - BorderData.Bottom.BorderColor = TableStyle.dxfBottom != null ? PdfColor.SetColorFromHex(TableStyle.dxfBottom.Color.LookupColor(TableStyle.dxfBottom)) : PdfColor.SetColorFromHex(cell.Style.Border.Bottom.Color.LookupColor(cell.Style.Border)); - BorderData.Bottom.X = LocalPosition.X; - BorderData.Bottom.Y = LocalPosition.Y; - BorderData.Bottom.Width = Size.X; - BorderData.Bottom.Height = Size.Y; - //BorderData.Left = new PdfCellBorderData(LineType.Left); - BorderData.Left.BorderStyle = TableStyle.xfLeft.Style == ExcelBorderStyle.None ? ((TableStyle.dxfLeft != null && TableStyle.dxfLeft.HasValue) ? (ExcelBorderStyle)TableStyle.dxfLeft.Style : ExcelBorderStyle.None) : TableStyle.xfLeft.Style; - BorderData.Left.BorderColor = TableStyle.dxfLeft != null ? PdfColor.SetColorFromHex(TableStyle.dxfLeft.Color.LookupColor(TableStyle.dxfLeft)) : PdfColor.SetColorFromHex(cell.Style.Border.Left.Color.LookupColor(cell.Style.Border)); - BorderData.Left.X = LocalPosition.X; - BorderData.Left.Y = LocalPosition.Y; - BorderData.Left.Width = Size.X; - BorderData.Left.Height = Size.Y; - //BorderData.Right = new PdfCellBorderData(LineType.Right); - BorderData.Right.BorderStyle = TableStyle.xfRight.Style == ExcelBorderStyle.None ? ((TableStyle.dxfRight != null && TableStyle.dxfRight.HasValue) ? (ExcelBorderStyle)TableStyle.dxfRight.Style : ExcelBorderStyle.None) : TableStyle.xfRight.Style; - BorderData.Right.BorderColor = TableStyle.dxfRight != null ? PdfColor.SetColorFromHex(TableStyle.dxfRight.Color.LookupColor(TableStyle.dxfRight)) : PdfColor.SetColorFromHex(cell.Style.Border.Right.Color.LookupColor(cell.Style.Border)); - BorderData.Right.X = LocalPosition.X; - BorderData.Right.Y = LocalPosition.Y; - BorderData.Right.Width = Size.X; - BorderData.Right.Height = Size.Y; + //public void InitEdgeBorders(ExcelRangeBase cell) + //{ + // if (cell != null) + // { + // //BorderData.Top = new PdfCellBorderData(LineType.Top); + // BorderData.Top.BorderStyle = TableStyle.xfTop.Style == ExcelBorderStyle.None ? ((TableStyle.dxfTop != null && TableStyle.dxfTop.HasValue) ? (ExcelBorderStyle)TableStyle.dxfTop.Style : ExcelBorderStyle.None ) : TableStyle.xfTop.Style; + // BorderData.Top.BorderColor = TableStyle.dxfTop != null ? PdfColor.SetColorFromHex(TableStyle.dxfTop.Color.LookupColor(TableStyle.dxfTop)) : PdfColor.SetColorFromHex(cell.Style.Border.Top.Color.LookupColor(cell.Style.Border)); + // BorderData.Top.X = LocalPosition.X; + // BorderData.Top.Y = LocalPosition.Y; + // BorderData.Top.Width = Size.X; + // BorderData.Top.Height = Size.Y; + // //BorderData.Bottom = new PdfCellBorderData(LineType.Bottom); + // BorderData.Bottom.BorderStyle = TableStyle.xfBottom.Style == ExcelBorderStyle.None ? ((TableStyle.dxfBottom != null && TableStyle.dxfBottom.HasValue) ? (ExcelBorderStyle)TableStyle.dxfBottom.Style : ExcelBorderStyle.None) : TableStyle.xfBottom.Style; + // BorderData.Bottom.BorderColor = TableStyle.dxfBottom != null ? PdfColor.SetColorFromHex(TableStyle.dxfBottom.Color.LookupColor(TableStyle.dxfBottom)) : PdfColor.SetColorFromHex(cell.Style.Border.Bottom.Color.LookupColor(cell.Style.Border)); + // BorderData.Bottom.X = LocalPosition.X; + // BorderData.Bottom.Y = LocalPosition.Y; + // BorderData.Bottom.Width = Size.X; + // BorderData.Bottom.Height = Size.Y; + // //BorderData.Left = new PdfCellBorderData(LineType.Left); + // BorderData.Left.BorderStyle = TableStyle.xfLeft.Style == ExcelBorderStyle.None ? ((TableStyle.dxfLeft != null && TableStyle.dxfLeft.HasValue) ? (ExcelBorderStyle)TableStyle.dxfLeft.Style : ExcelBorderStyle.None) : TableStyle.xfLeft.Style; + // BorderData.Left.BorderColor = TableStyle.dxfLeft != null ? PdfColor.SetColorFromHex(TableStyle.dxfLeft.Color.LookupColor(TableStyle.dxfLeft)) : PdfColor.SetColorFromHex(cell.Style.Border.Left.Color.LookupColor(cell.Style.Border)); + // BorderData.Left.X = LocalPosition.X; + // BorderData.Left.Y = LocalPosition.Y; + // BorderData.Left.Width = Size.X; + // BorderData.Left.Height = Size.Y; + // //BorderData.Right = new PdfCellBorderData(LineType.Right); + // BorderData.Right.BorderStyle = TableStyle.xfRight.Style == ExcelBorderStyle.None ? ((TableStyle.dxfRight != null && TableStyle.dxfRight.HasValue) ? (ExcelBorderStyle)TableStyle.dxfRight.Style : ExcelBorderStyle.None) : TableStyle.xfRight.Style; + // BorderData.Right.BorderColor = TableStyle.dxfRight != null ? PdfColor.SetColorFromHex(TableStyle.dxfRight.Color.LookupColor(TableStyle.dxfRight)) : PdfColor.SetColorFromHex(cell.Style.Border.Right.Color.LookupColor(cell.Style.Border)); + // BorderData.Right.X = LocalPosition.X; + // BorderData.Right.Y = LocalPosition.Y; + // BorderData.Right.Width = Size.X; + // BorderData.Right.Height = Size.Y; - ////BorderData.Top = new PdfCellBorderData(LineType.Top); - //BorderData.Top.BorderStyle = cell.Style.Border.Top.Style; - //BorderData.Top.BorderColor = PdfColor.SetColorFromHex(cell.Style.Border.Top.Color.LookupColor(cell.Style.Border)); - //BorderData.Top.X = LocalPosition.X; - //BorderData.Top.Y = LocalPosition.Y; - //BorderData.Top.Width = Size.X; - //BorderData.Top.Height = Size.Y; - ////BorderData.Bottom = new PdfCellBorderData(LineType.Bottom); - //BorderData.Bottom.BorderStyle = cell.Style.Border.Bottom.Style; - //BorderData.Bottom.BorderColor = PdfColor.SetColorFromHex(cell.Style.Border.Bottom.Color.LookupColor(cell.Style.Border)); - //BorderData.Bottom.X = LocalPosition.X; - //BorderData.Bottom.Y = LocalPosition.Y; - //BorderData.Bottom.Width = Size.X; - //BorderData.Bottom.Height = Size.Y; - ////BorderData.Left = new PdfCellBorderData(LineType.Left); - //BorderData.Left.BorderStyle = cell.Style.Border.Left.Style; - //BorderData.Left.BorderColor = PdfColor.SetColorFromHex(cell.Style.Border.Left.Color.LookupColor(cell.Style.Border)); - //BorderData.Left.X = LocalPosition.X; - //BorderData.Left.Y = LocalPosition.Y; - //BorderData.Left.Width = Size.X; - //BorderData.Left.Height = Size.Y; - ////BorderData.Right = new PdfCellBorderData(LineType.Right); - //BorderData.Right.BorderStyle = cell.Style.Border.Right.Style; - //BorderData.Right.BorderColor = PdfColor.SetColorFromHex(cell.Style.Border.Right.Color.LookupColor(cell.Style.Border)); - //BorderData.Right.X = LocalPosition.X; - //BorderData.Right.Y = LocalPosition.Y; - //BorderData.Right.Width = Size.X; - //BorderData.Right.Height = Size.Y; - } - } + // ////BorderData.Top = new PdfCellBorderData(LineType.Top); + // //BorderData.Top.BorderStyle = cell.Style.Border.Top.Style; + // //BorderData.Top.BorderColor = PdfColor.SetColorFromHex(cell.Style.Border.Top.Color.LookupColor(cell.Style.Border)); + // //BorderData.Top.X = LocalPosition.X; + // //BorderData.Top.Y = LocalPosition.Y; + // //BorderData.Top.Width = Size.X; + // //BorderData.Top.Height = Size.Y; + // ////BorderData.Bottom = new PdfCellBorderData(LineType.Bottom); + // //BorderData.Bottom.BorderStyle = cell.Style.Border.Bottom.Style; + // //BorderData.Bottom.BorderColor = PdfColor.SetColorFromHex(cell.Style.Border.Bottom.Color.LookupColor(cell.Style.Border)); + // //BorderData.Bottom.X = LocalPosition.X; + // //BorderData.Bottom.Y = LocalPosition.Y; + // //BorderData.Bottom.Width = Size.X; + // //BorderData.Bottom.Height = Size.Y; + // ////BorderData.Left = new PdfCellBorderData(LineType.Left); + // //BorderData.Left.BorderStyle = cell.Style.Border.Left.Style; + // //BorderData.Left.BorderColor = PdfColor.SetColorFromHex(cell.Style.Border.Left.Color.LookupColor(cell.Style.Border)); + // //BorderData.Left.X = LocalPosition.X; + // //BorderData.Left.Y = LocalPosition.Y; + // //BorderData.Left.Width = Size.X; + // //BorderData.Left.Height = Size.Y; + // ////BorderData.Right = new PdfCellBorderData(LineType.Right); + // //BorderData.Right.BorderStyle = cell.Style.Border.Right.Style; + // //BorderData.Right.BorderColor = PdfColor.SetColorFromHex(cell.Style.Border.Right.Color.LookupColor(cell.Style.Border)); + // //BorderData.Right.X = LocalPosition.X; + // //BorderData.Right.Y = LocalPosition.Y; + // //BorderData.Right.Width = Size.X; + // //BorderData.Right.Height = Size.Y; + // } + //} - public void InitDiagonalBorders(ExcelRangeBase cell, double width, double height) - { - if (cell != null) - { - //BorderData.DiagonalUp = new PdfCellBorderData(LineType.DiagonalUp); - BorderData.DiagonalUp.BorderStyle = cell.Style.Border.DiagonalUp ? cell.Style.Border.Diagonal.Style : ExcelBorderStyle.None; - BorderData.DiagonalUp.BorderColor = PdfColor.SetColorFromHex(cell.Style.Border.Diagonal.Color.LookupColor(cell.Style.Border)); - BorderData.DiagonalUp.MergedDiagonalWidth = width; - BorderData.DiagonalUp.MergedDiagonalHeight = height; - //BorderData.DiagonalDown = new PdfCellBorderData(LineType.DiagonalDown); - BorderData.DiagonalDown.BorderStyle = cell.Style.Border.DiagonalDown ? cell.Style.Border.Diagonal.Style : ExcelBorderStyle.None; - BorderData.DiagonalDown.BorderColor = PdfColor.SetColorFromHex(cell.Style.Border.Diagonal.Color.LookupColor(cell.Style.Border)); - BorderData.DiagonalDown.MergedDiagonalWidth = width; - BorderData.DiagonalDown.MergedDiagonalHeight = height; - } - } + //public void InitDiagonalBorders(ExcelRangeBase cell, double width, double height) + //{ + // if (cell != null) + // { + // //BorderData.DiagonalUp = new PdfCellBorderData(LineType.DiagonalUp); + // BorderData.DiagonalUp.BorderStyle = cell.Style.Border.DiagonalUp ? cell.Style.Border.Diagonal.Style : ExcelBorderStyle.None; + // BorderData.DiagonalUp.BorderColor = PdfColor.SetColorFromHex(cell.Style.Border.Diagonal.Color.LookupColor(cell.Style.Border)); + // BorderData.DiagonalUp.MergedDiagonalWidth = width; + // BorderData.DiagonalUp.MergedDiagonalHeight = height; + // //BorderData.DiagonalDown = new PdfCellBorderData(LineType.DiagonalDown); + // BorderData.DiagonalDown.BorderStyle = cell.Style.Border.DiagonalDown ? cell.Style.Border.Diagonal.Style : ExcelBorderStyle.None; + // BorderData.DiagonalDown.BorderColor = PdfColor.SetColorFromHex(cell.Style.Border.Diagonal.Color.LookupColor(cell.Style.Border)); + // BorderData.DiagonalDown.MergedDiagonalWidth = width; + // BorderData.DiagonalDown.MergedDiagonalHeight = height; + // } + //} public void UpdateLocalBorderPosition() { @@ -273,7 +274,7 @@ public static ExcelDxfBorderItem GetTopBorderItem(ExcelRangeBase cell, ExcelBord { if (table.ShowColumnStripes &&/* tableStyle.FirstColumnStripe.Style.Border.Top.HasValue &&*/ (tableCol & 1) == 0) { - if (cell._fromRow - ts > range._fromRow && cell._fromRow < range._toRow) + if (cell._fromRow - ts > range._fromRow && cell._fromRow < range._toRow) { top = tableStyle.FirstColumnStripe.Style.Border.Horizontal; } diff --git a/src/EPPlus.Export.Pdf/Layout/PdfCellContentLayout.cs b/src/EPPlus.Export.Pdf/Layout/PdfCellContentLayout.cs index 56ff85d136..312598012d 100644 --- a/src/EPPlus.Export.Pdf/Layout/PdfCellContentLayout.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfCellContentLayout.cs @@ -10,20 +10,13 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ +using EPPlus.Export.Pdf.Helpers; using EPPlus.Export.Pdf.Resources; using EPPlus.Export.Pdf.Settings; using EPPlus.Fonts.OpenType.Integration; using EPPlus.Graphics; -using OfficeOpenXml; -using OfficeOpenXml.Export.PdfExport.Data; -using OfficeOpenXml.Export.PdfExport.TextShaping; -using OfficeOpenXml.Interfaces.Fonts; -using OfficeOpenXml.Style; -using System; using System.Collections.Generic; using System.Diagnostics; -using System.Drawing; -using System.Linq; using Vector2 = EPPlus.Graphics.Geometry.Vector2; namespace EPPlus.Export.Pdf.Layout @@ -38,7 +31,6 @@ internal class PdfCellContentLayout : Transform public bool IsHeading; public bool IsPrintTitle; public PdfCellStyle CellStyle; - public ExcelRangeBase cell; public TextLayoutEngine textLayoutEngine; //private List textFormats = new List(); public TextLineCollection TextLines; @@ -51,14 +43,14 @@ internal class PdfCellContentLayout : Transform private double bottomMargin = 3.5d; //Guessed number private double rightMargin = 1.4d; //I guessed this one too.. - public PdfCellContentLayout(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfCell cell, MergedCellDrawInfo mergedCellInfo, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) + public PdfCellContentLayout(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfCellBase cell, MergedCellDrawInfo mergedCellInfo, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) : base(x, y-height, width, height, scaleX, scaleY, rotation, parent) { Z = 2; CellAlignmentData = cell.ContentAligmnet; TextLines = cell.TextLines; ShapedTexts = cell.ShapedTexts; - TextLayoutEngine = cell.TextLayoutEngine; + textLayoutEngine = cell.TextLayoutEngine; double totalTextHeight = 0d; foreach (var line in TextLines) { @@ -75,7 +67,7 @@ public PdfCellContentLayout(PdfPageSettings pageSettings, PdfDictionaries dictio Z = 2; TextLines = headerFooter.Content.TextLines; ShapedTexts = headerFooter.Content.ShapedTexts; - TextLayoutEngine = headerFooter.Content.TextLayoutEngine; + textLayoutEngine = headerFooter.Content.TextLayoutEngine; CellAlignmentData = headerFooter.Content.ContentAligmnet; double totalTextHeight = 0d; foreach (var line in TextLines) @@ -219,277 +211,277 @@ private Vector2 CalculateRotatedAlignment(double textLength, double textHeight, - public PdfCellContentLayout(ExcelRangeBase cell, PdfCellStyle CellStyle, PdfPageSettings pageSettings, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null, PdfDictionaries dictionaries = null) - : base(x, y, width, height, scaleX, scaleY, rotation, parent) - { - this.cell = cell; - this.CellStyle = CellStyle; - CellAlignmentData = new PdfCellAlignmentData(); - CellAlignmentData.HorizontalAlignment = cell.Style.HorizontalAlignment; - CellAlignmentData.VerticalAlignment = cell.Style.VerticalAlignment; - CellAlignmentData.Indent = cell.Style.Indent; - CellAlignmentData.WrapText = cell.Style.WrapText; - CellAlignmentData.ShrinkToFit = cell.Style.ShrinkToFit; - CellAlignmentData.TextRotation = (cell.Style.TextRotation > 90) ? ((cell.Style.TextRotation == 255) ? 0 : 90 - cell.Style.TextRotation) : cell.Style.TextRotation; - CellAlignmentData.IsVertical = cell.Style.TextRotation == 255 ? true : false; - CellAlignmentData.TextDirection = cell.Style.ReadingOrder; - if (!cell.IsRichText) cell._rtc = new ExcelRichTextCollection(cell.Text, cell); - //HandleText(pageSettings, dictionaries, x, y, width, height, CellAlignmentData.TextRotation, CellStyle); - //CalculateTextSpill(width, CellAlignmentData.TextRotation); - //LocalPosition = CalculateAlignmentPositionAndTextOffsets(cell, x, y, width, height); - Size = new Vector2(x + width - LocalPosition.X, y + height - LocalPosition.Y); - //CheckClipping(cell, x, y, width, height); - } - - public PdfCellContentLayout(string text, ExcelRangeBase cell, PdfCellStyle CellStyle, PdfPageSettings pageSettings, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null, PdfDictionaries dictionaries = null) - : base(x, y, width, height, scaleX, scaleY, rotation, parent) - { - this.cell = cell; - this.CellStyle = CellStyle; - CellAlignmentData = new PdfCellAlignmentData(); - CellAlignmentData.HorizontalAlignment = ExcelHorizontalAlignment.Center; - CellAlignmentData.VerticalAlignment = ExcelVerticalAlignment.Bottom; - cell._rtc = new ExcelRichTextCollection(text, cell); - var ns = cell.Worksheet.Workbook.Styles.GetNormalStyle(); - cell._rtc[0].FontName = ns.Style.Font.Name; - cell._rtc[0].Family = ns.Style.Font.Family; - cell._rtc[0].Size = ns.Style.Font.Size; - cell._rtc[0].Color = Color.Black; - //HandleText(pageSettings, dictionaries, x, y, width, height, CellAlignmentData.TextRotation, CellStyle); - //CalculateTextSpill(width, CellAlignmentData.TextRotation); - //LocalPosition = CalculateAlignmentPositionAndTextOffsets(cell, x, y, width, height); - Size = new Vector2(x + width - LocalPosition.X, y + height - LocalPosition.Y); - //CheckClipping(cell, x, y, width, height); - } - - public void CalculateTextSpill(double maxWidth, double rotation) - { - if (maxWidth < textLength) - { - if (rotation != 0) - { - var baseVec = new Vector2(textLength, 0); - var rad = rotation * Math.PI / 180.0d; - var rotVec = new Vector2(textLength * Math.Cos(rad), textLength * Math.Sin(rad)); - var length = Vector2.Project(rotVec, baseVec).Length; - if (length > maxWidth) - { - if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.General) - { - if (double.TryParse(cell.Value.ToString(), out double value)) - { - LeftTextSpillLength = textLength; - } - else - { - RightTextSpillLength = textLength; - } - } - else if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Left) - { - RightTextSpillLength = length; - } - else if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Right) - { - LeftTextSpillLength = length; - } - else if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Center) - { - LeftTextSpillLength = length / 2d; - RightTextSpillLength = length / 2d; - } - - } - return; - } - if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.General) - { - if (double.TryParse(cell.Value.ToString(), out double value)) - { - LeftTextSpillLength = textLength - Size.X; - } - else - { - RightTextSpillLength = textLength - Size.X; - } - } - else if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Left) - { - RightTextSpillLength = textLength - Size.X; - } - else if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Right) - { - LeftTextSpillLength = textLength - Size.X; - } - else if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Center) - { - LeftTextSpillLength = (textLength - Size.X) / 2d; - RightTextSpillLength = (textLength - Size.X) / 2d; - } - } - } - - //Calculate text position from alignment and offsets for each line of text. - public Vector2 CalculateAlignmentPositionAndTextOffsets(ExcelRangeBase cell, double cellX, double CellY, double cellWidth, double cellHeight) - { - double x = 0d; - double y = 0d; - double xOffset = 0d; - double yOffset = 0d; - switch (CellAlignmentData.HorizontalAlignment) - { - case ExcelHorizontalAlignment.Fill: - case ExcelHorizontalAlignment.General: - if (double.TryParse(cell.Value.ToString(), out double value)) - { - x = cellX + (cellWidth - textLength) - rightMargin; - } - else - { - x = cellX + rightMargin; - } - break; - case ExcelHorizontalAlignment.Left: - x = cellX + rightMargin; - break; - case ExcelHorizontalAlignment.Center: - x = cellX + (cellWidth - textLength) / 2d; - break; - case ExcelHorizontalAlignment.Right: - x = cellX + (cellWidth - textLength) - rightMargin; - break; - } - switch (CellAlignmentData.VerticalAlignment) - { - case ExcelVerticalAlignment.Top: - y = (CellY + cellHeight) - (textHeight / 2d) - bottomMargin; - break; - case ExcelVerticalAlignment.Center: - y = CellY + (cellHeight / 2d) - (textHeight / 4d); ; - break; - case ExcelVerticalAlignment.Bottom: - y = CellY + bottomMargin; - break; - } - if (CellAlignmentData.TextRotation < 0) - { - double rot = CellAlignmentData.TextRotation * System.Math.PI / 180.0; - x += textLength * (1 - System.Math.Cos(rot)); - y -= textLength * System.Math.Sin(rot); - } - else if (CellAlignmentData.TextRotation > 0) - { - double rot = CellAlignmentData.TextRotation * System.Math.PI / 180.0; - x += textLength * (1 - System.Math.Cos(rot)); - } - return new Vector2(x, y); - } - - //Check if clipping is needed. - - - //Check if clipping is needed. - - public void CheckClipping(ExcelRangeBase cell, double x, double y, double width, double height) - { - if (textLength >= width || cell.Merge) - { - if (cell.Merge || - CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Fill) - { - Clipping = new Rect() - { - X = x + rightMargin, - Y = y, - Width = width - rightMargin * 2, - Height = height - }; - } - } - } - - // Set clipping to the cell's own bounds. cellY is the top edge (same convention as the constructor). - internal void SetupClipping(double cellX, double cellY, double cellWidth, double cellHeight) - { - Clip = true; - Clipping = new Rect() - { - X = cellX + rightMargin, - Y = cellY - cellHeight, // bottom-left corner in PDF space - Width = cellWidth - rightMargin * 2, - Height = cellHeight - }; - } + //public PdfCellContentLayout(ExcelRangeBase cell, PdfCellStyle CellStyle, PdfPageSettings pageSettings, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null, PdfDictionaries dictionaries = null) + // : base(x, y, width, height, scaleX, scaleY, rotation, parent) + //{ + // this.cell = cell; + // this.CellStyle = CellStyle; + // CellAlignmentData = new PdfCellAlignmentData(); + // CellAlignmentData.HorizontalAlignment = cell.Style.HorizontalAlignment; + // CellAlignmentData.VerticalAlignment = cell.Style.VerticalAlignment; + // CellAlignmentData.Indent = cell.Style.Indent; + // CellAlignmentData.WrapText = cell.Style.WrapText; + // CellAlignmentData.ShrinkToFit = cell.Style.ShrinkToFit; + // CellAlignmentData.TextRotation = (cell.Style.TextRotation > 90) ? ((cell.Style.TextRotation == 255) ? 0 : 90 - cell.Style.TextRotation) : cell.Style.TextRotation; + // CellAlignmentData.IsVertical = cell.Style.TextRotation == 255 ? true : false; + // CellAlignmentData.TextDirection = cell.Style.ReadingOrder; + // if (!cell.IsRichText) cell._rtc = new ExcelRichTextCollection(cell.Text, cell); + // //HandleText(pageSettings, dictionaries, x, y, width, height, CellAlignmentData.TextRotation, CellStyle); + // //CalculateTextSpill(width, CellAlignmentData.TextRotation); + // //LocalPosition = CalculateAlignmentPositionAndTextOffsets(cell, x, y, width, height); + // Size = new Vector2(x + width - LocalPosition.X, y + height - LocalPosition.Y); + // //CheckClipping(cell, x, y, width, height); + //} + + //public PdfCellContentLayout(string text, ExcelRangeBase cell, PdfCellStyle CellStyle, PdfPageSettings pageSettings, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null, PdfDictionaries dictionaries = null) + // : base(x, y, width, height, scaleX, scaleY, rotation, parent) + //{ + // this.cell = cell; + // this.CellStyle = CellStyle; + // CellAlignmentData = new PdfCellAlignmentData(); + // CellAlignmentData.HorizontalAlignment = ExcelHorizontalAlignment.Center; + // CellAlignmentData.VerticalAlignment = ExcelVerticalAlignment.Bottom; + // cell._rtc = new ExcelRichTextCollection(text, cell); + // var ns = cell.Worksheet.Workbook.Styles.GetNormalStyle(); + // cell._rtc[0].FontName = ns.Style.Font.Name; + // cell._rtc[0].Family = ns.Style.Font.Family; + // cell._rtc[0].Size = ns.Style.Font.Size; + // cell._rtc[0].Color = Color.Black; + // //HandleText(pageSettings, dictionaries, x, y, width, height, CellAlignmentData.TextRotation, CellStyle); + // //CalculateTextSpill(width, CellAlignmentData.TextRotation); + // //LocalPosition = CalculateAlignmentPositionAndTextOffsets(cell, x, y, width, height); + // Size = new Vector2(x + width - LocalPosition.X, y + height - LocalPosition.Y); + // //CheckClipping(cell, x, y, width, height); + //} + + //public void CalculateTextSpill(double maxWidth, double rotation) + //{ + // if (maxWidth < textLength) + // { + // if (rotation != 0) + // { + // var baseVec = new Vector2(textLength, 0); + // var rad = rotation * Math.PI / 180.0d; + // var rotVec = new Vector2(textLength * Math.Cos(rad), textLength * Math.Sin(rad)); + // var length = Vector2.Project(rotVec, baseVec).Length; + // if (length > maxWidth) + // { + // if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.General) + // { + // if (double.TryParse(cell.Value.ToString(), out double value)) + // { + // LeftTextSpillLength = textLength; + // } + // else + // { + // RightTextSpillLength = textLength; + // } + // } + // else if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Left) + // { + // RightTextSpillLength = length; + // } + // else if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Right) + // { + // LeftTextSpillLength = length; + // } + // else if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Center) + // { + // LeftTextSpillLength = length / 2d; + // RightTextSpillLength = length / 2d; + // } + + // } + // return; + // } + // if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.General) + // { + // if (double.TryParse(cell.Value.ToString(), out double value)) + // { + // LeftTextSpillLength = textLength - Size.X; + // } + // else + // { + // RightTextSpillLength = textLength - Size.X; + // } + // } + // else if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Left) + // { + // RightTextSpillLength = textLength - Size.X; + // } + // else if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Right) + // { + // LeftTextSpillLength = textLength - Size.X; + // } + // else if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Center) + // { + // LeftTextSpillLength = (textLength - Size.X) / 2d; + // RightTextSpillLength = (textLength - Size.X) / 2d; + // } + // } + //} + + ////Calculate text position from alignment and offsets for each line of text. + //public Vector2 CalculateAlignmentPositionAndTextOffsets(ExcelRangeBase cell, double cellX, double CellY, double cellWidth, double cellHeight) + //{ + // double x = 0d; + // double y = 0d; + // double xOffset = 0d; + // double yOffset = 0d; + // switch (CellAlignmentData.HorizontalAlignment) + // { + // case ExcelHorizontalAlignment.Fill: + // case ExcelHorizontalAlignment.General: + // if (double.TryParse(cell.Value.ToString(), out double value)) + // { + // x = cellX + (cellWidth - textLength) - rightMargin; + // } + // else + // { + // x = cellX + rightMargin; + // } + // break; + // case ExcelHorizontalAlignment.Left: + // x = cellX + rightMargin; + // break; + // case ExcelHorizontalAlignment.Center: + // x = cellX + (cellWidth - textLength) / 2d; + // break; + // case ExcelHorizontalAlignment.Right: + // x = cellX + (cellWidth - textLength) - rightMargin; + // break; + // } + // switch (CellAlignmentData.VerticalAlignment) + // { + // case ExcelVerticalAlignment.Top: + // y = (CellY + cellHeight) - (textHeight / 2d) - bottomMargin; + // break; + // case ExcelVerticalAlignment.Center: + // y = CellY + (cellHeight / 2d) - (textHeight / 4d); ; + // break; + // case ExcelVerticalAlignment.Bottom: + // y = CellY + bottomMargin; + // break; + // } + // if (CellAlignmentData.TextRotation < 0) + // { + // double rot = CellAlignmentData.TextRotation * System.Math.PI / 180.0; + // x += textLength * (1 - System.Math.Cos(rot)); + // y -= textLength * System.Math.Sin(rot); + // } + // else if (CellAlignmentData.TextRotation > 0) + // { + // double rot = CellAlignmentData.TextRotation * System.Math.PI / 180.0; + // x += textLength * (1 - System.Math.Cos(rot)); + // } + // return new Vector2(x, y); + //} + + ////Check if clipping is needed. + + + ////Check if clipping is needed. + + //public void CheckClipping(ExcelRangeBase cell, double x, double y, double width, double height) + //{ + // if (textLength >= width || cell.Merge) + // { + // if (cell.Merge || + // CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Fill) + // { + // Clipping = new Rect() + // { + // X = x + rightMargin, + // Y = y, + // Width = width - rightMargin * 2, + // Height = height + // }; + // } + // } + //} + + //// Set clipping to the cell's own bounds. cellY is the top edge (same convention as the constructor). + //internal void SetupClipping(double cellX, double cellY, double cellWidth, double cellHeight) + //{ + // Clip = true; + // Clipping = new Rect() + // { + // X = cellX + rightMargin, + // Y = cellY - cellHeight, // bottom-left corner in PDF space + // Width = cellWidth - rightMargin * 2, + // Height = cellHeight + // }; + //} + + ////Create clipping rectangle. + //internal void CreateClippingRect(List cells) + //{ + // if (Clip) + // { + // var cellName = Name.Split('_')[0]; + // var pcc = cells.Where(x => x.Name.Contains(cellName)).Where(x => x is PdfCellLayout).ToList(); + // if (pcc.Count > 0) + // { + // Clipping = new Rect() + // { + // X = pcc[0].LocalPosition.X + rightMargin, + // Y = pcc[0].LocalPosition.Y, + // Width = pcc[0].Size.X - rightMargin * 2, + // Height = pcc[0].Size.Y + // }; + // } + // } + //} //Create clipping rectangle. - internal void CreateClippingRect(List cells) - { - if (Clip) - { - var cellName = Name.Split('_')[0]; - var pcc = cells.Where(x => x.Name.Contains(cellName)).Where(x => x is PdfCellLayout).ToList(); - if (pcc.Count > 0) - { - Clipping = new Rect() - { - X = pcc[0].LocalPosition.X + rightMargin, - Y = pcc[0].LocalPosition.Y, - Width = pcc[0].Size.X - rightMargin * 2, - Height = pcc[0].Size.Y - }; - } - } - } - - //Create clipping rectangle. - internal void CreateClippingRect(PdfCellLayout cell, double targetX) - { - //need a way to detect starting X position if text is spilled from left or right - var width = targetX - cell.LocalPosition.X; - Clipping = new Rect() - { - X = cell.LocalPosition.X + rightMargin, - Y = cell.LocalPosition.Y, - Width = width - rightMargin * 2, - Height = cell.Size.Y - }; - } - - internal void GidsAndCharMap(PdfDictionaries dictionaries) - { - foreach (var tf in ShapedTexts) - { - var usedFonts = tf.UsedFonts; - - foreach (var glyph in tf.ShapedText.Glyphs) - { - if (glyph.FontId >= usedFonts.Count) - continue; - - var font = usedFonts[glyph.FontId]; - - dictionaries.Fonts[font.FullName].Gids.Add(glyph.GlyphId); - dictionaries.Fonts[font.FullName].fontData = font; - - if (!dictionaries.Fonts[font.FullName].charactermappings.ContainsKey(glyph.GlyphId)) - { - var chars = ExtractCharactersForGlyph(glyph, tf.ShapedText.OriginalText); - if (!string.IsNullOrEmpty(chars)) - { - dictionaries.Fonts[font.FullName].charactermappings[glyph.GlyphId] = chars; - } - } - } - } - } - private string ExtractCharactersForGlyph(ShapedGlyph glyph, string textLine) - { - var chars = new System.Text.StringBuilder(); - for (int i = 0; i < glyph.CharCount && glyph.ClusterIndex + i < textLine.Length; i++) - { - chars.Append(textLine[glyph.ClusterIndex + i]); - } - return chars.ToString(); - } + //internal void CreateClippingRect(PdfCellLayout cell, double targetX) + //{ + // //need a way to detect starting X position if text is spilled from left or right + // var width = targetX - cell.LocalPosition.X; + // Clipping = new Rect() + // { + // X = cell.LocalPosition.X + rightMargin, + // Y = cell.LocalPosition.Y, + // Width = width - rightMargin * 2, + // Height = cell.Size.Y + // }; + //} + + //internal void GidsAndCharMap(PdfDictionaries dictionaries) + //{ + // foreach (var tf in ShapedTexts) + // { + // var usedFonts = tf.UsedFonts; + + // foreach (var glyph in tf.ShapedText.Glyphs) + // { + // if (glyph.FontId >= usedFonts.Count) + // continue; + + // var font = usedFonts[glyph.FontId]; + + // dictionaries.Fonts[font.FullName].Gids.Add(glyph.GlyphId); + // dictionaries.Fonts[font.FullName].fontData = font; + + // if (!dictionaries.Fonts[font.FullName].charactermappings.ContainsKey(glyph.GlyphId)) + // { + // var chars = ExtractCharactersForGlyph(glyph, tf.ShapedText.OriginalText); + // if (!string.IsNullOrEmpty(chars)) + // { + // dictionaries.Fonts[font.FullName].charactermappings[glyph.GlyphId] = chars; + // } + // } + // } + // } + //} + //private string ExtractCharactersForGlyph(ShapedGlyph glyph, string textLine) + //{ + // var chars = new System.Text.StringBuilder(); + // for (int i = 0; i < glyph.CharCount && glyph.ClusterIndex + i < textLine.Length; i++) + // { + // chars.Append(textLine[glyph.ClusterIndex + i]); + // } + // return chars.ToString(); + //} } } diff --git a/src/EPPlus.Export.Pdf/Layout/PdfCellLayout.cs b/src/EPPlus.Export.Pdf/Layout/PdfCellLayout.cs index 83b0f98209..b1024000fb 100644 --- a/src/EPPlus.Export.Pdf/Layout/PdfCellLayout.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfCellLayout.cs @@ -15,9 +15,6 @@ Date Author Change using EPPlus.Export.Pdf.Settings; using EPPlus.Graphics; using EPPlus.Graphics.Geometry; -using OfficeOpenXml; -using OfficeOpenXml.Export.PdfExport.Data; -using OfficeOpenXml.Style; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; @@ -28,7 +25,6 @@ namespace EPPlus.Export.Pdf.Layout [DebuggerDisplay("Cell: {Name}")] internal class PdfCellLayout : Transform { - public ExcelRangeBase cell; public PdfCellFillData CellFillData; public PdfCellStyle CellStyle; public double LeftTextSpillLength = 0d; @@ -114,85 +110,85 @@ public PdfCellLayout(PdfDictionaries dictionaries, PdfCellStyle CellStyle, doubl } - public PdfCellLayout(PdfDictionaries dictionaries, ExcelRangeBase cell, PdfCellStyle CellStyle, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) - : base(x, y - height, width, height, scaleX, scaleY, rotation, parent) - { - if (cell != null) - { - this.cell = cell; - this.CellStyle = CellStyle; - var xfFill = CellStyle.xfFill; - var dxfFill = CellStyle.dxfFill; - CellFillData = new PdfCellFillData(); - if (dxfFill != null && xfFill.IsEmpty()) - { - CellFillData.PatternStyle = dxfFill.PatternType != null ? (ExcelFillStyle)dxfFill.PatternType : ExcelFillStyle.Solid; - if (CellFillData.PatternStyle == ExcelFillStyle.Solid) - { - CellFillData.BackgroundColor = PdfColor.SetColorFromHex(dxfFill.BackgroundColor.LookupColor()); - } - else if (CellFillData.PatternStyle != ExcelFillStyle.None) - { - CellFillData.BackgroundColor = PdfColor.SetColorFromHex(dxfFill.PatternColor.Color == null ? "#FFFFFFFF" : dxfFill.PatternColor.LookupColor()); - CellFillData.PatternColor = PdfColor.SetColorFromHex(dxfFill.BackgroundColor.LookupColor()); - CellFillData.id = AddPatternResourceData(dictionaries.Patterns, CellFillData.PatternStyle.ToString() + CellFillData.PatternColor.ToHexString() + CellFillData.BackgroundColor.ToHexString()); - } - else if (dxfFill.Gradient != null) - { - CellFillData.GradientFillData = new PdfCellGradientFillData(); - CellFillData.GradientFillData.GradientType = dxfFill.Gradient.GradientType == null ? ExcelFillGradientType.None : (ExcelFillGradientType)dxfFill.Gradient.GradientType; - CellFillData.GradientFillData.Color1 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[0].Color.LookupColor()); - CellFillData.GradientFillData.Color2 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[1].Color.LookupColor()); - CellFillData.GradientFillData.Color3 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[2].Color.LookupColor()); - CellFillData.GradientFillData.Degree = dxfFill.Gradient.Degree == null ? 0 : (double)dxfFill.Gradient.Degree; - CellFillData.GradientFillData.Top = dxfFill.Gradient.Top == null ? 0 : (double)dxfFill.Gradient.Top; - CellFillData.GradientFillData.Bottom = dxfFill.Gradient.Bottom == null ? 0 : (double)dxfFill.Gradient.Bottom; - CellFillData.GradientFillData.Left = dxfFill.Gradient.Left == null ? 0 : (double)dxfFill.Gradient.Left; - CellFillData.GradientFillData.Right = dxfFill.Gradient.Right == null ? 0 : (double)dxfFill.Gradient.Right; - CellFillData.id = CellFillData.GradientFillData.ToString() + $"_{x:F4}_{(y - height):F4}_{width:F4}_{height:F4}"; - AddShadingResourceData(dictionaries.Shadings, CellFillData.id); - } - } - else - { - if (xfFill.PatternType == ExcelFillStyle.Solid) - { - var bkgc = xfFill.BackgroundColor; - CellFillData.PatternStyle = xfFill.PatternType; - if (string.IsNullOrEmpty(bkgc.LookupColor()) && !string.IsNullOrEmpty(cell.Text)) - { - CellFillData.BackgroundColor = Color.Empty; - } - else - { - CellFillData.BackgroundColor = PdfColor.SetColorFromHex(bkgc.LookupColor()); - } - } - else if (xfFill.PatternType != ExcelFillStyle.None) - { - CellFillData.PatternStyle = xfFill.PatternType; - CellFillData.BackgroundColor = PdfColor.SetColorFromHex(xfFill.PatternColor.Rgb == null ? "#FFFFFFFF" : xfFill.PatternColor.LookupColor()); - CellFillData.PatternColor = PdfColor.SetColorFromHex(xfFill.BackgroundColor.LookupColor()); - CellFillData.id = AddPatternResourceData(dictionaries.Patterns, CellFillData.PatternStyle.ToString() + CellFillData.PatternColor.ToHexString() + CellFillData.BackgroundColor.ToHexString()); - } - else if (xfFill.HasGradient) - { - CellFillData.GradientFillData = new PdfCellGradientFillData(); - CellFillData.GradientFillData.GradientType = xfFill.Gradient.Type; - CellFillData.GradientFillData.Color1 = PdfColor.SetColorFromHex(xfFill.Gradient.Color1.LookupColor()); - CellFillData.GradientFillData.Color2 = PdfColor.SetColorFromHex(xfFill.Gradient.Color2.LookupColor()); - CellFillData.GradientFillData.Color3 = PdfColor.SetColorFromHex(xfFill.Gradient.Color3.LookupColor()); - CellFillData.GradientFillData.Degree = xfFill.Gradient.Degree; - CellFillData.GradientFillData.Top = double.IsNaN(xfFill.Gradient.Top) ? 0 : xfFill.Gradient.Top; - CellFillData.GradientFillData.Bottom = double.IsNaN(xfFill.Gradient.Bottom) ? 0 : xfFill.Gradient.Bottom; - CellFillData.GradientFillData.Left = double.IsNaN(xfFill.Gradient.Left) ? 0 : xfFill.Gradient.Left; - CellFillData.GradientFillData.Right = double.IsNaN(xfFill.Gradient.Right) ? 0 : xfFill.Gradient.Right; - CellFillData.id = CellFillData.GradientFillData.ToString() + $"_{x:F4}_{(y - height):F4}_{width:F4}_{height:F4}"; - AddShadingResourceData(dictionaries.Shadings, CellFillData.id); - } - } - } - } + //public PdfCellLayout(PdfDictionaries dictionaries, ExcelRangeBase cell, PdfCellStyle CellStyle, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) + // : base(x, y - height, width, height, scaleX, scaleY, rotation, parent) + //{ + // if (cell != null) + // { + // this.cell = cell; + // this.CellStyle = CellStyle; + // var xfFill = CellStyle.xfFill; + // var dxfFill = CellStyle.dxfFill; + // CellFillData = new PdfCellFillData(); + // if (dxfFill != null && xfFill.IsEmpty()) + // { + // CellFillData.PatternStyle = dxfFill.PatternType != null ? (ExcelFillStyle)dxfFill.PatternType : ExcelFillStyle.Solid; + // if (CellFillData.PatternStyle == ExcelFillStyle.Solid) + // { + // CellFillData.BackgroundColor = PdfColor.SetColorFromHex(dxfFill.BackgroundColor.LookupColor()); + // } + // else if (CellFillData.PatternStyle != ExcelFillStyle.None) + // { + // CellFillData.BackgroundColor = PdfColor.SetColorFromHex(dxfFill.PatternColor.Color == null ? "#FFFFFFFF" : dxfFill.PatternColor.LookupColor()); + // CellFillData.PatternColor = PdfColor.SetColorFromHex(dxfFill.BackgroundColor.LookupColor()); + // CellFillData.id = AddPatternResourceData(dictionaries.Patterns, CellFillData.PatternStyle.ToString() + CellFillData.PatternColor.ToHexString() + CellFillData.BackgroundColor.ToHexString()); + // } + // else if (dxfFill.Gradient != null) + // { + // CellFillData.GradientFillData = new PdfCellGradientFillData(); + // CellFillData.GradientFillData.GradientType = dxfFill.Gradient.GradientType == null ? ExcelFillGradientType.None : (ExcelFillGradientType)dxfFill.Gradient.GradientType; + // CellFillData.GradientFillData.Color1 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[0].Color.LookupColor()); + // CellFillData.GradientFillData.Color2 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[1].Color.LookupColor()); + // CellFillData.GradientFillData.Color3 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[2].Color.LookupColor()); + // CellFillData.GradientFillData.Degree = dxfFill.Gradient.Degree == null ? 0 : (double)dxfFill.Gradient.Degree; + // CellFillData.GradientFillData.Top = dxfFill.Gradient.Top == null ? 0 : (double)dxfFill.Gradient.Top; + // CellFillData.GradientFillData.Bottom = dxfFill.Gradient.Bottom == null ? 0 : (double)dxfFill.Gradient.Bottom; + // CellFillData.GradientFillData.Left = dxfFill.Gradient.Left == null ? 0 : (double)dxfFill.Gradient.Left; + // CellFillData.GradientFillData.Right = dxfFill.Gradient.Right == null ? 0 : (double)dxfFill.Gradient.Right; + // CellFillData.id = CellFillData.GradientFillData.ToString() + $"_{x:F4}_{(y - height):F4}_{width:F4}_{height:F4}"; + // AddShadingResourceData(dictionaries.Shadings, CellFillData.id); + // } + // } + // else + // { + // if (xfFill.PatternType == ExcelFillStyle.Solid) + // { + // var bkgc = xfFill.BackgroundColor; + // CellFillData.PatternStyle = xfFill.PatternType; + // if (string.IsNullOrEmpty(bkgc.LookupColor()) && !string.IsNullOrEmpty(cell.Text)) + // { + // CellFillData.BackgroundColor = Color.Empty; + // } + // else + // { + // CellFillData.BackgroundColor = PdfColor.SetColorFromHex(bkgc.LookupColor()); + // } + // } + // else if (xfFill.PatternType != ExcelFillStyle.None) + // { + // CellFillData.PatternStyle = xfFill.PatternType; + // CellFillData.BackgroundColor = PdfColor.SetColorFromHex(xfFill.PatternColor.Rgb == null ? "#FFFFFFFF" : xfFill.PatternColor.LookupColor()); + // CellFillData.PatternColor = PdfColor.SetColorFromHex(xfFill.BackgroundColor.LookupColor()); + // CellFillData.id = AddPatternResourceData(dictionaries.Patterns, CellFillData.PatternStyle.ToString() + CellFillData.PatternColor.ToHexString() + CellFillData.BackgroundColor.ToHexString()); + // } + // else if (xfFill.HasGradient) + // { + // CellFillData.GradientFillData = new PdfCellGradientFillData(); + // CellFillData.GradientFillData.GradientType = xfFill.Gradient.Type; + // CellFillData.GradientFillData.Color1 = PdfColor.SetColorFromHex(xfFill.Gradient.Color1.LookupColor()); + // CellFillData.GradientFillData.Color2 = PdfColor.SetColorFromHex(xfFill.Gradient.Color2.LookupColor()); + // CellFillData.GradientFillData.Color3 = PdfColor.SetColorFromHex(xfFill.Gradient.Color3.LookupColor()); + // CellFillData.GradientFillData.Degree = xfFill.Gradient.Degree; + // CellFillData.GradientFillData.Top = double.IsNaN(xfFill.Gradient.Top) ? 0 : xfFill.Gradient.Top; + // CellFillData.GradientFillData.Bottom = double.IsNaN(xfFill.Gradient.Bottom) ? 0 : xfFill.Gradient.Bottom; + // CellFillData.GradientFillData.Left = double.IsNaN(xfFill.Gradient.Left) ? 0 : xfFill.Gradient.Left; + // CellFillData.GradientFillData.Right = double.IsNaN(xfFill.Gradient.Right) ? 0 : xfFill.Gradient.Right; + // CellFillData.id = CellFillData.GradientFillData.ToString() + $"_{x:F4}_{(y - height):F4}_{width:F4}_{height:F4}"; + // AddShadingResourceData(dictionaries.Shadings, CellFillData.id); + // } + // } + // } + //} private string AddPatternResourceData(Dictionary patternResources, string key) { @@ -225,110 +221,111 @@ private string AddShadingResourceData(Dictionary sha } //Adjust size and position slightly for aesthetics. - public void AdjustForGridLines() - { - Size = new Vector2(Size.X + GridLine.HalfWidth, Size.Y + GridLine.HalfWidth); - LocalPosition = new Vector2(LocalPosition.X + GridLine.FourthWidth, LocalPosition.Y + GridLine.FourthWidth); - } + // public void AdjustForGridLines() + // { + // Size = new Vector2(Size.X + GridLine.HalfWidth, Size.Y + GridLine.HalfWidth); + // LocalPosition = new Vector2(LocalPosition.X + GridLine.FourthWidth, LocalPosition.Y + GridLine.FourthWidth); + // } - public void UpdateShadingPositionMatrix(PdfPageSettings pageSettings) - { - //LocalPosition = new Vector2(LocalPosition.X, pageSettings.PageSize.HeightPu - System.Math.Abs(LocalPosition.Y) - Size.Y); - if (CellFillData.GradientFillData != null) - { - if (CellFillData.GradientFillData.GradientType == ExcelFillGradientType.Linear) - { - switch (CellFillData.GradientFillData.Degree) - { - case 45d: - CellFillData.GradientFillData.coords = [0, 1, 1, 0]; - break; - case 90d: - CellFillData.GradientFillData.coords = [0, 1, 0, 0]; - break; - case 135d: - CellFillData.GradientFillData.coords = [1, 1, 0, 0]; - break; - case 180d: - CellFillData.GradientFillData.coords = [1, 0, 0, 0]; - break; - case 225d: - CellFillData.GradientFillData.coords = [1, 0, 0, 1]; - break; - case 270d: - CellFillData.GradientFillData.coords = [0, 0, 0, 1]; - break; - case 315d: - CellFillData.GradientFillData.coords = [0, 0, 1, 1]; - break; - case 0d: - default: - CellFillData.GradientFillData.coords = [0, 0, 1, 0]; - break; - } - CellFillData.GradientFillData.matrix = [Size.X, 0, 0, Size.Y, LocalPosition.X, LocalPosition.Y]; - } - else if (CellFillData.GradientFillData.GradientType == ExcelFillGradientType.Path) - { - double x = LocalPosition.X; - double y = LocalPosition.Y; - double width = Size.X; - double height = Size.Y; - var top = CellFillData.GradientFillData.Top; - var bottom = CellFillData.GradientFillData.Bottom; - var left = CellFillData.GradientFillData.Left; - var right = CellFillData.GradientFillData.Right; - double r = 1; - if (top == 0 && bottom == 0 && left == 0 && right == 0) - { - CellFillData.GradientFillData.coords = [0, 1, 0, 0, 1, r]; - } - else if (top == 0 && bottom == 0 && left == 1 && right == 1) - { - CellFillData.GradientFillData.coords = [1, 1, 0, 1, 1, r]; - } - else if (top == 1 && bottom == 1 && left == 0 && right == 0) - { - CellFillData.GradientFillData.coords = [0, 0, 0, 0, 0, r]; - } - else if (top == 1 && bottom == 1 && left == 1 && right == 1) - { - CellFillData.GradientFillData.coords = [1, 0, 0, 1, 0, r]; - } - else if (top == 0.5 && bottom == 0.5 && left == 0.5 && right == 0.5) - { - CellFillData.GradientFillData.coords = [0.5, 0.5, 0, 0.5, 0.5, r]; - } - CellFillData.GradientFillData.matrix = [Size.X, 0, 0, Size.Y, LocalPosition.X, LocalPosition.Y]; - //double cx = x; - //double cy = y + height; - //if (!double.IsNaN(left) && !double.IsNaN(right) && !double.IsNaN(top) && !double.IsNaN(bottom)) // bottom-right - //{ - // if (top < 1d && bottom < 1d &&left < 1d && right < 1d) - // { - // cx = x + width / 2d; - // cy = y + height / 2d; - // } - // else - // { - // cx = x + width; - // cy = y; - // } - //} - //else if (!double.IsNaN(left) && !double.IsNaN(right)) // bottom-left - //{ - // cx = x + width; - // cy = y + height; - //} - //else if (!double.IsNaN(top) && !double.IsNaN(bottom)) // top-right - //{ - // cx = x; - // cy = y; - //} - //double r = System.Math.Sqrt(width * width + height * height) / 2d; - //CellFillData.GradientFillData.coords = [cx, cy, 0, cx, cy, r]; - } - } - } + // public void UpdateShadingPositionMatrix(PdfPageSettings pageSettings) + // { + // //LocalPosition = new Vector2(LocalPosition.X, pageSettings.PageSize.HeightPu - System.Math.Abs(LocalPosition.Y) - Size.Y); + // if (CellFillData.GradientFillData != null) + // { + // if (CellFillData.GradientFillData.GradientType == ExcelFillGradientType.Linear) + // { + // switch (CellFillData.GradientFillData.Degree) + // { + // case 45d: + // CellFillData.GradientFillData.coords = [0, 1, 1, 0]; + // break; + // case 90d: + // CellFillData.GradientFillData.coords = [0, 1, 0, 0]; + // break; + // case 135d: + // CellFillData.GradientFillData.coords = [1, 1, 0, 0]; + // break; + // case 180d: + // CellFillData.GradientFillData.coords = [1, 0, 0, 0]; + // break; + // case 225d: + // CellFillData.GradientFillData.coords = [1, 0, 0, 1]; + // break; + // case 270d: + // CellFillData.GradientFillData.coords = [0, 0, 0, 1]; + // break; + // case 315d: + // CellFillData.GradientFillData.coords = [0, 0, 1, 1]; + // break; + // case 0d: + // default: + // CellFillData.GradientFillData.coords = [0, 0, 1, 0]; + // break; + // } + // CellFillData.GradientFillData.matrix = [Size.X, 0, 0, Size.Y, LocalPosition.X, LocalPosition.Y]; + // } + // else if (CellFillData.GradientFillData.GradientType == ExcelFillGradientType.Path) + // { + // double x = LocalPosition.X; + // double y = LocalPosition.Y; + // double width = Size.X; + // double height = Size.Y; + // var top = CellFillData.GradientFillData.Top; + // var bottom = CellFillData.GradientFillData.Bottom; + // var left = CellFillData.GradientFillData.Left; + // var right = CellFillData.GradientFillData.Right; + // double r = 1; + // if (top == 0 && bottom == 0 && left == 0 && right == 0) + // { + // CellFillData.GradientFillData.coords = [0, 1, 0, 0, 1, r]; + // } + // else if (top == 0 && bottom == 0 && left == 1 && right == 1) + // { + // CellFillData.GradientFillData.coords = [1, 1, 0, 1, 1, r]; + // } + // else if (top == 1 && bottom == 1 && left == 0 && right == 0) + // { + // CellFillData.GradientFillData.coords = [0, 0, 0, 0, 0, r]; + // } + // else if (top == 1 && bottom == 1 && left == 1 && right == 1) + // { + // CellFillData.GradientFillData.coords = [1, 0, 0, 1, 0, r]; + // } + // else if (top == 0.5 && bottom == 0.5 && left == 0.5 && right == 0.5) + // { + // CellFillData.GradientFillData.coords = [0.5, 0.5, 0, 0.5, 0.5, r]; + // } + // CellFillData.GradientFillData.matrix = [Size.X, 0, 0, Size.Y, LocalPosition.X, LocalPosition.Y]; + // //double cx = x; + // //double cy = y + height; + // //if (!double.IsNaN(left) && !double.IsNaN(right) && !double.IsNaN(top) && !double.IsNaN(bottom)) // bottom-right + // //{ + // // if (top < 1d && bottom < 1d &&left < 1d && right < 1d) + // // { + // // cx = x + width / 2d; + // // cy = y + height / 2d; + // // } + // // else + // // { + // // cx = x + width; + // // cy = y; + // // } + // //} + // //else if (!double.IsNaN(left) && !double.IsNaN(right)) // bottom-left + // //{ + // // cx = x + width; + // // cy = y + height; + // //} + // //else if (!double.IsNaN(top) && !double.IsNaN(bottom)) // top-right + // //{ + // // cx = x; + // // cy = y; + // //} + // //double r = System.Math.Sqrt(width * width + height * height) / 2d; + // //CellFillData.GradientFillData.coords = [cx, cy, 0, cx, cy, r]; + // } + // } + // } + //} } } \ No newline at end of file diff --git a/src/EPPlus.Export.Pdf/Layout/PdfPageLayout.cs b/src/EPPlus.Export.Pdf/Layout/PdfPageLayout.cs index 8bd756c30f..ec7de2209c 100644 --- a/src/EPPlus.Export.Pdf/Layout/PdfPageLayout.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfPageLayout.cs @@ -51,50 +51,6 @@ internal class HorizontalLineRun public int ColEnd; } - internal struct Page - { - public int FromRow; - public int FromColumn; - public int ToRow; - public int ToColumn; - - public bool HasPrintTitle; - public double PrintTitleWidth; - public double PrintTitleHeight; - - public PdfCellCollection Map; - - public PdfHeaderFooterCollection HeaderFooters; - - public Dictionary MergedCells; - public List PrintTitleCells; - public List PrintTitleGridLines; - public List PrintTitleHeadings; - public List SpillCells; - public List PrintTitleBorders; - - public double[] RowHeights; - - public double HeadingWidth; - public double HeadingHeight; - - } - - internal struct Pages - { - public Page[] Page; - public int Width; - public int Height; - public bool IsCommentsPage; - public string HeadingFontName; - public float HeadingFontSize; - public ExcelFill HeadingFill; - public int Count - { - get { return Width * Height; } - } - } - [DebuggerDisplay("{Name}")] internal class PdfPageLayout : Transform { diff --git a/src/EPPlus/Export/PdfExport/TextShaping/PdfShapedText.cs b/src/EPPlus.Export.Pdf/Layout/PdfShapedText.cs similarity index 96% rename from src/EPPlus/Export/PdfExport/TextShaping/PdfShapedText.cs rename to src/EPPlus.Export.Pdf/Layout/PdfShapedText.cs index 80fed3f096..b28e8c4f1a 100644 --- a/src/EPPlus/Export/PdfExport/TextShaping/PdfShapedText.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfShapedText.cs @@ -14,7 +14,7 @@ Date Author Change using OfficeOpenXml.Interfaces.Fonts; using System.Collections.Generic; -namespace OfficeOpenXml.Export.PdfExport.TextShaping +namespace EPPlus.Export.Pdf.Layout { internal struct PdfShapedText { diff --git a/src/EPPlus.Export.Pdf/Resources/PdfDictionaries.cs b/src/EPPlus.Export.Pdf/Resources/PdfDictionaries.cs index 9b4fc600bf..961780e96a 100644 --- a/src/EPPlus.Export.Pdf/Resources/PdfDictionaries.cs +++ b/src/EPPlus.Export.Pdf/Resources/PdfDictionaries.cs @@ -14,41 +14,43 @@ Date Author Change using OfficeOpenXml.Interfaces.Fonts; using System.Collections.Generic; using System.Linq; +using EPPlus.Export.Pdf.Settings; namespace EPPlus.Export.Pdf.Resources - -internal class PdfDictionaries { - internal readonly Dictionary Fonts = new Dictionary(); - internal readonly Dictionary Patterns = new Dictionary(); - internal readonly Dictionary Shadings = new Dictionary(); - internal Dictionary ShapedProviders = new Dictionary(); - - public void AddFont(PdfPageSettings pageSettings, string FontName, FontSubFamily SubFamily, string Text) + internal class PdfDictionaries { - var fullFont = FontName + " " + SubFamily.ToString(); - if (!Fonts.ContainsKey(fullFont)) + internal readonly Dictionary Fonts = new Dictionary(); + internal readonly Dictionary Patterns = new Dictionary(); + internal readonly Dictionary Shadings = new Dictionary(); + internal Dictionary ShapedProviders = new Dictionary(); + + public void AddFont(PdfPageSettings pageSettings, string FontName, FontSubFamily SubFamily, string Text) { - int label = 1; - if (Fonts.Count > 0) + var fullFont = FontName + " " + SubFamily.ToString(); + if (!Fonts.ContainsKey(fullFont)) { - label = Fonts.Last().Value.labelNumber + 1; + int label = 1; + if (Fonts.Count > 0) + { + label = Fonts.Last().Value.labelNumber + 1; + } + Fonts.Add(fullFont, new PdfFontResource(FontName, SubFamily, label, pageSettings)); } - Fonts.Add(fullFont, new PdfFontResource(FontName, SubFamily, label, pageSettings)); + var manger = Fonts[fullFont].fontSubsetManager; + manger.AddText(Text); } - var manger = Fonts[fullFont].fontSubsetManager; - manger.AddText(Text); - } - internal PdfFontResource GetFont(string FontName) - { - if (!Fonts.ContainsKey(FontName)) + internal PdfFontResource GetFont(string FontName) { - return null; + if (!Fonts.ContainsKey(FontName)) + { + return null; + } + return Fonts[FontName]; } - return Fonts[FontName]; - } - //this should move and be on worksheet level. - internal readonly Dictionary CommentsAndNotes = new Dictionary(); + //this should move and be on worksheet level. + internal readonly Dictionary CommentsAndNotes = new Dictionary(); + } } diff --git a/src/EPPlus.Export.Pdf/Resources/PdfFontResource.cs b/src/EPPlus.Export.Pdf/Resources/PdfFontResource.cs index 21ec1ca890..24644f1f6f 100644 --- a/src/EPPlus.Export.Pdf/Resources/PdfFontResource.cs +++ b/src/EPPlus.Export.Pdf/Resources/PdfFontResource.cs @@ -10,6 +10,8 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ +using EPPlus.Export.Pdf.DocumentObjects.Fonts; +using EPPlus.Export.Pdf.Settings; using EPPlus.Fonts.OpenType; using EPPlus.Fonts.OpenType.Tables.Os2; using EPPlus.Fonts.OpenType.TextShaping; @@ -18,7 +20,6 @@ Date Author Change using System.Collections.Generic; using System.Linq; using OfficeOpenXml.Interfaces.Fonts; -using OfficeOpenXml.Export.PdfExport.ObjectData; namespace EPPlus.Export.Pdf.Resources { diff --git a/src/EPPlus.Export.Pdf/Resources/PdfPatternResource.cs b/src/EPPlus.Export.Pdf/Resources/PdfPatternResource.cs index 2cad5eed22..c0eeb2882a 100644 --- a/src/EPPlus.Export.Pdf/Resources/PdfPatternResource.cs +++ b/src/EPPlus.Export.Pdf/Resources/PdfPatternResource.cs @@ -10,9 +10,10 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -using EPPlus.Export.Pdf.PdfLayout; -using EPPlus.Export.Pdf.PdfObjects.PdfPatterns; -using OfficeOpenXml.Style; +using EPPlus.Export.Pdf.Layout; +using EPPlus.Export.Pdf.DocumentObjects.Patterns; +using EPPlus.Export.Pdf.DocumentObjects; +using EPPlus.Export.Pdf.Helpers; namespace EPPlus.Export.Pdf.Resources { diff --git a/src/EPPlus/EPPlus.csproj b/src/EPPlus/EPPlus.csproj index 8bec3801ca..f4740d94ad 100644 --- a/src/EPPlus/EPPlus.csproj +++ b/src/EPPlus/EPPlus.csproj @@ -713,6 +713,7 @@ + diff --git a/src/EPPlus/Export/PdfExport/Data/PageData.cs b/src/EPPlus/Export/PdfExport/Data/PageData.cs new file mode 100644 index 0000000000..795fcb1217 --- /dev/null +++ b/src/EPPlus/Export/PdfExport/Data/PageData.cs @@ -0,0 +1,65 @@ +/************************************************************************************************* + 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 OfficeOpenXml.Export.PdfExport.TextMapping; +using OfficeOpenXml.Style; +using System.Collections.Generic; +using EPPlus.Export.Pdf.Layout; +using OfficeOpenXml.Export.PdfExport.Layout; + + +namespace OfficeOpenXml.Export.PdfExport.Data +{ + internal struct Page + { + public int FromRow; + public int FromColumn; + public int ToRow; + public int ToColumn; + + public bool HasPrintTitle; + public double PrintTitleWidth; + public double PrintTitleHeight; + + public PdfCellCollection Map; + + public PdfHeaderFooterCollection HeaderFooters; + + public Dictionary MergedCells; + public List PrintTitleCells; + public List PrintTitleGridLines; + public List PrintTitleHeadings; + public List SpillCells; + public List PrintTitleBorders; + + public double[] RowHeights; + + public double HeadingWidth; + public double HeadingHeight; + + } + + internal struct Pages + { + public Page[] Page; + public int Width; + public int Height; + public bool IsCommentsPage; + public string HeadingFontName; + public float HeadingFontSize; + public ExcelFill HeadingFill; + public int Count + { + get { return Width * Height; } + } + } +} diff --git a/src/EPPlus/Export/PdfExport/Data/PdfCell.cs b/src/EPPlus/Export/PdfExport/Data/PdfCell.cs index 77c65ba5f0..fee27c6cff 100644 --- a/src/EPPlus/Export/PdfExport/Data/PdfCell.cs +++ b/src/EPPlus/Export/PdfExport/Data/PdfCell.cs @@ -10,52 +10,16 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -using EPPlus.Export.Pdf.PdfLayout; -using EPPlus.Fonts.OpenType.Integration; -using OfficeOpenXml.Export.PdfExport.TextShaping; -using OfficeOpenXml.Style; -using System.Collections.Generic; +using EPPlus.Export.Pdf.Layout; namespace OfficeOpenXml.Export.PdfExport.Data { - internal class PdfCell + internal class PdfCell : PdfCellBase { - public string Name { get; set; } - public bool Hidden; public PdfCellStyle CellStyle; - public PdfCellAlignmentData ContentAligmnet; - public List TextFragments { get; set; } - public List ShapedTexts { get; set; } - public TextLineCollection TextLines { get; set; } - public string Text { get; set; } - - - public double TotalTextLength { get; set; } - public double ColumnWidth { get; set; } - public double Width { get; set; } - public double Height { get; set; } - - public TextLayoutEngine TextLayoutEngine { get; set; } public bool Merged; public PdfCell Main; public ExcelAddressBase MergedAddress; - - public bool IsPrintTitleRow; - public bool IsPrintTitleCol; - } - - internal class PdfCellAlignmentData - { - public ExcelHorizontalAlignment HorizontalAlignment = ExcelHorizontalAlignment.General; - public ExcelVerticalAlignment VerticalAlignment = ExcelVerticalAlignment.Bottom; - public int Indent = 0; - public bool WrapText = false; - public bool ShrinkToFit = false; - public int TextRotation = 0; - public ExcelReadingOrder TextDirection = ExcelReadingOrder.ContextDependent; - public bool IsVertical = false; - - public PdfCellAlignmentData() { } } } diff --git a/src/EPPlus.Export.Pdf/Layout/PdfGridlinesLayout.cs b/src/EPPlus/Export/PdfExport/Layout/PdfGridlinesLayout.cs similarity index 97% rename from src/EPPlus.Export.Pdf/Layout/PdfGridlinesLayout.cs rename to src/EPPlus/Export/PdfExport/Layout/PdfGridlinesLayout.cs index 8e128fe467..04e915e316 100644 --- a/src/EPPlus.Export.Pdf/Layout/PdfGridlinesLayout.cs +++ b/src/EPPlus/Export/PdfExport/Layout/PdfGridlinesLayout.cs @@ -12,25 +12,10 @@ Date Author Change *************************************************************************************************/ using EPPlus.Export.Pdf.Settings; using OfficeOpenXml.Export.PdfExport.Data; -using OfficeOpenXml.Style; +using EPPlus.Export.Pdf.Layout; -namespace EPPlus.Export.Pdf.Layout +namespace OfficeOpenXml.Export.PdfExport.Layout { - internal class GridLine - { - public static double Width = 0.125d; - public static double HalfWidth = Width / 2d; - public static double FourthWidth = Width / 4d; - public double X1; - public double Y1; - public double X2; - public double Y2; - - public GridLine(double x1, double y1, double x2, double y2) - { - X1 = x1; Y1 = y1; X2 = x2; Y2 = y2; - } - } internal class PdfGridlinesLayout { //------------------------------------------------------------------------------------------ diff --git a/src/EPPlus/Export/PdfExport/Layout/PdfLayout.cs b/src/EPPlus/Export/PdfExport/Layout/PdfLayout.cs index 246cba0a2f..71ae811010 100644 --- a/src/EPPlus/Export/PdfExport/Layout/PdfLayout.cs +++ b/src/EPPlus/Export/PdfExport/Layout/PdfLayout.cs @@ -10,17 +10,18 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ +using EPPlus.Export.Pdf.Layout; +using EPPlus.Export.Pdf.Resources; +using EPPlus.Export.Pdf.Settings; using EPPlus.Fonts.OpenType.Integration; using EPPlus.Fonts.OpenType.Integration.DataHolders; using EPPlus.Graphics; using OfficeOpenXml.Export.PdfExport.Data; -using OfficeOpenXml.Export.PdfExport.TextMapping; using OfficeOpenXml.Export.PdfExport.TextShaping; using OfficeOpenXml.Interfaces.Fonts; using OfficeOpenXml.Style; using System; using System.Collections.Generic; -using EPPlus.Export.Pdf.Settings; namespace OfficeOpenXml.Export.PdfExport.Layout { diff --git a/src/EPPlus/Export/PdfExport/TextMapping/PdfTextMap.cs b/src/EPPlus/Export/PdfExport/TextMapping/PdfTextMap.cs index 9712189d07..84a9503bca 100644 --- a/src/EPPlus/Export/PdfExport/TextMapping/PdfTextMap.cs +++ b/src/EPPlus/Export/PdfExport/TextMapping/PdfTextMap.cs @@ -10,12 +10,13 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ +using EPPlus.Export.Pdf.Resources; +using EPPlus.Export.Pdf.Layout; +using EPPlus.Export.Pdf.Settings; using EPPlus.Fonts.OpenType.Integration; using EPPlus.Fonts.OpenType.Integration.DataHolders; using EPPlus.Graphics.Units; using OfficeOpenXml.Export.PdfExport.Data; -using OfficeOpenXml.Export.PdfExport.Data.Dictionaries; -using OfficeOpenXml.Export.PdfExport.Layout; using OfficeOpenXml.Interfaces.Fonts; using OfficeOpenXml.Style; using OfficeOpenXml.Style.Dxf; @@ -439,14 +440,14 @@ private static PdfCellStyle GetFontStyle(ExcelRangeBase cell, PdfCellStyle cellS private static PdfCellAlignmentData GetContentAlignment(ExcelRangeBase cell) { var contentAlignment = new PdfCellAlignmentData(); - contentAlignment.HorizontalAlignment = cell.Style.HorizontalAlignment; - contentAlignment.VerticalAlignment = cell.Style.VerticalAlignment; + contentAlignment.HorizontalAlignment = (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)cell.Style.HorizontalAlignment; + contentAlignment.VerticalAlignment = (EPPlus.Export.Pdf.Enums.ExcelVerticalAlignment)cell.Style.VerticalAlignment; contentAlignment.Indent = cell.Style.Indent; contentAlignment.WrapText = cell.Style.WrapText; contentAlignment.ShrinkToFit = cell.Style.ShrinkToFit; contentAlignment.TextRotation = (cell.Style.TextRotation > 90) ? ((cell.Style.TextRotation == 255) ? 0 : 90 - cell.Style.TextRotation) : cell.Style.TextRotation; contentAlignment.IsVertical = cell.Style.TextRotation == 255 ? true : false; - contentAlignment.TextDirection = cell.Style.ReadingOrder; + contentAlignment.TextDirection = (EPPlus.Export.Pdf.Enums.ExcelReadingOrder)cell.Style.ReadingOrder; return contentAlignment; } @@ -603,13 +604,13 @@ public static PdfCellAlignmentData GetAlignmentData(PdfHeaderFooter headerFooter switch (headerFooter.Alignment) { case HeaderFooterAlignment.Left: - contentAlignment.HorizontalAlignment = ExcelHorizontalAlignment.Left; + contentAlignment.HorizontalAlignment = (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Left; break; case HeaderFooterAlignment.Center: - contentAlignment.HorizontalAlignment = ExcelHorizontalAlignment.Center; + contentAlignment.HorizontalAlignment = (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Center; break; case HeaderFooterAlignment.Right: - contentAlignment.HorizontalAlignment = ExcelHorizontalAlignment.Right; + contentAlignment.HorizontalAlignment = (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Right; break; } return contentAlignment; diff --git a/src/EPPlus/Properties/AssemblyInfo.cs b/src/EPPlus/Properties/AssemblyInfo.cs index 4d463e8c41..d3650b0bfb 100644 --- a/src/EPPlus/Properties/AssemblyInfo.cs +++ b/src/EPPlus/Properties/AssemblyInfo.cs @@ -21,7 +21,6 @@ Date Author Change [assembly: InternalsVisibleTo("EPPlusTest, PublicKey=00240000048000009400000006020000002400005253413100040000010001001dd11308ec93a6ebcec727e183a8972dc6f95c23ecc34aa04f40cbfc9c17b08b4a0ea5c00dcd203bace44d15a30ce8796e38176ae88e960ceff9cc439ab938738ba0e603e3d155fc298799b391c004fc0eb4393dd254ce25db341eb43303e4c488c9500e126f1288594f0710ec7d642e9c72e76dd860649f1c48249c00e31fba")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] [assembly: InternalsVisibleTo("EPPlus.DrawingRenderer.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010065aa00a9b0dcfa0e8debefb14c3b6c12ef658ce1cfbfafcb6eb7dbdc0c49e4f70ea144ba29d827453f0716ffc8c1d87450ce0cf255bda1def174915caaa78f373f291ce1a7edf91ba6f9dc961d937b19d46dd5d7d70a6e2097c749d43780f0d00b29a6f5aec5c1d191bee69de0c889a6d2566bef3cb235612351eae7015382d0")] -[assembly: InternalsVisibleTo("EPPlus.Export.Pdf, PublicKey=00240000048000009400000006020000002400005253413100040000010001005dd165010a983c48dcdee4d91434cd53fbc1651ba55263e9be73f5fc07ec63a666c90f3d9de7356c9214f461bae351a0375c5d75db0a9b45800bd347b81559c5a0bc84313de08d5d278b693e7da96678aa17d00fc8b825e37c0e28142c64cf6e7d174ebd11dc04142eb2ad438ca137f324ad9757bf02279a5f7a9d7479afbcb0")] [assembly: InternalsVisibleTo("EPPlus.Graphics, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e9b33e79ac9cbb1ca1e0df0aa47d956404607d99cb28a5982f936de662eb2fd3d3459c5b625fe4791606113ff89ee8b915e1446d348be8c6b5106fd3ffde14cf337587920e1c76bebc3bfd95d7525f3a5bc384fb19f3c06306b8cd5ffbe4284c36d8285f16cc50e6b408a2e197229108ce683d865a946b92a324cfba8dbf57c2")] [assembly: Guid("9dd43b8d-c4fe-4a8b-ad6e-47ef83bbbb01")] From b4c177999aee068899535ea80fe1ebb59ad665c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Wed, 24 Jun 2026 10:43:01 +0200 Subject: [PATCH 37/49] Fixed more errors --- .../DocumentObjects/PdfBorderRenderer.cs | 1 + .../DocumentObjects/PdfContentStream.cs | 1 + src/EPPlus.Export.Pdf/Enums/EPPlusEnums.cs | 2 +- src/EPPlus.Export.Pdf/ExcelPdf.cs | 2 +- .../Layout/CellAlignmentData.cs | 2 +- src/EPPlus.Export.Pdf/Layout/CellFillData.cs | 2 +- .../Layout/CellFillGradientData.cs | 1 + src/EPPlus.Export.Pdf/Layout/PdfBorderData.cs | 2 +- .../Layout/PdfCellBorderLayout.cs | 349 ----------------- .../Layout/PdfCellContentLayout.cs | 91 ++--- src/EPPlus.Export.Pdf/Layout/PdfCellLayout.cs | 202 +++++----- .../Resources/PdfPatternResource.cs | 2 +- .../Resources/PdfShadingResource.cs | 2 +- .../PdfExport/Layout/PdfGridlinesLayout.cs | 9 +- .../Export/PdfExport/Layout/PdfLayout.cs | 60 +-- src/EPPlus/Export/PdfExport/PdfCatalog.cs | 1 + .../TextMapping/PdfHeaderFooterCollection.cs | 3 +- .../PdfExport/TextMapping/PdfTextMap.cs | 358 +++++++++++++++++- .../PdfExport/TextShaping/PdfTextShaper.cs | 18 +- 19 files changed, 565 insertions(+), 543 deletions(-) diff --git a/src/EPPlus.Export.Pdf/DocumentObjects/PdfBorderRenderer.cs b/src/EPPlus.Export.Pdf/DocumentObjects/PdfBorderRenderer.cs index 15ee672a93..9f5b036db3 100644 --- a/src/EPPlus.Export.Pdf/DocumentObjects/PdfBorderRenderer.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/PdfBorderRenderer.cs @@ -10,6 +10,7 @@ Date Author Change ************************************************************************************************* 10/07/2025 EPPlus Software AB EPPlus.Fonts.OpenType 1.0 *************************************************************************************************/ +using EPPlus.Export.Pdf.Enums; using EPPlus.Export.Pdf.Helpers; using EPPlus.Export.Pdf.Layout; diff --git a/src/EPPlus.Export.Pdf/DocumentObjects/PdfContentStream.cs b/src/EPPlus.Export.Pdf/DocumentObjects/PdfContentStream.cs index 5c8f6b4c22..ebdf8876b7 100644 --- a/src/EPPlus.Export.Pdf/DocumentObjects/PdfContentStream.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/PdfContentStream.cs @@ -24,6 +24,7 @@ Date Author Change using EPPlus.Export.Pdf.Helpers; using EPPlus.Export.Pdf.Resources; using EPPlus.Export.Pdf.Settings; +using EPPlus.Export.Pdf.Enums; namespace EPPlus.Export.Pdf.DocumentObjects { diff --git a/src/EPPlus.Export.Pdf/Enums/EPPlusEnums.cs b/src/EPPlus.Export.Pdf/Enums/EPPlusEnums.cs index a98efa5b4e..16fd7987a6 100644 --- a/src/EPPlus.Export.Pdf/Enums/EPPlusEnums.cs +++ b/src/EPPlus.Export.Pdf/Enums/EPPlusEnums.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; -namespace EPPlus.Export.Pdf.Helpers +namespace EPPlus.Export.Pdf.Enums { /// /// Horizontal text alignment diff --git a/src/EPPlus.Export.Pdf/ExcelPdf.cs b/src/EPPlus.Export.Pdf/ExcelPdf.cs index 0db57f517b..dd806a3792 100644 --- a/src/EPPlus.Export.Pdf/ExcelPdf.cs +++ b/src/EPPlus.Export.Pdf/ExcelPdf.cs @@ -14,12 +14,12 @@ Date Author Change using EPPlus.Export.Pdf.DocumentObjects; using EPPlus.Export.Pdf.Resources; using EPPlus.Export.Pdf.Settings; -using EPPlus.Export.Pdf.Helpers; using EPPlus.Graphics; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using EPPlus.Export.Pdf.Enums; namespace EPPlus.Export.Pdf { diff --git a/src/EPPlus.Export.Pdf/Layout/CellAlignmentData.cs b/src/EPPlus.Export.Pdf/Layout/CellAlignmentData.cs index eeea904435..68cdd7a0e9 100644 --- a/src/EPPlus.Export.Pdf/Layout/CellAlignmentData.cs +++ b/src/EPPlus.Export.Pdf/Layout/CellAlignmentData.cs @@ -10,7 +10,7 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -using EPPlus.Export.Pdf.Helpers; +using EPPlus.Export.Pdf.Enums; namespace EPPlus.Export.Pdf.Layout { diff --git a/src/EPPlus.Export.Pdf/Layout/CellFillData.cs b/src/EPPlus.Export.Pdf/Layout/CellFillData.cs index 756c9bf5f3..bd78f0d7eb 100644 --- a/src/EPPlus.Export.Pdf/Layout/CellFillData.cs +++ b/src/EPPlus.Export.Pdf/Layout/CellFillData.cs @@ -11,7 +11,7 @@ Date Author Change 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ using System.Drawing; -using EPPlus.Export.Pdf.Helpers; +using EPPlus.Export.Pdf.Enums; namespace EPPlus.Export.Pdf.Layout { diff --git a/src/EPPlus.Export.Pdf/Layout/CellFillGradientData.cs b/src/EPPlus.Export.Pdf/Layout/CellFillGradientData.cs index bb0f4cd85a..dad5e422e3 100644 --- a/src/EPPlus.Export.Pdf/Layout/CellFillGradientData.cs +++ b/src/EPPlus.Export.Pdf/Layout/CellFillGradientData.cs @@ -10,6 +10,7 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ +using EPPlus.Export.Pdf.Enums; using EPPlus.Export.Pdf.Helpers; using System.Drawing; diff --git a/src/EPPlus.Export.Pdf/Layout/PdfBorderData.cs b/src/EPPlus.Export.Pdf/Layout/PdfBorderData.cs index f665b97da0..c261c809a4 100644 --- a/src/EPPlus.Export.Pdf/Layout/PdfBorderData.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfBorderData.cs @@ -11,7 +11,7 @@ Date Author Change 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ using System.Drawing; -using EPPlus.Export.Pdf.Helpers; +using EPPlus.Export.Pdf.Enums; namespace EPPlus.Export.Pdf.Layout { diff --git a/src/EPPlus.Export.Pdf/Layout/PdfCellBorderLayout.cs b/src/EPPlus.Export.Pdf/Layout/PdfCellBorderLayout.cs index 6d4fc415a7..0c9487730c 100644 --- a/src/EPPlus.Export.Pdf/Layout/PdfCellBorderLayout.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfCellBorderLayout.cs @@ -233,355 +233,6 @@ public void UpdateLocalBorderPosition() * Following methods needs to be refactored into 1 method. * Less code is more good. */ - public static ExcelDxfBorderItem GetTopBorderItem(ExcelRangeBase cell, ExcelBorderItem xfBorder, ExcelTable table, ExcelTableNamedStyle tableStyle) - { - var range = table.Range; - int tableRow = cell._fromRow - range._fromRow; - int tableCol = cell._fromCol - range._fromCol; - int ts = table.ShowHeader ? 1 : 0; - var top = tableRow == 0 ? tableStyle.WholeTable.Style.Border.Top : tableStyle.WholeTable.Style.Border.Horizontal; - if (table.ShowHeader && tableRow == 0) - { - if (tableStyle.HeaderRow.Style.Border.Top.HasValue) - { - top = tableStyle.HeaderRow.Style.Border.Top; - } - if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstHeaderCell.Style.Border.Top.HasValue) - { - top = tableStyle.FirstHeaderCell.Style.Border.Top; - } - if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastHeaderCell.Style.Border.Top.HasValue) - { - top = tableStyle.LastHeaderCell.Style.Border.Top; - } - } - else if (table.ShowTotal && cell._fromRow == range._toRow) - { - if (tableStyle.TotalRow.Style.Border.Top.HasValue) - { - top = tableStyle.TotalRow.Style.Border.Top; - } - if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstTotalCell.Style.Border.Top.HasValue) - { - top = tableStyle.FirstTotalCell.Style.Border.Top; - } - if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastTotalCell.Style.Border.Top.HasValue) - { - top = tableStyle.LastTotalCell.Style.Border.Top; - } - } - else - { - if (table.ShowColumnStripes &&/* tableStyle.FirstColumnStripe.Style.Border.Top.HasValue &&*/ (tableCol & 1) == 0) - { - if (cell._fromRow - ts > range._fromRow && cell._fromRow < range._toRow) - { - top = tableStyle.FirstColumnStripe.Style.Border.Horizontal; - } - else if (cell._fromRow <= range._toRow) - { - top = null; - } - else - { - top = tableStyle.FirstColumnStripe.Style.Border.Top; - } - } - if (table.ShowColumnStripes && /*tableStyle.SecondColumnStripe.Style.Border.Top.HasValue &&*/ (tableCol & 1) != 0) - { - if (cell._fromRow + ts > range._fromRow && cell._fromRow < range._toRow) - { - top = tableStyle.SecondColumnStripe.Style.Border.Horizontal; - } - else if (cell._fromRow <= range._toRow) - { - top = null; - } - else - { - top = tableStyle.SecondColumnStripe.Style.Border.Top; - } - } - if (table.ShowRowStripes && tableStyle.FirstRowStripe.Style.Border.Top.HasValue && (tableRow & 1) != 0) - { - top = tableStyle.FirstRowStripe.Style.Border.Top; - } - if (table.ShowRowStripes && tableStyle.SecondRowStripe.Style.Border.Top.HasValue && (tableRow & 1) == 0) - { - top = tableStyle.SecondRowStripe.Style.Border.Top; - } - if (table.ShowLastColumn && tableStyle.LastColumn.Style.Border.Top.HasValue && cell._fromCol == range._toCol) - { - top = tableStyle.LastColumn.Style.Border.Top; - } - if (table.ShowFirstColumn && tableStyle.FirstColumn.Style.Border.Top.HasValue && tableCol == range._toCol) - { - top = tableStyle.FirstColumn.Style.Border.Top; - } - } - return top; - } - public static ExcelDxfBorderItem GetBottomBorderItem(ExcelRangeBase cell, ExcelBorderItem xfBorder, ExcelTable table, ExcelTableNamedStyle tableStyle) - { - var range = table.Range; - int tableRow = cell._fromRow - range._fromRow; - int tableCol = cell._fromCol - range._fromCol; - var bottom = range._toRow == cell._fromRow ? tableStyle.WholeTable.Style.Border.Bottom : null; - if (table.ShowHeader && tableRow == 0) - { - if (tableStyle.HeaderRow.Style.Border.Bottom.HasValue) - { - bottom = tableStyle.HeaderRow.Style.Border.Bottom; - } - if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstHeaderCell.Style.Border.Bottom.HasValue) - { - bottom = tableStyle.FirstHeaderCell.Style.Border.Bottom; - } - if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastHeaderCell.Style.Border.Bottom.HasValue) - { - bottom = tableStyle.LastHeaderCell.Style.Border.Bottom; - } - } - else if (table.ShowTotal && cell._fromRow == range._toRow) - { - if (tableStyle.TotalRow.Style.Border.Bottom.HasValue) - { - bottom = tableStyle.TotalRow.Style.Border.Bottom; - } - if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstTotalCell.Style.Border.Bottom.HasValue) - { - bottom = tableStyle.FirstTotalCell.Style.Border.Bottom; - } - if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastTotalCell.Style.Border.Bottom.HasValue) - { - bottom = tableStyle.LastTotalCell.Style.Border.Bottom; - } - } - else - { - if (table.ShowColumnStripes && tableStyle.FirstColumnStripe.Style.Border.Bottom.HasValue && (tableCol & 1) != 0) - { - if (cell._fromRow > range._fromRow && cell._fromRow < range._toRow) - { - bottom = tableStyle.FirstColumnStripe.Style.Border.Horizontal; - } - else if (cell._fromRow < range._toRow) - { - bottom = null; - } - else - { - bottom = tableStyle.FirstColumnStripe.Style.Border.Bottom; - } - } - if (table.ShowColumnStripes && tableStyle.SecondColumnStripe.Style.Border.Bottom.HasValue && (tableCol & 1) == 0) - { - if (cell._fromRow > range._fromRow && cell._fromRow < range._toRow) - { - bottom = tableStyle.SecondColumnStripe.Style.Border.Horizontal; - } - else if (cell._fromRow < range._toRow) - { - bottom = null; - } - else - { - bottom = tableStyle.SecondColumnStripe.Style.Border.Bottom; - } - } - if (table.ShowRowStripes && tableStyle.FirstRowStripe.Style.Border.Bottom.HasValue && (tableRow & 1) != 0) - { - bottom = tableStyle.FirstRowStripe.Style.Border.Bottom; - } - if (table.ShowRowStripes && tableStyle.SecondRowStripe.Style.Border.Bottom.HasValue && (tableRow & 1) == 0) - { - bottom = tableStyle.SecondRowStripe.Style.Border.Bottom; - } - if (table.ShowLastColumn && tableStyle.LastColumn.Style.Border.Bottom.HasValue && cell._fromCol == range._toCol) - { - bottom = tableStyle.LastColumn.Style.Border.Bottom; - } - if (table.ShowFirstColumn && tableStyle.FirstColumn.Style.Border.Bottom.HasValue && tableCol == 0) - { - bottom = tableStyle.FirstColumn.Style.Border.Bottom; - } - } - return bottom; - } - public static ExcelDxfBorderItem GetLeftBorderItem(ExcelRangeBase cell, ExcelBorderItem xfBorder, ExcelTable table, ExcelTableNamedStyle tableStyle) - { - var range = table.Range; - int tableRow = cell._fromRow - range._fromRow; - int tableCol = cell._fromCol - range._fromCol; - var left = tableCol == 0 ? tableStyle.WholeTable.Style.Border.Left : tableStyle.WholeTable.Style.Border.Vertical; - if (table.ShowHeader && tableRow == 0) - { - if (tableStyle.HeaderRow.Style.Border.Left.HasValue) - { - left = tableStyle.HeaderRow.Style.Border.Left; - } - if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstHeaderCell.Style.Border.Left.HasValue) - { - left = tableStyle.FirstHeaderCell.Style.Border.Left; - } - if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastHeaderCell.Style.Border.Left.HasValue) - { - left = tableStyle.LastHeaderCell.Style.Border.Left; - } - } - else if (table.ShowTotal && cell._fromRow == range._toRow) - { - if (tableStyle.TotalRow.Style.Border.Left.HasValue) - { - left = tableStyle.TotalRow.Style.Border.Left; - } - if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstTotalCell.Style.Border.Left.HasValue) - { - left = tableStyle.FirstTotalCell.Style.Border.Left; - } - if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastTotalCell.Style.Border.Left.HasValue) - { - left = tableStyle.LastTotalCell.Style.Border.Left; - } - } - else - { - if (table.ShowColumnStripes && tableStyle.FirstColumnStripe.Style.Border.Left.HasValue && (tableCol & 1) != 0) - { - left = tableStyle.FirstColumnStripe.Style.Border.Left; - } - if (table.ShowColumnStripes && tableStyle.SecondColumnStripe.Style.Border.Left.HasValue && (tableCol & 1) == 0) - { - left = tableStyle.SecondColumnStripe.Style.Border.Left; - } - if (table.ShowRowStripes && tableStyle.FirstRowStripe.Style.Border.Left.HasValue && (tableRow & 1) != 0) - { - if (cell._fromCol > range._fromCol && cell._fromCol < range._toCol) - { - left = tableStyle.FirstRowStripe.Style.Border.Vertical; - } - else if (cell._fromCol >= range._toCol) - { - left = null; - } - else - { - left = tableStyle.FirstRowStripe.Style.Border.Left; - } - } - if (table.ShowRowStripes && tableStyle.SecondRowStripe.Style.Border.Left.HasValue && (tableRow & 1) == 0) - { - if (cell._fromCol > range._fromCol && cell._fromCol < range._toCol) - { - left = tableStyle.SecondRowStripe.Style.Border.Vertical; - } - else if (cell._fromCol >= range._toCol) - { - left = null; - } - else - { - left = tableStyle.SecondRowStripe.Style.Border.Left; - } - } - if (table.ShowLastColumn && tableStyle.LastColumn.Style.Border.Left.HasValue && cell._fromCol == range._toCol) - { - left = tableStyle.LastColumn.Style.Border.Left; - } - if (table.ShowFirstColumn && tableStyle.FirstColumn.Style.Border.Left.HasValue && tableCol == range._toCol) - { - left = tableStyle.FirstColumn.Style.Border.Left; - } - } - return left; - } - public static ExcelDxfBorderItem GetRightBorderItem(ExcelRangeBase cell, ExcelBorderItem xfBorder, ExcelTable table, ExcelTableNamedStyle tableStyle) - { - var range = table.Range; - int tableRow = cell._fromRow - range._fromRow; - int tableCol = cell._fromCol - range._fromCol; - var right = cell._fromCol == range._toCol ? tableStyle.WholeTable.Style.Border.Right : tableStyle.WholeTable.Style.Border.Vertical; - if (table.ShowHeader && tableRow == 0) - { - if (tableStyle.HeaderRow.Style.Border.Right.HasValue) - { - right = tableStyle.HeaderRow.Style.Border.Right; - } - if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstHeaderCell.Style.Border.Right.HasValue) - { - right = tableStyle.FirstHeaderCell.Style.Border.Right; - } - if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastHeaderCell.Style.Border.Right.HasValue) - { - right = tableStyle.LastHeaderCell.Style.Border.Right; - } - } - else if (table.ShowTotal && cell._fromRow == range._toRow) - { - if (tableStyle.TotalRow.Style.Border.Right.HasValue) - { - right = tableStyle.TotalRow.Style.Border.Right; - } - if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstTotalCell.Style.Border.Right.HasValue) - { - right = tableStyle.FirstTotalCell.Style.Border.Right; - } - if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastTotalCell.Style.Border.Right.HasValue) - { - right = tableStyle.LastTotalCell.Style.Border.Right; - } - } - else - { - if (table.ShowColumnStripes && tableStyle.FirstColumnStripe.Style.Border.Right.HasValue && (tableCol & 1) != 0) - { - right = tableStyle.FirstColumnStripe.Style.Border.Right; - } - if (table.ShowColumnStripes && tableStyle.SecondColumnStripe.Style.Border.Right.HasValue && (tableCol & 1) == 0) - { - right = tableStyle.SecondColumnStripe.Style.Border.Right; - } - if (table.ShowRowStripes && tableStyle.FirstRowStripe.Style.Border.Right.HasValue && (tableRow & 1) != 0) - { - if (cell._fromCol > range._fromCol && cell._fromCol < range._toCol) - { - right = tableStyle.FirstRowStripe.Style.Border.Vertical; - } - else if (cell._fromCol < range._toCol) - { - right = null; - } - else - { - right = tableStyle.FirstRowStripe.Style.Border.Right; - } - } - if (table.ShowRowStripes && tableStyle.SecondRowStripe.Style.Border.Right.HasValue && (tableRow & 1) == 0) - { - if (cell._fromCol > range._fromCol && cell._fromCol < range._toCol) - { - right = tableStyle.SecondRowStripe.Style.Border.Vertical; - } - else if (cell._fromCol < range._toCol) - { - right = null; - } - else - { - right = tableStyle.SecondRowStripe.Style.Border.Right; - } - } - if (table.ShowLastColumn && tableStyle.LastColumn.Style.Border.Right.HasValue && cell._fromCol == range._toCol) - { - right = tableStyle.LastColumn.Style.Border.Right; - } - if (table.ShowFirstColumn && tableStyle.FirstColumn.Style.Border.Right.HasValue && tableCol == range._toCol) - { - right = tableStyle.FirstColumn.Style.Border.Right; - } - } - return right; - } } } diff --git a/src/EPPlus.Export.Pdf/Layout/PdfCellContentLayout.cs b/src/EPPlus.Export.Pdf/Layout/PdfCellContentLayout.cs index 312598012d..69d0af62d6 100644 --- a/src/EPPlus.Export.Pdf/Layout/PdfCellContentLayout.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfCellContentLayout.cs @@ -10,11 +10,12 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -using EPPlus.Export.Pdf.Helpers; +using EPPlus.Export.Pdf.Enums; using EPPlus.Export.Pdf.Resources; using EPPlus.Export.Pdf.Settings; using EPPlus.Fonts.OpenType.Integration; using EPPlus.Graphics; +using OfficeOpenXml.Interfaces.Fonts; using System.Collections.Generic; using System.Diagnostics; using Vector2 = EPPlus.Graphics.Geometry.Vector2; @@ -400,18 +401,18 @@ private Vector2 CalculateRotatedAlignment(double textLength, double textHeight, // } //} - //// Set clipping to the cell's own bounds. cellY is the top edge (same convention as the constructor). - //internal void SetupClipping(double cellX, double cellY, double cellWidth, double cellHeight) - //{ - // Clip = true; - // Clipping = new Rect() - // { - // X = cellX + rightMargin, - // Y = cellY - cellHeight, // bottom-left corner in PDF space - // Width = cellWidth - rightMargin * 2, - // Height = cellHeight - // }; - //} + // Set clipping to the cell's own bounds. cellY is the top edge (same convention as the constructor). + internal void SetupClipping(double cellX, double cellY, double cellWidth, double cellHeight) + { + Clip = true; + Clipping = new Rect() + { + X = cellX + rightMargin, + Y = cellY - cellHeight, // bottom-left corner in PDF space + Width = cellWidth - rightMargin * 2, + Height = cellHeight + }; + } ////Create clipping rectangle. //internal void CreateClippingRect(List cells) @@ -447,41 +448,41 @@ private Vector2 CalculateRotatedAlignment(double textLength, double textHeight, // }; //} - //internal void GidsAndCharMap(PdfDictionaries dictionaries) - //{ - // foreach (var tf in ShapedTexts) - // { - // var usedFonts = tf.UsedFonts; + internal void GidsAndCharMap(PdfDictionaries dictionaries) + { + foreach (var tf in ShapedTexts) + { + var usedFonts = tf.UsedFonts; - // foreach (var glyph in tf.ShapedText.Glyphs) - // { - // if (glyph.FontId >= usedFonts.Count) - // continue; + foreach (var glyph in tf.ShapedText.Glyphs) + { + if (glyph.FontId >= usedFonts.Count) + continue; - // var font = usedFonts[glyph.FontId]; + var font = usedFonts[glyph.FontId]; - // dictionaries.Fonts[font.FullName].Gids.Add(glyph.GlyphId); - // dictionaries.Fonts[font.FullName].fontData = font; + dictionaries.Fonts[font.FullName].Gids.Add(glyph.GlyphId); + dictionaries.Fonts[font.FullName].fontData = font; - // if (!dictionaries.Fonts[font.FullName].charactermappings.ContainsKey(glyph.GlyphId)) - // { - // var chars = ExtractCharactersForGlyph(glyph, tf.ShapedText.OriginalText); - // if (!string.IsNullOrEmpty(chars)) - // { - // dictionaries.Fonts[font.FullName].charactermappings[glyph.GlyphId] = chars; - // } - // } - // } - // } - //} - //private string ExtractCharactersForGlyph(ShapedGlyph glyph, string textLine) - //{ - // var chars = new System.Text.StringBuilder(); - // for (int i = 0; i < glyph.CharCount && glyph.ClusterIndex + i < textLine.Length; i++) - // { - // chars.Append(textLine[glyph.ClusterIndex + i]); - // } - // return chars.ToString(); - //} + if (!dictionaries.Fonts[font.FullName].charactermappings.ContainsKey(glyph.GlyphId)) + { + var chars = ExtractCharactersForGlyph(glyph, tf.ShapedText.OriginalText); + if (!string.IsNullOrEmpty(chars)) + { + dictionaries.Fonts[font.FullName].charactermappings[glyph.GlyphId] = chars; + } + } + } + } + } + private string ExtractCharactersForGlyph(ShapedGlyph glyph, string textLine) + { + var chars = new System.Text.StringBuilder(); + for (int i = 0; i < glyph.CharCount && glyph.ClusterIndex + i < textLine.Length; i++) + { + chars.Append(textLine[glyph.ClusterIndex + i]); + } + return chars.ToString(); + } } } diff --git a/src/EPPlus.Export.Pdf/Layout/PdfCellLayout.cs b/src/EPPlus.Export.Pdf/Layout/PdfCellLayout.cs index b1024000fb..d39a15c2e0 100644 --- a/src/EPPlus.Export.Pdf/Layout/PdfCellLayout.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfCellLayout.cs @@ -10,6 +10,7 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ +using EPPlus.Export.Pdf.Enums; using EPPlus.Export.Pdf.Helpers; using EPPlus.Export.Pdf.Resources; using EPPlus.Export.Pdf.Settings; @@ -227,105 +228,104 @@ private string AddShadingResourceData(Dictionary sha // LocalPosition = new Vector2(LocalPosition.X + GridLine.FourthWidth, LocalPosition.Y + GridLine.FourthWidth); // } - // public void UpdateShadingPositionMatrix(PdfPageSettings pageSettings) - // { - // //LocalPosition = new Vector2(LocalPosition.X, pageSettings.PageSize.HeightPu - System.Math.Abs(LocalPosition.Y) - Size.Y); - // if (CellFillData.GradientFillData != null) - // { - // if (CellFillData.GradientFillData.GradientType == ExcelFillGradientType.Linear) - // { - // switch (CellFillData.GradientFillData.Degree) - // { - // case 45d: - // CellFillData.GradientFillData.coords = [0, 1, 1, 0]; - // break; - // case 90d: - // CellFillData.GradientFillData.coords = [0, 1, 0, 0]; - // break; - // case 135d: - // CellFillData.GradientFillData.coords = [1, 1, 0, 0]; - // break; - // case 180d: - // CellFillData.GradientFillData.coords = [1, 0, 0, 0]; - // break; - // case 225d: - // CellFillData.GradientFillData.coords = [1, 0, 0, 1]; - // break; - // case 270d: - // CellFillData.GradientFillData.coords = [0, 0, 0, 1]; - // break; - // case 315d: - // CellFillData.GradientFillData.coords = [0, 0, 1, 1]; - // break; - // case 0d: - // default: - // CellFillData.GradientFillData.coords = [0, 0, 1, 0]; - // break; - // } - // CellFillData.GradientFillData.matrix = [Size.X, 0, 0, Size.Y, LocalPosition.X, LocalPosition.Y]; - // } - // else if (CellFillData.GradientFillData.GradientType == ExcelFillGradientType.Path) - // { - // double x = LocalPosition.X; - // double y = LocalPosition.Y; - // double width = Size.X; - // double height = Size.Y; - // var top = CellFillData.GradientFillData.Top; - // var bottom = CellFillData.GradientFillData.Bottom; - // var left = CellFillData.GradientFillData.Left; - // var right = CellFillData.GradientFillData.Right; - // double r = 1; - // if (top == 0 && bottom == 0 && left == 0 && right == 0) - // { - // CellFillData.GradientFillData.coords = [0, 1, 0, 0, 1, r]; - // } - // else if (top == 0 && bottom == 0 && left == 1 && right == 1) - // { - // CellFillData.GradientFillData.coords = [1, 1, 0, 1, 1, r]; - // } - // else if (top == 1 && bottom == 1 && left == 0 && right == 0) - // { - // CellFillData.GradientFillData.coords = [0, 0, 0, 0, 0, r]; - // } - // else if (top == 1 && bottom == 1 && left == 1 && right == 1) - // { - // CellFillData.GradientFillData.coords = [1, 0, 0, 1, 0, r]; - // } - // else if (top == 0.5 && bottom == 0.5 && left == 0.5 && right == 0.5) - // { - // CellFillData.GradientFillData.coords = [0.5, 0.5, 0, 0.5, 0.5, r]; - // } - // CellFillData.GradientFillData.matrix = [Size.X, 0, 0, Size.Y, LocalPosition.X, LocalPosition.Y]; - // //double cx = x; - // //double cy = y + height; - // //if (!double.IsNaN(left) && !double.IsNaN(right) && !double.IsNaN(top) && !double.IsNaN(bottom)) // bottom-right - // //{ - // // if (top < 1d && bottom < 1d &&left < 1d && right < 1d) - // // { - // // cx = x + width / 2d; - // // cy = y + height / 2d; - // // } - // // else - // // { - // // cx = x + width; - // // cy = y; - // // } - // //} - // //else if (!double.IsNaN(left) && !double.IsNaN(right)) // bottom-left - // //{ - // // cx = x + width; - // // cy = y + height; - // //} - // //else if (!double.IsNaN(top) && !double.IsNaN(bottom)) // top-right - // //{ - // // cx = x; - // // cy = y; - // //} - // //double r = System.Math.Sqrt(width * width + height * height) / 2d; - // //CellFillData.GradientFillData.coords = [cx, cy, 0, cx, cy, r]; - // } - // } - // } - //} + public void UpdateShadingPositionMatrix(PdfPageSettings pageSettings) + { + //LocalPosition = new Vector2(LocalPosition.X, pageSettings.PageSize.HeightPu - System.Math.Abs(LocalPosition.Y) - Size.Y); + if (CellFillData.GradientFillData != null) + { + if (CellFillData.GradientFillData.GradientType == ExcelFillGradientType.Linear) + { + switch (CellFillData.GradientFillData.Degree) + { + case 45d: + CellFillData.GradientFillData.coords = [0, 1, 1, 0]; + break; + case 90d: + CellFillData.GradientFillData.coords = [0, 1, 0, 0]; + break; + case 135d: + CellFillData.GradientFillData.coords = [1, 1, 0, 0]; + break; + case 180d: + CellFillData.GradientFillData.coords = [1, 0, 0, 0]; + break; + case 225d: + CellFillData.GradientFillData.coords = [1, 0, 0, 1]; + break; + case 270d: + CellFillData.GradientFillData.coords = [0, 0, 0, 1]; + break; + case 315d: + CellFillData.GradientFillData.coords = [0, 0, 1, 1]; + break; + case 0d: + default: + CellFillData.GradientFillData.coords = [0, 0, 1, 0]; + break; + } + CellFillData.GradientFillData.matrix = [Size.X, 0, 0, Size.Y, LocalPosition.X, LocalPosition.Y]; + } + else if (CellFillData.GradientFillData.GradientType == ExcelFillGradientType.Path) + { + double x = LocalPosition.X; + double y = LocalPosition.Y; + double width = Size.X; + double height = Size.Y; + var top = CellFillData.GradientFillData.Top; + var bottom = CellFillData.GradientFillData.Bottom; + var left = CellFillData.GradientFillData.Left; + var right = CellFillData.GradientFillData.Right; + double r = 1; + if (top == 0 && bottom == 0 && left == 0 && right == 0) + { + CellFillData.GradientFillData.coords = [0, 1, 0, 0, 1, r]; + } + else if (top == 0 && bottom == 0 && left == 1 && right == 1) + { + CellFillData.GradientFillData.coords = [1, 1, 0, 1, 1, r]; + } + else if (top == 1 && bottom == 1 && left == 0 && right == 0) + { + CellFillData.GradientFillData.coords = [0, 0, 0, 0, 0, r]; + } + else if (top == 1 && bottom == 1 && left == 1 && right == 1) + { + CellFillData.GradientFillData.coords = [1, 0, 0, 1, 0, r]; + } + else if (top == 0.5 && bottom == 0.5 && left == 0.5 && right == 0.5) + { + CellFillData.GradientFillData.coords = [0.5, 0.5, 0, 0.5, 0.5, r]; + } + CellFillData.GradientFillData.matrix = [Size.X, 0, 0, Size.Y, LocalPosition.X, LocalPosition.Y]; + //double cx = x; + //double cy = y + height; + //if (!double.IsNaN(left) && !double.IsNaN(right) && !double.IsNaN(top) && !double.IsNaN(bottom)) // bottom-right + //{ + // if (top < 1d && bottom < 1d &&left < 1d && right < 1d) + // { + // cx = x + width / 2d; + // cy = y + height / 2d; + // } + // else + // { + // cx = x + width; + // cy = y; + // } + //} + //else if (!double.IsNaN(left) && !double.IsNaN(right)) // bottom-left + //{ + // cx = x + width; + // cy = y + height; + //} + //else if (!double.IsNaN(top) && !double.IsNaN(bottom)) // top-right + //{ + // cx = x; + // cy = y; + //} + //double r = System.Math.Sqrt(width * width + height * height) / 2d; + //CellFillData.GradientFillData.coords = [cx, cy, 0, cx, cy, r]; + } + } + } } -} \ No newline at end of file +} diff --git a/src/EPPlus.Export.Pdf/Resources/PdfPatternResource.cs b/src/EPPlus.Export.Pdf/Resources/PdfPatternResource.cs index c0eeb2882a..7b8e939348 100644 --- a/src/EPPlus.Export.Pdf/Resources/PdfPatternResource.cs +++ b/src/EPPlus.Export.Pdf/Resources/PdfPatternResource.cs @@ -13,7 +13,7 @@ Date Author Change using EPPlus.Export.Pdf.Layout; using EPPlus.Export.Pdf.DocumentObjects.Patterns; using EPPlus.Export.Pdf.DocumentObjects; -using EPPlus.Export.Pdf.Helpers; +using EPPlus.Export.Pdf.Enums; namespace EPPlus.Export.Pdf.Resources { diff --git a/src/EPPlus.Export.Pdf/Resources/PdfShadingResource.cs b/src/EPPlus.Export.Pdf/Resources/PdfShadingResource.cs index 944bb91c65..ba565f6b16 100644 --- a/src/EPPlus.Export.Pdf/Resources/PdfShadingResource.cs +++ b/src/EPPlus.Export.Pdf/Resources/PdfShadingResource.cs @@ -12,7 +12,7 @@ Date Author Change *************************************************************************************************/ using EPPlus.Export.Pdf.DocumentObjects.Patterns; using EPPlus.Export.Pdf.DocumentObjects.Shadings; -using EPPlus.Export.Pdf.Helpers; +using EPPlus.Export.Pdf.Enums; using EPPlus.Export.Pdf.Layout; namespace EPPlus.Export.Pdf.Resources diff --git a/src/EPPlus/Export/PdfExport/Layout/PdfGridlinesLayout.cs b/src/EPPlus/Export/PdfExport/Layout/PdfGridlinesLayout.cs index 04e915e316..d2f9f50e7d 100644 --- a/src/EPPlus/Export/PdfExport/Layout/PdfGridlinesLayout.cs +++ b/src/EPPlus/Export/PdfExport/Layout/PdfGridlinesLayout.cs @@ -13,6 +13,7 @@ Date Author Change using EPPlus.Export.Pdf.Settings; using OfficeOpenXml.Export.PdfExport.Data; using EPPlus.Export.Pdf.Layout; +using OfficeOpenXml.Style; namespace OfficeOpenXml.Export.PdfExport.Layout { @@ -109,20 +110,20 @@ public static void AddGridLines(PdfPageSettings pageSettings, Page page, PdfPage var halign = cell.ContentAligmnet.HorizontalAlignment; // Right spill — Left / General alignment. - if (halign == ExcelHorizontalAlignment.Left || - halign == ExcelHorizontalAlignment.General) + if (halign == (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Left || + halign == (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.General) { MarkSpillRight(page, ri, ci, colCount, colX, spill, spillBlocked); } // Left spill — Right alignment. - else if (halign == ExcelHorizontalAlignment.Right) + else if (halign == (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Right) { MarkSpillLeft(page, ri, ci, colX, spill, spillBlocked); } // Both directions — Center alignment, half the excess each way. - else if (halign == ExcelHorizontalAlignment.Center) + else if (halign == (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Center) { double halfSpill = spill / 2d; MarkSpillRight(page, ri, ci, colCount, colX, halfSpill, spillBlocked); diff --git a/src/EPPlus/Export/PdfExport/Layout/PdfLayout.cs b/src/EPPlus/Export/PdfExport/Layout/PdfLayout.cs index 71ae811010..b90a611ca8 100644 --- a/src/EPPlus/Export/PdfExport/Layout/PdfLayout.cs +++ b/src/EPPlus/Export/PdfExport/Layout/PdfLayout.cs @@ -158,10 +158,10 @@ internal static Transform GetCatalog(PdfPageSettings pageSettings, PdfDictionari height: info.Height); // full merge height diagBorder.Name = map.Name; // Suppress edge borders — this layout exists only for the diagonal - diagBorder.BorderData.Top.BorderStyle = ExcelBorderStyle.None; - diagBorder.BorderData.Bottom.BorderStyle = ExcelBorderStyle.None; - diagBorder.BorderData.Left.BorderStyle = ExcelBorderStyle.None; - diagBorder.BorderData.Right.BorderStyle = ExcelBorderStyle.None; + diagBorder.BorderData.Top.BorderStyle = (EPPlus.Export.Pdf.Enums.ExcelBorderStyle)ExcelBorderStyle.None; + diagBorder.BorderData.Bottom.BorderStyle = (EPPlus.Export.Pdf.Enums.ExcelBorderStyle)ExcelBorderStyle.None; + diagBorder.BorderData.Left.BorderStyle = (EPPlus.Export.Pdf.Enums.ExcelBorderStyle)ExcelBorderStyle.None; + diagBorder.BorderData.Right.BorderStyle = (EPPlus.Export.Pdf.Enums.ExcelBorderStyle)ExcelBorderStyle.None; pageLayout.AddChild(diagBorder); } } @@ -195,10 +195,10 @@ internal static Transform GetCatalog(PdfPageSettings pageSettings, PdfDictionari if (map.Merged && map.MergedAddress != null) { var addr = map.MergedAddress; - if (row != addr.Start.Row) border.BorderData.Top.BorderStyle = ExcelBorderStyle.None; - if (row != addr.End.Row) border.BorderData.Bottom.BorderStyle = ExcelBorderStyle.None; - if (col != addr.Start.Column) border.BorderData.Left.BorderStyle = ExcelBorderStyle.None; - if (col != addr.End.Column) border.BorderData.Right.BorderStyle = ExcelBorderStyle.None; + if (row != addr.Start.Row) border.BorderData.Top.BorderStyle = (EPPlus.Export.Pdf.Enums.ExcelBorderStyle)ExcelBorderStyle.None; + if (row != addr.End.Row) border.BorderData.Bottom.BorderStyle = (EPPlus.Export.Pdf.Enums.ExcelBorderStyle)ExcelBorderStyle.None; + if (col != addr.Start.Column) border.BorderData.Left.BorderStyle = (EPPlus.Export.Pdf.Enums.ExcelBorderStyle)ExcelBorderStyle.None; + if (col != addr.End.Column) border.BorderData.Right.BorderStyle = (EPPlus.Export.Pdf.Enums.ExcelBorderStyle)ExcelBorderStyle.None; } pageLayout.AddChild(border); } @@ -335,25 +335,25 @@ private static bool NeedsClipping(PdfCell map, Page page, int row, int col) { if (map.ContentAligmnet == null) return false; // Fill alignment always clips; WrapText is already wrapped but clip for safety. - if (map.ContentAligmnet.HorizontalAlignment == ExcelHorizontalAlignment.Fill || map.ContentAligmnet.WrapText) + if (map.ContentAligmnet.HorizontalAlignment == (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Fill || map.ContentAligmnet.WrapText) return true; if (map.TotalTextLength <= map.ColumnWidth) return false; var halign = map.ContentAligmnet.HorizontalAlignment; - if (halign == ExcelHorizontalAlignment.Left || halign == ExcelHorizontalAlignment.General) + if (halign == (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Left || halign == (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.General) { // Text spills right — clip if the right neighbour has content or we're at the page edge. if (col >= page.ToColumn) return true; var right = page.Map[row, col + 1]; return right != null && !string.IsNullOrEmpty(right.Text); } - else if (halign == ExcelHorizontalAlignment.Right) + else if (halign == (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Right) { // Text spills left — clip if the left neighbour has content or we're at the page edge. if (col <= page.FromColumn) return true; var left = page.Map[row, col - 1]; return left != null && !string.IsNullOrEmpty(left.Text); } - else if (halign == ExcelHorizontalAlignment.Center) + else if (halign == (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Center) { // Text spills both ways — clip if either neighbour blocks or we're at an edge. bool rightBlocked = col >= page.ToColumn || (page.Map[row, col + 1] != null && !string.IsNullOrEmpty(page.Map[row, col + 1].Text)); @@ -474,8 +474,8 @@ private static PdfCell CreateHeadingPdfCell(PdfPageSettings pageSettings, PdfDic cell.Text = text; cell.CellStyle = new PdfCellStyle(); cell.ContentAligmnet = new PdfCellAlignmentData(); - cell.ContentAligmnet.HorizontalAlignment = hAlign; - cell.ContentAligmnet.VerticalAlignment = ExcelVerticalAlignment.Bottom; + cell.ContentAligmnet.HorizontalAlignment = (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)hAlign; + cell.ContentAligmnet.VerticalAlignment = (EPPlus.Export.Pdf.Enums.ExcelVerticalAlignment)ExcelVerticalAlignment.Bottom; cell.ContentAligmnet.WrapText = false; if (!string.IsNullOrEmpty(text)) { @@ -1087,12 +1087,12 @@ private static void EmitBandGrid(List target, PdfRange range, int from if (string.IsNullOrEmpty(cell.Text)) continue; double spill = cell.TotalTextLength - cell.ColumnWidth; if (spill <= 0d) continue; - var hal = cell.ContentAligmnet?.HorizontalAlignment ?? ExcelHorizontalAlignment.General; - if (hal == ExcelHorizontalAlignment.Left || hal == ExcelHorizontalAlignment.General) + var hal = cell.ContentAligmnet?.HorizontalAlignment ?? (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.General; + if (hal == (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Left || hal == (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.General) BandMarkRight(range, row, ci, nc, fromCol, colX, spill, ri, blocked); - else if (hal == ExcelHorizontalAlignment.Right) + else if (hal == (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Right) BandMarkLeft(range, row, ci, fromCol, colX, spill, ri, blocked); - else if (hal == ExcelHorizontalAlignment.Center) + else if (hal == (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Center) { double half = spill / 2d; BandMarkRight(range, row, ci, nc, fromCol, colX, half, ri, blocked); @@ -1110,9 +1110,9 @@ private static void EmitBandGrid(List target, PdfRange range, int from if (cell == null) continue; if (cell.Merged) break; if (string.IsNullOrEmpty(cell.Text)) continue; - var hal = cell.ContentAligmnet?.HorizontalAlignment ?? ExcelHorizontalAlignment.General; - if (hal == ExcelHorizontalAlignment.Right) break; // spills away from the region - double rightExtent = (hal == ExcelHorizontalAlignment.Center) + var hal = cell.ContentAligmnet?.HorizontalAlignment ?? (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.General; + if (hal == (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Right) break; // spills away from the region + double rightExtent = (hal == (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Center) ? lx + w / 2d + cell.TotalTextLength / 2d : lx + cell.TotalTextLength; for (int g = 1; g <= nc - 1; g++) @@ -1133,9 +1133,9 @@ private static void EmitBandGrid(List target, PdfRange range, int from if (cell == null) { rx += w; continue; } if (cell.Merged) break; if (string.IsNullOrEmpty(cell.Text)) { rx += w; continue; } - var hal = cell.ContentAligmnet?.HorizontalAlignment ?? ExcelHorizontalAlignment.General; - if (hal == ExcelHorizontalAlignment.Left || hal == ExcelHorizontalAlignment.General) break; // spills away - double leftExtent = (hal == ExcelHorizontalAlignment.Center) + var hal = cell.ContentAligmnet?.HorizontalAlignment ?? (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.General; + if (hal == (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Left || hal == (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.General) break; // spills away + double leftExtent = (hal == (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Center) ? rx + w / 2d - cell.TotalTextLength / 2d : rx + w - cell.TotalTextLength; for (int g = nc - 1; g >= 1; g--) @@ -1509,10 +1509,10 @@ private static void AddIncomingSpill(Page page, PdfRange range, int fromRow, int if (cell == null) continue; if (cell.Merged) break; // merges don't spill, and block if (string.IsNullOrEmpty(cell.Text)) continue; - var hal = cell.ContentAligmnet?.HorizontalAlignment ?? ExcelHorizontalAlignment.General; + var hal = cell.ContentAligmnet?.HorizontalAlignment ?? (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.General; double rightExtent = - (hal == ExcelHorizontalAlignment.Center) ? lx + w / 2d + cell.TotalTextLength / 2d : - (hal == ExcelHorizontalAlignment.Right) ? lx + w : + (hal == (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Center) ? lx + w / 2d + cell.TotalTextLength / 2d : + (hal == (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Right) ? lx + w : lx + cell.TotalTextLength; if (rightExtent > windowOriginX) { @@ -1543,10 +1543,10 @@ private static void AddIncomingSpill(Page page, PdfRange range, int fromRow, int if (cell == null) { rx += w; continue; } if (cell.Merged) break; if (string.IsNullOrEmpty(cell.Text)) { rx += w; continue; } - var hal = cell.ContentAligmnet?.HorizontalAlignment ?? ExcelHorizontalAlignment.General; + var hal = cell.ContentAligmnet?.HorizontalAlignment ?? (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.General; double leftExtent = - (hal == ExcelHorizontalAlignment.Center) ? rx + w / 2d - cell.TotalTextLength / 2d : - (hal == ExcelHorizontalAlignment.Right) ? rx + w - cell.TotalTextLength : + (hal == (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Center) ? rx + w / 2d - cell.TotalTextLength / 2d : + (hal == (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Right) ? rx + w - cell.TotalTextLength : rx; if (leftExtent < windowRightX) { diff --git a/src/EPPlus/Export/PdfExport/PdfCatalog.cs b/src/EPPlus/Export/PdfExport/PdfCatalog.cs index f1781a8e8e..2dc57e0ce3 100644 --- a/src/EPPlus/Export/PdfExport/PdfCatalog.cs +++ b/src/EPPlus/Export/PdfExport/PdfCatalog.cs @@ -12,6 +12,7 @@ Date Author Change *************************************************************************************************/ using EPPlus.Graphics; using EPPlus.Export.Pdf.Settings; +using EPPlus.Export.Pdf.Resources; using OfficeOpenXml.Export.PdfExport.Data; using OfficeOpenXml.Export.PdfExport.Layout; using OfficeOpenXml.Export.PdfExport.RowResize; diff --git a/src/EPPlus/Export/PdfExport/TextMapping/PdfHeaderFooterCollection.cs b/src/EPPlus/Export/PdfExport/TextMapping/PdfHeaderFooterCollection.cs index 064694c49e..bc39a63073 100644 --- a/src/EPPlus/Export/PdfExport/TextMapping/PdfHeaderFooterCollection.cs +++ b/src/EPPlus/Export/PdfExport/TextMapping/PdfHeaderFooterCollection.cs @@ -10,8 +10,9 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ +using EPPlus.Export.Pdf.Settings; +using EPPlus.Export.Pdf.Resources; using OfficeOpenXml.Export.PdfExport.Data; -using OfficeOpenXml.Export.PdfExport.Data.Dictionaries; using System.Collections.Generic; using System.Linq; diff --git a/src/EPPlus/Export/PdfExport/TextMapping/PdfTextMap.cs b/src/EPPlus/Export/PdfExport/TextMapping/PdfTextMap.cs index 84a9503bca..6832d3ff57 100644 --- a/src/EPPlus/Export/PdfExport/TextMapping/PdfTextMap.cs +++ b/src/EPPlus/Export/PdfExport/TextMapping/PdfTextMap.cs @@ -295,10 +295,10 @@ private static void GetBorderStyles(ExcelRangeBase cell, PdfCellStyle cellStyle, tableStyle = new ExcelTableNamedStyle(cell.Worksheet.Workbook.Styles.NameSpaceManager, tmpNode, cell.Worksheet.Workbook.Styles); tableStyle.SetFromTemplate((TableStyles)table.TableStyle); } - cellStyle.dxfTop = PdfCellBorderLayout.GetTopBorderItem(cell, cellStyle.xfTop, table, tableStyle); - cellStyle.dxfBottom = PdfCellBorderLayout.GetBottomBorderItem(cell, cellStyle.xfBottom, table, tableStyle); - cellStyle.dxfLeft = PdfCellBorderLayout.GetLeftBorderItem(cell, cellStyle.xfLeft, table, tableStyle); - cellStyle.dxfRight = PdfCellBorderLayout.GetRightBorderItem(cell, cellStyle.xfRight, table, tableStyle); + cellStyle.dxfTop = GetTopBorderItem(cell, cellStyle.xfTop, table, tableStyle); + cellStyle.dxfBottom = GetBottomBorderItem(cell, cellStyle.xfBottom, table, tableStyle); + cellStyle.dxfLeft = GetLeftBorderItem(cell, cellStyle.xfLeft, table, tableStyle); + cellStyle.dxfRight = GetRightBorderItem(cell, cellStyle.xfRight, table, tableStyle); } var cfBorder = GetConditionalFormattingBorder(cell); if (cfBorder != null) @@ -716,5 +716,355 @@ public static int AddColumnsForNonWrappedText(PdfPageSettings pageSettings, Exce } return columnsToAdd; } + + public static ExcelDxfBorderItem GetTopBorderItem(ExcelRangeBase cell, ExcelBorderItem xfBorder, ExcelTable table, ExcelTableNamedStyle tableStyle) + { + var range = table.Range; + int tableRow = cell._fromRow - range._fromRow; + int tableCol = cell._fromCol - range._fromCol; + int ts = table.ShowHeader ? 1 : 0; + var top = tableRow == 0 ? tableStyle.WholeTable.Style.Border.Top : tableStyle.WholeTable.Style.Border.Horizontal; + if (table.ShowHeader && tableRow == 0) + { + if (tableStyle.HeaderRow.Style.Border.Top.HasValue) + { + top = tableStyle.HeaderRow.Style.Border.Top; + } + if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstHeaderCell.Style.Border.Top.HasValue) + { + top = tableStyle.FirstHeaderCell.Style.Border.Top; + } + if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastHeaderCell.Style.Border.Top.HasValue) + { + top = tableStyle.LastHeaderCell.Style.Border.Top; + } + } + else if (table.ShowTotal && cell._fromRow == range._toRow) + { + if (tableStyle.TotalRow.Style.Border.Top.HasValue) + { + top = tableStyle.TotalRow.Style.Border.Top; + } + if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstTotalCell.Style.Border.Top.HasValue) + { + top = tableStyle.FirstTotalCell.Style.Border.Top; + } + if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastTotalCell.Style.Border.Top.HasValue) + { + top = tableStyle.LastTotalCell.Style.Border.Top; + } + } + else + { + if (table.ShowColumnStripes &&/* tableStyle.FirstColumnStripe.Style.Border.Top.HasValue &&*/ (tableCol & 1) == 0) + { + if (cell._fromRow - ts > range._fromRow && cell._fromRow < range._toRow) + { + top = tableStyle.FirstColumnStripe.Style.Border.Horizontal; + } + else if (cell._fromRow <= range._toRow) + { + top = null; + } + else + { + top = tableStyle.FirstColumnStripe.Style.Border.Top; + } + } + if (table.ShowColumnStripes && /*tableStyle.SecondColumnStripe.Style.Border.Top.HasValue &&*/ (tableCol & 1) != 0) + { + if (cell._fromRow + ts > range._fromRow && cell._fromRow < range._toRow) + { + top = tableStyle.SecondColumnStripe.Style.Border.Horizontal; + } + else if (cell._fromRow <= range._toRow) + { + top = null; + } + else + { + top = tableStyle.SecondColumnStripe.Style.Border.Top; + } + } + if (table.ShowRowStripes && tableStyle.FirstRowStripe.Style.Border.Top.HasValue && (tableRow & 1) != 0) + { + top = tableStyle.FirstRowStripe.Style.Border.Top; + } + if (table.ShowRowStripes && tableStyle.SecondRowStripe.Style.Border.Top.HasValue && (tableRow & 1) == 0) + { + top = tableStyle.SecondRowStripe.Style.Border.Top; + } + if (table.ShowLastColumn && tableStyle.LastColumn.Style.Border.Top.HasValue && cell._fromCol == range._toCol) + { + top = tableStyle.LastColumn.Style.Border.Top; + } + if (table.ShowFirstColumn && tableStyle.FirstColumn.Style.Border.Top.HasValue && tableCol == range._toCol) + { + top = tableStyle.FirstColumn.Style.Border.Top; + } + } + return top; + } + public static ExcelDxfBorderItem GetBottomBorderItem(ExcelRangeBase cell, ExcelBorderItem xfBorder, ExcelTable table, ExcelTableNamedStyle tableStyle) + { + var range = table.Range; + int tableRow = cell._fromRow - range._fromRow; + int tableCol = cell._fromCol - range._fromCol; + var bottom = range._toRow == cell._fromRow ? tableStyle.WholeTable.Style.Border.Bottom : null; + if (table.ShowHeader && tableRow == 0) + { + if (tableStyle.HeaderRow.Style.Border.Bottom.HasValue) + { + bottom = tableStyle.HeaderRow.Style.Border.Bottom; + } + if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstHeaderCell.Style.Border.Bottom.HasValue) + { + bottom = tableStyle.FirstHeaderCell.Style.Border.Bottom; + } + if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastHeaderCell.Style.Border.Bottom.HasValue) + { + bottom = tableStyle.LastHeaderCell.Style.Border.Bottom; + } + } + else if (table.ShowTotal && cell._fromRow == range._toRow) + { + if (tableStyle.TotalRow.Style.Border.Bottom.HasValue) + { + bottom = tableStyle.TotalRow.Style.Border.Bottom; + } + if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstTotalCell.Style.Border.Bottom.HasValue) + { + bottom = tableStyle.FirstTotalCell.Style.Border.Bottom; + } + if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastTotalCell.Style.Border.Bottom.HasValue) + { + bottom = tableStyle.LastTotalCell.Style.Border.Bottom; + } + } + else + { + if (table.ShowColumnStripes && tableStyle.FirstColumnStripe.Style.Border.Bottom.HasValue && (tableCol & 1) != 0) + { + if (cell._fromRow > range._fromRow && cell._fromRow < range._toRow) + { + bottom = tableStyle.FirstColumnStripe.Style.Border.Horizontal; + } + else if (cell._fromRow < range._toRow) + { + bottom = null; + } + else + { + bottom = tableStyle.FirstColumnStripe.Style.Border.Bottom; + } + } + if (table.ShowColumnStripes && tableStyle.SecondColumnStripe.Style.Border.Bottom.HasValue && (tableCol & 1) == 0) + { + if (cell._fromRow > range._fromRow && cell._fromRow < range._toRow) + { + bottom = tableStyle.SecondColumnStripe.Style.Border.Horizontal; + } + else if (cell._fromRow < range._toRow) + { + bottom = null; + } + else + { + bottom = tableStyle.SecondColumnStripe.Style.Border.Bottom; + } + } + if (table.ShowRowStripes && tableStyle.FirstRowStripe.Style.Border.Bottom.HasValue && (tableRow & 1) != 0) + { + bottom = tableStyle.FirstRowStripe.Style.Border.Bottom; + } + if (table.ShowRowStripes && tableStyle.SecondRowStripe.Style.Border.Bottom.HasValue && (tableRow & 1) == 0) + { + bottom = tableStyle.SecondRowStripe.Style.Border.Bottom; + } + if (table.ShowLastColumn && tableStyle.LastColumn.Style.Border.Bottom.HasValue && cell._fromCol == range._toCol) + { + bottom = tableStyle.LastColumn.Style.Border.Bottom; + } + if (table.ShowFirstColumn && tableStyle.FirstColumn.Style.Border.Bottom.HasValue && tableCol == 0) + { + bottom = tableStyle.FirstColumn.Style.Border.Bottom; + } + } + return bottom; + } + public static ExcelDxfBorderItem GetLeftBorderItem(ExcelRangeBase cell, ExcelBorderItem xfBorder, ExcelTable table, ExcelTableNamedStyle tableStyle) + { + var range = table.Range; + int tableRow = cell._fromRow - range._fromRow; + int tableCol = cell._fromCol - range._fromCol; + var left = tableCol == 0 ? tableStyle.WholeTable.Style.Border.Left : tableStyle.WholeTable.Style.Border.Vertical; + if (table.ShowHeader && tableRow == 0) + { + if (tableStyle.HeaderRow.Style.Border.Left.HasValue) + { + left = tableStyle.HeaderRow.Style.Border.Left; + } + if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstHeaderCell.Style.Border.Left.HasValue) + { + left = tableStyle.FirstHeaderCell.Style.Border.Left; + } + if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastHeaderCell.Style.Border.Left.HasValue) + { + left = tableStyle.LastHeaderCell.Style.Border.Left; + } + } + else if (table.ShowTotal && cell._fromRow == range._toRow) + { + if (tableStyle.TotalRow.Style.Border.Left.HasValue) + { + left = tableStyle.TotalRow.Style.Border.Left; + } + if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstTotalCell.Style.Border.Left.HasValue) + { + left = tableStyle.FirstTotalCell.Style.Border.Left; + } + if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastTotalCell.Style.Border.Left.HasValue) + { + left = tableStyle.LastTotalCell.Style.Border.Left; + } + } + else + { + if (table.ShowColumnStripes && tableStyle.FirstColumnStripe.Style.Border.Left.HasValue && (tableCol & 1) != 0) + { + left = tableStyle.FirstColumnStripe.Style.Border.Left; + } + if (table.ShowColumnStripes && tableStyle.SecondColumnStripe.Style.Border.Left.HasValue && (tableCol & 1) == 0) + { + left = tableStyle.SecondColumnStripe.Style.Border.Left; + } + if (table.ShowRowStripes && tableStyle.FirstRowStripe.Style.Border.Left.HasValue && (tableRow & 1) != 0) + { + if (cell._fromCol > range._fromCol && cell._fromCol < range._toCol) + { + left = tableStyle.FirstRowStripe.Style.Border.Vertical; + } + else if (cell._fromCol >= range._toCol) + { + left = null; + } + else + { + left = tableStyle.FirstRowStripe.Style.Border.Left; + } + } + if (table.ShowRowStripes && tableStyle.SecondRowStripe.Style.Border.Left.HasValue && (tableRow & 1) == 0) + { + if (cell._fromCol > range._fromCol && cell._fromCol < range._toCol) + { + left = tableStyle.SecondRowStripe.Style.Border.Vertical; + } + else if (cell._fromCol >= range._toCol) + { + left = null; + } + else + { + left = tableStyle.SecondRowStripe.Style.Border.Left; + } + } + if (table.ShowLastColumn && tableStyle.LastColumn.Style.Border.Left.HasValue && cell._fromCol == range._toCol) + { + left = tableStyle.LastColumn.Style.Border.Left; + } + if (table.ShowFirstColumn && tableStyle.FirstColumn.Style.Border.Left.HasValue && tableCol == range._toCol) + { + left = tableStyle.FirstColumn.Style.Border.Left; + } + } + return left; + } + public static ExcelDxfBorderItem GetRightBorderItem(ExcelRangeBase cell, ExcelBorderItem xfBorder, ExcelTable table, ExcelTableNamedStyle tableStyle) + { + var range = table.Range; + int tableRow = cell._fromRow - range._fromRow; + int tableCol = cell._fromCol - range._fromCol; + var right = cell._fromCol == range._toCol ? tableStyle.WholeTable.Style.Border.Right : tableStyle.WholeTable.Style.Border.Vertical; + if (table.ShowHeader && tableRow == 0) + { + if (tableStyle.HeaderRow.Style.Border.Right.HasValue) + { + right = tableStyle.HeaderRow.Style.Border.Right; + } + if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstHeaderCell.Style.Border.Right.HasValue) + { + right = tableStyle.FirstHeaderCell.Style.Border.Right; + } + if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastHeaderCell.Style.Border.Right.HasValue) + { + right = tableStyle.LastHeaderCell.Style.Border.Right; + } + } + else if (table.ShowTotal && cell._fromRow == range._toRow) + { + if (tableStyle.TotalRow.Style.Border.Right.HasValue) + { + right = tableStyle.TotalRow.Style.Border.Right; + } + if (tableCol == 0 && table.ShowFirstColumn && tableStyle.FirstTotalCell.Style.Border.Right.HasValue) + { + right = tableStyle.FirstTotalCell.Style.Border.Right; + } + if (cell._fromCol == range._toCol && table.ShowLastColumn && tableStyle.LastTotalCell.Style.Border.Right.HasValue) + { + right = tableStyle.LastTotalCell.Style.Border.Right; + } + } + else + { + if (table.ShowColumnStripes && tableStyle.FirstColumnStripe.Style.Border.Right.HasValue && (tableCol & 1) != 0) + { + right = tableStyle.FirstColumnStripe.Style.Border.Right; + } + if (table.ShowColumnStripes && tableStyle.SecondColumnStripe.Style.Border.Right.HasValue && (tableCol & 1) == 0) + { + right = tableStyle.SecondColumnStripe.Style.Border.Right; + } + if (table.ShowRowStripes && tableStyle.FirstRowStripe.Style.Border.Right.HasValue && (tableRow & 1) != 0) + { + if (cell._fromCol > range._fromCol && cell._fromCol < range._toCol) + { + right = tableStyle.FirstRowStripe.Style.Border.Vertical; + } + else if (cell._fromCol < range._toCol) + { + right = null; + } + else + { + right = tableStyle.FirstRowStripe.Style.Border.Right; + } + } + if (table.ShowRowStripes && tableStyle.SecondRowStripe.Style.Border.Right.HasValue && (tableRow & 1) == 0) + { + if (cell._fromCol > range._fromCol && cell._fromCol < range._toCol) + { + right = tableStyle.SecondRowStripe.Style.Border.Vertical; + } + else if (cell._fromCol < range._toCol) + { + right = null; + } + else + { + right = tableStyle.SecondRowStripe.Style.Border.Right; + } + } + if (table.ShowLastColumn && tableStyle.LastColumn.Style.Border.Right.HasValue && cell._fromCol == range._toCol) + { + right = tableStyle.LastColumn.Style.Border.Right; + } + if (table.ShowFirstColumn && tableStyle.FirstColumn.Style.Border.Right.HasValue && tableCol == range._toCol) + { + right = tableStyle.FirstColumn.Style.Border.Right; + } + } + return right; + } } } diff --git a/src/EPPlus/Export/PdfExport/TextShaping/PdfTextShaper.cs b/src/EPPlus/Export/PdfExport/TextShaping/PdfTextShaper.cs index bb8016efd7..c501f7212b 100644 --- a/src/EPPlus/Export/PdfExport/TextShaping/PdfTextShaper.cs +++ b/src/EPPlus/Export/PdfExport/TextShaping/PdfTextShaper.cs @@ -1,8 +1,22 @@ -using EPPlus.Fonts.OpenType; +/************************************************************************************************* + 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.Fonts.OpenType; using EPPlus.Fonts.OpenType.Integration; using EPPlus.Fonts.OpenType.TextShaping; +using EPPlus.Export.Pdf.Resources; +using EPPlus.Export.Pdf.Settings; +using EPPlus.Export.Pdf.Layout; using OfficeOpenXml.Export.PdfExport.Data; -using OfficeOpenXml.Export.PdfExport.Data.Dictionaries; using OfficeOpenXml.Interfaces.Fonts; using System; using System.Collections.Generic; From 1ad8bccb99f20cd88f766d8362812387336ab84b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Wed, 24 Jun 2026 17:03:16 +0200 Subject: [PATCH 38/49] All errors has been slain. --- .../EPPlus.Export.Pdf.Tests.csproj | 1 + src/EPPlus.Export.Pdf.Tests/PdfTests.cs | 250 +++++++++--------- src/EPPlus.Export.Pdf/ExcelPdf.cs | 250 +++++++++++------- .../Layout/PdfCellBorderLayout.cs | 42 +-- .../Layout/PdfCellContentLayout.cs | 1 - src/EPPlus.Export.Pdf/Layout/PdfCellLayout.cs | 167 +++++++----- .../Layout}/PdfHeaderFooter.cs | 7 +- .../Resources/PdfDictionaries.cs | 3 - .../Export/PdfExport/Layout/PdfLayout.cs | 130 ++++++++- src/EPPlus/Export/PdfExport/PdfCatalog.cs | 23 +- .../TextMapping/PdfHeaderFooterCollection.cs | 1 + src/EPPlus/Properties/AssemblyInfo.cs | 1 + 12 files changed, 543 insertions(+), 333 deletions(-) rename src/{EPPlus/Export/PdfExport/TextMapping => EPPlus.Export.Pdf/Layout}/PdfHeaderFooter.cs (92%) diff --git a/src/EPPlus.Export.Pdf.Tests/EPPlus.Export.Pdf.Tests.csproj b/src/EPPlus.Export.Pdf.Tests/EPPlus.Export.Pdf.Tests.csproj index 133a0c0a24..37ce84a67b 100644 --- a/src/EPPlus.Export.Pdf.Tests/EPPlus.Export.Pdf.Tests.csproj +++ b/src/EPPlus.Export.Pdf.Tests/EPPlus.Export.Pdf.Tests.csproj @@ -18,6 +18,7 @@ + diff --git a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs index a363d2b21f..a99e6ae7bd 100644 --- a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs +++ b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs @@ -10,20 +10,16 @@ Date Author Change ************************************************************************************************* 10/07/2025 EPPlus Software AB EPPlus.Fonts.OpenType 1.0 *************************************************************************************************/ +using EPPlus.Export.Pdf.Settings; +using OfficeOpenXml.Export.PdfExport; +using EPPlus.Export.Pdf.Settings.PdfPageSizes; using EPPlus.Export.Pdf; -using EPPlus.Export.Pdf.PdfCatalog; -using EPPlus.Export.Pdf.PdfLayout; -using EPPlus.Export.Pdf.PdfSettings; -using EPPlus.Export.Pdf.PdfSettings.PdfPageSizes; using EPPlus.Fonts.OpenType; using EPPlus.Fonts.OpenType.Integration; using OfficeOpenXml; -using OfficeOpenXml.DataValidation.Formulas.Contracts; -using OfficeOpenXml.Interfaces.Drawing.Text; using OfficeOpenXml.Interfaces.RichText; using OfficeOpenXml.Style; using System.Diagnostics; -using System.Drawing.Printing; namespace EPPlusTest.PDF { @@ -111,55 +107,61 @@ public class PdfTests : TestBase * When all worksheets have been processed, combine all into one catalog and create the pdf document. */ - [TestMethod] - public void TestWritePdf() - { - using var p = OpenTemplatePackage("PDFTest.xlsx"); - //using var p = OpenTemplatePackage("PdfGrids\\PdfTextTest.xlsx"); - //using var p = OpenTemplatePackage("PdfGrids\\PdfPageBreakTest.xlsx"); - //using var p = OpenTemplatePackage("PDFTest - Copy (2).xlsx"); - //using var p = OpenTemplatePackage("PdfBorders.xlsx"); - //using var p = OpenTemplatePackage("PdfGrids\\3 2 Page Crazy Cells.xlsx"); - //using var p = OpenTemplatePackage("PdfGrids\\3 2 Page Crazy Cells Merged.xlsx"); - //using var p = OpenTemplatePackage("Gradient.xlsx"); - //using var p = OpenTemplatePackage("PatternFill.xlsx"); - var ws = p.Workbook.Worksheets[0]; - //var ws = p.Workbook.Worksheets[1]; - PdfPageSettings pageSettings = new PdfPageSettings(); - pageSettings.ShowGridLines = true; - pageSettings.PageSize = PdfPageSize.A4; - pageSettings.Orientation = Orientations.Portrait; - pageSettings.Margins = PdfMargins.Normal; - pageSettings.ShowGridLines = true; - pageSettings.CenterOnPageHorizontally = true; - pageSettings.CenterOnPageVertically = true; - pageSettings.ShowHeadings = true; - pageSettings.CommentsAndNotes = CommentsAndNotes.AtEndOfSheet; - //Debug Flags - pageSettings.Debug = true; - pageSettings.PrintAsText = true; + /// + /// Old Test + /// + //[TestMethod] + //public void TestWritePdf() + //{ + // using var p = OpenTemplatePackage("PDFTest.xlsx"); + // //using var p = OpenTemplatePackage("PdfGrids\\PdfTextTest.xlsx"); + // //using var p = OpenTemplatePackage("PdfGrids\\PdfPageBreakTest.xlsx"); + // //using var p = OpenTemplatePackage("PDFTest - Copy (2).xlsx"); + // //using var p = OpenTemplatePackage("PdfBorders.xlsx"); + // //using var p = OpenTemplatePackage("PdfGrids\\3 2 Page Crazy Cells.xlsx"); + // //using var p = OpenTemplatePackage("PdfGrids\\3 2 Page Crazy Cells Merged.xlsx"); + // //using var p = OpenTemplatePackage("Gradient.xlsx"); + // //using var p = OpenTemplatePackage("PatternFill.xlsx"); + // var ws = p.Workbook.Worksheets[0]; + // //var ws = p.Workbook.Worksheets[1]; + // PdfPageSettings pageSettings = new PdfPageSettings(); + // pageSettings.ShowGridLines = true; + // pageSettings.PageSize = PdfPageSize.A4; + // pageSettings.Orientation = Orientations.Portrait; + // pageSettings.Margins = PdfMargins.Normal; + // pageSettings.ShowGridLines = true; + // pageSettings.CenterOnPageHorizontally = true; + // pageSettings.CenterOnPageVertically = true; + // pageSettings.ShowHeadings = true; + // pageSettings.CommentsAndNotes = CommentsAndNotes.AtEndOfSheet; + // //Debug Flags + // pageSettings.Debug = true; + // pageSettings.PrintAsText = true; - ExcelPdf pedeef = new ExcelPdf(ws, pageSettings); - pedeef.CreatePdf("c:\\epplustest\\pdf\\FullPageTest49.pdf"); - } + // ExcelPdf pedeef = new ExcelPdf(ws, pageSettings); + // pedeef.CreatePdf("c:\\epplustest\\pdf\\FullPageTest49.pdf"); + //} - [TestMethod] - public void TestWritePdf2() - { - using var p = OpenTemplatePackage("PDFTest2.xlsx"); - PdfPageSettings pageSettings = new PdfPageSettings(); - pageSettings.ShowGridLines = true; - pageSettings.PageSize = PdfPageSize.A4; - pageSettings.Orientation = Orientations.Portrait; - pageSettings.Margins = PdfMargins.Normal; - pageSettings.ShowGridLines = true; - //Debug Flags - pageSettings.Debug = true; - pageSettings.PrintAsText = true; + /// + /// Old Test + /// + //[TestMethod] + //public void TestWritePdf2() + //{ + // using var p = OpenTemplatePackage("PDFTest2.xlsx"); + // PdfPageSettings pageSettings = new PdfPageSettings(); + // pageSettings.ShowGridLines = true; + // pageSettings.PageSize = PdfPageSize.A4; + // pageSettings.Orientation = Orientations.Portrait; + // pageSettings.Margins = PdfMargins.Normal; + // pageSettings.ShowGridLines = true; + // //Debug Flags + // pageSettings.Debug = true; + // pageSettings.PrintAsText = true; - ExcelPdf pedeef = new ExcelPdf(p.Workbook.Worksheets.First(), pageSettings); - pedeef.CreatePdf("c:\\epplustest\\pdf\\EmojiTest.pdf"); - } + // ExcelPdf pedeef = new ExcelPdf(p.Workbook.Worksheets.First(), pageSettings); + // pedeef.CreatePdf("c:\\epplustest\\pdf\\EmojiTest.pdf"); + //} [TestMethod] public void ReadPrintAreas() @@ -250,86 +252,92 @@ public void WorkbookTests(string sourceFile, string outputPath, string wsName) PdfCatalog catalog = new PdfCatalog(pageSettings, ws, outputPath); } - [TestMethod] - public void CalculatePages() - { - using var p = new ExcelPackage(); - var ws = p.Workbook.Worksheets.Add("Sheet 1"); - PdfPageSettings pageSettings = new PdfPageSettings(); - pageSettings.ShowHeadings = true; - PdfWorksheet pws = new PdfWorksheet(); - pws.Worksheet = ws; - pws.ZeroCharWidth = PdfWorksheet.GetThemeFont0Width(ws); - pws.ToRow = 256; - PdfRange range = new PdfRange(); - range.TotalWidth = 800; - range.TotalHeight = 1600; + /// + /// This test we can make somehing of. + /// + //[TestMethod] + //public void CalculatePages() + //{ + // using var p = new ExcelPackage(); + // var ws = p.Workbook.Worksheets.Add("Sheet 1"); + // PdfPageSettings pageSettings = new PdfPageSettings(); + // pageSettings.ShowHeadings = true; + // PdfWorksheet pws = new PdfWorksheet(); + // pws.Worksheet = ws; + // pws.ZeroCharWidth = PdfWorksheet.GetThemeFont0Width(ws); + // pws.ToRow = 256; + // PdfRange range = new PdfRange(); + // range.TotalWidth = 800; + // range.TotalHeight = 1600; - var result = PdfLayout.GetNumberOfPages(pageSettings, pws, ref range); - } + // var result = PdfLayout.GetNumberOfPages(pageSettings, pws, ref range); + //} - [TestMethod] - public void TestWrapText() - { - using var p = OpenTemplatePackage("PDFTest.xlsx"); - var cell = p.Workbook.Worksheets[0].Cells["P118"]; + /// + /// Old Test + /// + //[TestMethod] + //public void TestWrapText() + //{ + // using var p = OpenTemplatePackage("PDFTest.xlsx"); + // var cell = p.Workbook.Worksheets[0].Cells["P118"]; - List TextFragments = GetTextFragments(cell.RichText).Cast().ToList(); + // List TextFragments = GetTextFragments(cell.RichText).Cast().ToList(); - var layout = OpenTypeFonts.GetTextLayoutEngineForFont((IFontFormatBase)TextFragments[0].RichTextOptions); + // var layout = OpenTypeFonts.GetTextLayoutEngineForFont((IFontFormatBase)TextFragments[0].RichTextOptions); - var TextLines = layout.WrapRichTextLineCollection(TextFragments, 51d); - } + // var TextLines = layout.WrapRichTextLineCollection(TextFragments, 51d); + //} - private static List GetTextFragments(ExcelRichTextCollection RichTextCollection, PdfCellStyle cellStyle = null) - { - var textFragments = new List(); - bool bold = false, italic = false, underline = false, strike = false; - ExcelUnderLineType underLineType = ExcelUnderLineType.None; - if (cellStyle != null && cellStyle.dxfFont != null) - { - bold = cellStyle.dxfFont.Bold != null ? (bool)cellStyle.dxfFont.Bold : false; - italic = cellStyle.dxfFont.Italic != null ? (bool)cellStyle.dxfFont.Italic : false; - strike = cellStyle.dxfFont.Strike != null ? (bool)cellStyle.dxfFont.Strike : false; - underline = cellStyle.dxfFont.Underline != null; - underLineType = cellStyle.dxfFont.Underline != null ? (ExcelUnderLineType)cellStyle.dxfFont.Underline : ExcelUnderLineType.None; - } - for (int i = 0; i < RichTextCollection.Count; i++) - { - var rt = RichTextCollection[i]; - var textFrag = new TextFragment(); - textFrag.Text = rt.Text; + //private static List GetTextFragments(ExcelRichTextCollection RichTextCollection, PdfCellStyle cellStyle = null) + //{ + // var textFragments = new List(); + // bool bold = false, italic = false, underline = false, strike = false; + // ExcelUnderLineType underLineType = ExcelUnderLineType.None; + // if (cellStyle != null && cellStyle.dxfFont != null) + // { + // bold = cellStyle.dxfFont.Bold != null ? (bool)cellStyle.dxfFont.Bold : false; + // italic = cellStyle.dxfFont.Italic != null ? (bool)cellStyle.dxfFont.Italic : false; + // strike = cellStyle.dxfFont.Strike != null ? (bool)cellStyle.dxfFont.Strike : false; + // underline = cellStyle.dxfFont.Underline != null; + // underLineType = cellStyle.dxfFont.Underline != null ? (ExcelUnderLineType)cellStyle.dxfFont.Underline : ExcelUnderLineType.None; + // } + // for (int i = 0; i < RichTextCollection.Count; i++) + // { + // var rt = RichTextCollection[i]; + // var textFrag = new TextFragment(); + // textFrag.Text = rt.Text; - textFrag.Font.Family = rt.FontName; - textFrag.Font.Size = rt.Size; + // textFrag.Font.Family = rt.FontName; + // textFrag.Font.Size = rt.Size; - textFrag.RichTextOptions.Bold = rt.Bold || bold; - textFrag.RichTextOptions.Italic = rt.Italic || italic; - //underline - //none : 12 - //single : 13 - //Double : 4 - //accouting does not exsist - textFrag.RichTextOptions.UnderlineType = 12; - textFrag.RichTextOptions.UnderlineType = rt.UnderLineType == ExcelUnderLineType.Single ? 13 : textFrag.RichTextOptions.UnderlineType; - textFrag.RichTextOptions.UnderlineType = rt.UnderLineType == ExcelUnderLineType.Double ? 4 : textFrag.RichTextOptions.UnderlineType; - textFrag.RichTextOptions.StrikeType = rt.Strike || strike ? 2 : 1; - textFrag.RichTextOptions.SuperScript = rt.VerticalAlign == ExcelVerticalAlignmentFont.Superscript; - textFrag.RichTextOptions.SubScript = rt.VerticalAlign == ExcelVerticalAlignmentFont.Subscript; - textFrag.RichTextOptions.FontColor = rt.Color; + // textFrag.RichTextOptions.Bold = rt.Bold || bold; + // textFrag.RichTextOptions.Italic = rt.Italic || italic; + // //underline + // //none : 12 + // //single : 13 + // //Double : 4 + // //accouting does not exsist + // textFrag.RichTextOptions.UnderlineType = 12; + // textFrag.RichTextOptions.UnderlineType = rt.UnderLineType == ExcelUnderLineType.Single ? 13 : textFrag.RichTextOptions.UnderlineType; + // textFrag.RichTextOptions.UnderlineType = rt.UnderLineType == ExcelUnderLineType.Double ? 4 : textFrag.RichTextOptions.UnderlineType; + // textFrag.RichTextOptions.StrikeType = rt.Strike || strike ? 2 : 1; + // textFrag.RichTextOptions.SuperScript = rt.VerticalAlign == ExcelVerticalAlignmentFont.Superscript; + // textFrag.RichTextOptions.SubScript = rt.VerticalAlign == ExcelVerticalAlignmentFont.Subscript; + // textFrag.RichTextOptions.FontColor = rt.Color; - //Should no longer be neccesary - //textFrag.Font.Style = (textFrag.RichTextOptions.Bold ? MeasurementFontStyles.Bold : 0) | - // (textFrag.RichTextOptions.Italic ? MeasurementFontStyles.Italic : 0) | - // (textFrag.RichTextOptions.UnderlineType != 12 ? MeasurementFontStyles.Underline : 0) | - // (textFrag.RichTextOptions.StrikeType > 1 ? MeasurementFontStyles.Strikeout : 0); + // //Should no longer be neccesary + // //textFrag.Font.Style = (textFrag.RichTextOptions.Bold ? MeasurementFontStyles.Bold : 0) | + // // (textFrag.RichTextOptions.Italic ? MeasurementFontStyles.Italic : 0) | + // // (textFrag.RichTextOptions.UnderlineType != 12 ? MeasurementFontStyles.Underline : 0) | + // // (textFrag.RichTextOptions.StrikeType > 1 ? MeasurementFontStyles.Strikeout : 0); - textFragments.Add(textFrag); - } + // textFragments.Add(textFrag); + // } - return textFragments; - } + // return textFragments; + //} } } diff --git a/src/EPPlus.Export.Pdf/ExcelPdf.cs b/src/EPPlus.Export.Pdf/ExcelPdf.cs index dd806a3792..e007f387b8 100644 --- a/src/EPPlus.Export.Pdf/ExcelPdf.cs +++ b/src/EPPlus.Export.Pdf/ExcelPdf.cs @@ -10,8 +10,9 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -using EPPlus.Export.Pdf.Layout; using EPPlus.Export.Pdf.DocumentObjects; +using EPPlus.Export.Pdf.Enums; +using EPPlus.Export.Pdf.Layout; using EPPlus.Export.Pdf.Resources; using EPPlus.Export.Pdf.Settings; using EPPlus.Graphics; @@ -19,7 +20,6 @@ Date Author Change using System.IO; using System.Linq; using System.Text; -using EPPlus.Export.Pdf.Enums; namespace EPPlus.Export.Pdf { @@ -126,9 +126,9 @@ private PdfPages AddPages() } //Create Catalog - private PdfObjects.PdfCatalog AddCatalog(int pagesObjectNumber) + private PdfCatalog AddCatalog(int pagesObjectNumber) { - var catalog = new PdfObjects.PdfCatalog(_document.Count + 1, pagesObjectNumber); + var catalog = new PdfCatalog(_document.Count + 1, pagesObjectNumber); _document.Add(catalog); return catalog; } @@ -239,19 +239,6 @@ private PdfInfoObject AddInfoObject(string workBookName = "") } internal void CreatePdf(PdfPageSettings pageSettings, PdfDictionaries dictionaries, Transform layout, string fileName) - { - using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write)) - { - CreatePdf(pageSettings, dictionaries, layout, fs); - } - - if (_pageSettings.Debug && _pageSettings.PrintAsText) - { - WriteDebugText(fileName); - } - } - - internal void CreatePdf(PdfPageSettings pageSettings, PdfDictionaries dictionaries, Transform layout, Stream stream) { _pageSettings = pageSettings; _dictionaries = dictionaries; @@ -275,92 +262,169 @@ internal void CreatePdf(PdfPageSettings pageSettings, PdfDictionaries dictionari pages.pageObjectNumbers.Add(page.objectNumber); } var info = AddInfoObject(); - - WriteDocumentToStream(stream, catalog, info); - } - - /// - /// Create the pdf from the supplied worksheet. - /// - /// The file name - internal void CreatePdf(string Filename) - { - using (var fs = new FileStream(Filename, FileMode.Create, FileAccess.Write)) + string debugString = ""; + //write to pdf + PdfCrossRefTable crossRefTable = new PdfCrossRefTable(); + //start wring pdf binary + using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write)) { - CreatePdf(fs); + using (var bw = new BinaryWriter(fs, Encoding.ASCII)) + { + //Write header + bw.Write(Encoding.ASCII.GetBytes(Header)); + debugString += Header; + //Write body + foreach (var pdfobj in _document) + { + crossRefTable.AddPosition(fs.Position); + pdfobj.ToPdfBytes(bw); + debugString += pdfobj.ToPdfString(); + } + //Write CrossReference + crossRefTable.Write(bw, fs.Position, _document.Count); + debugString += crossRefTable.WriteString(_document.Count); + // Write trailer + PdfTrailer.Write(bw, _document.Count, catalog.objectNumber, info.objectNumber, crossRefTable.StartPosition); + debugString += PdfTrailer.WriteString(_document.Count, catalog.objectNumber, info.objectNumber, crossRefTable.StartPosition); + } } - + //Write pdf as txt for debug. if (_pageSettings.Debug && _pageSettings.PrintAsText) { - WriteDebugText(Filename); + using (var fs = new FileStream(fileName + ".txt", FileMode.Create, FileAccess.Write)) + { + using (var wr = new StreamWriter(fs)) + { + wr.Write(debugString); + } + } } } - /// - /// Create the pdf from the supplied worksheet and write it to a stream. - /// - /// The stream to write the pdf to. The stream will not be closed. - internal void CreatePdf(Stream stream) - { - //Create Catalog - var catalogLayout = new PdfCatalogLayout(_workheets[0], _pageSettings, _dictionaries); - var catalog = AddCatalog(2); - //Create Pages - var pagesLayout = catalogLayout.ChildObjects[0]; - var pages = AddPages(); - //Create Fonts - AddFontData(); - //Create Patterns - AddPatternData(); - //Create Shadings - AddShadingsData(); - //Create Page and Content - for (int i = 0; i < pagesLayout.ChildObjects.Count; i++) - { - var pageLayout = pagesLayout.ChildObjects[i]; - var page = AddPage(2, new List(), _pageSettings); - AddContent(pageLayout, page); - pages.pageObjectNumbers.Add(page.objectNumber); - } - var info = AddInfoObject(_workheets[0].Workbook._package.File.Name); - WriteDocumentToStream(stream, catalog, info); - } + //internal void CreatePdf(PdfPageSettings pageSettings, PdfDictionaries dictionaries, Transform layout, string fileName) + //{ + // using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write)) + // { + // CreatePdf(pageSettings, dictionaries, layout, fs); + // } - //Write the document and cross-ref/trailer to the supplied stream. - //The stream is not closed; the caller owns it. - private void WriteDocumentToStream(Stream stream, PdfObjects.PdfCatalog catalog, PdfInfoObject info) - { - _debugString = ""; - PdfCrossRefTable crossRefTable = new PdfCrossRefTable(); + // if (_pageSettings.Debug && _pageSettings.PrintAsText) + // { + // WriteDebugText(fileName); + // } + //} - //Use a BinaryWriter without disposing it, so the underlying stream stays open for the caller. - //BinaryWriter does not own the stream when we don't dispose it; we just flush at the end. - var bw = new BinaryWriter(stream, Encoding.ASCII); - try - { - //Write header - bw.Write(Encoding.ASCII.GetBytes(Header)); - _debugString += Header; - //Write body - foreach (var pdfobj in _document) - { - crossRefTable.AddPosition(stream.Position); - pdfobj.ToPdfBytes(bw); - _debugString += pdfobj.ToPdfString(); - } - //Write CrossReference - crossRefTable.Write(bw, stream.Position, _document.Count); - _debugString += crossRefTable.WriteString(_document.Count); - // Write trailer - PdfTrailer.Write(bw, _document.Count, catalog.objectNumber, info.objectNumber, crossRefTable.StartPosition); - _debugString += PdfTrailer.WriteString(_document.Count, catalog.objectNumber, info.objectNumber, crossRefTable.StartPosition); - } - finally - { - bw.Flush(); - } - } + //internal void CreatePdf(PdfPageSettings pageSettings, PdfDictionaries dictionaries, Transform layout, Stream stream) + //{ + // _pageSettings = pageSettings; + // _dictionaries = dictionaries; + + // var catalog = AddCatalog(2); + // //Create Pages + // var pagesLayout = layout.ChildObjects[0]; + // var pages = AddPages(); + // //Create Fonts + // AddFontData(); + // //Create Patterns + // AddPatternData(); + // //Create Shadings + // AddShadingsData(); + // //Create Page and Content + // for (int i = 0; i < layout.ChildObjects.Count; i++) + // { + // var pageLayout = layout.ChildObjects[i]; + // var page = AddPage(2, new List(), _pageSettings); + // AddContent(pageLayout, page); + // pages.pageObjectNumbers.Add(page.objectNumber); + // } + // var info = AddInfoObject(); + + // WriteDocumentToStream(stream, catalog, info); + //} + + ///// + ///// Create the pdf from the supplied worksheet. + ///// + ///// The file name + //internal void CreatePdf(string Filename) + //{ + // using (var fs = new FileStream(Filename, FileMode.Create, FileAccess.Write)) + // { + // CreatePdf(fs); + // } + + // if (_pageSettings.Debug && _pageSettings.PrintAsText) + // { + // WriteDebugText(Filename); + // } + //} + + /////// + /////// Create the pdf from the supplied worksheet and write it to a stream. + /////// + /////// The stream to write the pdf to. The stream will not be closed. + ////internal void CreatePdf(Stream stream) + ////{ + //// //Create Catalog + //// var catalogLayout = new PdfCatalogLayout(_workheets[0], _pageSettings, _dictionaries); + //// var catalog = AddCatalog(2); + //// //Create Pages + //// var pagesLayout = catalogLayout.ChildObjects[0]; + //// var pages = AddPages(); + //// //Create Fonts + //// AddFontData(); + //// //Create Patterns + //// AddPatternData(); + //// //Create Shadings + //// AddShadingsData(); + //// //Create Page and Content + //// for (int i = 0; i < pagesLayout.ChildObjects.Count; i++) + //// { + //// var pageLayout = pagesLayout.ChildObjects[i]; + //// var page = AddPage(2, new List(), _pageSettings); + //// AddContent(pageLayout, page); + //// pages.pageObjectNumbers.Add(page.objectNumber); + //// } + //// var info = AddInfoObject(_workheets[0].Workbook._package.File.Name); + + //// WriteDocumentToStream(stream, catalog, info); + ////} + + ////Write the document and cross-ref/trailer to the supplied stream. + ////The stream is not closed; the caller owns it. + //private void WriteDocumentToStream(Stream stream, PdfObjects.PdfCatalog catalog, PdfInfoObject info) + //{ + // _debugString = ""; + // PdfCrossRefTable crossRefTable = new PdfCrossRefTable(); + + // //Use a BinaryWriter without disposing it, so the underlying stream stays open for the caller. + // //BinaryWriter does not own the stream when we don't dispose it; we just flush at the end. + // var bw = new BinaryWriter(stream, Encoding.ASCII); + // try + // { + // //Write header + // bw.Write(Encoding.ASCII.GetBytes(Header)); + // _debugString += Header; + // //Write body + // foreach (var pdfobj in _document) + // { + // crossRefTable.AddPosition(stream.Position); + // pdfobj.ToPdfBytes(bw); + // _debugString += pdfobj.ToPdfString(); + // } + // //Write CrossReference + // crossRefTable.Write(bw, stream.Position, _document.Count); + // _debugString += crossRefTable.WriteString(_document.Count); + // // Write trailer + // PdfTrailer.Write(bw, _document.Count, catalog.objectNumber, info.objectNumber, crossRefTable.StartPosition); + // _debugString += PdfTrailer.WriteString(_document.Count, catalog.objectNumber, info.objectNumber, crossRefTable.StartPosition); + // } + // finally + // { + // bw.Flush(); + // } + //} private void WriteDebugText(string fileName) { diff --git a/src/EPPlus.Export.Pdf/Layout/PdfCellBorderLayout.cs b/src/EPPlus.Export.Pdf/Layout/PdfCellBorderLayout.cs index 0c9487730c..a87c2b5927 100644 --- a/src/EPPlus.Export.Pdf/Layout/PdfCellBorderLayout.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfCellBorderLayout.cs @@ -29,11 +29,9 @@ internal class PdfCellBorderLayout : Transform public string range; - internal PdfCellStyle TableStyle; - public PdfCellBorderLayout() { } - public PdfCellBorderLayout(PdfCellStyle style, bool isMerged, MergedCellCorners corners, MergedCellDrawInfo info, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) + public PdfCellBorderLayout(bool isMerged, MergedCellCorners corners, MergedCellDrawInfo info, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) : base(x, y - height, width, height, scaleX, scaleY, rotation, parent) { Z = 3; @@ -43,45 +41,59 @@ public PdfCellBorderLayout(PdfCellStyle style, bool isMerged, MergedCellCorners BorderData = new PdfCellBordersData(); - BorderData.Top.BorderStyle = style.xfTop.Style == ExcelBorderStyle.None ? ((style.dxfTop != null && style.dxfTop.HasValue) ? (ExcelBorderStyle)style.dxfTop.Style : ExcelBorderStyle.None) : style.xfTop.Style; - BorderData.Top.BorderColor = style.dxfTop != null ? PdfColor.SetColorFromHex(style.dxfTop.Color.LookupColor(style.dxfTop)) : PdfColor.SetColorFromHex(style.xfTop.Color.LookupColor(style.xfTop)); BorderData.Top.X = LocalPosition.X; BorderData.Top.Y = LocalPosition.Y; BorderData.Top.Width = Size.X; BorderData.Top.Height = Size.Y; - BorderData.Bottom.BorderStyle = style.xfBottom.Style == ExcelBorderStyle.None ? ((style.dxfBottom != null && style.dxfBottom.HasValue) ? (ExcelBorderStyle)style.dxfBottom.Style : ExcelBorderStyle.None) : style.xfBottom.Style; - BorderData.Bottom.BorderColor = style.dxfBottom != null ? PdfColor.SetColorFromHex(style.dxfBottom.Color.LookupColor(style.dxfBottom)) : PdfColor.SetColorFromHex(style.xfBottom.Color.LookupColor(style.xfBottom)); BorderData.Bottom.X = LocalPosition.X; BorderData.Bottom.Y = LocalPosition.Y; BorderData.Bottom.Width = Size.X; BorderData.Bottom.Height = Size.Y; - BorderData.Left.BorderStyle = style.xfLeft.Style == ExcelBorderStyle.None ? ((style.dxfLeft != null && style.dxfLeft.HasValue) ? (ExcelBorderStyle)style.dxfLeft.Style : ExcelBorderStyle.None) : style.xfLeft.Style; - BorderData.Left.BorderColor = style.dxfLeft != null ? PdfColor.SetColorFromHex(style.dxfLeft.Color.LookupColor(style.dxfLeft)) : PdfColor.SetColorFromHex(style.xfLeft.Color.LookupColor(style.xfLeft)); BorderData.Left.X = LocalPosition.X; BorderData.Left.Y = LocalPosition.Y; BorderData.Left.Width = Size.X; BorderData.Left.Height = Size.Y; - BorderData.Right.BorderStyle = style.xfRight.Style == ExcelBorderStyle.None ? ((style.dxfRight != null && style.dxfRight.HasValue) ? (ExcelBorderStyle)style.dxfRight.Style : ExcelBorderStyle.None) : style.xfRight.Style; - BorderData.Right.BorderColor = style.dxfRight != null ? PdfColor.SetColorFromHex(style.dxfRight.Color.LookupColor(style.dxfRight)) : PdfColor.SetColorFromHex(style.xfRight.Color.LookupColor(style.xfRight)); BorderData.Right.X = LocalPosition.X; BorderData.Right.Y = LocalPosition.Y; BorderData.Right.Width = Size.X; BorderData.Right.Height = Size.Y; - BorderData.DiagonalUp.BorderStyle = style.DiagonalUp ? style.Diagonal.Style : ExcelBorderStyle.None; - BorderData.DiagonalUp.BorderColor = style.DiagonalUp ? PdfColor.SetColorFromHex(style.Diagonal.Color.LookupColor(style.Diagonal)) : Color.Transparent; BorderData.DiagonalUp.MergedDiagonalWidth = width; BorderData.DiagonalUp.MergedDiagonalHeight = height; - BorderData.DiagonalDown.BorderStyle = style.DiagonalDown ? style.Diagonal.Style : ExcelBorderStyle.None; - BorderData.DiagonalDown.BorderColor = style.DiagonalDown ? PdfColor.SetColorFromHex(style.Diagonal.Color.LookupColor(style.Diagonal)) : Color.Transparent; BorderData.DiagonalDown.MergedDiagonalWidth = width; BorderData.DiagonalDown.MergedDiagonalHeight = height; } + internal void SetStyle(ExcelBorderStyle topBorderStyle, Color topBorderColor, + ExcelBorderStyle bottomBorderStyle, Color bottomBorderColor, + ExcelBorderStyle leftBorderStyle, Color leftBorderColor, + ExcelBorderStyle rightBorderStyle, Color rightBorderColor, + ExcelBorderStyle diagUpBorderStyle, Color diagUpBorderColor, + ExcelBorderStyle diagdDownBorderStyle, Color diagDownBorderColor) + { + BorderData.Top.BorderStyle = topBorderStyle; + BorderData.Top.BorderColor = topBorderColor; + + BorderData.Bottom.BorderStyle = bottomBorderStyle; + BorderData.Bottom.BorderColor = bottomBorderColor; + + BorderData.Left.BorderStyle = leftBorderStyle; + BorderData.Left.BorderColor = leftBorderColor; + + BorderData.Right.BorderStyle = rightBorderStyle; + BorderData.Right.BorderColor = rightBorderColor; + + BorderData.DiagonalUp.BorderStyle = diagUpBorderStyle; + BorderData.DiagonalUp.BorderColor = diagUpBorderColor; + + BorderData.DiagonalDown.BorderStyle = diagdDownBorderStyle; + BorderData.DiagonalDown.BorderColor = diagDownBorderColor; + } + diff --git a/src/EPPlus.Export.Pdf/Layout/PdfCellContentLayout.cs b/src/EPPlus.Export.Pdf/Layout/PdfCellContentLayout.cs index 69d0af62d6..1cc9061015 100644 --- a/src/EPPlus.Export.Pdf/Layout/PdfCellContentLayout.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfCellContentLayout.cs @@ -31,7 +31,6 @@ internal class PdfCellContentLayout : Transform public bool IsHeaderFooter; public bool IsHeading; public bool IsPrintTitle; - public PdfCellStyle CellStyle; public TextLayoutEngine textLayoutEngine; //private List textFormats = new List(); public TextLineCollection TextLines; diff --git a/src/EPPlus.Export.Pdf/Layout/PdfCellLayout.cs b/src/EPPlus.Export.Pdf/Layout/PdfCellLayout.cs index d39a15c2e0..e939b4cd04 100644 --- a/src/EPPlus.Export.Pdf/Layout/PdfCellLayout.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfCellLayout.cs @@ -27,89 +27,114 @@ namespace EPPlus.Export.Pdf.Layout internal class PdfCellLayout : Transform { public PdfCellFillData CellFillData; - public PdfCellStyle CellStyle; public double LeftTextSpillLength = 0d; public double RightTextSpillLength = 0d; public bool delete = false; public bool IsHeading = false; public bool IsPrintTitle = false; - public PdfCellLayout(PdfDictionaries dictionaries, PdfCellStyle CellStyle, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) + public PdfCellLayout(double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) : base(x, y - height, width, height, scaleX, scaleY, rotation, parent) { Z = 1; - var xfFill = CellStyle.xfFill; - var dxfFill = CellStyle.dxfFill; + //var xfFill = CellStyle.xfFill; + //var dxfFill = CellStyle.dxfFill; CellFillData = new PdfCellFillData(); - if (dxfFill != null && xfFill.IsEmpty()) - { - CellFillData.PatternStyle = dxfFill.PatternType != null ? (ExcelFillStyle)dxfFill.PatternType : ExcelFillStyle.Solid; - if (CellFillData.PatternStyle == ExcelFillStyle.Solid) - { - CellFillData.BackgroundColor = PdfColor.SetColorFromHex(dxfFill.BackgroundColor.LookupColor()); - } - else if (CellFillData.PatternStyle != ExcelFillStyle.None) - { - CellFillData.BackgroundColor = PdfColor.SetColorFromHex(dxfFill.PatternColor.Color == null ? "#FFFFFFFF" : dxfFill.PatternColor.LookupColor()); - CellFillData.PatternColor = PdfColor.SetColorFromHex(dxfFill.BackgroundColor.LookupColor()); - CellFillData.id = AddPatternResourceData(dictionaries.Patterns, CellFillData.PatternStyle.ToString() + CellFillData.PatternColor.ToHexString() + CellFillData.BackgroundColor.ToHexString()); - } - else if (dxfFill.Gradient != null) - { - CellFillData.GradientFillData = new PdfCellGradientFillData(); - CellFillData.GradientFillData.GradientType = dxfFill.Gradient.GradientType == null ? ExcelFillGradientType.None : (ExcelFillGradientType)dxfFill.Gradient.GradientType; - CellFillData.GradientFillData.Color1 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[0].Color.LookupColor()); - CellFillData.GradientFillData.Color2 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[1].Color.LookupColor()); - CellFillData.GradientFillData.Color3 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[2].Color.LookupColor()); - CellFillData.GradientFillData.Degree = dxfFill.Gradient.Degree == null ? 0 : (double)dxfFill.Gradient.Degree; - CellFillData.GradientFillData.Top = dxfFill.Gradient.Top == null ? 0 : (double)dxfFill.Gradient.Top; - CellFillData.GradientFillData.Bottom = dxfFill.Gradient.Bottom == null ? 0 : (double)dxfFill.Gradient.Bottom; - CellFillData.GradientFillData.Left = dxfFill.Gradient.Left == null ? 0 : (double)dxfFill.Gradient.Left; - CellFillData.GradientFillData.Right = dxfFill.Gradient.Right == null ? 0 : (double)dxfFill.Gradient.Right; - CellFillData.id = CellFillData.GradientFillData.ToString() + $"_{x:F4}_{(y - height):F4}_{width:F4}_{height:F4}"; - AddShadingResourceData(dictionaries.Shadings, CellFillData.id); - } - } - else - { - if (xfFill.PatternType == ExcelFillStyle.Solid) - { - var bkgc = xfFill.BackgroundColor; - CellFillData.PatternStyle = xfFill.PatternType; - if (string.IsNullOrEmpty(bkgc.LookupColor()) && !string.IsNullOrEmpty(cell.Text)) - { - CellFillData.BackgroundColor = Color.Empty; - } - else - { - CellFillData.BackgroundColor = PdfColor.SetColorFromHex(bkgc.LookupColor()); - } - } - else if (xfFill.PatternType != ExcelFillStyle.None) - { - CellFillData.PatternStyle = xfFill.PatternType; - CellFillData.BackgroundColor = PdfColor.SetColorFromHex(xfFill.PatternColor.Rgb == null ? "#FFFFFFFF" : xfFill.PatternColor.LookupColor()); - CellFillData.PatternColor = PdfColor.SetColorFromHex(xfFill.BackgroundColor.LookupColor()); - CellFillData.id = AddPatternResourceData(dictionaries.Patterns, CellFillData.PatternStyle.ToString() + CellFillData.PatternColor.ToHexString() + CellFillData.BackgroundColor.ToHexString()); - } - else if (xfFill.HasGradient) - { - CellFillData.GradientFillData = new PdfCellGradientFillData(); - CellFillData.GradientFillData.GradientType = xfFill.Gradient.Type; - CellFillData.GradientFillData.Color1 = PdfColor.SetColorFromHex(xfFill.Gradient.Color1.LookupColor()); - CellFillData.GradientFillData.Color2 = PdfColor.SetColorFromHex(xfFill.Gradient.Color2.LookupColor()); - CellFillData.GradientFillData.Color3 = PdfColor.SetColorFromHex(xfFill.Gradient.Color3.LookupColor()); - CellFillData.GradientFillData.Degree = xfFill.Gradient.Degree; - CellFillData.GradientFillData.Top = double.IsNaN(xfFill.Gradient.Top) ? 0 : xfFill.Gradient.Top; - CellFillData.GradientFillData.Bottom = double.IsNaN(xfFill.Gradient.Bottom) ? 0 : xfFill.Gradient.Bottom; - CellFillData.GradientFillData.Left = double.IsNaN(xfFill.Gradient.Left) ? 0 : xfFill.Gradient.Left; - CellFillData.GradientFillData.Right = double.IsNaN(xfFill.Gradient.Right) ? 0 : xfFill.Gradient.Right; - CellFillData.id = CellFillData.GradientFillData.ToString() + $"_{x:F4}_{(y - height):F4}_{width:F4}_{height:F4}"; - AddShadingResourceData(dictionaries.Shadings, CellFillData.id); - } - } + //if (dxfFill != null && xfFill.IsEmpty()) + //{ + // CellFillData.PatternStyle = dxfFill.PatternType != null ? (ExcelFillStyle)dxfFill.PatternType : ExcelFillStyle.Solid; + // if (CellFillData.PatternStyle == ExcelFillStyle.Solid) + // { + // CellFillData.BackgroundColor = PdfColor.SetColorFromHex(dxfFill.BackgroundColor.LookupColor()); + // } + // else if (CellFillData.PatternStyle != ExcelFillStyle.None) + // { + // CellFillData.BackgroundColor = PdfColor.SetColorFromHex(dxfFill.PatternColor.Color == null ? "#FFFFFFFF" : dxfFill.PatternColor.LookupColor()); + // CellFillData.PatternColor = PdfColor.SetColorFromHex(dxfFill.BackgroundColor.LookupColor()); + // CellFillData.id = AddPatternResourceData(dictionaries.Patterns, CellFillData.PatternStyle.ToString() + CellFillData.PatternColor.ToHexString() + CellFillData.BackgroundColor.ToHexString()); + // } + // else if (dxfFill.Gradient != null) + // { + // CellFillData.GradientFillData = new PdfCellGradientFillData(); + // CellFillData.GradientFillData.GradientType = dxfFill.Gradient.GradientType == null ? ExcelFillGradientType.None : (ExcelFillGradientType)dxfFill.Gradient.GradientType; + // CellFillData.GradientFillData.Color1 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[0].Color.LookupColor()); + // CellFillData.GradientFillData.Color2 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[1].Color.LookupColor()); + // CellFillData.GradientFillData.Color3 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[2].Color.LookupColor()); + // CellFillData.GradientFillData.Degree = dxfFill.Gradient.Degree == null ? 0 : (double)dxfFill.Gradient.Degree; + // CellFillData.GradientFillData.Top = dxfFill.Gradient.Top == null ? 0 : (double)dxfFill.Gradient.Top; + // CellFillData.GradientFillData.Bottom = dxfFill.Gradient.Bottom == null ? 0 : (double)dxfFill.Gradient.Bottom; + // CellFillData.GradientFillData.Left = dxfFill.Gradient.Left == null ? 0 : (double)dxfFill.Gradient.Left; + // CellFillData.GradientFillData.Right = dxfFill.Gradient.Right == null ? 0 : (double)dxfFill.Gradient.Right; + // CellFillData.id = CellFillData.GradientFillData.ToString() + $"_{x:F4}_{(y - height):F4}_{width:F4}_{height:F4}"; + // AddShadingResourceData(dictionaries.Shadings, CellFillData.id); + // } + //} + //else + //{ + // if (xfFill.PatternType == ExcelFillStyle.Solid) + // { + // var bkgc = xfFill.BackgroundColor; + // CellFillData.PatternStyle = xfFill.PatternType; + // if (string.IsNullOrEmpty(bkgc.LookupColor()) && !string.IsNullOrEmpty(cell.Text)) + // { + // CellFillData.BackgroundColor = Color.Empty; + // } + // else + // { + // CellFillData.BackgroundColor = PdfColor.SetColorFromHex(bkgc.LookupColor()); + // } + // } + // else if (xfFill.PatternType != ExcelFillStyle.None) + // { + // CellFillData.PatternStyle = xfFill.PatternType; + // CellFillData.BackgroundColor = PdfColor.SetColorFromHex(xfFill.PatternColor.Rgb == null ? "#FFFFFFFF" : xfFill.PatternColor.LookupColor()); + // CellFillData.PatternColor = PdfColor.SetColorFromHex(xfFill.BackgroundColor.LookupColor()); + // CellFillData.id = AddPatternResourceData(dictionaries.Patterns, CellFillData.PatternStyle.ToString() + CellFillData.PatternColor.ToHexString() + CellFillData.BackgroundColor.ToHexString()); + // } + // else if (xfFill.HasGradient) + // { + // CellFillData.GradientFillData = new PdfCellGradientFillData(); + // CellFillData.GradientFillData.GradientType = xfFill.Gradient.Type; + // CellFillData.GradientFillData.Color1 = PdfColor.SetColorFromHex(xfFill.Gradient.Color1.LookupColor()); + // CellFillData.GradientFillData.Color2 = PdfColor.SetColorFromHex(xfFill.Gradient.Color2.LookupColor()); + // CellFillData.GradientFillData.Color3 = PdfColor.SetColorFromHex(xfFill.Gradient.Color3.LookupColor()); + // CellFillData.GradientFillData.Degree = xfFill.Gradient.Degree; + // CellFillData.GradientFillData.Top = double.IsNaN(xfFill.Gradient.Top) ? 0 : xfFill.Gradient.Top; + // CellFillData.GradientFillData.Bottom = double.IsNaN(xfFill.Gradient.Bottom) ? 0 : xfFill.Gradient.Bottom; + // CellFillData.GradientFillData.Left = double.IsNaN(xfFill.Gradient.Left) ? 0 : xfFill.Gradient.Left; + // CellFillData.GradientFillData.Right = double.IsNaN(xfFill.Gradient.Right) ? 0 : xfFill.Gradient.Right; + // CellFillData.id = CellFillData.GradientFillData.ToString() + $"_{x:F4}_{(y - height):F4}_{width:F4}_{height:F4}"; + // AddShadingResourceData(dictionaries.Shadings, CellFillData.id); + // } + //} } + internal void SetFill(Color backgroundColor) + { + CellFillData.BackgroundColor = Color.Empty; + } + internal void SetPattern(PdfDictionaries dictionaries, ExcelFillStyle patternStyle, Color backgroundColor, Color patternColor) + { + CellFillData.PatternStyle = patternStyle; + CellFillData.BackgroundColor = backgroundColor; + CellFillData.PatternColor = patternColor; + CellFillData.id = AddPatternResourceData(dictionaries.Patterns, CellFillData.PatternStyle.ToString() + CellFillData.PatternColor.ToHexString() + CellFillData.BackgroundColor.ToHexString()); + } + internal void SetGradient(PdfDictionaries dictionaries, ExcelFillGradientType gradientType, Color color1, Color color2, Color color3, double degree, double top, double bottom, double left, double right) + { + CellFillData.GradientFillData = new PdfCellGradientFillData(); + CellFillData.GradientFillData.GradientType = gradientType; + CellFillData.GradientFillData.Color1 = color1; + CellFillData.GradientFillData.Color2 = color2; + CellFillData.GradientFillData.Color3 = color3; + CellFillData.GradientFillData.Degree = degree; + CellFillData.GradientFillData.Top = top; + CellFillData.GradientFillData.Bottom = bottom; + CellFillData.GradientFillData.Left = left; + CellFillData.GradientFillData.Right = right; + CellFillData.id = CellFillData.GradientFillData.ToString() + $"_{Position.X:F4}_{(Position.Y - Size.Y):F4}_{Size.X:F4}_{Size.Y:F4}"; + AddShadingResourceData(dictionaries.Shadings, CellFillData.id); + } //public PdfCellLayout(PdfDictionaries dictionaries, ExcelRangeBase cell, PdfCellStyle CellStyle, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) // : base(x, y - height, width, height, scaleX, scaleY, rotation, parent) diff --git a/src/EPPlus/Export/PdfExport/TextMapping/PdfHeaderFooter.cs b/src/EPPlus.Export.Pdf/Layout/PdfHeaderFooter.cs similarity index 92% rename from src/EPPlus/Export/PdfExport/TextMapping/PdfHeaderFooter.cs rename to src/EPPlus.Export.Pdf/Layout/PdfHeaderFooter.cs index e7f38c51d6..137ab93172 100644 --- a/src/EPPlus/Export/PdfExport/TextMapping/PdfHeaderFooter.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfHeaderFooter.cs @@ -11,10 +11,9 @@ Date Author Change 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ using EPPlus.Fonts.OpenType.Integration; -using OfficeOpenXml.Export.PdfExport.Data; using System.Collections.Generic; -namespace OfficeOpenXml.Export.PdfExport +namespace EPPlus.Export.Pdf.Layout { internal enum HeaderFooterType { @@ -42,14 +41,14 @@ internal class PdfHeaderFooter public HeaderFooterAlignment Alignment; public HeaderFooterSection Section; - public PdfCell Content; + public PdfCellBase Content; public List NumberOfPagesIndexes = new List(); public List PageNumberIndexes = new List(); public PdfHeaderFooter(List textFormats, List pageNumberIndexes, List numberOfPagesIndexes, HeaderFooterType type, HeaderFooterAlignment alignment, HeaderFooterSection section) { - Content = new PdfCell(); + Content = new PdfCellBase(); Content.TextFragments = textFormats; PageNumberIndexes = pageNumberIndexes; NumberOfPagesIndexes = numberOfPagesIndexes; diff --git a/src/EPPlus.Export.Pdf/Resources/PdfDictionaries.cs b/src/EPPlus.Export.Pdf/Resources/PdfDictionaries.cs index 961780e96a..96fb2a6e3d 100644 --- a/src/EPPlus.Export.Pdf/Resources/PdfDictionaries.cs +++ b/src/EPPlus.Export.Pdf/Resources/PdfDictionaries.cs @@ -49,8 +49,5 @@ internal PdfFontResource GetFont(string FontName) } return Fonts[FontName]; } - - //this should move and be on worksheet level. - internal readonly Dictionary CommentsAndNotes = new Dictionary(); } } diff --git a/src/EPPlus/Export/PdfExport/Layout/PdfLayout.cs b/src/EPPlus/Export/PdfExport/Layout/PdfLayout.cs index b90a611ca8..0e351f82c5 100644 --- a/src/EPPlus/Export/PdfExport/Layout/PdfLayout.cs +++ b/src/EPPlus/Export/PdfExport/Layout/PdfLayout.cs @@ -10,6 +10,7 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ +using EPPlus.Export.Pdf.Helpers; using EPPlus.Export.Pdf.Layout; using EPPlus.Export.Pdf.Resources; using EPPlus.Export.Pdf.Settings; @@ -18,10 +19,12 @@ Date Author Change using EPPlus.Graphics; using OfficeOpenXml.Export.PdfExport.Data; using OfficeOpenXml.Export.PdfExport.TextShaping; +using OfficeOpenXml.FormulaParsing.Excel.Functions.Text; using OfficeOpenXml.Interfaces.Fonts; using OfficeOpenXml.Style; using System; using System.Collections.Generic; +using System.Drawing; namespace OfficeOpenXml.Export.PdfExport.Layout { @@ -127,8 +130,8 @@ internal static Transform GetCatalog(PdfPageSettings pageSettings, PdfDictionari { //Fill var cellStyle = map.Main?.CellStyle ?? map.CellStyle; - var fill = new PdfCellLayout(dictionaries, cellStyle, - info.X, info.Y, info.Width, info.Height); + var fill = new PdfCellLayout(info.X, info.Y, info.Width, info.Height); + SetFill(dictionaries, cellStyle, map.Text, fill); fill.Name = map.Name; fill.UpdateShadingPositionMatrix(pageSettings); pageLayout.AddChild(fill); @@ -148,7 +151,6 @@ internal static Transform GetCatalog(PdfPageSettings pageSettings, PdfDictionari if (HasDiagonalBorder(mergeMainStyle)) { var diagBorder = new PdfCellBorderLayout( - mergeMainStyle, isMerged: false, // use X/Y/W/H path in renderer, not info.* corners: MergedCellCorners.All, info: info, @@ -156,6 +158,7 @@ internal static Transform GetCatalog(PdfPageSettings pageSettings, PdfDictionari y: info.Y, // virtual full-merge top Y width: info.Width, // full merge width height: info.Height); // full merge height + SetBorderStyle(mergeMainStyle, diagBorder); diagBorder.Name = map.Name; // Suppress edge borders — this layout exists only for the diagonal diagBorder.BorderData.Top.BorderStyle = (EPPlus.Export.Pdf.Enums.ExcelBorderStyle)ExcelBorderStyle.None; @@ -171,7 +174,8 @@ internal static Transform GetCatalog(PdfPageSettings pageSettings, PdfDictionari else { //Fill - var fill = new PdfCellLayout(dictionaries, map.CellStyle, x, y, map.ColumnWidth, rowHeight); + var fill = new PdfCellLayout(x, y, map.ColumnWidth, rowHeight); + SetFill(dictionaries, map.CellStyle, map.Text, fill); fill.UpdateShadingPositionMatrix(pageSettings); fill.Name = map.Name; pageLayout.AddChild(fill); @@ -190,7 +194,8 @@ internal static Transform GetCatalog(PdfPageSettings pageSettings, PdfDictionari var borderStyle = (map.Merged && map.Main != null) ? map.Main.CellStyle : map.CellStyle; if (HasBorder(map.CellStyle)) { - var border = new PdfCellBorderLayout(map.CellStyle, map.Merged, GetCorners(map.MergedAddress, row, col), info, x, y, map.ColumnWidth, rowHeight); + var border = new PdfCellBorderLayout(map.Merged, GetCorners(map.MergedAddress, row, col), info, x, y, map.ColumnWidth, rowHeight); + SetBorderStyle(map.CellStyle, border); border.Name = map.Name; if (map.Merged && map.MergedAddress != null) { @@ -311,6 +316,75 @@ internal static Transform GetCatalog(PdfPageSettings pageSettings, PdfDictionari return Catalog; } + private static void SetFill(PdfDictionaries dictionaries, PdfCellStyle cellStyle, string text, PdfCellLayout fill) + { + var xfFill = cellStyle.xfFill; + var dxfFill = cellStyle.dxfFill; + if (dxfFill != null && xfFill.IsEmpty()) + { + var patternStyle = dxfFill.PatternType != null ? (ExcelFillStyle)dxfFill.PatternType : ExcelFillStyle.Solid; + if (patternStyle == ExcelFillStyle.Solid) + { + fill.SetFill( PdfColor.SetColorFromHex(dxfFill.BackgroundColor.LookupColor())); + } + else if (patternStyle != ExcelFillStyle.None) + { + var bkgc = PdfColor.SetColorFromHex(dxfFill.PatternColor.Color == null ? "#FFFFFFFF" : dxfFill.PatternColor.LookupColor()); + var patc = PdfColor.SetColorFromHex(dxfFill.BackgroundColor.LookupColor()); + fill.SetPattern(dictionaries, (EPPlus.Export.Pdf.Enums.ExcelFillStyle)patternStyle, bkgc, patc); + } + else if (dxfFill.Gradient != null) + { + var gradientType = dxfFill.Gradient.GradientType == null ? ExcelFillGradientType.None : (ExcelFillGradientType)dxfFill.Gradient.GradientType; + var color1 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[0].Color.LookupColor()); + var color2 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[1].Color.LookupColor()); + var color3 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[2].Color.LookupColor()); + var degree = dxfFill.Gradient.Degree == null ? 0 : (double)dxfFill.Gradient.Degree; + var top = dxfFill.Gradient.Top == null ? 0 : (double)dxfFill.Gradient.Top; + var bottom = dxfFill.Gradient.Bottom == null ? 0 : (double)dxfFill.Gradient.Bottom; + var left = dxfFill.Gradient.Left == null ? 0 : (double)dxfFill.Gradient.Left; + var right = dxfFill.Gradient.Right == null ? 0 : (double)dxfFill.Gradient.Right; + fill.SetGradient(dictionaries, (EPPlus.Export.Pdf.Enums.ExcelFillGradientType)gradientType, color1, color2, color3, degree, top, bottom, left, right); + } + } + else + { + if (xfFill.PatternType == ExcelFillStyle.Solid) + { + var bkgc = xfFill.BackgroundColor; + var patternStyle = xfFill.PatternType; + if (string.IsNullOrEmpty(bkgc.LookupColor()) && !string.IsNullOrEmpty(text)) + { + fill.SetFill(Color.Empty); + } + else + { + fill.SetFill(PdfColor.SetColorFromHex(bkgc.LookupColor())); + } + } + else if (xfFill.PatternType != ExcelFillStyle.None) + { + var patternStyle = xfFill.PatternType; + var bkgc = PdfColor.SetColorFromHex(xfFill.PatternColor.Rgb == null ? "#FFFFFFFF" : xfFill.PatternColor.LookupColor()); + var patc = PdfColor.SetColorFromHex(xfFill.BackgroundColor.LookupColor()); + fill.SetPattern(dictionaries, (EPPlus.Export.Pdf.Enums.ExcelFillStyle)patternStyle, bkgc, patc); + } + else if (xfFill.HasGradient) + { + var gradientType = xfFill.Gradient.Type; + var color1 = PdfColor.SetColorFromHex(xfFill.Gradient.Color1.LookupColor()); + var color2 = PdfColor.SetColorFromHex(xfFill.Gradient.Color2.LookupColor()); + var color3 = PdfColor.SetColorFromHex(xfFill.Gradient.Color3.LookupColor()); + var degree = xfFill.Gradient.Degree; + var top = double.IsNaN(xfFill.Gradient.Top) ? 0 : xfFill.Gradient.Top; + var bottom = double.IsNaN(xfFill.Gradient.Bottom) ? 0 : xfFill.Gradient.Bottom; + var left = double.IsNaN(xfFill.Gradient.Left) ? 0 : xfFill.Gradient.Left; + var right = double.IsNaN(xfFill.Gradient.Right) ? 0 : xfFill.Gradient.Right; + fill.SetGradient(dictionaries, (EPPlus.Export.Pdf.Enums.ExcelFillGradientType)gradientType, color1, color2, color3, degree, top, bottom, left, right); + } + } + } + static MergedCellCorners GetCorners(ExcelAddressBase addr, int row, int col) { if (addr == null) return MergedCellCorners.All; @@ -387,7 +461,8 @@ private static void AddHeadingCell(PdfPageSettings pageSettings, PdfDictionaries { if (width == 0d || height == 0d) return; - var fill = new PdfCellLayout(dictionaries, headingStyle, x, y, width, height); + var fill = new PdfCellLayout(x, y, width, height); + SetFill(dictionaries, headingStyle, label, fill); fill.Name = namePrefix; fill.IsHeading = true; fill.UpdateShadingPositionMatrix(pageSettings); @@ -411,8 +486,8 @@ private static void AddHeadingCells(PdfPageSettings pageSettings, PdfDictionarie var headingStyle = new PdfCellStyle(); headingStyle.xfFill = fill; - var cornerFill = new PdfCellLayout(dictionaries, headingStyle, - pageSettings.ContentBounds.Left, pageSettings.ContentBounds.Top, headingWidth, headingHeight); + var cornerFill = new PdfCellLayout(pageSettings.ContentBounds.Left, pageSettings.ContentBounds.Top, headingWidth, headingHeight); + SetFill(dictionaries, headingStyle, "", cornerFill); cornerFill.Name = "Heading_Corner"; cornerFill.UpdateShadingPositionMatrix(pageSettings); pageLayout.AddChild(cornerFill); @@ -521,7 +596,8 @@ private static void AddPrintTitleCells(PdfPageSettings pageSettings, PdfDictiona { var map = t.Cell; - var fill = new PdfCellLayout(dictionaries, map.CellStyle, t.X, t.Y, t.Width, t.Height); + var fill = new PdfCellLayout(t.X, t.Y, t.Width, t.Height); + SetFill(dictionaries, map.CellStyle, map.Text, fill); fill.Name = map.Name; fill.IsPrintTitle = true; fill.UpdateShadingPositionMatrix(pageSettings); @@ -550,7 +626,8 @@ private static void AddPrintTitleCells(PdfPageSettings pageSettings, PdfDictiona if (!map.Merged && HasBorder(map.CellStyle)) // was: if (HasBorder(map.CellStyle)) { - var border = new PdfCellBorderLayout(map.CellStyle, false, MergedCellCorners.All, new MergedCellDrawInfo(), t.X, t.Y, t.Width, t.Height); + var border = new PdfCellBorderLayout(false, MergedCellCorners.All, new MergedCellDrawInfo(), t.X, t.Y, t.Width, t.Height); + SetBorderStyle(map.CellStyle, border); border.IsPrintTitle = true; border.Name = "PrintTitleBorder_" + map.Name; pageLayout.AddChild(border); @@ -560,7 +637,8 @@ private static void AddPrintTitleCells(PdfPageSettings pageSettings, PdfDictiona { var sub = b.Cell; if (!HasBorder(sub.CellStyle)) continue; - var border = new PdfCellBorderLayout(sub.CellStyle, false, MergedCellCorners.All, new MergedCellDrawInfo(), b.X, b.Y, b.Width, b.Height); + var border = new PdfCellBorderLayout(false, MergedCellCorners.All, new MergedCellDrawInfo(), b.X, b.Y, b.Width, b.Height); + SetBorderStyle(sub.CellStyle, border); border.IsPrintTitle = true; border.Name = "PrintTitleMergeBorder_" + sub.Name; pageLayout.AddChild(border); @@ -568,6 +646,34 @@ private static void AddPrintTitleCells(PdfPageSettings pageSettings, PdfDictiona } } + private static void SetBorderStyle(PdfCellStyle style, PdfCellBorderLayout border) + { + var topStyle = style.xfTop.Style == ExcelBorderStyle.None ? ((style.dxfTop != null && style.dxfTop.HasValue) ? (ExcelBorderStyle)style.dxfTop.Style : ExcelBorderStyle.None) : style.xfTop.Style; + var topColor = style.dxfTop != null ? PdfColor.SetColorFromHex(style.dxfTop.Color.LookupColor(style.dxfTop)) : PdfColor.SetColorFromHex(style.xfTop.Color.LookupColor(style.xfTop)); + + var bottomStyle = style.xfBottom.Style == ExcelBorderStyle.None ? ((style.dxfBottom != null && style.dxfBottom.HasValue) ? (ExcelBorderStyle)style.dxfBottom.Style : ExcelBorderStyle.None) : style.xfBottom.Style; + var bottomColor = style.dxfBottom != null ? PdfColor.SetColorFromHex(style.dxfBottom.Color.LookupColor(style.dxfBottom)) : PdfColor.SetColorFromHex(style.xfBottom.Color.LookupColor(style.xfBottom)); + + var leftStyle = style.xfLeft.Style == ExcelBorderStyle.None ? ((style.dxfLeft != null && style.dxfLeft.HasValue) ? (ExcelBorderStyle)style.dxfLeft.Style : ExcelBorderStyle.None) : style.xfLeft.Style; + var leftColor = style.dxfLeft != null ? PdfColor.SetColorFromHex(style.dxfLeft.Color.LookupColor(style.dxfLeft)) : PdfColor.SetColorFromHex(style.xfLeft.Color.LookupColor(style.xfLeft)); + + var rightStyle = style.xfRight.Style == ExcelBorderStyle.None ? ((style.dxfRight != null && style.dxfRight.HasValue) ? (ExcelBorderStyle)style.dxfRight.Style : ExcelBorderStyle.None) : style.xfRight.Style; + var rightColor = style.dxfRight != null ? PdfColor.SetColorFromHex(style.dxfRight.Color.LookupColor(style.dxfRight)) : PdfColor.SetColorFromHex(style.xfRight.Color.LookupColor(style.xfRight)); + + var diagUpStyle = style.DiagonalUp ? style.Diagonal.Style : ExcelBorderStyle.None; + var diagUpColor = style.DiagonalUp ? PdfColor.SetColorFromHex(style.Diagonal.Color.LookupColor(style.Diagonal)) : Color.Transparent; + + var diagDownStyle = style.DiagonalDown ? style.Diagonal.Style : ExcelBorderStyle.None; + var diagDownColor = style.DiagonalDown ? PdfColor.SetColorFromHex(style.Diagonal.Color.LookupColor(style.Diagonal)) : Color.Transparent; + + border.SetStyle((EPPlus.Export.Pdf.Enums.ExcelBorderStyle)topStyle, topColor, + (EPPlus.Export.Pdf.Enums.ExcelBorderStyle)bottomStyle, bottomColor, + (EPPlus.Export.Pdf.Enums.ExcelBorderStyle)leftStyle, leftColor, + (EPPlus.Export.Pdf.Enums.ExcelBorderStyle)rightStyle, rightColor, + (EPPlus.Export.Pdf.Enums.ExcelBorderStyle)diagUpStyle, diagUpColor, + (EPPlus.Export.Pdf.Enums.ExcelBorderStyle)diagDownStyle, diagDownColor); + } + // TODO Count total pages when creating them isntead of looping them here. private static int GetTotalPages(List pdfPages) @@ -1481,7 +1587,7 @@ private static void SubstitutePageNumbers(PdfPageSettings pageSettings, PdfDicti foreach (var idx in hf.NumberOfPagesIndexes) hf.Content.TextFragments[idx].Text = totalPages.ToString(); } - PdfTextShaper.ShapeText(pageSettings, dictionaries, hf.Content); + PdfTextShaper.ShapeText(pageSettings, dictionaries, (PdfCell)hf.Content); } private static void AddIncomingSpill(Page page, PdfRange range, int fromRow, int toRow, int windowFromCol, int windowToCol, double windowOriginX, double windowOriginY, bool isPrintTitle) diff --git a/src/EPPlus/Export/PdfExport/PdfCatalog.cs b/src/EPPlus/Export/PdfExport/PdfCatalog.cs index 2dc57e0ce3..5b8cc13199 100644 --- a/src/EPPlus/Export/PdfExport/PdfCatalog.cs +++ b/src/EPPlus/Export/PdfExport/PdfCatalog.cs @@ -11,6 +11,7 @@ Date Author Change 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ using EPPlus.Graphics; +using EPPlus.Export.Pdf; using EPPlus.Export.Pdf.Settings; using EPPlus.Export.Pdf.Resources; using OfficeOpenXml.Export.PdfExport.Data; @@ -18,10 +19,8 @@ Date Author Change using OfficeOpenXml.Export.PdfExport.RowResize; using OfficeOpenXml.Export.PdfExport.TextMapping; using OfficeOpenXml.Export.PdfExport.TextShaping; -using System; using System.Collections.Generic; using System.Diagnostics; -using System.IO; using System.Linq; namespace OfficeOpenXml.Export.PdfExport @@ -69,17 +68,15 @@ private void HandleWorksheetCollection(PdfPageSettings pageSettings, ExcelWorksh public PdfCatalog(PdfPageSettings pageSettings, ExcelWorksheet worksheet, string fileName) { - BuildPdf(pageSettings, worksheet, (excelPdf, layout) => - excelPdf.CreatePdf(pageSettings, _dictionaries, layout, fileName)); + BuildPdf(pageSettings, worksheet, fileName); } - public PdfCatalog(PdfPageSettings pageSettings, ExcelWorksheet worksheet, Stream stream) - { - BuildPdf(pageSettings, worksheet, (excelPdf, layout) => - excelPdf.CreatePdf(pageSettings, _dictionaries, layout, stream)); - } + //public PdfCatalog(PdfPageSettings pageSettings, ExcelWorksheet worksheet, Stream stream) + //{ + // BuildPdf(pageSettings, worksheet); + //} - private void BuildPdf(PdfPageSettings pageSettings, ExcelWorksheet worksheet, Action writePdf) + private void BuildPdf(PdfPageSettings pageSettings, ExcelWorksheet worksheet, string fileName) { //pageSettings.defaultFontName = worksheet.Workbook.ThemeManager.CurrentTheme.FontScheme.MinorFont[0].Typeface; pageSettings.defaultFontName = worksheet.Workbook.ThemeManager.GetOrCreateTheme().FontScheme.MinorFont[0].Typeface; @@ -110,15 +107,15 @@ private void BuildPdf(PdfPageSettings pageSettings, ExcelWorksheet worksheet, Ac sw.Start(); //Create Layout - var Layout = GetLayout(pageSettings, pdfSheet); + var layout = GetLayout(pageSettings, pdfSheet); sw.Stop(); var CreateLayoutTime = sw.ElapsedMilliseconds; sw.Reset(); sw.Start(); - //Create Pdf //Done in pdf export project + //Write Pdf Document ExcelPdf excelPdf = new ExcelPdf(); - writePdf(excelPdf, Layout); + excelPdf.CreatePdf(pageSettings, _dictionaries, layout, fileName); sw.Stop(); var CreatePdfTime = sw.ElapsedMilliseconds; sw.Reset(); diff --git a/src/EPPlus/Export/PdfExport/TextMapping/PdfHeaderFooterCollection.cs b/src/EPPlus/Export/PdfExport/TextMapping/PdfHeaderFooterCollection.cs index bc39a63073..0f6e9be031 100644 --- a/src/EPPlus/Export/PdfExport/TextMapping/PdfHeaderFooterCollection.cs +++ b/src/EPPlus/Export/PdfExport/TextMapping/PdfHeaderFooterCollection.cs @@ -10,6 +10,7 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ +using EPPlus.Export.Pdf.Layout; using EPPlus.Export.Pdf.Settings; using EPPlus.Export.Pdf.Resources; using OfficeOpenXml.Export.PdfExport.Data; diff --git a/src/EPPlus/Properties/AssemblyInfo.cs b/src/EPPlus/Properties/AssemblyInfo.cs index d3650b0bfb..7d2f621091 100644 --- a/src/EPPlus/Properties/AssemblyInfo.cs +++ b/src/EPPlus/Properties/AssemblyInfo.cs @@ -19,6 +19,7 @@ Date Author Change // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: InternalsVisibleTo("EPPlusTest, PublicKey=00240000048000009400000006020000002400005253413100040000010001001dd11308ec93a6ebcec727e183a8972dc6f95c23ecc34aa04f40cbfc9c17b08b4a0ea5c00dcd203bace44d15a30ce8796e38176ae88e960ceff9cc439ab938738ba0e603e3d155fc298799b391c004fc0eb4393dd254ce25db341eb43303e4c488c9500e126f1288594f0710ec7d642e9c72e76dd860649f1c48249c00e31fba")] +[assembly: InternalsVisibleTo("EPPlus.Export.Pdf.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dd3a3466a88cbf5d374fe992cec433c48022414fe96608933e8e36782001213dd31bc454dc6f962a54a3a76cfb9e03a32cd4c658ecd49d1a98709971a080ab92d5c5b65346155f8d6422db4ffbf662f78913996a9a8b78ee11ff3cda7e585208cd4468fb3201f15bbb1dfc45c120703c9d6ad495bb9de66893ae5ab5ac8f40dc")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] [assembly: InternalsVisibleTo("EPPlus.DrawingRenderer.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010065aa00a9b0dcfa0e8debefb14c3b6c12ef658ce1cfbfafcb6eb7dbdc0c49e4f70ea144ba29d827453f0716ffc8c1d87450ce0cf255bda1def174915caaa78f373f291ce1a7edf91ba6f9dc961d937b19d46dd5d7d70a6e2097c749d43780f0d00b29a6f5aec5c1d191bee69de0c889a6d2566bef3cb235612351eae7015382d0")] [assembly: InternalsVisibleTo("EPPlus.Graphics, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e9b33e79ac9cbb1ca1e0df0aa47d956404607d99cb28a5982f936de662eb2fd3d3459c5b625fe4791606113ff89ee8b915e1446d348be8c6b5106fd3ffde14cf337587920e1c76bebc3bfd95d7525f3a5bc384fb19f3c06306b8cd5ffbe4284c36d8285f16cc50e6b408a2e197229108ce683d865a946b92a324cfba8dbf57c2")] From b9b1f697cff4df3b3b8c5a7d8744c04aa58aacd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Thu, 25 Jun 2026 15:08:29 +0200 Subject: [PATCH 39/49] Removed some unused code. --- src/EPPlus.Export.Pdf.Tests/PdfTests.cs | 2 +- .../DocumentObjects/Fonts/PdfCIDFont.cs | 5 +- .../DocumentObjects/Fonts/PdfCidSet.cs | 1 + .../DocumentObjects/Fonts/PdfFont.cs | 6 +- .../Fonts/PdfFontDescriptor.cs | 2 +- .../DocumentObjects/Fonts/PdfFontWidths.cs | 78 ----- .../DocumentObjects/Fonts/PdfToUnicodeCMap.cs | 18 -- .../DocumentObjects/Fonts/PdfType0FontDict.cs | 1 - .../Patterns/PdfPatternFill.cs | 1 - .../Patterns/PdfPatternMaskFill.cs | 4 +- .../DocumentObjects/PdfBorderRenderer.cs | 6 - .../DocumentObjects/PdfContentStream.cs | 245 +------------- .../DocumentObjects/PdfInfoObject.cs | 1 - .../DocumentObjects/PdfObject.cs | 9 - .../DocumentObjects/Shadings/PdfShading.cs | 2 +- src/EPPlus.Export.Pdf/ExcelPdf.cs | 149 --------- src/EPPlus.Export.Pdf/Helpers/PdfString.cs | 14 - src/EPPlus.Export.Pdf/Layout/PdfBorderData.cs | 3 - src/EPPlus.Export.Pdf/Layout/PdfCell.cs | 24 +- .../Layout/PdfCellBorderLayout.cs | 157 --------- .../Layout/PdfCellContentLayout.cs | 239 +------------- src/EPPlus.Export.Pdf/Layout/PdfCellLayout.cs | 188 +---------- .../Layout/PdfHeaderFooter.cs | 2 - src/EPPlus.Export.Pdf/Layout/PdfPageLayout.cs | 303 +----------------- src/EPPlus.Export.Pdf/Layout/PdfShapedText.cs | 2 - .../Resources/PdfDictionaries.cs | 9 +- .../Resources/PdfFontResource.cs | 57 ---- .../Resources/PdfPatternResource.cs | 10 +- src/EPPlus/Export/PdfExport/Data/PageData.cs | 7 - src/EPPlus/Export/PdfExport/Data/PdfCell.cs | 1 - .../PdfExport/Data/PdfCellCollection.cs | 4 +- src/EPPlus/Export/PdfExport/Data/PdfRange.cs | 7 +- .../Export/PdfExport/Layout/PdfLayout.cs | 187 +---------- .../RowResize/PdfCalculateRowHeight.cs | 8 - .../TextMapping/PdfCommentsAndNotes.cs | 1 + .../PdfExport/TextMapping/PdfTextMap.cs | 53 +-- .../PdfExport/TextShaping/PdfTextShaper.cs | 78 ++++- 37 files changed, 128 insertions(+), 1756 deletions(-) diff --git a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs index a99e6ae7bd..f814f4d22d 100644 --- a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs +++ b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs @@ -178,7 +178,7 @@ public void ReadPrintAreas() pageSettings.PrintAsText = true; pageSettings.ShowGridLines = true; pageSettings.ShowHeadings = true; - PdfCatalog catlog = new PdfCatalog(pageSettings, ws, "C:\\epplustest\\pdf\\FullPageTest57.pdf"); + PdfCatalog catlog = new PdfCatalog(pageSettings, ws, "C:\\epplustest\\pdf\\FullPageTest58.pdf"); //line breaks //wrap comments diff --git a/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfCIDFont.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfCIDFont.cs index 0ae23865e5..c5415513a4 100644 --- a/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfCIDFont.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfCIDFont.cs @@ -19,7 +19,7 @@ Date Author Change namespace EPPlus.Export.Pdf.DocumentObjects.Fonts { - public enum CIDFontSubtype + internal enum CIDFontSubtype { CIDFontType0, CIDFontType2 @@ -70,7 +70,6 @@ internal override string RenderDictionary() } if (Gids != null) { - //var widthsStr = string.Join(" ", W.Select(w => w.ToString()).ToArray()); sb.AppendFormat($"\n /W [{BuildWidthsArray()}]"); } if (DW2 != null) @@ -107,7 +106,6 @@ internal override void RenderDictionary(BinaryWriter bw) } if (Gids != null) { - //var widthsStr = string.Join(" ", W.Select(w => w.ToString()).ToArray()); sb.AppendFormat($"\n /W [ {BuildWidthsArray()} ]"); } if (DW2 != null) @@ -134,7 +132,6 @@ private string BuildWidthsArray() { var sortedGids = Gids.OrderBy(g => g).ToList(); var sb = new StringBuilder(); - int i = 0; while (i < sortedGids.Count) { diff --git a/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfCidSet.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfCidSet.cs index 35566d6232..ce46bcf6a7 100644 --- a/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfCidSet.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfCidSet.cs @@ -17,6 +17,7 @@ namespace EPPlus.Export.Pdf.DocumentObjects.Fonts internal class PdfCidSet : PdfObject { byte[] CidSet; + public PdfCidSet(int objectNumber, byte[] cidSet , int version = 0) : base(objectNumber, version) { CidSet = cidSet; diff --git a/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFont.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFont.cs index be5226a1af..54ece281b1 100644 --- a/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFont.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFont.cs @@ -15,7 +15,7 @@ Date Author Change namespace EPPlus.Export.Pdf.DocumentObjects.Fonts { - public enum PdfFontSubType + internal enum PdfFontSubType { Type0, //Used for embedded fonts Type1, //Used for built-in fonts @@ -26,7 +26,7 @@ public enum PdfFontSubType /*TODO*/CIDFontType2, // } - public enum PdfFontEncoding + internal enum PdfFontEncoding { None, WinAnsiEncoding, @@ -43,8 +43,6 @@ internal class PdfFont : PdfObject private readonly int widthObjectNumber; private readonly int fontDescriptorObjectNumber; - - public PdfFont(int objectNumber, string fontName = "Helvetica", PdfFontSubType subType = PdfFontSubType.Type1, int firstChar = -1, int lastChar = -1, int widthObjectNumber = -1, int fontDescObjectNumner = -1, PdfFontEncoding encoding = PdfFontEncoding.WinAnsiEncoding) : base(objectNumber, 0) { diff --git a/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontDescriptor.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontDescriptor.cs index e283d19305..a1833327b2 100644 --- a/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontDescriptor.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontDescriptor.cs @@ -21,7 +21,7 @@ namespace EPPlus.Export.Pdf.DocumentObjects.Fonts /// Font descriptor flags /// [Flags] - public enum FontDescriptorFlags + internal enum FontDescriptorFlags { FixedPitch = 1, Serif = 2, diff --git a/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontWidths.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontWidths.cs index 7fc4ee0a64..1e515f51f1 100644 --- a/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontWidths.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontWidths.cs @@ -26,84 +26,6 @@ public PdfFontWidths(int objectNumber, List widths, int version = 0) this.widths = widths.ToArray(); } - ////can Remove this construcotr probably. - //public PdfFontWidths(int objectNumber, Dictionary w, Dictionary m, int version = 0) - // : base(objectNumber, version) - //{ - // float e = 2048; - // float d = 72f / 96f; - // float r = e * d; - // float k = 1000f; - // var l = (int)(w[m[' ']] * r * k / e); - // List exsist = new List(); - // List no = new List(); - // for (int i = 0; i <= 900; i++) - // { - // char c = (char)i; - // if (m.ContainsKey(c)) - // { - // exsist.Add(c); - // } - // else - // { - // no.Add(c); - // } - // } - // //this.widths = new int[] - // //{ - // // (int)((w[m[' ']]*r*k)/e), (int)((w[m['!']]*r*k)/e), (int)((w[m['"']]*r*k)/e), (int)((w[m['#']]*r*k)/e), (int)((w[m['$']]*r*k)/e), (int)((w[m['%']]*r*k)/e), (int)((w[m['&']]*r*k)/e), (int)((w[m['\'']]*r*k)/e), (int)((w[m['(']]*r*k)/e), (int)((w[m[')']]*r*k)/e), - // // (int)((w[m['*']]*r*k)/e), (int)((w[m['+']]*r*k)/e), (int)((w[m[',']]*r*k)/e), (int)((w[m['-']]*r*k)/e), (int)((w[m['.']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['0']]*r*k)/e), (int)((w[m['1']]*r*k)/e), (int)((w[m['2']]*r*k)/e), (int)((w[m['3']]*r*k)/e), - // // (int)((w[m['4']]*r*k)/e), (int)((w[m['5']]*r*k)/e), (int)((w[m['6']]*r*k)/e), (int)((w[m['7']]*r*k)/e), (int)((w[m['8']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m[':']]*r*k)/e), (int)((w[m[';']]*r*k)/e), (int)((w[m['<']]*r*k)/e), (int)((w[m['=']]*r*k)/e), - // // (int)((w[m['>']]*r*k)/e), (int)((w[m['?']]*r*k)/e), (int)((w[m['@']]*r*k)/e), (int)((w[m['A']]*r*k)/e), (int)((w[m['B']]*r*k)/e), (int)((w[m['C']]*r*k)/e), (int)((w[m['D']]*r*k)/e), (int)((w[m['E']]*r*k)/e), (int)((w[m['F']]*r*k)/e), (int)((w[m['G']]*r*k)/e), - // // (int)((w[m['H']]*r*k)/e), (int)((w[m['I']]*r*k)/e), (int)((w[m['J']]*r*k)/e), (int)((w[m['K']]*r*k)/e), (int)((w[m['L']]*r*k)/e), (int)((w[m['M']]*r*k)/e), (int)((w[m['N']]*r*k)/e), (int)((w[m['O']]*r*k)/e), (int)((w[m['P']]*r*k)/e), (int)((w[m['Q']]*r*k)/e), - // // (int)((w[m['R']]*r*k)/e), (int)((w[m['S']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['U']]*r*k)/e), (int)((w[m['V']]*r*k)/e), (int)((w[m['W']]*r*k)/e), (int)((w[m['X']]*r*k)/e), (int)((w[m['Y']]*r*k)/e), (int)((w[m['Z']]*r*k)/e), (int)((w[m['[']]*r*k)/e), - // // (int)((w[m['\\']]*r*k)/e), (int)((w[m[']']]*r*k)/e), (int)((w[m['^']]*r*k)/e), (int)((w[m['_']]*r*k)/e), (int)((w[m['`']]*r*k)/e), (int)((w[m['a']]*r*k)/e), (int)((w[m['b']]*r*k)/e), (int)((w[m['c']]*r*k)/e), (int)((w[m['d']]*r*k)/e), (int)((w[m['e']]*r*k)/e), - // // (int)((w[m['f']]*r*k)/e), (int)((w[m['g']]*r*k)/e), (int)((w[m['h']]*r*k)/e), (int)((w[m['i']]*r*k)/e), (int)((w[m['j']]*r*k)/e), (int)((w[m['k']]*r*k)/e), (int)((w[m['l']]*r*k)/e), (int)((w[m['m']]*r*k)/e), (int)((w[m['n']]*r*k)/e), (int)((w[m['o']]*r*k)/e), - // // (int)((w[m['p']]*r*k)/e), (int)((w[m['q']]*r*k)/e), (int)((w[m['r']]*r*k)/e), (int)((w[m['s']]*r*k)/e), (int)((w[m['t']]*r*k)/e), (int)((w[m['u']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['w']]*r*k)/e), (int)((w[m['x']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), - // // (int)((w[m['z']]*r*k)/e), (int)((w[m['{']]*r*k)/e), (int)((w[m['|']]*r*k)/e), (int)((w[m['}']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['€']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['‚']]*r*k)/e), (int)((w[m['ƒ']]*r*k)/e), - // // (int)((w[m[' ']]*r*k)/e), (int)((w[m['„']]*r*k)/e), (int)((w[m['…']]*r*k)/e), (int)((w[m['†']]*r*k)/e), (int)((w[m['‡']]*r*k)/e), (int)((w[m['ˆ']]*r*k)/e), (int)((w[m['‰']]*r*k)/e), (int)((w[m['Š']]*r*k)/e), (int)((w[m['‹']]*r*k)/e), (int)((w[m['Œ']]*r*k)/e), - // // (int)((w[m[' ']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['Ž']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['‘']]*r*k)/e), (int)((w[m['’']]*r*k)/e), (int)((w[m['“']]*r*k)/e), (int)((w[m['”']]*r*k)/e), (int)((w[m['•']]*r*k)/e), - // // (int)((w[m['–']]*r*k)/e), (int)((w[m['—']]*r*k)/e), (int)((w[m['˜']]*r*k)/e), (int)((w[m['™']]*r*k)/e), (int)((w[m['š']]*r*k)/e), (int)((w[m['›']]*r*k)/e), (int)((w[m['œ']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['ž']]*r*k)/e), (int)((w[m['Ÿ']]*r*k)/e), - // // (int)((w[m['¡']]*r*k)/e), (int)((w[m['¢']]*r*k)/e), (int)((w[m['£']]*r*k)/e), (int)((w[m['¤']]*r*k)/e), (int)((w[m['¥']]*r*k)/e), (int)((w[m['¦']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['¨']]*r*k)/e), (int)((w[m['©']]*r*k)/e), (int)((w[m['ª']]*r*k)/e), - // // (int)((w[m['«']]*r*k)/e), (int)((w[m['¬']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['¯']]*r*k)/e), (int)((w[m['°']]*r*k)/e), (int)((w[m['±']]*r*k)/e), (int)((w[m['²']]*r*k)/e), (int)((w[m['³']]*r*k)/e), (int)((w[m['´']]*r*k)/e), - // // (int)((w[m['µ']]*r*k)/e), (int)((w[m['¶']]*r*k)/e), (int)((w[m['·']]*r*k)/e), (int)((w[m['¸']]*r*k)/e), (int)((w[m['¹']]*r*k)/e), (int)((w[m['º']]*r*k)/e), (int)((w[m['»']]*r*k)/e), (int)((w[m['¼']]*r*k)/e), (int)((w[m['½']]*r*k)/e), (int)((w[m['¾']]*r*k)/e), - // // (int)((w[m['¿']]*r*k)/e), (int)((w[m['À']]*r*k)/e), (int)((w[m['Á']]*r*k)/e), (int)((w[m['Â']]*r*k)/e), (int)((w[m['Ã']]*r*k)/e), (int)((w[m['Ä']]*r*k)/e), (int)((w[m['Å']]*r*k)/e), (int)((w[m['Æ']]*r*k)/e), (int)((w[m['Ç']]*r*k)/e), (int)((w[m['È']]*r*k)/e), - // // (int)((w[m['É']]*r*k)/e), (int)((w[m['Ê']]*r*k)/e), (int)((w[m['Ë']]*r*k)/e), (int)((w[m['Ì']]*r*k)/e), (int)((w[m['Í']]*r*k)/e), (int)((w[m['Î']]*r*k)/e), (int)((w[m['Ï']]*r*k)/e), (int)((w[m['Ð']]*r*k)/e), (int)((w[m['Ñ']]*r*k)/e), (int)((w[m['Ò']]*r*k)/e), - // // (int)((w[m['Ó']]*r*k)/e), (int)((w[m['Ô']]*r*k)/e), (int)((w[m['Õ']]*r*k)/e), (int)((w[m['Ö']]*r*k)/e), (int)((w[m['×']]*r*k)/e), (int)((w[m['Ø']]*r*k)/e), (int)((w[m['Ù']]*r*k)/e), (int)((w[m['Ú']]*r*k)/e), (int)((w[m['Û']]*r*k)/e), (int)((w[m['Ü']]*r*k)/e), - // // (int)((w[m['Ý']]*r*k)/e), (int)((w[m['Þ']]*r*k)/e), (int)((w[m['ß']]*r*k)/e), (int)((w[m['à']]*r*k)/e), (int)((w[m['á']]*r*k)/e), (int)((w[m['â']]*r*k)/e), (int)((w[m['ã']]*r*k)/e), (int)((w[m['ä']]*r*k)/e), (int)((w[m['å']]*r*k)/e), (int)((w[m['æ']]*r*k)/e), - // // (int)((w[m['ç']]*r*k)/e), (int)((w[m['è']]*r*k)/e), (int)((w[m['é']]*r*k)/e), (int)((w[m['ê']]*r*k)/e), (int)((w[m['ë']]*r*k)/e), (int)((w[m['ì']]*r*k)/e), (int)((w[m['í']]*r*k)/e), (int)((w[m['î']]*r*k)/e), (int)((w[m['ï']]*r*k)/e), (int)((w[m['ð']]*r*k)/e), - // // (int)((w[m['ñ']]*r*k)/e), (int)((w[m['ò']]*r*k)/e), (int)((w[m['ó']]*r*k)/e), (int)((w[m['ô']]*r*k)/e), (int)((w[m['õ']]*r*k)/e), (int)((w[m['ö']]*r*k)/e), (int)((w[m['÷']]*r*k)/e), (int)((w[m['ø']]*r*k)/e), (int)((w[m['ù']]*r*k)/e), (int)((w[m['ú']]*r*k)/e), - // // (int)((w[m['û']]*r*k)/e), (int)((w[m['ü']]*r*k)/e), (int)((w[m[' ']]*r*k)/e), (int)((w[m['þ']]*r*k)/e), (int)((w[m['ÿ']]*r*k)/e) - // //}; - // widths = new int[] - // { - // (int)(w[m[' ']]*r*k/e), (int)(w[m['!']]*r*k/e), (int)(w[m['"']]*r*k/e), (int)(w[m['#']]*r*k/e), (int)(w[m['$']]*r*k/e), (int)(w[m['%']]*r*k/e), (int)(w[m['&']]*r*k/e), (int)(w[m['\'']]*r*k/e), (int)(w[m['(']]*r*k/e), (int)(w[m[')']]*r*k/e), - // (int)(w[m['*']]*r*k/e), (int)(w[m['+']]*r*k/e), (int)(w[m[',']]*r*k/e), (int)(w[m['-']]*r*k/e), (int)(w[m['.']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m['0']]*r*k/e), (int)(w[m['1']]*r*k/e), (int)(w[m['2']]*r*k/e), (int)(w[m['3']]*r*k/e), - // (int)(w[m['4']]*r*k/e), (int)(w[m['5']]*r*k/e), (int)(w[m['6']]*r*k/e), (int)(w[m['7']]*r*k/e), (int)(w[m['8']]*r*k/e), (int)(w[m['8']]*r*k/e), (int)(w[m[':']]*r*k/e), (int)(w[m[';']]*r*k/e), (int)(w[m['<']]*r*k/e), (int)(w[m['=']]*r*k/e), - // (int)(w[m['>']]*r*k/e), (int)(w[m['?']]*r*k/e), (int)(w[m['@']]*r*k/e), (int)(w[m['A']]*r*k/e), (int)(w[m['B']]*r*k/e), (int)(w[m['C']]*r*k/e), (int)(w[m['D']]*r*k/e), (int)(w[m['E']]*r*k/e), (int)(w[m['F']]*r*k/e), (int)(w[m['G']]*r*k/e), - // (int)(w[m['H']]*r*k/e), (int)(w[m['I']]*r*k/e), (int)(w[m['J']]*r*k/e), (int)(w[m['K']]*r*k/e), (int)(w[m['L']]*r*k/e), (int)(w[m['M']]*r*k/e), (int)(w[m['N']]*r*k/e), (int)(w[m['O']]*r*k/e), (int)(w[m['P']]*r*k/e), (int)(w[m['Q']]*r*k/e), - // (int)(w[m['R']]*r*k/e), (int)(w[m['S']]*r*k/e), (int)(w[m['W']]*r*k/e), (int)(w[m['U']]*r*k/e), (int)(w[m['V']]*r*k/e), (int)(w[m['W']]*r*k/e), (int)(w[m['X']]*r*k/e), (int)(w[m['Y']]*r*k/e), (int)(w[m['Z']]*r*k/e), (int)(w[m['[']]*r*k/e), - // (int)(w[m['\\']]*r*k/e), (int)(w[m[']']]*r*k/e), (int)(w[m['^']]*r*k/e), (int)(w[m['_']]*r*k/e), (int)(w[m['`']]*r*k/e), (int)(w[m['a']]*r*k/e), (int)(w[m['b']]*r*k/e), (int)(w[m['c']]*r*k/e), (int)(w[m['d']]*r*k/e), (int)(w[m['e']]*r*k/e), - // (int)(w[m['f']]*r*k/e), (int)(w[m['g']]*r*k/e), (int)(w[m['h']]*r*k/e), (int)(w[m['i']]*r*k/e), (int)(w[m['j']]*r*k/e), (int)(w[m['k']]*r*k/e), (int)(w[m['l']]*r*k/e), (int)(w[m['m']]*r*k/e), (int)(w[m['n']]*r*k/e), (int)(w[m['o']]*r*k/e), - // (int)(w[m['p']]*r*k/e), (int)(w[m['q']]*r*k/e), (int)(w[m['r']]*r*k/e), (int)(w[m['s']]*r*k/e), (int)(w[m['t']]*r*k/e), (int)(w[m['u']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m['w']]*r*k/e), (int)(w[m['x']]*r*k/e), (int)(w[m[' ']]*r*k/e), - // (int)(w[m['z']]*r*k/e), (int)(w[m['{']]*r*k/e), (int)(w[m['|']]*r*k/e), (int)(w[m['}']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), - // (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), - // (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), - // (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), - // (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), - // (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), - // (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), - // (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), - // (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), - // (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), - // (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), - // (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), - // (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), - // (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e), (int)(w[m[' ']]*r*k/e) - // }; - - //} - internal override string RenderDictionary() { var widthsStr = string.Join(" ", widths.Select(w => w.ToString()).ToArray()); diff --git a/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfToUnicodeCMap.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfToUnicodeCMap.cs index be397dbc8d..b317005fa5 100644 --- a/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfToUnicodeCMap.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfToUnicodeCMap.cs @@ -45,13 +45,11 @@ internal override string RenderDictionary() { var cmapContent = GenerateCMapContent(); var length = Encoding.UTF8.GetByteCount(cmapContent); - var sb = new StringBuilder(); sb.AppendLine(string.Format("<< /Length {0} >>", length)); sb.AppendLine("stream"); sb.Append(cmapContent); sb.Append("\nendstream"); - return sb.ToString(); } @@ -59,47 +57,39 @@ internal override void RenderDictionary(BinaryWriter bw) { var cmapContent = GenerateCMapContent(); var cmapBytes = Encoding.ASCII.GetBytes(cmapContent); - var sb = new StringBuilder(); sb.AppendFormat(($"<< /Length {cmapBytes.Length} >>\n")); sb.Append("stream\n"); sb.Append(cmapContent); sb.Append("\nendstream"); - WriteAscii(bw, sb.ToString()); } private string GenerateCMapContent() { var sb = new StringBuilder(); - // CMap header sb.Append("/CIDInit /ProcSet findresource begin\n"); sb.Append("12 dict begin\n"); sb.Append("begincmap\n"); - // CIDSystemInfo - required for ToUnicode CMaps sb.Append("/CIDSystemInfo\n"); sb.Append("<< /Registry (Adobe)\n"); sb.Append(" /Ordering (UCS)\n"); sb.Append(" /Supplement 0\n"); sb.Append(">> def\n"); - // CMap name and type sb.Append("/CMapName /Adobe-Identity-UCS def\n"); sb.Append("/CMapType 2 def\n"); - // Define codespace range sb.Append("1 begincodespacerange\n"); sb.AppendFormat($"<{FormatCode(CodeSpaceMin)}> <{FormatCode(CodeSpaceMax)}>\n"); sb.Append("endcodespacerange\n"); - // Generate character mappings if (CharacterMappings.Count > 0) { GenerateCharacterMappings(sb); } - // CMap footer sb.Append("endcmap\n"); sb.Append("CMapName currentdict /CMap defineresource pop\n"); @@ -114,12 +104,10 @@ private void GenerateCharacterMappings(StringBuilder sb) var sortedMappings = CharacterMappings.OrderBy(kvp => kvp.Key).ToList(); var ranges = new List(); var individualMappings = new List(); - // Try to identify consecutive ranges for (int i = 0; i < sortedMappings.Count; i++) { var current = sortedMappings[i]; - // Try to parse as simple Unicode value for range detection int unicodeValue; if (TryParseSimpleUnicode(current.Value, out unicodeValue)) @@ -127,7 +115,6 @@ private void GenerateCharacterMappings(StringBuilder sb) int rangeStart = current.Key; int rangeEnd = current.Key; int unicodeStart = unicodeValue; - // Check if we can extend this into a range while (i + 1 < sortedMappings.Count) { @@ -145,7 +132,6 @@ private void GenerateCharacterMappings(StringBuilder sb) break; } } - // If we found a range of 3 or more, use beginbfrange if (rangeEnd - rangeStart >= 2) { @@ -167,7 +153,6 @@ private void GenerateCharacterMappings(StringBuilder sb) individualMappings.Add(new CharacterMapping(current.Key, current.Value)); } } - // Output ranges if (ranges.Count > 0) { @@ -178,7 +163,6 @@ private void GenerateCharacterMappings(StringBuilder sb) } sb.Append("endbfrange\n"); } - // Output individual character mappings if (individualMappings.Count > 0) { @@ -201,13 +185,11 @@ private void GenerateCharacterMappings(StringBuilder sb) private bool TryParseSimpleUnicode(string hexString, out int unicodeValue) { unicodeValue = 0; - // Check if it's a simple 4-digit hex Unicode value (e.g., "0041" for 'A') if (hexString.Length == 4 && int.TryParse(hexString, System.Globalization.NumberStyles.HexNumber, null, out unicodeValue)) { return true; } - return false; } diff --git a/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfType0FontDict.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfType0FontDict.cs index 2fc470e056..010a673461 100644 --- a/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfType0FontDict.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfType0FontDict.cs @@ -21,7 +21,6 @@ internal class PdfType0FontDict : PdfObject private readonly string BaseFont; private readonly string Encoding; private readonly int DescendantFontsObjectNumbers; - private readonly int ToUnicodeObjectNumber; public PdfType0FontDict(int objectNumber, string basefont, string encoding, int descendantFontsObjectNumbers, int toUnicodeObjectNumber = -1, int version = 0) diff --git a/src/EPPlus.Export.Pdf/DocumentObjects/Patterns/PdfPatternFill.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Patterns/PdfPatternFill.cs index 5511bf2db0..959bf16971 100644 --- a/src/EPPlus.Export.Pdf/DocumentObjects/Patterns/PdfPatternFill.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Patterns/PdfPatternFill.cs @@ -19,7 +19,6 @@ internal abstract class PdfPatternFill { public Color Background; public Color Foreground; - private readonly List commands = new List(); protected PdfPatternFill(Color foreground, Color background) { diff --git a/src/EPPlus.Export.Pdf/DocumentObjects/Patterns/PdfPatternMaskFill.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Patterns/PdfPatternMaskFill.cs index d7b3445b4a..504598c138 100644 --- a/src/EPPlus.Export.Pdf/DocumentObjects/Patterns/PdfPatternMaskFill.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Patterns/PdfPatternMaskFill.cs @@ -45,15 +45,13 @@ public override string CreatePatternResource() { const int size = 8; var sb = new StringBuilder(); - - // Fill the whole tile with the background color. + //Fill the whole tile with the background color. sb.Append(Background.ToFillCommand()); sb.Append("\n0 0 "); sb.Append(size.ToString()); sb.Append(" "); sb.Append(size.ToString()); sb.Append(" re\nf\n"); - // Draw foreground rectangles. mask value 0 == foreground. sb.Append(Foreground.ToFillCommand()); sb.Append("\n"); diff --git a/src/EPPlus.Export.Pdf/DocumentObjects/PdfBorderRenderer.cs b/src/EPPlus.Export.Pdf/DocumentObjects/PdfBorderRenderer.cs index 9f5b036db3..489da65399 100644 --- a/src/EPPlus.Export.Pdf/DocumentObjects/PdfBorderRenderer.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/PdfBorderRenderer.cs @@ -24,23 +24,17 @@ internal class PdfBorderRenderer private readonly PdfCellBorderData Right; private readonly PdfCellBorderData DiagonalUp; private readonly PdfCellBorderData DiagonalDown; - private readonly double X; private readonly double Y; private readonly string Name; private readonly bool IsMerged; private readonly MergedCellDrawInfo info; - private readonly MergedCellCorners corners; private readonly double Width; private readonly double Height; - private readonly double MergedDiagnoalWidth; - private readonly double MergedDiagnoalHeight; - public PdfBorderRenderer(PdfCellBorderLayout cell) { IsMerged = cell.IsMerged; - corners = cell.Corners; info = cell.MergedCellInfo; X = cell.LocalPosition.X; Y = cell.LocalPosition.Y; diff --git a/src/EPPlus.Export.Pdf/DocumentObjects/PdfContentStream.cs b/src/EPPlus.Export.Pdf/DocumentObjects/PdfContentStream.cs index ebdf8876b7..7039d470ad 100644 --- a/src/EPPlus.Export.Pdf/DocumentObjects/PdfContentStream.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/PdfContentStream.cs @@ -12,7 +12,6 @@ Date Author Change *************************************************************************************************/ using EPPlus.Graphics; using EPPlus.Graphics.Geometry; -using OfficeOpenXml.Interfaces.Fonts; using System; using System.Collections.Generic; using System.Drawing; @@ -74,7 +73,6 @@ public void AddCellLayout(PdfCellLayout cell, string label) else if (cell.CellFillData.PatternStyle != ExcelFillStyle.None) { commands.Add($"% Pattern Start: {cell.Name}"); - // Draw the solid cell background only when one is set. The pattern // tile already fills itself with its own background color, so the // pattern must be rendered regardless of whether the cell has a @@ -106,30 +104,6 @@ public void AddBorderLayout(PdfCellBorderLayout cell) borderRenderer.RenderBorder(this); } - - //Get font label //need to update this one too for same reasons as AddFontData - internal PdfFontResource GetFontResource(PdfDictionaries Dictionaries, PdfPageSettings PageSettings, string fontName, FontSubFamily subFamily, double fontSize) - { - var lookUpName = fontName + " " + subFamily.ToString(); - if (!Dictionaries.Fonts.ContainsKey(lookUpName)) - { - int label = 1; - if (Dictionaries.Fonts.Count > 0) - { - label = Dictionaries.Fonts.Last().Value.labelNumber + 1; - } - PdfFontResource fr = new PdfFontResource(fontName, subFamily, label, PageSettings); - if (fontName != "Courier New") - { - //Document.Add(fr.GetFontDescriptorObject(Document.Count + 1)); - //Document.Add(fr.GetWidthsObject(Document.Count + 1)); - } - //Document.Add(fr.GetFontObject(Document.Count + 1)); - Dictionaries.Fonts.Add(fontName, fr); - } - return Dictionaries.Fonts[lookUpName]; - } - public void AddText(PdfCellContentLayout cell, Vector2 position, double textRotation, PdfDictionaries dictionaries, PdfPageSettings pageSettings) { double advanceY = 0d; @@ -147,14 +121,11 @@ public void AddText(PdfCellContentLayout cell, Vector2 position, double textRota case ExcelHorizontalAlignment.Center: lineOffsetX = (line0Width - line.Width) / 2d; break; - // Left / General / Fill: no adjustment needed. } double advanceX = 0; for (int i = 0; i < line.LineFragments.Count; i++) { - //var textFormat = cell.TextFormats[i]; var textFormat = line.LineFragments[i]; - // find which ShapedText owns this fragment int shapedTextIndex = 0; int shapedTextCharStart = 0; @@ -172,16 +143,6 @@ public void AddText(PdfCellContentLayout cell, Vector2 position, double textRota // find the starting glyph within that ShapedText using StartRtIdx int glyphStart = 0; - //int rtCharCount = 0; - //for (int g = 0; g < shapedText.ShapedText.Glyphs.Length; g++) - //{ - // if (rtCharCount >= textFormat.StartRtIdx) - // { - // glyphStart = g; - // break; - // } - // rtCharCount += shapedText.ShapedText.Glyphs[g].CharCount; - //} int charOffsetInShapedText = textFormat.StartFullTextIdx - shapedTextCharStart; int rtCharCount = 0; for (int g = 0; g < shapedText.ShapedText.Glyphs.Length; g++) @@ -196,23 +157,19 @@ public void AddText(PdfCellContentLayout cell, Vector2 position, double textRota var originalFragment = ((TextFragment)textFormat.OriginalTextFragment); while (glyphStart < shapedText.ShapedText.Glyphs.Length && shapedText.ShapedText.Glyphs[glyphStart].GlyphId == 0) { - //Console.WriteLine($"[AddText] GlyphId==0 in shapedText {shapedTextIndex} of {cell.ShapedTexts.Count}, advancing. Cell.Text='{cell.cell?.Text}' textFormat.Text='{textFormat.Text}'"); shapedTextIndex++; if (shapedTextIndex >= cell.ShapedTexts.Count) { - //Console.WriteLine($"[AddText] OUT OF RANGE! Original text: '{originalFragment.Text}'"); break; // temp safety } shapedText = cell.ShapedTexts[shapedTextIndex]; glyphStart = 0; } - var richInfo = originalFragment.RichTextOptions; - var textLength = shapedText.ShapedText.GetWidthInPoints((float)richInfo.Size); var color = richInfo.FontColor; - var fontResource = GetFontResource(dictionaries, pageSettings, richInfo.Family, richInfo.SubFamily, richInfo.Size); + var fontResource = dictionaries.GetFont(richInfo.Family, richInfo.SubFamily); double size = richInfo.Size; double scale = textFormat.OriginalTextFragment.RichTextOptions.Size / fontResource.fontData.HeadTable.UnitsPerEm; Matrix3x3 textMatrix = new Matrix3x3(System.Math.Cos(rotation), System.Math.Sin(rotation), -System.Math.Sin(rotation), System.Math.Cos(rotation), position.X + lineOffsetX, position.Y + advanceY); @@ -268,7 +225,6 @@ public void AddText(PdfCellContentLayout cell, Vector2 position, double textRota ? shapedText.FontIdMap[currentFontId] : fontResource.Label; commands.Add($"/{currentFontLabel} {size.ToPdfString()} Tf"); - int fragmentCharCount = textFormat.Text.Length; int charsRendered = 0; var sb = new StringBuilder(); @@ -278,7 +234,6 @@ public void AddText(PdfCellContentLayout cell, Vector2 position, double textRota if (charsRendered >= fragmentCharCount) break; var glyph = shapedText.ShapedText.Glyphs[j]; - if (glyph.FontId != currentFontId) { // Close TJ array, switch font, open new TJ array @@ -287,16 +242,13 @@ public void AddText(PdfCellContentLayout cell, Vector2 position, double textRota sb.Append("["); currentFontId = glyph.FontId; } - sb.Append($"<{glyph.GlyphId:X4}>"); int kerning = glyph.XAdvance - glyph.BaseAdvance; - if (kerning != 0) { double adjustment = -(kerning * 1000.0 / 1000); sb.Append($" {adjustment.ToPdfStringF0()}"); } - if (j < shapedText.ShapedText.Glyphs.Length - 1) { sb.Append(" "); @@ -311,163 +263,6 @@ public void AddText(PdfCellContentLayout cell, Vector2 position, double textRota } } - //public void AddText(Vector2 position, PdfCellLines lines, PdfCellAlignmentData alignment, PdfDictionaries dictionaries, PdfPageSettings pageSettings) - //{ - // double rot = alignment.TextRotation * System.Math.PI / 180.0; - // bool isVertical = alignment.IsVertical; - // Matrix3x3 textMatrix = new Matrix3x3(System.Math.Cos(rot), System.Math.Sin(rot), -System.Math.Sin(rot), System.Math.Cos(rot), position.X, position.Y); - // for (int i = 0; i < lines.Lines.Count; i++) - // { - // var line = lines.Lines[i]; - // Matrix3x3 textRunMatrix = textMatrix; - // Matrix3x3 modifierMatrix = Matrix3x3.Identity; - // bool useModifiedMatrix = false; - // commands.Add("BT"); - // commands.Add($"{textMatrix.A.ToPdfString()} {textMatrix.B.ToPdfString()} {textMatrix.C.ToPdfString()} {textMatrix.D.ToPdfString()} {textMatrix.E.ToPdfString()} {textMatrix.F.ToPdfString()} Tm"); - // PdfTextFormat lastCharacter = line.Words[0].Characters[0]; - // PdfTextFormat currentStyle = line.Words[0].Characters[0]; - // string textRun = string.Empty; - // double textAdvance = 0d; - // double textVAdvance = 0d; - // int wordIndex = 0; - // for (int j = 0; j < line.Words.Count; j++) - // { - // var words = line.Words[j]; - - // for (int k = wordIndex; k < words.Characters.Count; k++, wordIndex++) - // { - // if (!currentStyle.Equals(words.Characters[k])) - // { - // break; - // } - // textRun += words.Characters[k].Text; - // textAdvance += words.Characters[k].TextLength; - // textVAdvance = words.Characters[k].LineHeight; - // if (isVertical) - // { - // wordIndex++; - // break; - // } - // } - - // if (wordIndex == words.Characters.Count && j < line.Words.Count - 1) - // { - // wordIndex = 0; - // continue; - // } - - // var font = GetFontResource(dictionaries, pageSettings, currentStyle.FullFontName, currentStyle.SubFamily, currentStyle.FontSize); - // double size = currentStyle.FontSize; - // double scale = currentStyle.FontSize / font.fontData.HeadTable.UnitsPerEm; - // if (currentStyle.Bold) - // { - // commands.Add("0.25 w"); - // commands.Add("2 Tr"); - // commands.Add(currentStyle.FontColor.ToStrokeCommand()); - // } - // else - // { - // commands.Add("0 Tr"); - // } - // if (currentStyle.Italic) - // { - // var ia = font.fontData.PostTable.italicAngle.FloatValue; - // if (ia <= 0) ia = 12f * (float)System.Math.PI / 180.0f; - // modifierMatrix.C = System.Math.Tan(ia); - // modifierMatrix = modifierMatrix * textRunMatrix; - // useModifiedMatrix = true; - // } - // if (currentStyle.SuperScript) - // { - // var supOffX = font.fontData.Os2Table.ySuperscriptXOffset * scale; - // var supOffY = font.fontData.Os2Table.ySuperscriptYOffset * scale; - // var supSizeX = font.fontData.Os2Table.ySuperscriptXSize * scale; - // var supSizeY = font.fontData.Os2Table.ySuperscriptYSize * scale; - // modifierMatrix.E = textMatrix.E + supOffX; - // modifierMatrix.F = textMatrix.F + supOffY; - // size = supSizeY; - // useModifiedMatrix = true; - // } - // else if (currentStyle.SubScript) - // { - // var supOffX = font.fontData.Os2Table.ySubscriptXOffset * scale; - // var supOffY = font.fontData.Os2Table.ySubscriptYOffset * scale; - // var supSizeX = font.fontData.Os2Table.ySubscriptXSize * scale; - // var supSizeY = font.fontData.Os2Table.ySubscriptYSize * scale; - // modifierMatrix.E = textMatrix.E + supOffX; - // modifierMatrix.F = textMatrix.F + supOffY; - // size = supSizeY; - // useModifiedMatrix = true; - // } - // if (currentStyle.Underline) - // { - // var underlinePos = font.fontData.PostTable.underlinePosition * scale; - // var underlineWidth = font.fontData.PostTable.underlineThickness * scale; - // var start = textRunMatrix.Transform(new Vector2(0, underlinePos)); - // var end = textRunMatrix.Transform(new Vector2(textAdvance, underlinePos)); - // commands.Add($"{underlineWidth.ToPdfString()} w"); - // commands.Add($"{start.X.ToPdfString()} {start.Y.ToPdfString()} m"); - // commands.Add($"{end.X.ToPdfString()} {end.Y.ToPdfString()} l"); - // commands.Add($"S"); - // } - // if (currentStyle.Strike) - // { - // var strikePos = font.fontData.Os2Table.yStrikeoutPosition * scale; - // var strikeWidth = font.fontData.Os2Table.yStrikeoutSize * scale; - // var start = textRunMatrix.Transform(new Vector2(0, strikePos)); - // var end = textRunMatrix.Transform(new Vector2(textAdvance, strikePos)); - // commands.Add($"{strikeWidth.ToPdfString()} w"); - // commands.Add($"{start.X.ToPdfString()} {start.Y.ToPdfString()} m"); - // commands.Add($"{end.X.ToPdfString()} {end.Y.ToPdfString()} l"); - // commands.Add($"S"); - // } - // if (useModifiedMatrix) - // { - // commands.Add($"{modifierMatrix.A.ToPdfString()} {modifierMatrix.B.ToPdfString()} {modifierMatrix.C.ToPdfString()} {modifierMatrix.D.ToPdfString()} {modifierMatrix.E.ToPdfString()} {modifierMatrix.F.ToPdfString()} Tm"); - // modifierMatrix = Matrix3x3.Identity; - // } - // else if ((isVertical)) - // { - // commands.Add($"{textRunMatrix.A.ToPdfString()} {textRunMatrix.B.ToPdfString()} {textRunMatrix.C.ToPdfString()} {textRunMatrix.D.ToPdfString()} {textRunMatrix.E.ToPdfString()} {textRunMatrix.F.ToPdfString()} Tm"); - // } - // commands.Add($"/{font.Label} {size.ToPdfString()} Tf"); - // commands.Add(currentStyle.FontColor.ToFillCommand()); - // commands.Add($"({FixEscapeCharacters(textRun)}) Tj"); - // if (isVertical) - // { - // textRunMatrix = textRunMatrix * Matrix3x3.Translation(0, -textVAdvance); - // } - // else - // { - // textRunMatrix = textRunMatrix * Matrix3x3.Translation(textAdvance, 0); - // } - - // if (useModifiedMatrix) commands.Add($"{textRunMatrix.A.ToPdfString()} {textRunMatrix.B.ToPdfString()} {textRunMatrix.C.ToPdfString()} {textRunMatrix.D.ToPdfString()} {textRunMatrix.E.ToPdfString()} {textRunMatrix.F.ToPdfString()} Tm"); - // useModifiedMatrix = false; - // textRun = string.Empty; - // textAdvance = 0; - - // if (wordIndex < words.Characters.Count) - // { - // currentStyle = words.Characters[wordIndex]; - // j--; - // } - // } - // if (i + 1 < lines.Lines.Count) - // { - // if (isVertical) - // { - // textMatrix = textRunMatrix * Matrix3x3.Translation(line.TextLength, line.TextHeight + lines.Lines[i + 1].Offset); - // } - // else - // { - // textMatrix = textRunMatrix * Matrix3x3.Translation(-line.TextLength + lines.Lines[i + 1].Offset, -lines.Lines[i + 1].LineHeight); - // } - // } - // commands.Add("ET"); - // } - //} - public void AddCellContentLayout(PdfCellContentLayout cell, PdfDictionaries dictionaries, PdfPageSettings pageSettings) { commands.Add($"% Content Start: {cell.Name}"); @@ -483,15 +278,6 @@ private void AddClipping(PdfCellContentLayout cell) commands.Add($"{cell.Clipping.X.ToPdfString()} {cell.Clipping.Y.ToPdfString()} {cell.Clipping.Width.ToPdfString()} {cell.Clipping.Height.ToPdfString()} re W n"); } - //public void AddCellContentLayout(PdfHeaderFooterLayout cell, PdfDictionaries dictionaries, PdfPageSettings pageSettings) - //{ - // commands.Add($"% HeaderFooter Start: {cell.Name}"); - // commands.Add("q"); - // //AddText(cell, cell.LocalPosition, 0, dictionaries, pageSettings); - // commands.Add("Q"); - // commands.Add($"% HeaderFooter End: {cell.Name}"); - //} - public void AddInnerGridLines(Transform pageLayout) { if (pageLayout is not PdfPageLayout pl) return; @@ -575,28 +361,6 @@ public void AddOuterGridBorder(Transform pageLayout) commands.Add($"% Gridlines Border End"); } - //public void AddMarginClipping(PdfPageLayout pageLayout) - //{ - // if (pageLayout is not PdfPageLayout pl) return; - // if (pageLayout.isCommentsPage) return; - - // commands.Add($"% Margin Clip Start"); - // double y = pageLayout.ContentTop; - // double width = 0d; - // foreach (var line in pl.BorderLines) - // { - // width = System.Math.Max(width, System.Math.Max(line.X1, line.X2)); - // y = System.Math.Min(y, System.Math.Min(line.Y1, line.Y2)); - // } - // var heightAdjust = y - pageLayout.ContentBottom; - - // var x = (pageLayout.ContentLeft) - (GridLine.Width * 4 ); - // width = width - pageLayout.ContentLeft + (GridLine.Width * 8); - // var height = pageLayout.ContentHeight - heightAdjust + (GridLine.Width * 4); - - // commands.Add($"{x.ToPdfString()} {y.ToPdfString()} {width.ToPdfString()} {height.ToPdfString()} re W n"); - //} - public void AddMarginClipping(PdfPageLayout pageLayout) { if (pageLayout is not PdfPageLayout pl) return; @@ -624,8 +388,6 @@ public void AddMarginClipping(PdfPageLayout pageLayout) commands.Add($"{x.ToPdfString()} {y.ToPdfString()} {width.ToPdfString()} {height.ToPdfString()} re W n"); } - - internal override string RenderDictionary() { var content = string.Join("\n", commands.ToArray()) + "\n"; @@ -639,10 +401,5 @@ internal override void RenderDictionary(BinaryWriter bw) var bytes = Encoding.ASCII.GetBytes(content); WriteAscii(bw, $"<< /Length {bytes.Length} >>\nstream\n{content}\nendstream"); } - - private string FixEscapeCharacters(string text) - { - return text.Replace(@"\", @"\\").Replace(@"(", @"\(").Replace(@")", @"\)"); - } } } \ No newline at end of file diff --git a/src/EPPlus.Export.Pdf/DocumentObjects/PdfInfoObject.cs b/src/EPPlus.Export.Pdf/DocumentObjects/PdfInfoObject.cs index ad56f5081a..4937c84fb2 100644 --- a/src/EPPlus.Export.Pdf/DocumentObjects/PdfInfoObject.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/PdfInfoObject.cs @@ -32,7 +32,6 @@ internal override string RenderDictionary() string sign = offset < TimeSpan.Zero ? "-" : "+"; offset = offset.Duration(); string pdfDate = string.Format("D:{0:yyyyMMddHHmmss}{1}{2:00}'{3:00}'", now, sign, offset.Hours, offset.Minutes); - var sb = new StringBuilder(); sb.AppendFormat($"<< /Title ({Title})\n" + $" /Author (EPPlus)\n" + diff --git a/src/EPPlus.Export.Pdf/DocumentObjects/PdfObject.cs b/src/EPPlus.Export.Pdf/DocumentObjects/PdfObject.cs index bb3c361503..b8213d3c05 100644 --- a/src/EPPlus.Export.Pdf/DocumentObjects/PdfObject.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/PdfObject.cs @@ -35,15 +35,6 @@ public virtual string ToPdfString() return sb.ToString(); } - public virtual byte[] ToPdfBytes() - { - var sb = new StringBuilder(); - sb.AppendFormat("{0} {1} obj\n", objectNumber, version); - sb.Append(RenderDictionary()); - sb.Append("\nendobj\n"); - return Encoding.ASCII.GetBytes(sb.ToString()); - } - public virtual void ToPdfBytes(BinaryWriter bw) { var sb = new StringBuilder(); diff --git a/src/EPPlus.Export.Pdf/DocumentObjects/Shadings/PdfShading.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Shadings/PdfShading.cs index 12daf26cf8..ba3754247a 100644 --- a/src/EPPlus.Export.Pdf/DocumentObjects/Shadings/PdfShading.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Shadings/PdfShading.cs @@ -16,7 +16,7 @@ Date Author Change namespace EPPlus.Export.Pdf.DocumentObjects.Shadings { - public enum DeviceColorSpace + internal enum DeviceColorSpace { DeviceGray, DeviceRGB, diff --git a/src/EPPlus.Export.Pdf/ExcelPdf.cs b/src/EPPlus.Export.Pdf/ExcelPdf.cs index e007f387b8..d9d1834d56 100644 --- a/src/EPPlus.Export.Pdf/ExcelPdf.cs +++ b/src/EPPlus.Export.Pdf/ExcelPdf.cs @@ -31,7 +31,6 @@ internal class ExcelPdf private PdfPageSettings _pageSettings; private PdfDictionaries _dictionaries; private List _document = new List(); - private string _debugString; internal static string Header @@ -219,17 +218,6 @@ private void AddContent(Transform pageLayout, PdfPage page) contentStream.AddCommand($"% {pageLayout.Name} end"); } - //Add Header Footer - //private void AddHeaderFooter(PdfContentStream contentStream, Transform pageLayout, PdfPage page) - //{ - // var headerFooter = pageLayout.ChildObjects.Where(t => t is PdfHeaderFooterLayout); - // foreach (var hf in headerFooter) - // { - // var headerFooterLayout = hf as PdfHeaderFooterLayout; - // contentStream.AddCellContentLayout(headerFooterLayout, _dictionaries, _pageSettings); - // } - //} - //Add Info private PdfInfoObject AddInfoObject(string workBookName = "") { @@ -242,7 +230,6 @@ internal void CreatePdf(PdfPageSettings pageSettings, PdfDictionaries dictionari { _pageSettings = pageSettings; _dictionaries = dictionaries; - var catalog = AddCatalog(2); //Create Pages var pagesLayout = layout.ChildObjects[0]; @@ -300,141 +287,5 @@ internal void CreatePdf(PdfPageSettings pageSettings, PdfDictionaries dictionari } } } - - - //internal void CreatePdf(PdfPageSettings pageSettings, PdfDictionaries dictionaries, Transform layout, string fileName) - //{ - // using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write)) - // { - // CreatePdf(pageSettings, dictionaries, layout, fs); - // } - - // if (_pageSettings.Debug && _pageSettings.PrintAsText) - // { - // WriteDebugText(fileName); - // } - //} - - //internal void CreatePdf(PdfPageSettings pageSettings, PdfDictionaries dictionaries, Transform layout, Stream stream) - //{ - // _pageSettings = pageSettings; - // _dictionaries = dictionaries; - - // var catalog = AddCatalog(2); - // //Create Pages - // var pagesLayout = layout.ChildObjects[0]; - // var pages = AddPages(); - // //Create Fonts - // AddFontData(); - // //Create Patterns - // AddPatternData(); - // //Create Shadings - // AddShadingsData(); - // //Create Page and Content - // for (int i = 0; i < layout.ChildObjects.Count; i++) - // { - // var pageLayout = layout.ChildObjects[i]; - // var page = AddPage(2, new List(), _pageSettings); - // AddContent(pageLayout, page); - // pages.pageObjectNumbers.Add(page.objectNumber); - // } - // var info = AddInfoObject(); - - // WriteDocumentToStream(stream, catalog, info); - //} - - ///// - ///// Create the pdf from the supplied worksheet. - ///// - ///// The file name - //internal void CreatePdf(string Filename) - //{ - // using (var fs = new FileStream(Filename, FileMode.Create, FileAccess.Write)) - // { - // CreatePdf(fs); - // } - - // if (_pageSettings.Debug && _pageSettings.PrintAsText) - // { - // WriteDebugText(Filename); - // } - //} - - /////// - /////// Create the pdf from the supplied worksheet and write it to a stream. - /////// - /////// The stream to write the pdf to. The stream will not be closed. - ////internal void CreatePdf(Stream stream) - ////{ - //// //Create Catalog - //// var catalogLayout = new PdfCatalogLayout(_workheets[0], _pageSettings, _dictionaries); - //// var catalog = AddCatalog(2); - //// //Create Pages - //// var pagesLayout = catalogLayout.ChildObjects[0]; - //// var pages = AddPages(); - //// //Create Fonts - //// AddFontData(); - //// //Create Patterns - //// AddPatternData(); - //// //Create Shadings - //// AddShadingsData(); - //// //Create Page and Content - //// for (int i = 0; i < pagesLayout.ChildObjects.Count; i++) - //// { - //// var pageLayout = pagesLayout.ChildObjects[i]; - //// var page = AddPage(2, new List(), _pageSettings); - //// AddContent(pageLayout, page); - //// pages.pageObjectNumbers.Add(page.objectNumber); - //// } - //// var info = AddInfoObject(_workheets[0].Workbook._package.File.Name); - - //// WriteDocumentToStream(stream, catalog, info); - ////} - - ////Write the document and cross-ref/trailer to the supplied stream. - ////The stream is not closed; the caller owns it. - //private void WriteDocumentToStream(Stream stream, PdfObjects.PdfCatalog catalog, PdfInfoObject info) - //{ - // _debugString = ""; - // PdfCrossRefTable crossRefTable = new PdfCrossRefTable(); - - // //Use a BinaryWriter without disposing it, so the underlying stream stays open for the caller. - // //BinaryWriter does not own the stream when we don't dispose it; we just flush at the end. - // var bw = new BinaryWriter(stream, Encoding.ASCII); - // try - // { - // //Write header - // bw.Write(Encoding.ASCII.GetBytes(Header)); - // _debugString += Header; - // //Write body - // foreach (var pdfobj in _document) - // { - // crossRefTable.AddPosition(stream.Position); - // pdfobj.ToPdfBytes(bw); - // _debugString += pdfobj.ToPdfString(); - // } - // //Write CrossReference - // crossRefTable.Write(bw, stream.Position, _document.Count); - // _debugString += crossRefTable.WriteString(_document.Count); - // // Write trailer - // PdfTrailer.Write(bw, _document.Count, catalog.objectNumber, info.objectNumber, crossRefTable.StartPosition); - // _debugString += PdfTrailer.WriteString(_document.Count, catalog.objectNumber, info.objectNumber, crossRefTable.StartPosition); - // } - // finally - // { - // bw.Flush(); - // } - //} - - private void WriteDebugText(string fileName) - { - using (var fs = new FileStream(fileName + ".txt", FileMode.Create, FileAccess.Write)) - { - using (var wr = new StreamWriter(fs)) - { - wr.Write(_debugString); - } - } - } } } \ No newline at end of file diff --git a/src/EPPlus.Export.Pdf/Helpers/PdfString.cs b/src/EPPlus.Export.Pdf/Helpers/PdfString.cs index 20802e17d4..d26aea06f6 100644 --- a/src/EPPlus.Export.Pdf/Helpers/PdfString.cs +++ b/src/EPPlus.Export.Pdf/Helpers/PdfString.cs @@ -45,19 +45,5 @@ internal static string ToPdfStringF0(this double val) { return val.ToString("F0", CultureInfo.InvariantCulture); } - - //Delete this method? - public static bool IsNullOrWhiteSpace(string s) - { - if (s == null) - return true; - - for (int i = 0; i < s.Length; i++) - { - if (!char.IsWhiteSpace(s[i])) - return false; - } - return true; - } } } diff --git a/src/EPPlus.Export.Pdf/Layout/PdfBorderData.cs b/src/EPPlus.Export.Pdf/Layout/PdfBorderData.cs index c261c809a4..5af7cc4015 100644 --- a/src/EPPlus.Export.Pdf/Layout/PdfBorderData.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfBorderData.cs @@ -15,9 +15,6 @@ Date Author Change namespace EPPlus.Export.Pdf.Layout { - internal class PdfBorderData - { - } internal class PdfCellBordersData { public PdfCellBorderData Top = new PdfCellBorderData(LineType.Top); diff --git a/src/EPPlus.Export.Pdf/Layout/PdfCell.cs b/src/EPPlus.Export.Pdf/Layout/PdfCell.cs index 67d38dae19..7ab8ab39fb 100644 --- a/src/EPPlus.Export.Pdf/Layout/PdfCell.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfCell.cs @@ -1,26 +1,36 @@ -using EPPlus.Fonts.OpenType.Integration; +/************************************************************************************************* + 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.Fonts.OpenType.Integration; using System.Collections.Generic; namespace EPPlus.Export.Pdf.Layout { internal class PdfCellBase { - public string Name { get; set; } public bool Hidden; public PdfCellAlignmentData ContentAligmnet; + public bool IsPrintTitleRow; + public bool IsPrintTitleCol; + + public string Name { get; set; } public List TextFragments { get; set; } public List ShapedTexts { get; set; } public TextLineCollection TextLines { get; set; } public string Text { get; set; } - public double TotalTextLength { get; set; } public double ColumnWidth { get; set; } public double Width { get; set; } public double Height { get; set; } - public TextLayoutEngine TextLayoutEngine { get; set; } - - public bool IsPrintTitleRow; - public bool IsPrintTitleCol; } } diff --git a/src/EPPlus.Export.Pdf/Layout/PdfCellBorderLayout.cs b/src/EPPlus.Export.Pdf/Layout/PdfCellBorderLayout.cs index a87c2b5927..d3a4062680 100644 --- a/src/EPPlus.Export.Pdf/Layout/PdfCellBorderLayout.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfCellBorderLayout.cs @@ -11,7 +11,6 @@ Date Author Change 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ using EPPlus.Export.Pdf.Enums; -using EPPlus.Export.Pdf.Helpers; using EPPlus.Graphics; using System.Diagnostics; using System.Drawing; @@ -26,11 +25,8 @@ internal class PdfCellBorderLayout : Transform public MergedCellDrawInfo MergedCellInfo; public MergedCellCorners Corners; public bool IsPrintTitle; - public string range; - public PdfCellBorderLayout() { } - public PdfCellBorderLayout(bool isMerged, MergedCellCorners corners, MergedCellDrawInfo info, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) : base(x, y - height, width, height, scaleX, scaleY, rotation, parent) { @@ -38,7 +34,6 @@ public PdfCellBorderLayout(bool isMerged, MergedCellCorners corners, MergedCellD IsMerged = isMerged; MergedCellInfo = info; Corners = corners; - BorderData = new PdfCellBordersData(); BorderData.Top.X = LocalPosition.X; @@ -93,158 +88,6 @@ internal void SetStyle(ExcelBorderStyle topBorderStyle, Color topBorderColor, BorderData.DiagonalDown.BorderStyle = diagdDownBorderStyle; BorderData.DiagonalDown.BorderColor = diagDownBorderColor; } - - - - - - - //public PdfCellBorderLayout(ExcelRangeBase cell, PdfCellStyle tableStyle, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) - // : base(x, y-height, width, height, scaleX, scaleY, rotation, parent) - //{ - // this.cell = cell; - // this.TableStyle = tableStyle; - // if (cell != null) - // { - // IsMerged = cell.Merge; - // BorderData = new PdfCellBordersData(); - // } - //} - - //public void InitEdgeBorders(ExcelRangeBase cell) - //{ - // if (cell != null) - // { - // //BorderData.Top = new PdfCellBorderData(LineType.Top); - // BorderData.Top.BorderStyle = TableStyle.xfTop.Style == ExcelBorderStyle.None ? ((TableStyle.dxfTop != null && TableStyle.dxfTop.HasValue) ? (ExcelBorderStyle)TableStyle.dxfTop.Style : ExcelBorderStyle.None ) : TableStyle.xfTop.Style; - // BorderData.Top.BorderColor = TableStyle.dxfTop != null ? PdfColor.SetColorFromHex(TableStyle.dxfTop.Color.LookupColor(TableStyle.dxfTop)) : PdfColor.SetColorFromHex(cell.Style.Border.Top.Color.LookupColor(cell.Style.Border)); - // BorderData.Top.X = LocalPosition.X; - // BorderData.Top.Y = LocalPosition.Y; - // BorderData.Top.Width = Size.X; - // BorderData.Top.Height = Size.Y; - // //BorderData.Bottom = new PdfCellBorderData(LineType.Bottom); - // BorderData.Bottom.BorderStyle = TableStyle.xfBottom.Style == ExcelBorderStyle.None ? ((TableStyle.dxfBottom != null && TableStyle.dxfBottom.HasValue) ? (ExcelBorderStyle)TableStyle.dxfBottom.Style : ExcelBorderStyle.None) : TableStyle.xfBottom.Style; - // BorderData.Bottom.BorderColor = TableStyle.dxfBottom != null ? PdfColor.SetColorFromHex(TableStyle.dxfBottom.Color.LookupColor(TableStyle.dxfBottom)) : PdfColor.SetColorFromHex(cell.Style.Border.Bottom.Color.LookupColor(cell.Style.Border)); - // BorderData.Bottom.X = LocalPosition.X; - // BorderData.Bottom.Y = LocalPosition.Y; - // BorderData.Bottom.Width = Size.X; - // BorderData.Bottom.Height = Size.Y; - // //BorderData.Left = new PdfCellBorderData(LineType.Left); - // BorderData.Left.BorderStyle = TableStyle.xfLeft.Style == ExcelBorderStyle.None ? ((TableStyle.dxfLeft != null && TableStyle.dxfLeft.HasValue) ? (ExcelBorderStyle)TableStyle.dxfLeft.Style : ExcelBorderStyle.None) : TableStyle.xfLeft.Style; - // BorderData.Left.BorderColor = TableStyle.dxfLeft != null ? PdfColor.SetColorFromHex(TableStyle.dxfLeft.Color.LookupColor(TableStyle.dxfLeft)) : PdfColor.SetColorFromHex(cell.Style.Border.Left.Color.LookupColor(cell.Style.Border)); - // BorderData.Left.X = LocalPosition.X; - // BorderData.Left.Y = LocalPosition.Y; - // BorderData.Left.Width = Size.X; - // BorderData.Left.Height = Size.Y; - // //BorderData.Right = new PdfCellBorderData(LineType.Right); - // BorderData.Right.BorderStyle = TableStyle.xfRight.Style == ExcelBorderStyle.None ? ((TableStyle.dxfRight != null && TableStyle.dxfRight.HasValue) ? (ExcelBorderStyle)TableStyle.dxfRight.Style : ExcelBorderStyle.None) : TableStyle.xfRight.Style; - // BorderData.Right.BorderColor = TableStyle.dxfRight != null ? PdfColor.SetColorFromHex(TableStyle.dxfRight.Color.LookupColor(TableStyle.dxfRight)) : PdfColor.SetColorFromHex(cell.Style.Border.Right.Color.LookupColor(cell.Style.Border)); - // BorderData.Right.X = LocalPosition.X; - // BorderData.Right.Y = LocalPosition.Y; - // BorderData.Right.Width = Size.X; - // BorderData.Right.Height = Size.Y; - - - // ////BorderData.Top = new PdfCellBorderData(LineType.Top); - // //BorderData.Top.BorderStyle = cell.Style.Border.Top.Style; - // //BorderData.Top.BorderColor = PdfColor.SetColorFromHex(cell.Style.Border.Top.Color.LookupColor(cell.Style.Border)); - // //BorderData.Top.X = LocalPosition.X; - // //BorderData.Top.Y = LocalPosition.Y; - // //BorderData.Top.Width = Size.X; - // //BorderData.Top.Height = Size.Y; - // ////BorderData.Bottom = new PdfCellBorderData(LineType.Bottom); - // //BorderData.Bottom.BorderStyle = cell.Style.Border.Bottom.Style; - // //BorderData.Bottom.BorderColor = PdfColor.SetColorFromHex(cell.Style.Border.Bottom.Color.LookupColor(cell.Style.Border)); - // //BorderData.Bottom.X = LocalPosition.X; - // //BorderData.Bottom.Y = LocalPosition.Y; - // //BorderData.Bottom.Width = Size.X; - // //BorderData.Bottom.Height = Size.Y; - // ////BorderData.Left = new PdfCellBorderData(LineType.Left); - // //BorderData.Left.BorderStyle = cell.Style.Border.Left.Style; - // //BorderData.Left.BorderColor = PdfColor.SetColorFromHex(cell.Style.Border.Left.Color.LookupColor(cell.Style.Border)); - // //BorderData.Left.X = LocalPosition.X; - // //BorderData.Left.Y = LocalPosition.Y; - // //BorderData.Left.Width = Size.X; - // //BorderData.Left.Height = Size.Y; - // ////BorderData.Right = new PdfCellBorderData(LineType.Right); - // //BorderData.Right.BorderStyle = cell.Style.Border.Right.Style; - // //BorderData.Right.BorderColor = PdfColor.SetColorFromHex(cell.Style.Border.Right.Color.LookupColor(cell.Style.Border)); - // //BorderData.Right.X = LocalPosition.X; - // //BorderData.Right.Y = LocalPosition.Y; - // //BorderData.Right.Width = Size.X; - // //BorderData.Right.Height = Size.Y; - // } - //} - - //public void InitDiagonalBorders(ExcelRangeBase cell, double width, double height) - //{ - // if (cell != null) - // { - // //BorderData.DiagonalUp = new PdfCellBorderData(LineType.DiagonalUp); - // BorderData.DiagonalUp.BorderStyle = cell.Style.Border.DiagonalUp ? cell.Style.Border.Diagonal.Style : ExcelBorderStyle.None; - // BorderData.DiagonalUp.BorderColor = PdfColor.SetColorFromHex(cell.Style.Border.Diagonal.Color.LookupColor(cell.Style.Border)); - // BorderData.DiagonalUp.MergedDiagonalWidth = width; - // BorderData.DiagonalUp.MergedDiagonalHeight = height; - // //BorderData.DiagonalDown = new PdfCellBorderData(LineType.DiagonalDown); - // BorderData.DiagonalDown.BorderStyle = cell.Style.Border.DiagonalDown ? cell.Style.Border.Diagonal.Style : ExcelBorderStyle.None; - // BorderData.DiagonalDown.BorderColor = PdfColor.SetColorFromHex(cell.Style.Border.Diagonal.Color.LookupColor(cell.Style.Border)); - // BorderData.DiagonalDown.MergedDiagonalWidth = width; - // BorderData.DiagonalDown.MergedDiagonalHeight = height; - // } - //} - - public void UpdateLocalBorderPosition() - { - //if (cell.Address == FirstCellInMerge) - //{ - // BorderData.Top.X = LocalPosition.X; - // BorderData.Top.Y = LocalPosition.Y + Size.Y - BorderData.Top.Height; - //} - //else - //{ - // BorderData.Top.X = LocalPosition.X; - // BorderData.Top.Y = LocalPosition.Y; - //} - //BorderData.Bottom.X = LocalPosition.X; - //BorderData.Bottom.Y = LocalPosition.Y; - //if (cell.Address == FirstCellInMerge) - //{ - // BorderData.Left.X = LocalPosition.X; - // BorderData.Left.Y = LocalPosition.Y + Size.Y - BorderData.Left.Height; - //} - //else - //{ - // BorderData.Left.X = LocalPosition.X; - // BorderData.Left.Y = LocalPosition.Y; - //} - //BorderData.Right.X = LocalPosition.X; - //BorderData.Right.Y = LocalPosition.Y; - } - - - //Get Methods fort border styles - /* -  Whole Table -  First Column Stripe -  Second Column Stripe -  First Row Stripe -  Second Row Stripe -  Last Column -  First Column -  Header Row -  Total Row -  First Header Cell -  Last Header Cell -  First Total Cell -  Last Total Cell - */ - - - - /* - * Following methods needs to be refactored into 1 method. - * Less code is more good. - */ } } diff --git a/src/EPPlus.Export.Pdf/Layout/PdfCellContentLayout.cs b/src/EPPlus.Export.Pdf/Layout/PdfCellContentLayout.cs index 1cc9061015..17b10557fe 100644 --- a/src/EPPlus.Export.Pdf/Layout/PdfCellContentLayout.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfCellContentLayout.cs @@ -32,17 +32,14 @@ internal class PdfCellContentLayout : Transform public bool IsHeading; public bool IsPrintTitle; public TextLayoutEngine textLayoutEngine; - //private List textFormats = new List(); public TextLineCollection TextLines; - public List ShapedTexts { get; set; } - - private double textLength = 0; - private double textHeight = 0; public double LeftTextSpillLength = 0d; public double RightTextSpillLength = 0d; private double bottomMargin = 3.5d; //Guessed number private double rightMargin = 1.4d; //I guessed this one too.. + public List ShapedTexts { get; set; } + public PdfCellContentLayout(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfCellBase cell, MergedCellDrawInfo mergedCellInfo, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) : base(x, y-height, width, height, scaleX, scaleY, rotation, parent) { @@ -159,11 +156,9 @@ private Vector2 CalculateRotatedAlignment(double textLength, double textHeight, double ascent = firstLineAscent; double descent = textHeight - firstLineAscent; if (descent < 0d) descent = 0d; - double theta = CellAlignmentData.TextRotation * System.Math.PI / 180.0; double cos = System.Math.Cos(theta); double sin = System.Math.Sin(theta); - // Bounding box of the rotated block. Reading direction spans [0, textLength]; // the cross (line-height) direction spans [-descent, ascent]. double[] gx = { 0d, textLength, 0d, textLength }; @@ -180,7 +175,6 @@ private Vector2 CalculateRotatedAlignment(double textLength, double textHeight, } double blockWidth = maxX - minX; double blockHeight = maxY - minY; - double bx; switch (CellAlignmentData.HorizontalAlignment) { @@ -191,7 +185,6 @@ private Vector2 CalculateRotatedAlignment(double textLength, double textHeight, default: // Center / General bx = x + (width - blockWidth) / 2d; break; } - double by; switch (CellAlignmentData.VerticalAlignment) { @@ -202,204 +195,10 @@ private Vector2 CalculateRotatedAlignment(double textLength, double textHeight, default: // Center / Justify / Distributed by = y + (height - blockHeight) / 2d; break; } - // Convert the bounding-box lower-left back to the baseline origin the matrix expects. return new Vector2(bx - minX, by - minY); } - - - - - //public PdfCellContentLayout(ExcelRangeBase cell, PdfCellStyle CellStyle, PdfPageSettings pageSettings, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null, PdfDictionaries dictionaries = null) - // : base(x, y, width, height, scaleX, scaleY, rotation, parent) - //{ - // this.cell = cell; - // this.CellStyle = CellStyle; - // CellAlignmentData = new PdfCellAlignmentData(); - // CellAlignmentData.HorizontalAlignment = cell.Style.HorizontalAlignment; - // CellAlignmentData.VerticalAlignment = cell.Style.VerticalAlignment; - // CellAlignmentData.Indent = cell.Style.Indent; - // CellAlignmentData.WrapText = cell.Style.WrapText; - // CellAlignmentData.ShrinkToFit = cell.Style.ShrinkToFit; - // CellAlignmentData.TextRotation = (cell.Style.TextRotation > 90) ? ((cell.Style.TextRotation == 255) ? 0 : 90 - cell.Style.TextRotation) : cell.Style.TextRotation; - // CellAlignmentData.IsVertical = cell.Style.TextRotation == 255 ? true : false; - // CellAlignmentData.TextDirection = cell.Style.ReadingOrder; - // if (!cell.IsRichText) cell._rtc = new ExcelRichTextCollection(cell.Text, cell); - // //HandleText(pageSettings, dictionaries, x, y, width, height, CellAlignmentData.TextRotation, CellStyle); - // //CalculateTextSpill(width, CellAlignmentData.TextRotation); - // //LocalPosition = CalculateAlignmentPositionAndTextOffsets(cell, x, y, width, height); - // Size = new Vector2(x + width - LocalPosition.X, y + height - LocalPosition.Y); - // //CheckClipping(cell, x, y, width, height); - //} - - //public PdfCellContentLayout(string text, ExcelRangeBase cell, PdfCellStyle CellStyle, PdfPageSettings pageSettings, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null, PdfDictionaries dictionaries = null) - // : base(x, y, width, height, scaleX, scaleY, rotation, parent) - //{ - // this.cell = cell; - // this.CellStyle = CellStyle; - // CellAlignmentData = new PdfCellAlignmentData(); - // CellAlignmentData.HorizontalAlignment = ExcelHorizontalAlignment.Center; - // CellAlignmentData.VerticalAlignment = ExcelVerticalAlignment.Bottom; - // cell._rtc = new ExcelRichTextCollection(text, cell); - // var ns = cell.Worksheet.Workbook.Styles.GetNormalStyle(); - // cell._rtc[0].FontName = ns.Style.Font.Name; - // cell._rtc[0].Family = ns.Style.Font.Family; - // cell._rtc[0].Size = ns.Style.Font.Size; - // cell._rtc[0].Color = Color.Black; - // //HandleText(pageSettings, dictionaries, x, y, width, height, CellAlignmentData.TextRotation, CellStyle); - // //CalculateTextSpill(width, CellAlignmentData.TextRotation); - // //LocalPosition = CalculateAlignmentPositionAndTextOffsets(cell, x, y, width, height); - // Size = new Vector2(x + width - LocalPosition.X, y + height - LocalPosition.Y); - // //CheckClipping(cell, x, y, width, height); - //} - - //public void CalculateTextSpill(double maxWidth, double rotation) - //{ - // if (maxWidth < textLength) - // { - // if (rotation != 0) - // { - // var baseVec = new Vector2(textLength, 0); - // var rad = rotation * Math.PI / 180.0d; - // var rotVec = new Vector2(textLength * Math.Cos(rad), textLength * Math.Sin(rad)); - // var length = Vector2.Project(rotVec, baseVec).Length; - // if (length > maxWidth) - // { - // if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.General) - // { - // if (double.TryParse(cell.Value.ToString(), out double value)) - // { - // LeftTextSpillLength = textLength; - // } - // else - // { - // RightTextSpillLength = textLength; - // } - // } - // else if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Left) - // { - // RightTextSpillLength = length; - // } - // else if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Right) - // { - // LeftTextSpillLength = length; - // } - // else if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Center) - // { - // LeftTextSpillLength = length / 2d; - // RightTextSpillLength = length / 2d; - // } - - // } - // return; - // } - // if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.General) - // { - // if (double.TryParse(cell.Value.ToString(), out double value)) - // { - // LeftTextSpillLength = textLength - Size.X; - // } - // else - // { - // RightTextSpillLength = textLength - Size.X; - // } - // } - // else if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Left) - // { - // RightTextSpillLength = textLength - Size.X; - // } - // else if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Right) - // { - // LeftTextSpillLength = textLength - Size.X; - // } - // else if (CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Center) - // { - // LeftTextSpillLength = (textLength - Size.X) / 2d; - // RightTextSpillLength = (textLength - Size.X) / 2d; - // } - // } - //} - - ////Calculate text position from alignment and offsets for each line of text. - //public Vector2 CalculateAlignmentPositionAndTextOffsets(ExcelRangeBase cell, double cellX, double CellY, double cellWidth, double cellHeight) - //{ - // double x = 0d; - // double y = 0d; - // double xOffset = 0d; - // double yOffset = 0d; - // switch (CellAlignmentData.HorizontalAlignment) - // { - // case ExcelHorizontalAlignment.Fill: - // case ExcelHorizontalAlignment.General: - // if (double.TryParse(cell.Value.ToString(), out double value)) - // { - // x = cellX + (cellWidth - textLength) - rightMargin; - // } - // else - // { - // x = cellX + rightMargin; - // } - // break; - // case ExcelHorizontalAlignment.Left: - // x = cellX + rightMargin; - // break; - // case ExcelHorizontalAlignment.Center: - // x = cellX + (cellWidth - textLength) / 2d; - // break; - // case ExcelHorizontalAlignment.Right: - // x = cellX + (cellWidth - textLength) - rightMargin; - // break; - // } - // switch (CellAlignmentData.VerticalAlignment) - // { - // case ExcelVerticalAlignment.Top: - // y = (CellY + cellHeight) - (textHeight / 2d) - bottomMargin; - // break; - // case ExcelVerticalAlignment.Center: - // y = CellY + (cellHeight / 2d) - (textHeight / 4d); ; - // break; - // case ExcelVerticalAlignment.Bottom: - // y = CellY + bottomMargin; - // break; - // } - // if (CellAlignmentData.TextRotation < 0) - // { - // double rot = CellAlignmentData.TextRotation * System.Math.PI / 180.0; - // x += textLength * (1 - System.Math.Cos(rot)); - // y -= textLength * System.Math.Sin(rot); - // } - // else if (CellAlignmentData.TextRotation > 0) - // { - // double rot = CellAlignmentData.TextRotation * System.Math.PI / 180.0; - // x += textLength * (1 - System.Math.Cos(rot)); - // } - // return new Vector2(x, y); - //} - - ////Check if clipping is needed. - - - ////Check if clipping is needed. - - //public void CheckClipping(ExcelRangeBase cell, double x, double y, double width, double height) - //{ - // if (textLength >= width || cell.Merge) - // { - // if (cell.Merge || - // CellAlignmentData.HorizontalAlignment == ExcelHorizontalAlignment.Fill) - // { - // Clipping = new Rect() - // { - // X = x + rightMargin, - // Y = y, - // Width = width - rightMargin * 2, - // Height = height - // }; - // } - // } - //} - // Set clipping to the cell's own bounds. cellY is the top edge (same convention as the constructor). internal void SetupClipping(double cellX, double cellY, double cellWidth, double cellHeight) { @@ -413,40 +212,6 @@ internal void SetupClipping(double cellX, double cellY, double cellWidth, double }; } - ////Create clipping rectangle. - //internal void CreateClippingRect(List cells) - //{ - // if (Clip) - // { - // var cellName = Name.Split('_')[0]; - // var pcc = cells.Where(x => x.Name.Contains(cellName)).Where(x => x is PdfCellLayout).ToList(); - // if (pcc.Count > 0) - // { - // Clipping = new Rect() - // { - // X = pcc[0].LocalPosition.X + rightMargin, - // Y = pcc[0].LocalPosition.Y, - // Width = pcc[0].Size.X - rightMargin * 2, - // Height = pcc[0].Size.Y - // }; - // } - // } - //} - - //Create clipping rectangle. - //internal void CreateClippingRect(PdfCellLayout cell, double targetX) - //{ - // //need a way to detect starting X position if text is spilled from left or right - // var width = targetX - cell.LocalPosition.X; - // Clipping = new Rect() - // { - // X = cell.LocalPosition.X + rightMargin, - // Y = cell.LocalPosition.Y, - // Width = width - rightMargin * 2, - // Height = cell.Size.Y - // }; - //} - internal void GidsAndCharMap(PdfDictionaries dictionaries) { foreach (var tf in ShapedTexts) diff --git a/src/EPPlus.Export.Pdf/Layout/PdfCellLayout.cs b/src/EPPlus.Export.Pdf/Layout/PdfCellLayout.cs index e939b4cd04..dd037480c9 100644 --- a/src/EPPlus.Export.Pdf/Layout/PdfCellLayout.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfCellLayout.cs @@ -15,7 +15,6 @@ Date Author Change using EPPlus.Export.Pdf.Resources; using EPPlus.Export.Pdf.Settings; using EPPlus.Graphics; -using EPPlus.Graphics.Geometry; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; @@ -37,81 +36,13 @@ public PdfCellLayout(double x, double y, double width, double height, double sca : base(x, y - height, width, height, scaleX, scaleY, rotation, parent) { Z = 1; - //var xfFill = CellStyle.xfFill; - //var dxfFill = CellStyle.dxfFill; CellFillData = new PdfCellFillData(); - //if (dxfFill != null && xfFill.IsEmpty()) - //{ - // CellFillData.PatternStyle = dxfFill.PatternType != null ? (ExcelFillStyle)dxfFill.PatternType : ExcelFillStyle.Solid; - // if (CellFillData.PatternStyle == ExcelFillStyle.Solid) - // { - // CellFillData.BackgroundColor = PdfColor.SetColorFromHex(dxfFill.BackgroundColor.LookupColor()); - // } - // else if (CellFillData.PatternStyle != ExcelFillStyle.None) - // { - // CellFillData.BackgroundColor = PdfColor.SetColorFromHex(dxfFill.PatternColor.Color == null ? "#FFFFFFFF" : dxfFill.PatternColor.LookupColor()); - // CellFillData.PatternColor = PdfColor.SetColorFromHex(dxfFill.BackgroundColor.LookupColor()); - // CellFillData.id = AddPatternResourceData(dictionaries.Patterns, CellFillData.PatternStyle.ToString() + CellFillData.PatternColor.ToHexString() + CellFillData.BackgroundColor.ToHexString()); - // } - // else if (dxfFill.Gradient != null) - // { - // CellFillData.GradientFillData = new PdfCellGradientFillData(); - // CellFillData.GradientFillData.GradientType = dxfFill.Gradient.GradientType == null ? ExcelFillGradientType.None : (ExcelFillGradientType)dxfFill.Gradient.GradientType; - // CellFillData.GradientFillData.Color1 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[0].Color.LookupColor()); - // CellFillData.GradientFillData.Color2 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[1].Color.LookupColor()); - // CellFillData.GradientFillData.Color3 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[2].Color.LookupColor()); - // CellFillData.GradientFillData.Degree = dxfFill.Gradient.Degree == null ? 0 : (double)dxfFill.Gradient.Degree; - // CellFillData.GradientFillData.Top = dxfFill.Gradient.Top == null ? 0 : (double)dxfFill.Gradient.Top; - // CellFillData.GradientFillData.Bottom = dxfFill.Gradient.Bottom == null ? 0 : (double)dxfFill.Gradient.Bottom; - // CellFillData.GradientFillData.Left = dxfFill.Gradient.Left == null ? 0 : (double)dxfFill.Gradient.Left; - // CellFillData.GradientFillData.Right = dxfFill.Gradient.Right == null ? 0 : (double)dxfFill.Gradient.Right; - // CellFillData.id = CellFillData.GradientFillData.ToString() + $"_{x:F4}_{(y - height):F4}_{width:F4}_{height:F4}"; - // AddShadingResourceData(dictionaries.Shadings, CellFillData.id); - // } - //} - //else - //{ - // if (xfFill.PatternType == ExcelFillStyle.Solid) - // { - // var bkgc = xfFill.BackgroundColor; - // CellFillData.PatternStyle = xfFill.PatternType; - // if (string.IsNullOrEmpty(bkgc.LookupColor()) && !string.IsNullOrEmpty(cell.Text)) - // { - // CellFillData.BackgroundColor = Color.Empty; - // } - // else - // { - // CellFillData.BackgroundColor = PdfColor.SetColorFromHex(bkgc.LookupColor()); - // } - // } - // else if (xfFill.PatternType != ExcelFillStyle.None) - // { - // CellFillData.PatternStyle = xfFill.PatternType; - // CellFillData.BackgroundColor = PdfColor.SetColorFromHex(xfFill.PatternColor.Rgb == null ? "#FFFFFFFF" : xfFill.PatternColor.LookupColor()); - // CellFillData.PatternColor = PdfColor.SetColorFromHex(xfFill.BackgroundColor.LookupColor()); - // CellFillData.id = AddPatternResourceData(dictionaries.Patterns, CellFillData.PatternStyle.ToString() + CellFillData.PatternColor.ToHexString() + CellFillData.BackgroundColor.ToHexString()); - // } - // else if (xfFill.HasGradient) - // { - // CellFillData.GradientFillData = new PdfCellGradientFillData(); - // CellFillData.GradientFillData.GradientType = xfFill.Gradient.Type; - // CellFillData.GradientFillData.Color1 = PdfColor.SetColorFromHex(xfFill.Gradient.Color1.LookupColor()); - // CellFillData.GradientFillData.Color2 = PdfColor.SetColorFromHex(xfFill.Gradient.Color2.LookupColor()); - // CellFillData.GradientFillData.Color3 = PdfColor.SetColorFromHex(xfFill.Gradient.Color3.LookupColor()); - // CellFillData.GradientFillData.Degree = xfFill.Gradient.Degree; - // CellFillData.GradientFillData.Top = double.IsNaN(xfFill.Gradient.Top) ? 0 : xfFill.Gradient.Top; - // CellFillData.GradientFillData.Bottom = double.IsNaN(xfFill.Gradient.Bottom) ? 0 : xfFill.Gradient.Bottom; - // CellFillData.GradientFillData.Left = double.IsNaN(xfFill.Gradient.Left) ? 0 : xfFill.Gradient.Left; - // CellFillData.GradientFillData.Right = double.IsNaN(xfFill.Gradient.Right) ? 0 : xfFill.Gradient.Right; - // CellFillData.id = CellFillData.GradientFillData.ToString() + $"_{x:F4}_{(y - height):F4}_{width:F4}_{height:F4}"; - // AddShadingResourceData(dictionaries.Shadings, CellFillData.id); - // } - //} } internal void SetFill(Color backgroundColor) { - CellFillData.BackgroundColor = Color.Empty; + CellFillData.PatternStyle = ExcelFillStyle.Solid; + CellFillData.BackgroundColor = backgroundColor; } internal void SetPattern(PdfDictionaries dictionaries, ExcelFillStyle patternStyle, Color backgroundColor, Color patternColor) { @@ -136,86 +67,6 @@ internal void SetGradient(PdfDictionaries dictionaries, ExcelFillGradientType gr AddShadingResourceData(dictionaries.Shadings, CellFillData.id); } - //public PdfCellLayout(PdfDictionaries dictionaries, ExcelRangeBase cell, PdfCellStyle CellStyle, double x, double y, double width, double height, double scaleX = 1, double scaleY = 1, double rotation = 0, Transform parent = null) - // : base(x, y - height, width, height, scaleX, scaleY, rotation, parent) - //{ - // if (cell != null) - // { - // this.cell = cell; - // this.CellStyle = CellStyle; - // var xfFill = CellStyle.xfFill; - // var dxfFill = CellStyle.dxfFill; - // CellFillData = new PdfCellFillData(); - // if (dxfFill != null && xfFill.IsEmpty()) - // { - // CellFillData.PatternStyle = dxfFill.PatternType != null ? (ExcelFillStyle)dxfFill.PatternType : ExcelFillStyle.Solid; - // if (CellFillData.PatternStyle == ExcelFillStyle.Solid) - // { - // CellFillData.BackgroundColor = PdfColor.SetColorFromHex(dxfFill.BackgroundColor.LookupColor()); - // } - // else if (CellFillData.PatternStyle != ExcelFillStyle.None) - // { - // CellFillData.BackgroundColor = PdfColor.SetColorFromHex(dxfFill.PatternColor.Color == null ? "#FFFFFFFF" : dxfFill.PatternColor.LookupColor()); - // CellFillData.PatternColor = PdfColor.SetColorFromHex(dxfFill.BackgroundColor.LookupColor()); - // CellFillData.id = AddPatternResourceData(dictionaries.Patterns, CellFillData.PatternStyle.ToString() + CellFillData.PatternColor.ToHexString() + CellFillData.BackgroundColor.ToHexString()); - // } - // else if (dxfFill.Gradient != null) - // { - // CellFillData.GradientFillData = new PdfCellGradientFillData(); - // CellFillData.GradientFillData.GradientType = dxfFill.Gradient.GradientType == null ? ExcelFillGradientType.None : (ExcelFillGradientType)dxfFill.Gradient.GradientType; - // CellFillData.GradientFillData.Color1 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[0].Color.LookupColor()); - // CellFillData.GradientFillData.Color2 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[1].Color.LookupColor()); - // CellFillData.GradientFillData.Color3 = PdfColor.SetColorFromHex(dxfFill.Gradient.Colors[2].Color.LookupColor()); - // CellFillData.GradientFillData.Degree = dxfFill.Gradient.Degree == null ? 0 : (double)dxfFill.Gradient.Degree; - // CellFillData.GradientFillData.Top = dxfFill.Gradient.Top == null ? 0 : (double)dxfFill.Gradient.Top; - // CellFillData.GradientFillData.Bottom = dxfFill.Gradient.Bottom == null ? 0 : (double)dxfFill.Gradient.Bottom; - // CellFillData.GradientFillData.Left = dxfFill.Gradient.Left == null ? 0 : (double)dxfFill.Gradient.Left; - // CellFillData.GradientFillData.Right = dxfFill.Gradient.Right == null ? 0 : (double)dxfFill.Gradient.Right; - // CellFillData.id = CellFillData.GradientFillData.ToString() + $"_{x:F4}_{(y - height):F4}_{width:F4}_{height:F4}"; - // AddShadingResourceData(dictionaries.Shadings, CellFillData.id); - // } - // } - // else - // { - // if (xfFill.PatternType == ExcelFillStyle.Solid) - // { - // var bkgc = xfFill.BackgroundColor; - // CellFillData.PatternStyle = xfFill.PatternType; - // if (string.IsNullOrEmpty(bkgc.LookupColor()) && !string.IsNullOrEmpty(cell.Text)) - // { - // CellFillData.BackgroundColor = Color.Empty; - // } - // else - // { - // CellFillData.BackgroundColor = PdfColor.SetColorFromHex(bkgc.LookupColor()); - // } - // } - // else if (xfFill.PatternType != ExcelFillStyle.None) - // { - // CellFillData.PatternStyle = xfFill.PatternType; - // CellFillData.BackgroundColor = PdfColor.SetColorFromHex(xfFill.PatternColor.Rgb == null ? "#FFFFFFFF" : xfFill.PatternColor.LookupColor()); - // CellFillData.PatternColor = PdfColor.SetColorFromHex(xfFill.BackgroundColor.LookupColor()); - // CellFillData.id = AddPatternResourceData(dictionaries.Patterns, CellFillData.PatternStyle.ToString() + CellFillData.PatternColor.ToHexString() + CellFillData.BackgroundColor.ToHexString()); - // } - // else if (xfFill.HasGradient) - // { - // CellFillData.GradientFillData = new PdfCellGradientFillData(); - // CellFillData.GradientFillData.GradientType = xfFill.Gradient.Type; - // CellFillData.GradientFillData.Color1 = PdfColor.SetColorFromHex(xfFill.Gradient.Color1.LookupColor()); - // CellFillData.GradientFillData.Color2 = PdfColor.SetColorFromHex(xfFill.Gradient.Color2.LookupColor()); - // CellFillData.GradientFillData.Color3 = PdfColor.SetColorFromHex(xfFill.Gradient.Color3.LookupColor()); - // CellFillData.GradientFillData.Degree = xfFill.Gradient.Degree; - // CellFillData.GradientFillData.Top = double.IsNaN(xfFill.Gradient.Top) ? 0 : xfFill.Gradient.Top; - // CellFillData.GradientFillData.Bottom = double.IsNaN(xfFill.Gradient.Bottom) ? 0 : xfFill.Gradient.Bottom; - // CellFillData.GradientFillData.Left = double.IsNaN(xfFill.Gradient.Left) ? 0 : xfFill.Gradient.Left; - // CellFillData.GradientFillData.Right = double.IsNaN(xfFill.Gradient.Right) ? 0 : xfFill.Gradient.Right; - // CellFillData.id = CellFillData.GradientFillData.ToString() + $"_{x:F4}_{(y - height):F4}_{width:F4}_{height:F4}"; - // AddShadingResourceData(dictionaries.Shadings, CellFillData.id); - // } - // } - // } - //} - private string AddPatternResourceData(Dictionary patternResources, string key) { if (!patternResources.ContainsKey(key)) @@ -246,16 +97,8 @@ private string AddShadingResourceData(Dictionary sha return key; } - //Adjust size and position slightly for aesthetics. - // public void AdjustForGridLines() - // { - // Size = new Vector2(Size.X + GridLine.HalfWidth, Size.Y + GridLine.HalfWidth); - // LocalPosition = new Vector2(LocalPosition.X + GridLine.FourthWidth, LocalPosition.Y + GridLine.FourthWidth); - // } - public void UpdateShadingPositionMatrix(PdfPageSettings pageSettings) { - //LocalPosition = new Vector2(LocalPosition.X, pageSettings.PageSize.HeightPu - System.Math.Abs(LocalPosition.Y) - Size.Y); if (CellFillData.GradientFillData != null) { if (CellFillData.GradientFillData.GradientType == ExcelFillGradientType.Linear) @@ -322,33 +165,6 @@ public void UpdateShadingPositionMatrix(PdfPageSettings pageSettings) CellFillData.GradientFillData.coords = [0.5, 0.5, 0, 0.5, 0.5, r]; } CellFillData.GradientFillData.matrix = [Size.X, 0, 0, Size.Y, LocalPosition.X, LocalPosition.Y]; - //double cx = x; - //double cy = y + height; - //if (!double.IsNaN(left) && !double.IsNaN(right) && !double.IsNaN(top) && !double.IsNaN(bottom)) // bottom-right - //{ - // if (top < 1d && bottom < 1d &&left < 1d && right < 1d) - // { - // cx = x + width / 2d; - // cy = y + height / 2d; - // } - // else - // { - // cx = x + width; - // cy = y; - // } - //} - //else if (!double.IsNaN(left) && !double.IsNaN(right)) // bottom-left - //{ - // cx = x + width; - // cy = y + height; - //} - //else if (!double.IsNaN(top) && !double.IsNaN(bottom)) // top-right - //{ - // cx = x; - // cy = y; - //} - //double r = System.Math.Sqrt(width * width + height * height) / 2d; - //CellFillData.GradientFillData.coords = [cx, cy, 0, cx, cy, r]; } } } diff --git a/src/EPPlus.Export.Pdf/Layout/PdfHeaderFooter.cs b/src/EPPlus.Export.Pdf/Layout/PdfHeaderFooter.cs index 137ab93172..1765ee5c62 100644 --- a/src/EPPlus.Export.Pdf/Layout/PdfHeaderFooter.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfHeaderFooter.cs @@ -40,9 +40,7 @@ internal class PdfHeaderFooter public HeaderFooterType PageType; public HeaderFooterAlignment Alignment; public HeaderFooterSection Section; - public PdfCellBase Content; - public List NumberOfPagesIndexes = new List(); public List PageNumberIndexes = new List(); diff --git a/src/EPPlus.Export.Pdf/Layout/PdfPageLayout.cs b/src/EPPlus.Export.Pdf/Layout/PdfPageLayout.cs index ec7de2209c..2a1d0d9a88 100644 --- a/src/EPPlus.Export.Pdf/Layout/PdfPageLayout.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfPageLayout.cs @@ -16,319 +16,20 @@ Date Author Change namespace EPPlus.Export.Pdf.Layout { - [DebuggerDisplay("Cell: {Name}")] - internal struct PageMap - { - public string Name; - public int row; - public int col; - public PdfCellLayout cell; - public PdfCellContentLayout content; - public PdfCellBorderLayout border; - public enum CellType - { - Normal, - Merged, - } - public CellType Type; - - //Scoops up text spill from adjecent cells - public double LeftTextBucketSpill; - public double RightTextBucketSpill; - } - - internal class VerticalLineRun - { - public int Col; - public int RowStart; - public int RowEnd; - } - - internal class HorizontalLineRun - { - public int Row; - public int ColStart; - public int ColEnd; - } - [DebuggerDisplay("{Name}")] internal class PdfPageLayout : Transform { internal List GridLines = new List(); internal List BorderLines = new List(); public List PrintTitleGridLines = new List(); - - public int FromRow = int.MaxValue; - public int ToRow = 0; - public int FromCol = int.MaxValue; - public int ToCol = 0; - public int RowCount = 0; - public int ColCount = 0; - - public PageMap[,] Map; - - public double ContentTop; - public double ContentBottom; - public double ContentLeft; - public double ContentRight; - public double ContentX; - public double ContentHeight; - public double HeadingWidth; public double HeadingHeight; - public double PrintTitleWidth; public double PrintTitleHeight; - public bool isCommentsPage = false; public PdfPageLayout(double x, double y, double width, double height) - : base(x, y, width, height) { } - - //public void AddCell(Transform cell) - //{ - // if (cell is PdfCellLayout cl) - // { - // FromRow = Math.Min(FromRow, cl.cell._fromRow); - // ToRow = Math.Max(ToRow, cl.cell._toRow); - // FromCol = Math.Min(FromCol, cl.cell._fromCol); - // ToCol = Math.Max(ToCol, cl.cell._toCol); - // } - // ChildObjects[0].AddChild(cell); - //} - - //public void CreateMap() - //{ - // RowCount = ToRow - FromRow + 1; - // ColCount = ToCol - FromCol + 1; - // Map = new PageMap[RowCount, ColCount]; - //} - - //internal void GenerateGridLines() - //{ - // HashSet xCoords = new HashSet(); - // HashSet yCoords = new HashSet(); - // // Collect all unique X and Y coordinates. - // var cells = ChildObjects.Where(x => x is PdfCellLayout).ToList(); - // foreach (var c in cells) - // { - // if (c is PdfMergedCellLayout) continue; - // yCoords.Add(c.LocalPosition.Y + c.Size.Y); // Top - // yCoords.Add(c.LocalPosition.Y); // Bottom - // xCoords.Add(c.LocalPosition.X); // Left - // xCoords.Add(c.LocalPosition.X + c.Size.X); // Right - // } - // double minX = cells.Where(c => c is not PdfMergedCellLayout).Min(c => c.LocalPosition.X); - // double maxX = cells.Where(c => c is not PdfMergedCellLayout).Max(c => c.LocalPosition.X + c.Size.X); - // double minY = cells.Where(c => c is not PdfMergedCellLayout).Min(c => c.LocalPosition.Y); - // double maxY = cells.Where(c => c is not PdfMergedCellLayout).Max(c => c.LocalPosition.Y + c.Size.Y); - // foreach (var x in xCoords.OrderBy(v => v)) - // { - // var line = new GridLine(x, minY, x, maxY); - // if (x == minX || x == maxX) - // BorderLines.Add(line); - // else - // GridLines.Add(line); - // } - // foreach (var y in yCoords.OrderBy(v => v)) - // { - // var line = new GridLine(minX, y, maxX, y); - // if (y == minY || y == maxY) - // BorderLines.Add(line); - // else - // GridLines.Add(line); - // } - //} - - //internal void GenerateVerticalGridLines(ExcelWorksheet ws) - //{ - // int addedColumns = PdfWorksheetLayout.AddColumnsForNonWrappedText(ws); - // bool resetStart = true; - // var startX = 0d; - // var startY = 0d; - // var endX = 0d; - // var endY = 0d; - // for (int col = FromCol+1; col <= ToCol; col++) - // { - // double length = 0; - // //string name = ""; - // //var f = ws.Cells[FromRow, col]; - // //var start = ChildObjects.Where(x => x.Name == f.Address || x.Name == f.Address + "_m" || x.Name == f.Address + "*").ToList(); - // for (int row = FromRow; row <= ToRow; row++) - // { - // var cell = ws.Cells[row, col]; - // var name = cell.Address; - // var layouts = ChildObjects.Where(x => x.Name == cell.Address || x.Name == cell.Address + "_m" || x.Name == cell.Address + "*").ToList(); - // PdfMergedCellLayout m = null; - // PdfCellLayout l = null; - // foreach (var layout in layouts) - // { - // if (layout is PdfMergedCellLayout) m = (PdfMergedCellLayout)layout; - // else if (layout is PdfCellLayout) l = (PdfCellLayout)layout; - // } - // if (l != null) - // { - // if (l.LeftTextSpillLength > 0) - // { - // length = 0; - // if (startX != 0d) - // { - // var line = new GridLine(startX, startY, endX, endY); - // GridLines.Add(line); - // resetStart = true; - // } - // } - // else - // { - // length += l.Size.Y; - // endX = l.LocalPosition.X; - // endY = l.LocalPosition.Y; - // if (resetStart) - // { - // startX = l.LocalPosition.X; - // startY = l.LocalPosition.Y + l.Size.Y; - // resetStart = false; - // } - // } - // } - // else if (m != null) - // { - // length += m.Size.Y; - // endX = m.LocalPosition.X; - // endY = m.LocalPosition.Y; - // if (resetStart) - // { - // startX = m.LocalPosition.X; - // startY = m.LocalPosition.Y + m.Size.Y; - // resetStart = false; - // } - // } - // else if (l == null && m == null) - // { - // if (length > 0) - // { - // length = 0; - // if (startX != 0d) - // { - // var line = new GridLine(startX, startY, endX, endY); - // GridLines.Add(line); - // resetStart = true; - // } - // } - // } - // } - // if (startX != 0d) - // { - // var line2 = new GridLine(startX, startY, endX, endY); - // GridLines.Add(line2); - // } - // resetStart = true; - // } - //} - - //internal void GenerateHorizontalGridLines(ExcelWorksheet ws) - //{ - // int addedColumns = PdfWorksheetLayout.AddColumnsForNonWrappedText(ws); - // bool resetStart = true; - // var startX = 0d; - // var startY = 0d; - // var endX = 0d; - // var endY = 0d; - // for (int row = FromRow+1; row <= ToRow; row++) - // { - // double length = 0; - // //string name = ""; - // //var f = ws.Cells[row, FromCol]; - // //var start = ChildObjects.Where(x => x.Name == f.Address || x.Name == f.Address + "_m" || x.Name == f.Address + "*").ToList(); - // for (int col = FromCol; col <= ToCol; col++) - // { - // var cell = ws.Cells[row, col]; - // var name = cell.Address; - // var layouts = ChildObjects.Where(x => x.Name == cell.Address || x.Name == cell.Address + "_m" || x.Name == cell.Address + "*").ToList(); - // PdfMergedCellLayout m = null; - // PdfCellLayout l = null; - // foreach (var layout in layouts) - // { - // if (layout is PdfMergedCellLayout) m = (PdfMergedCellLayout)layout; - // else if (layout is PdfCellLayout) l = (PdfCellLayout)layout; - // } - // if (l != null) - // { - // length += l.Size.X; - // endX = l.LocalPosition.X + l.Size.X; - // endY = l.LocalPosition.Y + l.Size.Y; - // if (resetStart) - // { - // startX = l.LocalPosition.X; - // startY = l.LocalPosition.Y + l.Size.Y; - // resetStart = false; - // } - // } - // else if (m != null) - // { - // length += m.Size.X; - // endX = m.LocalPosition.X + m.Size.X; - // endY = m.LocalPosition.Y + m.Size.Y; - // if (resetStart) - // { - // startX = m.LocalPosition.X; - // startY = m.LocalPosition.Y + m.Size.Y; - // resetStart = false; - // } - // } - // else if (l == null && m == null) - // { - // if (length > 0) - // { - // length = 0; - // if (startX != 0d) - // { - // var line = new GridLine(startX, startY, endX, endY); - // GridLines.Add(line); - // resetStart = true; - // } - // } - // } - // } - // if (startX != 0d) - // { - // var line2 = new GridLine(startX, startY, endX, endY); - // GridLines.Add(line2); - // } - // resetStart = true; - // } - //} - - //internal void GenerateBorderLines() - //{ - // HashSet xCoords = new HashSet(); - // HashSet yCoords = new HashSet(); - // // Collect all unique X and Y coordinates. - // var cells = ChildObjects.Where(x => x is PdfCellLayout).ToList(); - // foreach (var c in cells) - // { - // if (c is PdfMergedCellLayout) continue; - // yCoords.Add(c.LocalPosition.Y + c.Size.Y); // Top - // yCoords.Add(c.LocalPosition.Y); // Bottom - // xCoords.Add(c.LocalPosition.X); // Left - // xCoords.Add(c.LocalPosition.X + c.Size.X); // Right - // } - // double minX = cells.Where(c => c is not PdfMergedCellLayout).Min(c => c.LocalPosition.X); - // double maxX = cells.Where(c => c is not PdfMergedCellLayout).Max(c => c.LocalPosition.X + c.Size.X); - // double minY = cells.Where(c => c is not PdfMergedCellLayout).Min(c => c.LocalPosition.Y); - // double maxY = cells.Where(c => c is not PdfMergedCellLayout).Max(c => c.LocalPosition.Y + c.Size.Y); - // foreach (var x in xCoords.OrderBy(v => v)) - // { - // var line = new GridLine(x, minY, x, maxY); - // if (x == minX || x == maxX) - // BorderLines.Add(line); - // } - // foreach (var y in yCoords.OrderBy(v => v)) - // { - // var line = new GridLine(minX, y, maxX, y); - // if (y == minY || y == maxY) - // BorderLines.Add(line); - // } - //} + : base(x, y, width, height) + { } } } \ No newline at end of file diff --git a/src/EPPlus.Export.Pdf/Layout/PdfShapedText.cs b/src/EPPlus.Export.Pdf/Layout/PdfShapedText.cs index b28e8c4f1a..6162af4f9e 100644 --- a/src/EPPlus.Export.Pdf/Layout/PdfShapedText.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfShapedText.cs @@ -19,8 +19,6 @@ namespace EPPlus.Export.Pdf.Layout internal struct PdfShapedText { public IFontProvider FontProvider; - //sometimes a font can have other fonts for certain characters. Key as the glyph id, Value is the font label. - //public Dictionary FontIDLabel; public Dictionary FontIdMap; public List UsedFonts; public ShapedText ShapedText; diff --git a/src/EPPlus.Export.Pdf/Resources/PdfDictionaries.cs b/src/EPPlus.Export.Pdf/Resources/PdfDictionaries.cs index 96fb2a6e3d..0cfa817aa1 100644 --- a/src/EPPlus.Export.Pdf/Resources/PdfDictionaries.cs +++ b/src/EPPlus.Export.Pdf/Resources/PdfDictionaries.cs @@ -41,13 +41,14 @@ public void AddFont(PdfPageSettings pageSettings, string FontName, FontSubFamily manger.AddText(Text); } - internal PdfFontResource GetFont(string FontName) + internal PdfFontResource GetFont(string fontName, FontSubFamily subFamily) { - if (!Fonts.ContainsKey(FontName)) + var lookUpName = fontName + " " + subFamily.ToString(); + if (!Fonts.ContainsKey(lookUpName)) { - return null; + throw new KeyNotFoundException($"Font: {lookUpName} is missing from dictionary."); } - return Fonts[FontName]; + return Fonts[lookUpName]; } } } diff --git a/src/EPPlus.Export.Pdf/Resources/PdfFontResource.cs b/src/EPPlus.Export.Pdf/Resources/PdfFontResource.cs index 24644f1f6f..a9382a780a 100644 --- a/src/EPPlus.Export.Pdf/Resources/PdfFontResource.cs +++ b/src/EPPlus.Export.Pdf/Resources/PdfFontResource.cs @@ -39,16 +39,12 @@ internal class PdfFontResource : PdfResource private int firstChar = 32; private int lastChar = 255; private CIDSystemInfo cidSystemInfo = null; - internal string type0Encoding = "Identity-H"; - internal TextShaper Shaper = null; internal ShapedText ShapedText; - internal HashSet Subset = new HashSet(); internal HashSet Gids = new HashSet(); internal Dictionary charactermappings = new Dictionary(); - internal FontSubsetManager fontSubsetManager; public PdfFontResource(string fontName, FontSubFamily subFamily, int labelNumber, PdfPageSettings pageSettings) @@ -60,28 +56,6 @@ public PdfFontResource(string fontName, FontSubFamily subFamily, int labelNumber fontSubsetManager = new FontSubsetManager(pageSettings.FontEngine, fontData); } - internal static OpenTypeFont GetFontData(PdfPageSettings pageSettings, string fontName, FontSubFamily subFamily) - { - return pageSettings.FontEngine.LoadFont(fontName,subFamily); - } - - //Get font data from fontResources. If font does not exsist, add it to fontResources. - //internal static OpenTypeFont GetFontResourceData(Dictionary fontResources, PdfPageSettings pageSettings, PdfTextFormat FontData) - //{ - // if (!fontResources.ContainsKey(FontData.FullFontName)) - // { - // int label = 1; - // if (fontResources.Count > 0) - // { - // label = fontResources.Last().Value.labelNumber + 1; - // } - // PdfFontResource fr = new PdfFontResource(FontData.FontName, FontData.SubFamily, label, pageSettings); - // fontResources.Add(FontData.FullFontName, fr); - // fontResources.Last().Value.fontData = GetFontData(pageSettings, FontData.FontName, FontData.SubFamily); - // } - // return fontResources[FontData.FullFontName].fontData; - //} - //Get the Font Descriptor object to write in PDF. internal PdfFontDescriptor GetFontDescriptorObject(int objectNumber, int version = 0) { @@ -192,7 +166,6 @@ internal PdfCIDFont GetCIDFontObject(int objectNumber, int version = 0) cidSystemInfo.Registry = "Adobe"; cidSystemInfo.Ordering = "Identity"; cidSystemInfo.Supplement = 0; - return new PdfCIDFont(objectNumber, fontData, Gids, CIDFontSubtype.CIDFontType2, cidSystemInfo, "Identity", fontDescObjectNumber); } @@ -207,15 +180,6 @@ internal PdfToUnicodeCMap GetUnicodeCmapObject(int objectNumber, int version = 0 unicodeCMapFontObjectNumber = objectNumber; return new PdfToUnicodeCMap(objectNumber, charactermappings); } - private string ExtractCharactersForGlyph(ShapedGlyph glyph, string textLine) - { - var chars = new System.Text.StringBuilder(); - for (int i = 0; i < glyph.CharCount && glyph.ClusterIndex + i < textLine.Length; i++) - { - chars.Append(textLine[glyph.ClusterIndex + i]); - } - return chars.ToString(); - } internal PdfFontStream GetEmbeddedFontStreamObject(int objectNumber, int version = 0) { @@ -227,11 +191,9 @@ internal PdfCidSet GetCidSet(int objectNumber, int version = 0) { if (!fontData.IsSubset || Gids.Count == 0) return null; - int maxGid = Gids.Max(); int numBytes = (maxGid / 8) + 1; var cidSet = new byte[numBytes]; - foreach (var gid in Gids) { int byteIndex = gid / 8; @@ -241,24 +203,5 @@ internal PdfCidSet GetCidSet(int objectNumber, int version = 0) cidSetObjectNumber = objectNumber; return new PdfCidSet(objectNumber, cidSet, version); } - - internal void CreateGidsAndCharMaps() - { - if (ShapedText != null) - { - foreach (var glyph in ShapedText.Glyphs) - { - Gids.Add(glyph.GlyphId); - if (!charactermappings.ContainsKey(glyph.GlyphId)) - { - var chars = ExtractCharactersForGlyph(glyph, ShapedText.OriginalText); - if (!string.IsNullOrEmpty(chars)) - { - charactermappings.Add(glyph.GlyphId, chars); - } - } - } - } - } } } diff --git a/src/EPPlus.Export.Pdf/Resources/PdfPatternResource.cs b/src/EPPlus.Export.Pdf/Resources/PdfPatternResource.cs index 7b8e939348..58a4a7aace 100644 --- a/src/EPPlus.Export.Pdf/Resources/PdfPatternResource.cs +++ b/src/EPPlus.Export.Pdf/Resources/PdfPatternResource.cs @@ -26,7 +26,8 @@ internal class PdfPatternResource : PdfResource // single source of geometry, so BBox and the tiling step are 8x8 for every // pattern. private static readonly double[] PatternBBox = new double[] { 0d, 0d, 8d, 8d }; - private const double PatternStep = 8d; + private const double PatternStepX = 8d; + private const double PatternStepY = 8d; // The 8x8 pattern space is scaled down to match the physical tile size // Excel uses (0.75 pt for a full tile, i.e. 0.75 / 8 per pattern unit). @@ -45,23 +46,20 @@ public PdfPatternResource(int labelNumber, PdfCellFillData cellFillData) public PdfPattern GetPatternObject(int objectNumber, int version = 0) { this.objectNumber = objectNumber; - - // None and Solid are not patterns; they are handled as special cases + // None and Solid are not patterns; they are handled as special cases // elsewhere in the export. if (CellFillData.PatternStyle == ExcelFillStyle.None || CellFillData.PatternStyle == ExcelFillStyle.Solid) { return null; } - ExcelPatternMask mask; if (!TryGetMask(CellFillData.PatternStyle, out mask)) { return null; } - var fill = new PdfPatternMaskFill(mask, CellFillData.PatternColor, CellFillData.BackgroundColor); - var pattern = new PdfTilingPattern(objectNumber, fill, PatternBBox, PatternStep, PatternStep, version); + var pattern = new PdfTilingPattern(objectNumber, fill, PatternBBox, PatternStepX, PatternStepY, version); pattern.Matrix = PatternMatrix; return pattern; } diff --git a/src/EPPlus/Export/PdfExport/Data/PageData.cs b/src/EPPlus/Export/PdfExport/Data/PageData.cs index 795fcb1217..6a7d8c510c 100644 --- a/src/EPPlus/Export/PdfExport/Data/PageData.cs +++ b/src/EPPlus/Export/PdfExport/Data/PageData.cs @@ -25,27 +25,20 @@ internal struct Page public int FromColumn; public int ToRow; public int ToColumn; - public bool HasPrintTitle; public double PrintTitleWidth; public double PrintTitleHeight; - public PdfCellCollection Map; - public PdfHeaderFooterCollection HeaderFooters; - public Dictionary MergedCells; public List PrintTitleCells; public List PrintTitleGridLines; public List PrintTitleHeadings; public List SpillCells; public List PrintTitleBorders; - public double[] RowHeights; - public double HeadingWidth; public double HeadingHeight; - } internal struct Pages diff --git a/src/EPPlus/Export/PdfExport/Data/PdfCell.cs b/src/EPPlus/Export/PdfExport/Data/PdfCell.cs index fee27c6cff..ed31455598 100644 --- a/src/EPPlus/Export/PdfExport/Data/PdfCell.cs +++ b/src/EPPlus/Export/PdfExport/Data/PdfCell.cs @@ -17,7 +17,6 @@ namespace OfficeOpenXml.Export.PdfExport.Data internal class PdfCell : PdfCellBase { public PdfCellStyle CellStyle; - public bool Merged; public PdfCell Main; public ExcelAddressBase MergedAddress; diff --git a/src/EPPlus/Export/PdfExport/Data/PdfCellCollection.cs b/src/EPPlus/Export/PdfExport/Data/PdfCellCollection.cs index 6b39fc7d9b..6fda2ed901 100644 --- a/src/EPPlus/Export/PdfExport/Data/PdfCellCollection.cs +++ b/src/EPPlus/Export/PdfExport/Data/PdfCellCollection.cs @@ -16,13 +16,13 @@ namespace OfficeOpenXml.Export.PdfExport.Data { internal class PdfCellCollection { - private PdfCell[,] Cells; - public readonly int FromRow; public readonly int FromColumn; public readonly int ToRow; public readonly int ToColumn; + private PdfCell[,] Cells; + public PdfCellCollection(int fromRow, int toRow, int fromColumn, int toColumn) { if(fromRow > toRow) throw new ArgumentOutOfRangeException("Invalid row range. toRow must be equal or greater than fromRow"); diff --git a/src/EPPlus/Export/PdfExport/Data/PdfRange.cs b/src/EPPlus/Export/PdfExport/Data/PdfRange.cs index cddbe8b907..74206fe8e5 100644 --- a/src/EPPlus/Export/PdfExport/Data/PdfRange.cs +++ b/src/EPPlus/Export/PdfExport/Data/PdfRange.cs @@ -22,9 +22,6 @@ internal struct RowHeight internal struct PdfRange { - public ExcelRangeBase Range { get; set; } - public bool ExtendColumns { get; set; } - public PdfCellCollection Map { get; set; } public List RowHeights = new List(); public List ColWidths = new List(); public double TotalHeight; @@ -36,6 +33,10 @@ internal struct PdfRange public int PrintTitleRowTo = -1; public int PrintTitleColTo = -1; + public ExcelRangeBase Range { get; set; } + public bool ExtendColumns { get; set; } + public PdfCellCollection Map { get; set; } + public PdfRange(ExcelRangeBase range, bool extendColumns) { Range = range; diff --git a/src/EPPlus/Export/PdfExport/Layout/PdfLayout.cs b/src/EPPlus/Export/PdfExport/Layout/PdfLayout.cs index 0e351f82c5..b5b9b3f719 100644 --- a/src/EPPlus/Export/PdfExport/Layout/PdfLayout.cs +++ b/src/EPPlus/Export/PdfExport/Layout/PdfLayout.cs @@ -19,7 +19,6 @@ Date Author Change using EPPlus.Graphics; using OfficeOpenXml.Export.PdfExport.Data; using OfficeOpenXml.Export.PdfExport.TextShaping; -using OfficeOpenXml.FormulaParsing.Excel.Functions.Text; using OfficeOpenXml.Interfaces.Fonts; using OfficeOpenXml.Style; using System; @@ -70,23 +69,14 @@ public static Transform GetLayout(PdfPageSettings pageSettings, PdfDictionaries return Catalog; } - /*Header and footer notes: - * First header/footer is only used on the first worksheets first page if it exsists - * Then we use each worksheets odd and even respectively. worksheet 1 has its set of odd and even we use and worksheet 2 has it's own set we will use - * Page number does not reset and total number of pages is across all worksheets pages - * starting page number does not affect if first header/footer is used or not. - */ - internal static Transform GetCatalog(PdfPageSettings pageSettings, PdfDictionaries dictionaries, List pdfPages) { Transform Catalog = new Transform(0d, 0d, 0d, 0d); int totalPages = GetTotalPages(pdfPages); for (int i = 0; i < pdfPages.Count; i++) { - var pages = pdfPages[i].Page; int pageNumber = pageSettings.FirstPageNumber; - for (int j = 0; j < pages.Length; j++) { var page = pages[j]; @@ -97,9 +87,6 @@ internal static Transform GetCatalog(PdfPageSettings pageSettings, PdfDictionari pageLayout.PrintTitleWidth = page.PrintTitleWidth; pageLayout.PrintTitleHeight = page.PrintTitleHeight; var drawnMergedCells = new HashSet(); - //var drawnMergedCellsText = new HashSet(); - //PdfContentLayout contentLayout = new PdfContentLayout(0d, 0d, pageSettings.ContentBounds); - //pageLayout.AddChild(contentLayout); double contentStartX = pageSettings.ContentBounds.Left + page.HeadingWidth + page.PrintTitleWidth; double contentStartY = pageSettings.ContentBounds.Top - page.HeadingHeight - page.PrintTitleHeight; if (pageSettings.ShowHeadings && !pdfPages[i].IsCommentsPage) @@ -109,11 +96,8 @@ internal static Transform GetCatalog(PdfPageSettings pageSettings, PdfDictionari AddSpillCells(pageSettings, dictionaries, page, pageLayout); } AddPrintTitleCells(pageSettings, dictionaries, page, pageLayout); - - double y = contentStartY;//pageSettings.ContentBounds.Top; - double x = contentStartX;//pageSettings.ContentBounds.Left; - //create cells & headings if exsists - + double y = contentStartY; + double x = contentStartX; for (int row = pages[j].FromRow; row <= pages[j].ToRow; row++) { double rowHeight = pages[j].RowHeights[row - pages[j].FromRow]; @@ -299,9 +283,7 @@ internal static Transform GetCatalog(PdfPageSettings pageSettings, PdfDictionari pageLayout.AddChild(text); } } - PdfGridlinesLayout.AddGridLines(pageSettings, pages[j], pageLayout, borderOnly: !pageSettings.ShowGridLines || pdfPages[i].IsCommentsPage); - pageLayout.ChildObjects.Sort((a, b) => { int cmp = a.Z.CompareTo(b.Z); @@ -401,7 +383,6 @@ static MergedCellCorners GetCorners(ExcelAddressBase addr, int row, int col) if (row == addr.End.Row && col == addr.End.Column) result |= MergedCellCorners.BottomRight; - return result; } @@ -436,9 +417,6 @@ private static bool NeedsClipping(PdfCell map, Page page, int row, int col) } return false; } - - - private static bool HasBorder(PdfCellStyle cellStyle) { if (cellStyle == null) return false; @@ -460,14 +438,12 @@ private static bool HasBorder(PdfCellStyle cellStyle) private static void AddHeadingCell(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfPageLayout pageLayout, PdfCellStyle headingStyle, string label, double x, double y, double width, double height, string fontName, float fontSize, string namePrefix) { if (width == 0d || height == 0d) return; - var fill = new PdfCellLayout(x, y, width, height); SetFill(dictionaries, headingStyle, label, fill); fill.Name = namePrefix; fill.IsHeading = true; fill.UpdateShadingPositionMatrix(pageSettings); pageLayout.AddChild(fill); - var cell = CreateHeadingPdfCell(pageSettings, dictionaries, label, ExcelHorizontalAlignment.Center, width, height, fontName, fontSize); if (cell.TextLines != null && cell.TextLines.Count > 0) { @@ -485,13 +461,11 @@ private static void AddHeadingCells(PdfPageSettings pageSettings, PdfDictionarie { var headingStyle = new PdfCellStyle(); headingStyle.xfFill = fill; - var cornerFill = new PdfCellLayout(pageSettings.ContentBounds.Left, pageSettings.ContentBounds.Top, headingWidth, headingHeight); SetFill(dictionaries, headingStyle, "", cornerFill); cornerFill.Name = "Heading_Corner"; cornerFill.UpdateShadingPositionMatrix(pageSettings); pageLayout.AddChild(cornerFill); - double x = contentStartX; for (int col = page.FromColumn; col <= page.ToColumn; col++) { @@ -502,7 +476,6 @@ private static void AddHeadingCells(PdfPageSettings pageSettings, PdfDictionarie x, pageSettings.ContentBounds.Top, colWidth, headingHeight, fontName, fontSize, "Heading_Col_" + colLetter); x += colWidth; } - double y = contentStartY; for (int row = page.FromRow; row <= page.ToRow; row++) { @@ -595,7 +568,6 @@ private static void AddPrintTitleCells(PdfPageSettings pageSettings, PdfDictiona foreach (var t in page.PrintTitleCells) { var map = t.Cell; - var fill = new PdfCellLayout(t.X, t.Y, t.Width, t.Height); SetFill(dictionaries, map.CellStyle, map.Text, fill); fill.Name = map.Name; @@ -613,17 +585,6 @@ private static void AddPrintTitleCells(PdfPageSettings pageSettings, PdfDictiona text.SetupClipping(t.ClipX, t.ClipY, t.ClipWidth, t.ClipHeight); pageLayout.AddChild(text); } - - //if (map.TextLines != null && map.TextLines.Count > 0) - //{ - // var text = new PdfCellContentLayout(pageSettings, dictionaries, map, new MergedCellDrawInfo(), t.X, t.Y, t.Width, t.Height); - // text.Name = map.Name; - // text.IsPrintTitle = true; - // text.GidsAndCharMap(dictionaries); - // text.SetupClipping(t.X, t.Y, t.Width, t.Height); - // pageLayout.AddChild(text); - //} - if (!map.Merged && HasBorder(map.CellStyle)) // was: if (HasBorder(map.CellStyle)) { var border = new PdfCellBorderLayout(false, MergedCellCorners.All, new MergedCellDrawInfo(), t.X, t.Y, t.Width, t.Height); @@ -673,9 +634,6 @@ private static void SetBorderStyle(PdfCellStyle style, PdfCellBorderLayout borde (EPPlus.Export.Pdf.Enums.ExcelBorderStyle)diagUpStyle, diagUpColor, (EPPlus.Export.Pdf.Enums.ExcelBorderStyle)diagDownStyle, diagDownColor); } - - - // TODO Count total pages when creating them isntead of looping them here. private static int GetTotalPages(List pdfPages) { int totalPages = 0; @@ -716,12 +674,6 @@ internal static List GetPages(PdfPageSettings pageSettings, PdfWorksheet[ pageSettings.ShowHeadings = savedShowHeadings; pages.IsCommentsPage = true; PagesCollection.Add(pages); - - //var pages = GetNumberOfPages(pageSettings, pdfSheet, ref pdfSheet.CommentsAndNotes); - //pages = AssignRangeToPages(pageSettings, pdfSheet.CommentsAndNotes, pages); - //pages = MapPage(pdfSheet.CommentsAndNotes, pages); - //pages.IsCommentsPage = true; - //PagesCollection.Add(pages); } } return PagesCollection; @@ -849,7 +801,6 @@ private static Page PrecomputePagePrintTitleCells(PdfPageSettings pageSettings, contentColX[c] = cx; cx += RangeColWidth(range, page.FromRow, c); } - // Content-row Y. var contentRowY = new Dictionary(); double cy = pageSettings.ContentBounds.Top - page.HeadingHeight - page.PrintTitleHeight; @@ -858,7 +809,6 @@ private static Page PrecomputePagePrintTitleCells(PdfPageSettings pageSettings, contentRowY[r] = cy; cy -= RangeRowHeight(range, r); } - // Title-column X (left band): just right of the heading gutter. var titleColX = new Dictionary(); if (leftBand) @@ -870,7 +820,6 @@ private static Page PrecomputePagePrintTitleCells(PdfPageSettings pageSettings, tx += RangeColWidth(range, page.FromRow, c); } } - // Title-row Y (top band): just below the heading gutter. var titleRowY = new Dictionary(); if (topBand) @@ -882,7 +831,6 @@ private static Page PrecomputePagePrintTitleCells(PdfPageSettings pageSettings, ty -= RangeRowHeight(range, r); } } - if (topBand) ProcessBandRegionCells(page, range, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, page.FromColumn, page.ToColumn, contentColX, titleRowY); if (leftBand) @@ -890,27 +838,6 @@ private static Page PrecomputePagePrintTitleCells(PdfPageSettings pageSettings, if (topBand && leftBand) ProcessBandRegionCells(page, range, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, titleColX, titleRowY); - //// Top band: title rows × this page's content columns. - //if (topBand) - // for (int r = pdfSheet.PrintTitleRowFrom; r <= pdfSheet.PrintTitleRowTo; r++) - // for (int c = page.FromColumn; c <= page.ToColumn; c++) - // AddPrintTitleDraw(page.PrintTitleCells, range, r, c, - // contentColX[c], titleRowY[r], RangeColWidth(range, page.FromRow, c), RangeRowHeight(range, r)); - - //// Left band: this page's content rows × title columns. - //if (leftBand) - // for (int r = page.FromRow; r <= page.ToRow; r++) - // for (int c = pdfSheet.PrintTitleColFrom; c <= pdfSheet.PrintTitleColTo; c++) - // AddPrintTitleDraw(page.PrintTitleCells, range, r, c, - // titleColX[c], contentRowY[r], RangeColWidth(range, page.FromRow, c), RangeRowHeight(range, r)); - - //// Corner: title rows × title columns. - //if (topBand && leftBand) - // for (int r = pdfSheet.PrintTitleRowFrom; r <= pdfSheet.PrintTitleRowTo; r++) - // for (int c = pdfSheet.PrintTitleColFrom; c <= pdfSheet.PrintTitleColTo; c++) - // AddPrintTitleDraw(page.PrintTitleCells, range, r, c, - // titleColX[c], titleRowY[r], RangeColWidth(range, page.FromRow, c), RangeRowHeight(range, r)); - page.PrintTitleGridLines = new List(); if (pageSettings.ShowGridLines) { @@ -927,27 +854,6 @@ private static Page PrecomputePagePrintTitleCells(PdfPageSettings pageSettings, BandEdgesX(titleColX, pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, range, page.FromRow), BandEdgesY(titleRowY, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, range)); } - - // ---- band gridlines: full grid per region, rendered outside the clip ---- - //page.PrintTitleGridLines = new List(); - //if (pageSettings.ShowGridLines) - //{ - // if (topBand) - // EmitFullGrid(page.PrintTitleGridLines, - // BandEdgesX(contentColX, page.FromColumn, page.ToColumn, range, page.FromRow), - // BandEdgesY(titleRowY, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, range)); - - // if (leftBand) - // EmitFullGrid(page.PrintTitleGridLines, - // BandEdgesX(titleColX, pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, range, page.FromRow), - // BandEdgesY(contentRowY, page.FromRow, page.ToRow, range)); - - // if (topBand && leftBand) - // EmitFullGrid(page.PrintTitleGridLines, - // BandEdgesX(titleColX, pdfSheet.PrintTitleColFrom, pdfSheet.PrintTitleColTo, range, page.FromRow), - // BandEdgesY(titleRowY, pdfSheet.PrintTitleRowFrom, pdfSheet.PrintTitleRowTo, range)); - //} - // ---- band headings: original row numbers / column letters, in the gutter gaps ---- page.PrintTitleHeadings = new List(); if (pageSettings.ShowHeadings) @@ -1021,7 +927,6 @@ private static void ProcessBandRegionCells(Page page, PdfRange range, int fromRo double regBottom = rowY[toRow] - RangeRowHeight(range, toRow); double regW = regRight - regLeft; double regH = regTop - regBottom; - var drawnMerges = new HashSet(); for (int r = fromRow; r <= toRow; r++) { @@ -1035,19 +940,16 @@ private static void ProcessBandRegionCells(Page page, PdfRange range, int fromRo if (!drawnMerges.Add(cell.MergedAddress.Address)) continue; var addr = cell.MergedAddress; var main = cell.Main ?? cell; - int vFromCol = Math.Max(addr._fromCol, fromCol); int vToCol = Math.Min(addr._toCol, toCol); int vFromRow = Math.Max(addr._fromRow, fromRow); int vToRow = Math.Min(addr._toRow, toRow); if (vFromCol > vToCol || vFromRow > vToRow) continue; - double x = colX[vFromCol]; double width = (colX[vToCol] + RangeColWidth(range, page.FromRow, vToCol)) - colX[vFromCol]; double y = rowY[vFromRow]; double height = (rowY[vFromRow] - rowY[vToRow]) + RangeRowHeight(range, vToRow); if (width <= 0d || height <= 0d) continue; - // merged cells don't spill — clip text to the merge itself (matches content) page.PrintTitleCells.Add(new PrintTitleCellDraw { @@ -1087,7 +989,6 @@ private static void ProcessBandRegionCells(Page page, PdfRange range, int fromRo double w = RangeColWidth(range, page.FromRow, c); double h = RangeRowHeight(range, r); if (w <= 0d || h <= 0d) continue; - // non-merged: clip to the band region so text can spill within it but not into content page.PrintTitleCells.Add(new PrintTitleCellDraw { @@ -1111,22 +1012,13 @@ private static void EmitBandGrid(List target, PdfRange range, int from int nr = toRow - fromRow + 1; int nc = toCol - fromCol + 1; if (nr <= 0 || nc <= 0) return; - var spill = BuildBandSpillMask(range, fromRow, toRow, fromCol, toCol, colX); double top = rowY[0], bottom = rowY[nr]; double left = colX[0], right = colX[nc]; - EmitBandFrameH(target, range, top, fromRow, fromCol, nc, colX, CellHasTopBorder); EmitBandFrameH(target, range, bottom, toRow, fromCol, nc, colX, CellHasBottomBorder); EmitBandFrameV(target, range, left, fromCol, fromRow, nr, rowY, CellHasLeftBorder); EmitBandFrameV(target, range, right, toCol, fromRow, nr, rowY, CellHasRightBorder); - - // region frame (outer edges; harmless overlap with page frame / heading borders / band↔content seam) - //target.Add(new GridLine(left, top, left, bottom)); - //target.Add(new GridLine(right, top, right, bottom)); - //target.Add(new GridLine(left, top, right, top)); - //target.Add(new GridLine(left, bottom, right, bottom)); - // interior verticals — suppress where a merge spans the gap for (int gi = 1; gi < nc; gi++) { @@ -1143,15 +1035,8 @@ private static void EmitBandGrid(List target, PdfRange range, int from if (!block) { if (runStart == null) runStart = rowY[ri]; runEnd = rowY[ri + 1]; } else if (runStart != null) { target.Add(new GridLine(x, runStart.Value, x, runEnd)); runStart = null; } } - //for (int ri = 0; ri < nr; ri++) - //{ - // int r = fromRow + ri; - // if (!SameMerge(range, r, leftCol, r, rightCol)) { if (runStart == null) runStart = rowY[ri]; runEnd = rowY[ri + 1]; } - // else if (runStart != null) { target.Add(new GridLine(x, runStart.Value, x, runEnd)); runStart = null; } - //} if (runStart != null) target.Add(new GridLine(x, runStart.Value, x, runEnd)); } - // interior horizontals — suppress where a merge spans the gap for (int gj = 1; gj < nr; gj++) { @@ -1165,7 +1050,6 @@ private static void EmitBandGrid(List target, PdfRange range, int from CellHasBottomBorder(RangeCell(range, topRow, c)) || CellHasTopBorder(RangeCell(range, bottomRow, c)); if (!block) { if (runStart == null) runStart = colX[ci]; runEnd = colX[ci + 1]; } - //if (!SameMerge(range, topRow, c, bottomRow, c)) { if (runStart == null) runStart = colX[ci]; runEnd = colX[ci + 1]; } else if (runStart != null) { target.Add(new GridLine(runStart.Value, y, runEnd, y)); runStart = null; } } if (runStart != null) target.Add(new GridLine(runStart.Value, y, runEnd, y)); @@ -1179,11 +1063,9 @@ private static void EmitBandGrid(List target, PdfRange range, int from var blocked = new bool[Math.Max(nr, 1), Math.Max(nc, 1)]; // [ri, g], g in 1..nc-1 if (nr <= 0 || nc <= 1) return blocked; int repRow = fromRow; - for (int ri = 0; ri < nr; ri++) { int row = fromRow + ri; - // (a) cells spilling within the band region for (int ci = 0; ci < nc; ci++) { @@ -1205,7 +1087,6 @@ private static void EmitBandGrid(List target, PdfRange range, int from BandMarkLeft(range, row, ci, fromCol, colX, half, ri, blocked); } } - // (b) spill entering from the LEFT of the region (left/general/center → spilling right in) double lx = colX[0]; for (int c = fromCol - 1; c >= range.Range._fromCol; c--) @@ -1306,26 +1187,6 @@ private static double[] BandEdgesY(Dictionary topY, int fromRow, in return arr; } - private static void EmitFullGrid(List target, double[] colX, double[] rowY) - { - int nc = colX.Length - 1; - int nr = rowY.Length - 1; - if (nc < 1 || nr < 1) return; - double top = rowY[0], bottom = rowY[nr]; - double left = colX[0], right = colX[nc]; - for (int i = 0; i <= nc; i++) target.Add(new GridLine(colX[i], top, colX[i], bottom)); // verticals - for (int j = 0; j <= nr; j++) target.Add(new GridLine(left, rowY[j], right, rowY[j])); // horizontals - } - - private static void AddPrintTitleDraw(List sink, PdfRange range, int row, int col, double x, double y, double w, double h) - { - if (w <= 0d || h <= 0d) return; - var cell = RangeCell(range, row, col); - if (cell == null) return; - if (cell.Merged) return; // merged title cells: handled in a later sub-step - sink.Add(new PrintTitleCellDraw { Cell = cell, X = x, Y = y, Width = w, Height = h }); - } - private static PdfCell RangeCell(PdfRange range, int row, int col) { if (row < range.Range._fromRow || row > range.Range._toRow) return null; @@ -1401,9 +1262,6 @@ internal static Pages AssignRangeToPages(PdfPageSettings pageSettings, PdfRange if (pageSettings.PageOrders == PageOrders.DownThenOver) { - //foreach (var colSeg in colSegments) - // foreach (var rowSeg in rowSegments) - // pages.Page[i++] = new Page { FromColumn = colSeg.From, ToColumn = colSeg.To, FromRow = rowSeg.From, ToRow = rowSeg.To }; for (int ci = 0; ci < colSegments.Count; ci++) for (int ri = 0; ri < rowSegments.Count; ri++) pages.Page[i++] = new Page @@ -1420,9 +1278,6 @@ internal static Pages AssignRangeToPages(PdfPageSettings pageSettings, PdfRange } else //if (pageSettings.PageOrders == PageOrders.OverThenDown) { - //foreach (var rowSeg in rowSegments) - // foreach (var colSeg in colSegments) - // pages.Page[i++] = new Page { FromColumn = colSeg.From, ToColumn = colSeg.To, FromRow = rowSeg.From, ToRow = rowSeg.To }; for (int ri = 0; ri < rowSegments.Count; ri++) for (int ci = 0; ci < colSegments.Count; ci++) pages.Page[i++] = new Page @@ -1437,14 +1292,6 @@ internal static Pages AssignRangeToPages(PdfPageSettings pageSettings, PdfRange PrintTitleHeight = (range.PrintTitleRowTo >= 0 && rowSegments[ri].From > range.PrintTitleRowTo) ? range.PrintTitleHeight : 0d, }; } - - //for (int k = 0; k < pages.Page.Length; k++) - //{ - // var p = pages.Page[k]; - // p.HeadingWidth = addedWidth; - // p.HeadingHeight = addedHeight; - // pages.Page[k] = p; - //} pdfPages = pages; return pdfPages; } @@ -1461,14 +1308,11 @@ private static List GetColumnSegments(PdfPageSettings pageSettings, var segments = new List(); int segStartIdx = 0; double width = 0d; - for (int col = 0; col < range.ColWidths.Count; col++) { int actualCol = range.Range._fromCol + col; - bool reserveTitle = titleWidth > 0d && printTitleColTo >= 0 && (range.Map.FromColumn + segStartIdx) > printTitleColTo; double effectiveAdded = addedWidth + (reserveTitle ? titleWidth : 0d); - // Content-bounds overflow: col doesn't fit, end segment before it and reprocess. if (width + range.ColWidths[col] + effectiveAdded >= pageSettings.ContentBounds.Width) { @@ -1478,9 +1322,7 @@ private static List GetColumnSegments(PdfPageSettings pageSettings, col--; // reprocess this col as the first col of the next segment continue; } - width += range.ColWidths[col]; - // Explicit page break: col is included on this page, next segment starts after it. if (worksheet.Column(actualCol).PageBreak) { @@ -1489,11 +1331,9 @@ private static List GetColumnSegments(PdfPageSettings pageSettings, width = 0d; } } - // Remaining cols form the last segment. if (segStartIdx < range.ColWidths.Count) segments.Add(new PageSegment(range.Map.FromColumn + segStartIdx, range.Map.FromColumn + range.ColWidths.Count - 1)); - return segments; } @@ -1502,14 +1342,11 @@ private static List GetRowSegments(PdfPageSettings pageSettings, Pd var segments = new List(); int segStartIdx = 0; double height = 0d; - for (int row = 0; row < range.RowHeights.Count; row++) { int actualRow = range.Range._fromRow + row; - bool reserveTitle = titleHeight > 0d && printTitleRowTo >= 0 && (range.Map.FromRow + segStartIdx) > printTitleRowTo; double effectiveAdded = addedHeight + (reserveTitle ? titleHeight : 0d); - // Content-bounds overflow: row doesn't fit, end segment before it and reprocess. if (height + range.RowHeights[row].Height + effectiveAdded >= pageSettings.ContentBounds.Height) { @@ -1519,9 +1356,7 @@ private static List GetRowSegments(PdfPageSettings pageSettings, Pd row--; // reprocess this row as the first row of the next segment continue; } - height += range.RowHeights[row].Height; - // Explicit page break: row is included on this page, next segment starts after it. if (worksheet.Row(actualRow).PageBreak) { @@ -1530,11 +1365,9 @@ private static List GetRowSegments(PdfPageSettings pageSettings, Pd height = 0d; } } - // Remaining rows form the last segment. if (segStartIdx < range.RowHeights.Count) segments.Add(new PageSegment(range.Map.FromRow + segStartIdx, range.Map.FromRow + range.RowHeights.Count - 1)); - return segments; } @@ -1587,16 +1420,14 @@ private static void SubstitutePageNumbers(PdfPageSettings pageSettings, PdfDicti foreach (var idx in hf.NumberOfPagesIndexes) hf.Content.TextFragments[idx].Text = totalPages.ToString(); } - PdfTextShaper.ShapeText(pageSettings, dictionaries, (PdfCell)hf.Content); + PdfTextShaper.ShapeText(pageSettings, dictionaries, hf.Content); } private static void AddIncomingSpill(Page page, PdfRange range, int fromRow, int toRow, int windowFromCol, int windowToCol, double windowOriginX, double windowOriginY, bool isPrintTitle) { if (page.SpillCells == null) return; // initialised by the caller - double windowRightX = windowOriginX; for (int c = windowFromCol; c <= windowToCol; c++) windowRightX += RangeColWidth(range, page.FromRow, c); - double rowTop = windowOriginY; for (int r = fromRow; r <= toRow; r++) { @@ -1604,7 +1435,6 @@ private static void AddIncomingSpill(Page page, PdfRange range, int fromRow, int double y = rowTop; rowTop -= rowH; if (rowH <= 0d) continue; - // spill entering from the LEFT (left/general/center) double lx = windowOriginX; for (int c = windowFromCol - 1; c >= range.Range._fromCol; c--) @@ -1618,8 +1448,7 @@ private static void AddIncomingSpill(Page page, PdfRange range, int fromRow, int var hal = cell.ContentAligmnet?.HorizontalAlignment ?? (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.General; double rightExtent = (hal == (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Center) ? lx + w / 2d + cell.TotalTextLength / 2d : - (hal == (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Right) ? lx + w : - lx + cell.TotalTextLength; + (hal == (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Right) ? lx + w : lx + cell.TotalTextLength; if (rightExtent > windowOriginX) { double clipRight = FirstBlockedX(page, range, r, windowFromCol, windowToCol, windowOriginX, windowRightX, fromLeft: true); @@ -1639,7 +1468,6 @@ private static void AddIncomingSpill(Page page, PdfRange range, int fromRow, int } break; } - // spill entering from the RIGHT (right/center) double rx = windowRightX; for (int c = windowToCol + 1; c <= range.Range._toCol; c++) @@ -1652,8 +1480,7 @@ private static void AddIncomingSpill(Page page, PdfRange range, int fromRow, int var hal = cell.ContentAligmnet?.HorizontalAlignment ?? (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.General; double leftExtent = (hal == (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Center) ? rx + w / 2d - cell.TotalTextLength / 2d : - (hal == (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Right) ? rx + w - cell.TotalTextLength : - rx; + (hal == (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Right) ? rx + w - cell.TotalTextLength : rx; if (leftExtent < windowRightX) { double clipLeft = FirstBlockedX(page, range, r, windowFromCol, windowToCol, windowOriginX, windowRightX, fromLeft: false); @@ -1720,16 +1547,19 @@ private static bool CellHasRightBorder(PdfCell cell) var cs = cell?.CellStyle; if (cs == null) return false; return (cs.xfRight != null && cs.xfRight.Style != ExcelBorderStyle.None) || (cs.dxfRight?.HasValue ?? false); } + private static bool CellHasLeftBorder(PdfCell cell) { var cs = cell?.CellStyle; if (cs == null) return false; return (cs.xfLeft != null && cs.xfLeft.Style != ExcelBorderStyle.None) || (cs.dxfLeft?.HasValue ?? false); } + private static bool CellHasTopBorder(PdfCell cell) { var cs = cell?.CellStyle; if (cs == null) return false; return (cs.xfTop != null && cs.xfTop.Style != ExcelBorderStyle.None) || (cs.dxfTop?.HasValue ?? false); } + private static bool CellHasBottomBorder(PdfCell cell) { var cs = cell?.CellStyle; if (cs == null) return false; @@ -1761,6 +1591,5 @@ private static void EmitBandFrameV(List target, PdfRange range, double } if (rs != null) target.Add(new GridLine(x, rs.Value, x, re)); } - } } diff --git a/src/EPPlus/Export/PdfExport/RowResize/PdfCalculateRowHeight.cs b/src/EPPlus/Export/PdfExport/RowResize/PdfCalculateRowHeight.cs index 09d3d82092..0d1421fd8e 100644 --- a/src/EPPlus/Export/PdfExport/RowResize/PdfCalculateRowHeight.cs +++ b/src/EPPlus/Export/PdfExport/RowResize/PdfCalculateRowHeight.cs @@ -24,7 +24,6 @@ public static void ResizeRowHeights(PdfWorksheet pdfSheet) ResizeRange(ref range); pdfSheet.Ranges[r] = range; } - if (pdfSheet.CommentsAndNotes.Map != null) { var cnRange = pdfSheet.CommentsAndNotes; @@ -36,11 +35,9 @@ public static void ResizeRowHeights(PdfWorksheet pdfSheet) public static void ResizeRange(ref PdfRange range) { double newTotalHeight = 0d; - for (int rowIdx = 0; rowIdx < range.RowHeights.Count; rowIdx++) { var rowHeight = range.RowHeights[rowIdx]; - if (rowHeight.Height == 0d) continue; @@ -49,16 +46,13 @@ public static void ResizeRange(ref PdfRange range) newTotalHeight += rowHeight.Height; continue; } - int row = range.Range._fromRow + rowIdx; double maxRequired = rowHeight.Height; bool hasWrappedCell = false; - for (int colIdx = 0; colIdx < range.ColWidths.Count; colIdx++) { int col = range.Range._fromCol + colIdx; var cell = range.Map[row, col]; - if (cell == null || cell.Hidden) continue; if (cell.Merged) @@ -75,7 +69,6 @@ public static void ResizeRange(ref PdfRange range) if (required > maxRequired) maxRequired = required; } - if (hasWrappedCell) { rowHeight.Height = maxRequired; @@ -84,7 +77,6 @@ public static void ResizeRange(ref PdfRange range) newTotalHeight += rowHeight.Height; } range.TotalHeight = newTotalHeight; - } private static double GetRequiredHeightFromLines(PdfCell cell) diff --git a/src/EPPlus/Export/PdfExport/TextMapping/PdfCommentsAndNotes.cs b/src/EPPlus/Export/PdfExport/TextMapping/PdfCommentsAndNotes.cs index 92e949b5ed..defb31548d 100644 --- a/src/EPPlus/Export/PdfExport/TextMapping/PdfCommentsAndNotes.cs +++ b/src/EPPlus/Export/PdfExport/TextMapping/PdfCommentsAndNotes.cs @@ -27,6 +27,7 @@ public PdfCommentsAndNotes(ExcelComment comment) { Comment = comment; } + public PdfCommentsAndNotes(ExcelThreadedCommentThread tComment) { ThreadedComment = tComment; diff --git a/src/EPPlus/Export/PdfExport/TextMapping/PdfTextMap.cs b/src/EPPlus/Export/PdfExport/TextMapping/PdfTextMap.cs index 6832d3ff57..e09939f34b 100644 --- a/src/EPPlus/Export/PdfExport/TextMapping/PdfTextMap.cs +++ b/src/EPPlus/Export/PdfExport/TextMapping/PdfTextMap.cs @@ -143,7 +143,6 @@ private static void HandleMergedCell(PdfPageSettings pageSettings, PdfDictionari GetBorderStyles(main, cellStyle, mainCell); GetFillStyles(main, cellStyle); GetFontStyle(main, cellStyle); - mainCell.ContentAligmnet = GetContentAlignment(main); if (!string.IsNullOrEmpty(main.Text)) { @@ -169,7 +168,6 @@ private static void GetFillStyles(ExcelRangeBase cell, PdfCellStyle cellStyle) { // Sort ascending — priority 1 beats priority 2, etc. var ordered = cf.OrderBy(r => r.Priority); - foreach (var rule in ordered) { // Use the core per-rule evaluator (the same one the HTML exporter @@ -196,14 +194,12 @@ private static void GetFillStyles(ExcelRangeBase cell, PdfCellStyle cellStyle) if (rule.StopIfTrue) break; } } - //Table var tables = cell.Worksheet.Tables.GetIntersectingRanges(cell); if (tables.Count > 0) { var table = tables[0].Value; var range = table.Range; - int tableRow = 0; int tableCol = 0; ExcelTableNamedStyle tableStyle; @@ -254,14 +250,6 @@ private static void GetFillStyles(ExcelRangeBase cell, PdfCellStyle cellStyle) private static void GetBorderStyles(ExcelRangeBase cell, PdfCellStyle cellStyle, PdfCell pcell) { - - /* Kika på varje del av border top bottom left right - * om cell har top använd den - * om cell inte har border, gå igenom prio ordning på tabell borders och använd WholeTable om null. - * om cellein i tabellen är i mitten av tabellen använd horizontal och vertical border istället. - * I fallet top så ska om vi är i header row eller om cell fromrow är samma som table fromrow så ska top border vara top border. annar är det horizontal som gäller - * Glöm ej vertical border om fromcol är samma som tabell fromcol. - */ if (cell != null) { cellStyle.xfTop = cell.Style.Border.Top; @@ -451,19 +439,13 @@ private static PdfCellAlignmentData GetContentAlignment(ExcelRangeBase cell) return contentAlignment; } - internal static long subFamilyTicks; - internal static long addFontTicks; - internal static int subFamilyCount; - private static List GetTextFragments(PdfPageSettings pageSettings, PdfDictionaries dictionaries, ExcelRangeBase cell, PdfCellStyle cellStyle) { bool dxfBold, dxfItalic, dxfStrike, dxfUnderline; ExcelUnderLineType dxfUnderLineType; System.Drawing.Color? dxfColor; ReadDxfFontOverrides(cellStyle, out dxfBold, out dxfItalic, out dxfStrike, out dxfUnderline, out dxfUnderLineType, out dxfColor); - string forcedText = ResolveErrorText(pageSettings, cell); - if (cell.IsRichText) { return GetTextFragmentsFromRichText(pageSettings, dictionaries, cell.RichText, forcedText, @@ -483,10 +465,8 @@ private static List GetTextFragmentsFromRichText(PdfPageSettings p var textFrag = new TextFragment(); textFrag.Font = new RichTextFormatSimple(); textFrag.Text = forcedText == null ? rt.Text : forcedText; - textFrag.Font.Family = rt.FontName; textFrag.Font.Size = rt.Size; - textFrag.RichTextOptions.Bold = rt.Bold || dxfBold; textFrag.RichTextOptions.Italic = rt.Italic || dxfItalic; textFrag.RichTextOptions.UnderlineType = MapUnderlineType(rt.UnderLineType); @@ -494,9 +474,7 @@ private static List GetTextFragmentsFromRichText(PdfPageSettings p textFrag.RichTextOptions.SuperScript = rt.VerticalAlign == ExcelVerticalAlignmentFont.Superscript; textFrag.RichTextOptions.SubScript = rt.VerticalAlign == ExcelVerticalAlignmentFont.Subscript; textFrag.RichTextOptions.FontColor = dxfColor ?? rt.Color; - textFrag.Font.SubFamily = ComputeFontStyle(textFrag); - textFragments.Add(textFrag); dictionaries.AddFont(pageSettings, textFrag.Font.Family, textFrag.Font.SubFamily, textFrag.Text); } @@ -508,17 +486,13 @@ private static List GetTextFragmentsFromCellStyle(PdfPageSettings { var font = cell.Style.Font; var textFragments = new List(1); - var textFrag = new TextFragment(); textFrag.Font = new RichTextFormatSimple(); textFrag.Text = forcedText == null ? cell.Text : forcedText; - textFrag.Font.Family = font.Name; textFrag.Font.Size = font.Size; - textFrag.RichTextOptions.Bold = font.Bold || dxfBold; textFrag.RichTextOptions.Italic = font.Italic || dxfItalic; - // Cell-style underline; dxf overrides only when the cell itself is not underlined. ExcelUnderLineType underLineType; if (font.UnderLine) @@ -534,14 +508,11 @@ private static List GetTextFragmentsFromCellStyle(PdfPageSettings underLineType = ExcelUnderLineType.None; } textFrag.RichTextOptions.UnderlineType = MapUnderlineType(underLineType); - textFrag.RichTextOptions.StrikeType = (font.Strike || dxfStrike) ? 2 : 1; textFrag.RichTextOptions.SuperScript = font.VerticalAlign == ExcelVerticalAlignmentFont.Superscript; textFrag.RichTextOptions.SubScript = font.VerticalAlign == ExcelVerticalAlignmentFont.Subscript; textFrag.RichTextOptions.FontColor = dxfColor ?? font.Color.ToColor(); - textFrag.Font.SubFamily = ComputeFontStyle(textFrag); - textFragments.Add(textFrag); dictionaries.AddFont(pageSettings, textFrag.Font.Family, textFrag.Font.SubFamily, textFrag.Text); return textFragments; @@ -598,7 +569,7 @@ private static FontSubFamily ComputeFontStyle(TextFragment textFrag) return FontSubFamily.Regular; } - public static PdfCellAlignmentData GetAlignmentData(PdfHeaderFooter headerFooter) + internal static PdfCellAlignmentData GetAlignmentData(PdfHeaderFooter headerFooter) { var contentAlignment = new PdfCellAlignmentData(); switch (headerFooter.Alignment) @@ -616,7 +587,7 @@ public static PdfCellAlignmentData GetAlignmentData(PdfHeaderFooter headerFooter return contentAlignment; } - public static PdfHeaderFooter GetTextFormats(PdfPageSettings pageSettings, PdfDictionaries dictionaries, ExcelWorksheet ws, ExcelHeaderFooterTextCollection textCollection, HeaderFooterType type, HeaderFooterAlignment alignment, HeaderFooterSection section) + internal static PdfHeaderFooter GetTextFormats(PdfPageSettings pageSettings, PdfDictionaries dictionaries, ExcelWorksheet ws, ExcelHeaderFooterTextCollection textCollection, HeaderFooterType type, HeaderFooterAlignment alignment, HeaderFooterSection section) { if (textCollection == null || textCollection.Count <= 1) return null; var ns = ws.Workbook.Styles.GetNormalStyle(); @@ -628,10 +599,8 @@ public static PdfHeaderFooter GetTextFormats(PdfPageSettings pageSettings, PdfDi var hf = textCollection[i]; var textFrag = new TextFragment(); textFrag.Font = new RichTextFormatSimple(); - textFrag.Font.Family = string.IsNullOrEmpty(hf.FontName) ? ns.Style.Font.Name : hf.FontName; textFrag.Font.Size = hf.FontSize == null ? ns.Style.Font.Size : (float)hf.FontSize; - textFrag.RichTextOptions.Bold = hf.Bold; textFrag.RichTextOptions.Italic = hf.Italic; //underline @@ -644,13 +613,7 @@ public static PdfHeaderFooter GetTextFormats(PdfPageSettings pageSettings, PdfDi textFrag.RichTextOptions.UnderlineType = hf.DoubleUnderline ? 4 : textFrag.RichTextOptions.UnderlineType; textFrag.RichTextOptions.StrikeType = hf.Striketrough ? 2 : 1; textFrag.RichTextOptions.FontColor = hf.Color; - textFrag.Font.SubFamily = ComputeFontStyle(textFrag); - //(textFrag.RichTextOptions.Bold ? MeasurementFontStyles.Bold : 0) | - //(textFrag.RichTextOptions.Italic ? MeasurementFontStyles.Italic : 0) | - //(textFrag.RichTextOptions.UnderlineType != 12 ? MeasurementFontStyles.Underline : 0) | - //(textFrag.RichTextOptions.StrikeType > 1 ? MeasurementFontStyles.Strikeout : 0); - var text = string.Empty; switch (hf.FormatCode) { @@ -681,9 +644,7 @@ public static PdfHeaderFooter GetTextFormats(PdfPageSettings pageSettings, PdfDi text += hf.Text; break; } - textFrag.Text = text; - textFragments.Add(textFrag); dictionaries.AddFont(pageSettings, textFrag.Font.Family, textFrag.Font.SubFamily, textFrag.Text); if (NumberOfPagesIndexes.Count > 0 || PageNumberIndexes.Count > 0) dictionaries.AddFont(pageSettings, textFrag.Font.Family, textFrag.Font.SubFamily, "1234567890"); @@ -696,7 +657,7 @@ public static PdfHeaderFooter GetTextFormats(PdfPageSettings pageSettings, PdfDi /// /// The worksheet to check. /// The number of columns to add. - public static int AddColumnsForNonWrappedText(PdfPageSettings pageSettings, ExcelWorksheet ws, PdfWorksheet pdfSheet) + internal static int AddColumnsForNonWrappedText(PdfPageSettings pageSettings, ExcelWorksheet ws, PdfWorksheet pdfSheet) { int columnsToAdd = 0; var catalog = new PdfCatalog(); @@ -717,7 +678,7 @@ public static int AddColumnsForNonWrappedText(PdfPageSettings pageSettings, Exce return columnsToAdd; } - public static ExcelDxfBorderItem GetTopBorderItem(ExcelRangeBase cell, ExcelBorderItem xfBorder, ExcelTable table, ExcelTableNamedStyle tableStyle) + internal static ExcelDxfBorderItem GetTopBorderItem(ExcelRangeBase cell, ExcelBorderItem xfBorder, ExcelTable table, ExcelTableNamedStyle tableStyle) { var range = table.Range; int tableRow = cell._fromRow - range._fromRow; @@ -805,7 +766,7 @@ public static ExcelDxfBorderItem GetTopBorderItem(ExcelRangeBase cell, ExcelBord } return top; } - public static ExcelDxfBorderItem GetBottomBorderItem(ExcelRangeBase cell, ExcelBorderItem xfBorder, ExcelTable table, ExcelTableNamedStyle tableStyle) + internal static ExcelDxfBorderItem GetBottomBorderItem(ExcelRangeBase cell, ExcelBorderItem xfBorder, ExcelTable table, ExcelTableNamedStyle tableStyle) { var range = table.Range; int tableRow = cell._fromRow - range._fromRow; @@ -892,7 +853,7 @@ public static ExcelDxfBorderItem GetBottomBorderItem(ExcelRangeBase cell, ExcelB } return bottom; } - public static ExcelDxfBorderItem GetLeftBorderItem(ExcelRangeBase cell, ExcelBorderItem xfBorder, ExcelTable table, ExcelTableNamedStyle tableStyle) + internal static ExcelDxfBorderItem GetLeftBorderItem(ExcelRangeBase cell, ExcelBorderItem xfBorder, ExcelTable table, ExcelTableNamedStyle tableStyle) { var range = table.Range; int tableRow = cell._fromRow - range._fromRow; @@ -979,7 +940,7 @@ public static ExcelDxfBorderItem GetLeftBorderItem(ExcelRangeBase cell, ExcelBor } return left; } - public static ExcelDxfBorderItem GetRightBorderItem(ExcelRangeBase cell, ExcelBorderItem xfBorder, ExcelTable table, ExcelTableNamedStyle tableStyle) + internal static ExcelDxfBorderItem GetRightBorderItem(ExcelRangeBase cell, ExcelBorderItem xfBorder, ExcelTable table, ExcelTableNamedStyle tableStyle) { var range = table.Range; int tableRow = cell._fromRow - range._fromRow; diff --git a/src/EPPlus/Export/PdfExport/TextShaping/PdfTextShaper.cs b/src/EPPlus/Export/PdfExport/TextShaping/PdfTextShaper.cs index c501f7212b..f179868d6f 100644 --- a/src/EPPlus/Export/PdfExport/TextShaping/PdfTextShaper.cs +++ b/src/EPPlus/Export/PdfExport/TextShaping/PdfTextShaper.cs @@ -33,7 +33,6 @@ internal static class PdfTextShaper public static void CollectText(PdfDictionaries dictionaries, PdfCell cell) { if (cell == null || cell.TextFragments == null) return; - for (int i = 0; i < cell.TextFragments.Count; i++) { var tf = cell.TextFragments[i]; @@ -48,41 +47,33 @@ public static void ShapeText(PdfPageSettings pageSettings, PdfDictionaries dicti var totalTextLength = 0d; var maxLineHeight = 0d; if (cell == null || cell.TextFragments == null) return; - cell.ShapedTexts = new List(); - for (int i = 0; i < cell.TextFragments.Count; i++) { var tf = cell.TextFragments[i]; cell.ShapedTexts.Add(new PdfShapedText()); var st = cell.ShapedTexts[i]; - if (!dictionaries.ShapedProviders.TryGetValue(tf.FullFontName, out var provider)) { continue; } st.FontProvider = provider; - if (!shaperCache.TryGetValue(st.FontProvider, out var shaper)) { shaper = new TextShaper(st.FontProvider); shaperCache[st.FontProvider] = shaper; } - if (!layoutEngineCache.TryGetValue(st.FontProvider, out var layoutEngine)) { layoutEngine = new TextLayoutEngine(shaper); layoutEngineCache[st.FontProvider] = layoutEngine; } - var options = ShapingOptions.Default; options.ApplyPositioning = true; options.ApplySubstitutions = true; - var shaped = shaper.Shape(tf.Text, options); var usedFonts = shaper.GetUsedFonts().ToList(); var fontIdMap = new Dictionary(); - for (byte fontId = 0; fontId < usedFonts.Count; fontId++) { var font = usedFonts[fontId]; @@ -97,19 +88,15 @@ public static void ShapeText(PdfPageSettings pageSettings, PdfDictionaries dicti } fontIdMap[fontId] = dictionaries.Fonts[font.FullName].Label; } - cell.TextLayoutEngine = layoutEngine; st.ShapedText = shaped; - totalTextLength += st.ShapedText.GetWidthInPoints((float)tf.Font.Size); maxLineHeight = Math.Max(st.ShapedText.GetLineHeightInPoints((float)tf.Font.Size), maxLineHeight); - st.FontIdMap = fontIdMap; st.UsedFonts = usedFonts; cell.TextFragments[i] = tf; cell.ShapedTexts[i] = st; } - if (cell.TextLayoutEngine != null) { double wrapWidth = (cell.Merged && cell.Main == null) ? cell.Width : cell.ColumnWidth; @@ -117,7 +104,72 @@ public static void ShapeText(PdfPageSettings pageSettings, PdfDictionaries dicti ? cell.TextLayoutEngine.WrapRichTextLineCollection(cell.TextFragments, wrapWidth) : cell.TextLayoutEngine.WrapRichTextLineCollection(cell.TextFragments, double.MaxValue); } + cell.TotalTextLength = totalTextLength; + } + // Pass 2: shape text using already-built providers from PdfDictionaries.ShapedProviders + public static void ShapeText(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfCellBase cell) + { + var totalTextLength = 0d; + var maxLineHeight = 0d; + if (cell == null || cell.TextFragments == null) return; + cell.ShapedTexts = new List(); + for (int i = 0; i < cell.TextFragments.Count; i++) + { + var tf = cell.TextFragments[i]; + cell.ShapedTexts.Add(new PdfShapedText()); + var st = cell.ShapedTexts[i]; + if (!dictionaries.ShapedProviders.TryGetValue(tf.FullFontName, out var provider)) + { + continue; + } + st.FontProvider = provider; + if (!shaperCache.TryGetValue(st.FontProvider, out var shaper)) + { + shaper = new TextShaper(st.FontProvider); + shaperCache[st.FontProvider] = shaper; + } + if (!layoutEngineCache.TryGetValue(st.FontProvider, out var layoutEngine)) + { + layoutEngine = new TextLayoutEngine(shaper); + layoutEngineCache[st.FontProvider] = layoutEngine; + } + var options = ShapingOptions.Default; + options.ApplyPositioning = true; + options.ApplySubstitutions = true; + var shaped = shaper.Shape(tf.Text, options); + var usedFonts = shaper.GetUsedFonts().ToList(); + var fontIdMap = new Dictionary(); + for (byte fontId = 0; fontId < usedFonts.Count; fontId++) + { + var font = usedFonts[fontId]; + if (!dictionaries.Fonts.ContainsKey(font.FullName)) + { + int label = dictionaries.Fonts.Count > 0 + ? dictionaries.Fonts.Last().Value.labelNumber + 1 + : 1; + var fontResource = new PdfFontResource(font.FullName, font.NameTable.GetSubfamilyEnum(), label, pageSettings); + fontResource.fontData = font; + dictionaries.Fonts.Add(font.FullName, fontResource); + } + fontIdMap[fontId] = dictionaries.Fonts[font.FullName].Label; + } + cell.TextLayoutEngine = layoutEngine; + st.ShapedText = shaped; + totalTextLength += st.ShapedText.GetWidthInPoints((float)tf.Font.Size); + maxLineHeight = Math.Max(st.ShapedText.GetLineHeightInPoints((float)tf.Font.Size), maxLineHeight); + st.FontIdMap = fontIdMap; + st.UsedFonts = usedFonts; + cell.TextFragments[i] = tf; + cell.ShapedTexts[i] = st; + } + if (cell.TextLayoutEngine != null) + { + double wrapWidth = cell.Width; + cell.TextLines = cell.ContentAligmnet.WrapText + ? cell.TextLayoutEngine.WrapRichTextLineCollection(cell.TextFragments, wrapWidth) + : cell.TextLayoutEngine.WrapRichTextLineCollection(cell.TextFragments, double.MaxValue); + } cell.TotalTextLength = totalTextLength; } } From 818499f6b5fff75355dfaed5facb3c4625bf5e36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Thu, 25 Jun 2026 15:10:43 +0200 Subject: [PATCH 40/49] test --- src/EPPlus.Export.Pdf.Tests/PdfTests.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs index f814f4d22d..1b7c50e41f 100644 --- a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs +++ b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs @@ -215,24 +215,24 @@ public void PerfTest() [TestMethod] // works as expected. //[DataRow("PDFTest.xlsx", "C:\\epplustest\\pdf\\FullPageTest56.pdf", "Sheet1")] - //[DataRow("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx", "C:\\epplustest\\pdf\\OutputTest1.1.pdf", "SAP Data")] + [DataRow("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx", "C:\\epplustest\\pdf\\OutputTest1.1.pdf", "SAP Data")] // Output file: OutputTest1.2.pdf // 1. Minus signs alignment in cells differs from Excel. ------------------------------------------------ Comment: Currently no support for number formats. Requires implementing number formats. // 2. Dimension seems to differ from Excel, Excel stops at row 75, EPPlus goes to row 89. --------------- Fixed // 3. Row headings are sligthly wider in EPPlus than in Excel. ------------------------------------------ Fixed - //[DataRow("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx", "C:\\epplustest\\pdf\\OutputTest1.2.pdf", "Summary")] + [DataRow("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx", "C:\\epplustest\\pdf\\OutputTest1.2.pdf", "Summary")] // works as expected - //[DataRow("Aico WiP 120180 FBL3N for 0110 in 2025-04.xlsx", "C:\\epplustest\\pdf\\OutputTest1.4.pdf", "Technical")] - //[DataRow("Aico KKS1 Variance Calculation for 0105 in 2025-04 (25_4_2025 15_43_40) .xlsx", "C:\\epplustest\\pdf\\OutputTest1.5.pdf", "Technical")] + [DataRow("Aico WiP 120180 FBL3N for 0110 in 2025-04.xlsx", "C:\\epplustest\\pdf\\OutputTest1.4.pdf", "Technical")] + [DataRow("Aico KKS1 Variance Calculation for 0105 in 2025-04 (25_4_2025 15_43_40) .xlsx", "C:\\epplustest\\pdf\\OutputTest1.5.pdf", "Technical")] // Output file: OutputTest1.6.pdf // 1. Merged cells not working ------------------------------------ Fixed. Comment Merged cells was fine, it was borders being rendered inside merged cells. // 2. Pattern fills looks differnt, in some cases not working ----- // 3. Rotation of text in cells not working (the dates). ---------- // [DataRow("R05.xlsx", "C:\\epplustest\\pdf\\OutputTest1.6.pdf", "R05 Arbeitseinteilung")] - //[DataRow("R05 - Copy.xlsx", "C:\\epplustest\\pdf\\OutputTest1.6.pdf", "R05 Arbeitseinteilung")] - [DataRow("PatternStyles.xlsx", "C:\\epplustest\\pdf\\OutputTest1.8.pdf", "Sheet1")] + [DataRow("R05 - Copy.xlsx", "C:\\epplustest\\pdf\\OutputTest1.6.pdf", "R05 Arbeitseinteilung")] + //[DataRow("PatternStyles.xlsx", "C:\\epplustest\\pdf\\OutputTest1.8.pdf", "Sheet1")] public void WorkbookTests(string sourceFile, string outputPath, string wsName) { using var p = OpenTemplatePackage(sourceFile); From f4c6bf508b4cd9b8a3bb40ab620fc81b718ad416 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Thu, 25 Jun 2026 15:19:33 +0200 Subject: [PATCH 41/49] Updated todo. --- src/EPPlus.Export.Pdf.Tests/PdfTests.cs | 89 ++++++------------------- 1 file changed, 19 insertions(+), 70 deletions(-) diff --git a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs index 1b7c50e41f..6ce9e4da3e 100644 --- a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs +++ b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs @@ -27,84 +27,33 @@ namespace EPPlusTest.PDF public class PdfTests : TestBase { /* BIG PDF TODO - * This should be turned into tickets on github.. * - * FEATURES: - * Cells: Icons - * Cells: Pictures in cells : Prio 3 - * Cells: Conditional Formatting - * Cells: Calculate clip rect for merged cells with content bounds in mind + * Missing Features: + * Embedding pictures as drawings + * Embedding pictures as cell content + * Embedding pictures as header/footer + * Number formatting + * Scaling document + * Conditional Formatting icons + * Vertical text + * Equations + * Pivot tables + * Shapes + * Charts + * 3D models + * Compression * - * Text: Wrap text - * Text: Vertical text again - * Text: Equations : Prio 5 - * Text: Shrink To Fit (Scaling) + * Bugs: + * Merged cell uses full width/height even when columns/rows are hidden. + * Conditional formatting not worksing 100% of the time. + * Table could sometimes select wrong style * - * Layout: Scaling - * Layout: Fit to number of pages (Scaling) - * Layout: Excel Workbook to pdf (Export) - * Layout: Selected worksheets to pdf (Export) - * Layout: Cell range to pdf (Export) - * Layout: Print Area (This is like a collection of cell ranges) (Export) - * Layout: Print titles - * Layout: Background (Excel does not add this to pdf it seems) (Images) : Prio 4 - * Layout: Remove empty last page - * Layout: Autofit row - * - * Pivot Table: Pivot table implementation : Prio 5 - * - * Drawings: Pictures (Images) : Prio 3 - * Drawings Shapes (Images) : Prio 5 - * Drawings: Charts (Images) : Prio 5 - * Drawings: 3D models (Images) : Prio 5 - * - * PDF: Compress parts - * - * IMPROVEMENTS; + * Other * Cells: Remove stroke from solid fill and adjust size and position of cell more precise and only use fill command. - * - * Tables: Table Implementation - * Tables: Fix order of applying table styling. Right now Table is used if it exsists, but cell styling should be prioritised and table should be ignored if cell has styluing. - * - * Patterns: Adjust and make patterns look better. Fixed: DarkUp, DarkDown - * * Gradients: Make diamond gradients instead of radial gradtient in from corner and center gradient - * * Text: Set up to use back up techniques when not embedding font - * - * Layout: Calculate height of cells more correctly - * * Borders: Adjust and make border look better - */ - - /* REFACTOR: - * 1. Collect: Gather all text in all worksheets, headerfooter and comments - * 2. Shape : Shape the Text in all worksheets, headerfooter and comments - * 3. Layout : Autofit row, add print titles, add row and column headings, set up print areas, page breaks - * 4. Pages: : Create number of pages needed, assign elements to pages - * 5. PDF : Export to pdf - * * - * --------------0. Set up --------------------- - * Create class catalog that takes in a workbook, or a collection of worksheets, or a range. - * When step1 is done we check in a comments collections if there are comments and process the comments worksheet the same way. - * for each worksheet do following: - * --------------1. Collect--------------------- - * Create array map of worksheet with coords, text, fills and so on. - * This is done when all worksheets have been mapped. - * Comments and notes will create a temporary worksheet in current workbook with all notes and comments inside cells. - * --------------2. Shape --------------------- - * Go through all text in all workbbooks and shape the text. - * We also check text height measurements and adjust row height. - * --------------3. Layout --------------------- - * Precalculate content area of each page using content area and page breaks and print areas. We need to take scaling into account here! - * Add pictures, shapes, notes and other elements that lies on top of cells. - * Then loop the collection and insert row and column headings, print titles. - * We also need to create gridlines here - * --------------4. Pages ---------------------- - * Create Pages and create transform objects of each element from the array and assign them to their respective page. - * --------------5. PDF ------------------------ - * When all worksheets have been processed, combine all into one catalog and create the pdf document. */ /// From bc40b0a5ae602859e5bd7ed115251cb0b8fb1091 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Mon, 29 Jun 2026 15:44:08 +0200 Subject: [PATCH 42/49] Added entry points for pdf export. --- src/EPPlus.Export.Pdf.Tests/PdfTests.cs | 6 +- .../Settings/PdfPageSettings.cs | 23 +- .../Settings/PdfPageSizes/PdfMargins.cs | 2 +- .../Settings/PdfPageSizes/PdfPageSize.cs | 57 +++++ src/EPPlus.Graphics/Units/UnitConversion.cs | 5 + src/EPPlus/ExcelPrinterSettings.cs | 124 +++++++++++ src/EPPlus/ExcelRangeBase.cs | 12 ++ src/EPPlus/ExcelWorkbook.cs | 91 +++++--- src/EPPlus/ExcelWorksheet.cs | 16 +- src/EPPlus/Export/PdfExport/PdfCatalog.cs | 194 ++++++++++++++--- .../PdfExport/Settings/GetPdfSettings.cs | 198 ++++++++++++++++++ 11 files changed, 660 insertions(+), 68 deletions(-) create mode 100644 src/EPPlus/Export/PdfExport/Settings/GetPdfSettings.cs diff --git a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs index 6ce9e4da3e..7208b461ff 100644 --- a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs +++ b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs @@ -127,7 +127,7 @@ public void ReadPrintAreas() pageSettings.PrintAsText = true; pageSettings.ShowGridLines = true; pageSettings.ShowHeadings = true; - PdfCatalog catlog = new PdfCatalog(pageSettings, ws, "C:\\epplustest\\pdf\\FullPageTest58.pdf"); + PdfCatalog catlog = new PdfCatalog("C:\\epplustest\\pdf\\FullPageTest58.pdf", pageSettings, ws); //line breaks //wrap comments @@ -156,7 +156,7 @@ public void PerfTest() ShowGridLines = false, ShowHeadings = true }; - PdfCatalog catalog = new PdfCatalog(pageSettings, ws, "C:\\epplustest\\pdf\\OutputTest1.3.pdf"); + PdfCatalog catalog = new PdfCatalog("C:\\epplustest\\pdf\\OutputTest1.3.pdf", pageSettings, ws); Console.WriteLine($"workbook exported time elapsed: {sw.ElapsedMilliseconds} ms"); } @@ -198,7 +198,7 @@ public void WorkbookTests(string sourceFile, string outputPath, string wsName) pageSettings.ShowGridLines = false; pageSettings.ShowHeadings = false; - PdfCatalog catalog = new PdfCatalog(pageSettings, ws, outputPath); + PdfCatalog catalog = new PdfCatalog(outputPath, pageSettings, ws); } /// diff --git a/src/EPPlus.Export.Pdf/Settings/PdfPageSettings.cs b/src/EPPlus.Export.Pdf/Settings/PdfPageSettings.cs index d5fdf6486a..139a5ea63b 100644 --- a/src/EPPlus.Export.Pdf/Settings/PdfPageSettings.cs +++ b/src/EPPlus.Export.Pdf/Settings/PdfPageSettings.cs @@ -95,6 +95,16 @@ public OpenTypeFontEngine FontEngine /// public bool ShowHeadings = false; + /// + /// Set to true for to only use black and white. + /// + public bool BlackAndWhite = false; + + /// + /// Set to true for to make a draft. + /// + public bool Draft = false; + /// /// Set the range to repeat at the top of the page. /// @@ -105,6 +115,11 @@ public OpenTypeFontEngine FontEngine /// public string ColumnsToRepeatAtLeft = null; + /// + /// Specific range to print. + /// + public string PrintArea = null; + /// /// Set if comments and notes should be included. /// @@ -257,13 +272,13 @@ public enum CommentsAndNotes /// None, /// - /// Comments and Notes will be displayed on a seprate page at the end. + /// Notes will be displayed on the sheet. (Comments will not be shown.) /// - AtEndOfSheet, + AsDisplayedOnSheet, /// - /// Notes will be displayed on the sheet. (Comments will not be shown.) + /// Comments and Notes will be displayed on a seprate page at the end. /// - AsDisplayedOnSheet + AtEndOfSheet, } /// diff --git a/src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfMargins.cs b/src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfMargins.cs index 1fee87bf8e..e0de44473e 100644 --- a/src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfMargins.cs +++ b/src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfMargins.cs @@ -39,7 +39,7 @@ public PdfMargins() FooterPu = UnitConversion.MmToPoints(Footer); } - public PdfMargins(double Top, double Left, double Right, double Bottom, double Header, double Footer) + public PdfMargins(double Top, double Bottom, double Left, double Right, double Header, double Footer) { this.Top = Top; this.Left = Left; diff --git a/src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfPageSize.cs b/src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfPageSize.cs index 8dce84bc52..359075daa3 100644 --- a/src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfPageSize.cs +++ b/src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfPageSize.cs @@ -35,8 +35,65 @@ public PdfPageSize(double width, double height) public static PdfPageSize B5 => new PdfPageSize(182d, 257d); public static PdfPageSize B4 => new PdfPageSize(257d, 364d); public static PdfPageSize Letter => new PdfPageSize(215.9d, 279.4d); //(612, 792); + public static PdfPageSize LetterSmall => new PdfPageSize(215.9d, 279.4d); public static PdfPageSize Legal => new PdfPageSize(215.9d, 355.6d); //(612, 1008); public static PdfPageSize Statement => new PdfPageSize(139.7d, 215.9d); public static PdfPageSize Executive => new PdfPageSize(184.2d, 266.7d); + public static PdfPageSize Tabloid => new PdfPageSize(279.4d, 431.8d); + public static PdfPageSize Ledger => new PdfPageSize(431.84d, 279.4d); + public static PdfPageSize A4Small => new PdfPageSize(210d, 297d); + public static PdfPageSize Folio => new PdfPageSize(215.9d, 330.2d); // 8.5 x 13 in + public static PdfPageSize Quarto => new PdfPageSize(215d, 275d); + public static PdfPageSize Standard10_14 => new PdfPageSize(254d, 355.6d); // 10 x 14 in + public static PdfPageSize Standard11_17 => new PdfPageSize(279.4d, 431.8d); // 11 x 17 in + public static PdfPageSize Note => new PdfPageSize(215.9d, 279.4d); // 8.5 x 11 in + public static PdfPageSize Envelope9 => new PdfPageSize(98.425d, 225.425d); // 3.875 x 8.875 in + public static PdfPageSize Envelope10 => new PdfPageSize(104.775d, 241.3d); // 4.125 x 9.5 in + public static PdfPageSize Envelope11 => new PdfPageSize(114.3d, 263.525d); // 4.5 x 10.375 in + public static PdfPageSize Envelope12 => new PdfPageSize(120.65d, 279.4d); // 4.75 x 11 in + public static PdfPageSize Envelope14 => new PdfPageSize(127d, 292.1d); // 5 x 11.5 in + public static PdfPageSize C => new PdfPageSize(431.8d, 558.8d); // 17 x 22 in + public static PdfPageSize D => new PdfPageSize(558.8d, 863.6d); // 22 x 34 in + public static PdfPageSize E => new PdfPageSize(863.6d, 1117.6d); // 34 x 44 in + public static PdfPageSize DLEnvelope => new PdfPageSize(110d, 220d); + public static PdfPageSize C5Envelope => new PdfPageSize(162d, 229d); + public static PdfPageSize C3Envelope => new PdfPageSize(324d, 458d); + public static PdfPageSize C4Envelope => new PdfPageSize(229d, 324d); + public static PdfPageSize C6Envelope => new PdfPageSize(114d, 162d); + public static PdfPageSize C65Envelope => new PdfPageSize(114d, 229d); + public static PdfPageSize B4Envelope => new PdfPageSize(250d, 353d); + public static PdfPageSize B5Envelope => new PdfPageSize(176d, 250d); + public static PdfPageSize B6Envelope => new PdfPageSize(176d, 125d); + public static PdfPageSize ItalyEnvelope => new PdfPageSize(110d, 230d); + public static PdfPageSize MonarchEnvelope => new PdfPageSize(98.425d, 190.5d); // 3.875 x 7.5 in + public static PdfPageSize Six3_4Envelope => new PdfPageSize(92.075d, 165.1d); // 3.625 x 6.5 in + public static PdfPageSize USStandard => new PdfPageSize(377.825d, 279.4d); // 14.875 x 11 in + public static PdfPageSize GermanStandard => new PdfPageSize(215.9d, 304.8d); // 8.5 x 12 in + public static PdfPageSize GermanLegal => new PdfPageSize(215.9d, 330.2d); // 8.5 x 13 in + public static PdfPageSize ISOB4 => new PdfPageSize(250d, 353d); + public static PdfPageSize JapaneseDoublePostcard => new PdfPageSize(200d, 148d); + public static PdfPageSize Standard9 => new PdfPageSize(228.6d, 279.4d); // 9 x 11 in + public static PdfPageSize Standard10 => new PdfPageSize(254d, 279.4d); // 10 x 11 in + public static PdfPageSize Standard15 => new PdfPageSize(381d, 279.4d); // 15 x 11 in + public static PdfPageSize InviteEnvelope => new PdfPageSize(220d, 220d); + public static PdfPageSize LetterExtra => new PdfPageSize(235.585d, 304.8d); // 9.275 x 12 in + public static PdfPageSize LegalExtra => new PdfPageSize(235.585d, 381d); // 9.275 x 15 in + public static PdfPageSize TabloidExtra => new PdfPageSize(296.926d, 457.2d); // 11.69 x 18 in + public static PdfPageSize A4Extra => new PdfPageSize(236d, 322d); + public static PdfPageSize LetterTransverse => new PdfPageSize(210.185d, 279.4d); // 8.275 x 11 in + public static PdfPageSize A4Transverse => new PdfPageSize(210d, 297d); + public static PdfPageSize LetterExtraTransverse => new PdfPageSize(235.585d, 304.8d); // 9.275 x 12 in + public static PdfPageSize SuperA => new PdfPageSize(227d, 356d); + public static PdfPageSize SuperB => new PdfPageSize(305d, 487d); + public static PdfPageSize LetterPlus => new PdfPageSize(215.9d, 322.326d); // 8.5 x 12.69 in + public static PdfPageSize A4Plus => new PdfPageSize(210d, 330d); + public static PdfPageSize A5Transverse => new PdfPageSize(148d, 210d); + public static PdfPageSize JISB5Transverse => new PdfPageSize(182d, 257d); + public static PdfPageSize A3Extra => new PdfPageSize(322d, 445d); + public static PdfPageSize A5Extra => new PdfPageSize(174d, 235d); + public static PdfPageSize ISOB5 => new PdfPageSize(201d, 276d); + public static PdfPageSize A2 => new PdfPageSize(420d, 594d); + public static PdfPageSize A3Transverse => new PdfPageSize(297d, 420d); + public static PdfPageSize A3ExtraTransverse => new PdfPageSize(322d, 445d); } } \ No newline at end of file diff --git a/src/EPPlus.Graphics/Units/UnitConversion.cs b/src/EPPlus.Graphics/Units/UnitConversion.cs index f13e067581..76031a1e82 100644 --- a/src/EPPlus.Graphics/Units/UnitConversion.cs +++ b/src/EPPlus.Graphics/Units/UnitConversion.cs @@ -52,5 +52,10 @@ public static double ExcelRowHeightToPoints(double rowHeight) { return rowHeight; //These values are guessed. } + + public static double ToMillimeters(double inches) + { + return inches * 25.4; + } } } diff --git a/src/EPPlus/ExcelPrinterSettings.cs b/src/EPPlus/ExcelPrinterSettings.cs index 40b4090e96..168a67d8f8 100644 --- a/src/EPPlus/ExcelPrinterSettings.cs +++ b/src/EPPlus/ExcelPrinterSettings.cs @@ -319,6 +319,46 @@ public enum ePageOrder /// OverThenDown } + /// + /// Specifies how comments and notes will be displayed + /// + public enum eCellComments + { + /// + /// Comments will not be dispalyed + /// + None, + /// + /// Notes will be displyed in a box next to the cell + /// + AsDisplayed, + /// + /// Adds pages at the end containing notes and commnets + /// + AtEnd + } + /// + /// Specifies how to display errors + /// + public enum ePrintError + { + /// + /// Show error as it is + /// + Displayed, + /// + /// Show eror as blank + /// + Blank, + /// + /// Show error as a dash + /// + Dash, + /// + /// Show error as N/A + /// + NA + } #endregion /// /// Printer settings @@ -806,6 +846,90 @@ public ePaperSize PaperSize SetXmlNodeString(_paperSizePath, ((int)value).ToString()); } } + const string _commentPath = "d:pageSetup/@cellComments"; + /// + /// Specifies how cell comments are printed. + /// + public eCellComments CellComments + { + get + { + return GetXmlNodeString(_commentPath) switch + { + "asDisplayed" => eCellComments.AsDisplayed, + "atEnd" => eCellComments.AtEnd, + _ => eCellComments.None + }; + } + set + { + var xmlValue = value switch + { + eCellComments.AsDisplayed => "asDisplayed", + eCellComments.AtEnd => "atEnd", + _ => "none" + }; + SetXmlNodeString(_commentPath, xmlValue == "none" ? null : xmlValue, true); + } + } + const string _errorPath = "d:pageSetup/@errors"; + /// + /// Specifies how cell error values are printed. + /// + public ePrintError Errors + { + get + { + return GetXmlNodeString(_errorPath) switch + { + "blank" => ePrintError.Blank, + "dash" => ePrintError.Dash, + "NA" => ePrintError.NA, + _ => ePrintError.Displayed + }; + } + set + { + var xmlValue = value switch + { + ePrintError.Blank => "blank", + ePrintError.Dash => "dash", + ePrintError.NA => "NA", + _ => "displayed" + }; + SetXmlNodeString(_errorPath, xmlValue == "displayed" ? null : xmlValue, true); + } + } + const string _firstPageNumberPath = "d:pageSetup/@firstPageNumber"; + const string _useFirstPageNumberPath = "d:pageSetup/@useFirstPageNumber"; + /// + /// The page number to use for the first printed page. + /// Set to 1 or less to revert to automatic page numbering. + /// Setting this also controls the useFirstPageNumber attribute automatically. + /// + public int FirstPageNumber + { + get + { + if (!GetXmlNodeBool(_useFirstPageNumberPath)) + return 1; // automatic return the default + return GetXmlNodeInt(_firstPageNumberPath); + } + set + { + if (value <= 1) + { + // Revert to automatic remove both attributes + DeleteNode(_firstPageNumberPath); + DeleteNode(_useFirstPageNumberPath); + } + else + { + SetXmlNodeString(_firstPageNumberPath, value.ToString()); + SetXmlNodeBool(_useFirstPageNumberPath, true, true); + } + } + } /// /// All or none of the margin attributes must exist. Create all att ones. /// diff --git a/src/EPPlus/ExcelRangeBase.cs b/src/EPPlus/ExcelRangeBase.cs index 288464982d..ef3486691a 100644 --- a/src/EPPlus/ExcelRangeBase.cs +++ b/src/EPPlus/ExcelRangeBase.cs @@ -17,6 +17,8 @@ Date Author Change using OfficeOpenXml.Core.Worksheet; using OfficeOpenXml.DataValidation; using OfficeOpenXml.Export.HtmlExport.Interfaces; +using OfficeOpenXml.Export.PdfExport; +using OfficeOpenXml.Export.PdfExport.Settings; using OfficeOpenXml.FormulaParsing; using OfficeOpenXml.FormulaParsing.Excel.Functions; using OfficeOpenXml.FormulaParsing.LexicalAnalysis; @@ -1115,6 +1117,16 @@ public IExcelHtmlRangeExporter CreateHtmlExporter() return new OfficeOpenXml.Export.HtmlExport.Exporters.ExcelHtmlRangeExporter(this); } + /// + /// Save range to PDF. + /// + /// Name of file. + public void SaveAsPdf(string fileName) + { + var setttings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(this.Worksheet.PrinterSettings); + PdfCatalog catalog = new PdfCatalog(fileName, setttings, this); + } + //public ExcelHtmlRangeExporter CreateHtmlExporter() //{ // return new ExcelHtmlRangeExporter(this); diff --git a/src/EPPlus/ExcelWorkbook.cs b/src/EPPlus/ExcelWorkbook.cs index 02b0c79d79..f18c146154 100644 --- a/src/EPPlus/ExcelWorkbook.cs +++ b/src/EPPlus/ExcelWorkbook.cs @@ -10,42 +10,44 @@ Date Author Change ************************************************************************************************* 01/27/2020 EPPlus Software AB Initial release EPPlus 5 *************************************************************************************************/ -using System; -using System.Xml; -using System.IO; -using System.Collections.Generic; -using System.Text; -using System.Globalization; -using System.Linq; -using OfficeOpenXml.VBA; -using OfficeOpenXml.FormulaParsing; -using OfficeOpenXml.FormulaParsing.LexicalAnalysis; -using OfficeOpenXml.Packaging.Ionic.Zip; -using OfficeOpenXml.Drawing.Theme; +using OfficeOpenXml.CellPictures; using OfficeOpenXml.Compatibility; +using OfficeOpenXml.Constants; using OfficeOpenXml.Core.CellStore; -using OfficeOpenXml.Drawing.Slicer; -using OfficeOpenXml.ThreadedComments; -using OfficeOpenXml.Table; -using OfficeOpenXml.Table.PivotTable; +using OfficeOpenXml.Data.Connection; +using OfficeOpenXml.Data.CustomXml; +using OfficeOpenXml.DigitalSignatures; using OfficeOpenXml.Drawing; -using OfficeOpenXml.Constants; -using OfficeOpenXml.ExternalReferences; -using OfficeOpenXml.Packaging; -using OfficeOpenXml.Export.HtmlExport.Interfaces; +using OfficeOpenXml.Drawing.Slicer; +using OfficeOpenXml.Drawing.Theme; using OfficeOpenXml.Export.HtmlExport.Exporters; +using OfficeOpenXml.Export.HtmlExport.Interfaces; +using OfficeOpenXml.Export.PdfExport; +using OfficeOpenXml.Export.PdfExport.Settings; +using OfficeOpenXml.ExternalReferences; +using OfficeOpenXml.FormulaParsing; +using OfficeOpenXml.FormulaParsing.LexicalAnalysis; using OfficeOpenXml.Metadata; +using OfficeOpenXml.Packaging; +using OfficeOpenXml.Packaging.Ionic.Zip; using OfficeOpenXml.RichData; -using OfficeOpenXml.Style; -using OfficeOpenXml.CellPictures; using OfficeOpenXml.RichData.IndexRelations; -using OfficeOpenXml.DigitalSignatures; -using OfficeOpenXml.Utils.XML; -using OfficeOpenXml.Utils.TypeConversion; -using OfficeOpenXml.Utils.FileUtils; +using OfficeOpenXml.Style; +using OfficeOpenXml.Table; +using OfficeOpenXml.Table.PivotTable; +using OfficeOpenXml.ThreadedComments; using OfficeOpenXml.Utils.EnumUtils; -using OfficeOpenXml.Data.CustomXml; -using OfficeOpenXml.Data.Connection; +using OfficeOpenXml.Utils.FileUtils; +using OfficeOpenXml.Utils.TypeConversion; +using OfficeOpenXml.Utils.XML; +using OfficeOpenXml.VBA; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml; namespace OfficeOpenXml { @@ -732,6 +734,39 @@ public IExcelHtmlRangeExporter CreateHtmlExporter(params ExcelRangeBase[] ranges return new ExcelHtmlWorkbookExporter(ranges); } + /// + /// Export workbook to PDF. + /// + /// Name of file. + public void SaveAsPdf(string fileName) + { + var setttings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(Worksheets[View.ActiveTab+1].PrinterSettings); + PdfCatalog catalog = new PdfCatalog(fileName, setttings, this); + } + + /// + /// Export selected worksheets to PDF. + /// + /// Name of File. + /// Worksheets to export. + public void SaveAsPdf(string fileName, params ExcelWorksheet[] worksheets) + { + var setttings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(Worksheets[0].PrinterSettings); + PdfCatalog catalog = new PdfCatalog(fileName, setttings, worksheets); + } + + /// + /// Export selected ranges to PDF. + /// + /// Name of file. + /// Ranges to export. + public void SaveAsPdf(string fileName, params ExcelRangeBase[] ranges) + { + var setttings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(ranges[0].Worksheet.PrinterSettings); + + PdfCatalog catalog = new PdfCatalog(fileName, setttings, ranges); + } + //public ExcelHtmlRangeExporter CreateHtmlExporter(params ExcelRangeBase[] ranges) //{ // foreach (var range in ranges) diff --git a/src/EPPlus/ExcelWorksheet.cs b/src/EPPlus/ExcelWorksheet.cs index e4543bcc19..a81629b37b 100644 --- a/src/EPPlus/ExcelWorksheet.cs +++ b/src/EPPlus/ExcelWorksheet.cs @@ -15,23 +15,21 @@ Date Author Change using OfficeOpenXml.Constants; using OfficeOpenXml.Core; using OfficeOpenXml.Core.CellStore; -using OfficeOpenXml.Core.RangeQuadTree; using OfficeOpenXml.Core.RichValues; using OfficeOpenXml.Core.Worksheet; using OfficeOpenXml.Core.Worksheet.XmlWriter; -using OfficeOpenXml.Data.Connection.IOHandlers; using OfficeOpenXml.Data.QueryTable; using OfficeOpenXml.DataValidation; using OfficeOpenXml.Drawing; using OfficeOpenXml.Drawing.Interfaces; using OfficeOpenXml.Drawing.Vml; +using OfficeOpenXml.Export.PdfExport.Settings; using OfficeOpenXml.Filter; using OfficeOpenXml.FormulaParsing; using OfficeOpenXml.FormulaParsing.LexicalAnalysis; using OfficeOpenXml.Packaging; using OfficeOpenXml.Packaging.Ionic.Zip; using OfficeOpenXml.RichData; -using OfficeOpenXml.RichData.RichValues.WebImages; using OfficeOpenXml.Sorting; using OfficeOpenXml.Sparkline; using OfficeOpenXml.Style; @@ -44,6 +42,7 @@ Date Author Change using OfficeOpenXml.Utils.String; using OfficeOpenXml.Utils.TypeConversion; using OfficeOpenXml.Utils.XML; +using OfficeOpenXml.Export.PdfExport; using System; using System.Collections; using System.Collections.Generic; @@ -51,7 +50,6 @@ Date Author Change using System.Globalization; using System.IO; using System.Linq; -using System.Security.Permissions; using System.Text; using System.Xml; @@ -3745,6 +3743,16 @@ internal bool IsDisposed } } + /// + /// Export worksheet to PDF. + /// + /// Name of file. + public void SaveAsPdf(string fileName) + { + var setttings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(PrinterSettings); + PdfCatalog catalog = new PdfCatalog(fileName, setttings, this); + } + ExcelPackage IPictureRelationDocument.Package { get { return _package; } } Dictionary _hashes = new Dictionary(); diff --git a/src/EPPlus/Export/PdfExport/PdfCatalog.cs b/src/EPPlus/Export/PdfExport/PdfCatalog.cs index 5b8cc13199..17ee00d762 100644 --- a/src/EPPlus/Export/PdfExport/PdfCatalog.cs +++ b/src/EPPlus/Export/PdfExport/PdfCatalog.cs @@ -10,10 +10,10 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -using EPPlus.Graphics; using EPPlus.Export.Pdf; -using EPPlus.Export.Pdf.Settings; using EPPlus.Export.Pdf.Resources; +using EPPlus.Export.Pdf.Settings; +using EPPlus.Graphics; using OfficeOpenXml.Export.PdfExport.Data; using OfficeOpenXml.Export.PdfExport.Layout; using OfficeOpenXml.Export.PdfExport.RowResize; @@ -22,6 +22,7 @@ Date Author Change using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using static OfficeOpenXml.Drawing.OleObject.Structures.OleObjectDataStructures; namespace OfficeOpenXml.Export.PdfExport { @@ -30,52 +31,79 @@ internal class PdfCatalog internal PdfDictionaries _dictionaries = new PdfDictionaries(); private bool _addTextForHeadings = true; + public PdfCatalog() { } + // // CONSTRUCTORS FOR MULTIPLE WORKSHEETS AS INPUT // - public PdfCatalog() { } - public PdfCatalog(PdfPageSettings pageSettings, ExcelWorkbook workbook) + public PdfCatalog(string fileName, PdfPageSettings pageSettings, ExcelWorkbook workbook) { - HandleWorksheetCollection(pageSettings, workbook.Worksheets.ToArray()); + HandleWorksheetCollection(pageSettings, workbook.Worksheets.ToArray(), fileName); } - public PdfCatalog(PdfPageSettings pageSettings, ExcelWorksheet[] worksheets) + public PdfCatalog(string fileName, PdfPageSettings pageSettings, ExcelWorksheet[] worksheets) { - HandleWorksheetCollection(pageSettings, worksheets); + HandleWorksheetCollection(pageSettings, worksheets, fileName); } - public PdfCatalog(PdfPageSettings pageSettings, List worksheets) + public PdfCatalog(string fileName, PdfPageSettings pageSettings, List worksheets) { - HandleWorksheetCollection(pageSettings, worksheets.ToArray()); + HandleWorksheetCollection(pageSettings, worksheets.ToArray(), fileName); } - private void HandleWorksheetCollection(PdfPageSettings pageSettings, ExcelWorksheet[] worksheets) + private void HandleWorksheetCollection(PdfPageSettings pageSettings, ExcelWorksheet[] worksheets, string fileName) { - var pdfSheets = GetPdfWorksheets(pageSettings, worksheets); - foreach (var pdfSheet in pdfSheets) + // Match the single-worksheet path: resolve the default font before building. + pageSettings.defaultFontName = worksheets[0].Workbook.ThemeManager.GetOrCreateTheme().FontScheme.MinorFont[0].Typeface; + + PdfWorksheet[] pdfSheets = null; + try { - ShapeTextInPdfWorksheet(pageSettings, pdfSheet); - PdfCalculateRowHeight.ResizeRowHeights(pdfSheet); + // Collect text for every worksheet. + pdfSheets = GetPdfWorksheets(pageSettings, worksheets); + + // Shape text and auto-fit rows per sheet. + foreach (var pdfSheet in pdfSheets) + { + ShapeTextInPdfWorksheet(pageSettings, pdfSheet); + PdfCalculateRowHeight.ResizeRowHeights(pdfSheet); + } + + // One layout spanning all sheets and their ranges. + var layout = GetLayout(pageSettings, pdfSheets); + + // Write the PDF document. + ExcelPdf excelPdf = new ExcelPdf(); + excelPdf.CreatePdf(pageSettings, _dictionaries, layout, fileName); + } + finally + { + // Clean up the temporary comments/notes worksheet created for each sheet, + // so the source workbook isn't permanently mutated by the PDF export. + if (pdfSheets != null) + { + foreach (var pdfSheet in pdfSheets) + { + if (pdfSheet != null && pdfSheet.CommentsAndNotesSheet != null) + { + pdfSheet.Worksheet.Workbook.Worksheets.Delete(pdfSheet.CommentsAndNotesSheet); + pdfSheet.CommentsAndNotesSheet = null; + } + } + } } - var Layout = GetLayout(pageSettings, pdfSheets); - //send Layout to pdf export here. } // // CONSTRUCTORS FOR SINGLE WORKSHEET AS INPUT // - public PdfCatalog(PdfPageSettings pageSettings, ExcelWorksheet worksheet, string fileName) + public PdfCatalog(string fileName, PdfPageSettings pageSettings, ExcelWorksheet worksheet) { BuildPdf(pageSettings, worksheet, fileName); } - //public PdfCatalog(PdfPageSettings pageSettings, ExcelWorksheet worksheet, Stream stream) - //{ - // BuildPdf(pageSettings, worksheet); - //} - private void BuildPdf(PdfPageSettings pageSettings, ExcelWorksheet worksheet, string fileName) { //pageSettings.defaultFontName = worksheet.Workbook.ThemeManager.CurrentTheme.FontScheme.MinorFont[0].Typeface; @@ -136,13 +164,76 @@ private void BuildPdf(PdfPageSettings pageSettings, ExcelWorksheet worksheet, st // CONSTRUCTORS FOR RANGE AS INPUT // - public PdfCatalog(PdfPageSettings pageSettings, ExcelRangeBase range) + public PdfCatalog(string fileName, PdfPageSettings pageSettings, ExcelRangeBase range) { - PdfWorksheet pdfSheet = GetPdfWorksheet(pageSettings, range); - ShapeTextInPdfWorksheet(pageSettings, pdfSheet); - PdfCalculateRowHeight.ResizeRowHeights(pdfSheet); - var Layout = GetLayout(pageSettings, pdfSheet); - //send Layout to pdf export here. + pageSettings.defaultFontName = range.Worksheet.Workbook.ThemeManager.GetOrCreateTheme().FontScheme.MinorFont[0].Typeface; + + PdfWorksheet pdfSheet = null; + try + { + pdfSheet = GetPdfWorksheet(pageSettings, range); + ShapeTextInPdfWorksheet(pageSettings, pdfSheet); + PdfCalculateRowHeight.ResizeRowHeights(pdfSheet); + var layout = GetLayout(pageSettings, pdfSheet); // single-sheet GetLayout overload + + ExcelPdf excelPdf = new ExcelPdf(); + excelPdf.CreatePdf(pageSettings, _dictionaries, layout, fileName); + } + finally + { + if (pdfSheet != null && pdfSheet.CommentsAndNotesSheet != null) + { + pdfSheet.Worksheet.Workbook.Worksheets.Delete(pdfSheet.CommentsAndNotesSheet); + pdfSheet.CommentsAndNotesSheet = null; + } + } + } + + public PdfCatalog(string fileName, PdfPageSettings pageSettings, ExcelRangeBase[] ranges) + { + HandleRangeCollection(pageSettings, ranges, fileName); + } + + public PdfCatalog(string fileName, PdfPageSettings pageSettings, List ranges) + { + HandleRangeCollection(pageSettings, ranges.ToArray(), fileName); + } + + private void HandleRangeCollection(PdfPageSettings pageSettings, ExcelRangeBase[] ranges, string fileName) + { + pageSettings.defaultFontName = ranges[0].Worksheet.Workbook.ThemeManager.GetOrCreateTheme().FontScheme.MinorFont[0].Typeface; + + PdfWorksheet[] pdfSheets = null; + try + { + // One PdfWorksheet per worksheet, each carrying all of its ranges. + pdfSheets = GetPdfWorksheets(pageSettings, ranges); + + foreach (var pdfSheet in pdfSheets) + { + ShapeTextInPdfWorksheet(pageSettings, pdfSheet); + PdfCalculateRowHeight.ResizeRowHeights(pdfSheet); + } + + var layout = GetLayout(pageSettings, pdfSheets); + + ExcelPdf excelPdf = new ExcelPdf(); + excelPdf.CreatePdf(pageSettings, _dictionaries, layout, fileName); + } + finally + { + if (pdfSheets != null) + { + foreach (var pdfSheet in pdfSheets) + { + if (pdfSheet != null && pdfSheet.CommentsAndNotesSheet != null) + { + pdfSheet.Worksheet.Workbook.Worksheets.Delete(pdfSheet.CommentsAndNotesSheet); + pdfSheet.CommentsAndNotesSheet = null; + } + } + } + } } internal PdfCellCollection GetCellCollectionFromRange(PdfPageSettings pageSettings, ExcelRangeBase range) @@ -254,6 +345,53 @@ private PdfWorksheet GetPdfWorksheet(PdfPageSettings pageSettings, ExcelRangeBas return pdfSheet; } + private PdfWorksheet[] GetPdfWorksheets(PdfPageSettings pageSettings, ExcelRangeBase[] ranges) + { + // Group ranges by worksheet, preserving input order. + var sheetOrder = new List(); + var rangesBySheet = new Dictionary>(); + foreach (var range in ranges) + { + var ws = range.Worksheet; + if (!rangesBySheet.TryGetValue(ws, out var list)) + { + list = new List(); + rangesBySheet[ws] = list; + sheetOrder.Add(ws); + } + list.Add(range); + } + + var pdfSheets = new PdfWorksheet[sheetOrder.Count]; + for (int i = 0; i < sheetOrder.Count; i++) + { + var ws = sheetOrder[i]; + pdfSheets[i] = GetPdfWorksheet(pageSettings, ws, rangesBySheet[ws]); + } + return pdfSheets; + } + + private PdfWorksheet GetPdfWorksheet(PdfPageSettings pageSettings, ExcelWorksheet worksheet, List ranges) + { + PdfWorksheet pdfSheet = new PdfWorksheet(); + pdfSheet.Worksheet = worksheet; + pdfSheet.Ranges = new List(); + foreach (var range in ranges) + { + pdfSheet.Ranges.Add(new PdfRange(range, false)); + } + + if (pageSettings.ShowHeadings && _addTextForHeadings) + _dictionaries.AddFont(pageSettings, pdfSheet.NormalStyle.Style.Font.Name, pdfSheet.GetSubFamilyFromNormalStyle, "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); + _addTextForHeadings = false; + + GetMaps(pageSettings, pdfSheet, pdfSheet.Ranges); + GetPrintTitles(pageSettings, pdfSheet); + GetHeaderFooter(pageSettings, pdfSheet); + GetCommentsAndNotes(pageSettings, pdfSheet); + return pdfSheet; + } + private List GetRanges(ExcelWorksheet worksheet) { List ranges = new List(); diff --git a/src/EPPlus/Export/PdfExport/Settings/GetPdfSettings.cs b/src/EPPlus/Export/PdfExport/Settings/GetPdfSettings.cs new file mode 100644 index 0000000000..2ca9164b68 --- /dev/null +++ b/src/EPPlus/Export/PdfExport/Settings/GetPdfSettings.cs @@ -0,0 +1,198 @@ +/************************************************************************************************* + 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.Export.Pdf.Settings; +using EPPlus.Export.Pdf.Settings.PdfPageSizes; +using EPPlus.Graphics.Units; +using System; + +namespace OfficeOpenXml.Export.PdfExport.Settings +{ + internal static class GetPdfSettings + { + internal static PdfPageSettings GetPdfSettingsFromPrinterSettings(ExcelPrinterSettings eps) + { + var settings = new PdfPageSettings(); + var leftMargin = UnitConversion.ToMillimeters(eps.LeftMargin); + var rightMargin = UnitConversion.ToMillimeters(eps.RightMargin); + var topMargin = UnitConversion.ToMillimeters(eps.TopMargin); + var bottomMargin = UnitConversion.ToMillimeters(eps.BottomMargin); + var headerMargin = UnitConversion.ToMillimeters(eps.HeaderMargin); + var footerMargin = UnitConversion.ToMillimeters(eps.FooterMargin); + settings.Margins = new PdfMargins(topMargin, bottomMargin, leftMargin, rightMargin, headerMargin, footerMargin); + settings.Orientation = (Orientations)eps.Orientation; + //Scaling is not yet implemented. + settings.Scaling = new PdfScaling(eps.Scale); + settings.ShowHeadings = eps.ShowHeaders; + settings.RowsToRepeatAtTop = eps.RepeatRows.Address; + settings.ColumnsToRepeatAtLeft = eps.RepeatColumns.Address; + //Print area is implemented and uses the defined name instead of this setting. this setting should override the print area defined name. + settings.PrintArea = eps.PrintArea.Address; + settings.ShowGridLines = eps.ShowGridLines; + settings.CenterOnPageHorizontally = eps.HorizontalCentered; + settings.CenterOnPageVertically = eps.VerticalCentered; + settings.PageOrders = (PageOrders)eps.PageOrder; + //Black and white is not yet implemented. + settings.BlackAndWhite = eps.BlackAndWhite; + //Draft is not implemtened. + settings.Draft = eps.Draft; + settings.PageSize = GetPageSize(eps.PaperSize); + settings.CommentsAndNotes = (CommentsAndNotes)eps.CellComments; + settings.CellErrors = (CellErrors)eps.Errors; + settings.FirstPageNumber = eps.FirstPageNumber; + return settings; + } + + private static PdfPageSize GetPageSize(ePaperSize PaperSize) + { + switch (PaperSize) + { + case ePaperSize.Letter: + return PdfPageSize.Letter; + case ePaperSize.LetterSmall: + return PdfPageSize.LetterSmall; + case ePaperSize.Tabloid: + return PdfPageSize.Tabloid; + case ePaperSize.Ledger: + return PdfPageSize.Ledger; + case ePaperSize.Legal: + return PdfPageSize.Legal; + case ePaperSize.Statement: + return PdfPageSize.Statement; + case ePaperSize.Executive: + return PdfPageSize.Executive; + case ePaperSize.A3: + return PdfPageSize.A3; + case ePaperSize.A4: + return PdfPageSize.A4; + case ePaperSize.A4Small: + return PdfPageSize.A4Small; + case ePaperSize.A5: + return PdfPageSize.A5; + case ePaperSize.B4: + return PdfPageSize.B4; + case ePaperSize.B5: + return PdfPageSize.B5; + case ePaperSize.Folio: + return PdfPageSize.Folio; + case ePaperSize.Quarto: + return PdfPageSize.Quarto; + case ePaperSize.Standard10_14: + return PdfPageSize.Standard10_14; + case ePaperSize.Standard11_17: + return PdfPageSize.Standard11_17; + case ePaperSize.Note: + return PdfPageSize.Note; + case ePaperSize.Envelope9: + return PdfPageSize.Envelope9; + case ePaperSize.Envelope10: + return PdfPageSize.Envelope10; + case ePaperSize.Envelope11: + return PdfPageSize.Envelope11; + case ePaperSize.Envelope12: + return PdfPageSize.Envelope12; + case ePaperSize.Envelope14: + return PdfPageSize.Envelope14; + case ePaperSize.C: + return PdfPageSize.C; + case ePaperSize.D: + return PdfPageSize.D; + case ePaperSize.E: + return PdfPageSize.E; + case ePaperSize.DLEnvelope: + return PdfPageSize.DLEnvelope; + case ePaperSize.C5Envelope: + return PdfPageSize.C5Envelope; + case ePaperSize.C3Envelope: + return PdfPageSize.C3Envelope; + case ePaperSize.C4Envelope: + return PdfPageSize.C4Envelope; + case ePaperSize.C6Envelope: + return PdfPageSize.C6Envelope; + case ePaperSize.C65Envelope: + return PdfPageSize.C65Envelope; + case ePaperSize.B4Envelope: + return PdfPageSize.B4Envelope; + case ePaperSize.B5Envelope: + return PdfPageSize.B5Envelope; + case ePaperSize.B6Envelope: + return PdfPageSize.B6Envelope; + case ePaperSize.ItalyEnvelope: + return PdfPageSize.ItalyEnvelope; + case ePaperSize.MonarchEnvelope: + return PdfPageSize.MonarchEnvelope; + case ePaperSize.Six3_4Envelope: + return PdfPageSize.Six3_4Envelope; + case ePaperSize.USStandard: + return PdfPageSize.USStandard; + case ePaperSize.GermanStandard: + return PdfPageSize.GermanStandard; + case ePaperSize.GermanLegal: + return PdfPageSize.GermanLegal; + case ePaperSize.ISOB4: + return PdfPageSize.ISOB4; + case ePaperSize.JapaneseDoublePostcard: + return PdfPageSize.JapaneseDoublePostcard; + case ePaperSize.Standard9: + return PdfPageSize.Standard9; + case ePaperSize.Standard10: + return PdfPageSize.Standard10; + case ePaperSize.Standard15: + return PdfPageSize.Standard15; + case ePaperSize.InviteEnvelope: + return PdfPageSize.InviteEnvelope; + case ePaperSize.LetterExtra: + return PdfPageSize.LetterExtra; + case ePaperSize.LegalExtra: + return PdfPageSize.LegalExtra; + case ePaperSize.TabloidExtra: + return PdfPageSize.TabloidExtra; + case ePaperSize.A4Extra: + return PdfPageSize.A4Extra; + case ePaperSize.LetterTransverse: + return PdfPageSize.LetterTransverse; + case ePaperSize.A4Transverse: + return PdfPageSize.A4Transverse; + case ePaperSize.LetterExtraTransverse: + return PdfPageSize.LetterExtraTransverse; + case ePaperSize.SuperA: + return PdfPageSize.SuperA; + case ePaperSize.SuperB: + return PdfPageSize.SuperB; + case ePaperSize.LetterPlus: + return PdfPageSize.LetterPlus; + case ePaperSize.A4Plus: + return PdfPageSize.A4Plus; + case ePaperSize.A5Transverse: + return PdfPageSize.A5Transverse; + case ePaperSize.JISB5Transverse: + return PdfPageSize.JISB5Transverse; + case ePaperSize.A3Extra: + return PdfPageSize.A3Extra; + case ePaperSize.A5Extra: + return PdfPageSize.A5Extra; + case ePaperSize.ISOB5: + return PdfPageSize.ISOB5; + case ePaperSize.A2: + return PdfPageSize.A2; + case ePaperSize.A3Transverse: + return PdfPageSize.A3Transverse; + case ePaperSize.A3ExtraTransverse: + return PdfPageSize.A3ExtraTransverse; + default: + //should return custom paper size instead. + throw new ArgumentOutOfRangeException(nameof(PaperSize), PaperSize, "Unsupported paper size."); + } + + } + } +} From 33b1bf66c15b4558a7bc7edad025415a747a1303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Tue, 30 Jun 2026 14:36:16 +0200 Subject: [PATCH 43/49] Async methods. Some tests and issues fixed regarding orientation. --- src/EPPlus.Export.Pdf.Tests/PdfTests.cs | 224 ++++++------------ .../Settings/PdfPageSettings.cs | 43 ++-- src/EPPlus/ExcelRangeBase.cs | 18 ++ src/EPPlus/ExcelWorkbook.cs | 81 ++++++- src/EPPlus/ExcelWorksheet.cs | 20 +- .../PdfExport/Settings/GetPdfSettings.cs | 6 +- 6 files changed, 211 insertions(+), 181 deletions(-) diff --git a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs index 7208b461ff..f6e712d89c 100644 --- a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs +++ b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs @@ -42,6 +42,7 @@ public class PdfTests : TestBase * Charts * 3D models * Compression + * Unique pdf settings per worksheet * * Bugs: * Merged cell uses full width/height even when columns/rows are hidden. @@ -56,62 +57,9 @@ public class PdfTests : TestBase * */ - /// - /// Old Test - /// - //[TestMethod] - //public void TestWritePdf() - //{ - // using var p = OpenTemplatePackage("PDFTest.xlsx"); - // //using var p = OpenTemplatePackage("PdfGrids\\PdfTextTest.xlsx"); - // //using var p = OpenTemplatePackage("PdfGrids\\PdfPageBreakTest.xlsx"); - // //using var p = OpenTemplatePackage("PDFTest - Copy (2).xlsx"); - // //using var p = OpenTemplatePackage("PdfBorders.xlsx"); - // //using var p = OpenTemplatePackage("PdfGrids\\3 2 Page Crazy Cells.xlsx"); - // //using var p = OpenTemplatePackage("PdfGrids\\3 2 Page Crazy Cells Merged.xlsx"); - // //using var p = OpenTemplatePackage("Gradient.xlsx"); - // //using var p = OpenTemplatePackage("PatternFill.xlsx"); - // var ws = p.Workbook.Worksheets[0]; - // //var ws = p.Workbook.Worksheets[1]; - // PdfPageSettings pageSettings = new PdfPageSettings(); - // pageSettings.ShowGridLines = true; - // pageSettings.PageSize = PdfPageSize.A4; - // pageSettings.Orientation = Orientations.Portrait; - // pageSettings.Margins = PdfMargins.Normal; - // pageSettings.ShowGridLines = true; - // pageSettings.CenterOnPageHorizontally = true; - // pageSettings.CenterOnPageVertically = true; - // pageSettings.ShowHeadings = true; - // pageSettings.CommentsAndNotes = CommentsAndNotes.AtEndOfSheet; - // //Debug Flags - // pageSettings.Debug = true; - // pageSettings.PrintAsText = true; - - // ExcelPdf pedeef = new ExcelPdf(ws, pageSettings); - // pedeef.CreatePdf("c:\\epplustest\\pdf\\FullPageTest49.pdf"); - //} - - /// - /// Old Test - /// - //[TestMethod] - //public void TestWritePdf2() - //{ - // using var p = OpenTemplatePackage("PDFTest2.xlsx"); - // PdfPageSettings pageSettings = new PdfPageSettings(); - // pageSettings.ShowGridLines = true; - // pageSettings.PageSize = PdfPageSize.A4; - // pageSettings.Orientation = Orientations.Portrait; - // pageSettings.Margins = PdfMargins.Normal; - // pageSettings.ShowGridLines = true; - // //Debug Flags - // pageSettings.Debug = true; - // pageSettings.PrintAsText = true; - - // ExcelPdf pedeef = new ExcelPdf(p.Workbook.Worksheets.First(), pageSettings); - // pedeef.CreatePdf("c:\\epplustest\\pdf\\EmojiTest.pdf"); - //} + protected static string pdfPath = _worksheetPath + "\\PDF\\"; + //ta bort [TestMethod] public void ReadPrintAreas() { @@ -127,17 +75,81 @@ public void ReadPrintAreas() pageSettings.PrintAsText = true; pageSettings.ShowGridLines = true; pageSettings.ShowHeadings = true; - PdfCatalog catlog = new PdfCatalog("C:\\epplustest\\pdf\\FullPageTest58.pdf", pageSettings, ws); + PdfCatalog catlog = new PdfCatalog("C:\\epplustest\\pdf\\FullPageTest59.pdf", pageSettings, ws); + } + + [TestMethod] + public void SaveWorksheetAsPdfTest1() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var ws = p.Workbook.Worksheets[0]; + ws.SaveAsPdf(pdfPath + "WorksheetTest1.pdf"); + } - //line breaks - //wrap comments - //text placement - //alignment - //vertical + [TestMethod] + public void SaveWorksheetAsPdfTest2() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var ws = p.Workbook.Worksheets[0]; + ws.PrinterSettings.Orientation = eOrientation.Landscape; + ws.PrinterSettings.ShowGridLines = false; + ws.PrinterSettings.ShowHeaders = false; + ws.PrinterSettings.PaperSize = ePaperSize.A3; + ws.SaveAsPdf(pdfPath + "WorksheetTest2.pdf"); } [TestMethod] - public void PerfTest() + public void SaveRangeAsPdfTest1() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var range = p.Workbook.Worksheets[0].Cells["D3:F6"]; + range.SaveAsPdf(pdfPath + "RangeTest1.pdf"); + } + + [TestMethod] + public void SaveWorkbookAsPdfTest1() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var wb = p.Workbook; + wb.SaveAsPdf(pdfPath + "WorkbookTest1.pdf"); + } + + [TestMethod] + public void SaveWorksheetsAsPdfTest2() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var wb = p.Workbook; + var ws0 = wb.Worksheets[0]; + var ws1 = wb.Worksheets[1]; + var ws2 = wb.Worksheets[2]; + wb.SaveAsPdf(pdfPath + "WorksheetsTest2.pdf", ws0, ws1, ws2); + } + + [TestMethod] + public void SaveWorksheetsAsPdfTest1() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var wb = p.Workbook; + var ws0 = wb.Worksheets[0]; + var ws2 = wb.Worksheets[2]; + wb.SaveAsPdf(pdfPath + "WorksheetsTest1.pdf", ws0, ws2); + } + + [TestMethod] + public void SaveRangesAsPdfTest1() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var wb = p.Workbook; + var ws = wb.Worksheets[0]; + var r1 = ws.Cells["D3:F6"]; + var r2 = ws.Cells["B36:F39"]; + var r3 = ws.Cells["K49:Q58"]; + var r4 = ws.Cells["L142:Q147"]; + wb.SaveAsPdf(pdfPath + "RangesTest1.pdf", r1, r2, r3, r4); + } + + [TestMethod] + public void PerformanceTest() { var sw = new Stopwatch(); Console.WriteLine("Starting..."); @@ -200,93 +212,5 @@ public void WorkbookTests(string sourceFile, string outputPath, string wsName) PdfCatalog catalog = new PdfCatalog(outputPath, pageSettings, ws); } - - /// - /// This test we can make somehing of. - /// - //[TestMethod] - //public void CalculatePages() - //{ - // using var p = new ExcelPackage(); - // var ws = p.Workbook.Worksheets.Add("Sheet 1"); - // PdfPageSettings pageSettings = new PdfPageSettings(); - // pageSettings.ShowHeadings = true; - // PdfWorksheet pws = new PdfWorksheet(); - // pws.Worksheet = ws; - // pws.ZeroCharWidth = PdfWorksheet.GetThemeFont0Width(ws); - // pws.ToRow = 256; - // PdfRange range = new PdfRange(); - // range.TotalWidth = 800; - // range.TotalHeight = 1600; - - // var result = PdfLayout.GetNumberOfPages(pageSettings, pws, ref range); - //} - - /// - /// Old Test - /// - //[TestMethod] - //public void TestWrapText() - //{ - // using var p = OpenTemplatePackage("PDFTest.xlsx"); - // var cell = p.Workbook.Worksheets[0].Cells["P118"]; - - // List TextFragments = GetTextFragments(cell.RichText).Cast().ToList(); - - - // var layout = OpenTypeFonts.GetTextLayoutEngineForFont((IFontFormatBase)TextFragments[0].RichTextOptions); - - // var TextLines = layout.WrapRichTextLineCollection(TextFragments, 51d); - //} - - //private static List GetTextFragments(ExcelRichTextCollection RichTextCollection, PdfCellStyle cellStyle = null) - //{ - // var textFragments = new List(); - // bool bold = false, italic = false, underline = false, strike = false; - // ExcelUnderLineType underLineType = ExcelUnderLineType.None; - // if (cellStyle != null && cellStyle.dxfFont != null) - // { - // bold = cellStyle.dxfFont.Bold != null ? (bool)cellStyle.dxfFont.Bold : false; - // italic = cellStyle.dxfFont.Italic != null ? (bool)cellStyle.dxfFont.Italic : false; - // strike = cellStyle.dxfFont.Strike != null ? (bool)cellStyle.dxfFont.Strike : false; - // underline = cellStyle.dxfFont.Underline != null; - // underLineType = cellStyle.dxfFont.Underline != null ? (ExcelUnderLineType)cellStyle.dxfFont.Underline : ExcelUnderLineType.None; - // } - // for (int i = 0; i < RichTextCollection.Count; i++) - // { - // var rt = RichTextCollection[i]; - // var textFrag = new TextFragment(); - // textFrag.Text = rt.Text; - - // textFrag.Font.Family = rt.FontName; - // textFrag.Font.Size = rt.Size; - - // textFrag.RichTextOptions.Bold = rt.Bold || bold; - // textFrag.RichTextOptions.Italic = rt.Italic || italic; - // //underline - // //none : 12 - // //single : 13 - // //Double : 4 - // //accouting does not exsist - // textFrag.RichTextOptions.UnderlineType = 12; - // textFrag.RichTextOptions.UnderlineType = rt.UnderLineType == ExcelUnderLineType.Single ? 13 : textFrag.RichTextOptions.UnderlineType; - // textFrag.RichTextOptions.UnderlineType = rt.UnderLineType == ExcelUnderLineType.Double ? 4 : textFrag.RichTextOptions.UnderlineType; - // textFrag.RichTextOptions.StrikeType = rt.Strike || strike ? 2 : 1; - // textFrag.RichTextOptions.SuperScript = rt.VerticalAlign == ExcelVerticalAlignmentFont.Superscript; - // textFrag.RichTextOptions.SubScript = rt.VerticalAlign == ExcelVerticalAlignmentFont.Subscript; - // textFrag.RichTextOptions.FontColor = rt.Color; - - // //Should no longer be neccesary - // //textFrag.Font.Style = (textFrag.RichTextOptions.Bold ? MeasurementFontStyles.Bold : 0) | - // // (textFrag.RichTextOptions.Italic ? MeasurementFontStyles.Italic : 0) | - // // (textFrag.RichTextOptions.UnderlineType != 12 ? MeasurementFontStyles.Underline : 0) | - // // (textFrag.RichTextOptions.StrikeType > 1 ? MeasurementFontStyles.Strikeout : 0); - - - // textFragments.Add(textFrag); - // } - - // return textFragments; - //} } } diff --git a/src/EPPlus.Export.Pdf/Settings/PdfPageSettings.cs b/src/EPPlus.Export.Pdf/Settings/PdfPageSettings.cs index 139a5ea63b..ca6560f1d7 100644 --- a/src/EPPlus.Export.Pdf/Settings/PdfPageSettings.cs +++ b/src/EPPlus.Export.Pdf/Settings/PdfPageSettings.cs @@ -147,19 +147,23 @@ public PdfPageSize PageSize } set { - _pageSize = new PdfPageSize(value.Height, value.Width); - if (value.Height > value.Width) + var size = new PdfPageSize(value.Width, value.Height); // store as authored, no transpose + if (_orientationExplicitlySet) { - _orientation = Orientations.Portrait; + size = ApplyOrientation(size, _orientation); } else { - _orientation = Orientations.Landscape; + _orientation = size.Height >= size.Width + ? Orientations.Portrait + : Orientations.Landscape; } - ContentBounds.CalculateBounds(Margins, PageSize); + _pageSize = size; + ContentBounds.CalculateBounds(Margins, _pageSize); } } + private bool _orientationExplicitlySet = false; private Orientations _orientation = Orientations.Portrait; /// /// Set the orientation of the pages. @@ -172,22 +176,10 @@ public Orientations Orientation } set { - if (value == Orientations.Portrait) - { - if (_pageSize.Height < _pageSize.Width) - { - _pageSize = new PdfPageSize(_pageSize.Height, _pageSize.Width); - _orientation = value; - } - } - if (value == Orientations.Landscape) - { - if (_pageSize.Height > _pageSize.Width) - { - _pageSize = new PdfPageSize(_pageSize.Height, _pageSize.Width); - _orientation = value; - } - } + _orientation = value; + _orientationExplicitlySet = true; + _pageSize = ApplyOrientation(_pageSize, value); + ContentBounds.CalculateBounds(Margins, _pageSize); } } @@ -230,6 +222,15 @@ public PdfScaling Scaling //DEBUG internal bool Debug = false; internal bool PrintAsText = false; + + private static PdfPageSize ApplyOrientation(PdfPageSize size, Orientations orientation) + { + bool isPortrait = size.Height >= size.Width; + bool wantPortrait = orientation == Orientations.Portrait; + return isPortrait == wantPortrait + ? size + : new PdfPageSize(size.Height, size.Width); // swap (ctor is width, height) + } } /// diff --git a/src/EPPlus/ExcelRangeBase.cs b/src/EPPlus/ExcelRangeBase.cs index ef3486691a..484741baff 100644 --- a/src/EPPlus/ExcelRangeBase.cs +++ b/src/EPPlus/ExcelRangeBase.cs @@ -33,6 +33,8 @@ Date Author Change using System.Collections; using System.Collections.Generic; using System.Globalization; +using System.Threading; +using System.Threading.Tasks; namespace OfficeOpenXml { @@ -1127,6 +1129,22 @@ public void SaveAsPdf(string fileName) PdfCatalog catalog = new PdfCatalog(fileName, setttings, this); } + /// + /// Save range to PDF asynchronously. + /// + /// Name of file. + /// Token to cancel the operation. + /// A task representing the asynchronous operation. + public Task SaveAsPdfAsync(string fileName, CancellationToken cancellationToken = default) + { + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(this.Worksheet.PrinterSettings); + return Task.Run(() => + { + cancellationToken.ThrowIfCancellationRequested(); + _ = new PdfCatalog(fileName, settings, this); + }, cancellationToken); + } + //public ExcelHtmlRangeExporter CreateHtmlExporter() //{ // return new ExcelHtmlRangeExporter(this); diff --git a/src/EPPlus/ExcelWorkbook.cs b/src/EPPlus/ExcelWorkbook.cs index f18c146154..6f88380105 100644 --- a/src/EPPlus/ExcelWorkbook.cs +++ b/src/EPPlus/ExcelWorkbook.cs @@ -47,6 +47,8 @@ Date Author Change using System.IO; using System.Linq; using System.Text; +using System.Threading; +using System.Threading.Tasks; using System.Xml; namespace OfficeOpenXml @@ -740,8 +742,8 @@ public IExcelHtmlRangeExporter CreateHtmlExporter(params ExcelRangeBase[] ranges /// Name of file. public void SaveAsPdf(string fileName) { - var setttings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(Worksheets[View.ActiveTab+1].PrinterSettings); - PdfCatalog catalog = new PdfCatalog(fileName, setttings, this); + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(Worksheets[View.ActiveTab].PrinterSettings); + PdfCatalog catalog = new PdfCatalog(fileName, settings, this); } /// @@ -751,8 +753,8 @@ public void SaveAsPdf(string fileName) /// Worksheets to export. public void SaveAsPdf(string fileName, params ExcelWorksheet[] worksheets) { - var setttings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(Worksheets[0].PrinterSettings); - PdfCatalog catalog = new PdfCatalog(fileName, setttings, worksheets); + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(worksheets[0].PrinterSettings); + PdfCatalog catalog = new PdfCatalog(fileName, settings, worksheets); } /// @@ -762,9 +764,76 @@ public void SaveAsPdf(string fileName, params ExcelWorksheet[] worksheets) /// Ranges to export. public void SaveAsPdf(string fileName, params ExcelRangeBase[] ranges) { - var setttings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(ranges[0].Worksheet.PrinterSettings); + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(ranges[0].Worksheet.PrinterSettings); + PdfCatalog catalog = new PdfCatalog(fileName, settings, ranges); + } + + /// + /// Export workbook to PDF asynchronously. + /// + /// Name of file. + /// Token to cancel the operation. + /// A task representing the asynchronous operation. + public Task SaveAsPdfAsync(string fileName, CancellationToken cancellationToken = default) + { + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(Worksheets[View.ActiveTab + 1].PrinterSettings); + return Task.Run(() => + { + cancellationToken.ThrowIfCancellationRequested(); + _ = new PdfCatalog(fileName, settings, this); + }, cancellationToken); + } + + /// + /// Export selected worksheets to PDF asynchronously. + /// + /// Name of file. + /// Worksheets to export. + /// A task representing the asynchronous operation. + public Task SaveAsPdfAsync(string fileName, params ExcelWorksheet[] worksheets) + => SaveAsPdfAsync(fileName, CancellationToken.None, worksheets); + + /// + /// Export selected worksheets to PDF asynchronously. + /// + /// Name of file. + /// Token to cancel the operation. + /// Worksheets to export. + /// A task representing the asynchronous operation. + public Task SaveAsPdfAsync(string fileName, CancellationToken cancellationToken, params ExcelWorksheet[] worksheets) + { + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings( worksheets[0].PrinterSettings); + return Task.Run(() => + { + cancellationToken.ThrowIfCancellationRequested(); + _ = new PdfCatalog(fileName, settings, worksheets); + }, cancellationToken); + } + + /// + /// Export selected ranges to PDF asynchronously. + /// + /// Name of file. + /// Ranges to export. + /// A task representing the asynchronous operation. + public Task SaveAsPdfAsync(string fileName, params ExcelRangeBase[] ranges) + => SaveAsPdfAsync(fileName, CancellationToken.None, ranges); - PdfCatalog catalog = new PdfCatalog(fileName, setttings, ranges); + /// + /// Export selected ranges to PDF asynchronously. + /// + /// Name of file. + /// Token to cancel the operation. + /// Ranges to export. + /// A task representing the asynchronous operation. + public Task SaveAsPdfAsync(string fileName, CancellationToken cancellationToken, params ExcelRangeBase[] ranges) + { + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(ranges[0].Worksheet.PrinterSettings); + return Task.Run(() => + { + cancellationToken.ThrowIfCancellationRequested(); + _ = new PdfCatalog(fileName, settings, ranges); + }, cancellationToken); } //public ExcelHtmlRangeExporter CreateHtmlExporter(params ExcelRangeBase[] ranges) diff --git a/src/EPPlus/ExcelWorksheet.cs b/src/EPPlus/ExcelWorksheet.cs index a81629b37b..8717d071c2 100644 --- a/src/EPPlus/ExcelWorksheet.cs +++ b/src/EPPlus/ExcelWorksheet.cs @@ -23,6 +23,7 @@ Date Author Change using OfficeOpenXml.Drawing; using OfficeOpenXml.Drawing.Interfaces; using OfficeOpenXml.Drawing.Vml; +using OfficeOpenXml.Export.PdfExport; using OfficeOpenXml.Export.PdfExport.Settings; using OfficeOpenXml.Filter; using OfficeOpenXml.FormulaParsing; @@ -42,7 +43,6 @@ Date Author Change using OfficeOpenXml.Utils.String; using OfficeOpenXml.Utils.TypeConversion; using OfficeOpenXml.Utils.XML; -using OfficeOpenXml.Export.PdfExport; using System; using System.Collections; using System.Collections.Generic; @@ -51,6 +51,8 @@ Date Author Change using System.IO; using System.Linq; using System.Text; +using System.Threading; +using System.Threading.Tasks; using System.Xml; namespace OfficeOpenXml @@ -3753,6 +3755,22 @@ public void SaveAsPdf(string fileName) PdfCatalog catalog = new PdfCatalog(fileName, setttings, this); } + /// + /// Export worksheet to PDF asynchronously. + /// + /// Name of file. + /// Token to cancel the operation. + /// A task representing the asynchronous operation. + public Task SaveAsPdfAsync(string fileName, CancellationToken cancellationToken = default) + { + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(PrinterSettings); + return Task.Run(() => + { + cancellationToken.ThrowIfCancellationRequested(); + _ = new PdfCatalog(fileName, settings, this); + }, cancellationToken); + } + ExcelPackage IPictureRelationDocument.Package { get { return _package; } } Dictionary _hashes = new Dictionary(); diff --git a/src/EPPlus/Export/PdfExport/Settings/GetPdfSettings.cs b/src/EPPlus/Export/PdfExport/Settings/GetPdfSettings.cs index 2ca9164b68..fe30db6bf9 100644 --- a/src/EPPlus/Export/PdfExport/Settings/GetPdfSettings.cs +++ b/src/EPPlus/Export/PdfExport/Settings/GetPdfSettings.cs @@ -33,10 +33,10 @@ internal static PdfPageSettings GetPdfSettingsFromPrinterSettings(ExcelPrinterSe //Scaling is not yet implemented. settings.Scaling = new PdfScaling(eps.Scale); settings.ShowHeadings = eps.ShowHeaders; - settings.RowsToRepeatAtTop = eps.RepeatRows.Address; - settings.ColumnsToRepeatAtLeft = eps.RepeatColumns.Address; + settings.RowsToRepeatAtTop = eps.RepeatRows != null ? eps.RepeatRows.Address : null; + settings.ColumnsToRepeatAtLeft = eps.RepeatColumns != null ? eps.RepeatColumns.Address : null; //Print area is implemented and uses the defined name instead of this setting. this setting should override the print area defined name. - settings.PrintArea = eps.PrintArea.Address; + settings.PrintArea = eps.PrintArea != null ? eps.PrintArea.Address : null; settings.ShowGridLines = eps.ShowGridLines; settings.CenterOnPageHorizontally = eps.HorizontalCentered; settings.CenterOnPageVertically = eps.VerticalCentered; From 709cc6a3aa2918026c38010be47f8c12b7547981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Wed, 1 Jul 2026 10:28:18 +0200 Subject: [PATCH 44/49] Added tests and stream versions of save pdf to worksheet and range --- src/EPPlus.Export.Pdf.Tests/PdfTests.cs | 456 ++++++++++++++++++++-- src/EPPlus.Export.Pdf/ExcelPdf.cs | 143 +++++-- src/EPPlus/ExcelRangeBase.cs | 27 ++ src/EPPlus/ExcelWorkbook.cs | 102 ++++- src/EPPlus/ExcelWorksheet.cs | 26 ++ src/EPPlus/Export/PdfExport/PdfCatalog.cs | 83 +++- 6 files changed, 751 insertions(+), 86 deletions(-) diff --git a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs index f6e712d89c..eb665856b5 100644 --- a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs +++ b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs @@ -11,21 +11,38 @@ Date Author Change 10/07/2025 EPPlus Software AB EPPlus.Fonts.OpenType 1.0 *************************************************************************************************/ using EPPlus.Export.Pdf.Settings; -using OfficeOpenXml.Export.PdfExport; -using EPPlus.Export.Pdf.Settings.PdfPageSizes; -using EPPlus.Export.Pdf; -using EPPlus.Fonts.OpenType; -using EPPlus.Fonts.OpenType.Integration; using OfficeOpenXml; -using OfficeOpenXml.Interfaces.RichText; -using OfficeOpenXml.Style; +using OfficeOpenXml.Export.PdfExport; using System.Diagnostics; +using System.Text; namespace EPPlusTest.PDF { [TestClass] public class PdfTests : TestBase { + private static void AssertLooksLikePdf(byte[] bytes) + { + Assert.IsTrue(bytes.Length > 0, "PDF output is empty."); + string head = Encoding.ASCII.GetString(bytes, 0, Math.Min(8, bytes.Length)); + Assert.IsTrue(head.StartsWith("%PDF-"), $"Missing PDF header. Got: '{head}'"); + int tailLen = Math.Min(8, bytes.Length); + string tail = Encoding.ASCII.GetString(bytes, bytes.Length - tailLen, tailLen); + Assert.IsTrue(tail.Contains("%%EOF"), "Missing %%EOF trailer marker."); + } + + private static long ParseStartXref(byte[] bytes, int pdfStart) + { + string text = Encoding.ASCII.GetString(bytes, pdfStart, bytes.Length - pdfStart); + int idx = text.LastIndexOf("startxref", StringComparison.Ordinal); + Assert.IsTrue(idx >= 0, "startxref keyword not found."); + int i = idx + "startxref".Length; + while (i < text.Length && (text[i] == '\n' || text[i] == '\r' || text[i] == ' ')) i++; + int start = i; + while (i < text.Length && char.IsDigit(text[i])) i++; + return long.Parse(text.Substring(start, i - start)); + } + /* BIG PDF TODO * * Missing Features: @@ -46,7 +63,7 @@ public class PdfTests : TestBase * * Bugs: * Merged cell uses full width/height even when columns/rows are hidden. - * Conditional formatting not worksing 100% of the time. + * Conditional formatting not working 100% of the time. * Table could sometimes select wrong style * * Other @@ -59,31 +76,15 @@ public class PdfTests : TestBase protected static string pdfPath = _worksheetPath + "\\PDF\\"; - //ta bort - [TestMethod] - public void ReadPrintAreas() - { - //using var p = OpenTemplatePackage("PdfPrintAreas.xlsx"); - using var p = OpenTemplatePackage("PDFTest.xlsx"); - //using var p = OpenTemplatePackage("PDFTest_old.xlsx"); - //using var p = OpenTemplatePackage("DoubleBorder.xlsx"); - var ws = p.Workbook.Worksheets[0]; - PdfPageSettings pageSettings = new PdfPageSettings(); - pageSettings.CommentsAndNotes = CommentsAndNotes.AtEndOfSheet; - pageSettings.CellErrors = CellErrors.NA; - pageSettings.Debug = true; - pageSettings.PrintAsText = true; - pageSettings.ShowGridLines = true; - pageSettings.ShowHeadings = true; - PdfCatalog catlog = new PdfCatalog("C:\\epplustest\\pdf\\FullPageTest59.pdf", pageSettings, ws); - } - [TestMethod] public void SaveWorksheetAsPdfTest1() { using var p = OpenTemplatePackage("PDFTest.xlsx"); var ws = p.Workbook.Worksheets[0]; - ws.SaveAsPdf(pdfPath + "WorksheetTest1.pdf"); + string path = pdfPath + "WorksheetTest1.pdf"; + ws.SaveAsPdf(path); + Assert.IsTrue(File.Exists(path), "PDF file was not created."); + AssertLooksLikePdf(File.ReadAllBytes(path)); } [TestMethod] @@ -95,7 +96,10 @@ public void SaveWorksheetAsPdfTest2() ws.PrinterSettings.ShowGridLines = false; ws.PrinterSettings.ShowHeaders = false; ws.PrinterSettings.PaperSize = ePaperSize.A3; - ws.SaveAsPdf(pdfPath + "WorksheetTest2.pdf"); + string path = pdfPath + "WorksheetTest2.pdf"; + ws.SaveAsPdf(path); + Assert.IsTrue(File.Exists(path), "PDF file was not created."); + AssertLooksLikePdf(File.ReadAllBytes(path)); } [TestMethod] @@ -103,7 +107,10 @@ public void SaveRangeAsPdfTest1() { using var p = OpenTemplatePackage("PDFTest.xlsx"); var range = p.Workbook.Worksheets[0].Cells["D3:F6"]; - range.SaveAsPdf(pdfPath + "RangeTest1.pdf"); + string path = pdfPath + "RangeTest1.pdf"; + range.SaveAsPdf(path); + Assert.IsTrue(File.Exists(path), "PDF file was not created."); + AssertLooksLikePdf(File.ReadAllBytes(path)); } [TestMethod] @@ -111,7 +118,10 @@ public void SaveWorkbookAsPdfTest1() { using var p = OpenTemplatePackage("PDFTest.xlsx"); var wb = p.Workbook; - wb.SaveAsPdf(pdfPath + "WorkbookTest1.pdf"); + string path = pdfPath + "WorkbookTest1.pdf"; + wb.SaveAsPdf(path); + Assert.IsTrue(File.Exists(path), "PDF file was not created."); + AssertLooksLikePdf(File.ReadAllBytes(path)); } [TestMethod] @@ -122,7 +132,10 @@ public void SaveWorksheetsAsPdfTest2() var ws0 = wb.Worksheets[0]; var ws1 = wb.Worksheets[1]; var ws2 = wb.Worksheets[2]; - wb.SaveAsPdf(pdfPath + "WorksheetsTest2.pdf", ws0, ws1, ws2); + string path = pdfPath + "WorksheetsTest2.pdf"; + wb.SaveAsPdf(path, ws0, ws1, ws2); + Assert.IsTrue(File.Exists(path), "PDF file was not created."); + AssertLooksLikePdf(File.ReadAllBytes(path)); } [TestMethod] @@ -132,7 +145,10 @@ public void SaveWorksheetsAsPdfTest1() var wb = p.Workbook; var ws0 = wb.Worksheets[0]; var ws2 = wb.Worksheets[2]; - wb.SaveAsPdf(pdfPath + "WorksheetsTest1.pdf", ws0, ws2); + string path = pdfPath + "WorksheetsTest1.pdf"; + wb.SaveAsPdf(path, ws0, ws2); + Assert.IsTrue(File.Exists(path), "PDF file was not created."); + AssertLooksLikePdf(File.ReadAllBytes(path)); } [TestMethod] @@ -145,7 +161,376 @@ public void SaveRangesAsPdfTest1() var r2 = ws.Cells["B36:F39"]; var r3 = ws.Cells["K49:Q58"]; var r4 = ws.Cells["L142:Q147"]; - wb.SaveAsPdf(pdfPath + "RangesTest1.pdf", r1, r2, r3, r4); + string path = pdfPath + "RangesTest1.pdf"; + wb.SaveAsPdf(path, r1, r2, r3, r4); + Assert.IsTrue(File.Exists(path), "PDF file was not created."); + AssertLooksLikePdf(File.ReadAllBytes(path)); + } + + [TestMethod] + public async Task SaveWorkbookAsPdfAsyncTest() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var wb = p.Workbook; + string tempFile = Path.GetTempFileName(); + try + { + await wb.SaveAsPdfAsync(tempFile); + AssertLooksLikePdf(File.ReadAllBytes(tempFile)); + } + finally + { + if (File.Exists(tempFile)) File.Delete(tempFile); + } + } + + [TestMethod] + public async Task SaveWorksheetsAsPdfAsyncTest() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var wb = p.Workbook; + var ws0 = wb.Worksheets[0]; + var ws2 = wb.Worksheets[2]; + string tempFile = Path.GetTempFileName(); + try + { + await wb.SaveAsPdfAsync(tempFile, ws0, ws2); + AssertLooksLikePdf(File.ReadAllBytes(tempFile)); + } + finally + { + if (File.Exists(tempFile)) File.Delete(tempFile); + } + } + + [TestMethod] + public async Task SaveWorksheetsAsPdfAsyncWithTokenTest() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var wb = p.Workbook; + var ws0 = wb.Worksheets[0]; + var ws2 = wb.Worksheets[2]; + string tempFile = Path.GetTempFileName(); + try + { + await wb.SaveAsPdfAsync(tempFile, CancellationToken.None, ws0, ws2); + AssertLooksLikePdf(File.ReadAllBytes(tempFile)); + } + finally + { + if (File.Exists(tempFile)) File.Delete(tempFile); + } + } + + [TestMethod] + public async Task SaveRangesAsPdfAsyncTest() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var wb = p.Workbook; + var ws = wb.Worksheets[0]; + var r1 = ws.Cells["D3:F6"]; + var r2 = ws.Cells["B36:F39"]; + string tempFile = Path.GetTempFileName(); + try + { + await wb.SaveAsPdfAsync(tempFile, r1, r2); + AssertLooksLikePdf(File.ReadAllBytes(tempFile)); + } + finally + { + if (File.Exists(tempFile)) File.Delete(tempFile); + } + } + + [TestMethod] + public async Task SaveRangesAsPdfAsyncWithTokenTest() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var wb = p.Workbook; + var ws = wb.Worksheets[0]; + var r1 = ws.Cells["D3:F6"]; + var r2 = ws.Cells["B36:F39"]; + string tempFile = Path.GetTempFileName(); + try + { + await wb.SaveAsPdfAsync(tempFile, CancellationToken.None, r1, r2); + AssertLooksLikePdf(File.ReadAllBytes(tempFile)); + } + finally + { + if (File.Exists(tempFile)) File.Delete(tempFile); + } + } + + [TestMethod] + public async Task SaveWorkbookAsPdfAsyncWithCanceledTokenThrowsTest() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var wb = p.Workbook; + string tempFile = Path.GetTempFileName(); + try + { + using var cts = new CancellationTokenSource(); + cts.Cancel(); + + await Assert.ThrowsExactlyAsync( + () => wb.SaveAsPdfAsync(tempFile, cts.Token)); + } + finally + { + if (File.Exists(tempFile)) File.Delete(tempFile); + } + } + + [TestMethod] + public void SaveWorkbookAsPdfToStreamTest() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var wb = p.Workbook; + using var ms = new MemoryStream(); + wb.SaveAsPdf(ms); + AssertLooksLikePdf(ms.ToArray()); + } + + [TestMethod] + public void SaveWorksheetsAsPdfToStreamTest() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var wb = p.Workbook; + var ws0 = wb.Worksheets[0]; + var ws2 = wb.Worksheets[2]; + using var ms = new MemoryStream(); + wb.SaveAsPdf(ms, ws0, ws2); + AssertLooksLikePdf(ms.ToArray()); + } + + [TestMethod] + public void SaveRangesAsPdfToStreamTest() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var wb = p.Workbook; + var ws = wb.Worksheets[0]; + var r1 = ws.Cells["D3:F6"]; + var r2 = ws.Cells["B36:F39"]; + using var ms = new MemoryStream(); + wb.SaveAsPdf(ms, r1, r2); + AssertLooksLikePdf(ms.ToArray()); + } + + [TestMethod] + public async Task SaveWorkbookAsPdfToStreamAsyncTest() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var wb = p.Workbook; + using var ms = new MemoryStream(); + await wb.SaveAsPdfAsync(ms); + AssertLooksLikePdf(ms.ToArray()); + } + + [TestMethod] + public async Task SaveAsPdfAsyncWithCanceledTokenThrowsTest() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var wb = p.Workbook; + using var ms = new MemoryStream(); + using var cts = new CancellationTokenSource(); + cts.Cancel(); + await Assert.ThrowsExactlyAsync( () => wb.SaveAsPdfAsync(ms, cts.Token)); + } + + [TestMethod] + public void SaveAsPdfToStreamLeavesStreamOpenTest() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var wb = p.Workbook; + using var ms = new MemoryStream(); + wb.SaveAsPdf(ms); + Assert.IsTrue(ms.CanWrite, "Stream was closed by the export."); + Assert.IsTrue(ms.CanRead, "Stream was closed by the export."); + Assert.IsTrue(ms.Length > 0, "Nothing was written to the stream."); + } + + [TestMethod] + public void StreamOffsetsAreRelativeToPdfStartTest() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var wb = p.Workbook; + using var ms = new MemoryStream(); + // Pre-fill the stream so the PDF does not start at offset 0. + byte[] preamble = Encoding.ASCII.GetBytes("LEADING BYTES THAT ARE NOT PART OF THE PDF"); + ms.Write(preamble, 0, preamble.Length); + int pdfStart = (int)ms.Position; + wb.SaveAsPdf(ms); + byte[] all = ms.ToArray(); + // The PDF itself still starts with the header at the captured position. + string header = Encoding.ASCII.GetString(all, pdfStart, 5); + Assert.AreEqual("%PDF-", header, "PDF was not written at the stream's current position."); + // startxref must point at the xref table relative to the PDF start. + long startXref = ParseStartXref(all, pdfStart); + string atOffset = Encoding.ASCII.GetString(all, pdfStart + (int)startXref, 4); + Assert.AreEqual("xref", atOffset, "startxref offset is not relative to the PDF start."); + } + + [TestMethod] + public void FileAndStreamProduceSamePdfLengthTest() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var wb = p.Workbook; + string tempFile = Path.GetTempFileName(); + try + { + wb.SaveAsPdf(tempFile); + long fileLength = new FileInfo(tempFile).Length; + using var ms = new MemoryStream(); + wb.SaveAsPdf(ms); + Assert.AreEqual(fileLength, ms.Length, "Stream output length differs from file output length."); + } + finally + { + if (File.Exists(tempFile)) File.Delete(tempFile); + } + } + + [TestMethod] + public void SaveWorksheetToStreamViaCatalogTest() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var ws = p.Workbook.Worksheets[0]; + var pageSettings = new PdfPageSettings(); + using var ms = new MemoryStream(); + _ = new PdfCatalog(ms, pageSettings, ws); + AssertLooksLikePdf(ms.ToArray()); + } + + [TestMethod] + public void SaveAsPdfToNonWritableStreamThrowsTest() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var wb = p.Workbook; + using var readOnly = new MemoryStream(new byte[16], writable: false); + Assert.ThrowsExactly(() => wb.SaveAsPdf(readOnly)); + } + + [TestMethod] + public void SaveWorksheetAsPdfToStreamTest() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var ws = p.Workbook.Worksheets[0]; + using var ms = new MemoryStream(); + ws.SaveAsPdf(ms); + AssertLooksLikePdf(ms.ToArray()); + } + + [TestMethod] + public async Task SaveWorksheetAsPdfToStreamAsyncTest() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var ws = p.Workbook.Worksheets[0]; + using var ms = new MemoryStream(); + await ws.SaveAsPdfAsync(ms); + AssertLooksLikePdf(ms.ToArray()); + } + + [TestMethod] + public void SaveWorksheetAsPdfToStreamLeavesStreamOpenTest() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var ws = p.Workbook.Worksheets[0]; + using var ms = new MemoryStream(); + ws.SaveAsPdf(ms); + Assert.IsTrue(ms.CanWrite, "Stream was closed by the export."); + Assert.IsTrue(ms.CanRead, "Stream was closed by the export."); + Assert.IsTrue(ms.Length > 0, "Nothing was written to the stream."); + } + + [TestMethod] + public async Task SaveWorksheetAsPdfAsyncToStreamWithCanceledTokenThrowsTest() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var ws = p.Workbook.Worksheets[0]; + using var ms = new MemoryStream(); + using var cts = new CancellationTokenSource(); + cts.Cancel(); + await Assert.ThrowsExactlyAsync(() => ws.SaveAsPdfAsync(ms, cts.Token)); + } + + [TestMethod] + public void SaveWorksheetToStreamOffsetsAreRelativeToPdfStartTest() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var ws = p.Workbook.Worksheets[0]; + using var ms = new MemoryStream(); + byte[] preamble = Encoding.ASCII.GetBytes("LEADING BYTES THAT ARE NOT PART OF THE PDF"); + ms.Write(preamble, 0, preamble.Length); + int pdfStart = (int)ms.Position; + ws.SaveAsPdf(ms); + byte[] all = ms.ToArray(); + string header = Encoding.ASCII.GetString(all, pdfStart, 5); + Assert.AreEqual("%PDF-", header, "PDF was not written at the stream's current position."); + long startXref = ParseStartXref(all, pdfStart); + string atOffset = Encoding.ASCII.GetString(all, pdfStart + (int)startXref, 4); + Assert.AreEqual("xref", atOffset, "startxref offset is not relative to the PDF start."); + } + + [TestMethod] + public void SaveWorksheetToNonWritableStreamThrowsTest() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var ws = p.Workbook.Worksheets[0]; + using var readOnly = new MemoryStream(new byte[16], writable: false); + Assert.ThrowsExactly(() => ws.SaveAsPdf(readOnly)); + } + + [TestMethod] + public void SaveRangeAsPdfToStreamTest() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var range = p.Workbook.Worksheets[0].Cells["D3:F6"]; + using var ms = new MemoryStream(); + range.SaveAsPdf(ms); + AssertLooksLikePdf(ms.ToArray()); + } + + [TestMethod] + public async Task SaveRangeAsPdfToStreamAsyncTest() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var range = p.Workbook.Worksheets[0].Cells["D3:F6"]; + using var ms = new MemoryStream(); + await range.SaveAsPdfAsync(ms); + AssertLooksLikePdf(ms.ToArray()); + } + + [TestMethod] + public void SaveRangeAsPdfToStreamLeavesStreamOpenTest() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var range = p.Workbook.Worksheets[0].Cells["D3:F6"]; + using var ms = new MemoryStream(); + range.SaveAsPdf(ms); + Assert.IsTrue(ms.CanWrite, "Stream was closed by the export."); + Assert.IsTrue(ms.CanRead, "Stream was closed by the export."); + Assert.IsTrue(ms.Length > 0, "Nothing was written to the stream."); + } + + [TestMethod] + public async Task SaveRangeAsPdfAsyncToStreamWithCanceledTokenThrowsTest() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var range = p.Workbook.Worksheets[0].Cells["D3:F6"]; + using var ms = new MemoryStream(); + using var cts = new CancellationTokenSource(); + cts.Cancel(); + await Assert.ThrowsExactlyAsync(() => range.SaveAsPdfAsync(ms, cts.Token)); + } + + [TestMethod] + public void SaveRangeToNonWritableStreamThrowsTest() + { + using var p = OpenTemplatePackage("PDFTest.xlsx"); + var range = p.Workbook.Worksheets[0].Cells["D3:F6"]; + using var readOnly = new MemoryStream(new byte[16], writable: false); + Assert.ThrowsExactly(() => range.SaveAsPdf(readOnly)); } [TestMethod] @@ -172,8 +557,7 @@ public void PerformanceTest() Console.WriteLine($"workbook exported time elapsed: {sw.ElapsedMilliseconds} ms"); } - - [TestMethod] + [TestMethod] // works as expected. //[DataRow("PDFTest.xlsx", "C:\\epplustest\\pdf\\FullPageTest56.pdf", "Sheet1")] [DataRow("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx", "C:\\epplustest\\pdf\\OutputTest1.1.pdf", "SAP Data")] diff --git a/src/EPPlus.Export.Pdf/ExcelPdf.cs b/src/EPPlus.Export.Pdf/ExcelPdf.cs index d9d1834d56..debcae80c8 100644 --- a/src/EPPlus.Export.Pdf/ExcelPdf.cs +++ b/src/EPPlus.Export.Pdf/ExcelPdf.cs @@ -16,6 +16,7 @@ Date Author Change using EPPlus.Export.Pdf.Resources; using EPPlus.Export.Pdf.Settings; using EPPlus.Graphics; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -228,6 +229,33 @@ private PdfInfoObject AddInfoObject(string workBookName = "") internal void CreatePdf(PdfPageSettings pageSettings, PdfDictionaries dictionaries, Transform layout, string fileName) { + //Write the PDF to the file. The Stream overload does the actual work and + //populates _debugString. + using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write)) + { + CreatePdf(pageSettings, dictionaries, layout, fs); + } + //Write pdf as txt for debug. + if (_pageSettings.Debug && _pageSettings.PrintAsText) + { + using (var fs = new FileStream(fileName + ".txt", FileMode.Create, FileAccess.Write)) + { + using (var wr = new StreamWriter(fs)) + { + wr.Write(_debugString); + } + } + } + } + + internal void CreatePdf(PdfPageSettings pageSettings, PdfDictionaries dictionaries, Transform layout, Stream stream) + { + if (stream == null) throw new ArgumentNullException(nameof(stream)); + if (!stream.CanWrite) throw new ArgumentException("The stream must be writable.", nameof(stream)); + //The cross-reference table stores byte offsets that the PDF reader uses to + //seek to each object, so the target stream has to support querying its position. + if (!stream.CanSeek) throw new ArgumentException("The stream must be seekable, because the PDF cross-reference table requires byte offsets.", nameof(stream)); + _pageSettings = pageSettings; _dictionaries = dictionaries; var catalog = AddCatalog(2); @@ -249,43 +277,96 @@ internal void CreatePdf(PdfPageSettings pageSettings, PdfDictionaries dictionari pages.pageObjectNumbers.Add(page.objectNumber); } var info = AddInfoObject(); - string debugString = ""; + _debugString = ""; //write to pdf PdfCrossRefTable crossRefTable = new PdfCrossRefTable(); - //start wring pdf binary - using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write)) + //Cross-reference offsets are relative to the start of the PDF. A freshly created + //FileStream starts at position 0, but a caller-supplied stream may already hold + //data, so capture the starting position and make every offset relative to it. + long start = stream.Position; + //start writing pdf binary. leaveOpen: true so a caller-supplied stream is not closed. + using (var bw = new BinaryWriter(stream, Encoding.ASCII, true)) { - using (var bw = new BinaryWriter(fs, Encoding.ASCII)) + //Write header + bw.Write(Encoding.ASCII.GetBytes(Header)); + _debugString += Header; + //Write body + foreach (var pdfobj in _document) { - //Write header - bw.Write(Encoding.ASCII.GetBytes(Header)); - debugString += Header; - //Write body - foreach (var pdfobj in _document) - { - crossRefTable.AddPosition(fs.Position); - pdfobj.ToPdfBytes(bw); - debugString += pdfobj.ToPdfString(); - } - //Write CrossReference - crossRefTable.Write(bw, fs.Position, _document.Count); - debugString += crossRefTable.WriteString(_document.Count); - // Write trailer - PdfTrailer.Write(bw, _document.Count, catalog.objectNumber, info.objectNumber, crossRefTable.StartPosition); - debugString += PdfTrailer.WriteString(_document.Count, catalog.objectNumber, info.objectNumber, crossRefTable.StartPosition); - } - } - //Write pdf as txt for debug. - if (_pageSettings.Debug && _pageSettings.PrintAsText) - { - using (var fs = new FileStream(fileName + ".txt", FileMode.Create, FileAccess.Write)) - { - using (var wr = new StreamWriter(fs)) - { - wr.Write(debugString); - } + crossRefTable.AddPosition(stream.Position - start); + pdfobj.ToPdfBytes(bw); + _debugString += pdfobj.ToPdfString(); } + //Write CrossReference + crossRefTable.Write(bw, stream.Position - start, _document.Count); + _debugString += crossRefTable.WriteString(_document.Count); + // Write trailer + PdfTrailer.Write(bw, _document.Count, catalog.objectNumber, info.objectNumber, crossRefTable.StartPosition); + _debugString += PdfTrailer.WriteString(_document.Count, catalog.objectNumber, info.objectNumber, crossRefTable.StartPosition); + bw.Flush(); } } + + //internal void CreatePdf(PdfPageSettings pageSettings, PdfDictionaries dictionaries, Transform layout, string fileName) + //{ + // _pageSettings = pageSettings; + // _dictionaries = dictionaries; + // var catalog = AddCatalog(2); + // //Create Pages + // var pagesLayout = layout.ChildObjects[0]; + // var pages = AddPages(); + // //Create Fonts + // AddFontData(); + // //Create Patterns + // AddPatternData(); + // //Create Shadings + // AddShadingsData(); + // //Create Page and Content + // for (int i = 0; i < layout.ChildObjects.Count; i++) + // { + // var pageLayout = layout.ChildObjects[i]; + // var page = AddPage(2, new List(), _pageSettings); + // AddContent(pageLayout, page); + // pages.pageObjectNumbers.Add(page.objectNumber); + // } + // var info = AddInfoObject(); + // string debugString = ""; + // //write to pdf + // PdfCrossRefTable crossRefTable = new PdfCrossRefTable(); + // //start wring pdf binary + // using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write)) + // { + // using (var bw = new BinaryWriter(fs, Encoding.ASCII)) + // { + // //Write header + // bw.Write(Encoding.ASCII.GetBytes(Header)); + // debugString += Header; + // //Write body + // foreach (var pdfobj in _document) + // { + // crossRefTable.AddPosition(fs.Position); + // pdfobj.ToPdfBytes(bw); + // debugString += pdfobj.ToPdfString(); + // } + // //Write CrossReference + // crossRefTable.Write(bw, fs.Position, _document.Count); + // debugString += crossRefTable.WriteString(_document.Count); + // // Write trailer + // PdfTrailer.Write(bw, _document.Count, catalog.objectNumber, info.objectNumber, crossRefTable.StartPosition); + // debugString += PdfTrailer.WriteString(_document.Count, catalog.objectNumber, info.objectNumber, crossRefTable.StartPosition); + // } + // } + // //Write pdf as txt for debug. + // if (_pageSettings.Debug && _pageSettings.PrintAsText) + // { + // using (var fs = new FileStream(fileName + ".txt", FileMode.Create, FileAccess.Write)) + // { + // using (var wr = new StreamWriter(fs)) + // { + // wr.Write(debugString); + // } + // } + // } + //} } } \ No newline at end of file diff --git a/src/EPPlus/ExcelRangeBase.cs b/src/EPPlus/ExcelRangeBase.cs index 484741baff..61f357fe9f 100644 --- a/src/EPPlus/ExcelRangeBase.cs +++ b/src/EPPlus/ExcelRangeBase.cs @@ -33,6 +33,7 @@ Date Author Change using System.Collections; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Threading; using System.Threading.Tasks; @@ -1145,6 +1146,32 @@ public Task SaveAsPdfAsync(string fileName, CancellationToken cancellationToken }, cancellationToken); } + /// + /// Save range to PDF, writing to a stream. + /// + /// Stream to write the PDF to. The stream is not closed; the caller owns it. + public void SaveAsPdf(Stream stream) + { + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(this.Worksheet.PrinterSettings); + PdfCatalog catalog = new PdfCatalog(stream, settings, this); + } + + /// + /// Save range to PDF asynchronously, writing to a stream. + /// + /// Stream to write the PDF to. The stream is not closed; the caller owns it. + /// Token to cancel the operation. + /// A task representing the asynchronous operation. + public Task SaveAsPdfAsync(Stream stream, CancellationToken cancellationToken = default) + { + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(this.Worksheet.PrinterSettings); + return Task.Run(() => + { + cancellationToken.ThrowIfCancellationRequested(); + _ = new PdfCatalog(stream, settings, this); + }, cancellationToken); + } + //public ExcelHtmlRangeExporter CreateHtmlExporter() //{ // return new ExcelHtmlRangeExporter(this); diff --git a/src/EPPlus/ExcelWorkbook.cs b/src/EPPlus/ExcelWorkbook.cs index 6f88380105..03e45ea284 100644 --- a/src/EPPlus/ExcelWorkbook.cs +++ b/src/EPPlus/ExcelWorkbook.cs @@ -776,7 +776,7 @@ public void SaveAsPdf(string fileName, params ExcelRangeBase[] ranges) /// A task representing the asynchronous operation. public Task SaveAsPdfAsync(string fileName, CancellationToken cancellationToken = default) { - var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(Worksheets[View.ActiveTab + 1].PrinterSettings); + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(Worksheets[View.ActiveTab].PrinterSettings); return Task.Run(() => { cancellationToken.ThrowIfCancellationRequested(); @@ -836,6 +836,106 @@ public Task SaveAsPdfAsync(string fileName, CancellationToken cancellationToken, }, cancellationToken); } + /// + /// Export workbook to PDF, writing to a stream. + /// + /// Stream to write the PDF to. The stream is not closed; the caller owns it. + public void SaveAsPdf(Stream stream) + { + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(Worksheets[View.ActiveTab].PrinterSettings); + PdfCatalog catalog = new PdfCatalog(stream, settings, this); + } + + /// + /// Export selected worksheets to PDF, writing to a stream. + /// + /// Stream to write the PDF to. The stream is not closed; the caller owns it. + /// Worksheets to export. + public void SaveAsPdf(Stream stream, params ExcelWorksheet[] worksheets) + { + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(worksheets[0].PrinterSettings); + PdfCatalog catalog = new PdfCatalog(stream, settings, worksheets); + } + + /// + /// Export selected ranges to PDF, writing to a stream. + /// + /// Stream to write the PDF to. The stream is not closed; the caller owns it. + /// Ranges to export. + public void SaveAsPdf(Stream stream, params ExcelRangeBase[] ranges) + { + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(ranges[0].Worksheet.PrinterSettings); + PdfCatalog catalog = new PdfCatalog(stream, settings, ranges); + } + + /// + /// Export workbook to PDF asynchronously, writing to a stream. + /// + /// Stream to write the PDF to. The stream is not closed; the caller owns it. + /// Token to cancel the operation. + /// A task representing the asynchronous operation. + public Task SaveAsPdfAsync(Stream stream, CancellationToken cancellationToken = default) + { + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(Worksheets[View.ActiveTab].PrinterSettings); + return Task.Run(() => + { + cancellationToken.ThrowIfCancellationRequested(); + _ = new PdfCatalog(stream, settings, this); + }, cancellationToken); + } + + /// + /// Export selected worksheets to PDF asynchronously, writing to a stream. + /// + /// Stream to write the PDF to. The stream is not closed; the caller owns it. + /// Worksheets to export. + /// A task representing the asynchronous operation. + public Task SaveAsPdfAsync(Stream stream, params ExcelWorksheet[] worksheets) + => SaveAsPdfAsync(stream, CancellationToken.None, worksheets); + + /// + /// Export selected worksheets to PDF asynchronously, writing to a stream. + /// + /// Stream to write the PDF to. The stream is not closed; the caller owns it. + /// Token to cancel the operation. + /// Worksheets to export. + /// A task representing the asynchronous operation. + public Task SaveAsPdfAsync(Stream stream, CancellationToken cancellationToken, params ExcelWorksheet[] worksheets) + { + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(worksheets[0].PrinterSettings); + return Task.Run(() => + { + cancellationToken.ThrowIfCancellationRequested(); + _ = new PdfCatalog(stream, settings, worksheets); + }, cancellationToken); + } + + /// + /// Export selected ranges to PDF asynchronously, writing to a stream. + /// + /// Stream to write the PDF to. The stream is not closed; the caller owns it. + /// Ranges to export. + /// A task representing the asynchronous operation. + public Task SaveAsPdfAsync(Stream stream, params ExcelRangeBase[] ranges) + => SaveAsPdfAsync(stream, CancellationToken.None, ranges); + + /// + /// Export selected ranges to PDF asynchronously, writing to a stream. + /// + /// Stream to write the PDF to. The stream is not closed; the caller owns it. + /// Token to cancel the operation. + /// Ranges to export. + /// A task representing the asynchronous operation. + public Task SaveAsPdfAsync(Stream stream, CancellationToken cancellationToken, params ExcelRangeBase[] ranges) + { + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(ranges[0].Worksheet.PrinterSettings); + return Task.Run(() => + { + cancellationToken.ThrowIfCancellationRequested(); + _ = new PdfCatalog(stream, settings, ranges); + }, cancellationToken); + } + //public ExcelHtmlRangeExporter CreateHtmlExporter(params ExcelRangeBase[] ranges) //{ // foreach (var range in ranges) diff --git a/src/EPPlus/ExcelWorksheet.cs b/src/EPPlus/ExcelWorksheet.cs index 8717d071c2..e015f08cc5 100644 --- a/src/EPPlus/ExcelWorksheet.cs +++ b/src/EPPlus/ExcelWorksheet.cs @@ -3771,6 +3771,32 @@ public Task SaveAsPdfAsync(string fileName, CancellationToken cancellationToken }, cancellationToken); } + /// + /// Export worksheet to PDF, writing to a stream. + /// + /// Stream to write the PDF to. The stream is not closed; the caller owns it. + public void SaveAsPdf(Stream stream) + { + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(PrinterSettings); + PdfCatalog catalog = new PdfCatalog(stream, settings, this); + } + + /// + /// Export worksheet to PDF asynchronously, writing to a stream. + /// + /// Stream to write the PDF to. The stream is not closed; the caller owns it. + /// Token to cancel the operation. + /// A task representing the asynchronous operation. + public Task SaveAsPdfAsync(Stream stream, CancellationToken cancellationToken = default) + { + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(PrinterSettings); + return Task.Run(() => + { + cancellationToken.ThrowIfCancellationRequested(); + _ = new PdfCatalog(stream, settings, this); + }, cancellationToken); + } + ExcelPackage IPictureRelationDocument.Package { get { return _package; } } Dictionary _hashes = new Dictionary(); diff --git a/src/EPPlus/Export/PdfExport/PdfCatalog.cs b/src/EPPlus/Export/PdfExport/PdfCatalog.cs index 17ee00d762..d84af67ccf 100644 --- a/src/EPPlus/Export/PdfExport/PdfCatalog.cs +++ b/src/EPPlus/Export/PdfExport/PdfCatalog.cs @@ -19,10 +19,11 @@ Date Author Change using OfficeOpenXml.Export.PdfExport.RowResize; using OfficeOpenXml.Export.PdfExport.TextMapping; using OfficeOpenXml.Export.PdfExport.TextShaping; +using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; -using static OfficeOpenXml.Drawing.OleObject.Structures.OleObjectDataStructures; namespace OfficeOpenXml.Export.PdfExport { @@ -39,20 +40,36 @@ public PdfCatalog() { } public PdfCatalog(string fileName, PdfPageSettings pageSettings, ExcelWorkbook workbook) { - HandleWorksheetCollection(pageSettings, workbook.Worksheets.ToArray(), fileName); + HandleWorksheetCollection(pageSettings, workbook.Worksheets.ToArray(), WriteToFile(pageSettings, fileName)); } public PdfCatalog(string fileName, PdfPageSettings pageSettings, ExcelWorksheet[] worksheets) { - HandleWorksheetCollection(pageSettings, worksheets, fileName); + HandleWorksheetCollection(pageSettings, worksheets, WriteToFile(pageSettings, fileName)); } public PdfCatalog(string fileName, PdfPageSettings pageSettings, List worksheets) { - HandleWorksheetCollection(pageSettings, worksheets.ToArray(), fileName); + HandleWorksheetCollection(pageSettings, worksheets.ToArray(), WriteToFile(pageSettings, fileName)); } - private void HandleWorksheetCollection(PdfPageSettings pageSettings, ExcelWorksheet[] worksheets, string fileName) + + public PdfCatalog(Stream stream, PdfPageSettings pageSettings, ExcelWorkbook workbook) + { + HandleWorksheetCollection(pageSettings, workbook.Worksheets.ToArray(), WriteToStream(pageSettings, stream)); + } + + public PdfCatalog(Stream stream, PdfPageSettings pageSettings, ExcelWorksheet[] worksheets) + { + HandleWorksheetCollection(pageSettings, worksheets, WriteToStream(pageSettings, stream)); + } + + public PdfCatalog(Stream stream, PdfPageSettings pageSettings, List worksheets) + { + HandleWorksheetCollection(pageSettings, worksheets.ToArray(), WriteToStream(pageSettings, stream)); + } + + private void HandleWorksheetCollection(PdfPageSettings pageSettings, ExcelWorksheet[] worksheets, Action writePdf) { // Match the single-worksheet path: resolve the default font before building. pageSettings.defaultFontName = worksheets[0].Workbook.ThemeManager.GetOrCreateTheme().FontScheme.MinorFont[0].Typeface; @@ -74,8 +91,7 @@ private void HandleWorksheetCollection(PdfPageSettings pageSettings, ExcelWorksh var layout = GetLayout(pageSettings, pdfSheets); // Write the PDF document. - ExcelPdf excelPdf = new ExcelPdf(); - excelPdf.CreatePdf(pageSettings, _dictionaries, layout, fileName); + writePdf(layout); } finally { @@ -101,10 +117,15 @@ private void HandleWorksheetCollection(PdfPageSettings pageSettings, ExcelWorksh public PdfCatalog(string fileName, PdfPageSettings pageSettings, ExcelWorksheet worksheet) { - BuildPdf(pageSettings, worksheet, fileName); + BuildPdf(pageSettings, worksheet, WriteToFile(pageSettings, fileName)); } - private void BuildPdf(PdfPageSettings pageSettings, ExcelWorksheet worksheet, string fileName) + public PdfCatalog(Stream stream, PdfPageSettings pageSettings, ExcelWorksheet worksheet) + { + BuildPdf(pageSettings, worksheet, WriteToStream(pageSettings, stream)); + } + + private void BuildPdf(PdfPageSettings pageSettings, ExcelWorksheet worksheet, Action writePdf) { //pageSettings.defaultFontName = worksheet.Workbook.ThemeManager.CurrentTheme.FontScheme.MinorFont[0].Typeface; pageSettings.defaultFontName = worksheet.Workbook.ThemeManager.GetOrCreateTheme().FontScheme.MinorFont[0].Typeface; @@ -142,8 +163,7 @@ private void BuildPdf(PdfPageSettings pageSettings, ExcelWorksheet worksheet, st sw.Start(); //Write Pdf Document - ExcelPdf excelPdf = new ExcelPdf(); - excelPdf.CreatePdf(pageSettings, _dictionaries, layout, fileName); + writePdf(layout); sw.Stop(); var CreatePdfTime = sw.ElapsedMilliseconds; sw.Reset(); @@ -165,6 +185,16 @@ private void BuildPdf(PdfPageSettings pageSettings, ExcelWorksheet worksheet, st // public PdfCatalog(string fileName, PdfPageSettings pageSettings, ExcelRangeBase range) + { + BuildPdfFromRange(pageSettings, range, WriteToFile(pageSettings, fileName)); + } + + public PdfCatalog(Stream stream, PdfPageSettings pageSettings, ExcelRangeBase range) + { + BuildPdfFromRange(pageSettings, range, WriteToStream(pageSettings, stream)); + } + + private void BuildPdfFromRange(PdfPageSettings pageSettings, ExcelRangeBase range, Action writePdf) { pageSettings.defaultFontName = range.Worksheet.Workbook.ThemeManager.GetOrCreateTheme().FontScheme.MinorFont[0].Typeface; @@ -176,8 +206,7 @@ public PdfCatalog(string fileName, PdfPageSettings pageSettings, ExcelRangeBase PdfCalculateRowHeight.ResizeRowHeights(pdfSheet); var layout = GetLayout(pageSettings, pdfSheet); // single-sheet GetLayout overload - ExcelPdf excelPdf = new ExcelPdf(); - excelPdf.CreatePdf(pageSettings, _dictionaries, layout, fileName); + writePdf(layout); } finally { @@ -191,15 +220,25 @@ public PdfCatalog(string fileName, PdfPageSettings pageSettings, ExcelRangeBase public PdfCatalog(string fileName, PdfPageSettings pageSettings, ExcelRangeBase[] ranges) { - HandleRangeCollection(pageSettings, ranges, fileName); + HandleRangeCollection(pageSettings, ranges, WriteToFile(pageSettings, fileName)); } public PdfCatalog(string fileName, PdfPageSettings pageSettings, List ranges) { - HandleRangeCollection(pageSettings, ranges.ToArray(), fileName); + HandleRangeCollection(pageSettings, ranges.ToArray(), WriteToFile(pageSettings, fileName)); } - private void HandleRangeCollection(PdfPageSettings pageSettings, ExcelRangeBase[] ranges, string fileName) + public PdfCatalog(Stream stream, PdfPageSettings pageSettings, ExcelRangeBase[] ranges) + { + HandleRangeCollection(pageSettings, ranges, WriteToStream(pageSettings, stream)); + } + + public PdfCatalog(Stream stream, PdfPageSettings pageSettings, List ranges) + { + HandleRangeCollection(pageSettings, ranges.ToArray(), WriteToStream(pageSettings, stream)); + } + + private void HandleRangeCollection(PdfPageSettings pageSettings, ExcelRangeBase[] ranges, Action writePdf) { pageSettings.defaultFontName = ranges[0].Worksheet.Workbook.ThemeManager.GetOrCreateTheme().FontScheme.MinorFont[0].Typeface; @@ -217,8 +256,7 @@ private void HandleRangeCollection(PdfPageSettings pageSettings, ExcelRangeBase[ var layout = GetLayout(pageSettings, pdfSheets); - ExcelPdf excelPdf = new ExcelPdf(); - excelPdf.CreatePdf(pageSettings, _dictionaries, layout, fileName); + writePdf(layout); } finally { @@ -246,6 +284,15 @@ internal PdfCellCollection GetCellCollectionFromRange(PdfPageSettings pageSettin //Private Methods + private Action WriteToFile(PdfPageSettings pageSettings, string fileName) + { + return layout => new ExcelPdf().CreatePdf(pageSettings, _dictionaries, layout, fileName); + } + + private Action WriteToStream(PdfPageSettings pageSettings, Stream stream) + { + return layout => new ExcelPdf().CreatePdf(pageSettings, _dictionaries, layout, stream); + } //Create Layout Methods From f9d9305fc06e59af19fc9cf8753123043d8be081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Wed, 1 Jul 2026 10:32:36 +0200 Subject: [PATCH 45/49] removed font export debug --- .../DocumentObjects/Fonts/PdfFontStream.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontStream.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontStream.cs index c3112d256e..b4a82f4c9b 100644 --- a/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontStream.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontStream.cs @@ -38,14 +38,6 @@ internal override void RenderDictionary(BinaryWriter bw) WriteAscii(bw, $"<< /Length {fontBytes.Length} >>\nstream\n"); bw.Write(fontBytes); WriteAscii(bw, "\nendstream"); - - using (var fs = new FileStream("c:\\epplustest\\pdf\\ExportedFont.ttf", FileMode.Create, FileAccess.Write)) - { - using (var bw2 = new BinaryWriter(fs, Encoding.ASCII)) - { - bw2.Write(fontBytes); - } - } } } } From da47d3fa08ac0a392c14e07656976461f4330a69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Wed, 1 Jul 2026 14:15:17 +0200 Subject: [PATCH 46/49] Using font enigne in workbook --- .../EPPlus.Export.Pdf.Tests.csproj | 1 + src/EPPlus.Export.Pdf.Tests/PdfTests.cs | 124 +----------------- src/EPPlus.Export.Pdf/ExcelPdf.cs | 27 ---- .../Settings/PdfPageSettings.cs | 25 +--- .../Settings/PdfPageSizes/PdfPageSize.cs | 3 - src/EPPlus/ExcelRangeBase.cs | 8 +- src/EPPlus/ExcelWorkbook.cs | 24 ++-- src/EPPlus/ExcelWorksheet.cs | 8 +- src/EPPlus/Export/PdfExport/PdfCatalog.cs | 2 +- .../PdfExport/Settings/GetPdfSettings.cs | 4 +- 10 files changed, 31 insertions(+), 195 deletions(-) diff --git a/src/EPPlus.Export.Pdf.Tests/EPPlus.Export.Pdf.Tests.csproj b/src/EPPlus.Export.Pdf.Tests/EPPlus.Export.Pdf.Tests.csproj index 37ce84a67b..902dc26e6d 100644 --- a/src/EPPlus.Export.Pdf.Tests/EPPlus.Export.Pdf.Tests.csproj +++ b/src/EPPlus.Export.Pdf.Tests/EPPlus.Export.Pdf.Tests.csproj @@ -17,6 +17,7 @@ + diff --git a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs index 6e98088fba..b6feaec3cb 100644 --- a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs +++ b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs @@ -52,37 +52,6 @@ private static long ParseStartXref(byte[] bytes, int pdfStart) return long.Parse(text.Substring(start, i - start)); } - /* BIG PDF TODO - * - * Missing Features: - * Embedding pictures as drawings - * Embedding pictures as cell content - * Embedding pictures as header/footer - * Number formatting - * Scaling document - * Conditional Formatting icons - * Vertical text - * Equations - * Pivot tables - * Shapes - * Charts - * 3D models - * Compression - * Unique pdf settings per worksheet - * - * Bugs: - * Merged cell uses full width/height even when columns/rows are hidden. - * Conditional formatting not working 100% of the time. - * Table could sometimes select wrong style - * - * Other - * Cells: Remove stroke from solid fill and adjust size and position of cell more precise and only use fill command. - * Gradients: Make diamond gradients instead of radial gradtient in from corner and center gradient - * Text: Set up to use back up techniques when not embedding font - * Borders: Adjust and make border look better - * - */ - protected static string pdfPath = _worksheetPath + "\\PDF\\"; [TestMethod] @@ -405,7 +374,7 @@ public void SaveWorksheetToStreamViaCatalogTest() { using var p = OpenTemplatePackage("PDFTest.xlsx"); var ws = p.Workbook.Worksheets[0]; - var pageSettings = new PdfPageSettings(); + var pageSettings = new PdfPageSettings(ws.Workbook.RenderContext.FontEngine); using var ms = new MemoryStream(); _ = new PdfCatalog(ms, pageSettings, ws); AssertLooksLikePdf(ms.ToArray()); @@ -542,30 +511,6 @@ public void SaveRangeToNonWritableStreamThrowsTest() Assert.ThrowsExactly(() => range.SaveAsPdf(readOnly)); } - [TestMethod] - public void PerformanceTest() - { - var sw = new Stopwatch(); - Console.WriteLine("Starting..."); - sw.Start(); - using var p = OpenTemplatePackage("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx"); - Console.WriteLine($"Read package time elapsed: {sw.ElapsedMilliseconds} ms"); - sw.Restart(); - var ws = p.Workbook.Worksheets[0]; - var dun = ws.Dimension; - var pageSettings = new PdfPageSettings - { - CommentsAndNotes = CommentsAndNotes.AtEndOfSheet, - CellErrors = CellErrors.NA, - Debug = true, - PrintAsText = true, - ShowGridLines = false, - ShowHeadings = true - }; - PdfCatalog catalog = new PdfCatalog("C:\\epplustest\\pdf\\OutputTest1.3.pdf", pageSettings, ws); - Console.WriteLine($"workbook exported time elapsed: {sw.ElapsedMilliseconds} ms"); - } - [TestMethod] // works as expected. //[DataRow("PDFTest.xlsx", "C:\\epplustest\\pdf\\FullPageTest56.pdf", "Sheet1")] @@ -594,7 +539,7 @@ public void WorkbookTests(string sourceFile, string outputPath, string wsName) var d = ws.Dimension; var d2 = ws.DimensionByValue; - PdfPageSettings pageSettings = new PdfPageSettings(); + PdfPageSettings pageSettings = new PdfPageSettings(ws.Workbook.RenderContext.FontEngine); pageSettings.CommentsAndNotes = CommentsAndNotes.AtEndOfSheet; pageSettings.CellErrors = CellErrors.Displayed; @@ -606,70 +551,5 @@ public void WorkbookTests(string sourceFile, string outputPath, string wsName) PdfCatalog catalog = new PdfCatalog(outputPath, pageSettings, ws); } - - [TestMethod] - public void PerfTest() - { - var sw = new Stopwatch(); - Console.WriteLine("Starting..."); - sw.Start(); - using var p = OpenTemplatePackage("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx"); - Console.WriteLine($"Read package time elapsed: {sw.ElapsedMilliseconds} ms"); - sw.Restart(); - var ws = p.Workbook.Worksheets[0]; - var dun = ws.Dimension; - var pageSettings = new PdfPageSettings - { - CommentsAndNotes = CommentsAndNotes.AtEndOfSheet, - CellErrors = CellErrors.NA, - Debug = true, - PrintAsText = true, - ShowGridLines = false, - ShowHeadings = true - }; - PdfCatalog catalog = new PdfCatalog(pageSettings, ws, "C:\\epplustest\\pdf\\OutputTest1.3.pdf"); - Console.WriteLine($"workbook exported time elapsed: {sw.ElapsedMilliseconds} ms"); - } - - - [TestMethod] - // works as expected. - //[DataRow("PDFTest.xlsx", "C:\\epplustest\\pdf\\FullPageTest56.pdf", "Sheet1")] - [DataRow("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx", "C:\\epplustest\\pdf\\OutputTest1.1.pdf", "SAP Data")] - - // Output file: OutputTest1.2.pdf - // 1. Minus signs alignment in cells differs from Excel. ------------------------------------------------ Comment: Currently no support for number formats. Requires implementing number formats. - // 2. Dimension seems to differ from Excel, Excel stops at row 75, EPPlus goes to row 89. --------------- Fixed - // 3. Row headings are sligthly wider in EPPlus than in Excel. ------------------------------------------ Fixed - [DataRow("Aico_0105_S_ALR_87011990_AICO_ASSET_ITE_2025-04_BS.xlsx", "C:\\epplustest\\pdf\\OutputTest1.2.pdf", "Summary")] - // works as expected - [DataRow("Aico WiP 120180 FBL3N for 0110 in 2025-04.xlsx", "C:\\epplustest\\pdf\\OutputTest1.4.pdf", "Technical")] - [DataRow("Aico KKS1 Variance Calculation for 0105 in 2025-04 (25_4_2025 15_43_40) .xlsx", "C:\\epplustest\\pdf\\OutputTest1.5.pdf", "Technical")] - - // Output file: OutputTest1.6.pdf - // 1. Merged cells not working ------------------------------------ Fixed. Comment Merged cells was fine, it was borders being rendered inside merged cells. - // 2. Pattern fills looks differnt, in some cases not working ----- - // 3. Rotation of text in cells not working (the dates). ---------- - // [DataRow("R05.xlsx", "C:\\epplustest\\pdf\\OutputTest1.6.pdf", "R05 Arbeitseinteilung")] - [DataRow("R05 - Copy.xlsx", "C:\\epplustest\\pdf\\OutputTest1.6.pdf", "R05 Arbeitseinteilung")] - //[DataRow("PatternStyles.xlsx", "C:\\epplustest\\pdf\\OutputTest1.8.pdf", "Sheet1")] - public void WorkbookTests(string sourceFile, string outputPath, string wsName) - { - using var p = OpenTemplatePackage(sourceFile); - var ws = p.Workbook.Worksheets[wsName]; - var d = ws.Dimension; - var d2 = ws.DimensionByValue; - - PdfPageSettings pageSettings = new PdfPageSettings(); - pageSettings.CommentsAndNotes = CommentsAndNotes.AtEndOfSheet; - - pageSettings.CellErrors = CellErrors.Displayed; - pageSettings.Debug = true; - pageSettings.PrintAsText = true; - pageSettings.ShowGridLines = false; - pageSettings.ShowHeadings = false; - - PdfCatalog catalog = new PdfCatalog(pageSettings, ws, outputPath); - } } } diff --git a/src/EPPlus.Export.Pdf/ExcelPdf.cs b/src/EPPlus.Export.Pdf/ExcelPdf.cs index 3fbf8c026b..2c03a03def 100644 --- a/src/EPPlus.Export.Pdf/ExcelPdf.cs +++ b/src/EPPlus.Export.Pdf/ExcelPdf.cs @@ -296,33 +296,6 @@ internal void CreatePdf(PdfPageSettings pageSettings, PdfDictionaries dictionari crossRefTable.AddPosition(stream.Position - start); pdfobj.ToPdfBytes(bw); _debugString += pdfobj.ToPdfString(); - //Write header - bw.Write(Encoding.ASCII.GetBytes(Header)); - debugString += Header; - //Write body - foreach (var pdfobj in _document) - { - crossRefTable.AddPosition(fs.Position); - pdfobj.ToPdfBytes(bw); - debugString += pdfobj.ToPdfString(); - } - //Write CrossReference - crossRefTable.Write(bw, fs.Position, _document.Count); - debugString += crossRefTable.WriteString(_document.Count); - // Write trailer - PdfTrailer.Write(bw, _document.Count, catalog.objectNumber, info.objectNumber, crossRefTable.StartPosition); - debugString += PdfTrailer.WriteString(_document.Count, catalog.objectNumber, info.objectNumber, crossRefTable.StartPosition); - } - } - //Write pdf as txt for debug. - if (_pageSettings.Debug && _pageSettings.PrintAsText) - { - using (var fs = new FileStream(fileName + ".txt", FileMode.Create, FileAccess.Write)) - { - using (var wr = new StreamWriter(fs)) - { - wr.Write(debugString); - } } //Write CrossReference crossRefTable.Write(bw, stream.Position - start, _document.Count); diff --git a/src/EPPlus.Export.Pdf/Settings/PdfPageSettings.cs b/src/EPPlus.Export.Pdf/Settings/PdfPageSettings.cs index ca6560f1d7..5416f18870 100644 --- a/src/EPPlus.Export.Pdf/Settings/PdfPageSettings.cs +++ b/src/EPPlus.Export.Pdf/Settings/PdfPageSettings.cs @@ -31,26 +31,6 @@ public OpenTypeFontEngine FontEngine { get { - if(_fontEngine == null) - { - _fontEngine = new OpenTypeFontEngine(x => - { - if(FontDirectories != null && FontDirectories.Any()) - { - foreach(var dir in FontDirectories) - { - if (!System.IO.Directory.Exists(dir)) - { - throw new System.IO.DirectoryNotFoundException($"Font directory not found: {dir}"); - } - x.FontDirectories.Add(dir); - } - x.SearchSystemDirectories = SearchSystemDirectories; - - } - - }); - } return _fontEngine; } } @@ -223,6 +203,11 @@ public PdfScaling Scaling internal bool Debug = false; internal bool PrintAsText = false; + public PdfPageSettings(OpenTypeFontEngine fontEngine) + { + _fontEngine = fontEngine; + } + private static PdfPageSize ApplyOrientation(PdfPageSize size, Orientations orientation) { bool isPortrait = size.Height >= size.Width; diff --git a/src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfPageSize.cs b/src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfPageSize.cs index e3ebc5f78b..359075daa3 100644 --- a/src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfPageSize.cs +++ b/src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfPageSize.cs @@ -95,8 +95,5 @@ public PdfPageSize(double width, double height) public static PdfPageSize A2 => new PdfPageSize(420d, 594d); public static PdfPageSize A3Transverse => new PdfPageSize(297d, 420d); public static PdfPageSize A3ExtraTransverse => new PdfPageSize(322d, 445d); - public static PdfPageSize Legal => new PdfPageSize(215.9d, 355.6d); //(612, 1008); - public static PdfPageSize Statement => new PdfPageSize(139.7d, 215.9d); - public static PdfPageSize Executive => new PdfPageSize(184.2d, 266.7d); } } \ No newline at end of file diff --git a/src/EPPlus/ExcelRangeBase.cs b/src/EPPlus/ExcelRangeBase.cs index 895e2109f7..5e55aebba4 100644 --- a/src/EPPlus/ExcelRangeBase.cs +++ b/src/EPPlus/ExcelRangeBase.cs @@ -1144,7 +1144,7 @@ public IExcelHtmlRangeExporter CreateHtmlExporter() /// Name of file. public void SaveAsPdf(string fileName) { - var setttings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(this.Worksheet.PrinterSettings); + var setttings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(this.Worksheet.Workbook, this.Worksheet.PrinterSettings); PdfCatalog catalog = new PdfCatalog(fileName, setttings, this); } @@ -1156,7 +1156,7 @@ public void SaveAsPdf(string fileName) /// A task representing the asynchronous operation. public Task SaveAsPdfAsync(string fileName, CancellationToken cancellationToken = default) { - var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(this.Worksheet.PrinterSettings); + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(this.Worksheet.Workbook, this.Worksheet.PrinterSettings); return Task.Run(() => { cancellationToken.ThrowIfCancellationRequested(); @@ -1170,7 +1170,7 @@ public Task SaveAsPdfAsync(string fileName, CancellationToken cancellationToken /// Stream to write the PDF to. The stream is not closed; the caller owns it. public void SaveAsPdf(Stream stream) { - var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(this.Worksheet.PrinterSettings); + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(this.Worksheet.Workbook, this.Worksheet.PrinterSettings); PdfCatalog catalog = new PdfCatalog(stream, settings, this); } @@ -1182,7 +1182,7 @@ public void SaveAsPdf(Stream stream) /// A task representing the asynchronous operation. public Task SaveAsPdfAsync(Stream stream, CancellationToken cancellationToken = default) { - var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(this.Worksheet.PrinterSettings); + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(this.Worksheet.Workbook, this.Worksheet.PrinterSettings); return Task.Run(() => { cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/EPPlus/ExcelWorkbook.cs b/src/EPPlus/ExcelWorkbook.cs index 1520b84899..f52be370f8 100644 --- a/src/EPPlus/ExcelWorkbook.cs +++ b/src/EPPlus/ExcelWorkbook.cs @@ -748,7 +748,7 @@ public IExcelHtmlRangeExporter CreateHtmlExporter(params ExcelRangeBase[] ranges /// Name of file. public void SaveAsPdf(string fileName) { - var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(Worksheets[View.ActiveTab].PrinterSettings); + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(this, Worksheets[View.ActiveTab].PrinterSettings); PdfCatalog catalog = new PdfCatalog(fileName, settings, this); } @@ -759,7 +759,7 @@ public void SaveAsPdf(string fileName) /// Worksheets to export. public void SaveAsPdf(string fileName, params ExcelWorksheet[] worksheets) { - var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(worksheets[0].PrinterSettings); + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(this, worksheets[0].PrinterSettings); PdfCatalog catalog = new PdfCatalog(fileName, settings, worksheets); } @@ -770,7 +770,7 @@ public void SaveAsPdf(string fileName, params ExcelWorksheet[] worksheets) /// Ranges to export. public void SaveAsPdf(string fileName, params ExcelRangeBase[] ranges) { - var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(ranges[0].Worksheet.PrinterSettings); + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(this, ranges[0].Worksheet.PrinterSettings); PdfCatalog catalog = new PdfCatalog(fileName, settings, ranges); } @@ -782,7 +782,7 @@ public void SaveAsPdf(string fileName, params ExcelRangeBase[] ranges) /// A task representing the asynchronous operation. public Task SaveAsPdfAsync(string fileName, CancellationToken cancellationToken = default) { - var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(Worksheets[View.ActiveTab].PrinterSettings); + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(this, Worksheets[View.ActiveTab].PrinterSettings); return Task.Run(() => { cancellationToken.ThrowIfCancellationRequested(); @@ -808,7 +808,7 @@ public Task SaveAsPdfAsync(string fileName, params ExcelWorksheet[] worksheets) /// A task representing the asynchronous operation. public Task SaveAsPdfAsync(string fileName, CancellationToken cancellationToken, params ExcelWorksheet[] worksheets) { - var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings( worksheets[0].PrinterSettings); + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(this, worksheets[0].PrinterSettings); return Task.Run(() => { cancellationToken.ThrowIfCancellationRequested(); @@ -834,7 +834,7 @@ public Task SaveAsPdfAsync(string fileName, params ExcelRangeBase[] ranges) /// A task representing the asynchronous operation. public Task SaveAsPdfAsync(string fileName, CancellationToken cancellationToken, params ExcelRangeBase[] ranges) { - var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(ranges[0].Worksheet.PrinterSettings); + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(this, ranges[0].Worksheet.PrinterSettings); return Task.Run(() => { cancellationToken.ThrowIfCancellationRequested(); @@ -848,7 +848,7 @@ public Task SaveAsPdfAsync(string fileName, CancellationToken cancellationToken, /// Stream to write the PDF to. The stream is not closed; the caller owns it. public void SaveAsPdf(Stream stream) { - var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(Worksheets[View.ActiveTab].PrinterSettings); + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(this, Worksheets[View.ActiveTab].PrinterSettings); PdfCatalog catalog = new PdfCatalog(stream, settings, this); } @@ -859,7 +859,7 @@ public void SaveAsPdf(Stream stream) /// Worksheets to export. public void SaveAsPdf(Stream stream, params ExcelWorksheet[] worksheets) { - var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(worksheets[0].PrinterSettings); + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(this, worksheets[0].PrinterSettings); PdfCatalog catalog = new PdfCatalog(stream, settings, worksheets); } @@ -870,7 +870,7 @@ public void SaveAsPdf(Stream stream, params ExcelWorksheet[] worksheets) /// Ranges to export. public void SaveAsPdf(Stream stream, params ExcelRangeBase[] ranges) { - var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(ranges[0].Worksheet.PrinterSettings); + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(this, ranges[0].Worksheet.PrinterSettings); PdfCatalog catalog = new PdfCatalog(stream, settings, ranges); } @@ -882,7 +882,7 @@ public void SaveAsPdf(Stream stream, params ExcelRangeBase[] ranges) /// A task representing the asynchronous operation. public Task SaveAsPdfAsync(Stream stream, CancellationToken cancellationToken = default) { - var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(Worksheets[View.ActiveTab].PrinterSettings); + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(this, Worksheets[View.ActiveTab].PrinterSettings); return Task.Run(() => { cancellationToken.ThrowIfCancellationRequested(); @@ -908,7 +908,7 @@ public Task SaveAsPdfAsync(Stream stream, params ExcelWorksheet[] worksheets) /// A task representing the asynchronous operation. public Task SaveAsPdfAsync(Stream stream, CancellationToken cancellationToken, params ExcelWorksheet[] worksheets) { - var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(worksheets[0].PrinterSettings); + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(this, worksheets[0].PrinterSettings); return Task.Run(() => { cancellationToken.ThrowIfCancellationRequested(); @@ -934,7 +934,7 @@ public Task SaveAsPdfAsync(Stream stream, params ExcelRangeBase[] ranges) /// A task representing the asynchronous operation. public Task SaveAsPdfAsync(Stream stream, CancellationToken cancellationToken, params ExcelRangeBase[] ranges) { - var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(ranges[0].Worksheet.PrinterSettings); + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(this, ranges[0].Worksheet.PrinterSettings); return Task.Run(() => { cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/EPPlus/ExcelWorksheet.cs b/src/EPPlus/ExcelWorksheet.cs index e015f08cc5..3a17332944 100644 --- a/src/EPPlus/ExcelWorksheet.cs +++ b/src/EPPlus/ExcelWorksheet.cs @@ -3751,7 +3751,7 @@ internal bool IsDisposed /// Name of file. public void SaveAsPdf(string fileName) { - var setttings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(PrinterSettings); + var setttings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(this.Workbook, PrinterSettings); PdfCatalog catalog = new PdfCatalog(fileName, setttings, this); } @@ -3763,7 +3763,7 @@ public void SaveAsPdf(string fileName) /// A task representing the asynchronous operation. public Task SaveAsPdfAsync(string fileName, CancellationToken cancellationToken = default) { - var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(PrinterSettings); + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(this.Workbook, PrinterSettings); return Task.Run(() => { cancellationToken.ThrowIfCancellationRequested(); @@ -3777,7 +3777,7 @@ public Task SaveAsPdfAsync(string fileName, CancellationToken cancellationToken /// Stream to write the PDF to. The stream is not closed; the caller owns it. public void SaveAsPdf(Stream stream) { - var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(PrinterSettings); + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(this.Workbook, PrinterSettings); PdfCatalog catalog = new PdfCatalog(stream, settings, this); } @@ -3789,7 +3789,7 @@ public void SaveAsPdf(Stream stream) /// A task representing the asynchronous operation. public Task SaveAsPdfAsync(Stream stream, CancellationToken cancellationToken = default) { - var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(PrinterSettings); + var settings = GetPdfSettings.GetPdfSettingsFromPrinterSettings(this.Workbook, PrinterSettings); return Task.Run(() => { cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/EPPlus/Export/PdfExport/PdfCatalog.cs b/src/EPPlus/Export/PdfExport/PdfCatalog.cs index 1db8dd6544..5f207c5c71 100644 --- a/src/EPPlus/Export/PdfExport/PdfCatalog.cs +++ b/src/EPPlus/Export/PdfExport/PdfCatalog.cs @@ -560,7 +560,7 @@ private void GetCommentsAndNotes(PdfPageSettings pageSettings, PdfWorksheet pdfS { if (pageSettings.CommentsAndNotes == CommentsAndNotes.AtEndOfSheet && pdfSheet.CommentsAndNotesCollections.Count > 0) { - var cnPageSettings = new PdfPageSettings(); + var cnPageSettings = new PdfPageSettings(pdfSheet.Worksheet.Workbook.RenderContext.FontEngine); cnPageSettings.CommentsAndNotes = CommentsAndNotes.None; cnPageSettings.ShowHeadings = false; pdfSheet.CommentsAndNotesSheet = PdfCommentsAndNotes.CreateCommentAndNotesPages(pdfSheet.CommentsAndNotesCollections, pdfSheet.Worksheet); diff --git a/src/EPPlus/Export/PdfExport/Settings/GetPdfSettings.cs b/src/EPPlus/Export/PdfExport/Settings/GetPdfSettings.cs index fe30db6bf9..46a4770a7d 100644 --- a/src/EPPlus/Export/PdfExport/Settings/GetPdfSettings.cs +++ b/src/EPPlus/Export/PdfExport/Settings/GetPdfSettings.cs @@ -19,9 +19,9 @@ namespace OfficeOpenXml.Export.PdfExport.Settings { internal static class GetPdfSettings { - internal static PdfPageSettings GetPdfSettingsFromPrinterSettings(ExcelPrinterSettings eps) + internal static PdfPageSettings GetPdfSettingsFromPrinterSettings(ExcelWorkbook workbook, ExcelPrinterSettings eps) { - var settings = new PdfPageSettings(); + var settings = new PdfPageSettings(workbook.RenderContext.FontEngine); var leftMargin = UnitConversion.ToMillimeters(eps.LeftMargin); var rightMargin = UnitConversion.ToMillimeters(eps.RightMargin); var topMargin = UnitConversion.ToMillimeters(eps.TopMargin); From fdf104f425b5a3bf82fc0675c5c908542b7af7ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Wed, 1 Jul 2026 15:33:18 +0200 Subject: [PATCH 47/49] fixed margins using normal if not set. --- src/EPPlus.Export.Pdf.Tests/PdfTests.cs | 87 +++++++++++++++++++ .../PdfExport/Settings/GetPdfSettings.cs | 7 +- 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs index b6feaec3cb..c4fd99dcba 100644 --- a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs +++ b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs @@ -551,5 +551,92 @@ public void WorkbookTests(string sourceFile, string outputPath, string wsName) PdfCatalog catalog = new PdfCatalog(outputPath, pageSettings, ws); } + + [TestMethod] + public void EPPlusToPdf() + { + string[][] pixels = + { + new[] { "#805840", "#805840", "#805840", "#C0A070", "#C0A070", "#C0A070", "#402820", "#402820", "#402820", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#402820", "#402820", "#805840", "#402820", "#000000", "#000000", "#805840", "#402820", "#402820", "#000000", "#402820", "#402820" }, + new[] { "#805840", "#805840", "#C0A070", "#C0A070", "#402820", "#805840", "#805840", "#402820", "#402820", "#C0A070", "#C0A070", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#402820", "#805840", "#402820", "#000000", "#805840", "#402820", "#000000", "#402820", "#402820", "#402820" }, + new[] { "#805840", "#805840", "#C0A070", "#C0A070", "#C0A070", "#402820", "#A87850", "#A87850", "#402820", "#402820", "#C0A070", "#C0A070", "#402820", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#402820", "#805840", "#402820", "#402820", "#402820", "#000000", "#402820", "#402820", "#402820", "#402820" }, + new[] { "#E0C8A0", "#E0C8A0", "#A87850", "#C0A070", "#C0A070", "#402820", "#402820", "#C0A070", "#C0A070", "#C0A070", "#C0A070", "#C0A070", "#402820", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#805840", "#805840", "#805840", "#000000", "#402820", "#402820", "#402820", "#000000" }, + new[] { "#E0C8A0", "#E0C8A0", "#E0C8A0", "#A87850", "#C0A070", "#805840", "#402820", "#402820", "#C0A070", "#A87850", "#805840", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#402820", "#402820", "#402820", "#402820", "#805840", "#805840", "#000000", "#406850", "#000000", "#000000", "#384038" }, + new[] { "#402820", "#402820", "#E0C8A0", "#E0C8A0", "#A87850", "#805840", "#402820", "#402820", "#A87850", "#C0A070", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#402820", "#402820", "#402820", "#402820", "#402820", "#406850", "#406850", "#406850", "#406850", "#000000", "#384038", "#384038" }, + new[] { "#70A070", "#70A070", "#000000", "#000000", "#E0C8A0", "#805840", "#805840", "#402820", "#A87850", "#C0A070", "#C0A070", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#402820", "#402820", "#402820", "#384038", "#384038", "#384038", "#406850", "#406850", "#406850", "#406850", "#000000", "#384038", "#384038" }, + new[] { "#70A070", "#70A070", "#70A070", "#70A070", "#406850", "#406850", "#406850", "#000000", "#000000", "#000000", "#000000", "#402820", "#402820", "#000000", "#000000", "#000000", "#000000", "#000000", "#384038", "#384038", "#384038", "#384038", "#384038", "#384038", "#384038", "#406850", "#406850", "#406850", "#000000", "#384038", "#384038", "#384038" }, + new[] { "#70A070", "#70A070", "#70A070", "#70A070", "#70A070", "#70A070", "#70A070", "#384038", "#384038", "#406850", "#406850", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#384038", "#384038", "#384038", "#384038", "#384038", "#384038", "#384038", "#406850", "#406850", "#406850", "#000000", "#384038", "#384038", "#384038" }, + new[] { "#70A070", "#70A070", "#70A070", "#70A070", "#70A070", "#70A070", "#70A070", "#70A070", "#384038", "#406850", "#406850", "#406850", "#406850", "#406850", "#406850", "#000000", "#000000", "#000000", "#384038", "#384038", "#384038", "#384038", "#384038", "#384038", "#384038", "#406850", "#406850", "#384038", "#000000", "#384038", "#384038", "#384038" }, + new[] { "#70A070", "#70A070", "#70A070", "#70A070", "#70A070", "#70A070", "#70A070", "#70A070", "#406850", "#406850", "#406850", "#406850", "#406850", "#406850", "#406850", "#000000", "#000000", "#000000", "#384038", "#384038", "#384038", "#384038", "#384038", "#000000", "#000000", "#384038", "#384038", "#384038", "#000000", "#406850", "#384038", "#384038" }, + new[] { "#384038", "#70A070", "#70A070", "#70A070", "#70A070", "#70A070", "#70A070", "#70A070", "#406850", "#406850", "#406850", "#406850", "#406850", "#406850", "#406850", "#000000", "#000000", "#000000", "#384038", "#384038", "#384038", "#384038", "#384038", "#000000", "#000000", "#406850", "#406850", "#406850", "#406850", "#406850", "#384038", "#384038" }, + new[] { "#70A070", "#384038", "#70A070", "#70A070", "#70A070", "#70A070", "#70A070", "#70A070", "#70A070", "#406850", "#406850", "#406850", "#406850", "#406850", "#406850", "#406850", "#000000", "#000000", "#000000", "#384038", "#384038", "#384038", "#384038", "#000000", "#000000", "#406850", "#406850", "#406850", "#406850", "#406850", "#384038", "#384038" }, + new[] { "#70A070", "#70A070", "#384038", "#70A070", "#70A070", "#70A070", "#70A070", "#70A070", "#70A070", "#406850", "#406850", "#406850", "#406850", "#406850", "#406850", "#406850", "#406850", "#000000", "#000000", "#384038", "#384038", "#384038", "#384038", "#000000", "#384038", "#406850", "#406850", "#406850", "#406850", "#384038", "#384038", "#384038" }, + new[] { "#406850", "#406850", "#406850", "#70A070", "#70A070", "#70A070", "#70A070", "#70A070", "#70A070", "#384038", "#384038", "#406850", "#406850", "#406850", "#406850", "#406850", "#406850", "#000000", "#000000", "#384038", "#384038", "#384038", "#000000", "#384038", "#406850", "#406850", "#406850", "#406850", "#384038", "#384038", "#384038", "#384038" }, + new[] { "#000000", "#406850", "#406850", "#70A070", "#70A070", "#70A070", "#70A070", "#70A070", "#70A070", "#384038", "#384038", "#406850", "#406850", "#406850", "#406850", "#406850", "#000000", "#000000", "#384038", "#384038", "#000000", "#000000", "#384038", "#406850", "#406850", "#406850", "#406850", "#406850", "#384038", "#384038", "#000000", "#000000" }, + new[] { "#000000", "#406850", "#406850", "#70A070", "#70A070", "#70A070", "#70A070", "#70A070", "#70A070", "#384038", "#384038", "#384038", "#384038", "#406850", "#406850", "#000000", "#000000", "#384038", "#000000", "#000000", "#000000", "#384038", "#406850", "#406850", "#406850", "#406850", "#406850", "#406850", "#000000", "#000000", "#000000", "#000000" }, + new[] { "#000000", "#000000", "#000000", "#000000", "#406850", "#406850", "#406850", "#70A070", "#70A070", "#70A070", "#70A070", "#406850", "#000000", "#406850", "#406850", "#406850", "#000000", "#384038", "#000000", "#406850", "#406850", "#406850", "#406850", "#384038", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000" }, + new[] { "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#70A070", "#70A070", "#406850", "#000000", "#406850", "#406850", "#406850", "#000000", "#384038", "#000000", "#406850", "#406850", "#406850", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000" }, + new[] { "#000000", "#000000", "#402820", "#402820", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#406850", "#406850", "#406850", "#406850", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#402820", "#402820", "#805840", "#805840" }, + new[] { "#000000", "#000000", "#402820", "#402820", "#000000", "#B8A898", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#402820", "#402820", "#402820", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#805840", "#805840", "#000000", "#000000", "#000000", "#000000", "#000000", "#402820", "#805840", "#C0A070", "#805840" }, + new[] { "#000000", "#000000", "#000000", "#805840", "#805840", "#000000", "#B8A898", "#B8A898", "#000000", "#000000", "#000000", "#000000", "#402820", "#E0C8A0", "#E0C8A0", "#402820", "#402820", "#000000", "#402820", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#402820", "#402820", "#805840", "#C0A070", "#C0A070", "#805840" }, + new[] { "#000000", "#000000", "#000000", "#FFF0E0", "#FFF0E0", "#FFF0E0", "#805840", "#000000", "#000000", "#000000", "#000000", "#000000", "#E0C8A0", "#FFF0E0", "#FFF0E0", "#402820", "#402820", "#000000", "#000000", "#402820", "#402820", "#402820", "#402820", "#402820", "#805840", "#805840", "#805840", "#805840", "#C0A070", "#C0A070", "#805840", "#805840" }, + new[] { "#000000", "#000000", "#000000", "#E0C8A0", "#FFF0E0", "#FFF0E0", "#805840", "#805840", "#805840", "#C0A070", "#C0A070", "#402820", "#E0C8A0", "#FFF0E0", "#FFF0E0", "#805840", "#805840", "#A87850", "#000000", "#000000", "#402820", "#402820", "#402820", "#402820", "#805840", "#805840", "#805840", "#C0A070", "#C0A070", "#C0A070", "#805840", "#000000" }, + new[] { "#000000", "#000000", "#000000", "#000000", "#FFF0E0", "#FFF0E0", "#FFF0E0", "#E0C8A0", "#E0C8A0", "#C0A070", "#805840", "#E0C8A0", "#E0C8A0", "#FFF0E0", "#FFF0E0", "#805840", "#805840", "#A87850", "#805840", "#000000", "#000000", "#402820", "#805840", "#C0A070", "#C0A070", "#C0A070", "#C0A070", "#C0A070", "#C0A070", "#805840", "#000000", "#805840" }, + new[] { "#000000", "#000000", "#000000", "#000000", "#FFF0E0", "#FFF0E0", "#FFF0E0", "#E0C8A0", "#E0C8A0", "#C0A070", "#805840", "#E0C8A0", "#E0C8A0", "#FFF0E0", "#FFF0E0", "#805840", "#A87850", "#A87850", "#A87850", "#805840", "#000000", "#402820", "#805840", "#C0A070", "#C0A070", "#C0A070", "#C0A070", "#C0A070", "#C0A070", "#805840", "#000000", "#805840" }, + new[] { "#000000", "#000000", "#000000", "#000000", "#C0A070", "#FFF0E0", "#FFF0E0", "#E0C8A0", "#E0C8A0", "#805840", "#FFF0E0", "#E0C8A0", "#E0C8A0", "#FFF0E0", "#FFF0E0", "#805840", "#A87850", "#A87850", "#A87850", "#805840", "#805840", "#000000", "#000000", "#402820", "#402820", "#C0A070", "#C0A070", "#402820", "#000000", "#000000", "#000000", "#805840" }, + new[] { "#000000", "#000000", "#000000", "#000000", "#C0A070", "#FFF0E0", "#FFF0E0", "#FFF0E0", "#E0C8A0", "#FFF0E0", "#FFF0E0", "#FFF0E0", "#E0C8A0", "#FFF0E0", "#FFF0E0", "#805840", "#A87850", "#A87850", "#A87850", "#A87850", "#805840", "#805840", "#402820", "#402820", "#402820", "#C0A070", "#402820", "#000000", "#000000", "#805840", "#805840", "#805840" }, + new[] { "#000000", "#000000", "#000000", "#000000", "#000000", "#C0A070", "#C0A070", "#FFF0E0", "#E0C8A0", "#FFF0E0", "#FFF0E0", "#FFF0E0", "#E0C8A0", "#FFF0E0", "#FFF0E0", "#402820", "#A87850", "#A87850", "#A87850", "#C0A070", "#805840", "#805840", "#402820", "#402820", "#402820", "#402820", "#000000", "#000000", "#805840", "#805840", "#E0C8A0", "#A87850" }, + new[] { "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#E0C8A0", "#E0C8A0", "#E0C8A0", "#FFF0E0", "#FFF0E0", "#FFF0E0", "#FFF0E0", "#FFF0E0", "#FFF0E0", "#402820", "#A87850", "#A87850", "#C0A070", "#C0A070", "#805840", "#805840", "#402820", "#402820", "#000000", "#000000", "#000000", "#805840", "#805840", "#E0C8A0", "#E0C8A0", "#A87850" }, + new[] { "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#FFF0E0", "#E0C8A0", "#E0C8A0", "#FFF0E0", "#FFF0E0", "#A87850", "#FFF0E0", "#FFF0E0", "#FFF0E0", "#402820", "#A87850", "#A87850", "#402820", "#402820", "#A87850", "#A87850", "#805840", "#805840", "#000000", "#000000", "#000000", "#805840", "#E0C8A0", "#E0C8A0", "#E0C8A0", "#A87850" }, + new[] { "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#FFF0E0", "#E0C8A0", "#E0C8A0", "#FFF0E0", "#FFF0E0", "#FFF0E0", "#A87850", "#A87850", "#402820", "#402820", "#000000", "#000000", "#000000", "#A87850", "#A87850", "#A87850", "#A87850", "#A87850", "#000000", "#402820", "#402820", "#E0C8A0", "#C0A070", "#C0A070", "#E0C8A0", "#E0C8A0" }, + new[] { "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#FFF0E0", "#E0C8A0", "#E0C8A0", "#FFF0E0", "#FFF0E0", "#FFF0E0", "#FFF0E0", "#A87850", "#402820", "#000000", "#000000", "#000000", "#A87850", "#A87850", "#A87850", "#402820", "#402820", "#402820", "#000000", "#805840", "#805840", "#C0A070", "#C0A070", "#C0A070", "#E0C8A0", "#E0C8A0" }, + new[] { "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#A87850", "#FFF0E0", "#E0C8A0", "#FFF0E0", "#A87850", "#FFF0E0", "#FFF0E0", "#FFF0E0", "#FFF0E0", "#000000", "#000000", "#A87850", "#A87850", "#A87850", "#805840", "#000000", "#000000", "#402820", "#000000", "#805840", "#A87850", "#C0A070", "#C0A070", "#C0A070", "#C0A070", "#402820" }, + new[] { "#000000", "#000000", "#000000", "#000000", "#70A070", "#70A070", "#402820", "#FFF0E0", "#E0C8A0", "#FFF0E0", "#000000", "#805840", "#805840", "#402820", "#000000", "#000000", "#805840", "#805840", "#000000", "#000000", "#000000", "#000000", "#000000", "#402820", "#000000", "#000000", "#A87850", "#C0A070", "#C0A070", "#C0A070", "#C0A070", "#402820" }, + new[] { "#000000", "#000000", "#000000", "#70A070", "#70A070", "#70A070", "#406850", "#000000", "#E0C8A0", "#FFF0E0", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#C0A070", "#C0A070", "#C0A070", "#C0A070", "#402820", "#A87850" }, + new[] { "#000000", "#000000", "#406850", "#406850", "#406850", "#406850", "#406850", "#000000", "#E0C8A0", "#FFF0E0", "#000000", "#000000", "#000000", "#FFF0E0", "#FFF0E0", "#FFF0E0", "#FFF0E0", "#FFF0E0", "#805840", "#805840", "#805840", "#805840", "#000000", "#000000", "#000000", "#000000", "#C0A070", "#E0C8A0", "#C0A070", "#402820", "#A87850", "#A87850" }, + new[] { "#000000", "#406850", "#406850", "#406850", "#406850", "#406850", "#406850", "#000000", "#402820", "#A87850", "#805840", "#000000", "#FFF0E0", "#FFF0E0", "#A87850", "#A87850", "#A87850", "#A87850", "#805840", "#805840", "#805840", "#805840", "#402820", "#402820", "#000000", "#000000", "#E0C8A0", "#E0C8A0", "#C0A070", "#402820", "#A87850", "#A87850" }, + new[] { "#406850", "#406850", "#406850", "#406850", "#406850", "#406850", "#406850", "#000000", "#000000", "#A87850", "#805840", "#E0C8A0", "#E0C8A0", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#805840", "#805840", "#805840", "#000000", "#000000", "#E0C8A0", "#E0C8A0", "#402820", "#A87850", "#A87850", "#384038" }, + new[] { "#406850", "#406850", "#406850", "#406850", "#406850", "#406850", "#406850", "#000000", "#000000", "#000000", "#805840", "#A87850", "#402820", "#402820", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#805840", "#402820", "#000000", "#000000", "#E0C8A0", "#000000", "#000000", "#A87850", "#384038", "#384038" }, + new[] { "#406850", "#406850", "#406850", "#406850", "#406850", "#406850", "#406850", "#000000", "#000000", "#000000", "#402820", "#E0C8A0", "#402820", "#402820", "#E0C8A0", "#E0C8A0", "#E0C8A0", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#C0A070", "#000000", "#000000", "#000000", "#A87850", "#000000", "#406850" }, + new[] { "#384038", "#384038", "#384038", "#384038", "#406850", "#406850", "#406850", "#000000", "#000000", "#000000", "#402820", "#A87850", "#E0C8A0", "#E0C8A0", "#E0C8A0", "#E0C8A0", "#E0C8A0", "#A87850", "#000000", "#000000", "#000000", "#000000", "#000000", "#805840", "#000000", "#C0A070", "#000000", "#000000", "#000000", "#000000", "#406850", "#406850" }, + new[] { "#70A070", "#406850", "#406850", "#384038", "#406850", "#406850", "#000000", "#000000", "#000000", "#000000", "#402820", "#A87850", "#A87850", "#E0C8A0", "#FFF0E0", "#FFF0E0", "#FFF0E0", "#A87850", "#000000", "#000000", "#000000", "#000000", "#000000", "#805840", "#805840", "#000000", "#000000", "#000000", "#000000", "#406850", "#406850", "#406850" }, + new[] { "#70A070", "#70A070", "#406850", "#000000", "#406850", "#406850", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#A87850", "#A87850", "#FFF0E0", "#FFF0E0", "#FFF0E0", "#E0C8A0", "#000000", "#000000", "#000000", "#000000", "#805840", "#805840", "#000000", "#000000", "#000000", "#000000", "#406850", "#406850", "#406850", "#406850" }, + new[] { "#70A070", "#70A070", "#70A070", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#A87850", "#000000", "#A87850", "#FFF0E0", "#FFF0E0", "#FFF0E0", "#E0C8A0", "#000000", "#000000", "#000000", "#000000", "#805840", "#000000", "#000000", "#402820", "#000000", "#406850", "#406850", "#406850", "#406850", "#406850" }, + new[] { "#000000", "#000000", "#70A070", "#384038", "#406850", "#406850", "#406850", "#000000", "#000000", "#000000", "#000000", "#A87850", "#000000", "#402820", "#402820", "#402820", "#402820", "#402820", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#402820", "#402820", "#000000", "#406850", "#406850", "#406850", "#000000", "#000000" }, + new[] { "#384038", "#384038", "#70A070", "#70A070", "#384038", "#406850", "#406850", "#000000", "#000000", "#000000", "#000000", "#000000", "#A87850", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#402820", "#402820", "#000000", "#406850", "#384038", "#000000", "#000000", "#000000", "#000000" }, + new[] { "#384038", "#384038", "#000000", "#70A070", "#406850", "#406850", "#406850", "#406850", "#000000", "#000000", "#000000", "#000000", "#A87850", "#A87850", "#A87850", "#A87850", "#A87850", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#402820", "#402820", "#000000", "#406850", "#384038", "#000000", "#000000", "#000000", "#000000" }, + }; + var p = new ExcelPackage(); + var ws = p.Workbook.Worksheets.Add("Snake"); + const double rowHeightPts = 15; + double columnWidth = rowHeightPts * (96.0 / 72.0) / 7.0; + ws.Column(1).Width = columnWidth; + ws.Cells["A1"].Value = "Solid"; + ws.Cells["AE50"].Value = "Snake"; + const int startRow = 2; + const int startCol = 1; // D + for (int y = 0; y < pixels.Length; y++) + { + for (int x = 0; x < pixels[y].Length; x++) + { + var cell = ws.Cells[startRow + y, startCol + x]; + var color = System.Drawing.ColorTranslator.FromHtml(pixels[y][x]); + cell.Style.Fill.PatternType = ExcelFillStyle.Solid; + cell.Style.Fill.BackgroundColor.SetColor(color); + ws.Column(startCol + x).Width = columnWidth; + } + ws.Row(startRow + y).Height = 15; + } + ws.PrinterSettings.Orientation = eOrientation.Portrait; + ws.PrinterSettings.PaperSize = ePaperSize.A4; + ws.PrinterSettings.TopMargin = 0.15d; + ws.PrinterSettings.BottomMargin = 0.15d; + ws.PrinterSettings.LeftMargin = 0.15d; + ws.PrinterSettings.RightMargin = 0.15d; + ws.PrinterSettings.HeaderMargin = 0.15d; + ws.PrinterSettings.FooterMargin = 0.15d; + p.Workbook.SaveAsPdf(pdfPath + "Snake.Pdf"); + p.SaveAs(pdfPath + "Snake.xlsx"); + } } } diff --git a/src/EPPlus/Export/PdfExport/Settings/GetPdfSettings.cs b/src/EPPlus/Export/PdfExport/Settings/GetPdfSettings.cs index 46a4770a7d..b057c8ba99 100644 --- a/src/EPPlus/Export/PdfExport/Settings/GetPdfSettings.cs +++ b/src/EPPlus/Export/PdfExport/Settings/GetPdfSettings.cs @@ -28,8 +28,11 @@ internal static PdfPageSettings GetPdfSettingsFromPrinterSettings(ExcelWorkbook var bottomMargin = UnitConversion.ToMillimeters(eps.BottomMargin); var headerMargin = UnitConversion.ToMillimeters(eps.HeaderMargin); var footerMargin = UnitConversion.ToMillimeters(eps.FooterMargin); - settings.Margins = new PdfMargins(topMargin, bottomMargin, leftMargin, rightMargin, headerMargin, footerMargin); - settings.Orientation = (Orientations)eps.Orientation; + if (double.IsNaN(topMargin) || double.IsNaN(bottomMargin) || double.IsNaN(leftMargin) || double.IsNaN(rightMargin) || double.IsNaN(headerMargin) || double.IsNaN(footerMargin)) + settings.Margins = PdfMargins.Normal; + else + settings.Margins = new PdfMargins(topMargin, bottomMargin, leftMargin, rightMargin, headerMargin, footerMargin); + settings.Orientation = eps.Orientation != null ? (Orientations)eps.Orientation : Orientations.Portrait; //Scaling is not yet implemented. settings.Scaling = new PdfScaling(eps.Scale); settings.ShowHeadings = eps.ShowHeaders; From ba6508606ba39bbb1d57798295b29bf5e43a0dbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Wed, 1 Jul 2026 16:21:09 +0200 Subject: [PATCH 48/49] printer settings now has default values for margins and orientation. --- src/EPPlus.Export.Pdf.Tests/PdfTests.cs | 19 +++++++++---------- src/EPPlus/ExcelPrinterSettings.cs | 15 ++++++++------- .../PdfExport/Settings/GetPdfSettings.cs | 8 +++----- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs index c4fd99dcba..3d2f8840d6 100644 --- a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs +++ b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs @@ -611,8 +611,9 @@ public void EPPlusToPdf() const double rowHeightPts = 15; double columnWidth = rowHeightPts * (96.0 / 72.0) / 7.0; ws.Column(1).Width = columnWidth; - ws.Cells["A1"].Value = "Solid"; - ws.Cells["AE50"].Value = "Snake"; + ws.Cells["A1"].Value = "SOLID"; + ws.Cells["AE50"].Value = "SNAKE"; + ws.Cells["AF51"].Value = " "; const int startRow = 2; const int startCol = 1; // D for (int y = 0; y < pixels.Length; y++) @@ -627,14 +628,12 @@ public void EPPlusToPdf() } ws.Row(startRow + y).Height = 15; } - ws.PrinterSettings.Orientation = eOrientation.Portrait; - ws.PrinterSettings.PaperSize = ePaperSize.A4; - ws.PrinterSettings.TopMargin = 0.15d; - ws.PrinterSettings.BottomMargin = 0.15d; - ws.PrinterSettings.LeftMargin = 0.15d; - ws.PrinterSettings.RightMargin = 0.15d; - ws.PrinterSettings.HeaderMargin = 0.15d; - ws.PrinterSettings.FooterMargin = 0.15d; + ws.PrinterSettings.TopMargin = 0.1d; + ws.PrinterSettings.BottomMargin = 0.1d; + ws.PrinterSettings.LeftMargin = 0.1d; + ws.PrinterSettings.RightMargin = 0.1d; + ws.PrinterSettings.HorizontalCentered = true; + ws.PrinterSettings.VerticalCentered = true; p.Workbook.SaveAsPdf(pdfPath + "Snake.Pdf"); p.SaveAs(pdfPath + "Snake.xlsx"); } diff --git a/src/EPPlus/ExcelPrinterSettings.cs b/src/EPPlus/ExcelPrinterSettings.cs index 168a67d8f8..a844557968 100644 --- a/src/EPPlus/ExcelPrinterSettings.cs +++ b/src/EPPlus/ExcelPrinterSettings.cs @@ -382,7 +382,7 @@ public double LeftMargin { get { - return GetXmlNodeDouble(_leftMarginPath); + return GetXmlNodeDouble(_leftMarginPath, 0.7480d); } set { @@ -398,7 +398,7 @@ public double RightMargin { get { - return GetXmlNodeDouble(_rightMarginPath); + return GetXmlNodeDouble(_rightMarginPath, 0.7480d); } set { @@ -414,7 +414,7 @@ public double TopMargin { get { - return GetXmlNodeDouble(_topMarginPath); + return GetXmlNodeDouble(_topMarginPath, 0.7480d); } set { @@ -430,7 +430,7 @@ public double BottomMargin { get { - return GetXmlNodeDouble(_bottomMarginPath); + return GetXmlNodeDouble(_bottomMarginPath, 0.7480d); } set { @@ -446,7 +446,7 @@ public double HeaderMargin { get { - return GetXmlNodeDouble(_headerMarginPath); + return GetXmlNodeDouble(_headerMarginPath, 0.315d); } set { @@ -462,7 +462,7 @@ public double FooterMargin { get { - return GetXmlNodeDouble(_footerMarginPath); + return GetXmlNodeDouble(_footerMarginPath, 0.315d); } set { @@ -479,7 +479,8 @@ public eOrientation Orientation { get { - return (eOrientation)Enum.Parse(typeof(eOrientation), GetXmlNodeString(_orientationPath), true); + var orientation = GetXmlNodeString(_orientationPath); + return string.IsNullOrEmpty( orientation) ? eOrientation.Portrait : (eOrientation)Enum.Parse(typeof(eOrientation), orientation, true); } set { diff --git a/src/EPPlus/Export/PdfExport/Settings/GetPdfSettings.cs b/src/EPPlus/Export/PdfExport/Settings/GetPdfSettings.cs index b057c8ba99..571ee41e0f 100644 --- a/src/EPPlus/Export/PdfExport/Settings/GetPdfSettings.cs +++ b/src/EPPlus/Export/PdfExport/Settings/GetPdfSettings.cs @@ -28,11 +28,8 @@ internal static PdfPageSettings GetPdfSettingsFromPrinterSettings(ExcelWorkbook var bottomMargin = UnitConversion.ToMillimeters(eps.BottomMargin); var headerMargin = UnitConversion.ToMillimeters(eps.HeaderMargin); var footerMargin = UnitConversion.ToMillimeters(eps.FooterMargin); - if (double.IsNaN(topMargin) || double.IsNaN(bottomMargin) || double.IsNaN(leftMargin) || double.IsNaN(rightMargin) || double.IsNaN(headerMargin) || double.IsNaN(footerMargin)) - settings.Margins = PdfMargins.Normal; - else - settings.Margins = new PdfMargins(topMargin, bottomMargin, leftMargin, rightMargin, headerMargin, footerMargin); - settings.Orientation = eps.Orientation != null ? (Orientations)eps.Orientation : Orientations.Portrait; + settings.Margins = new PdfMargins(topMargin, bottomMargin, leftMargin, rightMargin, headerMargin, footerMargin); + settings.Orientation = (Orientations)eps.Orientation; //Scaling is not yet implemented. settings.Scaling = new PdfScaling(eps.Scale); settings.ShowHeadings = eps.ShowHeaders; @@ -41,6 +38,7 @@ internal static PdfPageSettings GetPdfSettingsFromPrinterSettings(ExcelWorkbook //Print area is implemented and uses the defined name instead of this setting. this setting should override the print area defined name. settings.PrintArea = eps.PrintArea != null ? eps.PrintArea.Address : null; settings.ShowGridLines = eps.ShowGridLines; + //Centering is not implemented. settings.CenterOnPageHorizontally = eps.HorizontalCentered; settings.CenterOnPageVertically = eps.VerticalCentered; settings.PageOrders = (PageOrders)eps.PageOrder; From 96f1b42b4226b00b459f3cb9a5015ccced653df2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Wed, 1 Jul 2026 16:48:01 +0200 Subject: [PATCH 49/49] Changed to use DimensionByValue --- src/EPPlus.Export.Pdf.Tests/PdfTests.cs | 2 +- src/EPPlus/ExcelWorksheet.cs | 12 ++++++------ src/EPPlus/Export/PdfExport/PdfCatalog.cs | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs index 3d2f8840d6..71424c9d46 100644 --- a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs +++ b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs @@ -613,7 +613,7 @@ public void EPPlusToPdf() ws.Column(1).Width = columnWidth; ws.Cells["A1"].Value = "SOLID"; ws.Cells["AE50"].Value = "SNAKE"; - ws.Cells["AF51"].Value = " "; + //ws.Cells["AF51"].Value = " "; const int startRow = 2; const int startCol = 1; // D for (int y = 0; y < pixels.Length; y++) diff --git a/src/EPPlus/ExcelWorksheet.cs b/src/EPPlus/ExcelWorksheet.cs index 3a17332944..28f394c33c 100644 --- a/src/EPPlus/ExcelWorksheet.cs +++ b/src/EPPlus/ExcelWorksheet.cs @@ -3029,8 +3029,8 @@ private ExcelRangeBase GetDimension(bool byVisibility) CheckSheetTypeAndNotDisposed(); if (_values.GetDimension(out int fr, out int fc, out int tr, out int tc)) { - var fvc = FirstValueCell; - var lvc = LastValueCell; + var fvc = Cells[fr, fc]; + var lvc = Cells[tr, tc]; // Row range comes from values only — styling never extends the height. var fromRow = fvc._fromRow; var toRow = lvc._toRow; @@ -3091,10 +3091,10 @@ private ExcelRangeBase GetDimension(bool byVisibility) { for (int c = fc; c <= tc; c++) { - if (c >= fromCol && c <= toCol) - { - continue; // already inside the range - } + //if (c >= fromCol && c <= toCol) + //{ + // continue; // already inside the range + //} for (int r = fromRow; r <= toRow; r++) { if (HasVisibleStyle(r, c)) diff --git a/src/EPPlus/Export/PdfExport/PdfCatalog.cs b/src/EPPlus/Export/PdfExport/PdfCatalog.cs index 5f207c5c71..2bfb60d0ab 100644 --- a/src/EPPlus/Export/PdfExport/PdfCatalog.cs +++ b/src/EPPlus/Export/PdfExport/PdfCatalog.cs @@ -458,7 +458,7 @@ private List GetRanges(ExcelWorksheet worksheet) } else { - var range = worksheet.DimensionByValue; + var range = worksheet.DimensionByVisibility; var pdfRange = new PdfRange(range, true); pdfRange.ExtendColumns = true; ranges.Add(pdfRange);