Skip to content

Commit

Permalink
Properly apply translation and clipping in TextRenderer (#4525)
Browse files Browse the repository at this point in the history
Translation and clipping should only be applied from Graphics objects when they are explicitly asked for in the TextFormatFlags.

Adds regression tests.

Fixes #4524
  • Loading branch information
JeremyKuhne committed Feb 10, 2021
1 parent 5c5aece commit b04dbad
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ internal static partial class Gdi32
public bool IsNull => Handle == IntPtr.Zero;

public static explicit operator IntPtr(HENHMETAFILE hmetafile) => hmetafile.Handle;
public static explicit operator HENHMETAFILE(IntPtr hmetafile) => new HENHMETAFILE(hmetafile);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace System.Windows.Forms.Metafiles
internal class EmfScope : DisposalTracking.Tracker, IDisposable
{
public Gdi32.HDC HDC { get; }
private Gdi32.HENHMETAFILE _hmf;
private Gdi32.HENHMETAFILE _hemf;

public unsafe EmfScope()
: this (Gdi32.CreateEnhMetaFileW(hdc: default, lpFilename: null, lprc: null, lpDesc: null))
Expand All @@ -24,7 +24,12 @@ public unsafe EmfScope()
public EmfScope(Gdi32.HDC hdc)
{
HDC = hdc;
_hmf = default;
_hemf = default;
}

public EmfScope(Gdi32.HENHMETAFILE hemf)
{
_hemf = hemf;
}

public unsafe static EmfScope Create() => new EmfScope();
Expand All @@ -33,17 +38,17 @@ public Gdi32.HENHMETAFILE HENHMETAFILE
{
get
{
if (HDC.IsNull)
if (_hemf.IsNull)
{
return default;
}
if (HDC.IsNull)
{
return default;
}

if (_hmf.IsNull)
{
_hmf = Gdi32.CloseEnhMetaFile(HDC);
_hemf = Gdi32.CloseEnhMetaFile(HDC);
}

return _hmf;
return _hemf;
}
}

Expand Down
31 changes: 28 additions & 3 deletions src/System.Windows.Forms/src/System/Windows/Forms/TextRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

using System.Drawing;
using System.Drawing.Text;
using System.Windows.Forms.Internal;
using static Interop;

namespace System.Windows.Forms
Expand Down Expand Up @@ -301,7 +300,7 @@ public static void DrawText(IDeviceContext dc, string? text, Font? font, Rectang
// This MUST come before retreiving the HDC, which locks the Graphics object
Gdi32.QUALITY quality = FontQualityFromTextRenderingHint(dc);

using var hdc = new DeviceContextHdcScope(dc);
using var hdc = new DeviceContextHdcScope(dc, GetApplyStateFlags(dc, flags));

DrawTextInternal(hdc, text, font, bounds, foreColor, quality, backColor, flags);
}
Expand Down Expand Up @@ -534,7 +533,9 @@ public static Size MeasureText(IDeviceContext dc, ReadOnlySpan<char> text, Font?
// This MUST come before retreiving the HDC, which locks the Graphics object
Gdi32.QUALITY quality = FontQualityFromTextRenderingHint(dc);

using var hdc = new DeviceContextHdcScope(dc);
// Applying state may not impact text size measurements. Rather than risk missing some
// case we'll apply as we have historically to avoid suprise regressions.
using var hdc = new DeviceContextHdcScope(dc, GetApplyStateFlags(dc, flags));
using var hfont = GdiCache.GetHFONT(font, quality, hdc);
return hdc.HDC.MeasureText(text, hfont, proposedSize, flags);
}
Expand Down Expand Up @@ -596,5 +597,29 @@ private static Gdi32.QUALITY GetDefaultFontQuality()
return SystemInformation.FontSmoothingType == 0x0002
? Gdi32.QUALITY.CLEARTYPE : Gdi32.QUALITY.ANTIALIASED;
}

/// <summary>
/// Gets the proper <see cref="ApplyGraphicsProperties"/> flags for the given <paramref name="textFormatFlags"/>.
/// </summary>
private static ApplyGraphicsProperties GetApplyStateFlags(IDeviceContext deviceContext, TextFormatFlags textFormatFlags)
{
if (deviceContext is not Graphics)
{
return ApplyGraphicsProperties.None;
}

var apply = ApplyGraphicsProperties.None;
if (textFormatFlags.HasFlag(TextFormatFlags.PreserveGraphicsClipping))
{
apply |= ApplyGraphicsProperties.Clipping;
}

if (textFormatFlags.HasFlag(TextFormatFlags.PreserveGraphicsTranslateTransform))
{
apply |= ApplyGraphicsProperties.TranslateTransform;
}

return apply;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@

using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Drawing.Text;
using System.IO;
using System.Windows.Forms.Metafiles;
using Moq;
using WinForms.Common.Tests;
Expand Down Expand Up @@ -829,5 +832,87 @@ public void TextRenderer_MeasureText_Padding(TextFormatFlags flags, Size expecte
{ TextFormatFlags.LeftAndRightPadding, new Size(82, 13) },
{ TextFormatFlags.NoPadding, new Size(71, 13) }
};

[WinFormsTheory]
[MemberData(nameof(TextRenderer_DrawText_ApplyState_TestData))]
public void TextRenderer_DrawText_ApplyState(TextFormatFlags flags, Rectangle expectedBounds)
{
using var hdc = new Interop.Gdi32.CreateDcScope(default);
DeviceContextState state = new DeviceContextState(hdc);

using MemoryStream stream = new MemoryStream(1024);
using (Metafile metafileRecorder = new Metafile(stream, hdc.HDC.Handle, EmfType.EmfOnly))
using (Graphics graphics = Graphics.FromImage(metafileRecorder))
{
using Matrix matrix = new Matrix();
matrix.Translate(5, 10);
graphics.Transform = matrix;
using Region region = new(new Rectangle(1, 2, 6, 8));
graphics.Clip = region;

TextRenderer.DrawText(
graphics,
"Landshark",
SystemFonts.DefaultFont,
new Rectangle(0, 0, int.MaxValue, int.MaxValue),
Color.Red,
flags);
}

// Need to queue the stream back to the beginning for the reader
stream.Position = 0;
using Metafile metafile = new Metafile(stream);
using var emf = new EmfScope((Interop.Gdi32.HENHMETAFILE)metafile.GetHenhmetafile());

emf.Validate(
state,
Validate.TextOut(
"Landshark",
expectedBounds,
State.FontFace(SystemFonts.DefaultFont.Name),
State.TextColor(Color.Red)));
}

public static TheoryData<TextFormatFlags, Rectangle> TextRenderer_DrawText_ApplyState_TestData
=> new TheoryData<TextFormatFlags, Rectangle>
{
{ TextFormatFlags.Default, new Rectangle(3, 0, 49, 12) },
{ TextFormatFlags.PreserveGraphicsTranslateTransform, new Rectangle(8, 10, 49, 12) },
{ TextFormatFlags.PreserveGraphicsClipping, new Rectangle(6, 12, 5, 0) },
{ TextFormatFlags.PreserveGraphicsClipping | TextFormatFlags.PreserveGraphicsTranslateTransform, new Rectangle(8, 12, 3, 7) },
};

[WinFormsTheory]
[MemberData(nameof(TextRenderer_MeasureText_ApplyState_TestData))]
public void TextRenderer_MeasureText_ApplyState(TextFormatFlags flags, Size expectedSize)
{
using var image = new Bitmap(200, 50);
using Graphics graphics = Graphics.FromImage(image);
using Matrix matrix = new Matrix();
matrix.Translate(5, 10);
graphics.Transform = matrix;
using Region region = new(new Rectangle(1, 2, 6, 8));
graphics.Clip = region;

Size size = TextRenderer.MeasureText(
graphics,
"Landshark",
SystemFonts.DefaultFont,
new Size(int.MaxValue, int.MaxValue),
flags);

Assert.Equal(expectedSize, size);
}

public static TheoryData<TextFormatFlags, Size> TextRenderer_MeasureText_ApplyState_TestData
=> new TheoryData<TextFormatFlags, Size>
{
// State application doesn't practially impact size measurements, but we still want to have a regession test
// here in case something sneaks in.
{ TextFormatFlags.Default, new Size(57, 13) },
{ TextFormatFlags.PreserveGraphicsTranslateTransform, new Size(57, 13) },
{ TextFormatFlags.PreserveGraphicsClipping, new Size(57, 13) },
{ TextFormatFlags.PreserveGraphicsClipping | TextFormatFlags.PreserveGraphicsTranslateTransform, new Size(57, 13) },
};
}
}

0 comments on commit b04dbad

Please sign in to comment.