diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 04f80c67f3..6ccdb3125f 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -4,7 +4,6 @@ - diff --git a/src/EPPlus.Compression/EPPlus.Compression.csproj b/src/EPPlus.Compression/EPPlus.Compression.csproj index de594e155c..c7093d41c9 100644 --- a/src/EPPlus.Compression/EPPlus.Compression.csproj +++ b/src/EPPlus.Compression/EPPlus.Compression.csproj @@ -6,5 +6,6 @@ OfficeOpenXml.Packaging.Ionic true EPPlus.Compression.snk + false \ No newline at end of file diff --git a/src/EPPlus.DrawingRenderer.Tests/Chart/BarChartTests.cs b/src/EPPlus.DrawingRenderer.Tests/Chart/BarChartTests.cs index 364ea2ef7e..f992657e70 100644 --- a/src/EPPlus.DrawingRenderer.Tests/Chart/BarChartTests.cs +++ b/src/EPPlus.DrawingRenderer.Tests/Chart/BarChartTests.cs @@ -51,7 +51,7 @@ public void DatalabelBarCharts() { var ws = p.Workbook.Worksheets[0]; var drawings = ws.Drawings; - var ix = 1; + var ix = 0; for (int i = ix; i < drawings.Count; i++) { diff --git a/src/EPPlus.DrawingRenderer.Tests/Chart/ChartStyleFallbackTest.cs b/src/EPPlus.DrawingRenderer.Tests/Chart/ChartStyleFallbackTest.cs index 110a010eb7..89d91fc863 100644 --- a/src/EPPlus.DrawingRenderer.Tests/Chart/ChartStyleFallbackTest.cs +++ b/src/EPPlus.DrawingRenderer.Tests/Chart/ChartStyleFallbackTest.cs @@ -5,347 +5,537 @@ using System.Collections.Generic; using System.Drawing; using tc = OfficeOpenXml.Utils.TypeConversion; +using System.Globalization; +using OfficeOpenXml.Drawing.Style.Coloring; +using OfficeOpenXml.Drawing.Theme; namespace EPPlus.DrawingRenderer.Tests.Chart { [TestClass] public class ChartStyleFallbackTest : TestBase { - [TestMethod] - public void ReadExcelFile() + + [AssemblyInitialize] + public static async Task AssemblyInit(TestContext context) { ExcelPackage.License.SetNonCommercialOrganization("EPPlus Project"); - CreatePathIfNotExists("StyleExamples\\"); - using (var p = OpenTemplatePackage("StyleExamples\\ExcelUnchangedEmptyChart.xlsx")) + } + + private void CreateStyleExampleAndExportIt(string fileName, Func, bool> assertIfTestSuccessful) + { + bool testSucceded = false; + + using (var p = OpenTemplatePackage($"StyleExamples\\{fileName}.xlsx")) { var ws = p.Workbook.Worksheets[0]; - foreach (ExcelChart c in ws.Drawings) - { - var borderRef = c.StyleManager.Style.ChartArea.BorderReference; - var borderSetting = c.Border; - var borderDirectColor = borderSetting.Fill.Color; + List outputSvgs = new List(); - var svg = c.ToSvg(); - SaveTextFileToWorkbook($"svg\\ExcelDefault{ws.Name}_{c.Name}.svg", svg); + foreach (var d in ws.Drawings) + { + if (d is ExcelChart c) + { + var svg = c.ToSvg(); + outputSvgs.Add(svg); + SaveTextFileToWorkbook($"svg\\{fileName}_{ws.Name}_{c.Name}.svg", svg); + } } - var fi = GetOutputFile("StyleExamples", "ExcelUnchangedEmptyChart_out.xlsx"); + + testSucceded = assertIfTestSuccessful(outputSvgs); + + var fi = GetOutputFile("StyleExamples", $"{fileName}_Out.xlsx"); p.SaveAs(fi); } + Assert.IsTrue(testSucceded); } [TestMethod] - public void EpplusGeneratedChart() + public void ReadExcelFile() { - ExcelPackage.License.SetNonCommercialOrganization("EPPlus Project"); + CreateStyleExampleAndExportIt("ExcelUnchangedEmptyChart", (List outputSvgs) => + { + //Create expected color + var col = Color.FromArgb(217, 217, 217); + var expectedStr = ColorTranslator.ToHtml(col).ToLower(); - CreatePathIfNotExists("StyleExamples\\"); + var svgSplitOnSpace = outputSvgs[0].Split(' '); + + //Get the first stroke and extract the hexCode for the expected color + var firstStroke = svgSplitOnSpace.First(s => s.StartsWith("stroke")); + var colorResult = firstStroke.Substring(8, 7).ToLower(); + + //Get the resulting width + var strokeWidth = svgSplitOnSpace.First(s => s.StartsWith("stroke-width")); + var widthStr = strokeWidth.Substring(14, strokeWidth.Length - 14 -1).ToLower(); + var widthResult = double.Parse(widthStr, CultureInfo.InvariantCulture); - using (var p = OpenPackage("StyleExamples\\epplusDefaultTest.xlsx",true)) + //Assert + Assert.AreEqual(expectedStr, colorResult); + Assert.AreEqual(1d, widthResult); + + return expectedStr == colorResult && 1d == widthResult; + }); + } + + [TestMethod] + public void ReadEmptyDefaultChartStyle() + { + CreateStyleExampleAndExportIt("emptyDefault", (List outputSvgs) => { - var ws = p.Workbook.Worksheets.Add("EpplusGeneratedChart"); + //Create expected color + var col = Color.FromArgb(217, 217, 217); + var expectedStr = ColorTranslator.ToHtml(col).ToLower(); - //ws.Workbook.ThemeManager.GetOrCreateTheme(); + var svgSplitOnSpace = outputSvgs[0].Split(' '); + //Get the first stroke and extract the hexCode for the expected color + var firstStroke = svgSplitOnSpace.First(s => s.StartsWith("stroke")); + var colorResult = firstStroke.Substring(8, 7).ToLower(); - ws.Cells["A1:A3"].Formula = "ROW()+COLUMN()"; + //Get the resulting width + var strokeWidth = svgSplitOnSpace.First(s => s.StartsWith("stroke-width")); + var widthStr = strokeWidth.Substring(14, strokeWidth.Length - 14 - 1).ToLower(); + var widthResult = double.Parse(widthStr, CultureInfo.InvariantCulture); - ws.Calculate(); + //Assert + Assert.AreEqual(expectedStr, colorResult); + Assert.AreEqual(13.3333d, widthResult, 0.003); - var emptyLines = ws.Drawings.AddLineChart("EmptyLineChart", eLineChartType.Line); - var generatedBar = ws.Drawings.AddBarChart("EpplusBarChart", eBarChartType.ColumnClustered); + return expectedStr == colorResult && 13.3333d == Math.Round(widthResult,4); + }); + } - generatedBar.SetPosition(1, 1000); - var defaultRect = ws.Drawings.AddShape("MyDefaultShape", OfficeOpenXml.Drawing.eShapeStyle.Round1Rect); - var gradientRect = ws.Drawings.AddShape("GradRect", OfficeOpenXml.Drawing.eShapeStyle.Round1Rect); + [TestMethod] + public void ReadExcelEditedRemovedStyles() + { + string fileName = "emptyManuallyRemovedLnStyles"; - defaultRect.SetPosition(300, 1); - gradientRect.SetPosition(300, 1000); + CreateStyleExampleAndExportIt(fileName, (List outputSvgs) => + { + //Create expected color + var col = Color.FromArgb(137, 137, 137); + var expectedStr = ColorTranslator.ToHtml(col).ToLower(); - defaultRect.Fill.Style = OfficeOpenXml.Drawing.eFillStyle.SolidFill; - gradientRect.Fill.Style = OfficeOpenXml.Drawing.eFillStyle.GradientFill; - generatedBar.Series.Add(ws.Cells["A1:A3"]); + var svgSplitOnSpace = outputSvgs[0].Split(' '); - //foreach (ExcelChart c in ws.Drawings) - //{ - // var borderRef = c.StyleManager.Style.ChartArea.BorderReference; - // var borderSetting = c.Border; - // var borderDirectColor = borderSetting.Fill.Color; + //Get the first stroke and extract the hexCode for the expected color + var firstStroke = svgSplitOnSpace.First(s => s.StartsWith("stroke")); + var colorResult = firstStroke.Substring(8, 7).ToLower(); - // var svg = c.ToSvg(); - // SaveTextFileToWorkbook($"svg\\epplusDefault{ws.Name}_{c.Name}.svg", svg); - //} - //GetOutputFile("StyleExamples", ""); - SaveAndCleanup(p); - } + //Get the resulting width + var strokeWidth = svgSplitOnSpace.First(s => s.StartsWith("stroke-width")); + var widthStr = strokeWidth.Substring(14, strokeWidth.Length - 14 - 1).ToLower(); + var widthResult = double.Parse(widthStr, CultureInfo.InvariantCulture); + + //Assert + Assert.AreEqual(expectedStr, colorResult); + Assert.AreEqual(13.3333d, widthResult, 0.003); + + return expectedStr == colorResult && 13.3333d == Math.Round(widthResult, 4); + }); } [TestMethod] - public void ReadEmptyDefaultChartStyle() + public void EditedTheme() { - ExcelPackage.License.SetNonCommercialOrganization("EPPlus Project"); - - CreatePathIfNotExists("StyleExamples\\"); + string fileName = "ExcelThemeEdited"; - using (var p = OpenTemplatePackage("StyleExamples\\emptyDefault.xlsx")) + CreateStyleExampleAndExportIt(fileName, (List outputSvgs) => { - var ws = p.Workbook.Worksheets[0]; + //Create expected color + var col = Color.FromArgb(255, 255, 199, 199); + var expectedStr = ColorTranslator.ToHtml(col).ToLower(); - foreach (ExcelChart c in ws.Drawings) - { - var borderRef = c.StyleManager.Style.ChartArea.BorderReference; - var borderSetting = c.Border; - var borderDirectColor = borderSetting.Fill.Color; + var svgSplitOnSpace = outputSvgs[0].Split(' '); - var svg = c.ToSvg(); - SaveTextFileToWorkbook($"svg\\emptyDefaultStyle{ws.Name}_{c.Name}.svg", svg); - } - var fi = GetOutputFile("StyleExamples", "emptyDefault_out.xlsx"); - p.SaveAs(fi); - } + //Get the first stroke and extract the hexCode for the expected color + var firstStroke = svgSplitOnSpace.First(s => s.StartsWith("stroke")); + var colorResult = firstStroke.Substring(8, 7).ToLower(); + + //Get the resulting width + var strokeWidth = svgSplitOnSpace.First(s => s.StartsWith("stroke-width")); + var widthStr = strokeWidth.Substring(14, strokeWidth.Length - 14 - 1).ToLower(); + var widthResult = double.Parse(widthStr, CultureInfo.InvariantCulture); + + //Assert + Assert.AreEqual(expectedStr, colorResult); + Assert.AreEqual(13.3333d, widthResult, 0.003); + + return expectedStr == colorResult && 13.3333d == Math.Round(widthResult, 4); + }); } + + [TestMethod] - public void GenerateSimpleChart() + public void ManualSystemText() { - ExcelPackage.License.SetNonCommercialOrganization("EPPlus Project"); - - string fileName = "EpplusSimpleChart"; + string fileName = "ExcelThemeManualSystemText"; - using (var p = OpenPackage($"{fileName}.xlsx",true)) + CreateStyleExampleAndExportIt(fileName, (List outputSvgs) => { - var ws = p.Workbook.Worksheets.Add("s1"); - ws.Drawings.AddBarChart("simpleChart", eBarChartType.ColumnClustered); + //Create expected color + var col = Color.FromArgb(255, 0, 0, 0); + var expectedStr = ColorTranslator.ToHtml(col).ToLower(); - SaveAndCleanup(p); - } + var svgSplitOnSpace = outputSvgs[0].Split(' '); + + //Get the first stroke and extract the hexCode for the expected color + var firstStroke = svgSplitOnSpace.First(s => s.StartsWith("stroke")); + var colorResult = firstStroke.Substring(8, 7).ToLower(); + + //Get the resulting width + var strokeWidth = svgSplitOnSpace.First(s => s.StartsWith("stroke-width")); + var widthStr = strokeWidth.Substring(14, strokeWidth.Length - 14 - 1).ToLower(); + var widthResult = double.Parse(widthStr, CultureInfo.InvariantCulture); + + //Assert + Assert.AreEqual(expectedStr, colorResult); + Assert.AreEqual(13.3333d, widthResult, 0.003); + + return expectedStr == colorResult && 13.3333d == Math.Round(widthResult, 4); + }); } + [TestMethod] - public void ReadChartBorderThemeTint() + public void ExcelThemeLnDeleted() { - ExcelPackage.License.SetNonCommercialOrganization("EPPlus Project"); - - var fileName = "ChartBorderThemeTint"; - CreatePathIfNotExists("StyleExamples\\"); + string fileName = "ExcelThemeLnDeleted"; - using (var p = OpenTemplatePackage($"StyleExamples\\{fileName}.xlsx")) + CreateStyleExampleAndExportIt(fileName, (List outputSvgs) => { - var ws = p.Workbook.Worksheets[0]; - var lChart = ws.Drawings[0].As.Chart.LineChart; + //Create expected color + var col = Color.Transparent; + var expectedStr = ColorTranslator.ToHtml(col).ToLower(); - lChart.StyleManager.Style.ChartArea.Border.Fill.SolidFill.Color.SetSchemeColor(OfficeOpenXml.Drawing.eSchemeColor.Accent1); - - //100 - input is what excel seems to apply - //lChart.StyleManager.Style.ChartArea.BorderReference.Color.Transforms.AddTint(13); + var svgSplitOnSpace = outputSvgs[0].Split(' '); - //Adding Less Tint makes the object Lighter. Which is the inverse of how excel does it. - lChart.StyleManager.Style.ChartArea.Border.Fill.SolidFill.Color.Transforms.AddTint(60); - lChart.StyleManager.Style.ChartArea.Border.Width = 10d; - lChart.StyleManager.ApplyStyles(); + //Get the first stroke and extract the hexCode for the expected color + var firstStroke = svgSplitOnSpace.First(s => s.StartsWith("stroke")); + var colorResult = firstStroke.Substring(8, 4).ToLower(); - var fi = GetOutputFile("StyleExamples", $"{fileName}_Out.xlsx"); - p.SaveAs(fi); - } + Assert.AreEqual("none", colorResult); + + return "none" == colorResult; + }); } + [TestMethod] - public void RemovedStyles() + public void PureExcelTheme() { - ExcelPackage.License.SetNonCommercialOrganization("EPPlus Project"); + string fileName = "PureExcelTheme"; - string fileName = "emptyManuallyRemovedLnStyles"; - CreatePathIfNotExists("StyleExamples\\"); + //Border and Fill for chartArea expected colors + List ExpectedColors = new List() { "#ffcaca", "#bbd7f9" }; - using (var p = OpenTemplatePackage($"StyleExamples\\{fileName}.xlsx")) + //Test un-edited excel theme with custom colors set in excel + CreateStyleExampleAndExportIt(fileName, (List outputSvgs) => { - var ws = p.Workbook.Worksheets[0]; - - foreach (var d in ws.Drawings) + foreach(var svg in outputSvgs) { - if (d is ExcelChart c) - { - //var borderSetting = c.Border; - //var borderDirectColor = borderSetting.Fill.Color; - //var theme = p.Workbook.ThemeManager.GetOrCreateTheme(); + var svgSplitOnSpace = svg.Split(' '); - //var defaultColorFromTheme = theme.ColorScheme.Dark1; + //Get the first stroke and extract the hexCode for the expected color + var firstStroke = svgSplitOnSpace.First(s => s.StartsWith("stroke")); + var borderResult = firstStroke.Substring(8, 7).ToLower(); - var svg = c.ToSvg(); - SaveTextFileToWorkbook($"svg\\{fileName}_{ws.Name}_{c.Name}.svg", svg); - } + //Get the first stroke and extract the hexCode for the expected color + var firstFill = svgSplitOnSpace.First(s => s.StartsWith("fill")); + var fillResult = firstFill.Substring(6, 7).ToLower(); + + Assert.AreEqual(ExpectedColors[0], borderResult); + Assert.AreEqual(ExpectedColors[1], fillResult); } - var fi = GetOutputFile("StyleExamples", $"{fileName}_Out.xlsx"); - p.SaveAs(fi); - } + + return true; + }); } + [TestMethod] - public void EditedTheme() + public void ChartWithChartStyle() { - ExcelPackage.License.SetNonCommercialOrganization("EPPlus Project"); + string fileName = "ChartWithChartStyleMEdit"; - CreatePathIfNotExists("StyleExamples\\"); + //Read test for if chart style is applied appropriately + CreateStyleExampleAndExportIt(fileName, (List outputSvgs) => + { + //Create expected color + var col = Color.FromArgb(255, 217, 217, 217); + var expectedStr = ColorTranslator.ToHtml(col).ToLower(); - string fileName = "ExcelThemeEdited"; + var svgSplitOnSpace = outputSvgs[0].Split(' '); - using (var p = OpenTemplatePackage($"StyleExamples\\{fileName}.xlsx")) + //Get the first stroke and extract the hexCode for the expected color + var firstStroke = svgSplitOnSpace.First(s => s.StartsWith("stroke")); + var colorResult = firstStroke.Substring(8, 7).ToLower(); + + //Get the resulting width + var strokeWidth = svgSplitOnSpace.First(s => s.StartsWith("stroke-width")); + var widthStr = strokeWidth.Substring(14, strokeWidth.Length - 14 - 1).ToLower(); + var widthResult = double.Parse(widthStr, CultureInfo.InvariantCulture); + + //Assert + Assert.AreEqual(expectedStr, colorResult); + Assert.AreEqual(1d, widthResult, 0.003); + + return expectedStr == colorResult && 1d == Math.Round(widthResult, 1); + }); + } + + [TestMethod] + public void ReadChartBorderThemeTint() + { + var fileName = "ChartBorderThemeTint"; + + //Read test for if chart style is applied appropriately + CreateStyleExampleAndExportIt(fileName, (List outputSvgs) => { - var ws = p.Workbook.Worksheets[0]; + //Create expected color + var col = Color.FromArgb(255, 217, 217, 217); + var expectedStr = ColorTranslator.ToHtml(col).ToLower(); - foreach (var d in ws.Drawings) - { - if (d is ExcelChart c) - { - //var borderSetting = c.Border; - //var borderDirectColor = borderSetting.Fill.Color; - //var theme = p.Workbook.ThemeManager.GetOrCreateTheme(); + var svgSplitOnSpace = outputSvgs[0].Split(' '); - //var defaultColorFromTheme = theme.ColorScheme.Dark1; + //Get the first stroke and extract the hexCode for the expected color + var firstStroke = svgSplitOnSpace.First(s => s.StartsWith("stroke")); + var colorResult = firstStroke.Substring(8, 7).ToLower(); - var svg = c.ToSvg(); - SaveTextFileToWorkbook($"svg\\{fileName}_{ws.Name}_{c.Name}.svg", svg); + //Get the resulting width + var strokeWidth = svgSplitOnSpace.First(s => s.StartsWith("stroke-width")); + var widthStr = strokeWidth.Substring(14, strokeWidth.Length - 14 - 1).ToLower(); + var widthResult = double.Parse(widthStr, CultureInfo.InvariantCulture); - var theme = p.Workbook.ThemeManager.GetOrCreateTheme(); - var themeColor = tc.ColorConverter.GetThemeColor(theme, eThemeSchemeColor.Text1); - var themedLine = theme.FormatScheme.BorderStyle[0]; - //themeColor = tc.ColorConverter.ApplyTransforms(themeColor, themedLine.Fill.SolidFill.Color.Transforms); - themeColor = tc.ColorConverter.ApplyTintDrawing(themeColor, 0.55d); - var ExpectedColor = Color.FromArgb(255, 255, 199, 199); - Assert.AreEqual(ExpectedColor.ToArgb(), themeColor.ToArgb()); - } - } - var fi = GetOutputFile("StyleExamples", $"{fileName}_Out.xlsx"); - p.SaveAs(fi); - } + //Assert + Assert.AreEqual(expectedStr, colorResult); + Assert.AreEqual(1d, widthResult, 0.003); + + return expectedStr == colorResult && 1d == Math.Round(widthResult, 1); + }); + //using (var p = OpenTemplatePackage($"StyleExamples\\{fileName}.xlsx")) + //{ + // var ws = p.Workbook.Worksheets[0]; + // var lChart = ws.Drawings[0].As.Chart.LineChart; + + // lChart.StyleManager.Style.ChartArea.Border.Fill.SolidFill.Color.SetSchemeColor(OfficeOpenXml.Drawing.eSchemeColor.Accent1); + + // //100 - input is what excel seems to apply + // //lChart.StyleManager.Style.ChartArea.BorderReference.Color.Transforms.AddTint(13); + + // //Adding Less Tint makes the object Lighter. Which is the inverse of how excel does it. + // lChart.StyleManager.Style.ChartArea.Border.Fill.SolidFill.Color.Transforms.AddTint(60); + // lChart.StyleManager.Style.ChartArea.Border.Width = 10d; + // lChart.StyleManager.ApplyStyles(); + + // var fi = GetOutputFile("StyleExamples", $"{fileName}_Out.xlsx"); + // p.SaveAs(fi); + //} } [TestMethod] - public void ManualSystemText() + public void Epp_Gen_DefaultLine() { - ExcelPackage.License.SetNonCommercialOrganization("EPPlus Project"); - - string fileName = "ExcelThemeManualSystemText"; + using (var p = OpenPackage("StyleExamples\\epplusDefaultChart_Line.xlsx", true)) + { + var ws = p.Workbook.Worksheets.Add("Empty"); - CreatePathIfNotExists("StyleExamples\\"); + ws.Cells["A1:A3"].Formula = "ROW()+COLUMN()"; - using (var p = OpenTemplatePackage($"StyleExamples\\{fileName}.xlsx")) - { - var ws = p.Workbook.Worksheets[0]; + ws.Calculate(); - foreach (var d in ws.Drawings) + var emptyLines = ws.Drawings.AddLineChart("Chart", eLineChartType.Line); + foreach (ExcelDrawing d in ws.Drawings) { - if (d is ExcelChart c) - { - //var borderSetting = c.Border; - //var borderDirectColor = borderSetting.Fill.Color; - //var theme = p.Workbook.ThemeManager.GetOrCreateTheme(); + var svg = d.ToSvg(); - //var defaultColorFromTheme = theme.ColorScheme.Dark1; + SaveTextFileToWorkbook($"svg\\epplusDefaultChart_Line{ws.Name}_{d.Name}.svg", svg); - var svg = c.ToSvg(); - SaveTextFileToWorkbook($"svg\\{fileName}_{ws.Name}_{c.Name}.svg", svg); - } + //Create expected color + var fill = Color.FromArgb(255, 255, 255, 255); + var expectedFill = ColorTranslator.ToHtml(fill).ToLower(); + var col = Color.FromArgb(255, 137, 137, 137); + var expectedStroke = ColorTranslator.ToHtml(col).ToLower(); + + var svgSplitOnSpace = svg.Split(' '); + var fills = svgSplitOnSpace.Where(s => s.StartsWith("fill")).ToArray(); + + //Get the first fill and extract the hexCode for the expected color + var firstFill = fills[0]; + var fillResult = firstFill.Substring(6, 7).ToLower(); + + var secondFill = fills[1]; + var secondResult = secondFill.Substring(6, 7).ToLower(); + + //Get the first stroke and extract the hexCode for the expected color + var firstStroke = svgSplitOnSpace.First(s => s.StartsWith("stroke")); + var strokeResult = firstStroke.Substring(8, 7).ToLower(); + + //Get the resulting width + var strokeWidth = svgSplitOnSpace.First(s => s.StartsWith("stroke-width")); + var widthStr = strokeWidth.Substring(14, strokeWidth.Length - 14 - 1).ToLower(); + var widthResult = double.Parse(widthStr, CultureInfo.InvariantCulture); + + //Assert + Assert.AreEqual(expectedFill, fillResult); + Assert.AreEqual(expectedStroke, strokeResult); + Assert.AreEqual(1d, widthResult, 0.003); + //Plot area should also be white as in Excel + Assert.AreEqual(expectedFill, secondResult); } - var fi = GetOutputFile("StyleExamples", $"{fileName}_Out.xlsx"); - p.SaveAs(fi); + + SaveAndCleanup(p); } } - [TestMethod] - public void ExcelThemeLnDeleted() + public void Epp_Gen_DefaultShape() { - ExcelPackage.License.SetNonCommercialOrganization("EPPlus Project"); + string fileName = "epplusShape"; - CreatePathIfNotExists("StyleExamples\\"); + using (var p = OpenPackage($"StyleExamples\\{fileName}.xlsx", true)) + { + var ws = p.Workbook.Worksheets.Add("ws1"); - string fileName = "ExcelThemeLnDeleted"; + var defaultRect = ws.Drawings.AddShape("Default", OfficeOpenXml.Drawing.eShapeStyle.Round1Rect); + var gradientRect = ws.Drawings.AddShape("GradRect", OfficeOpenXml.Drawing.eShapeStyle.Round1Rect); - using (var p = OpenTemplatePackage($"StyleExamples\\{fileName}.xlsx")) - { - var ws = p.Workbook.Worksheets[0]; + var aThemeStyle = defaultRect.ThemeStyles.BorderReference; - foreach (var d in ws.Drawings) - { - if (d is ExcelChart c) - { - //var borderSetting = c.Border; - //var borderDirectColor = borderSetting.Fill.Color; - //var theme = p.Workbook.ThemeManager.GetOrCreateTheme(); + defaultRect.SetPosition(300, 1); + gradientRect.SetPosition(300, 1000); - //var defaultColorFromTheme = theme.ColorScheme.Dark1; + var svgDefault = defaultRect.ToSvg(); + SaveTextFileToWorkbook($"svg\\{fileName}_{ws.Name}_{defaultRect.Name}.svg", svgDefault); - var svg = c.ToSvg(); - SaveTextFileToWorkbook($"svg\\{fileName}_{ws.Name}_{c.Name}.svg", svg); - } - } - var fi = GetOutputFile("StyleExamples", $"{fileName}_Out.xlsx"); - p.SaveAs(fi); + //Create expected color + var fill = Color.FromArgb(255, 21, 96, 130); + var expectedFill = ColorTranslator.ToHtml(fill).ToLower(); + var col = Color.FromArgb(255, 4, 36, 51); + var expectedStroke = ColorTranslator.ToHtml(col).ToLower(); + + var svgSplitOnSpace = svgDefault.Split(' '); + + var fills = svgSplitOnSpace.Where(s => s.StartsWith("fill")).ToArray(); + + //Get the first fill and extract the hexCode for the expected color + var firstFill = fills[0]; + var fillResult = firstFill.Substring(6, 7).ToLower(); + + //Get the first stroke and extract the hexCode for the expected color + var firstStroke = svgSplitOnSpace.First(s => s.StartsWith("stroke")); + var strokeResult = firstStroke.Substring(8, 7).ToLower(); + + //Get the resulting width + var strokeWidth = svgSplitOnSpace.First(s => s.StartsWith("stroke-width")); + var widthStr = strokeWidth.Substring(14, strokeWidth.Length - 14 - 1).ToLower(); + var widthResult = double.Parse(widthStr, CultureInfo.InvariantCulture); + + //Assert + Assert.AreEqual(expectedFill, fillResult); + Assert.AreEqual(expectedStroke, strokeResult); + Assert.AreEqual(1d, widthResult, 0.003); + + gradientRect.Fill.Style = OfficeOpenXml.Drawing.eFillStyle.GradientFill; + + var svgGradient = gradientRect.ToSvg(); + SaveTextFileToWorkbook($"svg\\{fileName}_{ws.Name}_{gradientRect.Name}.svg", svgGradient); + + SaveAndCleanup(p); } } - [TestMethod] - public void PureExcelTheme() + public void EpplusGeneratedChart() { - ExcelPackage.License.SetNonCommercialOrganization("EPPlus Project"); + using (var p = OpenPackage("StyleExamples\\epplusDefaultTest.xlsx", true)) + { + var ws = p.Workbook.Worksheets.Add("EpplusGeneratedChart"); - string fileName = "PureExcelTheme"; + //ws.Workbook.ThemeManager.GetOrCreateTheme(); - CreatePathIfNotExists("StyleExamples\\"); + //ws.Workbook.ThemeManager.GetOrCreateTheme().FormatScheme.BackgroundFillStyle[0] = true; + ws.Cells["A1:A3"].Formula = "ROW()+COLUMN()"; - using (var p = OpenTemplatePackage($"StyleExamples\\{fileName}.xlsx")) - { - var ws = p.Workbook.Worksheets[0]; + ws.Calculate(); - foreach (var d in ws.Drawings) - { - if (d is ExcelChart c) - { - var borderSetting = c.Border; - var borderDirectColor = borderSetting.Fill.Color; - var theme = p.Workbook.ThemeManager.GetOrCreateTheme(); + var emptyLines = ws.Drawings.AddLineChart("EmptyLineChart", eLineChartType.Line); + var generatedBar = ws.Drawings.AddBarChart("EpplusBarChart", eBarChartType.ColumnClustered); - var defaultColorFromTheme = theme.ColorScheme.Dark1; + generatedBar.SetPosition(1, 1000); - var svg = c.ToSvg(); - SaveTextFileToWorkbook($"svg\\{fileName}_{ws.Name}_{c.Name}.svg", svg); - } + var defaultRect = ws.Drawings.AddShape("MyDefaultShape", OfficeOpenXml.Drawing.eShapeStyle.Round1Rect); + var gradientRect = ws.Drawings.AddShape("GradRect", OfficeOpenXml.Drawing.eShapeStyle.Round1Rect); + + defaultRect.SetPosition(300, 1); + gradientRect.SetPosition(300, 1000); + + //defaultRect.Fill.Style = OfficeOpenXml.Drawing.eFillStyle.SolidFill; + gradientRect.Fill.Style = OfficeOpenXml.Drawing.eFillStyle.GradientFill; + generatedBar.Series.Add(ws.Cells["A1:A3"]); + + List outputSvgs = new List(); + + foreach (ExcelDrawing d in ws.Drawings) + { + var svg = d.ToSvg(); + outputSvgs.Add(svg); + SaveTextFileToWorkbook($"svg\\epplusDefault{ws.Name}_{d.Name}.svg", svg); } - var fi = GetOutputFile("StyleExamples", $"{fileName}_Out.xlsx"); - p.SaveAs(fi); + //GetOutputFile("StyleExamples", ""); + SaveAndCleanup(p); } } - [TestMethod] - public void ChartWithChartStyle() + public void GenerateSimpleChart() { - ExcelPackage.License.SetNonCommercialOrganization("EPPlus Project"); + string fileName = "EpplusSimpleChart"; - string fileName = "ChartWithChartStyleMEdit"; + using (var p = OpenPackage($"{fileName}.xlsx", true)) + { + var ws = p.Workbook.Worksheets.Add("s1"); + ws.Drawings.AddBarChart("simpleChart", eBarChartType.ColumnClustered); - CreatePathIfNotExists("StyleExamples\\"); + SaveAndCleanup(p); + } + } - using (var p = OpenTemplatePackage($"StyleExamples\\{fileName}.xlsx")) + [TestMethod] + public void ReadObjectDefaults() + { + string fileName = "ObjectDefaultsChanged"; + + using (var p = OpenTemplatePackage($"{fileName}.xlsx")) { - var ws = p.Workbook.Worksheets[0]; + var theme = p.Workbook.ThemeManager.GetOrCreateTheme(); + var shapeStyle = theme.ObjectDefaults.ShapeDefinition.Style; + var fillRef = shapeStyle.FillReference; - foreach (var d in ws.Drawings) - { - if (d is ExcelChart c) - { - var svg = c.ToSvg(); - SaveTextFileToWorkbook($"svg\\{fileName}_{ws.Name}_{c.Name}.svg", svg); - } - } - var fi = GetOutputFile("StyleExamples", $"{fileName}_Out.xlsx"); - p.SaveAs(fi); + var lnRef = shapeStyle.BorderReference; + + Assert.AreEqual(eSchemeColor.Accent2, lnRef.ShapeColor.SchemeColor.Color); + Assert.IsTrue(lnRef.ShapeColor.Transforms.Count > 0); + Assert.AreEqual(eColorTransformType.Shade, lnRef.ShapeColor.Transforms[0].Type); + Assert.AreEqual(15, lnRef.ShapeColor.Transforms[0].Value); + Assert.AreEqual(2, lnRef.Index); + + Assert.IsTrue(fillRef.HasColor); + var schemeClr = fillRef.ShapeColor.SchemeColor; + var col = schemeClr.Color; + Assert.AreEqual(eSchemeColor.Accent2, col); + Assert.AreEqual(1, fillRef.Index); + + var fontRef = theme.ObjectDefaults.ShapeDefinition.Style.FontReference; + Assert.AreEqual(eSchemeColor.Light1, fontRef.Color.SchemeColor.Color); + Assert.AreEqual(eThemeFontCollectionType.Minor, fontRef.Index); + + SaveAndCleanup(p); } } } diff --git a/src/EPPlus.DrawingRenderer.Tests/Chart/ColumnChartTests.cs b/src/EPPlus.DrawingRenderer.Tests/Chart/ColumnChartTests.cs index 6d6002687e..0f69cf48d4 100644 --- a/src/EPPlus.DrawingRenderer.Tests/Chart/ColumnChartTests.cs +++ b/src/EPPlus.DrawingRenderer.Tests/Chart/ColumnChartTests.cs @@ -53,5 +53,48 @@ public void GenerateSvgForColumnCharts2() } } } + [TestMethod] + public void GenerateColumnChartFromBlazorSample() + { + ExcelPackage.License.SetNonCommercialOrganization("EPPlus Project"); + using (var p = OpenTemplatePackage("BlazorSample1-Column.xlsx")) + { + var ws = p.Workbook.Worksheets[1]; + + //var ix = 2; + //var c = ws.Drawings[ix]; + //var svg = renderer.RenderDrawingToSvg(c); + //SaveTextFileToWorkbook($"svg\\ChartForSvg_ind{ix++}.svg", svg); + + var ix = 0; + foreach (ExcelChart c in ws.Drawings) + { + var svg = c.ToSvg(); + SaveTextFileToWorkbook($"svg\\BlazorSample_Column_sheet2_{ix++}.svg", svg); + } + } + } + [TestMethod] + public void GenerateBarChartFromBlazorSample() + { + ExcelPackage.License.SetNonCommercialOrganization("EPPlus Project"); + using (var p = OpenTemplatePackage("BlazorSample1-BarChart.xlsx")) + { + var ws = p.Workbook.Worksheets[1]; + + //var ix = 2; + //var c = ws.Drawings[ix]; + //var svg = renderer.RenderDrawingToSvg(c); + //SaveTextFileToWorkbook($"svg\\ChartForSvg_ind{ix++}.svg", svg); + + var ix = 0; + foreach (ExcelChart c in ws.Drawings) + { + var svg = c.ToSvg(); + SaveTextFileToWorkbook($"svg\\BlazorSample_Bar_sheet2_{ix++}.svg", svg); + } + } + } + } } diff --git a/src/EPPlus.DrawingRenderer.Tests/Chart/LineChartToSvgTests.cs b/src/EPPlus.DrawingRenderer.Tests/Chart/LineChartToSvgTests.cs index 6d55224e92..02d264ddae 100644 --- a/src/EPPlus.DrawingRenderer.Tests/Chart/LineChartToSvgTests.cs +++ b/src/EPPlus.DrawingRenderer.Tests/Chart/LineChartToSvgTests.cs @@ -1,7 +1,12 @@ using OfficeOpenXml; using OfficeOpenXml.Drawing.Chart; +using OfficeOpenXml.Drawing.Chart.Style; +using OfficeOpenXml.Export.HtmlExport; +using OfficeOpenXml.Style; +using OfficeOpenXml.Table; using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -322,6 +327,88 @@ public void GenerateLineChartWithDropLine() } } } + [TestMethod] + public void GenerateBlazorSample1() + { + ExcelPackage.License.SetNonCommercialOrganization("EPPlus Project"); + using (var p = OpenTemplatePackage("BlazorSample1.xlsx")) + { + var ws = p.Workbook.Worksheets[1]; + + //var ix = 1; + //var c = ws.Drawings[ix]; + //var svg = c.ToSvg(); + //SaveTextFileToWorkbook($"svg\\5.3-SampleLines{ix}.svg", svg); + + for (int i = 0; i < ws.Drawings.Count; i++) + { + var c = ws.Drawings[i]; + var svg = c.ToSvg(); + SaveTextFileToWorkbook($"svg\\BlazorSample1{i}.svg", svg); + } + } + } + [TestMethod] + public async Task HtmlExportWithLineChart() + { + using (var package = new ExcelPackage()) + { + var style = TableStyles.Dark3; + var sheet = package.Workbook.Worksheets.Add("Html export sample 8"); + var csvFileInfo = new FileInfo(Path.Combine(_dataPath, $"currencies2011weekly.csv")); + if (csvFileInfo.Exists == false) return; + var format = new ExcelTextFormat + { + Delimiter = ';', + Culture = CultureInfo.InvariantCulture, + DataTypes = new eDataTypes[] { eDataTypes.DateTime, eDataTypes.Number, eDataTypes.Number, eDataTypes.Number, eDataTypes.Number } + }; + var tableRange = sheet.Cells["A15"].LoadFromText(csvFileInfo, format, style, true); + + sheet.Cells["B1:E1"].Style.HorizontalAlignment = ExcelHorizontalAlignment.Right; + sheet.Cells[tableRange.Start.Row, 1, tableRange.End.Row, 1].Style.Numberformat.Format = "yyyy-MM-dd"; + sheet.Cells[tableRange.Start.Row, 2, tableRange.End.Row, 5].Style.Numberformat.Format = "#,##0.0000"; + tableRange.AutoFitColumns(); + + var table = sheet.Tables.GetFromRange(tableRange); + table.ShowFirstColumn = true; + var chart = sheet.Drawings.AddLineChart("LineChart1", eLineChartType.Line); + + var serie1 = chart.Series.Add(tableRange.TakeColumnsBetween(1,1).SkipRows(1), tableRange.TakeColumns(1).SkipRows(1)); + serie1.HeaderAddress = sheet.Cells["B15"]; + + var serie2 = chart.Series.Add(tableRange.TakeColumnsBetween(2, 1).SkipRows(1), tableRange.TakeColumns(1).SkipRows(1)); + serie2.HeaderAddress = sheet.Cells["C15"]; + + var serie3 = chart.Series.Add(tableRange.TakeColumnsBetween(3, 1).SkipRows(1), tableRange.TakeColumns(1).SkipRows(1)); + serie3.HeaderAddress = sheet.Cells["D15"]; + + chart.SetPosition(0, 0); + chart.To.Row = 14; + chart.To.Column = 10; + chart.StyleManager.SetChartStyle(ePresetChartStyle.LineChartStyle5); + + var exporter = sheet.Cells.CreateHtmlExporter(); + var settings = exporter.Settings; + settings.Drawings.Include = eDrawingInclude.Include; + settings.Culture = CultureInfo.InvariantCulture; + settings.SetRowHeight = true; + settings.SetColumnWidth = true; + settings.TableId = "currency-table"; + settings.AdditionalTableClassNames.Add("table"); + settings.AdditionalTableClassNames.Add("table-sm"); + settings.AdditionalTableClassNames.Add("table-borderless"); + settings.Drawings.Position = eDrawingPosition.Absolute; + SaveWorkbook("HtmlExportWithLineChart.xlsx", package); + // export css and html + //var css = exporter.GetCssString(); + //var html = exporter.GetHtmlString(); + var html = await exporter.GetSinglePageAsync(); + + SaveSvg("HtmlExportWithLineChart.html", html); + } + + } //2.4-CreateAFileSystemReport.xlsx //3.3-FxReportFromDatabase.xlsx } diff --git a/src/EPPlus.DrawingRenderer.Tests/Shape/ShapeWrappingTests.cs b/src/EPPlus.DrawingRenderer.Tests/Shape/ShapeWrappingTests.cs new file mode 100644 index 0000000000..16c45c9749 --- /dev/null +++ b/src/EPPlus.DrawingRenderer.Tests/Shape/ShapeWrappingTests.cs @@ -0,0 +1,75 @@ +using OfficeOpenXml; +using OfficeOpenXml.Drawing; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EPPlus.DrawingRenderer.Tests.Shape +{ + [TestClass] + public class ShapeWrappingTests : TestBase + { + [TestMethod] + public void WrapEveryLetter() + { + using(var p = OpenPackage("WrapEveryLetterSvg.xlsx",true)) + { + var ws = p.Workbook.Worksheets.Add("wrapShapes"); + + var txtBox = ws.Drawings.AddTextbox("txtBox1", "MY WORLD"); + txtBox.As.Shape.SetSize(30, 150); + + var svg = txtBox.ToSvg(); + File.WriteAllText(GetOutputFile("svg\\", "WrapEveryLetter.svg").FullName, svg); + SaveAndCleanup(p); + } + } + + [TestMethod] + public void WrapAndColor() + { + using (var p = OpenPackage("WrapInRect.xlsx", true)) + { + var ws = p.Workbook.Worksheets.Add("wrap"); + + var _currentShape = ws.Drawings.AddShape("MyShape", OfficeOpenXml.Drawing.eShapeStyle.Rect); + _currentShape.SetSize(36, 200); + + _currentShape.Font.Color = System.Drawing.Color.Goldenrod; + _currentShape.TextBody.LeftInsert = 0; + _currentShape.TextBody.RightInsert = 0; + _currentShape.TextBody.TopInsert = 0; + _currentShape.TextBody.BottomInsert = 0; + + var rt1 = _currentShape.RichText.Add("M", true); + var rt2 = _currentShape.RichText.Add("Y ", false); + var rt3 = _currentShape.RichText.Add("W", false); + var rt4 = _currentShape.RichText.Add("O", false); + var rt5 = _currentShape.RichText.Add("R", false); + var rt6 = _currentShape.RichText.Add("L", false); + var rt7 = _currentShape.RichText.Add("D", false); + _currentShape.RichText.Add("MY WORLD", true); + + var startColor = KnownColor.Plum; + + _currentShape.RichText.Add("Default world", true); + + foreach (var item in _currentShape.RichText) + { + item.Color = System.Drawing.Color.FromKnownColor(startColor); + startColor += 1; + } + + _currentShape.TextBody.Anchor = eTextAnchoringType.Top; + + var svg = _currentShape.ToSvg(); + File.WriteAllText(GetOutputFile("svg\\", "WrapAndColor.svg").FullName, svg); + + SaveAndCleanup(p); + } + } + } +} diff --git a/src/EPPlus.DrawingRenderer.Tests/TestBase.cs b/src/EPPlus.DrawingRenderer.Tests/TestBase.cs index 6c4f4dff6f..d22ac7a764 100644 --- a/src/EPPlus.DrawingRenderer.Tests/TestBase.cs +++ b/src/EPPlus.DrawingRenderer.Tests/TestBase.cs @@ -83,6 +83,7 @@ private class GeoData protected static string _testInputPathOptional = @"c:\epplusTest\workbooks\"; //Team shared workbooks for tests protected static string _testInputLocalPathOptional = @"c:\epplusTest\workbooks\"; //Local workboks for tests protected static string _imagePath = @"c:\epplusTest\images\"; + protected static string _dataPath = @"c:\epplusTest\data\"; /// ///Gets or sets the test context which provides ///information about and functionality for the current test run. diff --git a/src/EPPlus.DrawingRenderer.Tests/TextRenderTests.cs b/src/EPPlus.DrawingRenderer.Tests/TextRenderTests.cs index d04da9f575..895f286aec 100644 --- a/src/EPPlus.DrawingRenderer.Tests/TextRenderTests.cs +++ b/src/EPPlus.DrawingRenderer.Tests/TextRenderTests.cs @@ -39,7 +39,7 @@ // var c = System.Drawing.Color.FromName(color); // if (c.IsEmpty) // { -// var sc = Enum.Parse(color); +// var sc = (eSchemeColor)Enum.Parse(typeof(eSchemeColor), color); // fill.SolidFill.Color.SetSchemeColor(sc); // } // else @@ -49,7 +49,7 @@ // } // catch // { -// var sc = Enum.Parse(color); +// var sc = (eSchemeColor)Enum.Parse(typeof(eSchemeColor), color); // fill.SolidFill.Color.SetSchemeColor(sc); // } // } @@ -76,13 +76,13 @@ // // fontSizes.Add(runFont.Size); // // } - + // // return new TextFragmentCollectionSimple(fonts, runContents); // //} // //List GetWrappedText(ExcelDrawingTextRunCollection runs, TextFragmentCollectionSimple fragments) // //{ - + // // List fonts = new List(); // // for (int i = 0; i < runs.Count(); i++) @@ -146,7 +146,7 @@ // Style = MeasurementFontStyles.Regular // }; -// List fonts = new() { /*font1,*/ font2, font3, font4, font5, font6}; +// List fonts = new() { /*font1,*/ font2, font3, font4, font5, font6 }; // var maxSizePoints = Math.Round(300d, 0, MidpointRounding.AwayFromZero).PixelToPoint(); // var ttMeasurer = OpenTypeFonts.GetTextLayoutEngineForFont(font2); @@ -224,14 +224,14 @@ // var txtRuns2 = tbItem.Paragraphs[1].Runs; -// Assert.AreEqual(53.20963541666667d, txtRuns2[0].Bounds.Width.PointToPixel(),0.2); +// Assert.AreEqual(53.20963541666667d, txtRuns2[0].Bounds.Width.PointToPixel(), 0.2); // var currentLineWidth = txtRuns2[0].Bounds.Width.PointToPixel(); -// Assert.AreEqual(currentLineWidth, txtRuns2[1].Bounds.Left.PointToPixel(),0.2); -// Assert.AreEqual(69.55924479166667d, txtRuns2[1].Bounds.Width.PointToPixel(),0.2); +// Assert.AreEqual(currentLineWidth, txtRuns2[1].Bounds.Left.PointToPixel(), 0.2); +// Assert.AreEqual(69.55924479166667d, txtRuns2[1].Bounds.Width.PointToPixel(), 0.2); // currentLineWidth += txtRuns2[1].Bounds.Width.PointToPixel(); -// Assert.AreEqual(currentLineWidth, txtRuns2[2].Bounds.Left.PointToPixel(),0.0001); +// Assert.AreEqual(currentLineWidth, txtRuns2[2].Bounds.Left.PointToPixel(), 0.0001); // Assert.AreEqual(49.89388020833334, txtRuns2[2].Bounds.Width.PointToPixel(), 0.0001); // currentLineWidth += txtRuns2[2].Bounds.Width.PointToPixel(); @@ -328,7 +328,7 @@ // //Appears off by 1-2 px bc of border width -// Assert.AreEqual(190d, tbItem.Bounds.GlobalTop.PointToPixel() , 1.0); +// Assert.AreEqual(190d, tbItem.Bounds.GlobalTop.PointToPixel(), 1.0); // } @@ -379,7 +379,7 @@ // //var trItem = new SvgTextRunItem(svgShape, lastpara.Bounds, font, "MyText"); // //lastpara.Runs.Add(trItem); -// lastpara.AddOwnText(new TextFragment() {Text = "my new text", Font = font }); +// lastpara.AddOwnText(new TextFragment() { Text = "my new text", Font = font }); // svgShape.Render(sb); // var str = sb.ToString(); diff --git a/src/EPPlus.DrawingRenderer/EPPlus.DrawingRenderer.csproj b/src/EPPlus.DrawingRenderer/EPPlus.DrawingRenderer.csproj index a8ab2f7e66..5d22f79446 100644 --- a/src/EPPlus.DrawingRenderer/EPPlus.DrawingRenderer.csproj +++ b/src/EPPlus.DrawingRenderer/EPPlus.DrawingRenderer.csproj @@ -11,6 +11,7 @@ latest True EPPlus.DrawingRenderer.snk + false diff --git a/src/EPPlus.DrawingRenderer/Svg/Core/SvgShapeRenderer.cs b/src/EPPlus.DrawingRenderer/Svg/Core/SvgShapeRenderer.cs index 786a22ba98..f3f5e993c2 100644 --- a/src/EPPlus.DrawingRenderer/Svg/Core/SvgShapeRenderer.cs +++ b/src/EPPlus.DrawingRenderer/Svg/Core/SvgShapeRenderer.cs @@ -622,8 +622,46 @@ private string GetXy(RenderItem item, UserSpaceSettings userSpace, double? angle double x1 = cx - halfX, y1 = cy - halfY; double x2 = cx + halfX, y2 = cy + halfY; + if(item.DefId == "xGridLine") + { + //If global, has to stretch the whole length. + //This case is special because it stretches in the same direction as the attempted gradient + x1 = item.Bounds.Left; + x2 = w - x1; + } + return $" x1=\"{(x1).PointToPixelString("0.00")}\" x2=\"{(x2).PointToPixelString("0.00")}\" y1=\"{y1.PointToPixelString("0.00")}\" y2=\"{y2.PointToPixelString("0.00")}\""; } + else if (angle.HasValue && angle != 0) + { + var x1 = 0D; + var x2 = 0D; + var y1 = 0D; + var y2 = 0D; + angle %= 360; + if (angle <= 90) + { + x2 = 1D - Math.Sin(MathHelper.Radians(angle.Value)); + y2 = Math.Sin(MathHelper.Radians(angle.Value)); + } + else if (angle <= 180) + { + y2 = Math.Sin(MathHelper.Radians(angle.Value)); + x1 = 1D - Math.Sin(MathHelper.Radians(angle.Value)); + } + else if (angle <= 270) + { + y1 = Math.Sin(MathHelper.Radians(angle.Value - 180)); + x1 = 1D - Math.Sin(MathHelper.Radians(angle.Value - 180)); + } + else + { + y1 = Math.Sin(MathHelper.Radians(angle.Value - 180)); + x2 = 1D - Math.Sin(MathHelper.Radians(angle.Value - 180)); + } + + return $" x1=\"{(x1).ToString("0.00%", CultureInfo.InvariantCulture)}\" x2=\"{(x2).ToString("0.00%", CultureInfo.InvariantCulture)}\" y1=\"{y1.ToString("0.00%", CultureInfo.InvariantCulture)}\" y2=\"{y2.ToString("0.00%", CultureInfo.InvariantCulture)}\""; + } return ""; } diff --git a/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontDescriptor.cs b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontDescriptor.cs index a1833327b2..8111423791 100644 --- a/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontDescriptor.cs +++ b/src/EPPlus.Export.Pdf/DocumentObjects/Fonts/PdfFontDescriptor.cs @@ -10,6 +10,7 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ +using EPPlus.Export.Pdf.Helpers; using EPPlus.Graphics; using System; using System.IO; @@ -69,7 +70,7 @@ internal override string RenderDictionary() sb.AppendFormat($"<< /Type /FontDescriptor\n" + $" /FontName /{fontName.Replace(" ", "")}\n" + $" /Flags {flags}\n" + - $" /FontBBox [{fontBBox.X} {fontBBox.Y} {fontBBox.Width} {fontBBox.Height}]\n" + + $" /FontBBox [{fontBBox.X.ToPdfStringF0()} {fontBBox.Y.ToPdfStringF0()} {fontBBox.Width.ToPdfStringF0()} {fontBBox.Height.ToPdfStringF0()}]\n" + $" /Ascent {ascent}\n" + $" /Descent {descent}\n" + $" /CapHeight {capheight}\n" + @@ -93,12 +94,12 @@ internal override void RenderDictionary(BinaryWriter bw) sb.AppendFormat($"<< /Type /FontDescriptor\n" + $" /FontName /{fontName.Replace(" ", "")}\n" + $" /Flags {flags}\n" + - $" /FontBBox [{fontBBox.X} {fontBBox.Y} {fontBBox.Width} {fontBBox.Height}]\n" + - $" /Ascent {ascent}\n" + - $" /Descent {descent}\n" + - $" /CapHeight {capheight}\n" + - $" /ItalicAngle {(int)italicAngle}\n" + - $" /StemV {(int)stemV}"); + $" /FontBBox [{fontBBox.X.ToPdfStringF0()} {fontBBox.Y.ToPdfStringF0()} {fontBBox.Width.ToPdfStringF0()} {fontBBox.Height.ToPdfStringF0()}]\n" + + $" /Ascent {ascent.ToPdfStringF0()}\n" + + $" /Descent {descent.ToPdfStringF0()}\n" + + $" /CapHeight {capheight.ToPdfStringF0()}\n" + + $" /ItalicAngle {((int)italicAngle).ToPdfStringF0()}\n" + + $" /StemV {((int)stemV).ToPdfStringF0()}"); if (FontFile2ObjectNumber > 0) { sb.AppendFormat($"\n /FontFile2 {FontFile2ObjectNumber} 0 R"); diff --git a/src/EPPlus.Export.Pdf/EPPlus.Export.Pdf.csproj b/src/EPPlus.Export.Pdf/EPPlus.Export.Pdf.csproj index 87beb9760a..eb14a2dee8 100644 --- a/src/EPPlus.Export.Pdf/EPPlus.Export.Pdf.csproj +++ b/src/EPPlus.Export.Pdf/EPPlus.Export.Pdf.csproj @@ -7,7 +7,7 @@ false true EPPlus.Export.Pdf.snk - latest + latest diff --git a/src/EPPlus.Export.Pdf/Helpers/PdfString.cs b/src/EPPlus.Export.Pdf/Helpers/PdfString.cs index d26aea06f6..6c49ff4d14 100644 --- a/src/EPPlus.Export.Pdf/Helpers/PdfString.cs +++ b/src/EPPlus.Export.Pdf/Helpers/PdfString.cs @@ -45,5 +45,14 @@ internal static string ToPdfStringF0(this double val) { return val.ToString("F0", CultureInfo.InvariantCulture); } + /// + /// Returns the value formated for use in pdf document. + /// + /// Value to turn into a string. + /// The value repsented as a string with no decimals. + internal static string ToPdfStringF0(this int val) + { + return val.ToString("F0", CultureInfo.InvariantCulture); + } } } diff --git a/src/EPPlus.Export.Pdf/Settings/PdfPageSettings.cs b/src/EPPlus.Export.Pdf/Settings/PdfPageSettings.cs index 76287e5a12..fe20194ede 100644 --- a/src/EPPlus.Export.Pdf/Settings/PdfPageSettings.cs +++ b/src/EPPlus.Export.Pdf/Settings/PdfPageSettings.cs @@ -200,8 +200,8 @@ public PdfScaling Scaling internal string defaultFontName = ""; //DEBUG - internal bool Debug = false; - internal bool PrintAsText = false; + internal bool Debug = true; + internal bool PrintAsText = true; public PdfPageSettings(OpenTypeFontEngine fontEngine) { diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs index cb578c0e66..214963a2a0 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs @@ -57,7 +57,7 @@ public class TextLineSimple public double GetWidthWithoutTrailingSpaces() { var trailingSpaceCount = 0; - + if (string.IsNullOrEmpty(Text)) return 0D; for (int i = Text.Count() - 1; i > 0; i--) { if (Text[i] != ' ') diff --git a/src/EPPlus.Fonts.OpenType/Integration/RichText/LayoutSystem.cs b/src/EPPlus.Fonts.OpenType/Integration/RichText/LayoutSystem.cs index ce14476ae4..df5e09b552 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/RichText/LayoutSystem.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/RichText/LayoutSystem.cs @@ -261,7 +261,7 @@ public TextLineCollection Wrap(double maxWidth) for (int i = 1; i < wrappedLines.Count-1; i++) { var startIdx = wrappedLines[i].InternalLineFragments[0].StartOriginal; - var len = wrappedLines[i].Text.Length; + var len = wrappedLines[i].Text?.Length??0; for(int j = startIdx; j< (startIdx + len); j++) { AllChars[j].Line = i; diff --git a/src/EPPlus.Interfaces/EPPlus.Interfaces.csproj b/src/EPPlus.Interfaces/EPPlus.Interfaces.csproj index 6ae5284ef3..346b81e4e2 100644 --- a/src/EPPlus.Interfaces/EPPlus.Interfaces.csproj +++ b/src/EPPlus.Interfaces/EPPlus.Interfaces.csproj @@ -1,9 +1,9 @@  net10.0;net9.0;net8.0;netstandard2.1;netstandard2.0;net462 - 8.6.2.0 - 8.6.2.0 - 8.6.2 + 9.0.0.0 + 9.0.0.0 + 9.0.0-preview true license.md git diff --git a/src/EPPlus.System.Drawing/EPPlus.System.Drawing.csproj b/src/EPPlus.System.Drawing/EPPlus.System.Drawing.csproj index 2560da378d..356ea70064 100644 --- a/src/EPPlus.System.Drawing/EPPlus.System.Drawing.csproj +++ b/src/EPPlus.System.Drawing/EPPlus.System.Drawing.csproj @@ -2,9 +2,9 @@ net10.0;net9.0;net8.0;netstandard2.1;netstandard2.0;net462 - 8.6.2.0 - 8.6.2.0 - 8.6.2 + 9.0.0.0 + 9.0.0.0 + 9.0.0-preview true license.md true diff --git a/src/EPPlus/Core/AutofitHelper.cs b/src/EPPlus/Core/AutofitHelper.cs index e1db3e54a0..52a271741d 100644 --- a/src/EPPlus/Core/AutofitHelper.cs +++ b/src/EPPlus/Core/AutofitHelper.cs @@ -28,7 +28,7 @@ namespace OfficeOpenXml.Core internal class AutofitHelper { // Approximate width in pixels (at 96 DPI) of the autofilter dropdown arrow rendered by Excel. - private const double AutoFilterArrowWidthPixels = 15d; + private const double AutoFilterArrowWidthPixels = 19d; private ExcelRangeBase _range; ITextMeasurer _genericMeasurer = new GenericFontMetricsTextMeasurer(); MeasurementFont _nonExistingFont = new MeasurementFont() { FontFamily = FontSize.NonExistingFont }; diff --git a/src/EPPlus/Drawing/Chart/ExcelChart.cs b/src/EPPlus/Drawing/Chart/ExcelChart.cs index a0a94f4968..1990f59f39 100644 --- a/src/EPPlus/Drawing/Chart/ExcelChart.cs +++ b/src/EPPlus/Drawing/Chart/ExcelChart.cs @@ -73,6 +73,23 @@ private void Init(ExcelDrawings drawings, XmlDocument chartXml) #endregion internal ExcelChartStyleManager _styleManager = null; + internal readonly static HashSet _svgSupportedChartTypes = new HashSet() + { + eChartType.Line, + eChartType.LineMarkers, + eChartType.LineMarkersStacked, + eChartType.LineStacked, + eChartType.LineStacked100, + eChartType.LineMarkersStacked100, + eChartType.ColumnClustered, + eChartType.ColumnStacked, + eChartType.ColumnStacked100, + eChartType.BarClustered, + eChartType.BarStacked, + eChartType.BarStacked100, + eChartType.Pie, + eChartType.PieExploded, + }; /// /// Manage style settings for the chart /// @@ -1240,5 +1257,9 @@ internal override void SaveDrawing(bool hasLoadedPivotTables) cs.Drawings.DrawingXml.Save(xrd); } } + /// + /// Returns true if the chart supports svg export via the . + /// + public override bool SupportsSvgExport => _svgSupportedChartTypes.Contains(ChartType); } } diff --git a/src/EPPlus/Drawing/Chart/ExcelChartAxis.cs b/src/EPPlus/Drawing/Chart/ExcelChartAxis.cs index e5fec2f806..89c895e1e2 100644 --- a/src/EPPlus/Drawing/Chart/ExcelChartAxis.cs +++ b/src/EPPlus/Drawing/Chart/ExcelChartAxis.cs @@ -132,6 +132,9 @@ public string Format } } } + /// + /// Returns the number format code for the axis. If the axis has no format code, it will return the format code of the first series in the chart that uses this axis. If no series has a format code, it will return an empty string. + /// public string FormatOrFirstValueFormat { get @@ -144,19 +147,11 @@ public string FormatOrFirstValueFormat { if(ct.XAxis.Id == Id) { - foreach (var serie in ct.Series) - { - if(string.IsNullOrEmpty(serie.XSeries)) - { - continue; - } - var adr = new ExcelAddressBase(serie.XSeries); - var ws = wb.Worksheets[adr.WorkSheetName]; - if(ws!=null) - { - return ws.Cells[adr.Address].Style.Numberformat.Format; - } - } + return GetFormatFromSeries(ct, true); + } + else if (ct.YAxis.Id == Id) + { + return GetFormatFromSeries(ct, false); } } return ""; @@ -165,6 +160,26 @@ public string FormatOrFirstValueFormat return f; } } + + private string GetFormatFromSeries(ExcelChart ct, bool isXAxis) + { + foreach (var serie in ct.Series) + { + var address = isXAxis ? serie.XSeries : serie.Series; + if (string.IsNullOrEmpty(address)) + { + continue; + } + var adr = new ExcelAddressBase(address); + var ws = _chart.WorkSheet.Workbook.Worksheets[adr.WorkSheetName]; + if (ws != null) + { + return ws.Cells[adr.Address].FirstOrDefault(x=>string.IsNullOrEmpty(x.Style.Numberformat.Format)==false).Style.Numberformat.Format ?? ""; + } + } + return ""; + } + /// /// The Numberformats are linked to the source data. /// diff --git a/src/EPPlus/Drawing/Chart/ExcelChartAxisStandard.cs b/src/EPPlus/Drawing/Chart/ExcelChartAxisStandard.cs index 67345c5f20..7de2abf586 100644 --- a/src/EPPlus/Drawing/Chart/ExcelChartAxisStandard.cs +++ b/src/EPPlus/Drawing/Chart/ExcelChartAxisStandard.cs @@ -15,6 +15,7 @@ Date Author Change using OfficeOpenXml.FormulaParsing.Excel.Functions.Information; using OfficeOpenXml.FormulaParsing.Excel.Functions.MathFunctions; using OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup; +using OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup.Sorting; using OfficeOpenXml.FormulaParsing.Excel.Functions.Text; using OfficeOpenXml.FormulaParsing.Utilities; using OfficeOpenXml.Style.XmlAccess; @@ -919,7 +920,7 @@ internal override List GetAxisValues(out bool isCount, out bool isNumeri { if (dl[0] is object[]) { - dl = dl.OrderBy(x => ((object[])x)[3]).ToList(); + dl = dl.OrderBy(x => ((object[])x)[3], new SortByComparer()).ToList(); } else { diff --git a/src/EPPlus/Drawing/Chart/ExcelPieChart.cs b/src/EPPlus/Drawing/Chart/ExcelPieChart.cs index 541abfca35..d4e0968520 100644 --- a/src/EPPlus/Drawing/Chart/ExcelPieChart.cs +++ b/src/EPPlus/Drawing/Chart/ExcelPieChart.cs @@ -123,6 +123,5 @@ internal set /// A collection of series for a Pie Chart /// public new ExcelChartSeries Series { get; } = new ExcelChartSeries(); - } } diff --git a/src/EPPlus/Drawing/Chart/Style/ExcelChartStyleReference.cs b/src/EPPlus/Drawing/Chart/Style/ExcelChartStyleReference.cs index 194869e0f3..6f90f72312 100644 --- a/src/EPPlus/Drawing/Chart/Style/ExcelChartStyleReference.cs +++ b/src/EPPlus/Drawing/Chart/Style/ExcelChartStyleReference.cs @@ -10,6 +10,7 @@ Date Author Change ************************************************************************************************* 01/27/2020 EPPlus Software AB Initial release EPPlus 5 *************************************************************************************************/ +using OfficeOpenXml.Drawing.Shape.Style; using System; using System.Globalization; using System.Xml; @@ -19,29 +20,14 @@ namespace OfficeOpenXml.Drawing.Chart.Style /// /// A reference from a chart style to the theme collection /// - public class ExcelChartStyleReference : XmlHelper + public class ExcelChartStyleReference : ShapeStyleReference { string _path; - internal ExcelChartStyleReference(XmlNamespaceManager nsm, XmlNode topNode, string path) : base(nsm, topNode) + internal ExcelChartStyleReference(XmlNamespaceManager nsm, XmlNode topNode, string path) : base(nsm, topNode, path) { _path = path; } - /// - /// The index to the theme style matrix. - /// - /// - public int Index - { - get - { - return GetXmlNodeInt($"{_path}/@idx"); - } - set - { - if (value < 0) throw new ArgumentOutOfRangeException("Index", "Can't be negative"); - SetXmlNodeString($"{_path}/@idx", value.ToString(CultureInfo.InvariantCulture)); - } - } + ExcelChartStyleColorManager _color = null; /// /// The color to be used for the reference. @@ -59,16 +45,5 @@ public ExcelChartStyleColorManager Color return _color; } } - /// - /// If the reference has a color - /// - public bool HasColor - { - get - { - var node = GetNode(_path); - return node!=null && node.HasChildNodes; - } - } } } \ No newline at end of file diff --git a/src/EPPlus/Drawing/ExcelDrawing.cs b/src/EPPlus/Drawing/ExcelDrawing.cs index 7ba71d3f5f..1b5d38fa86 100644 --- a/src/EPPlus/Drawing/ExcelDrawing.cs +++ b/src/EPPlus/Drawing/ExcelDrawing.cs @@ -20,6 +20,7 @@ Date Author Change using OfficeOpenXml.Drawing.Controls; using OfficeOpenXml.Drawing.OleObject; using OfficeOpenXml.Drawing.Slicer; +using OfficeOpenXml.Export.HtmlExport; using OfficeOpenXml.FormulaParsing.Excel.Functions.Text; using OfficeOpenXml.Packaging; using OfficeOpenXml.Utils.Drawings; @@ -1764,7 +1765,11 @@ public ExcelGroupShape ParentGroup } internal ExcelDrawingCustomGeometry CustomGeom { get; private set; } - + /// + /// Returns true if the drawing supports svg export via the . + /// + public virtual bool SupportsSvgExport { get => false; } + internal eDrawingInclude? IncludeInHtmlExport { get; set; } = eDrawingInclude.Include; internal virtual void DeleteMe() { TopNode.ParentNode.RemoveChild(TopNode); @@ -2595,6 +2600,7 @@ internal virtual void SaveDrawing(bool hasLoadedPivotTables) /// /// Converts the drawing to a SVG image. /// This is currently only supported for shapes, line-, column-, bar- and pie- charts. + /// Please use to verify svg export is supported. /// /// The svg image. /// If the drawing type is not supported diff --git a/src/EPPlus/Drawing/ExcelPicture.cs b/src/EPPlus/Drawing/ExcelPicture.cs index aeecc7c3b0..aeee3e88ac 100644 --- a/src/EPPlus/Drawing/ExcelPicture.cs +++ b/src/EPPlus/Drawing/ExcelPicture.cs @@ -628,5 +628,9 @@ public bool VerticalFlip internal PictureLocation LocationType = PictureLocation.None; internal ZipPackageRelationship LinkedImageRel = null; + /// + /// Returns true if the drawing supports svg export via the . + /// + public override bool SupportsSvgExport => true; } } \ No newline at end of file diff --git a/src/EPPlus/Drawing/ExcelShape.cs b/src/EPPlus/Drawing/ExcelShape.cs index 7af5c06558..6467461daa 100644 --- a/src/EPPlus/Drawing/ExcelShape.cs +++ b/src/EPPlus/Drawing/ExcelShape.cs @@ -104,6 +104,9 @@ public string ToSvg(SvgRenderOptions options) svg.Render(sr.RenderItems); return sb.ToString(); } - + /// + /// Returns true if the drawing supports svg export via the . + /// + public override bool SupportsSvgExport => true; } } diff --git a/src/EPPlus/Drawing/Renderer/Chart/Axis/DateAxisScaleCalculator.cs b/src/EPPlus/Drawing/Renderer/Chart/Axis/DateAxisScaleCalculator.cs index 30551fb198..e0e3c9802f 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/Axis/DateAxisScaleCalculator.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/Axis/DateAxisScaleCalculator.cs @@ -36,7 +36,8 @@ internal static AxisScale Calculate(double dataMin, double dataMax, AxisOptions Max = axisMax, MajorInterval = majorValue, MajorDateUnit = majorUnit, - MinorDateUnit = majorUnit + MinorDateUnit = majorUnit, + TextOrientation = eTextOrientation.Horizontal }; } // Excel's date serial epoch: Dec 30, 1899 @@ -315,46 +316,89 @@ internal static AxisScale CalculateByWidthAllowDiagonal(List values, dou else { GetStartInterval(values.Count, max - min, out interval, out unit); - //Get interval for maximum width with vertical text. - while (FitAsVerticalDiagonalText(min, max, interval, unit, res.Height, res.Height * 0.3, plotAreaWidth) == false) + if(ax.TextBody.Rotation.HasValue==false || ax.TextBody.Rotation==-1000) { - AddIntervall(ref interval, ref unit); - } + //Get interval for maximum width with vertical text. + while (FitAsVerticalDiagonalText(min, max, interval, unit, res.Height, res.Height * 0.3, plotAreaWidth) == false) + { + AddIntervall(ref interval, ref unit); + } - //Get max text width when using diagonal text - var width = mf.Size * Math.Sqrt(2); - var margin = mf.Size * 0.5; + //Get max text width when using diagonal text + var width = mf.Size * Math.Sqrt(2); + var margin = mf.Size * 0.5; - if (FitAsVerticalDiagonalText(min, max, interval, unit, width, margin, plotAreaWidth)) //Check diagonal - { - if (FitAsHorizontalText(tm, options, min, max, interval, unit, res.Height, plotAreaWidth)) //Check horizontal + if (FitAsVerticalDiagonalText(min, max, interval, unit, width, margin, plotAreaWidth)) //Check diagonal { - return new AxisScale() + if (FitAsHorizontalText(tm, options, min, max, interval, unit, res.Height, plotAreaWidth)) //Check horizontal + { + return new AxisScale() + { + MajorInterval = interval, + MinorInterval = 1, + MinorDateUnit = unit, + MajorDateUnit = unit, + Min = min, + Max = max, + TextOrientation = eTextOrientation.Horizontal, + }; + } + else { - MajorInterval = interval, - MinorInterval = 1, - MinorDateUnit = unit, - MajorDateUnit = unit, - Min = min, - Max = max, - TextOrientation = eTextOrientation.Horizontal, - }; + return new AxisScale() + { + MajorInterval = interval, + MinorInterval = 1, + MinorDateUnit = unit, + MajorDateUnit = unit, + Min = min, + Max = max, + TextOrientation = eTextOrientation.Diagonal, + }; + } } - else + } + else + { + var rot = ax.TextBody.Rotation.Value % 360; + var sin = Math.Sin(MathHelper.Radians(rot)); + var cos = Math.Cos(MathHelper.Radians(rot)); + var width = res.Width * cos + res.Height * sin; + //Get interval for maximum width with vertical text. + while (FitAsVerticalDiagonalText(min, max, interval, unit, width, width * 0.3, plotAreaWidth) == false) { - return new AxisScale() - { - MajorInterval = interval, - MinorInterval = 1, - MinorDateUnit = unit, - MajorDateUnit = unit, - Min = min, - Max = max, - TextOrientation = eTextOrientation.Diagonal, - }; + AddIntervall(ref interval, ref unit); } + eTextOrientation orientation; + switch(rot) + { + case 45: + case 315: + orientation = eTextOrientation.Diagonal; + break; + case 90: + case 270: + orientation = eTextOrientation.Vertical; + break; + case 0: + case 180: + orientation = eTextOrientation.Horizontal; + break; + default: + orientation = eTextOrientation.Custom; + break; + } + return new AxisScale() + { + MajorInterval = interval, + MinorInterval = 1, + MinorDateUnit = unit, + MajorDateUnit = unit, + Min = min, + Max = max, + TextOrientation = orientation, + }; } - } return new AxisScale() @@ -464,7 +508,6 @@ private static bool FitAsHorizontalText(ITextMeasurer tm, AxisOptions options, d var horizontalWidth = 0D; var nf = options.NumberFormat; var mf = options.Axis.Font.GetMeasureFont(); - var angMult = Math.Sin(MathHelper.Radians(45)); while (date < maxDate) { var textWidth = tm.MeasureText(date.ToString(), mf).Width; diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartAreaRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartAreaRenderer.cs index 4168999ec0..f712910270 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartAreaRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartAreaRenderer.cs @@ -13,13 +13,13 @@ Date Author Change using EPPlus.DrawingRenderer.RenderItems; using EPPlus.DrawingRenderer.Svg; using OfficeOpenXml.Drawing; -using OfficeOpenXml.Drawing.Renderer.Chart.ChartElementStyleTables; +using OfficeOpenXml.Drawing.Renderer.Chart.Defaults; using System.Collections.Generic; using System.Drawing; namespace EPPlusImageRenderer.Svg { - internal class ChartAreaRenderer : ChartDrawingObjectWithDefaults + internal class ChartAreaRenderer : ChartDrawingDefaultObject { public ChartAreaRenderer(ChartRenderer sc, SvgRenderOptions options) : base(sc) { @@ -35,20 +35,19 @@ public ChartAreaRenderer(ChartRenderer sc, SvgRenderOptions options) : base(sc) Rectangle = new RectRenderItem(sc.Bounds); } - internal override Color? DefaultFillColor { get => ChartRenderer.Theme.ColorScheme.Light1.GetColor(); } - internal override Color? DefaultBorderColor - { - get - { - return Color.FromArgb(0x89, 0x89, 0x89); - } - } + internal override Color? DefaultFillColor { get => GetDefaultFillColor(); } + internal override Color? DefaultBorderColor { get => GetDefaultBorderColor(); } public override void AppendRenderItems(List renderItems) { renderItems.Add(Rectangle); } + internal override Color? GetDefaultFillColor() + { + return GetDefaultFillColorForElement(ChartElement.ChartArea, (int)Chart.Style); + } + internal override Color? GetDefaultBorderColor() { //We only get here if the node is null or empty @@ -57,10 +56,5 @@ public override void AppendRenderItems(List renderItems) //var themedLine = GetThemedLine(ChartElement.ChartArea, (int)Chart.Style, out Color? lineCol); return lineColor; } - - internal override Color? GetDefaultFillColor() - { - return GetDefaultFillColorForElement(ChartElement.ChartArea, (int)Chart.Style); - } } } diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs index 9bd578f978..7bba7b2df8 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs @@ -25,7 +25,9 @@ Date Author Change using OfficeOpenXml.Drawing; using OfficeOpenXml.Drawing.Chart; using OfficeOpenXml.Drawing.Chart.Style; +using OfficeOpenXml.Drawing.Renderer.Chart.Defaults; using OfficeOpenXml.Drawing.Renderer.TextBox; +using OfficeOpenXml.FormulaParsing.Excel.Functions; using OfficeOpenXml.FormulaParsing.Excel.Functions.DateAndTime; using OfficeOpenXml.FormulaParsing.Excel.Functions.Logical; using OfficeOpenXml.FormulaParsing.Excel.Functions.MathFunctions; @@ -36,12 +38,13 @@ Date Author Change using OfficeOpenXml.Utils.TypeConversion; using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; using System.Security.AccessControl; namespace EPPlusImageRenderer.Svg { - internal class ChartAxisRenderer : ChartDrawingObject, IDrawingChartAxis + internal class ChartAxisRenderer : ChartDrawingDefaultObject, IDrawingChartAxis { private const double COS45 = 0.70710678118654757; //Constant for Math.Sin(Math.PI / 4) --45 degrees @@ -74,9 +77,9 @@ internal ChartAxisRenderer(ChartRenderer sc, ExcelChartAxisStandard ax) : base(s Min = min ?? 0D; Max = max ?? (Values.Count > 0 ? ConvertUtil.GetValueDouble(Values[Values.Count - 1], false, true) : 0D); MajorUnit = majorUnit ?? 1; - if (AutoAxisType == eAxisType.Cat || (dateUnit.HasValue && dateUnit == eTimeUnit.Days)) + if (AutoAxisType == eAxisType.Cat || IsDateAutoAxis || IsDateScale) { - MinorUnit = 1; + MinorUnit = ax.MinorUnit ?? 1; } else { @@ -120,7 +123,7 @@ internal ChartAxisRenderer(ChartRenderer sc, ExcelChartAxisStandard ax) : base(s Rectangle.FillColor = "none"; Line = new LineRenderItem(Rectangle.Bounds); - Line.SetDrawingPropertiesBorder(ChartRenderer.Theme, ax.Border, sc.Chart.StyleManager.Style?.Title.BorderReference.Color, ax.Border.IsEmpty==true || ax.Border.Fill.Style != eFillStyle.NoFill, DefaultBorderColor, 1); + Line.SetDrawingPropertiesBorder(ChartRenderer.Theme, ax.Border, sc.Chart.StyleManager.Style?.Title.BorderReference.Color, ax.Border.IsEmpty==true || ax.Border.Fill.Style != eFillStyle.NoFill, GetDefaultBorderColor, 1); if(Line.BorderWidth < 1) { Line.BorderWidth = 1; @@ -296,19 +299,21 @@ public override void AppendRenderItems(List renderItems) internal void AddTickmarksAndValues(List DefItems) { - if (Axis.Deleted == true) return; - if (Axis.MajorTickMark != eAxisTickMark.None) + if (Axis.Deleted == false) { - MajorTickMarkPositions = AddTickmarks(MajorUnit, MajorDateUnit, double.NaN, 4D.PixelToPoint(), Axis.MajorTickMark); - } + if (Axis.MajorTickMark != eAxisTickMark.None) + { + MajorTickMarkPositions = AddTickmarks(MajorUnit, MajorDateUnit, double.NaN, 4D.PixelToPoint(), Axis.MajorTickMark); + } - if (Axis.MinorTickMark != eAxisTickMark.None && MinorUnit < MajorUnit) - { - MinorTickMarkPositions = AddTickmarks(MinorUnit, MajorDateUnit, MajorUnit, 2D.PixelToPoint(), Axis.MinorTickMark); - } - else - { - MinorTickMarkPositions = null; + if (Axis.MinorTickMark != eAxisTickMark.None && MinorUnit < MajorUnit) + { + MinorTickMarkPositions = AddTickmarks(MinorUnit, MajorDateUnit, MajorUnit, 2D.PixelToPoint(), Axis.MinorTickMark); + } + else + { + MinorTickMarkPositions = null; + } } if(Axis.HasMajorGridlines) @@ -357,10 +362,15 @@ private List GetAxisValueTextBoxes() maxWidth = (Rectangle.Width + Rectangle.Height) / COS45; maxHeight = ChartRenderer.ChartArea.Rectangle.Height / 3; //TODO: Check this value. break; - default: + case eTextOrientation.Horizontal: maxWidth = Rectangle.Width / AxisValues.Count; maxHeight = ChartRenderer.ChartArea.Rectangle.Height / 3; //TODO: Check this value. break; + default: // custom + var radRot = MathHelper.Radians(Axis.TextBody.Rotation.Value); + maxWidth = (Rectangle.Width * Math.Sin(radRot) + Rectangle.Height * Math.Cos(radRot)) ; + maxHeight = ChartRenderer.ChartArea.Rectangle.Height / 3; //TODO: Check this value. + break; } } double widest=0; @@ -502,7 +512,7 @@ private List GetAxisValueTextBoxes() var min = ConvertUtil.GetValueDouble(Values[0]); var max = ConvertUtil.GetValueDouble(Values.Last()); var minUnit = (max - min) / MinorUnit; - majorWidth = (min - min) / minUnit; + majorWidth = Rectangle.Width / minUnit; } else { @@ -548,7 +558,18 @@ private List GetAxisValueTextBoxes() { if (!(Axis.CrossingAxis == null || Axis.CrossingAxis.CrossBetween == eCrossBetween.MidCat)) { - var majorWidth = Rectangle.Width / AxisValues.Count; + double majorWidth; + if (IsDateAutoAxis || IsDateScale) + { + var min = ConvertUtil.GetValueDouble(Values[0]); + var max = ConvertUtil.GetValueDouble(Values.Last()); + var minUnit = (max - min) / MinorUnit; + majorWidth = Rectangle.Width / minUnit; + } + else + { + majorWidth = Rectangle.Width / AxisValues.Count; + } foreach (var tb in ret) { tb.Left += majorWidth / 2; @@ -688,13 +709,20 @@ private List AddTickmarks(double units, eTimeUnit? dateUnit, dou if (Axis.AxisType == eAxisType.Cat && IsDateAutoAxis==false) { min = 0; - if (Axis.CrossingAxis==null || Axis.CrossingAxis.CrossBetween == eCrossBetween.Between) + if(AxisValues != null) { - max = AxisValues.Count; + if (Axis.CrossingAxis == null || Axis.CrossingAxis.CrossBetween == eCrossBetween.Between) + { + max = AxisValues.Count; + } + else + { + max = AxisValues.Count - 1; + } } else { - max = AxisValues.Count - 1; + max = 0; } } else @@ -720,7 +748,9 @@ private List AddTickmarks(double units, eTimeUnit? dateUnit, dou while (d <= maxPos) { var addPosition = (d - min); - if (double.IsNaN(parentUnit) || (addPosition % parentUnit != 0)) + if (double.IsNaN(parentUnit) || + (dateUnit.HasValue==false && addPosition % parentUnit != 0) || + (dateUnit.HasValue==true && IsMinorDateUnit(dateUnit.Value, parentUnit, d))) { double x1, y1, x2, y2; switch (Axis.ActualAxisPosition) @@ -761,7 +791,7 @@ private List AddTickmarks(double units, eTimeUnit? dateUnit, dou tm.Y1 = y1; tm.X2 = x2; tm.Y2 = y2; - tm.SetDrawingPropertiesBorder(ChartRenderer.Theme, Axis.Border, axisStyle?.BorderReference.Color, true, DefaultBorderColor, 0.75); + tm.SetDrawingPropertiesBorder(ChartRenderer.Theme, Axis.Border, axisStyle?.BorderReference.Color, true, GetDefaultBorderColor, 0.75); if(tm.BorderWidth < 0.75) //Excel seems to have this as minimum width for tick marks, so we enforce it here to make sure they are visible. { tm.BorderWidth = 0.75; @@ -793,6 +823,26 @@ private List AddTickmarks(double units, eTimeUnit? dateUnit, dou } return tms; } + + private bool IsMinorDateUnit(eTimeUnit dateUnit, double parentUnit, double d) + { + switch(dateUnit) + { + case eTimeUnit.Days: + return d % parentUnit != 0; + case eTimeUnit.Months: + var minDt = DateTime.FromOADate(Min); + var dt = DateTime.FromOADate(d); + return minDt.Month % parentUnit != dt.Month % parentUnit; + case eTimeUnit.Years: + minDt = DateTime.FromOADate(Min); + dt = DateTime.FromOADate(d); + return minDt.Year % parentUnit != dt.Year % parentUnit; + default: + throw new InvalidOperationException("Invalid date unit"); + } + } + private List AddGridlines(double units, double parentUnit, ExcelDrawingBorder lineItem, ExcelChartStyleEntry styleEntry) { var axisStyle = GetAxisStyleEntry(); @@ -810,7 +860,7 @@ private List AddGridlines(double units, double parentUnit, ExcelDraw var pa = ChartRenderer.Plotarea; var diff = Max - min; - List points = new List(); + List points = new List(); var group = ChartRenderer.Plotarea.Group; for (double d = min; d <= Max; d += units) { @@ -821,12 +871,12 @@ private List AddGridlines(double units, double parentUnit, ExcelDraw { case eAxisPosition.Left: case eAxisPosition.Right: - points.Add(new Point(0f, (float)(pa.Rectangle.Height - ((d - min) / diff * pa.Rectangle.Height)))); + points.Add(new EPPlus.Graphics.Point(0f, (float)(pa.Rectangle.Height - ((d - min) / diff * pa.Rectangle.Height)))); break; case eAxisPosition.Top: case eAxisPosition.Bottom: var xValue = (float)(((d - min) / diff * pa.Rectangle.Width)); - points.Add(new Point(xValue, 0f)); + points.Add(new EPPlus.Graphics.Point(xValue, 0f)); break; default: throw new InvalidOperationException("Invalid axis position."); @@ -866,8 +916,13 @@ private List AddGridlines(double units, double parentUnit, ExcelDraw tm.Y1 = y1; tm.X2 = x2; tm.Y2 = y2; + + if(id == "xGridLine") + { + tm.Bounds.Width = pa.Rectangle.Width; + } //var lineWidth = lineItem.Width <= 0 ? 0.75 : lineItem.Width; - tm.SetDrawingPropertiesBorder(ChartRenderer.Theme, lineItem, styleEntry?.BorderReference.Color, true, ChartRenderer.Theme.ColorScheme.Dark1.GetColor(), 0.75); + tm.SetDrawingPropertiesBorder(ChartRenderer.Theme, lineItem, styleEntry?.BorderReference.Color, true, GetDefaultBorderColor, 0.75); tm.DefId = id; @@ -1203,5 +1258,35 @@ private bool ShouldHavePadding() { return Axis.AxisType == eAxisType.Val || (Chart.IsTypeLine() && Axis.AxisType == eAxisType.Date); } + + internal override Color? GetDefaultFillColor() + { + return GetDefaultFillColorForElement(ChartElement.Axis, (int)Chart.Style); + } + + internal override Color? GetDefaultBorderColor() + { + return GetDefaultBorderColorForElement(ChartElement.Axis, (int)Chart.Style); + } + + internal double GetCrossesValue() + { + if (Axis.CrossingAxis.CrossesAt.HasValue) + { + return Axis.CrossingAxis.CrossesAt.Value; + } + else + { + switch (Axis.CrossingAxis.Crosses) + { + case eCrosses.Min: + return Min; + case eCrosses.Max: + return Max; + default: + return 0D; + } + } + } } } \ No newline at end of file diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs index dccce0305f..7330b957ab 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs @@ -84,7 +84,7 @@ internal ChartLegendRenderer(ChartRenderer sc) : base(sc) } Rectangle.SetDrawingPropertiesFill(sc.Theme, l.Fill, sc.Chart.StyleManager.Style?.Title.FillReference.Color, UserSpaceSettings.UserSpaceOnUse_Global, DefaultFillColor); - Rectangle.SetDrawingPropertiesBorder(sc.Theme, l.Border, sc.Chart.StyleManager.Style?.Legend.BorderReference.Color, l.Border.Fill.Style != eFillStyle.NoFill, DefaultBorderColor, 0.75); + Rectangle.SetDrawingPropertiesBorder(sc.Theme, l.Border, sc.Chart.StyleManager.Style?.Legend.BorderReference.Color, l.Border.Fill.Style != eFillStyle.NoFill, () => DefaultBorderColor, 0.75); var pSls = SetLegendSeries(entryWidth, entryHeight); SetLegendTrendlines(entryWidth, entryHeight, pSls); @@ -854,7 +854,8 @@ private LineRenderItem GetLineSeriesIcon(ExcelChart ct, ExcelChartStandardSerie { var line = new LineRenderItem(Rectangle.Bounds); //line.SetDrawingPropertiesFill(ChartRenderer.Theme, cStandardSerie.Fill, Chart.StyleManager.Style?.SeriesLine.FillReference.Color, false, ChartRenderer.Theme.ColorScheme.Accent1.GetColor()); - line.SetDrawingPropertiesBorder(ChartRenderer.Theme, cStandardSerie.Border, Chart.StyleManager.Style?.SeriesLine.BorderReference.Color, cStandardSerie.Border.IsEmpty || cStandardSerie.Border.Fill.Style != eFillStyle.NoFill, ChartRenderer.Theme.ColorScheme.Accent1.GetColor(), 3); + //Default style is NoLine NoFill + line.SetDrawingPropertiesBorder(ChartRenderer.Theme, cStandardSerie.Border, Chart.StyleManager.Style?.SeriesLine.BorderReference.Color, cStandardSerie.Border.IsEmpty || cStandardSerie.Border.Fill.Style != eFillStyle.NoFill, () => Color.Empty, 3); double iconTop = 0, iconLeft = 0; pSls?.GetIconTopLeft(out iconTop, out iconLeft); @@ -872,7 +873,8 @@ private LineRenderItem GetTrendLineSeriesIcon(ExcelChart ct, ExcelChartTrendline { var line = new LineRenderItem(Rectangle.Bounds); line.SetDrawingPropertiesFill(ChartRenderer.Theme, tl.Fill, Chart.StyleManager.Style?.Trendline.FillReference.Color, UserSpaceSettings.UserSpaceOnUse_Global, DefaultFillColor); - line.SetDrawingPropertiesBorder(ChartRenderer.Theme, tl.Border, Chart.StyleManager.Style?.Trendline.BorderReference.Color, tl.Border.Fill.Style != eFillStyle.NoFill, DefaultBorderColor, 0.75); + //Default is actually NoLine + line.SetDrawingPropertiesBorder(ChartRenderer.Theme, tl.Border, Chart.StyleManager.Style?.Trendline.BorderReference.Color, tl.Border.Fill.Style != eFillStyle.NoFill, () => DefaultBorderColor, 0.75); double iconTop = 0, iconLeft = 0; pSls?.GetIconTopLeft(out iconTop, out iconLeft); @@ -922,7 +924,7 @@ private RectRenderItem GetPieSeriesIcon(ExcelChart ct, ExcelPieChartSerie pcS, D item.Height = iconHeight; item.SetDrawingPropertiesFill(ChartRenderer.Theme, pcS.Fill, Chart.StyleManager.Style?.SeriesLine.FillReference.Color); - item.SetDrawingPropertiesBorder(ChartRenderer.Theme, pcS.Border, Chart.StyleManager.Style?.SeriesLine.BorderReference.Color, pcS.Border.Fill.Style != eFillStyle.NoFill, null, 0.75); + item.SetDrawingPropertiesBorder(ChartRenderer.Theme, pcS.Border, Chart.StyleManager.Style?.SeriesLine.BorderReference.Color, pcS.Border.Fill.Style != eFillStyle.NoFill, () => DefaultBorderColor, 1.5d); return item; } diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartPlotareaRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartPlotareaRenderer.cs index cfabf254d4..1ffc3955ea 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartPlotareaRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartPlotareaRenderer.cs @@ -16,6 +16,7 @@ Date Author Change using EPPlusImageRenderer.RenderItems; using OfficeOpenXml.Drawing; using OfficeOpenXml.Drawing.Chart; +using OfficeOpenXml.Drawing.Renderer.Chart.Defaults; using OfficeOpenXml.FormulaParsing.Excel.Functions.Text; using System; using System.Collections.Generic; @@ -24,7 +25,7 @@ Date Author Change namespace EPPlusImageRenderer.Svg { - internal class ChartPlotareaRenderer : ChartDrawingObject + internal class ChartPlotareaRenderer : ChartDrawingDefaultObject { public ChartPlotareaRenderer(ChartRenderer sc) : base(sc) { @@ -32,15 +33,18 @@ public ChartPlotareaRenderer(ChartRenderer sc) : base(sc) } public List ChartTypeDrawers { get; set; } public GroupRenderItem Group { get; private set; } + + ExcelChartPlotArea _pa; + internal void SetPlotAreaRectangle() { - var pa = Chart.PlotArea; + _pa = Chart.PlotArea; TopMargin = BottomMargin = LeftMargin = RightMargin = 10.5; //14px Group = new GroupRenderItem(ChartRenderer.Bounds); var rect = new RectRenderItem(Group.Bounds); - if (pa.Layout.HasLayout) + if (_pa.Layout.HasLayout) { - rect = GetRectFromManualLayout(ChartRenderer, pa.Layout); + rect = GetRectFromManualLayout(ChartRenderer, _pa.Layout); } else { @@ -61,8 +65,8 @@ internal void SetPlotAreaRectangle() ChartRenderer.Legend.Rectangle.Top = Group.Top + rect.Height / 2 - ChartRenderer.Legend.Rectangle.Height / 2; } - rect.SetDrawingPropertiesFill(ChartRenderer.Theme, pa.Fill, ChartRenderer.Chart.StyleManager.Style?.PlotArea.FillReference.Color, UserSpaceSettings.ObjectBoundingBox, DefaultFillColor); - rect.SetDrawingPropertiesBorder(ChartRenderer.Theme, pa.Border, ChartRenderer.Chart.StyleManager.Style?.PlotArea.BorderReference.Color, pa.Border.Fill.Style != eFillStyle.NoFill, DefaultBorderColor, 0.75); + rect.SetDrawingPropertiesFill(ChartRenderer.Theme, _pa.Fill, ChartRenderer.Chart.StyleManager.Style?.PlotArea.FillReference.Color, UserSpaceSettings.ObjectBoundingBox, DefaultFillColor); + rect.SetDrawingPropertiesBorder(ChartRenderer.Theme, _pa.Border, ChartRenderer.Chart.StyleManager.Style?.PlotArea.BorderReference.Color, _pa.Border.Fill.Style != eFillStyle.NoFill, GetDefaultBorderColor, 0.75); Rectangle = rect; } @@ -195,7 +199,7 @@ private double GetPlotAreaTop() haHeight = (topAxis.Rectangle?.Height ?? 0D) + (topSecondAxis?.Rectangle?.Height ?? 0D) + (topAxis.Title?.TextBox?.GetActualHeight() ?? 0D); } - return (Chart.Legend?.Position == eLegendPosition.Top ? ChartRenderer.Legend.Rectangle.Bounds.Bottom : ChartRenderer.Title?.Rectangle?.GlobalBottom ?? 0d) + haHeight; + return (Chart.Legend?.Position == eLegendPosition.Top ? ChartRenderer.Legend.Rectangle.Bounds.Bottom : ChartRenderer.Title?.Rectangle?.GlobalBottom ?? TopMargin) + haHeight; } private ChartAxisRenderer GetAxisActualByPosition(eActualAxisPosition pos) @@ -251,6 +255,17 @@ internal void DrawSeries() drawer.DrawSeries(); } } - internal override Color? DefaultFillColor { get => null; } + + internal override Color? GetDefaultFillColor() + { + return GetDefaultFillColorForElement(ChartElement.PlotArea2d, (int)Chart.Style); + } + + internal override Color? GetDefaultBorderColor() + { + return null; + } + + internal override Color? DefaultFillColor { get => GetDefaultFillColor(); } } } diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTitleRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTitleRenderer.cs index b8e26b8bb9..4a3dc9f5c9 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTitleRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTitleRenderer.cs @@ -22,6 +22,7 @@ Date Author Change using OfficeOpenXml; using OfficeOpenXml.Drawing; using OfficeOpenXml.Drawing.Chart; +using OfficeOpenXml.Drawing.Renderer.Chart.Defaults; using OfficeOpenXml.Drawing.Renderer.TextBox; using OfficeOpenXml.FormulaParsing.Excel.Functions.MathFunctions; using OfficeOpenXml.FormulaParsing.Excel.Functions.Text; @@ -103,9 +104,9 @@ internal ChartTitleRenderer(ChartRenderer sc, ExcelChartTitleStandard t, string SetAxisTitleRect(sc, axis); } } - - Rectangle.SetDrawingPropertiesFill(sc.Theme, t.Fill, sc.Chart.StyleManager.Style?.Title.FillReference.Color, UserSpaceSettings.ObjectBoundingBox, DefaultFillColor); - Rectangle.SetDrawingPropertiesBorder(sc.Theme, t.Border, sc.Chart.StyleManager.Style?.Title.BorderReference.Color, t.Border.Fill.Style != eFillStyle.NoFill, DefaultBorderColor, 0.75); + //Default NoFill for title and axis titles if not set + Rectangle.SetDrawingPropertiesFill(sc.Theme, t.Fill, sc.Chart.StyleManager.Style?.Title.FillReference.Color, UserSpaceSettings.ObjectBoundingBox, null); + Rectangle.SetDrawingPropertiesBorder(sc.Theme, t.Border, sc.Chart.StyleManager.Style?.Title.BorderReference.Color, t.Border.Fill.Style != eFillStyle.NoFill, () => null, 0.75); } private void SetAxisTitleRect(ChartRenderer sc, ChartAxisRenderer axis) @@ -227,7 +228,8 @@ public override void AppendRenderItems(List renderItems) { TextBox.TextBody.FontColorString = "#" + p.DefaultRunProperties.Fill.Color.ToColorString(); TextBox.Rectangle.SetDrawingPropertiesFill(_svgChart.Theme, _title.Fill, _svgChart.Chart.StyleManager.Style?.Title.FillReference.Color, UserSpaceSettings.ObjectBoundingBox, DefaultFillColor); - TextBox.Rectangle.SetDrawingPropertiesBorder(_svgChart.Theme, _title.Border, _svgChart.Chart.StyleManager.Style?.Title.BorderReference.Color, _title.Border.Fill.Style != eFillStyle.NoFill, DefaultBorderColor, 0.75); + //Default is actually NoLine + TextBox.Rectangle.SetDrawingPropertiesBorder(_svgChart.Theme, _title.Border, _svgChart.Chart.StyleManager.Style?.Title.BorderReference.Color, _title.Border.Fill.Style != eFillStyle.NoFill, () => DefaultBorderColor, 0.75); } TextBox.AppendRenderItems(renderItems); } diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/BarColumnChartTypeDrawer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/BarColumnChartTypeDrawer.cs index ddd4124bd8..1f59cd0762 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/BarColumnChartTypeDrawer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/BarColumnChartTypeDrawer.cs @@ -6,6 +6,7 @@ using EPPlusImageRenderer.RenderItems; using EPPlusImageRenderer.Svg; using OfficeOpenXml.Drawing.Chart; +using OfficeOpenXml.FormulaParsing.Excel.Functions.MathFunctions; using OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup; using OfficeOpenXml.Utils.TypeConversion; using System; @@ -91,6 +92,10 @@ internal override void DrawSeries() for (int j = 0; j < dataPoints.Count; j++) { + //var parentHolder = dataPoints[j].Parent; + + var globalDPBounds = dataPoints[j].GetGlobalBoundingbox(); + //Initialize transforms Transform basePoint = new Transform(); Transform endPoint = new Transform(); @@ -99,15 +104,15 @@ internal override void DrawSeries() if (isColumn == true) { - var middleRight = dataPoints[j].Left + (dataPoints[j].Width / 2); + var middleRight = globalDPBounds.Left + (globalDPBounds.Width / 2); - if (chartBaseY <= dataPoints[j].Top) + if (chartBaseY <= globalDPBounds.Top) { //We are a negative column // ----- Base-Axis // |_| Col - basePoint.Position = new Vector2(middleRight, dataPoints[j].Top); - endPoint.Position = new Vector2(middleRight, dataPoints[j].Bottom); + basePoint.Position = new Vector2(middleRight, globalDPBounds.Top); + endPoint.Position = new Vector2(middleRight, globalDPBounds.Bottom); } else { @@ -115,29 +120,28 @@ internal override void DrawSeries() // _ // | | Col // ----- Base-Axis - basePoint.Position = new Vector2(middleRight, dataPoints[j].Bottom); - endPoint.Position = new Vector2(middleRight, dataPoints[j].Top); + basePoint.Position = new Vector2(middleRight, globalDPBounds.Bottom); + endPoint.Position = new Vector2(middleRight, globalDPBounds.Top); } datalabel.SetDimensions(j, basePoint, endPoint); } else { - var middleHeight = dataPoints[j].Top + (dataPoints[j].Height / 2); + var middleHeight = globalDPBounds.Top + (globalDPBounds.Height / 2); basePoint.Position = new Vector2(chartBaseY, middleHeight); - if (chartBaseY > dataPoints[j].Left) + if (chartBaseY > globalDPBounds.Left) { - endPoint.Position = new Vector2(chartBaseY - dataPoints[j].Width, middleHeight); + endPoint.Position = new Vector2(chartBaseY - globalDPBounds.Width, middleHeight); } else { - endPoint.Position = new Vector2(dataPoints[j].Left + dataPoints[j].Width, middleHeight); + endPoint.Position = new Vector2(globalDPBounds.Left + globalDPBounds.Width, middleHeight); } datalabel.SetDimensions(j, basePoint, endPoint); } } - serCounter++; } } @@ -207,7 +211,11 @@ private void AddBar(ExcelBarChart chartType, ExcelBarChartSerie serie, List y)) //Below axis { rect.Top = chartBaseY; rect.Height = yPos - chartBaseY; @@ -316,7 +324,7 @@ private void AddBar(ExcelBarChart chartType, ExcelBarChartSerie serie, List y)) //Below axis { rect.Left = yPos; rect.Width = chartBaseY - yPos; diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/ChartErrorBarRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/ChartErrorBarRenderer.cs index 966e935375..0c524ca674 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/ChartErrorBarRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/ChartErrorBarRenderer.cs @@ -4,6 +4,7 @@ using EPPlusImageRenderer.RenderItems; using EPPlusImageRenderer.Svg; using OfficeOpenXml.Drawing.Chart; +using OfficeOpenXml.Drawing.Renderer.Chart.Defaults; using OfficeOpenXml.FormulaParsing.Excel.Functions.MathFunctions; using OfficeOpenXml.Utils.TypeConversion; using System; @@ -17,7 +18,7 @@ namespace OfficeOpenXml.Drawing.Renderer.Chart.ChartTypeDrawers { - internal class ChartErrorBarRenderer : ChartDrawingObject + internal class ChartErrorBarRenderer : ChartDrawingDefaultObject { internal ExcelChartErrorBars _errorbars; private double[] _ySerie; @@ -228,30 +229,46 @@ internal List GetErrorBarRenderItem(int index, ChartAxisRenderer xAx { if (_errorbars.Border.LineElement == null) { - ri.SetDrawingPropertiesBorder(ChartRenderer.Theme, ChartRenderer.Chart.StyleManager.Style?.ErrorBar.Border, ChartRenderer.Chart.StyleManager.Style?.ErrorBar.BorderReference.Color, true, DefaultFillColor, 0.75); - ri.SetDrawingPropertiesBorder(ChartRenderer.Theme, ChartRenderer.Chart.StyleManager.Style?.ErrorBar.Border, ChartRenderer.Chart.StyleManager.Style?.ErrorBar.BorderReference.Color, true, DefaultBorderColor, 0.75d); + ri.SetDrawingPropertiesBorder(ChartRenderer.Theme, ChartRenderer.Chart.StyleManager.Style?.ErrorBar.Border, ChartRenderer.Chart.StyleManager.Style?.ErrorBar.BorderReference.Color, true, GetDefaultFillColor, 0.75); + ri.SetDrawingPropertiesBorder(ChartRenderer.Theme, ChartRenderer.Chart.StyleManager.Style?.ErrorBar.Border, ChartRenderer.Chart.StyleManager.Style?.ErrorBar.BorderReference.Color, true, GetDefaultBorderColor, 0.75d); } else { - ri.SetDrawingPropertiesBorder(ChartRenderer.Theme, _errorbars.Border, ChartRenderer.Chart.StyleManager.Style?.ErrorBar.BorderReference.Color, _errorbars.Border.Fill.Style != eFillStyle.NoFill, DefaultBorderColor, 0.75); + ri.SetDrawingPropertiesBorder(ChartRenderer.Theme, _errorbars.Border, ChartRenderer.Chart.StyleManager.Style?.ErrorBar.BorderReference.Color, _errorbars.Border.Fill.Style != eFillStyle.NoFill, GetDefaultBorderColor, 0.75); } ri.SetDrawingPropertiesEffects(ChartRenderer.Theme, _errorbars.Effect); } return l; } + + internal override Color? GetDefaultFillColor() + { + return GetDefaultFillColorForElement(ChartElement.OtherLines, (int)Chart.Style); + } + + internal override Color? GetDefaultBorderColor() + { + //We only get here if the node is null or empty + var themedLine = GetThemedLine(ChartElement.OtherLines, (int)Chart.Style, _errorbars.Border.Fill != null && _errorbars.Border.Fill.IsEmpty, out Color? lineColor); + ////Kept here in case needed in future for effect etc. + //var themedLine = GetThemedLine(ChartElement.ChartArea, (int)Chart.Style, out Color? lineCol); + return lineColor; + } + internal override Color? DefaultBorderColor { get { - var borderStyleFill = ChartRenderer.Theme.FormatScheme.BorderStyle[0].Fill; - if (borderStyleFill.IsEmpty == false && borderStyleFill.SolidFill != null && borderStyleFill.SolidFill.Color.ColorType != eDrawingColorType.Scheme) - { - return ChartRenderer.Theme.FormatScheme.BorderStyle[0].Fill?.Color; - } - else - { - return null; - } + return GetDefaultBorderColor(); + //var borderStyleFill = ChartRenderer.Theme.FormatScheme.BorderStyle[0].Fill; + //if (borderStyleFill.IsEmpty == false && borderStyleFill.SolidFill != null && borderStyleFill.SolidFill.Color.ColorType != eDrawingColorType.Scheme) + //{ + // return ChartRenderer.Theme.FormatScheme.BorderStyle[0].Fill?.Color; + //} + //else + //{ + // return null; + //} } } } diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/ChartTypeDrawer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/ChartTypeDrawer.cs index 9fd79c1d94..645a4e1bc9 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/ChartTypeDrawer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/ChartTypeDrawer.cs @@ -233,13 +233,13 @@ internal bool IsOnAxis(ExcelChartAxisStandard ax) { return _chartType.YAxis==ax || _chartType.XAxis==ax; } - internal static void SetFillDataPoint(ExcelChart chart, ExcelChartStandardSerie cStandardSerie, int index, RenderItem item, ExcelChartDataPoint dp, ExcelChartStyleEntry entry) + internal static void SetFillDataPoint(ExcelChart chart, ExcelChartStandardSerie cStandardSerie, int index, RenderItem item, ExcelChartDataPoint dp, ExcelChartStyleEntry entry, UserSpaceSettings spaceSettings = UserSpaceSettings.UserSpaceOnUse_Object) { var theme = chart.WorkSheet.Workbook.ThemeManager.GetOrCreateTheme(); var color = GetVaryColor(theme, chart.StyleManager?.ColorsManager, index); - item.SetDrawingPropertiesFill(theme, dp.Fill.IsEmpty ? cStandardSerie.Fill : dp.Fill, entry?.FillReference.Color, UserSpaceSettings.UserSpaceOnUse_Object, color); - item.SetDrawingPropertiesBorder(theme, dp.Border.IsEmpty ? cStandardSerie.Border : dp.Border, entry?.BorderReference.Color, dp.Border.Fill.Style != eFillStyle.NoFill, null, 0.75); + item.SetDrawingPropertiesFill(theme, dp.Fill.IsEmpty ? cStandardSerie.Fill : dp.Fill, entry?.FillReference.Color, spaceSettings, color); + item.SetDrawingPropertiesBorder(theme, dp.Border.IsEmpty ? cStandardSerie.Border : dp.Border, entry?.BorderReference.Color, dp.Border.Fill.Style != eFillStyle.NoFill, () => Color.Transparent, 0.75); } internal static void SetFillSerie(ExcelChart chart, ExcelChart ct, ExcelChartStandardSerie cStandardSerie, int serieIndex, int index, RenderItem item) @@ -256,7 +256,7 @@ internal static void SetFillSerie(ExcelChart chart, ExcelChart ct, ExcelChartSta var color = GetVaryColor(theme, chart.StyleManager?.ColorsManager, serieIndex); item.SetDrawingPropertiesFill(theme, cStandardSerie.Fill, chart.StyleManager.Style?.SeriesLine.FillReference.Color, UserSpaceSettings.UserSpaceOnUse_Object, color); } - item.SetDrawingPropertiesBorder(theme, cStandardSerie.Border, chart.StyleManager.Style?.SeriesLine.BorderReference.Color, cStandardSerie.Border.Fill.Style != eFillStyle.NoFill, null, 0.75); + item.SetDrawingPropertiesBorder(theme, cStandardSerie.Border, chart.StyleManager.Style?.SeriesLine.BorderReference.Color, cStandardSerie.Border.Fill.Style != eFillStyle.NoFill, () => Color.Transparent, 0.75); } private static Color? GetVaryColor(ExcelTheme theme, ExcelChartColorsManager colorsManager, int index) diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs index 5ba1d1bcb3..b865ffa3e7 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs @@ -82,7 +82,8 @@ private void CreateDropLine(ExcelLineChart chartType, List coords) Y2 = yBottom, }; dl.Bounds.Name = $"DropLine {i/2 + 1}"; - dl.SetDrawingPropertiesBorder(ChartRenderer.Theme, chartType.DropLine.Border, chartType.StyleManager.Style?.DropLine.BorderReference.Color, true, DefaultBorderColor, 1.5,DrawingRenderer.UserSpaceSettings.UserSpaceOnUse_Parent); + //TODO: DropLines should actually use the "Other Lines" DefaultDrawingObject + dl.SetDrawingPropertiesBorder(ChartRenderer.Theme, chartType.DropLine.Border, chartType.StyleManager.Style?.DropLine.BorderReference.Color, true, () => DefaultBorderColor, 1.5,DrawingRenderer.UserSpaceSettings.UserSpaceOnUse_Parent); dl.SetDrawingPropertiesEffects(ChartRenderer.Theme, chartType.DropLine.Effect); _dropLines.Add(dl); @@ -245,10 +246,12 @@ private void AddLine(ExcelLineChart chartType, ExcelLineChartSerie serie, List ChartRenderer.Theme.FormatScheme.FillStyle[0].Color); } } - lineDp.SetDrawingPropertiesBorder(ChartRenderer.Theme, dp.Border, chartType.StyleManager.Style?.SeriesLine.BorderReference.Color, true, DefaultBorderColor, 3); + lineDp.SetDrawingPropertiesBorder(ChartRenderer.Theme, dp.Border, chartType.StyleManager.Style?.SeriesLine.BorderReference.Color, true, () => DefaultBorderColor, 3); lineDp.SetDrawingPropertiesEffects(ChartRenderer.Theme, dp.Effect); dataPointOverrides.Add(lineDp); } @@ -257,7 +260,7 @@ private void AddLine(ExcelLineChart chartType, ExcelLineChartSerie serie, List DefaultBorderColor, 3); linePath.SetDrawingPropertiesEffects(ChartRenderer.Theme, serie.Effect); linePath.FillColor = "none"; //No fill for line linePath.StrokeMiterLimit = 4; //A much higher value of the miter limit, might cause the "spike" to get beyond the data point on the vertical scale.. diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs index b09fac446a..ad94c2507b 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs @@ -259,7 +259,7 @@ internal override void DrawSeries() //maxBoundsForBestFit.Parent = innerGroup; var bounds = Slices[j].GetBounds(); - + //BoundingBox box = new BoundingBox(bounds.Left, bounds.Top, bounds.Width, bounds.Height); //box.Parent = innerGroup; diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/Trendlines/ChartTrendlineRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/Trendlines/ChartTrendlineRenderer.cs index 4d65fbf810..077bbe8c20 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/Trendlines/ChartTrendlineRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/Trendlines/ChartTrendlineRenderer.cs @@ -19,6 +19,7 @@ Date Author Change using EPPlusImageRenderer.Svg; using OfficeOpenXml.Drawing; using OfficeOpenXml.Drawing.Chart; +using OfficeOpenXml.Drawing.Renderer.Chart.Defaults; using OfficeOpenXml.Drawing.Renderer.TextBox; using OfficeOpenXml.FormulaParsing.Excel.Functions.Statistical; using OfficeOpenXml.Utils.TypeConversion; @@ -29,7 +30,7 @@ Date Author Change using System.Text; namespace EPPlus.Export.ImageRenderer.Svg.Chart { - internal class ChartTrendlineRenderer : ChartDrawingObject + internal class ChartTrendlineRenderer : ChartDrawingDefaultObject { private ExcelChartTrendline _trendline; private double[] _ySerie; @@ -191,7 +192,7 @@ private void CreateDatalabel() DataLabel.Rectangle.SetDrawingPropertiesFill(ChartRenderer.Theme, _trendline.Label.Fill, Chart.StyleManager.Style.TrendlineLabel.FillReference.Color, UserSpaceSettings.ObjectBoundingBox, DefaultFillColor); - DataLabel.Rectangle.SetDrawingPropertiesBorder(ChartRenderer.Theme, _trendline.Label.Border, Chart.StyleManager.Style.TrendlineLabel.BorderReference.Color, true, DefaultBorderColor, _trendline.Label.Border.Width); + DataLabel.Rectangle.SetDrawingPropertiesBorder(ChartRenderer.Theme, _trendline.Label.Border, Chart.StyleManager.Style.TrendlineLabel.BorderReference.Color, true, GetDefaultBorderColor, _trendline.Label.Border.Width); DataLabel.Rectangle.SetDrawingPropertiesEffects(ChartRenderer.Theme, _trendline.Label.Effect); } @@ -665,7 +666,7 @@ public override void AppendRenderItems(List renderItems) var pathItem = new PathRenderItem(ChartRenderer.Plotarea.Rectangle.Bounds); pathItem.Commands.Add(new EPPlusImageRenderer.PathCommands(PathCommandType.Move, RenderCoordinates)); pathItem.FillColor = "none"; - pathItem.SetDrawingPropertiesBorder(ChartRenderer.Theme, _trendline.Border, Chart.StyleManager.Style?.Trendline.BorderReference.Color, true, DefaultBorderColor, _trendline.Border.Width); + pathItem.SetDrawingPropertiesBorder(ChartRenderer.Theme, _trendline.Border, Chart.StyleManager.Style?.Trendline.BorderReference.Color, true, GetDefaultBorderColor, _trendline.Border.Width); pathItem.SetDrawingPropertiesEffects(ChartRenderer.Theme, _trendline.Effect); renderItems.Add(pathItem); } @@ -784,19 +785,34 @@ private double GetLinearValueAtPosition(double x) { return Coefficients[1] + Coefficients[0] * x; } + + internal override Color? GetDefaultFillColor() + { + return GetDefaultFillColorForElement(ChartElement.OtherLines, (int)Chart.Style); + } + + internal override Color? GetDefaultBorderColor() + { + //We only get here if the node is null or empty + var themedLine = GetThemedLine(ChartElement.OtherLines, (int)Chart.Style, _trendline.Border.Fill != null && _trendline.Border.Fill.IsEmpty, out Color? lineColor); + ////Kept here in case needed in future for effect etc. + //var themedLine = GetThemedLine(ChartElement.ChartArea, (int)Chart.Style, out Color? lineCol); + return lineColor; + } internal override Color? DefaultBorderColor { get { - var borderStyleFill = ChartRenderer.Theme.FormatScheme.BorderStyle[0].Fill; - if (borderStyleFill.IsEmpty == false && borderStyleFill.SolidFill != null && borderStyleFill.SolidFill.Color.ColorType != eDrawingColorType.Scheme) - { - return ChartRenderer.Theme.FormatScheme.BorderStyle[0].Fill?.Color; - } - else - { - return null; - } + return GetDefaultBorderColor(); + //var borderStyleFill = ChartRenderer.Theme.FormatScheme.BorderStyle[0].Fill; + //if (borderStyleFill.IsEmpty == false && borderStyleFill.SolidFill != null && borderStyleFill.SolidFill.Color.ColorType != eDrawingColorType.Scheme) + //{ + // return ChartRenderer.Theme.FormatScheme.BorderStyle[0].Fill?.Color; + //} + //else + //{ + // return null; + //} } } } diff --git a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs index 6bc1f99959..084143f7dd 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs @@ -37,7 +37,7 @@ public ChartSerieDataLabelRenderer(ChartRenderer chart, ExcelChartSerieDataLabel _dlblSerie = dlblSerie; plotAreaBounds = chart.Plotarea.Group.Bounds; - DefaultFillColor = Color.Transparent; + DefaultFillColor = dlblSerie.Fill != null && dlblSerie.Fill.Color.IsEmpty == false ? dlblSerie.Fill.Color : Color.Transparent; if(yValues != null && yValues.Count != 0) @@ -126,7 +126,7 @@ private RenderItem GetSeriesIcon(ExcelChartStandardSerie serie, BoundingBox maxB private void AddDatalabel(ExcelChartStandardSerie serie, ExcelChartDataLabelStandard dataLabel, object xValue, object yValue, BoundingBox maxBounds) { - var newDataLabel = new SvgDataLabelPoint(ChartRenderer, dataLabel); + var newDataLabel = new SvgDataLabelPoint(ChartRenderer, dataLabel, DefaultFillColor); newDataLabel.ImportDataLabel(serie, dataLabel, xValue, yValue, defaultParagraph, maxBounds, _defaultMargins, SummedSeries); if(dataLabel.ShowLegendKey) diff --git a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs index 2ca6fa5f45..56072ecae8 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs @@ -9,6 +9,7 @@ using OfficeOpenXml.Drawing; using OfficeOpenXml.Drawing.Chart; using OfficeOpenXml.Drawing.Renderer.TextBox; +using OfficeOpenXml.FormulaParsing.Utilities; using OfficeOpenXml.Utils.EnumUtils; using OfficeOpenXml.Utils.TypeConversion; using System; @@ -48,9 +49,9 @@ internal class SvgDataLabelPoint : ChartDrawingObject // TxtBox = txtBox; //} - public SvgDataLabelPoint(ChartRenderer chart, ExcelChartDataLabelStandard standard) : base(chart) + public SvgDataLabelPoint(ChartRenderer chart, ExcelChartDataLabelStandard standard, Color? defaultFillColor = null) : base(chart) { - DefaultFillColor = Color.Transparent; + DefaultFillColor = defaultFillColor.HasValue ? defaultFillColor : Color.Transparent; _labelPosition = GetDefaultPositionBasedOnChartType(standard); Rectangle = new RectRenderItem(chart.Bounds); } @@ -73,6 +74,7 @@ eLabelPosition GetDefaultPositionBasedOnChartType(ExcelChartDataLabelStandard st case eChartType.LineStacked: case eChartType.XYScatterLines: case eChartType.Bubble: + return eLabelPosition.Right; case eChartType.StockHLC: case eChartType.StockVOHLC: case eChartType.StockVHLC: @@ -129,12 +131,20 @@ internal void ImportDataLabel(ExcelChartStandardSerie serie, ExcelChartDataLabel } if (dataLabel.ShowCategory) { + if (xValue.IsNumeric()) + { + xValue = Math.Round((double)xValue, 6); + } dlblStrings.Add(xValue.ToString()); } if (dataLabel.ShowValue) { if (yValue != null) { + if(yValue.IsNumeric()) + { + yValue = Math.Round((double)yValue,6); + } dlblStrings.Add(yValue.ToString()); } } @@ -211,16 +221,7 @@ internal void ImportDataLabel(ExcelChartStandardSerie serie, ExcelChartDataLabel Rectangle.Bounds.Height = txtBox.Rectangle.Bounds.Height; _txtBox = txtBox; - - if (dataLabel.Fill.IsEmpty == false) - { - _txtBox.Rectangle.SetDrawingPropertiesFill(ChartRenderer.Theme, dataLabel.Fill, null, UserSpaceSettings.ObjectBoundingBox, DefaultFillColor); - } - else - { - _txtBox.Rectangle.SetDrawingPropertiesFill(ChartRenderer.Theme, dataLabel.Fill, null, UserSpaceSettings.ObjectBoundingBox, DefaultFillColor); - //_txtBox.Rectangle.FillColor = "transparent"; - } + _txtBox.Rectangle.SetDrawingPropertiesFill(ChartRenderer.Theme, dataLabel.Fill, null, UserSpaceSettings.ObjectBoundingBox, DefaultFillColor); if (dataLabel.Font.IsEmpty == false) { @@ -377,7 +378,7 @@ private void SetAdjustedTextBoxPosition(Vector2 direction, bool reverseDirection var directionOnly = direction / direction.Length; //Get txtbox-size based vector - var txtBoxAdjustVector = new Vector2(_txtBox.Width / 2d, _txtBox.Height / 2d); + var txtBoxAdjustVector = new Vector2(Rectangle.Width / 2d, Rectangle.Height / 2d ); //Apply translation to current position Rectangle.Bounds.Position += directionOnly * txtBoxAdjustVector; @@ -401,7 +402,7 @@ private void AdjustPositionIfOutsideChartAndNotManualLayout() var chartBounds = ChartRenderer.ChartArea.Rectangle.Bounds; var plotBounds = ChartRenderer.GetPlotAreaTop(); - var chartMinY = chartBounds.Position.Y - ChartRenderer.GetPlotAreaTop(); + var chartMinY = ChartRenderer.Bounds.GlobalTop; var chartMinX = chartBounds.Position.X - ChartRenderer.Plotarea.LeftMargin; if (gTop < chartMinY) diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartStyleDefaults/ChartElementStyleTables.cs b/src/EPPlus/Drawing/Renderer/Chart/Defaults/ChartDrawingDefaultObject.cs similarity index 83% rename from src/EPPlus/Drawing/Renderer/Chart/ChartStyleDefaults/ChartElementStyleTables.cs rename to src/EPPlus/Drawing/Renderer/Chart/Defaults/ChartDrawingDefaultObject.cs index 24bf5c9866..cc3f01dc4c 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartStyleDefaults/ChartElementStyleTables.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/Defaults/ChartDrawingDefaultObject.cs @@ -7,7 +7,7 @@ using System.Drawing; using tc = OfficeOpenXml.Utils.TypeConversion; -namespace OfficeOpenXml.Drawing.Renderer.Chart.ChartElementStyleTables +namespace OfficeOpenXml.Drawing.Renderer.Chart.Defaults { [Flags] enum ChartElement @@ -25,9 +25,9 @@ enum ChartElement OtherLines = 512, } - internal abstract class ChartDrawingObjectWithDefaults : ChartDrawingObject + internal abstract class ChartDrawingDefaultObject : ChartDrawingObject { - public ChartDrawingObjectWithDefaults(ChartRenderer chart) : base(chart) + public ChartDrawingDefaultObject(ChartRenderer chart) : base(chart) { } @@ -63,7 +63,10 @@ private Color GetThemeColorTint(eThemeSchemeColor themeColor, double tint = 0.0d if (styleId == 0) { - return Color.Empty; + //Set to default instead for export + //Otherwise epplus generated get weird. + styleId = 2; + //return Color.Empty; } if (styleId <= 32) @@ -100,8 +103,9 @@ protected ExcelThemeLine GetThemedLine(ChartElement element, int ChartStyleId, b //Alternatively it's an unkown or unset style which should also default to style2 var styleId = ChartStyleId > (int)eChartStyle.Style48 ? (int)eChartStyle.Style2 : ChartStyleId; - var AreaOrFloor = (ChartElement.ChartArea | ChartElement.Floor); - if (AreaOrFloor.HasFlag(element)) + //Everything defaults to subtle style except Area and Floor at certain styles + var AreaOrFloorOrOther = (ChartElement.ChartArea | ChartElement.Floor | ChartElement.OtherLines); + if (AreaOrFloorOrOther.HasFlag(element)) { var themedLine = ChartRenderer.Theme.FormatScheme.BorderStyle[0]; @@ -109,7 +113,32 @@ protected ExcelThemeLine GetThemedLine(ChartElement element, int ChartStyleId, b //It directly applies the themedLineColor if (nodeIsEmpty) { - lineColor = themedLine.Fill.Color; + //Is node empty inside the theme + if(themedLine.HasFill == false) + { + lineColor = Color.Transparent; + return themedLine; + } + + bool isSchemeColor = themedLine.Fill.SolidFill.Color.ColorType == eDrawingColorType.Scheme && themedLine.Fill.SolidFill.Color.SchemeColor.Color == eSchemeColor.Style; + + if (isSchemeColor) + { + lineColor = GetDefaultBorderColorForElement(element, styleId); + + if (themedLine.Fill.SolidFill.Color.Transforms.Count > 0 && lineColor.HasValue) + { + //var schemeClr = tc.ColorConverter.GetSchemeColor(ChartRenderer.Theme, eSchemeColor.Dark1); + //var tint = GetSchemeColorTint(eSchemeColor.Dark1, 0.45d); + lineColor = tc.ColorConverter.ApplyTransforms(lineColor.Value, themedLine.Fill.SolidFill.Color.Transforms); + } + + return themedLine; + } + else + { + lineColor = themedLine.Fill.Color; + } return themedLine; } @@ -123,7 +152,7 @@ protected ExcelThemeLine GetThemedLine(ChartElement element, int ChartStyleId, b return themedLine; } - if (styleId < 41) + if (styleId < 41 || element == ChartElement.OtherLines) { if (themedLine.Fill.SolidFill.Color.Transforms.Count > 0) { @@ -266,6 +295,8 @@ protected ExcelDrawingFill GetThemedFill(ChartElement element, int ChartStyleId, protected Color? GetDefaultFillColorForElement(ChartElement element, int ChartStyleId) { + ChartStyleId = ChartStyleId > (int)eChartStyle.Style48 ? (int)eChartStyle.Style2 : ChartStyleId; + if (element.HasFlag(ChartElement.ChartArea)) { var retCol = GetSchemeColorTint(eSchemeColor.Background1); @@ -278,7 +309,11 @@ protected ExcelDrawingFill GetThemedFill(ChartElement element, int ChartStyleId, { var retCol = GetSchemeColorTint(eSchemeColor.Background1); var retCol2 = GetSchemeColorTint(eSchemeColor.Background1, 0.2d); - var retCol3 = GetDefaultAccent(ChartStyleId); + Color? retCol3 = Color.Empty; + if(ChartStyleId > 35 && ChartStyleId < 40) + { + retCol3 = GetDefaultAccent(ChartStyleId); + } var retCol4 = GetSchemeColorTint(eSchemeColor.Background1, 0.95d); return GetStyleColorOrDefault(ChartStyleId, retCol, retCol2, retCol3.Value, retCol4); diff --git a/src/EPPlus/Drawing/Renderer/Chart/LineMarkerHelper.cs b/src/EPPlus/Drawing/Renderer/Chart/LineMarkerHelper.cs index 1d127fd6cb..b3ae77ca89 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/LineMarkerHelper.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/LineMarkerHelper.cs @@ -35,11 +35,13 @@ internal static RenderItem GetMarkerItem(ChartRenderer sc, ExcelLineChartSerie l { if (marker.Border.Fill.IsEmpty) { - item?.SetDrawingPropertiesBorder(sc.Theme, ls.Border, sc.Chart.StyleManager.Style.DataPointMarker.BorderReference.Color, ls.Border.Fill.Style != eFillStyle.NoFill, sc.Theme.FormatScheme.BorderStyle[0].Fill.Color, 0.75d); + //Datapoints including markers actually have a way more complex fallback TODO: Handle later. + item?.SetDrawingPropertiesBorder(sc.Theme, ls.Border, sc.Chart.StyleManager.Style.DataPointMarker.BorderReference.Color, ls.Border.Fill.Style != eFillStyle.NoFill, () => sc.Theme.FormatScheme.BorderStyle[0].Fill.Color, 0.75d); } else { - item?.SetDrawingPropertiesBorder(sc.Theme, marker.Border, sc.Chart.StyleManager.Style.DataPointMarker.BorderReference.Color, ls.Marker.Border.Fill.Style != eFillStyle.NoFill, sc.Theme.FormatScheme.BorderStyle[0].Fill.Color, 0.75d); + //Datapoints including markers actually have a way more complex fallback TODO: Handle later. + item?.SetDrawingPropertiesBorder(sc.Theme, marker.Border, sc.Chart.StyleManager.Style.DataPointMarker.BorderReference.Color, ls.Marker.Border.Fill.Style != eFillStyle.NoFill, () => sc.Theme.FormatScheme.BorderStyle[0].Fill.Color, 0.75d); } } return item; diff --git a/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs b/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs index dabad2e3da..f638800802 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs @@ -87,7 +87,6 @@ bool ExistWithinRange(double target, double min, double max) void CalculateWidthHeight(double prevSliceDegrees) { - var endPointDegrees = prevSliceDegrees + Degrees; if (endPointDegrees < 0) { @@ -107,7 +106,7 @@ void CalculateWidthHeight(double prevSliceDegrees) double minY; double minX; - if (ExistWithinRange(90, startPointDegrees, endPointDegrees)) + if (/*endPointDegrees < startPointDegrees || */ExistWithinRange(90, startPointDegrees, endPointDegrees)) { maxY = _circleCenter.Top + _radius; } @@ -327,7 +326,7 @@ internal void ImportStlyeInfo(ExcelPieChartSerie serie, ExcelPieChart chartType, if (position >= 0 && serie.DataPoints.ContainsKey(position)) { var dp = serie.DataPoints[position]; - ChartTypeDrawer.SetFillDataPoint(Chart, serie, position, _slicePath, dp, Chart.StyleManager.Style?.SeriesLine); + ChartTypeDrawer.SetFillDataPoint(Chart, serie, position, _slicePath, dp, Chart.StyleManager.Style?.SeriesLine, UserSpaceSettings.ObjectBoundingBox); } else { @@ -557,7 +556,7 @@ void CalculateLargestRectWithinCircleSegment() //Calculate thetha = alpha/4 var angleForTriangle = Degrees / 4d; - var angleForYTriangle = angleForTriangle + 1d; + var angleForYTriangle = angleForTriangle + 0.64d; var yTriangle = (Math.Sin(MConverter.DegreesToRadians(angleForYTriangle)) * _radius);// add 1 for small rounding fault making too small var xTriangle = (Math.Cos(MConverter.DegreesToRadians(angleForTriangle)) * _radius); @@ -599,7 +598,7 @@ void CalculateLargestRectWithinCircleSegment() { //Formula for largest (unrotated) rectangle within a semi-circle LargestWidthRectangle = Math.Sqrt(2d) *_radius; - LargestHeightRectangle = (Math.Sqrt(2d)/2) * _radius; + LargestHeightRectangle = (Math.Sqrt(2d)/2d) * _radius; } } @@ -683,7 +682,7 @@ internal Transform GetInnerGroupWithTransformOriginTranslated() { Transform transform = new Transform(); transform.Parent = _innerGroup.Bounds.Parent; - transform.LocalPosition += new Vector2(_innerGroup.TransformOrigin.X + _innerGroup.TranslationOffset.Left, _innerGroup.TransformOrigin.Y + _innerGroup.TranslationOffset.Top); + transform.LocalPosition += new Vector2(_innerGroup.TransformOrigin.X + _innerGroup.TranslationOffset.Left - _innerGroup.Left, _innerGroup.TransformOrigin.Y + _innerGroup.TranslationOffset.Top- _innerGroup.Top); return transform; } @@ -700,7 +699,6 @@ internal BoundingBox GetBounds() BoundingBox box = new BoundingBox(LargestWidthRectangle, LargestHeightRectangle); box.Parent = ExtremePoints.Parent; box.Left = ExtremePoints.Left; - box.Top = ExtremePoints.Top; return box; } diff --git a/src/EPPlus/Drawing/Renderer/Chart/eTextOrientation.cs b/src/EPPlus/Drawing/Renderer/Chart/eTextOrientation.cs index 8677f954b9..9fa95cef02 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/eTextOrientation.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/eTextOrientation.cs @@ -17,5 +17,6 @@ internal enum eTextOrientation Horizontal, Diagonal, Vertical, + Custom } } \ No newline at end of file diff --git a/src/EPPlus/Drawing/Renderer/ChartRenderer.cs b/src/EPPlus/Drawing/Renderer/ChartRenderer.cs index 26b2fa0244..131d6ae4d8 100644 --- a/src/EPPlus/Drawing/Renderer/ChartRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/ChartRenderer.cs @@ -103,12 +103,16 @@ private void SetAxisPositionsFromPlotarea() //Make sure the horizontal axis is moved up if the vertical axis has a negative minimum value, so that the 0 value is at the correct position. if (VerticalAxis.Axis.TickLabelPosition == eTickLabelPosition.NextTo && HorizontalAxis.Axis.AxisType == eAxisType.Val && HorizontalAxis.Min < 0D) { - Plotarea.Rectangle.Width += VerticalAxis.Rectangle.Width; - Plotarea.Group.Left = VerticalAxis.Rectangle.Left; - var newRight = Plotarea.Group.Left + HorizontalAxis.GetPositionInPlotarea(0D); - var rightDiff = newRight - VerticalAxis.Rectangle.Width; - VerticalAxis.Rectangle.Left = rightDiff; - VerticalAxis.Line.X1 = VerticalAxis.Line.X2 = newRight; + var CrossValue = HorizontalAxis.GetCrossesValue(); + var newRight = Plotarea.Group.Left + HorizontalAxis.GetPositionInPlotarea(CrossValue); + if(newRight > Plotarea.Group.Left) + { + Plotarea.Rectangle.Width += VerticalAxis.Rectangle.Width; + Plotarea.Group.Left = VerticalAxis.Rectangle.Left; + var rightDiff = newRight - VerticalAxis.Rectangle.Width; + VerticalAxis.Rectangle.Left = rightDiff; + VerticalAxis.Line.X1 = VerticalAxis.Line.X2 = newRight; + } } VerticalAxis.AddTickmarksAndValues(DefItems); } @@ -118,13 +122,17 @@ private void SetAxisPositionsFromPlotarea() PlaceHorizontalAxis(HorizontalAxis, false); //Make sure the horizontal axis is moved up if the vertical axis has a negative minimum value, so that the 0 value is at the correct position. - if (HorizontalAxis.Axis.TickLabelPosition == eTickLabelPosition.NextTo && VerticalAxis.Axis.AxisType == eAxisType.Val && VerticalAxis.Min < 0D) + if (HorizontalAxis.Axis.TickLabelPosition == eTickLabelPosition.NextTo && VerticalAxis.Axis.AxisType == eAxisType.Val && VerticalAxis.Min < 0D && HorizontalAxis.Axis.Crosses == eCrosses.AutoZero) { - var newtop = VerticalAxis.GetPositionInPlotarea(0D) + Plotarea.Group.Top; - var topDiff = HorizontalAxis.Rectangle.Top - newtop; - HorizontalAxis.Rectangle.Top = newtop; - HorizontalAxis.Rectangle.Height += topDiff; - HorizontalAxis.Line.Y1 = HorizontalAxis.Line.Y2 = newtop; + var CrossValue = HorizontalAxis.GetCrossesValue(); + var newtop = VerticalAxis.GetPositionInPlotarea(CrossValue) + Plotarea.Group.Top; + if (newtop > Plotarea.Group.Top) + { + var topDiff = HorizontalAxis.Rectangle.Top - newtop; + HorizontalAxis.Rectangle.Top = newtop; + HorizontalAxis.Rectangle.Height += topDiff; + HorizontalAxis.Line.Y1 = HorizontalAxis.Line.Y2 = newtop; + } } HorizontalAxis.AddTickmarksAndValues(DefItems); @@ -379,15 +387,14 @@ private void SetChartArea(SvgRenderOptions options) var reference = Chart.StyleManager.Style?.ChartArea.BorderReference; - var chartBorder = GetChartAreaDefaultColor((int)styleType, out ExcelThemeLine themedLine); + //var chartStyleId = Chart.StyleManager.Style.Id; - item.Rectangle.SetDrawingBorderPropertiesNew( + item.Rectangle.SetDrawingPropertiesBorder( Theme, - reference?.Color, - Chart.Border, - 1d, + Chart.Border, + reference?.Color, Chart.Border.Fill.Style != eFillStyle.NoFill, - () => item.GetDefaultBorderColor()); + () => item.GetDefaultBorderColor(), 0.75d); item.Rectangle.RoundedCornerRadius = Chart.RoundedCorners ? 9 : 0; item.AppendRenderItems(RenderItems); @@ -395,64 +402,6 @@ private void SetChartArea(SvgRenderOptions options) ChartArea = item; } - private Color? GetChartAreaDefaultColor(int styleId, out ExcelThemeLine themedLine) - { - themedLine = null; - Color? themeColor = null; - styleId = styleId > (int)eChartStyle.Style48 ? (int)eChartStyle.Style2 : styleId; - - if (styleId == 0) - { - return Color.Empty; - } - - themedLine = Theme.FormatScheme.BorderStyle[0]; - var bg = Theme.FormatScheme.BackgroundFillStyle[0]; - - if(themedLine.HasFill == false) - { - //Node exists but has no fill. Excel considers this the same as transparent/noFill - return Color.Transparent; - } - //TODO: Fix for colortypes other than solidFill - themeColor = tc.ColorConverter.GetThemeColor(Theme, themedLine.Fill.SolidFill.Color); - - if (themedLine.Fill.SolidFill.Color.ColorType == eDrawingColorType.Scheme && themedLine.Fill.SolidFill.Color.SchemeColor.Color == eSchemeColor.Style) - { - if (styleId <= 40) - { - //Text1 AKA dk1 (in standard case) - themeColor = tc.ColorConverter.GetThemeColor(Theme, eThemeSchemeColor.Text1); - - //var bg1Col = tc.ColorConverter.GetThemeColor(Theme, eThemeSchemeColor.Background1); - - if (themedLine.Fill.SolidFill.Color.Transforms.Count > 0) - { - //themeColor = tc.ColorConverter.ApplyTintDrawing(themeColor.Value, 0.15d); - //but even in this case if there is no ln node found in style it appears to default to 75% despite a scheme color existing in the theme - themeColor = tc.ColorConverter.ApplyTransforms(themeColor.Value, themedLine.Fill.SolidFill.Color.Transforms); - } - else - { - //Default value Should arguably be 75% tint themeColor but something is strange... - //It appears closer to 50% in this specific case - //It also appears to be tx1 (black) and apply color and tint 0.25 in vba - var newTheme = tc.ColorConverter.ApplyTintDrawing(themeColor.Value, 0.25d); - themeColor = newTheme; - } - } - else - { - //41-48 - //aka light1 - themeColor = tc.ColorConverter.GetThemeColor(Theme, eThemeSchemeColor.Background1); - themedLine = null; - } - } - return themeColor; - } - - private ChartAxisRenderer GetAxis(bool vertical, int offset = 0) { var axis = (ExcelChartAxisStandard)Chart.Axis[offset]; @@ -539,17 +488,18 @@ internal double GetPlotAreaTop() internal LineRenderItem GetSeriesIcon(ExcelChartStandardSerie s, int index, BoundingBox parentItem) { const float MarginExtra = 1.5f; - const float LineLength = 21; + const float DefaultStrokeWidth = 0.75f; + const float LineLength = 21.0f; var item = new LineRenderItem(parentItem); item.SetDrawingPropertiesFill(Theme, s.Fill, Chart.StyleManager.Style.SeriesLine.FillReference.Color, UserSpaceSettings.ObjectBoundingBox); - item.SetDrawingPropertiesBorder(Theme, s.Border, Chart.StyleManager.Style.SeriesLine.BorderReference.Color, s.Border.Fill.Style != eFillStyle.NoFill, null, 0.75, UserSpaceSettings.ObjectBoundingBox); + item.SetDrawingPropertiesBorder(Theme, s.Border, Chart.StyleManager.Style.SeriesLine.BorderReference.Color, s.Border.Fill.Style != eFillStyle.NoFill, null, DefaultStrokeWidth, UserSpaceSettings.ObjectBoundingBox); float y = (float)parentItem.Top + MarginExtra; float x = 0; item.X1 = x; item.Y1 = y; - item.X2 = x + LineLength; + item.X2 = x + (LineLength - (float)item.BorderWidth); item.Y2 = y; item.LineCap = LineCap.Round; diff --git a/src/EPPlus/Drawing/Renderer/RenderItems/DrawingRenderItemExtentions.cs b/src/EPPlus/Drawing/Renderer/RenderItems/DrawingRenderItemExtentions.cs index 82a6098d2f..bab68463a0 100644 --- a/src/EPPlus/Drawing/Renderer/RenderItems/DrawingRenderItemExtentions.cs +++ b/src/EPPlus/Drawing/Renderer/RenderItems/DrawingRenderItemExtentions.cs @@ -57,10 +57,7 @@ internal static void SetDrawingPropertiesFill(this RenderItem item, ExcelTheme t internal static void SetDrawingPropertiesFillBasic(this RenderItem item, ExcelTheme theme, ExcelDrawingFillBasic fill, ExcelDrawingColorManager color, UserSpaceSettings gradientUserSpaceOnUse, Color? nullColor) { double opacity = double.NaN; - double? opacityOld = double.NaN; - - var oldFill = GetFillColor(theme, fill, color, item.FillColorSource, out opacityOld, nullColor); - var fillNew = GetFillNew(fill, theme, color, item.FillColorSource, out opacity, () => { return nullColor; }, out DrawingRenderGradientFill gradFill); + var fillNew = GetFillNew(fill, theme, color, item.FillColorSource, out opacity, () => { return nullColor; }, out DrawingRenderGradientFill gradFill, gradientUserSpaceOnUse); if(gradFill != null) { @@ -78,33 +75,6 @@ internal static void SetDrawingPropertiesFillBasic(this RenderItem item, ExcelTh { item.FillOpacity = opacity; } - - //switch (fill.Style) - //{ - // case eFillStyle.NoFill: - // item.FillColor = GetFillNew(fill) - // //if (fill.IsEmpty) //Do NOT remove. This if is required for Shapes - // //{ - // // item.FillColor = GetFillColor(theme, fill, color, item.FillColorSource, out opacity, nullColor); - // //} - // //else - // //{ - // // item.FillColor = "none"; - // //} - // break; - // case eFillStyle.SolidFill: - // item.FillColor = GetFillColor(theme, fill, color, item.FillColorSource, out opacity); - // break; - // case eFillStyle.GradientFill: - // item.GradientFill = new DrawingRenderGradientFill(theme, fill.GradientFill, gradientUserSpaceOnUse); - // item.FillType = FillType.GradientFill; - // item.FillColor = null; - // break; - //} - //if (opacity.HasValue) - //{ - // item.FillOpacity = opacity; - //} } //bg1 is the hard-coded default of solid fill according to ooxml docs (MS-OE376) @@ -155,11 +125,16 @@ private static string GetFallbackFill(ExcelTheme theme, ExcelDrawingFillBasic it //Move on to 3. Theme fc = GetFillColorFromTheme(theme, GetDefaultThemeColor); + if(fc.HasValue && fc.Value.ToArgb() == Color.Transparent.ToArgb()) + { + opacity = 1d; + return "none"; + } } } else { - opacity = 0d; + opacity = 1d; //The node has specifically been set to NoFill AKA Transparent return "none"; } @@ -187,7 +162,7 @@ private static string GetAdjustmentsAndTransparency(Color fc, PathFillMode color return "#" + fc.ToArgb().ToString("x8").Substring(2); } - internal static string GetFillNew(ExcelDrawingFillBasic fill, ExcelTheme theme, ExcelDrawingColorManager reference, PathFillMode fillMode, out double opacity, Func GetHardCodedDefaultForItem, out DrawingRenderGradientFill gradFill) + internal static string GetFillNew(ExcelDrawingFillBasic fill, ExcelTheme theme, ExcelDrawingColorManager reference, PathFillMode fillMode, out double opacity, Func GetHardCodedDefaultForItem, out DrawingRenderGradientFill gradFill, UserSpaceSettings gradientUserSpaceOnUse = UserSpaceSettings.UserSpaceOnUse_Global) { string fillStr = string.Empty; gradFill = null; @@ -218,7 +193,7 @@ internal static string GetFillNew(ExcelDrawingFillBasic fill, ExcelTheme theme, fillStr = GetAdjustmentsAndTransparency(fc, fillMode, out opacity); break; case eFillStyle.GradientFill: - gradFill = new DrawingRenderGradientFill(theme, fill.GradientFill, UserSpaceSettings.UserSpaceOnUse_Global); + gradFill = new DrawingRenderGradientFill(theme, fill.GradientFill, gradientUserSpaceOnUse); break; } @@ -226,26 +201,28 @@ internal static string GetFillNew(ExcelDrawingFillBasic fill, ExcelTheme theme, return fillStr; } - internal static void SetDrawingBorderPropertiesNew(this RenderItem item, ExcelTheme theme, ExcelChartStyleColorManager reference, ExcelDrawingBorder border, double opacity, bool hasBorder, Func GetHardCodedDefaultForItem) + internal static void SetDrawingPropertiesBorder(this RenderItem item, ExcelTheme theme, ExcelDrawingBorder border, ExcelChartStyleColorManager reference, bool hasBorder, Func GetStyleDefaultColor, double defaultWidth = 1.5d, UserSpaceSettings gradientUserSpaceOnUse = UserSpaceSettings.UserSpaceOnUse_Global) { string fillColorStr = null; DrawingRenderGradientFill gradFill = null; + double opacity = 1d; + if (border == null) { if (hasBorder) { - fillColorStr = GetFillNew(null, theme, reference, item.BorderColorSource, out opacity, GetHardCodedDefaultForItem, out gradFill); + fillColorStr = GetFillNew(null, theme, reference, item.BorderColorSource, out opacity, GetStyleDefaultColor, out gradFill); } } else { - fillColorStr = GetFillNew(border.Fill, theme, reference, item.BorderColorSource, out opacity, GetHardCodedDefaultForItem, out gradFill); + fillColorStr = GetFillNew(border.Fill, theme, reference, item.BorderColorSource, out opacity, GetStyleDefaultColor, out gradFill); } if(gradFill != null) { //Special case as gradfill does not return a string - item.BorderGradientFill = new DrawingRenderGradientFill(theme, border.Fill.GradientFill, UserSpaceSettings.UserSpaceOnUse_Global); + item.BorderGradientFill = new DrawingRenderGradientFill(theme, border.Fill.GradientFill, gradientUserSpaceOnUse); item.BorderColor = null; } else @@ -258,7 +235,7 @@ internal static void SetDrawingBorderPropertiesNew(this RenderItem item, ExcelTh if (item.BorderColorSource != PathFillMode.None) { - item.BorderWidth = (border?.Width ?? 0D) == 0D ? 0.75d : border.Width; + item.BorderWidth = (border?.Width ?? 0D) == 0D ? defaultWidth : border.Width; if (border != null && border.LineStyle.HasValue && border.LineStyle != eLineStyle.Solid) { item.BorderDashArray = GetDashArray(border, item.BorderWidth.Value); @@ -271,63 +248,63 @@ internal static void SetDrawingBorderPropertiesNew(this RenderItem item, ExcelTh } } - internal static void SetDrawingPropertiesBorder(this RenderItem item, ExcelTheme theme, ExcelDrawingBorder border, ExcelChartStyleColorManager color, bool hasBorder, Color? nullColor=null, double defaultWidth = 1.5, UserSpaceSettings gradientUserSpaceOnUse = UserSpaceSettings.UserSpaceOnUse_Global, eChartStyle styleId = eChartStyle.Style2) - { - double? opacity = null; - GetFillColor(theme, null, color, item.BorderColorSource, out opacity, nullColor ?? theme.ColorScheme.Dark1.GetColor()); - opacity = double.NaN; - SetDrawingBorderPropertiesNew(item, theme, color, border, opacity.Value, hasBorder, () => { return nullColor; }); - //if (border == null) - //{ - // if (hasBorder) - // { - // item.BorderColor = GetFillColor(theme, null, color, item.BorderColorSource, out opacity, nullColor ?? theme.ColorScheme.Dark1.GetColor()); - // } - //} - //else - //{ - // switch (border.Fill.Style) - // { - // case eFillStyle.NoFill: - // if (border.Fill.IsEmpty) - // { - // item.BorderColor = GetFillColor(theme, border.Fill, color, item.BorderColorSource, out opacity, nullColor ?? theme.ColorScheme.Dark1.GetColor()); - // } - // else - // { - // item.BorderColor = "none"; - // } - // break; - // case eFillStyle.SolidFill: - // item.BorderColor = GetFillColor(theme, border.Fill, color, item.BorderColorSource, out opacity); - // item.BorderGradientFill = null; - // break; - // case eFillStyle.GradientFill: - // item.BorderGradientFill = new DrawingRenderGradientFill(theme, border.Fill.GradientFill, gradientUserSpaceOnUse); - // item.BorderColor = null; - // break; - // } - //} - - //if (opacity != double.NaN) - //{ - // item.BorderOpacity = opacity; - //} - - //if (hasBorder && item.BorderColorSource != PathFillMode.None) - //{ - // item.BorderWidth = (border?.Width??0D) == 0D ? defaultWidth : border.Width; - // if (border!=null && border.LineStyle.HasValue && border.LineStyle != eLineStyle.Solid) - // { - // item.BorderDashArray = GetDashArray(border, item.BorderWidth.Value); - // } - // if (border != null && border.CompoundLineStyle != eCompoundLineStyle.Single) - // { - // item.CompoundLineStyle = (CompoundLineStyle)border.CompoundLineStyle; - // //TODO:Add support double compound borders. - // } - //} - } + //internal static void SetDrawingPropertiesBorder(this RenderItem item, ExcelTheme theme, ExcelDrawingBorder border, ExcelChartStyleColorManager color, bool hasBorder, Color? nullColor=null, double defaultWidth = 1.5, UserSpaceSettings gradientUserSpaceOnUse = UserSpaceSettings.UserSpaceOnUse_Global, eChartStyle styleId = eChartStyle.Style2) + //{ + // double? opacity = null; + // //GetFillColor(theme, null, color, item.BorderColorSource, out opacity, nullColor ?? theme.ColorScheme.Dark1.GetColor(), styleId); + // opacity = double.NaN; + // SetDrawingPropertiesBorder(item, theme, color, border, opacity.Value, hasBorder, () => { return nullColor; }, gradientUserSpaceOnUse); + // //if (border == null) + // //{ + // // if (hasBorder) + // // { + // // item.BorderColor = GetFillColor(theme, null, color, item.BorderColorSource, out opacity, nullColor ?? theme.ColorScheme.Dark1.GetColor()); + // // } + // //} + // //else + // //{ + // // switch (border.Fill.Style) + // // { + // // case eFillStyle.NoFill: + // // if (border.Fill.IsEmpty) + // // { + // // item.BorderColor = GetFillColor(theme, border.Fill, color, item.BorderColorSource, out opacity, nullColor ?? theme.ColorScheme.Dark1.GetColor()); + // // } + // // else + // // { + // // item.BorderColor = "none"; + // // } + // // break; + // // case eFillStyle.SolidFill: + // // item.BorderColor = GetFillColor(theme, border.Fill, color, item.BorderColorSource, out opacity); + // // item.BorderGradientFill = null; + // // break; + // // case eFillStyle.GradientFill: + // // item.BorderGradientFill = new DrawingRenderGradientFill(theme, border.Fill.GradientFill, gradientUserSpaceOnUse); + // // item.BorderColor = null; + // // break; + // // } + // //} + + // //if (opacity != double.NaN) + // //{ + // // item.BorderOpacity = opacity; + // //} + + // //if (hasBorder && item.BorderColorSource != PathFillMode.None) + // //{ + // // item.BorderWidth = (border?.Width??0D) == 0D ? defaultWidth : border.Width; + // // if (border!=null && border.LineStyle.HasValue && border.LineStyle != eLineStyle.Solid) + // // { + // // item.BorderDashArray = GetDashArray(border, item.BorderWidth.Value); + // // } + // // if (border != null && border.CompoundLineStyle != eCompoundLineStyle.Single) + // // { + // // item.CompoundLineStyle = (CompoundLineStyle)border.CompoundLineStyle; + // // //TODO:Add support double compound borders. + // // } + // //} + //} internal static void SetDrawingPropertiesEffects(this RenderItem item, ExcelTheme theme, ExcelDrawingEffectStyle effect) { if (effect.HasGlow) diff --git a/src/EPPlus/Drawing/Renderer/RenderItems/Fill/DrawingRenderGradientFill.cs b/src/EPPlus/Drawing/Renderer/RenderItems/Fill/DrawingRenderGradientFill.cs index b29c8ef8a1..973b959abb 100644 --- a/src/EPPlus/Drawing/Renderer/RenderItems/Fill/DrawingRenderGradientFill.cs +++ b/src/EPPlus/Drawing/Renderer/RenderItems/Fill/DrawingRenderGradientFill.cs @@ -34,6 +34,17 @@ public DrawingRenderGradientFill(ExcelTheme theme, ExcelDrawingGradientFill grad Colors.Add(c); } + //Node is empty. Add excel's hardcoded default + if(gradientFill._topNode.HasChildNodes == false) + { + var c = new GradientFillColor(0, Color.Black); + c.Opacity = 1; + var c2 = new GradientFillColor(100, Color.White); + c2.Opacity = 1; + Colors.Add(c); + Colors.Add(c2); + } + if (gradientFill.FocusPoint != null) { FocusPoint = gradientFill.FocusPoint.AsOffsetRectangle(); diff --git a/src/EPPlus/Drawing/Renderer/ShapeRenderer.cs b/src/EPPlus/Drawing/Renderer/ShapeRenderer.cs index e00d01666c..7016f88513 100644 --- a/src/EPPlus/Drawing/Renderer/ShapeRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/ShapeRenderer.cs @@ -22,6 +22,7 @@ Date Author Change using EPPlusImageRenderer.RenderItems; using OfficeOpenXml; using OfficeOpenXml.Drawing.Renderer.TextBox; +using OfficeOpenXml.Utils.TypeConversion; using OfficeOpenXml.Utils.Drawing; using System; using System.Collections.Generic; @@ -180,10 +181,11 @@ protected RenderItem AddFromPaths(BoundingBox parent, DrawingPath path, bool dra pi.Commands[pi.Commands.Count - 1].Coordinates = coordinates.ToArray(); } var shape = (ExcelShape)Drawing; + var shapeDefaultStyle = Theme.ObjectDefaults.ShapeDefinition.Style; if (drawFill) { - pi.FillColorSource = path.Fill; - pi.SetDrawingPropertiesFill(Theme, shape.Fill, shape.ThemeStyles.FillReference.Color); + pi.FillColorSource = path.Fill; + pi.SetDrawingPropertiesFill(Theme, shape.Fill, shape.ThemeStyles.FillReference.Color, UserSpaceSettings.ObjectBoundingBox, ColorConverter.GetThemeColor(Theme, shapeDefaultStyle.FillReference.ShapeColor)); } else { @@ -194,7 +196,7 @@ protected RenderItem AddFromPaths(BoundingBox parent, DrawingPath path, bool dra if (drawBorder) { pi.BorderColorSource = path.Stroke ? PathFillMode.Norm : PathFillMode.None; - pi.SetDrawingPropertiesBorder(Theme, shape.Border, shape.ThemeStyles.BorderReference.Color, path.Stroke); + pi.SetDrawingPropertiesBorder(Theme, shape.Border, shape.ThemeStyles.BorderReference.Color, path.Stroke, ()=> ColorConverter.GetThemeColor(Theme, shapeDefaultStyle.BorderReference.ShapeColor), 0.75d); } else { diff --git a/src/EPPlus/Drawing/Shape/DefaultShapeDefinition.cs b/src/EPPlus/Drawing/Shape/DefaultShapeDefinition.cs new file mode 100644 index 0000000000..28c1645bb0 --- /dev/null +++ b/src/EPPlus/Drawing/Shape/DefaultShapeDefinition.cs @@ -0,0 +1,88 @@ +using OfficeOpenXml.Drawing.Interfaces; +using OfficeOpenXml.Drawing.Shape.Style; +using System.Xml; + +namespace OfficeOpenXml.Drawing.Shape +{ + /// + /// Roughly Represents CT_DefaultShapeDefinition + /// + internal class DefaultShapeDefinition : XmlHelper + { + string _fillPath = "{0}/{1}:spPr"; + string _defaultTextBodyPath = "{0}/{1}:bodyPr"; + string _stylePath = "{0}/{1}:style"; + + //Do we support this? Does Excel? Excel appears to in this specific case. + //TODO: ImplementTextList + + //TODO: Implement ExtLst + //private ExtLst + + private readonly IPictureRelationDocument _pictureRelationDocument; + + string _prefix; + + internal DefaultShapeDefinition(XmlNamespaceManager nsm, XmlNode topNode, string path, IPictureRelationDocument pictureRelationDocument, string prefix = "a") : base(nsm, topNode) + { + _prefix = prefix; + + _fillPath = string.Format(_fillPath, path, _prefix); + _defaultTextBodyPath = string.Format(_defaultTextBodyPath, path, _prefix); + _stylePath = string.Format(_stylePath, path, _prefix); + } + + + private ExcelDrawingFill _fill; + /// + /// + /// Reference to fill settings for a chart part + /// + public ExcelDrawingFill Fill + { + get + { + if (_fill == null) + { + _fill = new ExcelDrawingFill(_pictureRelationDocument, NameSpaceManager, TopNode, _fillPath, SchemaNodeOrder); + } + return _fill; + } + } + + private ExcelTextBody _defaultTextBody = null; + /// + /// Reference to default text body run settings for a chart part + /// + public ExcelTextBody DefaultTextBody + { + get + { + if (_defaultTextBody == null) + { + _defaultTextBody = new ExcelTextBody(_pictureRelationDocument, NameSpaceManager, TopNode, _defaultTextBodyPath); + } + return _defaultTextBody; + + } + } + + ExcelShapeStyleEntry _style; + + /// + /// Reference to default text body run settings for a chart part + /// + public ExcelShapeStyleEntry Style + { + get + { + if (_style == null) + { + _style = new ExcelShapeStyleEntry(NameSpaceManager, TopNode, _stylePath, _pictureRelationDocument, _prefix); + } + return _style; + + } + } + } +} diff --git a/src/EPPlus/Drawing/Shape/Style/ExcelShapeStyleEntry.cs b/src/EPPlus/Drawing/Shape/Style/ExcelShapeStyleEntry.cs new file mode 100644 index 0000000000..23251d360b --- /dev/null +++ b/src/EPPlus/Drawing/Shape/Style/ExcelShapeStyleEntry.cs @@ -0,0 +1,86 @@ +using OfficeOpenXml.Drawing.Chart.Style; +using OfficeOpenXml.Drawing.Interfaces; +using System.Xml; + +namespace OfficeOpenXml.Drawing.Shape.Style +{ + //Style (CT_ShapeStyle node) + internal class ExcelShapeStyleEntry : XmlHelper + { + string _fillReferencePath = "{0}/{1}:fillRef"; + string _borderReferencePath = "{0}/{1}:lnRef"; + string _effectReferencePath = "{0}/{1}:effectRef"; + string _fontReferencePath = "{0}/{1}:fontRef"; + + private readonly IPictureRelationDocument _pictureRelationDocument; + internal ExcelShapeStyleEntry(XmlNamespaceManager nsm, XmlNode topNode, string path, IPictureRelationDocument pictureRelationDocument, string prefix = "a") : base(nsm, topNode) + { + _fillReferencePath = string.Format(_fillReferencePath, path, prefix); + _borderReferencePath = string.Format(_borderReferencePath, path, prefix); + _effectReferencePath = string.Format(_effectReferencePath, path, prefix); + _fontReferencePath = string.Format(_fontReferencePath, path, prefix); + } + private ShapeStyleReference _borderReference = null; + /// Border reference. + /// Contains an index reference to the theme and a color to be used in border styling + public ShapeStyleReference BorderReference + { + get + { + if (_borderReference == null) + { + _borderReference = new ShapeStyleReference(NameSpaceManager, TopNode, _borderReferencePath); + } + return _borderReference; + } + } + private ShapeStyleReference _fillReference = null; + /// + /// Fill reference. + /// Contains an index reference to the theme and a fill color to be used in fills + /// + public ShapeStyleReference FillReference + { + get + { + if (_fillReference == null) + { + _fillReference = new ShapeStyleReference(NameSpaceManager, TopNode, _fillReferencePath); + } + return _fillReference; + } + } + private ShapeStyleReference _effectReference = null; + /// + /// Effect reference. + /// Contains an index reference to the theme and a color to be used in effects + /// + public ShapeStyleReference EffectReference + { + get + { + if (_effectReference == null) + { + _effectReference = new ShapeStyleReference(NameSpaceManager, TopNode, _effectReferencePath); + } + return _effectReference; + } + } + ExcelChartStyleFontReference _fontReference = null; + /// + /// Font reference. + /// Contains an index reference to the theme and a color to be used for font styling + /// + public ExcelChartStyleFontReference FontReference + { + get + { + if (_fontReference == null) + { + _fontReference = new ExcelChartStyleFontReference(NameSpaceManager, TopNode, _fontReferencePath); + } + return _fontReference; + } + } + } +} diff --git a/src/EPPlus/Drawing/Shape/Style/ExcelShapeStyleFontReference.cs b/src/EPPlus/Drawing/Shape/Style/ExcelShapeStyleFontReference.cs new file mode 100644 index 0000000000..3ca7e56d8b --- /dev/null +++ b/src/EPPlus/Drawing/Shape/Style/ExcelShapeStyleFontReference.cs @@ -0,0 +1,65 @@ +using OfficeOpenXml.Drawing.Chart.Style; +using OfficeOpenXml.Drawing.Style.Coloring; +using OfficeOpenXml.Drawing.Theme; +using OfficeOpenXml.Utils.EnumUtils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml; + +namespace OfficeOpenXml.Drawing.Shape.Style +{ + //Represents CT_FontReference + internal class ExcelShapeStyleFontReference : XmlHelper + { + string _path; + internal ExcelShapeStyleFontReference(XmlNamespaceManager nsm, XmlNode topNode, string path) : base(nsm, topNode) + { + _path = path; + } + /// + /// The index to the style matrix. + /// This property referes to the theme + /// + public eThemeFontCollectionType Index + { + get + { + return GetXmlNodeString($"{_path}/@idx").ToEnum(eThemeFontCollectionType.None); + } + set + { + SetXmlNodeString($"{_path}/@idx", value.ToEnumString()); + } + } + ExcelDrawingColorManager _color = null; + /// + /// The color of the font + /// This will replace any the StyleClr node in the chart style xml. + /// + public ExcelDrawingColorManager Color + { + get + { + if (_color == null) + { + _color = new ExcelDrawingColorManager(NameSpaceManager, TopNode, _path, SchemaNodeOrder); + } + + return _color; + } + } + /// + /// If the reference has a color + /// + public bool HasColor + { + get + { + var node = GetNode(_path); + return node != null && node.HasChildNodes; + } + } + } +} diff --git a/src/EPPlus/Drawing/Shape/Style/ShapeStyleReference.cs b/src/EPPlus/Drawing/Shape/Style/ShapeStyleReference.cs new file mode 100644 index 0000000000..a4497ca4de --- /dev/null +++ b/src/EPPlus/Drawing/Shape/Style/ShapeStyleReference.cs @@ -0,0 +1,68 @@ +using OfficeOpenXml.Drawing.Chart.Style; +using OfficeOpenXml.Drawing.Style.Coloring; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Xml; + +//StyleMatrixReference +namespace OfficeOpenXml.Drawing.Shape.Style +{ + public class ShapeStyleReference : XmlHelper + { + string _path; + internal ShapeStyleReference(XmlNamespaceManager nsm, XmlNode topNode, string path) : base(nsm, topNode) + { + _path = path; + } + + /// + /// The index to the theme style matrix. + /// + /// + public int Index + { + get + { + return GetXmlNodeInt($"{_path}/@idx"); + } + set + { + if (value < 0) throw new ArgumentOutOfRangeException("Index", "Can't be negative"); + SetXmlNodeString($"{_path}/@idx", value.ToString(CultureInfo.InvariantCulture)); + } + } + + ExcelDrawingColorManager _color; + /// + /// The color to be used for the reference. + /// simplerForm of Color on ChartNodes + /// + public ExcelDrawingColorManager ShapeColor + { + get + { + if (_color == null) + { + _color = new ExcelDrawingColorManager(NameSpaceManager, TopNode, _path, SchemaNodeOrder); + } + + return _color; + } + } + /// + /// If the reference has a color + /// + public bool HasColor + { + get + { + var node = GetNode(_path); + return node != null && node.HasChildNodes; + } + } + } +} diff --git a/src/EPPlus/Drawing/Style/Coloring/ExcelDrawingColorManager.cs b/src/EPPlus/Drawing/Style/Coloring/ExcelDrawingColorManager.cs index 23ba26ebc9..ec581de8e9 100644 --- a/src/EPPlus/Drawing/Style/Coloring/ExcelDrawingColorManager.cs +++ b/src/EPPlus/Drawing/Style/Coloring/ExcelDrawingColorManager.cs @@ -14,6 +14,7 @@ Date Author Change using System; using System.Linq; using System.Collections.Generic; +using System.Drawing; namespace OfficeOpenXml.Drawing.Style.Coloring { diff --git a/src/EPPlus/Drawing/Style/Coloring/ExcelDrawingThemeColorManager.cs b/src/EPPlus/Drawing/Style/Coloring/ExcelDrawingThemeColorManager.cs index 84ab4ef526..3e95304b16 100644 --- a/src/EPPlus/Drawing/Style/Coloring/ExcelDrawingThemeColorManager.cs +++ b/src/EPPlus/Drawing/Style/Coloring/ExcelDrawingThemeColorManager.cs @@ -292,7 +292,7 @@ private XmlNode GetPathNode() } return _pathNode; } - internal Color GetColor() + internal virtual Color GetColor() { return OfficeOpenXml.Utils.TypeConversion.ColorConverter.GetThemeColor(this); } diff --git a/src/EPPlus/Drawing/Theme/ExcelThemeBase.cs b/src/EPPlus/Drawing/Theme/ExcelThemeBase.cs index 9d16bbd2dd..66b71568d4 100644 --- a/src/EPPlus/Drawing/Theme/ExcelThemeBase.cs +++ b/src/EPPlus/Drawing/Theme/ExcelThemeBase.cs @@ -27,6 +27,8 @@ public class ExcelThemeBase : XmlHelper, IPictureRelationDocument readonly string _colorSchemePath = "{0}a:clrScheme"; readonly string _fontSchemePath = "{0}a:fontScheme"; readonly string _fmtSchemePath = "{0}a:fmtScheme"; + readonly string _objectDefaultsPath = "{0}a:objectDefaults"; + readonly ExcelPackage _pck; Dictionary _hashes=new Dictionary(); internal ExcelThemeBase(ExcelPackage package, XmlNamespaceManager nsm, ZipPackageRelationship rel, string path) @@ -42,6 +44,8 @@ internal ExcelThemeBase(ExcelPackage package, XmlNamespaceManager nsm, ZipPackag _colorSchemePath = string.Format(_colorSchemePath, path); _fontSchemePath = string.Format(_fontSchemePath, path); _fmtSchemePath = string.Format(_fmtSchemePath, path); + //ObjectDefaults is part of the Theme node rather than themeElements + _objectDefaultsPath = string.Format(_objectDefaultsPath, ""); _pck = package; if (!NameSpaceManager.HasNamespace("a")) NameSpaceManager.AddNamespace("a", ExcelPackage.schemaDrawings); } @@ -98,6 +102,23 @@ public ExcelFormatScheme FormatScheme } } + ExcelThemeObjectDefaults _objectDefaults = null; + + /// + /// + /// + internal ExcelThemeObjectDefaults ObjectDefaults + { + get + { + if (_objectDefaults == null) + { + _objectDefaults = new ExcelThemeObjectDefaults(NameSpaceManager, TopNode.SelectSingleNode(_objectDefaultsPath, NameSpaceManager), this); + } + return _objectDefaults; + } + } + ExcelPackage IPictureRelationDocument.Package { get => _pck; } Dictionary IPictureRelationDocument.Hashes { get => _hashes; } diff --git a/src/EPPlus/Drawing/Theme/ExcelThemeObjectDefaults.cs b/src/EPPlus/Drawing/Theme/ExcelThemeObjectDefaults.cs new file mode 100644 index 0000000000..cbf8f66f68 --- /dev/null +++ b/src/EPPlus/Drawing/Theme/ExcelThemeObjectDefaults.cs @@ -0,0 +1,73 @@ +using OfficeOpenXml.Drawing.Shape; +using System.Xml; + +namespace OfficeOpenXml.Drawing.Theme +{ + internal class ExcelThemeObjectDefaults : XmlHelper + { + private readonly ExcelThemeBase _theme; + private readonly string _path = "objectDefaults"; + private readonly string _spDefPath = "a:spDef"; + private readonly string _lnDefPath = "a:lnDef"; + private readonly string _txDefPath = "a:txDef"; + private const string defaultSpDefInnerXml = ""; + + public ExcelThemeObjectDefaults(XmlNamespaceManager nameSpaceManager, XmlNode topNode, ExcelThemeBase theme) : base(nameSpaceManager, topNode) + { + _theme = theme; + } + + DefaultShapeDefinition _spDef = null; + DefaultShapeDefinition _lnDef = null; + DefaultShapeDefinition _txDef = null; + + public DefaultShapeDefinition ShapeDefinition + { + get + { + if (_spDef == null) + { + var spDefNode = TopNode.SelectSingleNode(_spDefPath, NameSpaceManager); + //Despite there being no SpDef node/no child nodes Excel Acts as if the @defaultSpDefXml is there. + //Therefore if the node is not there or if it is empty create the default + if (spDefNode == null || spDefNode.HasChildNodes == false) + { + spDefNode = CreateNode(_spDefPath); + spDefNode.InnerXml = defaultSpDefInnerXml; + } + _spDef = new DefaultShapeDefinition(NameSpaceManager, TopNode, _spDefPath, _theme); + } + + return _spDef; + } + } + + public DefaultShapeDefinition LineDefinition + { + get + { + if (_lnDef == null) + { + _lnDef = new DefaultShapeDefinition(NameSpaceManager, TopNode, _lnDefPath, _theme); + } + + return _lnDef; + } + } + + public DefaultShapeDefinition TextDefinition + { + get + { + if (_txDef == null) + { + _txDef = new DefaultShapeDefinition(NameSpaceManager, TopNode, _txDefPath, _theme); + } + + return _txDef; + } + } + + //TODO: Implement ExtLst + } +} diff --git a/src/EPPlus/EPPlus.csproj b/src/EPPlus/EPPlus.csproj index 1719f8fcc2..2fa1b01218 100644 --- a/src/EPPlus/EPPlus.csproj +++ b/src/EPPlus/EPPlus.csproj @@ -1,10 +1,12 @@  net8.0;net9.0;net10.0;netstandard2.1;netstandard2.0;net462 - 9.0.0.0 - 9.0.0.0 + 9.0.0.2 + 9.0.0.2 9.0.0-preview true + + $(TargetsForTfmSpecificBuildOutput);IncludeReferencedProjectsInPackage https://epplussoftware.com EPPlus Software AB license.md @@ -736,16 +738,30 @@ EPPlusLogo.png - + - - - - - + + + + + + + - + + + + + + + + bin\$(Configuration)\$(TargetFramework)\EPPlus.xml @@ -771,18 +787,6 @@ - - - - - - - - - - - - @@ -794,7 +798,7 @@ - + @@ -841,7 +845,7 @@ - Never + Never @@ -869,10 +873,10 @@ Never - PreserveNewest + PreserveNewest PreserveNewest - + \ No newline at end of file diff --git a/src/EPPlus/ExcelHeaderFooter.cs b/src/EPPlus/ExcelHeaderFooter.cs index c5e349a7a8..a7ac0f6cda 100644 --- a/src/EPPlus/ExcelHeaderFooter.cs +++ b/src/EPPlus/ExcelHeaderFooter.cs @@ -65,14 +65,14 @@ internal ExcelHeaderFooterText(XmlNode TextNode, ExcelWorksheet ws, string hf) string text = TextNode.InnerText; string code = text.Substring(0, 2); int startPos = 2; - for (int pos = startPos; pos < text.Length - 2; pos++) + for (int pos = startPos; pos < text.Length - 1; pos++) { string newCode = text.Substring(pos, 2); - if (newCode == "&C" || newCode == "&R") + if (newCode == "&L" || newCode == "&C" || newCode == "&R") { SetText(code, text.Substring(startPos, pos - startPos)); startPos = pos + 2; - pos = startPos; + pos = startPos - 1; code = newCode; } } diff --git a/src/EPPlus/ExcelRange.cs b/src/EPPlus/ExcelRange.cs index f253b1d5ca..d5676cb4fb 100644 --- a/src/EPPlus/ExcelRange.cs +++ b/src/EPPlus/ExcelRange.cs @@ -187,5 +187,6 @@ public void SetFormula(string formula, bool asSharedFormula = true) } } } + } } diff --git a/src/EPPlus/ExcelRangeBase.cs b/src/EPPlus/ExcelRangeBase.cs index 5e55aebba4..ecd4ffa0b6 100644 --- a/src/EPPlus/ExcelRangeBase.cs +++ b/src/EPPlus/ExcelRangeBase.cs @@ -35,6 +35,7 @@ Date Author Change using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -774,7 +775,7 @@ internal ExcelAddressBase GetAddressDimension() { GetAddressDimensionFullRowAndColumn(out int dimFromRow, out int dimFromCol, out int dimToRow, out int dimToCol); //If the range is only full column or full row the dimension of the worksheet, return null. - if (dimFromCol==0 || dimFromRow>dimToCol || dimFromCol > dimToCol) + if (dimFromCol==0 || dimFromRow>dimToRow || dimFromCol > dimToCol) { return null; } @@ -2943,5 +2944,13 @@ public bool UseImplicitItersection } } } + internal ExcelTable GetIntersectingTable() + { + if (_worksheet == null) + { + return null; + } + return _worksheet.Tables.GetIntersectingRanges(this).Select(x => x.Value).FirstOrDefault(); + } } } diff --git a/src/EPPlus/Export/HtmlExport/CssCollections/CssTableRuleCollection.cs b/src/EPPlus/Export/HtmlExport/CssCollections/CssTableRuleCollection.cs index 762981b769..334aa31644 100644 --- a/src/EPPlus/Export/HtmlExport/CssCollections/CssTableRuleCollection.cs +++ b/src/EPPlus/Export/HtmlExport/CssCollections/CssTableRuleCollection.cs @@ -19,6 +19,8 @@ Date Author Change using OfficeOpenXml.Style.Table; using OfficeOpenXml.Table; using System.Collections.Generic; +using System.Data; +using System.Text; using static OfficeOpenXml.Export.HtmlExport.ColumnDataTypeManager; namespace OfficeOpenXml.Export.HtmlExport.CssCollections @@ -104,13 +106,22 @@ internal void AddAlignment(string name, List dataTypes) } } - internal void AddToCollection(string name, ExcelTableStyleElement element, string htmlElement) + internal void AddToCollection(string name, ExcelTableStyleElement element, string htmlElement, bool isRangeName=false) { if (element.Style.HasValue == false) return; //Dont add empty elements var s = element.Style; - var styleClass = new CssRule($"table.{name}{htmlElement}",int.MaxValue); + string rule; + if (isRangeName) + { + rule = $".{name}"; + } + else + { + rule = $"table.{name}{htmlElement}"; + } + var styleClass = new CssRule(rule,int.MaxValue); var translators = new List(); @@ -202,7 +213,45 @@ internal void AddTableToCollection(ExcelTable table, List datatypes, str var tableClassFC = $"{tableClass}-first-column"; AddToCollection($"{tableClassFC}", tblStyle.FirstColumn, " tbody tr td:first-child"); } + internal void AddRangeTableToCollection(ExcelTable table, List datatypes, string tableClassPreset) + { + var tblStyle = table.GetTableNamedStyle(); + + var tableClass = HtmlExportTableUtil.GetTableBaseClassName(table, true); + + //AddHyperlink($"{tableClass}-default", tblStyle.WholeTable); + //AddAlignment($"{tableClass}", datatypes); + + AddToCollection($"{tableClass}-default", tblStyle.WholeTable,"", true); + // AddToCollectionVH($"{tableClass}", tblStyle.WholeTable, ""); + + //Header + AddToCollection($"{tableClass}-header", tblStyle.HeaderRow, "", true); + //AddToCollectionVH($"{tableClass}", tblStyle.HeaderRow, ""); + AddToCollection($"{tableClass}-last-header-cell", tblStyle.LastHeaderCell, "", true); + AddToCollection($"{tableClass}-first-header-cell", tblStyle.FirstHeaderCell, "", true); + + //Total + AddToCollection($"{tableClass}-total", tblStyle.TotalRow, "", true); + //AddToCollectionVH($"{tableClass}", tblStyle.TotalRow, ""); + AddToCollection($"{tableClass}-last-total-cell", tblStyle.LastTotalCell, "", true); + AddToCollection($"{tableClass}-first-total-cell", tblStyle.FirstTotalCell, "", true); + + //Columns stripes + AddToCollection($"{tableClass}-first-col-stripe", tblStyle.FirstColumnStripe, "", true); + AddToCollection($"{tableClass}-second-col-stripe", tblStyle.SecondColumnStripe, "", true); + + //Row stripes + AddToCollection($"{tableClass}-first-row-stripe", tblStyle.FirstRowStripe, "", true); + AddToCollection($"{tableClass}-second-row-stripe", tblStyle.SecondRowStripe, "", true); + + //Last column + AddToCollection($"{tableClass}-last-col", tblStyle.LastColumn, "", true); + + //First column + AddToCollection($"{tableClass}-first-col", tblStyle.FirstColumn, "", true); + } internal void AddOtherCollectionToThisCollection(CssRuleCollection otherCollection) { foreach (var otherRule in otherCollection) diff --git a/src/EPPlus/Export/HtmlExport/Enums/eDrawingInclude.cs b/src/EPPlus/Export/HtmlExport/Enums/eDrawingInclude.cs index d092f66c83..a27c8617ae 100644 --- a/src/EPPlus/Export/HtmlExport/Enums/eDrawingInclude.cs +++ b/src/EPPlus/Export/HtmlExport/Enums/eDrawingInclude.cs @@ -6,28 +6,25 @@ namespace OfficeOpenXml.Export.HtmlExport { /// - /// What drawings to include in html export + /// How to include picture drawings in the html /// - [Flags] public enum eDrawingInclude { /// - /// Include no drawings + /// Do not include supported drawing objects in the html export. Default /// - None = 0, + Exclude, /// - /// Include Shapes + /// Include in css only, so they drawing images can be added manually. /// - Shapes = 2, + IncludeInCssOnly, /// - /// Include Charts + /// Include the drawings as images in the html export. /// - Charts = 4, - - //TODO: This is already handled by image enum. We may need restructure here + Include, /// - /// Include Images ? + /// Include the drawings as images in the HTML only . /// - Images = 8, + IncludeInHtmlOnly } } diff --git a/src/EPPlus/Export/HtmlExport/Enums/ePictureInclude.cs b/src/EPPlus/Export/HtmlExport/Enums/ePictureInclude.cs index b5e8f7bb97..171532fa04 100644 --- a/src/EPPlus/Export/HtmlExport/Enums/ePictureInclude.cs +++ b/src/EPPlus/Export/HtmlExport/Enums/ePictureInclude.cs @@ -11,11 +11,15 @@ Date Author Change 05/11/2021 EPPlus Software AB ExcelTable Html Export *************************************************************************************************/ +using System; + namespace OfficeOpenXml.Export.HtmlExport { /// - /// How to include picture drawings in the html + /// Obsolete: How to include picture drawings in the html + /// /// + [Obsolete("Use general Drawings.Include property to set drawing behaviour.")] public enum ePictureInclude { /// diff --git a/src/EPPlus/Export/HtmlExport/Enums/ePicturePosition.cs b/src/EPPlus/Export/HtmlExport/Enums/ePicturePosition.cs index 4252a49e49..979d16983a 100644 --- a/src/EPPlus/Export/HtmlExport/Enums/ePicturePosition.cs +++ b/src/EPPlus/Export/HtmlExport/Enums/ePicturePosition.cs @@ -11,11 +11,14 @@ Date Author Change 05/11/2021 EPPlus Software AB ExcelTable Html Export *************************************************************************************************/ +using System; + namespace OfficeOpenXml.Export.HtmlExport { /// /// If the Blip is absolut or relative to the table cell /// + [Obsolete("Use eDrawingPosition on the Drawing.Position property instead.")] public enum ePicturePosition { /// @@ -31,4 +34,23 @@ public enum ePicturePosition /// Relative } + /// + /// If the drawing image is absolut or relative to the table cell + /// + public enum eDrawingPosition + { + /// + /// No CSS is added for Position + /// + DontSet, + /// + /// Position is Absolute in the CSS + /// + Absolute, + /// + /// Position is Relative in the CSS + /// + Relative + } + } diff --git a/src/EPPlus/Export/HtmlExport/Exporters/ExcelHtmlExporterBase.cs b/src/EPPlus/Export/HtmlExport/Exporters/ExcelHtmlExporterBase.cs index ced1a36c9f..8297c38a79 100644 --- a/src/EPPlus/Export/HtmlExport/Exporters/ExcelHtmlExporterBase.cs +++ b/src/EPPlus/Export/HtmlExport/Exporters/ExcelHtmlExporterBase.cs @@ -15,6 +15,7 @@ Date Author Change using System.Linq; using System.Text; using OfficeOpenXml.Core; +using OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup; namespace OfficeOpenXml.Export.HtmlExport.Exporters { @@ -73,14 +74,7 @@ public EPPlusReadOnlyList Ranges private void AddRange(ExcelRangeBase range) { - if (range.IsFullColumn && range.IsFullRow) - { - _ranges.Add(new ExcelRangeBase(range.Worksheet, range.Worksheet.Dimension.Address)); - } - else - { - _ranges.Add(range); - } + _ranges.Add(range); } } } diff --git a/src/EPPlus/Export/HtmlExport/Exporters/Internal/AbstractRangeExporter.cs b/src/EPPlus/Export/HtmlExport/Exporters/Internal/AbstractRangeExporter.cs index ca8d63f8bf..283ad78e9f 100644 --- a/src/EPPlus/Export/HtmlExport/Exporters/Internal/AbstractRangeExporter.cs +++ b/src/EPPlus/Export/HtmlExport/Exporters/Internal/AbstractRangeExporter.cs @@ -15,6 +15,7 @@ Date Author Change using OfficeOpenXml.FormulaParsing.Excel.Functions.Text; using OfficeOpenXml.Table; using OfficeOpenXml.Utils.String; +using System; using System.Collections.Generic; using System.Linq; @@ -29,6 +30,7 @@ public AbstractHtmlExporter() internal const string TableClass = "epplus-table"; internal List _rangePictures = null; internal List _rangeDrawings = null; + protected bool _hasIntersectingTables = false; //Intersecting tables; protected List _dataTypes = new List(); protected ExporterContext _exporterContext; @@ -55,11 +57,17 @@ internal void LoadRangeDrawings(List ranges) } _rangePictures = new List(); _rangeDrawings = new List(); + var processedDrawings = new HashSet(); //Render in-cell images. - foreach (var worksheet in ranges.Select(x => x.Worksheet).Distinct()) + foreach (var range in ranges) { + var worksheet = range.Worksheet; foreach (var d in worksheet.Drawings) { + if (processedDrawings.Contains(d)) continue; + processedDrawings.Add(d); + var drawingAddress = d.GetAddress(); + if (drawingAddress.Collide(range) == ExcelAddressBase.eAddressCollition.No) continue; if (d is ExcelPicture p) { p.GetFromBounds(out int fromRow, out int fromRowOff, out int fromCol, out int fromColOff); @@ -79,26 +87,7 @@ internal void LoadRangeDrawings(List ranges) ToColumnOff = toColOff }); } - else if(d is ExcelShape s) - { - s.GetFromBounds(out int fromRow, out int fromRowOff, out int fromCol, out int fromColOff); - s.GetToBounds(out int toRow, out int toRowOff, out int toCol, out int toColOff); - - _rangeDrawings.Add(new HtmlSvgDrawing() - { - WorksheetId = worksheet.PositionId, - Drawing = s, - FromRow = fromRow, - FromRowOff = fromRowOff, - FromColumn = fromCol, - FromColumnOff = fromColOff, - ToRow = toRow, - ToRowOff = toRowOff, - ToColumn = toCol, - ToColumnOff = toColOff - }); - } - else if(d is ExcelChart) + else if(d.SupportsSvgExport && (d is ExcelShape || d is ExcelChart)) { d.GetFromBounds(out int fromRow, out int fromRowOff, out int fromCol, out int fromColOff); d.GetToBounds(out int toRow, out int toRowOff, out int toCol, out int toColOff); @@ -168,5 +157,71 @@ protected HtmlSvgDrawing GetDrawing(int worksheetId, int row, int col) } return null; } + /// + /// Adjust all drawings for the worksheets dimension and include any draings that are outside the dimension. + /// + /// + /// + protected void AdjustRangeForDimensionAndDrawings(List ranges, bool includeDrawings) + { + for(int i=0;i drawMinRow) + { + if (toRowOff > 0) toRow++; + if (toColOff > 0) toCol++; + } + + if (range.Collide(fromRow, fromCol, toRow, toCol) != ExcelAddressBase.eAddressCollition.Inside) + { + if (fromRow < drawMinRow) drawMinRow = fromRow; + if (fromCol < drawMinCol) drawMinCol = fromCol; + if (toRow > drawMaxRow) drawMaxRow = toRow; + if (toCol > drawMaxCol) drawMaxCol = toCol; + } + } + } + + if (newRange != null && + newRange._fromRow > drawMinRow || + newRange._fromCol > drawMinCol || + newRange._toRow > drawMinRow || + newRange._toCol > drawMinCol) + { + return range.Worksheet.Cells[drawMinRow < newRange._fromRow ? Math.Max(drawMinRow, range._fromRow) : newRange._fromRow, + drawMinCol < newRange._fromCol ? Math.Max(drawMinCol, range._fromCol) : newRange._fromCol, + drawMaxRow > newRange._toRow ? Math.Min(drawMaxRow, range._toRow) : newRange._toRow, + drawMaxCol > newRange._toCol ? Math.Min(drawMaxCol, range._toCol) : newRange._toCol]; + } + } + return range.Worksheet.Cells[newRange.Address]; + } } } diff --git a/src/EPPlus/Export/HtmlExport/Exporters/Internal/CssExporterBase.cs b/src/EPPlus/Export/HtmlExport/Exporters/Internal/CssExporterBase.cs index 74dada4db1..e472642575 100644 --- a/src/EPPlus/Export/HtmlExport/Exporters/Internal/CssExporterBase.cs +++ b/src/EPPlus/Export/HtmlExport/Exporters/Internal/CssExporterBase.cs @@ -51,13 +51,13 @@ public CssExporterBase(HtmlExportSettings settings, ExcelRangeBase range) if (range.Addresses == null) { - AddRange(range); + AddRange(range, settings.Drawings.Include!=eDrawingInclude.Exclude); } else { foreach (var address in range.Addresses) { - AddRange(range.Worksheet.Cells[address.Address]); + AddRange(range.Worksheet.Cells[address.Address], settings.Drawings.Include != eDrawingInclude.Exclude); } } } @@ -66,6 +66,7 @@ public CssExporterBase(HtmlRangeExportSettings settings, EPPlusReadOnlyList _ranges = new EPPlusReadOnlyList(); internal const string TableStyleClassPrefix = "ts-"; - private void AddRange(ExcelRangeBase range) + private void AddRange(ExcelRangeBase range, bool includeDrawings) { if (range.IsFullColumn && range.IsFullRow) { - _ranges.Add(new ExcelRangeBase(range.Worksheet, range.Worksheet.Dimension.Address)); + _ranges.Add(AdjustRangeForDimensionAndDrawings(range, includeDrawings)); } else { - _ranges.Add(range); + _ranges.Add(range); } } @@ -134,10 +135,30 @@ protected void AddCssRulesToCollection(CssRangeRuleCollection cssTranslator, Htm ); addedTableStyles.Add(table.TableStyle); } + else + { + var tables = range.Worksheet.Tables.GetIntersectingRanges(range).Select(x=>x.Value).ToList(); + if(tables.Count>0) + { + if (tableSettings == null) + { + tableSettings = new HtmlTableExportSettings() { Minify = Settings.Minify }; + } + + foreach (var t in tables) + { + cssTranslator.AddOtherCollectionToThisCollection + ( + CreateRangeTableCssRules(t, tableSettings, _dataTypes).RuleCollection + ); + addedTableStyles.Add(t.TableStyle); + } + } + } } } - if (Settings.Pictures.Include == ePictureInclude.Include || Settings.Pictures.Include == ePictureInclude.IncludeInCssOnly) + if (Settings.Drawings.Include == eDrawingInclude.Include || Settings.Drawings.Include == eDrawingInclude.IncludeInCssOnly) { LoadRangeDrawings(_ranges._list); foreach (var p in _rangePictures) @@ -146,7 +167,7 @@ protected void AddCssRulesToCollection(CssRangeRuleCollection cssTranslator, Htm } } - if(Settings.Drawings.Include == ePictureInclude.Include || Settings.Drawings.Include == ePictureInclude.IncludeInCssOnly) + if(Settings.Drawings.Include == eDrawingInclude.Include || Settings.Drawings.Include == eDrawingInclude.IncludeInCssOnly) { LoadRangeDrawings(_ranges._list); foreach(var d in _rangeDrawings) @@ -308,6 +329,14 @@ internal static CssTableRuleCollection CreateTableCssRules(ExcelTable table, Htm return tableRules; } + internal static CssTableRuleCollection CreateRangeTableCssRules(ExcelTable table, HtmlTableExportSettings settings, List datatypes) + { + var tableRules = new CssTableRuleCollection(table, settings); + var tableClass = HtmlExportTableUtil.GetTableBaseClassName(table, true); + tableRules.AddRangeTableToCollection(table, datatypes, tableClass); + + return tableRules; + } internal CssWriter GetTableCssWriter(Stream stream, ExcelTable table, HtmlTableExportSettings tableSettings) { diff --git a/src/EPPlus/Export/HtmlExport/Exporters/Internal/HtmlExportTableUtil.cs b/src/EPPlus/Export/HtmlExport/Exporters/Internal/HtmlExportTableUtil.cs index e6cf4ec662..95e04416ed 100644 --- a/src/EPPlus/Export/HtmlExport/Exporters/Internal/HtmlExportTableUtil.cs +++ b/src/EPPlus/Export/HtmlExport/Exporters/Internal/HtmlExportTableUtil.cs @@ -12,9 +12,14 @@ Date Author Change *************************************************************************************************/ using OfficeOpenXml.Export.HtmlExport.HtmlCollections; using OfficeOpenXml.Export.HtmlExport.Settings; +using OfficeOpenXml.FormulaParsing.Excel.Functions.MathFunctions; using OfficeOpenXml.Table; +using System; + + #if !NET35 && !NET40 using System.Threading.Tasks; +using static OfficeOpenXml.RichData.Structures.Constants.SpecialKeyNames; #endif namespace OfficeOpenXml.Export.HtmlExport.Exporters.Internal @@ -68,15 +73,7 @@ internal static string GetWorksheetClassName(string styleClassPrefix, string nam internal static string GetTableClasses(ExcelTable table) { - string styleClass; - if (table.TableStyle == TableStyles.Custom) - { - styleClass = TableStyleClassPrefix + GetClassName(table.StyleName, $"tablestyle{table.Id}"); - } - else - { - styleClass = TableStyleClassPrefix + table.TableStyle.ToString().ToLowerInvariant(); - } + string styleClass = GetTableBaseClassName(table, false); var tblClasses = $"{styleClass}"; if (table.ShowHeader) @@ -112,6 +109,25 @@ internal static string GetTableClasses(ExcelTable table) return tblClasses; } + internal static string GetTableBaseClassName(ExcelTable table, bool inRangeName) + { + string styleClass; + if (table.TableStyle == TableStyles.Custom) + { + styleClass = TableStyleClassPrefix + GetClassName(table.StyleName, $"tablestyle{table.Id}"); + } + else + { + styleClass = TableStyleClassPrefix + table.TableStyle.ToString().ToLowerInvariant(); + if(inRangeName) + { + return styleClass + $"-tbl{table.Id}"; + } + } + + return styleClass; + } + internal static void AddClassesAttributes(HTMLElement element, ExcelTable table, HtmlTableExportSettings settings) { if (table.TableStyle == TableStyles.None) @@ -137,5 +153,66 @@ internal static void AddClassesAttributes(HTMLElement element, ExcelTable table, element.AddAttribute(HtmlAttributes.Id, settings.TableId); } } + + internal static string GetInRangeTableClass(ExcelRangeBase cell, ExcelTable tbl) + { + + var tblStyle = tbl.GetTableNamedStyle(); + + var styleClass = GetTableBaseClassName(tbl, true); + var classes = styleClass + "-default"; + var tblAdr = tbl.Address; + + if (tbl.ShowHeader && tblAdr._fromRow == cell._fromRow && tblAdr._fromCol == cell._fromCol && tblStyle.FirstHeaderCell.Style.HasValue) + { + classes += " " + styleClass + "-first-header-cell"; + } + if (tbl.ShowHeader && tblAdr._fromRow == cell._fromRow && tblAdr._toCol == cell._fromCol && tblStyle.LastHeaderCell.Style.HasValue) + { + classes += " " + styleClass + "-last-header-cell"; + } + else if (tbl.ShowHeader && tblAdr._fromRow == cell._fromRow && tblStyle.HeaderRow.Style.HasValue) + { + classes += " " + styleClass + "-header"; + } + else if (tbl.ShowTotal && tblAdr._toRow == cell._fromRow && tblAdr._fromRow == cell._fromRow && tblStyle.FirstTotalCell.Style.HasValue) + { + classes += " " + styleClass + "-first-total-cell"; + } + else if (tbl.ShowTotal && tblAdr._toRow == cell._fromRow && tblAdr._toRow == cell._fromRow && tblStyle.LastTotalCell.Style.HasValue) + { + classes += " " + styleClass + "-last-total-cell"; + } + else if (tbl.ShowTotal && tblAdr._toRow == cell._fromRow && tblStyle.TotalRow.Style.HasValue) + { + classes += " " + styleClass + "-total"; + } + else if (tbl.ShowFirstColumn && tblAdr._fromCol == cell._fromCol && tblStyle.FirstColumn.Style.HasValue) + { + classes += " " + styleClass + "-first-col"; + } + else if (tbl.ShowLastColumn && tblAdr._toCol == cell._fromCol && tblStyle.LastColumn.Style.HasValue) + { + classes += " " + styleClass + "-last-col"; + } + else if (tbl.ShowRowStripes && ((cell._fromRow - (tbl.ShowHeader ? tbl.Address._fromRow + 1 : tbl.Address._fromRow)) % 2) == 0 && (tblStyle.FirstRowStripe.Style.HasValue)) + { + classes += " " + styleClass + "-first-row-stripe"; + } + else if (tbl.ShowRowStripes && ((cell._fromRow - (tbl.ShowHeader ? tbl.Address._fromRow + 1 : tbl.Address._fromRow)) % 2) == 1 && (tblStyle.SecondRowStripe.Style.HasValue)) + { + classes += " " + styleClass + "-second-row-stripe"; + } + else if (tbl.ShowColumnStripes && ((cell._fromCol - tbl.Address._fromCol) % 2) == 0) + { + classes += " " + styleClass + "-first-col-stripe"; + } + else if (tbl.ShowColumnStripes && ((cell._fromCol - tbl.Address._fromCol) % 2) == 1) + { + classes += " " + styleClass + "-second-col-stripe"; + } + + return classes; + } } } diff --git a/src/EPPlus/Export/HtmlExport/Exporters/Internal/HtmlExporterBaseInternal.cs b/src/EPPlus/Export/HtmlExport/Exporters/Internal/HtmlExporterBaseInternal.cs index 997be280a4..f083fbe4fd 100644 --- a/src/EPPlus/Export/HtmlExport/Exporters/Internal/HtmlExporterBaseInternal.cs +++ b/src/EPPlus/Export/HtmlExport/Exporters/Internal/HtmlExporterBaseInternal.cs @@ -10,6 +10,7 @@ Date Author Change ************************************************************************************************* 6/4/2022 EPPlus Software AB ExcelTable Html Export *************************************************************************************************/ +using Microsoft.VisualBasic; using OfficeOpenXml.Core; using OfficeOpenXml.Drawing; using OfficeOpenXml.Drawing.Interfaces; @@ -41,13 +42,13 @@ public HtmlExporterBaseInternal(HtmlExportSettings settings, ExcelRangeBase rang if (range.Addresses == null) { - AddRange(range); + AddRange(range, settings.Drawings.Include!=eDrawingInclude.Exclude); } else { foreach (var address in range.Addresses) { - AddRange(range.Worksheet.Cells[address.Address]); + AddRange(range.Worksheet.Cells[address.Address], settings.Drawings.Include != eDrawingInclude.Exclude); } } @@ -58,6 +59,7 @@ public HtmlExporterBaseInternal(HtmlExportSettings settings, EPPlusReadOnlyList< { Settings = settings; Require.Argument(ranges).IsNotNull("ranges"); + AdjustRangeForDimensionAndDrawings(ranges._list, settings.Drawings.Include!=eDrawingInclude.Exclude); _ranges = ranges; //TODO: Fix support for all ranges LoadRangeDrawings(_ranges._list); @@ -112,7 +114,7 @@ protected HTMLElement GetThead(ExcelRangeBase range, List headers = null { table = range.GetTable(); } - + ExcelTable inRangeTable = null; int headerRows = GetHeaderRows(table); for (int i = 0; i < headerRows; i++) @@ -138,24 +140,28 @@ protected HTMLElement GetThead(ExcelRangeBase range, List headers = null HTMLElement contentElement; + if (_hasIntersectingTables && Settings.TableStyle == eHtmlRangeTableInclude.Include) + { + inRangeTable = cell.GetIntersectingTable(); + } + if (Settings.IncludeCssClassNames) { - GetClassData(th, true, image, cell, Settings, _exporterContext, out contentElement, true); + GetClassData(th, true, image, cell, Settings, _exporterContext, inRangeTable, out contentElement, true); } else { contentElement = th; } - AddTableData(table, contentElement, col); - if ((Settings.Pictures.Include == ePictureInclude.Include) || (Settings.Pictures.Include == ePictureInclude.IncludeInHtmlOnly)) + if ((Settings.Drawings.Include == eDrawingInclude.Include) || (Settings.Drawings.Include == eDrawingInclude.IncludeInHtmlOnly)) { image = GetImage(cell.Worksheet.PositionId, cell._fromRow, cell._fromCol); } - if ((Settings.Drawings.Include == ePictureInclude.Include) || (Settings.Drawings.Include == ePictureInclude.IncludeInHtmlOnly)) + if ((Settings.Drawings.Include == eDrawingInclude.Include) || (Settings.Drawings.Include == eDrawingInclude.IncludeInHtmlOnly)) { drawing = GetDrawing(cell.Worksheet.PositionId, cell._fromRow, cell._fromCol); } @@ -229,7 +235,7 @@ protected HTMLElement GetTableBody(ExcelRangeBase range, int row, int endRow) } var table = range.GetTable(); - + ExcelTable inRangeTable=null; var ws = range.Worksheet; HtmlImage image = null; HtmlDrawing drawing = null; @@ -271,31 +277,35 @@ protected HTMLElement GetTableBody(ExcelRangeBase range, int row, int endRow) if (InMergeCellSpan(row, col)) continue; var colIx = col - range._fromCol; var cell = ws.Cells[row, col]; - + if(Settings.TableStyle== eHtmlRangeTableInclude.Include && _hasIntersectingTables) + { + inRangeTable = cell.GetIntersectingTable(); + } var dataType = HtmlRawDataProvider.GetHtmlDataTypeFromValue(cell.Value); var tblData = new HTMLElement(HtmlElements.TableData); SetColRowSpan(range, tblData, cell); - if ((Settings.Pictures.Include == ePictureInclude.Include) || (Settings.Pictures.Include == ePictureInclude.IncludeInHtmlOnly)) + if ((Settings.Drawings.Include == eDrawingInclude.Include) || (Settings.Drawings.Include == eDrawingInclude.IncludeInHtmlOnly)) { image = GetImage(cell.Worksheet.PositionId, cell._fromRow, cell._fromCol); } - if (Settings.Drawings.Include == (ePictureInclude.Include | ePictureInclude.IncludeInHtmlOnly)) + if (Settings.Drawings.Include == (eDrawingInclude.Include | eDrawingInclude.IncludeInHtmlOnly)) { drawing = GetDrawing(cell.Worksheet.PositionId, cell._fromRow, cell._fromCol); } if (cell.Hyperlink == null) { - var addRowScope = table == null ? false : table.ShowFirstColumn && col == table.Address._fromCol || table.ShowLastColumn && col == table.Address._toCol; - AddTableDataFromCell(cell, dataType, tblData, Settings, addRowScope, image, _exporterContext); + var t = table ?? inRangeTable; + var addRowScope = t == null ? false : t.ShowFirstColumn && col == t.Address._fromCol || t.ShowLastColumn && col == t.Address._toCol; + AddTableDataFromCell(cell, dataType, tblData, Settings, addRowScope, image, _exporterContext, inRangeTable); } else { - GetClassData(tblData, table != null, image, cell, Settings, _exporterContext, out HTMLElement contentElement); + GetClassData(tblData, table != null, image, cell, Settings, _exporterContext, inRangeTable, out HTMLElement contentElement); AddImage(contentElement, Settings, image, cell.Value); AddHyperlink(contentElement, cell, Settings); @@ -423,12 +433,12 @@ protected void AddImage(HTMLElement parent, HtmlExportSettings settings, HtmlIma var name = GetPictureName(image); string imageName = HtmlExportTableUtil.GetClassName(image.Picture.Name, ((IPictureContainer)image.Picture).ImageHash); child.AddAttribute("alt", image.Picture.Name); - if (settings.Pictures.AddNameAsId) + if (settings.Drawings.AddNameAsId) { child.AddAttribute("id", imageName); } - if(settings.Pictures.Include == ePictureInclude.IncludeInHtmlOnly) + if(settings.Drawings.Include == eDrawingInclude.IncludeInHtmlOnly) { ePictureType? type; var _encodedImage = ImageEncoder.EncodeImage(image, out type); @@ -450,12 +460,12 @@ protected void AddDrawing(HTMLElement parent, HtmlExportSettings settings, HtmlS var child = new HTMLElement(HtmlElements.Img); string drawingName = HtmlExportTableUtil.GetClassName(d.Drawing.Name, $"drawing{d.Drawing.Id}"); child.AddAttribute("alt", d.Drawing.Name); - if (settings.Pictures.AddNameAsId) + if (settings.Drawings.AddNameAsId) { child.AddAttribute("id", drawingName); } - if (settings.Drawings.Include == ePictureInclude.IncludeInHtmlOnly) + if (settings.Drawings.Include == eDrawingInclude.IncludeInHtmlOnly) { child = new HTMLElement(HtmlElements.Svg); child.ElementName = "div"; @@ -492,18 +502,17 @@ protected void LoadVisibleColumns(ExcelRangeBase range) protected EPPlusReadOnlyList _ranges = new EPPlusReadOnlyList(); - private void AddRange(ExcelRangeBase range) + private void AddRange(ExcelRangeBase range, bool includeDrawings) { if (range.IsFullColumn && range.IsFullRow) { - _ranges.Add(new ExcelRangeBase(range.Worksheet, range.Worksheet.Dimension.Address)); + _ranges.Add(AdjustRangeForDimensionAndDrawings(new ExcelRangeBase(range.Worksheet, range.Worksheet.Dimension.Address), includeDrawings)); } else { _ranges.Add(range); } } - protected void ValidateRangeIndex(int rangeIndex) { if (rangeIndex < 0 || rangeIndex >= _ranges.Count) @@ -721,12 +730,19 @@ protected void AddClassesAttributes(HTMLElement element, ExcelTable table, strin } } - internal void GetClassData(HTMLElement element, bool isTable, HtmlImage image, ExcelRangeBase cell, HtmlExportSettings settings, ExporterContext content, out HTMLElement valueElement, bool isHeader = false) + internal void GetClassData(HTMLElement element, bool isTable, HtmlImage image, ExcelRangeBase cell, HtmlExportSettings settings, ExporterContext content, ExcelTable inRangeTable, out HTMLElement valueElement, bool isHeader = false) { - var imageCellClassName = GetImageCellClassName(image, Settings, isTable); - var classString = AttributeTranslator.GetClassAttributeFromStyle(cell, isHeader, settings, imageCellClassName, content); + var additionalCellClassName = GetImageCellClassName(image, Settings, isTable); + + if (inRangeTable != null) + { + additionalCellClassName += (string.IsNullOrEmpty(additionalCellClassName) ? "" : " ") + HtmlExportTableUtil.GetInRangeTableClass(cell, inRangeTable); + } + + var classString = AttributeTranslator.GetClassAttributeFromStyle(cell, isHeader, settings, additionalCellClassName, content); var stylesAndExtras = AttributeTranslator.GetConditionalFormattings(cell, settings, content, ref classString); + if (cell.Style.Checkbox) { if (cell.Value == null || HtmlRawDataProvider.GetHtmlDataTypeFromValue(cell.Value) == HtmlDataTypes.Boolean) @@ -818,7 +834,7 @@ private static bool IsTextRotationExcluded(HtmlExportSettings settings, bool isH } - public void AddTableDataFromCell(ExcelRangeBase cell, string dataType, HTMLElement element, HtmlExportSettings settings, bool addRowScope, HtmlImage image, ExporterContext content) + public void AddTableDataFromCell(ExcelRangeBase cell, string dataType, HTMLElement element, HtmlExportSettings settings, bool addRowScope, HtmlImage image, ExporterContext content, ExcelTable inRangeTable) { if (dataType != ColumnDataTypeManager.HtmlDataTypes.String && settings.RenderDataAttributes) { @@ -837,7 +853,7 @@ public void AddTableDataFromCell(ExcelRangeBase cell, string dataType, HTMLEleme } } - GetClassData(element, true, image, cell, settings, content, out HTMLElement contentElement); + GetClassData(element, true, image, cell, settings, content, inRangeTable, out HTMLElement contentElement); AddImage(contentElement, settings, image, cell.Value); diff --git a/src/EPPlus/Export/HtmlExport/Exporters/Internal/HtmlRangeExporterBase.cs b/src/EPPlus/Export/HtmlExport/Exporters/Internal/HtmlRangeExporterBase.cs index ab8b9d5614..a8db72b601 100644 --- a/src/EPPlus/Export/HtmlExport/Exporters/Internal/HtmlRangeExporterBase.cs +++ b/src/EPPlus/Export/HtmlExport/Exporters/Internal/HtmlRangeExporterBase.cs @@ -13,7 +13,9 @@ Date Author Change using OfficeOpenXml.Core; using OfficeOpenXml.Export.HtmlExport.HtmlCollections; using OfficeOpenXml.Table; +using System; using System.Collections.Generic; +using System.Linq; namespace OfficeOpenXml.Export.HtmlExport.Exporters.Internal { @@ -42,6 +44,10 @@ protected HTMLElement GenerateHTML(int rangeIndex, ExcelHtmlOverrideExportSettin if (Settings.TableStyle != eHtmlRangeTableInclude.Exclude) { table = range.GetTable(); + if(table==null) + { + _hasIntersectingTables = HasIntersctingTables(range); + } } var tableId = GetTableId(rangeIndex, overrideSettings); @@ -65,12 +71,18 @@ protected HTMLElement GenerateHTML(int rangeIndex, ExcelHtmlOverrideExportSettin { AddHeaderRow(range, htmlTable, table, headers); } + // table rows AddTableRows(htmlTable, range); return htmlTable; } + private bool HasIntersctingTables(ExcelRangeBase range) + { + return range.Worksheet.Tables.GetIntersectingRanges(range).Count>0; + } + private void AddTableRows(HTMLElement htmlTable, ExcelRangeBase range) { var row = range._fromRow + _settings.HeaderRows; diff --git a/src/EPPlus/Export/HtmlExport/Exporters/Internal/HtmlTableExporterBase.cs b/src/EPPlus/Export/HtmlExport/Exporters/Internal/HtmlTableExporterBase.cs index 64633caec8..0920d1539d 100644 --- a/src/EPPlus/Export/HtmlExport/Exporters/Internal/HtmlTableExporterBase.cs +++ b/src/EPPlus/Export/HtmlExport/Exporters/Internal/HtmlTableExporterBase.cs @@ -146,6 +146,7 @@ private void AddTotalRow(HTMLElement table) var address = _table.Address; HtmlImage image = null; + ExcelTable inRangeTable = null; foreach (var col in _columns) { var tblData = new HTMLElement(HtmlElements.TableData); @@ -155,7 +156,12 @@ private void AddTotalRow(HTMLElement table) { tblData.AddAttribute("role", "cell"); } - GetClassData(tblData, true, image, cell, Settings, _exporterContext, out HTMLElement contentElement); + if (Settings.TableStyle == eHtmlRangeTableInclude.Include && _hasIntersectingTables) + { + inRangeTable = cell.GetIntersectingTable(); + } + + GetClassData(tblData, true, image, cell, Settings, _exporterContext, inRangeTable, out HTMLElement contentElement); AddImage(contentElement, Settings, image, cell.Value); diff --git a/src/EPPlus/Export/HtmlExport/Settings/HtmlDrawingSettings.cs b/src/EPPlus/Export/HtmlExport/Settings/HtmlDrawingSettings.cs index d94bfbb869..71d14561b0 100644 --- a/src/EPPlus/Export/HtmlExport/Settings/HtmlDrawingSettings.cs +++ b/src/EPPlus/Export/HtmlExport/Settings/HtmlDrawingSettings.cs @@ -1,32 +1,91 @@ -using System; +using OfficeOpenXml.Drawing; +using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace OfficeOpenXml.Export.HtmlExport { + /// + /// Drawing handler + /// public class HtmlDrawingSettings { internal HtmlDrawingSettings() { } - - //Use picture for now. Possibly re-name /// - /// If how drawings should be included in the html. Default is + /// Optional handle to set individual settings for a drawing. Returning null will use the default settings. /// - public ePictureInclude Include = ePictureInclude.Exclude; - + public Func IndividualDrawingHandler { get; set; } = null; /// - /// Which type of drawing should be included + /// Option to handle if a drawing should be excluded or not. /// - public eDrawingInclude DrawTypeInclude = eDrawingInclude.None; - + public Func ExcludeDrawingHandler { get; set; } = null; /// - /// Is absolute by default for charts + /// If a drawing should be included in the export or not. Only pictures and drawings with set to true will be included. /// - public ePicturePosition Position = ePicturePosition.DontSet; - + public eDrawingInclude Include = eDrawingInclude.Exclude; + /// + /// If the drawing image should be added as absolut or relative in the css. + /// + public eDrawingPosition Position { get; set; } = eDrawingPosition.Relative; + /// + /// If the margin in pixels from the top corner should be used. + /// If this property is set to true, the cells vertical alignment will be set to 'top', + /// otherwise alignment will be set to middle. + /// + public bool AddMarginTop { get; set; } = false; + /// + /// If the margin in pixels from the left corner should be used. + /// If this property is set to true, the cells text alignment will be set to 'left', + /// otherwise alignment will be set to center. + /// + public bool AddMarginLeft { get; set; } = false; + /// + /// If set to true the original size of the image is used, + /// otherwise the size in the workbook is used. Default is false. + /// + public bool KeepOriginalSizeOnPictures { get; set; } = false; + /// + /// Exclude settings + /// + public PictureCssExclude PictureCssExclude { get; } = new PictureCssExclude(); + /// + /// Adds the Blip name as Id for the img element in the HTML. + /// Characters [A-Z][0-9]-_ are allowed. The first character allows [A-Z]_. + /// Other characters will be replaced with an hyphen (-). + /// + public bool AddNameAsId + { + get; + set; + } = true; + /// + /// Reset the setting to it's default values. + /// + public void ResetToDefault() + { + Include = eDrawingInclude.Exclude; + Position = eDrawingPosition.Relative; + AddMarginLeft = false; + AddMarginTop = false; + KeepOriginalSizeOnPictures = false; + PictureCssExclude.ResetToDefault(); + } + /// + /// Copy the values from another settings object. + /// + /// The object to copy. + public void Copy(HtmlDrawingSettings copy) + { + Include = copy.Include; + Position = copy.Position; + AddMarginLeft = copy.AddMarginLeft; + AddMarginTop = copy.AddMarginTop; + KeepOriginalSizeOnPictures = copy.KeepOriginalSizeOnPictures; + PictureCssExclude.Copy(copy.PictureCssExclude); + } } } diff --git a/src/EPPlus/Export/HtmlExport/Settings/HtmlExportSettings.cs b/src/EPPlus/Export/HtmlExport/Settings/HtmlExportSettings.cs index 5cde7470e7..d1c1b8d671 100644 --- a/src/EPPlus/Export/HtmlExport/Settings/HtmlExportSettings.cs +++ b/src/EPPlus/Export/HtmlExport/Settings/HtmlExportSettings.cs @@ -11,6 +11,7 @@ Date Author Change 05/11/2021 EPPlus Software AB ExcelTable Html Export *************************************************************************************************/ using OfficeOpenXml.Export.HtmlExport.Accessibility; +using System; using System.Collections.Generic; using System.Globalization; using System.Text; @@ -129,15 +130,24 @@ public string DataValueAttributeName /// public string IconPrefix { get; set; } = "ic"; + HtmlPictureSettings _legacyPicturesSettings; /// /// If picture drawings will be included. Default is true. /// + [Obsolete("Use the Drawings.Pictures property instead")] public HtmlPictureSettings Pictures { - get; - } = new HtmlPictureSettings(); - /// - /// If and which Charts and/or Shapes will be included + get + { + if(_legacyPicturesSettings==null) + { + _legacyPicturesSettings = new HtmlPictureSettings(Drawings); + } + return _legacyPicturesSettings; + } + } + /// + /// If and which drawing objects like Charts, Pictures and/or Shapes will be included /// public HtmlDrawingSettings Drawings { diff --git a/src/EPPlus/Export/HtmlExport/Settings/HtmlPictureSettings.cs b/src/EPPlus/Export/HtmlExport/Settings/HtmlPictureSettings.cs index 366f25e081..afc2d18d9d 100644 --- a/src/EPPlus/Export/HtmlExport/Settings/HtmlPictureSettings.cs +++ b/src/EPPlus/Export/HtmlExport/Settings/HtmlPictureSettings.cs @@ -11,47 +11,106 @@ Date Author Change 05/11/2021 EPPlus Software AB ExcelTable Html Export *************************************************************************************************/ +using System; + namespace OfficeOpenXml.Export.HtmlExport { /// /// Setting for rendering of picture drawings /// + [Obsolete("Use HtmlDrawingSettings instead (Settings.Drawings).")] public class HtmlPictureSettings { - internal HtmlPictureSettings() + HtmlDrawingSettings _drawingsSettings; + internal HtmlPictureSettings(HtmlDrawingSettings drawingsSettings) { - + _drawingsSettings = drawingsSettings; } /// /// If picture drawings should be included in the html. Default is /// - public ePictureInclude Include { get; set; } = ePictureInclude.Exclude; + public ePictureInclude Include + { + get + { + return (ePictureInclude)_drawingsSettings.Include; + } + set + { + _drawingsSettings.Include = (eDrawingInclude)value; + } + } /// /// If the image should be added as absolut or relative in the css. /// - public ePicturePosition Position { get; set; } = ePicturePosition.Relative; + public ePicturePosition Position + { + get + { + return (ePicturePosition)_drawingsSettings.Position; + } + set + { + _drawingsSettings.Position = (eDrawingPosition)value; + } + } /// /// If the margin in pixels from the top corner should be used. /// If this property is set to true, the cells vertical alignment will be set to 'top', /// otherwise alignment will be set to middle. /// - public bool AddMarginTop { get; set; } = false; + public bool AddMarginTop + { + get + { + return _drawingsSettings.AddMarginTop; + } + set + { + _drawingsSettings.AddMarginTop = value; + } + } /// /// If the margin in pixels from the left corner should be used. /// If this property is set to true, the cells text alignment will be set to 'left', /// otherwise alignment will be set to center. /// - public bool AddMarginLeft { get; set; } = false; - /// - /// If set to true the original size of the image is used, - /// otherwise the size in the workbook is used. Default is false. - /// - public bool KeepOriginalSize { get; set; } = false; + public bool AddMarginLeft + { + get + { + return _drawingsSettings.AddMarginLeft; + } + set + { + _drawingsSettings.AddMarginLeft = value; + } + } /// + /// If set to true the original size of the image is used, + /// otherwise the size in the workbook is used. Default is false. + /// + public bool KeepOriginalSize + { + get + { + return _drawingsSettings.KeepOriginalSizeOnPictures; + } + set + { + _drawingsSettings.KeepOriginalSizeOnPictures = value; + } + } /// /// Exclude settings /// - public PictureCssExclude CssExclude { get; } = new PictureCssExclude(); + public PictureCssExclude CssExclude + { + get + { + return _drawingsSettings.PictureCssExclude; + } + } /// /// Adds the Blip name as Id for the img element in the HTML. /// Characters [A-Z][0-9]-_ are allowed. The first character allows [A-Z]_. @@ -59,9 +118,15 @@ internal HtmlPictureSettings() /// public bool AddNameAsId { - get; - set; - } = true; + get + { + return _drawingsSettings.AddNameAsId; + } + set + { + _drawingsSettings.AddNameAsId = value; + } + } /// /// Reset the setting to it's default values. /// diff --git a/src/EPPlus/Export/HtmlExport/Translators/AttributeTranslator.cs b/src/EPPlus/Export/HtmlExport/Translators/AttributeTranslator.cs index de7595f503..b2d1964551 100644 --- a/src/EPPlus/Export/HtmlExport/Translators/AttributeTranslator.cs +++ b/src/EPPlus/Export/HtmlExport/Translators/AttributeTranslator.cs @@ -49,11 +49,11 @@ internal static string GetClassAttributeFromStyle(ExcelRangeBase cell, bool isHe { if (ConvertUtil.IsNumericOrDate(cell.Value)) { - cls = $"{styleClassPrefix}ar"; + cls += $" {styleClassPrefix}ar"; } else if (isHeader) { - cls = $"{styleClassPrefix}al"; + cls += $" {styleClassPrefix}al"; } } @@ -93,7 +93,7 @@ internal static string GetClassAttributeFromStyle(ExcelRangeBase cell, bool isHe } } - return cls; + return cls.Trim(); } internal static List GetConditionalFormattings(ExcelRangeBase cell, HtmlExportSettings settings, ExporterContext context, ref string cls) diff --git a/src/EPPlus/Export/HtmlExport/Translators/CssDrawingPropertiesTranslator.cs b/src/EPPlus/Export/HtmlExport/Translators/CssDrawingPropertiesTranslator.cs index dab122d097..88e8e80590 100644 --- a/src/EPPlus/Export/HtmlExport/Translators/CssDrawingPropertiesTranslator.cs +++ b/src/EPPlus/Export/HtmlExport/Translators/CssDrawingPropertiesTranslator.cs @@ -39,7 +39,7 @@ internal CssDrawingPropertiesTranslator(HtmlSvgDrawing d) internal override List GenerateDeclarationList(TranslatorContext context) { - if (context.Drawings.Position == ePicturePosition.Relative) + if (context.Drawings.Position == eDrawingPosition.Relative) { if (_bounds.Left != 0) { @@ -50,19 +50,14 @@ internal override List GenerateDeclarationList(TranslatorContext co AddDeclaration("top", $"{_bounds.Top.PointToPixel():F0}px"); } } - else if (context.Drawings.Position == ePicturePosition.Absolute) + else if (context.Drawings.Position == eDrawingPosition.Absolute) { - if (_bounds.Left != 0) - { - AddDeclaration("left", $"{_bounds.GlobalLeft.PointToPixel():F0}px"); - } - if (_bounds.Top != 0) - { - AddDeclaration("top", $"{_bounds.GlobalTop.PointToPixel():F0}px"); - } + AddDeclaration("position", $"{context.Drawings.Position.ToString().ToLower()}"); + AddDeclaration("left", $"{_bounds.GlobalLeft.PointToPixel():F0}px"); + AddDeclaration("top", $"{_bounds.GlobalTop.PointToPixel():F0}px"); } - if (context.Pictures.KeepOriginalSize == false) + if (context.Drawings.KeepOriginalSizeOnPictures == false) { if (_width != _bounds.Width) { @@ -74,7 +69,7 @@ internal override List GenerateDeclarationList(TranslatorContext co } } - if (_border.LineStyle != null && context.Pictures.CssExclude.Border == false) + if (_border.LineStyle != null && context.Drawings.PictureCssExclude.Border == false) { var border = GetDrawingBorder(); AddDeclaration("border", border); diff --git a/src/EPPlus/Export/HtmlExport/Translators/CssImageAlignmentTranslator.cs b/src/EPPlus/Export/HtmlExport/Translators/CssImageAlignmentTranslator.cs index 46dc89bf32..f62228a5a2 100644 --- a/src/EPPlus/Export/HtmlExport/Translators/CssImageAlignmentTranslator.cs +++ b/src/EPPlus/Export/HtmlExport/Translators/CssImageAlignmentTranslator.cs @@ -17,17 +17,17 @@ namespace OfficeOpenXml.Export.HtmlExport.Translators { internal class CssImageAlignmentTranslator : TranslatorBase { - HtmlPictureSettings _picSettings; + HtmlDrawingSettings _drawingsSettings; - internal CssImageAlignmentTranslator(HtmlPictureSettings picSettings) + internal CssImageAlignmentTranslator(HtmlDrawingSettings drawingsSettings) { - _picSettings = picSettings; + _drawingsSettings = drawingsSettings; } internal override List GenerateDeclarationList(TranslatorContext context) { - AddDeclaration("vertical-align", _picSettings.AddMarginTop ? "top" : "middle"); - AddDeclaration("text-align", _picSettings.AddMarginLeft ? "left" : "center"); + AddDeclaration("vertical-align", _drawingsSettings.AddMarginTop ? "top" : "middle"); + AddDeclaration("text-align", _drawingsSettings.AddMarginLeft ? "left" : "center"); return declarations; } diff --git a/src/EPPlus/Export/HtmlExport/Translators/CssImagePropertiesTranslator.cs b/src/EPPlus/Export/HtmlExport/Translators/CssImagePropertiesTranslator.cs index 1d3ee325fe..e1a44b77a0 100644 --- a/src/EPPlus/Export/HtmlExport/Translators/CssImagePropertiesTranslator.cs +++ b/src/EPPlus/Export/HtmlExport/Translators/CssImagePropertiesTranslator.cs @@ -34,7 +34,7 @@ internal CssImagePropertiesTranslator(HtmlImage image) internal override List GenerateDeclarationList(TranslatorContext context) { - if (context.Pictures.KeepOriginalSize == false) + if (context.Drawings.KeepOriginalSizeOnPictures == false) { if (_width != _bounds.Width) { @@ -46,7 +46,7 @@ internal override List GenerateDeclarationList(TranslatorContext co } } - if (_border.LineStyle != null && context.Pictures.CssExclude.Border == false) + if (_border.LineStyle != null && context.Drawings.PictureCssExclude.Border == false) { var border = GetDrawingBorder(); AddDeclaration("border", border); diff --git a/src/EPPlus/Export/HtmlExport/Translators/CssImageTranslator.cs b/src/EPPlus/Export/HtmlExport/Translators/CssImageTranslator.cs index e3648b2a8c..c02f2f4d17 100644 --- a/src/EPPlus/Export/HtmlExport/Translators/CssImageTranslator.cs +++ b/src/EPPlus/Export/HtmlExport/Translators/CssImageTranslator.cs @@ -47,23 +47,23 @@ internal override List GenerateDeclarationList(TranslatorContext co { AddDeclaration("content", $"url('data:{GetContentType(type.Value)};base64,{_encodedImage}')"); - if (context.Pictures.Position != ePicturePosition.DontSet) - { - AddDeclaration("position", $"{context.Pictures.Position.ToString().ToLower()}"); - } + //if (context.Drawings.Position != eDrawingPosition.DontSet) + //{ + // AddDeclaration("position", $"{context.Drawings.Position.ToString().ToLower()}"); + //} - if(isDrawing && context.Drawings.Position != ePicturePosition.DontSet) - { - AddDeclaration("position", $"{context.Drawings.Position.ToString().ToLower()}"); - } + //if(isDrawing && context.Drawings.Position != eDrawingPosition.DontSet) + //{ + // AddDeclaration("position", $"{context.Drawings.Position.ToString().ToLower()}"); + //} - if (_p.FromColumnOff != 0 && context.Pictures.AddMarginLeft) + if (_p.FromColumnOff != 0 && context.Drawings.AddMarginLeft) { var leftOffset = _p.FromColumnOff / ExcelPicture.EMU_PER_PIXEL; AddDeclaration("margin-left", $"{leftOffset}px"); } - if (_p.FromRowOff != 0 && context.Pictures.AddMarginTop) + if (_p.FromRowOff != 0 && context.Drawings.AddMarginTop) { var topOffset = _p.FromRowOff / ExcelPicture.EMU_PER_PIXEL; AddDeclaration("margin-top", $"{topOffset}px"); diff --git a/src/EPPlus/Export/HtmlExport/Translators/TranslatorContext.cs b/src/EPPlus/Export/HtmlExport/Translators/TranslatorContext.cs index 92e90b0b6b..ee45883c06 100644 --- a/src/EPPlus/Export/HtmlExport/Translators/TranslatorContext.cs +++ b/src/EPPlus/Export/HtmlExport/Translators/TranslatorContext.cs @@ -33,7 +33,6 @@ internal class TranslatorContext internal CssExclude Exclude; internal CssExportSettings Settings; - internal HtmlPictureSettings Pictures; internal HtmlDrawingSettings Drawings; private TranslatorBase strategy; @@ -53,14 +52,12 @@ public TranslatorContext(HtmlRangeExportSettings settings) { Exclude = settings.Css.CssExclude; Settings = settings.Css; - Pictures = settings.Pictures; Drawings = settings.Drawings; } public TranslatorContext(HtmlTableExportSettings settings, CssExclude exclude) { Settings = settings.Css; - Pictures = settings.Pictures; Drawings = settings.Drawings; Exclude = exclude; } diff --git a/src/EPPlus/Style/Dxf/DxfStyleHandler.cs b/src/EPPlus/Style/Dxf/DxfStyleHandler.cs index e830f44400..5da4198c15 100644 --- a/src/EPPlus/Style/Dxf/DxfStyleHandler.cs +++ b/src/EPPlus/Style/Dxf/DxfStyleHandler.cs @@ -210,7 +210,9 @@ private static void UpdateConditionalFormatting(ExcelWorksheet ws, ExcelStyleCol { foreach (var cf in ws.ConditionalFormatting) { - if (cf.Style.HasValue) + //If at least one border exists then a dxf style for the border must be added even if the value for that border is empty + //(Thus meaning HasValue is false) + if (cf.Style.HasValue || cf.Style.Border != null && cf.Style.Border.AtLeastOneBorderExists()) { var standardDxfStyle = cf.Style.ToDxfStyle(); @@ -228,11 +230,9 @@ private static void UpdateConditionalFormatting(ExcelWorksheet ws, ExcelStyleCol { ((ExcelConditionalFormattingRule)cf).DxfId = ix; cf.Style.DxfId = ix; - //cf.Style.DxfId = ix; } } } - //var num = dxfs._list[129]; } internal static void CopyDxfStylesTable(ExcelTable tblFrom, ExcelTable tblTo) { diff --git a/src/EPPlus/Style/Dxf/ExcelDxfBorder.cs b/src/EPPlus/Style/Dxf/ExcelDxfBorder.cs index 5b51e8a3c7..2b369f1883 100644 --- a/src/EPPlus/Style/Dxf/ExcelDxfBorder.cs +++ b/src/EPPlus/Style/Dxf/ExcelDxfBorder.cs @@ -205,14 +205,36 @@ internal override DxfStyleBase Clone() Horizontal = (ExcelDxfBorderItem)Horizontal.Clone(), }; } + + private bool BorderItemExists(ExcelDxfBorderItem bi) + { + if (bi.Style != null) + { + return true; + } + return false; + } + + internal bool AtLeastOneBorderExists() + { + if (BorderItemExists(Left)) return true; + if (BorderItemExists(Right)) return true; + if (BorderItemExists(Bottom)) return true; + if (BorderItemExists(Top)) return true; + if (BorderItemExists(Vertical)) return true; + if (BorderItemExists(Horizontal)) return true; + + return false; + } + internal override void SetValuesFromXml(XmlHelper helper) { if (helper.ExistsNode("d:border")) { Left = GetBorderItem(helper, "d:border/d:left", eStyleClass.BorderLeft); - Right = GetBorderItem(helper, "d:border/d:right", eStyleClass.BorderLeft); - Bottom = GetBorderItem(helper, "d:border/d:bottom", eStyleClass.BorderLeft); - Top = GetBorderItem(helper, "d:border/d:top", eStyleClass.BorderLeft); + Right = GetBorderItem(helper, "d:border/d:right", eStyleClass.BorderRight); + Bottom = GetBorderItem(helper, "d:border/d:bottom", eStyleClass.BorderBottom); + Top = GetBorderItem(helper, "d:border/d:top", eStyleClass.BorderTop); Vertical = GetBorderItem(helper, "d:border/d:vertical", eStyleClass.Border); Horizontal = GetBorderItem(helper, "d:border/d:horizontal", eStyleClass.Border); } @@ -224,7 +246,8 @@ private ExcelDxfBorderItem GetBorderItem(XmlHelper helper, string path, eStyleCl if (exists) { var style = helper.GetXmlNodeString(path + "/@style"); - bi.Style = GetBorderStyleEnum(style); + //When exists and border has no style the CT_BorderPr\ST_BorderStyle node defaults to BorderNone if the node exists even when empty + bi.Style = GetBorderStyleEnum(style) ?? ExcelBorderStyle.None; bi.Color = GetColor(helper, path + "/d:color", styleClass); } return bi; diff --git a/src/EPPlus/Style/Dxf/ExcelDxfStyleLimitedFont.cs b/src/EPPlus/Style/Dxf/ExcelDxfStyleLimitedFont.cs index 4e5a15ae73..08a4d370e6 100644 --- a/src/EPPlus/Style/Dxf/ExcelDxfStyleLimitedFont.cs +++ b/src/EPPlus/Style/Dxf/ExcelDxfStyleLimitedFont.cs @@ -52,7 +52,7 @@ internal override void CreateNodes(XmlHelper helper, string path) { if (Font.HasValue) Font.CreateNodes(helper, "d:font"); if (Fill.HasValue) Fill.CreateNodes(helper, "d:fill"); - if (Border.HasValue) Border.CreateNodes(helper, "d:border"); + if (Border.HasValue || Border.AtLeastOneBorderExists()) Border.CreateNodes(helper, "d:border"); } /// /// If the object has any properties set diff --git a/src/EPPlus/Table/ExcelTable.cs b/src/EPPlus/Table/ExcelTable.cs index b4f4b07da7..ed270f97c8 100644 --- a/src/EPPlus/Table/ExcelTable.cs +++ b/src/EPPlus/Table/ExcelTable.cs @@ -33,6 +33,8 @@ Date Author Change using OfficeOpenXml.Data.QueryTable; using OfficeOpenXml.Data.Connection.IOHandlers; using OfficeOpenXml.Utils.EnumUtils; +using OfficeOpenXml.Style.Table; + @@ -1522,5 +1524,23 @@ public ExcelTable Copy(ExcelRangeBase range) Range.Copy(range); return WorkSheet.Tables.FirstOrDefault(x => x.Address.Collide(range) != ExcelAddressBase.eAddressCollition.No); } + + internal ExcelTableNamedStyle GetTableNamedStyle() + { + ExcelTableNamedStyle tblStyle; + if (TableStyle == TableStyles.Custom) + { + tblStyle = WorkSheet.Workbook.Styles.TableStyles[StyleName].As.TableStyle; + } + else + { + var tmpNode = WorkSheet.Workbook.StylesXml.CreateElement("c:tableStyle"); + tblStyle = new ExcelTableNamedStyle(WorkSheet.Workbook.Styles.NameSpaceManager, tmpNode, WorkSheet.Workbook.Styles); + tblStyle.SetFromTemplate(TableStyle); + } + + return tblStyle; + } } } + diff --git a/src/EPPlus/XmlHelper.cs b/src/EPPlus/XmlHelper.cs index 9c6ef4d656..37148b1e43 100644 --- a/src/EPPlus/XmlHelper.cs +++ b/src/EPPlus/XmlHelper.cs @@ -1141,7 +1141,7 @@ internal int GetXmlNodeInt(string path, int defaultValue = int.MinValue) internal double GetXmlNodeAngle(string path, double defaultValue = 0) { int a = GetXmlNodeInt(path); - if (a < 0) return defaultValue; + if (a == int.MinValue) return defaultValue; return a / 60000D; } internal double GetXmlNodeEmuToPt(string path, double defaultValue = 0) diff --git a/src/EPPlusTest/Core/Worksheet/HeaderFooterSectionOrderTests.cs b/src/EPPlusTest/Core/Worksheet/HeaderFooterSectionOrderTests.cs new file mode 100644 index 0000000000..58be5d0ae2 --- /dev/null +++ b/src/EPPlusTest/Core/Worksheet/HeaderFooterSectionOrderTests.cs @@ -0,0 +1,367 @@ +/******************************************************************************* + * You may amend and distribute as you like, but don't remove this header! + * + * Required Notice: Copyright (C) EPPlus Software AB. + * https://epplussoftware.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php + * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html + * + * All code and executables are provided "" as is "" with no warranty either express or implied. + * The author accepts no liability for any damage or loss of business that this product may cause. + * + * Code change notes: + * + Date Author Change + ******************************************************************************* + 08/25/2026 EPPlus Software AB Regression tests: header/footer sections + must parse regardless of their order + *******************************************************************************/ +using System; +using System.IO; +using System.Xml; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OfficeOpenXml; + +namespace EPPlusTest.Core.Worksheet +{ + /// + /// Regression tests for header/footer sections being lost when they are not stored in + /// Left, Center, Right order. + /// + /// The ExcelHeaderFooterText constructor takes the first section code from the first two + /// characters of the raw string, then scans for further section codes. Before the fix its + /// scan only recognized "&C" and "&R" - never "&L" - so a "&L" appearing + /// anywhere other than at position 0 was not treated as the start of a new section. Its + /// content was swallowed into the preceding section, and then discarded when that section + /// was normalized by ReadHeaderFooterFormat/WriteHeaderFooterFormat, taking any "&G" + /// picture placeholder with it. The picture survived in the VML collection, so + /// HeaderFooter.Pictures.Count was unchanged, but nothing referenced it any more and it + /// stopped rendering in Excel. + /// + /// Excel writes the sections in the order the user created them, which is frequently not + /// Left, Center, Right - so this affected ordinary files produced by Excel itself. + /// + /// Reported case (oddfooter-left-corruption-template.xlsx), oddFooter raw value: + /// &C&"-,Bold"&12{FORMID}&"-,Regular"&11\n&L&G&R&G + /// which before the fix was persisted as: + /// &C&"-,Bold"&12{FORMID}&"-,Regular"&11\n&R&G + /// + /// The same file's oddHeader ("&L&G&R&G") was unaffected, both because it + /// starts with "&L" and because Save() only rewrites a node whose backing field is + /// non-null - and the reported repro only ever touched OddFooter. + /// + [TestClass] + public class HeaderFooterSectionOrderTests : TestBase + { + /// The customer's exact oddFooter value. Section order: Center, Left, Right. + private const string CustomerOddFooter = "&C&\"-,Bold\"&12{FORMID}&\"-,Regular\"&11\n&L&G&R&G"; + + /// The customer's exact oddHeader value. Section order: Left, Right. + private const string CustomerOddHeader = "&L&G&R&G"; + + [ClassInitialize] + public static void Init(TestContext testContext) + { + InitBase(); + } + + [TestMethod] + public void CustomerOddFooter_SurvivesRoundTrip_WhenOddFooterIsTouched() + { + // The exact scenario from the support ticket: a footer whose sections are stored + // in Center, Left, Right order, with a single read of a header/footer property as + // the only interaction before saving. + using (var pkg = new ExcelPackage()) + { + var ws = pkg.Workbook.Worksheets.Add("Sheet1"); + SetRawHeaderFooterNode(ws, "oddFooter", CustomerOddFooter); + SetRawHeaderFooterNode(ws, "oddHeader", CustomerOddHeader); + + // The single interaction from the customer's repro script. + var _ = ws.HeaderFooter.OddFooter.LeftAlignedText; + + var persistedFooter = SaveAndReadRawNode(pkg, "oddFooter"); + var persistedHeader = SaveAndReadRawNode(pkg, "oddHeader"); + + StringAssert.Contains(persistedFooter, "&L" + ExcelHeaderFooter.Image, + "The Left section's picture placeholder must survive the round trip. " + + $"Persisted footer was: \"{Escape(persistedFooter)}\"."); + + Assert.AreEqual(CustomerOddHeader, persistedHeader, + "The untouched oddHeader must be unchanged by the save."); + } + } + + [TestMethod] + public void LeftSection_IsParsed_RegardlessOfSectionOrder() + { + // Every one of these holds the same three logical sections, only reordered. The + // Left section always contains a picture placeholder, and must always come back. + var variants = new[] + { + new { Name = "L,C,R", Raw = "&L&G&CCenterText&R&G" }, + new { Name = "C,L,R", Raw = "&CCenterText&L&G&R&G" }, + new { Name = "R,L,C", Raw = "&R&G&L&G&CCenterText" }, + new { Name = "C,R,L", Raw = "&CCenterText&R&G&L&G" }, + new { Name = "R,C,L", Raw = "&R&G&CCenterText&L&G" }, + new { Name = "L,R", Raw = "&L&G&R&G" }, + new { Name = "C,L", Raw = "&CCenterText&L&G" }, + new { Name = "R,L", Raw = "&R&G&L&G" }, + }; + + foreach (var v in variants) + { + using (var pkg = new ExcelPackage()) + { + var ws = pkg.Workbook.Worksheets.Add("Sheet1"); + SetRawHeaderFooterNode(ws, "oddFooter", v.Raw); + + var oddFooter = ws.HeaderFooter.OddFooter; + + StringAssert.Contains(oddFooter.LeftAlignedText, ExcelHeaderFooter.Image, + $"[{v.Name}] The Left section's '&G' placeholder was not parsed from " + + $"\"{Escape(v.Raw)}\". Left parsed as \"{Escape(oddFooter.LeftAlignedText)}\"."); + + // Guard against the content merely being relocated into another section. + Assert.IsFalse(Contains(oddFooter.CenteredText, "&L"), + $"[{v.Name}] The Center section swallowed a stray '&L': " + + $"\"{Escape(oddFooter.CenteredText)}\"."); + Assert.IsFalse(Contains(oddFooter.RightAlignedText, "&L"), + $"[{v.Name}] The Right section swallowed a stray '&L': " + + $"\"{Escape(oddFooter.RightAlignedText)}\"."); + } + } + } + + [TestMethod] + public void AllSections_SurviveRoundTrip_RegardlessOfSectionOrder() + { + // As above, but verifying what actually reaches the file. Section order is not + // required to be preserved - only the content of each section. + var variants = new[] + { + new { Name = "L,C,R", Raw = "&L&G&CCenterText&R&G" }, + new { Name = "C,L,R", Raw = "&CCenterText&L&G&R&G" }, + new { Name = "R,L,C", Raw = "&R&G&L&G&CCenterText" }, + new { Name = "C,R,L", Raw = "&CCenterText&R&G&L&G" }, + new { Name = "R,C,L", Raw = "&R&G&CCenterText&L&G" }, + }; + + foreach (var v in variants) + { + using (var pkg = new ExcelPackage()) + { + var ws = pkg.Workbook.Worksheets.Add("Sheet1"); + SetRawHeaderFooterNode(ws, "oddFooter", v.Raw); + + // Touch the object so Save() rewrites the node. + var _ = ws.HeaderFooter.OddFooter.CenteredText; + + var persisted = SaveAndReadRawNode(pkg, "oddFooter"); + + StringAssert.Contains(persisted, "&L" + ExcelHeaderFooter.Image, + $"[{v.Name}] Left section lost. Raw was \"{Escape(v.Raw)}\", " + + $"persisted \"{Escape(persisted)}\"."); + StringAssert.Contains(persisted, "&CCenterText", + $"[{v.Name}] Center section lost. Persisted \"{Escape(persisted)}\"."); + StringAssert.Contains(persisted, "&R" + ExcelHeaderFooter.Image, + $"[{v.Name}] Right section lost. Persisted \"{Escape(persisted)}\"."); + } + } + } + + [TestMethod] + public void EmptySectionBetweenTwoSections_DoesNotConsumeFollowingSectionCode() + { + // Covers the "pos = startPos - 1" part of the fix. With the previous + // "pos = startPos" the loop's pos++ skipped the character at startPos, so a + // section code starting immediately after a consumed code - i.e. an empty + // section - was missed, and the following section was swallowed by it. + // + // The empty section must sit in the MIDDLE for this to bite: the first section + // code is taken outside the loop, so no skip has happened yet at that point. + // "&C&L&G&R&G" therefore parses correctly even without the fix and would not + // catch a regression here - the empty Center has to follow a match made inside + // the loop, as below. + const string raw = "&L&G&C&R&G"; + + using (var pkg = new ExcelPackage()) + { + var ws = pkg.Workbook.Worksheets.Add("Sheet1"); + SetRawHeaderFooterNode(ws, "oddFooter", raw); + + var oddFooter = ws.HeaderFooter.OddFooter; + + Console.WriteLine($"Raw : \"{Escape(raw)}\""); + Console.WriteLine($"Left : \"{Escape(oddFooter.LeftAlignedText)}\""); + Console.WriteLine($"Center : \"{Escape(oddFooter.CenteredText)}\""); + Console.WriteLine($"Right : \"{Escape(oddFooter.RightAlignedText)}\""); + + StringAssert.Contains(oddFooter.LeftAlignedText, ExcelHeaderFooter.Image, + $"Left section not parsed from \"{Escape(raw)}\"."); + + // The empty Center must not swallow the Right section's code. + Assert.IsFalse(Contains(oddFooter.CenteredText, "&R"), + $"The empty Center section swallowed the following '&R': " + + $"\"{Escape(oddFooter.CenteredText)}\"."); + + // This is the assertion that should fail if "pos = startPos - 1" is reverted: + // Right is never set, so RightAlignedText comes back as just "&R". + StringAssert.Contains(oddFooter.RightAlignedText, ExcelHeaderFooter.Image, + $"Right section not parsed from \"{Escape(raw)}\" - it was most likely " + + $"consumed by the empty Center section. Right parsed as " + + $"\"{Escape(oddFooter.RightAlignedText)}\"."); + } + } + + [TestMethod] + public void SectionCodeAtEndOfString_IsRecognized() + { + // Covers the "text.Length - 1" part of the fix. The previous "text.Length - 2" + // bound meant a section code occupying the final two characters was never seen, + // so it was swallowed into the preceding section instead of starting an empty one. + const string raw = "&L&G&R"; + + using (var pkg = new ExcelPackage()) + { + var ws = pkg.Workbook.Worksheets.Add("Sheet1"); + SetRawHeaderFooterNode(ws, "oddFooter", raw); + + var oddFooter = ws.HeaderFooter.OddFooter; + + Assert.IsFalse(Contains(oddFooter.LeftAlignedText, "&R"), + $"The trailing '&R' was swallowed into the Left section: " + + $"\"{Escape(oddFooter.LeftAlignedText)}\"."); + StringAssert.Contains(oddFooter.LeftAlignedText, ExcelHeaderFooter.Image, + "The Left section's picture placeholder should still be parsed."); + } + } + + [TestMethod] + public void HeaderSections_AreParsed_RegardlessOfSectionOrder() + { + // The same parsing path backs OddHeader, so it needs the same coverage - the + // reported case simply never touched the header. + const string raw = "&CHeaderCenter&L&G&R&G"; + + using (var pkg = new ExcelPackage()) + { + var ws = pkg.Workbook.Worksheets.Add("Sheet1"); + SetRawHeaderFooterNode(ws, "oddHeader", raw); + + var oddHeader = ws.HeaderFooter.OddHeader; + + StringAssert.Contains(oddHeader.LeftAlignedText, ExcelHeaderFooter.Image, + $"Left header section not parsed from \"{Escape(raw)}\". " + + $"Left parsed as \"{Escape(oddHeader.LeftAlignedText)}\"."); + } + } + + [TestMethod] + public void PictureCount_IsUnchanged_ByHeaderFooterTextRoundTrip() + { + // The reported symptom that made this hard to spot: the picture object itself was + // never lost, only the text reference to it, so Pictures.Count kept reporting the + // original value. This pins that behavior down so a future change cannot start + // silently dropping the VML pictures instead. + using (var pkg = new ExcelPackage()) + { + var ws = pkg.Workbook.Worksheets.Add("Sheet1"); + SetRawHeaderFooterNode(ws, "oddFooter", CustomerOddFooter); + + var countBefore = ws.HeaderFooter.Pictures.Count; + var _ = ws.HeaderFooter.OddFooter.LeftAlignedText; + + using (var stream = new MemoryStream()) + { + pkg.SaveAs(stream); + using (var reloaded = new ExcelPackage(stream)) + { + Assert.AreEqual(countBefore, + reloaded.Workbook.Worksheets[0].HeaderFooter.Pictures.Count, + "HeaderFooter.Pictures.Count changed across the round trip."); + } + } + } + } + + #region Helpers + + private static string Escape(string s) + { + return s == null ? "" : s.Replace("\n", "\\n").Replace("\r", "\\r"); + } + + private static bool Contains(string haystack, string needle) + { + return haystack != null && haystack.Contains(needle); + } + + private static XmlNamespaceManager GetNsm(ExcelWorksheet ws) + { + var nsm = new XmlNamespaceManager(ws.WorksheetXml.NameTable); + nsm.AddNamespace("d", ExcelPackage.schemaMain); + return nsm; + } + + /// + /// Writes a raw string straight into d:headerFooter/d:{nodeName}, so section ordering + /// is under the test's control instead of EPPlus's own always-Left-Center-Right + /// authoring order - which is what makes these orderings reachable at all. + /// + private static void SetRawHeaderFooterNode(ExcelWorksheet ws, string nodeName, string rawText) + { + var nsm = GetNsm(ws); + var wsNode = ws.WorksheetXml.SelectSingleNode("d:worksheet", nsm); + + var hfNode = wsNode.SelectSingleNode("d:headerFooter", nsm); + if (hfNode == null) + { + hfNode = ws.WorksheetXml.CreateElement("headerFooter", ExcelPackage.schemaMain); + wsNode.AppendChild(hfNode); + } + + var node = hfNode.SelectSingleNode("d:" + nodeName, nsm); + if (node == null) + { + node = ws.WorksheetXml.CreateElement(nodeName, ExcelPackage.schemaMain); + hfNode.AppendChild(node); + } + node.InnerText = rawText; + } + + /// + /// Saves the package to a stream, reloads it, and returns the raw text of the + /// requested header/footer node as persisted - i.e. what Excel would read. + /// + private static string SaveAndReadRawNode(ExcelPackage pkg, string nodeName) + { + using (var stream = new MemoryStream()) + { + pkg.SaveAs(stream); + + using (var reloaded = new ExcelPackage(stream)) + { + var ws = reloaded.Workbook.Worksheets[0]; + var nsm = GetNsm(ws); + var node = ws.WorksheetXml.SelectSingleNode( + "d:worksheet/d:headerFooter/d:" + nodeName, nsm); + return node == null ? null : node.InnerText; + } + } + } + + #endregion + } +} \ No newline at end of file diff --git a/src/EPPlusTest/Drawing/Chart/DataPointsTest.cs b/src/EPPlusTest/Drawing/Chart/DataPointsTest.cs index 6964621938..9052f04051 100644 --- a/src/EPPlusTest/Drawing/Chart/DataPointsTest.cs +++ b/src/EPPlusTest/Drawing/Chart/DataPointsTest.cs @@ -87,9 +87,8 @@ public void PieChart() var svg = chart.ToSvg(); + File.WriteAllText($"{_worksheetPath}svg\\EPPlusPieChart1.svg", svg); //SaveAndCleanup(_pck); - - //File.WriteAllText($"{_worksheetPath}svg\\EPPlusPieChart1.svg", svg); } [TestMethod] public void BarChart() @@ -137,7 +136,7 @@ public void GradientPieChart() } } - [TestMethod] + [TestMethod] public void DataLabelsMultipleOneSeriesExport() { using (var pck = OpenPackage("DataLabelsMultipleOneSeriesExport.xlsx", true)) diff --git a/src/EPPlusTest/Export/HtmlExport/RangeExporterTests.cs b/src/EPPlusTest/Export/HtmlExport/RangeExporterTests.cs index 7ec061fb6a..9a27e3c33d 100644 --- a/src/EPPlusTest/Export/HtmlExport/RangeExporterTests.cs +++ b/src/EPPlusTest/Export/HtmlExport/RangeExporterTests.cs @@ -222,9 +222,8 @@ public async Task TaskWriteChartAndShape() var setting = exporter.Settings.Drawings; - setting.DrawTypeInclude = eDrawingInclude.Shapes & eDrawingInclude.Shapes; - setting.Include = ePictureInclude.Include; - setting.Position = ePicturePosition.Absolute; + setting.Include = eDrawingInclude.Include; + setting.Position = eDrawingPosition.Absolute; exporter.Settings.Minify = false; exporter.Settings.Encoding = Encoding.UTF8; @@ -253,9 +252,8 @@ public async Task TaskWriteChartSimple() var setting = exporter.Settings.Drawings; - setting.DrawTypeInclude = eDrawingInclude.Charts; - setting.Include = ePictureInclude.Include; - setting.Position = ePicturePosition.Relative; + setting.Include = eDrawingInclude.Include; + setting.Position = eDrawingPosition.Relative; exporter.Settings.Minify = false; exporter.Settings.Encoding = Encoding.UTF8; @@ -282,7 +280,7 @@ public async Task WriteImagesAsync() exporter.Settings.SetColumnWidth = true; exporter.Settings.SetRowHeight = true; - exporter.Settings.Pictures.Include = ePictureInclude.Include; + exporter.Settings.Drawings.Include = eDrawingInclude.Include; exporter.Settings.Minify = false; exporter.Settings.Encoding = Encoding.UTF8; var html = exporter.GetSinglePage(); @@ -303,7 +301,7 @@ public async Task WriteImagesAsyncHTMLOnlyEmbed() exporter.Settings.SetColumnWidth = true; exporter.Settings.SetRowHeight = true; - exporter.Settings.Pictures.Include = ePictureInclude.IncludeInHtmlOnly; + exporter.Settings.Drawings.Include = eDrawingInclude.IncludeInHtmlOnly; exporter.Settings.Minify = false; exporter.Settings.Encoding = Encoding.UTF8; diff --git a/src/EPPlusTest/Export/HtmlExport/SvgShapeExportTests.cs b/src/EPPlusTest/Export/HtmlExport/SvgShapeExportTests.cs index 9ae2dfb929..74d731f5ed 100644 --- a/src/EPPlusTest/Export/HtmlExport/SvgShapeExportTests.cs +++ b/src/EPPlusTest/Export/HtmlExport/SvgShapeExportTests.cs @@ -40,8 +40,7 @@ public void ExportBasicShapeWorksheet() var exporter = ws.Cells["A1:C20"].CreateHtmlExporter(); - exporter.Settings.Drawings.Include = ePictureInclude.IncludeInHtmlOnly; - exporter.Settings.Drawings.DrawTypeInclude = eDrawingInclude.Shapes; + exporter.Settings.Drawings.Include = eDrawingInclude.IncludeInHtmlOnly; var htmlPage = exporter.GetSinglePage(); @@ -147,8 +146,7 @@ public void ExportBarChartWithCategories() var exporter = ws.Cells["A1:C20"].CreateHtmlExporter(); - exporter.Settings.Drawings.Include = ePictureInclude.IncludeInHtmlOnly; - exporter.Settings.Drawings.DrawTypeInclude = eDrawingInclude.Charts; + exporter.Settings.Drawings.Include = eDrawingInclude.IncludeInHtmlOnly; var htmlPage = exporter.GetSinglePage(); diff --git a/src/EPPlusTest/Export/HtmlExport/TableExporterTests.cs b/src/EPPlusTest/Export/HtmlExport/TableExporterTests.cs index bf4e961317..83f704699a 100644 --- a/src/EPPlusTest/Export/HtmlExport/TableExporterTests.cs +++ b/src/EPPlusTest/Export/HtmlExport/TableExporterTests.cs @@ -497,7 +497,7 @@ public async Task WriteMultipleRangeWithTableAndRange() exporterRange.Settings.SetRowHeight = true; exporterRange.Settings.Minify = false; exporterRange.Settings.TableStyle = eHtmlRangeTableInclude.Include; - exporterRange.Settings.Pictures.Include = ePictureInclude.Include; + exporterRange.Settings.Drawings.Include = eDrawingInclude.Include; var html1 = exporterRange.GetHtmlString(0); var html2 = exporterRange.GetHtmlString(1); @@ -509,7 +509,7 @@ public async Task WriteMultipleRangeWithTableAndRange() var outputHtml = string.Format("\r\n\r\n\r\n\r\n\r\n{0}
{1}
{2}
{3}
\r\n", html1, html2, html3, html4, css); - File.WriteAllText("${_htmlOutput}RangeAndThreeTables.html", outputHtml); + File.WriteAllText($"{_htmlOutput}RangeAndThreeTables.html", outputHtml); Assert.AreEqual(css, cssAsync); } diff --git a/src/EPPlusTest/Issues/ConditionalFormattingIssues.cs b/src/EPPlusTest/Issues/ConditionalFormattingIssues.cs index 1130ea6626..6fa34c1bdf 100644 --- a/src/EPPlusTest/Issues/ConditionalFormattingIssues.cs +++ b/src/EPPlusTest/Issues/ConditionalFormattingIssues.cs @@ -8,6 +8,8 @@ using System.Drawing; using System.Globalization; using System.IO; +using System.Linq; +using System.Runtime.InteropServices; using System.Threading; using ColorTranslator = System.Drawing.ColorTranslator; @@ -264,6 +266,127 @@ public void RoundTrip_ExtIconSetWithNumericFormulaCfvo_DoesNotThrow() } } + + [TestMethod] + public void ReadEppGeneratedCFBorderStylesCorrectly() + { + using (var p = OpenPackage("i2488_EppGen.xlsx", true)) + { + var ws = p.Workbook.Worksheets.Add("readBorderNone"); + + var notEqual = ws.Cells["C1:C5"].ConditionalFormatting.AddNotEqual(); + + notEqual.Formula = "1"; + + ws.Cells["D5"].Style.Border.Left.Style = ExcelBorderStyle.None; + + //Set a borderstyle to thick so that Conditional Formatting can then Set it to None + ws.Cells["C2"].Style.Border.Left.Style = ExcelBorderStyle.Thick; + ws.Cells["C2"].Style.Border.Top.Style = ExcelBorderStyle.Thick; + ws.Cells["C2"].Style.Border.Right.Style = ExcelBorderStyle.Thick; + ws.Cells["C2"].Style.Border.Bottom.Style = ExcelBorderStyle.Thick; + + notEqual.Style.Border.Left.Style = ExcelBorderStyle.None; + notEqual.Style.Border.Top.Style = ExcelBorderStyle.None; + notEqual.Style.Border.Right.Style = ExcelBorderStyle.None; + notEqual.Style.Border.Bottom.Style = ExcelBorderStyle.None; + + SaveAndCleanup(p); + } + using (var p = OpenPackage("i2488_EppGen.xlsx", false)) + { + var ws = p.Workbook.Worksheets[0]; + var cfs = ws.Cells["C1:C5"].ConditionalFormatting.GetConditionalFormattings(); + + var rightStyle = cfs[0].Style.Border.Right.Style; + Assert.AreEqual(ExcelBorderStyle.None, ws.Cells["D5"].Style.Border.Left.Style); + //Assert that we can read borderStyle None. This is different from NoNode and the same as an Empty border node + Assert.AreEqual(ExcelBorderStyle.None, cfs[0].Style.Border.Right.Style); + Assert.AreEqual(ExcelBorderStyle.None, cfs[0].Style.Border.Left.Style); + Assert.AreEqual(ExcelBorderStyle.None, cfs[0].Style.Border.Top.Style); + Assert.AreEqual(ExcelBorderStyle.None, cfs[0].Style.Border.Bottom.Style); + } + } + + /// + /// Epplus did not write out ExcelBorderStyle.None correctly into DXF styles + /// Causing Cell-Styling to be applied instead of the conditionallyFormatted "None" style + /// + [TestMethod] + public void CopyingIssue2488_ActualCase() + { + string expectedId = ""; + var fi = GetOutputFile("", "i2488_out.xlsx"); + var copiedName = ""; + + using (var p = OpenTemplatePackage("i2488.xlsx")) + { + var wkbook = p.Workbook; + var wkSheet = wkbook.Worksheets[0]; + + var conditionalFormatting = wkSheet.Cells["S15"].ConditionalFormatting.GetConditionalFormattings().First(cf => cf.Address.Address == "S7:Y22"); + var dxfId = conditionalFormatting.DxfId; + expectedId = conditionalFormatting.Style.ToDxfStyle().Id; + Assert.AreEqual(expectedId, wkbook.Styles.Dxfs[dxfId].Id); + var sheetName = wkSheet.Name; + var copyCount = 1; + + copiedName = $"{sheetName}_{1}"; + + for (int i = 1; i <= copyCount; i++) + { + p.Workbook.Worksheets.Copy( + wkSheet.Name, $"{sheetName}_{i}"); + } + + wkSheet = wkbook.Worksheets[1]; + sheetName = wkbook.Worksheets[1].Name; + + for (int i = 1; i <= copyCount; i++) + { + p.Workbook.Worksheets.Copy( + wkSheet.Name, $"{sheetName}_{i}"); + } + p.SaveAs(fi); + } + using (var p = OpenPackage(fi.Name, false)) + { + var wkbook = p.Workbook; + var wkSheet = wkbook.Worksheets[copiedName]; + + var conditionalFormatting = wkSheet.Cells["S15"].ConditionalFormatting.GetConditionalFormattings().First(cf => cf.Address.Address == "S7:Y22"); + Assert.AreEqual(expectedId, conditionalFormatting.Style.ToDxfStyle().Id); + } + } + + [TestMethod] + public void CopyingIssue2488_StyleRead() + { + string expectedId = ""; + var fi = GetOutputFile("", "i2488_Read_out.xlsx"); + + using (var p = OpenTemplatePackage("i2488.xlsx")) + { + var wkbook = p.Workbook; + var wkSheet = wkbook.Worksheets[0]; + + var conditionalFormatting = wkSheet.Cells["S15"].ConditionalFormatting.GetConditionalFormattings().First(cf=> cf.Address.Address == "S7:Y22"); + var dxfId = conditionalFormatting.DxfId; + expectedId = conditionalFormatting.Style.ToDxfStyle().Id; + Assert.AreEqual(expectedId, wkbook.Styles.Dxfs[dxfId].Id); + p.SaveAs(fi); + } + + using (var p = OpenPackage(fi.Name, false)) + { + var wkbook = p.Workbook; + var wkSheet = wkbook.Worksheets[0]; + + var conditionalFormatting = wkSheet.Cells["S15"].ConditionalFormatting.GetConditionalFormattings().First(cf => cf.Address.Address == "S7:Y22"); + Assert.AreEqual(expectedId, conditionalFormatting.Style.ToDxfStyle().Id); + } + } + [TestMethod] public void RoundTrip_RegularIconSetWithNumericFormulaCfvo_DoesNotThrow() {