55using SixLabors . Fonts ;
66using SixLabors . ImageSharp . Drawing ;
77using SixLabors . ImageSharp . Drawing . Processing ;
8+ using Brush = SixLabors . ImageSharp . Drawing . Processing . Brush ;
89using Brushes = SixLabors . ImageSharp . Drawing . Processing . Brushes ;
910using Color = SixLabors . ImageSharp . Color ;
1011using Font = SixLabors . Fonts . Font ;
1112using FontFamily = SixLabors . Fonts . FontFamily ;
1213using FontStyle = SixLabors . Fonts . FontStyle ;
14+ using Pen = SixLabors . ImageSharp . Drawing . Processing . Pen ;
1315using Pens = SixLabors . ImageSharp . Drawing . Processing . Pens ;
1416using PointF = SixLabors . ImageSharp . PointF ;
1517using Size = SixLabors . ImageSharp . Size ;
@@ -67,10 +69,28 @@ internal sealed class ManualTextFlowScene : RenderScene
6769 private static readonly Color SlotColor = Color . SteelBlue . WithAlpha ( .24F ) ;
6870 private static readonly Color CircleColor = Color . SteelBlue . WithAlpha ( .18F ) ;
6971 private static readonly Color TextColor = Color . ParseHex ( "#111827" ) ;
72+ private static readonly Brush BackgroundBrush = Brushes . Solid ( BackgroundColor ) ;
73+ private static readonly Brush PageBrush = Brushes . Solid ( PageColor ) ;
74+ private static readonly Brush SlotBrush = Brushes . Solid ( SlotColor ) ;
75+ private static readonly Brush ObstacleBrush = Brushes . Solid ( CircleColor ) ;
76+ private static readonly Brush TextBrush = Brushes . Solid ( TextColor ) ;
77+ private static readonly Pen PageOutlinePen = Pens . Solid ( PageOutlineColor , 1.5F ) ;
78+ private static readonly Pen ObstacleOutlinePen = Pens . Solid ( Color . SteelBlue , 2F ) ;
7079
7180 private readonly Font bodyFont ;
7281 private readonly TextBlock textBlock ;
82+ private readonly List < BandInterval > scanBlockedIntervals = new ( 16 ) ;
83+ private readonly List < BandInterval > blockedIntervals = new ( 16 ) ;
84+ private readonly List < BandInterval > slots = new ( 8 ) ;
85+ private readonly List < float > scanYs = new ( 8 ) ;
86+ private readonly List < float > intersections = new ( 16 ) ;
7387 private PointF obstacleCenter ;
88+ private ManualTextFlowObstacleShape obstacleShape = ManualTextFlowObstacleShape . Circle ;
89+ private IPath ? cachedObstaclePath ;
90+ private LinearGeometry ? cachedObstacleGeometry ;
91+ private PointF cachedObstacleCenter ;
92+ private float cachedObstacleSize ;
93+ private ManualTextFlowObstacleShape cachedObstacleShape ;
7494 private bool hasPointer ;
7595
7696 public ManualTextFlowScene ( )
@@ -98,12 +118,26 @@ public ManualTextFlowScene()
98118 /// <summary>
99119 /// Gets or sets the closed shape used as the flow obstacle.
100120 /// </summary>
101- public ManualTextFlowObstacleShape ObstacleShape { get ; set ; } = ManualTextFlowObstacleShape . Circle ;
121+ public ManualTextFlowObstacleShape ObstacleShape
122+ {
123+ get => this . obstacleShape ;
124+ set
125+ {
126+ if ( this . obstacleShape == value )
127+ {
128+ return ;
129+ }
130+
131+ this . obstacleShape = value ;
132+ this . cachedObstaclePath = null ;
133+ this . cachedObstacleGeometry = null ;
134+ }
135+ }
102136
103137 public override void Paint ( DrawingCanvas canvas , TimeSpan deltaTime )
104138 {
105139 Size viewportSize = canvas . Bounds . Size ;
106- canvas . Fill ( Brushes . Solid ( BackgroundColor ) , canvas . Bounds ) ;
140+ canvas . Fill ( BackgroundBrush , canvas . Bounds ) ;
107141
108142 // The page rectangle is the region we subtract obstacle coverage from.
109143 // Text slots are always produced within these left/right limits.
@@ -132,23 +166,33 @@ public override void Paint(DrawingCanvas canvas, TimeSpan deltaTime)
132166 // The visible obstacle is deliberately just an IPath. The text-flow code
133167 // below never needs to know whether this came from a circle, star, rectangle,
134168 // or any other closed shape that can be linearized by ImageSharp.Drawing.
135- IPath obstaclePath = this . CreateObstaclePath ( obstacleCenter , obstacleSize ) ;
136- LinearGeometry obstacleGeometry = obstaclePath . ToLinearGeometry ( Vector2 . One ) ;
169+ // Linearization is the expensive part, so reuse it until the obstacle input
170+ // changes through pointer movement, resizing, or the shape selector.
171+ IPath ? obstaclePath = this . cachedObstaclePath ;
172+ LinearGeometry ? obstacleGeometry = this . cachedObstacleGeometry ;
173+ if ( obstaclePath is null
174+ || obstacleGeometry is null
175+ || ! this . cachedObstacleCenter . Equals ( obstacleCenter )
176+ || this . cachedObstacleSize != obstacleSize
177+ || this . cachedObstacleShape != this . obstacleShape )
178+ {
179+ obstaclePath = this . CreateObstaclePath ( obstacleCenter , obstacleSize ) ;
180+ obstacleGeometry = obstaclePath . ToLinearGeometry ( Vector2 . One ) ;
181+ this . cachedObstaclePath = obstaclePath ;
182+ this . cachedObstacleGeometry = obstacleGeometry ;
183+ this . cachedObstacleCenter = obstacleCenter ;
184+ this . cachedObstacleSize = obstacleSize ;
185+ this . cachedObstacleShape = this . obstacleShape ;
186+ }
137187
138- canvas . Fill ( Brushes . Solid ( PageColor ) , new RectangularPolygon ( pageLeft , pageTop , pageRight - pageLeft , pageBottom - pageTop ) ) ;
139- canvas . Draw ( Pens . Solid ( PageOutlineColor , 1.5F ) , new RectangularPolygon ( pageLeft , pageTop , pageRight - pageLeft , pageBottom - pageTop ) ) ;
140- canvas . Fill ( Brushes . Solid ( CircleColor ) , obstaclePath ) ;
141- canvas . Draw ( Pens . Solid ( Color . SteelBlue , 2F ) , obstaclePath ) ;
188+ canvas . Fill ( PageBrush , new RectangularPolygon ( pageLeft , pageTop , pageRight - pageLeft , pageBottom - pageTop ) ) ;
189+ canvas . Draw ( PageOutlinePen , new RectangularPolygon ( pageLeft , pageTop , pageRight - pageLeft , pageBottom - pageTop ) ) ;
190+ canvas . Fill ( ObstacleBrush , obstaclePath ) ;
191+ canvas . Draw ( ObstacleOutlinePen , obstaclePath ) ;
142192
143193 // Rows can be split into several usable slots by an arbitrary closed path.
144- // Keep the working buffers outside the row loop so pointer movement does
145- // not introduce avoidable per-line allocations.
146- List < BandInterval > scanBlockedIntervals = new ( 16 ) ;
147- List < BandInterval > blockedIntervals = new ( 16 ) ;
148- List < BandInterval > slots = new ( 8 ) ;
149- List < float > scanYs = new ( 8 ) ;
150- List < float > intersections = new ( 16 ) ;
151-
194+ // The scene owns the temporary buffers so continuous repainting does not
195+ // allocate fresh lists for every row of every frame.
152196 while ( hasMoreText && y < pageBottom )
153197 {
154198 float rowProbeHeight = this . bodyFont . Size * 1.45F ;
@@ -167,16 +211,16 @@ public override void Paint(DrawingCanvas canvas, TimeSpan deltaTime)
167211 pageRight ,
168212 obstaclePadding ,
169213 minSlotWidth ,
170- scanBlockedIntervals ,
171- blockedIntervals ,
172- slots ,
173- scanYs ,
174- intersections ) ;
214+ this . scanBlockedIntervals ,
215+ this . blockedIntervals ,
216+ this . slots ,
217+ this . scanYs ,
218+ this . intersections ) ;
175219
176220 float rowHeight = rowProbeHeight ;
177- for ( int i = 0 ; i < slots . Count && hasMoreText ; i ++ )
221+ for ( int i = 0 ; i < this . slots . Count && hasMoreText ; i ++ )
178222 {
179- BandInterval slot = slots [ i ] ;
223+ BandInterval slot = this . slots [ i ] ;
180224 float slotWidth = slot . Width ;
181225
182226 // Each MoveNext call consumes just enough prepared text for the
@@ -194,8 +238,8 @@ public override void Paint(DrawingCanvas canvas, TimeSpan deltaTime)
194238 // The translucent slot fill is a visual aid for the sample. It
195239 // makes the row splitting visible so readers can compare the
196240 // available rectangles with the selected obstacle shape.
197- canvas . Fill ( Brushes . Solid ( SlotColor ) , new RectangularPolygon ( slot . Left , y , slotWidth , lineHeight ) ) ;
198- canvas . DrawText ( line , new PointF ( slot . Left , y ) , Brushes . Solid ( TextColor ) , pen : null ) ;
241+ canvas . Fill ( SlotBrush , new RectangularPolygon ( slot . Left , y , slotWidth , lineHeight ) ) ;
242+ canvas . DrawText ( line , new PointF ( slot . Left , y ) , TextBrush , pen : null ) ;
199243
200244 rowHeight = MathF . Max ( rowHeight , lineHeight ) ;
201245 }
0 commit comments