33
44using System ;
55using System . Collections . Generic ;
6+ using System . Linq ;
67using System . Numerics ;
78using SixLabors . Fonts ;
89using SixLabors . ImageSharp . Drawing . Processing . Processors . Drawing ;
@@ -57,98 +58,86 @@ protected override void AfterImageApply()
5758 /// <inheritdoc/>
5859 protected override void OnFrameApply ( ImageFrame < TPixel > source )
5960 {
60- Draw ( this . textRenderer . FillOperations , this . definition . Brush ) ;
61- Draw ( this . textRenderer . OutlineOperations , this . definition . Pen ? . StrokeFill ) ;
61+ if ( this . textRenderer . DrawingOperations . Count > 0 )
62+ {
63+ Draw ( this . textRenderer . DrawingOperations . OrderBy ( x => x . RenderPass ) ) ;
64+ }
6265
63- void Draw ( List < DrawingOperation > operations , IBrush brush )
66+ void Draw ( IEnumerable < DrawingOperation > operations )
6467 {
65- if ( operations ? . Count > 0 )
68+ var brushes = new Dictionary < IBrush , BrushApplicator < TPixel > > ( ) ;
69+ foreach ( DrawingOperation operation in operations )
6670 {
67- var brushes = new Dictionary < Color , BrushApplicator < TPixel > > ( ) ;
68- foreach ( DrawingOperation operation in operations )
71+ if ( operation . Brush != null )
6972 {
70- if ( operation . Color . HasValue )
73+ if ( ! brushes . TryGetValue ( operation . Brush , out _ ) )
7174 {
72- if ( ! brushes . TryGetValue ( operation . Color . Value , out _ ) )
73- {
74- brushes [ operation . Color . Value ] = new SolidBrush ( operation . Color . Value ) . CreateApplicator (
75- this . Configuration ,
76- this . textRenderer . Options . GraphicsOptions ,
77- source ,
78- this . SourceRectangle ) ;
79- }
75+ brushes [ operation . Brush ] = operation . Brush . CreateApplicator ( this . Configuration , this . textRenderer . Options . GraphicsOptions , source , this . SourceRectangle ) ;
8076 }
8177 }
78+ }
8279
83- using ( BrushApplicator < TPixel > app = brush . CreateApplicator ( this . Configuration , this . textRenderer . Options . GraphicsOptions , source , this . SourceRectangle ) )
84- {
85- foreach ( DrawingOperation operation in operations )
86- {
87- BrushApplicator < TPixel > currentApp = app ;
88- if ( operation . Color != null )
89- {
90- brushes . TryGetValue ( operation . Color . Value , out currentApp ) ;
91- }
92-
93- Buffer2D < float > buffer = operation . Map ;
94- int startY = operation . Location . Y ;
95- int startX = operation . Location . X ;
96- int offsetSpan = 0 ;
97-
98- if ( startX + buffer . Height < 0 )
99- {
100- continue ;
101- }
80+ foreach ( DrawingOperation operation in operations )
81+ {
82+ var app = brushes [ operation . Brush ] ;
10283
103- if ( startX + buffer . Width < 0 )
104- {
105- continue ;
106- }
84+ Buffer2D < float > buffer = operation . Map ;
85+ int startY = operation . Location . Y ;
86+ int startX = operation . Location . X ;
87+ int offsetSpan = 0 ;
10788
108- if ( startX < 0 )
109- {
110- offsetSpan = - startX ;
111- startX = 0 ;
112- }
89+ if ( startX + buffer . Height < 0 )
90+ {
91+ continue ;
92+ }
11393
114- if ( startX >= source . Width )
115- {
116- continue ;
117- }
94+ if ( startX + buffer . Width < 0 )
95+ {
96+ continue ;
97+ }
11898
119- int firstRow = 0 ;
120- if ( startY < 0 )
121- {
122- firstRow = - startY ;
123- }
99+ if ( startX < 0 )
100+ {
101+ offsetSpan = - startX ;
102+ startX = 0 ;
103+ }
124104
125- int maxHeight = source . Height - startY ;
126- int end = Math . Min ( operation . Map . Height , maxHeight ) ;
105+ if ( startX >= source . Width )
106+ {
107+ continue ;
108+ }
127109
128- for ( int row = firstRow ; row < end ; row ++ )
129- {
130- int y = startY + row ;
131- Span < float > span = buffer . DangerousGetRowSpan ( row ) . Slice ( offsetSpan ) ;
132- currentApp . Apply ( span , startX , y ) ;
133- }
134- }
110+ int firstRow = 0 ;
111+ if ( startY < 0 )
112+ {
113+ firstRow = - startY ;
135114 }
136115
137- foreach ( BrushApplicator < TPixel > app in brushes . Values )
116+ int maxHeight = source . Height - startY ;
117+ int end = Math . Min ( operation . Map . Height , maxHeight ) ;
118+
119+ for ( int row = firstRow ; row < end ; row ++ )
138120 {
139- app . Dispose ( ) ;
121+ int y = startY + row ;
122+ Span < float > span = buffer . DangerousGetRowSpan ( row ) . Slice ( offsetSpan ) ;
123+ app . Apply ( span , startX , y ) ;
140124 }
141125 }
126+
127+ foreach ( BrushApplicator < TPixel > app in brushes . Values )
128+ {
129+ app . Dispose ( ) ;
130+ }
142131 }
143132 }
144133
145134 private struct DrawingOperation
146135 {
147136 public Buffer2D < float > Map { get ; set ; }
148137
149- public Point Location { get ; set ; }
138+ public byte RenderPass { get ; set ; }
150139
151- public Color ? Color { get ; set ; }
140+ public Point Location { get ; set ; }
152141
153142 public IBrush Brush { get ; internal set ; }
154143 }
@@ -163,14 +152,15 @@ private class CachingGlyphRenderer : IColorGlyphRenderer, IDisposable
163152 private const float AccuracyMultiple = 8 ;
164153 private readonly Matrix3x2 transform ;
165154 private readonly PathBuilder builder ;
155+ private readonly Dictionary < Color , IBrush > brushLookup = new ( ) ;
166156
167157 private Point currentRenderPosition ;
168158 private ( GlyphRendererParameters Glyph , PointF SubPixelOffset ) currentGlyphRenderParams ;
169159 private readonly int offset ;
170160 private PointF currentPoint ;
171161 private Color ? currentColor ;
172- private IBrush ? currentBrush ;
173- private IPen ? currentPen ;
162+ private IBrush currentBrush ;
163+ private IPen currentPen ;
174164
175165 private readonly Dictionary < ( GlyphRendererParameters Glyph , PointF SubPixelOffset ) , GlyphRenderData > glyphData = new ( ) ;
176166
@@ -183,15 +173,12 @@ public CachingGlyphRenderer(MemoryAllocator memoryAllocator, int size, TextDrawi
183173 this . Pen = pen ;
184174 this . Brush = brush ;
185175 this . offset = ( int ) textOptions . Font . Size ;
186- this . FillOperations = new List < DrawingOperation > ( size ) ;
187- this . OutlineOperations = new List < DrawingOperation > ( size ) ;
176+ this . DrawingOperations = new List < DrawingOperation > ( size ) ;
188177 this . transform = transform ;
189178 this . builder = new PathBuilder ( ) ;
190179 }
191180
192- public List < DrawingOperation > FillOperations { get ; }
193-
194- public List < DrawingOperation > OutlineOperations { get ; }
181+ public List < DrawingOperation > DrawingOperations { get ; }
195182
196183 public MemoryAllocator MemoryAllocator { get ; internal set ; }
197184
@@ -260,8 +247,7 @@ public bool BeginGlyph(FontRectangle bounds, GlyphRendererParameters parameters)
260247 public void BeginText ( FontRectangle bounds )
261248 {
262249 // Not concerned about this one
263- this . OutlineOperations ? . Clear ( ) ;
264- this . FillOperations ? . Clear ( ) ;
250+ this . DrawingOperations . Clear ( ) ;
265251 }
266252
267253 public void CubicBezierTo ( Vector2 secondControlPoint , Vector2 thirdControlPoint , Vector2 point )
@@ -296,29 +282,48 @@ public void EndGlyph()
296282 return ;
297283 }
298284
285+ // fix up the text runs colors
286+ // only if both brush and pen is null do we fallback to the defualt value
287+ if ( this . currentBrush == null && this . currentPen == null )
288+ {
289+ this . currentBrush = this . Brush ;
290+ this . currentPen = this . Pen ;
291+ }
292+
299293 // If we are using the fonts color layers we ignore the request to draw an outline only
300294 // cause that wont really work and instead force drawing with fill with the requested color
301295 // if color fonts disabled then this.currentColor will always be null
302- var brush = this . Brush ?? this . currentBrush ;
303- if ( brush != null || this . currentColor != null )
296+ if ( this . currentBrush != null || this . currentColor != null )
304297 {
305298 renderData . FillMap = this . Render ( path ) ;
306- renderData . Color = this . currentColor ;
299+
300+ if ( this . currentColor . HasValue )
301+ {
302+ if ( this . brushLookup . TryGetValue ( this . currentColor . Value , out var brush ) )
303+ {
304+ this . currentBrush = brush ;
305+ }
306+ else
307+ {
308+ this . currentBrush = new SolidBrush ( this . currentColor . Value ) ;
309+ this . brushLookup [ this . currentColor . Value ] = this . currentBrush ;
310+ }
311+ }
307312 }
308313
309- var pen = this . currentPen ?? this . Pen ;
310- if ( pen != null && this . currentColor == null )
314+ if ( this . currentPen != null && this . currentColor == null )
311315 {
312- if ( pen . StrokePattern . Length == 0 )
316+ if ( this . currentPen . StrokePattern . Length == 0 )
313317 {
314- path = path . GenerateOutline ( pen . StrokeWidth ) ;
318+ path = path . GenerateOutline ( this . currentPen . StrokeWidth ) ;
315319 }
316320 else
317321 {
318- path = path . GenerateOutline ( pen . StrokeWidth , pen . StrokePattern , pen . JointStyle , pen . EndCapStyle ) ;
322+ path = path . GenerateOutline ( this . currentPen . StrokeWidth , this . currentPen . StrokePattern , this . currentPen . JointStyle , this . currentPen . EndCapStyle ) ;
319323 }
320324
321325 renderData . OutlineMap = this . Render ( path ) ;
326+ this . currentBrush = this . currentPen . StrokeFill ;
322327 }
323328
324329 this . glyphData [ this . currentGlyphRenderParams ] = renderData ;
@@ -330,22 +335,23 @@ public void EndGlyph()
330335
331336 if ( renderData . FillMap != null )
332337 {
333- this . FillOperations . Add ( new DrawingOperation
338+ this . DrawingOperations . Add ( new DrawingOperation
334339 {
335340 Location = this . currentRenderPosition ,
336341 Map = renderData . FillMap ,
337- Color = this . currentColor ,
338342 Brush = this . currentBrush ,
343+ RenderPass = 1
339344 } ) ;
340345 }
341346
342347 if ( renderData . OutlineMap != null )
343348 {
344- this . OutlineOperations . Add ( new DrawingOperation
349+ this . DrawingOperations . Add ( new DrawingOperation
345350 {
346351 Location = this . currentRenderPosition ,
347352 Map = renderData . OutlineMap ,
348- Brush = this . currentPen ? . StrokeFill ,
353+ Brush = this . currentBrush ,
354+ RenderPass = 2 // render outlines 2nd to ensure they are always ontop of fills
349355 } ) ;
350356 }
351357 }
0 commit comments