Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
11e29df
Drafting classes for better richtext handling
OssianEPPlus Apr 21, 2026
45df22c
Added complex dict for lookups
OssianEPPlus Apr 21, 2026
380da9b
Added substrings to linefrag. Clarified debug view
OssianEPPlus Apr 22, 2026
007a8f3
Successfully added func<> lookup in TextLinecollection
OssianEPPlus Apr 22, 2026
7c58862
Added richText interface+query collection fragment
OssianEPPlus Apr 22, 2026
cb4d6ec
Renamed LineFragments and made internal
OssianEPPlus Apr 22, 2026
2d4afa5
Finalized lineFragmentData as LineFragments
OssianEPPlus Apr 22, 2026
ecb675a
Added linenum method + cleanup
OssianEPPlus Apr 22, 2026
00a279a
Cleaned up unused classes + added comments
OssianEPPlus Apr 22, 2026
aa8845d
Fixed bug where every char has richtext while wrapping on space
OssianEPPlus Apr 27, 2026
4aefa27
Adjusted old test
OssianEPPlus Apr 27, 2026
cd2a5f3
Added original position index
OssianEPPlus Apr 30, 2026
9dffaaa
fixed merge conflict leftover
OssianEPPlus Apr 30, 2026
377e34b
fixed some issues but it remains 0
OssianEPPlus Apr 30, 2026
3599e9b
Bugged implementation of keeping track of rt and original indicies
OssianEPPlus May 4, 2026
3e3d951
Began implementing new Text Layout logic
OssianEPPlus May 4, 2026
162f71d
First TextParagraph passing basic test
OssianEPPlus May 4, 2026
f811655
Sequence equal success
OssianEPPlus May 4, 2026
2d1e9c8
Renaming TextParagraph->layoutSystem, SubParagraph-> paragraph
OssianEPPlus May 4, 2026
860cd37
Sequence equal when using runs all the way down
OssianEPPlus May 4, 2026
240006b
Fixed font-fallback issue
OssianEPPlus May 4, 2026
47b73a2
Ensure actual output is actually correct
OssianEPPlus May 4, 2026
e6e2c3b
Start of fixing overall index in SplitFragment
OssianEPPlus May 4, 2026
8b174d0
Fixed tracking of indicies in both variants
OssianEPPlus May 5, 2026
21d7308
Functional tests
OssianEPPlus May 5, 2026
208d6c4
Added extra tests
OssianEPPlus May 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions src/EPPlus.Export.ImageRenderer.Test/TextboxTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,24 @@ public void TextBoxVerification()

item.ExternalRenderItemsNoBounds.Add(txtBoxNotMaxed);

var sb = new StringBuilder();

item.Render(sb);
var svgString = sb.ToString();

//before we assumed we consider the space widths
var widthWithSpace = txtBox.TextBody.Paragraphs[0].SpaceWidthsPerLine[0] + txtBox.Width;


//Assert.AreEqual(36d, txtBox.TextBody.Width);
Assert.AreEqual(37.5d, txtBox.Width, 0.5);
Assert.AreEqual(37.5d, widthWithSpace, 0.5);
Assert.AreNotEqual(36d, txtBoxNotMaxed.Width);
Assert.AreEqual(16.29052734375d, txtBoxNotMaxed.Width);

var sb = new StringBuilder();
//var sb = new StringBuilder();

item.Render(sb);
var svgString = sb.ToString();
//item.Render(sb);
//var svgString = sb.ToString();

SaveTextFileToWorkbook($"svg\\StandAloneTextBox.svg", svgString);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ internal abstract class ParagraphItem : RenderItem

private double? _centerAdjustment = null;

internal List<double> SpaceWidthsPerLine = new List<double>();

public ParagraphItem(TextBodyItem textBody, DrawingBase renderer, BoundingBox parent) : base(renderer, parent)
{
ParentTextBody = textBody;
Expand Down Expand Up @@ -294,12 +296,16 @@ internal void AddLinesAndTextRuns(string textIfEmpty)
//START
var idxOfLargestLine = 0;
double widthOfLargestLine = lines[0].Width;
//SpaceWidthsPerLine.Add(lines[0].lastFontSpaceWidth);


for (int i = 1; i < lines.Count; i++)
{
if (lines[i].Width > widthOfLargestLine)
{
var ctrLineWidth = lines[i].GetWidthWithoutTrailingSpaces();
SpaceWidthsPerLine.Add(lines[i].lastFontSpaceWidth);

widthOfLargestLine = ctrLineWidth;
idxOfLargestLine = i;
}
Expand Down Expand Up @@ -345,7 +351,7 @@ internal void AddLinesAndTextRuns(string textIfEmpty)

foreach (var lineFragment in line.LineFragments)
{
var displayText = line.GetLineFragmentText(lineFragment);
var displayText = lineFragment.Text;

if (string.IsNullOrEmpty(textIfEmpty) == false)
{
Expand Down Expand Up @@ -408,12 +414,14 @@ private void AddLinesAndTextRuns(ExcelDrawingParagraph p, string textIfEmpty)
//START
var idxOfLargestLine = 0;
double widthOfLargestLine = lines[0].GetWidthWithoutTrailingSpaces();
//SpaceWidthsPerLine.Add(lines[0].lastFontSpaceWidth);

for (int i = 1; i < lines.Count; i++)
{
if (lines[i].Width > widthOfLargestLine)
{
var ctrLineWidth = lines[i].GetWidthWithoutTrailingSpaces();
SpaceWidthsPerLine.Add(lines[i].lastFontSpaceWidth);
widthOfLargestLine = ctrLineWidth;
idxOfLargestLine = i;
}
Expand Down Expand Up @@ -460,15 +468,16 @@ private void AddLinesAndTextRuns(ExcelDrawingParagraph p, string textIfEmpty)

foreach (var lineFragment in line.LineFragments)
{
var displayText = line.GetLineFragmentText(lineFragment);
var displayText = lineFragment.Text;

if (p.TextRuns.Count == 0 && string.IsNullOrEmpty(textIfEmpty) == false)
{
AddText(displayText, p.DefaultRunProperties, prevWidth);
}
else
{
AddRenderItemTextRun(p.TextRuns[lineFragment.RtFragIdx], displayText, prevWidth);
var idx = _newTextFragments.IndexOf(lineFragment.OriginalTextFragment);
AddRenderItemTextRun(p.TextRuns[idx], displayText, prevWidth);
}

TextRunItem runItem = Runs.Last();
Expand Down
85 changes: 82 additions & 3 deletions src/EPPlus.Export.Pdf.Tests/PdfTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,19 @@ Date Author Change
*************************************************************************************************
10/07/2025 EPPlus Software AB EPPlus.Fonts.OpenType 1.0
*************************************************************************************************/
using EPPlus.Export.Pdf;
using EPPlus.Export.Pdf.PdfLayout;
using EPPlus.Export.Pdf.PdfSettings;
using EPPlus.Export.Pdf.PdfSettings.PdfPageSizes;
using EPPlus.Fonts.OpenType;
using EPPlus.Fonts.OpenType.Integration;
using OfficeOpenXml.Interfaces.Drawing.Text;
using OfficeOpenXml.Style;
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using EPPlus.Export.Pdf;
using EPPlus.Export.Pdf.PdfSettings;
using EPPlus.Export.Pdf.PdfSettings.PdfPageSizes;

namespace EPPlusTest.PDF
{
Expand Down Expand Up @@ -125,5 +130,79 @@ public void TestWritePdf2()
ExcelPdf pedeef = new ExcelPdf(p.Workbook.Worksheets.First(), pageSettings);
pedeef.CreatePdf("c:\\epplustest\\pdf\\EmojiTest.pdf");
}

[TestMethod]
public void WritePrintAreas()
{
using var p = OpenTemplatePackage("PDFTest.xlsx");
var cell = p.Workbook.Worksheets[0].Cells["P118"];

List<TextFragment> TextFragments = GetTextFragments(cell.RichText);


var layout = OpenTypeFonts.GetTextLayoutEngineForFont(TextFragments[0].Font);

var TextLines = layout.WrapRichTextLineCollection(TextFragments, 51d);
////var ShapedTexts = new List<PdfShapedText>();
//for (int i = 0; i < TextFragments.Count; i++)
//{
// var tf = TextFragments[i];
// var layout = OpenTypeFonts.GetTextLayoutEngineForFont(TextFragments[i].Font);

// var TextLines = layout.WrapRichTextLineCollection(TextFragments, 51d);
//}

}

private static List<TextFragment> GetTextFragments(ExcelRichTextCollection RichTextCollection, PdfCellStyle cellStyle = null)
{
var textFragments = new List<TextFragment>();
bool bold = false, italic = false, underline = false, strike = false;
ExcelUnderLineType underLineType = ExcelUnderLineType.None;
if (cellStyle != null && cellStyle.dxfFont != null)
{
bold = cellStyle.dxfFont.Bold != null ? (bool)cellStyle.dxfFont.Bold : false;
italic = cellStyle.dxfFont.Italic != null ? (bool)cellStyle.dxfFont.Italic : false;
strike = cellStyle.dxfFont.Strike != null ? (bool)cellStyle.dxfFont.Strike : false;
underline = cellStyle.dxfFont.Underline != null;
underLineType = cellStyle.dxfFont.Underline != null ? (ExcelUnderLineType)cellStyle.dxfFont.Underline : ExcelUnderLineType.None;
}
for (int i = 0; i < RichTextCollection.Count; i++)
{
var rt = RichTextCollection[i];
var textFrag = new TextFragment();
textFrag.Font = new MeasurementFont();
textFrag.Text = rt.Text;

textFrag.Font.FontFamily = rt.FontName;
textFrag.Font.Size = rt.Size;

textFrag.RichTextOptions.IsBold = rt.Bold || bold;
textFrag.RichTextOptions.IsItalic = rt.Italic || italic;
//underline
//none : 12
//single : 13
//Double : 4
//accouting does not exsist
textFrag.RichTextOptions.UnderlineType = 12;
textFrag.RichTextOptions.UnderlineType = rt.UnderLineType == ExcelUnderLineType.Single ? 13 : textFrag.RichTextOptions.UnderlineType;
textFrag.RichTextOptions.UnderlineType = rt.UnderLineType == ExcelUnderLineType.Double ? 4 : textFrag.RichTextOptions.UnderlineType;
textFrag.RichTextOptions.StrikeType = rt.Strike || strike ? 2 : 1;
textFrag.RichTextOptions.SuperScript = rt.VerticalAlign == ExcelVerticalAlignmentFont.Superscript;
textFrag.RichTextOptions.SubScript = rt.VerticalAlign == ExcelVerticalAlignmentFont.Subscript;
textFrag.RichTextOptions.FontColor = rt.Color;

textFrag.Font.Style = (textFrag.RichTextOptions.IsBold ? MeasurementFontStyles.Bold : 0) |
(textFrag.RichTextOptions.IsItalic ? MeasurementFontStyles.Italic : 0) |
(textFrag.RichTextOptions.UnderlineType != 12 ? MeasurementFontStyles.Underline : 0) |
(textFrag.RichTextOptions.StrikeType > 1 ? MeasurementFontStyles.Strikeout : 0);


textFragments.Add(textFrag);
OpenTypeFonts.GetFontSubFamily(textFrag.Font.Style);
}

return textFragments;
}
}
}
117 changes: 117 additions & 0 deletions src/EPPlus.Fonts.OpenType.Tests/DataHolders/TextLineSimpleTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using EPPlus.Fonts.OpenType.Integration;
using EPPlus.Fonts.OpenType.TextShaping;
using EPPlus.Fonts.OpenType.Utils;
using OfficeOpenXml.Interfaces.Drawing.Text;
using OfficeOpenXml.Style;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EPPlus.Fonts.OpenType.Tests.DataHolders
{
[TestClass]
public class TextLineSimpleTests
{
[TestMethod]
public void TestLineFragmentAbstraction()
{
var maxSizePoints = Math.Round(300d, 0, MidpointRounding.AwayFromZero).PixelToPoint();

var fragments = GetTextFragments();

var layout = OpenTypeFonts.GetTextLayoutEngineForFont(fragments[0].Font);
var wrappedLines = layout.WrapRichTextLines(fragments, maxSizePoints);
var wrappedCollection = layout.WrapRichTextLineCollection(fragments, maxSizePoints);


}

[TestMethod]
public void TestLineFragmentSeeWhatLinesUseWhatRichText()
{
var maxSizePoints = Math.Round(300d, 0, MidpointRounding.AwayFromZero).PixelToPoint();

var fragments = GetTextFragments();

fragments[4].RichTextOptions.FontColor = Color.DarkRed;

var layout = OpenTypeFonts.GetTextLayoutEngineForFont(fragments[0].Font);

var wrappedLines = layout.WrapRichTextLines(fragments, maxSizePoints);
var wrappedCollection = layout.WrapRichTextLineCollection(fragments, maxSizePoints);

var lines = wrappedCollection.GetTextLinesThatUse(fragments[4]);
var specificFragments = wrappedCollection.GetLineFragmentsThatUse(fragments[4]);
var lineIndicies = wrappedCollection.GetLineNumbersThatUse(fragments[4]);

Assert.AreEqual(lines[0].LineFragments[1], specificFragments[0]);
Assert.AreEqual(fragments[4], wrappedCollection.LineFragments[6].OriginalTextFragment);
Assert.AreEqual(Color.DarkRed, wrappedCollection.LineFragments[6].OriginalTextFragment.RichTextOptions.FontColor);

var expectedArr = new int[] { 3, 4 };
expectedArr.SequenceCompareTo(lineIndicies);
}

List<TextFragment> GetTextFragments()
{
List<string> 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<MeasurementFont> fonts = new() { font1, font2, font3, font4, font5, font6 };
var fragments = new List<TextFragment>();

for (int i = 0; i < lstOfRichText.Count(); i++)
{
var currentFrag = new TextFragment() { Text = lstOfRichText[i], Font = fonts[i] };
fragments.Add(currentFrag);
}

return fragments;
}
}
}
Loading
Loading