
Adds a Fonts type the render interface. Here's an example of how you could use this in `gui.zig`: ```zig const fonts = render.Fonts.init(); defer fonts.deinit(); const count = fonts.count(); std.log.info("{} fonts", .{count}); for (0..count) |font_index| { const name = fonts.getName(font_index); std.log.info("font {} '{}'", .{ font_index, std.unicode.fmtUtf16Le(name.slice()) }); } ```
177 lines
6.1 KiB
Zig
177 lines
6.1 KiB
Zig
const std = @import("std");
|
|
const win32 = @import("win32").everything;
|
|
|
|
const FontFace = @import("FontFace.zig");
|
|
const XY = @import("xy.zig").XY;
|
|
|
|
const global = struct {
|
|
var init_called: bool = false;
|
|
var dwrite_factory: *win32.IDWriteFactory = undefined;
|
|
};
|
|
|
|
pub fn init() void {
|
|
std.debug.assert(!global.init_called);
|
|
global.init_called = true;
|
|
{
|
|
const hr = win32.DWriteCreateFactory(
|
|
win32.DWRITE_FACTORY_TYPE_SHARED,
|
|
win32.IID_IDWriteFactory,
|
|
@ptrCast(&global.dwrite_factory),
|
|
);
|
|
if (hr < 0) fatalHr("DWriteCreateFactory", hr);
|
|
}
|
|
}
|
|
|
|
pub const Font = struct {
|
|
text_format: *win32.IDWriteTextFormat,
|
|
cell_size: XY(u16),
|
|
|
|
pub fn init(dpi: u32, size: f32, face: *const FontFace) Font {
|
|
var text_format: *win32.IDWriteTextFormat = undefined;
|
|
{
|
|
const hr = global.dwrite_factory.CreateTextFormat(
|
|
face.ptr(),
|
|
null,
|
|
.NORMAL, //weight
|
|
.NORMAL, // style
|
|
.NORMAL, // stretch
|
|
win32.scaleDpi(f32, size, dpi),
|
|
win32.L(""), // locale
|
|
&text_format,
|
|
);
|
|
if (hr < 0) std.debug.panic(
|
|
"CreateTextFormat '{}' height {d} failed, hresult=0x{x}",
|
|
.{ std.unicode.fmtUtf16le(face.slice()), size, @as(u32, @bitCast(hr)) },
|
|
);
|
|
}
|
|
errdefer _ = text_format.IUnknown.Release();
|
|
|
|
const cell_size = blk: {
|
|
var text_layout: *win32.IDWriteTextLayout = undefined;
|
|
{
|
|
const hr = global.dwrite_factory.CreateTextLayout(
|
|
win32.L("█"),
|
|
1,
|
|
text_format,
|
|
std.math.floatMax(f32),
|
|
std.math.floatMax(f32),
|
|
&text_layout,
|
|
);
|
|
if (hr < 0) fatalHr("CreateTextLayout", hr);
|
|
}
|
|
defer _ = text_layout.IUnknown.Release();
|
|
|
|
var metrics: win32.DWRITE_TEXT_METRICS = undefined;
|
|
{
|
|
const hr = text_layout.GetMetrics(&metrics);
|
|
if (hr < 0) fatalHr("GetMetrics", hr);
|
|
}
|
|
break :blk .{
|
|
.x = @as(u16, @intFromFloat(@floor(metrics.width))),
|
|
.y = @as(u16, @intFromFloat(@floor(metrics.height))),
|
|
};
|
|
};
|
|
|
|
return .{
|
|
.text_format = text_format,
|
|
.cell_size = cell_size,
|
|
};
|
|
}
|
|
|
|
pub fn deinit(self: *Font) void {
|
|
_ = self.text_format.IUnknown.Release();
|
|
self.* = undefined;
|
|
}
|
|
|
|
pub fn getCellSize(self: Font, comptime T: type) XY(T) {
|
|
return .{
|
|
.x = @intCast(self.cell_size.x),
|
|
.y = @intCast(self.cell_size.y),
|
|
};
|
|
}
|
|
};
|
|
|
|
pub const Fonts = struct {
|
|
collection: *win32.IDWriteFontCollection,
|
|
pub fn init() Fonts {
|
|
var collection: *win32.IDWriteFontCollection = undefined;
|
|
const hr = global.dwrite_factory.GetSystemFontCollection(
|
|
&collection,
|
|
1, // check for updates (not sure why this is even an option)
|
|
);
|
|
if (hr < 0) fatalHr("GetSystemFontCollection", hr);
|
|
return .{ .collection = collection };
|
|
}
|
|
pub fn deinit(self: Fonts) void {
|
|
_ = self.collection.IUnknown.Release();
|
|
}
|
|
pub fn count(self: Fonts) usize {
|
|
return @intCast(self.collection.GetFontFamilyCount());
|
|
}
|
|
|
|
pub fn getName(self: Fonts, index: usize) FontFace {
|
|
var family: *win32.IDWriteFontFamily = undefined;
|
|
{
|
|
const hr = self.collection.GetFontFamily(@intCast(index), &family);
|
|
if (hr < 0) fatalHr("GetFontFamily", hr);
|
|
}
|
|
defer _ = family.IUnknown.Release();
|
|
|
|
var names: *win32.IDWriteLocalizedStrings = undefined;
|
|
{
|
|
const hr = family.GetFamilyNames(&names);
|
|
if (hr < 0) fatalHr("GetFamilyNames", hr);
|
|
}
|
|
defer _ = names.IUnknown.Release();
|
|
|
|
// code currently assumes this is always true
|
|
std.debug.assert(names.GetCount() >= 1);
|
|
|
|
// leaving this code in in case we ever want to implement
|
|
// some sort logic to pick a string based on locale
|
|
if (false) {
|
|
const locale_count = names.GetCount();
|
|
std.log.info("Font {} has {} string locales", .{ index, locale_count });
|
|
for (0..locale_count) |i| {
|
|
var locale_name_len: u32 = undefined;
|
|
{
|
|
const hr = names.GetLocaleNameLength(@intCast(i), &locale_name_len);
|
|
if (hr < 0) fatalHr("GetLocaleNameLength", hr);
|
|
}
|
|
std.debug.assert(locale_name_len <= win32.LOCALE_NAME_MAX_LENGTH);
|
|
var locale_name_buf: [win32.LOCALE_NAME_MAX_LENGTH + 1]u16 = undefined;
|
|
{
|
|
const hr = names.GetLocaleName(@intCast(i), @ptrCast(&locale_name_buf), locale_name_buf.len);
|
|
if (hr < 0) fatalHr("GetLocaleName", hr);
|
|
}
|
|
const locale_name = locale_name_buf[0..locale_name_len];
|
|
std.log.info(" {} '{}'", .{ i, std.unicode.fmtUtf16Le(locale_name) });
|
|
}
|
|
}
|
|
|
|
var name_length: u32 = undefined;
|
|
{
|
|
const hr = names.GetStringLength(0, &name_length);
|
|
if (hr < 0) fatalHr("GetStringLength", hr);
|
|
}
|
|
|
|
if (name_length > FontFace.max) std.debug.panic(
|
|
"font name length {} too long (max is {}, we either need to increase max or use allocation)",
|
|
.{ name_length, FontFace.max },
|
|
);
|
|
|
|
var result: FontFace = .{ .buf = undefined, .len = @intCast(name_length) };
|
|
|
|
{
|
|
// note: we're just asking for the first one, whatever locale it is
|
|
const hr = names.GetString(0, @ptrCast(&result.buf), name_length + 1);
|
|
if (hr < 0) fatalHr("GetString", hr);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
};
|
|
|
|
fn fatalHr(what: []const u8, hresult: win32.HRESULT) noreturn {
|
|
std.debug.panic("{s} failed, hresult=0x{x}", .{ what, @as(u32, @bitCast(hresult)) });
|
|
}
|