From 11e29dffe71865f5a90a6700b95e483cfa7a3551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Tue, 21 Apr 2026 14:41:57 +0200 Subject: [PATCH 1/9] Drafting classes for better richtext handling --- .../DataHolders/TextLineSimpleTests.cs | 89 +++++++++++++++++ .../DataHolders/LineFragmentCollection.cs | 91 +++++++++++++++++ .../Integration/DataHolders/TextFragment.cs | 78 --------------- .../DataHolders/TextFragmentAdvanced.cs | 78 +++++++++++++++ ...n.cs => TextFragmentCollectionAdvanced.cs} | 0 .../TextFragmentCollectionSimple.cs | 2 + .../Integration/DataHolders/TextLine.cs | 17 ---- .../DataHolders/TextLineCollection.cs | 99 +++++++++++++++++++ .../Integration/DataHolders/TextLineSimple.cs | 54 +++++++++- .../DataHolders/TextLineVisualizer.cs | 45 +++++++++ .../Integration/TextLayoutEngine.RichText.cs | 2 + 11 files changed, 456 insertions(+), 99 deletions(-) create mode 100644 src/EPPlus.Fonts.OpenType.Tests/DataHolders/TextLineSimpleTests.cs create mode 100644 src/EPPlus.Fonts.OpenType/Integration/DataHolders/LineFragmentCollection.cs delete mode 100644 src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragment.cs create mode 100644 src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentAdvanced.cs rename src/EPPlus.Fonts.OpenType/Integration/DataHolders/{TextFragmentCollection.cs => TextFragmentCollectionAdvanced.cs} (100%) delete mode 100644 src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLine.cs create mode 100644 src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs create mode 100644 src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineVisualizer.cs diff --git a/src/EPPlus.Fonts.OpenType.Tests/DataHolders/TextLineSimpleTests.cs b/src/EPPlus.Fonts.OpenType.Tests/DataHolders/TextLineSimpleTests.cs new file mode 100644 index 000000000..205857ef2 --- /dev/null +++ b/src/EPPlus.Fonts.OpenType.Tests/DataHolders/TextLineSimpleTests.cs @@ -0,0 +1,89 @@ +using EPPlus.Fonts.OpenType.Integration; +using EPPlus.Fonts.OpenType.TextShaping; +using EPPlus.Fonts.OpenType.Utils; +using OfficeOpenXml.Interfaces.Drawing.Text; +using System; +using System.Collections.Generic; +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 line1 = wrappedLines[0]; + } + + List GetTextFragments() + { + 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); + } + + return fragments; + } + } +} diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/LineFragmentCollection.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/LineFragmentCollection.cs new file mode 100644 index 000000000..2e22dec58 --- /dev/null +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/LineFragmentCollection.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace EPPlus.Fonts.OpenType.Integration.DataHolders +{ + public class LineFragmentCollection : List + { + private string _text; + + public string FullText + { + get { return _text; } + internal set { RichTextSubstrings.Clear(); _text = value; } + } + + internal List RichTextSubstrings { get; private set; } + + + + public LineFragmentCollection(string originalText) + { + FullText = originalText; + } + + //Note: Array size never intended to be larger than 2 + List StartEndPerFragment = new List(); + + + /// + /// Logs start and end idx per fragment + /// Then adds fragment as regular list + /// + /// + public new void Add(LineFragment fragment) + { + var endIdx = FullText.Length - 1; + if (Count != 0) + { + StartEndPerFragment.Last()[1] = fragment.StartIdx; + } + + StartEndPerFragment.Add(new int[] { fragment.StartIdx, endIdx}); + base.Add(fragment); + } + + public string GetLineFragmentText(LineFragment rtFragment) + { + if (this.Contains(rtFragment) == false) + { + throw new InvalidOperationException($"GetFragmentText failed. Cannot retrieve {rtFragment} since it is not part of this textLine: {this}"); + } + + if (string.IsNullOrEmpty(FullText)) + { + return FullText; + } + + var startIdx = rtFragment.StartIdx; + + var idxInLst = this.FindIndex(x => x == rtFragment); + if (idxInLst == this.Count - 1) + { + return FullText.Substring(startIdx, FullText.Length - startIdx); + } + else + { + var endIdx = this[idxInLst + 1].StartIdx; + return FullText.Substring(startIdx, endIdx - startIdx); + } + } + + internal void GenerateSubstrings() + { + RichTextSubstrings.Clear(); + + //for (int i = 0; i < StartEndPerFragment.Count; i++) + //{ + // var startIdx = StartEndPerFragment[i][0]; + // var endIdx = StartEndPerFragment[i][1]; + + // RichTextSubstrings.Add(FullText[1..5]); + //} + for (int i = 0; i < Count; i++) + { + RichTextSubstrings.Add(GetLineFragmentText(this[i])); + } + } + } +} diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragment.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragment.cs deleted file mode 100644 index 9627201cc..000000000 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragment.cs +++ /dev/null @@ -1,78 +0,0 @@ -//using System; -//using System.Collections.Generic; -//using System.Linq; -//using System.Text; - -//namespace EPPlus.Fonts.OpenType.Integration.DataHolders -//{ -// //TextFragment is a portion of a longer string -// //the fragment itself may contain line-breaks -// internal class TextFragment -// { -// /// -// /// Char index starting position for this fragment -// /// -// internal int StartPos; - -// internal List IsPartOfLines; -// internal string ThisFragment; -// internal List PointWidthPerOutputLineInFragment = new(); -// internal List PointYIncreasePerLine = new(); - -// List positionsToBreakAt = new(); - -// internal float FontSize { get; set; } = float.NaN; - -// internal TextFragment(string thisFragment, int startPos) -// { -// ThisFragment = thisFragment; -// StartPos = startPos; -// } - -// internal int GetEndPosition() -// { -// return StartPos + ThisFragment.Length; -// } - -// /// -// /// Line-breaks to be added after wrapping -// /// -// /// -// internal void AddLocalLbPositon(int pos) -// { -// var locaLnPos = pos - StartPos; -// positionsToBreakAt.Add(locaLnPos); -// } - -// internal string GetLineBreakFreeFragment() -// { -// string freeFragment = ThisFragment.Replace("\r", ""); -// freeFragment.Replace("\n", ""); -// return freeFragment; -// } - -// internal string GetWrappedFragment() -// { -// if (positionsToBreakAt.Count == 0) -// { -// return ThisFragment; -// } - -// string alteredFragment = ThisFragment; -// int deleteSpaceCount = 0; -// for (int i = 0; i < positionsToBreakAt.Count; i++) -// { -// var insertPosition = positionsToBreakAt[i] + i - deleteSpaceCount; -// alteredFragment = alteredFragment.Insert(insertPosition, Environment.NewLine); -// //Spaces after inserted newlines are to be removed -// if (alteredFragment[insertPosition + Environment.NewLine.Length] == ' ') -// { -// alteredFragment = alteredFragment.Remove(insertPosition + Environment.NewLine.Length, 1); -// deleteSpaceCount++; -// } -// } - -// return alteredFragment; -// } -// } -//} diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentAdvanced.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentAdvanced.cs new file mode 100644 index 000000000..2879bda9f --- /dev/null +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentAdvanced.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace EPPlus.Fonts.OpenType.Integration.DataHolders +{ + //TextFragment is a portion of a longer string + //the fragment itself may contain line-breaks + internal class TextFragmentAdvanced + { + /// + /// Char index starting position for this fragment + /// + internal int StartPos; + + internal List IsPartOfLines; + internal string ThisFragment; + internal List PointWidthPerOutputLineInFragment = new(); + internal List PointYIncreasePerLine = new(); + + List positionsToBreakAt = new(); + + internal float FontSize { get; set; } = float.NaN; + + internal TextFragmentAdvanced(string thisFragment, int startPos) + { + ThisFragment = thisFragment; + StartPos = startPos; + } + + internal int GetEndPosition() + { + return StartPos + ThisFragment.Length; + } + + /// + /// Line-breaks to be added after wrapping + /// + /// + internal void AddLocalLbPositon(int pos) + { + var locaLnPos = pos - StartPos; + positionsToBreakAt.Add(locaLnPos); + } + + internal string GetLineBreakFreeFragment() + { + string freeFragment = ThisFragment.Replace("\r", ""); + freeFragment.Replace("\n", ""); + return freeFragment; + } + + internal string GetWrappedFragment() + { + if (positionsToBreakAt.Count == 0) + { + return ThisFragment; + } + + string alteredFragment = ThisFragment; + int deleteSpaceCount = 0; + for (int i = 0; i < positionsToBreakAt.Count; i++) + { + var insertPosition = positionsToBreakAt[i] + i - deleteSpaceCount; + alteredFragment = alteredFragment.Insert(insertPosition, Environment.NewLine); + //Spaces after inserted newlines are to be removed + if (alteredFragment[insertPosition + Environment.NewLine.Length] == ' ') + { + alteredFragment = alteredFragment.Remove(insertPosition + Environment.NewLine.Length, 1); + deleteSpaceCount++; + } + } + + return alteredFragment; + } + } +} diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentCollection.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentCollectionAdvanced.cs similarity index 100% rename from src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentCollection.cs rename to src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentCollectionAdvanced.cs diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentCollectionSimple.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentCollectionSimple.cs index 1fe1a27af..ed9778360 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentCollectionSimple.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentCollectionSimple.cs @@ -16,5 +16,7 @@ public TextFragmentCollectionSimple(List fonts, List te Add(new TextFragment() { Font = fonts[i], Text = texts[i] }); } } + + } } diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLine.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLine.cs deleted file mode 100644 index dcc63f71e..000000000 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLine.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace EPPlus.Fonts.OpenType.Integration -{ - public class TextLine - { - internal List richTextIndicies; - internal string content; - internal int startIndex; - internal List rtContentStartIndexPerRt; - internal int lastRtInternalIndex; - internal int startRtInternalIndex; - } -} diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs new file mode 100644 index 000000000..4735defbc --- /dev/null +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs @@ -0,0 +1,99 @@ +using OfficeOpenXml.Interfaces.Drawing.Text; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; + + + +namespace EPPlus.Fonts.OpenType.Integration +{ + public class TextLineCollection : List, IEnumerable + { + + public List GetFragments() + { + List fragments = new List(); + + for (int i = 0; i< Lines.Count; i++) + { + var line = Lines[i]; + for (int j = 0; j< line.lineFragmentText.Count; j++) + { + var fragment = new TextFragment() + { + Text = line.lineFragmentText[j], + Font = GetFont(line.fragIds[j]), + }; + fragments.Add(fragment); + } + } + + return fragments; + } + + internal List Lines = new List(); + + List _originalFragments; + + internal MeasurementFont GetFont(int fragIdx) + { + return _originalFragments[fragIdx].Font; + } + + public TextLineCollection(List lines, List originalFragments) + { + _originalFragments = originalFragments; + + //foreach (var line in lines) + //{ + // foreach (var lf in line.LineFragments) + // { + // var text = line.GetLineFragmentText(lf); + // smallestTextFragments.Add(text); + // } + //} + + for(int i = 0; i < lines.Count; i++) + { + int lineNum = i; + Lines.Add(new TextLineVizualizer(this, lines[i], i)); + //foreach (var lf in _lines[i].LineFragments) + //{ + // var text = _lines[i].GetLineFragmentText(lf); + // var font = fonts[lf.RtFragIdx]; + // var details = _lines[i].LineFragments; + // //smallestTextFragments.Add(text); + //} + } + + //foreach (var line in _lines) + //{ + // //TextLineVizualizer visualizer = ne + // foreach (var lf in line.LineFragments) + // { + // var text = line.GetLineFragmentText(lf); + // var font = fonts[lf.RtFragIdx]; + // int lineNum + // //smallestTextFragments.Add(text); + // } + // //Lines.Add(new TextLineVizualizer(line)); + //} + } + + IEnumerator IEnumerable.GetEnumerator() + { + for (int i = 0; i < Lines.Count; i++) + { + yield return Lines[i].TextLineDetails; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return Lines.GetEnumerator(); + } + } +} + diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs index e98fe3b46..350268bfe 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs @@ -1,11 +1,14 @@ using EPPlus.Fonts.OpenType.Integration; +using EPPlus.Fonts.OpenType.Integration.DataHolders; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; namespace EPPlus.Fonts.OpenType.Integration { + [DebuggerTypeProxy(typeof(TextLineSimpleVizualizer))] public class TextLineSimple { public List LineFragments { get; internal set; } = new List(); @@ -43,9 +46,9 @@ public double GetWidthWithoutTrailingSpaces() var trailingSpaceCount = 0; - for(int i = Text.Count()-1; i > 0; i--) + for (int i = Text.Count() - 1; i > 0; i--) { - if(Text[i] != ' ') + if (Text[i] != ' ') { break; } @@ -53,7 +56,7 @@ public double GetWidthWithoutTrailingSpaces() trailingSpaceCount++; } - if(WasWrappedOnSpace) + if (WasWrappedOnSpace) { trailingSpaceCount++; } @@ -68,7 +71,7 @@ public TextLineSimple() public TextLineSimple(string text, double largestFontSize, double largestAscent, double largestDescent) { - Text = text; + LineFragments = new LineFragmentCollection(text); LargestFontSize = largestFontSize; LargestAscent = largestAscent; LargestDescent = largestDescent; @@ -111,4 +114,47 @@ internal LineFragment SplitAndGetLeftoverLineFragment(ref LineFragment origLf, d return newLineFragment; } } + + internal class TextLineSimpleVizualizer + { + public List Display + { + + get + { + List startIndices = new List(); + List lines = new List(); + + foreach(var fragment in _content.LineFragments ) + { + //startIndices.Add(fragment.StartIdx); + lines.Add(_content.GetLineFragmentText(fragment)+$" rtIdx:{fragment.RtFragIdx}"); + } + + //startIndices.Add(_content.Text.Length -1); + + //List lines = new List(); + + + //for (int i = 0; i < startIndices.Count -1; i++) + //{ + // var startidx = startIndices[i + 1]+1; + // var length = startidx - startIndices[i]; + // var substring = _content.Text.Substring(startIndices[i], length); + // lines.Add(substring); + //} + + + + return lines; + } + } + + private TextLineSimple _content; + + public TextLineSimpleVizualizer(TextLineSimple content) + { + _content = content; + } + } } diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineVisualizer.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineVisualizer.cs new file mode 100644 index 000000000..d59d14c86 --- /dev/null +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineVisualizer.cs @@ -0,0 +1,45 @@ +using OfficeOpenXml.Interfaces.Drawing.Text; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; + +namespace EPPlus.Fonts.OpenType.Integration +{ + + public class TextLineVizualizer + { + TextLineCollection _parentCollection; + internal TextLineSimple TextLineDetails; + int _lineNum; + + internal List lineFragmentText = new List(); + internal List fragIds = new List(); + + //internal MeasurementFont font + //{ get + // { + // return _parentCollection.GetFont(TextLineDetails.) + // } + //} + + public TextLineVizualizer(TextLineCollection parentCollection, TextLineSimple line, int lineNum) + { + _lineNum = lineNum; + TextLineDetails = line; + _parentCollection = parentCollection; + + foreach(var lf in line.LineFragments) + { + lineFragmentText.Add(line.GetLineFragmentText(lf)); + fragIds.Add(lf.RtFragIdx); + } + //var text = _lines[i].GetLineFragmentText(lf); + //var font = fonts[lf.RtFragIdx]; + //var details = _lines[i].LineFragments; + + } + } +} diff --git a/src/EPPlus.Fonts.OpenType/Integration/TextLayoutEngine.RichText.cs b/src/EPPlus.Fonts.OpenType/Integration/TextLayoutEngine.RichText.cs index e563926c1..2c862a627 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/TextLayoutEngine.RichText.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/TextLayoutEngine.RichText.cs @@ -132,6 +132,8 @@ public List WrapRichTextLines( _lineListBuffer.Add(string.Empty); } + + return state.Lines; } From 45df22ccbec56ac38711293cf7df1224efa6ed89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Tue, 21 Apr 2026 16:01:14 +0200 Subject: [PATCH 2/9] Added complex dict for lookups --- .../TextFragmentCollectionAdvanced.cs | 803 +++++++++--------- .../DataHolders/TextLineCollection.cs | 46 +- .../DataHolders/TextLineVisualizer.cs | 14 +- 3 files changed, 444 insertions(+), 419 deletions(-) diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentCollectionAdvanced.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentCollectionAdvanced.cs index cdd23b3bc..d849b8a74 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentCollectionAdvanced.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentCollectionAdvanced.cs @@ -1,388 +1,415 @@ -//using System; -//using System.Collections.Generic; -//using System.Linq; -//using System.Text; -//using System.Xml; - -//namespace EPPlus.Fonts.OpenType.Integration -//{ -// public class TextFragmentCollection -// { -// internal Dictionary CharLookup = new(); - -// internal List TextFragments = new(); - -// internal string AllText; -// internal List AllTextNewLineIndicies = new(); -// List _fragmentItems = new List(); -// List _lines = new List(); -// List outputFragments = new List(); -// //List outputStrings = new List(); -// internal List LargestFontSizePerLine { get; private set; } = new(); -// internal List AscentPerLine { get; private set; } = new(); -// internal List DescentPerLine { get; private set; } = new(); - -// /// -// /// Added linewidths in points -// /// -// internal List lineWidths { get; private set; } = new List(); - -// public List IndiciesToWrapAt {get; internal set; } - -// List TextLines = new(); - -// public TextFragmentCollection(List textFragments) -// { -// TextFragments = textFragments; -// IndiciesToWrapAt = new List(); - -// //Set data for each fragment and for AllText -// var currentTotalLength = 0; -// for (int i = 0; i < textFragments.Count; i++) -// { -// var currString = textFragments[i]; -// var fragment = new TextFragment(currString, currentTotalLength); -// currentTotalLength += currString.Length; -// _fragmentItems.Add(fragment); -// } - -// AllText = string.Join(string.Empty, textFragments.ToArray()); - -// //Get the indicies where newlines occur in the combined string -// AllTextNewLineIndicies = GetFirstCharPositionOfNewLines(AllText); - -// //Save minor information about each char so each char knows its line/fragment -// int charCount = 0; -// int lineIndex = 0; - -// List currFragments = new(); -// int lineStartCharIndex = 0; - -// //For each fragment -// for (int i = 0; i < TextFragments.Count; i++) -// { -// var textFragment = TextFragments[i]; - -// //For each char in current fragment -// for (int j = 0; j < textFragment.Length; j++) -// { -// 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 = trimmedText, -// startIndex = lineStartCharIndex, -// }; - -// lineStartCharIndex = AllTextNewLineIndicies[lineIndex]; -// _lines.Add(line); -// currFragments.Clear(); -// lineIndex++; -// } - -// var info = new CharInfo(charCount, i, lineIndex); -// CharLookup.Add(charCount, info); -// charCount++; -// } -// currFragments.Add(i); -// } - -// //Add the last line -// var lastText = AllText.Substring(lineStartCharIndex, charCount - lineStartCharIndex); -// var lastTrimmedText = lastText.Trim(['\r', '\n']); - -// var lastLine = new TextLine() -// { -// richTextIndicies = currFragments, -// content = lastTrimmedText, -// startIndex = lineStartCharIndex, -// }; - -// _lines.Add(lastLine); -// currFragments.Clear(); -// } - -// public TextFragmentCollection(List textFragments, List fontSizes) : this(textFragments) -// { -// if(textFragments.Count() != fontSizes.Count) -// { -// throw new InvalidOperationException($"TextFragment list and FontSizes list must be equal." + -// $"Counts:" + -// $"textFragment: {textFragments.Count()}" + -// $"fontSizes: {fontSizes.Count()}"); -// } - -// for(int i = 0; i< textFragments.Count; i++) -// { -// _fragmentItems[i].FontSize = fontSizes[i]; -// } -// } - -// private List GetFirstCharPositionOfNewLines(string stringsCombined) -// { -// List positions = new List(); -// for (int i = 0; i < stringsCombined.Length; i++) -// { -// if (stringsCombined[i] == '\n') -// { -// if (i < stringsCombined.Length - 1) -// { -// positions.Add(i + 1); -// } -// else -// { -// positions.Add(i); -// } -// } -// } -// return positions; -// } - -// internal void AddLargestFontSizePerLine(double fontSize) -// { -// LargestFontSizePerLine.Add(fontSize); -// } - -// internal void AddAscentPerLine(double Ascent) -// { -// AscentPerLine.Add(Ascent); -// } - -// internal void AddDescentPerLine(double Descent) -// { -// DescentPerLine.Add(Descent); -// } - -// internal void AddWrappingIndex(int index) -// { -// IndiciesToWrapAt.Add(index); -// } - -// public List GetFragmentsWithFinalLineBreaks() -// { -// //var newFragments = TextFragments.ToArray(); -// foreach(var i in IndiciesToWrapAt) -// { -// _fragmentItems[CharLookup[i].Fragment].AddLocalLbPositon(i); -// } - -// foreach(var fragment in _fragmentItems) -// { -// outputFragments.Add(fragment.GetWrappedFragment()); -// } - -// return outputFragments; -// } - -// public List GetOutputLines() -// { -// var fragments = GetFragmentsWithFinalLineBreaks(); - -// string outputAllText = ""; - -// //Create final all text -// for (int i = 0; i < fragments.Count(); i++) -// { -// outputAllText += fragments[i]; -// } - -// var newLineIndiciesOutput = GetFirstCharPositionOfNewLines(outputAllText); -// List outputTextLines = new List(); - -// //Save minor information about each char so each char knows its line/fragment -// int charCount = 0; -// int lineIndex = 0; - -// List currFragments = new(); -// int lineStartCharIndex = 0; -// int RtInternalStartIndex = 0; - -// //For each fragment -// for (int i = 0; i < fragments.Count; i++) -// { -// var textFragment = fragments[i]; - -// //For each char in current fragment -// for (int j = 0; j < textFragment.Length; j++) -// { -// if (lineIndex <= newLineIndiciesOutput.Count - 1 && -// charCount >= newLineIndiciesOutput[lineIndex]) -// { -// //We have reached a new line -// var text = outputAllText.Substring(lineStartCharIndex, charCount - lineStartCharIndex); -// var trimmedText = text.Trim(['\r', '\n']); - -// var line = new TextLine() -// { -// richTextIndicies = currFragments, -// content = trimmedText, -// startIndex = lineStartCharIndex, -// rtContentStartIndexPerRt = new List(j), -// lastRtInternalIndex = j, -// startRtInternalIndex = RtInternalStartIndex -// }; - -// lineStartCharIndex = newLineIndiciesOutput[lineIndex]; -// outputTextLines.Add(line); -// currFragments.Clear(); -// RtInternalStartIndex = j; -// lineIndex++; -// } - -// //var info = new CharInfo(charCount, i, lineIndex); -// //CharLookup.Add(charCount, info); -// charCount++; -// } -// currFragments.Add(i); -// } - -// //Add the last line -// var lastText = outputAllText.Substring(lineStartCharIndex, charCount - lineStartCharIndex); -// var lastTrimmedText = lastText.Trim(['\r', '\n']); - -// var lastLine = new TextLine() -// { -// richTextIndicies = currFragments, -// content = lastTrimmedText, -// startIndex = lineStartCharIndex, -// lastRtInternalIndex = fragments.Last().Length, -// startRtInternalIndex = RtInternalStartIndex -// }; - -// outputTextLines.Add(lastLine); -// currFragments.Clear(); - -// return outputTextLines; -// } - -// //public void GetTextFragmentOutputStartIndiciesOnFinalLines(List finalOutputLines) -// //{ -// // var lineBreakStrings = GetFragmentsWithFinalLineBreaks(); -// // //var fragmentsWithoutLineBreaks = GetFragmentsWithoutLineBreaks(); -// // string outputAllText = ""; - -// // //List> eachFragmentForEachLine = new List>(); - -// // for(int i = 0; i< finalOutputLines.Count(); i++) -// // { -// // var line = new TextLine(); -// // line.content = finalOutputLines[i]; - -// // } - -// // //Create final all text -// // for(int i = 0; i< lineBreakStrings.Count(); i++) -// // { - -// // ////var length = lineBreakStrings[i].Length; -// // //outputAllText += lineBreakStrings[i]; -// // } - - -// //} - -// //public List GetSimpleTextLines(List wrappedLines) -// //{ -// // //The wrapped lines -// // var lines = wrappedLines; -// // //The richText data in a form that is one to one in chars -// // var frags = GetFragmentsWithoutLineBreaks(); - -// // int fragIdx = 0; -// // int charIdx = 0; -// // int lastFragLength = 0; - -// // var currentFragment = frags[fragIdx]; - -// // for (int i = 0; i< wrappedLines.Count(); i++) -// // { -// // var textLineSimple = new TextLineSimple(); - -// // for (int j = 0; j < wrappedLines[i].Length; j++) -// // { -// // textLineSimple -// // charIdx++; -// // } -// // } -// //} - -// public List GetFragmentsWithoutLineBreaks() -// { -// ////var newFragments = TextFragments.ToArray(); -// //foreach (var i in IndiciesToWrapAt) -// //{ -// // _fragmentItems[CharLookup[i].Fragment].AddLocalLbPositon(i); -// //} - -// foreach (var fragment in _fragmentItems) -// { -// outputFragments.Add(fragment.GetLineBreakFreeFragment()); -// } - -// return outputFragments; -// } - -// internal void AddLineWidth(double width) -// { -// lineWidths.Add(width); -// } - -// /// -// /// Should arguably be done in constructor instead of created every time -// /// -// /// -// /// -// public List GetLargestFontSizesOfEachLine() -// { -// //List largestFontSizes = new List(); -// //foreach(var line in _lines) -// //{ -// // float largest = float.MinValue; -// // foreach(var idx in line.richTextIndicies) -// // { -// // if (float.IsNaN(_fragmentItems[idx].FontSize) == false && _fragmentItems[idx].FontSize > largest) -// // { -// // largest = _fragmentItems[idx].FontSize; -// // } -// // } -// // if (largest != float.MinValue) -// // { -// // largestFontSizes.Add(largest); -// // } -// //} -// //return largestFontSizes; -// return LargestFontSizePerLine; -// } - -// public double GetAscent(int lineIdx) -// { -// return AscentPerLine[lineIdx]; -// } - -// public double GetDescent(int lineIdx) -// { -// return DescentPerLine[lineIdx]; -// } - -// internal void AddFragmentWidth(int fragmentIdx, double width) -// { -// _fragmentItems[fragmentIdx].PointWidthPerOutputLineInFragment.Add(width); -// } - -// public List GetFragmentWidths(int fragmentIdx) -// { -// return _fragmentItems[fragmentIdx].PointWidthPerOutputLineInFragment; -// } - -// public List GetLinesFragmentIsPartOf(int fragmentIndex) -// { -// //Add throw if index does not exist? -// return _fragmentItems[fragmentIndex].IsPartOfLines; -// } -// } -//} +using EPPlus.Fonts.OpenType.Integration.DataHolders; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml; + +namespace EPPlus.Fonts.OpenType.Integration +{ + public class TextFragmentCollectionAdvanced + { + internal Dictionary CharLookup = new(); + + internal List TextFragments = new(); + + internal string AllText; + internal List AllTextNewLineIndicies = new(); + List _fragmentItems = new List(); + List _lines = new List(); + List outputFragments = new List(); + //List outputStrings = new List(); + internal List LargestFontSizePerLine { get; private set; } = new(); + internal List AscentPerLine { get; private set; } = new(); + internal List DescentPerLine { get; private set; } = new(); + + /// + /// Added linewidths in points + /// + internal List lineWidths { get; private set; } = new List(); + + public List IndiciesToWrapAt { get; internal set; } + + List TextLines = new(); + + public TextFragmentCollectionAdvanced(List textFragments) + { + TextFragments = textFragments; + IndiciesToWrapAt = new List(); + + //Set data for each fragment and for AllText + var currentTotalLength = 0; + for (int i = 0; i < textFragments.Count; i++) + { + var currString = textFragments[i]; + var fragment = new TextFragmentAdvanced(currString, currentTotalLength); + currentTotalLength += currString.Length; + _fragmentItems.Add(fragment); + } + + AllText = string.Join(string.Empty, textFragments.ToArray()); + + //Get the indicies where newlines occur in the combined string + AllTextNewLineIndicies = GetFirstCharPositionOfNewLines(AllText); + + //Save minor information about each char so each char knows its line/fragment + int charCount = 0; + int lineIndex = 0; + + List currFragments = new(); + int lineStartCharIndex = 0; + + //For each fragment + for (int i = 0; i < TextFragments.Count; i++) + { + var textFragment = TextFragments[i]; + + //For each char in current fragment + for (int j = 0; j < textFragment.Length; j++) + { + if (lineIndex <= AllTextNewLineIndicies.Count - 1 && + charCount >= AllTextNewLineIndicies[lineIndex]) + { + var text = AllText.Substring(lineStartCharIndex, charCount - lineStartCharIndex); + var trimmedText = text.Trim(['\r', '\n']); + + var line = new TextLineSimple(); + line.Text = trimmedText; + + foreach (var idx in currFragments) + { + var fragment = new LineFragment(i, idx); + } + + lineStartCharIndex = AllTextNewLineIndicies[lineIndex]; + _lines.Add(line); + currFragments.Clear(); + lineIndex++; + } + + var info = new CharInfo(charCount, i, lineIndex); + CharLookup.Add(charCount, info); + charCount++; + } + currFragments.Add(i); + } + + //Add the last line + var lastText = AllText.Substring(lineStartCharIndex, charCount - lineStartCharIndex); + var lastTrimmedText = lastText.Trim(['\r', '\n']); + + var lastLine = new TextLineSimple(); + lastLine.Text = lastTrimmedText; + + foreach (var idx in currFragments) + { + var fragment = new LineFragment(lineStartCharIndex, idx); + } + + _lines.Add(lastLine); + currFragments.Clear(); + } + + public TextFragmentCollectionAdvanced(List textFragments, List fontSizes) : this(textFragments) + { + if (textFragments.Count() != fontSizes.Count) + { + throw new InvalidOperationException($"TextFragment list and FontSizes list must be equal." + + $"Counts:" + + $"textFragment: {textFragments.Count()}" + + $"fontSizes: {fontSizes.Count()}"); + } + + for (int i = 0; i < textFragments.Count; i++) + { + _fragmentItems[i].FontSize = fontSizes[i]; + } + } + + private List GetFirstCharPositionOfNewLines(string stringsCombined) + { + List positions = new List(); + for (int i = 0; i < stringsCombined.Length; i++) + { + if (stringsCombined[i] == '\n') + { + if (i < stringsCombined.Length - 1) + { + positions.Add(i + 1); + } + else + { + positions.Add(i); + } + } + } + return positions; + } + + internal void AddLargestFontSizePerLine(double fontSize) + { + LargestFontSizePerLine.Add(fontSize); + } + + internal void AddAscentPerLine(double Ascent) + { + AscentPerLine.Add(Ascent); + } + + internal void AddDescentPerLine(double Descent) + { + DescentPerLine.Add(Descent); + } + + internal void AddWrappingIndex(int index) + { + IndiciesToWrapAt.Add(index); + } + + public List GetFragmentsWithFinalLineBreaks() + { + //var newFragments = TextFragments.ToArray(); + foreach (var i in IndiciesToWrapAt) + { + _fragmentItems[CharLookup[i].Fragment].AddLocalLbPositon(i); + } + + foreach (var fragment in _fragmentItems) + { + outputFragments.Add(fragment.GetWrappedFragment()); + } + + return outputFragments; + } + + public List GetOutputLines() + { + var fragments = GetFragmentsWithFinalLineBreaks(); + + string outputAllText = ""; + + //Create final all text + for (int i = 0; i < fragments.Count(); i++) + { + outputAllText += fragments[i]; + } + + var newLineIndiciesOutput = GetFirstCharPositionOfNewLines(outputAllText); + List outputTextLines = new List(); + + //Save minor information about each char so each char knows its line/fragment + int charCount = 0; + int lineIndex = 0; + + List currFragments = new(); + int lineStartCharIndex = 0; + int RtInternalStartIndex = 0; + + //For each fragment + for (int i = 0; i < fragments.Count; i++) + { + var textFragment = fragments[i]; + + //For each char in current fragment + for (int j = 0; j < textFragment.Length; j++) + { + if (lineIndex <= newLineIndiciesOutput.Count - 1 && + charCount >= newLineIndiciesOutput[lineIndex]) + { + //We have reached a new line + var text = outputAllText.Substring(lineStartCharIndex, charCount - lineStartCharIndex); + var trimmedText = text.Trim(['\r', '\n']); + + var line = new TextLineSimple(); + line.Text = trimmedText; + + foreach (var idx in currFragments) + { + var fragment = new LineFragment(i, idx); + } + + + //var line = new TextLine() + //{ + // richTextIndicies = currFragments, + // content = trimmedText, + // startIndex = lineStartCharIndex, + // rtContentStartIndexPerRt = new List(j), + // lastRtInternalIndex = j, + // startRtInternalIndex = RtInternalStartIndex + //}; + + lineStartCharIndex = newLineIndiciesOutput[lineIndex]; + outputTextLines.Add(line); + currFragments.Clear(); + RtInternalStartIndex = j; + lineIndex++; + } + + //var info = new CharInfo(charCount, i, lineIndex); + //CharLookup.Add(charCount, info); + charCount++; + } + currFragments.Add(i); + } + + //Add the last line + var lastText = AllText.Substring(lineStartCharIndex, charCount - lineStartCharIndex); + var lastTrimmedText = lastText.Trim(['\r', '\n']); + + var lastLine = new TextLineSimple(); + lastLine.Text = lastTrimmedText; + + foreach (var idx in currFragments) + { + var fragment = new LineFragment(lineStartCharIndex, idx); + } + + _lines.Add(lastLine); + currFragments.Clear(); + + ////Add the last line + //var lastText = outputAllText.Substring(lineStartCharIndex, charCount - lineStartCharIndex); + //var lastTrimmedText = lastText.Trim(['\r', '\n']); + + //var lastLine = new TextLine() + //{ + // richTextIndicies = currFragments, + // content = lastTrimmedText, + // startIndex = lineStartCharIndex, + // lastRtInternalIndex = fragments.Last().Length, + // startRtInternalIndex = RtInternalStartIndex + //}; + + //outputTextLines.Add(lastLine); + //currFragments.Clear(); + + return outputTextLines; + } + + //public void GetTextFragmentOutputStartIndiciesOnFinalLines(List finalOutputLines) + //{ + // var lineBreakStrings = GetFragmentsWithFinalLineBreaks(); + // //var fragmentsWithoutLineBreaks = GetFragmentsWithoutLineBreaks(); + // string outputAllText = ""; + + // //List> eachFragmentForEachLine = new List>(); + + // for(int i = 0; i< finalOutputLines.Count(); i++) + // { + // var line = new TextLine(); + // line.content = finalOutputLines[i]; + + // } + + // //Create final all text + // for(int i = 0; i< lineBreakStrings.Count(); i++) + // { + + // ////var length = lineBreakStrings[i].Length; + // //outputAllText += lineBreakStrings[i]; + // } + + + //} + + //public List GetSimpleTextLines(List wrappedLines) + //{ + // //The wrapped lines + // var lines = wrappedLines; + // //The richText data in a form that is one to one in chars + // var frags = GetFragmentsWithoutLineBreaks(); + + // int fragIdx = 0; + // int charIdx = 0; + // int lastFragLength = 0; + + // var currentFragment = frags[fragIdx]; + + // for (int i = 0; i< wrappedLines.Count(); i++) + // { + // var textLineSimple = new TextLineSimple(); + + // for (int j = 0; j < wrappedLines[i].Length; j++) + // { + // textLineSimple + // charIdx++; + // } + // } + //} + + public List GetFragmentsWithoutLineBreaks() + { + ////var newFragments = TextFragments.ToArray(); + //foreach (var i in IndiciesToWrapAt) + //{ + // _fragmentItems[CharLookup[i].Fragment].AddLocalLbPositon(i); + //} + + foreach (var fragment in _fragmentItems) + { + outputFragments.Add(fragment.GetLineBreakFreeFragment()); + } + + return outputFragments; + } + + internal void AddLineWidth(double width) + { + lineWidths.Add(width); + } + + /// + /// Should arguably be done in constructor instead of created every time + /// + /// + /// + public List GetLargestFontSizesOfEachLine() + { + //List largestFontSizes = new List(); + //foreach(var line in _lines) + //{ + // float largest = float.MinValue; + // foreach(var idx in line.richTextIndicies) + // { + // if (float.IsNaN(_fragmentItems[idx].FontSize) == false && _fragmentItems[idx].FontSize > largest) + // { + // largest = _fragmentItems[idx].FontSize; + // } + // } + // if (largest != float.MinValue) + // { + // largestFontSizes.Add(largest); + // } + //} + //return largestFontSizes; + return LargestFontSizePerLine; + } + + public double GetAscent(int lineIdx) + { + return AscentPerLine[lineIdx]; + } + + public double GetDescent(int lineIdx) + { + return DescentPerLine[lineIdx]; + } + + internal void AddFragmentWidth(int fragmentIdx, double width) + { + _fragmentItems[fragmentIdx].PointWidthPerOutputLineInFragment.Add(width); + } + + public List GetFragmentWidths(int fragmentIdx) + { + return _fragmentItems[fragmentIdx].PointWidthPerOutputLineInFragment; + } + + public List GetLinesFragmentIsPartOf(int fragmentIndex) + { + //Add throw if index does not exist? + return _fragmentItems[fragmentIndex].IsPartOfLines; + } + } +} diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs index 4735defbc..f4d4db88c 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs @@ -2,6 +2,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; @@ -9,6 +10,7 @@ namespace EPPlus.Fonts.OpenType.Integration { + [DebuggerDisplay("Lines = {Lines}")] public class TextLineCollection : List, IEnumerable { @@ -37,6 +39,15 @@ public List GetFragments() List _originalFragments; + /// + /// The id of the orginal fragment may correspond to + /// multiple lines with multiple different richtext fragments + /// So. fragIdLookup[fragId] returns dictionary of lines that contains the fragment + /// fragIdLookup[fragId][lineNum] returns list of output fragments that contain the font + /// fragIdLookup[fragId][lineNum][0] returns first richtextfragment within the line that contains the font. + /// + Dictionary>> fragIdLookup = new Dictionary>>(); + internal MeasurementFont GetFont(int fragIdx) { return _originalFragments[fragIdx].Font; @@ -46,40 +57,17 @@ public TextLineCollection(List lines, List origina { _originalFragments = originalFragments; - //foreach (var line in lines) - //{ - // foreach (var lf in line.LineFragments) - // { - // var text = line.GetLineFragmentText(lf); - // smallestTextFragments.Add(text); - // } - //} + for(int i = 0; i < originalFragments.Count; i++) + { + fragIdLookup.Add(i, new Dictionary>()); + } for(int i = 0; i < lines.Count; i++) { int lineNum = i; - Lines.Add(new TextLineVizualizer(this, lines[i], i)); - //foreach (var lf in _lines[i].LineFragments) - //{ - // var text = _lines[i].GetLineFragmentText(lf); - // var font = fonts[lf.RtFragIdx]; - // var details = _lines[i].LineFragments; - // //smallestTextFragments.Add(text); - //} - } - //foreach (var line in _lines) - //{ - // //TextLineVizualizer visualizer = ne - // foreach (var lf in line.LineFragments) - // { - // var text = line.GetLineFragmentText(lf); - // var font = fonts[lf.RtFragIdx]; - // int lineNum - // //smallestTextFragments.Add(text); - // } - // //Lines.Add(new TextLineVizualizer(line)); - //} + Lines.Add(new TextLineVizualizer(this, lines[i], i, ref fragIdLookup)); + } } IEnumerator IEnumerable.GetEnumerator() diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineVisualizer.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineVisualizer.cs index d59d14c86..c1d46f816 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineVisualizer.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineVisualizer.cs @@ -25,16 +25,26 @@ public class TextLineVizualizer // } //} - public TextLineVizualizer(TextLineCollection parentCollection, TextLineSimple line, int lineNum) + public TextLineVizualizer(TextLineCollection parentCollection, TextLineSimple line, int lineNum, + ref Dictionary>> fragmentLookup) { _lineNum = lineNum; TextLineDetails = line; _parentCollection = parentCollection; - + int fragCount = 0; foreach(var lf in line.LineFragments) { lineFragmentText.Add(line.GetLineFragmentText(lf)); fragIds.Add(lf.RtFragIdx); + + if (fragmentLookup[lf.RtFragIdx].ContainsKey(lineNum)== false) + { + fragmentLookup[lf.RtFragIdx].Add(lineNum, new List()); + } + + fragmentLookup[lf.RtFragIdx][lineNum].Add(fragCount); + + fragCount++; } //var text = _lines[i].GetLineFragmentText(lf); //var font = fonts[lf.RtFragIdx]; From 380da9baca8c144f7c6e3f0484b98cd276db3fa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Wed, 22 Apr 2026 11:00:35 +0200 Subject: [PATCH 3/9] Added substrings to linefrag. Clarified debug view --- .../RenderItems/Shared/ParagraphItem.cs | 2 +- .../DataHolders/TextLineSimpleTests.cs | 4 + .../TextFragmentCollectionAdvanced.cs | 2 +- .../DataHolders/TextLineCollection.cs | 52 ++++++++---- .../Integration/DataHolders/TextLineSimple.cs | 79 +++++++++++-------- .../DataHolders/TextLineVisualizer.cs | 37 ++++----- .../Integration/LineFragment.cs | 35 +++++++- .../Integration/TextLayoutEngine.RichText.cs | 14 ++-- .../Integration/WrapStateRichText.cs | 2 +- 9 files changed, 148 insertions(+), 79 deletions(-) diff --git a/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/ParagraphItem.cs b/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/ParagraphItem.cs index be029a5db..c709e37a0 100644 --- a/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/ParagraphItem.cs +++ b/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/ParagraphItem.cs @@ -468,7 +468,7 @@ private void AddLinesAndTextRuns(ExcelDrawingParagraph p, string textIfEmpty) } else { - AddRenderItemTextRun(p.TextRuns[lineFragment.RtFragIdx], displayText, prevWidth); + AddRenderItemTextRun(p.TextRuns[lineFragment.FragmentIndex], displayText, prevWidth); } TextRunItem runItem = Runs.Last(); diff --git a/src/EPPlus.Fonts.OpenType.Tests/DataHolders/TextLineSimpleTests.cs b/src/EPPlus.Fonts.OpenType.Tests/DataHolders/TextLineSimpleTests.cs index 205857ef2..f48500b79 100644 --- a/src/EPPlus.Fonts.OpenType.Tests/DataHolders/TextLineSimpleTests.cs +++ b/src/EPPlus.Fonts.OpenType.Tests/DataHolders/TextLineSimpleTests.cs @@ -24,6 +24,10 @@ public void TestLineFragmentAbstraction() var layout = OpenTypeFonts.GetTextLayoutEngineForFont(fragments[0].Font); var wrappedLines = layout.WrapRichTextLines(fragments, maxSizePoints); + var collection = new TextLineCollection(wrappedLines, fragments); + + var frags = collection.ezFrags; + var line1 = wrappedLines[0]; } diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentCollectionAdvanced.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentCollectionAdvanced.cs index d849b8a74..16501d1a7 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentCollectionAdvanced.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentCollectionAdvanced.cs @@ -103,7 +103,7 @@ public TextFragmentCollectionAdvanced(List textFragments) foreach (var idx in currFragments) { - var fragment = new LineFragment(lineStartCharIndex, idx); + var fragment = new LineFragment(idx, lineStartCharIndex); } _lines.Add(lastLine); diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs index f4d4db88c..849a4345f 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs @@ -10,9 +10,16 @@ namespace EPPlus.Fonts.OpenType.Integration { - [DebuggerDisplay("Lines = {Lines}")] + public struct ezFrag() + { + public string text; + public MeasurementFont font; + } + public class TextLineCollection : List, IEnumerable { + public ezFrag[] ezFrags; + public List individualFragments; public List GetFragments() { @@ -23,15 +30,26 @@ public List GetFragments() var line = Lines[i]; for (int j = 0; j< line.lineFragmentText.Count; j++) { - var fragment = new TextFragment() - { + var fragment = new TextFragment() + { Text = line.lineFragmentText[j], Font = GetFont(line.fragIds[j]), + AscentPoints = _originalFragments[j].AscentPoints, + DescentPoints = _originalFragments[j].DescentPoints }; fragments.Add(fragment); } } + ezFrags = new ezFrag[fragments.Count]; + + individualFragments = fragments; + + for (int i = 0; i < Lines.Count; i++) + { + ezFrags[i] = new ezFrag() { text = fragments[i].Text, font = fragments[i].Font }; + } + return fragments; } @@ -65,22 +83,24 @@ public TextLineCollection(List lines, List origina for(int i = 0; i < lines.Count; i++) { int lineNum = i; + int fragCount = 0; + foreach (var lf in lines[i].LineFragments) + { + //lineFragmentText.Add(line.GetLineFragmentText(lf)); + //fragIds.Add(lf.RtFragIdx); - Lines.Add(new TextLineVizualizer(this, lines[i], i, ref fragIdLookup)); - } - } + //if (fragmentLookup[lf.RtFragIdx].ContainsKey(lineNum) == false) + //{ + // fragmentLookup[lf.RtFragIdx].Add(lineNum, new List()); + //} - IEnumerator IEnumerable.GetEnumerator() - { - for (int i = 0; i < Lines.Count; i++) - { - yield return Lines[i].TextLineDetails; - } - } + //fragmentLookup[lf.RtFragIdx][lineNum].Add(fragCount); - IEnumerator IEnumerable.GetEnumerator() - { - return Lines.GetEnumerator(); + fragCount++; + } + Add(lines[i]); + } + GetFragments(); } } } diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs index 350268bfe..1e7e3aea2 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs @@ -8,7 +8,8 @@ namespace EPPlus.Fonts.OpenType.Integration { - [DebuggerTypeProxy(typeof(TextLineSimpleVizualizer))] + //[DebuggerTypeProxy(typeof(TextLineSimpleVizualizer))] + [DebuggerDisplay("{Text}")] public class TextLineSimple { public List LineFragments { get; internal set; } = new List(); @@ -77,6 +78,18 @@ public TextLineSimple(string text, double largestFontSize, double largestAscent, LargestDescent = largestDescent; } + /// + /// Inserts the relevant substrings directly into the line fragments + /// + internal void CreateFinalizedSubstringsInLineFragments() + { + foreach (var lineFragment in LineFragments) + { + var text = GetLineFragmentText(lineFragment); + lineFragment.SetFinalizedText(text); + } + } + public string GetLineFragmentText(LineFragment rtFragment) { if (LineFragments.Contains(rtFragment) == false) @@ -106,7 +119,7 @@ public string GetLineFragmentText(LineFragment rtFragment) 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); + var newLineFragment = new LineFragment(origLf.FragmentIndex, 0); newLineFragment.Width = origLf.Width - widthAtSplit; origLf.Width = widthAtSplit; @@ -115,46 +128,46 @@ internal LineFragment SplitAndGetLeftoverLineFragment(ref LineFragment origLf, d } } - internal class TextLineSimpleVizualizer - { - public List Display - { + //internal class TextLineSimpleVizualizer + //{ + // public List Display + // { - get - { - List startIndices = new List(); - List lines = new List(); + // get + // { + // List startIndices = new List(); + // List lines = new List(); - foreach(var fragment in _content.LineFragments ) - { - //startIndices.Add(fragment.StartIdx); - lines.Add(_content.GetLineFragmentText(fragment)+$" rtIdx:{fragment.RtFragIdx}"); - } + // foreach(var fragment in _content.LineFragments ) + // { + // //startIndices.Add(fragment.StartIdx); + // lines.Add(_content.GetLineFragmentText(fragment)+$" rtIdx:{fragment.RtFragIdx}"); + // } - //startIndices.Add(_content.Text.Length -1); + // //startIndices.Add(_content.Text.Length -1); - //List lines = new List(); + // //List lines = new List(); - //for (int i = 0; i < startIndices.Count -1; i++) - //{ - // var startidx = startIndices[i + 1]+1; - // var length = startidx - startIndices[i]; - // var substring = _content.Text.Substring(startIndices[i], length); - // lines.Add(substring); - //} + // //for (int i = 0; i < startIndices.Count -1; i++) + // //{ + // // var startidx = startIndices[i + 1]+1; + // // var length = startidx - startIndices[i]; + // // var substring = _content.Text.Substring(startIndices[i], length); + // // lines.Add(substring); + // //} - return lines; - } - } + // return lines; + // } + // } - private TextLineSimple _content; + // private TextLineSimple _content; - public TextLineSimpleVizualizer(TextLineSimple content) - { - _content = content; - } - } + // public TextLineSimpleVizualizer(TextLineSimple content) + // { + // _content = content; + // } + //} } diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineVisualizer.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineVisualizer.cs index c1d46f816..2b3a23494 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineVisualizer.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineVisualizer.cs @@ -12,7 +12,6 @@ namespace EPPlus.Fonts.OpenType.Integration public class TextLineVizualizer { TextLineCollection _parentCollection; - internal TextLineSimple TextLineDetails; int _lineNum; internal List lineFragmentText = new List(); @@ -25,27 +24,25 @@ public class TextLineVizualizer // } //} - public TextLineVizualizer(TextLineCollection parentCollection, TextLineSimple line, int lineNum, + public TextLineVizualizer(List fragments, int lineNum, ref Dictionary>> fragmentLookup) { - _lineNum = lineNum; - TextLineDetails = line; - _parentCollection = parentCollection; - int fragCount = 0; - foreach(var lf in line.LineFragments) - { - lineFragmentText.Add(line.GetLineFragmentText(lf)); - fragIds.Add(lf.RtFragIdx); - - if (fragmentLookup[lf.RtFragIdx].ContainsKey(lineNum)== false) - { - fragmentLookup[lf.RtFragIdx].Add(lineNum, new List()); - } - - fragmentLookup[lf.RtFragIdx][lineNum].Add(fragCount); - - fragCount++; - } + //_lineNum = lineNum; + //int fragCount = 0; + //foreach(var lf in fragments) + //{ + // lineFragmentText.Add(line.GetLineFragmentText(lf)); + // fragIds.Add(lf.RtFragIdx); + + // if (fragmentLookup[lf.RtFragIdx].ContainsKey(lineNum)== false) + // { + // fragmentLookup[lf.RtFragIdx].Add(lineNum, new List()); + // } + + // fragmentLookup[lf.RtFragIdx][lineNum].Add(fragCount); + + // fragCount++; + //} //var text = _lines[i].GetLineFragmentText(lf); //var font = fonts[lf.RtFragIdx]; //var details = _lines[i].LineFragments; diff --git a/src/EPPlus.Fonts.OpenType/Integration/LineFragment.cs b/src/EPPlus.Fonts.OpenType/Integration/LineFragment.cs index 26dbc439b..1bc9e8c77 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/LineFragment.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/LineFragment.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; @@ -8,20 +9,50 @@ namespace EPPlus.Fonts.OpenType.Integration /// /// The fragment of a richTextFragment that is within a line /// + [DebuggerDisplay("{Text}")] public class LineFragment { + /// + /// Start char position within TextLineSimple.Text + /// public int StartIdx { get; set; } + /// + /// Width of this fragment + /// public double Width { get; set; } - public int RtFragIdx { get; set; } + /// + /// Index of the orginal TextFragment this line fragment came from + /// + public int FragmentIndex { get; set; } + /// + /// Width of a space in the original TextFragment + /// public double SpaceWidth { get; internal set; } + public string Text { get { return _finalFragmentText; } } + + //Seeing duplicated string in debugger creates clutter + //Better to check Text and then realise the private variable is the actual value holder + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private string _finalFragmentText; + + /// + /// Only to be set after finishing all operations + /// Only to be set by TextLineSimple + /// This class should be essentially read only after setting this + /// + /// + internal void SetFinalizedText(string text) + { + _finalFragmentText = text; + } //public double AscentInPoints { get; private set; } //public double DescentInPoints { get; private set; } internal LineFragment(int rtFragmentIdx, int idxWithinLine/*, double ascentInPoints, double descentInPoints*/) { - RtFragIdx = rtFragmentIdx; + FragmentIndex = rtFragmentIdx; StartIdx = idxWithinLine; //AscentInPoints = ascentInPoints; //DescentInPoints = descentInPoints; diff --git a/src/EPPlus.Fonts.OpenType/Integration/TextLayoutEngine.RichText.cs b/src/EPPlus.Fonts.OpenType/Integration/TextLayoutEngine.RichText.cs index 2c862a627..cbef97891 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/TextLayoutEngine.RichText.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/TextLayoutEngine.RichText.cs @@ -118,13 +118,15 @@ public List WrapRichTextLines( double largestDescent = 0; foreach (var lineFragment in line.LineFragments) { - var frag = fragments[lineFragment.RtFragIdx]; + var frag = fragments[lineFragment.FragmentIndex]; if (frag == null) continue; largestAscent = Math.Max(frag.AscentPoints, largestAscent); largestDescent = Math.Max(frag.DescentPoints, largestDescent); } line.LargestAscent = largestAscent; line.LargestDescent = largestDescent; + + line.CreateFinalizedSubstringsInLineFragments(); } if (_lineListBuffer.Count == 0) @@ -161,10 +163,12 @@ private void ProcessFragment( var spaceWidth = shaper.Shape(" ", options).GetWidthInPoints(fragment.Font.Size); - state.LineFrag = new LineFragment(state.CurrentFragmentIdx, lineBuilder.Length); - state.LineFrag.SpaceWidth = spaceWidth; - state.LineFrag.StartIdx = lineBuilder.Length; - state.LineFrag.RtFragIdx = state.CurrentFragmentIdx; + state.LineFrag = new LineFragment(state.CurrentFragmentIdx, lineBuilder.Length) + { + SpaceWidth = spaceWidth, + StartIdx = lineBuilder.Length, + FragmentIndex = state.CurrentFragmentIdx + }; int i = 0; while (i < len) diff --git a/src/EPPlus.Fonts.OpenType/Integration/WrapStateRichText.cs b/src/EPPlus.Fonts.OpenType/Integration/WrapStateRichText.cs index d645430ca..8d7f92af6 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/WrapStateRichText.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/WrapStateRichText.cs @@ -65,7 +65,7 @@ internal void SetAndLogWordStartState(int wordStart) _listIdxWithinLine = CurrentTextLine.LineFragments.Count - 1; - if (CurrentTextLine.LineFragments[_listIdxWithinLine].RtFragIdx < _rtIdxAtWordStart) + if (CurrentTextLine.LineFragments[_listIdxWithinLine].FragmentIndex < _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 From 007a8f3cfeec9f07140d5ad63ca78c23556855b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Wed, 22 Apr 2026 11:50:03 +0200 Subject: [PATCH 4/9] Successfully added func<> lookup in TextLinecollection --- .../DataHolders/TextLineCollection.cs | 24 ++++++++---- .../Integration/LineFragment.cs | 2 +- .../Integration/LineFragmentData.cs | 38 +++++++++++++++++++ 3 files changed, 56 insertions(+), 8 deletions(-) create mode 100644 src/EPPlus.Fonts.OpenType/Integration/LineFragmentData.cs diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs index 849a4345f..efdbde970 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs @@ -16,10 +16,12 @@ public struct ezFrag() public MeasurementFont font; } + [DebuggerDisplay("{this[2].LineFragments[1]} {LineFragments[3]}")] public class TextLineCollection : List, IEnumerable { public ezFrag[] ezFrags; public List individualFragments; + public List LineFragments = new List(); public List GetFragments() { @@ -84,17 +86,25 @@ public TextLineCollection(List lines, List origina { int lineNum = i; int fragCount = 0; + + lines[i].CreateFinalizedSubstringsInLineFragments(); + foreach (var lf in lines[i].LineFragments) { - //lineFragmentText.Add(line.GetLineFragmentText(lf)); - //fragIds.Add(lf.RtFragIdx); + var idx = lf.FragmentIndex; + + if (fragIdLookup[idx].ContainsKey(lineNum) == false) + { + fragIdLookup[idx].Add(lineNum, new List()); + } - //if (fragmentLookup[lf.RtFragIdx].ContainsKey(lineNum) == false) - //{ - // fragmentLookup[lf.RtFragIdx].Add(lineNum, new List()); - //} + fragIdLookup[idx][lineNum].Add(fragCount); - //fragmentLookup[lf.RtFragIdx][lineNum].Add(fragCount); + LineFragmentData data = new LineFragmentData( + () => { return _originalFragments[idx]; }, + () => { return lf.Width; }, + lines[i].GetLineFragmentText(lf)); + LineFragments.Add(data); fragCount++; } diff --git a/src/EPPlus.Fonts.OpenType/Integration/LineFragment.cs b/src/EPPlus.Fonts.OpenType/Integration/LineFragment.cs index 1bc9e8c77..3c72b40c6 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/LineFragment.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/LineFragment.cs @@ -21,7 +21,7 @@ public class LineFragment /// public double Width { get; set; } /// - /// Index of the orginal TextFragment this line fragment came from + /// Index of original TextFragment /// public int FragmentIndex { get; set; } diff --git a/src/EPPlus.Fonts.OpenType/Integration/LineFragmentData.cs b/src/EPPlus.Fonts.OpenType/Integration/LineFragmentData.cs new file mode 100644 index 000000000..0d2a43ae2 --- /dev/null +++ b/src/EPPlus.Fonts.OpenType/Integration/LineFragmentData.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; + +namespace EPPlus.Fonts.OpenType.Integration +{ + [DebuggerDisplay("{Text}")] + public class LineFragmentData + { + + /// + /// Width of this fragment + /// + public double Width { get { return _getWidth(); } } + + /// + /// Text of this fragment + /// + public string Text { get; } + + /// + /// Original text fragment + /// + public TextFragment OriginalTextFragment { get { return _getTextFragment(); } } + + Func _getTextFragment; + Func _getWidth; + + internal LineFragmentData(Func getTextFragment, Func getWidth, string text) + { + _getTextFragment = getTextFragment; + _getWidth = getWidth; + Text = text; + } + } +} From 7c58862275222aba1fda35d8aee7d5de8e0812cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Wed, 22 Apr 2026 15:16:04 +0200 Subject: [PATCH 5/9] Added richText interface+query collection fragment --- .../DataHolders/TextLineSimpleTests.cs | 25 +++- .../DataHolders/IRichTextInfoBase.cs | 38 +++++ .../TextFragmentCollectionSimple.cs | 2 - .../DataHolders/TextLineCollection.cs | 141 ++++++++++++++---- .../DataHolders/TextLineVisualizer.cs | 52 ------- .../Integration/LineFragmentData.cs | 3 + .../Integration/TextFragment.cs | 34 +++++ .../Integration/TextLayoutEngine.RichText.cs | 13 +- 8 files changed, 216 insertions(+), 92 deletions(-) create mode 100644 src/EPPlus.Fonts.OpenType/Integration/DataHolders/IRichTextInfoBase.cs delete mode 100644 src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineVisualizer.cs diff --git a/src/EPPlus.Fonts.OpenType.Tests/DataHolders/TextLineSimpleTests.cs b/src/EPPlus.Fonts.OpenType.Tests/DataHolders/TextLineSimpleTests.cs index f48500b79..39985cb8d 100644 --- a/src/EPPlus.Fonts.OpenType.Tests/DataHolders/TextLineSimpleTests.cs +++ b/src/EPPlus.Fonts.OpenType.Tests/DataHolders/TextLineSimpleTests.cs @@ -4,6 +4,7 @@ using OfficeOpenXml.Interfaces.Drawing.Text; using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -23,12 +24,30 @@ public void TestLineFragmentAbstraction() var layout = OpenTypeFonts.GetTextLayoutEngineForFont(fragments[0].Font); var wrappedLines = layout.WrapRichTextLines(fragments, maxSizePoints); + var wrappedCollection = layout.WrapRichTextLineCollection(fragments, maxSizePoints); - var collection = new TextLineCollection(wrappedLines, fragments); + var line1 = wrappedLines[0]; + } - var frags = collection.ezFrags; + [TestMethod] + public void TestLineFragmentSeeWhatLinesUseWhatRichText() + { - var line1 = wrappedLines[0]; + 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 wrappedCollection = layout.WrapRichTextLineCollection(fragments, maxSizePoints); + + var lines = wrappedCollection.GetTextLinesThatUse(fragments[4]); + var specificFragments = wrappedCollection.GetLineFragmentsThatUse(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); } List GetTextFragments() diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/IRichTextInfoBase.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/IRichTextInfoBase.cs new file mode 100644 index 000000000..12ad97970 --- /dev/null +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/IRichTextInfoBase.cs @@ -0,0 +1,38 @@ +using OfficeOpenXml.Interfaces.Fonts; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; + + +namespace EPPlus.Fonts.OpenType.Integration.DataHolders +{ + public interface IRichTextInfoBase + { + + bool IsItalic { get; set; } + bool IsBold { get; set; } + bool SubScript { get; set; } + bool SuperScript { get; set; } + + /// + /// Represents value in enum OfficeOpenXml.Style.eUnderlineType + /// + int UnderlineType { get; set; } + /// + /// Represents value in enum OfficeOpenXml.Style.eStrikeType + /// + int StrikeType { get; set; } + + #region potentially remove + /// + /// Represents OfficeOpenXml.Drawing.eTextCapsType + /// + int Capitalization { get; set; } + #endregion + + Color UnderlineColor { get; set; } + public Color FontColor { get; set; } + } +} diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentCollectionSimple.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentCollectionSimple.cs index ed9778360..1fe1a27af 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentCollectionSimple.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentCollectionSimple.cs @@ -16,7 +16,5 @@ public TextFragmentCollectionSimple(List fonts, List te Add(new TextFragment() { Font = fonts[i], Text = texts[i] }); } } - - } } diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs index efdbde970..fedc7595a 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs @@ -10,54 +10,89 @@ namespace EPPlus.Fonts.OpenType.Integration { - public struct ezFrag() - { - public string text; - public MeasurementFont font; - } - [DebuggerDisplay("{this[2].LineFragments[1]} {LineFragments[3]}")] public class TextLineCollection : List, IEnumerable { - public ezFrag[] ezFrags; - public List individualFragments; public List LineFragments = new List(); + List _originalFragments; - public List GetFragments() + /// + /// Returns null if fragment is not found in any lines + /// + /// + /// + /// + public List GetTextLinesThatUse(TextFragment fragment) { - List fragments = new List(); + var idx = _originalFragments.IndexOf(fragment); - for (int i = 0; i< Lines.Count; i++) + if(idx != -1) { - var line = Lines[i]; - for (int j = 0; j< line.lineFragmentText.Count; j++) + List retLines = new List(); + foreach (var key in fragIdLookup[idx].Keys) { - var fragment = new TextFragment() - { - Text = line.lineFragmentText[j], - Font = GetFont(line.fragIds[j]), - AscentPoints = _originalFragments[j].AscentPoints, - DescentPoints = _originalFragments[j].DescentPoints - }; - fragments.Add(fragment); + retLines.Add(this[key]); } + return retLines; } + else + { + return null; + } + } + /// + /// Returns null if fragment is not found in any linefragments + /// + /// + /// + public List GetLineFragmentsThatUse(TextFragment fragment) + { + var idx = _originalFragments.IndexOf(fragment); - ezFrags = new ezFrag[fragments.Count]; - - individualFragments = fragments; + List retFragments = null; - for (int i = 0; i < Lines.Count; i++) + if (idx != -1) { - ezFrags[i] = new ezFrag() { text = fragments[i].Text, font = fragments[i].Font }; + retFragments = new List(); + + foreach (var key in fragIdLookup[idx].Keys) + { + foreach(var lineFragment in fragIdLookup[idx][key]) + { + retFragments.Add(this[key].LineFragments[lineFragment]); + } + } } - return fragments; + return retFragments; } - internal List Lines = new List(); - - List _originalFragments; + ///// + ///// Returns null if fragment is not found in any linefragments + ///// + ///// + ///// + //public List GetLineFragmentDataThatUses(TextFragment fragment) + //{ + // var idx = _originalFragments.IndexOf(fragment); + + // List retFragments = null; + + // if (idx != -1) + // { + // retFragments = new List(); + + // foreach (var key in fragIdLookup[idx].Keys) + // { + // foreach (var lineFragment in fragIdLookup[idx][key]) + // { + // retFragments.Add(this[key].LineFragments[lineFragment]); + // } + // } + // } + + // return retFragments; + //} /// /// The id of the orginal fragment may correspond to @@ -73,6 +108,51 @@ internal MeasurementFont GetFont(int fragIdx) return _originalFragments[fragIdx].Font; } + public TextLineCollection(TextFragmentCollectionSimple fragmentCollection) + { + _originalFragments = fragmentCollection; + + for (int i = 0; i < fragmentCollection.Count; i++) + { + fragIdLookup.Add(i, new Dictionary>()); + } + } + + private void AddToDictionary(int idx, int lineNum, int fragPosInline) + { + + if (fragIdLookup[idx].ContainsKey(lineNum) == false) + { + fragIdLookup[idx].Add(lineNum, new List()); + } + + fragIdLookup[idx][lineNum].Add(fragPosInline); + } + + internal void FinalizeTextLineData(List lines) + { + for (int i = 0; i < lines.Count; i++) + { + int lineNum = i; + int fragCount = 0; + + foreach (var lf in lines[i].LineFragments) + { + var idx = lf.FragmentIndex; + AddToDictionary(idx, lineNum, fragCount); + + LineFragmentData data = new LineFragmentData( + () => { return _originalFragments[idx]; }, + () => { return lf.Width; }, + lines[i].GetLineFragmentText(lf)); + LineFragments.Add(data); + + fragCount++; + } + Add(lines[i]); + } + } + public TextLineCollection(List lines, List originalFragments) { _originalFragments = originalFragments; @@ -110,7 +190,6 @@ public TextLineCollection(List lines, List origina } Add(lines[i]); } - GetFragments(); } } } diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineVisualizer.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineVisualizer.cs deleted file mode 100644 index 2b3a23494..000000000 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineVisualizer.cs +++ /dev/null @@ -1,52 +0,0 @@ -using OfficeOpenXml.Interfaces.Drawing.Text; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using System.Text; - -namespace EPPlus.Fonts.OpenType.Integration -{ - - public class TextLineVizualizer - { - TextLineCollection _parentCollection; - int _lineNum; - - internal List lineFragmentText = new List(); - internal List fragIds = new List(); - - //internal MeasurementFont font - //{ get - // { - // return _parentCollection.GetFont(TextLineDetails.) - // } - //} - - public TextLineVizualizer(List fragments, int lineNum, - ref Dictionary>> fragmentLookup) - { - //_lineNum = lineNum; - //int fragCount = 0; - //foreach(var lf in fragments) - //{ - // lineFragmentText.Add(line.GetLineFragmentText(lf)); - // fragIds.Add(lf.RtFragIdx); - - // if (fragmentLookup[lf.RtFragIdx].ContainsKey(lineNum)== false) - // { - // fragmentLookup[lf.RtFragIdx].Add(lineNum, new List()); - // } - - // fragmentLookup[lf.RtFragIdx][lineNum].Add(fragCount); - - // fragCount++; - //} - //var text = _lines[i].GetLineFragmentText(lf); - //var font = fonts[lf.RtFragIdx]; - //var details = _lines[i].LineFragments; - - } - } -} diff --git a/src/EPPlus.Fonts.OpenType/Integration/LineFragmentData.cs b/src/EPPlus.Fonts.OpenType/Integration/LineFragmentData.cs index 0d2a43ae2..2822d8243 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/LineFragmentData.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/LineFragmentData.cs @@ -25,8 +25,11 @@ public class LineFragmentData /// public TextFragment OriginalTextFragment { get { return _getTextFragment(); } } + + #region Callbacks Func _getTextFragment; Func _getWidth; + #endregion internal LineFragmentData(Func getTextFragment, Func getWidth, string text) { diff --git a/src/EPPlus.Fonts.OpenType/Integration/TextFragment.cs b/src/EPPlus.Fonts.OpenType/Integration/TextFragment.cs index bf7bb0e05..4f55773ba 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/TextFragment.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/TextFragment.cs @@ -10,8 +10,10 @@ Date Author Change ************************************************************************************************* 01/20/2025 EPPlus Software AB TextLayoutEngine implementation *************************************************************************************************/ +using EPPlus.Fonts.OpenType.Integration.DataHolders; using OfficeOpenXml.Interfaces.Drawing.Text; using OfficeOpenXml.Interfaces.Fonts; +using System.Drawing; namespace EPPlus.Fonts.OpenType.Integration { @@ -21,10 +23,42 @@ namespace EPPlus.Fonts.OpenType.Integration public class TextFragment { public string Text { get; set; } + public MeasurementFont Font { get; set; } public ShapingOptions Options { get; set; } + /// + /// Store rich-text info. + /// Nothing is supposed to be done with this within OpenType + /// but we hold the data so users may more easily recognize what rich text this is in the output. + /// + public IRichTextInfoBase RichTextOptions { get; set; } = new RichTextDefaults(); + public double AscentPoints { get; set; } public double DescentPoints { get; set; } } + + public class RichTextDefaults : IRichTextInfoBase + { + internal RichTextDefaults() + { + } + public bool IsItalic { get; set; } = false; + + public bool IsBold { get; set; } = false; + + public bool SubScript { get; set; } = false; + + public bool SuperScript { get; set; } = false; + + public int UnderlineType { get; set; } = -1; + + public int StrikeType { get; set; } = -1; + + public int Capitalization { get; set; } = -1; + + public Color UnderlineColor { get; set; } + + public Color FontColor { get; set; } + } } diff --git a/src/EPPlus.Fonts.OpenType/Integration/TextLayoutEngine.RichText.cs b/src/EPPlus.Fonts.OpenType/Integration/TextLayoutEngine.RichText.cs index cbef97891..d8285a7cc 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/TextLayoutEngine.RichText.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/TextLayoutEngine.RichText.cs @@ -82,6 +82,15 @@ public List WrapRichTextLines( return WrapRichTextLines(tCollection, maxWidthPoints); } + public TextLineCollection WrapRichTextLineCollection( + List fragments, + double maxWidthPoints) + { + var innerLines = WrapRichTextLines(fragments, maxWidthPoints); + var collection = new TextLineCollection(innerLines, fragments); + return collection; + } + public List WrapRichTextLines( List fragments, double maxWidthPoints) @@ -125,8 +134,6 @@ public List WrapRichTextLines( } line.LargestAscent = largestAscent; line.LargestDescent = largestDescent; - - line.CreateFinalizedSubstringsInLineFragments(); } if (_lineListBuffer.Count == 0) @@ -134,8 +141,6 @@ public List WrapRichTextLines( _lineListBuffer.Add(string.Empty); } - - return state.Lines; } From cb4d6ec696c419cd57ba86f3fc54ad234fb7cdeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Wed, 22 Apr 2026 15:17:35 +0200 Subject: [PATCH 6/9] Renamed LineFragments and made internal --- .../DataHolders/TextLineSimpleTests.cs | 2 +- .../Integration/TextLayoutEngineTests.cs | 24 +++++++------- .../TextFragmentCollectionTests.cs | 32 +++++++++---------- .../DataHolders/TextLineCollection.cs | 6 ++-- .../Integration/DataHolders/TextLineSimple.cs | 16 +++++----- .../Integration/TextLayoutEngine.RichText.cs | 4 +-- .../Integration/WrapStateRichText.cs | 24 +++++++------- 7 files changed, 54 insertions(+), 54 deletions(-) diff --git a/src/EPPlus.Fonts.OpenType.Tests/DataHolders/TextLineSimpleTests.cs b/src/EPPlus.Fonts.OpenType.Tests/DataHolders/TextLineSimpleTests.cs index 39985cb8d..6707d96d7 100644 --- a/src/EPPlus.Fonts.OpenType.Tests/DataHolders/TextLineSimpleTests.cs +++ b/src/EPPlus.Fonts.OpenType.Tests/DataHolders/TextLineSimpleTests.cs @@ -45,7 +45,7 @@ public void TestLineFragmentSeeWhatLinesUseWhatRichText() var lines = wrappedCollection.GetTextLinesThatUse(fragments[4]); var specificFragments = wrappedCollection.GetLineFragmentsThatUse(fragments[4]); - Assert.AreEqual(lines[0].LineFragments[1], specificFragments[0]); + Assert.AreEqual(lines[0].InternalLineFragments[1], specificFragments[0]); Assert.AreEqual(fragments[4], wrappedCollection.LineFragments[6].OriginalTextFragment); Assert.AreEqual(Color.DarkRed, wrappedCollection.LineFragments[6].OriginalTextFragment.RichTextOptions.FontColor); } diff --git a/src/EPPlus.Fonts.OpenType.Tests/Integration/TextLayoutEngineTests.cs b/src/EPPlus.Fonts.OpenType.Tests/Integration/TextLayoutEngineTests.cs index 7f91bcd57..c560fe1f1 100644 --- a/src/EPPlus.Fonts.OpenType.Tests/Integration/TextLayoutEngineTests.cs +++ b/src/EPPlus.Fonts.OpenType.Tests/Integration/TextLayoutEngineTests.cs @@ -524,8 +524,8 @@ public void EnsureRichTextLineWrappingSameAsNonRichWhenNoWrap() var wrappedLines = layoutEngine.WrapRichTextLines(comparatorFragments, 225d); Assert.AreEqual(pointsTotal, wrappedLines[0].Width); - Assert.AreEqual(points1, wrappedLines[0].LineFragments[0].Width); - Assert.AreEqual(points2, wrappedLines[0].LineFragments[1].Width); + Assert.AreEqual(points1, wrappedLines[0].InternalLineFragments[0].Width); + Assert.AreEqual(points2, wrappedLines[0].InternalLineFragments[1].Width); } [TestMethod] @@ -574,8 +574,8 @@ public void EnsureRichTextLineWrappingSameAsNonRichWhenNoWrapAndSpaceTrail() var wrappedLines = layoutEngine.WrapRichTextLines(comparatorFragments, 225d); Assert.AreEqual(pointsTotal, wrappedLines[0].Width); - Assert.AreEqual(points1, wrappedLines[0].LineFragments[0].Width); - Assert.AreEqual(points2, wrappedLines[0].LineFragments[1].Width); + Assert.AreEqual(points1, wrappedLines[0].InternalLineFragments[0].Width); + Assert.AreEqual(points2, wrappedLines[0].InternalLineFragments[1].Width); var noSpaceWidth = wrappedLines[0].GetWidthWithoutTrailingSpaces(); Assert.AreEqual(202.8916f, noSpaceWidth); } @@ -631,7 +631,7 @@ public void EnsureLineFragmentsAreMeasuredCorrectlyWhenWrapping() var wrappedLines = layout.WrapRichTextLines(fragments, maxSizePoints); - Assert.AreEqual(12.55224609375d, wrappedLines[0].LineFragments[2].Width); + Assert.AreEqual(12.55224609375d, wrappedLines[0].InternalLineFragments[2].Width); Assert.AreEqual(202.8916f, wrappedLines[1].GetWidthWithoutTrailingSpaces()); List smallestTextFragments = new List(); @@ -639,7 +639,7 @@ public void EnsureLineFragmentsAreMeasuredCorrectlyWhenWrapping() //Ensure each linefragment can get correct text foreach(var line in wrappedLines) { - foreach(var lf in line.LineFragments) + foreach(var lf in line.InternalLineFragments) { var text = line.GetLineFragmentText(lf); smallestTextFragments.Add(text); @@ -736,25 +736,25 @@ public void WrapRichTextDifficultCaseCompare() Assert.AreEqual(210.890625d, wrappedLines[3].Width, epsilon); Assert.AreEqual(127.04296875d, wrappedLines[4].Width, epsilon); - var line1FragmentsNew = wrappedLines[0].LineFragments; + var line1FragmentsNew = wrappedLines[0].InternalLineFragments; Assert.AreEqual(32.87646484375d, line1FragmentsNew[0].Width, epsilon); - var line2FragmentsNew = wrappedLines[1].LineFragments; + var line2FragmentsNew = wrappedLines[1].InternalLineFragments; Assert.AreEqual(5.30126953125d, line2FragmentsNew[0].Width, epsilon); - var line3FragmentsNew = wrappedLines[2].LineFragments; + var line3FragmentsNew = wrappedLines[2].InternalLineFragments; Assert.AreEqual(40.21875d, line3FragmentsNew[0].Width, epsilon); Assert.AreEqual(52.16943359375d, line3FragmentsNew[1].Width, epsilon); Assert.AreEqual(12.55712890625d, line3FragmentsNew[2].Width, epsilon); - var line4FragmentsNew = wrappedLines[3].LineFragments; + var line4FragmentsNew = wrappedLines[3].InternalLineFragments; Assert.AreEqual(24.86328125d, line4FragmentsNew[0].Width, epsilon); Assert.AreEqual(186.02734375d, line4FragmentsNew[1].Width, epsilon); - var line5FragmentsNew = wrappedLines[4].LineFragments; + var line5FragmentsNew = wrappedLines[4].InternalLineFragments; Assert.AreEqual(26.390625d, line5FragmentsNew[0].Width, epsilon); Assert.AreEqual(100.65234375d, line5FragmentsNew[1].Width, epsilon); @@ -983,7 +983,7 @@ public void VerifyWrappingSingleChar() var wrappedLines = layout.WrapRichTextLines(fragments, maxWidthPt); - Assert.AreEqual(0, wrappedLines[1].LineFragments[0].StartIdx); + Assert.AreEqual(0, wrappedLines[1].InternalLineFragments[0].StartIdx); } } } \ No newline at end of file diff --git a/src/EPPlus.Fonts.OpenType.Tests/TextFragmentCollectionTests.cs b/src/EPPlus.Fonts.OpenType.Tests/TextFragmentCollectionTests.cs index 6710875f3..e5fab0817 100644 --- a/src/EPPlus.Fonts.OpenType.Tests/TextFragmentCollectionTests.cs +++ b/src/EPPlus.Fonts.OpenType.Tests/TextFragmentCollectionTests.cs @@ -124,7 +124,7 @@ public void MeasureWrappedWidthsWithInternalLineBreaks() var line1 = wrappedLines[0]; - var pixels11 = Math.Round(line1.LineFragments[0].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); + var pixels11 = Math.Round(line1.InternalLineFragments[0].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); var pixelsWholeline1 = Math.Round(line1.Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); Assert.AreEqual(44, pixels11); @@ -132,7 +132,7 @@ public void MeasureWrappedWidthsWithInternalLineBreaks() var line2 = wrappedLines[1]; - var pixels21 = Math.Round(line2.LineFragments[0].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); + var pixels21 = Math.Round(line2.InternalLineFragments[0].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); var pixelsWholeline2 = Math.Round(line2.Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); Assert.AreEqual(7, pixels21); @@ -140,9 +140,9 @@ public void MeasureWrappedWidthsWithInternalLineBreaks() var line3 = wrappedLines[2]; - var pixels31 = Math.Round(line3.LineFragments[0].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); - var pixels32 = Math.Round(line3.LineFragments[1].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); - var pixels33 = Math.Round(line3.LineFragments[2].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); + var pixels31 = Math.Round(line3.InternalLineFragments[0].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); + var pixels32 = Math.Round(line3.InternalLineFragments[1].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); + var pixels33 = Math.Round(line3.InternalLineFragments[2].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); var pixelsWholeLine3 = Math.Round(line3.Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); //~54 px @@ -157,8 +157,8 @@ public void MeasureWrappedWidthsWithInternalLineBreaks() var line4 = wrappedLines[3]; - var pixels41 = Math.Round(line4.LineFragments[0].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); - var pixels42 = Math.Round(line4.LineFragments[1].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); + var pixels41 = Math.Round(line4.InternalLineFragments[0].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); + var pixels42 = Math.Round(line4.InternalLineFragments[1].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); var pixelsWholeLine4 = Math.Round(line4.Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); //~34 px Assert.AreEqual(33, pixels41); @@ -169,8 +169,8 @@ public void MeasureWrappedWidthsWithInternalLineBreaks() var line5 = wrappedLines[4]; - var pixels51 = Math.Round(line5.LineFragments[0].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); - var pixels52 = Math.Round(line5.LineFragments[1].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); + var pixels51 = Math.Round(line5.InternalLineFragments[0].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); + var pixels52 = Math.Round(line5.InternalLineFragments[1].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); var pixelsWholeLine5 = Math.Round(line5.Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); Assert.AreEqual(35, pixels51); //This line does NOT contain a space at the end @@ -230,9 +230,9 @@ public void MeasureWrappedWidths() var wrappedLines = ttMeasurer.WrapRichTextLines(textFragments, maxSizePoints); - var pixels1 = Math.Round(wrappedLines[0].LineFragments[0].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); - var pixels2 = Math.Round(wrappedLines[0].LineFragments[1].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); - var pixels3 = Math.Round(wrappedLines[0].LineFragments[2].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); + var pixels1 = Math.Round(wrappedLines[0].InternalLineFragments[0].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); + var pixels2 = Math.Round(wrappedLines[0].InternalLineFragments[1].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); + var pixels3 = Math.Round(wrappedLines[0].InternalLineFragments[2].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); var pixelsWholeLine = Math.Round(wrappedLines[0].Width.PointToPixel(),0, MidpointRounding.AwayFromZero); //~54 px @@ -245,8 +245,8 @@ public void MeasureWrappedWidths() //Total Width: ~140 Assert.AreEqual(140d, pixelsWholeLine); - var pixels21 = Math.Round(wrappedLines[1].LineFragments[0].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); - var pixels22 = Math.Round(wrappedLines[1].LineFragments[1].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); + var pixels21 = Math.Round(wrappedLines[1].InternalLineFragments[0].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); + var pixels22 = Math.Round(wrappedLines[1].InternalLineFragments[1].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); var pixelsWholeLine2 = Math.Round(wrappedLines[1].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); //~34 px Assert.AreEqual(33, pixels21); @@ -255,8 +255,8 @@ public void MeasureWrappedWidths() Assert.AreEqual(281, pixelsWholeLine2); - var pixels31 = Math.Round(wrappedLines[2].LineFragments[0].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); - var pixels32 = Math.Round(wrappedLines[2].LineFragments[1].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); + var pixels31 = Math.Round(wrappedLines[2].InternalLineFragments[0].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); + var pixels32 = Math.Round(wrappedLines[2].InternalLineFragments[1].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); var pixelsWholeLine3 = Math.Round(wrappedLines[2].Width.PointToPixel(), 0, MidpointRounding.AwayFromZero); Assert.AreEqual(35, pixels31); //This line does NOT contain a space at the end diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs index fedc7595a..681e95aaa 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs @@ -59,7 +59,7 @@ public List GetLineFragmentsThatUse(TextFragment fragment) { foreach(var lineFragment in fragIdLookup[idx][key]) { - retFragments.Add(this[key].LineFragments[lineFragment]); + retFragments.Add(this[key].InternalLineFragments[lineFragment]); } } } @@ -136,7 +136,7 @@ internal void FinalizeTextLineData(List lines) int lineNum = i; int fragCount = 0; - foreach (var lf in lines[i].LineFragments) + foreach (var lf in lines[i].InternalLineFragments) { var idx = lf.FragmentIndex; AddToDictionary(idx, lineNum, fragCount); @@ -169,7 +169,7 @@ public TextLineCollection(List lines, List origina lines[i].CreateFinalizedSubstringsInLineFragments(); - foreach (var lf in lines[i].LineFragments) + foreach (var lf in lines[i].InternalLineFragments) { var idx = lf.FragmentIndex; diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs index 1e7e3aea2..cd9a0e976 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs @@ -12,7 +12,7 @@ namespace EPPlus.Fonts.OpenType.Integration [DebuggerDisplay("{Text}")] public class TextLineSimple { - public List LineFragments { get; internal set; } = new List(); + internal List InternalLineFragments { get; set; } = new List(); public string Text { get; internal set; } /// @@ -43,7 +43,7 @@ public class TextLineSimple /// public double GetWidthWithoutTrailingSpaces() { - lastFontSpaceWidth = LineFragments.Last().SpaceWidth; + lastFontSpaceWidth = InternalLineFragments.Last().SpaceWidth; var trailingSpaceCount = 0; @@ -72,7 +72,7 @@ public TextLineSimple() public TextLineSimple(string text, double largestFontSize, double largestAscent, double largestDescent) { - LineFragments = new LineFragmentCollection(text); + InternalLineFragments = new LineFragmentCollection(text); LargestFontSize = largestFontSize; LargestAscent = largestAscent; LargestDescent = largestDescent; @@ -83,7 +83,7 @@ public TextLineSimple(string text, double largestFontSize, double largestAscent, /// internal void CreateFinalizedSubstringsInLineFragments() { - foreach (var lineFragment in LineFragments) + foreach (var lineFragment in InternalLineFragments) { var text = GetLineFragmentText(lineFragment); lineFragment.SetFinalizedText(text); @@ -92,7 +92,7 @@ internal void CreateFinalizedSubstringsInLineFragments() public string GetLineFragmentText(LineFragment rtFragment) { - if (LineFragments.Contains(rtFragment) == false) + if (InternalLineFragments.Contains(rtFragment) == false) { throw new InvalidOperationException($"GetFragmentText failed. Cannot retrieve {rtFragment} since it is not part of this textLine: {this}"); } @@ -104,14 +104,14 @@ public string GetLineFragmentText(LineFragment rtFragment) var startIdx = rtFragment.StartIdx; - var idxInLst = LineFragments.FindIndex(x => x == rtFragment); - if (idxInLst == LineFragments.Count - 1) + var idxInLst = InternalLineFragments.FindIndex(x => x == rtFragment); + if (idxInLst == InternalLineFragments.Count - 1) { return Text.Substring(startIdx, Text.Length - startIdx); } else { - var endIdx = LineFragments[idxInLst + 1].StartIdx; + var endIdx = InternalLineFragments[idxInLst + 1].StartIdx; return Text.Substring(startIdx, endIdx - startIdx); } } diff --git a/src/EPPlus.Fonts.OpenType/Integration/TextLayoutEngine.RichText.cs b/src/EPPlus.Fonts.OpenType/Integration/TextLayoutEngine.RichText.cs index d8285a7cc..dcb2f41ec 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/TextLayoutEngine.RichText.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/TextLayoutEngine.RichText.cs @@ -125,7 +125,7 @@ public List WrapRichTextLines( { double largestAscent = 0; double largestDescent = 0; - foreach (var lineFragment in line.LineFragments) + foreach (var lineFragment in line.InternalLineFragments) { var frag = fragments[lineFragment.FragmentIndex]; if (frag == null) continue; @@ -213,7 +213,7 @@ private void ProcessFragment( if (state.LineFrag.Width > 0) { - state.CurrentTextLine.LineFragments.Add(state.LineFrag); + state.CurrentTextLine.InternalLineFragments.Add(state.LineFrag); } state.CurrentFragmentIdx++; diff --git a/src/EPPlus.Fonts.OpenType/Integration/WrapStateRichText.cs b/src/EPPlus.Fonts.OpenType/Integration/WrapStateRichText.cs index 8d7f92af6..907b5ddad 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/WrapStateRichText.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/WrapStateRichText.cs @@ -15,9 +15,9 @@ public WrapStateRichText(double lineWidth) internal void EndCurrentTextLine() { - if (CurrentTextLine.LineFragments.Contains(LineFrag) == false) + if (CurrentTextLine.InternalLineFragments.Contains(LineFrag) == false) { - CurrentTextLine.LineFragments.Add(LineFrag); + CurrentTextLine.InternalLineFragments.Add(LineFrag); } Lines.Add(CurrentTextLine); } @@ -36,7 +36,7 @@ internal void EndCurrentTextLineAndIntializeNext(int startIdxOfNewFragment) else { //_fragmentsForNextLine.Add(LineFrag); - nextLine.LineFragments = _fragmentsForNextLine; + nextLine.InternalLineFragments = _fragmentsForNextLine; //LineFrag.StartIdx = ; Lines.Add(CurrentTextLine); @@ -57,15 +57,15 @@ internal void SetAndLogWordStartState(int wordStart) _rtIdxAtWordStart = CurrentFragmentIdx; _lineFragWidthAtWordStart = LineFrag.Width; - if(CurrentTextLine.LineFragments.Count == 0) + if(CurrentTextLine.InternalLineFragments.Count == 0) { _listIdxWithinLine = 0; return; } - _listIdxWithinLine = CurrentTextLine.LineFragments.Count - 1; + _listIdxWithinLine = CurrentTextLine.InternalLineFragments.Count - 1; - if (CurrentTextLine.LineFragments[_listIdxWithinLine].FragmentIndex < _rtIdxAtWordStart) + if (CurrentTextLine.InternalLineFragments[_listIdxWithinLine].FragmentIndex < _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 @@ -91,23 +91,23 @@ internal void AdjustLineFragmentsForNextLine() { //If we are On the fragment we have not added it yet //Do so before splitting - CurrentTextLine.LineFragments.Add(LineFrag); + CurrentTextLine.InternalLineFragments.Add(LineFrag); } - var origFragment = CurrentTextLine.LineFragments[_listIdxWithinLine]; + var origFragment = CurrentTextLine.InternalLineFragments[_listIdxWithinLine]; var resultingFragment = CurrentTextLine.SplitAndGetLeftoverLineFragment(ref origFragment, _lineFragWidthAtWordStart); - CurrentTextLine.LineFragments[_listIdxWithinLine] = origFragment; + CurrentTextLine.InternalLineFragments[_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--) + for (int i = CurrentTextLine.InternalLineFragments.Count()-1; i > _listIdxWithinLine; i--) { //Add fragment to the new list - _fragmentsForNextLine.Insert(0, CurrentTextLine.LineFragments[i]); + _fragmentsForNextLine.Insert(0, CurrentTextLine.InternalLineFragments[i]); //Remove it from the old - CurrentTextLine.LineFragments.RemoveAt(i); + CurrentTextLine.InternalLineFragments.RemoveAt(i); } if (_rtIdxAtWordStart != CurrentFragmentIdx) From 2d4afa5e259eba1a9f3ee6888831383667ab5a64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Wed, 22 Apr 2026 15:41:20 +0200 Subject: [PATCH 7/9] Finalized lineFragmentData as LineFragments --- .../RenderItems/Shared/ParagraphItem.cs | 7 ++-- .../DataHolders/TextLineSimpleTests.cs | 2 ++ .../DataHolders/TextLineCollection.cs | 8 +++-- .../Integration/DataHolders/TextLineSimple.cs | 35 +++++++++++++++---- .../Integration/LineFragment.cs | 31 ++++++++-------- .../Integration/LineFragmentData.cs | 8 +++-- .../Integration/TextLayoutEngine.RichText.cs | 2 ++ 7 files changed, 62 insertions(+), 31 deletions(-) diff --git a/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/ParagraphItem.cs b/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/ParagraphItem.cs index c709e37a0..b5f32f34c 100644 --- a/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/ParagraphItem.cs +++ b/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/ParagraphItem.cs @@ -345,7 +345,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) { @@ -460,7 +460,7 @@ 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) { @@ -468,7 +468,8 @@ private void AddLinesAndTextRuns(ExcelDrawingParagraph p, string textIfEmpty) } else { - AddRenderItemTextRun(p.TextRuns[lineFragment.FragmentIndex], displayText, prevWidth); + var idx = _newTextFragments.IndexOf(lineFragment.OriginalTextFragment); + AddRenderItemTextRun(p.TextRuns[idx], displayText, prevWidth); } TextRunItem runItem = Runs.Last(); diff --git a/src/EPPlus.Fonts.OpenType.Tests/DataHolders/TextLineSimpleTests.cs b/src/EPPlus.Fonts.OpenType.Tests/DataHolders/TextLineSimpleTests.cs index 6707d96d7..5d662147a 100644 --- a/src/EPPlus.Fonts.OpenType.Tests/DataHolders/TextLineSimpleTests.cs +++ b/src/EPPlus.Fonts.OpenType.Tests/DataHolders/TextLineSimpleTests.cs @@ -40,6 +40,8 @@ public void TestLineFragmentSeeWhatLinesUseWhatRichText() 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]); diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs index 681e95aaa..9551f6d53 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs @@ -144,7 +144,8 @@ internal void FinalizeTextLineData(List lines) LineFragmentData data = new LineFragmentData( () => { return _originalFragments[idx]; }, () => { return lf.Width; }, - lines[i].GetLineFragmentText(lf)); + lines[i].GetLineFragmentText(lf), + lf.StartIdx); LineFragments.Add(data); fragCount++; @@ -167,7 +168,7 @@ public TextLineCollection(List lines, List origina int lineNum = i; int fragCount = 0; - lines[i].CreateFinalizedSubstringsInLineFragments(); + //lines[i].CreateFinalizedSubstringsInLineFragments(); foreach (var lf in lines[i].InternalLineFragments) { @@ -183,7 +184,8 @@ public TextLineCollection(List lines, List origina LineFragmentData data = new LineFragmentData( () => { return _originalFragments[idx]; }, () => { return lf.Width; }, - lines[i].GetLineFragmentText(lf)); + lines[i].GetLineFragmentText(lf), + lf.StartIdx); LineFragments.Add(data); fragCount++; diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs index cd9a0e976..a5e884f5a 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs @@ -12,8 +12,16 @@ namespace EPPlus.Fonts.OpenType.Integration [DebuggerDisplay("{Text}")] public class TextLineSimple { + /// + /// Internal data for making operations + /// internal List InternalLineFragments { get; set; } = new List(); + /// + /// External output data for reading + /// + public List LineFragments { get; internal set; } = new List(); + public string Text { get; internal set; } /// /// The largest font size within this line @@ -78,15 +86,28 @@ public TextLineSimple(string text, double largestFontSize, double largestAscent, LargestDescent = largestDescent; } - /// - /// Inserts the relevant substrings directly into the line fragments - /// - internal void CreateFinalizedSubstringsInLineFragments() + ///// + ///// Inserts the relevant substrings directly into the line fragments + ///// + //internal void CreateFinalizedSubstringsInLineFragments() + //{ + // foreach (var lineFragment in InternalLineFragments) + // { + // var text = GetLineFragmentText(lineFragment); + // lineFragment.SetFinalizedText(text); + // } + //} + + internal void FinalizeLineFragments(List originalFragments) { - foreach (var lineFragment in InternalLineFragments) + foreach (var lf in InternalLineFragments) { - var text = GetLineFragmentText(lineFragment); - lineFragment.SetFinalizedText(text); + LineFragmentData data = new LineFragmentData( + () => { return originalFragments[lf.FragmentIndex]; }, + () => { return lf.Width; }, + GetLineFragmentText(lf), + lf.StartIdx); + LineFragments.Add(data); } } diff --git a/src/EPPlus.Fonts.OpenType/Integration/LineFragment.cs b/src/EPPlus.Fonts.OpenType/Integration/LineFragment.cs index 3c72b40c6..ee7f18e62 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/LineFragment.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/LineFragment.cs @@ -9,7 +9,6 @@ namespace EPPlus.Fonts.OpenType.Integration /// /// The fragment of a richTextFragment that is within a line /// - [DebuggerDisplay("{Text}")] public class LineFragment { /// @@ -30,23 +29,23 @@ public class LineFragment /// public double SpaceWidth { get; internal set; } - public string Text { get { return _finalFragmentText; } } + //public string Text { get { return _finalFragmentText; } } - //Seeing duplicated string in debugger creates clutter - //Better to check Text and then realise the private variable is the actual value holder - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private string _finalFragmentText; + ////Seeing duplicated string in debugger creates clutter + ////Better to check Text and then realise the private variable is the actual value holder + //[DebuggerBrowsable(DebuggerBrowsableState.Never)] + //private string _finalFragmentText; - /// - /// Only to be set after finishing all operations - /// Only to be set by TextLineSimple - /// This class should be essentially read only after setting this - /// - /// - internal void SetFinalizedText(string text) - { - _finalFragmentText = text; - } + ///// + ///// Only to be set after finishing all operations + ///// Only to be set by TextLineSimple + ///// This class should be essentially read only after setting this + ///// + ///// + //internal void SetFinalizedText(string text) + //{ + // _finalFragmentText = text; + //} //public double AscentInPoints { get; private set; } //public double DescentInPoints { get; private set; } diff --git a/src/EPPlus.Fonts.OpenType/Integration/LineFragmentData.cs b/src/EPPlus.Fonts.OpenType/Integration/LineFragmentData.cs index 2822d8243..b241d4dfd 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/LineFragmentData.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/LineFragmentData.cs @@ -9,7 +9,10 @@ namespace EPPlus.Fonts.OpenType.Integration [DebuggerDisplay("{Text}")] public class LineFragmentData { - + /// + /// Char idx within the line + /// + public int StartIdx { get; } /// /// Width of this fragment /// @@ -31,11 +34,12 @@ public class LineFragmentData Func _getWidth; #endregion - internal LineFragmentData(Func getTextFragment, Func getWidth, string text) + internal LineFragmentData(Func getTextFragment, Func getWidth, string text, int startidx) { _getTextFragment = getTextFragment; _getWidth = getWidth; Text = text; + StartIdx = startidx; } } } diff --git a/src/EPPlus.Fonts.OpenType/Integration/TextLayoutEngine.RichText.cs b/src/EPPlus.Fonts.OpenType/Integration/TextLayoutEngine.RichText.cs index dcb2f41ec..d8d6eaf66 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/TextLayoutEngine.RichText.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/TextLayoutEngine.RichText.cs @@ -134,6 +134,8 @@ public List WrapRichTextLines( } line.LargestAscent = largestAscent; line.LargestDescent = largestDescent; + + line.FinalizeLineFragments(fragments); } if (_lineListBuffer.Count == 0) From ecb675a4caded3136e16a15deb387b71e923748e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Wed, 22 Apr 2026 16:36:09 +0200 Subject: [PATCH 8/9] Added linenum method + cleanup --- .../DataHolders/TextLineSimpleTests.cs | 10 +- .../DataHolders/TextLineCollection.cs | 131 +++++++++--------- .../Integration/DataHolders/TextLineSimple.cs | 9 +- .../Integration/LineFragment.cs | 24 +--- ...eFragmentData.cs => LineFragmentOutput.cs} | 16 ++- 5 files changed, 93 insertions(+), 97 deletions(-) rename src/EPPlus.Fonts.OpenType/Integration/{LineFragmentData.cs => LineFragmentOutput.cs} (55%) diff --git a/src/EPPlus.Fonts.OpenType.Tests/DataHolders/TextLineSimpleTests.cs b/src/EPPlus.Fonts.OpenType.Tests/DataHolders/TextLineSimpleTests.cs index 5d662147a..ca683ee1c 100644 --- a/src/EPPlus.Fonts.OpenType.Tests/DataHolders/TextLineSimpleTests.cs +++ b/src/EPPlus.Fonts.OpenType.Tests/DataHolders/TextLineSimpleTests.cs @@ -17,7 +17,6 @@ public class TextLineSimpleTests [TestMethod] public void TestLineFragmentAbstraction() { - var maxSizePoints = Math.Round(300d, 0, MidpointRounding.AwayFromZero).PixelToPoint(); var fragments = GetTextFragments(); @@ -26,13 +25,12 @@ public void TestLineFragmentAbstraction() var wrappedLines = layout.WrapRichTextLines(fragments, maxSizePoints); var wrappedCollection = layout.WrapRichTextLineCollection(fragments, maxSizePoints); - var line1 = wrappedLines[0]; + } [TestMethod] public void TestLineFragmentSeeWhatLinesUseWhatRichText() { - var maxSizePoints = Math.Round(300d, 0, MidpointRounding.AwayFromZero).PixelToPoint(); var fragments = GetTextFragments(); @@ -46,10 +44,14 @@ public void TestLineFragmentSeeWhatLinesUseWhatRichText() var lines = wrappedCollection.GetTextLinesThatUse(fragments[4]); var specificFragments = wrappedCollection.GetLineFragmentsThatUse(fragments[4]); + var lineIndicies = wrappedCollection.GetLineNumbersThatUse(fragments[4]); - Assert.AreEqual(lines[0].InternalLineFragments[1], specificFragments[0]); + 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 GetTextFragments() diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs index 9551f6d53..95fce058d 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs @@ -13,9 +13,30 @@ namespace EPPlus.Fonts.OpenType.Integration public class TextLineCollection : List, IEnumerable { - public List LineFragments = new List(); + /// + /// Array of the line numbers where fragment occurs + /// + public List LineFragments = new List(); List _originalFragments; + public int[] GetLineNumbersThatUse(TextFragment fragment) + { + var idx = _originalFragments.IndexOf(fragment); + List result = new List(); + if (idx != -1) + { + foreach (var key in fragIdLookup[idx].Keys) + { + result.Add(key); + } + return result.ToArray(); + } + else + { + return null; + } + } + /// /// Returns null if fragment is not found in any lines /// @@ -45,7 +66,7 @@ public List GetTextLinesThatUse(TextFragment fragment) /// /// /// - public List GetLineFragmentsThatUse(TextFragment fragment) + public List GetInternalLineFragmentsThatUse(TextFragment fragment) { var idx = _originalFragments.IndexOf(fragment); @@ -67,32 +88,32 @@ public List GetLineFragmentsThatUse(TextFragment fragment) return retFragments; } - ///// - ///// Returns null if fragment is not found in any linefragments - ///// - ///// - ///// - //public List GetLineFragmentDataThatUses(TextFragment fragment) - //{ - // var idx = _originalFragments.IndexOf(fragment); - - // List retFragments = null; - - // if (idx != -1) - // { - // retFragments = new List(); - - // foreach (var key in fragIdLookup[idx].Keys) - // { - // foreach (var lineFragment in fragIdLookup[idx][key]) - // { - // retFragments.Add(this[key].LineFragments[lineFragment]); - // } - // } - // } - - // return retFragments; - //} + /// + /// Returns null if fragment is not found in any linefragments + /// + /// + /// + public List GetLineFragmentsThatUse(TextFragment fragment) + { + var idx = _originalFragments.IndexOf(fragment); + + List retFragments = null; + + if (idx != -1) + { + retFragments = new List(); + + foreach (var key in fragIdLookup[idx].Keys) + { + foreach (var lineFragment in fragIdLookup[idx][key]) + { + retFragments.Add(this[key].LineFragments[lineFragment]); + } + } + } + + return retFragments; + } /// /// The id of the orginal fragment may correspond to @@ -108,6 +129,10 @@ internal MeasurementFont GetFont(int fragIdx) return _originalFragments[fragIdx].Font; } + /// + /// If using this MUST call FinalizeTextLineData to finish the information gathering + /// + /// public TextLineCollection(TextFragmentCollectionSimple fragmentCollection) { _originalFragments = fragmentCollection; @@ -140,12 +165,22 @@ internal void FinalizeTextLineData(List lines) { var idx = lf.FragmentIndex; AddToDictionary(idx, lineNum, fragCount); + LineFragmentOutput data; + if (lines[i].LineFragments == null || lines[i].LineFragments.Count != lines[i].InternalLineFragments.Count) + { + data = new LineFragmentOutput( + () => { return _originalFragments[idx]; }, + () => { return lf.Width; }, + () => { return lf.StartIdx; }, + lines[i].GetLineFragmentText(lf) + ); + } + else + { + //If already calculate don't duplicate just use the data + data = lines[i].LineFragments[fragCount]; + } - LineFragmentData data = new LineFragmentData( - () => { return _originalFragments[idx]; }, - () => { return lf.Width; }, - lines[i].GetLineFragmentText(lf), - lf.StartIdx); LineFragments.Add(data); fragCount++; @@ -163,35 +198,7 @@ public TextLineCollection(List lines, List origina fragIdLookup.Add(i, new Dictionary>()); } - for(int i = 0; i < lines.Count; i++) - { - int lineNum = i; - int fragCount = 0; - - //lines[i].CreateFinalizedSubstringsInLineFragments(); - - foreach (var lf in lines[i].InternalLineFragments) - { - var idx = lf.FragmentIndex; - - if (fragIdLookup[idx].ContainsKey(lineNum) == false) - { - fragIdLookup[idx].Add(lineNum, new List()); - } - - fragIdLookup[idx][lineNum].Add(fragCount); - - LineFragmentData data = new LineFragmentData( - () => { return _originalFragments[idx]; }, - () => { return lf.Width; }, - lines[i].GetLineFragmentText(lf), - lf.StartIdx); - LineFragments.Add(data); - - fragCount++; - } - Add(lines[i]); - } + FinalizeTextLineData(lines); } } } diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs index a5e884f5a..909256cdd 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs @@ -20,7 +20,7 @@ public class TextLineSimple /// /// External output data for reading /// - public List LineFragments { get; internal set; } = new List(); + public List LineFragments { get; internal set; } = new List(); public string Text { get; internal set; } /// @@ -102,11 +102,12 @@ internal void FinalizeLineFragments(List originalFragments) { foreach (var lf in InternalLineFragments) { - LineFragmentData data = new LineFragmentData( + LineFragmentOutput data = new LineFragmentOutput( () => { return originalFragments[lf.FragmentIndex]; }, () => { return lf.Width; }, - GetLineFragmentText(lf), - lf.StartIdx); + () => { return lf.StartIdx; }, + GetLineFragmentText(lf) + ); LineFragments.Add(data); } } diff --git a/src/EPPlus.Fonts.OpenType/Integration/LineFragment.cs b/src/EPPlus.Fonts.OpenType/Integration/LineFragment.cs index ee7f18e62..cdb8d7a2f 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/LineFragment.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/LineFragment.cs @@ -29,32 +29,10 @@ public class LineFragment /// public double SpaceWidth { get; internal set; } - //public string Text { get { return _finalFragmentText; } } - - ////Seeing duplicated string in debugger creates clutter - ////Better to check Text and then realise the private variable is the actual value holder - //[DebuggerBrowsable(DebuggerBrowsableState.Never)] - //private string _finalFragmentText; - - ///// - ///// Only to be set after finishing all operations - ///// Only to be set by TextLineSimple - ///// This class should be essentially read only after setting this - ///// - ///// - //internal void SetFinalizedText(string text) - //{ - // _finalFragmentText = text; - //} - //public double AscentInPoints { get; private set; } - //public double DescentInPoints { get; private set; } - - internal LineFragment(int rtFragmentIdx, int idxWithinLine/*, double ascentInPoints, double descentInPoints*/) + internal LineFragment(int rtFragmentIdx, int idxWithinLine) { FragmentIndex = rtFragmentIdx; StartIdx = idxWithinLine; - //AscentInPoints = ascentInPoints; - //DescentInPoints = descentInPoints; } } } diff --git a/src/EPPlus.Fonts.OpenType/Integration/LineFragmentData.cs b/src/EPPlus.Fonts.OpenType/Integration/LineFragmentOutput.cs similarity index 55% rename from src/EPPlus.Fonts.OpenType/Integration/LineFragmentData.cs rename to src/EPPlus.Fonts.OpenType/Integration/LineFragmentOutput.cs index b241d4dfd..7458c808d 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/LineFragmentData.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/LineFragmentOutput.cs @@ -7,12 +7,12 @@ namespace EPPlus.Fonts.OpenType.Integration { [DebuggerDisplay("{Text}")] - public class LineFragmentData + public class LineFragmentOutput { /// /// Char idx within the line /// - public int StartIdx { get; } + public int StartIdx { get { return _getStartIdx(); } } /// /// Width of this fragment /// @@ -20,26 +20,34 @@ public class LineFragmentData /// /// Text of this fragment + /// Is not a callback since the LineFragment class does not store the string data directly. + /// This is for performance reasons. /// public string Text { get; } /// /// Original text fragment + /// If this becomes null/loses its reference the original textfragment has been deleted + /// Don't delete/clear the original textFragment list if you plan to use/check it here. /// public TextFragment OriginalTextFragment { get { return _getTextFragment(); } } + /// + /// Looks up the data in the internal class LineFragment instead of making copies + /// #region Callbacks Func _getTextFragment; Func _getWidth; + Func _getStartIdx; #endregion - internal LineFragmentData(Func getTextFragment, Func getWidth, string text, int startidx) + internal LineFragmentOutput(Func getTextFragment, Func getWidth, Func startIdx ,string text) { _getTextFragment = getTextFragment; _getWidth = getWidth; + _getStartIdx = startIdx; Text = text; - StartIdx = startidx; } } } From 00a279a245b5c52404bb55f6dabba4a7a1967c45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Wed, 22 Apr 2026 16:48:38 +0200 Subject: [PATCH 9/9] Cleaned up unused classes + added comments --- .../Integration/DataHolders/CharInfo.cs | 1 + .../Integration/DataHolders/GlyphRect.cs | 28 -- .../DataHolders/IRichTextInfoBase.cs | 3 + .../DataHolders/LineFragmentCollection.cs | 91 ---- .../DataHolders/RichTextFragmentSimple.cs | 46 -- .../DataHolders/TextFragmentAdvanced.cs | 78 ---- .../TextFragmentCollectionAdvanced.cs | 415 ------------------ .../Integration/DataHolders/TextLineSimple.cs | 64 --- .../Integration/DataHolders/TextParagraph.cs | 83 ---- .../Integration/FragmentPosition.cs | 28 -- .../Integration/LineFragmentOutput.cs | 3 + .../Integration/TextFragment.cs | 3 + .../Integration/TextRun.cs | 27 -- .../Integration/WrappedLine.cs | 25 -- 14 files changed, 10 insertions(+), 885 deletions(-) delete mode 100644 src/EPPlus.Fonts.OpenType/Integration/DataHolders/GlyphRect.cs delete mode 100644 src/EPPlus.Fonts.OpenType/Integration/DataHolders/LineFragmentCollection.cs delete mode 100644 src/EPPlus.Fonts.OpenType/Integration/DataHolders/RichTextFragmentSimple.cs delete mode 100644 src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentAdvanced.cs delete mode 100644 src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentCollectionAdvanced.cs delete mode 100644 src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextParagraph.cs delete mode 100644 src/EPPlus.Fonts.OpenType/Integration/FragmentPosition.cs delete mode 100644 src/EPPlus.Fonts.OpenType/Integration/TextRun.cs delete mode 100644 src/EPPlus.Fonts.OpenType/Integration/WrappedLine.cs diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/CharInfo.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/CharInfo.cs index c6ddae562..599fa5e3c 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/CharInfo.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/CharInfo.cs @@ -4,6 +4,7 @@ using System.Text; namespace EPPlus.Fonts.OpenType.Integration { + //Leaving this for now. May be neccesary for vertical text internal class CharInfo { internal int Index; diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/GlyphRect.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/GlyphRect.cs deleted file mode 100644 index cf53f8d99..000000000 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/GlyphRect.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace EPPlus.Fonts.OpenType.Integration -{ - internal class GlyphRect - { - //BoundingRectangle BoundingRectFontDesign; - ushort glyphIndex; - internal double advanceWidth; - - //Debug var only? - string fontName; - - internal GlyphRect(ushort glyphIndex, double advanceWidth, string fontName) - { - this.glyphIndex = glyphIndex; - this.advanceWidth = advanceWidth; - this.fontName = fontName; - } - //BoundingRectangle CalculateBoundingRect(double fontSize) - //{ - - //} - } -} diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/IRichTextInfoBase.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/IRichTextInfoBase.cs index 12ad97970..2206e1add 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/IRichTextInfoBase.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/IRichTextInfoBase.cs @@ -8,6 +8,9 @@ namespace EPPlus.Fonts.OpenType.Integration.DataHolders { + /// + /// Interface for pdf/svg/future richtext users to unify richtext styling + /// public interface IRichTextInfoBase { diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/LineFragmentCollection.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/LineFragmentCollection.cs deleted file mode 100644 index 2e22dec58..000000000 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/LineFragmentCollection.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace EPPlus.Fonts.OpenType.Integration.DataHolders -{ - public class LineFragmentCollection : List - { - private string _text; - - public string FullText - { - get { return _text; } - internal set { RichTextSubstrings.Clear(); _text = value; } - } - - internal List RichTextSubstrings { get; private set; } - - - - public LineFragmentCollection(string originalText) - { - FullText = originalText; - } - - //Note: Array size never intended to be larger than 2 - List StartEndPerFragment = new List(); - - - /// - /// Logs start and end idx per fragment - /// Then adds fragment as regular list - /// - /// - public new void Add(LineFragment fragment) - { - var endIdx = FullText.Length - 1; - if (Count != 0) - { - StartEndPerFragment.Last()[1] = fragment.StartIdx; - } - - StartEndPerFragment.Add(new int[] { fragment.StartIdx, endIdx}); - base.Add(fragment); - } - - public string GetLineFragmentText(LineFragment rtFragment) - { - if (this.Contains(rtFragment) == false) - { - throw new InvalidOperationException($"GetFragmentText failed. Cannot retrieve {rtFragment} since it is not part of this textLine: {this}"); - } - - if (string.IsNullOrEmpty(FullText)) - { - return FullText; - } - - var startIdx = rtFragment.StartIdx; - - var idxInLst = this.FindIndex(x => x == rtFragment); - if (idxInLst == this.Count - 1) - { - return FullText.Substring(startIdx, FullText.Length - startIdx); - } - else - { - var endIdx = this[idxInLst + 1].StartIdx; - return FullText.Substring(startIdx, endIdx - startIdx); - } - } - - internal void GenerateSubstrings() - { - RichTextSubstrings.Clear(); - - //for (int i = 0; i < StartEndPerFragment.Count; i++) - //{ - // var startIdx = StartEndPerFragment[i][0]; - // var endIdx = StartEndPerFragment[i][1]; - - // RichTextSubstrings.Add(FullText[1..5]); - //} - for (int i = 0; i < Count; i++) - { - RichTextSubstrings.Add(GetLineFragmentText(this[i])); - } - } - } -} diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/RichTextFragmentSimple.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/RichTextFragmentSimple.cs deleted file mode 100644 index 7f77f0cc6..000000000 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/RichTextFragmentSimple.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace EPPlus.Fonts.OpenType.Integration -{ - public class RichTextFragmentSimple - { - public int Fragidx { get; internal set; } - - internal int OverallParagraphStartCharIdx { get; set; } - //Char length - internal int charStarIdxWithinCurrentLine { get; set; } - /// - /// Width in points - /// - public double Width { get; internal set; } - - public RichTextFragmentSimple() - { - Width = 0; - Fragidx = 0; - OverallParagraphStartCharIdx = 0; - charStarIdxWithinCurrentLine = 0; - } - - //public RichTextFragmentSimple(int fragIdx, ) - //{ - // Width = 0; - // Fragidx = 0; - // OverallParagraphStartCharIdx = 0; - // charStarIdxWithinCurrentLine = 0; - //} - - internal RichTextFragmentSimple Clone() - { - var fragment = new RichTextFragmentSimple(); - fragment.Width = Width; - fragment.OverallParagraphStartCharIdx = OverallParagraphStartCharIdx; - fragment.charStarIdxWithinCurrentLine = charStarIdxWithinCurrentLine; - fragment.Fragidx = Fragidx; - return fragment; - } - } -} diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentAdvanced.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentAdvanced.cs deleted file mode 100644 index 2879bda9f..000000000 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentAdvanced.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace EPPlus.Fonts.OpenType.Integration.DataHolders -{ - //TextFragment is a portion of a longer string - //the fragment itself may contain line-breaks - internal class TextFragmentAdvanced - { - /// - /// Char index starting position for this fragment - /// - internal int StartPos; - - internal List IsPartOfLines; - internal string ThisFragment; - internal List PointWidthPerOutputLineInFragment = new(); - internal List PointYIncreasePerLine = new(); - - List positionsToBreakAt = new(); - - internal float FontSize { get; set; } = float.NaN; - - internal TextFragmentAdvanced(string thisFragment, int startPos) - { - ThisFragment = thisFragment; - StartPos = startPos; - } - - internal int GetEndPosition() - { - return StartPos + ThisFragment.Length; - } - - /// - /// Line-breaks to be added after wrapping - /// - /// - internal void AddLocalLbPositon(int pos) - { - var locaLnPos = pos - StartPos; - positionsToBreakAt.Add(locaLnPos); - } - - internal string GetLineBreakFreeFragment() - { - string freeFragment = ThisFragment.Replace("\r", ""); - freeFragment.Replace("\n", ""); - return freeFragment; - } - - internal string GetWrappedFragment() - { - if (positionsToBreakAt.Count == 0) - { - return ThisFragment; - } - - string alteredFragment = ThisFragment; - int deleteSpaceCount = 0; - for (int i = 0; i < positionsToBreakAt.Count; i++) - { - var insertPosition = positionsToBreakAt[i] + i - deleteSpaceCount; - alteredFragment = alteredFragment.Insert(insertPosition, Environment.NewLine); - //Spaces after inserted newlines are to be removed - if (alteredFragment[insertPosition + Environment.NewLine.Length] == ' ') - { - alteredFragment = alteredFragment.Remove(insertPosition + Environment.NewLine.Length, 1); - deleteSpaceCount++; - } - } - - return alteredFragment; - } - } -} diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentCollectionAdvanced.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentCollectionAdvanced.cs deleted file mode 100644 index 16501d1a7..000000000 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentCollectionAdvanced.cs +++ /dev/null @@ -1,415 +0,0 @@ -using EPPlus.Fonts.OpenType.Integration.DataHolders; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Xml; - -namespace EPPlus.Fonts.OpenType.Integration -{ - public class TextFragmentCollectionAdvanced - { - internal Dictionary CharLookup = new(); - - internal List TextFragments = new(); - - internal string AllText; - internal List AllTextNewLineIndicies = new(); - List _fragmentItems = new List(); - List _lines = new List(); - List outputFragments = new List(); - //List outputStrings = new List(); - internal List LargestFontSizePerLine { get; private set; } = new(); - internal List AscentPerLine { get; private set; } = new(); - internal List DescentPerLine { get; private set; } = new(); - - /// - /// Added linewidths in points - /// - internal List lineWidths { get; private set; } = new List(); - - public List IndiciesToWrapAt { get; internal set; } - - List TextLines = new(); - - public TextFragmentCollectionAdvanced(List textFragments) - { - TextFragments = textFragments; - IndiciesToWrapAt = new List(); - - //Set data for each fragment and for AllText - var currentTotalLength = 0; - for (int i = 0; i < textFragments.Count; i++) - { - var currString = textFragments[i]; - var fragment = new TextFragmentAdvanced(currString, currentTotalLength); - currentTotalLength += currString.Length; - _fragmentItems.Add(fragment); - } - - AllText = string.Join(string.Empty, textFragments.ToArray()); - - //Get the indicies where newlines occur in the combined string - AllTextNewLineIndicies = GetFirstCharPositionOfNewLines(AllText); - - //Save minor information about each char so each char knows its line/fragment - int charCount = 0; - int lineIndex = 0; - - List currFragments = new(); - int lineStartCharIndex = 0; - - //For each fragment - for (int i = 0; i < TextFragments.Count; i++) - { - var textFragment = TextFragments[i]; - - //For each char in current fragment - for (int j = 0; j < textFragment.Length; j++) - { - if (lineIndex <= AllTextNewLineIndicies.Count - 1 && - charCount >= AllTextNewLineIndicies[lineIndex]) - { - var text = AllText.Substring(lineStartCharIndex, charCount - lineStartCharIndex); - var trimmedText = text.Trim(['\r', '\n']); - - var line = new TextLineSimple(); - line.Text = trimmedText; - - foreach (var idx in currFragments) - { - var fragment = new LineFragment(i, idx); - } - - lineStartCharIndex = AllTextNewLineIndicies[lineIndex]; - _lines.Add(line); - currFragments.Clear(); - lineIndex++; - } - - var info = new CharInfo(charCount, i, lineIndex); - CharLookup.Add(charCount, info); - charCount++; - } - currFragments.Add(i); - } - - //Add the last line - var lastText = AllText.Substring(lineStartCharIndex, charCount - lineStartCharIndex); - var lastTrimmedText = lastText.Trim(['\r', '\n']); - - var lastLine = new TextLineSimple(); - lastLine.Text = lastTrimmedText; - - foreach (var idx in currFragments) - { - var fragment = new LineFragment(idx, lineStartCharIndex); - } - - _lines.Add(lastLine); - currFragments.Clear(); - } - - public TextFragmentCollectionAdvanced(List textFragments, List fontSizes) : this(textFragments) - { - if (textFragments.Count() != fontSizes.Count) - { - throw new InvalidOperationException($"TextFragment list and FontSizes list must be equal." + - $"Counts:" + - $"textFragment: {textFragments.Count()}" + - $"fontSizes: {fontSizes.Count()}"); - } - - for (int i = 0; i < textFragments.Count; i++) - { - _fragmentItems[i].FontSize = fontSizes[i]; - } - } - - private List GetFirstCharPositionOfNewLines(string stringsCombined) - { - List positions = new List(); - for (int i = 0; i < stringsCombined.Length; i++) - { - if (stringsCombined[i] == '\n') - { - if (i < stringsCombined.Length - 1) - { - positions.Add(i + 1); - } - else - { - positions.Add(i); - } - } - } - return positions; - } - - internal void AddLargestFontSizePerLine(double fontSize) - { - LargestFontSizePerLine.Add(fontSize); - } - - internal void AddAscentPerLine(double Ascent) - { - AscentPerLine.Add(Ascent); - } - - internal void AddDescentPerLine(double Descent) - { - DescentPerLine.Add(Descent); - } - - internal void AddWrappingIndex(int index) - { - IndiciesToWrapAt.Add(index); - } - - public List GetFragmentsWithFinalLineBreaks() - { - //var newFragments = TextFragments.ToArray(); - foreach (var i in IndiciesToWrapAt) - { - _fragmentItems[CharLookup[i].Fragment].AddLocalLbPositon(i); - } - - foreach (var fragment in _fragmentItems) - { - outputFragments.Add(fragment.GetWrappedFragment()); - } - - return outputFragments; - } - - public List GetOutputLines() - { - var fragments = GetFragmentsWithFinalLineBreaks(); - - string outputAllText = ""; - - //Create final all text - for (int i = 0; i < fragments.Count(); i++) - { - outputAllText += fragments[i]; - } - - var newLineIndiciesOutput = GetFirstCharPositionOfNewLines(outputAllText); - List outputTextLines = new List(); - - //Save minor information about each char so each char knows its line/fragment - int charCount = 0; - int lineIndex = 0; - - List currFragments = new(); - int lineStartCharIndex = 0; - int RtInternalStartIndex = 0; - - //For each fragment - for (int i = 0; i < fragments.Count; i++) - { - var textFragment = fragments[i]; - - //For each char in current fragment - for (int j = 0; j < textFragment.Length; j++) - { - if (lineIndex <= newLineIndiciesOutput.Count - 1 && - charCount >= newLineIndiciesOutput[lineIndex]) - { - //We have reached a new line - var text = outputAllText.Substring(lineStartCharIndex, charCount - lineStartCharIndex); - var trimmedText = text.Trim(['\r', '\n']); - - var line = new TextLineSimple(); - line.Text = trimmedText; - - foreach (var idx in currFragments) - { - var fragment = new LineFragment(i, idx); - } - - - //var line = new TextLine() - //{ - // richTextIndicies = currFragments, - // content = trimmedText, - // startIndex = lineStartCharIndex, - // rtContentStartIndexPerRt = new List(j), - // lastRtInternalIndex = j, - // startRtInternalIndex = RtInternalStartIndex - //}; - - lineStartCharIndex = newLineIndiciesOutput[lineIndex]; - outputTextLines.Add(line); - currFragments.Clear(); - RtInternalStartIndex = j; - lineIndex++; - } - - //var info = new CharInfo(charCount, i, lineIndex); - //CharLookup.Add(charCount, info); - charCount++; - } - currFragments.Add(i); - } - - //Add the last line - var lastText = AllText.Substring(lineStartCharIndex, charCount - lineStartCharIndex); - var lastTrimmedText = lastText.Trim(['\r', '\n']); - - var lastLine = new TextLineSimple(); - lastLine.Text = lastTrimmedText; - - foreach (var idx in currFragments) - { - var fragment = new LineFragment(lineStartCharIndex, idx); - } - - _lines.Add(lastLine); - currFragments.Clear(); - - ////Add the last line - //var lastText = outputAllText.Substring(lineStartCharIndex, charCount - lineStartCharIndex); - //var lastTrimmedText = lastText.Trim(['\r', '\n']); - - //var lastLine = new TextLine() - //{ - // richTextIndicies = currFragments, - // content = lastTrimmedText, - // startIndex = lineStartCharIndex, - // lastRtInternalIndex = fragments.Last().Length, - // startRtInternalIndex = RtInternalStartIndex - //}; - - //outputTextLines.Add(lastLine); - //currFragments.Clear(); - - return outputTextLines; - } - - //public void GetTextFragmentOutputStartIndiciesOnFinalLines(List finalOutputLines) - //{ - // var lineBreakStrings = GetFragmentsWithFinalLineBreaks(); - // //var fragmentsWithoutLineBreaks = GetFragmentsWithoutLineBreaks(); - // string outputAllText = ""; - - // //List> eachFragmentForEachLine = new List>(); - - // for(int i = 0; i< finalOutputLines.Count(); i++) - // { - // var line = new TextLine(); - // line.content = finalOutputLines[i]; - - // } - - // //Create final all text - // for(int i = 0; i< lineBreakStrings.Count(); i++) - // { - - // ////var length = lineBreakStrings[i].Length; - // //outputAllText += lineBreakStrings[i]; - // } - - - //} - - //public List GetSimpleTextLines(List wrappedLines) - //{ - // //The wrapped lines - // var lines = wrappedLines; - // //The richText data in a form that is one to one in chars - // var frags = GetFragmentsWithoutLineBreaks(); - - // int fragIdx = 0; - // int charIdx = 0; - // int lastFragLength = 0; - - // var currentFragment = frags[fragIdx]; - - // for (int i = 0; i< wrappedLines.Count(); i++) - // { - // var textLineSimple = new TextLineSimple(); - - // for (int j = 0; j < wrappedLines[i].Length; j++) - // { - // textLineSimple - // charIdx++; - // } - // } - //} - - public List GetFragmentsWithoutLineBreaks() - { - ////var newFragments = TextFragments.ToArray(); - //foreach (var i in IndiciesToWrapAt) - //{ - // _fragmentItems[CharLookup[i].Fragment].AddLocalLbPositon(i); - //} - - foreach (var fragment in _fragmentItems) - { - outputFragments.Add(fragment.GetLineBreakFreeFragment()); - } - - return outputFragments; - } - - internal void AddLineWidth(double width) - { - lineWidths.Add(width); - } - - /// - /// Should arguably be done in constructor instead of created every time - /// - /// - /// - public List GetLargestFontSizesOfEachLine() - { - //List largestFontSizes = new List(); - //foreach(var line in _lines) - //{ - // float largest = float.MinValue; - // foreach(var idx in line.richTextIndicies) - // { - // if (float.IsNaN(_fragmentItems[idx].FontSize) == false && _fragmentItems[idx].FontSize > largest) - // { - // largest = _fragmentItems[idx].FontSize; - // } - // } - // if (largest != float.MinValue) - // { - // largestFontSizes.Add(largest); - // } - //} - //return largestFontSizes; - return LargestFontSizePerLine; - } - - public double GetAscent(int lineIdx) - { - return AscentPerLine[lineIdx]; - } - - public double GetDescent(int lineIdx) - { - return DescentPerLine[lineIdx]; - } - - internal void AddFragmentWidth(int fragmentIdx, double width) - { - _fragmentItems[fragmentIdx].PointWidthPerOutputLineInFragment.Add(width); - } - - public List GetFragmentWidths(int fragmentIdx) - { - return _fragmentItems[fragmentIdx].PointWidthPerOutputLineInFragment; - } - - public List GetLinesFragmentIsPartOf(int fragmentIndex) - { - //Add throw if index does not exist? - return _fragmentItems[fragmentIndex].IsPartOfLines; - } - } -} diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs index 909256cdd..1e5367e66 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs @@ -8,7 +8,6 @@ namespace EPPlus.Fonts.OpenType.Integration { - //[DebuggerTypeProxy(typeof(TextLineSimpleVizualizer))] [DebuggerDisplay("{Text}")] public class TextLineSimple { @@ -78,26 +77,6 @@ public TextLineSimple() { } - public TextLineSimple(string text, double largestFontSize, double largestAscent, double largestDescent) - { - InternalLineFragments = new LineFragmentCollection(text); - LargestFontSize = largestFontSize; - LargestAscent = largestAscent; - LargestDescent = largestDescent; - } - - ///// - ///// Inserts the relevant substrings directly into the line fragments - ///// - //internal void CreateFinalizedSubstringsInLineFragments() - //{ - // foreach (var lineFragment in InternalLineFragments) - // { - // var text = GetLineFragmentText(lineFragment); - // lineFragment.SetFinalizedText(text); - // } - //} - internal void FinalizeLineFragments(List originalFragments) { foreach (var lf in InternalLineFragments) @@ -149,47 +128,4 @@ internal LineFragment SplitAndGetLeftoverLineFragment(ref LineFragment origLf, d return newLineFragment; } } - - //internal class TextLineSimpleVizualizer - //{ - // public List Display - // { - - // get - // { - // List startIndices = new List(); - // List lines = new List(); - - // foreach(var fragment in _content.LineFragments ) - // { - // //startIndices.Add(fragment.StartIdx); - // lines.Add(_content.GetLineFragmentText(fragment)+$" rtIdx:{fragment.RtFragIdx}"); - // } - - // //startIndices.Add(_content.Text.Length -1); - - // //List lines = new List(); - - - // //for (int i = 0; i < startIndices.Count -1; i++) - // //{ - // // var startidx = startIndices[i + 1]+1; - // // var length = startidx - startIndices[i]; - // // var substring = _content.Text.Substring(startIndices[i], length); - // // lines.Add(substring); - // //} - - - - // return lines; - // } - // } - - // private TextLineSimple _content; - - // public TextLineSimpleVizualizer(TextLineSimple content) - // { - // _content = content; - // } - //} } diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextParagraph.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextParagraph.cs deleted file mode 100644 index aa1bc9c71..000000000 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextParagraph.cs +++ /dev/null @@ -1,83 +0,0 @@ -//using EPPlus.Fonts.OpenType.Tables.Cmap; -//using EPPlus.Fonts.OpenType.Tables.Cmap.Mappings; -//using OfficeOpenXml.Interfaces.Drawing.Text; -//using OfficeOpenXml.Interfaces.Fonts; -//using System; -//using System.Collections; -//using System.Collections.Generic; -//using System.Linq; -//using System.Text; - -//namespace EPPlus.Fonts.OpenType.Integration -//{ -// public class TextParagraph -// { -// //prevent creating multiple OpenTypeFonts via cache/indexing -// internal Dictionary FontIndexDict = new(); -// internal Dictionary GlyphMappings = new(); -// internal int TotalLength = 0; - -// internal List FontSizes = new(); - -// internal TextFragmentCollection Fragments; - -// public TextParagraph(TextFragmentCollection fragments, List fonts) -// { -// List openTypeFonts = new List(); -// FontSizes = new List(); -// Fragments = fragments; - -// var distinctFonts = fonts.Distinct().ToArray(); - -// //Collect fonts that are actually distinct -// foreach (var distinctFont in distinctFonts) -// { -// var subFont = GetFontSubType(distinctFont.Style); -// var font = GetFont(distinctFont.FontFamily, subFont); -// openTypeFonts.Add(font); -// } - -// //Setup lookup for different properties -// for (int i = 0; i < fonts.Count; i++) -// { -// for (int j = 0; j < distinctFonts.Count(); j++) -// { -// if (fonts[i] == distinctFonts[j]) -// { -// FontIndexDict.Add(i, openTypeFonts[j]); -// GlyphMappings.Add(i, openTypeFonts[j].CmapTable.GetPreferredSubtable().GetGlyphMappings()); -// } -// } -// FontSizes.Add(fonts[i].Size); -// } -// } - -// public TextParagraph(List textFragment, List fontSizes, Dictionary fontIndexDict) -// { - -// } - -// OpenTypeFont GetFont(string fontName, FontSubFamily subFamily) -// { -// return OpenTypeFonts.LoadFont(fontName, subFamily); -// } - -// private FontSubFamily GetFontSubType(MeasurementFontStyles Style) -// { -// if ((Style & (MeasurementFontStyles.Bold | MeasurementFontStyles.Italic)) == (MeasurementFontStyles.Bold | MeasurementFontStyles.Italic)) -// { -// return FontSubFamily.BoldItalic; -// } -// else if ((Style & MeasurementFontStyles.Bold) == MeasurementFontStyles.Bold) -// { -// return FontSubFamily.Bold; -// } -// else if ((Style & MeasurementFontStyles.Italic) == MeasurementFontStyles.Italic) -// { -// return FontSubFamily.Italic; -// } - -// return FontSubFamily.Regular; -// } -// } -//} diff --git a/src/EPPlus.Fonts.OpenType/Integration/FragmentPosition.cs b/src/EPPlus.Fonts.OpenType/Integration/FragmentPosition.cs deleted file mode 100644 index 73c5734d2..000000000 --- a/src/EPPlus.Fonts.OpenType/Integration/FragmentPosition.cs +++ /dev/null @@ -1,28 +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 - ************************************************************************************************* - 01/20/2025 EPPlus Software AB TextLayoutEngine implementation - *************************************************************************************************/ -using OfficeOpenXml.Interfaces.Drawing.Text; -using OfficeOpenXml.Interfaces.Fonts; - -namespace EPPlus.Fonts.OpenType.Integration -{ - /// - /// Internal class to track fragment positions in the full text. - /// - internal class FragmentPosition - { - public int StartIndex { get; set; } - public int EndIndex { get; set; } - public MeasurementFont Font { get; set; } - public ShapingOptions Options { get; set; } - } -} diff --git a/src/EPPlus.Fonts.OpenType/Integration/LineFragmentOutput.cs b/src/EPPlus.Fonts.OpenType/Integration/LineFragmentOutput.cs index 7458c808d..9b47269ea 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/LineFragmentOutput.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/LineFragmentOutput.cs @@ -6,6 +6,9 @@ namespace EPPlus.Fonts.OpenType.Integration { + /// + /// Finalized output which uses callbacks to get data but can never have data set. + /// [DebuggerDisplay("{Text}")] public class LineFragmentOutput { diff --git a/src/EPPlus.Fonts.OpenType/Integration/TextFragment.cs b/src/EPPlus.Fonts.OpenType/Integration/TextFragment.cs index 4f55773ba..aef9ae09b 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/TextFragment.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/TextFragment.cs @@ -38,6 +38,9 @@ public class TextFragment public double DescentPoints { get; set; } } + /// + /// Simple class to provide some kind of fallback/defaults + /// public class RichTextDefaults : IRichTextInfoBase { internal RichTextDefaults() diff --git a/src/EPPlus.Fonts.OpenType/Integration/TextRun.cs b/src/EPPlus.Fonts.OpenType/Integration/TextRun.cs deleted file mode 100644 index 869b2b07c..000000000 --- a/src/EPPlus.Fonts.OpenType/Integration/TextRun.cs +++ /dev/null @@ -1,27 +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 - ************************************************************************************************* - 01/20/2025 EPPlus Software AB TextRun implementation - *************************************************************************************************/ -using OfficeOpenXml.Interfaces.Drawing.Text; - -namespace EPPlus.Fonts.OpenType.Integration -{ - /// - /// Represents a portion of text with consistent formatting. - /// - public class TextRun - { - public string Text { get; set; } - public MeasurementFont Font { get; set; } - public int StartIndex { get; set; } - public int Length { get; set; } - } -} diff --git a/src/EPPlus.Fonts.OpenType/Integration/WrappedLine.cs b/src/EPPlus.Fonts.OpenType/Integration/WrappedLine.cs deleted file mode 100644 index 08320da41..000000000 --- a/src/EPPlus.Fonts.OpenType/Integration/WrappedLine.cs +++ /dev/null @@ -1,25 +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 - ************************************************************************************************* - 01/20/2025 EPPlus Software AB WrappedLine implementation - *************************************************************************************************/ -using System.Collections.Generic; - -namespace EPPlus.Fonts.OpenType.Integration -{ - /// - /// Represents a wrapped line with rich text information. - /// - public class WrappedLine - { - public string Text { get; set; } - public List Runs { get; set; } - } -}