diff --git a/src/EPPlus.Export.ImageRenderer.Test/SvgPathTests.cs b/src/EPPlus.Export.ImageRenderer.Test/SvgPathTests.cs index c60f4eedb..9de1cbd58 100644 --- a/src/EPPlus.Export.ImageRenderer.Test/SvgPathTests.cs +++ b/src/EPPlus.Export.ImageRenderer.Test/SvgPathTests.cs @@ -346,7 +346,7 @@ public void CustomPath() var ws = p.Workbook.Worksheets[0]; //var d = ws.Drawings[0].As.Shape; //Assert.AreEqual(1, d.CustomGeom.DrawingPaths.Count); - //d.Textbox = "Rectangle Rectangle Rectangle Rectangle"; + //d.Textbox = "GetRectangle GetRectangle GetRectangle GetRectangle"; //d.TextAlignment = OfficeOpenXml.Drawing.eTextAlignment.Left; //d.TextAnchoring = OfficeOpenXml.Drawing.eTextAnchoringType.Bottom; var renderer = new EPPlusImageRenderer.ImageRenderer(); @@ -511,20 +511,22 @@ public void GenerateSvgForCharts() { var ws = p.Workbook.Worksheets[0]; var renderer = new EPPlusImageRenderer.ImageRenderer(); - //var ix = 0; - //var c = ws.Drawings[ix]; - //var svg = renderer.RenderDrawingToSvg(c); - //SaveTextFileToWorkbook($"svg\\ChartForSvg_ind{ix++}.svg", svg); - var ix = 1; - foreach (ExcelChart c in ws.Drawings) - { - var svg = renderer.RenderDrawingToSvg(c); - SaveTextFileToWorkbook($"svg\\ChartForSvg{ix++}.svg", svg); - } + + var ix = 2; + var c = ws.Drawings[ix]; + var svg = renderer.RenderDrawingToSvg(c); + SaveTextFileToWorkbook($"svg\\ChartForSvg_ind{ix++}.svg", svg); + + //var ix = 1; + //foreach (ExcelChart c in ws.Drawings) + //{ + // var svg = renderer.RenderDrawingToSvg(c); + // SaveTextFileToWorkbook($"svg\\ChartForSvg{ix++}.svg", svg); + //} } } [TestMethod] - public void GenerateSvgForLineCharts() + public void GenerateSvgForLineCharts() { ExcelPackage.License.SetNonCommercialOrganization("EPPlus Project"); using (var p = OpenTemplatePackage("LineChartRenderTest.xlsx")) diff --git a/src/EPPlus.Export.ImageRenderer.Test/TestTextContainer.cs b/src/EPPlus.Export.ImageRenderer.Test/TestTextContainer.cs deleted file mode 100644 index 03f7fa1f6..000000000 --- a/src/EPPlus.Export.ImageRenderer.Test/TestTextContainer.cs +++ /dev/null @@ -1,132 +0,0 @@ -using EPPlus.Export.ImageRenderer.RenderItems.Shared; -using EPPlus.Export.ImageRenderer.RenderItems.SvgItem; -using EPPlus.Export.ImageRenderer.Text; -using EPPlus.Fonts.OpenType; -using EPPlus.Graphics; -using OfficeOpenXml; -using OfficeOpenXml.Drawing; -using OfficeOpenXml.Interfaces.Drawing.Text; -using OfficeOpenXml.Style; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -using EPPlusImageRenderer; - -namespace EPPlus.Export.ImageRenderer.Tests -{ - [TestClass] - public class TestTextContainer - { - [TestMethod] - public void TestDynamicSizeSingleLine() - { - string content = "TextBox"; - var expectedWidth = 48d; - var expectedHeight = 18d; - - var mf = new MeasurementFont() { FontFamily = "Aptos", Size = 11, Style = MeasurementFontStyles.Regular }; - - var container = new TextContainer(content, mf, true); - - Assert.AreEqual(expectedWidth, Math.Round(container.Width, 0)); - Assert.AreEqual(expectedHeight, Math.Round(container.Height, 0)); - } - - [TestMethod] - public void TestDynamicSizeMultipleLines() - { - string content = "TextBox\r\na very long line2\r\nline3"; - var expectedWidth = 93d; - var expectedHeight = 54d; - - //0.57 inches - //0.98 inches - - var mf = new MeasurementFont() { FontFamily = "Aptos Narrow", Size = 11, Style = MeasurementFontStyles.Regular }; - - var container = new TextContainer(content, mf, true); - - Assert.AreEqual(expectedWidth, Math.Round(container.Width, 0)); - Assert.AreEqual(expectedHeight, Math.Round(container.Height, 0)); - } - - [TestMethod] - public void TestNonStandardFontSizesMultiLine() - { - string content = "TextBox\r\na very long line2\r\nline3"; - var expectedWidth = 203d; - var expectedHeight = 117d; - - var mf = new MeasurementFont() { FontFamily = "Aptos Narrow", Size = 24, Style = MeasurementFontStyles.Regular }; - - var container = new TextContainer(content, mf, true, true); - //0,056640625 * font size width correction for pixels (minimum) - Assert.AreEqual(expectedWidth, Math.Round(container.Width, 0)); - Assert.AreEqual(expectedHeight, Math.Round(container.Height, 0)); - } - - [TestMethod] - public void TestNonStandardFontSizesMultiLineLargeFont() - { - string content = "TextBox\r\na very long line2\r\nline3"; - var expectedWidth = 813d; - var expectedHeight = 469d; - - var mf = new MeasurementFont() { FontFamily = "Aptos Narrow", Size = 96, Style = MeasurementFontStyles.Regular }; - - var container = new TextContainer(content, mf, true, true); - //0,072265625 * font size width correction for pixels - //0,0442708333333333 * font size height correction for pixels - Assert.AreEqual(expectedWidth, Math.Round(container.Width, 0)); - Assert.AreEqual(expectedHeight, Math.Round(container.Height, 0)); - } - - [TestMethod] - public void TestNonStandardFontSizesMultiLineLargeFontGoudyStout() - { - string content = "TextBox\r\na very long line2\r\nline3"; - //This is the actual height in excel. - //var expectedHeight = 533d; - //It is unclear why as the Max possible glyph height in pixels for each line is - //175.125 pixels. *3 = 525.375 - //Best guess is it adds 4 pixels per row for potential "outline" - - //This is the glyph yMax and YMin - var expectedHeight = 525d; - //Excel has 2332d but they add some kind of internal buffer/minX spacing for lineEnding - var expectedWidth = 2309d; - - var mf = new MeasurementFont() { FontFamily = "Goudy Stout", Size = 96, Style = MeasurementFontStyles.Regular }; - - var container = new TextContainer(content, mf, true, true); - //0,072265625 * font size width correction for pixels - //0,0442708333333333 * font size height correction for pixels - Assert.AreEqual(expectedWidth, Math.Round(container.Width, 0)); - Assert.AreEqual(expectedHeight, Math.Round(container.Height, 0)); - } - - //[TestMethod] - //public void EnsureTextBodyAddsRunsCorrectly() - //{ - // BoundingBox shapeRect = new BoundingBox(); - - // shapeRect.Width = 20; - // shapeRect.Height = 10; - - // FontMeasurerTrueType measurer = new FontMeasurerTrueType(12, "Aptos Narrow", FontSubFamily.Regular); - // var body = new SvgTextBodyItem(shapeRect); - - // body.Bounds.Name = "TxtBody"; - - // body.AddText("A new Paragraph", measurer); - // body.AddText("Second paragraph", measurer); - - // Assert.AreEqual(2, body.Paragraphs[0].Bounds.Transform.ChildObjects.Count); - // Assert.AreEqual(body.Bounds.Transform.ChildObjects[0], body.Paragraphs[0].Bounds.Transform); - - // Assert.AreEqual(shapeRect.Transform, body.Bounds.Transform.TopDrawingHandler); - //} - } -} diff --git a/src/EPPlus.Export.ImageRenderer/ImageRenderer.cs b/src/EPPlus.Export.ImageRenderer/ImageRenderer.cs index f6221cb93..0642b6dc5 100644 --- a/src/EPPlus.Export.ImageRenderer/ImageRenderer.cs +++ b/src/EPPlus.Export.ImageRenderer/ImageRenderer.cs @@ -17,7 +17,6 @@ Date Author Change using EPPlus.Export.ImageRenderer.Svg; using EPPlus.Export.ImageRenderer.Svg.NodeAttributes; using EPPlus.Export.ImageRenderer.Svg.Writer; -using EPPlus.Export.ImageRenderer.Text; using EPPlus.Fonts.OpenType; using EPPlus.Graphics; using EPPlusImageRenderer.Svg; @@ -37,7 +36,6 @@ public class ImageRenderer { public string RenderDrawingToSvg(ExcelDrawing drawing) { - drawing.GetSizeInPixels(out int width, out int height); var sb = new StringBuilder(); if (drawing is ExcelShape shape) { @@ -54,6 +52,8 @@ public string RenderDrawingToSvg(ExcelDrawing drawing) throw new NotImplementedException("Image rendering for drawing type not implemented."); } + + //public string RenderRangeToSvg(ExcelRange range) //{ // var ws = range.Worksheet; @@ -80,33 +80,33 @@ public string RenderDrawingToSvg(ExcelDrawing drawing) // return sb.ToString(); //} - public string RenderBox(string boxText) - { - string retStr = ""; - var container = new TextContainerBase(boxText); - var element = GenerateSvg(container); + //public string RenderBox(string boxText) + //{ + // string retStr = ""; + // var container = new TextContainerBase(boxText); + // var element = GenerateSvg(container); - using (var ms = EPPlusMemoryManager.GetStream()) - { - SvgWriter writer = new SvgWriter(ms, Encoding.UTF8); - writer.RenderSvgElement(element, true); - ms.Position = 0; - using (var sr = new StreamReader(ms)) - { - retStr = sr.ReadToEnd(); - return retStr; - } - } + // using (var ms = EPPlusMemoryManager.GetStream()) + // { + // SvgWriter writer = new SvgWriter(ms, Encoding.UTF8); + // writer.RenderSvgElement(element, true); + // ms.Position = 0; + // using (var sr = new StreamReader(ms)) + // { + // retStr = sr.ReadToEnd(); + // return retStr; + // } + // } - //writer.RenderSvgElement(element, true); + // //writer.RenderSvgElement(element, true); - //StreamReader reader = new StreamReader(ms); - //retStr = reader.ReadToEnd(); + // //StreamReader reader = new StreamReader(ms); + // //retStr = reader.ReadToEnd(); - ////SvgParagraph para = new SvgParagraph(container.GetContent(),); - ////var doc = new SvgEpplusDocument(); - //return retStr; - } + // ////SvgParagraph para = new SvgParagraph(container.GetContent(),); + // ////var doc = new SvgEpplusDocument(); + // //return retStr; + //} //public string RenderTextBody(ExcelTextBody body, double shapeWidth, double shapeHeight) //{ @@ -178,61 +178,61 @@ internal SvgElement GetDefinitions(BoundingBox boundingBox, out string nameId, b return def; } - internal SvgElement GenerateSvg(TextContainerBase container) - { - var fullString = container.GetContent(); + //internal SvgElement GenerateSvg(TextContainerBase container) + //{ + // var fullString = container.GetContent(); - var doc = new SvgEpplusDocument(500, 500); + // var doc = new SvgEpplusDocument(500, 500); - var bg = new SvgElement("rect"); - bg.AddAttribute("width", "100%"); - bg.AddAttribute("height", "100%"); - bg.AddAttribute("fill", "red"); - bg.AddAttribute("opacity", "0.1"); + // var bg = new SvgElement("rect"); + // bg.AddAttribute("width", "100%"); + // bg.AddAttribute("height", "100%"); + // bg.AddAttribute("fill", "red"); + // bg.AddAttribute("opacity", "0.1"); - var nameId = "boundingBox"; - var def = new SvgElement("defs"); - var clipPath = new SvgElement("clipPath"); - clipPath.AddAttribute("id", nameId); + // var nameId = "boundingBox"; + // var def = new SvgElement("defs"); + // var clipPath = new SvgElement("clipPath"); + // clipPath.AddAttribute("id", nameId); - def.AddChildElement(clipPath); + // def.AddChildElement(clipPath); - var bb = new SvgElement("rect"); - bb.AddAttribute("x", container.Position.X); - bb.AddAttribute("y", container.Position.Y); - bb.AddAttribute("width", container.Width); - bb.AddAttribute("height", container.Height); - //bb.AddAttribute("fill", "blue"); - //bb.AddAttribute("opacity", "0.5"); + // var bb = new SvgElement("rect"); + // bb.AddAttribute("x", container.Position.X); + // bb.AddAttribute("y", container.Position.Y); + // bb.AddAttribute("width", container.Width); + // bb.AddAttribute("height", container.Height); + // //bb.AddAttribute("fill", "blue"); + // //bb.AddAttribute("opacity", "0.5"); - clipPath.AddChildElement(bb); + // clipPath.AddChildElement(bb); - var fontSizePx = 16d; + // var fontSizePx = 16d; - var renderElement = new SvgElement("text"); - renderElement.AddAttribute("x", container.Position.X); - renderElement.AddAttribute("y", container.Position.Y + fontSizePx); - renderElement.AddAttribute("_measurementFont-size", $"{fontSizePx}px"); - renderElement.AddAttribute("clip-path", $"url(#{nameId})"); + // var renderElement = new SvgElement("text"); + // renderElement.AddAttribute("x", container.Position.X); + // renderElement.AddAttribute("y", container.Position.Y + fontSizePx); + // renderElement.AddAttribute("_measurementFont-size", $"{fontSizePx}px"); + // renderElement.AddAttribute("clip-path", $"url(#{nameId})"); - renderElement.Content = fullString; + // renderElement.Content = fullString; - var bbVisual = new SvgElement("rect"); - bbVisual.AddAttribute("x", container.Position.X); - bbVisual.AddAttribute("y", container.Position.Y); - bbVisual.AddAttribute("width", container.Width); - bbVisual.AddAttribute("height", container.Height); - bbVisual.AddAttribute("fill", "blue"); - bbVisual.AddAttribute("opacity", "0.5"); + // var bbVisual = new SvgElement("rect"); + // bbVisual.AddAttribute("x", container.Position.X); + // bbVisual.AddAttribute("y", container.Position.Y); + // bbVisual.AddAttribute("width", container.Width); + // bbVisual.AddAttribute("height", container.Height); + // bbVisual.AddAttribute("fill", "blue"); + // bbVisual.AddAttribute("opacity", "0.5"); - doc.AddChildElement(def); - doc.AddChildElement(bg); - doc.AddChildElement(bbVisual); - doc.AddChildElement(renderElement); + // doc.AddChildElement(def); + // doc.AddChildElement(bg); + // doc.AddChildElement(bbVisual); + // doc.AddChildElement(renderElement); - doc.AddAttributes(); + // doc.AddAttributes(); - return doc; - } + // return doc; + //} } } diff --git a/src/EPPlus.Export.ImageRenderer/PathCommands.cs b/src/EPPlus.Export.ImageRenderer/PathCommands.cs index ed2c6a1e2..b8fb0753e 100644 --- a/src/EPPlus.Export.ImageRenderer/PathCommands.cs +++ b/src/EPPlus.Export.ImageRenderer/PathCommands.cs @@ -32,7 +32,7 @@ public PathCommands(PathCommandType type, SvgRenderItem item, params double[] co public SvgAdjustmentPoint AdjustmentPoint { get; set; } public int CommandIndex { get; set; } - public void Render(int width, int height, StringBuilder sb) + public void Render(double width, double height, StringBuilder sb) { sb.Append(Type.AsCommandChar()); for (int i = 0; i < Coordinates.Length; i++) @@ -92,80 +92,6 @@ private double AdjustWithPoint(int width, int height, int i, double x) } return x; } - - private double AdjustPointHalf(int width, int height, int i, double x, bool minus) - { - if (i % 2 == 0) - { - if (width > height) - { - x *= Math.Max(width, height); - if (minus) - { - x -= (Math.Abs(width - height) / 2); - } - else - { - x += (Math.Abs(width - height) / 2); - } - } - else - { - x *= width; - } - } - else - { - if (height > width) - { - x *= Math.Max(width, height); - if (minus) - { - x -= (Math.Abs(width - height) / 2); - } - else - { - x += (Math.Abs(width - height) / 2); - } - } - else - { - x *= height; - } - } - - return x; - } - - private static double AdjustToWidthHight(int width, int height, int i, double x) - { - if (i % 2 == 0) - { - if (width > height) - { - x *= Math.Min(width, height); - x += (Math.Abs(width - height) / 2); - } - else - { - x *= width; - } - } - else - { - if (height > width) - { - x *= Math.Min(width, height); - x += (Math.Abs(width - height) / 2); - } - else - { - x *= height; - } - } - - return x; - } internal PathCommands Clone() { return new PathCommands(Type, RenderItem) diff --git a/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/ParagraphItem.cs b/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/ParagraphItem.cs index c2cea7a45..d65ff42bf 100644 --- a/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/ParagraphItem.cs +++ b/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/ParagraphItem.cs @@ -1,5 +1,4 @@ -using EPPlus.Export.ImageRenderer.Text; -using EPPlus.Fonts.OpenType; +using EPPlus.Fonts.OpenType; using EPPlus.Fonts.OpenType.TrueTypeMeasurer.DataHolders; using EPPlus.Fonts.OpenType.Utils; using EPPlus.Graphics; @@ -26,8 +25,7 @@ internal abstract class ParagraphItem : RenderItem ITextMeasurerWrap _measurer; double _leftMargin; - double _rightMargin; - protected eTextAlignment _hAlign; + double _rightMargin; eDrawingTextLineSpacing _lsType; double _lineSpacingAscendantOnly; @@ -40,6 +38,7 @@ internal abstract class ParagraphItem : RenderItem internal protected MeasurementFont _paragraphFont; internal TextBodyItem ParentTextBody { get; set; } internal double ParagraphLineSpacing { get; private set; } + internal eTextAlignment HorizontalAlignment { get; private set; } = eTextAlignment.Left; internal List Runs { get; set; } = new List(); public ParagraphItem(TextBodyItem textBody, DrawingBase renderer, BoundingBox parent) : base(renderer, parent) @@ -86,10 +85,16 @@ public ParagraphItem(TextBodyItem textBody, DrawingBase renderer, BoundingBox pa _leftMargin = p.LeftMargin + p.Indent + indent; _rightMargin = p.RightMargin; - _hAlign = p.HorizontalAlignment; + _leftMargin = _leftMargin.PixelToPoint(); + _rightMargin = _rightMargin.PixelToPoint(); - Bounds.Left = GetAlignmentHorizontal(_hAlign); - Bounds.Width = parent.Width - p.RightMargin - p.LeftMargin; + HorizontalAlignment = p.HorizontalAlignment; + + if (ParentTextBody.AutoSize == false) + { + Bounds.Left = GetAlignmentHorizontal(HorizontalAlignment); + Bounds.Width = parent.Width - _rightMargin - _leftMargin; + } //---Get measurer--- _measurer = p._prd.Package.Settings.TextSettings.GenericTextMeasurerTrueType; @@ -97,7 +102,7 @@ public ParagraphItem(TextBodyItem textBody, DrawingBase renderer, BoundingBox pa //---Calculate linespacing--- int numLines = _paragraphLines.Count; _lsType = p.LineSpacing.LineSpacingType; - ParagraphLineSpacing = GetParagraphLineSpacingInPixels(p.LineSpacing.Value, _measurer); + ParagraphLineSpacing = GetParagraphLineSpacingInPoints(p.LineSpacing.Value, _measurer); //---Initialize / calculate lines and runs--- //measurer must be set before AddLinesAndRichText @@ -107,15 +112,15 @@ public ParagraphItem(TextBodyItem textBody, DrawingBase renderer, BoundingBox pa AddLinesAndTextRuns(p, textIfEmpty); } - private double GetParagraphLineSpacingInPixels(double spacingValue, ITextMeasurerWrap fmExact) + private double GetParagraphLineSpacingInPoints(double spacingValue, ITextMeasurerWrap fmExact) { if (_lsType == eDrawingTextLineSpacing.Exactly) { if (IsFirstParagraph) { - _lineSpacingAscendantOnly = spacingValue.PointToPixel(); + _lineSpacingAscendantOnly = spacingValue; } - return spacingValue.PointToPixel(); + return spacingValue; } else { @@ -123,9 +128,9 @@ private double GetParagraphLineSpacingInPixels(double spacingValue, ITextMeasure _lsMultiplier = multiplier; if (IsFirstParagraph) { - _lineSpacingAscendantOnly = multiplier * fmExact.GetBaseLine().PointToPixel(); + _lineSpacingAscendantOnly = multiplier * fmExact.GetBaseLine(); } - return multiplier * fmExact.GetSingleLineSpacing().PointToPixel(); + return multiplier * fmExact.GetSingleLineSpacing(); } } @@ -249,6 +254,7 @@ private void AddLinesAndTextRuns(ExcelDrawingParagraph p, string textIfEmpty) lastDescent = line.LargestDescent; } Bounds.Height = runLineSpacing + lastDescent; + Bounds.Width = greatestWidth; } List WrapToSimpleTextLines(ExcelDrawingParagraph p, TextFragmentCollection fragments) @@ -263,7 +269,7 @@ List WrapToSimpleTextLines(ExcelDrawingParagraph p, TextFragment fonts.Add(runFont); } - var maxWidthPoints = Math.Round(Bounds.Width, 0, MidpointRounding.AwayFromZero).PixelToPoint(); + var maxWidthPoints = Math.Round(ParentTextBody.MaxWidth, 0, MidpointRounding.AwayFromZero); return ttMeasurer.WrapMultipleTextFragmentsToTextLines(fragments, fonts, maxWidthPoints); } diff --git a/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/TextBodyItem.cs b/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/TextBodyItem.cs index db40afda8..b49f168fd 100644 --- a/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/TextBodyItem.cs +++ b/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/TextBodyItem.cs @@ -22,11 +22,11 @@ namespace EPPlus.Export.ImageRenderer.RenderItems.Shared /// internal abstract class TextBodyItem : DrawingObject { - public TextBodyItem(DrawingBase renderer, BoundingBox parent) : base(renderer, parent) + public TextBodyItem(DrawingBase renderer, BoundingBox parent, bool autoSize) : base(renderer, parent) { Bounds.Name = "TxtBody"; - Bounds.Parent = parent; + AutoSize = autoSize; } public TextBodyItem(DrawingBase renderer, BoundingBox parent, double maxWidth, double maxHeight) : base(renderer, parent) { @@ -34,7 +34,9 @@ public TextBodyItem(DrawingBase renderer, BoundingBox parent, double maxWidth, d Bounds.Parent = parent; MaxWidth = maxWidth; MaxHeight = maxHeight; + AutoSize = false; } + internal bool AutoSize { get; set; } internal double MaxWidth { get; set; } internal double MaxHeight { get; set; } @@ -71,13 +73,47 @@ public void ImportParagraph(ExcelDrawingParagraph item, double startingY, string _text = text; if(Paragraphs.Count == 0) { - Bounds.Width = paragraph.Bounds.Width; + Bounds.Height = paragraph.Bounds.Height; } else { - Bounds.Width += paragraph.Bounds.Width; + Bounds.Height += paragraph.Bounds.Height; + } + + if(Bounds.Width < paragraph.Bounds.Width || (Bounds.Width == MaxWidth && Paragraphs.Count==0)) + { + Bounds.Width = paragraph.Bounds.Width; } Paragraphs.Add(paragraph); + SetHorizontalAlignmentPosition(); + } + + private void SetHorizontalAlignmentPosition() + { + if (AutoSize) + { + foreach (var p in Paragraphs) + { + switch (p.HorizontalAlignment) + { + case eTextAlignment.Left: + p.Bounds.Left = 0; + break; + case eTextAlignment.Center: + p.Bounds.Left = Bounds.Width / 2 - p.Bounds.Width / 2; + break; + case eTextAlignment.Right: + p.Bounds.Left = Bounds.Right - p.Bounds.Width; + break; + case eTextAlignment.Distributed: + case eTextAlignment.Justified: + case eTextAlignment.JustifiedLow: + case eTextAlignment.ThaiDistributed: + p.Bounds.Left = 0; //TODO: Set left for now as we do not support distributed spacing yet + break; + } + } + } } internal virtual void ImportTextBody(ExcelTextBody body) @@ -133,7 +169,6 @@ internal virtual void ImportTextBody(ExcelTextBody body) Bounds.Height = paragraphStartY; } } - private double GetParagraphAscendantSpacingInPixels(eDrawingTextLineSpacing lineSpacingType, double spacingValue, ITextMeasurerWrap fmExact, out double multiplier) { if (lineSpacingType == eDrawingTextLineSpacing.Exactly) @@ -199,7 +234,7 @@ private double GetAlignmentVertical() alignmentY = 0; break; //Center means center of a Shape's ENTIRE bounding box height. - //Not center of the Inset Rectangle + //Not center of the Inset GetRectangle case eTextAnchoringType.Center: var globalHeight = (DrawingRenderer.Bounds.Height / 2) + Bounds.Top+2; var adjustedHeight = globalHeight - Bounds.Position.Y; //Global position. diff --git a/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/TextRunItem.cs b/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/TextRunItem.cs index 7e35a5ae2..0f8c8b872 100644 --- a/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/TextRunItem.cs +++ b/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/TextRunItem.cs @@ -127,6 +127,8 @@ internal TextRunItem(DrawingBase renderer, BoundingBox parent, ExcelParagraphTex FontSizeInPixels = ((double)_measurementFont.Size).PointToPixel(true); Bounds.Height = _measurementFont.Size; + _horizontalTextAlignment = run.Paragraph.HorizontalAlignment; + if (run.Fill.IsEmpty == false && run.Fill.Style == eFillStyle.SolidFill) { FillColor = "#" + run.Fill.Color.To6CharHexString(); @@ -145,72 +147,6 @@ internal TextRunItem(DrawingBase renderer, BoundingBox parent, ExcelParagraphTex _strikeType = run.FontStrike; } - //internal double CalculateLineSpacing() - //{ - // ////---Calculation of total y-height--- - - // ////If already did this - // if (YIncreasePerLine.Count > 0) - // { - // return Bounds.Height; - // } - - // //bool lineIsFirstInParagraph = _isFirstInParagraph; - - // //foreach (var line in Lines) - // //{ - // // //Despite new textrun it could still be on the same line as previous textrun - // // //Therefore only do line increase if we are first in paragraph or if we are not Lines[0]. - // // //This as line == Lines[0] && isFirstInParagraph == false means we are continuing on the same line as previous textRun - // // //This is important if for example we have rich text where two letters on the same line has different colors. - // // if (line != Lines[0] || lineIsFirstInParagraph) - // // { - // // var yIncrease = lineIsFirstInParagraph ? BaseLineSpacing : LineSpacingPerNewLine; - // // lineIsFirstInParagraph = false; - - // // //yIncrease = Fonts.OpenType.Utils.TextUtils.RoundToWhole(yIncrease); - - // // YIncreasePerLine.Add(yIncrease); - - // // _yEndPos += yIncrease; - // // //if (Double.IsNaN(ClippingHeight) == false && _yEndPos >= ClippingHeight) - // // //{ - // // // bool displayLine = false - // // //} - // // } - // // else - // // { - // // YIncreasePerLine.Add(0); - // // } - // //} - - // //for (int i = 0; i < YIncreasePerLine) - - // if (_yEndPos == 0) - // { - // Bounds.Height = FontSizeInPixels; - // } - // else - // { - // Bounds.Height = _yEndPos; - // } - - // return _yEndPos; - - //} - - internal List GetLines(string text) - { - //var inputWidth = _wrapText ? _maxWidthPixels : double.NaN; - //Lines = TextWrapper.GetLines(text, _measurer, inputWidth); - return text.Split(new string[] { Environment.NewLine }, StringSplitOptions.None).ToList(); - } - - private int GetNumberOfLines() - { - return Lines.Count(); - } - /// /// Calculates right/bottom /// @@ -222,111 +158,8 @@ internal override void GetBounds(out double il, out double it, out double ir, ou { il = Bounds.Left; it = Bounds.Top; - //ib = Bounds.Bottom; ir = Bounds.Right; ib = Bounds.Bottom; - ////Sets bounds bottom correctly - //CalculateLineSpacing(); - //ib = Bounds.Bottom; - - ////Caculate bounds right - //ir = CalculateRightPositionInPixels(); - //Bounds.Width = ir-Bounds.Left; } - - //internal double CalculateRightPositionInPixels() - //{ - // var numLines = GetNumberOfLines(); - // double retPos = Bounds.Left; - - // double textLengthPixels = 0; - - // if (numLines <= 0 || string.IsNullOrEmpty(_originalText)) - // { - // textLengthPixels = 0; - // return retPos; - // } - - // double longestWidth = -1; - - // for (int i = 0; i < numLines; i++) - // { - // var text = Lines[i]; - // var width = CalculateTextWidth(Lines[i]).PointToPixel(); - // PerLineWidth.Add(width); - - // if (width > longestWidth) - // { - // longestWidth = width; - // } - // } - - // textLengthPixels = longestWidth; - - // retPos += longestWidth; - - // //Calculates right - // Bounds.Width = textLengthPixels; - - // return Bounds.Right; - //} - - //internal double CalculateBottomPositionInPixels() - //{ - // return Bounds.Height; - //} - - //internal double CalculateTextWidth(string targetString) - //{ - // _measurer.SetFont(_measurementFont); - // _measurer.MeasureWrappedTextCells = true; - // var width = _measurer.MeasureTextWidth(targetString); - - // return width; - //} - - //internal void SetPerLineWidths(List widthsInPoints) - //{ - // var newList = new List(); - // for (int i = 0; i < widthsInPoints.Count; i++) - // { - // newList.Add(widthsInPoints[i].PointToPixel()); - // } - - // PerLineWidth.Clear(); - // PerLineWidth = newList; - //} - - //internal void AddLineSpacing(double lineSpacing) - //{ - // YIncreasePerLine.Add(lineSpacing); - // _yEndPos += lineSpacing; - // Bounds.Height = _yEndPos; - // //bool lineIsFirstInParagraph = _isFirstInParagraph; - - // //for(int i = 0; i< Lines.Count(); i++) - // //{ - // // if (Lines[i] != Lines[0] || lineIsFirstInParagraph) - // // { - // // var yIncrease = lineIsFirstInParagraph ? ascent : ascent + descent; - // // YIncreasePerLine.Add() - - // // lineIsFirstInParagraph = false; - // // } - // //} - - // //for (int i = 0; i < YIncreasePerLine) - - // // if (_yEndPos == 0) - // // { - // // Bounds.Height = FontSizeInPixels; - // // } - // // else - // // { - // // Bounds.Height = _yEndPos; - // // } - - // //return _yEndPos; - //} } } diff --git a/src/EPPlus.Export.ImageRenderer/RenderItems/SvgGroupItem.cs b/src/EPPlus.Export.ImageRenderer/RenderItems/SvgGroupItem.cs index 779a91a15..ae92137b1 100644 --- a/src/EPPlus.Export.ImageRenderer/RenderItems/SvgGroupItem.cs +++ b/src/EPPlus.Export.ImageRenderer/RenderItems/SvgGroupItem.cs @@ -10,6 +10,7 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ +using EPPlus.Fonts.OpenType.Utils; using EPPlus.Graphics; using EPPlusImageRenderer.Svg; using OfficeOpenXml.Drawing.Theme; @@ -43,7 +44,7 @@ internal SvgGroupItem(DrawingBase renderer, BoundingBox bounds) : base(renderer) Bounds = bounds; if (Bounds.Left != 0 || Bounds.Top != 0) { - GroupTransform = $"transform=\"translate({Bounds.Left.ToString(CultureInfo.InvariantCulture)}, {Bounds.Top.ToString(CultureInfo.InvariantCulture)})\""; + GroupTransform = $"transform=\"translate({Bounds.Left.PointToPixelString()}, {Bounds.Top.PointToPixelString()})\""; } } internal SvgGroupItem(DrawingBase renderer, BoundingBox parent, double rotation) : base(renderer, parent) @@ -53,7 +54,7 @@ internal SvgGroupItem(DrawingBase renderer, BoundingBox parent, double rotation) Bounds = parent; if (Bounds.Left!=0 || Bounds.Top!=0) { - bounds = $"translate({Bounds.Left.ToString(CultureInfo.InvariantCulture)}, {Bounds.Top.ToString(CultureInfo.InvariantCulture)})"; + bounds = $"translate({Bounds.Left.PointToPixelString()}, {Bounds.Top.PointToPixelString()})"; } if(rotation!=0) { @@ -95,11 +96,6 @@ public override void Render(StringBuilder sb) } } - //internal override SvgRenderItem Clone(SvgShape svgDocument) - //{ - // return this.Clone(svgDocument); - //} - internal override void GetBounds(out double il, out double it, out double ir, out double ib) { il = 0; diff --git a/src/EPPlus.Export.ImageRenderer/RenderItems/SvgItem/SvgParagraphItem.cs b/src/EPPlus.Export.ImageRenderer/RenderItems/SvgItem/SvgParagraphItem.cs index 2c80a87ab..8f4d3e9cd 100644 --- a/src/EPPlus.Export.ImageRenderer/RenderItems/SvgItem/SvgParagraphItem.cs +++ b/src/EPPlus.Export.ImageRenderer/RenderItems/SvgItem/SvgParagraphItem.cs @@ -51,7 +51,7 @@ public override void Render(StringBuilder sb) { var fontSize = _paragraphFont.Size.PointToPixel().ToString(CultureInfo.InvariantCulture); - sb.AppendLine($""); + sb.AppendLine($""); sb.AppendLine("paragraph "); diff --git a/src/EPPlus.Export.ImageRenderer/RenderItems/SvgItem/SvgTextBodyItem.cs b/src/EPPlus.Export.ImageRenderer/RenderItems/SvgItem/SvgTextBodyItem.cs index e6ae234a8..5310727cb 100644 --- a/src/EPPlus.Export.ImageRenderer/RenderItems/SvgItem/SvgTextBodyItem.cs +++ b/src/EPPlus.Export.ImageRenderer/RenderItems/SvgItem/SvgTextBodyItem.cs @@ -17,15 +17,13 @@ namespace EPPlus.Export.ImageRenderer.RenderItems.SvgItem { internal class SvgTextBodyItem : TextBodyItem { - public SvgTextBodyItem(DrawingBase renderer, BoundingBox parent, bool clampedToParent = false) : base(renderer, parent) + public SvgTextBodyItem(DrawingBase renderer, BoundingBox parent, bool autoSize, bool clampedToParent = false) : base(renderer, parent, autoSize) { - Bounds.ClampedToParent = clampedToParent; + //Bounds.ClampedToParent = clampedToParent; MaxWidth = parent.Width; MaxHeight = parent.Height; - //Bounds.Width = MaxWidth; - //Bounds.Height = MaxHeight; } - public SvgTextBodyItem(DrawingBase renderer, BoundingBox parent, double left, double top, double maxWidth, double maxHeight, bool clampedToParent = false) : base(renderer, parent) + public SvgTextBodyItem(DrawingBase renderer, BoundingBox parent, double left, double top, double maxWidth, double maxHeight, bool clampedToParent = false) : base(renderer, parent, false) { Bounds.Left = left; Bounds.Top = top; @@ -33,7 +31,7 @@ public SvgTextBodyItem(DrawingBase renderer, BoundingBox parent, double left, do Bounds.Height = maxHeight; MaxWidth = maxWidth; MaxHeight = maxHeight; - Bounds.ClampedToParent = clampedToParent; + //Bounds.ClampedToParent = clampedToParent; } internal override List Paragraphs { get; set; } = new List(); diff --git a/src/EPPlus.Export.ImageRenderer/RenderItems/SvgItem/SvgTextBox.cs b/src/EPPlus.Export.ImageRenderer/RenderItems/SvgItem/SvgTextBox.cs new file mode 100644 index 000000000..af3cfda0c --- /dev/null +++ b/src/EPPlus.Export.ImageRenderer/RenderItems/SvgItem/SvgTextBox.cs @@ -0,0 +1,141 @@ +using EPPlus.Fonts.OpenType.Utils; +using EPPlus.Graphics; +using EPPlusImageRenderer; +using EPPlusImageRenderer.RenderItems; +using EPPlusImageRenderer.Svg; +using OfficeOpenXml.Drawing; +using OfficeOpenXml.FormulaParsing.Excel.Functions.Text; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Xml.Serialization; +using static System.Net.Mime.MediaTypeNames; + +namespace EPPlus.Export.ImageRenderer.RenderItems.SvgItem +{ + internal class SvgTextBox : DrawingObjectNoBounds + { + internal SvgTextBox(DrawingBase renderer, BoundingBox parent, double left, double top, double width, double height, double maxWidth = double.NaN, double maxHeight = double.NaN) : base(renderer) + { + Left = left; + Top = top; + + Init(renderer, parent, maxWidth, maxHeight); + } + + private void Init(DrawingBase renderer, BoundingBox parent, double maxWidth, double maxHeight) + { + Parent = parent; + _rectangle = new SvgRenderRectItem(DrawingRenderer, Parent); + TextBody = new SvgTextBodyItem(renderer, Rectangle.Bounds, true); + TextBody.MaxWidth = maxWidth; + TextBody.MaxHeight = maxHeight; + } + + internal SvgTextBox(DrawingBase renderer, BoundingBox parent, double maxWidth, double maxHeight) : base(renderer) + { + Init(renderer, parent, maxWidth, maxHeight); + } + + //Simplified input + internal SvgTextBox(DrawingBase renderer, BoundingBox parent, BoundingBox maxBounds) : this( + renderer, parent, maxBounds.Left, maxBounds.Top, maxBounds.Width, maxBounds.Height, maxBounds.Width, maxBounds.Height) + { + } + SvgRenderItem _rectangle=null; + public SvgRenderItem Rectangle + { + get + { + _rectangle.Bounds.Width = Width; + _rectangle.Bounds.Height = Height; + return _rectangle; + } + } + public SvgTextBodyItem TextBody {get;set;} + public double Left { get; set; } + public double Top { get; set; } + public double Width + { + get + { + return LeftMargin + (TextBody?.Width ?? 0D) + RightMargin; + } + } + public double Height + { + get + { + return TopMargin + (TextBody?.Height ?? 0d) + BottomMargin; + } + } + internal double LeftMargin + { + get; set; + } + + internal double TopMargin + { + get; set; + } + + internal double RightMargin + { + get; set; + } + + internal double BottomMargin + { + get; set; + } + internal BoundingBox Parent { get; private set; } + internal double Rotation + { + get + { + return Rectangle.Bounds.Rotation; + } + set + { + Rectangle.Bounds.Rotation = value; + } + } + + internal void ImportTextBody(ExcelTextBody body) + { + double l, r, t, b; + body.GetInsetsOrDefaults(out l, out t, out r, out b); + LeftMargin = l; + TopMargin = t; + RightMargin = r; + BottomMargin = b; + + TextBody.ImportTextBody(body); + } + + internal override void AppendRenderItems(List renderItems) + { + var rect = Rectangle; + SvgGroupItem groupItem; + if (Rotation == 0) + { + groupItem = new SvgGroupItem(DrawingRenderer, new BoundingBox(Left, Top, Width, Height)); + } + else + { + groupItem = new SvgGroupItem(DrawingRenderer, new BoundingBox(Left, Top, Width, Height), Rotation); + } + renderItems.Add(groupItem); + renderItems.Add(rect); + TextBody.Bounds.Left = LeftMargin; + TextBody.Bounds.Top = TopMargin; + TextBody.AppendRenderItems(renderItems); + renderItems.Add(new SvgEndGroupItem(DrawingRenderer, rect.Bounds)); + } + + internal void ImportParagraph(ExcelDrawingParagraph item, double startingY, string text = null) + { + TextBody.ImportParagraph(item, startingY, text); + } + } +} diff --git a/src/EPPlus.Export.ImageRenderer/RenderItems/SvgItem/SvgTextBoxItem.cs b/src/EPPlus.Export.ImageRenderer/RenderItems/SvgItem/SvgTextBoxItem.cs deleted file mode 100644 index fc4bf1982..000000000 --- a/src/EPPlus.Export.ImageRenderer/RenderItems/SvgItem/SvgTextBoxItem.cs +++ /dev/null @@ -1,83 +0,0 @@ -using EPPlus.Fonts.OpenType.Utils; -using EPPlus.Graphics; -using EPPlusImageRenderer; -using EPPlusImageRenderer.RenderItems; -using EPPlusImageRenderer.Svg; -using OfficeOpenXml.Drawing; -using OfficeOpenXml.FormulaParsing.Excel.Functions.Text; -using System.Collections.Generic; -using System.Drawing; -using System.Xml.Serialization; - -namespace EPPlus.Export.ImageRenderer.RenderItems.SvgItem -{ - internal class SvgTextBoxItem : DrawingObject - { - internal SvgTextBoxItem(DrawingBase renderer, BoundingBox parent, double left, double top, double width, double height, double maxWidth = double.NaN, double maxHeight = double.NaN) : base(renderer, parent) - { - Bounds.Left = left; - Bounds.Top = top; - Bounds.Width = width; - Bounds.Height = height; - Bounds.Name = "TextBox"; - - TextBody = new SvgTextBodyItem(renderer, Bounds, true); - TextBody.MaxWidth = maxWidth; - TextBody.MaxHeight = maxHeight; - //Bounds.Width = TextBody.Width; - //Bounds.Height = TextBody.Height; - - Rectangle = new SvgRenderRectItem(renderer, parent); - Rectangle.Bounds = Bounds; - } - internal SvgTextBoxItem(DrawingBase renderer, BoundingBox parent, double maxWidth, double maxHeight) : base(renderer, parent) - { - } - - //Simplified input - internal SvgTextBoxItem(DrawingBase renderer, BoundingBox parent, BoundingBox maxBounds) : this( - renderer, parent, maxBounds.Left, maxBounds.Top, maxBounds.Width, maxBounds.Height) - { - } - - public SvgRenderItem Rectangle { get; set; } - public SvgTextBodyItem TextBody {get;set;} - internal double LeftMargin { get { return TextBody.Bounds.Left; } set { TextBody.Bounds.Left = value; } } - - internal double TopMargin { get { return TextBody.Bounds.Top; } set { TextBody.Bounds.Top = value; } } - - internal double RightMargin { get { return Bounds.Width - TextBody.Bounds.Left - TextBody.Bounds.Width ; } set { TextBody.Bounds.Width = Bounds.Width - TextBody.Bounds.Left - value; } } - internal double BottomMargin { get { return Bounds.Height - TextBody.Bounds.Top - TextBody.Bounds.Height; } set { TextBody.Bounds.Height = Bounds.Height - TextBody.Bounds.Top - value; } } - internal void ImportTextBody(ExcelTextBody body) - { - double l, r, t, b; - body.GetInsetsOrDefaults(out l, out t, out r, out b); - LeftMargin = l.PointToPixel(); - TopMargin = t.PointToPixel(); - RightMargin = r.PointToPixel(); - BottomMargin = b.PointToPixel(); - - TextBody.ImportTextBody(body); - } - - internal override void AppendRenderItems(List renderItems) - { - SvgGroupItem groupItem; - if (Bounds.Rotation == 0) - { - groupItem = new SvgGroupItem(DrawingRenderer, Bounds); - } - else - { - groupItem = new SvgGroupItem(DrawingRenderer, Bounds, Bounds.Rotation); - } - renderItems.Add(groupItem); - //handled by group now - Rectangle.Bounds.Top = 0; - Rectangle.Bounds.Left = 0; - renderItems.Add(Rectangle); - TextBody.AppendRenderItems(renderItems); - renderItems.Add(new SvgEndGroupItem(DrawingRenderer, Bounds)); - } - } -} diff --git a/src/EPPlus.Export.ImageRenderer/RenderItems/SvgItem/SvgTextRunItem.cs b/src/EPPlus.Export.ImageRenderer/RenderItems/SvgItem/SvgTextRunItem.cs index cb23c717b..73934f497 100644 --- a/src/EPPlus.Export.ImageRenderer/RenderItems/SvgItem/SvgTextRunItem.cs +++ b/src/EPPlus.Export.ImageRenderer/RenderItems/SvgItem/SvgTextRunItem.cs @@ -85,21 +85,21 @@ string GetFontStyleAttributes() public override void Render(StringBuilder sb) { string finalString = ""; - var xString = $"x =\"{(Bounds.Left.PointToPixel()).ToString(CultureInfo.InvariantCulture)}\" "; + var xString = $"x =\"{(Bounds.Left.PointToPixelString())}\" "; var currentYEndPos = Bounds.Position.Y; // Global position Y finalString += $"= ClippingHeight) { - visibility = " display=\"none\""; + //visibility = " display=\"none\""; } finalString += visibility; finalString += $"{GetFontStyleAttributes()}"; @@ -121,56 +121,7 @@ public override void Render(StringBuilder sb) finalString += ">"; finalString += _currentText; finalString += ""; - //for (int i = 0; i < Lines.Count; i++) - //{ - // var line = Lines[i]; - - // finalString += $" 0 && YIncreasePerLine[i] != 0) - // { - // var yIncrease = Fonts.OpenType.Utils.TextUtils.RoundToWhole(YIncreasePerLine[i]); - - // currentYEndPos += yIncrease; - // if (double.IsNaN(ClippingHeight) == false && currentYEndPos >= ClippingHeight) - // { - // visibility = "display=\"none\""; - // } - - // var yIncreaseString = yIncrease.ToString(CultureInfo.InvariantCulture); - // var dyString = $"dy=\"{yIncreaseString}px\" "; - // finalString += dyString; - // finalString += "x=\"0\" "; - // } - // else - // { - // finalString += xString; - // } - - // finalString += $"{visibility} " + $"{GetFontStyleAttributes()} "; - - // if (_measurementFont != null) - // { - // finalString += $"font-family=\"{_measurementFont.FontFamily}," - // + $"{_measurementFont.FontFamily}_MSFontService,sans-serif\" " - // + $"font-size=\"{FontSizeInPixels.ToString(CultureInfo.InvariantCulture)}px\" "; - // } - - // sb.Append(finalString); - - // //Get color etc. - // //Renders up until this point - // SvgBaseRenderer.BaseRender(sb, this); - // //Since final string has been written in base.render erase it. - // finalString = ""; - - // finalString += ">"; - // finalString += line; - // finalString += ""; - //} + sb.Append(finalString); } } diff --git a/src/EPPlus.Export.ImageRenderer/RenderItems/SvgRenderEllipseItem.cs b/src/EPPlus.Export.ImageRenderer/RenderItems/SvgRenderEllipseItem.cs index b9f613971..9eb13a072 100644 --- a/src/EPPlus.Export.ImageRenderer/RenderItems/SvgRenderEllipseItem.cs +++ b/src/EPPlus.Export.ImageRenderer/RenderItems/SvgRenderEllipseItem.cs @@ -11,6 +11,7 @@ Date Author Change 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ using EPPlus.Export.ImageRenderer.Utils; +using EPPlus.Fonts.OpenType.Utils; using EPPlus.Graphics; using EPPlusImageRenderer.Svg; using OfficeOpenXml.Drawing; @@ -34,11 +35,11 @@ public SvgRenderEllipseItem(DrawingBase renderer, BoundingBox parent) : base(ren public override void Render(StringBuilder sb) { - sb.AppendFormat(""); } diff --git a/src/EPPlus.Export.ImageRenderer/ShapeDefinitions/ShapeDefinition.cs b/src/EPPlus.Export.ImageRenderer/ShapeDefinitions/ShapeDefinition.cs index a7f200384..d8d0a236d 100644 --- a/src/EPPlus.Export.ImageRenderer/ShapeDefinitions/ShapeDefinition.cs +++ b/src/EPPlus.Export.ImageRenderer/ShapeDefinitions/ShapeDefinition.cs @@ -10,7 +10,6 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ -using EPPlus.Export.ImageRenderer.Text; using OfficeOpenXml.Drawing; using OfficeOpenXml.FormulaParsing.Excel.Functions.MathFunctions; using System; diff --git a/src/EPPlus.Export.ImageRenderer/Svg/Chart/Axis/ValueAxisScaleCalculator.cs b/src/EPPlus.Export.ImageRenderer/Svg/Chart/Axis/ValueAxisScaleCalculator.cs index 8fd6f39f8..ead28dc09 100644 --- a/src/EPPlus.Export.ImageRenderer/Svg/Chart/Axis/ValueAxisScaleCalculator.cs +++ b/src/EPPlus.Export.ImageRenderer/Svg/Chart/Axis/ValueAxisScaleCalculator.cs @@ -33,8 +33,8 @@ private static AxisScale GetNumberScale(ref double dataMin, ref double dataMax, } } - var isAllPositive = dataMin > 0 && dataMax > 0; - var isAllNegativ = dataMin < 0 && dataMax < 0; + var isAllPositive = dataMin >= 0 && dataMax >= 0; + var isAllNegativ = dataMin <= 0 && dataMax <= 0; if(dataMin < 0 && dataMax > 0 && axisOptions.IsStacked100) { desiredTicks *= 2; diff --git a/src/EPPlus.Export.ImageRenderer/Svg/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs b/src/EPPlus.Export.ImageRenderer/Svg/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs index be422752e..d71446e78 100644 --- a/src/EPPlus.Export.ImageRenderer/Svg/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs +++ b/src/EPPlus.Export.ImageRenderer/Svg/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs @@ -37,7 +37,7 @@ internal LineChartTypeDrawer(SvgChart svgChart, ExcelChart chartType) : base(svg ExcelChartAxisStandard.CalculateStacked100(yValues); } - for(var i= 0;i < xValues.Count;i++) + for(var i= 0; i < xValues.Count; i++) { var xSerie = xValues[i]; var ySerie = yValues[i]; diff --git a/src/EPPlus.Export.ImageRenderer/Svg/Chart/SvgChart.cs b/src/EPPlus.Export.ImageRenderer/Svg/Chart/SvgChart.cs index 9e21f4197..83528fdce 100644 --- a/src/EPPlus.Export.ImageRenderer/Svg/Chart/SvgChart.cs +++ b/src/EPPlus.Export.ImageRenderer/Svg/Chart/SvgChart.cs @@ -11,6 +11,7 @@ Date Author Change 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ using EPPlus.Export.ImageRenderer.Svg.Chart; +using EPPlus.Fonts.OpenType.Utils; using EPPlusImageRenderer.RenderItems; using EPPlusImageRenderer.Utils; using OfficeOpenXml.Drawing.Chart; @@ -56,7 +57,7 @@ public SvgChart(ExcelChart chart) : base(chart) Plotarea = new SvgChartPlotarea(this); SetAxisPositionsFromPlotarea(this); - foreach(var ct in chart.PlotArea.ChartTypes) + foreach (var ct in chart.PlotArea.ChartTypes) { Plotarea.ChartTypeDrawers = ChartTypeDrawer.Create(this); } @@ -78,8 +79,18 @@ private void SetAxisPositionsFromPlotarea(SvgChart sc) if(VerticalAxis.Title!=null) { - VerticalAxis.Title.Rectangle.Top = Plotarea.Rectangle.Top + (Plotarea.Rectangle.Height / 2) - (VerticalAxis.Title.Rectangle.Height / 2); + VerticalAxis.Title.Rectangle.Height = Plotarea.Rectangle.Height; + VerticalAxis.Title.Rectangle.Width = sc.Bounds.Width / 4; VerticalAxis.Title.InitTextBox(); + VerticalAxis.Title.TextBox.Top = Plotarea.Rectangle.Top + (Plotarea.Rectangle.Height / 2) - (VerticalAxis.Title.TextBox.Height / 2); + if (VerticalAxis.Rectangle == null) + { + VerticalAxis.Title.TextBox.Left = sc.ChartArea.LeftMargin; + } + else + { + VerticalAxis.Title.TextBox.Left = VerticalAxis.Rectangle.Left - VerticalAxis.Title.TextBox.Width ; + } } VerticalAxis.AddTickmarksAndValues(); @@ -96,30 +107,41 @@ private void SetAxisPositionsFromPlotarea(SvgChart sc) if (HorizontalAxis.Title != null) { - HorizontalAxis.Title.Rectangle.Left = Plotarea.Rectangle.Left + (Plotarea.Rectangle.Width / 2) - (HorizontalAxis.Title.Rectangle.Width / 2); + HorizontalAxis.Title.Rectangle.Height = sc.Bounds.Height / 4; + HorizontalAxis.Title.Rectangle.Width = HorizontalAxis.Rectangle?.Width ?? sc.Plotarea.Rectangle.Width; HorizontalAxis.Title.InitTextBox(); + HorizontalAxis.Title.TextBox.Left = Plotarea.Rectangle.Left + (Plotarea.Rectangle.Width / 2) - (HorizontalAxis.Title.TextBox.Width / 2); + HorizontalAxis.Title.TextBox.Top = HorizontalAxis.Rectangle.Bottom; } HorizontalAxis.AddTickmarksAndValues(); } if (SecondVerticalAxis!=null) { - SecondVerticalAxis.Rectangle.Top = Plotarea.Rectangle.Top; - SecondVerticalAxis.Rectangle.Height = Plotarea.Rectangle.Height; - SecondVerticalAxis.Rectangle.Left = Plotarea.Rectangle.Right; - VerticalAxis.Line.X1 = VerticalAxis.Line.X2 = (float)Plotarea.Rectangle.Right; - SecondVerticalAxis.Line.Y1 = (float)SecondVerticalAxis.Rectangle.Top; - SecondVerticalAxis.Line.Y2 = (float)SecondVerticalAxis.Rectangle.Bottom; + if (SecondVerticalAxis.Rectangle != null) + { + SecondVerticalAxis.Rectangle.Top = Plotarea.Rectangle.Top; + SecondVerticalAxis.Rectangle.Height = Plotarea.Rectangle.Height; + SecondVerticalAxis.Rectangle.Left = Plotarea.Rectangle.Left - SecondVerticalAxis.Rectangle.Width; + SecondVerticalAxis.Line.X1 = SecondVerticalAxis.Line.X2 = (float)Plotarea.Rectangle.Left; + SecondVerticalAxis.Line.Y1 = (float)SecondVerticalAxis.Rectangle.Top; + SecondVerticalAxis.Line.Y2 = (float)SecondVerticalAxis.Rectangle.Bottom; + } + if (SecondVerticalAxis.Title != null) { - SecondVerticalAxis.Title.Rectangle.Top = Plotarea.Rectangle.Top + (Plotarea.Rectangle.Height / 2) - (SecondVerticalAxis.Title.Rectangle.Height / 2); + SecondVerticalAxis.Title.Rectangle.Height = SecondVerticalAxis.Rectangle.Height; + SecondVerticalAxis.Title.Rectangle.Width = sc.Bounds.Width / 4; SecondVerticalAxis.Title.InitTextBox(); + SecondVerticalAxis.Title.TextBox.Top = Plotarea.Rectangle.Top + (Plotarea.Rectangle.Height / 2) - (SecondVerticalAxis.Title.TextBox.Height / 2); + SecondVerticalAxis.Title.TextBox.Left = SecondVerticalAxis.Title.Rectangle.Left; } + SecondVerticalAxis.AddTickmarksAndValues(); } } - internal SvgRenderRectItem ChartArea { get; set; } + internal SvgChartObject ChartArea { get; set; } internal SvgChartLegend Legend { get; set; } internal SvgChartTitle Title { get; set; } internal SvgChartPlotarea Plotarea { get; set; } @@ -132,12 +154,12 @@ private void SetAxisPositionsFromPlotarea(SvgChart sc) private void SetChartArea() { - var item = new SvgRenderRectItem(this, Bounds); - item.Width = Bounds.Width; - item.Height = Bounds.Height; - item.SetDrawingPropertiesFill(Chart.Fill, Chart.StyleManager.Style.ChartArea.FillReference.Color); - item.SetDrawingPropertiesBorder(Chart.Border, Chart.StyleManager.Style.ChartArea.BorderReference.Color, Chart.Border.Width > 0); - RenderItems.Add(item); + var item = new SvgChartArea(this); + item.Rectangle.Width = Bounds.Width; + item.Rectangle.Height = Bounds.Height; + item.Rectangle.SetDrawingPropertiesFill(Chart.Fill, Chart.StyleManager.Style.ChartArea.FillReference.Color); + item.Rectangle.SetDrawingPropertiesBorder(Chart.Border, Chart.StyleManager.Style.ChartArea.BorderReference.Color, Chart.Border.Width > 0); + item.AppendRenderItems(RenderItems); ChartArea = item; } internal List DefItems { get; } = new List(); @@ -153,15 +175,17 @@ public void Render(StringBuilder sb) VerticalAxis?.AppendRenderItems(RenderItems); SecondVerticalAxis?.AppendRenderItems(RenderItems); - foreach (var drawer in Plotarea?.ChartTypeDrawers) + if (Plotarea != null) { - drawer.AppendRenderItems(RenderItems); + foreach (var drawer in Plotarea?.ChartTypeDrawers) + { + drawer.AppendRenderItems(RenderItems); + } } - Legend?.AppendRenderItems(RenderItems); Title?.AppendRenderItems(RenderItems); - sb.Append($""); + sb.Append($""); //Write defs used for gradient colors var writer = new SvgDrawingWriter(this); writer.WriteSvgDefs(sb, RenderItems); diff --git a/src/EPPlus.Export.ImageRenderer/Svg/Chart/SvgChartAxis.cs b/src/EPPlus.Export.ImageRenderer/Svg/Chart/SvgChartAxis.cs index 501d9db3a..cedc87b09 100644 --- a/src/EPPlus.Export.ImageRenderer/Svg/Chart/SvgChartAxis.cs +++ b/src/EPPlus.Export.ImageRenderer/Svg/Chart/SvgChartAxis.cs @@ -18,7 +18,6 @@ Date Author Change using EPPlus.Fonts.OpenType.Utils; using EPPlus.Graphics; using EPPlusImageRenderer.RenderItems; -using EPPlusImageRenderer.Text; using OfficeOpenXml.Drawing; using OfficeOpenXml.Drawing.Chart; using OfficeOpenXml.Drawing.Chart.Style; @@ -88,7 +87,7 @@ internal SvgChartAxis(SvgChart sc, ExcelChartAxisStandard ax) : base(sc) else { Rectangle.Width = GetTextWidest(sc, ax) + RightMargin; - var lp = sc.ChartArea.Width - Rectangle.Width - 8D; + var lp = sc.ChartArea.Rectangle.Width - Rectangle.Width - 8D; if (sc.Chart.Legend.Position == eLegendPosition.Right) { lp = sc.Legend.Rectangle.Left + -Rectangle.Width; @@ -99,7 +98,7 @@ internal SvgChartAxis(SvgChart sc, ExcelChartAxisStandard ax) : base(sc) else { Rectangle.Height = GetTextHeight(sc, ax); - Rectangle.Top = Title == null || ax.AxisPosition == eAxisPosition.Top ? sc.ChartArea.Height - 8 - Rectangle.Height : Title.Rectangle.Top - Rectangle.Height - 8; + Rectangle.Top = Title == null || ax.AxisPosition == eAxisPosition.Top ? sc.ChartArea.Rectangle.Height - 8 - Rectangle.Height : Title.Rectangle.Top - Rectangle.Height - 8; } } @@ -182,7 +181,7 @@ public List Values public List MinorAxisPositions { get; private set; } public List MajorGridlinePositions { get; private set; } public List MinorGridlinePositions { get; private set; } - public List AxisValuesTextBoxes + public List AxisValuesTextBoxes { get; private set; @@ -278,16 +277,16 @@ internal void AddTickmarksAndValues() } } - private List GetAxisValueTextBoxes() + private List GetAxisValueTextBoxes() { var tm = Chart.WorkSheet._package.Settings.TextSettings.GenericTextMeasurerTrueType; var mf = Axis.Font.GetMeasureFont(); var axisStyle = GetAxisStyleEntry(); - var ret= new List(); + var ret= new List(); double maxWidth, maxHeight; if(Axis.AxisPosition==eAxisPosition.Left || Axis.AxisPosition == eAxisPosition.Right) { - maxWidth = SvgChart.ChartArea.Bounds.Width / 3; //TODO: Check this value. + maxWidth = SvgChart.ChartArea.Rectangle.Width / 3; //TODO: Check this value. maxHeight = Rectangle.Height / AxisValues.Count; } else @@ -307,7 +306,7 @@ private List GetAxisValueTextBoxes() //bounds.Top = y; var width = m.Width.PointToPixel(); var height = m.Height.PointToPixel(); - var tb = new SvgTextBoxItem(SvgChart, Rectangle.Bounds, x, y, width, height, maxWidth, maxHeight); + var tb = new SvgTextBox(SvgChart, Rectangle.Bounds, x, y, width, height, maxWidth, maxHeight); var p = Axis.TextBody.Paragraphs.FirstOrDefault(); tb.TextBody.ImportParagraph(p, 0, v); @@ -587,6 +586,15 @@ protected List GetAxisValue(ExcelChartAxisStandard ax, RenderItem rect, }; var length = ax.AxisPosition == eAxisPosition.Left || ax.AxisPosition == eAxisPosition.Right ? SvgChart.Bounds.Height : SvgChart.Bounds.Width; //Fix and use plotarea width/height. + if(isCount) + { + majorUnit = 1; + for(int i=1;i<=max;i++) + { + l.Add(i); + } + return l; + } if (ax.IsDate) { var res = DateAxisScaleCalculator.Calculate(min ?? 0, max ?? 0, length, options); @@ -628,123 +636,5 @@ protected List GetAxisValue(ExcelChartAxisStandard ax, RenderItem rect, return l; } - - private void GetAutoMinMaxValue(ExcelChartAxisStandard ax, int maxMajorTickmarks, bool isCount, ref double? min, ref double? max, out double? majorUnit) - { - if (ax.MinValue.HasValue) - { - min = ax.MinValue; - } - else - { - if (isCount) - { - min = 1; - } - else - { - var diffFromZero = (max - min) / max; - if (diffFromZero > 0.091 && min > 0D) - { - min = 0; - } - } - } - - if (isCount) - { - majorUnit = 1; - } - else - { - if (ax.MaxValue.HasValue) - { - max = ax.MaxValue; - majorUnit = ax.MajorUnit ?? GetAutoUnit(min.Value, max.Value); - if (ax.MinValue.HasValue == false) - { - var newMin = max - majorUnit; - while (newMin > min) - { - newMin -= majorUnit.Value; - } - min = newMin; - } - } - else - { - majorUnit = ax.MajorUnit ?? GetAutoUnit(min.Value, max.Value); - if (isCount == false) - { - var diff = max.Value - min.Value; - var newMax = min.Value + majorUnit; - while ((newMax - min) < (diff * 1.05)) - { - newMax += majorUnit.Value; - } - max = newMax; - } - if (min != 0 && max - min < 9) - { - min -= 2; - } - } - var newUnit = majorUnit; - while (newUnit >= 2 && (max - min) / newUnit > maxMajorTickmarks) - { - newUnit /= 2; - } - } - } - - private double GetAutoUnit(double min, double max) - { - //if (diff < 8) - //{ - // return 1; - //} - //else - //{ - if (min < 0) - { - var diff = max - min; - return 0; - } - else - { - var diff = max - min; - var rawMajorUnit = diff; - var exponent = Math.Floor(Math.Log10(rawMajorUnit)); - var fraction = rawMajorUnit / (Math.Pow(10, exponent)); - double unit; - if (fraction <= 1) - { - unit = 1D; - } - else if (fraction <= 2) - { - unit = 2; - } - else if (fraction <= 2.5) - { - unit = 2.5; - } - else if (fraction <= 5) - { - unit = 5; - } - else - { - unit = 10; - } - - var axMax = unit * Math.Pow(10, exponent); - var axMin = Math.Floor(min / axMax) * axMax; - axMax = Math.Ceiling(max / axMax) * axMax; - return axMax / 10; - } - //} - } - } } \ No newline at end of file diff --git a/src/EPPlus.Export.ImageRenderer/Svg/Chart/SvgChartLegend.cs b/src/EPPlus.Export.ImageRenderer/Svg/Chart/SvgChartLegend.cs index 71565d3e5..6fcb1342b 100644 --- a/src/EPPlus.Export.ImageRenderer/Svg/Chart/SvgChartLegend.cs +++ b/src/EPPlus.Export.ImageRenderer/Svg/Chart/SvgChartLegend.cs @@ -14,7 +14,6 @@ Date Author Change using EPPlus.Export.ImageRenderer.RenderItems.SvgItem; using EPPlus.Export.ImageRenderer.Svg; using EPPlusImageRenderer.RenderItems; -using EPPlusImageRenderer.Text; using OfficeOpenXml; using OfficeOpenXml.ConditionalFormatting; using OfficeOpenXml.Drawing; @@ -46,8 +45,8 @@ internal SvgChartLegend(SvgChart sc) : base(sc) } var l = ((ExcelChartStandard)sc.Chart).Legend; - LeftMargin = RightMargin = 4; - TopMargin = BottomMargin = 4; + LeftMargin = RightMargin = 3; //4px + TopMargin = BottomMargin = 3; //4px if (l.Layout.HasLayout) { @@ -57,6 +56,7 @@ internal SvgChartLegend(SvgChart sc) : base(sc) { Rectangle = GetLegendRectangle(sc, l); } + Bounds.Left = Rectangle.Left; Bounds.Top = Rectangle.Top; Bounds.Width = Rectangle.Width; @@ -127,14 +127,14 @@ private SvgRenderRectItem GetLegendRectangle(SvgChart sc, ExcelChartLegend l) case eLegendPosition.Bottom: rect.Width = textWidth + LeftMargin + RightMargin + ((LineLength + MarginExtra) * index + (MiddleMargin*Math.Max(index-1,0))) ; // 28 is for the line length + 2px between line and text rect.Height = TopMargin + BottomMargin + highest + MarginExtra; - rect.Left = (sc.ChartArea.Width - rect.Width) / 2; + rect.Left = (sc.ChartArea.Rectangle.Width - rect.Width) / 2; if (l.Position == eLegendPosition.Top) { rect.Top = sc.Title.Rectangle.Top+ sc.Title.Rectangle.Height + MiddleMargin; } else { - rect.Top = sc.ChartArea.Height - rect.Height - BottomMargin; + rect.Top = sc.ChartArea.Rectangle.Height - rect.Height - BottomMargin; } break; case eLegendPosition.Right: @@ -145,7 +145,7 @@ private SvgRenderRectItem GetLegendRectangle(SvgChart sc, ExcelChartLegend l) if (l.Position == eLegendPosition.Right || l.Position == eLegendPosition.TopRight) { - rect.Left = sc.ChartArea.Width - rect.Width - TopMargin; + rect.Left = sc.ChartArea.Rectangle.Width - rect.Width - TopMargin; } else { @@ -154,7 +154,7 @@ private SvgRenderRectItem GetLegendRectangle(SvgChart sc, ExcelChartLegend l) if (l.Position == eLegendPosition.Left || l.Position == eLegendPosition.Right) { - rect.Top = sc.ChartArea.Height / 2 + TopMargin + 2; + rect.Top = sc.ChartArea.Rectangle.Height / 2 + TopMargin + 2; } else { @@ -172,7 +172,7 @@ private SvgRenderRectItem GetLegendRectangle(SvgChart sc, ExcelChartLegend l) if (isVertical) { - //var top = sc.Title.Rectangle.Height+8+10; + //var top = sc.Title.GetRectangle.Height+8+10; //var width = margin; } return rect; diff --git a/src/EPPlus.Export.ImageRenderer/Svg/Chart/SvgChartObject.cs b/src/EPPlus.Export.ImageRenderer/Svg/Chart/SvgChartObject.cs index bd4e57fac..52aab8346 100644 --- a/src/EPPlus.Export.ImageRenderer/Svg/Chart/SvgChartObject.cs +++ b/src/EPPlus.Export.ImageRenderer/Svg/Chart/SvgChartObject.cs @@ -23,8 +23,12 @@ namespace EPPlusImageRenderer.Svg internal abstract class DrawingObject { internal protected DrawingBase DrawingRenderer { get; } - internal BoundingBox Bounds { get; set; } + internal virtual BoundingBox Bounds { get; set; } + protected DrawingObject(DrawingBase renderer) + { + DrawingRenderer = renderer; + } protected DrawingObject(DrawingBase renderer, BoundingBox parent) { DrawingRenderer = renderer; @@ -32,6 +36,28 @@ protected DrawingObject(DrawingBase renderer, BoundingBox parent) } internal abstract void AppendRenderItems(List renderItems); } + internal abstract class DrawingObjectNoBounds + { + internal protected DrawingBase DrawingRenderer { get; } + + protected DrawingObjectNoBounds(DrawingBase renderer) + { + DrawingRenderer = renderer; + } + internal abstract void AppendRenderItems(List renderItems); + } + internal class SvgChartArea : SvgChartObject + { + public SvgChartArea(SvgChart sc) : base(sc) + { + Rectangle = new SvgRenderRectItem(sc, sc.Bounds); + } + + internal override void AppendRenderItems(List renderItems) + { + renderItems.Add(Rectangle); + } + } internal abstract class SvgChartObject : DrawingObject { internal DrawingChart ChartRenderer; @@ -43,10 +69,10 @@ internal SvgChartObject(DrawingChart chart) : base(chart, chart.Bounds) internal void SetMargins(ExcelTextBody tb) { tb.GetInsetsOrDefaults(out double l, out double r, out double t, out double b); - LeftMargin = l.PointToPixel(); - RightMargin = r.PointToPixel(); - TopMargin = t.PointToPixel(); - BottomMargin = b.PointToPixel(); + LeftMargin = l; + RightMargin = r; + TopMargin = t; + BottomMargin = b; } internal double LeftMargin { get; set; } internal double RightMargin { get; set; } diff --git a/src/EPPlus.Export.ImageRenderer/Svg/Chart/SvgChartPlotarea.cs b/src/EPPlus.Export.ImageRenderer/Svg/Chart/SvgChartPlotarea.cs index 295f00ce2..c70a230ac 100644 --- a/src/EPPlus.Export.ImageRenderer/Svg/Chart/SvgChartPlotarea.cs +++ b/src/EPPlus.Export.ImageRenderer/Svg/Chart/SvgChartPlotarea.cs @@ -30,7 +30,7 @@ public SvgChartPlotarea(SvgChart sc) : base(sc) internal SvgRenderRectItem GetPlotAreaRectangle(SvgChart sc) { var pa = sc.Chart.PlotArea; - TopMargin = BottomMargin = LeftMargin = RightMargin = 14; + TopMargin = BottomMargin = LeftMargin = RightMargin = 10.5; //14px var rect = new SvgRenderRectItem(sc, sc.Bounds); if (pa.Layout.HasLayout) { @@ -52,7 +52,7 @@ internal SvgRenderRectItem GetPlotAreaRectangle(SvgChart sc) rect.Width = (lp == eLegendPosition.Right || lp == eLegendPosition.TopRight ? sc.Legend.Bounds.GlobalLeft - RightMargin : - sc.ChartArea.Width - RightMargin) + sc.ChartArea.Rectangle.Width - RightMargin) - rect.GlobalLeft; double vaHeight=0, vaTitleHeight=0; diff --git a/src/EPPlus.Export.ImageRenderer/Svg/Chart/SvgChartTitle.cs b/src/EPPlus.Export.ImageRenderer/Svg/Chart/SvgChartTitle.cs index 1f1308d00..8717399d6 100644 --- a/src/EPPlus.Export.ImageRenderer/Svg/Chart/SvgChartTitle.cs +++ b/src/EPPlus.Export.ImageRenderer/Svg/Chart/SvgChartTitle.cs @@ -12,8 +12,9 @@ Date Author Change *************************************************************************************************/ using EPPlus.Export.ImageRenderer.RenderItems.Shared; using EPPlus.Export.ImageRenderer.RenderItems.SvgItem; +using EPPlus.Fonts.OpenType.Utils; +using EPPlus.Graphics; using EPPlusImageRenderer.RenderItems; -using EPPlusImageRenderer.Text; using OfficeOpenXml; using OfficeOpenXml.Drawing; using OfficeOpenXml.Drawing.Chart; @@ -42,9 +43,10 @@ internal class SvgChartTitle : SvgChartObject internal SvgChartTitle(SvgChart sc, ExcelChartTitleStandard t, string defaultText, SvgChartAxis axis=null) : base(sc) { _svgChart = sc; + //These are hard coded margins for the title box. - LeftMargin = RightMargin = 4; - TopMargin = BottomMargin = 2; + LeftMargin = RightMargin = 3; //4px + TopMargin = BottomMargin = 1.5; //2px var maxWidth = sc.Bounds.Width * 0.8; var maxHeight = sc.Bounds.Height / 2D; @@ -64,36 +66,40 @@ internal SvgChartTitle(SvgChart sc, ExcelChartTitleStandard t, string defaultTex _titleText = defaultText; } } - - var rect = t.TextBody.Paragraphs.GetSizeInPixels(maxWidth, maxHeight, _titleText, t.Font, LeftMargin, TopMargin, t.Rotation); - - if (t.Layout.HasLayout) + + if (t.Layout.HasLayout) //Only for the main chart title, axis titles don't support manual layout in Excel. { Rectangle = GetRectFromManualLayout(sc, t.Layout); - if (double.IsNaN(Rectangle.Width)) - { - Rectangle.Width = (float)(rect.Width); - } - if (double.IsNaN(Rectangle.Height)) - { - Rectangle.Height = (float)(rect.Height); - } + var top = Rectangle.Top; + var left = Rectangle.Left; + Rectangle.Width = (float)maxWidth; + Rectangle.Height = (float)maxHeight; + InitTextBox(); + TextBox.Top = top; + TextBox.Left = left; } else { Rectangle = new SvgRenderRectItem(sc, sc.Bounds); if (axis==null) - { - Rectangle.Top = (float)8; //8 pixels for the chart title standard offset - Rectangle.Left = (float)(sc.Bounds.Width - rect.Width) / 2; - Rectangle.Height = (float)rect.Height; - Rectangle.Width = (float)rect.Width; + { + Rectangle.Width = (float)maxWidth; + Rectangle.Height = (float)maxHeight; + InitTextBox(); + TextBox.Top = (float)6; //6 point for the chart title standard offset. + TextBox.Left = (float)(sc.Bounds.Width - TextBox.Width) / 2; } else { - SetAxisTitleRect(sc, axis, rect); + var isVertical = axis.Axis.IsVertical; + Rectangle.Width = sc.Bounds.Width * (isVertical ? 0.2 : 0.8); //Max Width. + Rectangle.Height = sc.Bounds.Height * (isVertical ? 0.8 : 0.2); //Max Height. + + InitTextBox(); + Rectangle = (SvgRenderRectItem)TextBox.Rectangle; + SetAxisTitleRect(sc, axis); } } @@ -101,7 +107,7 @@ internal SvgChartTitle(SvgChart sc, ExcelChartTitleStandard t, string defaultTex Rectangle.SetDrawingPropertiesBorder(t.Border, sc.Chart.StyleManager.Style.Title.BorderReference.Color, t.Border.Fill.Style != eFillStyle.NoFill, 0.75); } - private void SetAxisTitleRect(SvgChart sc, SvgChartAxis axis, RectBase rect) + private void SetAxisTitleRect(SvgChart sc, SvgChartAxis axis) { var margin = 8F; switch (axis.Axis.AxisPosition) @@ -111,12 +117,10 @@ private void SetAxisTitleRect(SvgChart sc, SvgChartAxis axis, RectBase rect) Rectangle.Left = sc.Chart.HasLegend && sc.Chart.Legend.Position == eLegendPosition.Left ? sc.Legend.Rectangle.Right : margin; break; case eAxisPosition.Bottom: - Rectangle.Top = sc.ChartArea.Height - margin - rect.Height; + Rectangle.Top = sc.ChartArea.Rectangle.Height - margin - Rectangle.Height; Rectangle.Left = GetHorizontalLeft(sc); break; } - Rectangle.Width = rect.Width; - Rectangle.Height = rect.Height; } private double GetHorizontalLeft(SvgChart sc) @@ -171,40 +175,36 @@ private static string GetDefaultChartTitleText(SvgChart sc, ExcelChartTitleStand internal void InitTextBox() { - TextBox = new SvgTextBoxItem(_svgChart, _svgChart.ChartArea.Bounds, Rectangle.Left, Rectangle.Top, Rectangle.Width, Rectangle.Height, Rectangle.Width, Rectangle.Height); - TextBox.LeftMargin = LeftMargin; - TextBox.RightMargin = RightMargin; - TextBox.TopMargin = TopMargin; - TextBox.BottomMargin = BottomMargin; - TextBox.TextBody.VerticalAlignment = eTextAnchoringType.Top; + TextBox = new SvgTextBox(_svgChart, _svgChart.ChartArea.Bounds, Rectangle.Width, Rectangle.Height); if(_title.Rotation != 0) { - TextBox.Bounds.Rotation = _title.Rotation; + TextBox.Rotation = _title.Rotation; } if (_title.TextBody.Paragraphs.Count > 0) { - //foreach (var p in _title.TextBody.Paragraphs) - //{ - // TextBox.TextBody.ImportParagraph(p, 0); - //} - TextBox.TextBody.ImportTextBody(_title.TextBody); + TextBox.ImportTextBody(_title.TextBody); } else { - //TextBox.AddText(string.IsNullOrEmpty(_title.Text) ? _titleText : _title.Text, _title.Font); var text = string.IsNullOrEmpty(_title.Text) ? _titleText : _title.Text; var p = _title.DefaultTextBody.Paragraphs.FirstOrDefault(); - TextBox.TextBody.ImportParagraph(p, 0, text); + TextBox.ImportParagraph(p, 0, text); } + + TextBox.LeftMargin = LeftMargin; + TextBox.RightMargin = RightMargin; + TextBox.TopMargin = TopMargin; + TextBox.BottomMargin = BottomMargin; + TextBox.TextBody.VerticalAlignment = eTextAnchoringType.Top; + Rectangle = (SvgRenderRectItem)TextBox.Rectangle; } - public SvgTextBoxItem TextBox + public SvgTextBox TextBox { get; private set; } internal override void AppendRenderItems(List renderItems) { - TextBox.AppendRenderItems(renderItems); TextBox.Rectangle.SetDrawingPropertiesFill(_title.Fill, _svgChart.Chart.StyleManager.Style.Title.FillReference.Color); TextBox.Rectangle.SetDrawingPropertiesBorder(_title.Border, _svgChart.Chart.StyleManager.Style.Title.BorderReference.Color, _title.Border.Fill.Style != eFillStyle.NoFill, 0.75); diff --git a/src/EPPlus.Export.ImageRenderer/Svg/LineMarkerHelper.cs b/src/EPPlus.Export.ImageRenderer/Svg/LineMarkerHelper.cs index 1c4738169..4542fdf01 100644 --- a/src/EPPlus.Export.ImageRenderer/Svg/LineMarkerHelper.cs +++ b/src/EPPlus.Export.ImageRenderer/Svg/LineMarkerHelper.cs @@ -18,10 +18,10 @@ internal static RenderItem GetMarkerItem(SvgChart sc, ExcelLineChartSerie ls, do float maxSize = isLegend ? 7f : float.MaxValue; var size = m.Size > maxSize ? maxSize : m.Size; var halfSize = size / 2; - var xPath = x / sc.ChartArea.Width; - var yPath = y / sc.ChartArea.Height; - var halfY = halfSize / sc.ChartArea.Height; - var halfX = halfSize / sc.ChartArea.Width; + var xPath = x / sc.ChartArea.Rectangle.Width; + var yPath = y / sc.ChartArea.Rectangle.Height; + var halfY = halfSize / sc.ChartArea.Rectangle.Height; + var halfX = halfSize / sc.ChartArea.Rectangle.Width; switch (m.Style) { case eMarkerStyle.Circle: diff --git a/src/EPPlus.Export.ImageRenderer/Svg/SvgParagraph.cs b/src/EPPlus.Export.ImageRenderer/Svg/SvgParagraph.cs deleted file mode 100644 index d75c96e9e..000000000 --- a/src/EPPlus.Export.ImageRenderer/Svg/SvgParagraph.cs +++ /dev/null @@ -1,403 +0,0 @@ -/************************************************************************************************* - Required Notice: Copyright (C) EPPlus Software AB. - This software is licensed under PolyForm Noncommercial License 1.0.0 - and may only be used for noncommercial purposes - https://polyformproject.org/licenses/noncommercial/1.0.0/ - - A commercial license to use this software can be purchased at https://epplussoftware.com - ************************************************************************************************* - Date Author Change - ************************************************************************************************* - 27/11/2025 EPPlus Software AB EPPlus 9 - *************************************************************************************************/ -using EPPlus.Fonts.OpenType; -using EPPlus.Fonts.OpenType.Utils; -using EPPlusImageRenderer.RenderItems; -using OfficeOpenXml.Drawing; -using OfficeOpenXml.Interfaces.Drawing.Text; -using OfficeOpenXml.Style; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Xml.Schema; - -namespace EPPlusImageRenderer.Svg -{ //internal class SvgParagraph : SvgRenderItem - //{ - // int numLines = 0; - - // public override void Render(StringBuilder sb) - // { - // sb.Append(""); - - // if (TextRuns.Count > 0) - // { - // foreach (var textRun in TextRuns) - // { - // textRun.Render(sb); - // } - // } - - // sb.Append(""); - // } - - // public override RenderItemType Type => RenderItemType.Text; - - // internal override SvgRenderItem Clone(SvgShape svgDocument) - // { - // throw new NotImplementedException(); - // } - - // internal override void GetBounds(out double il, out double it, out double ir, out double ib) - // { - // il = ParagraphArea.Left + LeftMargin; - // it = ParagraphArea.Top; - // ir = ParagraphArea.Right - RightMargin; - // ib = ParagraphArea.Bottom; - // } - - // string vertAlignAttribute = ""; - // protected List TextRuns = new List(); - - // //ExcelDrawingParagraph Paragraph; - // double RightMargin; - // double LeftMargin; - - // eTextAlignment HorizontalAlignment; - - // RectBase ParagraphArea; - - // /// - // /// Left position - // /// - // protected double XPos; - - // /// - // /// Line-Spacing in pixels - // /// - // protected double LineSpacing; - // protected double LineSpacingAscendantOnly; - - // MeasurementFont _measurementFont; - - // bool IsFirstParagraph = false; - - // ITextMeasurerWrap fmtt; - - // double? lnMultiplier = null; - // double paragraphHeight; - // private string text; - // private ExcelTextFont font; - // private RectBase area; - // private double posY; - // /// - // /// First paragraph must use different linespacing - // /// - // /// paragraph data. Ideally only used in constructor as datasource - // /// - // /// - // /// - // /// - // /// - // public SvgParagraph(ExcelDrawingParagraph p, RectBase paragraphArea, string VertAlign, double yPosition, bool isFirstParagraph = false) - // { - // fmtt = p._prd.Package.Settings.TextSettings.GenericTextMeasurerTrueType; - - // _measurementFont = p.DefaultRunProperties.GetMeasureFont(); - // fmtt.SetFont(_measurementFont); - // vertAlignAttribute = VertAlign; - - // //Seperated out in case of some final render item - // //needing to adjust without changing the original values - // RightMargin = p.RightMargin; - - // //p.Indent - // //0.5 inches per indent level 48 pixels - - // var indent = 48 * p.IndentLevel; - // LeftMargin = p.LeftMargin + p.Indent + indent; - - // ParagraphArea = paragraphArea; - // HorizontalAlignment = p.HorizontalAlignment; - - // //Must be set before linespacing - // IsFirstParagraph = isFirstParagraph; - - - // XPos = GetAlignmentHorizontal(HorizontalAlignment); - - // GetBounds(out double l, out double t, out double r, out double b); - // var textMaxWidth = r - l; - - // foreach (var run in p.TextRuns) - // { - // AddTextRun(run, paragraphArea.Bottom, yPosition); - // } - - // if (p._paragraphs.WrapText == eTextWrappingType.Square) - // { - // List textFragments = new List(); - // List fonts = new List(); - - // foreach (var txtRun in p.TextRuns) - // { - // textFragments.Add(txtRun.Text); - // fonts.Add(txtRun.GetMeasurementFont()); - // } - - // var trueTypeMeasurer = (FontMeasurerTrueType)fmtt; - // var maxWidthPoints = textMaxWidth.PixelToPoint(); - // var svgLines = trueTypeMeasurer.WrapMultipleTextFragments(textFragments, fonts, maxWidthPoints); - - // numLines = svgLines.Count; - - // List txtRunStrings = new List(); - // List txtRunStartIndicies = new List(); - // List txtRunEndIndicies = new List(); - - // int lastIndex = 0; - // for (int i = 0; i < TextRuns.Count; i++) - // { - // var txtString = TextRuns[i].originalText; - // txtRunStrings.Add(txtString); - // var indexOfRun = p.Text.IndexOf(txtString, lastIndex); - // txtRunStartIndicies.Add(indexOfRun); - // txtRunEndIndicies.Add(indexOfRun + txtString.Length); - // lastIndex = indexOfRun; - // } - - // List lineIndicies = new List(); - // lastIndex = 0; - - // //Last line should be handled by paragraph handling - // for (int i = 0; i< svgLines.Count(); i++) - // { - // var txtString = svgLines[i]; - // txtRunStrings.Add(txtString); - // var startIndex = p.Text.IndexOf(txtString, lastIndex); - // lastIndex = startIndex + txtString.Length; - // lineIndicies.Add(startIndex + txtString.Length); - // } - - // for (int i = 0; i < lineIndicies.Count; i++) - // { - // var lnBreakPosition = lineIndicies[i]; - // for (int j = 0; j < txtRunEndIndicies.Count; j++) - // { - // var start = txtRunStartIndicies[j]; - // var end = txtRunEndIndicies[j]; - - // bool containsBreak = (start <= lnBreakPosition && lnBreakPosition < end); - // if (containsBreak) - // { - // var localLnBreakPosition = lnBreakPosition - start; - // TextRuns[j].InsertLineBreak(localLnBreakPosition); - // break; - // } - // } - // } - // SetParagraphLineSpacingInPixels(p, fmtt); - // } - // else - // { - // numLines = p.Text.Split(new string[] { Environment.NewLine }, StringSplitOptions.None).Count(); - // } - // } - - - - // /// - // /// First paragraph must use different linespacing - // /// - // /// paragraph data. Ideally only used in constructor as datasource - // /// - // /// - // /// - // /// - // /// - // public SvgParagraph(string text, ExcelTextFont font, RectBase paragraphArea, string VertAlign, double yPosition) - // { - // fmtt = font.PictureRelationDocument.Package.Settings.TextSettings.GenericTextMeasurerTrueType; - - // _measurementFont = font.GetMeasureFont(); - // fmtt.SetFont(_measurementFont); - // vertAlignAttribute = VertAlign; - - // //p.Indent - // //0.5 inches per indent level 48 pixels - - // ParagraphArea = paragraphArea; - // HorizontalAlignment = eTextAlignment.Left; - - // LineSpacingAscendantOnly = fmtt.GetBaseLine().PointToPixel(); - // LineSpacing = fmtt.GetSingleLineSpacing().PointToPixel(); - - // //Must be set before linespacing - // IsFirstParagraph = true; - - // XPos = GetAlignmentHorizontal(HorizontalAlignment); - - // //GetBounds(out double l, out double t, out double r, out double b); - // //var textMaxWidth = r - l; - - // var tr = new SvgTextRun(text, font, LineSpacing, paragraphArea.Bottom, XPos, yPosition, LineSpacingAscendantOnly); - // TextRuns.Add(tr); - // } - - // private string GetHorizontalAlignmentAttribute(double indentX) - // { - // string ret = ""; - // var xStr = indentX.ToString(CultureInfo.InvariantCulture); - - // switch (HorizontalAlignment) - // { - // default: - // case eTextAlignment.Left: - // ret = $"text-anchor=\"start\" x=\"{xStr}\" "; - // break; - // case eTextAlignment.Center: - // ret = $"text-anchor=\"middle\" x=\"{xStr}\" "; - // break; - // case eTextAlignment.Right: - - // ret = $"text-anchor=\"end\" x=\"{xStr}\" "; - // break; - // } - - // return ret; - // } - - // private void SetParagraphLineSpacingInPixels(ExcelDrawingParagraph p, ITextMeasurerWrap fmExact) - // { - // if (p.LineSpacing.LineSpacingType == eDrawingTextLineSpacing.Exactly) - // { - // if (IsFirstParagraph) - // { - // LineSpacingAscendantOnly = p.LineSpacing.Value.PointToPixel(); - // } - // var lineSpacing= p.LineSpacing.Value.PointToPixel(); - // var lines = 1; - // foreach (var tr in TextRuns) - // { - // var lc = tr.GetLineCount(); - // if(lc>1) - // { - // lines += lc - 1; - // } - // } - // LineSpacing = lineSpacing * lines; - // } - // else - // { - // var multiplier = (p.LineSpacing.Value / 100); - // lnMultiplier = multiplier; - // if (IsFirstParagraph) - // { - // LineSpacingAscendantOnly = multiplier * fmExact.GetBaseLine().PointToPixel(); - // } - // if (TextRuns.Count == 0) - // { - // LineSpacing = multiplier * fmExact.GetSingleLineSpacing().PointToPixel(); - // } - // else - // { - // var lineSpacing=0D; - // var currentMaxLineSpacing = 0D; - // foreach(var tr in TextRuns) - // { - // if(currentMaxLineSpacing < tr.LineSpacing) - // { - // currentMaxLineSpacing = tr.LineSpacing; - // } - // var lc = tr.GetLineCount(); - // if (lc > 1) - // { - // lineSpacing += currentMaxLineSpacing; - // currentMaxLineSpacing = tr.LineSpacing; - // if(lc>2) - // { - // lineSpacing += (lc - 2) * tr.LineSpacing; - // } - // } - // } - // LineSpacing = multiplier * (lineSpacing + currentMaxLineSpacing); - // } - // } - // } - - // internal double GetAlignmentHorizontal(eTextAlignment txAlignment) - // { - // var area = ParagraphArea; - // double x = 0; - // switch (txAlignment) - // { - // case eTextAlignment.Left: - // default: - // x = area.Left + LeftMargin; - // break; - // case eTextAlignment.Center: - // x = (area.Right / 2) + LeftMargin - RightMargin; - // break; - // case eTextAlignment.Right: - // x = area.Right - RightMargin; - // break; - // } - - // return TextUtils.RoundToWhole(x); - // } - - // internal void AddTextRun(ExcelParagraphTextRunBase txtRun, double clippingHeight, double yPosition) - // { - // GetBounds(out double l, out double t, out double r, out double b); - // var textMaxWidth = r - l; - - // SvgTextRun textRun; - - // //if (TextRuns.Count == 0 && IsFirstParagraph == true) - // //{ - // // textRun = new SvgTextRun(txtRun, textMaxWidth, clippingHeight, XPos, yPosition); - // //} - // //else - // //{ - // textRun = new SvgTextRun(txtRun, textMaxWidth, clippingHeight, XPos, yPosition); - - // //If there are multiple sizes/multiple fonts with multiple sizes - // //if (lnType != eDrawingTextLineSpacing.Exactly && txtRun.FontSize != _measurementFont.Bounds) - // if(lnMultiplier.HasValue) - // { - // textRun.AdjustLineSpacing(lnMultiplier.Value); - // } - // //} - - // TextRuns.Add(textRun); - // } - - // internal double GetBottomYPosition() - // { - // //double bottomY = 0; - // //if (IsFirstParagraph) - // //{ - // // bottomY = LineSpacingAscendantOnly + LineSpacing * (numLines - 1); - // //} - // //else - // //{ - // // var bottomY = LineSpacing; - // //} - // return ParagraphArea.Top + LineSpacing; - // } - - // internal void CalculateTextWrapping(double maxWidth, MeasurementFont mFont, string fullParagraphText) - // { - // List NewContentLines = new List(); - // fmtt.SetFont(mFont); - // var textWidth = fmtt.MeasureText(fullParagraphText, mFont); - // } - //} -} \ No newline at end of file diff --git a/src/EPPlus.Export.ImageRenderer/Svg/SvgRange.cs b/src/EPPlus.Export.ImageRenderer/Svg/SvgRange.cs index 9d0da8dc6..cc7ca1206 100644 --- a/src/EPPlus.Export.ImageRenderer/Svg/SvgRange.cs +++ b/src/EPPlus.Export.ImageRenderer/Svg/SvgRange.cs @@ -1,5 +1,4 @@ using EPPlusImageRenderer.RenderItems; -using EPPlusImageRenderer.Text; using OfficeOpenXml; using System; using System.Collections.Generic; diff --git a/src/EPPlus.Export.ImageRenderer/Svg/SvgShape.cs b/src/EPPlus.Export.ImageRenderer/Svg/SvgShape.cs index 3ace69702..fb4bfc8b9 100644 --- a/src/EPPlus.Export.ImageRenderer/Svg/SvgShape.cs +++ b/src/EPPlus.Export.ImageRenderer/Svg/SvgShape.cs @@ -12,17 +12,17 @@ Date Author Change *************************************************************************************************/ using EPPlus.Export.ImageRenderer.RenderItems.Shared; using EPPlus.Export.ImageRenderer.RenderItems.SvgItem; -using EPPlus.Export.ImageRenderer.Text; using EPPlus.Export.ImageRenderer.Utils; using EPPlus.Fonts.OpenType; +using EPPlus.Fonts.OpenType.Utils; using EPPlus.Graphics; using EPPlusImageRenderer.RenderItems; using EPPlusImageRenderer.ShapeDefinitions; -using EPPlusImageRenderer.Text; using EPPlusImageRenderer.Utils; using OfficeOpenXml; using OfficeOpenXml.Drawing; using OfficeOpenXml.Drawing.Theme; +using OfficeOpenXml.FormulaParsing.Excel.Functions.Logical; using OfficeOpenXml.Style; using System; using System.Collections.Generic; @@ -43,7 +43,7 @@ internal class SvgShape : DrawingShape /// /// Textbox from memory /// - public SvgTextBoxItem TextBox { get; internal set; } + public SvgTextBox TextBox { get; internal set; } public SvgShape(ExcelShape shape) : base(shape) { @@ -88,19 +88,19 @@ public SvgShape(ExcelShape shape) : base(shape) if (shapeDef.TextBoxRect != null) { InsetTextBox = new SvgRenderRectItem(this, Bounds); - InsetTextBox.Bounds.Left = (float)shapeDef.TextBoxRect.LeftValue; - InsetTextBox.Bounds.Top = (float)shapeDef.TextBoxRect.TopValue; + InsetTextBox.Bounds.Left = (float)shapeDef.TextBoxRect.LeftValue.PixelToPoint(); + InsetTextBox.Bounds.Top = (float)shapeDef.TextBoxRect.TopValue.PixelToPoint(); InsetTextBox.FillOpacity = 0.3d; if (shape.TextBody.TextAutofit != eTextAutofit.ShapeAutofit) { - InsetTextBox.Width = (float)shapeDef.TextBoxRect.RightValue - (float)shapeDef.TextBoxRect.LeftValue; - InsetTextBox.Height = (float)shapeDef.TextBoxRect.BottomValue - (float)shapeDef.TextBoxRect.TopValue; + InsetTextBox.Width = ((double)((float)shapeDef.TextBoxRect.RightValue - (float)shapeDef.TextBoxRect.LeftValue)).PixelToPoint(); + InsetTextBox.Height = ((double)((float)shapeDef.TextBoxRect.BottomValue - (float)shapeDef.TextBoxRect.TopValue)).PixelToPoint(); } else { - InsetTextBox.Width = (float)shapeDef.TextBoxRect.RightValue; - InsetTextBox.Height = (float)shapeDef.TextBoxRect.BottomValue; + InsetTextBox.Width = (float)shapeDef.TextBoxRect.RightValue.PixelToPoint(); + InsetTextBox.Height = (float)shapeDef.TextBoxRect.BottomValue.PixelToPoint(); } } else @@ -109,6 +109,7 @@ public SvgShape(ExcelShape shape) : base(shape) } TextBox = CreateTextBodyItem(); + TextBox.Rectangle.FillOpacity = 0.3; TextBox.ImportTextBody(_shape.TextBody); TextBox.AppendRenderItems(RenderItems); } @@ -214,7 +215,7 @@ public string ViewBox public void Render(StringBuilder sb) { - sb.Append($""); + sb.Append($""); //Write defs used for gradient colors var writer = new SvgDrawingWriter(this); @@ -245,19 +246,19 @@ public void Render(StringBuilder sb) sb.AppendLine(""); } - SvgTextBoxItem CreateTextBodyItem() + SvgTextBox CreateTextBodyItem() { if (InsetTextBox == null) { GetShapeInnerBound(out double x, out double y, out double width, out double height); InsetTextBox = new SvgRenderRectItem(this, Bounds); - InsetTextBox.Bounds.Left = x; - InsetTextBox.Bounds.Top = y; - InsetTextBox.Width = width; - InsetTextBox.Height = height; - InsetTextBox.Bounds.Parent = TextBox.Bounds; //TODO:Check that textBody is correct. + InsetTextBox.Bounds.Left = x.PixelToPoint(); + InsetTextBox.Bounds.Top = y.PixelToPoint(); + InsetTextBox.Width = width.PixelToPoint(); + InsetTextBox.Height = height.PixelToPoint(); + InsetTextBox.Bounds.Parent = TextBox.Parent; //TODO:Check that textBody is correct. } - var txtBodyItem = new SvgTextBoxItem(this, Bounds, InsetTextBox.Bounds); + var txtBodyItem = new SvgTextBox(this, Bounds, InsetTextBox.Bounds); return txtBodyItem; } diff --git a/src/EPPlus.Export.ImageRenderer/Svg/SvgTextRun.cs b/src/EPPlus.Export.ImageRenderer/Svg/SvgTextRun.cs deleted file mode 100644 index e83da3b7f..000000000 --- a/src/EPPlus.Export.ImageRenderer/Svg/SvgTextRun.cs +++ /dev/null @@ -1,540 +0,0 @@ -/************************************************************************************************* - Required Notice: Copyright (C) EPPlus Software AB. - This software is licensed under PolyForm Noncommercial License 1.0.0 - and may only be used for noncommercial purposes - https://polyformproject.org/licenses/noncommercial/1.0.0/ - - A commercial license to use this software can be purchased at https://epplussoftware.com - ************************************************************************************************* - Date Author Change - ************************************************************************************************* - 27/11/2025 EPPlus Software AB EPPlus 9 - *************************************************************************************************/ -using EPPlus.Fonts.OpenType; -using EPPlus.Fonts.OpenType.Utils; -using EPPlusImageRenderer.RenderItems; -using OfficeOpenXml.Drawing; -using OfficeOpenXml.Interfaces.Drawing.Text; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -using OfficeOpenXml.Style; -using OfficeOpenXml.Utils; -using System.Text.RegularExpressions; - -namespace EPPlusImageRenderer.Svg -{ - //internal class SvgTextRun : SvgRenderItem - //{ - // internal double LineSpacing { get; set; } - // double _yEndPos; - // double ClippingHeight = Double.NaN; - // double fontSizeInPixels; - - // internal RectBase BoundingBox = new RectBase(); - // internal Coordinate origin = new Coordinate(0, 0); - // List Lines; - - // MeasurementFont measurementFont; - // FontMeasurerTrueType fmExact; - - // //Unnecesary?? - // double TextLengthInPixels; - // double LineSpacingAscendantOnly; - - // string horizontalAttribute; - // internal readonly string originalText; - // private string currentText; - - // double _xPosition; - - // string fontStyleAttributes; - - // bool isFirstInParagraph; - - // /// - // /// constructor. enter linespacing in pixels - // /// - // /// - // /// - // internal SvgTextRun(ExcelParagraphTextRunBase textRun, double textMaxX, double textMaxY, double xPosition, double yPosition) : base() - // { - // originalText = textRun.Text; - // Lines = SplitIntoLines(originalText); - // currentText = originalText; - - // measurementFont = textRun.GetMeasureFont(); - - // isFirstInParagraph = textRun.IsFirstInParagraph; - - // if(textRun.Fill.Style==eFillStyle.SolidFill) - // { - // FillColor = "#"+textRun.Fill.Color.To6CharHexString(); - // } - - // fmExact = new FontMeasurerTrueType(measurementFont); - - // //Used for the first line - // LineSpacingAscendantOnly = fmExact.GetBaseLine().PointToPixel(); - - // //LineSpacing = lineSpacing; - // LineSpacing = fmExact.GetSingleLineSpacing().PointToPixel(); - - - // _xPosition = xPosition; - // _yEndPos = yPosition; - - // fontSizeInPixels = ((double)measurementFont.Bounds).PointToPixel(true); - - // ClippingHeight = textMaxY; - // if (textRun.Paragraph._paragraphs.WrapText == eTextWrappingType.Square) - // { - // CalculateTextWrapping(textMaxX); - // } - - // if (textRun.FontItalic) - // { - // fontStyleAttributes += " font-style=\"italic\" "; - // } - // if(textRun.FontBold) - // { - // fontStyleAttributes += "font-weight=\"bold\" "; - // } - // if(textRun.FontUnderLine != eUnderLineType.None | textRun.FontStrike != eStrikeType.No) - // { - - // fontStyleAttributes += "text-decoration=\""; - // if (textRun.FontUnderLine != eUnderLineType.None) - // { - // switch (textRun.FontUnderLine) - // { - // case eUnderLineType.Single: - // fontStyleAttributes += "underline"; - // break; - // //These are all css only apparently - // //case eUnderLineType.Double: - // // fontStyleAttributes += "double"; - // // break; - // //case eUnderLineType.Dotted: - // // fontStyleAttributes += "dotted"; - // // break; - // //case eUnderLineType.Dash: - // // fontStyleAttributes += "dashed"; - // // break; - // //case eUnderLineType.Wavy: - // // fontStyleAttributes += "wavy"; - // // break; - // default: - // fontStyleAttributes += "underline"; - // break; - // //throw new NotImplementedException("Not implemented yet"); - // } - // } - - // if(textRun.FontStrike == eStrikeType.Single) - // { - // if(textRun.FontUnderLine != eUnderLineType.None) - // { - // fontStyleAttributes += ","; - // } - // fontStyleAttributes += "line-through"; - // } - - // fontStyleAttributes += "\" "; - // } - // } - // /// - // /// A item with a font property and no rich text. - // /// - // /// The text - // /// The font - // /// - // /// - // /// - // /// - // /// - // /// - // internal SvgTextRun(string text, ExcelTextFont font, double lineSpacing, double textMaxY, double xPosition, double yPosition, double baselineLineSpacing = double.NaN) : base() - // { - // originalText = text; - // currentText = text; - // isFirstInParagraph = true; - // Lines = SplitIntoLines(originalText); - - // measurementFont = font.GetMeasureFont(); - - - // if (font.Fill.Style == eFillStyle.SolidFill) - // { - // FillColor = "#" + font.Fill.Color.To6CharHexString(); - // } - - // fmExact = new FontMeasurerTrueType(measurementFont); - - // //horizontalTextAlignment = font..Paragraph.HorizontalAlignment; - - // LineSpacing = lineSpacing; - - // //Used for the first line - // LineSpacingAscendantOnly = baselineLineSpacing; - - // _xPosition = xPosition; - // _yEndPos = yPosition; - - // //origin.Left = xPosition; - // //origin.Top = yPosition; - - // fontSizeInPixels = ((double)measurementFont.Bounds).PointToPixel(true); - - // //ClippingHeight = textMaxY; - // //CalculateTextWrapping(textMaxX); - - // if (font.Italic) - // { - // fontStyleAttributes += " _measurementFont-style=\"italic\" "; - // } - // if (font.Bold) - // { - // fontStyleAttributes += "_measurementFont-weight=\"bold\" "; - // } - // if (font.UnderLine != eUnderLineType.None | font.Strike != eStrikeType.No) - // { - - // fontStyleAttributes += "text-decoration=\""; - // if (font.UnderLine != eUnderLineType.None) - // { - // switch (font.UnderLine) - // { - // case eUnderLineType.Single: - // fontStyleAttributes += "underline"; - // break; - // //These are all css only apparently - // //case eUnderLineType.Double: - // // fontStyleAttributes += "double"; - // // break; - // //case eUnderLineType.Dotted: - // // fontStyleAttributes += "dotted"; - // // break; - // //case eUnderLineType.Dash: - // // fontStyleAttributes += "dashed"; - // // break; - // //case eUnderLineType.Wavy: - // // fontStyleAttributes += "wavy"; - // // break; - // default: - // fontStyleAttributes += "underline"; - // break; - // //throw new NotImplementedException("Not implemented yet"); - // } - // } - - // if (font.Strike == eStrikeType.Single) - // { - // if (font.UnderLine != eUnderLineType.None) - // { - // fontStyleAttributes += ","; - // } - // fontStyleAttributes += "line-through"; - // } - - // fontStyleAttributes += "\" "; - // } - // } - - // //internal SvgTextRun(ExcelRichText textRun, double lineSpacing, double textMaxX, double textMaxY, double xPosition, double yPosition, MeasurementFont mf, ExcelHorizontalAlignment horAlign, double baselineLineSpacing = double.NaN) : base() - // //{ - // // originalText = textRun.Textbox; - // // Lines = SplitIntoLines(originalText); - - // // fmExact = new FontMeasurerTrueType(mf); - // // measurementFont = mf; - - // // horizontalTextAlignment = (eTextAlignment)horAlign; - - // // LineSpacing = fmExact.GetSingleLineSpacing().PointToPixel(true); - - // // //Used for the first line - // // LineSpacingAscendantOnly = fmExact.GetBaseLine().PointToPixel(true); - - // // _xPosition = xPosition; - // // _yPosition = yPosition; - - // // fontSizeInPixels = ((double)mf.Bounds).PointToPixel(true); - - // // ClippingHeight = textMaxY; - // // //No idea how to get this property now since we have no access to textbody/there may not be a direct text body as this might be a cell - // // bool wrapText = true; - // // if (wrapText) - // // { - // // CalculateTextWrapping(textMaxX); - // // } - - // // if (textRun.FontItalic) - // // { - // // fontStyleAttributes += " font-style=\"italic\" "; - // // } - // // if(textRun.FontBold) - // // { - // // fontStyleAttributes += "font-weight=\"bold\" "; - // // } - // // if(textRun.FontUnderLine != eUnderLineType.None | textRun.FontStrike != eStrikeType.No) - // // { - - // // fontStyleAttributes += "text-decoration=\""; - // // if (textRun.FontUnderLine != eUnderLineType.None) - // // { - // // switch (textRun.FontUnderLine) - // // { - // // case eUnderLineType.Single: - // // fontStyleAttributes += "underline"; - // // break; - // // //These are all css only apparently - // // //case eUnderLineType.Double: - // // // fontStyleAttributes += "double"; - // // // break; - // // //case eUnderLineType.Dotted: - // // // fontStyleAttributes += "dotted"; - // // // break; - // // //case eUnderLineType.Dash: - // // // fontStyleAttributes += "dashed"; - // // // break; - // // //case eUnderLineType.Wavy: - // // // fontStyleAttributes += "wavy"; - // // // break; - // // default: - // // fontStyleAttributes += "underline"; - // // break; - // // //throw new NotImplementedException("Not implemented yet"); - // // } - // // } - - // // if(textRun.FontStrike == eStrikeType.Single) - // // { - // // if(textRun.FontUnderLine != eUnderLineType.None) - // // { - // // fontStyleAttributes += ","; - // // } - // // fontStyleAttributes += "line-through"; - // // } - - // // fontStyleAttributes += "\" "; - // // } - // //} - - // internal SvgTextRun(ExcelRichText textRun, double lineSpacing, double textMaxX, double textMaxY, double xPosition, double yPosition, MeasurementFont mf, ExcelHorizontalAlignment horAlign, double baselineLineSpacing = double.NaN) : base() - // { - // originalText = textRun.Text; - // Lines = SplitIntoLines(originalText); - - // fmExact = new FontMeasurerTrueType(mf); - // measurementFont = mf; - - // LineSpacing = fmExact.GetSingleLineSpacing().PointToPixel(true); - - // //Used for the first line - // LineSpacingAscendantOnly = fmExact.GetBaseLine().PointToPixel(true); - - // _xPosition = xPosition; - // _yEndPos = yPosition; - - // fontSizeInPixels = ((double)mf.Bounds).PointToPixel(true); - - // ClippingHeight = textMaxY; - // //No idea how to get this property now since we have no access to textbody - // bool wrapText = true; - // if (wrapText) - // { - // CalculateTextWrapping(textMaxX); - // } - // } - - // internal void AdjustLineSpacing(double lineMultiplier) - // { - // LineSpacing = lineMultiplier * fmExact.GetSingleLineSpacing().PointToPixel(true); - // } - - // public RectBase textArea; - - // public override RenderItemType Type => RenderItemType.TSpan; - - // public override void Render(StringBuilder sb) - // { - // string finalString = ""; - // bool useBaselineSpacing = double.IsNaN(LineSpacingAscendantOnly) == false; - // Lines = SplitIntoLines(currentText); - - // foreach (var line in Lines) - // { - // finalString += $"= ClippingHeight) - // { - // visibility = "display=\"none\""; - // } - - // var yIncreaseString = yIncrease.ToString(CultureInfo.InvariantCulture); - // var xString = $"x =\"{(_xPosition).ToString(CultureInfo.InvariantCulture)}\" "; - // var dyString = $"dy =\"{yIncreaseString}px\" "; - // finalString += xString; - // finalString += dyString; - // } - - // finalString += $"{visibility} " + $"{fontStyleAttributes} "; - // if (measurementFont != null) - // { - // finalString += $" font-family=\"{measurementFont.FontFamily}," - // + $"{measurementFont.FontFamily}_MSFontService,sans-serif\" " - // + $"font-size=\"{fontSizeInPixels.ToString(CultureInfo.InvariantCulture)}px\" "; - // } - // sb.Append(finalString); - // //Get color etc. - // base.Render(sb); - // finalString = ""; - - // finalString += ">"; - // finalString += line; - // finalString += ""; - // } - - // sb.Append(finalString); - // //throw new NotImplementedException(); - // } - - // internal override SvgRenderItem Clone(SvgShape svgDocument) - // { - // throw new NotImplementedException(); - // } - - // private int GetNumberOfLines() - // { - // if (originalText != null) - // { - // if(currentText == null) - // { - // currentText = originalText; - // } - - // SplitIntoLines(currentText); - // return Lines.Count; - // } - // else - // { - // return 0; - // } - // } - - // internal int GetLineCount() - // { - // if (Lines == null) return 0; - - // var count = 0; - // foreach (var l in Lines) - // { - // count=Regex.Matches(l, Environment.NewLine).Count+1; - // } - // return count; - // } - - // internal List SplitIntoLines(string text) - // { - // return (text ?? "").Split(new string[] { Environment.NewLine }, StringSplitOptions.None).ToList(); - // } - - // internal override void GetBounds(out double il, out double it, out double ir, out double ib) - // { - // il = origin.X; - // it = origin.Y; - - // ir = CalculateRightPositionInPixels(); - // ib = CalculateBottomPositionInPixels(); - - // BoundingBox.Left = il; - // BoundingBox.Top = it; - // BoundingBox.Right = ir; - // BoundingBox.Bottom = ib; - // } - - // internal double CalculateRightPositionInPixels() - // { - // var numLines = GetNumberOfLines(); - // double retPos = origin.X; - - // if (numLines <= 0 || string.IsNullOrEmpty(originalText)) - // { - // TextLengthInPixels = 0; - // return retPos; - // } - - // double longestWidth = -1; - - // for (int i = 0; i < numLines; i++) - // { - // var text = Lines[i]; - // var width = CalculateTextWidth(Lines[i]); - - // if (width > longestWidth) - // { - // longestWidth = width; - // } - // } - - // TextLengthInPixels = longestWidth; - - // retPos += longestWidth; - - // return retPos; - // } - // internal double CalculateBottomPositionInPixels() - // { - // var lineSize = ((double)measurementFont.Bounds).PointToPixel(true) + origin.Y; - // var bottomPosition = lineSize * GetNumberOfLines(); - // return bottomPosition; - // } - - // internal double CalculateTextWidth(string targetString) - // { - // var textMesurer = new FontMeasurerTrueType(measurementFont); - // textMesurer.MeasureWrappedTextCells = true; - // var width = textMesurer.MeasureTextWidth(targetString); - - // return width; - // } - - // internal void CalculateTextWrapping(double maxWidth) - // { - // List NewContentLines = new List(); - // var textMesurer = new FontMeasurerTrueType(measurementFont); - // var newLines = textMesurer.MeasureAndWrapText(originalText, measurementFont, maxWidth); - // Lines = newLines; - // } - - // internal void InsertLineBreak(int insertPosition) - // { - // while(insertPosition < currentText.Length && char.IsWhiteSpace(currentText[insertPosition])) - // { - // currentText=currentText.Remove(insertPosition, 1); - // } - // currentText = currentText.Insert(insertPosition, Environment.NewLine); - // //if(Lines.Count==1) - // //{ - // // Lines.Clear(); - // // Lines.Add(currentText.Substring(0, insertPosition)); - // // Lines.Add(currentText.Substring(insertPosition + Environment.NewLine.Length, currentText.Length-(insertPosition + Environment.NewLine.Length))); - // //} - // } - //} -} \ No newline at end of file diff --git a/src/EPPlus.Export.ImageRenderer/Text/EpplusTextRun.cs b/src/EPPlus.Export.ImageRenderer/Text/EpplusTextRun.cs deleted file mode 100644 index a9cdd5481..000000000 --- a/src/EPPlus.Export.ImageRenderer/Text/EpplusTextRun.cs +++ /dev/null @@ -1,66 +0,0 @@ -/************************************************************************************************* - Required Notice: Copyright (C) EPPlus Software AB. - This software is licensed under PolyForm Noncommercial License 1.0.0 - and may only be used for noncommercial purposes - https://polyformproject.org/licenses/noncommercial/1.0.0/ - - A commercial license to use this software can be purchased at https://epplussoftware.com - ************************************************************************************************* - Date Author Change - ************************************************************************************************* - 27/11/2025 EPPlus Software AB EPPlus 9 - *************************************************************************************************/ -using OfficeOpenXml.Drawing; -using OfficeOpenXml.Style; - -namespace EPPlusImageRenderer.Text -{ - internal class EpplusTextRun - { - /// - /// The capitalization that is to be applied - /// - public eTextCapsType Capitalization; - - /// - /// The minimum font size at which character kerning occurs - /// - public double Kerning; - - /// - /// Fontsize - /// Spans from 0-4000 - /// - public double FontSize; - - /// - /// The spacing between between characters - /// - public double Spacing; - - /// - /// The baseline for both the superscript and subscript fonts in percentage - /// - public double Baseline; - - /// - /// FontBold text - /// - public bool Bold; - - /// - /// FontItalic text - /// - public bool Italic; - - /// - /// FontStrike-out text - /// - public eStrikeType Strike; - - /// - /// Underlined text - /// - public eUnderLineType UnderLine; - } -} diff --git a/src/EPPlus.Export.ImageRenderer/Text/FontWrapContainer.cs b/src/EPPlus.Export.ImageRenderer/Text/FontWrapContainer.cs deleted file mode 100644 index 3a2ddbe38..000000000 --- a/src/EPPlus.Export.ImageRenderer/Text/FontWrapContainer.cs +++ /dev/null @@ -1,95 +0,0 @@ -using EPPlus.Fonts.OpenType; -using EPPlus.Graphics; -using OfficeOpenXml.Interfaces.Drawing.Text; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace EPPlus.Export.ImageRenderer.Text -{ - /// - /// Only measures widths and handles the actual strings - /// This class only changes its bounding box if initDefaults = true - /// Any other changes to its size must be determined by the caller - /// If wrapping is true, uses parent rects width as maximum - /// - internal class FontWrapContainer : TextContainerBase - { - protected FontMeasurerTrueType _measurer; - - /// - /// If this is true it is presumed there is also a parent with a maxwidth - /// If there is no MaxWidth this class will not wrap - /// - public bool WrapText = false; - - public double MaxWidthPixels - { - get - { - if(Parent != null) - { - return Parent.Size.X; - } - else - { - return double.NaN; - } - } - } - - public FontWrapContainer(FontMeasurerTrueType txtMeasurer, bool initDefaults = true) : base(initDefaults) - { - Initialize(txtMeasurer); - } - - public FontWrapContainer(FontMeasurerTrueType txtMeasurer, string content, bool initDefaults = false) : base(content, initDefaults) - { - Initialize(txtMeasurer); - } - - private void Initialize(FontMeasurerTrueType txtMeasurer) - { - _measurer = txtMeasurer; - //Transform = parent.Transform; - //if (parent != null) - //{ - // SetParent(parent); - //} - } - - //void SetParent(Rect parent) - //{ - // TopDrawingHandler = parent; - // Transform.TopDrawingHandler = parent.Transform; - //} - - private void SplitContentToLines() - { - if (Content.Length == 1) - { - //If width is NaN textWrapper only applies line endings within the text itself - var inputWidth = WrapText ? MaxWidthPixels : double.NaN; - - Content = TextWrapper.GetLines(Content[0], _measurer, inputWidth).ToArray(); - } - } - - internal int GetNumberOfLines() - { - SplitContentToLines(); - return Content.Length; - } - - /// - /// Returns lines of content in pixel width - /// - /// - public double[] GetContentWidths() - { - SplitContentToLines(); - return TextWrapper.GetContentWidths(Content,_measurer, MaxWidthPixels).ToArray(); - } - } -} diff --git a/src/EPPlus.Export.ImageRenderer/Text/LineFormatter.cs b/src/EPPlus.Export.ImageRenderer/Text/LineFormatter.cs deleted file mode 100644 index 63e182b21..000000000 --- a/src/EPPlus.Export.ImageRenderer/Text/LineFormatter.cs +++ /dev/null @@ -1,104 +0,0 @@ -using EPPlus.Fonts.OpenType; -using OfficeOpenXml.Drawing; -using OfficeOpenXml.Interfaces.Drawing.Text; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace EPPlus.Export.ImageRenderer.Text -{ - internal static class LineFormatter - { - /// - /// Gets the starting indices of each individual run in the total text string - /// And the individual glyph advance widths in points for each textrun - /// - /// - /// - /// - internal static List GetTextRunIndiciesAndWidths(ExcelTextBody body, out List> AdvanceWidths) - { - var paragraphs = body.Paragraphs; - var text = paragraphs.Text; - - List txtRunIndicies = new List(); - - AdvanceWidths = new List>(); - - FontMeasurerTrueType measurer = new FontMeasurerTrueType(); - - int lastIndex = 0; - for (int i = 0; i < paragraphs.Count; i++) - { - var p = paragraphs[i]; - - for (int j = 0; j < p.TextRuns.Count; j++) - { - var run = p.TextRuns[j]; - - var indexOfRun = text.IndexOf(run.Text, lastIndex); - txtRunIndicies.Add(indexOfRun); - AdvanceWidths.Add(measurer.GetGlyphAdvanceWidths(run.Text, run.GetMeasurementFont())); - lastIndex = indexOfRun; - } - } - - return txtRunIndicies; - } - - - //internal static List GetFormattedLines(ExcelTextBody body, out List lineWidths, double MaxWidth = double.NaN) - //{ - // var paragraphs = body.Paragraphs; - // var text = paragraphs.Text; - - // List txtRunIndicies = new List(); - // List fonts = new List(); - - // int lastIndex = 0; - - // FontMeasurerTrueType measurer = new FontMeasurerTrueType(); - - // for (int i = 0; i < paragraphs.Count; i++) - // { - // var p = paragraphs[i]; - - // for (int j = 0; j < p.TextRuns.Count; j++) - // { - // var run = p.TextRuns[j]; - // TextWrapper.GetLines() - - // fonts.Add(run.GetMeasurementFont()); - // var indexOfRun = text.IndexOf(run.Text, lastIndex); - // txtRunIndicies.Add(indexOfRun); - // lastIndex = indexOfRun; - // } - // } - - - - // //return List test = new List(); - // //foreach (var font in fonts) - // //{ - // // FontMeasurerTrueType measurer = new FontMeasurerTrueType(); - // // measurer.SetFont(font); - - - // //} - - // //} - - // //internal static List WrapLines(ExcelDrawingTextRunCollection txtRuns, double maxWidth) - // //{ - // // txtRun - // // foreach (var run in txtRuns) - // // { - - // // } - // //} - //} - } -} - - diff --git a/src/EPPlus.Export.ImageRenderer/Text/TextBox.cs b/src/EPPlus.Export.ImageRenderer/Text/TextBox.cs deleted file mode 100644 index 4cbd10e16..000000000 --- a/src/EPPlus.Export.ImageRenderer/Text/TextBox.cs +++ /dev/null @@ -1,338 +0,0 @@ -/************************************************************************************************* - Required Notice: Copyright (C) EPPlus Software AB. - This software is licensed under PolyForm Noncommercial License 1.0.0 - and may only be used for noncommercial purposes - https://polyformproject.org/licenses/noncommercial/1.0.0/ - - A commercial license to use this software can be purchased at https://epplussoftware.com - ************************************************************************************************* - Date Author Change - ************************************************************************************************* - 27/11/2025 EPPlus Software AB EPPlus 9 - *************************************************************************************************/ -using EPPlus.Fonts.OpenType.Utils; -using EPPlusImageRenderer.RenderItems; -using EPPlusImageRenderer.Svg; -using OfficeOpenXml; -using OfficeOpenXml.Drawing; -using OfficeOpenXml.Utils; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Text; -using EPPlus.Graphics; -using System.Linq; -using EPPlus.Export.ImageRenderer.Utils; - -namespace EPPlusImageRenderer.Text -{ - //internal class TextBox : RenderItem - //{ - // internal RectMargins Bounds = new RectMargins(); - - // internal eTextAnchoringType VerticalAlignment=eTextAnchoringType.Top; - - // internal double ClippingHeight; - - // internal bool WrapText = true; - - // internal double Width - // { - // get; - // set; - // } - - // internal double Height - // { - // get; - // set; - // } - - // public override RenderItemType Type => RenderItemType.Rect; - - // /// - // /// Top of the paragraph bounding box - // /// - // double paragraphStartPosY = 0; - // private OfficeOpenXml.Interfaces.Drawing.Text.MeasurementFont mFontTextRun = null; - - // List Paragraphs = new List(); - // List CellTextRuns = new List(); - - // internal string fontColor; - - // /// - // /// The input parameters are assumed to be in pixels - // /// - // /// - // /// - // /// - // /// - // internal TextBox(ExcelDrawing drawing, double l, double t, double width, double height) : base(drawing.GetBoundingBox()) - // { - // Bounds.Left = l; Bounds.Top = t; Width = width; Height = height; - - // Bounds.Right = Bounds.Left + Width; - // Bounds.Bottom = Bounds.Top + Height; - - // //Initalize margins to 0 - // Bounds.MarginLeft = Bounds.MarginRight = Bounds.MarginTop = Bounds.MarginBottom = 0; - - // paragraphStartPosY = Bounds.Top; - - // ClippingHeight = Bounds.GetInnerBottom(); - // } - - // ///// - // ///// It is pressumed that txBody has units in EMUs and SvgRenderRectItem in pixels - // ///// - // ///// - // ///// - // //internal TextBox(ExcelTextBody txBody, SvgRenderRectItem textBoxRender) - // //{ - // // Bounds.Left = textBoxRender.Left; - // // Bounds.Top = textBoxRender.Top; - - // // //var Transform = Bounds.Transform; - - // // //var pos = Transform.Position; - - // // //pos.Y = 2; - - // // //Transform.LocalPosition.X = textBoxRender.X; - // // //Transform.LocalPosition.Y = textBoxRender.Y; - - // // Width = textBoxRender.Width; - // // Height = textBoxRender.Height; - - // // Bounds.Right = Bounds.Left + Width; - // // Bounds.Bottom = Bounds.Top + Height; - - // // txBody.GetInsetsOrDefaults(out double l, out double r, out double t, out double b); - - // // //Ensure margins are in pixels - // // Bounds.MarginLeft = l.PointToPixel(); - // // Bounds.MarginRight = r.PointToPixel(); - // // Bounds.MarginTop = t.PointToPixel(); - // // Bounds.MarginBottom = b.PointToPixel(); - - // // paragraphStartPosY = Bounds.Top + Bounds.MarginTop; - - // // ClippingHeight = Bounds.GetInnerBottom(); - // //} - - // //public TextBox() - // //{ - // //} - - // double? _alignmentY = null; - - // /// - // /// Get the start of text space vertically - // /// - // /// - // /// - // private double GetAlignmentVertical() - // { - // double alignmentY = 0; - // double y = paragraphStartPosY; - // var height = y - Bounds.Bottom; - - // switch (VerticalAlignment) - // { - // case eTextAnchoringType.Top: - // alignmentY = y; - // break; - // case eTextAnchoringType.Center: - // var adjustedHeight = y + (height / 2); - - // alignmentY = adjustedHeight + Bounds.MarginTop; - // break; - // case eTextAnchoringType.Bottom: - // alignmentY = height - Bounds.MarginBottom; - // break; - // } - - // _alignmentY = alignmentY; - - // return _alignmentY.Value; - // } - - // private string GetVerticalAlignAttribute(double textOrigY) - // { - // string ret = "dominant-baseline="; - - // switch (VerticalAlignment) - // { - // case eTextAnchoringType.Top: - // ret += $"\"text-top\" "; - // break; - // case eTextAnchoringType.Center: - // ret += $"\"middle\" "; - // break; - // case eTextAnchoringType.Bottom: - // ret += $"\"text-bottom\" "; - // break; - // default: - // return ""; - // } - - // var yStrValue = textOrigY.ToString(CultureInfo.InvariantCulture); - // ret += $" y=\"{yStrValue}\""; - - // return ret; - // } - - // public RectBase GetTextArea() - // { - // return Bounds.GetInnerRect(); - // } - - // internal SvgParagraph ImportParagraph(ExcelDrawingParagraph item) - // { - // var measureFont = item.GetMeasurementFont(); - - // //Document Top position for the paragraph text based on vertical alignment - // var posY = GetAlignmentVertical(); - // var vertAlignAttribute = GetVerticalAlignAttribute(posY); - - // var area = GetTextArea(); - - // //Limit bounding area with the space taken by previous paragraphs - // //Note that this is ONLY identical to PosY if the vertical alignment is top - // area.Top = paragraphStartPosY; - - // //The first run in the first paragraph must apply different line-spacing - // bool isFirst = Paragraphs.Count == 0; - // var svgParagraph = new SvgParagraph(item, area, vertAlignAttribute, posY, isFirst); - - // //Set starting position for next paragraph - // paragraphStartPosY = svgParagraph.GetBottomYPosition(); - - // svgParagraph.FillColor = string.IsNullOrEmpty(fontColor) ? item.DefaultRunProperties.Fill.Color.Name : fontColor; - - // //Above we've calculated heights from empty paragraphs - // //But below we do not add them for rendering (As they would not have any visible effect anyway) - // if (item.TextRuns.Count != 0) - // { - // Paragraphs.Add(svgParagraph); - // } - - // return svgParagraph; - // } - - // internal void RemoveParagraph(SvgParagraph item) - // { - // if(Paragraphs.Contains(item)) - // { - // Paragraphs.Remove(item); - // } - // } - // internal override void GetBounds(out double il, out double it, out double ir, out double ib) - // { - // il = Bounds.GetInnerLeft(); - // ir = Bounds.GetInnerRight(); - // it = Bounds.GetInnerTop(); - // ib = Bounds.GetInnerBottom(); - // } - - // public override void Render(StringBuilder sb) - // { - // RenderParagraphs(sb); - // } - - // internal void RenderParagraphs(StringBuilder sb) - // { - // //TODO: add textbody property stuff here - // foreach (var paragraph in Paragraphs) - // { - // paragraph.Render(sb); - // } - // } - // internal void RenderTextRuns(StringBuilder sb) - // { - // var groupItem = new SvgGroupItem(); - // groupItem.Render(sb); - - // var innerTop = Bounds.GetInnerRect().Top; - // var fontFamilyAttr = $"_measurementFont-family=\"{mFontTextRun.FontFamily},{mFontTextRun.FontFamily}_MSFontService,sans-serif\" "; - // sb.Append($""); - // //TODO: add textbody property stuff here - // foreach (var textRun in CellTextRuns) - // { - // textRun.Render(sb); - // } - // sb.Append(""); - // groupItem.RenderEndGroup(sb); - // } - - // internal void AddCellTextRun(ExcelRangeBase cell) - // { - // var fontStyle = cell.Style.Font; - - // //Line spacing in points and left margin in pixels - // double lineSpacing = (0.205d * fontStyle.Bounds + 1); - // Bounds.MarginLeft = lineSpacing; - // Bounds.MarginRight = lineSpacing; - - // mFontTextRun = fontStyle.GetMeasureFont(); - - // var lineSpacingPixels = (lineSpacing / 72d) * 92d; - - // var posY = GetAlignmentVertical(); - // var vertAlignAttribute = GetVerticalAlignAttribute(posY); - - // var area = GetTextArea(); - // var horizontalAlign = cell.Style.HorizontalAlignment; - - - // foreach (var rt in cell.RichText) - // { - // var textrun = new SvgTextRun(rt, lineSpacingPixels, - // Bounds.GetInnerRight(), Bounds.GetInnerBottom(), - // Bounds.GetInnerLeft(), Bounds.GetInnerTop(), mFontTextRun, horizontalAlign); - - // CellTextRuns.Add(textrun); - // } - - // //var lines = CellTextRuns.Last().GetLineCount(); - - // //if(lines * ) - - // //if (botPos > Bounds.Height) - // //{ - // // //Negative values increases bounds - // // Bounds.Bottom = Bounds.Top + botPos; - // //} - // } - // public string Text { get; set; } - // public double Rotation { get; internal set; } - - //internal void AddText(string text, OfficeOpenXml.Style.ExcelTextFont font) - //{ - // var measureFont = font.GetMeasureFont(); - - // var area = GetTextArea(); - // paragraphStartPosY = area.Top; - // //Document Top position for the paragraph text based on vertical alignment - // var posY = GetAlignmentVertical(); - // var vertAlignAttribute = GetVerticalAlignAttribute(posY); - - - - // var measurer = font.PictureRelationDocument.Package.Settings.TextSettings.GenericTextMeasurerTrueType; - // var m = measurer.MeasureText(text, measureFont); - // //Limit bounding area with the space taken by previous paragraphs - // //Note that this is ONLY identical to PosY if the vertical alignment is top - - // //The first run in the first paragraph must apply different line-spacing - // var svgParagraph = new SvgParagraph(text, font, area, vertAlignAttribute, posY); - - // svgParagraph.FillColor = font.Fill.Color.To6CharHexString(); - - // paragraphStartPosY = svgParagraph.GetBottomYPosition(); - - // Paragraphs.Add(svgParagraph); - //} - //} -} diff --git a/src/EPPlus.Export.ImageRenderer/Text/TextContainer.cs b/src/EPPlus.Export.ImageRenderer/Text/TextContainer.cs deleted file mode 100644 index 352c5fdb6..000000000 --- a/src/EPPlus.Export.ImageRenderer/Text/TextContainer.cs +++ /dev/null @@ -1,140 +0,0 @@ -using EPPlus.Fonts.OpenType; -using EPPlusImageRenderer.Text; -using OfficeOpenXml.Drawing; -using OfficeOpenXml.FormulaParsing.Excel.Functions.MathFunctions; -using OfficeOpenXml.Interfaces.Drawing.Text; -using System; -using System.Collections.Generic; -using System.Linq; -using EPPlus.Graphics; - -namespace EPPlus.Export.ImageRenderer.Text -{ - internal class TextContainer : RectMargins - { - /// - /// Should we resize the container depending on the text - /// - bool ResizeToFit = false; - - - List _textContentCollection = new List(); - - MeasurementFont MeasurementFont; - FontMeasurerTrueType _textMeasurerTrueType = null; - - public bool AddDeltaHeight = false; - - List TextContent - { - get - { - return _textContentCollection; - } - set - { - _textContentCollection = value; - if (ResizeToFit) - { - AutofitContainer(); - } - } - } - - - public RectBase GetTextArea() - { - return GetInnerRect(); - } - - - //To create a placeholder with default values - public TextContainer() : base() - { - ResizeToFit = false; - MeasurementFont = new MeasurementFont() - { - FontFamily = "Aptos Narrow", - Style = MeasurementFontStyles.Regular, - Size = 11 - }; - - //Excel at 100% size appears to have this pixel right and bottom for cells - Left = 0; Top = 0; Right = 64; Bottom = 20d; - - //Initalize margins to 0 - MarginLeft = MarginRight = MarginTop = MarginBottom = 0; - - _textContentCollection = new List(); - } - - /// - /// - /// - /// - /// - public TextContainer(List textCollection, MeasurementFont font, bool resizeToFit = false) - { - MeasurementFont = font; - ResizeToFit = resizeToFit; - TextContent = textCollection; - } - - /// - /// - /// - /// - /// - /// - public TextContainer(string text, MeasurementFont font, bool resizeToFit = false, bool addDeltaHeight = false) - { - MeasurementFont = font; - AddDeltaHeight = addDeltaHeight; - ResizeToFit = resizeToFit; - TextContent = SplitStrings(text); - } - - public static readonly string[] lineEndings = { "\r\n", "\r", "\n" }; - - List SplitStrings(string text) - { - string[] splitLines = text.Split - ( - lineEndings, - StringSplitOptions.None - ); - return splitLines.ToList(); - } - - void AutofitContainer() - { - if (_textMeasurerTrueType == null) - { - _textMeasurerTrueType = new FontMeasurerTrueType(MeasurementFont); - } - - double maxWidth = 0; - foreach (var line in TextContent) - { - var lineWidth = _textMeasurerTrueType.MeasureTextWidthInPixels(line); - //var lineWidthAlt = _textMeasurerTrueType.MeasureTextWidthInPixels(line+"\r\n"); - //lineWidth += _textMeasurerTrueType.GetMinXInPixels(); - if (lineWidth > maxWidth) - { - maxWidth = lineWidth; - } - } - var totalHeight = _textMeasurerTrueType.GetHeightOfTextInPixels(_textContentCollection); - - //if(AddDeltaHeight) - //{ - // //Textbox boxes in excel have a slight additional pixel size roughly equal to difference between typoAscent and winAscent - // var deltaAscent = _textMeasurerTrueType.GetDeltaAscent(TextUnit.Pixels); - // totalHeight += deltaAscent; - //} - - Width = maxWidth; - Height = totalHeight; - } - } -} diff --git a/src/EPPlus.Export.ImageRenderer/Text/TextContainerBase.cs b/src/EPPlus.Export.ImageRenderer/Text/TextContainerBase.cs deleted file mode 100644 index a812a6244..000000000 --- a/src/EPPlus.Export.ImageRenderer/Text/TextContainerBase.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using EPPlus.Graphics; - -namespace EPPlus.Export.ImageRenderer.Text -{ - /// - /// Simple base-class. - /// A Rect that also holds an arr of strings - /// No assumptions are made about where or if the text is placed inside the rect. - /// - internal class TextContainerBase : BoundingBox - { - protected string[] Content = null; - - /// - /// - /// - /// If true initializes the container to 64 width and 20 height - public TextContainerBase(bool initDefaults = true) - { - if (initDefaults) - { - //Right and Bottom Pixel defaults for a Cell in excel at 96 PPI - //(15pts height, 8.43pts width) - Left = 0; Top = 0; Width = 64; Height = 20d; - SetContent("Some Text"); - } - } - - public TextContainerBase(string content, bool initDefaults = true) - { - if (initDefaults) - { - //Right and Bottom Pixel defaults for a Cell in excel at 96 PPI - //(15pts height, 8.43pts width) - Left = 0; Top = 0; Width = 64; Height = 20d; - } - - SetContent(content); - } - - public void SetContent(string content) - { - Content = new string[] { content }; - } - - public string GetContent() - { - var combinedString = ""; - combinedString = string.Join(Environment.NewLine, Content); - return combinedString; - } - } -} diff --git a/src/EPPlus.Export.ImageRenderer/Text/TextField.cs b/src/EPPlus.Export.ImageRenderer/Text/TextField.cs deleted file mode 100644 index f3b4e1c52..000000000 --- a/src/EPPlus.Export.ImageRenderer/Text/TextField.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace EPPlusImageRenderer.Text -{ - internal class TextField - { - } -} diff --git a/src/EPPlus.Export.ImageRenderer/Text/TextWrapper.cs b/src/EPPlus.Export.ImageRenderer/Text/TextWrapper.cs deleted file mode 100644 index 80931d0b9..000000000 --- a/src/EPPlus.Export.ImageRenderer/Text/TextWrapper.cs +++ /dev/null @@ -1,54 +0,0 @@ -using EPPlus.Fonts.OpenType; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace EPPlus.Export.ImageRenderer.Text -{ - internal static class TextWrapper - { - internal static List GetLines(string content, FontMeasurerTrueType measurer, double maxWidth = double.NaN) - { - if (string.IsNullOrEmpty(content)) return null; - - List lines = new List(); - - if (double.IsNaN(maxWidth)) - { - lines = measurer.MeasureAndWrapText(content, maxWidth); - } - else - { - lines = content.Split(new string[] { Environment.NewLine }, StringSplitOptions.None).ToList(); - } - - return lines; - } - - internal static List GetContentWidths(string content, FontMeasurerTrueType measurer, double maxWidth = double.NaN) - { - var lines = GetLines(content, measurer, maxWidth); - return GetContentWidths(lines.ToArray(), measurer, maxWidth); - } - - /// - /// Assumes you already have correctly calculated lines - /// - /// - /// - /// - /// - internal static List GetContentWidths(string[] content, FontMeasurerTrueType measurer, double maxWidth = double.NaN) - { - double[] Widths = new double[content.Length]; - - for (int i = 0; i < content.Length; i++) - { - Widths[i] = measurer.MeasureTextWidth(content[i]); - } - - return Widths.ToList(); - } - } -} diff --git a/src/EPPlus.Export.ImageRenderer/Utils/DrawingExtensions.cs b/src/EPPlus.Export.ImageRenderer/Utils/DrawingExtensions.cs index e702ba482..ac62618b8 100644 --- a/src/EPPlus.Export.ImageRenderer/Utils/DrawingExtensions.cs +++ b/src/EPPlus.Export.ImageRenderer/Utils/DrawingExtensions.cs @@ -1,4 +1,5 @@ -using EPPlus.Graphics; +using EPPlus.Fonts.OpenType.Utils; +using EPPlus.Graphics; using OfficeOpenXml.Drawing; namespace EPPlus.Export.ImageRenderer.Utils @@ -11,8 +12,8 @@ internal static BoundingBox GetBoundingBox(this ExcelDrawing drawing) { Left = 0, Top = 0, - Width = drawing.GetPixelWidth(), - Height = drawing.GetPixelHeight() + Width = drawing.GetPixelWidth().PixelToPoint(), + Height = drawing.GetPixelHeight().PixelToPoint() }; } } diff --git a/src/EPPlus.Fonts.OpenType.Tests/Integration/TextLayoutEngineTests.cs b/src/EPPlus.Fonts.OpenType.Tests/Integration/TextLayoutEngineTests.cs index 6b4990090..63dbfce6b 100644 --- a/src/EPPlus.Fonts.OpenType.Tests/Integration/TextLayoutEngineTests.cs +++ b/src/EPPlus.Fonts.OpenType.Tests/Integration/TextLayoutEngineTests.cs @@ -3,6 +3,7 @@ using EPPlus.Fonts.OpenType.TrueTypeMeasurer.DataHolders; using EPPlus.Fonts.OpenType.Utils; using Microsoft.VisualStudio.TestTools.UnitTesting; +using OfficeOpenXml.Export.HtmlExport.StyleCollectors.StyleContracts; using OfficeOpenXml.Interfaces.Drawing.Text; using System.Collections.Generic; using System.Diagnostics; @@ -431,6 +432,182 @@ public void WrapRichTextDifficultCase() Assert.AreEqual("16SvgSize 24", wrappedLines[4]); } + [TestMethod] + public void EnsureLineFragmentsAreMeasuredCorrectlyWhenWrapping() + { + List lstOfRichText = new() { "TextBox2", "ra underline", "La Strike", "Goudy size 16"}; + var font2 = new MeasurementFont() + { + FontFamily = "Aptos Narrow", + Size = 11, + Style = MeasurementFontStyles.Bold + }; + + var font3 = new MeasurementFont() + { + FontFamily = "Aptos Narrow", + Size = 11, + Style = MeasurementFontStyles.Underline + }; + + var font4 = new MeasurementFont() + { + FontFamily = "Aptos Narrow", + Size = 11, + Style = MeasurementFontStyles.Strikeout + }; + + var font5 = new MeasurementFont() + { + FontFamily = "Goudy Stout", + Size = 16, + Style = MeasurementFontStyles.Regular + }; + + + List fonts = new() { font2, font3, font4, font5}; + var fragments = new List(); + + for (int i = 0; i < lstOfRichText.Count(); i++) + { + var currentFrag = new TextFragment() { Text = lstOfRichText[i], Font = fonts[i] }; + fragments.Add(currentFrag); + } + + var maxSizePoints = Math.Round(300d, 0, MidpointRounding.AwayFromZero).PixelToPoint(); + var startFont = TextData.GetFontData(font2.FontFamily, GetFontSubType(font2.Style)); + + var shaper = new TextShaper(startFont); + var layout = new TextLayoutEngine(shaper); + + var wrappedLines = layout.WrapRichTextLines(fragments, maxSizePoints); + + + Assert.AreEqual(12.55224609375d, wrappedLines[0].LineFragments[2].Width); + + } + + [TestMethod] + public void WrapRichTextDifficultCaseCompare() + { + List lstOfRichText = new() { "TextBox\r\na\r\n", "TextBox2", "ra underline", "La Strike", "Goudy size 16", "SvgSize 24" }; + + var font1 = new MeasurementFont() + { + FontFamily = "Aptos Narrow", + Size = 11, + Style = MeasurementFontStyles.Regular + }; ; + + var font2 = new MeasurementFont() + { + FontFamily = "Aptos Narrow", + Size = 11, + Style = MeasurementFontStyles.Bold + }; + + var font3 = new MeasurementFont() + { + FontFamily = "Aptos Narrow", + Size = 11, + Style = MeasurementFontStyles.Underline + }; + + var font4 = new MeasurementFont() + { + FontFamily = "Aptos Narrow", + Size = 11, + Style = MeasurementFontStyles.Strikeout + }; + + var font5 = new MeasurementFont() + { + FontFamily = "Goudy Stout", + Size = 16, + Style = MeasurementFontStyles.Regular + }; + + + var font6 = new MeasurementFont() + { + FontFamily = "Aptos Narrow", + Size = 24, + Style = MeasurementFontStyles.Regular + }; + + List fonts = new() { font1, font2, font3, font4, font5, font6 }; + var fragments = new List(); + + for (int i = 0; i < lstOfRichText.Count(); i++) + { + var currentFrag = new TextFragment() { Text = lstOfRichText[i], Font = fonts[i] }; + fragments.Add(currentFrag); + } + + var maxSizePoints = Math.Round(300d, 0, MidpointRounding.AwayFromZero).PixelToPoint(); + var startFont = TextData.GetFontData(font1.FontFamily, GetFontSubType(font1.Style)); + + var shaper = new TextShaper(startFont); + var layout = new TextLayoutEngine(shaper); + + var wrappedLines = layout.WrapRichTextLines(fragments, maxSizePoints); + + var measurer = new FontMeasurerTrueType(font1); + + TextFragmentCollection collection = new TextFragmentCollection(lstOfRichText); + var simpleLines = measurer.WrapMultipleTextFragmentsToTextLines(collection, fonts, maxSizePoints); + + Assert.AreEqual("TextBox", wrappedLines[0].Text); + Assert.AreEqual("a", wrappedLines[1].Text); + Assert.AreEqual("TextBox2ra underlineLa", wrappedLines[2].Text); + Assert.AreEqual("StrikeGoudy size", wrappedLines[3].Text); + Assert.AreEqual("16SvgSize 24", wrappedLines[4].Text); + + Assert.AreEqual(simpleLines[0].Text, wrappedLines[0].Text); + Assert.AreEqual(simpleLines[1].Text, wrappedLines[1].Text); + Assert.AreEqual(simpleLines[2].Text, wrappedLines[2].Text); + Assert.AreEqual(simpleLines[3].Text, wrappedLines[3].Text); + Assert.AreEqual(simpleLines[4].Text, wrappedLines[4].Text); + + //Rather large epsilon but the char widths are each individually more correct now + var epsilon = 0.1d; + + Assert.AreEqual(simpleLines[0].Width, wrappedLines[0].Width, epsilon); + Assert.AreEqual(simpleLines[1].Width, wrappedLines[1].Width, epsilon); + Assert.AreEqual(simpleLines[2].Width, wrappedLines[2].Width, epsilon); + Assert.AreEqual(simpleLines[3].Width, wrappedLines[3].Width, epsilon); + Assert.AreEqual(simpleLines[4].Width, wrappedLines[4].Width, epsilon); + + var line1FragmentsOld = simpleLines[0].RtFragments; + var line1FragmentsNew = wrappedLines[0].LineFragments; + Assert.AreEqual(line1FragmentsOld[0].Width, line1FragmentsNew[0].Width, epsilon); + + var line2FragmentsOld = simpleLines[1].RtFragments; + var line2FragmentsNew = wrappedLines[1].LineFragments; + + Assert.AreEqual(line2FragmentsOld[0].Width, line2FragmentsNew[0].Width, epsilon); + + var line3FragmentsOld = simpleLines[2].RtFragments; + var line3FragmentsNew = wrappedLines[2].LineFragments; + + Assert.AreEqual(line3FragmentsOld[0].Width, line3FragmentsNew[0].Width, epsilon); + Assert.AreEqual(line3FragmentsOld[1].Width, line3FragmentsNew[1].Width, epsilon); + Assert.AreEqual(line3FragmentsOld[2].Width, line3FragmentsNew[2].Width, epsilon); + + + var line4FragmentsOld = simpleLines[3].RtFragments; + var line4FragmentsNew = wrappedLines[3].LineFragments; + + Assert.AreEqual(line4FragmentsOld[0].Width, line4FragmentsNew[0].Width, epsilon); + Assert.AreEqual(line4FragmentsOld[1].Width, line4FragmentsNew[1].Width, epsilon); + + var line5FragmentsOld = simpleLines[4].RtFragments; + var line5FragmentsNew = wrappedLines[4].LineFragments; + + Assert.AreEqual(line5FragmentsOld[0].Width, line5FragmentsNew[0].Width, epsilon); + Assert.AreEqual(line5FragmentsOld[1].Width, line5FragmentsNew[1].Width, epsilon); + } + [TestMethod] public void WrapRichText_WordSpanningFragments_MeasuresCorrectly() { diff --git a/src/EPPlus.Fonts.OpenType.Tests/TextFragmentCollectionTests.cs b/src/EPPlus.Fonts.OpenType.Tests/TextFragmentCollectionTests.cs index a35ef6132..7c3ce7e37 100644 --- a/src/EPPlus.Fonts.OpenType.Tests/TextFragmentCollectionTests.cs +++ b/src/EPPlus.Fonts.OpenType.Tests/TextFragmentCollectionTests.cs @@ -278,5 +278,31 @@ public void MeasureWrappedWidths() Assert.AreEqual(169, pixelsWholeLine3); } + + [TestMethod] + public void CorrectTextLinesAreReturnedWhenSmallMaxWidth() + { + var defaultFont = new MeasurementFont() + { + FontFamily = "Aptos Narrow", + Size = 11, + Style = MeasurementFontStyles.Regular + }; + + var ttMeasurer = new FontMeasurerTrueType(defaultFont); + + var txt = "This is my text"; + + //This should be small enough to put each word on a new row. + var maxWidth = 21.5d; + + var txtLines = ttMeasurer.MeasureAndWrapTextLines(txt, defaultFont, maxWidth); + + Assert.AreEqual(4, txtLines.Count); + Assert.AreEqual("This", txtLines[0].Text); + Assert.AreEqual("is", txtLines[1].Text); + Assert.AreEqual("my", txtLines[2].Text); + Assert.AreEqual("text", txtLines[3].Text); + } } } diff --git a/src/EPPlus.Fonts.OpenType/Integration/LineFragment.cs b/src/EPPlus.Fonts.OpenType/Integration/LineFragment.cs new file mode 100644 index 000000000..f36eac26e --- /dev/null +++ b/src/EPPlus.Fonts.OpenType/Integration/LineFragment.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace EPPlus.Fonts.OpenType.Integration +{ + /// + /// The fragment of a richTextFragment that is within a line + /// + public class LineFragment + { + public int StartIdx { get; set; } + public double Width { get; set; } + public int RtFragIdx { get; set; } + + internal LineFragment(int rtFragmentIdx, int idxWithinLine) + { + RtFragIdx = rtFragmentIdx; + StartIdx = idxWithinLine; + } + } +} diff --git a/src/EPPlus.Fonts.OpenType/Integration/TextLayoutEngine.RichText.cs b/src/EPPlus.Fonts.OpenType/Integration/TextLayoutEngine.RichText.cs index 3df9fe747..c4aad63d2 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/TextLayoutEngine.RichText.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/TextLayoutEngine.RichText.cs @@ -12,10 +12,12 @@ Date Author Change 01/22/2025 EPPlus Software AB Optimized with shaping cache 01/23/2025 EPPlus Software AB Fixed lastSpaceIndex bug in multi-fragment wrapping *************************************************************************************************/ +using EPPlus.Fonts.OpenType.TrueTypeMeasurer.DataHolders; using EPPlus.Fonts.OpenType.Utilities; using OfficeOpenXml.Interfaces.Drawing.Text; using System; using System.Collections.Generic; +using System.Linq; using System.Text; namespace EPPlus.Fonts.OpenType.Integration @@ -53,6 +55,7 @@ public List WrapRichText( } FinalizeCurrentLine(lineBuilder, state.CurrentLineWidth, state.WordStart); + state.EndCurrentTextLine(); if (_lineListBuffer.Count == 0) { @@ -62,6 +65,42 @@ public List WrapRichText( return new List(_lineListBuffer); } + public List WrapRichTextLines( + List fragments, + double maxWidthPoints) + { + if (fragments == null || fragments.Count == 0) + { + return new List(); + } + + _lineListBuffer.Clear(); + + var lineBuilder = new StringBuilder(512); + var state = new WrapStateRichText(0); + state.WordStart = -1; + state.LineStart = -1; + + foreach (var fragment in fragments) + { + if (string.IsNullOrEmpty(fragment.Text)) continue; + + ProcessFragment(fragment, maxWidthPoints, lineBuilder, state); + } + + FinalizeCurrentLine(lineBuilder, state.CurrentLineWidth, state.WordStart); + state.CurrentTextLine.Width = state.CurrentLineWidth; + state.CurrentTextLine.Text = lineBuilder.ToString(); + state.EndCurrentTextLine(); + + if (_lineListBuffer.Count == 0) + { + _lineListBuffer.Add(string.Empty); + } + + return state.Lines; + } + private void ProcessFragment( TextFragment fragment, double maxWidthPoints, @@ -81,6 +120,10 @@ private void ProcessFragment( Array.Clear(charWidths, 0, len); FillCharWidths(shaped.Glyphs, scale, len, charWidths); + state.LineFrag = new LineFragment(state.CurrentFragmentIdx, lineBuilder.Length); + state.LineFrag.StartIdx = lineBuilder.Length; + state.LineFrag.RtFragIdx = state.CurrentFragmentIdx; + int i = 0; while (i < len) { @@ -90,6 +133,7 @@ private void ProcessFragment( { HandleLineBreak(lineBuilder, state); SkipLineBreakChars(fragment.Text, ref i); + state.CurrentLineWidth = 0; state.CurrentWordWidth = 0; state.WordStart = -1; // Reset after line break @@ -99,39 +143,28 @@ private void ProcessFragment( state.CurrentLineWidth += charWidths[i]; state.CurrentWordWidth += charWidths[i]; + state.LineFrag.Width += charWidths[i]; lineBuilder.Append(c); if (c == ' ') { - state.WordStart = lineBuilder.Length - 1; - state.CurrentWordWidth = 0; + state.SetAndLogWordStartState(lineBuilder.Length - 1); } if (state.CurrentLineWidth > maxWidthPoints) { - WrapCurrentLine(lineBuilder, state, maxWidthPoints); - - state.CurrentWordWidth = state.CurrentLineWidth; - - state.WordStart = -1; - state.LineStart = -1; - //We do not append ending spaces to the new line - if (c != ' ') - { - //lineBuilder.Append(c); - if(state.CurrentWordWidth == 0) - { - //The char that made us move past maxWidth - //must be added to the new line - //A whole word being moved down is handled in wrapCurrentLine. - state.CurrentWordWidth = charWidths[i]; - state.CurrentLineWidth = charWidths[i]; - } - } + WrapCurrentLine(lineBuilder, state, maxWidthPoints, charWidths[i]); } i++; } + + if(state.LineFrag.Width > 0) + { + state.CurrentTextLine.LineFragments.Add(state.LineFrag); + } + + state.CurrentFragmentIdx++; } private void FillCharWidths(ShapedGlyph[] glyphs, double scale, int textLength, double[] charWidths) @@ -160,12 +193,17 @@ private void HandleLineBreak(StringBuilder lineBuilder, WrapStateRichText state) if (lineBuilder.Length > 0) { _lineListBuffer.Add(lineBuilder.ToString()); + state.CurrentTextLine.Text = lineBuilder.ToString(); } else if (state.CurrentLineWidth > 0) { _lineListBuffer.Add(string.Empty); + state.CurrentTextLine.Text = string.Empty; } + state.CurrentTextLine.Width = state.CurrentLineWidth; + state.EndCurrentTextLineAndIntializeNext(state.CurrentFragmentIdx, 0); + lineBuilder.Length = 0; } @@ -178,8 +216,10 @@ private void SkipLineBreakChars(string text, ref int i) i++; } - private void WrapCurrentLine(StringBuilder lineBuilder, WrapStateRichText state, double maxWidthPoints) + private void WrapCurrentLine(StringBuilder lineBuilder, WrapStateRichText state, double maxWidthPoints, double advanceWidth) { + int fragIdxAtBreak = state.CurrentFragmentIdx; + // Bounds check to prevent ArgumentOutOfRangeException if (state.WordStart >= 0 && state.WordStart < lineBuilder.Length) { @@ -187,20 +227,51 @@ private void WrapCurrentLine(StringBuilder lineBuilder, WrapStateRichText state, _lineListBuffer.Add(line); lineBuilder.Remove(0, state.WordStart + 1); + //handle line data + state.CurrentTextLine.Width = state.CurrentLineWidth - state.CurrentWordWidth; + state.CurrentTextLine.Text = line; + + fragIdxAtBreak = state.GetFragIdxAtWordStart(); + //Because of word-wrap we may have richTextFragments on the current line that is no longer part of it after wrap. + state.AdjustLineFragmentsForNextLine(); + //A word was moved down. The new line must have the width and pos of the word. state.CurrentLineWidth = state.CurrentWordWidth; state.LineStart = state.WordStart; } else { + var lastChar = lineBuilder[lineBuilder.Length-1]; + var line = lineBuilder.ToString(0, lineBuilder.Length - 1); // No valid space found - wrap entire line - _lineListBuffer.Add(lineBuilder.ToString(0, lineBuilder.Length -1)); + _lineListBuffer.Add(line); + + //handle line data + state.CurrentTextLine.Width = state.CurrentLineWidth - advanceWidth; + state.CurrentTextLine.Text = line; + + //Add the char that went over max to the next line state.CurrentLineWidth = 0; lineBuilder.Length = 0; - lineBuilder.Append(lastChar); + //Append the char that goes over max unless it is a space + if (lastChar != ' ') + { + lineBuilder.Append(lastChar); + + //The char that made us move past maxWidth + //must be added to the new line + state.CurrentWordWidth = advanceWidth; + state.CurrentLineWidth = advanceWidth; + } } + + state.EndCurrentTextLineAndIntializeNext(state.CurrentFragmentIdx, lineBuilder.Length); + state.CurrentWordWidth = state.CurrentLineWidth; + + state.WordStart = -1; + state.LineStart = -1; } private void FinalizeCurrentLine(StringBuilder lineBuilder, double lineWidth, int lastSpaceIndex) diff --git a/src/EPPlus.Fonts.OpenType/Integration/TextLayoutEngine.cs b/src/EPPlus.Fonts.OpenType/Integration/TextLayoutEngine.cs index 9569b3a30..cc48abd73 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/TextLayoutEngine.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/TextLayoutEngine.cs @@ -173,7 +173,7 @@ private List WrapParagraph( var charWidths = CalculateCharacterWidths(text, fontSize, options); - var state = new WrapState(startingWidthPoints, GetCachedSpaceWidth(fontSize, options)); + var state = new WrapStateText(startingWidthPoints, GetCachedSpaceWidth(fontSize, options)); PrepareLineBuilder(text.Length); diff --git a/src/EPPlus.Fonts.OpenType/Integration/WrapStateBase.cs b/src/EPPlus.Fonts.OpenType/Integration/WrapStateBase.cs index 4bd7f1420..d0fee80b1 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/WrapStateBase.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/WrapStateBase.cs @@ -1,4 +1,5 @@ -using System; +using EPPlus.Fonts.OpenType.TrueTypeMeasurer.DataHolders; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -12,6 +13,15 @@ internal abstract class WrapStateBase public double CurrentLineWidth { get; set; } public double CurrentWordWidth { get; set; } + /// + /// Data holders for the individual lines + /// + public List Lines = new List(); + + public TextLineSimple CurrentTextLine = new TextLineSimple(); + + internal int CurrentFragmentIdx = 0; + public bool IsCompleteWordReady(CharacterType charType, int currentPosition) { return (charType == CharacterType.Space || charType == CharacterType.EndOfText) diff --git a/src/EPPlus.Fonts.OpenType/Integration/WrapStateRichText.cs b/src/EPPlus.Fonts.OpenType/Integration/WrapStateRichText.cs index 043cefa2c..0dcfd5cbd 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/WrapStateRichText.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/WrapStateRichText.cs @@ -1,4 +1,5 @@ -using System; +using EPPlus.Fonts.OpenType.TrueTypeMeasurer.DataHolders; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -7,9 +8,120 @@ namespace EPPlus.Fonts.OpenType.Integration { internal class WrapStateRichText : WrapStateBase { + internal LineFragment LineFrag = null; + public WrapStateRichText(double lineWidth) { CurrentLineWidth = lineWidth; } + + internal void EndCurrentTextLine() + { + if (CurrentTextLine.LineFragments.Contains(LineFrag) == false) + { + CurrentTextLine.LineFragments.Add(LineFrag); + } + Lines.Add(CurrentTextLine); + } + + internal void EndCurrentTextLineAndIntializeNext(int fragIdx, int startIdxOfNewFragment) + { + var nextLine = new TextLineSimple(); + + if (_fragmentsForNextLine == null) + { + EndCurrentTextLine(); + LineFrag = new LineFragment(fragIdx, startIdxOfNewFragment); + } + else + { + //_fragmentsForNextLine.Add(LineFrag); + nextLine.LineFragments = _fragmentsForNextLine; + + Lines.Add(CurrentTextLine); + _fragmentsForNextLine = null; + } + CurrentTextLine = nextLine; + } + + int _rtIdxAtWordStart = -1; + int _listIdxWithinLine = -1; + double _lineFragWidthAtWordStart = -1; + + internal void SetAndLogWordStartState(int wordStart) + { + WordStart = wordStart; + CurrentWordWidth = 0; + + _rtIdxAtWordStart = CurrentFragmentIdx; + _lineFragWidthAtWordStart = LineFrag.Width; + + if(CurrentTextLine.LineFragments.Count == 0) + { + _listIdxWithinLine = 0; + return; + } + + _listIdxWithinLine = CurrentTextLine.LineFragments.Count - 1; + + if (CurrentTextLine.LineFragments[_listIdxWithinLine].RtFragIdx < _rtIdxAtWordStart) + { + //When the word begins we are on a fragment that has not yet been added to the list. + //It will be the next index when added + _listIdxWithinLine += 1; + } + } + + internal int GetFragIdxAtWordStart() + { + return _rtIdxAtWordStart; + } + + internal double GetFragmentWidthAtWordStart() + { + return _lineFragWidthAtWordStart; + } + + List _fragmentsForNextLine = null; + + internal void AdjustLineFragmentsForNextLine() + { + if(_rtIdxAtWordStart == CurrentFragmentIdx) + { + //If we are On the fragment we have not added it yet + //Do so before splitting + CurrentTextLine.LineFragments.Add(LineFrag); + } + + var origFragment = CurrentTextLine.LineFragments[_listIdxWithinLine]; + + var resultingFragment = CurrentTextLine.SplitAndGetLeftoverLineFragment(ref origFragment, _lineFragWidthAtWordStart); + CurrentTextLine.LineFragments[_listIdxWithinLine] = origFragment; + + _fragmentsForNextLine = new List(); + + //Iterate backwards from back of list until we hit fragment + for (int i = CurrentTextLine.LineFragments.Count()-1; i > _listIdxWithinLine; i--) + { + //Add fragment to the new list + _fragmentsForNextLine.Insert(0, CurrentTextLine.LineFragments[i]); + //Remove it from the old + CurrentTextLine.LineFragments.RemoveAt(i); + } + + if(_rtIdxAtWordStart != CurrentFragmentIdx) + { + //We also insert the fragment we've split out + _fragmentsForNextLine.Insert(0, resultingFragment); + } + else + { + LineFrag = resultingFragment; + } + + _rtIdxAtWordStart = -1; + _listIdxWithinLine = -1; + _lineFragWidthAtWordStart = -1; + } } } diff --git a/src/EPPlus.Fonts.OpenType/TrueTypeMeasurer/DataHolders/RichTextFragmentSimple.cs b/src/EPPlus.Fonts.OpenType/TrueTypeMeasurer/DataHolders/RichTextFragmentSimple.cs index 654434d6a..0afd62182 100644 --- a/src/EPPlus.Fonts.OpenType/TrueTypeMeasurer/DataHolders/RichTextFragmentSimple.cs +++ b/src/EPPlus.Fonts.OpenType/TrueTypeMeasurer/DataHolders/RichTextFragmentSimple.cs @@ -25,6 +25,14 @@ public RichTextFragmentSimple() charStarIdxWithinCurrentLine = 0; } + //public RichTextFragmentSimple(int fragIdx, ) + //{ + // Width = 0; + // Fragidx = 0; + // OverallParagraphStartCharIdx = 0; + // charStarIdxWithinCurrentLine = 0; + //} + internal RichTextFragmentSimple Clone() { var fragment = new RichTextFragmentSimple(); diff --git a/src/EPPlus.Fonts.OpenType/TrueTypeMeasurer/DataHolders/TextLineSimple.cs b/src/EPPlus.Fonts.OpenType/TrueTypeMeasurer/DataHolders/TextLineSimple.cs index bda52fe14..08918654b 100644 --- a/src/EPPlus.Fonts.OpenType/TrueTypeMeasurer/DataHolders/TextLineSimple.cs +++ b/src/EPPlus.Fonts.OpenType/TrueTypeMeasurer/DataHolders/TextLineSimple.cs @@ -1,4 +1,5 @@ -using System; +using EPPlus.Fonts.OpenType.Integration; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -9,6 +10,8 @@ public class TextLineSimple { public List RtFragments { get; private set; } = new List(); + public List LineFragments { get; internal set; } = new List(); + public string Text { get; internal set; } /// /// The largest font size within this line @@ -70,5 +73,16 @@ public string GetFragmentText(RichTextFragmentSimple rtFragment) return Text.Substring(startIdx, endIdx - startIdx); } } + + internal LineFragment SplitAndGetLeftoverLineFragment(ref LineFragment origLf, double widthAtSplit) + { + //If we are splitting a fragment its position in the new line should be 0 + var newLineFragment = new LineFragment(origLf.RtFragIdx, 0); + newLineFragment.Width = origLf.Width - widthAtSplit; + + origLf.Width = widthAtSplit; + + return newLineFragment; + } } } diff --git a/src/EPPlus.Fonts.OpenType/TrueTypeMeasurer/FontMeasurerTrueType.cs b/src/EPPlus.Fonts.OpenType/TrueTypeMeasurer/FontMeasurerTrueType.cs index 9ccb8793d..d28557930 100644 --- a/src/EPPlus.Fonts.OpenType/TrueTypeMeasurer/FontMeasurerTrueType.cs +++ b/src/EPPlus.Fonts.OpenType/TrueTypeMeasurer/FontMeasurerTrueType.cs @@ -239,10 +239,10 @@ public List MeasureAndWrapText(string text, MeasurementFont font, double return wrappedStrings; } - public List MeasureAndWrapTextLines(string text, MeasurementFont font, double MaxWidthInPixels, double preExistingWidthPixels = 0) + public List MeasureAndWrapTextLines(string text, MeasurementFont font, double maxWidthPoints, double preExistingWidthPixels = 0) { SetFont(font.Size, font.FontFamily); - var wrappedStrings = TextData.MeasureAndWrapTextLines(text, FontSize, CurrentFont, MaxWidthInPixels.PixelToPoint(), preExistingWidthPixels.PixelToPoint()); + var wrappedStrings = TextData.MeasureAndWrapTextLines(text, FontSize, CurrentFont, maxWidthPoints, preExistingWidthPixels.PixelToPoint()); return wrappedStrings; } diff --git a/src/EPPlus.Fonts.OpenType/TrueTypeMeasurer/TextData.cs b/src/EPPlus.Fonts.OpenType/TrueTypeMeasurer/TextData.cs index ecece0334..64d67c3fc 100644 --- a/src/EPPlus.Fonts.OpenType/TrueTypeMeasurer/TextData.cs +++ b/src/EPPlus.Fonts.OpenType/TrueTypeMeasurer/TextData.cs @@ -427,8 +427,10 @@ private static void MeasureAndWrapTextLine(string line, OpenTypeFont font, ref i List tmpLst = new List(); WrapAtCharPos(line, i, ref nextLineStartIndex, ref lineWidth, ref wordWidth, advanceWidth, tmpLst, spaceWidth, out int prevLineWidth); - actualLine.Text = line; + actualLine.Text = tmpLst.Last(); actualLine.Width = prevLineWidth; + wrappedStrings.Add(actualLine); + actualLine = new TextLineSimple(); } } @@ -1227,9 +1229,12 @@ internal static List WrapMultipleTextFragmentsToTextLines(TextPa ref maxWidth, ref lineWidth, ref wordWidth); //Font/fragment change currentFont = paragraph.FontIndexDict[fragmentIdx]; - if (paragraph.FontSizes[fragmentIdx] > CurrentLineLargestFontSize) + if (paragraph.FontSizes[fragmentIdx] >= CurrentLineLargestFontSize) { - largestFontCurrentLine = currentFont; + if(GetSingleLineSpacing(currentFont, paragraph.FontSizes[fragmentIdx]) > GetSingleLineSpacing(largestFontCurrentLine, CurrentLineLargestFontSize)) + { + largestFontCurrentLine = currentFont; + } CurrentLineLargestFontSize = paragraph.FontSizes[fragmentIdx]; } spaceWidth = CalcGlyphWidth(paragraph.GlyphMappings[fragmentIdx], ' ', currentFont, ref lastGlyphIndex, ref applyKerning); @@ -1279,14 +1284,17 @@ internal static List WrapMultipleTextFragmentsToTextLines(TextPa //Largest font might be wrong here as we may linebreak at a different font than current font if we break at the start of a word //and not current i - largestFontCurrentLine = null; - CurrentLineLargestFontSize = 0; + largestFontCurrentLine = paragraph.FontIndexDict[currentLineRtFragments[0].Fragidx]; + CurrentLineLargestFontSize = paragraph.FontSizes[currentLineRtFragments[0].Fragidx]; for (int j = currentLineRtFragments[0].Fragidx; j < fragIdxAtBreak + 1; j++) { if (paragraph.FontSizes[j] > CurrentLineLargestFontSize) { - largestFontCurrentLine = paragraph.FontIndexDict[j]; + if (GetSingleLineSpacing(paragraph.FontIndexDict[j], paragraph.FontSizes[j]) > GetSingleLineSpacing(largestFontCurrentLine, CurrentLineLargestFontSize)) + { + largestFontCurrentLine = paragraph.FontIndexDict[j]; + } CurrentLineLargestFontSize = paragraph.FontSizes[j]; } } @@ -1294,14 +1302,17 @@ internal static List WrapMultipleTextFragmentsToTextLines(TextPa AddDataToSimpleLine(currentTextLine, paragraph, currentFont, largestFontCurrentLine, CurrentLineLargestFontSize, prevLineWidth, fragmentIdx); //Largest font is not neccesarily current font as word wrap might have wrapped another font between - largestFontCurrentLine = null; - CurrentLineLargestFontSize = 0; + largestFontCurrentLine = paragraph.FontIndexDict[fragIdxAtBreak]; + CurrentLineLargestFontSize = paragraph.FontSizes[fragIdxAtBreak]; for (int j = fragIdxAtBreak; j < fragmentIdx + 1; j++) { if (paragraph.FontSizes[j] > CurrentLineLargestFontSize) { - largestFontCurrentLine = paragraph.FontIndexDict[j]; + if (GetSingleLineSpacing(paragraph.FontIndexDict[j], paragraph.FontSizes[j]) > GetSingleLineSpacing(largestFontCurrentLine, CurrentLineLargestFontSize)) + { + largestFontCurrentLine = paragraph.FontIndexDict[j]; + } CurrentLineLargestFontSize = paragraph.FontSizes[j]; } } @@ -1423,14 +1434,12 @@ internal static List WrapMultipleTextFragmentsToTextLines(TextPa } AddDataToSimpleLine(currentTextLine, paragraph, currentFont, largestFontCurrentLine, CurrentLineLargestFontSize, lineWidth, paragraph.Fragments.TextFragments.Count() - 1); - - AddWidthToFragment(currentRtFragment, currentFont.HeadTable.UnitsPerEm, fontSize, lineWidth, prevFragWidths, currentRtFragment.Fragidx); currentRtFragment.charStarIdxWithinCurrentLine = Math.Max(0, currentRtFragment.OverallParagraphStartCharIdx - prevLineBreakIndex); currentLineRtFragments.Add(currentRtFragment); - for (int j = 0; j < currentLineRtFragments.Count(); j++) + for (int j = 0; j < currentLineRtFragments.Count; j++) { currentLineRtFragments[j].charStarIdxWithinCurrentLine = Math.Max(0, currentLineRtFragments[j].OverallParagraphStartCharIdx - prevLineBreakIndex); currentTextLine.RtFragments.Add(currentLineRtFragments[j]); diff --git a/src/EPPlus.Fonts.OpenType/Utils/TextUtils.cs b/src/EPPlus.Fonts.OpenType/Utils/TextUtils.cs index 49297919b..d3120ca5a 100644 --- a/src/EPPlus.Fonts.OpenType/Utils/TextUtils.cs +++ b/src/EPPlus.Fonts.OpenType/Utils/TextUtils.cs @@ -1,5 +1,6 @@ using OfficeOpenXml.Drawing; using System; +using System.Globalization; namespace EPPlus.Fonts.OpenType.Utils { @@ -13,6 +14,11 @@ public static double PointToPixel(this double pointSize) //1 inch is 72 pts. "Inches * dots/inch = dots" aka Pixels return pointSize / 72 * 96; } + public static string PointToPixelString(this double pointSize) + { + //1 inch is 72 pts. "Inches * dots/inch = dots" aka Pixels + return (pointSize / 72 * 96).ToString(CultureInfo.InvariantCulture); + } public static double PointToPixel(this double pointSize, bool isFonts) { diff --git a/src/EPPlus.Graphics/BoundingBox.cs b/src/EPPlus.Graphics/BoundingBox.cs index 585d0d861..5116e4857 100644 --- a/src/EPPlus.Graphics/BoundingBox.cs +++ b/src/EPPlus.Graphics/BoundingBox.cs @@ -8,8 +8,6 @@ namespace EPPlus.Graphics { internal class BoundingBox : Transform { - internal bool ClampedToParent { get; set; } = false; - internal BoundingBox() : base() { } @@ -17,7 +15,7 @@ internal BoundingBox() : base() internal BoundingBox(double width, double height) : base(0, 0, width, height) { } - internal BoundingBox(double left, double top, double right, double bottom) : base(left,top, right-left, bottom-top) + internal BoundingBox(double left, double top, double width, double height) : base(left,top, width, height) { } @@ -89,7 +87,7 @@ internal double Right return LocalPosition.X + Size.X; } } - internal double Width + internal virtual double Width { get { @@ -101,7 +99,7 @@ internal double Width } } - internal double Height + internal virtual double Height { get { diff --git a/src/EPPlus/Drawing/Chart/ExcelChartAxis.cs b/src/EPPlus/Drawing/Chart/ExcelChartAxis.cs index c83c9b381..1df76b94d 100644 --- a/src/EPPlus/Drawing/Chart/ExcelChartAxis.cs +++ b/src/EPPlus/Drawing/Chart/ExcelChartAxis.cs @@ -472,6 +472,14 @@ public abstract bool HasTitle { get; } + internal bool IsVertical + { + get + { + return AxisPosition == eAxisPosition.Left || AxisPosition == eAxisPosition.Right; + } + } + ///  /// Removes Major and Minor gridlines from the Axis ///  diff --git a/src/EPPlus/Drawing/Chart/ExcelChartAxisStandard.cs b/src/EPPlus/Drawing/Chart/ExcelChartAxisStandard.cs index 943dad056..12793c96c 100644 --- a/src/EPPlus/Drawing/Chart/ExcelChartAxisStandard.cs +++ b/src/EPPlus/Drawing/Chart/ExcelChartAxisStandard.cs @@ -837,32 +837,15 @@ private void AddCountFromSeries(List l, string address, double[] numberL if (numberLiterals?.Length > 0) { l.Add(numberLiterals.Length); - //for(int i=1;i<= numberLiterals?.Length;i++) - //{ - // hs.Add(i); - //} } else if (stringLiterals?.Length > 0) { l.Add(stringLiterals.Length); - //for (int i = 1; i <= stringLiterals?.Length; i++) - //{ - // hs.Add(i); - //} } else { var a = new ExcelAddressBase(address); l.Add(Math.Max(a.Rows, a.Columns)); - //var ws = _chart.WorkSheet.Workbook.Worksheets[a.WorkSheetName]; - //int i = 1; - //if (ws != null) - //{ - // foreach (var c in ws.Cells[a.Address]) - // { - // hs.Add(i++); - // } - //} } } diff --git a/src/EPPlus/NumberFormatToTextArgs.cs b/src/EPPlus/NumberFormatToTextArgs.cs index a4a3ec55d..01fcc949c 100644 --- a/src/EPPlus/NumberFormatToTextArgs.cs +++ b/src/EPPlus/NumberFormatToTextArgs.cs @@ -49,7 +49,7 @@ internal NumberFormatToTextArgs(ExcelWorksheet ws, int row, int column, object v public ExcelNumberFormatXml NumberFormat { get - { + { return ValueToTextHandler.GetNumberFormat(_styleId, Worksheet.Workbook.Styles); } } diff --git a/src/EPPlusTest/Drawing/Chart/DatalabelTest.cs b/src/EPPlusTest/Drawing/Chart/DatalabelTest.cs index b5348fa16..b4afd3ee1 100644 --- a/src/EPPlusTest/Drawing/Chart/DatalabelTest.cs +++ b/src/EPPlusTest/Drawing/Chart/DatalabelTest.cs @@ -3,6 +3,7 @@ using OfficeOpenXml.Drawing.Chart; using System; using System.Drawing; +using System.Linq; namespace EPPlusTest.Drawing.Chart { diff --git a/src/EPPlusTest/Drawing/CopyDrawingTests.cs b/src/EPPlusTest/Drawing/CopyDrawingTests.cs index 26cf4e86a..d4317b3eb 100644 --- a/src/EPPlusTest/Drawing/CopyDrawingTests.cs +++ b/src/EPPlusTest/Drawing/CopyDrawingTests.cs @@ -760,7 +760,6 @@ public void CopyNamedRangeToTargetSheetWB(ExcelPackage src, ExcelPackage target, target.Workbook.Worksheets.Delete(tmpWs); } - [TestMethod] public void s814CopySameImageTwiceToEmptyNamedRanges() {