@@ -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 ;
0 commit comments