Files
alex 55ac8e6556 lightweight variant (#1)
Reviewed-on: #1
Co-authored-by: Aleksandr Neychev <alexnejchev73@gmail.com>
2026-08-12 13:37:30 +00:00

162 lines
6.2 KiB
C#

using System.Drawing;
using System.Runtime.InteropServices;
using CursorLang.Core.Interop;
namespace CursorLang.Agent.Interop;
/// <summary>
/// Plain GDI: a font, text, and an off-screen bitmap to draw them into.
/// </summary>
/// <remarks>
/// GDI rather than GDI+ on purpose. <c>System.Drawing.Common</c> would make the drawing
/// code shorter, but it is a separate assembly with a native GDI+ library behind it, and
/// how little this process weighs is the entire reason it exists apart from the window.
/// Everything the popup needs — one rounded rectangle and one line of text — GDI can do
/// on its own, and it draws the text with ClearType, exactly as Windows does everywhere else.
/// </remarks>
internal static class GdiNative
{
internal const int TRANSPARENT = 1;
internal const uint DT_SINGLELINE = 0x00000020;
internal const uint DT_CENTER = 0x00000001;
internal const uint DT_VCENTER = 0x00000004;
internal const uint DT_CALCRECT = 0x00000400;
internal const uint DT_NOPREFIX = 0x00000800;
internal const uint DT_NOCLIP = 0x00000100;
private const int DEFAULT_CHARSET = 1;
private const int OUT_TT_PRECIS = 4;
private const int CLIP_DEFAULT_PRECIS = 0;
private const int CLEARTYPE_QUALITY = 5;
private const int DEFAULT_PITCH = 0;
/// <summary>
/// The face the popup is written in.
/// </summary>
/// <remarks>
/// WPF is asked for "Segoe UI" at FontWeight SemiBold and resolves that to the
/// seguisb.ttf face. To GDI that face is a family of its own — "Segoe UI Semibold" —
/// and asking for the "Segoe UI" family at weight 600 lands on Bold instead, which
/// is visibly heavier. So the family is named outright and the weight is left to the
/// mapper: the family has one member and no synthetic emboldening happens.
/// </remarks>
private const string SemiBoldFace = "Segoe UI Semibold";
private const int FW_DONTCARE = 0;
/// <summary>
/// A font of the given size in physical pixels.
/// </summary>
/// <remarks>
/// The size in the settings is in WPF units, that is 1/96 inch, while GDI counts
/// pixels — hence the multiplication by the monitor scale. The height is negative:
/// that asks for the em size rather than the cell height, which is what a font size
/// means everywhere else.
/// </remarks>
internal static IntPtr CreateFont(double wpfFontSize, double scale)
{
var height = (int)Math.Round(wpfFontSize * scale);
return CreateFontW(
-height, 0, 0, 0, FW_DONTCARE,
false, false, false,
DEFAULT_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH,
SemiBoldFace);
}
/// <summary>The size of a single line of text with the font selected into the context.</summary>
internal static Size MeasureText(IntPtr deviceContext, string text)
{
var bounds = default(PopupWindowNative.Rect);
DrawText(deviceContext, text, text.Length, ref bounds,
DT_CALCRECT | DT_SINGLELINE | DT_NOPREFIX);
return new Size(bounds.Right - bounds.Left, bounds.Bottom - bounds.Top);
}
/// <summary>A colour as GDI wants it: 0x00BBGGRR, the alpha carried elsewhere.</summary>
internal static uint ToColorRef(Color color) =>
(uint)(color.R | (color.G << 8) | (color.B << 16));
[DllImport("gdi32.dll", CharSet = CharSet.Unicode, EntryPoint = "CreateFontW")]
private static extern IntPtr CreateFontW(int cHeight, int cWidth, int cEscapement, int cOrientation,
int cWeight, bool bItalic, bool bUnderline, bool bStrikeOut,
int iCharSet, int iOutPrecision, int iClipPrecision, int iQuality, int iPitchAndFamily,
string pszFaceName);
/// <summary>
/// A 32-bit surface to draw the popup into before anyone can see it.
/// </summary>
/// <remarks>
/// Top-down — a negative height — so that the first row of <paramref name="bits"/>
/// is the top row of the picture and the alpha fixing up afterwards can walk the
/// memory straight through.
/// </remarks>
internal static IntPtr CreateSurface(IntPtr deviceContext, int width, int height, out IntPtr bits)
{
var header = new BitmapInfoHeader
{
biSize = Marshal.SizeOf<BitmapInfoHeader>(),
biWidth = width,
biHeight = -height,
biPlanes = 1,
biBitCount = 32,
biCompression = BI_RGB,
};
return CreateDIBSection(deviceContext, ref header, DIB_RGB_COLORS, out bits, IntPtr.Zero, 0);
}
private const uint BI_RGB = 0;
private const uint DIB_RGB_COLORS = 0;
[DllImport("gdi32.dll")]
internal static extern IntPtr CreateCompatibleDC(IntPtr hdc);
[DllImport("gdi32.dll")]
internal static extern bool DeleteDC(IntPtr hdc);
[DllImport("gdi32.dll")]
private static extern IntPtr CreateDIBSection(IntPtr hdc, ref BitmapInfoHeader header, uint usage,
out IntPtr bits, IntPtr section, uint offset);
[DllImport("gdi32.dll")]
internal static extern IntPtr SelectObject(IntPtr hdc, IntPtr h);
[DllImport("gdi32.dll")]
internal static extern bool DeleteObject(IntPtr ho);
[DllImport("gdi32.dll")]
internal static extern int SetBkMode(IntPtr hdc, int mode);
[DllImport("gdi32.dll")]
internal static extern uint SetTextColor(IntPtr hdc, uint color);
[DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "DrawTextW")]
internal static extern int DrawText(IntPtr hdc, string lpchText, int cchText,
ref PopupWindowNative.Rect lprc, uint format);
[DllImport("user32.dll")]
internal static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("user32.dll")]
internal static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
[StructLayout(LayoutKind.Sequential)]
private struct BitmapInfoHeader
{
public int biSize;
public int biWidth;
public int biHeight;
public short biPlanes;
public short biBitCount;
public uint biCompression;
public uint biSizeImage;
public int biXPelsPerMeter;
public int biYPelsPerMeter;
public uint biClrUsed;
public uint biClrImportant;
}
}