From 58d80a975eb70aa018125a90d245789c73fad4eb Mon Sep 17 00:00:00 2001 From: swmal <897655+swmal@users.noreply.github.com> Date: Thu, 28 May 2026 16:56:13 +0200 Subject: [PATCH 1/2] Perf fixes --- src/EPPlus.Export.Pdf/AssemblyInfo.cs | 4 +- .../PdfCatalog/PdfCatalog.cs | 46 ++--- .../PdfCatalog/PdfTextMap.cs | 10 +- .../PdfCatalog/PdfTextShaper.cs | 160 +++++++++++++++--- .../PdfObjects/PdfContentStream.cs | 4 + .../PdfResources/PdfDictionaries.cs | 6 +- 6 files changed, 182 insertions(+), 48 deletions(-) diff --git a/src/EPPlus.Export.Pdf/AssemblyInfo.cs b/src/EPPlus.Export.Pdf/AssemblyInfo.cs index d79820d85e..adeb5b8146 100644 --- a/src/EPPlus.Export.Pdf/AssemblyInfo.cs +++ b/src/EPPlus.Export.Pdf/AssemblyInfo.cs @@ -28,4 +28,6 @@ Date Author Change // The following GUID is for the ID of the typelib if this project is exposed to COM. [assembly: Guid("60855b7d-19da-4dfa-90b2-78231d227d65")] -[assembly: InternalsVisibleTo("EPPlus.Export.Pdf.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dd3a3466a88cbf5d374fe992cec433c48022414fe96608933e8e36782001213dd31bc454dc6f962a54a3a76cfb9e03a32cd4c658ecd49d1a98709971a080ab92d5c5b65346155f8d6422db4ffbf662f78913996a9a8b78ee11ff3cda7e585208cd4468fb3201f15bbb1dfc45c120703c9d6ad495bb9de66893ae5ab5ac8f40dc")] \ No newline at end of file +[assembly: InternalsVisibleTo("EPPlus.Export.Pdf.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dd3a3466a88cbf5d374fe992cec433c48022414fe96608933e8e36782001213dd31bc454dc6f962a54a3a76cfb9e03a32cd4c658ecd49d1a98709971a080ab92d5c5b65346155f8d6422db4ffbf662f78913996a9a8b78ee11ff3cda7e585208cd4468fb3201f15bbb1dfc45c120703c9d6ad495bb9de66893ae5ab5ac8f40dc")] +// +[assembly: InternalsVisibleTo("EPPlus.PdfExportPerformance, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f5398d389a44aacee243c9e73152fae942e9ede958ebd4b06651a749e6b2c0395a66bc0fc133dce1cf83cd667fe8f761b433063e603968f12b0b17e474233410f8fe933853d2344e89526414ef65ed7236a7a5c012e30275ec30d7fb665dbe1bd3435439bec55f431b9e69e943294fd474942d6bcb431c94ef653cbeee5b8a9e")] \ No newline at end of file diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs index 052abc1a73..632e097070 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs @@ -8,7 +8,7 @@ namespace EPPlus.Export.Pdf.PdfCatalog { - internal class PdfCatalog + public class PdfCatalog { internal PdfDictionaries Dictionaries = new PdfDictionaries(); private bool AddTextForHeadings = true; @@ -61,7 +61,7 @@ public PdfCatalog(PdfPageSettings pageSettings, ExcelWorksheet worksheet, string sw.Start(); //Auto-Fit Rows - PdfCalculateRowHeight.ResizeRowHeights(pdfSheet); //call a method that does this so we can use it for comments sheet aswell! + PdfCalculateRowHeight.ResizeRowHeights(pdfSheet); sw.Stop(); var AutoFitRowTime = sw.ElapsedMilliseconds; sw.Reset(); @@ -88,7 +88,7 @@ public PdfCatalog(PdfPageSettings pageSettings, ExcelRangeBase range) ShapeTextInPdfWorksheet(pageSettings, pdfSheet); } - public PdfCellCollection GetCellCollectionFromRange(PdfPageSettings pageSettings, ExcelRangeBase range) + internal PdfCellCollection GetCellCollectionFromRange(PdfPageSettings pageSettings, ExcelRangeBase range) { PdfWorksheet pdfSheet = GetPdfWorksheet(pageSettings, range); ShapeTextInPdfWorksheet(pageSettings, pdfSheet); @@ -99,13 +99,28 @@ public PdfCellCollection GetCellCollectionFromRange(PdfPageSettings pageSettings //Create Layout Methods private Transform GetLayout(PdfPageSettings pageSettings, PdfWorksheet pdfSheet) { - PdfWorksheet[] pdfSheets = new PdfWorksheet[1]{ pdfSheet }; + PdfWorksheet[] pdfSheets = new PdfWorksheet[1] { pdfSheet }; var Layout = PdfLayout.GetLayout(pageSettings, Dictionaries, pdfSheets); return Layout; } //Shape Text Methods - private void ShapeTextInPdfWorksheet(PdfPageSettings pageSettings, PdfWorksheet pdfSheet) + internal void ShapeTextInPdfWorksheet(PdfPageSettings pageSettings, PdfWorksheet pdfSheet) + { + // Pass 1: collect text per font + IterateCells(pdfSheet, cell => PdfTextShaper.CollectText(Dictionaries, cell)); + + // Pass 2: build one provider per font + foreach (var kvp in Dictionaries.Fonts) + { + Dictionaries.ShapedProviders[kvp.Key] = kvp.Value.fontSubsetManager.CreateSubsettedProvider(); + } + + // Pass 3: shape text using the pre-built providers + IterateCells(pdfSheet, cell => PdfTextShaper.ShapeText(pageSettings, Dictionaries, cell)); + } + + private void IterateCells(PdfWorksheet pdfSheet, System.Action action) { foreach (var range in pdfSheet.Ranges) { @@ -113,29 +128,21 @@ private void ShapeTextInPdfWorksheet(PdfPageSettings pageSettings, PdfWorksheet { for (int j = range.Map.FromColumn; j <= range.Map.ToColumn; j++) { - var cell = range.Map[i, j]; - PdfTextShaper.LayoutAndShapeText(pageSettings, Dictionaries, cell); + action(range.Map[i, j]); } } } + if (pdfSheet.CommentsAndNotes.Map != null) { for (int i = pdfSheet.CommentsAndNotes.Map.FromRow; i <= pdfSheet.CommentsAndNotes.Map.ToRow; i++) { for (int j = pdfSheet.CommentsAndNotes.Map.FromColumn; j <= pdfSheet.CommentsAndNotes.Map.ToColumn; j++) { - var cell = pdfSheet.CommentsAndNotes.Map[i, j]; - PdfTextShaper.LayoutAndShapeText(pageSettings, Dictionaries, cell); + action(pdfSheet.CommentsAndNotes.Map[i, j]); } } } - //if (pdfSheet.HeaderFooters != null) - //{ - // foreach (var hf in pdfSheet.HeaderFooters.PdfHeaderFooterEntries) - // { - // PdfTextShaper.LayoutAndShapeText(pageSettings, Dictionaries, hf.Content); - // } - //} } //Collect Text Methods @@ -149,14 +156,13 @@ private PdfWorksheet[] GetPdfWorksheets(PdfPageSettings pageSettings, ExcelWorks return pdfSheets; } - private PdfWorksheet GetPdfWorksheet(PdfPageSettings pageSettings, ExcelWorksheet worksheet) + internal PdfWorksheet GetPdfWorksheet(PdfPageSettings pageSettings, ExcelWorksheet worksheet) { PdfWorksheet pdfSheet = new PdfWorksheet(); pdfSheet.Ranges = new List(); pdfSheet.Worksheet = worksheet; pdfSheet.Ranges = GetRanges(pdfSheet.Worksheet); - //pdfSheet.HeaderFooters = new PdfHeaderFooterCollection(pageSettings, Dictionaries, pdfSheet, pdfSheet.Worksheet.HeaderFooter); - if(pageSettings.ShowHeadings && AddTextForHeadings) Dictionaries.AddFont(pageSettings, pdfSheet.NormalStyle.Style.Font.Name, pdfSheet.GetSubFamilyFromNormalStyle, "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); + if (pageSettings.ShowHeadings && AddTextForHeadings) Dictionaries.AddFont(pageSettings, pdfSheet.NormalStyle.Style.Font.Name, pdfSheet.GetSubFamilyFromNormalStyle, "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); AddTextForHeadings = false; GetMaps(pageSettings, pdfSheet, pdfSheet.Ranges); GetHeaderFooter(pageSettings, pdfSheet); @@ -232,4 +238,4 @@ private void GetCommentsAndNotes(PdfPageSettings pageSettings, PdfWorksheet pdfS } } } -} +} \ No newline at end of file diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs index e21a796fe6..6d2d6a4398 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextMap.cs @@ -81,7 +81,11 @@ public static PdfCellCollection SetTextMap(PdfPageSettings pageSettings, PdfDict if (!string.IsNullOrEmpty(cell.Text)) { tempMap.Text = cell.Text; - if (!cell.IsRichText) cell._rtc = new ExcelRichTextCollection(cell.Text, cell); + if(row == 1 && col == 1) + { + Console.WriteLine($"Cell.RichText.Count: {cell.RichText.Count}"); + } + if (cell._rtc == null) cell._rtc = new ExcelRichTextCollection(cell.Text, cell); //tempMap.TextFormats = GetTextFormats(pageSettings, dictionaries, cell._rtc, cellStyle); tempMap.TextFragments = GetTextFragments(pageSettings, dictionaries, cell, cell._rtc, cellStyle); } @@ -560,6 +564,10 @@ private static List GetTextFragments(PdfPageSettings pageSettings, for (int i = 0; i < RichTextCollection.Count; i++) { var rt = RichTextCollection[i]; + if (cell.Address == "A1") + { + Console.WriteLine($"[GetTextFragments] A1 rt[{i}]: Text='{rt.Text}' Bold={rt.Bold} FontName='{rt.FontName}'"); + } var textFrag = new TextFragment(); textFrag.Font = new MeasurementFont(); textFrag.Text = text == null ? rt.Text : text; diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextShaper.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextShaper.cs index 8b94e51e28..b275240c5c 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextShaper.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfTextShaper.cs @@ -17,17 +17,134 @@ internal static class PdfTextShaper private static Dictionary shaperCache = new Dictionary(); private static Dictionary layoutEngineCache = new Dictionary(); - public static void LayoutAndShapeText(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfCell Cell) + // Pass 1: collect text per font so FontSubsetManager can build subsets once + public static void CollectText(PdfDictionaries dictionaries, PdfCell cell) + { + if (cell == null || cell.TextFragments == null) return; + + if (cell.TextFragments.Count > 1 || (cell.TextFragments.Count == 1 && cell.TextFragments[0].Text.Contains("should"))) + { + Console.WriteLine($"[CollectText] cell={cell.Name} fragments={cell.TextFragments.Count}"); + for (int i = 0; i < cell.TextFragments.Count; i++) + { + var tf = cell.TextFragments[i]; + Console.WriteLine($" [{i}] Text='{tf.Text}' FullFontName='{tf.FullFontName}' Bold={tf.RichTextOptions.Bold}"); + } + } + + for (int i = 0; i < cell.TextFragments.Count; i++) + { + var tf = cell.TextFragments[i]; + if (!dictionaries.Fonts.ContainsKey(tf.FullFontName)) continue; + dictionaries.Fonts[tf.FullFontName].fontSubsetManager.AddText(tf.Text); + } + } + + // Pass 2: shape text using already-built providers from PdfDictionaries.ShapedProviders + public static void ShapeText(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfCell cell) { var totalTextLength = 0d; var maxLineHeight = 0d; - if (Cell.TextFragments == null) return; - Cell.ShapedTexts = new List(); - for (int i = 0; i < Cell.TextFragments.Count; i++) + if (cell == null || cell.TextFragments == null) return; + + if (cell.Name == "A1" || (cell.TextFragments.Count > 0 && cell.TextFragments[0].Text.StartsWith("This text"))) { - var tf = Cell.TextFragments[i]; - Cell.ShapedTexts.Add(new PdfShapedText()); - var st = Cell.ShapedTexts[i]; + Console.WriteLine($"[ShapeText START] cell={cell.Name} fragments={cell.TextFragments.Count}"); + for (int i = 0; i < cell.TextFragments.Count; i++) + { + Console.WriteLine($" [{i}] Text='{cell.TextFragments[i].Text}' FullFontName='{cell.TextFragments[i].FullFontName}'"); + } + } + + cell.ShapedTexts = new List(); + + for (int i = 0; i < cell.TextFragments.Count; i++) + { + var tf = cell.TextFragments[i]; + cell.ShapedTexts.Add(new PdfShapedText()); + var st = cell.ShapedTexts[i]; + + if (!dictionaries.ShapedProviders.TryGetValue(tf.FullFontName, out var provider)) + { + continue; + } + st.FontProvider = provider; + + if (!shaperCache.TryGetValue(st.FontProvider, out var shaper)) + { + shaper = new TextShaper(st.FontProvider); + shaperCache[st.FontProvider] = shaper; + } + + if (!layoutEngineCache.TryGetValue(st.FontProvider, out var layoutEngine)) + { + layoutEngine = new TextLayoutEngine(shaper); + layoutEngineCache[st.FontProvider] = layoutEngine; + } + + var options = ShapingOptions.Default; + options.ApplyPositioning = true; + options.ApplySubstitutions = true; + + var shaped = shaper.Shape(tf.Text, options); + var usedFonts = shaper.GetUsedFonts().ToList(); + //Console.WriteLine($"[ShapeText] tf.Text='{tf.Text}' FullFontName='{tf.FullFontName}' usedFonts={string.Join(",", usedFonts.Select(f => f.FullName).ToArray())}"); + var fontIdMap = new Dictionary(); + + for (byte fontId = 0; fontId < usedFonts.Count; fontId++) + { + var font = usedFonts[fontId]; + if (!dictionaries.Fonts.ContainsKey(font.FullName)) + { + int label = dictionaries.Fonts.Count > 0 + ? dictionaries.Fonts.Last().Value.labelNumber + 1 + : 1; + var fontResource = new PdfFontResource(font.FullName, font.NameTable.GetSubfamilyEnum(), label, pageSettings); + fontResource.fontData = font; + dictionaries.Fonts.Add(font.FullName, fontResource); + } + fontIdMap[fontId] = dictionaries.Fonts[font.FullName].Label; + } + + cell.TextLayoutEngine = layoutEngine; + st.ShapedText = shaped; + + totalTextLength += st.ShapedText.GetWidthInPoints((float)tf.Font.Size); + maxLineHeight = Math.Max(st.ShapedText.GetLineHeightInPoints((float)tf.Font.Size), maxLineHeight); + + st.FontIdMap = fontIdMap; + st.UsedFonts = usedFonts; + cell.TextFragments[i] = tf; + cell.ShapedTexts[i] = st; + if (cell.Name == "A1" || (cell.TextFragments.Count > 0 && cell.TextFragments[0].Text.StartsWith("This text"))) + { + Console.WriteLine($"[ShapeText END loop {i}] tf.Text='{tf.Text}' provider={(st.FontProvider != null ? "OK" : "NULL")} usedFonts={string.Join(",", st.UsedFonts.Select(f => f.FullName).ToArray())}"); + } + } + + if (cell.TextLayoutEngine != null) + { + double wrapWidth = (cell.Merged && cell.Main == null) ? cell.Width : cell.ColumnWidth; + cell.TextLines = cell.ContentAligmnet.WrapText + ? cell.TextLayoutEngine.WrapRichTextLineCollection(cell.TextFragments, wrapWidth) + : cell.TextLayoutEngine.WrapRichTextLineCollection(cell.TextFragments, double.MaxValue); + } + + cell.TotalTextLength = totalTextLength; + } + + // Kept for backwards compatibility - not used by ShapeTextInPdfWorksheet anymore + public static void LayoutAndShapeText(PdfPageSettings pageSettings, PdfDictionaries dictionaries, PdfCell cell) + { + var totalTextLength = 0d; + var maxLineHeight = 0d; + if (cell.TextFragments == null) return; + cell.ShapedTexts = new List(); + for (int i = 0; i < cell.TextFragments.Count; i++) + { + var tf = cell.TextFragments[i]; + cell.ShapedTexts.Add(new PdfShapedText()); + var st = cell.ShapedTexts[i]; st.FontProvider = dictionaries.Fonts[tf.FullFontName].fontSubsetManager.CreateSubsettedProvider(); if (!shaperCache.TryGetValue(st.FontProvider, out var shaper)) @@ -55,7 +172,6 @@ public static void LayoutAndShapeText(PdfPageSettings pageSettings, PdfDictionar for (byte fontId = 0; fontId < usedFonts.Count; fontId++) { var font = usedFonts[fontId]; - if (!dictionaries.Fonts.ContainsKey(font.FullName)) { int label = 1; @@ -69,38 +185,35 @@ public static void LayoutAndShapeText(PdfPageSettings pageSettings, PdfDictionar } fontIdMap[fontId] = dictionaries.Fonts[font.FullName].Label; } - Cell.TextLayoutEngine = layoutEngine; + cell.TextLayoutEngine = layoutEngine; st.ShapedText = shaped; - var textWdith = st.ShapedText.GetWidthInPoints((float)tf.Font.Size); + var textWidth = st.ShapedText.GetWidthInPoints((float)tf.Font.Size); var textHeight = st.ShapedText.GetLineHeightInPoints((float)tf.Font.Size); - //tf.TextLength = textWdith; - //tf.TextHeight = textHeight; - totalTextLength += textWdith; + totalTextLength += textWidth; maxLineHeight = Math.Max(textHeight, maxLineHeight); st.FontIdMap = fontIdMap; st.UsedFonts = usedFonts; - Cell.TextFragments[i] = tf; - Cell.ShapedTexts[i] = st; + cell.TextFragments[i] = tf; + cell.ShapedTexts[i] = st; } - if (Cell.TextLayoutEngine != null) + if (cell.TextLayoutEngine != null) { - if (Cell.ContentAligmnet.WrapText) + if (cell.ContentAligmnet.WrapText) { - double wrapWidth = (Cell.Merged && Cell.Main == null) ? Cell.Width : Cell.ColumnWidth; - Cell.TextLines = Cell.TextLayoutEngine.WrapRichTextLineCollection(Cell.TextFragments, wrapWidth); + double wrapWidth = (cell.Merged && cell.Main == null) ? cell.Width : cell.ColumnWidth; + cell.TextLines = cell.TextLayoutEngine.WrapRichTextLineCollection(cell.TextFragments, wrapWidth); } else { - Cell.TextLines = Cell.TextLayoutEngine.WrapRichTextLineCollection(Cell.TextFragments, double.MaxValue); + cell.TextLines = cell.TextLayoutEngine.WrapRichTextLineCollection(cell.TextFragments, double.MaxValue); } } - Cell.TotalTextLength = totalTextLength; + cell.TotalTextLength = totalTextLength; } private static List GetTextFragments(List textFragments) { var fragments = new List(textFragments.Count); - foreach (var tf in textFragments) { var fragment = new TextFragment @@ -115,7 +228,6 @@ private static List GetTextFragments(List textFragme }; fragments.Add(fragment); } - return fragments; } @@ -129,4 +241,4 @@ private static MeasurementFontStyles GetMeasurementFontStyle(TextFragment tf) return style == 0 ? MeasurementFontStyles.Regular : (MeasurementFontStyles)style; } } -} +} \ No newline at end of file diff --git a/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs b/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs index dbdac41964..5f3e035b7f 100644 --- a/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs +++ b/src/EPPlus.Export.Pdf/PdfObjects/PdfContentStream.cs @@ -194,6 +194,10 @@ public void AddText(PdfCellContentLayout cell, Vector2 position, double textRota var textLength = shapedText.ShapedText.GetWidthInPoints((float)textFormat.OriginalTextFragment.Font.Size); var color = textFormat.OriginalTextFragment.RichTextOptions.FontColor; var fontResrouce = GetFontResource(dictionaries, pageSettings, textFormat.OriginalTextFragment.Font.FontFamily, OpenTypeFonts.GetFontSubFamily(textFormat.OriginalTextFragment.Font.Style), textFormat.OriginalTextFragment.Font.Size); + if (cell.Name == "A1") + { + Console.WriteLine($"[Render A1] fragmentText='{textFormat.OriginalTextFragment.Text}' FontFamily='{textFormat.OriginalTextFragment.Font.FontFamily}' Style={textFormat.OriginalTextFragment.Font.Style} shapedTextIndex={shapedTextIndex} fontResource.Label={fontResrouce.Label}"); + } double size = textFormat.OriginalTextFragment.Font.Size; double scale = textFormat.OriginalTextFragment.Font.Size / fontResrouce.fontData.HeadTable.UnitsPerEm; Matrix3x3 textMatrix = new Matrix3x3(System.Math.Cos(rotation), System.Math.Sin(rotation), -System.Math.Sin(rotation), System.Math.Cos(rotation), position.X + lineOffsetX, position.Y + advanceY); diff --git a/src/EPPlus.Export.Pdf/PdfResources/PdfDictionaries.cs b/src/EPPlus.Export.Pdf/PdfResources/PdfDictionaries.cs index ac7e2453ab..859e3d8ba0 100644 --- a/src/EPPlus.Export.Pdf/PdfResources/PdfDictionaries.cs +++ b/src/EPPlus.Export.Pdf/PdfResources/PdfDictionaries.cs @@ -12,17 +12,19 @@ Date Author Change *************************************************************************************************/ using EPPlus.Export.Pdf.PdfLayout; using EPPlus.Export.Pdf.PdfSettings; +using EPPlus.Fonts.OpenType; using OfficeOpenXml.Interfaces.Fonts; using System.Collections.Generic; using System.Linq; namespace EPPlus.Export.Pdf.PdfResources { - internal class PdfDictionaries + public class PdfDictionaries { internal readonly Dictionary Fonts = new Dictionary(); internal readonly Dictionary Patterns = new Dictionary(); internal readonly Dictionary Shadings = new Dictionary(); + internal Dictionary ShapedProviders = new Dictionary(); public void AddFont(PdfPageSettings pageSettings, string FontName, FontSubFamily SubFamily, string Text) { @@ -39,7 +41,7 @@ public void AddFont(PdfPageSettings pageSettings, string FontName, FontSubFamily manger.AddText(Text); } - public PdfFontResource GetFont(string FontName) + internal PdfFontResource GetFont(string FontName) { if (!Fonts.ContainsKey(FontName)) { From d9d855f18e8ec3b34d379b820731642769026b72 Mon Sep 17 00:00:00 2001 From: swmal <897655+swmal@users.noreply.github.com> Date: Tue, 2 Jun 2026 16:03:56 +0200 Subject: [PATCH 2/2] Fix PDF rich text rendering; remove source workbook mutation in export --- src/EPPlus.Export.Pdf/ExcelPdf.cs | 244 +++++++++--------- .../PdfCatalog/PdfCatalog.cs | 122 +++++---- .../PdfCatalog/PdfTextMap.cs | 204 +++++++++------ .../PdfCatalog/PdfTextShaper.cs | 23 -- .../PdfObjects/PdfContentStream.cs | 31 +-- .../PdfResources/PdfFontResource.cs | 4 +- src/EPPlus/Style/ExcelColor.cs | 20 ++ 7 files changed, 359 insertions(+), 289 deletions(-) diff --git a/src/EPPlus.Export.Pdf/ExcelPdf.cs b/src/EPPlus.Export.Pdf/ExcelPdf.cs index 65f34e8a1b..3ce420925c 100644 --- a/src/EPPlus.Export.Pdf/ExcelPdf.cs +++ b/src/EPPlus.Export.Pdf/ExcelPdf.cs @@ -34,10 +34,12 @@ public class ExcelPdf { internal List _workheets = new List(); internal ExcelRangeBase _range; - private PdfPageSettings PageSettings; - internal List Document = new List(); + private PdfPageSettings _pageSettings; + internal List _document = new List(); internal string header = "%PDF-1.7\n"; - internal PdfDictionaries Dictionaries = new PdfDictionaries(); + internal PdfDictionaries _dictionaries = new PdfDictionaries(); + private string _debugString; + public ExcelPdf() { } @@ -50,8 +52,8 @@ public ExcelPdf() public ExcelPdf(ExcelWorksheet worksheet, PdfPageSettings pageSettings = null) { _workheets.Add(worksheet); - PageSettings = pageSettings == null ? new PdfPageSettings() : pageSettings; - PageSettings.defaultFontName = worksheet.Workbook.ThemeManager.CurrentTheme.FontScheme.MinorFont[0].Typeface; + _pageSettings = pageSettings == null ? new PdfPageSettings() : pageSettings; + _pageSettings.defaultFontName = worksheet.Workbook.ThemeManager.CurrentTheme.FontScheme.MinorFont[0].Typeface; } /// @@ -96,9 +98,9 @@ internal string GetPatternLabel(PdfCellLayout layout) if ((layout.CellFillData.PatternStyle != ExcelFillStyle.Solid && layout.CellFillData.PatternStyle != ExcelFillStyle.None) || layout.CellFillData.GradientFillData != null) { var patternName = layout.CellFillData.id; - if (Dictionaries.Patterns.ContainsKey(patternName)) + if (_dictionaries.Patterns.ContainsKey(patternName)) { - return Dictionaries.Patterns[patternName].Label; + return _dictionaries.Patterns[patternName].Label; } } return null; @@ -107,28 +109,28 @@ internal string GetPatternLabel(PdfCellLayout layout) //Add Fonts //Need to update this method a bit. We should check for all default fonts and not only courier new? Also need to check if we are allowed to embedd the font. internal void AddFontData() { - if (PageSettings.EmbeddFonts) + if (_pageSettings.EmbeddFonts) { - foreach (var font in Dictionaries.Fonts) + foreach (var font in _dictionaries.Fonts) { //font.Value.CreateGidsAndCharMaps(); - var CidSet = font.Value.GetCidSet(Document.Count + 1); - if (CidSet != null) Document.Add(CidSet); - Document.Add(font.Value.GetEmbeddedFontStreamObject(Document.Count + 1)); - Document.Add(font.Value.GetFontDescriptorObject(Document.Count + 1)); - Document.Add(font.Value.GetCIDFontObject(Document.Count + 1)); - Document.Add(font.Value.GetUnicodeCmapObject(Document.Count + 1)); - Document.Add(font.Value.GetType0FontDictObject(Document.Count + 1)); - font.Value.GetFontObject(Document.Count); + var cidSet = font.Value.GetCidSet(_document.Count + 1); + if (cidSet != null) _document.Add(cidSet); + _document.Add(font.Value.GetEmbeddedFontStreamObject(_document.Count + 1)); + _document.Add(font.Value.GetFontDescriptorObject(_document.Count + 1)); + _document.Add(font.Value.GetCIDFontObject(_document.Count + 1)); + _document.Add(font.Value.GetUnicodeCmapObject(_document.Count + 1)); + _document.Add(font.Value.GetType0FontDictObject(_document.Count + 1)); + font.Value.GetFontObject(_document.Count); } } else { - foreach (var font in Dictionaries.Fonts) + foreach (var font in _dictionaries.Fonts) { - Document.Add(font.Value.GetFontDescriptorObject(Document.Count + 1)); - Document.Add(font.Value.GetWidthsObject(Document.Count + 1)); - Document.Add(font.Value.GetFontObject(Document.Count + 1)); + _document.Add(font.Value.GetFontDescriptorObject(_document.Count + 1)); + _document.Add(font.Value.GetWidthsObject(_document.Count + 1)); + _document.Add(font.Value.GetFontObject(_document.Count + 1)); } } } @@ -136,47 +138,47 @@ internal void AddFontData() //Add Patterns internal void AddPatternData() { - foreach (var pattern in Dictionaries.Patterns) + foreach (var pattern in _dictionaries.Patterns) { - Document.Add(pattern.Value.GetPatternObject(Document.Count + 1)); + _document.Add(pattern.Value.GetPatternObject(_document.Count + 1)); } } //Add Shadings and accompanying pattern - internal void AddShadingsData(PdfDictionaries dictionaries) + internal void AddShadingsData() { - foreach (var shading in Dictionaries.Shadings) + foreach (var shading in _dictionaries.Shadings) { - Document.Add(shading.Value.GetShadingObject(Document.Count + 1)); - Document.Add(shading.Value.GetShadingPatternObject(Document.Count + 1, Document.Count)); - int label = dictionaries.Patterns.Last().Value.labelNumber + 1; + _document.Add(shading.Value.GetShadingObject(_document.Count + 1)); + _document.Add(shading.Value.GetShadingPatternObject(_document.Count + 1, _document.Count)); + int label = _dictionaries.Patterns.Last().Value.labelNumber + 1; var pr = new PdfPatternResource(label, shading.Value.CellFillData); - pr.objectNumber = Document.Count; - dictionaries.Patterns.Add(shading.Value.CellFillData.id, pr); + pr.objectNumber = _document.Count; + _dictionaries.Patterns.Add(shading.Value.CellFillData.id, pr); } } //Create Page private PdfPage AddPage(int pagesObjectNumber, List contentObjectNumbers, PdfPageSettings settings) { - var page = new PdfPage(Document.Count + 1, pagesObjectNumber, contentObjectNumbers, settings.PageSize, Dictionaries); - Document.Add(page); + var page = new PdfPage(_document.Count + 1, pagesObjectNumber, contentObjectNumbers, settings.PageSize, _dictionaries); + _document.Add(page); return page; } //Create Pages private PdfPages AddPages() { - var pages = new PdfPages(Document.Count + 1, new List{}); - Document.Add(pages); + var pages = new PdfPages(_document.Count + 1, new List { }); + _document.Add(pages); return pages; } //Create Catalog private PdfObjects.PdfCatalog AddCatalog(int pagesObjectNumber) { - var catalog = new PdfObjects.PdfCatalog(Document.Count + 1, pagesObjectNumber); - Document.Add(catalog); + var catalog = new PdfObjects.PdfCatalog(_document.Count + 1, pagesObjectNumber); + _document.Add(catalog); return catalog; } @@ -186,12 +188,12 @@ private void AddContent(Transform pageLayout, PdfPage page) //var cells = pageLayout.ChildObjects.Where(t => t is PdfCellLayout || t is PdfCellContentLayout || t is PdfCellBorderLayout).GroupBy(t => t.Name); var cells = pageLayout.ChildObjects.Where(t => (t is PdfCellLayout || t is PdfCellContentLayout || t is PdfCellBorderLayout) && !(t is PdfCellContentLayout cc && cc.IsHeaderFooter)).GroupBy(t => t.Name); var headerFooterLayouts = pageLayout.ChildObjects.OfType().Where(t => t.IsHeaderFooter); - var contentStream = new PdfContentStream(Document.Count + 1); + var contentStream = new PdfContentStream(_document.Count + 1); contentStream.AddCommand($"% {pageLayout.Name} start"); //Add clipping rectangle around page content. contentStream.AddCommand("q"); contentStream.AddMarginClipping((PdfPageLayout)pageLayout); - if (PageSettings.ShowGridLines) + if (_pageSettings.ShowGridLines) { contentStream.AddInnerGridLines(pageLayout); } @@ -206,7 +208,7 @@ private void AddContent(Transform pageLayout, PdfPage page) contentStream.AddCellLayout(layout, GetPatternLabel(layout)); break; case PdfCellContentLayout contentLayout: - contentStream.AddCellContentLayout(contentLayout, Dictionaries, PageSettings); + contentStream.AddCellContentLayout(contentLayout, _dictionaries, _pageSettings); break; case PdfCellBorderLayout borderLayout: contentStream.AddBorderLayout(borderLayout); @@ -217,16 +219,16 @@ private void AddContent(Transform pageLayout, PdfPage page) //Close the clipping rectangle. contentStream.AddCommand("Q"); contentStream.AddCommand($"% Margin Clip End"); - if (PageSettings.ShowGridLines) + if (_pageSettings.ShowGridLines) { contentStream.AddOuterGridBorder(pageLayout); } //Add header and footer. foreach (var hf in headerFooterLayouts) { - contentStream.AddCellContentLayout(hf, Dictionaries, PageSettings); + contentStream.AddCellContentLayout(hf, _dictionaries, _pageSettings); } - Document.Add(contentStream); + _document.Add(contentStream); page.contentObjectNumbers.Add(contentStream.objectNumber); contentStream.AddCommand($"% {pageLayout.Name} end"); } @@ -238,23 +240,35 @@ private void AddHeaderFooter(PdfContentStream contentStream, Transform pageLayou foreach (var hf in headerFooter) { var headerFooterLayout = hf as PdfHeaderFooterLayout; - contentStream.AddCellContentLayout(headerFooterLayout, Dictionaries, PageSettings); + contentStream.AddCellContentLayout(headerFooterLayout, _dictionaries, _pageSettings); } } //Add Info private PdfInfoObject AddInfoObject(string workBookName = "") { - var info = new PdfInfoObject(Document.Count + 1, workBookName); - Document.Add(info); + var info = new PdfInfoObject(_document.Count + 1, workBookName); + _document.Add(info); return info; } - internal void CreatePdf(PdfPageSettings pageSettings, PdfDictionaries dictionaries, Transform layout, string fileName) { - PageSettings = pageSettings; - Dictionaries = dictionaries; + using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write)) + { + CreatePdf(pageSettings, dictionaries, layout, fs); + } + + if (_pageSettings.Debug && _pageSettings.PrintAsText) + { + WriteDebugText(fileName); + } + } + + internal void CreatePdf(PdfPageSettings pageSettings, PdfDictionaries dictionaries, Transform layout, Stream stream) + { + _pageSettings = pageSettings; + _dictionaries = dictionaries; var catalog = AddCatalog(2); //Create Pages @@ -265,64 +279,45 @@ internal void CreatePdf(PdfPageSettings pageSettings, PdfDictionaries dictionari //Create Patterns AddPatternData(); //Create Shadings - AddShadingsData(dictionaries); + AddShadingsData(); //Create Page and Content for (int i = 0; i < layout.ChildObjects.Count; i++) { var pageLayout = layout.ChildObjects[i]; - var page = AddPage(2, new List(), PageSettings); + var page = AddPage(2, new List(), _pageSettings); AddContent(pageLayout, page); pages.pageObjectNumbers.Add(page.objectNumber); } var info = AddInfoObject(); - string debugString = ""; - //write to pdf - PdfCrossRefTable crossRefTable = new PdfCrossRefTable(); - //start wring pdf binary - using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write)) + + WriteDocumentToStream(stream, catalog, info); + } + + /// + /// Create the pdf from the supplied worksheet. + /// + /// The file name + public void CreatePdf(string Filename) + { + using (var fs = new FileStream(Filename, FileMode.Create, FileAccess.Write)) { - using (var bw = new BinaryWriter(fs, Encoding.ASCII)) - { - //Write header - bw.Write(Encoding.ASCII.GetBytes(header)); - debugString += header; - //Write body - foreach (var pdfobj in Document) - { - crossRefTable.AddPosition(fs.Position); - pdfobj.ToPdfBytes(bw); - debugString += pdfobj.ToPdfString(); - } - //Write CrossReference - crossRefTable.Write(bw, fs.Position, Document.Count); - debugString += crossRefTable.WriteString(Document.Count); - // Write trailer - PdfTrailer.Write(bw, Document.Count, catalog.objectNumber, info.objectNumber, crossRefTable.StartPosition); - debugString += PdfTrailer.WriteString(Document.Count, catalog.objectNumber, info.objectNumber, crossRefTable.StartPosition); - } + CreatePdf(fs); } - //Write pdf as txt for debug. - if (PageSettings.Debug && PageSettings.PrintAsText) + + if (_pageSettings.Debug && _pageSettings.PrintAsText) { - using (var fs = new FileStream(fileName + ".txt", FileMode.Create, FileAccess.Write)) - { - using (var wr = new StreamWriter(fs)) - { - wr.Write(debugString); - } - } + WriteDebugText(Filename); } } - /// - /// Create the pdf from the supplied worksheet. + /// Create the pdf from the supplied worksheet and write it to a stream. /// - /// The file name - public void CreatePdf(string Filename) + /// The stream to write the pdf to. The stream will not be closed. + public void CreatePdf(Stream stream) { //Create Catalog - var catalogLayout = new PdfCatalogLayout(_workheets[0], PageSettings, Dictionaries); + var catalogLayout = new PdfCatalogLayout(_workheets[0], _pageSettings, _dictionaries); var catalog = AddCatalog(2); //Create Pages var pagesLayout = catalogLayout.ChildObjects[0]; @@ -332,53 +327,64 @@ public void CreatePdf(string Filename) //Create Patterns AddPatternData(); //Create Shadings - AddShadingsData(Dictionaries); + AddShadingsData(); //Create Page and Content for (int i = 0; i < pagesLayout.ChildObjects.Count; i++) { var pageLayout = pagesLayout.ChildObjects[i]; - var page = AddPage(2, new List(), PageSettings); + var page = AddPage(2, new List(), _pageSettings); AddContent(pageLayout, page); pages.pageObjectNumbers.Add(page.objectNumber); } var info = AddInfoObject(_workheets[0].Workbook._package.File.Name); - string debugString = ""; - //write to pdf + + WriteDocumentToStream(stream, catalog, info); + } + + //Write the document and cross-ref/trailer to the supplied stream. + //The stream is not closed; the caller owns it. + private void WriteDocumentToStream(Stream stream, PdfObjects.PdfCatalog catalog, PdfInfoObject info) + { + _debugString = ""; PdfCrossRefTable crossRefTable = new PdfCrossRefTable(); - //start wring pdf binary - using (var fs = new FileStream(Filename, FileMode.Create, FileAccess.Write)) + + //Use a BinaryWriter without disposing it, so the underlying stream stays open for the caller. + //BinaryWriter does not own the stream when we don't dispose it; we just flush at the end. + var bw = new BinaryWriter(stream, Encoding.ASCII); + try { - using (var bw = new BinaryWriter(fs, Encoding.ASCII)) + //Write header + bw.Write(Encoding.ASCII.GetBytes(header)); + _debugString += header; + //Write body + foreach (var pdfobj in _document) { - //Write header - bw.Write(Encoding.ASCII.GetBytes(header)); - debugString += header; - //Write body - foreach (var pdfobj in Document) - { - crossRefTable.AddPosition(fs.Position); - pdfobj.ToPdfBytes(bw); - debugString += pdfobj.ToPdfString(); - } - //Write CrossReference - crossRefTable.Write(bw, fs.Position, Document.Count); - debugString += crossRefTable.WriteString(Document.Count); - // Write trailer - PdfTrailer.Write(bw, Document.Count, catalog.objectNumber, info.objectNumber, crossRefTable.StartPosition); - debugString += PdfTrailer.WriteString(Document.Count, catalog.objectNumber, info.objectNumber, crossRefTable.StartPosition); + crossRefTable.AddPosition(stream.Position); + pdfobj.ToPdfBytes(bw); + _debugString += pdfobj.ToPdfString(); } + //Write CrossReference + crossRefTable.Write(bw, stream.Position, _document.Count); + _debugString += crossRefTable.WriteString(_document.Count); + // Write trailer + PdfTrailer.Write(bw, _document.Count, catalog.objectNumber, info.objectNumber, crossRefTable.StartPosition); + _debugString += PdfTrailer.WriteString(_document.Count, catalog.objectNumber, info.objectNumber, crossRefTable.StartPosition); + } + finally + { + bw.Flush(); } - //Write pdf as txt for debug. - if (PageSettings.Debug && PageSettings.PrintAsText) + } + + private void WriteDebugText(string fileName) + { + using (var fs = new FileStream(fileName + ".txt", FileMode.Create, FileAccess.Write)) { - using (var fs = new FileStream(Filename + ".txt", FileMode.Create, FileAccess.Write)) + using (var wr = new StreamWriter(fs)) { - using ( var wr = new StreamWriter(fs)) - { - wr.Write(debugString); - } + wr.Write(_debugString); } } } } -} +} \ No newline at end of file diff --git a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs b/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs index 632e097070..75c517d086 100644 --- a/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs +++ b/src/EPPlus.Export.Pdf/PdfCatalog/PdfCatalog.cs @@ -2,16 +2,18 @@ using EPPlus.Export.Pdf.PdfSettings; using EPPlus.Graphics; using OfficeOpenXml; +using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; namespace EPPlus.Export.Pdf.PdfCatalog { public class PdfCatalog { - internal PdfDictionaries Dictionaries = new PdfDictionaries(); - private bool AddTextForHeadings = true; + internal PdfDictionaries _dictionaries = new PdfDictionaries(); + private bool _addTextForHeadings = true; //Constructors public PdfCatalog() { } @@ -41,45 +43,71 @@ private void HandleWorksheetCollection(PdfPageSettings pageSettings, ExcelWorksh } public PdfCatalog(PdfPageSettings pageSettings, ExcelWorksheet worksheet, string fileName) + { + BuildPdf(pageSettings, worksheet, (excelPdf, layout) => + excelPdf.CreatePdf(pageSettings, _dictionaries, layout, fileName)); + } + + public PdfCatalog(PdfPageSettings pageSettings, ExcelWorksheet worksheet, Stream stream) + { + BuildPdf(pageSettings, worksheet, (excelPdf, layout) => + excelPdf.CreatePdf(pageSettings, _dictionaries, layout, stream)); + } + + private void BuildPdf(PdfPageSettings pageSettings, ExcelWorksheet worksheet, Action writePdf) { pageSettings.defaultFontName = worksheet.Workbook.ThemeManager.CurrentTheme.FontScheme.MinorFont[0].Typeface; - Stopwatch sw = Stopwatch.StartNew(); + PdfWorksheet pdfSheet = null; + try + { + Stopwatch sw = Stopwatch.StartNew(); - //Collect Text - PdfWorksheet pdfSheet = GetPdfWorksheet(pageSettings, worksheet); - sw.Stop(); - var CollectTextTime = sw.ElapsedMilliseconds; - sw.Reset(); - sw.Start(); + //Collect Text + pdfSheet = GetPdfWorksheet(pageSettings, worksheet); + sw.Stop(); + var CollectTextTime = sw.ElapsedMilliseconds; + sw.Reset(); + sw.Start(); - //Shape Text - ShapeTextInPdfWorksheet(pageSettings, pdfSheet); - sw.Stop(); - var ShapeTextTime = sw.ElapsedMilliseconds; - sw.Reset(); - sw.Start(); - - //Auto-Fit Rows - PdfCalculateRowHeight.ResizeRowHeights(pdfSheet); - sw.Stop(); - var AutoFitRowTime = sw.ElapsedMilliseconds; - sw.Reset(); - sw.Start(); - - //Create Layout - var Layout = GetLayout(pageSettings, pdfSheet); - sw.Stop(); - var CreateLayoutTime = sw.ElapsedMilliseconds; - sw.Reset(); - sw.Start(); - - //Create Pdf - ExcelPdf excelPdf = new ExcelPdf(); - excelPdf.CreatePdf(pageSettings, Dictionaries, Layout, fileName); - sw.Stop(); - var CreatePdfTime = sw.ElapsedMilliseconds; - sw.Reset(); + //Shape Text + ShapeTextInPdfWorksheet(pageSettings, pdfSheet); + sw.Stop(); + var ShapeTextTime = sw.ElapsedMilliseconds; + sw.Reset(); + sw.Start(); + + //Auto-Fit Rows + PdfCalculateRowHeight.ResizeRowHeights(pdfSheet); + sw.Stop(); + var AutoFitRowTime = sw.ElapsedMilliseconds; + sw.Reset(); + sw.Start(); + + //Create Layout + var Layout = GetLayout(pageSettings, pdfSheet); + sw.Stop(); + var CreateLayoutTime = sw.ElapsedMilliseconds; + sw.Reset(); + sw.Start(); + + //Create Pdf + ExcelPdf excelPdf = new ExcelPdf(); + writePdf(excelPdf, Layout); + sw.Stop(); + var CreatePdfTime = sw.ElapsedMilliseconds; + sw.Reset(); + } + finally + { + //Clean up the temporary worksheet used to build the comments/notes pages, + //so the source workbook isn't permanently mutated by the PDF export. + if (pdfSheet != null && pdfSheet.CommentsAndNotesSheet != null) + { + worksheet.Workbook.Worksheets.Delete(pdfSheet.CommentsAndNotesSheet); + pdfSheet.CommentsAndNotesSheet = null; + } + } } public PdfCatalog(PdfPageSettings pageSettings, ExcelRangeBase range) @@ -100,7 +128,7 @@ internal PdfCellCollection GetCellCollectionFromRange(PdfPageSettings pageSettin private Transform GetLayout(PdfPageSettings pageSettings, PdfWorksheet pdfSheet) { PdfWorksheet[] pdfSheets = new PdfWorksheet[1] { pdfSheet }; - var Layout = PdfLayout.GetLayout(pageSettings, Dictionaries, pdfSheets); + var Layout = PdfLayout.GetLayout(pageSettings, _dictionaries, pdfSheets); return Layout; } @@ -108,16 +136,16 @@ private Transform GetLayout(PdfPageSettings pageSettings, PdfWorksheet pdfSheet) internal void ShapeTextInPdfWorksheet(PdfPageSettings pageSettings, PdfWorksheet pdfSheet) { // Pass 1: collect text per font - IterateCells(pdfSheet, cell => PdfTextShaper.CollectText(Dictionaries, cell)); + IterateCells(pdfSheet, cell => PdfTextShaper.CollectText(_dictionaries, cell)); // Pass 2: build one provider per font - foreach (var kvp in Dictionaries.Fonts) + foreach (var kvp in _dictionaries.Fonts) { - Dictionaries.ShapedProviders[kvp.Key] = kvp.Value.fontSubsetManager.CreateSubsettedProvider(); + _dictionaries.ShapedProviders[kvp.Key] = kvp.Value.fontSubsetManager.CreateSubsettedProvider(); } // Pass 3: shape text using the pre-built providers - IterateCells(pdfSheet, cell => PdfTextShaper.ShapeText(pageSettings, Dictionaries, cell)); + IterateCells(pdfSheet, cell => PdfTextShaper.ShapeText(pageSettings, _dictionaries, cell)); } private void IterateCells(PdfWorksheet pdfSheet, System.Action action) @@ -162,8 +190,8 @@ internal PdfWorksheet GetPdfWorksheet(PdfPageSettings pageSettings, ExcelWorkshe pdfSheet.Ranges = new List(); pdfSheet.Worksheet = worksheet; pdfSheet.Ranges = GetRanges(pdfSheet.Worksheet); - if (pageSettings.ShowHeadings && AddTextForHeadings) Dictionaries.AddFont(pageSettings, pdfSheet.NormalStyle.Style.Font.Name, pdfSheet.GetSubFamilyFromNormalStyle, "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); - AddTextForHeadings = false; + if (pageSettings.ShowHeadings && _addTextForHeadings) _dictionaries.AddFont(pageSettings, pdfSheet.NormalStyle.Style.Font.Name, pdfSheet.GetSubFamilyFromNormalStyle, "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); + _addTextForHeadings = false; GetMaps(pageSettings, pdfSheet, pdfSheet.Ranges); GetHeaderFooter(pageSettings, pdfSheet); GetCommentsAndNotes(pageSettings, pdfSheet); @@ -176,8 +204,8 @@ private PdfWorksheet GetPdfWorksheet(PdfPageSettings pageSettings, ExcelRangeBas pdfSheet.Ranges = new List(); pdfSheet.Worksheet = excelRange.Worksheet; pdfSheet.Ranges.Add(new PdfRange(excelRange, false)); - if (pageSettings.ShowHeadings && AddTextForHeadings) Dictionaries.AddFont(pageSettings, pdfSheet.NormalStyle.Style.Font.Name, pdfSheet.GetSubFamilyFromNormalStyle, "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); - AddTextForHeadings = false; + if (pageSettings.ShowHeadings && _addTextForHeadings) _dictionaries.AddFont(pageSettings, pdfSheet.NormalStyle.Style.Font.Name, pdfSheet.GetSubFamilyFromNormalStyle, "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); + _addTextForHeadings = false; pdfSheet.Ranges[0] = GetMaps(pageSettings, pdfSheet, pdfSheet.Ranges[0]); GetHeaderFooter(pageSettings, pdfSheet); GetCommentsAndNotes(pageSettings, pdfSheet); @@ -216,14 +244,14 @@ private void GetMaps(PdfPageSettings pageSettings, PdfWorksheet pdfSheet, List + /// Returns the resolved color as a , taking theme, tint, indexed, RGB, and auto values into account. + /// Returns if the color is not set. + /// + public Color ToColor() + { + var hex = LookupColor(); + if (string.IsNullOrEmpty(hex) || hex == "0") return Color.Empty; + + // LookupColor returns strings like "#FFRRGGBB". Strip the leading '#'. + if (hex[0] == '#') hex = hex.Substring(1); + + int argb; + if (int.TryParse(hex, System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture, out argb)) + { + return Color.FromArgb(argb); + } + return Color.Empty; + } } }