Skip to content

Commit f5554b7

Browse files
authored
Fix/empty paragraph ln (#2207)
* Fixed scaling the textbox to text size for svg rendering * Added locks to remove racing multi-threading issues * Starting multi-line sizing operations * added fix for #2201 (preserveWhitSpace) * Start of line-handling§ * Fixed read/export svg for empty shape paragraphs * Merge conflict fix * Non-functional attempt * Imperfect but working richtext wrap * Highly-functional richtext-wrapping. But very slow * Removed duplicate openType font creation * Implemented wrapping into svgParagraph * Minor fix * Cleaned up solution * Further cleanup. Removed unused classes/faulty comment
1 parent fb8922b commit f5554b7

File tree

19 files changed

+551
-147
lines changed

19 files changed

+551
-147
lines changed

src/EPPlus.Export.ImageRenderer.Test/TestTextContainer.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public void TestNonStandardFontSizesMultiLineLargeFont()
7575
Assert.AreEqual(expectedHeight, Math.Round(container.Height, 0));
7676
}
7777

78-
[TestMethod, Ignore]
78+
[TestMethod]
7979
public void TestNonStandardFontSizesMultiLineLargeFontGoudyStout()
8080
{
8181
string content = "TextBox\r\na very long line2\r\nline3";
@@ -95,6 +95,7 @@ public void TestNonStandardFontSizesMultiLineLargeFontGoudyStout()
9595
var container = new TextContainer(content, mf, true, true);
9696
//0,072265625 * font size width correction for pixels
9797
//0,0442708333333333 * font size height correction for pixels
98+
Assert.AreEqual(expectedWidth, Math.Round(container.Width, 0));
9899
Assert.AreEqual(expectedHeight, Math.Round(container.Height, 0));
99100
}
100101
}

src/EPPlus.Export.ImageRenderer/Svg/SvgParagraph.cs

Lines changed: 92 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Date Author Change
1010
*************************************************************************************************
1111
27/11/2025 EPPlus Software AB EPPlus 9
1212
*************************************************************************************************/
13+
using EPPlus.Fonts.OpenType;
1314
using EPPlus.Fonts.OpenType.Utils;
1415
using EPPlusImageRenderer.RenderItems;
1516
using OfficeOpenXml.Drawing;
@@ -18,12 +19,15 @@ Date Author Change
1819
using System;
1920
using System.Collections.Generic;
2021
using System.Globalization;
22+
using System.Linq;
2123
using System.Text;
2224

2325
namespace EPPlusImageRenderer.Svg
2426
{
2527
internal class SvgParagraph : SvgRenderItem
2628
{
29+
int numLines = 0;
30+
2731
public override void Render(StringBuilder sb)
2832
{
2933
sb.Append("<text ");
@@ -134,16 +138,84 @@ public SvgParagraph(ExcelDrawingParagraph p, RectBase paragraphArea, string Vert
134138
GetBounds(out double l, out double t, out double r, out double b);
135139
var textMaxWidth = r - l;
136140

137-
paragraphHeight = p.GetParagraphHeightInPixels(fmtt, textMaxWidth.PixelToPoint());
138-
139141
lnType = p.LineSpacing.LineSpacingType;
140142

141143
foreach (var run in p.TextRuns)
142144
{
143145
AddTextRun(run, paragraphArea.Bottom, yPosition);
144146
}
147+
148+
if (p._paragraphs.WrapText == eTextWrappingType.Square)
149+
{
150+
List<string> textFragments = new List<string>();
151+
List<MeasurementFont> fonts = new List<MeasurementFont>();
152+
153+
foreach (var txtRun in p.TextRuns)
154+
{
155+
textFragments.Add(txtRun.Text);
156+
fonts.Add(txtRun.GetMeasurementFont());
157+
}
158+
159+
var trueTypeMeasurer = (FontMeasurerTrueType)fmtt;
160+
var maxWidthPoints = textMaxWidth.PixelToPoint();
161+
var svgLines = trueTypeMeasurer.WrapMultipleTextFragments(textFragments, fonts, maxWidthPoints);
162+
163+
numLines = svgLines.Count;
164+
165+
List<string> txtRunStrings = new List<string>();
166+
List<int> txtRunStartIndicies = new List<int>();
167+
List<int> txtRunEndIndicies = new List<int>();
168+
169+
int lastIndex = 0;
170+
for (int i = 0; i < TextRuns.Count; i++)
171+
{
172+
var txtString = TextRuns[i].originalText;
173+
txtRunStrings.Add(txtString);
174+
var indexOfRun = p.Text.IndexOf(txtString, lastIndex);
175+
txtRunStartIndicies.Add(indexOfRun);
176+
txtRunEndIndicies.Add(indexOfRun + txtString.Length);
177+
lastIndex = indexOfRun;
178+
}
179+
180+
List<int> lineIndicies = new List<int>();
181+
lastIndex = 0;
182+
183+
//Last line should be handled by paragraph handling
184+
for (int i = 0; i< svgLines.Count(); i++)
185+
{
186+
var txtString = svgLines[i];
187+
txtRunStrings.Add(txtString);
188+
var startIndex = p.Text.IndexOf(txtString, lastIndex);
189+
lastIndex = startIndex + txtString.Length;
190+
lineIndicies.Add(startIndex + txtString.Length);
191+
}
192+
193+
for (int i = 0; i < lineIndicies.Count; i++)
194+
{
195+
var lnBreakPosition = lineIndicies[i];
196+
for (int j = 0; j < txtRunEndIndicies.Count; j++)
197+
{
198+
var start = txtRunStartIndicies[j];
199+
var end = txtRunEndIndicies[j];
200+
201+
bool containsBreak = (start <= lnBreakPosition && lnBreakPosition < end);
202+
if (containsBreak)
203+
{
204+
var localLnBreakPosition = lnBreakPosition - start;
205+
TextRuns[j].InsertLineBreak(localLnBreakPosition);
206+
break;
207+
}
208+
}
209+
}
210+
}
211+
else
212+
{
213+
numLines = p.Text.Split(new string[] { Environment.NewLine }, StringSplitOptions.None).Count();
214+
}
145215
}
146216

217+
218+
147219
/// <summary>
148220
/// First paragraph must use different linespacing
149221
/// </summary>
@@ -257,6 +329,7 @@ internal void AddTextRun(ExcelParagraphTextRunBase txtRun, double clippingHeight
257329

258330
SvgTextRun textRun;
259331

332+
260333
if (TextRuns.Count == 0 && IsFirstParagraph == true)
261334
{
262335
textRun = new SvgTextRun(txtRun, lineSpacing, textMaxWidth, clippingHeight, XPos, yPosition, LineSpacingAscendantOnly);
@@ -278,27 +351,23 @@ internal void AddTextRun(ExcelParagraphTextRunBase txtRun, double clippingHeight
278351

279352
internal double GetBottomYPosition()
280353
{
281-
//int numberOfLines = 0;
282-
//foreach (var textRun in TextRuns)
283-
//{
284-
// numberOfLines += textRun.GetLineCount();
285-
//}
286-
287-
//double lineSpacingTotal;
288-
//if(IsFirstParagraph)
289-
//{
290-
// lineSpacingTotal = LineSpacingAscendantOnly + LineSpacing * (numberOfLines - 1);
291-
//}
292-
//else
293-
//{
294-
// lineSpacingTotal = LineSpacing * numberOfLines;
295-
//}
296-
297-
//heigh
298-
299-
//var totalY = ParagraphArea.Top + lineSpacingTotal;
300-
301-
return ParagraphArea.Top + paragraphHeight;
354+
double bottomY = 0;
355+
if (IsFirstParagraph)
356+
{
357+
bottomY = LineSpacingAscendantOnly + LineSpacing * (numLines - 1);
358+
}
359+
else
360+
{
361+
bottomY = LineSpacing * numLines;
362+
}
363+
return ParagraphArea.Top + bottomY;
364+
}
365+
366+
internal void CalculateTextWrapping(double maxWidth, MeasurementFont mFont, string fullParagraphText)
367+
{
368+
List<string> NewContentLines = new List<string>();
369+
fmtt.SetFont(mFont);
370+
var textWidth = fmtt.MeasureText(fullParagraphText, mFont);
302371
}
303372
}
304373
}

src/EPPlus.Export.ImageRenderer/Svg/SvgShape.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -133,12 +133,6 @@ private void LoadTextBox()
133133
//Paragraph level begins
134134
foreach (var paragraph in _shape.TextBody.Paragraphs)
135135
{
136-
//Do not render empty paragraphs
137-
if (paragraph.TextRuns.Count == 0)
138-
{
139-
continue;
140-
}
141-
142136
var svgParagraph = textBox.AddParagraph(paragraph);
143137
}
144138
}

src/EPPlus.Export.ImageRenderer/Svg/SvgTextRun.cs

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ namespace EPPlusImageRenderer.Svg
2828
internal class SvgTextRun : SvgRenderItem
2929
{
3030
double LineSpacingPerNewLine;
31-
double _yPosition;
31+
double _yPositionOriginal;
32+
double dyPos;
33+
double _yEndPos;
3234
double ClippingHeight = Double.NaN;
3335
double fontSizeInPixels;
3436
eTextAlignment horizontalTextAlignment;
@@ -46,7 +48,8 @@ internal class SvgTextRun : SvgRenderItem
4648
double BaselineSpacing;
4749

4850
string horizontalAttribute;
49-
string originalText;
51+
internal readonly string originalText;
52+
private string currentText;
5053

5154
double _xPosition;
5255

@@ -63,6 +66,7 @@ internal SvgTextRun(ExcelParagraphTextRunBase textRun, double lineSpacing, doubl
6366
{
6467
originalText = textRun.Text;
6568
Lines = SplitIntoLines(originalText);
69+
currentText = originalText;
6670

6771
measurementFont = textRun.GetMeasureFont();
6872

@@ -83,7 +87,8 @@ internal SvgTextRun(ExcelParagraphTextRunBase textRun, double lineSpacing, doubl
8387
BaselineSpacing = baselineLineSpacing;
8488

8589
_xPosition = xPosition;
86-
_yPosition = yPosition;
90+
_yPositionOriginal = yPosition;
91+
_yEndPos = yPosition;
8792

8893
//origin.X = xPosition;
8994
//origin.Y = yPosition;
@@ -183,7 +188,8 @@ internal SvgTextRun(string text, ExcelTextFont font, double lineSpacing, double
183188
BaselineSpacing = baselineLineSpacing;
184189

185190
_xPosition = xPosition;
186-
_yPosition = yPosition;
191+
_yPositionOriginal = yPosition;
192+
_yEndPos = yPosition;
187193

188194
//origin.X = xPosition;
189195
//origin.Y = yPosition;
@@ -341,7 +347,8 @@ internal SvgTextRun(ExcelRichText textRun, double lineSpacing, double textMaxX,
341347
BaselineSpacing = fmExact.GetBaseLine().PointToPixel(true);
342348

343349
_xPosition = xPosition;
344-
_yPosition = yPosition;
350+
_yPositionOriginal = yPosition;
351+
_yEndPos = yPosition;
345352

346353
fontSizeInPixels = ((double)mf.Size).PointToPixel(true);
347354

@@ -367,6 +374,7 @@ public override void Render(StringBuilder sb)
367374
{
368375
string finalString = "";
369376
bool useBaselineSpacing = double.IsNaN(BaselineSpacing) == false;
377+
Lines = SplitIntoLines(currentText);
370378

371379
foreach (var line in Lines)
372380
{
@@ -383,8 +391,8 @@ public override void Render(StringBuilder sb)
383391

384392
yIncrease = EPPlus.Fonts.OpenType.Utils.TextUtils.RoundToWhole(yIncrease);
385393

386-
_yPosition += yIncrease;
387-
if (Double.IsNaN(ClippingHeight) == false && _yPosition >= ClippingHeight)
394+
_yEndPos += yIncrease;
395+
if (Double.IsNaN(ClippingHeight) == false && _yEndPos >= ClippingHeight)
388396
{
389397
visibility = "display=\"none\"";
390398
}
@@ -424,7 +432,12 @@ private int GetNumberOfLines()
424432
{
425433
if (originalText != null)
426434
{
427-
SplitIntoLines(originalText);
435+
if(currentText == null)
436+
{
437+
currentText = originalText;
438+
}
439+
440+
SplitIntoLines(currentText);
428441
return Lines.Count;
429442
}
430443
else
@@ -512,5 +525,10 @@ internal void CalculateTextWrapping(double maxWidth)
512525
var newLines = textMesurer.MeasureAndWrapText(originalText, measurementFont, maxWidth);
513526
Lines = newLines;
514527
}
528+
529+
internal void InsertLineBreak(int insertPosition)
530+
{
531+
currentText = currentText.Insert(insertPosition, Environment.NewLine);
532+
}
515533
}
516534
}

src/EPPlus.Export.ImageRenderer/Text/LineFormatter.cs

Lines changed: 0 additions & 27 deletions
This file was deleted.

src/EPPlus.Export.ImageRenderer/Text/TextBox.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,11 +196,18 @@ internal SvgParagraph AddParagraph(ExcelDrawingParagraph item)
196196
bool isFirst = Paragraphs.Count == 0;
197197
var svgParagraph = new SvgParagraph(item, area, vertAlignAttribute, posY, isFirst);
198198

199+
//Set starting position for next paragraph
200+
paragraphStartPosY = svgParagraph.GetBottomYPosition();
201+
199202
svgParagraph.FillColor = string.IsNullOrEmpty(fontColor) ? item.DefaultRunProperties.Fill.Color.Name : fontColor;
200203

201-
paragraphStartPosY = svgParagraph.GetBottomYPosition();
204+
//Above we've calculated heights from empty paragraphs
205+
//But below we do not add them for rendering (As they would not have any visible effect anyway)
206+
if (item.TextRuns.Count != 0)
207+
{
208+
Paragraphs.Add(svgParagraph);
209+
}
202210

203-
Paragraphs.Add(svgParagraph);
204211
return svgParagraph;
205212
}
206213

src/EPPlus.Export.ImageRenderer/Text/TextLine.cs

Lines changed: 0 additions & 12 deletions
This file was deleted.

src/EPPlus.Fonts.OpenType.Tests/EPPlus.Fonts.OpenType.Tests.csproj

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
<Project Sdk="Microsoft.NET.Sdk">
2-
32
<PropertyGroup>
43
<TargetFramework>net8.0</TargetFramework>
54
<LangVersion>latest</LangVersion>
@@ -20,12 +19,6 @@
2019
<ProjectReference Include="..\EPPlus\EPPlus.csproj" />
2120
</ItemGroup>
2221

23-
<ItemGroup>
24-
<PackageReference Include="Microsoft.NET.Test.Sdk" />
25-
<PackageReference Include="MSTest.TestAdapter" />
26-
<PackageReference Include="MSTest.TestFramework" />
27-
</ItemGroup>
28-
2922
<ItemGroup>
3023
<None Update="Fonts\BIZUDGothic-Bold.ttf">
3124
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>

0 commit comments

Comments
 (0)