|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable enable
using System.Drawing;
using System.Numerics;
using System.Windows.Forms.Metafiles;
namespace System;
/// <summary>
/// Holder for tracking HDC state.
/// </summary>
internal unsafe class DeviceContextState
{
// Not all state is handled yet. Backfilling in as we write specific tests. Of special note is that we don't
// have tracking for Save/RestoreDC yet.
private readonly List<State> _savedStates = [];
private State _currentState;
/// <summary>
/// Initialize the current state of <paramref name="hdc"/>.
/// </summary>
public DeviceContextState(HDC hdc)
{
MapMode = PInvoke.GetMapMode(hdc);
BackColor = PInvoke.GetBkColor(hdc);
TextColor = PInvoke.GetTextColor(hdc);
Rop2Mode = PInvoke.GetROP2(hdc);
TextAlign = PInvoke.GetTextAlign(hdc);
BackgroundMode = PInvoke.GetBkMode(hdc);
Matrix3x2 transform = default;
PInvoke.GetWorldTransform(hdc, (XFORM*)(void*)&transform);
Transform = transform;
Point point = default;
PInvoke.GetBrushOrgEx(hdc, &point);
BrushOrigin = point;
var hfont = PInvoke.GetCurrentObject(hdc, OBJ_TYPE.OBJ_FONT);
PInvokeCore.GetObject(hfont, out LOGFONTW logfont);
SelectedFont = logfont;
var hpen = PInvoke.GetCurrentObject(hdc, OBJ_TYPE.OBJ_PEN);
PInvokeCore.GetObject(hpen, out LOGPEN logpen);
SelectedPen = logpen;
var hbrush = PInvoke.GetCurrentObject(hdc, OBJ_TYPE.OBJ_BRUSH);
PInvokeCore.GetObject(hbrush, out LOGBRUSH logbrush);
SelectedBrush = logbrush;
}
public HDC_MAP_MODE MapMode { get => _currentState.MapMode; set => _currentState.MapMode = value; }
public R2_MODE Rop2Mode { get => _currentState.Rop2Mode; set => _currentState.Rop2Mode = value; }
public COLORREF BackColor { get => _currentState.BackColor; set => _currentState.BackColor = value; }
public COLORREF TextColor { get => _currentState.TextColor; set => _currentState.TextColor = value; }
public Point BrushOrigin { get => _currentState.BrushOrigin; set => _currentState.BrushOrigin = value; }
public TEXT_ALIGN_OPTIONS TextAlign { get => _currentState.TextAlign; set => _currentState.TextAlign = value; }
public BACKGROUND_MODE BackgroundMode { get => _currentState.BackgroundMode; set => _currentState.BackgroundMode = value; }
public LOGFONTW SelectedFont { get => _currentState.SelectedFont; set => _currentState.SelectedFont = value; }
public LOGBRUSH SelectedBrush { get => _currentState.SelectedBrush; set => _currentState.SelectedBrush = value; }
public EXTLOGPEN32 SelectedPen { get => _currentState.SelectedPen; set => _currentState.SelectedPen = value; }
public Point LastBeginPathBrushOrigin { get => _currentState.LastBeginPathBrushOrigin; set => _currentState.LastBeginPathBrushOrigin = value; }
public bool InPath { get => _currentState.InPath; set => _currentState.InPath = value; }
public Matrix3x2 Transform { get => _currentState.Transform; set => _currentState.Transform = value; }
public RECT[] ClipRegion { get => _currentState.ClipRegion; set => _currentState.ClipRegion = value; }
private struct State
{
public HDC_MAP_MODE MapMode { get; set; }
public R2_MODE Rop2Mode { get; set; }
public COLORREF BackColor { get; set; }
public COLORREF TextColor { get; set; }
public Point BrushOrigin { get; set; }
public TEXT_ALIGN_OPTIONS TextAlign { get; set; }
public BACKGROUND_MODE BackgroundMode { get; set; }
public LOGFONTW SelectedFont { get; set; }
public LOGBRUSH SelectedBrush { get; set; }
public EXTLOGPEN32 SelectedPen { get; set; }
public Point LastBeginPathBrushOrigin { get; set; }
public bool InPath { get; set; }
public Matrix3x2 Transform { get; set; }
public RECT[] ClipRegion { get; set; }
}
/// <summary>
/// When using to parse a metafile, this is the list of known created objects.
/// </summary>
public List<EmfRecord> GdiObjects { get; } = [];
/// <summary>
/// Adds the given object to <see cref="GdiObjects"/>.
/// </summary>
public void AddGdiObject(ref EmfRecord record, int index)
{
// Ensure we have capacity
if (GdiObjects.Capacity <= index)
{
GdiObjects.Capacity = index + 1;
}
// Fill in any gaps if we have them
while (GdiObjects.Count <= index)
{
GdiObjects.Add(default);
}
GdiObjects[index] = record;
}
public void SaveDC() => _savedStates.Add(_currentState);
public void RestoreDC(int state)
{
int index;
if (state > 0)
{
// Positive removes a specific state
index = state - 1;
}
else
{
// Negative is relative (-1 is last saved state)
index = _savedStates.Count + state;
}
_currentState = _savedStates[index];
_savedStates.RemoveRange(index, _savedStates.Count - index);
}
/// <summary>
/// Applies the given selection record to the state.
/// </summary>
public void SelectGdiObject(EMRINDEXRECORD* selectionRecord)
{
// Not all records are handled yet. Backfilling in as we write specific tests.
if (selectionRecord->IsStockObject)
{
HGDIOBJ hgdiobj = PInvokeCore.GetStockObject(selectionRecord->StockObject);
switch (selectionRecord->StockObject)
{
case GET_STOCK_OBJECT_FLAGS.ANSI_FIXED_FONT:
case GET_STOCK_OBJECT_FLAGS.OEM_FIXED_FONT:
case GET_STOCK_OBJECT_FLAGS.ANSI_VAR_FONT:
case GET_STOCK_OBJECT_FLAGS.SYSTEM_FONT:
case GET_STOCK_OBJECT_FLAGS.DEVICE_DEFAULT_FONT:
case GET_STOCK_OBJECT_FLAGS.SYSTEM_FIXED_FONT:
case GET_STOCK_OBJECT_FLAGS.DEFAULT_GUI_FONT:
PInvokeCore.GetObject(hgdiobj, out LOGFONTW logfont);
SelectedFont = logfont;
break;
case GET_STOCK_OBJECT_FLAGS.WHITE_BRUSH:
case GET_STOCK_OBJECT_FLAGS.LTGRAY_BRUSH:
case GET_STOCK_OBJECT_FLAGS.GRAY_BRUSH:
case GET_STOCK_OBJECT_FLAGS.DKGRAY_BRUSH:
case GET_STOCK_OBJECT_FLAGS.BLACK_BRUSH:
case GET_STOCK_OBJECT_FLAGS.NULL_BRUSH:
case GET_STOCK_OBJECT_FLAGS.DC_BRUSH:
PInvokeCore.GetObject(hgdiobj, out LOGBRUSH logBrush);
SelectedBrush = logBrush;
break;
case GET_STOCK_OBJECT_FLAGS.WHITE_PEN:
case GET_STOCK_OBJECT_FLAGS.BLACK_PEN:
case GET_STOCK_OBJECT_FLAGS.NULL_PEN:
case GET_STOCK_OBJECT_FLAGS.DC_PEN:
PInvokeCore.GetObject(hgdiobj, out LOGPEN logPen);
SelectedPen = logPen;
break;
case GET_STOCK_OBJECT_FLAGS.DEFAULT_PALETTE:
break;
}
return;
}
// WARNING: You can not use fields that index out of the saved EmfRecord's contents here as the struct
// is just a copy of the original. Any pointers or offsets outside of the struct aren't valid.
EmfRecord savedRecord = GdiObjects[(int)selectionRecord->index];
switch (savedRecord.Type)
{
case ENHANCED_METAFILE_RECORD_TYPE.EMR_EXTCREATEFONTINDIRECTW:
SelectedFont = savedRecord.ExtCreateFontIndirectWRecord->elfw.elfLogFont;
break;
case ENHANCED_METAFILE_RECORD_TYPE.EMR_CREATEPEN:
SelectedPen = savedRecord.CreatePenRecord->lopn;
break;
case ENHANCED_METAFILE_RECORD_TYPE.EMR_EXTCREATEPEN:
SelectedPen = savedRecord.ExtCreatePenRecord->elp;
break;
case ENHANCED_METAFILE_RECORD_TYPE.EMR_CREATEBRUSHINDIRECT:
SelectedBrush = savedRecord.CreateBrushIndirectRecord->lb;
break;
}
}
public Point TransformPoint(Point point) => Transform.TransformPoint(point);
}
|