From 429c26aa86126cbcbb489ffb848d74d75e9cc4fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AdrianParn=C3=A9us?= Date: Tue, 9 Jun 2026 11:30:06 +0200 Subject: [PATCH 1/3] Fixed issue where rotated text would partially hide border --- .../Internal/HtmlExporterBaseInternal.cs | 26 +++++++ .../Translators/CssTextFormatTranslator.cs | 5 -- .../Export/HtmlExport/RangeExporterTests.cs | 72 +++++++++++++------ 3 files changed, 77 insertions(+), 26 deletions(-) diff --git a/src/EPPlus/Export/HtmlExport/Exporters/Internal/HtmlExporterBaseInternal.cs b/src/EPPlus/Export/HtmlExport/Exporters/Internal/HtmlExporterBaseInternal.cs index e970079292..46d4c2b748 100644 --- a/src/EPPlus/Export/HtmlExport/Exporters/Internal/HtmlExporterBaseInternal.cs +++ b/src/EPPlus/Export/HtmlExport/Exporters/Internal/HtmlExporterBaseInternal.cs @@ -16,6 +16,7 @@ Date Author Change using OfficeOpenXml.Export.HtmlExport.Accessibility; using OfficeOpenXml.Export.HtmlExport.HtmlCollections; using OfficeOpenXml.Export.HtmlExport.Parsers; +using OfficeOpenXml.Export.HtmlExport.Settings; using OfficeOpenXml.Export.HtmlExport.Translators; using OfficeOpenXml.Table; using OfficeOpenXml.Utils; @@ -731,8 +732,33 @@ internal void GetClassData(HTMLElement element, bool isTable, HtmlImage image, E element.AddChildElement(childHtml); } } + + var textRotation = cell.Style.TextRotation; + if (textRotation != 0 && textRotation != 255 && IsTextRotationExcluded(settings, isHeader) == false) + { + var rotationValue = textRotation > 90 ? textRotation - 90 : 360 - textRotation; + var rotationWrapper = new HTMLElement("div"); + rotationWrapper.AddAttribute("style", $"display:inline-block;transform:rotate({rotationValue}deg);"); + element.AddChildElement(rotationWrapper); + valueElement = rotationWrapper; + } + } + + private static bool IsTextRotationExcluded(HtmlExportSettings settings, bool isHeader) + { + if (settings is HtmlRangeExportSettings rangeSettings) + { + return rangeSettings.Css.CssExclude.TextRotation; + } + if (settings is HtmlTableExportSettings tableSettings) + { + var exclude = isHeader ? tableSettings.Css.Exclude.TableStyle : tableSettings.Css.Exclude.CellStyle; + return exclude.TextRotation; + } + return false; } + public void AddTableDataFromCell(ExcelRangeBase cell, string dataType, HTMLElement element, HtmlExportSettings settings, bool addRowScope, HtmlImage image, ExporterContext content) { if (dataType != ColumnDataTypeManager.HtmlDataTypes.String && settings.RenderDataAttributes) diff --git a/src/EPPlus/Export/HtmlExport/Translators/CssTextFormatTranslator.cs b/src/EPPlus/Export/HtmlExport/Translators/CssTextFormatTranslator.cs index 742c67ed26..a9cbefe456 100644 --- a/src/EPPlus/Export/HtmlExport/Translators/CssTextFormatTranslator.cs +++ b/src/EPPlus/Export/HtmlExport/Translators/CssTextFormatTranslator.cs @@ -73,11 +73,6 @@ internal override List GenerateDeclarationList(TranslatorContext co AddDeclaration("writing-mode", "vertical-lr"); AddDeclaration("text-orientation", "upright"); } - else - { - var rotationvalue = _textRotation > 90 ? _textRotation - 90 : 360 - _textRotation; - AddDeclaration("transform", $"rotate({rotationvalue}deg)"); - } } if (_indent > 0 && context.Exclude.Indent == false) { diff --git a/src/EPPlusTest/Export/HtmlExport/RangeExporterTests.cs b/src/EPPlusTest/Export/HtmlExport/RangeExporterTests.cs index 4c8f323d17..7e70c91a80 100644 --- a/src/EPPlusTest/Export/HtmlExport/RangeExporterTests.cs +++ b/src/EPPlusTest/Export/HtmlExport/RangeExporterTests.cs @@ -1,14 +1,15 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OfficeOpenXml; using OfficeOpenXml.Export.HtmlExport; +using OfficeOpenXml.FormulaParsing.Excel.Functions.Information; +using OfficeOpenXml.Style; using OfficeOpenXml.Table; using System; using System.Collections.Generic; -using System.IO; using System.Drawing; -using OfficeOpenXml.Style; -using System.Text; using System.Globalization; +using System.IO; +using System.Text; using System.Threading.Tasks; namespace EPPlusTest.Export.HtmlExport @@ -19,7 +20,7 @@ public class RangeExporterTests : TestBase [TestMethod] public void ShouldExportHtmlWithHeadersNoAccessibilityAttributes() { - using(var package = new ExcelPackage()) + using (var package = new ExcelPackage()) { var sheet = package.Workbook.Worksheets.Add("Test"); sheet.Cells["A1"].Value = "Name"; @@ -27,12 +28,12 @@ public void ShouldExportHtmlWithHeadersNoAccessibilityAttributes() sheet.Cells["A2"].Value = "John Doe"; sheet.Cells["B2"].Value = 23; var range = sheet.Cells["A1:B2"]; - using(var ms = new MemoryStream()) + using (var ms = new MemoryStream()) { var exporter = range.CreateHtmlExporter(); - exporter.Settings.Accessibility.TableSettings.AddAccessibilityAttributes=false; + exporter.Settings.Accessibility.TableSettings.AddAccessibilityAttributes = false; exporter.Settings.Culture = new CultureInfo("us-en"); - exporter.RenderHtml(ms); + exporter.RenderHtml(ms); var sr = new StreamReader(ms); ms.Position = 0; var result = sr.ReadToEnd(); @@ -81,12 +82,12 @@ public async Task ShouldExportHtmlWithHeadersWithStyles() var range = sheet.Cells["A1:B2"]; sheet.Cells["A1:B1"].Style.Font.Bold = true; sheet.Cells["A1:B1"].Style.Font.Color.SetColor(Color.Blue); - sheet.Cells["A1:B1"].Style.Border.Bottom.Style=ExcelBorderStyle.Thin; + sheet.Cells["A1:B1"].Style.Border.Bottom.Style = ExcelBorderStyle.Thin; sheet.Cells["A1:B1"].Style.Border.Bottom.Color.SetColor(Color.Red); sheet.Cells["A1:B1"].Style.Fill.PatternType = ExcelFillStyle.LightGray; sheet.Cells["A1:B1"].Style.Fill.BackgroundColor.SetColor(Color.LightCoral); sheet.Cells["A1:B1"].Style.Fill.PatternColor.SetColor(Color.LightCyan); - sheet.Cells["A2:B2"].Style.Font.Italic=true; + sheet.Cells["A2:B2"].Style.Font.Italic = true; sheet.Cells["B1:B2"].Style.Font.Name = "Consolas"; var exporter = range.CreateHtmlExporter(); @@ -95,7 +96,7 @@ public async Task ShouldExportHtmlWithHeadersWithStyles() var result = exporter.GetSinglePage(); var expected = "
NameAge
John Doe23
"; - + Assert.AreEqual(expected, result); var resultAsync = await exporter.GetSinglePageAsync(); Assert.AreEqual(result, resultAsync); @@ -142,7 +143,7 @@ public async Task ShouldExportHtmlWithMergedCells() var resultAsync = await exporter.GetSinglePageAsync(); SaveAndCleanup(package); Assert.AreEqual(result, resultAsync); - + } } [TestMethod] @@ -170,7 +171,7 @@ public void WriteAllsvenskan() exporter.Settings.SetColumnWidth = true; exporter.Settings.SetRowHeight = true; exporter.Settings.Pictures.Include = ePictureInclude.Include; - var html =exporter.GetSinglePage(); + var html = exporter.GetSinglePage(); File.WriteAllText("c:\\temp\\" + sheet.Name + ".html", html); SaveAndCleanup(p); } @@ -180,16 +181,16 @@ public async Task WriteImagesAsync() { using (var p = OpenTemplatePackage("20-CreateAFileSystemReport.xlsx")) { - var sheet = p.Workbook.Worksheets[0]; + var sheet = p.Workbook.Worksheets[0]; var exporter = sheet.Cells["A1:E30"].CreateHtmlExporter(); - + exporter.Settings.SetColumnWidth = true; exporter.Settings.SetRowHeight = true; exporter.Settings.Pictures.Include = ePictureInclude.Include; exporter.Settings.Minify = false; - exporter.Settings.Encoding = Encoding.UTF8; + exporter.Settings.Encoding = Encoding.UTF8; var html = exporter.GetSinglePage(); - var htmlAsync = await exporter.GetSinglePageAsync(); + var htmlAsync = await exporter.GetSinglePageAsync(); File.WriteAllText("c:\\temp\\" + sheet.Name + ".html", html); File.WriteAllText("c:\\temp\\" + sheet.Name + "-async.html", htmlAsync); Assert.AreEqual(html, htmlAsync); @@ -223,7 +224,7 @@ public void ExportMultipleRanges() { var sheet1 = p.Workbook.Worksheets[0]; var sheet2 = p.Workbook.Worksheets[1]; - + var exporter = p.Workbook.CreateHtmlExporter( sheet2.Cells["A1:B13"], sheet2.Cells["A16:B26"], @@ -564,11 +565,11 @@ public void ExportRangeIssue() x.TableId = "asia-toll-free"; }); var css = exporter.GetCssString(); - File.WriteAllText("c:\\temp\\html.html",$"{html1}"); + File.WriteAllText("c:\\temp\\html.html", $"{html1}"); } } - private static void SaveRangeFile(ExcelPackage package, string ws, string address, int headerRows=1) + private static void SaveRangeFile(ExcelPackage package, string ws, string address, int headerRows = 1) { var sheet = package.Workbook.Worksheets[ws]; var range = sheet.Cells[address]; @@ -584,7 +585,7 @@ public void NumberFormatColorShouldCreateCssColor() using (var package = OpenPackage("html_numfRed_text.xlsx", true)) { var wb = package.Workbook; - var aNewWs = wb.Worksheets.Add("NewWs"); + var aNewWs = wb.Worksheets.Add("NewWs"); var range = aNewWs.Cells["A1:A5"]; range.Formula = "ROW()"; @@ -605,6 +606,35 @@ public void NumberFormatColorShouldCreateCssColor() SaveAndCleanup(package); } } + + [TestMethod] + public void S1053() + { + { + using var p = OpenTemplatePackage("R05.xlsx"); + var ws = p.Workbook.Worksheets[0]; + + var range = ws.Cells["A1:AA7"]; + var exporter = range.CreateHtmlExporter(); + var settings = exporter.Settings; + settings.SetRowHeight = true; + settings.SetColumnWidth = true; + var page = exporter.GetSinglePage(); + File.WriteAllText("c:\\temp\\" + "R05" + ".html", page); + } + { + using var p = OpenTemplatePackage("CR168.xlsx"); + var ws = p.Workbook.Worksheets[0]; + + var range = ws.Cells["A1:AA36"]; + var exporter = range.CreateHtmlExporter(); + var settings = exporter.Settings; + settings.SetRowHeight = true; + settings.SetColumnWidth = true; + var page = exporter.GetSinglePage(); + File.WriteAllText("c:\\temp\\" + "CR168" + ".html", page); + } + } + } } - \ No newline at end of file From 5d0ecb4fb9ef9687db8dbfa457a908cef89e0db6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Fri, 12 Jun 2026 15:53:09 +0200 Subject: [PATCH 2/3] Fixed several bugs in the HTML export. Row height was not set using invariant culture. Pattern fill was incorrect when the color was blank. Border style was not correct for Right and Bottom borders on merged cells --- .../HtmlExport/Determinator/StyleChecker.cs | 2 +- .../Internal/HtmlExporterBaseInternal.cs | 4 ++-- .../Translators/CssFillTranslator.cs | 19 +++++++++++++++---- .../Export/HtmlExport/RangeExporterTests.cs | 6 ++++-- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/EPPlus/Export/HtmlExport/Determinator/StyleChecker.cs b/src/EPPlus/Export/HtmlExport/Determinator/StyleChecker.cs index 7e92fc704b..7a7b3519dd 100644 --- a/src/EPPlus/Export/HtmlExport/Determinator/StyleChecker.cs +++ b/src/EPPlus/Export/HtmlExport/Determinator/StyleChecker.cs @@ -82,7 +82,7 @@ internal bool ShouldAdd internal bool ShouldAddWithBorders(int bottomStyleId, int rightStyleId) { - if (IsAdded(bottomStyleId, rightStyleId)) + if (IsAdded(bottomStyleId, rightStyleId) && _styleList.Count > 1) { return false; } diff --git a/src/EPPlus/Export/HtmlExport/Exporters/Internal/HtmlExporterBaseInternal.cs b/src/EPPlus/Export/HtmlExport/Exporters/Internal/HtmlExporterBaseInternal.cs index 46d4c2b748..ff5085b4dd 100644 --- a/src/EPPlus/Export/HtmlExport/Exporters/Internal/HtmlExporterBaseInternal.cs +++ b/src/EPPlus/Export/HtmlExport/Exporters/Internal/HtmlExporterBaseInternal.cs @@ -508,7 +508,7 @@ internal void AddRowHeightStyle(HTMLElement element, ExcelRangeBase range, int r { if (rowInternal.Height != -1 && rowInternal.Height != range.Worksheet.DefaultRowHeight) { - element.AddAttribute("style", $"height:{rowInternal.Height}pt"); + element.AddAttribute("style", $"height:{rowInternal.Height.ToString(CultureInfo.InvariantCulture)}pt"); return; } } @@ -738,7 +738,7 @@ internal void GetClassData(HTMLElement element, bool isTable, HtmlImage image, E { var rotationValue = textRotation > 90 ? textRotation - 90 : 360 - textRotation; var rotationWrapper = new HTMLElement("div"); - rotationWrapper.AddAttribute("style", $"display:inline-block;transform:rotate({rotationValue}deg);"); + rotationWrapper.AddAttribute("style", $"display:inline-block;transform:rotate({rotationValue.ToString(CultureInfo.InvariantCulture)}deg);"); element.AddChildElement(rotationWrapper); valueElement = rotationWrapper; } diff --git a/src/EPPlus/Export/HtmlExport/Translators/CssFillTranslator.cs b/src/EPPlus/Export/HtmlExport/Translators/CssFillTranslator.cs index 438a2a3c15..7eb8b10a99 100644 --- a/src/EPPlus/Export/HtmlExport/Translators/CssFillTranslator.cs +++ b/src/EPPlus/Export/HtmlExport/Translators/CssFillTranslator.cs @@ -13,8 +13,11 @@ Date Author Change using OfficeOpenXml.Drawing.Theme; using OfficeOpenXml.Export.HtmlExport.CssCollections; +using OfficeOpenXml.Export.HtmlExport.StyleCollectors; using OfficeOpenXml.Export.HtmlExport.StyleCollectors.StyleContracts; using OfficeOpenXml.Style; +using OfficeOpenXml.Style.Dxf; +using OfficeOpenXml.Utils.TypeConversion; using System.Collections.Generic; using System.Linq; @@ -60,10 +63,18 @@ internal override List GenerateDeclarationList(TranslatorContext co } else { - string bgColor = _fill.GetBackgroundColor(_theme)??"#0"; - string patternColor = _fill.GetPatternColor(_theme)??"#0"; - - var svg = PatternFills.GetPatternSvgConvertedOnly(_fill.PatternType, bgColor, patternColor); + string bgColor, patternColor; + if (_fill is FillDxf) + { + bgColor = _fill.GetBackgroundColor(_theme) ?? "#" + ColorConverter.GetThemeColor(_theme.ColorScheme.GetColorByEnum(Drawing.eThemeSchemeColor.Background1)).ToArgb().ToString("x8").Substring(2); + patternColor = _fill.GetPatternColor(_theme) ?? "#" + ColorConverter.GetThemeColor(_theme.ColorScheme.GetColorByEnum(Drawing.eThemeSchemeColor.Text1)).ToArgb().ToString("x8").Substring(2); + } + else + { + patternColor = _fill.GetBackgroundColor(_theme) ?? "#" + ColorConverter.GetThemeColor(_theme.ColorScheme.GetColorByEnum(Drawing.eThemeSchemeColor.Text1)).ToArgb().ToString("x8").Substring(2); + bgColor = _fill.GetPatternColor(_theme) ?? "#" + ColorConverter.GetThemeColor(_theme.ColorScheme.GetColorByEnum(Drawing.eThemeSchemeColor.Background1)).ToArgb().ToString("x8").Substring(2); + } + var svg = PatternFills.GetPatternSvgConvertedOnly(_fill.PatternType, patternColor, bgColor); AddDeclaration("background-repeat", "repeat"); //arguably some of the values should be its own declaration...Should still work though. AddDeclaration("background", $"url(data:image/svg+xml;base64,{svg})"); diff --git a/src/EPPlusTest/Export/HtmlExport/RangeExporterTests.cs b/src/EPPlusTest/Export/HtmlExport/RangeExporterTests.cs index 7e70c91a80..82dac0cc99 100644 --- a/src/EPPlusTest/Export/HtmlExport/RangeExporterTests.cs +++ b/src/EPPlusTest/Export/HtmlExport/RangeExporterTests.cs @@ -619,8 +619,10 @@ public void S1053() var settings = exporter.Settings; settings.SetRowHeight = true; settings.SetColumnWidth = true; + settings.HeaderRows = 3; + settings.Encoding = Encoding.UTF8; var page = exporter.GetSinglePage(); - File.WriteAllText("c:\\temp\\" + "R05" + ".html", page); + File.WriteAllText("c:\\temp\\" + "R05" + ".html", page, Encoding.UTF8); } { using var p = OpenTemplatePackage("CR168.xlsx"); @@ -632,7 +634,7 @@ public void S1053() settings.SetRowHeight = true; settings.SetColumnWidth = true; var page = exporter.GetSinglePage(); - File.WriteAllText("c:\\temp\\" + "CR168" + ".html", page); + File.WriteAllText("c:\\temp\\" + "CR168" + ".html", page, Encoding.UTF8); } } From 1f90364a3a104e4745581be5dec6a659f7c5d49c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Mon, 15 Jun 2026 14:36:55 +0200 Subject: [PATCH 3/3] Solved rotated column headers + savefile location --- .../Internal/HtmlExporterBaseInternal.cs | 19 ++++++++++++++++++- .../Export/HtmlExport/RangeExporterTests.cs | 6 ++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/EPPlus/Export/HtmlExport/Exporters/Internal/HtmlExporterBaseInternal.cs b/src/EPPlus/Export/HtmlExport/Exporters/Internal/HtmlExporterBaseInternal.cs index ff5085b4dd..def7eec561 100644 --- a/src/EPPlus/Export/HtmlExport/Exporters/Internal/HtmlExporterBaseInternal.cs +++ b/src/EPPlus/Export/HtmlExport/Exporters/Internal/HtmlExporterBaseInternal.cs @@ -738,7 +738,24 @@ internal void GetClassData(HTMLElement element, bool isTable, HtmlImage image, E { var rotationValue = textRotation > 90 ? textRotation - 90 : 360 - textRotation; var rotationWrapper = new HTMLElement("div"); - rotationWrapper.AddAttribute("style", $"display:inline-block;transform:rotate({rotationValue.ToString(CultureInfo.InvariantCulture)}deg);"); + + string rotationStyle = ""; + if (rotationValue == 90 || rotationValue == 270) + { + if (rotationValue > 90) + { + rotationStyle = "writing-mode: sideways-lr;"; + } + else + { + rotationStyle += " writing-mode: sideways-rl;"; + } + } + else + { + rotationStyle = $"$display:inline-block;transform:rotate({rotationValue.ToString(CultureInfo.InvariantCulture)}deg);"; + } + rotationWrapper.AddAttribute("style", $"{rotationStyle}"); element.AddChildElement(rotationWrapper); valueElement = rotationWrapper; } diff --git a/src/EPPlusTest/Export/HtmlExport/RangeExporterTests.cs b/src/EPPlusTest/Export/HtmlExport/RangeExporterTests.cs index 82dac0cc99..98ac450ee3 100644 --- a/src/EPPlusTest/Export/HtmlExport/RangeExporterTests.cs +++ b/src/EPPlusTest/Export/HtmlExport/RangeExporterTests.cs @@ -622,7 +622,8 @@ public void S1053() settings.HeaderRows = 3; settings.Encoding = Encoding.UTF8; var page = exporter.GetSinglePage(); - File.WriteAllText("c:\\temp\\" + "R05" + ".html", page, Encoding.UTF8); + var file = GetOutputFile("html", "R05" + ".html"); + File.WriteAllText(file.FullName, page, Encoding.UTF8); } { using var p = OpenTemplatePackage("CR168.xlsx"); @@ -634,7 +635,8 @@ public void S1053() settings.SetRowHeight = true; settings.SetColumnWidth = true; var page = exporter.GetSinglePage(); - File.WriteAllText("c:\\temp\\" + "CR168" + ".html", page, Encoding.UTF8); + var file = GetOutputFile("html", "CR168" + ".html"); + File.WriteAllText(file.FullName, page, Encoding.UTF8); } }