diff --git a/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/ParagraphItem.cs b/src/EPPlus.Export.ImageRenderer/RenderItems/Shared/ParagraphItem.cs index be029a5db..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.RtFragIdx], 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 new file mode 100644 index 000000000..ca683ee1c --- /dev/null +++ b/src/EPPlus.Fonts.OpenType.Tests/DataHolders/TextLineSimpleTests.cs @@ -0,0 +1,116 @@ +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.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EPPlus.Fonts.OpenType.Tests.DataHolders +{ + [TestClass] + public class TextLineSimpleTests + { + [TestMethod] + public void TestLineFragmentAbstraction() + { + var maxSizePoints = Math.Round(300d, 0, MidpointRounding.AwayFromZero).PixelToPoint(); + + var fragments = GetTextFragments(); + + var layout = OpenTypeFonts.GetTextLayoutEngineForFont(fragments[0].Font); + var wrappedLines = layout.WrapRichTextLines(fragments, maxSizePoints); + var wrappedCollection = layout.WrapRichTextLineCollection(fragments, maxSizePoints); + + + } + + [TestMethod] + public void TestLineFragmentSeeWhatLinesUseWhatRichText() + { + var maxSizePoints = Math.Round(300d, 0, MidpointRounding.AwayFromZero).PixelToPoint(); + + var fragments = GetTextFragments(); + + fragments[4].RichTextOptions.FontColor = Color.DarkRed; + + var layout = OpenTypeFonts.GetTextLayoutEngineForFont(fragments[0].Font); + + var wrappedLines = layout.WrapRichTextLines(fragments, maxSizePoints); + var wrappedCollection = layout.WrapRichTextLineCollection(fragments, maxSizePoints); + + var lines = wrappedCollection.GetTextLinesThatUse(fragments[4]); + var specificFragments = wrappedCollection.GetLineFragmentsThatUse(fragments[4]); + var lineIndicies = wrappedCollection.GetLineNumbersThatUse(fragments[4]); + + Assert.AreEqual(lines[0].LineFragments[1], specificFragments[0]); + Assert.AreEqual(fragments[4], wrappedCollection.LineFragments[6].OriginalTextFragment); + Assert.AreEqual(Color.DarkRed, wrappedCollection.LineFragments[6].OriginalTextFragment.RichTextOptions.FontColor); + + var expectedArr = new int[] { 3, 4 }; + expectedArr.SequenceCompareTo(lineIndicies); + } + + List 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.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/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 new file mode 100644 index 000000000..2206e1add --- /dev/null +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/IRichTextInfoBase.cs @@ -0,0 +1,41 @@ +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 +{ + /// + /// Interface for pdf/svg/future richtext users to unify richtext styling + /// + 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/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/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/TextFragmentCollection.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentCollection.cs deleted file mode 100644 index cdd23b3bc..000000000 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextFragmentCollection.cs +++ /dev/null @@ -1,388 +0,0 @@ -//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; -// } -// } -//} 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..95fce058d --- /dev/null +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineCollection.cs @@ -0,0 +1,205 @@ +using OfficeOpenXml.Interfaces.Drawing.Text; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; + + + +namespace EPPlus.Fonts.OpenType.Integration +{ + + public class TextLineCollection : List, IEnumerable + { + /// + /// 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 + /// + /// + /// + /// + public List GetTextLinesThatUse(TextFragment fragment) + { + var idx = _originalFragments.IndexOf(fragment); + + if(idx != -1) + { + List retLines = new List(); + foreach (var key in fragIdLookup[idx].Keys) + { + retLines.Add(this[key]); + } + return retLines; + } + else + { + return null; + } + } + /// + /// Returns null if fragment is not found in any linefragments + /// + /// + /// + public List GetInternalLineFragmentsThatUse(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].InternalLineFragments[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 + /// 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; + } + + /// + /// If using this MUST call FinalizeTextLineData to finish the information gathering + /// + /// + 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].InternalLineFragments) + { + 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]; + } + + LineFragments.Add(data); + + fragCount++; + } + Add(lines[i]); + } + } + + public TextLineCollection(List lines, List originalFragments) + { + _originalFragments = originalFragments; + + for(int i = 0; i < originalFragments.Count; i++) + { + fragIdLookup.Add(i, new Dictionary>()); + } + + FinalizeTextLineData(lines); + } + } +} + diff --git a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs index e98fe3b46..1e5367e66 100644 --- a/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs +++ b/src/EPPlus.Fonts.OpenType/Integration/DataHolders/TextLineSimple.cs @@ -1,14 +1,25 @@ 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 { + [DebuggerDisplay("{Text}")] public class TextLineSimple { - public List LineFragments { get; internal set; } = new List(); + /// + /// 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; } /// @@ -39,13 +50,13 @@ public class TextLineSimple /// public double GetWidthWithoutTrailingSpaces() { - lastFontSpaceWidth = LineFragments.Last().SpaceWidth; + lastFontSpaceWidth = InternalLineFragments.Last().SpaceWidth; 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 +64,7 @@ public double GetWidthWithoutTrailingSpaces() trailingSpaceCount++; } - if(WasWrappedOnSpace) + if (WasWrappedOnSpace) { trailingSpaceCount++; } @@ -66,17 +77,23 @@ public TextLineSimple() { } - public TextLineSimple(string text, double largestFontSize, double largestAscent, double largestDescent) + internal void FinalizeLineFragments(List originalFragments) { - Text = text; - LargestFontSize = largestFontSize; - LargestAscent = largestAscent; - LargestDescent = largestDescent; + foreach (var lf in InternalLineFragments) + { + LineFragmentOutput data = new LineFragmentOutput( + () => { return originalFragments[lf.FragmentIndex]; }, + () => { return lf.Width; }, + () => { return lf.StartIdx; }, + GetLineFragmentText(lf) + ); + LineFragments.Add(data); + } } 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}"); } @@ -88,14 +105,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); } } @@ -103,7 +120,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; 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/LineFragment.cs b/src/EPPlus.Fonts.OpenType/Integration/LineFragment.cs index 26dbc439b..cdb8d7a2f 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; @@ -10,21 +11,28 @@ namespace EPPlus.Fonts.OpenType.Integration /// 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 original TextFragment + /// + public int FragmentIndex { get; set; } + /// + /// Width of a space in the original TextFragment + /// public double SpaceWidth { get; internal set; } - //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) { - RtFragIdx = rtFragmentIdx; + FragmentIndex = rtFragmentIdx; StartIdx = idxWithinLine; - //AscentInPoints = ascentInPoints; - //DescentInPoints = descentInPoints; } } } diff --git a/src/EPPlus.Fonts.OpenType/Integration/LineFragmentOutput.cs b/src/EPPlus.Fonts.OpenType/Integration/LineFragmentOutput.cs new file mode 100644 index 000000000..9b47269ea --- /dev/null +++ b/src/EPPlus.Fonts.OpenType/Integration/LineFragmentOutput.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; + +namespace EPPlus.Fonts.OpenType.Integration +{ + /// + /// Finalized output which uses callbacks to get data but can never have data set. + /// + [DebuggerDisplay("{Text}")] + public class LineFragmentOutput + { + /// + /// Char idx within the line + /// + public int StartIdx { get { return _getStartIdx(); } } + /// + /// Width of this fragment + /// + public double Width { get { return _getWidth(); } } + + /// + /// 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 LineFragmentOutput(Func getTextFragment, Func getWidth, Func startIdx ,string text) + { + _getTextFragment = getTextFragment; + _getWidth = getWidth; + _getStartIdx = startIdx; + Text = text; + } + } +} diff --git a/src/EPPlus.Fonts.OpenType/Integration/TextFragment.cs b/src/EPPlus.Fonts.OpenType/Integration/TextFragment.cs index bf7bb0e05..aef9ae09b 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,45 @@ 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; } } + + /// + /// Simple class to provide some kind of fallback/defaults + /// + 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 e563926c1..d8d6eaf66 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) @@ -116,15 +125,17 @@ 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.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.FinalizeLineFragments(fragments); } if (_lineListBuffer.Count == 0) @@ -159,10 +170,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) @@ -202,7 +215,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/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/WrapStateRichText.cs b/src/EPPlus.Fonts.OpenType/Integration/WrapStateRichText.cs index d645430ca..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].RtFragIdx < _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) 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; } - } -}