Skip to content

Commit a54796a

Browse files
dbriardDavid BRIARDtoptensoftware
authored
Fixed several issues (toptensoftware#83)
* Bad test * Bad default parameter * Missing halo parameters * Fix .net standard 2.0 nullable compilation issue * Added Extract and Insert styled text * Fixed SuperScript and SubScript * Fix for TextShaping and SubScript/SuperScript * Compute top/bottom overhang when Style.LineHeight is less than 1, and add MeasuredOverhang on TextDocument. --------- Co-authored-by: David BRIARD <dbriard@avanquest.com> Co-authored-by: Brad Robinson <github@toptensoftware.com>
1 parent d7ab3d7 commit a54796a

7 files changed

Lines changed: 195 additions & 16 deletions

File tree

Topten.RichTextKit/Editor/TextDocument.cs

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,54 @@ public float MeasuredWidth
333333
}
334334
}
335335

336+
/// <summary>
337+
/// The total width of the content of the document
338+
/// </summary>
339+
/// <remarks>
340+
/// For line-wrap or non-line-wrap documents this is
341+
/// the width of the widest paragraph.
342+
/// </remarks>
343+
public float MeasuredContentWidth
344+
{
345+
get
346+
{
347+
Layout();
348+
return _measuredWidth;
349+
}
350+
}
351+
352+
/// <summary>
353+
/// Gets the actual measured overhang in each direction based on the
354+
/// fonts used, and the supplied text.
355+
/// </summary>
356+
/// <remarks>
357+
/// The return rectangle describes overhang amounts for each edge - not
358+
/// rectangle co-ordinates.
359+
/// </remarks>
360+
public SKRect MeasuredOverhang
361+
{
362+
get
363+
{
364+
Layout();
365+
if (_paragraphs.Count == 0)
366+
return new SKRect();
367+
368+
var overhang = _paragraphs[0].TextBlock.MeasuredOverhang;
369+
float topOverhang = overhang.Top;
370+
float bottomOverhang = _paragraphs[_paragraphs.Count - 1].TextBlock.MeasuredOverhang.Bottom;
371+
float leftOverhang = overhang.Left;
372+
float rightOverhang = overhang.Right;
373+
for (int i = 1; i < _paragraphs.Count; i++)
374+
{
375+
overhang = _paragraphs[i].TextBlock.MeasuredOverhang;
376+
leftOverhang = Math.Max(leftOverhang, overhang.Left);
377+
rightOverhang = Math.Max(rightOverhang, overhang.Right);
378+
}
379+
380+
return new SKRect(leftOverhang, topOverhang, rightOverhang, bottomOverhang);
381+
}
382+
}
383+
336384
/// <summary>
337385
/// Gets the total length of the document in code points
338386
/// </summary>
@@ -418,6 +466,39 @@ public IStyle GetStyleAtOffset(int offset)
418466
return para.TextBlock.GetStyleAtOffset(offset);
419467
}
420468

469+
/// <summary>
470+
/// Get the text for a part of the document
471+
/// </summary>
472+
/// <param name="range">The text to retrieve</param>
473+
/// <returns>The styled text</returns>
474+
public StyledText Extract(TextRange range)
475+
{
476+
var other = new StyledText();
477+
478+
// Normalize and clamp range
479+
range = range.Normalized.Clamp(Length - 1);
480+
481+
// Get all subruns
482+
foreach (var subrun in _paragraphs.GetInterectingRuns(range.Start, range.Length))
483+
{
484+
// Get the paragraph
485+
var para = _paragraphs[subrun.Index];
486+
if (para.TextBlock == null)
487+
throw new NotImplementedException();
488+
489+
var styledText = para.TextBlock.Extract(subrun.Offset, subrun.Length);
490+
foreach (var sr in styledText.StyleRuns)
491+
{
492+
other.AddText(sr.CodePoints, sr.Style);
493+
}
494+
}
495+
496+
// Convert paragraph separators to new lines
497+
other.CodePoints.Replace('\u2029', '\n');
498+
499+
return other;
500+
}
501+
421502
/// <summary>
422503
/// Handles keyboard navigation events
423504
/// </summary>
@@ -812,6 +893,37 @@ public void ReplaceText(ITextDocumentView view, TextRange range, Slice<int> code
812893
ReplaceTextInternal(view, range, styledText, semantics, -1);
813894
}
814895

896+
/// <summary>
897+
/// Replaces a range of text with the specified text
898+
/// </summary>
899+
/// <param name="view">The view initiating the operation</param>
900+
/// <param name="range">The range to be replaced</param>
901+
/// <param name="styledText">The text to replace with</param>
902+
/// <param name="semantics">Controls how undo operations are coalesced and view selections updated</param>"
903+
public void ReplaceText(ITextDocumentView view, TextRange range, StyledText styledText, EditSemantics semantics)
904+
{
905+
// Check range is valid
906+
if (range.Minimum < 0 || range.Maximum > this.Length)
907+
throw new ArgumentException("Invalid range", nameof(range));
908+
909+
if (IsImeComposing)
910+
FinishImeComposition(view);
911+
912+
// Convert new lines to paragraph separators
913+
if (PlainTextMode)
914+
styledText.CodePoints.Replace('\n', '\u2029');
915+
916+
// Break at the first line break
917+
if (SingleLineMode)
918+
{
919+
int breakPos = styledText.CodePoints.SubSlice(0, styledText.Length).IndexOfAny('\n', '\r', '\u2029');
920+
if (breakPos >= 0)
921+
styledText.DeleteText(breakPos, styledText.Length - breakPos);
922+
}
923+
924+
ReplaceTextInternal(view, range, styledText, semantics, -1);
925+
}
926+
815927
/// <summary>
816928
/// Indicates if an IME composition is currently in progress
817929
/// </summary>
@@ -973,7 +1085,7 @@ int GetParagraphForCodePointIndex(CaretPosition position, out int indexInParagra
9731085
Layout();
9741086

9751087
// Search paragraphs
976-
int paraIndex = _paragraphs.BinarySearch(position.CodePointIndex, (para, a) =>
1088+
int paraIndex = _paragraphs.BinarySearch(position.CodePointIndex, (para, a) =>
9771089
{
9781090
if (a < para.CodePointIndex)
9791091
return 1;
@@ -1409,6 +1521,7 @@ int InsertInternal(int position, StyledText text)
14091521
bool _layoutValid = false;
14101522
bool _lineWrap = true;
14111523
internal List<Paragraph> _paragraphs;
1524+
14121525
UndoManager<TextDocument> _undoManager;
14131526
List<ITextDocumentView> _views = new List<ITextDocumentView>();
14141527
ITextDocumentView _initiatingView;

Topten.RichTextKit/FontRun.cs

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -464,15 +464,36 @@ private FontRun SplitRTL(int splitAtCodePoint)
464464
/// Calculate any overhang for this text line
465465
/// </summary>
466466
/// <param name="right"></param>
467+
/// <param name="updateTop">True to update topOverhang.</param>
468+
/// <param name="updateBottom">True to update bottomOverhang.</param>
467469
/// <param name="leftOverhang"></param>
468470
/// <param name="rightOverhang"></param>
469-
internal void UpdateOverhang(float right, ref float leftOverhang, ref float rightOverhang)
471+
/// <param name="topOverhang"></param>
472+
/// <param name="bottomOverhang"></param>
473+
internal void UpdateOverhang(float right, bool updateTop, bool updateBottom, ref float leftOverhang, ref float rightOverhang, ref float topOverhang, ref float bottomOverhang)
470474
{
471475
if (RunKind == FontRunKind.TrailingWhitespace)
472476
return;
473477

474478
if (Glyphs.Length == 0)
475479
return;
480+
481+
if (Style.LineHeight < 1)
482+
{
483+
// If LineHeight is less than 100%, the line can have top and bottom overhang
484+
if (updateTop)
485+
{
486+
var toh = -(TextHeight * (Style.LineHeight - 1) / 2);
487+
if (toh > topOverhang)
488+
topOverhang = toh;
489+
}
490+
if (updateBottom)
491+
{
492+
var boh = -(TextHeight * (Style.LineHeight - 1) / 2);
493+
if (boh > bottomOverhang)
494+
bottomOverhang = boh;
495+
}
496+
}
476497

477498
using (var paint = new SKPaint())
478499
{
@@ -509,7 +530,7 @@ internal void UpdateOverhang(float right, ref float leftOverhang, ref float righ
509530
leftOverhang = loh;
510531

511532
var roh = (gx + bounds[i].Right + 1) - right;
512-
if (roh > rightOverhang)
533+
if (roh > rightOverhang)
513534
rightOverhang = roh;
514535
}
515536
}
@@ -638,11 +659,18 @@ internal void Paint(PaintTextContext ctx)
638659
// Get glyph positions
639660
var glyphPositions = GlyphPositions.ToArray();
640661

662+
var scaledFontSize = this.Style.FontSize * glyphScale;
663+
641664
// Create the font
642665
if (_font == null)
643666
{
644-
_font = new SKFont(this.Typeface, this.Style.FontSize * glyphScale);
667+
_font = new SKFont(this.Typeface, scaledFontSize);
645668
}
669+
else if (_font.Size != scaledFontSize)
670+
{
671+
_font.Size = scaledFontSize;
672+
}
673+
646674
_font.Hinting = ctx.Options.Hinting;
647675
_font.Edging = ctx.Options.Edging;
648676
_font.Subpixel = ctx.Options.SubpixelPositioning;
@@ -741,10 +769,10 @@ internal void Paint(PaintTextContext ctx)
741769
float strikeYPos = Line.YCoord + Line.BaseLine + (_font.Metrics.StrikeoutPosition ?? 0) + glyphVOffset;
742770
ctx.Canvas.DrawLine(new SKPoint(XCoord, strikeYPos), new SKPoint(XCoord + Width, strikeYPos), paintHalo);
743771
}
744-
ctx.Canvas.DrawText(_textBlob, 0, 0, paintHalo);
772+
ctx.Canvas.DrawText(_textBlob, 0, glyphVOffset, paintHalo);
745773
}
746774

747-
ctx.Canvas.DrawText(_textBlob, 0, 0, paint);
775+
ctx.Canvas.DrawText(_textBlob, 0, glyphVOffset, paint);
748776
}
749777
}
750778

Topten.RichTextKit/IStyleExtensions.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public static class IStyleExtensions
3131
/// <returns>A key string</returns>
3232
public static string Key(this IStyle This)
3333
{
34-
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}";
34+
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}";
3535
}
3636

3737
/// <summary>
@@ -89,10 +89,13 @@ public static bool IsSame(this IStyle This, IStyle other)
8989
return false;
9090
if (This.StrikeThrough != other.StrikeThrough)
9191
return false;
92+
if (This.HaloBlur != other.HaloBlur)
93+
return false;
94+
if (This.HaloColor != other.HaloColor)
95+
return false;
96+
if (This.HaloWidth != other.HaloWidth)
97+
return false;
9298
return true;
9399
}
94-
95-
96-
97100
}
98101
}

Topten.RichTextKit/StyleManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ public IStyle Update(
265265
string fontFamily = null,
266266
float? fontSize = null,
267267
int? fontWeight = null,
268-
SKFontStyleWidth? fontWidth = 0,
268+
SKFontStyleWidth? fontWidth = null,
269269
bool? fontItalic = null,
270270
UnderlineStyle? underline = null,
271271
StrikeThroughStyle? strikeThrough = null,

Topten.RichTextKit/TextBlock.cs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,8 @@ public void Layout()
348348
_measuredWidth = 0;
349349
_leftOverhang = null;
350350
_rightOverhang = null;
351+
_topOverhang = null;
352+
_bottomOverhang = null;
351353
_truncated = false;
352354

353355
// Only layout if actually have some text
@@ -610,14 +612,20 @@ public SKRect MeasuredOverhang
610612
var right = _maxWidth ?? MeasuredWidth;
611613
float leftOverhang = 0;
612614
float rightOverhang = 0;
613-
foreach (var l in _lines)
615+
float topOverhang = 0;
616+
float bottomOverhang = 0;
617+
for (int l = 0; l < _lines.Count; l++)
614618
{
615-
l.UpdateOverhang(right, ref leftOverhang, ref rightOverhang);
619+
bool updateTop = l == 0;
620+
bool updateBottom = l == (_lines.Count - 1);
621+
_lines[l].UpdateOverhang(right, updateTop, updateBottom, ref leftOverhang, ref rightOverhang, ref topOverhang, ref bottomOverhang);
616622
}
617623
_leftOverhang = leftOverhang;
618624
_rightOverhang = rightOverhang;
625+
_topOverhang = topOverhang;
626+
_bottomOverhang = bottomOverhang;
619627
}
620-
return new SKRect(_leftOverhang.Value, 0, _rightOverhang.Value, 0);
628+
return new SKRect(_leftOverhang.Value, _topOverhang.Value, _rightOverhang.Value, _bottomOverhang.Value);
621629
}
622630
}
623631

@@ -1034,6 +1042,16 @@ void InvalidateLayout()
10341042
/// </summary>
10351043
float? _rightOverhang = null;
10361044

1045+
/// <summary>
1046+
/// The required top overhang
1047+
/// </summary>
1048+
float? _topOverhang = null;
1049+
1050+
/// <summary>
1051+
/// The required bottom overhang
1052+
/// </summary>
1053+
float? _bottomOverhang = null;
1054+
10371055
/// <summary>
10381056
/// Indicates if the text was truncated by max height/max lines limitations
10391057
/// </summary>
@@ -1334,6 +1352,9 @@ public bool CanShapeWith(UnshapedRun next)
13341352
{
13351353
return typeface == next.typeface &&
13361354
style.FontSize == next.style.FontSize &&
1355+
style.LetterSpacing == next.style.LetterSpacing &&
1356+
style.FontVariant == next.style.FontVariant &&
1357+
style.LineHeight == next.style.LineHeight &&
13371358
asFallbackFor == next.asFallbackFor &&
13381359
direction == next.direction &&
13391360
start + length == next.start;

Topten.RichTextKit/TextLine.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -382,11 +382,11 @@ void updateClosest(float xPosition, int codePointIndex, TextDirection dir)
382382
}
383383
}
384384

385-
internal void UpdateOverhang(float right, ref float leftOverhang, ref float rightOverhang)
385+
internal void UpdateOverhang(float right, bool updateTop, bool updateBottom, ref float leftOverhang, ref float rightOverhang, ref float topOverhang, ref float bottomOverhang)
386386
{
387387
foreach (var r in Runs)
388388
{
389-
r.UpdateOverhang(right, ref leftOverhang, ref rightOverhang);
389+
r.UpdateOverhang(right, updateTop, updateBottom, ref leftOverhang, ref rightOverhang, ref topOverhang, ref bottomOverhang);
390390
}
391391
}
392392

Topten.RichTextKit/Topten.RichTextKit.csproj

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@
55
<PropertyGroup>
66
<TargetFrameworks>netstandard2.0;netcoreapp2.1;net462;net5.0</TargetFrameworks>
77
</PropertyGroup>
8+
9+
<!-- Set the LangVersion = 8 -->
10+
<PropertyGroup>
11+
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
12+
<LangVersion>8.0</LangVersion>
13+
</PropertyGroup>
14+
15+
<!-- Only enable nullable feature for the supported frameworks -->
16+
<PropertyGroup Condition=" '$(TargetFramework)' != 'netstandard2.0' ">
17+
<Nullable>enable</Nullable>
18+
</PropertyGroup>
19+
<PropertyGroup Condition=" $(Nullable) != 'enable' ">
20+
<NoWarn>$(NoWarn);CS8632</NoWarn>
21+
</PropertyGroup>
822

923
<PropertyGroup Condition="'$(Configuration)'=='Release'">
1024
<TtsCodeSign>True</TtsCodeSign>

0 commit comments

Comments
 (0)