diff --git a/src/EPPlus.Export.ImageRenderer.Test/TestTextContainer.cs b/src/EPPlus.Export.ImageRenderer.Test/TestTextContainer.cs index 9efc8990a..0eada3a34 100644 --- a/src/EPPlus.Export.ImageRenderer.Test/TestTextContainer.cs +++ b/src/EPPlus.Export.ImageRenderer.Test/TestTextContainer.cs @@ -22,11 +22,11 @@ public void TestDynamicSizeSingleLine() 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)); + Assert.AreEqual(expectedWidth, Math.Round(container.Width, 0)); + Assert.AreEqual(expectedHeight, Math.Round(container.Height, 0)); } [TestMethod] @@ -111,17 +111,17 @@ public void EnsureTextBodyAddsRunsCorrectly() shapeRect.Height = 10; FontMeasurerTrueType measurer = new FontMeasurerTrueType(12, "Aptos Narrow", FontSubFamily.Regular); - var body = new TextBody(measurer, shapeRect, true); + var body = new TextBody(shapeRect); - body.transform.Name = "TxtBody"; + body.Bounds.transform.Name = "TxtBody"; - body.AddText("A new Paragraph"); - body.AddText("Second paragraph"); + body.AddText("A new Paragraph", measurer); + body.AddText("Second paragraph", measurer); - Assert.AreEqual(2, body.transform.ChildObjects.Count); - Assert.AreEqual(body.transform.ChildObjects[0], body.Runs[0].transform); + Assert.AreEqual(2, body.Bounds.transform.ChildObjects.Count); + Assert.AreEqual(body.Bounds.transform.ChildObjects[0], body.Paragraphs[0].Bounds.transform); - Assert.AreEqual(shapeRect.transform, body.transform.Parent); + Assert.AreEqual(shapeRect.transform, body.Bounds.transform.Parent); } //[TestMethod] diff --git a/src/EPPlus.Export.ImageRenderer/DrawingBase.cs b/src/EPPlus.Export.ImageRenderer/DrawingBase.cs index 8d4ddb9d2..aa2ced690 100644 --- a/src/EPPlus.Export.ImageRenderer/DrawingBase.cs +++ b/src/EPPlus.Export.ImageRenderer/DrawingBase.cs @@ -13,26 +13,32 @@ Date Author Change using EPPlus.Export.ImageRenderer; using EPPlusImageRenderer.RenderItems; +using OfficeOpenXml; using OfficeOpenXml.Drawing; +using OfficeOpenXml.Drawing.Theme; using OfficeOpenXml.Interfaces.Drawing.Text; using System.Collections.Generic; using System.Text; namespace EPPlusImageRenderer { - internal abstract class DrawingBase + internal abstract class DrawingBase : RenderItem { - internal DrawingBase(ExcelDrawing drawing) + protected ExcelWorkbook _wb; + + internal DrawingBase(ExcelDrawing drawing) : base(drawing) { drawing.GetSizeInPixels(out int width, out int height); Drawing = drawing; Size = new DrawingSize(width, height); TextMeasurer = drawing._drawings._package.Settings.TextSettings.PrimaryTextMeasurer; + + _wb = drawing._drawings.Worksheet.Workbook; + _theme = _wb.ThemeManager.GetOrCreateTheme(); } public ExcelDrawing Drawing { get; } internal ITextMeasurer TextMeasurer { get; } public List RenderItems { get; } = new List(); public DrawingSize Size { get; internal set; } - public abstract void Render(StringBuilder sb); } } \ No newline at end of file diff --git a/src/EPPlus.Export.ImageRenderer/DrawingShape.cs b/src/EPPlus.Export.ImageRenderer/DrawingShape.cs index d7729864a..7ee42114e 100644 --- a/src/EPPlus.Export.ImageRenderer/DrawingShape.cs +++ b/src/EPPlus.Export.ImageRenderer/DrawingShape.cs @@ -11,18 +11,23 @@ Date Author Change 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ +using EPPlus.Export.ImageRenderer.RenderItems.Shared; using EPPlusImageRenderer.RenderItems; +using OfficeOpenXml; using OfficeOpenXml.Drawing; +using OfficeOpenXml.Drawing.Theme; using System; using System.Collections.Generic; namespace EPPlusImageRenderer { - internal abstract class DrawingShape : DrawingBase + internal abstract class DrawingShape : DrawingBaseItem { protected ExcelShape _shape; protected DrawingShape(ExcelShape shape) : base(shape) { + var style = shape.Style; + _shape = shape; } protected static void AddCmd(SvgRenderPathItem pi, DrawingPath path, List coordinates, ref PathCommands cmd, PathsBase pp, PathsBase p, PathCommandType commandType) diff --git a/src/EPPlus.Export.ImageRenderer/ImageRenderer.cs b/src/EPPlus.Export.ImageRenderer/ImageRenderer.cs index 6028dcf4e..52caf765c 100644 --- a/src/EPPlus.Export.ImageRenderer/ImageRenderer.cs +++ b/src/EPPlus.Export.ImageRenderer/ImageRenderer.cs @@ -13,23 +13,34 @@ Date Author Change using EPPlus.Export.ImageRenderer; using EPPlus.Export.ImageRenderer.RenderItems.Shared; +using EPPlus.Export.ImageRenderer.RenderItems.Shared; 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 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; using OfficeOpenXml; using OfficeOpenXml.Drawing; using OfficeOpenXml.Drawing.Chart; using OfficeOpenXml.Utils; +using OfficeOpenXml.Utils; using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; using System.Text; using System.Xml.Linq; +using System.Xml.Linq; namespace EPPlusImageRenderer { @@ -77,6 +88,7 @@ public string RenderRangeToSvg(ExcelRange range) var sb = new StringBuilder(); sb.Append($""); + sb.Append($""); sRange.Render(sb); sb.Append($""); return sb.ToString(); @@ -118,24 +130,26 @@ public string RenderTextBody(string txtBody) shapeRect.Height = 200; FontMeasurerTrueType measurer = new FontMeasurerTrueType(11, "Aptos Narrow", FontSubFamily.Regular); - var body = new TextBody(measurer, shapeRect, true); + var body = new TextBody(shapeRect); + + body.Bounds.transform.Name = "TxtBody"; - body.transform.Name = "TxtBody"; + body.Bounds.X = 40; + body.Bounds.Y = 40; - body.X = 40; - body.Y = 40; + body.AddText(txtBody, measurer); - body.AddText(txtBody); + var para1 = body.Paragraphs[0]; - body.Runs[0].X = 20; - body.Runs[0].Y = 20; + para1.Runs[0].X = 20; + para1.Runs[0].Y = 20; - body.AddText("Extra Text"); - body.Runs[1].X = 30; - body.Runs[1].Y = 40; + para1.AddText("Extra Text", measurer); + para1.Runs[1].X = 30; + para1.Runs[1].Y = 40; - body.Width = 120; - body.Height = 100; + para1.Bounds.Width = 120; + para1.Bounds.Height = 100; var svgBody = GenerateSvgTextBody(body, (int)shapeRect.Width, (int)shapeRect.Height); @@ -173,14 +187,14 @@ internal SvgElement GenerateSvgTextBody(TextBody body, int width, int height) body.AllowOverflow = true; - var svgDefs = GetDefinitions(body, out string nameId, body.AllowOverflow); + var svgDefs = GetDefinitions(body.Bounds, out string nameId, body.AllowOverflow); var fontSizePx = 16d; doc.AddChildElement(svgDefs); doc.AddChildElement(bg); - foreach (var run in body.Runs) + foreach (var run in body.Paragraphs[0].Runs) { var bbVisual = new SvgElement("rect"); bbVisual.AddAttribute("x", run.GlobalX); @@ -194,20 +208,20 @@ internal SvgElement GenerateSvgTextBody(TextBody body, int width, int height) } var txBodyVisual = new SvgElement("rect"); - txBodyVisual.AddAttribute("x", body.GlobalX); - txBodyVisual.AddAttribute("y", body.GlobalY); - txBodyVisual.AddAttribute("width", body.Width); - txBodyVisual.AddAttribute("height", body.Height); + txBodyVisual.AddAttribute("x", body.Bounds.GlobalX); + txBodyVisual.AddAttribute("y", body.Bounds.GlobalY); + txBodyVisual.AddAttribute("width", body.Bounds.Width); + txBodyVisual.AddAttribute("height", body.Bounds.Height); txBodyVisual.AddAttribute("fill", "green"); txBodyVisual.AddAttribute("opacity", "0.5"); doc.AddChildElement(txBodyVisual); - foreach (var run in body.Runs) + foreach (var run in body.Paragraphs) { var renderElement = new SvgElement("text"); - renderElement.AddAttribute("x", run.GlobalX); - renderElement.AddAttribute("y", run.GlobalY + fontSizePx); + renderElement.AddAttribute("x", run.Bounds.GlobalX); + renderElement.AddAttribute("y", run.Bounds.GlobalY + fontSizePx); renderElement.AddAttribute("font-size", $"{fontSizePx}px"); renderElement.AddAttribute("clip-path", $"url(#{nameId})"); diff --git a/src/EPPlus.Export.ImageRenderer/RenderItems/RenderItem.cs b/src/EPPlus.Export.ImageRenderer/RenderItems/RenderItem.cs index 2e7880ddd..76b0f3081 100644 --- a/src/EPPlus.Export.ImageRenderer/RenderItems/RenderItem.cs +++ b/src/EPPlus.Export.ImageRenderer/RenderItems/RenderItem.cs @@ -10,6 +10,7 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ +using EPPlus.Graphics; using EPPlusImageRenderer.Utils; using OfficeOpenXml.Drawing; using OfficeOpenXml.Drawing.Chart.Style; @@ -64,6 +65,36 @@ protected void CloneBase(RenderItem item) item.LineJoin = LineJoin; item.FillColorSource = FillColorSource; } + + //internal virtual void SetDrawingPropertiesFill(ExcelDrawingFill fill) + //{ + // switch (fill.Style) + // { + // case eFillStyle.NoFill: + // if (fill.IsEmpty) + // { + // FillColor = GetFillColor(fill, FillColorSource); + // } + // else + // { + // FillColor = "none"; + // } + // break; + // case eFillStyle.SolidFill: + // FillColor = GetFillColor(fill, fill.SolidFill.Color, FillColorSource); + // break; + // case eFillStyle.GradientFill: + // GradientFill = new DrawGradientFill(_theme, fill.GradientFill); + // FillColor = null; + // break; + // case eFillStyle.PatternFill: + // PatternFill = fill.PatternFill; + // break; + // case eFillStyle.BlipFill: + // BlipFill = fill.BlipFill; + // break; + // } + //} internal virtual void SetDrawingPropertiesFill(ExcelDrawingFill fill, ExcelDrawingColorManager color) { switch (fill.Style) @@ -159,6 +190,32 @@ private double[] GetDashArray(ExcelDrawingBorder border) return null; } + //private string GetFillColor(ExcelDrawingFill fill, PathFillMode fillColorSource) + //{ + // if (fillColorSource == PathFillMode.None) + // { + // return "none"; + // } + + // Color fc; + + // if (fill == null || fill.Style == eFillStyle.NoFill) + // { + // fc = EPPlusColorConverter.GetThemeColor(_theme.ColorScheme.Accent1); + // } + // else if (fill.Style == eFillStyle.SolidFill) + // { + // fc = EPPlusColorConverter.GetThemeColor(_theme, fill.SolidFill.Color); + // } + // else + // { + // return string.Empty; + // } + + // fc = ColorUtils.GetAdjustedColor(fillColorSource, fc); + // return "#" + fc.ToArgb().ToString("x8").Substring(2); + //} + private string GetFillColor(ExcelDrawingFillBasic fill, ExcelDrawingColorManager styleFillColor, PathFillMode fillColorSource) { if (fillColorSource == PathFillMode.None) @@ -190,6 +247,8 @@ private string GetFillColor(ExcelDrawingFillBasic fill, ExcelDrawingColorManager fc = ColorUtils.GetAdjustedColor(fillColorSource, fc); return "#" + fc.ToArgb().ToString("x8").Substring(2); } + + internal BoundingBox Bounds = new BoundingBox(); internal abstract void GetBounds(out double il, out double it, out double ir, out double ib); } /// @@ -197,7 +256,7 @@ private string GetFillColor(ExcelDrawingFillBasic fill, ExcelDrawingColorManager /// internal abstract class RenderItemBase { - public abstract SvgItemType Type { get; } + public abstract RenderItemType Type { get; } public abstract void Render(StringBuilder sb); } diff --git a/src/EPPlus.Export.ImageRenderer/RenderItems/SvgItemType.cs b/src/EPPlus.Export.ImageRenderer/RenderItems/RenderItemType.cs similarity index 96% rename from src/EPPlus.Export.ImageRenderer/RenderItems/SvgItemType.cs rename to src/EPPlus.Export.ImageRenderer/RenderItems/RenderItemType.cs index 6cc58e98e..e3e3b1895 100644 --- a/src/EPPlus.Export.ImageRenderer/RenderItems/SvgItemType.cs +++ b/src/EPPlus.Export.ImageRenderer/RenderItems/RenderItemType.cs @@ -12,7 +12,7 @@ Date Author Change *************************************************************************************************/ namespace EPPlusImageRenderer.RenderItems { - internal enum SvgItemType + internal enum RenderItemType { Path = 0, Rect = 1, diff --git a/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/DrawingBaseItem.cs b/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/DrawingBaseItem.cs new file mode 100644 index 000000000..c2069c533 --- /dev/null +++ b/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/DrawingBaseItem.cs @@ -0,0 +1,37 @@ +using EPPlusImageRenderer; +using EPPlusImageRenderer.RenderItems; +using OfficeOpenXml.Drawing; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace EPPlus.Export.ImageRenderer.RenderItems.Shared +{ + internal abstract class DrawingBaseItem : DrawingBase + { + public DrawingBaseItem(ExcelShapeBase drawing) : base(drawing) + { + ImportEpplusDrawing(drawing); + } + + internal void ImportEpplusDrawing(ExcelShapeBase drawing) + { + Bounds.Left = drawing.GetPixelLeft(); + Bounds.Top = drawing.GetPixelTop(); + Bounds.Width = drawing.GetPixelWidth(); + Bounds.Height = drawing.GetPixelHeight(); + + SetDrawingPropertiesFill(drawing.Fill, null); + SetDrawingPropertiesBorder(drawing.Border, null, drawing.Border != null); + } + + public override RenderItemType Type => RenderItemType.Group; + + internal override void GetBounds(out double il, out double it, out double ir, out double ib) + { + il = Bounds.Left; it = Bounds.Top; ir = Bounds.Right; ib = Bounds.Bottom; + } + } +} diff --git a/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/InsetTextbox.cs b/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/InsetTextbox.cs new file mode 100644 index 000000000..3dca8c9b8 --- /dev/null +++ b/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/InsetTextbox.cs @@ -0,0 +1,22 @@ +using EPPlus.Graphics; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace EPPlus.Export.ImageRenderer.RenderItems.Shared +{ + /// + /// Calculated textbox position from a shape + /// That contains the textbody + /// + internal class InsetTextbox: BoundingBox + { + TextBody textBody; + + //internal InsetTextbox(double l, double t, double width, double height) + //{ + // textBody = new TextBody(this); + //} + } +} diff --git a/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/ParagraphContainer.cs b/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/ParagraphContainer.cs new file mode 100644 index 000000000..e1bf17969 --- /dev/null +++ b/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/ParagraphContainer.cs @@ -0,0 +1,74 @@ +using EPPlus.Export.ImageRenderer.Text; +using EPPlus.Fonts.OpenType; +using EPPlus.Graphics; +using EPPlusImageRenderer.RenderItems; +using OfficeOpenXml.Drawing; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace EPPlus.Export.ImageRenderer.RenderItems.Shared +{ + internal class ParagraphContainer : RenderItem + { + internal List Runs = new List(); + + public override RenderItemType Type => RenderItemType.Text; + + public ParagraphContainer() : base() + { + + } + + public ParagraphContainer(ExcelDrawingParagraph paragraph) + { + for(int i = 0; i < paragraph.TextRuns.Count(); i++) + { + + } + //paragraph.DefaultRunProperties + //SetDrawingPropertiesFill(paragraph.DefaultRunProperties.Fill, paragraph._prd.s) + //fill + } + + public ParagraphContainer(BoundingBox parent) + { + Bounds.Parent = parent; + } + + public void AddText(string text, FontMeasurerTrueType measurer) + { + var container = new FontWrapContainer(measurer); + container.Parent = Bounds; + + Runs.Add(container); + + container.transform.Name = $"Container{Runs.Count}"; + + container.SetContent(text); + } + + public string GetContent() + { + StringBuilder sb = new StringBuilder(); + + foreach (var item in Runs) + { + sb.Append(item.GetContent()); + } + + return sb.ToString(); + } + + public override void Render(StringBuilder sb) + { + throw new NotImplementedException(); + } + + internal override void GetBounds(out double il, out double it, out double ir, out double ib) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/ParagraphItem.cs b/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/ParagraphItem.cs index 745a9426c..67f5cf360 100644 --- a/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/ParagraphItem.cs +++ b/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/ParagraphItem.cs @@ -10,11 +10,13 @@ Date Author Change ************************************************************************************************* 27/11/2025 EPPlus Software AB EPPlus 9 *************************************************************************************************/ +using EPPlus.Export.ImageRenderer.Text; using EPPlus.Fonts.OpenType; using EPPlus.Fonts.OpenType.Utils; using OfficeOpenXml.Drawing; using OfficeOpenXml.Style; using System.Collections.Generic; +using System.Text; namespace EPPlusImageRenderer.RenderItems.Shared { diff --git a/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/ShapeItem.cs b/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/ShapeItem.cs new file mode 100644 index 000000000..3bf365b19 --- /dev/null +++ b/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/ShapeItem.cs @@ -0,0 +1,414 @@ +using EPPlus.Export.ImageRenderer.Text; +using EPPlus.Fonts.OpenType; +using EPPlusImageRenderer; +using EPPlusImageRenderer.RenderItems; +using EPPlusImageRenderer.ShapeDefinitions; +using EPPlusImageRenderer.Text; +using OfficeOpenXml.Drawing; +using OfficeOpenXml.Style; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using TypeConv = OfficeOpenXml.Utils.TypeConversion; + +namespace EPPlus.Export.ImageRenderer.RenderItems.Shared +{ + /// + /// Base shapeItem class for file-specific classes to inherit from + /// + internal abstract class ShapeItem : DrawingShape + { + SvgRenderRectItem _renderTextBox; + TextBox textBox; + + internal ShapeItem(ExcelShape shape) : base(shape) + { + var style = shape.Style; + + if (style == eShapeStyle.CustomShape) + { + _renderTextBox = null; + foreach (var path in shape.CustomGeom.DrawingPaths) + { + AddFromPaths(path); + } + } + else + { + var shapeDef = PresetShapeDefinitions.ShapeDefinitions[style].Clone(); + shapeDef.Calculate(shape); + + //RenderItems.Add(new SvgRenderPathItem(shape)); + RenderItems.Add(new SvgGroupItem(shapeDef.GetTransform(shape.Rotation))); + + //Draw Filled path's + foreach (var path in shapeDef.ShapePaths) + { + if (path.Fill != PathFillMode.None) + { + AddFromPaths(path, true, false); + } + } + + //Draw border path's + foreach (var path in shapeDef.ShapePaths) + { + if (path.Stroke) + { + AddFromPaths(path, false, true); + } + } + + //Position text + if (_shape.Text != null) + { + GenerateTextItems(shapeDef); + } + } + } + + private void LoadTextBox() + { + textBox.VerticalAlignment = _shape.TextAnchoring; + textBox.WrapText = _shape.TextBody.WrapText != eTextWrappingType.None; + var fontMeasurer = (FontMeasurerTrueType)_shape._drawings._package.Settings.TextSettings.GenericTextMeasurerTrueType; + + //Make width the doc width if meant to overflow + if (_shape.TextBody.HorizontalTextOverflow == eTextHorizontalOverflow.Overflow) + { + textBox.Width = Size.Width; + } + + string color = "#" + GetFontColor(); + textBox.fontColor = color; + + var totalText = _shape.Text; + List> charAdvanceWidths = new List>(); + var txtRunIndicies = LineFormatter.GetTextRunIndiciesAndWidths(_shape.TextBody, out charAdvanceWidths); + + //Paragraph level begins + foreach (var paragraph in _shape.TextBody.Paragraphs) + { + var svgParagraph = textBox.ImportParagraph(paragraph); + } + } + + protected void AddFromPaths(DrawingPath path, bool drawFill = true, bool drawBorder = true) + { + var pi = new SvgRenderPathItem(_shape); + var coordinates = new List(); + PathCommands cmd = null; + PathsBase pCmd = null; + double cx = 0, cy = 0; + foreach (var p in path.Paths) + { + switch (p.Type) + { + case PathDrawingType.MoveTo: + AddCmd(pi, path, coordinates, ref cmd, pCmd, p, PathCommandType.Move); + break; + case PathDrawingType.LineTo: + AddCmd(pi, path, coordinates, ref cmd, pCmd, p, PathCommandType.Line); + break; + case PathDrawingType.CubicBezierTo: + AddCmd(pi, path, coordinates, ref cmd, pCmd, p, PathCommandType.CubicBézier); + break; + case PathDrawingType.QuadBezierTo: + AddCmd(pi, path, coordinates, ref cmd, pCmd, p, PathCommandType.QuadraticBézier); + break; + case PathDrawingType.ArcTo: + SetCmdCoordinats(cmd, p, coordinates); + AddArc(pi, path, coordinates, pCmd, out cx, out cy, p); + cmd = null; + break; + case PathDrawingType.Close: + if (pi.Commands[pi.Commands.Count - 1].Type != PathCommandType.Arc) + { + pi.Commands[pi.Commands.Count - 1].Coordinates = coordinates.ToArray(); + coordinates.Clear(); + } + pi.Commands.Add(new PathCommands(PathCommandType.End, pi)); + cmd = null; + break; + } + pCmd = p; + } + if (coordinates.Count > 0) + { + pi.Commands[pi.Commands.Count - 1].Coordinates = coordinates.ToArray(); + } + if (drawFill) + { + pi.FillColorSource = path.Fill; + pi.SetDrawingPropertiesFill(_shape.Fill, _shape.ThemeStyles.FillReference.Color); + } + else + { + pi.FillColorSource = PathFillMode.None; + pi.FillColor = "none"; + } + + if (drawBorder) + { + pi.BorderColorSource = path.Stroke ? PathFillMode.Norm : PathFillMode.None; + pi.SetDrawingPropertiesBorder(_shape.Border, _shape.ThemeStyles.BorderReference.Color, path.Stroke); + } + else + { + pi.BorderColorSource = PathFillMode.None; + pi.BorderColor = "none"; + } + + RenderItems.Add(pi); + } + public string ViewBox + { + get + { + double l = 0, t = 0, r = 1, b = 1; + foreach (var item in RenderItems) + { + item.GetBounds(out var il, out var it, out var ir, out var ib); + if (il < l) + { + l = il; + } + if (it < t) + { + t = it; + } + if (ir > r) + { + r = ir; + } + if (ib > b) + { + b = ib; + } + } + return $"{(l * Size.Width).ToString(CultureInfo.InvariantCulture)},{(t * Size.Height).ToString(CultureInfo.InvariantCulture)},{((Math.Abs(l) + r) * Size.Width).ToString(CultureInfo.InvariantCulture)},{((Math.Abs(t) + b) * Size.Height).ToString(CultureInfo.InvariantCulture)}"; + } + } + + public override RenderItemType Type => throw new NotImplementedException(); + + private string GetFontColor() + { + string color; + + if (_shape.Font.Fill.Style == eFillStyle.SolidFill) + { + var c = TypeConv.ColorConverter.GetThemeColor(_shape.Font.Fill.SolidFill.Color); + color = ((uint)c.ToArgb()).ToString("x").Substring(2, 6); + } + else + { + var c = TypeConv.ColorConverter.GetThemeColor(_wb.ThemeManager.CurrentTheme, _shape.ThemeStyles.FontReference.Color); + color = ((uint)c.ToArgb()).ToString("x").Substring(2, 6); + } + + return color; + } + + TextBox GetTextBox() + { + if (_renderTextBox != null) + { + return new TextBox(_shape.TextBody, _renderTextBox); + } + else + { + GetShapeInnerBound(out double x, out double y, out double width, out double height); + return new TextBox(x, y, width, height); + } + } + + private void GetFontNameAndSize(ExcelFont nsFont, out string fontName, out double fontSize) + { + fontName = string.IsNullOrEmpty(_shape.Font.LatinFont) ? _shape.Font.ComplexFont : _shape.Font.LatinFont; + + fontSize = _shape.Font.Size; + if (string.IsNullOrEmpty(fontName)) fontName = nsFont?.Name ?? _theme.FontScheme.MajorFont.First().Typeface; + if (fontSize <= 0 && nsFont != null) fontSize = nsFont.Size; + } + + private void RenderText(StringBuilder sb) + { + RenderDebugTextBox(sb); + textBox.RenderParagraphs(sb); + } + + private void RenderDebugTextBox(StringBuilder sb) + { + _renderTextBox.FillColor = "green"; + _renderTextBox.Render(sb); + + var area = textBox.GetTextArea(); + + _renderTextBox.X = (float)area.Left; + _renderTextBox.Y = (float)area.Top; + _renderTextBox.Width = (float)area.Width; + _renderTextBox.Height = (float)area.Height; + _renderTextBox.FillColor = "blue"; + _renderTextBox.Render(sb); + } + + private void GetShapeInnerBound(out double x, out double y, out double width, out double height) + { + double currentX = 0, currentY = 0, xe, ye; + x = y = 0; + width = xe = Size.Width; + height = ye = Size.Height; + foreach (var ri in RenderItems) + { + switch (ri.Type) + { + case RenderItemType.Rect: + var rectItem = (SvgRenderRectItem)ri; + x = rectItem.X; + y = rectItem.Y; + width = rectItem.Width; + height = rectItem.Height; + break; + case RenderItemType.Path: + var pathItem = (SvgRenderPathItem)ri; + foreach (var cmd in pathItem.Commands) + { + var cmdCoordinates = new List(); + for (int i = 0; i < cmd.Coordinates.Length; i++) + { + switch (cmd.Type) + { + case PathCommandType.Move: + if (i == 0) + { + currentX = cmd.Coordinates[i]; + currentY = cmd.Coordinates[++i]; + } + else + { + HandleLine(ref currentX, ref currentY, ref xe, ref ye, cmd, cmdCoordinates, ref i); + } + break; + case PathCommandType.VerticalLine: + HandleVertical(y, ref currentY, ref xe, cmd.Coordinates[i]); + break; + case PathCommandType.HorizontalLine: + HandleHorizontal(x, ref currentX, ref xe, cmd.Coordinates[i]); + break; + case PathCommandType.Line: + HandleLine(ref currentX, ref currentY, ref xe, ref ye, cmd, cmdCoordinates, ref i); + break; + case PathCommandType.CubicBézier: + if (currentX > x) + { + x = currentX; + } + if (currentY > y) + { + y = currentY; + } + i += 4; + break; + + } + } + } + break; + } + } + if (xe != double.MinValue) + { + width = xe - x; + } + if (ye != double.MinValue) + { + height = ye - y; + } + } + + private static void HandleLine(ref double currentX, ref double currentY, ref double xe, ref double ye, PathCommands cmd, List cmdCoordinates, ref int i) + { + xe = cmd.Coordinates[i]; + ye = cmd.Coordinates[++i]; + if (xe == currentX || ye == currentY) + { + if (cmdCoordinates.Count == 0) + { + cmdCoordinates.Add(new Coordinate(currentX, currentY)); + } + cmdCoordinates.Add(new Coordinate(xe, ye)); + } + else + { + var w = Math.Abs(xe - currentX); + var h = Math.Abs(ye - currentY); + cmdCoordinates.Add(new Coordinate((Math.Min(xe, currentX) + w) / 2, (Math.Min(ye, currentY) + h) / 2)); + } + currentX = xe; + currentY = ye; + } + + + private static void HandleVertical(double y, ref double currentY, ref double ye, double yec) + { + if (currentY < y || currentY == double.MinValue) + { + currentY = y; + } + if (ye > yec || ye == double.MinValue) + { + ye = yec; + } + } + private static void HandleHorizontal(double x, ref double currentX, ref double xe, double xec) + { + if (currentX < x || currentX == double.MinValue) + { + currentX = x; + } + if (xe > xec || xe == double.MinValue) + { + xe = xec; + } + } + + private void GenerateTextItems(ShapeDefinition shapeDef) + { + if (shapeDef.TextBoxRect != null) + { + if (_shape.TextBody.TextAutofit != eTextAutofit.ShapeAutofit) + { + var rectItem = new SvgRenderRectItem(_shape); + + rectItem.X = (float)shapeDef.TextBoxRect.LeftValue; + rectItem.Y = (float)shapeDef.TextBoxRect.TopValue; + rectItem.Width = (float)shapeDef.TextBoxRect.RightValue - rectItem.X; + rectItem.Height = (float)shapeDef.TextBoxRect.BottomValue - rectItem.Y; + rectItem.FillOpacity = 0.3d; + _renderTextBox = rectItem; + } + else + { + var rectItem = new SvgRenderRectItem(_shape); + + rectItem.X = (float)shapeDef.TextBoxRect.LeftValue; + rectItem.Y = (float)shapeDef.TextBoxRect.TopValue; + rectItem.Width = (float)shapeDef.TextBoxRect.RightValue; + rectItem.Height = (float)shapeDef.TextBoxRect.BottomValue; + rectItem.FillOpacity = 0.3d; + _renderTextBox = rectItem; + } + } + else + { + _renderTextBox = null; + } + + textBox = GetTextBox(); + LoadTextBox(); + } + } +} diff --git a/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/TextBody.cs b/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/TextBody.cs index 471b9a3b2..e63ee403a 100644 --- a/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/TextBody.cs +++ b/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/TextBody.cs @@ -1,32 +1,166 @@ using EPPlus.Export.ImageRenderer.Text; using EPPlus.Fonts.OpenType; +using EPPlus.Fonts.OpenType.Utils; using EPPlus.Graphics; +using EPPlusImageRenderer.RenderItems; +using OfficeOpenXml.Drawing; +using OfficeOpenXml.FormulaParsing.Excel.Functions.MathFunctions; +using System; using System.Collections.Generic; +using System.Linq; +using System.Text; namespace EPPlus.Export.ImageRenderer.RenderItems.Shared { - internal class TextBody : FontWrapContainer + /// + /// Margin left = X + /// Margin right = Y + /// + internal class TextBody : RenderItem { - internal List Runs = new List(); + internal eTextAnchoringType VerticalAlignment = eTextAnchoringType.Top; + + internal List Paragraphs = new List(); public bool AllowOverflow; - public TextBody(FontMeasurerTrueType txtMeasurer, BoundingBox parent, bool initDefaults = true) : base(txtMeasurer, initDefaults) + private FontMeasurerTrueType _measurer = null; + + public TextBody(BoundingBox parent) : base() + { + Bounds.Parent = parent; + + Bounds.Width = parent.Width; + Bounds.Height = parent.Height; + } + + public void AddParagraph(string text, FontMeasurerTrueType measurer) { - transform.Parent = parent.transform; + if(_measurer == null && measurer != null) + { + _measurer = measurer; + } + + if (_measurer != null) + { + var paragraph = new ParagraphContainer(Bounds); + + Paragraphs.Add(paragraph); + + paragraph.AddText(text, measurer); + + paragraph.Bounds.transform.Name = $"Container{Paragraphs.Count}"; + return; + } + throw new NullReferenceException($"The FontMeasurer: {_measurer} object is null. Use SetMeasurer before adding text"); } + //public void ImportParagraph(ExcelDrawingParagraph item) + //{ + // var posY = GetAlignmentVertical(); - public void AddText(string text) + // SetDrawingPropertiesFill(item.DefaultRunProperties.Fill); + + // var measureFont = item.DefaultRunProperties.GetMeasureFont(); + // bool isFirst = Paragraphs.Count == 0; + // new ParagraphContainer(_measurer, Bounds); + //} + + public void ImportTextBody(ExcelTextBody body) { - var container = new FontWrapContainer(measurer, true); - container.transform.Parent = transform; + foreach (var paragraph in body.Paragraphs) + { - Runs.Add(container); + } + } + + public string GetContent() + { + return Paragraphs[0].GetContent(); + //string.Join(Paragraphs[}]) + } - container.transform.Name = $"Container{Runs.Count}"; + public void AddText(string text, FontMeasurerTrueType measurer) + { + if (Paragraphs.Count <= 0) + { + AddParagraph(text, measurer); + } + else + { + Paragraphs.Last().AddText(text, measurer); + } + } - container.SetContent(text); + public void SetMeasurer(FontMeasurerTrueType fontMeasurer) + { + _measurer = fontMeasurer; + } + + /// + /// Same as X or Left + /// + internal double LeftMargin { get { return Bounds.Left; } set { Bounds.Left = value; } } + + /// + /// Same as Y or Top + /// + internal double TopMargin { get { return Bounds.Y; } set { Bounds.Y = value; } } + + double _rightMargin = 0; + + /// + /// Setting this property also changes the width of the textbody + /// + internal double RightMargin { get { return _rightMargin; } set { _rightMargin = value; Bounds.Width = Bounds.Parent.Width - value; } } + + double _bottomMargin = 0; + /// + /// Setting this property also changes the height of the textbody + /// + internal double BottomMargin { get { return _bottomMargin; } set { _bottomMargin = value; Bounds.Height = Bounds.Parent.Height - value; } } + + public override RenderItemType Type => throw new NotImplementedException(); + + double? _alignmentY = null; + + /// + /// Get the start of text space vertically + /// + /// + /// + private double GetAlignmentVertical() + { + double alignmentY = 0; + + switch (VerticalAlignment) + { + case eTextAnchoringType.Top: + alignmentY = Bounds.Y; + break; + case eTextAnchoringType.Center: + var adjustedHeight = Bounds.Y + (Bounds.Height / 2); + + alignmentY = adjustedHeight; + break; + case eTextAnchoringType.Bottom: + alignmentY = Bounds.Height; + break; + } + + _alignmentY = alignmentY; + + return _alignmentY.Value; + } + + internal override void GetBounds(out double il, out double it, out double ir, out double ib) + { + il = Bounds.Left; it = Bounds.Top; ir = Bounds.Right; ib = Bounds.Bottom; + } + + public override void Render(StringBuilder sb) + { + //No rendering in generic position and styling class } } } diff --git a/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/TextRunItem.cs b/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/TextRunItem.cs index c31a6428a..3eddb65ec 100644 --- a/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/TextRunItem.cs +++ b/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/TextRunItem.cs @@ -16,13 +16,12 @@ Date Author Change using EPPlus.Fonts.OpenType.Utils; using System.Collections.Generic; using System.Linq; +using EPPlus.Graphics; namespace EPPlusImageRenderer.RenderItems.Shared { - internal abstract class TextRunItem + internal abstract class TextRunItem : RenderItem { - internal RectBase BoundingBox = new RectBase(); - internal Coordinate origin = new Coordinate(0,0); internal ExcelParagraphTextRunBase TextRun; @@ -47,6 +46,8 @@ internal TextRunItem(ExcelParagraphTextRunBase textRun) FontName = measurementFont.FontFamily; + //textRun.Paragraph._paragraphs.FirstDefaultRunProperties.sty + //textRun.Fill //double fontSize = TextRun.FontSize < 0 || TextRun.FontSize == minValueParagraphSize ? defaultFontSize : TextRun.FontSize; double fontSize = measurementFont.Size; @@ -69,7 +70,7 @@ internal TextRunItem(ExcelParagraphTextRunBase textRun) //public override SvgItemType Type => SvgItemType.Rect; - internal void GetBounds(out double il, out double it, out double ir, out double ib) + internal override void GetBounds(out double il, out double it, out double ir, out double ib) { il = origin.X; it = origin.Y; @@ -77,10 +78,10 @@ internal void GetBounds(out double il, out double it, out double ir, out double ir = CalculateRightPositionInPixels(); ib = CalculateBottomPositionInPixels(); - BoundingBox.Left = il; - BoundingBox.Top = it; - BoundingBox.Right = ir; - BoundingBox.Bottom = ib; + Bounds.Left = il; + Bounds.Top = it; + Bounds.Right = ir; + Bounds.Bottom = ib; } internal double CalculateBottomPositionInPixels() diff --git a/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/TextRunRenderItem.cs b/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/TextRunRenderItem.cs new file mode 100644 index 000000000..53a4d9022 --- /dev/null +++ b/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/TextRunRenderItem.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace EPPlus.Export.ImageRenderer.RenderItems.Shared +{ + internal class TextRunRenderItem + { + } +} diff --git a/src/EPPlus.Export.ImageRenderer/RenderItems/SvgGroupItem.cs b/src/EPPlus.Export.ImageRenderer/RenderItems/SvgGroupItem.cs index 0cd89a7da..0a628db47 100644 --- a/src/EPPlus.Export.ImageRenderer/RenderItems/SvgGroupItem.cs +++ b/src/EPPlus.Export.ImageRenderer/RenderItems/SvgGroupItem.cs @@ -17,7 +17,7 @@ namespace EPPlusImageRenderer.RenderItems { internal class SvgGroupItem : SvgRenderItem { - public override SvgItemType Type => SvgItemType.Group; + public override RenderItemType Type => RenderItemType.Group; public string GroupTransform = ""; diff --git a/src/EPPlus.Export.ImageRenderer/RenderItems/SvgRenderEllipseItem.cs b/src/EPPlus.Export.ImageRenderer/RenderItems/SvgRenderEllipseItem.cs index c2f982254..b60c3a6f7 100644 --- a/src/EPPlus.Export.ImageRenderer/RenderItems/SvgRenderEllipseItem.cs +++ b/src/EPPlus.Export.ImageRenderer/RenderItems/SvgRenderEllipseItem.cs @@ -27,7 +27,7 @@ public SvgRenderEllipseItem(ExcelDrawing drawing) : base(drawing) public float Rx { get; set; } public float Ry { get; set; } - public override SvgItemType Type => SvgItemType.Rect; + public override RenderItemType Type => RenderItemType.Rect; public override void Render(StringBuilder sb) { diff --git a/src/EPPlus.Export.ImageRenderer/RenderItems/SvgRenderLineItem.cs b/src/EPPlus.Export.ImageRenderer/RenderItems/SvgRenderLineItem.cs index 5f7933ae3..1c692276c 100644 --- a/src/EPPlus.Export.ImageRenderer/RenderItems/SvgRenderLineItem.cs +++ b/src/EPPlus.Export.ImageRenderer/RenderItems/SvgRenderLineItem.cs @@ -26,7 +26,7 @@ public SvgRenderLineItem(ExcelDrawing drawing) : base(drawing) public float Y1 { get; set; } public float X2 { get; set; } public float Y2 { get; set; } - public override SvgItemType Type => SvgItemType.Line; + public override RenderItemType Type => RenderItemType.Line; public override void Render(StringBuilder sb) { diff --git a/src/EPPlus.Export.ImageRenderer/RenderItems/SvgRenderPathItem.cs b/src/EPPlus.Export.ImageRenderer/RenderItems/SvgRenderPathItem.cs index 09ff1fa89..4a9f53e71 100644 --- a/src/EPPlus.Export.ImageRenderer/RenderItems/SvgRenderPathItem.cs +++ b/src/EPPlus.Export.ImageRenderer/RenderItems/SvgRenderPathItem.cs @@ -23,7 +23,7 @@ public SvgRenderPathItem(ExcelDrawing drawing) : base(drawing) { } - public override SvgItemType Type { get => SvgItemType.Path; } + public override RenderItemType Type { get => RenderItemType.Path; } public List Commands { get; set; } = new List(); internal string TransformOffset = ""; diff --git a/src/EPPlus.Export.ImageRenderer/RenderItems/SvgRenderRectItem.cs b/src/EPPlus.Export.ImageRenderer/RenderItems/SvgRenderRectItem.cs index a34dcde86..b659dd320 100644 --- a/src/EPPlus.Export.ImageRenderer/RenderItems/SvgRenderRectItem.cs +++ b/src/EPPlus.Export.ImageRenderer/RenderItems/SvgRenderRectItem.cs @@ -42,7 +42,7 @@ public SvgRenderRectItem(ExcelDrawing drawing) : base(drawing) /// double paragraphStartPosY = 0; - public override SvgItemType Type => SvgItemType.Rect; + public override RenderItemType Type => RenderItemType.Rect; public override void Render(StringBuilder sb) { diff --git a/src/EPPlus.Export.ImageRenderer/RenderItems/TextRunRenderItem.cs b/src/EPPlus.Export.ImageRenderer/RenderItems/TextRunRenderItem.cs index 23f3766d4..d9df40d8e 100644 --- a/src/EPPlus.Export.ImageRenderer/RenderItems/TextRunRenderItem.cs +++ b/src/EPPlus.Export.ImageRenderer/RenderItems/TextRunRenderItem.cs @@ -18,7 +18,7 @@ internal abstract class TextRunRenderItem : RenderItem /// BoundingBox /// internal BoundingBox boundingBox; - public override SvgItemType Type => SvgItemType.Text; + public override RenderItemType Type => RenderItemType.Text; internal readonly string originalText; private string currentText; diff --git a/src/EPPlus.Export.ImageRenderer/Svg/SvgChart.cs b/src/EPPlus.Export.ImageRenderer/Svg/SvgChart.cs index fdf7cd8c2..cb6fe7192 100644 --- a/src/EPPlus.Export.ImageRenderer/Svg/SvgChart.cs +++ b/src/EPPlus.Export.ImageRenderer/Svg/SvgChart.cs @@ -62,6 +62,9 @@ public SvgChart(ExcelChart chart) : base(chart) internal SvgChartTitle VerticalAxisTitle { get; set; } internal SvgChartTitle HorizontalAxisTitle { get; set; } internal SvgChartTitle SecondHorizontalAxisTitle { get; set; } + + public override RenderItemType Type => throw new System.NotImplementedException(); + private void AddPlotArea() { if (HorizontalAxis!=null) RenderItems.AddRange(HorizontalAxis?.RenderItems); @@ -93,7 +96,7 @@ public override void Render(StringBuilder sb) foreach (var item in RenderItems) { item.Render(sb); - if (item.Type == SvgItemType.Group && gItemTest == null) + if (item.Type == RenderItemType.Group && gItemTest == null) { gItemTest = (SvgGroupItem)item; } @@ -108,5 +111,10 @@ public override void Render(StringBuilder sb) } sb.Append(""); } + + internal override void GetBounds(out double il, out double it, out double ir, out double ib) + { + throw new System.NotImplementedException(); + } } } \ No newline at end of file diff --git a/src/EPPlus.Export.ImageRenderer/Svg/SvgChartAxis.cs b/src/EPPlus.Export.ImageRenderer/Svg/SvgChartAxis.cs index 92aa12d3b..9111b979f 100644 --- a/src/EPPlus.Export.ImageRenderer/Svg/SvgChartAxis.cs +++ b/src/EPPlus.Export.ImageRenderer/Svg/SvgChartAxis.cs @@ -73,10 +73,18 @@ public List Values private set; } public SvgChartTitle AxisTitle { get; } + + public override RenderItemType Type => throw new System.NotImplementedException(); + public override void Render(StringBuilder sb) { AxisTitle?.Render(sb); Rectangle.Render(sb); } + + internal override void GetBounds(out double il, out double it, out double ir, out double ib) + { + throw new System.NotImplementedException(); + } } } \ No newline at end of file diff --git a/src/EPPlus.Export.ImageRenderer/Svg/SvgChartLegend.cs b/src/EPPlus.Export.ImageRenderer/Svg/SvgChartLegend.cs index 40b3520c6..5acf89ea6 100644 --- a/src/EPPlus.Export.ImageRenderer/Svg/SvgChartLegend.cs +++ b/src/EPPlus.Export.ImageRenderer/Svg/SvgChartLegend.cs @@ -207,7 +207,14 @@ public override void Render(StringBuilder sb) } } + internal override void GetBounds(out double il, out double it, out double ir, out double ib) + { + throw new System.NotImplementedException(); + } + public List SeriesIcon { get; } = new List(); + + public override RenderItemType Type => throw new System.NotImplementedException(); } internal class SvgLegendSerie { diff --git a/src/EPPlus.Export.ImageRenderer/Svg/SvgChartPlotarea.cs b/src/EPPlus.Export.ImageRenderer/Svg/SvgChartPlotarea.cs index 83c0d9b38..587c33727 100644 --- a/src/EPPlus.Export.ImageRenderer/Svg/SvgChartPlotarea.cs +++ b/src/EPPlus.Export.ImageRenderer/Svg/SvgChartPlotarea.cs @@ -18,6 +18,8 @@ namespace EPPlusImageRenderer.Svg { internal class SvgChartPlotarea : SvgChartObject { + public override RenderItemType Type => throw new System.NotImplementedException(); + public SvgChartPlotarea(SvgChart sc) : base(sc.Chart) { Rectangle = GetPlotAreaRectangle(sc); @@ -51,5 +53,10 @@ public override void Render(StringBuilder sb) { Rectangle.Render(sb); } + + internal override void GetBounds(out double il, out double it, out double ir, out double ib) + { + throw new System.NotImplementedException(); + } } } diff --git a/src/EPPlus.Export.ImageRenderer/Svg/SvgChartTitle.cs b/src/EPPlus.Export.ImageRenderer/Svg/SvgChartTitle.cs index e1b640f54..fcd3ecd0f 100644 --- a/src/EPPlus.Export.ImageRenderer/Svg/SvgChartTitle.cs +++ b/src/EPPlus.Export.ImageRenderer/Svg/SvgChartTitle.cs @@ -102,7 +102,7 @@ private void InitTextBox(ExcelChartTitleStandard t, string defaultText) { foreach (var p in t.TextBody.Paragraphs) { - TextBox.AddParagraph(p); + TextBox.ImportParagraph(p); } } else @@ -115,10 +115,18 @@ public TextBox TextBox { get; private set; } + + public override RenderItemType Type => throw new System.NotImplementedException(); + public override void Render(StringBuilder sb) { Rectangle.Render(sb); TextBox.Render(sb); } + + internal override void GetBounds(out double il, out double it, out double ir, out double ib) + { + throw new System.NotImplementedException(); + } } } \ No newline at end of file diff --git a/src/EPPlus.Export.ImageRenderer/Svg/SvgParagraph.cs b/src/EPPlus.Export.ImageRenderer/Svg/SvgParagraph.cs index 6a7cb82dd..704665812 100644 --- a/src/EPPlus.Export.ImageRenderer/Svg/SvgParagraph.cs +++ b/src/EPPlus.Export.ImageRenderer/Svg/SvgParagraph.cs @@ -48,7 +48,7 @@ public override void Render(StringBuilder sb) sb.Append(""); } - public override SvgItemType Type => SvgItemType.Text; + public override RenderItemType Type => RenderItemType.Text; internal override SvgRenderItem Clone(SvgShape svgDocument) { diff --git a/src/EPPlus.Export.ImageRenderer/Svg/SvgShape.cs b/src/EPPlus.Export.ImageRenderer/Svg/SvgShape.cs index 1a3b30698..ac80234ba 100644 --- a/src/EPPlus.Export.ImageRenderer/Svg/SvgShape.cs +++ b/src/EPPlus.Export.ImageRenderer/Svg/SvgShape.cs @@ -33,13 +33,9 @@ internal class SvgShape : DrawingShape { SvgRenderRectItem _renderTextBox; TextBox textBox; - private ExcelTheme _theme; - ExcelWorkbook _wb; public SvgShape(ExcelShape shape) : base(shape) { - _wb = shape._drawings.Worksheet.Workbook; - _theme = _wb.ThemeManager.GetOrCreateTheme(); var style = shape.Style; if(style==eShapeStyle.CustomShape) @@ -151,7 +147,7 @@ private void LoadTextBox() //Paragraph level begins foreach (var paragraph in _shape.TextBody.Paragraphs) { - var svgParagraph = textBox.AddParagraph(paragraph); + var svgParagraph = textBox.ImportParagraph(paragraph); } } @@ -252,9 +248,11 @@ public string ViewBox } } + public override RenderItemType Type => throw new NotImplementedException(); + public override void Render(StringBuilder sb) { - sb.Append($""); + sb.Append($""); //Write defs used for gradient colors var writer = new SvgDrawingWriter(this); @@ -264,7 +262,7 @@ public override void Render(StringBuilder sb) foreach(var item in RenderItems) { item.Render(sb); - if(item.Type == SvgItemType.Group && gItemTest == null) + if(item.Type == RenderItemType.Group && gItemTest == null) { gItemTest = (SvgGroupItem)item; } @@ -352,14 +350,14 @@ private void GetShapeInnerBound(out double x, out double y, out double width, ou { switch (ri.Type) { - case SvgItemType.Rect: + case RenderItemType.Rect: var rectItem = (SvgRenderRectItem)ri; x = rectItem.X; y = rectItem.Y; width = rectItem.Width; height = rectItem.Height; break; - case SvgItemType.Path: + case RenderItemType.Path: var pathItem = (SvgRenderPathItem)ri; foreach (var cmd in pathItem.Commands) { @@ -461,5 +459,10 @@ private static void HandleHorizontal(double x, ref double currentX, ref double x xe = xec; } } + + internal override void GetBounds(out double il, out double it, out double ir, out double ib) + { + throw new NotImplementedException(); + } } } \ No newline at end of file diff --git a/src/EPPlus.Export.ImageRenderer/Svg/SvgTextRun.cs b/src/EPPlus.Export.ImageRenderer/Svg/SvgTextRun.cs index 412c2f9a8..47e4658bb 100644 --- a/src/EPPlus.Export.ImageRenderer/Svg/SvgTextRun.cs +++ b/src/EPPlus.Export.ImageRenderer/Svg/SvgTextRun.cs @@ -367,7 +367,7 @@ internal void AdjustLineSpacing(double lineMultiplier) public RectBase textArea; - public override SvgItemType Type => SvgItemType.TSpan; + public override RenderItemType Type => RenderItemType.TSpan; public override void Render(StringBuilder sb) { diff --git a/src/EPPlus.Export.ImageRenderer/Text/FontWrapContainer.cs b/src/EPPlus.Export.ImageRenderer/Text/FontWrapContainer.cs index e6d8771ed..3d2c213f5 100644 --- a/src/EPPlus.Export.ImageRenderer/Text/FontWrapContainer.cs +++ b/src/EPPlus.Export.ImageRenderer/Text/FontWrapContainer.cs @@ -18,8 +18,6 @@ internal class FontWrapContainer : TextContainerBase { protected FontMeasurerTrueType measurer; - Rect Parent = null; - /// /// 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 diff --git a/src/EPPlus.Export.ImageRenderer/Text/SvgTextRenderItem.cs b/src/EPPlus.Export.ImageRenderer/Text/SvgTextRenderItem.cs index f1096c02d..b9bc75c0c 100644 --- a/src/EPPlus.Export.ImageRenderer/Text/SvgTextRenderItem.cs +++ b/src/EPPlus.Export.ImageRenderer/Text/SvgTextRenderItem.cs @@ -11,7 +11,7 @@ internal class SvgTextRenderItem : SvgRenderItem { - public override SvgItemType Type => throw new NotImplementedException(); + public override RenderItemType Type => throw new NotImplementedException(); internal override SvgRenderItem Clone(SvgShape svgDocument) { diff --git a/src/EPPlus.Export.ImageRenderer/Text/TextBox.cs b/src/EPPlus.Export.ImageRenderer/Text/TextBox.cs index 83ecd6000..b4448049e 100644 --- a/src/EPPlus.Export.ImageRenderer/Text/TextBox.cs +++ b/src/EPPlus.Export.ImageRenderer/Text/TextBox.cs @@ -46,7 +46,7 @@ internal double Height set; } - public override SvgItemType Type => SvgItemType.Rect; + public override RenderItemType Type => RenderItemType.Rect; /// /// Top of the paragraph bounding box @@ -188,7 +188,7 @@ public RectBase GetTextArea() return Bounds.GetInnerRect(); } - internal SvgParagraph AddParagraph(ExcelDrawingParagraph item) + internal SvgParagraph ImportParagraph(ExcelDrawingParagraph item) { var measureFont = item.DefaultRunProperties.GetMeasureFont(); diff --git a/src/EPPlus.Fonts.OpenType/TrueTypeMeasurer/DataHolders/TextParagraph.cs b/src/EPPlus.Fonts.OpenType/TrueTypeMeasurer/DataHolders/TextParagraph.cs index c0cc9c8b4..ea7f90f02 100644 --- a/src/EPPlus.Fonts.OpenType/TrueTypeMeasurer/DataHolders/TextParagraph.cs +++ b/src/EPPlus.Fonts.OpenType/TrueTypeMeasurer/DataHolders/TextParagraph.cs @@ -66,8 +66,7 @@ public TextParagraph(List textFragments, List fonts) AllText = string.Join(string.Empty, textFragments.ToArray()); //Get the indicies where newlines occur in the combined string - AllTextNewLineIndicies = GetStartIndicies(AllText); - + AllTextNewLineIndicies = GetFirstCharPositionOfNewLines(AllText); //Save minor information about each char so each char knows its line/fragment int charCount = 0; @@ -85,14 +84,20 @@ public TextParagraph(List textFragments, List fonts) //For each char in current fragment for (int j = 0; j < textFragment.Length; j++) { - if (charCount >= AllTextNewLineIndicies[lineIndex]) + if (lineIndex <= AllTextNewLineIndicies.Count - 1 && + charCount >= AllTextNewLineIndicies[lineIndex]) { + var text = AllText.Substring(lineStartCharIndex, charCount - lineStartCharIndex); + var trimmedText = text.Trim(['\r', '\n']); + var line = new TextLine() - { richTextIndicies = currFragments, - content = AllText.Substring(lineStartCharIndex, charCount - lineStartCharIndex), + { + richTextIndicies = currFragments, + content = trimmedText, startIndex = lineStartCharIndex, }; + lineStartCharIndex = AllTextNewLineIndicies[lineIndex]; lines.Add(line); currFragments.Clear(); lineIndex++; @@ -104,22 +109,41 @@ public TextParagraph(List textFragments, List fonts) } currFragments.Add(i); } - } - private List GetStartIndicies(string stringsCombined) - { - List combinedStartIndicies = new List(); + //Add the last line - var strings = stringsCombined.Split([Environment.NewLine], StringSplitOptions.None); - TotalLength = 0; + var lastText = AllText.Substring(lineStartCharIndex, charCount - lineStartCharIndex); + var lastTrimmedText = lastText.Trim(['\r', '\n']); - for (int i = 0; i < strings.Count(); i++) + var lastLine = new TextLine() { - combinedStartIndicies.Add(strings[i].Length + TotalLength); - TotalLength += strings[i].Length; - } + richTextIndicies = currFragments, + content = lastTrimmedText, + startIndex = lineStartCharIndex, + }; + + lines.Add(lastLine); + currFragments.Clear(); + } - return combinedStartIndicies; + private List GetFirstCharPositionOfNewLines(string stringsCombined) + { + List positions = new List(); + for (int i = 0; i < stringsCombined.Count(); i++) + { + if (stringsCombined[i] == '\n') + { + if (i < stringsCombined.Length - 1) + { + positions.Add(i + 1); + } + else + { + positions.Add(i); + } + } + } + return positions; } public TextParagraph(List textFragment, List fontSizes, Dictionary fontIndexDict) diff --git a/src/EPPlus.Fonts.OpenType/TrueTypeMeasurer/TextData.cs b/src/EPPlus.Fonts.OpenType/TrueTypeMeasurer/TextData.cs index 21790ae53..3db7322d5 100644 --- a/src/EPPlus.Fonts.OpenType/TrueTypeMeasurer/TextData.cs +++ b/src/EPPlus.Fonts.OpenType/TrueTypeMeasurer/TextData.cs @@ -10,13 +10,14 @@ Date Author Change ************************************************************************************************* 10/07/2025 EPPlus Software AB EPPlus.Fonts.OpenType 1.0 *************************************************************************************************/ -using System.Collections.Generic; -using OfficeOpenXml.Interfaces.Drawing.Text; -using System.Linq; -using System; +using EPPlus.Fonts.OpenType.Tables.Cmap.Mappings; using EPPlus.Fonts.OpenType.Tables.Kern; using EPPlus.Fonts.OpenType.TrueTypeMeasurer; -using EPPlus.Fonts.OpenType.Tables.Cmap.Mappings; +using OfficeOpenXml.Interfaces.Drawing.Text; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml; namespace EPPlus.Fonts.OpenType { @@ -695,7 +696,7 @@ internal static void ConvertDesignUnits(OpenTypeFont origFont, double origSize, var factorTarget = ((double)targetFont.HeadTable.UnitsPerEm) / targetSize; - maxWidth = Convert.ToInt16(maxWidthInPoints * factorTarget); + maxWidth = maxWidthInPoints * factorTarget; lineWidth = Convert.ToInt16(lineWidthInPoints * factorTarget); wordWidth = Convert.ToInt16(wordWidthInPoints * factorTarget); } @@ -731,13 +732,19 @@ internal static List WrapMultipleTextFragments(TextParagraph paragraph, var fragmentIdx = charInfo.Fragment; //If we hit a pre-existing line break. Reset line and wordwidths - if (i >= paragraph.AllTextNewLineIndicies[currentLineIndex]) + var indexExists = paragraph.AllTextNewLineIndicies.Count() > currentLineIndex; + if(indexExists) { - lineWidth = 0; - wordWidth = 0; - leftOverLine = ""; - //prevLineEndIndex = i; - currentLineIndex++; + if (i >= paragraph.AllTextNewLineIndicies[currentLineIndex]) + { + var addedLine = leftOverLine.Trim(['\r', '\n']); + wrappedStrings.Add(addedLine); + lineWidth = 0; + wordWidth = 0; + leftOverLine = ""; + //prevLineEndIndex = i; + currentLineIndex++; + } } //If this char has a different font, do the neccesary conversions diff --git a/src/EPPlus.Graphics/BoundingBox.cs b/src/EPPlus.Graphics/BoundingBox.cs index 9b4f63509..aec6a0940 100644 --- a/src/EPPlus.Graphics/BoundingBox.cs +++ b/src/EPPlus.Graphics/BoundingBox.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System; namespace EPPlus.Graphics { @@ -9,6 +10,12 @@ internal class BoundingBox : Rect { internal Transform transform; + private BoundingBox _parent = null; + + internal BoundingBox Parent { get { return _parent; } set { _parent = value; transform.Parent = value.transform; } } + + bool ClampedToParent = false; + internal BoundingBox() : base() { transform = new Transform(); @@ -32,7 +39,7 @@ internal BoundingBox(double left, double top, double right, double bottom) : thi /// /// Y pos (min) /// - internal new double Top + internal override double Top { get { return transform.LocalPosition.Y; } set @@ -53,7 +60,7 @@ internal BoundingBox(double left, double top, double right, double bottom) : thi /// /// X pos (min) /// - internal new double Left + internal override double Left { get { return transform.LocalPosition.X; } set @@ -72,19 +79,55 @@ internal BoundingBox(double left, double top, double right, double bottom) : thi } } + /// + /// If @ClampedToParent is true will not set value beyond parent + /// + internal override double Bottom { get => base.Bottom; set => SetBottom(value); } + + /// + /// If @ClampedToParent is true will not set value beyond parent + /// + internal override double Right { get => base.Right; set => SetRight(value); } + + private void SetRight(double value) + { + if(ClampedToParent) + { + if(transform.Parent != null) + { + var newValue = System.Math.Min(value, Parent.Right); + base.Right = newValue; + } + } + base.Right = value; + } + + private void SetBottom(double value) + { + if (ClampedToParent) + { + if (transform.Parent != null) + { + var newValue = System.Math.Min(value, Parent.Bottom); + base.Bottom = newValue; + } + } + base.Bottom = value; + } + //Quick-access to underlying transform /// /// Local position X /// X-position from parent transform position /// - internal new double X { get { return Left; } set { Left = value; } } + internal override double X { get { return Left; } set { Left = value; } } /// /// Local position Y /// Y-position from parent transform position /// - internal new double Y { get { return Top; } set { Top = value; } } + internal override double Y { get { return Top; } set { Top = value; } } //Gets global position x and y internal double GlobalX { get { return transform.Position.X; } } diff --git a/src/EPPlus.Graphics/Rect.cs b/src/EPPlus.Graphics/Rect.cs index 1d0433650..67fb05fed 100644 --- a/src/EPPlus.Graphics/Rect.cs +++ b/src/EPPlus.Graphics/Rect.cs @@ -12,6 +12,8 @@ Date Author Change *************************************************************************************************/ using EPPlus.Graphics.Math; +using EPPlus.Graphics.Math; + namespace EPPlus.Graphics { //REname and move class? Inherit from transform? what do? @@ -39,22 +41,22 @@ internal Rect(double left, double top, double right, double bottom) : this() /// /// Y pos (min) /// - internal double Top; + internal virtual double Top { get; set; } /// /// Y pos (max) /// - internal double Bottom; + internal virtual double Bottom { get; set; } /// /// X pos (min) /// - internal double Left; + internal virtual double Left{ get; set; } /// /// X pos (max) /// - internal double Right; + internal virtual double Right { get; set; } /// /// Get or Set Width via the properties above @@ -90,12 +92,12 @@ internal double Height /// Local position X /// X-position from parent transform position /// - internal double X { get { return Left; } set { Left = value; } } + internal virtual double X { get { return Left; } set { Left = value; } } /// /// Local position Y /// Y-position from parent transform position /// - internal double Y { get { return Top; } set { Top = value; } } + internal virtual double Y { get { return Top; } set { Top = value; } } } }