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.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 93e204a6f4..6ce9e4da3e 100644 --- a/src/EPPlus.Export.Pdf.Tests/PdfTests.cs +++ b/src/EPPlus.Export.Pdf.Tests/PdfTests.cs @@ -10,17 +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.Interfaces.Drawing.Text; using OfficeOpenXml.Interfaces.RichText; using OfficeOpenXml.Style; +using System.Diagnostics; namespace EPPlusTest.PDF { @@ -28,141 +27,97 @@ 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. */ - [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() { //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,96 +127,166 @@ 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\\FullPageTest58.pdf"); //line breaks //wrap comments //text placement //alignment - //vertical + //vertical } - [TestMethod] - public void CalculatePages() + 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 = new ExcelPackage(); - var ws = p.Workbook.Worksheets.Add("Sheet 1"); + using var p = OpenTemplatePackage(sourceFile); + var ws = p.Workbook.Worksheets[wsName]; + var d = ws.Dimension; + var d2 = ws.DimensionByValue; + 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; + pageSettings.CommentsAndNotes = CommentsAndNotes.AtEndOfSheet; + + pageSettings.CellErrors = CellErrors.Displayed; + pageSettings.Debug = true; + pageSettings.PrintAsText = true; + pageSettings.ShowGridLines = false; + pageSettings.ShowHeadings = false; - var result = PdfLayout.GetNumberOfPages(pageSettings, pws, range); + PdfCatalog catalog = new PdfCatalog(pageSettings, ws, outputPath); } - [TestMethod] - public void TestWrapText() - { - using var p = OpenTemplatePackage("PDFTest.xlsx"); - var cell = p.Workbook.Worksheets[0].Cells["P118"]; + /// + /// 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; - List TextFragments = GetTextFragments(cell.RichText).Cast().ToList(); + // 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"]; - var layout = OpenTypeFonts.GetTextLayoutEngineForFont((IFontFormatBase)TextFragments[0].RichTextOptions); + // List TextFragments = GetTextFragments(cell.RichText).Cast().ToList(); - 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; + // var layout = OpenTypeFonts.GetTextLayoutEngineForFont((IFontFormatBase)TextFragments[0].RichTextOptions); - textFrag.Font.Family = rt.FontName; - textFrag.Font.Size = rt.Size; + // var TextLines = layout.WrapRichTextLineCollection(TextFragments, 51d); + //} - 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; + //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; - //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); + // 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; - textFragments.Add(textFrag); - } + // //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); - return textFragments; - } + + // textFragments.Add(textFrag); + // } + + // return textFragments; + //} } } diff --git a/src/EPPlus.Export.Pdf/AssemblyInfo.cs b/src/EPPlus.Export.Pdf/AssemblyInfo.cs index d79820d85e..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,6 +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.Export.Pdf.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dd3a3466a88cbf5d374fe992cec433c48022414fe96608933e8e36782001213dd31bc454dc6f962a54a3a76cfb9e03a32cd4c658ecd49d1a98709971a080ab92d5c5b65346155f8d6422db4ffbf662f78913996a9a8b78ee11ff3cda7e585208cd4468fb3201f15bbb1dfc45c120703c9d6ad495bb9de66893ae5ab5ac8f40dc")] \ No newline at end of file +[assembly: InternalsVisibleTo("EPPlus, PublicKey=00240000048000009400000006020000002400005253413100040000010001002981343969ed86fe604c56a84c61e33109424ef07bb458ff12e9533c11ea23ac8ef7e014b2a2de4ceb5f7528f963c755fe9b32f09cc35d21de94319d2a952a6e663cd46d6d98465998c77b52093d4f17cdc20ec054751244696f08afa6f4417d85267b147b73b6a3f5e9015b9dfd3dcc3328ce63df53a7c08a5544c1526ea5a5")] +[assembly: InternalsVisibleTo("EPPlus.Export.Pdf.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dd3a3466a88cbf5d374fe992cec433c48022414fe96608933e8e36782001213dd31bc454dc6f962a54a3a76cfb9e03a32cd4c658ecd49d1a98709971a080ab92d5c5b65346155f8d6422db4ffbf662f78913996a9a8b78ee11ff3cda7e585208cd4468fb3201f15bbb1dfc45c120703c9d6ad495bb9de66893ae5ab5ac8f40dc")] +// +[assembly: InternalsVisibleTo("EPPlus.PdfExportPerformance, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f5398d389a44aacee243c9e73152fae942e9ede958ebd4b06651a749e6b2c0395a66bc0fc133dce1cf83cd667fe8f761b433063e603968f12b0b17e474233410f8fe933853d2344e89526414ef65ed7236a7a5c012e30275ec30d7fb665dbe1bd3435439bec55f431b9e69e943294fd474942d6bcb431c94ef653cbeee5b8a9e")] diff --git a/src/EPPlus.Export.Pdf/DocumentObjects/ExcelPatternMask.cs b/src/EPPlus.Export.Pdf/DocumentObjects/ExcelPatternMask.cs new file mode 100644 index 0000000000..4ff9734381 --- /dev/null +++ b/src/EPPlus.Export.Pdf/DocumentObjects/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.DocumentObjects +{ + /// + /// 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/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..49a6506dae 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/CIDSystemInfo.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/CIDSystemInfo.cs @@ -10,8 +10,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 96% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfCIDFont.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfCIDFont.cs index 93b714006a..c5415513a4 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfCIDFont.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfCIDFont.cs @@ -17,9 +17,9 @@ Date Author Change using System.Linq; using System.Text; -namespace EPPlus.Export.Pdf.PdfObjects.PdfFonts +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/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..ce46bcf6a7 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfCidSet.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfCidSet.cs @@ -12,11 +12,12 @@ Date Author Change *************************************************************************************************/ using System.IO; -namespace EPPlus.Export.Pdf.PdfObjects.PdfFonts +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/PdfObjects/PdfFonts/PdfFont.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFont.cs similarity index 97% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfFont.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFont.cs index cffdcc7158..54ece281b1 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfFont.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFont.cs @@ -13,9 +13,9 @@ Date Author Change using System.IO; using System.Text; -namespace EPPlus.Export.Pdf.PdfObjects.PdfFonts +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/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..a1833327b2 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfFontDescriptor.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontDescriptor.cs @@ -15,13 +15,13 @@ Date Author Change using System.IO; using System.Text; -namespace EPPlus.Export.Pdf.PdfObjects.PdfFonts +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/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/DocumentObjects/Fonts/PdfFontWidths.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontWidths.cs new file mode 100644 index 0000000000..1e515f51f1 --- /dev/null +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontWidths.cs @@ -0,0 +1,41 @@ +/************************************************************************************************* + 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; +using System.IO; +using System.Linq; + +namespace EPPlus.Export.Pdf.DocumentObjects.Fonts +{ + internal class PdfFontWidths : PdfObject + { + internal readonly int[] widths; + + public PdfFontWidths(int objectNumber, List widths, int version = 0) + : base(objectNumber, version) + { + this.widths = widths.ToArray(); + } + + internal override string RenderDictionary() + { + var widthsStr = string.Join(" ", widths.Select(w => w.ToString()).ToArray()); + return $" [ {widthsStr} ]"; + } + + internal override void RenderDictionary(BinaryWriter bw) + { + var widthsStr = string.Join(" ", widths.Select(w => w.ToString()).ToArray()); + WriteAscii(bw, $" [ {widthsStr} ]"); + } + } +} 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..b317005fa5 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 { @@ -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/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..010a673461 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfType0FontDict.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfType0FontDict.cs @@ -14,14 +14,13 @@ 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 { 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/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 91% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternFill.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/Patterns/PdfPatternFill.cs index 5eb7aa576b..959bf16971 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternFill.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Patterns/PdfPatternFill.cs @@ -13,13 +13,12 @@ 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 { 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 new file mode 100644 index 0000000000..504598c138 --- /dev/null +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Patterns/PdfPatternMaskFill.cs @@ -0,0 +1,89 @@ +/************************************************************************************************* + 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.Helpers; +using System.Drawing; +using System.Text; + +namespace EPPlus.Export.Pdf.DocumentObjects.Patterns +{ + /// + /// 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/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 98% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfBorderRenderer.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/PdfBorderRenderer.cs index a87b9b14d6..489da65399 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 OfficeOpenXml.Style; +using EPPlus.Export.Pdf.Enums; +using EPPlus.Export.Pdf.Helpers; +using EPPlus.Export.Pdf.Layout; -namespace EPPlus.Export.Pdf.PdfObjects +namespace EPPlus.Export.Pdf.DocumentObjects { internal class PdfBorderRenderer { @@ -25,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/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 53% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/PdfContentStream.cs index 200855cff9..7039d470ad 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/PdfContentStream.cs @@ -10,16 +10,8 @@ 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 +19,13 @@ 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; +using EPPlus.Export.Pdf.Enums; -namespace EPPlus.Export.Pdf.PdfObjects +namespace EPPlus.Export.Pdf.DocumentObjects { internal class PdfContentStream : PdfObject { @@ -73,23 +70,31 @@ 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}"); } } @@ -99,29 +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) - { - if (!Dictionaries.Fonts.ContainsKey(fontName)) - { - 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[fontName]; - } - public void AddText(PdfCellContentLayout cell, Vector2 position, double textRotation, PdfDictionaries dictionaries, PdfPageSettings pageSettings) { double advanceY = 0d; @@ -139,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; @@ -164,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++) @@ -185,44 +154,47 @@ 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) { shapedTextIndex++; + if (shapedTextIndex >= cell.ShapedTexts.Count) + { + 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); var color = richInfo.FontColor; - var fontResrouce = GetFontResource(dictionaries, pageSettings, richInfo.Family, richInfo.SubFamily, richInfo.Size); + var fontResource = dictionaries.GetFont(richInfo.Family, richInfo.SubFamily); double size = richInfo.Size; - double scale = richInfo.Size / fontResrouce.fontData.HeadTable.UnitsPerEm; + 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); if (richInfo.SuperScript) { - var supOffX = fontResrouce.fontData.Os2Table.ySuperscriptXOffset * scale; - var supOffY = fontResrouce.fontData.Os2Table.ySuperscriptYOffset * scale; - var supSizeY = fontResrouce.fontData.Os2Table.ySuperscriptYSize * scale; + var supOffX = fontResource.fontData.Os2Table.ySuperscriptXOffset * scale; + var supOffY = fontResource.fontData.Os2Table.ySuperscriptYOffset * scale; + var supSizeY = fontResource.fontData.Os2Table.ySuperscriptYSize * scale; textMatrix = textMatrix * Matrix3x3.Translation(supOffX, supOffY); size = supSizeY; } else if (richInfo.SubScript) { - var supOffX = fontResrouce.fontData.Os2Table.ySubscriptXOffset * scale; - var supOffY = fontResrouce.fontData.Os2Table.ySubscriptYOffset * scale; - var supSizeY = fontResrouce.fontData.Os2Table.ySubscriptYSize * scale; + var supOffX = fontResource.fontData.Os2Table.ySubscriptXOffset * scale; + var supOffY = fontResource.fontData.Os2Table.ySubscriptYOffset * scale; + var supSizeY = fontResource.fontData.Os2Table.ySubscriptYSize * scale; textMatrix = textMatrix * Matrix3x3.Translation(supOffX, supOffY); size = supSizeY; } if (richInfo.UnderlineType != 12) { - var underlinePos = fontResrouce.fontData.PostTable.underlinePosition * scale; - var underlineWidth = fontResrouce.fontData.PostTable.underlineThickness * scale; + var underlinePos = fontResource.fontData.PostTable.underlinePosition * scale; + var underlineWidth = fontResource.fontData.PostTable.underlineThickness * scale; var start = textMatrix.Transform(new Vector2(0, underlinePos)); var end = textMatrix.Transform(new Vector2(textLength, underlinePos)); commands.Add($"{underlineWidth.ToPdfString()} w"); @@ -232,8 +204,8 @@ public void AddText(PdfCellContentLayout cell, Vector2 position, double textRota } if (richInfo.StrikeType > 1) { - var strikePos = fontResrouce.fontData.Os2Table.yStrikeoutPosition * scale; - var strikeWidth = fontResrouce.fontData.Os2Table.yStrikeoutSize * scale; + var strikePos = fontResource.fontData.Os2Table.yStrikeoutPosition * scale; + var strikeWidth = fontResource.fontData.Os2Table.yStrikeoutSize * scale; var start = textMatrix.Transform(new Vector2(0, strikePos)); var end = textMatrix.Transform(new Vector2(textLength, strikePos)); commands.Add($"{strikeWidth.ToPdfString()} w"); @@ -242,7 +214,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 @@ -251,9 +223,8 @@ public void AddText(PdfCellContentLayout cell, Vector2 position, double textRota byte currentFontId = shapedText.ShapedText.Glyphs.Length > 0 ? shapedText.ShapedText.Glyphs[0].FontId : (byte)0; string currentFontLabel = shapedText.FontIdMap.ContainsKey(currentFontId) ? shapedText.FontIdMap[currentFontId] - : fontResrouce.Label; + : fontResource.Label; commands.Add($"/{currentFontLabel} {size.ToPdfString()} Tf"); - int fragmentCharCount = textFormat.Text.Length; int charsRendered = 0; var sb = new StringBuilder(); @@ -263,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 @@ -272,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(" "); @@ -296,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}"); @@ -468,25 +278,47 @@ 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) + public void AddInnerGridLines(Transform pageLayout) { - commands.Add($"% HeaderFooter Start: {cell.Name}"); + if (pageLayout is not PdfPageLayout pl) return; + if (pl.isCommentsPage) return; + + commands.Add($"% Gridlines Start"); commands.Add("q"); - //AddText(cell, cell.LocalPosition, 0, dictionaries, pageSettings); + commands.Add($"{GridLine.Width.ToPdfString()} w"); + commands.Add(Color.Black.ToFillCommand()); + foreach (var line in pl.GridLines) + { + 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($"% HeaderFooter End: {cell.Name}"); + commands.Add($"% Gridlines End"); } - public void AddInnerGridLines(Transform pageLayout) + public void AddPrintTitleGridLines(Transform pageLayout) { if (pageLayout is not PdfPageLayout pl) return; if (pl.isCommentsPage) return; - commands.Add($"% Gridlines Start"); + 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.GridLines) + foreach (var line in pl.PrintTitleGridLines) { string w, h; if (line.X1 == line.X2) @@ -505,7 +337,7 @@ public void AddInnerGridLines(Transform pageLayout) } commands.Add("f"); commands.Add("Q"); - commands.Add($"% Gridlines End"); + commands.Add($"% Print Title Gridlines End"); } public void AddOuterGridBorder(Transform pageLayout) @@ -529,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; @@ -571,15 +381,13 @@ 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 + pl.PrintTitleWidth - pad; var y = bottom - pad; - var width = (right - left) + pad * 2; - var height = (top - 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"); } - - internal override string RenderDictionary() { var content = string.Join("\n", commands.ToArray()) + "\n"; @@ -593,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/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..4937c84fb2 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 { @@ -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/PdfObjects/PdfObject.cs b/src/EPPlus.Export.Pdf/DocumentObjects/PdfObject.cs similarity index 84% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfObject.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/PdfObject.cs index 06f2bce685..b8213d3c05 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 { @@ -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/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 97% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfShadings/PdfShading.cs rename to src/EPPlus.Export.Pdf/DocumentObjects/Shadings/PdfShading.cs index 1124724eb4..ba3754247a 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfShadings/PdfShading.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Shadings/PdfShading.cs @@ -14,9 +14,9 @@ Date Author Change using System.IO; using System.Text; -namespace EPPlus.Export.Pdf.PdfObjects.PdfShadings +namespace EPPlus.Export.Pdf.DocumentObjects.Shadings { - public enum DeviceColorSpace + internal enum DeviceColorSpace { DeviceGray, DeviceRGB, diff --git a/src/EPPlus.Export.Pdf/EPPlus.Export.Pdf.csproj b/src/EPPlus.Export.Pdf/EPPlus.Export.Pdf.csproj index 7e1db7a087..1aa441393c 100644 --- a/src/EPPlus.Export.Pdf/EPPlus.Export.Pdf.csproj +++ b/src/EPPlus.Export.Pdf/EPPlus.Export.Pdf.csproj @@ -16,7 +16,6 @@ - @@ -25,17 +24,14 @@ - - - diff --git a/src/EPPlus.Export.Pdf/Enums/EPPlusEnums.cs b/src/EPPlus.Export.Pdf/Enums/EPPlusEnums.cs new file mode 100644 index 0000000000..16fd7987a6 --- /dev/null +++ b/src/EPPlus.Export.Pdf/Enums/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.Enums +{ + /// + /// 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/ExcelPdf.cs b/src/EPPlus.Export.Pdf/ExcelPdf.cs index 65f34e8a1b..d9d1834d56 100644 --- a/src/EPPlus.Export.Pdf/ExcelPdf.cs +++ b/src/EPPlus.Export.Pdf/ExcelPdf.cs @@ -10,188 +10,147 @@ 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.DocumentObjects; +using EPPlus.Export.Pdf.Enums; +using EPPlus.Export.Pdf.Layout; +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(); - 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; - } + private PdfPageSettings _pageSettings; + private PdfDictionaries _dictionaries; + private List _document = new List(); + private string _debugString; - /// - /// 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) + internal static string Header { - //_ws = worksheet[0]; - //defaultFontName = worksheet[0].Workbook.ThemeManager.CurrentTheme.FontScheme.MinorFont[0].Typeface; - //PageSettings = pageSettings == null ? new PdfPageSettings() : pageSettings; - } - - /// - /// 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; - } - - /// - /// 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) - { - //_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) { - 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; + return _dictionaries.Patterns[patternName].Label; } } return null; } //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) + 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)); } } } //Add Patterns - internal void AddPatternData() + private 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) + private 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) + private PdfCatalog AddCatalog(int pagesObjectNumber) { - var catalog = new PdfObjects.PdfCatalog(Document.Count + 1, pagesObjectNumber); - Document.Add(catalog); + var catalog = new PdfCatalog(_document.Count + 1, pagesObjectNumber); + _document.Add(catalog); return catalog; } //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 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 || 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 contentStream = new PdfContentStream(Document.Count + 1); + 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. contentStream.AddCommand("q"); contentStream.AddMarginClipping((PdfPageLayout)pageLayout); - if (PageSettings.ShowGridLines) + if (_pageSettings.ShowGridLines) { contentStream.AddInnerGridLines(pageLayout); } @@ -206,7 +165,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,45 +176,60 @@ 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); + contentStream.AddPrintTitleGridLines(pageLayout); } //Add header and footer. foreach (var hf in headerFooterLayouts) { - contentStream.AddCellContentLayout(hf, Dictionaries, PageSettings); + contentStream.AddCellContentLayout(hf, _dictionaries, _pageSettings); } - Document.Add(contentStream); - page.contentObjectNumbers.Add(contentStream.objectNumber); - 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) + foreach (var titleCell in printTitleLayouts) { - var headerFooterLayout = hf as PdfHeaderFooterLayout; - contentStream.AddCellContentLayout(headerFooterLayout, Dictionaries, PageSettings); + 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"); } //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; - + _pageSettings = pageSettings; + _dictionaries = dictionaries; var catalog = AddCatalog(2); //Create Pages var pagesLayout = layout.ChildObjects[0]; @@ -265,12 +239,12 @@ 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); } @@ -284,25 +258,25 @@ internal void CreatePdf(PdfPageSettings pageSettings, PdfDictionaries dictionari using (var bw = new BinaryWriter(fs, Encoding.ASCII)) { //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) + 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); + 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); + 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) + if (_pageSettings.Debug && _pageSettings.PrintAsText) { using (var fs = new FileStream(fileName + ".txt", FileMode.Create, FileAccess.Write)) { @@ -313,72 +287,5 @@ internal void CreatePdf(PdfPageSettings pageSettings, PdfDictionaries dictionari } } } - - - /// - /// Create the pdf from the supplied worksheet. - /// - /// The file name - public void CreatePdf(string Filename) - { - //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(Dictionaries); - //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); - 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.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 84% rename from src/EPPlus.Export.Pdf/Pdfhelpers/PdfString.cs rename to src/EPPlus.Export.Pdf/Helpers/PdfString.cs index acd3fa5876..d26aea06f6 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 { @@ -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/CellAlignmentData.cs b/src/EPPlus.Export.Pdf/Layout/CellAlignmentData.cs new file mode 100644 index 0000000000..68cdd7a0e9 --- /dev/null +++ b/src/EPPlus.Export.Pdf/Layout/CellAlignmentData.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 + *************************************************************************************************/ +using EPPlus.Export.Pdf.Enums; + +namespace EPPlus.Export.Pdf.Layout +{ + 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/CellFillData.cs b/src/EPPlus.Export.Pdf/Layout/CellFillData.cs new file mode 100644 index 0000000000..bd78f0d7eb --- /dev/null +++ b/src/EPPlus.Export.Pdf/Layout/CellFillData.cs @@ -0,0 +1,29 @@ +/************************************************************************************************* + 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.Drawing; +using EPPlus.Export.Pdf.Enums; + +namespace EPPlus.Export.Pdf.Layout +{ + 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.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkUp.cs b/src/EPPlus.Export.Pdf/Layout/CellFillGradientData.cs similarity index 57% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkUp.cs rename to src/EPPlus.Export.Pdf/Layout/CellFillGradientData.cs index d3567955c3..dad5e422e3 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkUp.cs +++ b/src/EPPlus.Export.Pdf/Layout/CellFillGradientData.cs @@ -10,25 +10,29 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -using EPPlus.Export.Pdf.Pdfhelpers; +using EPPlus.Export.Pdf.Enums; +using EPPlus.Export.Pdf.Helpers; using System.Drawing; -using System.Text; -namespace EPPlus.Export.Pdf.PdfObjects.PdfPatterns +namespace EPPlus.Export.Pdf.Layout { - internal class PdfPatternDarkUp : PdfPatternFill + internal class PdfCellGradientFillData { - public PdfPatternDarkUp(Color foreground, Color background) : base(foreground, background) { } + 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 CreatePatternResource() + public override string ToString() { - 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(); + return GradientType.ToString() + Color1.ToHexString() + Color2.ToHexString() + Degree + Top + Bottom + Left + Right; } } } diff --git a/src/EPPlus.Export.Pdf/PdfLayout/PdfPagesLayout.cs b/src/EPPlus.Export.Pdf/Layout/GridLineData.cs similarity index 66% rename from src/EPPlus.Export.Pdf/PdfLayout/PdfPagesLayout.cs rename to src/EPPlus.Export.Pdf/Layout/GridLineData.cs index fac5135935..fab9877ebd 100644 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfPagesLayout.cs +++ b/src/EPPlus.Export.Pdf/Layout/GridLineData.cs @@ -10,22 +10,21 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -using OfficeOpenXml; -using EPPlus.Graphics; - -namespace EPPlus.Export.Pdf.PdfLayout +namespace EPPlus.Export.Pdf.Layout { - /// - /// Represents the pages object in the pdf document. - /// - internal class PdfPagesLayout : Transform + internal class GridLine { - internal ExcelRangeBase Range; + 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; - internal PdfPagesLayout(double x, double y, double height, double width) - : base(x, y, height, width) + 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/PdfCatalog/MergedCellCorners.cs b/src/EPPlus.Export.Pdf/Layout/MergedCellCorners.cs similarity index 60% rename from src/EPPlus.Export.Pdf/PdfCatalog/MergedCellCorners.cs rename to src/EPPlus.Export.Pdf/Layout/MergedCellCorners.cs index 62809e8044..8e16f18eec 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/MergedCellCorners.cs +++ b/src/EPPlus.Export.Pdf/Layout/MergedCellCorners.cs @@ -1,6 +1,18 @@ -using System; +/************************************************************************************************* + 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/ -namespace EPPlus.Export.Pdf.PdfCatalog + 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 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/PdfDrawingLayout.cs b/src/EPPlus.Export.Pdf/Layout/MergedCellDrawInfo.cs similarity index 68% rename from src/EPPlus.Export.Pdf/PdfLayout/PdfDrawingLayout.cs rename to src/EPPlus.Export.Pdf/Layout/MergedCellDrawInfo.cs index 9d89c81e2e..667702e8cf 100644 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfDrawingLayout.cs +++ b/src/EPPlus.Export.Pdf/Layout/MergedCellDrawInfo.cs @@ -10,20 +10,13 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -using OfficeOpenXml.Drawing; -using EPPlus.Graphics; - - -namespace EPPlus.Export.Pdf.PdfLayout +namespace EPPlus.Export.Pdf.Layout { - //NOT IMPLEMENTED YET. - internal class PdfDrawingLayout : Transform + internal struct MergedCellDrawInfo { - public ExcelDrawing Drawing; - public PdfDrawingLayout(ExcelDrawing drawing, double x, double y, double width, double height) - : base(x,y,width,height) - { - Drawing = drawing; - } + public double X; + public double Y; + public double Width; + public double Height; } } diff --git a/src/EPPlus.Export.Pdf/Layout/PdfBorderData.cs b/src/EPPlus.Export.Pdf/Layout/PdfBorderData.cs new file mode 100644 index 0000000000..5af7cc4015 --- /dev/null +++ b/src/EPPlus.Export.Pdf/Layout/PdfBorderData.cs @@ -0,0 +1,73 @@ +/************************************************************************************************* + 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.Drawing; +using EPPlus.Export.Pdf.Enums; + +namespace EPPlus.Export.Pdf.Layout +{ + 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.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkDown.cs b/src/EPPlus.Export.Pdf/Layout/PdfCell.cs similarity index 51% rename from src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkDown.cs rename to src/EPPlus.Export.Pdf/Layout/PdfCell.cs index df5a66d5e9..7ab8ab39fb 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfPatterns/PdfPatternDarkDown.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfCell.cs @@ -10,25 +10,27 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -using EPPlus.Export.Pdf.Pdfhelpers; -using System.Drawing; -using System.Text; +using EPPlus.Fonts.OpenType.Integration; +using System.Collections.Generic; -namespace EPPlus.Export.Pdf.PdfObjects.PdfPatterns +namespace EPPlus.Export.Pdf.Layout { - internal class PdfPatternDarkDown : PdfPatternFill + internal class PdfCellBase { - public PdfPatternDarkDown(Color foreground, Color background) : base(foreground, background) { } + public bool Hidden; + public PdfCellAlignmentData ContentAligmnet; + public bool IsPrintTitleRow; + public bool IsPrintTitleCol; - 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(); - } + 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; } } } diff --git a/src/EPPlus.Export.Pdf/Layout/PdfCellBorderLayout.cs b/src/EPPlus.Export.Pdf/Layout/PdfCellBorderLayout.cs new file mode 100644 index 0000000000..d3a4062680 --- /dev/null +++ b/src/EPPlus.Export.Pdf/Layout/PdfCellBorderLayout.cs @@ -0,0 +1,93 @@ +/************************************************************************************************* + 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.Enums; +using EPPlus.Graphics; +using System.Diagnostics; +using System.Drawing; + +namespace EPPlus.Export.Pdf.Layout +{ + [DebuggerDisplay("Border: {Name}")] + internal class PdfCellBorderLayout : Transform + { + public PdfCellBordersData BorderData; + public bool IsMerged = false; + public MergedCellDrawInfo MergedCellInfo; + public MergedCellCorners Corners; + public bool IsPrintTitle; + public string range; + + 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; + IsMerged = isMerged; + MergedCellInfo = info; + Corners = corners; + BorderData = new PdfCellBordersData(); + + BorderData.Top.X = LocalPosition.X; + BorderData.Top.Y = LocalPosition.Y; + BorderData.Top.Width = Size.X; + BorderData.Top.Height = Size.Y; + + BorderData.Bottom.X = LocalPosition.X; + BorderData.Bottom.Y = LocalPosition.Y; + BorderData.Bottom.Width = Size.X; + BorderData.Bottom.Height = Size.Y; + + BorderData.Left.X = LocalPosition.X; + BorderData.Left.Y = LocalPosition.Y; + BorderData.Left.Width = Size.X; + BorderData.Left.Height = Size.Y; + + BorderData.Right.X = LocalPosition.X; + BorderData.Right.Y = LocalPosition.Y; + BorderData.Right.Width = Size.X; + BorderData.Right.Height = Size.Y; + + BorderData.DiagonalUp.MergedDiagonalWidth = width; + BorderData.DiagonalUp.MergedDiagonalHeight = height; + + 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 new file mode 100644 index 0000000000..17b10557fe --- /dev/null +++ b/src/EPPlus.Export.Pdf/Layout/PdfCellContentLayout.cs @@ -0,0 +1,252 @@ +/************************************************************************************************* + 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.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; + +namespace EPPlus.Export.Pdf.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 TextLayoutEngine textLayoutEngine; + public TextLineCollection TextLines; + 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) + { + 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); + } + + // 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 + }; + } + + 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 new file mode 100644 index 0000000000..dd037480c9 --- /dev/null +++ b/src/EPPlus.Export.Pdf/Layout/PdfCellLayout.cs @@ -0,0 +1,172 @@ +/************************************************************************************************* + 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.Enums; +using EPPlus.Export.Pdf.Helpers; +using EPPlus.Export.Pdf.Resources; +using EPPlus.Export.Pdf.Settings; +using EPPlus.Graphics; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Linq; + +namespace EPPlus.Export.Pdf.Layout +{ + [DebuggerDisplay("Cell: {Name}")] + internal class PdfCellLayout : Transform + { + public PdfCellFillData CellFillData; + public double LeftTextSpillLength = 0d; + public double RightTextSpillLength = 0d; + public bool delete = false; + public bool IsHeading = false; + public bool IsPrintTitle = false; + + 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; + CellFillData = new PdfCellFillData(); + } + + internal void SetFill(Color backgroundColor) + { + CellFillData.PatternStyle = ExcelFillStyle.Solid; + CellFillData.BackgroundColor = backgroundColor; + } + 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); + } + + 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; + } + + public void UpdateShadingPositionMatrix(PdfPageSettings pageSettings) + { + 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]; + } + } + } + } +} diff --git a/src/EPPlus.Export.Pdf/Layout/PdfHeaderFooter.cs b/src/EPPlus.Export.Pdf/Layout/PdfHeaderFooter.cs new file mode 100644 index 0000000000..1765ee5c62 --- /dev/null +++ b/src/EPPlus.Export.Pdf/Layout/PdfHeaderFooter.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 EPPlus.Fonts.OpenType.Integration; +using System.Collections.Generic; + +namespace EPPlus.Export.Pdf.Layout +{ + 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 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 PdfCellBase(); + Content.TextFragments = textFormats; + PageNumberIndexes = pageNumberIndexes; + NumberOfPagesIndexes = numberOfPagesIndexes; + PageType = type; + Alignment = alignment; + Section = section; + } + } +} diff --git a/src/EPPlus.Export.Pdf/PdfLayout/PdfContentLayout.cs b/src/EPPlus.Export.Pdf/Layout/PdfPageLayout.cs similarity index 55% rename from src/EPPlus.Export.Pdf/PdfLayout/PdfContentLayout.cs rename to src/EPPlus.Export.Pdf/Layout/PdfPageLayout.cs index 94d2a83e82..2a1d0d9a88 100644 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfContentLayout.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfPageLayout.cs @@ -11,26 +11,25 @@ Date Author Change 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ using EPPlus.Graphics; -using EPPlus.Graphics.Geometry; -using EPPlus.Export.Pdf.PdfSettings; +using System.Collections.Generic; +using System.Diagnostics; -namespace EPPlus.Export.Pdf.PdfLayout +namespace EPPlus.Export.Pdf.Layout { - /// - /// Represents the content area for a page in the pdf document. - /// - internal class PdfContentLayout : Transform + [DebuggerDisplay("{Name}")] + internal class PdfPageLayout : Transform { - public PdfContentLayout(double x, double y, PdfContentBounds bounds) - { - Position = new Vector2(x, y); - Size = new Vector2(bounds.Width, bounds.Height); - } + internal List GridLines = new List(); + internal List BorderLines = new List(); + public List PrintTitleGridLines = new List(); + public double HeadingWidth; + public double HeadingHeight; + public double PrintTitleWidth; + public double PrintTitleHeight; + public bool isCommentsPage = false; - public PdfContentLayout(double x, double y, double width, double height) - { - Position = new Vector2(x, y); - Size = new Vector2(width, height); - } + public PdfPageLayout(double x, double y, double width, double height) + : base(x, y, width, height) + { } } -} +} \ No newline at end of file diff --git a/src/EPPlus.Export.Pdf/PdfLayout/IShadingLayout.cs b/src/EPPlus.Export.Pdf/Layout/PdfShapedText.cs similarity index 71% rename from src/EPPlus.Export.Pdf/PdfLayout/IShadingLayout.cs rename to src/EPPlus.Export.Pdf/Layout/PdfShapedText.cs index 06493c9999..6162af4f9e 100644 --- a/src/EPPlus.Export.Pdf/PdfLayout/IShadingLayout.cs +++ b/src/EPPlus.Export.Pdf/Layout/PdfShapedText.cs @@ -10,18 +10,17 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -using EPPlus.Export.Pdf.PdfSettings; +using EPPlus.Fonts.OpenType; +using OfficeOpenXml.Interfaces.Fonts; +using System.Collections.Generic; -namespace EPPlus.Export.Pdf.PdfLayout +namespace EPPlus.Export.Pdf.Layout { - /// - /// Interface for shading objects. - /// - internal interface IShadingLayout + internal struct PdfShapedText { - /// - /// Update position matrix. - /// - abstract void UpdateShadingPositionMatrix(PdfPageSettings pageSettings); + public IFontProvider FontProvider; + public Dictionary FontIdMap; + public List UsedFonts; + public ShapedText ShapedText; } } diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs deleted file mode 100644 index 052abc1a73..0000000000 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs +++ /dev/null @@ -1,235 +0,0 @@ -using EPPlus.Export.Pdf.PdfResources; -using EPPlus.Export.Pdf.PdfSettings; -using EPPlus.Graphics; -using OfficeOpenXml; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; - -namespace EPPlus.Export.Pdf.PdfCatalog -{ - internal class PdfCatalog - { - internal PdfDictionaries Dictionaries = new PdfDictionaries(); - private bool AddTextForHeadings = true; - - //Constructors - 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); - } - } - - public PdfCatalog(PdfPageSettings pageSettings, ExcelWorksheet worksheet, string fileName) - { - pageSettings.defaultFontName = worksheet.Workbook.ThemeManager.CurrentTheme.FontScheme.MinorFont[0].Typeface; - - Stopwatch sw = Stopwatch.StartNew(); - - //Collect Text - PdfWorksheet 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); //call a method that does this so we can use it for comments sheet aswell! - 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(); - } - - public PdfCatalog(PdfPageSettings pageSettings, ExcelRangeBase range) - { - PdfWorksheet pdfSheet = GetPdfWorksheet(pageSettings, range); - ShapeTextInPdfWorksheet(pageSettings, pdfSheet); - } - - public 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 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) - { - 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++) - { - var cell = range.Map[i, j]; - PdfTextShaper.LayoutAndShapeText(pageSettings, Dictionaries, cell); - } - } - } - 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); - } - } - } - //if (pdfSheet.HeaderFooters != null) - //{ - // foreach (var hf in pdfSheet.HeaderFooters.PdfHeaderFooterEntries) - // { - // PdfTextShaper.LayoutAndShapeText(pageSettings, Dictionaries, hf.Content); - // } - //} - } - - //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; - } - - private 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"); - AddTextForHeadings = false; - GetMaps(pageSettings, pdfSheet, pdfSheet.Ranges); - 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]); - 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.Dimension; - 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 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; - 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/PdfCell.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfCell.cs deleted file mode 100644 index d4ded6100a..0000000000 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCell.cs +++ /dev/null @@ -1,46 +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 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/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/PdfLayout.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs deleted file mode 100644 index d81a303e18..0000000000 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfLayout.cs +++ /dev/null @@ -1,681 +0,0 @@ -using EPPlus.Export.Pdf.PdfLayout; -using EPPlus.Export.Pdf.PdfResources; -using EPPlus.Export.Pdf.PdfSettings; -using EPPlus.Graphics; -using OfficeOpenXml; -using OfficeOpenXml.Style; -using System; -using System.Collections.Generic; - -namespace EPPlus.Export.Pdf.PdfCatalog -{ - - internal struct MergedCellDrawInfo - { - public double X; - public double Y; - public double Width; - public double Height; - } - - internal struct Page - { - public int FromRow; - public int FromColumn; - public int ToRow; - public int ToColumn; - - public bool HasPrintTitle; - - public PdfCellCollection Map; - - public PdfHeaderFooterCollection HeaderFooters; - - public Dictionary MergedCells; - - public double[] RowHeights; - } - - internal struct Pages - { - public Page[] Page; - public int Width; - public int Height; - public bool IsCommentsPage; - public int Count - { - get { return Width * Height; } - } - } - - internal class PdfLayout - { - private const double rowHeadingWith1CharWidth = 23.25d; - - 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; - var drawnMergedCells = new HashSet(); - //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; - //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; - pageLayout.AddChild(border); - } - x += map.ColumnWidth; - } - y -= rowHeight; - x = 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; - }); - - //Print titles - 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; - - // 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) - { - foreach (var range in pdfSheet.Ranges) - { - var pages = GetNumberOfPages(pageSettings, pdfSheet, range); - pages = AssignRangeToPages(pageSettings, range, pages); - pages = MapPage(range, pages); - pages = GetHeaderFooter(range, pages, pdfSheet); - pages = PrecomputeMergedCells(pageSettings, range, pages); - PagesCollection.Add(pages); - } - if (pdfSheet.CommentsAndNotes.Range != null) - { - var pages = GetNumberOfPages(pageSettings, pdfSheet, 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; - 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; - 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; - } - - internal static Pages GetNumberOfPages(PdfPageSettings pageSettings, PdfWorksheet pdfSheet, 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)); - - 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++; - } - //if (HasPrintTitles Row) - //if (HasPrintTitles Column) - - 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); - var rowSegments = GetRowSegments(pageSettings, range, worksheet, addedHeight); - - 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 }; - } - 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 }; - } - - 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) - { - 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; - - // Content-bounds overflow: col doesn't fit, end segment before it and reprocess. - if (width + range.ColWidths[col] + addedWidth >= 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) - { - 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; - - // Content-bounds overflow: row doesn't fit, end segment before it and reprocess. - if (height + range.RowHeights[row].Height + addedHeight >= 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.LayoutAndShapeText(pageSettings, dictionaries, hf.Content); - } - - } -} diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfRange.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfRange.cs deleted file mode 100644 index 43937d834c..0000000000 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfRange.cs +++ /dev/null @@ -1,30 +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 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 c6872fa5e4..0000000000 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs +++ /dev/null @@ -1,827 +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.Graphics.Units; -using OfficeOpenXml; -using OfficeOpenXml.ConditionalFormatting; -using OfficeOpenXml.ConditionalFormatting.Contracts; -using OfficeOpenXml.Interfaces.Drawing.Text; -using OfficeOpenXml.Style; -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 }); - //x = 0d; - 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; - if (!cell.IsRichText) cell._rtc = new ExcelRichTextCollection(cell.Text, cell); - //tempMap.TextFormats = GetTextFormats(pageSettings, dictionaries, cell._rtc, cellStyle); - tempMap.TextFragments = GetTextFragments(pageSettings, dictionaries, cell, cell._rtc, cellStyle); - } - } - tempMap.CellStyle = cellStyle; - - Map[row, col] = tempMap; - //if cell is hidden maybe we skip adding comment to comment collection. - 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; - } - } - - - //x += width; - //totalWidth = System.Math.Max(x, totalWidth); - - } - firstColumnRun = false; - //y -= height; - } - //HandleDrawings(worksheet); - 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; - if (!main.IsRichText) main._rtc = new ExcelRichTextCollection(main.Text, cell); - //mainCell.TextFormats = GetTextFormats(pageSettings, dictionaries, main._rtc, cellStyle); - mainCell.TextFragments = GetTextFragments(pageSettings, dictionaries, main, main._rtc, 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 > 1) - { - // Sort ascending — priority 1 beats priority 2, etc. - var ordered = cf.OrderBy(r => r.Priority); - - foreach (var rule in ordered) - { - if (!EvaluateConditionalFormattingRule(rule, 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; - return; // CF fill wins — skip table and xf entirely - } - - if (rule.StopIfTrue) break; // rule applied but defined no fill — stop evaluating lower rules - } - } - - //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); - } - } - } - - private static PdfCellStyle GetFontStyle(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; - } - 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; - } - - private static List GetTextFragments(PdfPageSettings pageSettings, PdfDictionaries dictionaries, ExcelRangeBase cell, ExcelRichTextCollection RichTextCollection, PdfCellStyle cellStyle = null) - { - var textFragments = new List(); - bool bold = false, italic = false, underline = false, strike = false; - string text = null; - 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; - } - if (ExcelErrorValue.IsErrorValue(cell.Text)) - { - switch (pageSettings.CellErrors) - { - case CellErrors.Blank: - text = ""; - break; - case CellErrors.Dashed: - text = "--"; - break; - case CellErrors.NA: - text = "#N/A"; - break; - case CellErrors.Displayed: - default: - text = null; - break; - - } - } - for (int i = 0; i < RichTextCollection.Count; i++) - { - var rt = RichTextCollection[i]; - var textFrag = new TextFragment(); - textFrag.Text = text == null ? rt.Text : text; - - textFrag.RichTextOptions.Family = rt.FontName; - textFrag.RichTextOptions.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); - dictionaries.AddFont(pageSettings, textFrag.FullFontName, textFrag.RichTextOptions.SubFamily, textFrag.Text); - } - - return textFragments; - } - - - //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.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; - - //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); - - 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.FullFontName, textFrag.RichTextOptions.SubFamily, textFrag.Text); - if (NumberOfPagesIndexes.Count > 0 || PageNumberIndexes.Count > 0) dictionaries.AddFont(pageSettings, textFrag.FullFontName, textFrag.RichTextOptions.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 64e8a5a080..0000000000 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextShaper.cs +++ /dev/null @@ -1,100 +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(); - - 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)) - { - 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 textWdith = st.ShapedText.GetWidthInPoints((float)tf.Font.Size); - var textHeight = st.ShapedText.GetLineHeightInPoints((float)tf.Font.Size); - //tf.TextLength = textWdith; - //tf.TextHeight = textHeight; - totalTextLength += textWdith; - 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; - } - } -} 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 513630d97f..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 9d241d160d..0000000000 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfCellBorderLayout.cs +++ /dev/null @@ -1,592 +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 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 616ad25579..0000000000 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfCellContentLayout.cs +++ /dev/null @@ -1,616 +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 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) - { - double newX = CalculateHorizontalAlignment(text, textLength, x, width, rightMargin); - double newY = CalculateVerticalAlignment(text, textHeight, firstLineAscent, lastLineAscent, y, height, 0d); - return CalculatePositionFromRotation(textLength, newX, newY); - } - - //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 8937e19a4a..0000000000 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfCellLayout.cs +++ /dev/null @@ -1,332 +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 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/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/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 3c53f623b9..0000000000 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfPageLayout.cs +++ /dev/null @@ -1,334 +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 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 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/PdfObjects/PdfFonts/PdfFontWidths.cs b/src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfFontWidths.cs deleted file mode 100644 index 9d7eda852c..0000000000 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfFonts/PdfFontWidths.cs +++ /dev/null @@ -1,120 +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.Core.Worksheet.Core.Worksheet.Fonts.GenericMeasurements; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace EPPlus.Export.Pdf.PdfObjects.PdfFonts -{ - internal class PdfFontWidths : PdfObject - { - internal readonly int[] widths; - - public PdfFontWidths(int objectNumber, List widths, int version = 0) - : base(objectNumber, version) - { - 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()); - return $" [ {widthsStr} ]"; - } - - internal override void RenderDictionary(BinaryWriter bw) - { - var widthsStr = string.Join(" ", widths.Select(w => w.ToString()).ToArray()); - WriteAscii(bw, $" [ {widthsStr} ]"); - } - } -} 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/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/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 deleted file mode 100644 index f219a41c3f..0000000000 --- a/src/EPPlus.Export.Pdf/PdfResources/PdfPatternResource.cs +++ /dev/null @@ -1,76 +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 EPPlus.Export.Pdf.PdfResources -{ - internal class PdfPatternResource : PdfResource - { - internal int objectNumber; - internal PdfCellFillData CellFillData; - - public PdfPatternResource(int labelNumber, PdfCellFillData cellFillData) - : base("P", labelNumber) - { - CellFillData = cellFillData; - } - - public PdfPattern GetPatternObject(int objectNumber, int version = 0) - { - this.objectNumber = objectNumber; - if (CellFillData.PatternStyle != ExcelFillStyle.None && CellFillData.PatternStyle != ExcelFillStyle.Solid) - { - 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); - } - } - return null; - } - } -} diff --git a/src/EPPlus.Export.Pdf/PdfSettings/PdfPageData/PdfExcelPageDataLookup.cs b/src/EPPlus.Export.Pdf/PdfSettings/PdfPageData/PdfExcelPageDataLookup.cs deleted file mode 100644 index e4428a25cb..0000000000 --- a/src/EPPlus.Export.Pdf/PdfSettings/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/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.Pdf/PdfResources/PdfDictionaries.cs b/src/EPPlus.Export.Pdf/Resources/PdfDictionaries.cs similarity index 69% rename from src/EPPlus.Export.Pdf/PdfResources/PdfDictionaries.cs rename to src/EPPlus.Export.Pdf/Resources/PdfDictionaries.cs index ac7e2453ab..0cfa817aa1 100644 --- a/src/EPPlus.Export.Pdf/PdfResources/PdfDictionaries.cs +++ b/src/EPPlus.Export.Pdf/Resources/PdfDictionaries.cs @@ -10,45 +10,45 @@ 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; +using EPPlus.Export.Pdf.Settings; -namespace EPPlus.Export.Pdf.PdfResources +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) { - 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); } - public 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]; } - - //this should move and be on worksheet level. - internal readonly Dictionary CommentsAndNotes = new Dictionary(); } } diff --git a/src/EPPlus.Export.Pdf/PdfResources/PdfFontResource.cs b/src/EPPlus.Export.Pdf/Resources/PdfFontResource.cs similarity index 77% rename from src/EPPlus.Export.Pdf/PdfResources/PdfFontResource.cs rename to src/EPPlus.Export.Pdf/Resources/PdfFontResource.cs index 9871582da8..a9382a780a 100644 --- a/src/EPPlus.Export.Pdf/PdfResources/PdfFontResource.cs +++ b/src/EPPlus.Export.Pdf/Resources/PdfFontResource.cs @@ -10,9 +10,8 @@ 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.Export.Pdf.DocumentObjects.Fonts; +using EPPlus.Export.Pdf.Settings; using EPPlus.Fonts.OpenType; using EPPlus.Fonts.OpenType.Tables.Os2; using EPPlus.Fonts.OpenType.TextShaping; @@ -22,7 +21,7 @@ Date Author Change using System.Linq; using OfficeOpenXml.Interfaces.Fonts; -namespace EPPlus.Export.Pdf.PdfResources +namespace EPPlus.Export.Pdf.Resources { internal class PdfFontResource : PdfResource { @@ -40,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) @@ -61,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) { @@ -136,7 +109,7 @@ internal PdfFontDescriptor GetFontDescriptorObject(int objectNumber, int version return new PdfFontDescriptor ( objectNumber, - fontName, + fontData.FullName, flag, fontBBox, Convert.ToDouble(fontData.PostTable.italicAngle.FloatValue), @@ -180,7 +153,7 @@ internal PdfFontWidths GetWidthsObject(int objectNumber, int version = 0) internal PdfFont GetFontObject(int objectNumber, int version = 0) { fontObjectNumber = objectNumber; - return new PdfFont(objectNumber, fontName, PdfFontSubType.Type1, firstChar, lastChar, fontWidthObjectNumber, fontDescObjectNumber, PdfFontEncoding.WinAnsiEncoding); + return new PdfFont(objectNumber, fontData.FullName, PdfFontSubType.Type1, firstChar, lastChar, fontWidthObjectNumber, fontDescObjectNumber, PdfFontEncoding.WinAnsiEncoding); } internal PdfCIDFont GetCIDFontObject(int objectNumber, int version = 0) @@ -193,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); } @@ -208,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) { @@ -228,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; @@ -242,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 new file mode 100644 index 0000000000..58a4a7aace --- /dev/null +++ b/src/EPPlus.Export.Pdf/Resources/PdfPatternResource.cs @@ -0,0 +1,99 @@ +/************************************************************************************************* + 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.Layout; +using EPPlus.Export.Pdf.DocumentObjects.Patterns; +using EPPlus.Export.Pdf.DocumentObjects; +using EPPlus.Export.Pdf.Enums; + +namespace EPPlus.Export.Pdf.Resources +{ + 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 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). + // 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, PatternStepX, PatternStepY, 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.Pdf/PdfResources/PdfResource.cs b/src/EPPlus.Export.Pdf/Resources/PdfResource.cs similarity index 96% rename from src/EPPlus.Export.Pdf/PdfResources/PdfResource.cs rename to src/EPPlus.Export.Pdf/Resources/PdfResource.cs index 8bf3ff2339..4c2e5d500f 100644 --- a/src/EPPlus.Export.Pdf/PdfResources/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.Pdf/PdfResources/PdfShadingResource.cs b/src/EPPlus.Export.Pdf/Resources/PdfShadingResource.cs similarity index 92% rename from src/EPPlus.Export.Pdf/PdfResources/PdfShadingResource.cs rename to src/EPPlus.Export.Pdf/Resources/PdfShadingResource.cs index 0070438776..ba565f6b16 100644 --- a/src/EPPlus.Export.Pdf/PdfResources/PdfShadingResource.cs +++ b/src/EPPlus.Export.Pdf/Resources/PdfShadingResource.cs @@ -10,12 +10,12 @@ 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; +using EPPlus.Export.Pdf.DocumentObjects.Patterns; +using EPPlus.Export.Pdf.DocumentObjects.Shadings; +using EPPlus.Export.Pdf.Enums; +using EPPlus.Export.Pdf.Layout; -namespace EPPlus.Export.Pdf.PdfResources +namespace EPPlus.Export.Pdf.Resources { internal class PdfShadingResource : PdfResource { diff --git a/src/EPPlus.Export.Pdf/PdfSettings/PdfPageSettings.cs b/src/EPPlus.Export.Pdf/Settings/PdfPageSettings.cs similarity index 93% rename from src/EPPlus.Export.Pdf/PdfSettings/PdfPageSettings.cs rename to src/EPPlus.Export.Pdf/Settings/PdfPageSettings.cs index 7ee46b12f1..d5fdf6486a 100644 --- a/src/EPPlus.Export.Pdf/PdfSettings/PdfPageSettings.cs +++ b/src/EPPlus.Export.Pdf/Settings/PdfPageSettings.cs @@ -10,13 +10,13 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -using EPPlus.Export.Pdf.PdfSettings.PdfPageSizes; +using EPPlus.Export.Pdf.Settings.PdfPageSizes; using EPPlus.Fonts.OpenType; using OfficeOpenXml; using System.Collections.Generic; using System.Linq; -namespace EPPlus.Export.Pdf.PdfSettings +namespace EPPlus.Export.Pdf.Settings { /// /// Settings object for exporting to PDF. @@ -24,6 +24,9 @@ namespace EPPlus.Export.Pdf.PdfSettings public class PdfPageSettings { private OpenTypeFontEngine _fontEngine; + /// + /// Get the current font engine that is being used by EPPlus. + /// public OpenTypeFontEngine FontEngine { get @@ -51,6 +54,7 @@ public OpenTypeFontEngine FontEngine return _fontEngine; } } + /// /// Add additional folders to search for fonts. /// @@ -94,12 +98,12 @@ public OpenTypeFontEngine FontEngine /// /// Set the range to repeat at the top of the page. /// - public ExcelRange RowsToRepeatAtTop = null; + public string RowsToRepeatAtTop = null; /// /// Set the range to repeat to the left of the page. /// - public ExcelRange ColumnsToRepeatAtLeft = null; + public string ColumnsToRepeatAtLeft = null; /// /// Set if comments and notes should be included. @@ -243,6 +247,9 @@ public enum PageOrders OverThenDown, } + /// + /// How comments will be displayed. + /// public enum CommentsAndNotes { /// @@ -259,6 +266,9 @@ public enum CommentsAndNotes AsDisplayedOnSheet } + /// + /// How errors will be displayed. + /// public enum CellErrors { /// @@ -278,31 +288,4 @@ public enum CellErrors /// 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 +} \ No newline at end of file diff --git a/src/EPPlus.Export.Pdf/PdfSettings/PdfContentBounds.cs b/src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfContentBounds.cs similarity index 97% rename from src/EPPlus.Export.Pdf/PdfSettings/PdfContentBounds.cs rename to src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfContentBounds.cs index 8472960241..6d05d77db5 100644 --- a/src/EPPlus.Export.Pdf/PdfSettings/PdfContentBounds.cs +++ b/src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfContentBounds.cs @@ -10,11 +10,9 @@ 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 +namespace EPPlus.Export.Pdf.Settings.PdfPageSizes { internal class PdfContentBounds : Rect { diff --git a/src/EPPlus.Export.Pdf/PdfSettings/PdfPageSizes/PdfMargins.cs b/src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfMargins.cs similarity index 98% rename from src/EPPlus.Export.Pdf/PdfSettings/PdfPageSizes/PdfMargins.cs rename to src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfMargins.cs index 7a052c256c..1fee87bf8e 100644 --- a/src/EPPlus.Export.Pdf/PdfSettings/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/PdfPageSizes/PdfPageSize.cs b/src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfPageSize.cs similarity index 97% rename from src/EPPlus.Export.Pdf/PdfSettings/PdfPageSizes/PdfPageSize.cs rename to src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfPageSize.cs index 47bd7ca6de..8dce84bc52 100644 --- a/src/EPPlus.Export.Pdf/PdfSettings/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/PdfPageSizes/PdfScaling.cs b/src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfScaling.cs similarity index 67% rename from src/EPPlus.Export.Pdf/PdfSettings/PdfPageSizes/PdfScaling.cs rename to src/EPPlus.Export.Pdf/Settings/PdfPageSizes/PdfScaling.cs index 161be7c9b0..5d48db05c2 100644 --- a/src/EPPlus.Export.Pdf/PdfSettings/PdfPageSizes/PdfScaling.cs +++ b/src/EPPlus.Export.Pdf/Settings/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.Settings.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.Fonts.OpenType/FontSubsetManager.cs b/src/EPPlus.Fonts.OpenType/FontSubsetManager.cs index 6e0094ff51..641933d282 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)) { 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 6cabaf5c45..508f67c331 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/RichText/LayoutSystem.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/RichText/LayoutSystem.cs @@ -260,7 +260,7 @@ public TextLineCollection Wrap(double maxWidth) } } - WrappedLineCollection = new TextLineCollection(wrappedLines, InputFragments); + WrappedLineCollection = new TextLineCollection(wrappedLines, InputFragments, true); return WrappedLineCollection; } 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; } } 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) 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/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. diff --git a/src/EPPlus/Export/PdfExport/Data/PageData.cs b/src/EPPlus/Export/PdfExport/Data/PageData.cs new file mode 100644 index 0000000000..6a7d8c510c --- /dev/null +++ b/src/EPPlus/Export/PdfExport/Data/PageData.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.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.Pdf/PdfLayout/IBorderLayout.cs b/src/EPPlus/Export/PdfExport/Data/PdfCell.cs similarity index 75% rename from src/EPPlus.Export.Pdf/PdfLayout/IBorderLayout.cs rename to src/EPPlus/Export/PdfExport/Data/PdfCell.cs index 1e7f190fa0..ed31455598 100644 --- a/src/EPPlus.Export.Pdf/PdfLayout/IBorderLayout.cs +++ b/src/EPPlus/Export/PdfExport/Data/PdfCell.cs @@ -10,17 +10,15 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ +using EPPlus.Export.Pdf.Layout; -namespace EPPlus.Export.Pdf.PdfLayout +namespace OfficeOpenXml.Export.PdfExport.Data { - /// - /// Interface for border objects - /// - internal interface IBorderLayout + internal class PdfCell : PdfCellBase { - /// - /// Update Border positions. - /// - abstract void UpdateLocalBorderPosition(); + public PdfCellStyle CellStyle; + public bool Merged; + public PdfCell Main; + public ExcelAddressBase MergedAddress; } } diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCellCollection.cs b/src/EPPlus/Export/PdfExport/Data/PdfCellCollection.cs similarity index 65% rename from src/EPPlus.Export.Pdf/PdfCatalog/PdfCellCollection.cs rename to src/EPPlus/Export/PdfExport/Data/PdfCellCollection.cs index 5cb2bb24fc..6fda2ed901 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCellCollection.cs +++ b/src/EPPlus/Export/PdfExport/Data/PdfCellCollection.cs @@ -1,20 +1,28 @@ -using OfficeOpenXml.FormulaParsing.Excel.Functions.Finance; +/************************************************************************************************* + 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.Collections.Generic; -using System.Linq; -using System.Text; -namespace EPPlus.Export.Pdf.PdfCatalog +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.Pdf/PdfLayout/PdfCellStyle.cs b/src/EPPlus/Export/PdfExport/Data/PdfCellStyle.cs similarity index 61% rename from src/EPPlus.Export.Pdf/PdfLayout/PdfCellStyle.cs rename to src/EPPlus/Export/PdfExport/Data/PdfCellStyle.cs index d6b33582a0..f5981d231a 100644 --- a/src/EPPlus.Export.Pdf/PdfLayout/PdfCellStyle.cs +++ b/src/EPPlus/Export/PdfExport/Data/PdfCellStyle.cs @@ -1,10 +1,19 @@ -using System; -using EPPlus.Export.Pdf.Pdfhelpers; -using OfficeOpenXml; +/************************************************************************************************* + 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 EPPlus.Export.Pdf.PdfLayout +namespace OfficeOpenXml.Export.PdfExport.Data { /// /// Holds the styles for the cell. diff --git a/src/EPPlus/Export/PdfExport/Data/PdfRange.cs b/src/EPPlus/Export/PdfExport/Data/PdfRange.cs new file mode 100644 index 0000000000..74206fe8e5 --- /dev/null +++ b/src/EPPlus/Export/PdfExport/Data/PdfRange.cs @@ -0,0 +1,46 @@ +/************************************************************************************************* + 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 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 ExcelRangeBase Range { get; set; } + public bool ExtendColumns { get; set; } + public PdfCellCollection Map { get; set; } + + public PdfRange(ExcelRangeBase range, bool extendColumns) + { + Range = range; + ExtendColumns = extendColumns; + } + } +} diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfWorksheet.cs b/src/EPPlus/Export/PdfExport/Data/PdfWorksheet.cs similarity index 63% rename from src/EPPlus.Export.Pdf/PdfCatalog/PdfWorksheet.cs rename to src/EPPlus/Export/PdfExport/Data/PdfWorksheet.cs index fe9b342cd0..9261792bac 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfWorksheet.cs +++ b/src/EPPlus/Export/PdfExport/Data/PdfWorksheet.cs @@ -1,16 +1,23 @@ -using EPPlus.Export.Pdf.PdfLayout; +/************************************************************************************************* + 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; -using OfficeOpenXml.Interfaces.Drawing.Text; +using OfficeOpenXml.Export.PdfExport.TextMapping; 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 +namespace OfficeOpenXml.Export.PdfExport.Data { internal class PdfWorksheet { @@ -21,6 +28,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; } diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfGridlinesLayout.cs b/src/EPPlus/Export/PdfExport/Layout/PdfGridlinesLayout.cs similarity index 89% rename from src/EPPlus.Export.Pdf/PdfCatalog/PdfGridlinesLayout.cs rename to src/EPPlus/Export/PdfExport/Layout/PdfGridlinesLayout.cs index f2e8976db9..d2f9f50e7d 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfGridlinesLayout.cs +++ b/src/EPPlus/Export/PdfExport/Layout/PdfGridlinesLayout.cs @@ -1,12 +1,21 @@ -using EPPlus.Export.Pdf.PdfLayout; -using EPPlus.Export.Pdf.PdfSettings; +/************************************************************************************************* + 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 OfficeOpenXml.Export.PdfExport.Data; +using EPPlus.Export.Pdf.Layout; using OfficeOpenXml.Style; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -namespace EPPlus.Export.Pdf.PdfCatalog +namespace OfficeOpenXml.Export.PdfExport.Layout { internal class PdfGridlinesLayout { @@ -41,7 +50,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 + page.PrintTitleWidth; for (int ci = 0; ci < colCount; ci++) { var cell = page.Map[page.FromRow, page.FromColumn + ci]; @@ -52,7 +61,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 - page.PrintTitleHeight; for (int ri = 0; ri < rowCount; ri++) { @@ -63,9 +72,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)); @@ -101,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 new file mode 100644 index 0000000000..b5b9b3f719 --- /dev/null +++ b/src/EPPlus/Export/PdfExport/Layout/PdfLayout.cs @@ -0,0 +1,1595 @@ +/************************************************************************************************* + 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.Helpers; +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.TextShaping; +using OfficeOpenXml.Interfaces.Fonts; +using OfficeOpenXml.Style; +using System; +using System.Collections.Generic; +using System.Drawing; + +namespace OfficeOpenXml.Export.PdfExport.Layout +{ + 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 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; + } + + 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(); + 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; + double x = contentStartX; + 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(info.X, info.Y, info.Width, info.Height); + SetFill(dictionaries, cellStyle, map.Text, fill); + 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( + 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 + 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; + 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); + } + } + drawnMergedCells.Add(key); + } + } + else + { + //Fill + 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); + //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.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) + { + var addr = map.MergedAddress; + 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); + } + 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; + } + + 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; + 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 == (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 == (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 == (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 == (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)); + 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(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) + { + 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(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++) + { + 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 = (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)hAlign; + cell.ContentAligmnet.VerticalAlignment = (EPPlus.Export.Pdf.Enums.ExcelVerticalAlignment)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(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); + 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.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); + SetBorderStyle(map.CellStyle, border); + 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(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); + } + } + } + + 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); + } + 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); + } + } + 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); + + 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 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); + // 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; } + } + 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]; } + 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 ?? (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 == (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Right) + BandMarkLeft(range, row, ci, fromCol, colX, spill, ri, blocked); + else if (hal == (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)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 ?? (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++) + { + 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 ?? (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--) + { + 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 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) + { + 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) + { + 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, + }; + } + 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 ?? (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; + 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 ?? (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; + 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/PdfCatalog.cs b/src/EPPlus/Export/PdfExport/PdfCatalog.cs new file mode 100644 index 0000000000..5b8cc13199 --- /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 EPPlus.Export.Pdf; +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; +using OfficeOpenXml.Export.PdfExport.TextMapping; +using OfficeOpenXml.Export.PdfExport.TextShaping; +using System.Collections.Generic; +using System.Diagnostics; +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, 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; + 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(); + + //Write Pdf Document + ExcelPdf excelPdf = new ExcelPdf(); + excelPdf.CreatePdf(pageSettings, _dictionaries, layout, fileName); + 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) + { + ExcelAddressBase repeatRows = new ExcelAddressBase(pageSettings.RowsToRepeatAtTop); + pdfSheet.PrintTitleRowFrom = repeatRows._fromRow; + pdfSheet.PrintTitleRowTo = repeatRows._toRow; + } + if (pageSettings.ColumnsToRepeatAtLeft != null) + { + 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 --- + 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/PdfCalculateRowHeight.cs b/src/EPPlus/Export/PdfExport/RowResize/PdfCalculateRowHeight.cs similarity index 74% rename from src/EPPlus.Export.Pdf/PdfCatalog/PdfCalculateRowHeight.cs rename to src/EPPlus/Export/PdfExport/RowResize/PdfCalculateRowHeight.cs index 957426be87..0d1421fd8e 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCalculateRowHeight.cs +++ b/src/EPPlus/Export/PdfExport/RowResize/PdfCalculateRowHeight.cs @@ -1,12 +1,18 @@ -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; +/************************************************************************************************* + 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/ -namespace EPPlus.Export.Pdf.PdfCatalog + 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 { @@ -18,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; @@ -27,16 +32,12 @@ 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; @@ -45,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) @@ -71,7 +69,6 @@ public static void ResizeRange(ref PdfRange range) if (required > maxRequired) maxRequired = required; } - if (hasWrappedCell) { rowHeight.Height = maxRequired; @@ -80,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.Pdf/PdfCatalog/PdfCommentsAndNotes.cs b/src/EPPlus/Export/PdfExport/TextMapping/PdfCommentsAndNotes.cs similarity index 84% rename from src/EPPlus.Export.Pdf/PdfCatalog/PdfCommentsAndNotes.cs rename to src/EPPlus/Export/PdfExport/TextMapping/PdfCommentsAndNotes.cs index 4568d1f0e6..defb31548d 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCommentsAndNotes.cs +++ b/src/EPPlus/Export/PdfExport/TextMapping/PdfCommentsAndNotes.cs @@ -1,18 +1,21 @@ -using EPPlus.Export.Pdf.PdfLayout; -using EPPlus.Export.Pdf.PdfResources; -using EPPlus.Export.Pdf.PdfSettings; -using OfficeOpenXml; -using OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup; +/************************************************************************************************* + 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; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Xml.Linq; -namespace EPPlus.Export.Pdf.PdfCatalog +namespace OfficeOpenXml.Export.PdfExport { internal class PdfCommentsAndNotes { @@ -24,6 +27,7 @@ public PdfCommentsAndNotes(ExcelComment comment) { Comment = comment; } + public PdfCommentsAndNotes(ExcelThreadedCommentThread tComment) { ThreadedComment = tComment; diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfHeaderFooterCollection.cs b/src/EPPlus/Export/PdfExport/TextMapping/PdfHeaderFooterCollection.cs similarity index 89% rename from src/EPPlus.Export.Pdf/PdfCatalog/PdfHeaderFooterCollection.cs rename to src/EPPlus/Export/PdfExport/TextMapping/PdfHeaderFooterCollection.cs index d21d13049b..0f6e9be031 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfHeaderFooterCollection.cs +++ b/src/EPPlus/Export/PdfExport/TextMapping/PdfHeaderFooterCollection.cs @@ -1,10 +1,23 @@ -using EPPlus.Export.Pdf.PdfResources; -using EPPlus.Export.Pdf.PdfSettings; -using OfficeOpenXml; +/************************************************************************************************* + 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.Layout; +using EPPlus.Export.Pdf.Settings; +using EPPlus.Export.Pdf.Resources; +using OfficeOpenXml.Export.PdfExport.Data; using System.Collections.Generic; using System.Linq; -namespace EPPlus.Export.Pdf.PdfCatalog +namespace OfficeOpenXml.Export.PdfExport.TextMapping { internal class PdfHeaderFooterCollection { diff --git a/src/EPPlus/Export/PdfExport/TextMapping/PdfTextMap.cs b/src/EPPlus/Export/PdfExport/TextMapping/PdfTextMap.cs new file mode 100644 index 0000000000..e09939f34b --- /dev/null +++ b/src/EPPlus/Export/PdfExport/TextMapping/PdfTextMap.cs @@ -0,0 +1,1031 @@ +/************************************************************************************************* + 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.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.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) + { + 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 = 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) + { + 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 = (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 = (EPPlus.Export.Pdf.Enums.ExcelReadingOrder)cell.Style.ReadingOrder; + return contentAlignment; + } + + 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; + } + + internal static PdfCellAlignmentData GetAlignmentData(PdfHeaderFooter headerFooter) + { + var contentAlignment = new PdfCellAlignmentData(); + switch (headerFooter.Alignment) + { + case HeaderFooterAlignment.Left: + contentAlignment.HorizontalAlignment = (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Left; + break; + case HeaderFooterAlignment.Center: + contentAlignment.HorizontalAlignment = (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Center; + break; + case HeaderFooterAlignment.Right: + contentAlignment.HorizontalAlignment = (EPPlus.Export.Pdf.Enums.ExcelHorizontalAlignment)ExcelHorizontalAlignment.Right; + break; + } + return contentAlignment; + } + + 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(); + 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); + 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. + internal 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; + } + + internal 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; + } + internal 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; + } + internal 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; + } + internal 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 new file mode 100644 index 0000000000..f179868d6f --- /dev/null +++ b/src/EPPlus/Export/PdfExport/TextShaping/PdfTextShaper.cs @@ -0,0 +1,176 @@ +/************************************************************************************************* + 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.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; + } + + // 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; + } + } +} \ No newline at end of file diff --git a/src/EPPlus/Properties/AssemblyInfo.cs b/src/EPPlus/Properties/AssemblyInfo.cs index 4d463e8c41..7d2f621091 100644 --- a/src/EPPlus/Properties/AssemblyInfo.cs +++ b/src/EPPlus/Properties/AssemblyInfo.cs @@ -19,9 +19,9 @@ 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.Export.Pdf, PublicKey=00240000048000009400000006020000002400005253413100040000010001005dd165010a983c48dcdee4d91434cd53fbc1651ba55263e9be73f5fc07ec63a666c90f3d9de7356c9214f461bae351a0375c5d75db0a9b45800bd347b81559c5a0bc84313de08d5d278b693e7da96678aa17d00fc8b825e37c0e28142c64cf6e7d174ebd11dc04142eb2ad438ca137f324ad9757bf02279a5f7a9d7479afbcb0")] [assembly: InternalsVisibleTo("EPPlus.Graphics, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e9b33e79ac9cbb1ca1e0df0aa47d956404607d99cb28a5982f936de662eb2fd3d3459c5b625fe4791606113ff89ee8b915e1446d348be8c6b5106fd3ffde14cf337587920e1c76bebc3bfd95d7525f3a5bc384fb19f3c06306b8cd5ffbe4284c36d8285f16cc50e6b408a2e197229108ce683d865a946b92a324cfba8dbf57c2")] [assembly: Guid("9dd43b8d-c4fe-4a8b-ad6e-47ef83bbbb01")] diff --git a/src/EPPlus/Style/ExcelColor.cs b/src/EPPlus/Style/ExcelColor.cs index ccd89184db..670610f295 100644 --- a/src/EPPlus/Style/ExcelColor.cs +++ b/src/EPPlus/Style/ExcelColor.cs @@ -316,5 +316,25 @@ internal bool IsEmpty() return false; } + + /// + /// 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; + } } } 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) {