diff --git a/.docsanity.yaml b/.docsanity.yaml index 4940d5d..ad3816e 100644 --- a/.docsanity.yaml +++ b/.docsanity.yaml @@ -22,7 +22,7 @@ project: # The assemblies for which documentation should be generated, # .xml files are expected in the same location assemblies: - - ./Build/Release/Topten.RichTextKit/netcoreapp2.1/Topten.RichTextKit.dll + - ./Build/Release/Topten.RichTextKit/net5.0/Topten.RichTextKit.dll # Namespaces to be assumed when formatting fully qualified type names namespaces: diff --git a/RichStringSandbox/App.config b/RichStringSandbox/App.config index 27ca6b0..4feeee1 100644 --- a/RichStringSandbox/App.config +++ b/RichStringSandbox/App.config @@ -1,9 +1,9 @@ - + - + - + - \ No newline at end of file + diff --git a/RichStringSandbox/Properties/Resources.Designer.cs b/RichStringSandbox/Properties/Resources.Designer.cs index 666d94c..9e6f153 100644 --- a/RichStringSandbox/Properties/Resources.Designer.cs +++ b/RichStringSandbox/Properties/Resources.Designer.cs @@ -8,10 +8,10 @@ // //------------------------------------------------------------------------------ -namespace Sandbox.Properties -{ - - +namespace RichStringSandbox.Properties { + using System; + + /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -19,51 +19,43 @@ namespace Sandbox.Properties // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources - { - + internal class Resources { + private static global::System.Resources.ResourceManager resourceMan; - + private static global::System.Globalization.CultureInfo resourceCulture; - + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() - { + internal Resources() { } - + /// /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager - { - get - { - if ((resourceMan == null)) - { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Sandbox.Properties.Resources", typeof(Resources).Assembly); + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("RichStringSandbox.Properties.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; } } - + /// /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture - { - get - { + internal static global::System.Globalization.CultureInfo Culture { + get { return resourceCulture; } - set - { + set { resourceCulture = value; } } diff --git a/RichStringSandbox/Properties/Settings.Designer.cs b/RichStringSandbox/Properties/Settings.Designer.cs index 68d968a..e282e24 100644 --- a/RichStringSandbox/Properties/Settings.Designer.cs +++ b/RichStringSandbox/Properties/Settings.Designer.cs @@ -8,21 +8,17 @@ // //------------------------------------------------------------------------------ -namespace Sandbox.Properties -{ - - +namespace RichStringSandbox.Properties { + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase - { - + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.8.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default - { - get - { + + public static Settings Default { + get { return defaultInstance; } } diff --git a/RichStringSandbox/RichStringSandbox.csproj b/RichStringSandbox/RichStringSandbox.csproj index ae4554f..774898c 100644 --- a/RichStringSandbox/RichStringSandbox.csproj +++ b/RichStringSandbox/RichStringSandbox.csproj @@ -8,12 +8,13 @@ WinExe RichStringSandbox RichStringSandbox - v4.7.1 + v4.8 512 true true + AnyCPU @@ -96,7 +97,7 @@ - + \ No newline at end of file diff --git a/Sandbox/App.config b/Sandbox/App.config index 27ca6b0..4feeee1 100644 --- a/Sandbox/App.config +++ b/Sandbox/App.config @@ -1,9 +1,9 @@ - + - + - + - \ No newline at end of file + diff --git a/Sandbox/Properties/Resources.Designer.cs b/Sandbox/Properties/Resources.Designer.cs index 666d94c..1d16ef7 100644 --- a/Sandbox/Properties/Resources.Designer.cs +++ b/Sandbox/Properties/Resources.Designer.cs @@ -8,10 +8,10 @@ // //------------------------------------------------------------------------------ -namespace Sandbox.Properties -{ - - +namespace Sandbox.Properties { + using System; + + /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -19,51 +19,43 @@ namespace Sandbox.Properties // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources - { - + internal class Resources { + private static global::System.Resources.ResourceManager resourceMan; - + private static global::System.Globalization.CultureInfo resourceCulture; - + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() - { + internal Resources() { } - + /// /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager - { - get - { - if ((resourceMan == null)) - { + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Sandbox.Properties.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; } } - + /// /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture - { - get - { + internal static global::System.Globalization.CultureInfo Culture { + get { return resourceCulture; } - set - { + set { resourceCulture = value; } } diff --git a/Sandbox/Properties/Settings.Designer.cs b/Sandbox/Properties/Settings.Designer.cs index 68d968a..24cc507 100644 --- a/Sandbox/Properties/Settings.Designer.cs +++ b/Sandbox/Properties/Settings.Designer.cs @@ -8,21 +8,17 @@ // //------------------------------------------------------------------------------ -namespace Sandbox.Properties -{ - - +namespace Sandbox.Properties { + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase - { - + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.8.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default - { - get - { + + public static Settings Default { + get { return defaultInstance; } } diff --git a/Sandbox/Sandbox.csproj b/Sandbox/Sandbox.csproj index b492f40..e9d17b4 100644 --- a/Sandbox/Sandbox.csproj +++ b/Sandbox/Sandbox.csproj @@ -8,12 +8,13 @@ WinExe Sandbox Sandbox - v4.7.1 + v4.8 512 true true + AnyCPU @@ -100,7 +101,7 @@ - + \ No newline at end of file diff --git a/SandboxDriver/SandboxDriver.csproj b/SandboxDriver/SandboxDriver.csproj index 7add069..06d7a48 100644 --- a/SandboxDriver/SandboxDriver.csproj +++ b/SandboxDriver/SandboxDriver.csproj @@ -13,7 +13,7 @@ - + diff --git a/Topten.RichTextKit.Test/Topten.RichTextKit.Test.csproj b/Topten.RichTextKit.Test/Topten.RichTextKit.Test.csproj index 4ff7ea6..4397156 100644 --- a/Topten.RichTextKit.Test/Topten.RichTextKit.Test.csproj +++ b/Topten.RichTextKit.Test/Topten.RichTextKit.Test.csproj @@ -14,7 +14,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Topten.RichTextKit.Test/WordBoundaryTest.cs b/Topten.RichTextKit.Test/WordBoundaryTest.cs index 9a783c4..44a7174 100644 --- a/Topten.RichTextKit.Test/WordBoundaryTest.cs +++ b/Topten.RichTextKit.Test/WordBoundaryTest.cs @@ -91,6 +91,32 @@ public void Punctuation2() Assert.Equal(9, boundaries[2]); } + [Fact] + public void Punctuation3() + { + var str = ".Hello World"; + var boundaries = WordBoundaryAlgorithm.FindWordBoundaries(new Utf32Buffer(str).AsSlice()).ToList(); + + Assert.Equal(3, boundaries.Count); + Assert.Equal(0, boundaries[0]); + Assert.Equal(1, boundaries[1]); + Assert.Equal(7, boundaries[2]); + } + + [Fact] + public void Punctuation4() + { + var str = ".Hello.World."; + var boundaries = WordBoundaryAlgorithm.FindWordBoundaries(new Utf32Buffer(str).AsSlice()).ToList(); + + Assert.Equal(5, boundaries.Count); + Assert.Equal(0, boundaries[0]); + Assert.Equal(1, boundaries[1]); + Assert.Equal(6, boundaries[2]); + Assert.Equal(7, boundaries[3]); + Assert.Equal(12, boundaries[4]); + } + [Fact] public void TestIsWordBoundary() { diff --git a/Topten.RichTextKit/Editor/TextDocument.cs b/Topten.RichTextKit/Editor/TextDocument.cs index b0d780e..a156ac9 100644 --- a/Topten.RichTextKit/Editor/TextDocument.cs +++ b/Topten.RichTextKit/Editor/TextDocument.cs @@ -333,6 +333,54 @@ public float MeasuredWidth } } + /// + /// The total width of the content of the document + /// + /// + /// For line-wrap or non-line-wrap documents this is + /// the width of the widest paragraph. + /// + public float MeasuredContentWidth + { + get + { + Layout(); + return _measuredWidth; + } + } + + /// + /// Gets the actual measured overhang in each direction based on the + /// fonts used, and the supplied text. + /// + /// + /// The return rectangle describes overhang amounts for each edge - not + /// rectangle co-ordinates. + /// + public SKRect MeasuredOverhang + { + get + { + Layout(); + if (_paragraphs.Count == 0) + return new SKRect(); + + var overhang = _paragraphs[0].TextBlock.MeasuredOverhang; + float topOverhang = overhang.Top; + float bottomOverhang = _paragraphs[_paragraphs.Count - 1].TextBlock.MeasuredOverhang.Bottom; + float leftOverhang = overhang.Left; + float rightOverhang = overhang.Right; + for (int i = 1; i < _paragraphs.Count; i++) + { + overhang = _paragraphs[i].TextBlock.MeasuredOverhang; + leftOverhang = Math.Max(leftOverhang, overhang.Left); + rightOverhang = Math.Max(rightOverhang, overhang.Right); + } + + return new SKRect(leftOverhang, topOverhang, rightOverhang, bottomOverhang); + } + } + /// /// Gets the total length of the document in code points /// @@ -415,7 +463,40 @@ public IStyle GetStyleAtOffset(int offset) throw new NotImplementedException(); // Get style from text block - return para.TextBlock.GetStyleAtOffset(offset); + return para.TextBlock.GetStyleAtOffset(indexInPara); + } + + /// + /// Get the text for a part of the document + /// + /// The text to retrieve + /// The styled text + public StyledText Extract(TextRange range) + { + var other = new StyledText(); + + // Normalize and clamp range + range = range.Normalized.Clamp(Length - 1); + + // Get all subruns + foreach (var subrun in _paragraphs.GetInterectingRuns(range.Start, range.Length)) + { + // Get the paragraph + var para = _paragraphs[subrun.Index]; + if (para.TextBlock == null) + throw new NotImplementedException(); + + var styledText = para.TextBlock.Extract(subrun.Offset, subrun.Length); + foreach (var sr in styledText.StyleRuns) + { + other.AddText(sr.CodePoints, sr.Style); + } + } + + // Convert paragraph separators to new lines + other.CodePoints.Replace('\u2029', '\n'); + + return other; } /// @@ -805,13 +886,44 @@ public void ReplaceText(ITextDocumentView view, TextRange range, Slice code } var styledText = new StyledText(codePoints); - if (styledText != null) + if (styleToUse != null) { styledText.ApplyStyle(0, styledText.Length, styleToUse); } ReplaceTextInternal(view, range, styledText, semantics, -1); } + /// + /// Replaces a range of text with the specified text + /// + /// The view initiating the operation + /// The range to be replaced + /// The text to replace with + /// Controls how undo operations are coalesced and view selections updated" + public void ReplaceText(ITextDocumentView view, TextRange range, StyledText styledText, EditSemantics semantics) + { + // Check range is valid + if (range.Minimum < 0 || range.Maximum > this.Length) + throw new ArgumentException("Invalid range", nameof(range)); + + if (IsImeComposing) + FinishImeComposition(view); + + // Convert new lines to paragraph separators + if (PlainTextMode) + styledText.CodePoints.Replace('\n', '\u2029'); + + // Break at the first line break + if (SingleLineMode) + { + int breakPos = styledText.CodePoints.SubSlice(0, styledText.Length).IndexOfAny('\n', '\r', '\u2029'); + if (breakPos >= 0) + styledText.DeleteText(breakPos, styledText.Length - breakPos); + } + + ReplaceTextInternal(view, range, styledText, semantics, -1); + } + /// /// Indicates if an IME composition is currently in progress /// @@ -973,7 +1085,7 @@ int GetParagraphForCodePointIndex(CaretPosition position, out int indexInParagra Layout(); // Search paragraphs - int paraIndex = _paragraphs.BinarySearch(position.CodePointIndex, (para, a) => + int paraIndex = _paragraphs.BinarySearch(position.CodePointIndex, (para, a) => { if (a < para.CodePointIndex) return 1; @@ -1409,6 +1521,7 @@ int InsertInternal(int position, StyledText text) bool _layoutValid = false; bool _lineWrap = true; internal List _paragraphs; + UndoManager _undoManager; List _views = new List(); ITextDocumentView _initiatingView; diff --git a/Topten.RichTextKit/FontRun.cs b/Topten.RichTextKit/FontRun.cs index 1b5e3d9..a97068f 100644 --- a/Topten.RichTextKit/FontRun.cs +++ b/Topten.RichTextKit/FontRun.cs @@ -464,15 +464,36 @@ private FontRun SplitRTL(int splitAtCodePoint) /// Calculate any overhang for this text line /// /// + /// True to update topOverhang. + /// True to update bottomOverhang. /// /// - internal void UpdateOverhang(float right, ref float leftOverhang, ref float rightOverhang) + /// + /// + internal void UpdateOverhang(float right, bool updateTop, bool updateBottom, ref float leftOverhang, ref float rightOverhang, ref float topOverhang, ref float bottomOverhang) { if (RunKind == FontRunKind.TrailingWhitespace) return; if (Glyphs.Length == 0) return; + + if (Style.LineHeight < 1) + { + // If LineHeight is less than 100%, the line can have top and bottom overhang + if (updateTop) + { + var toh = -(TextHeight * (Style.LineHeight - 1) / 2); + if (toh > topOverhang) + topOverhang = toh; + } + if (updateBottom) + { + var boh = -(TextHeight * (Style.LineHeight - 1) / 2); + if (boh > bottomOverhang) + bottomOverhang = boh; + } + } using (var paint = new SKPaint()) { @@ -509,7 +530,7 @@ internal void UpdateOverhang(float right, ref float leftOverhang, ref float righ leftOverhang = loh; var roh = (gx + bounds[i].Right + 1) - right; - if (roh > rightOverhang) + if (roh > rightOverhang) rightOverhang = roh; } } @@ -638,11 +659,18 @@ internal void Paint(PaintTextContext ctx) // Get glyph positions var glyphPositions = GlyphPositions.ToArray(); + var scaledFontSize = this.Style.FontSize * glyphScale; + // Create the font if (_font == null) { - _font = new SKFont(this.Typeface, this.Style.FontSize * glyphScale); + _font = new SKFont(this.Typeface, scaledFontSize); } + else if (_font.Size != scaledFontSize) + { + _font.Size = scaledFontSize; + } + _font.Hinting = ctx.Options.Hinting; _font.Edging = ctx.Options.Edging; _font.Subpixel = ctx.Options.SubpixelPositioning; @@ -741,10 +769,10 @@ internal void Paint(PaintTextContext ctx) float strikeYPos = Line.YCoord + Line.BaseLine + (_font.Metrics.StrikeoutPosition ?? 0) + glyphVOffset; ctx.Canvas.DrawLine(new SKPoint(XCoord, strikeYPos), new SKPoint(XCoord + Width, strikeYPos), paintHalo); } - ctx.Canvas.DrawText(_textBlob, 0, 0, paintHalo); + ctx.Canvas.DrawText(_textBlob, 0, glyphVOffset, paintHalo); } - ctx.Canvas.DrawText(_textBlob, 0, 0, paint); + ctx.Canvas.DrawText(_textBlob, 0, glyphVOffset, paint); } } diff --git a/Topten.RichTextKit/IStyleExtensions.cs b/Topten.RichTextKit/IStyleExtensions.cs index a8cd76b..01187fd 100644 --- a/Topten.RichTextKit/IStyleExtensions.cs +++ b/Topten.RichTextKit/IStyleExtensions.cs @@ -31,7 +31,7 @@ public static class IStyleExtensions /// A key string public static string Key(this IStyle This) { - return $"{This.FontFamily}.{This.FontSize}.{This.FontWeight}.{This.FontWidth}.{This.FontItalic}.{This.Underline}.{This.StrikeThrough}.{This.LineHeight}.{This.TextColor}.{This.BackgroundColor}.{This.LetterSpacing}.{This.FontVariant}.{This.TextDirection}.{This.ReplacementCharacter}"; + return $"{This.FontFamily}.{This.FontSize}.{This.FontWeight}.{This.FontWidth}.{This.FontItalic}.{This.Underline}.{This.StrikeThrough}.{This.LineHeight}.{This.TextColor}.{This.BackgroundColor}.{This.LetterSpacing}.{This.FontVariant}.{This.TextDirection}.{This.ReplacementCharacter}.{This.HaloWidth}.{This.HaloColor}.{This.HaloBlur}"; } /// @@ -89,10 +89,13 @@ public static bool IsSame(this IStyle This, IStyle other) return false; if (This.StrikeThrough != other.StrikeThrough) return false; + if (This.HaloBlur != other.HaloBlur) + return false; + if (This.HaloColor != other.HaloColor) + return false; + if (This.HaloWidth != other.HaloWidth) + return false; return true; } - - - } } diff --git a/Topten.RichTextKit/LineBreakAlgorithm/WordBoundaryAlgorithm.cs b/Topten.RichTextKit/LineBreakAlgorithm/WordBoundaryAlgorithm.cs index 9ca80ab..db329bd 100644 --- a/Topten.RichTextKit/LineBreakAlgorithm/WordBoundaryAlgorithm.cs +++ b/Topten.RichTextKit/LineBreakAlgorithm/WordBoundaryAlgorithm.cs @@ -77,6 +77,8 @@ public static IEnumerable FindWordBoundaries(Slice codePoints) // Switch to a different word kind without a space // just emit a word boundary here yield return i; + // Update wordGroup to the new boundary class + wordGroup = bg; } } } diff --git a/Topten.RichTextKit/StyleManager.cs b/Topten.RichTextKit/StyleManager.cs index 1e26b39..d8b1f72 100644 --- a/Topten.RichTextKit/StyleManager.cs +++ b/Topten.RichTextKit/StyleManager.cs @@ -265,7 +265,7 @@ public IStyle Update( string fontFamily = null, float? fontSize = null, int? fontWeight = null, - SKFontStyleWidth? fontWidth = 0, + SKFontStyleWidth? fontWidth = null, bool? fontItalic = null, UnderlineStyle? underline = null, StrikeThroughStyle? strikeThrough = null, diff --git a/Topten.RichTextKit/TextBlock.cs b/Topten.RichTextKit/TextBlock.cs index 1b29bda..f6f6001 100644 --- a/Topten.RichTextKit/TextBlock.cs +++ b/Topten.RichTextKit/TextBlock.cs @@ -348,6 +348,8 @@ public void Layout() _measuredWidth = 0; _leftOverhang = null; _rightOverhang = null; + _topOverhang = null; + _bottomOverhang = null; _truncated = false; // Only layout if actually have some text @@ -610,14 +612,20 @@ public SKRect MeasuredOverhang var right = _maxWidth ?? MeasuredWidth; float leftOverhang = 0; float rightOverhang = 0; - foreach (var l in _lines) + float topOverhang = 0; + float bottomOverhang = 0; + for (int l = 0; l < _lines.Count; l++) { - l.UpdateOverhang(right, ref leftOverhang, ref rightOverhang); + bool updateTop = l == 0; + bool updateBottom = l == (_lines.Count - 1); + _lines[l].UpdateOverhang(right, updateTop, updateBottom, ref leftOverhang, ref rightOverhang, ref topOverhang, ref bottomOverhang); } _leftOverhang = leftOverhang; _rightOverhang = rightOverhang; + _topOverhang = topOverhang; + _bottomOverhang = bottomOverhang; } - return new SKRect(_leftOverhang.Value, 0, _rightOverhang.Value, 0); + return new SKRect(_leftOverhang.Value, _topOverhang.Value, _rightOverhang.Value, _bottomOverhang.Value); } } @@ -1034,6 +1042,16 @@ void InvalidateLayout() /// float? _rightOverhang = null; + /// + /// The required top overhang + /// + float? _topOverhang = null; + + /// + /// The required bottom overhang + /// + float? _bottomOverhang = null; + /// /// Indicates if the text was truncated by max height/max lines limitations /// @@ -1334,6 +1352,9 @@ public bool CanShapeWith(UnshapedRun next) { return typeface == next.typeface && style.FontSize == next.style.FontSize && + style.LetterSpacing == next.style.LetterSpacing && + style.FontVariant == next.style.FontVariant && + style.LineHeight == next.style.LineHeight && asFallbackFor == next.asFallbackFor && direction == next.direction && start + length == next.start; @@ -1413,6 +1434,7 @@ void BreakLines() // Skip line breaks bool breakLine = false; + bool isRequired = false; while (lbrIndex < lineBreakPositions.Count) { // Past this run? @@ -1447,6 +1469,7 @@ void BreakLines() if (lbr.Required) { breakLine = true; + isRequired = true; break; } } @@ -1475,10 +1498,24 @@ void BreakLines() fr = _fontRuns[frIndex]; var room = _maxWidthResolved - fr.XCoord; frSplitIndex = frIndex; - codePointIndexSplit = fr.FindBreakPosition(room, frSplitIndex == frIndexStartOfLine); - codePointIndexWrap = codePointIndexSplit; - while (codePointIndexWrap < _codePoints.Length && UnicodeClasses.LineBreakClass(_codePoints[codePointIndexWrap]) == LineBreakClass.SP) - codePointIndexWrap++; + + var hasValidLinebreak = codePointIndexSplit >= fr.Start && codePointIndexSplit <= fr.End; + + // Only break at character if there is no required line break we can use. + if (!(isRequired && hasValidLinebreak)) + { + var breakPosition = fr.FindBreakPosition(room, frSplitIndex == frIndexStartOfLine); + + // Prefer breaking at a character rather than a line break position. + if (!hasValidLinebreak || breakPosition > codePointIndexSplit) + { + codePointIndexSplit = breakPosition; + codePointIndexWrap = codePointIndexSplit; + while (codePointIndexWrap < _codePoints.Length && + UnicodeClasses.LineBreakClass(_codePoints[codePointIndexWrap]) == LineBreakClass.SP) + codePointIndexWrap++; + } + } } // Split it diff --git a/Topten.RichTextKit/TextLine.cs b/Topten.RichTextKit/TextLine.cs index 1f2734d..18cdf52 100644 --- a/Topten.RichTextKit/TextLine.cs +++ b/Topten.RichTextKit/TextLine.cs @@ -382,11 +382,11 @@ void updateClosest(float xPosition, int codePointIndex, TextDirection dir) } } - internal void UpdateOverhang(float right, ref float leftOverhang, ref float rightOverhang) + internal void UpdateOverhang(float right, bool updateTop, bool updateBottom, ref float leftOverhang, ref float rightOverhang, ref float topOverhang, ref float bottomOverhang) { foreach (var r in Runs) { - r.UpdateOverhang(right, ref leftOverhang, ref rightOverhang); + r.UpdateOverhang(right, updateTop, updateBottom, ref leftOverhang, ref rightOverhang, ref topOverhang, ref bottomOverhang); } } diff --git a/Topten.RichTextKit/Topten.RichTextKit.csproj b/Topten.RichTextKit/Topten.RichTextKit.csproj index 878c17e..c996bf2 100644 --- a/Topten.RichTextKit/Topten.RichTextKit.csproj +++ b/Topten.RichTextKit/Topten.RichTextKit.csproj @@ -3,9 +3,9 @@ - netstandard2.0;netcoreapp2.1;net462;net5.0 + netstandard2.0;net462;net5.0 - + True True @@ -20,12 +20,15 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + diff --git a/Topten.RichTextKit/Unicode/UnicodeTrie.cs b/Topten.RichTextKit/Unicode/UnicodeTrie.cs index 540b6fe..9fc3d58 100644 --- a/Topten.RichTextKit/Unicode/UnicodeTrie.cs +++ b/Topten.RichTextKit/Unicode/UnicodeTrie.cs @@ -40,10 +40,10 @@ public UnicodeTrie(Stream stream) using (var bw = new BinaryReader(infl2, Encoding.UTF8, true)) { _data = new int[dataLength]; - for (int i = 0; i < _data.Length; i++) - { - _data[i] = bw.ReadInt32(); - } + + // Read the entire stream, then convert to int[] + var byteData = bw.ReadBytes(_data.Length * sizeof(int)); + Buffer.BlockCopy(byteData, 0, _data, 0, byteData.Length); } } diff --git a/build.js b/build.js index 0f4af2a..3e88e37 100644 --- a/build.js +++ b/build.js @@ -23,7 +23,7 @@ if (bt.options.official) // Build docs if (!bt.options.nodoc) { - bt.run(`docsanity`); + bt.run(`docsanity-net5`); bt.run(`git add doc`); bt.run(`git commit --allow-empty -m "Updated documentation"`); } diff --git a/doc/.common.page b/doc/.common.page index dccf764..81ed651 100644 --- a/doc/.common.page +++ b/doc/.common.page @@ -529,7 +529,9 @@ navTree: - text: MarginLeft|./ref/Topten.RichTextKit.Editor.TextDocument.MarginLeft - text: MarginRight|./ref/Topten.RichTextKit.Editor.TextDocument.MarginRight - text: MarginTop|./ref/Topten.RichTextKit.Editor.TextDocument.MarginTop + - text: MeasuredContentWidth|./ref/Topten.RichTextKit.Editor.TextDocument.MeasuredContentWidth - text: MeasuredHeight|./ref/Topten.RichTextKit.Editor.TextDocument.MeasuredHeight + - text: MeasuredOverhang|./ref/Topten.RichTextKit.Editor.TextDocument.MeasuredOverhang - text: MeasuredWidth|./ref/Topten.RichTextKit.Editor.TextDocument.MeasuredWidth - text: PageWidth|./ref/Topten.RichTextKit.Editor.TextDocument.PageWidth - text: PlainTextMode|./ref/Topten.RichTextKit.Editor.TextDocument.PlainTextMode @@ -538,6 +540,7 @@ navTree: - text: UndoManager|./ref/Topten.RichTextKit.Editor.TextDocument.UndoManager - text: Methods subItems: + - text: Extract|./ref/Topten.RichTextKit.Editor.TextDocument.Extract - text: FinishImeComposition|./ref/Topten.RichTextKit.Editor.TextDocument.FinishImeComposition - text: GetCaretInfo|./ref/Topten.RichTextKit.Editor.TextDocument.GetCaretInfo - text: GetOvertypeRange|./ref/Topten.RichTextKit.Editor.TextDocument.GetOvertypeRange diff --git a/doc/index.page b/doc/index.page index a97e874..7770418 100644 --- a/doc/index.page +++ b/doc/index.page @@ -32,8 +32,8 @@ RichTextKit is a rich-text layout, measurement and rendering library for SkiaSha ## Under Development -RichTextKit is still under development. It works fine on Windows under net45 and -netcoreapp2.1 but hasn't been tested on other platforms. +RichTextKit is still under development. It works fine on Windows under net462 and +net5.0 but hasn't been tested on other platforms. Also, text layout in general and Unicode and international text specifically are a complicated topics - almost certainly there are issues I'm unaware of and I'm diff --git a/doc/install.page b/doc/install.page index e5893ea..ddfaf03 100644 --- a/doc/install.page +++ b/doc/install.page @@ -4,7 +4,7 @@ isMarkdown: false ---

Installation

NuGet

-

RichTextKit is available as a NuGet package for net45 and netcoreapp2.1 frameworks:

+

RichTextKit is available as a NuGet package for net462 and net5.0 frameworks:

Install-Package Topten.RichTextKit
 

Note that RichTextKit is currently still under development and has only been tested on diff --git a/doc/ref/Topten.RichTextKit.Editor.TextDocument.Extract.page b/doc/ref/Topten.RichTextKit.Editor.TextDocument.Extract.page new file mode 100644 index 0000000..af5678d --- /dev/null +++ b/doc/ref/Topten.RichTextKit.Editor.TextDocument.Extract.page @@ -0,0 +1,55 @@ +--- +title: "TextDocument.Extract" +isMarkdown: false +import: "../.common.page" +--- + +

TextDocument.Extract Method

+ + +

+ + Assembly: Topten.RichTextKit.dll
+ Namespace: Topten.RichTextKit.Editor
+ + Declaring Type: TextDocument
+
+

+ + + + + + + + +

Get the text for a part of the document

+ + +
public Topten.RichTextKit.StyledText Extract(Topten.RichTextKit.TextRange range);
+ + +

Parameters

+ + + + +
+ + Topten.RichTextKit.TextRange range +

The text to retrieve

+
+

Returns

+ + + + +
Topten.RichTextKit.StyledText

The styled text

+
+ + + + + + + diff --git a/doc/ref/Topten.RichTextKit.Editor.TextDocument.MeasuredContentWidth.page b/doc/ref/Topten.RichTextKit.Editor.TextDocument.MeasuredContentWidth.page new file mode 100644 index 0000000..62dd331 --- /dev/null +++ b/doc/ref/Topten.RichTextKit.Editor.TextDocument.MeasuredContentWidth.page @@ -0,0 +1,40 @@ +--- +title: "TextDocument.MeasuredContentWidth" +isMarkdown: false +import: "../.common.page" +--- + +

TextDocument.MeasuredContentWidth Property

+ + +

+ + Assembly: Topten.RichTextKit.dll
+ Namespace: Topten.RichTextKit.Editor
+ + Declaring Type: TextDocument
+
+

+ + + + +

The total width of the content of the document

+ + +
public float MeasuredContentWidth { get; }
+ +

Remarks

For line-wrap or non-line-wrap documents this is +the width of the widest paragraph.

+ +

Property Type

+ + + + +
float
+ + + + + diff --git a/doc/ref/Topten.RichTextKit.Editor.TextDocument.MeasuredOverhang.page b/doc/ref/Topten.RichTextKit.Editor.TextDocument.MeasuredOverhang.page new file mode 100644 index 0000000..e45b629 --- /dev/null +++ b/doc/ref/Topten.RichTextKit.Editor.TextDocument.MeasuredOverhang.page @@ -0,0 +1,41 @@ +--- +title: "TextDocument.MeasuredOverhang" +isMarkdown: false +import: "../.common.page" +--- + +

TextDocument.MeasuredOverhang Property

+ + +

+ + Assembly: Topten.RichTextKit.dll
+ Namespace: Topten.RichTextKit.Editor
+ + Declaring Type: TextDocument
+
+

+ + + + +

Gets the actual measured overhang in each direction based on the +fonts used, and the supplied text.

+ + +
public SkiaSharp.SKRect MeasuredOverhang { get; }
+ +

Remarks

The return rectangle describes overhang amounts for each edge - not +rectangle co-ordinates.

+ +

Property Type

+ + + + +
SkiaSharp.SKRect
+ + + + + diff --git a/doc/ref/Topten.RichTextKit.Editor.TextDocument.ReplaceText.page b/doc/ref/Topten.RichTextKit.Editor.TextDocument.ReplaceText.page index d182669..09b91c9 100644 --- a/doc/ref/Topten.RichTextKit.Editor.TextDocument.ReplaceText.page +++ b/doc/ref/Topten.RichTextKit.Editor.TextDocument.ReplaceText.page @@ -23,24 +23,75 @@ import: "../.common.page"

Overloads

- + - + + + + +
ReplaceText(ITextDocumentView, Topten.RichTextKit.TextRange, string, EditSemantics)ReplaceText(ITextDocumentView, Topten.RichTextKit.TextRange, Topten.RichTextKit.StyledText, EditSemantics)

Replaces a range of text with the specified text

ReplaceText(ITextDocumentView, Topten.RichTextKit.TextRange, Topten.RichTextKit.Utils.Slice<int>, EditSemantics)ReplaceText(ITextDocumentView, Topten.RichTextKit.TextRange, string, EditSemantics, Topten.RichTextKit.IStyle)

Replaces a range of text with the specified text

+
ReplaceText(ITextDocumentView, Topten.RichTextKit.TextRange, Topten.RichTextKit.Utils.Slice<int>, EditSemantics, Topten.RichTextKit.IStyle)

Replaces a range of text with the specified text

-

ReplaceText(ITextDocumentView, Topten.RichTextKit.TextRange, string, EditSemantics)

+

ReplaceText(ITextDocumentView, Topten.RichTextKit.TextRange, Topten.RichTextKit.StyledText, EditSemantics)

Replaces a range of text with the specified text

-
public void ReplaceText(ITextDocumentView view, Topten.RichTextKit.TextRange range, string text, EditSemantics semantics);
+
public void ReplaceText(ITextDocumentView view, Topten.RichTextKit.TextRange range, Topten.RichTextKit.StyledText styledText, EditSemantics semantics);
+ + +

Parameters

+ + + + + + + + + + + + + + + + +
+ + ITextDocumentView view +

The view initiating the operation

+
+ + Topten.RichTextKit.TextRange range +

The range to be replaced

+
+ + Topten.RichTextKit.StyledText styledText +

The text to replace with

+
+ + EditSemantics semantics +

Controls how undo operations are coalesced and view selections updated

+
+ + + + +

ReplaceText(ITextDocumentView, Topten.RichTextKit.TextRange, string, EditSemantics, Topten.RichTextKit.IStyle)

+ +

Replaces a range of text with the specified text

+ + +
public void ReplaceText(ITextDocumentView view, Topten.RichTextKit.TextRange range, string text, EditSemantics semantics, Topten.RichTextKit.IStyle styleToUse = null);

Parameters

@@ -74,6 +125,14 @@ import: "../.common.page" EditSemantics semantics + + + +

Controls how undo operations are coalesced and view selections updated

+
+ + Topten.RichTextKit.IStyle styleToUse + = null

The style to use for the added text (optional)

@@ -81,12 +140,12 @@ import: "../.common.page" -

ReplaceText(ITextDocumentView, Topten.RichTextKit.TextRange, Topten.RichTextKit.Utils.Slice<int>, EditSemantics)

+

ReplaceText(ITextDocumentView, Topten.RichTextKit.TextRange, Topten.RichTextKit.Utils.Slice<int>, EditSemantics, Topten.RichTextKit.IStyle)

Replaces a range of text with the specified text

-
public void ReplaceText(ITextDocumentView view, Topten.RichTextKit.TextRange range, Topten.RichTextKit.Utils.Slice<int> codePoints, EditSemantics semantics);
+
public void ReplaceText(ITextDocumentView view, Topten.RichTextKit.TextRange range, Topten.RichTextKit.Utils.Slice<int> codePoints, EditSemantics semantics, Topten.RichTextKit.IStyle styleToUse = null);

Parameters

@@ -120,6 +179,14 @@ import: "../.common.page" EditSemantics semantics + + + +

Controls how undo operations are coalesced and view selections updated

+
+ + Topten.RichTextKit.IStyle styleToUse + = null

The style to use for the added text (optional)

diff --git a/doc/ref/Topten.RichTextKit.Editor.TextDocument.page b/doc/ref/Topten.RichTextKit.Editor.TextDocument.page index e7cd54c..1797533 100644 --- a/doc/ref/Topten.RichTextKit.Editor.TextDocument.page +++ b/doc/ref/Topten.RichTextKit.Editor.TextDocument.page @@ -91,11 +91,22 @@ import: "../.common.page" MarginTop

The document's top margin

+ + + + MeasuredContentWidth +

The total width of the content of the document

MeasuredHeight

The total height of the document

+ + + + MeasuredOverhang +

Gets the actual measured overhang in each direction based on the +fonts used, and the supplied text.

@@ -132,6 +143,11 @@ import: "../.common.page"

Methods

+ + + + - + + + + + - + diff --git a/doc/ref/Topten.RichTextKit.StyleManager.Update.page b/doc/ref/Topten.RichTextKit.StyleManager.Update.page index 7d7d71a..ba54be8 100644 --- a/doc/ref/Topten.RichTextKit.StyleManager.Update.page +++ b/doc/ref/Topten.RichTextKit.StyleManager.Update.page @@ -27,7 +27,7 @@ import: "../.common.page" style.

-
public IStyle Update(string fontFamily = null, Nullable<float> fontSize = null, Nullable<int> fontWeight = null, Nullable<SkiaSharp.SKFontStyleWidth> fontWidth = 0, Nullable<bool> fontItalic = null, Nullable<UnderlineStyle> underline = null, Nullable<StrikeThroughStyle> strikeThrough = null, Nullable<float> lineHeight = null, Nullable<SkiaSharp.SKColor> textColor = null, Nullable<SkiaSharp.SKColor> backgroundColor = null, Nullable<SkiaSharp.SKColor> haloColor = null, Nullable<float> haloWidth = null, Nullable<float> haloBlur = null, Nullable<float> letterSpacing = null, Nullable<FontVariant> fontVariant = null, Nullable<TextDirection> textDirection = null, Nullable<char> replacementCharacter = null);
+
public IStyle Update(string fontFamily = null, Nullable<float> fontSize = null, Nullable<int> fontWeight = null, Nullable<SkiaSharp.SKFontStyleWidth> fontWidth = null, Nullable<bool> fontItalic = null, Nullable<UnderlineStyle> underline = null, Nullable<StrikeThroughStyle> strikeThrough = null, Nullable<float> lineHeight = null, Nullable<SkiaSharp.SKColor> textColor = null, Nullable<SkiaSharp.SKColor> backgroundColor = null, Nullable<SkiaSharp.SKColor> haloColor = null, Nullable<float> haloWidth = null, Nullable<float> haloBlur = null, Nullable<float> letterSpacing = null, Nullable<FontVariant> fontVariant = null, Nullable<TextDirection> textDirection = null, Nullable<char> replacementCharacter = null);

Parameters

Extract(Topten.RichTextKit.TextRange)

Get the text for a part of the document

+
FinishImeComposition(ITextDocumentView)

Complete an IME composition

@@ -190,12 +206,17 @@ current word, line, paragraph or document

ReplaceText(ITextDocumentView, Topten.RichTextKit.TextRange, string, EditSemantics)ReplaceText(ITextDocumentView, Topten.RichTextKit.TextRange, Topten.RichTextKit.StyledText, EditSemantics)

Replaces a range of text with the specified text

+
ReplaceText(ITextDocumentView, Topten.RichTextKit.TextRange, string, EditSemantics, Topten.RichTextKit.IStyle)

Replaces a range of text with the specified text

ReplaceText(ITextDocumentView, Topten.RichTextKit.TextRange, Topten.RichTextKit.Utils.Slice<int>, EditSemantics)ReplaceText(ITextDocumentView, Topten.RichTextKit.TextRange, Topten.RichTextKit.Utils.Slice<int>, EditSemantics, Topten.RichTextKit.IStyle)

Replaces a range of text with the specified text

@@ -59,7 +59,7 @@ style.

+ = null diff --git a/docsrc/index.page b/docsrc/index.page index a97e874..7770418 100644 --- a/docsrc/index.page +++ b/docsrc/index.page @@ -32,8 +32,8 @@ RichTextKit is a rich-text layout, measurement and rendering library for SkiaSha ## Under Development -RichTextKit is still under development. It works fine on Windows under net45 and -netcoreapp2.1 but hasn't been tested on other platforms. +RichTextKit is still under development. It works fine on Windows under net462 and +net5.0 but hasn't been tested on other platforms. Also, text layout in general and Unicode and international text specifically are a complicated topics - almost certainly there are issues I'm unaware of and I'm diff --git a/docsrc/install.md b/docsrc/install.md index 661da94..3110d41 100644 --- a/docsrc/install.md +++ b/docsrc/install.md @@ -6,7 +6,7 @@ title: Installation ## NuGet -RichTextKit is available as a NuGet package for `net45` and `netcoreapp2.1` frameworks: +RichTextKit is available as a NuGet package for `net462` and `net5.0` frameworks: ~~~ Install-Package Topten.RichTextKit diff --git a/readme.md b/readme.md index b4a350a..a415b3a 100644 --- a/readme.md +++ b/readme.md @@ -25,8 +25,8 @@ RichTextKit is a rich-text layout, measurement and rendering library for SkiaSha ## Under Development -RichTextKit is still under development. It works fine on Windows under net45 and -netcoreapp2.1 but hasn't been tested on other platforms. +RichTextKit is still under development. It works fine on Windows under net462 and +net5.0 but hasn't been tested on other platforms. Also, text layout in general and Unicode and international text specifically are a complicated topics - almost certainly there are issues I'm unaware of and I'm diff --git a/version.cs b/version.cs index c2ffb2a..c905572 100644 --- a/version.cs +++ b/version.cs @@ -2,14 +2,14 @@ // Generated by build tool, do not edit using System; using System.Reflection; -[assembly: AssemblyCopyright("Copyright © 2019-2022 Topten Software. All Rights Reserved")] -[assembly: AssemblyVersion("0.4.165")] -[assembly: AssemblyFileVersion("0.4.165")] +[assembly: AssemblyCopyright("Copyright © 2019-2024 Topten Software. All Rights Reserved")] +[assembly: AssemblyVersion("0.4.167")] +[assembly: AssemblyFileVersion("0.4.167")] [assembly: AssemblyCompany("Topten Software")] [assembly: AssemblyProduct("Topten.RichTextKit")] static class BuildInfo { - public static DateTime Date = new DateTime(2022, 12, 7, 0, 42, 24, DateTimeKind.Utc); + public static DateTime Date = new DateTime(2024, 3, 6, 1, 23, 57, DateTimeKind.Utc); } \ No newline at end of file diff --git a/version.json b/version.json index 1d9c2e9..d5a700d 100644 --- a/version.json +++ b/version.json @@ -1,7 +1,7 @@ { "major": 0, "minor": 4, - "build": 165, + "build": 167, "suffix": "", "copyrightYear": 2019, "productName": "Topten.RichTextKit", diff --git a/version.props b/version.props index fddaa8a..0a83fbf 100644 --- a/version.props +++ b/version.props @@ -2,7 +2,7 @@ - 0.4.165 + 0.4.167 \ No newline at end of file
Nullable<SkiaSharp.SKFontStyleWidth> fontWidth - = 0

The new font width