@@ -24,7 +24,6 @@ internal sealed class RichTextGlyphRenderer : BaseGlyphBuilder, IColorGlyphRende
2424 private const byte RenderOrderOutline = 1 ;
2525 private const byte RenderOrderDecoration = 2 ;
2626
27- private readonly RichTextOptions textOptions ;
2827 private readonly DrawingOptions drawingOptions ;
2928 private readonly MemoryAllocator memoryAllocator ;
3029 private readonly Pen defaultPen ;
@@ -40,6 +39,7 @@ internal sealed class RichTextGlyphRenderer : BaseGlyphBuilder, IColorGlyphRende
4039 private TextDecorationDetails ? currentUnderline ;
4140 private TextDecorationDetails ? currentStrikout ;
4241 private TextDecorationDetails ? currentOverline ;
42+ private bool currentDecorationRotated ;
4343
4444 // Just enough accuracy to allow for 1/8 px differences which later are accumulated while rendering,
4545 // but do not grow into full px offsets.
@@ -60,7 +60,6 @@ public RichTextGlyphRenderer(
6060 Brush brush )
6161 : base ( drawingOptions . Transform )
6262 {
63- this . textOptions = textOptions ;
6463 this . drawingOptions = drawingOptions ;
6564 this . memoryAllocator = memoryAllocator ;
6665 this . defaultPen = pen ;
@@ -101,7 +100,7 @@ protected override void BeginText(in FontRectangle bounds)
101100 protected override void BeginGlyph ( in FontRectangle bounds , in GlyphRendererParameters parameters )
102101 {
103102 this . currentColor = null ;
104-
103+ this . currentDecorationRotated = parameters . LayoutMode . IsVertical ( ) || parameters . LayoutMode . IsVerticalMixed ( ) ;
105104 this . currentTextRun = parameters . TextRun ;
106105 if ( parameters . TextRun is RichTextRun drawingRun )
107106 {
@@ -219,41 +218,40 @@ public override void SetDecoration(TextDecorations textDecorations, Vector2 star
219218 }
220219 }
221220
222- // Clamp the line to whole pixels
223- Vector2 pad = new ( 0 , thickness * .5F ) ;
224- Vector2 tl = start - pad ;
225- Vector2 bl = start + pad ;
226- Vector2 tr = end - pad ;
227-
228- tl = ClampToPixel ( tl ) ;
229- bl = ClampToPixel ( bl ) ;
230- tr = ClampToPixel ( tr ) ;
231-
232221 // Always respect the pen stroke width if explicitly set.
233- if ( pen is null )
222+ if ( pen is not null )
234223 {
235- thickness = bl . Y - tl . Y ;
236- pen = new SolidPen ( this . currentBrush ?? this . defaultBrush , thickness ) ;
224+ thickness = pen . StrokeWidth ;
237225 }
238226 else
239227 {
240- thickness = pen . StrokeWidth ;
228+ // Clamp the thickness to whole pixels.
229+ thickness = MathF . Max ( 1F , MathF . Round ( thickness ) ) ;
230+ pen = new SolidPen ( this . currentBrush ?? this . defaultBrush , thickness ) ;
241231 }
242232
243233 // Drawing is always centered around the point so we need to offset by half.
244234 Vector2 offset = Vector2 . Zero ;
235+ bool rotated = this . currentDecorationRotated ;
245236 if ( textDecorations == TextDecorations . Overline )
246237 {
247238 // CSS overline is drawn above the position, so we need to move it up.
248- offset = new Vector2 ( 0 , - ( thickness * .5F ) ) ;
239+ offset = rotated ? new ( thickness * .5F , 0 ) : new ( 0 , - ( thickness * .5F ) ) ;
249240 }
250241 else if ( textDecorations == TextDecorations . Underline )
251242 {
252243 // CSS underline is drawn below the position, so we need to move it down.
253- offset = new Vector2 ( 0 , thickness * .5F ) ;
244+ offset = rotated ? new ( - ( thickness * .5F ) , 0 ) : new ( 0 , thickness * .5F ) ;
254245 }
255246
256- this . AppendDecoration ( ref targetDecoration , tl + offset , tr + offset , pen , thickness ) ;
247+ // We clamp the start and end points to the pixel grid to avoid anti-aliasing.
248+ this . AppendDecoration (
249+ ref targetDecoration ,
250+ ClampToPixel ( start + offset , ( int ) thickness , rotated ) ,
251+ ClampToPixel ( end + offset , ( int ) thickness , rotated ) ,
252+ pen ,
253+ thickness ,
254+ rotated ) ;
257255 }
258256
259257 protected override void EndGlyph ( )
@@ -395,6 +393,24 @@ protected override void EndText()
395393 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
396394 private static Point ClampToPixel ( PointF point ) => Point . Truncate ( point ) ;
397395
396+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
397+ private static PointF ClampToPixel ( PointF point , int thickness , bool rotated )
398+ {
399+ // Even. Clamp to whole pixels.
400+ if ( ( thickness & 1 ) == 0 )
401+ {
402+ return Point . Truncate ( point ) ;
403+ }
404+
405+ // Odd. Clamp to half pixels.
406+ if ( rotated )
407+ {
408+ return Point . Truncate ( point ) + new Vector2 ( .5F , 0 ) ;
409+ }
410+
411+ return Point . Truncate ( point ) + new Vector2 ( 0 , .5F ) ;
412+ }
413+
398414 // Point.Truncate(point);
399415 private void FinalizeDecoration ( ref TextDecorationDetails ? decoration )
400416 {
@@ -428,24 +444,47 @@ private void FinalizeDecoration(ref TextDecorationDetails? decoration)
428444 }
429445 }
430446
431- private void AppendDecoration ( ref TextDecorationDetails ? decoration , Vector2 start , Vector2 end , Pen pen , float thickness )
447+ private void AppendDecoration (
448+ ref TextDecorationDetails ? decoration ,
449+ Vector2 start ,
450+ Vector2 end ,
451+ Pen pen ,
452+ float thickness ,
453+ bool rotated )
432454 {
433455 if ( decoration != null )
434456 {
435457 // TODO: This only works well if we are not trying to follow a path.
436458 if ( this . path is null )
437459 {
438460 // Let's try and expand it first.
439- if ( thickness == decoration . Value . Thickness
440- && decoration . Value . End . Y == start . Y
441- && ( decoration . Value . End . X + 1 ) >= start . X
461+ if ( rotated )
462+ {
463+ if ( thickness == decoration . Value . Thickness
464+ && decoration . Value . End . Y + 1 >= start . Y
465+ && decoration . Value . End . X == start . X
442466 && decoration . Value . Pen . Equals ( pen ) )
467+ {
468+ // Expand the line
469+ start = decoration . Value . Start ;
470+
471+ // If this is null finalize does nothing.
472+ decoration = null ;
473+ }
474+ }
475+ else
443476 {
444- // Expand the line
445- start = decoration . Value . Start ;
477+ if ( thickness == decoration . Value . Thickness
478+ && decoration . Value . End . Y == start . Y
479+ && decoration . Value . End . X + 1 >= start . X
480+ && decoration . Value . Pen . Equals ( pen ) )
481+ {
482+ // Expand the line
483+ start = decoration . Value . Start ;
446484
447- // If this is null finalize does nothing.
448- decoration = null ;
485+ // If this is null finalize does nothing.
486+ decoration = null ;
487+ }
449488 }
450489 }
451490 }
@@ -456,7 +495,7 @@ private void AppendDecoration(ref TextDecorationDetails? decoration, Vector2 sta
456495 Start = start ,
457496 End = end ,
458497 Pen = pen ,
459- Thickness = MathF . Abs ( thickness )
498+ Thickness = thickness
460499 } ;
461500 }
462501
0 commit comments