Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Properly apply translation and clipping in TextRenderer #4525

Merged
merged 1 commit into from
Feb 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public readonly struct HENHMETAFILE
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
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 @@ internal static void DrawTextInternal(
// 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 @@ private static Size MeasureTextInternal(
// 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 static TheoryData<TextFormatFlags, Size> TextRenderer_MeasureText_Padding
{ 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) },
};
}
}