power_play/src/ttf_dwrite.cpp
2024-05-03 02:35:25 -05:00

306 lines
10 KiB
C++

/* Based on Allen Webster's dwrite rasterizer example -
* https://github.com/4th-dimention/examps */
extern "C"
{
#include "ttf.h"
#include "scratch.h"
#include "util.h"
#include "string.h"
#include "memory.h"
#include "arena.h"
#include "font.h"
}
#include <dwrite.h>
#include <dwrite_3.h>
#pragma comment(lib, "dwrite")
#pragma comment(lib, "gdi32")
/* TODO: Determine DPI accurately? */
#define DPI (96.0f)
#define CPPSTR(cstr_lit) { (sizeof((cstr_lit)) - 1), (u8 *)(cstr_lit) }
/* ========================== *
* Global state
* ========================== */
GLOBAL struct {
/* FIXME: Do we need to wrap this in a mutex? */
IDWriteFactory5 *factory;
} G = { 0 }, DEBUG_ALIAS(G, L_ttf_dwrite);
/* ========================== *
* Decode font
* ========================== */
INTERNAL i32 round_up(f32 x)
{
i32 r = (i32)x;
if ((f32)r < x) {
r += 1;
}
return r;
}
/* Call this during font system startup */
struct ttf_startup_receipt ttf_startup(void)
{
ASSERT(!G.factory);
/* FIXME: I think IDWriteFactory5 only exists on later updates of windows
* 10? Need to verify. Maybe should just use a custom loader. (We're only
* using a factory5 since I think WriteInMemoryFileLoader wasn't
* implemented until then) */
#if COMPILER_CLANG
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wlanguage-extension-token" /* for __uuidof */
#endif
HRESULT error = DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(IDWriteFactory5),
(IUnknown **)&G.factory
);
#if COMPILER_CLANG
# pragma clang diagnostic pop
#endif
if (error) {
sys_panic(STR("Error creating DWrite factory"));
}
return { 0 };
}
struct ttf_decode_result ttf_decode(struct arena *arena, struct buffer encoded, f32 point_size, u32 *cache_codes, u32 cache_codes_count)
{
COLORREF bg_color = RGB_32(0,0,0);
COLORREF fg_color = RGB_32(255,255,255);
IDWriteFactory5 *factory = G.factory;
/* TODO: handle errors */
HRESULT error = 0;
(UNUSED)error;
/* File */
IDWriteFontFile *font_file = NULL;
{
/* Create in memory loader */
IDWriteInMemoryFontFileLoader *loader = NULL;
error = factory->CreateInMemoryFontFileLoader(&loader);
error = factory->RegisterFontFileLoader(loader);
IDWriteFontSetBuilder1 *builder = NULL;
error = factory->CreateFontSetBuilder(&builder);
error = loader->CreateInMemoryFontFileReference(factory, encoded.data, (u32)encoded.size, NULL, &font_file);
error = builder->AddFontFile(font_file);
}
/* Face */
IDWriteFontFace *font_face = NULL;
error = factory->CreateFontFace(DWRITE_FONT_FACE_TYPE_TRUETYPE, 1, &font_file, 0, DWRITE_FONT_SIMULATIONS_NONE, &font_face);
/* Render settings */
IDWriteRenderingParams *default_rendering_params = NULL;
error = factory->CreateRenderingParams(&default_rendering_params);
IDWriteRenderingParams *rendering_params = NULL;
FLOAT gamma = default_rendering_params->GetGamma();
FLOAT enhanced_contrast = default_rendering_params->GetEnhancedContrast();
FLOAT clear_type_level = default_rendering_params->GetClearTypeLevel();
error = factory->CreateCustomRenderingParams(gamma,
enhanced_contrast,
clear_type_level,
DWRITE_PIXEL_GEOMETRY_FLAT,
DWRITE_RENDERING_MODE_DEFAULT,
&rendering_params);
/* Interop */
IDWriteGdiInterop *dwrite_gdi_interop = NULL;
error = factory->GetGdiInterop(&dwrite_gdi_interop);
/* Get Metrics */
DWRITE_FONT_METRICS metrics = { 0 };
font_face->GetMetrics(&metrics);
f32 pixel_per_em = point_size * (DPI / 72.0f);
f32 pixel_per_design_unit = pixel_per_em / ((f32)metrics.designUnitsPerEm);
i32 raster_target_w = (i32)(8.0f * ((f32)metrics.capHeight) * pixel_per_design_unit);
i32 raster_target_h = raster_target_w;
f32 raster_target_x = (f32)(raster_target_w / 2);
f32 raster_target_y = raster_target_x;
ASSERT((f32)((i32)raster_target_x) == raster_target_x);
ASSERT((f32)((i32)raster_target_y) == raster_target_y);
/* Glyph count */
u16 glyph_count = font_face->GetGlyphCount();
/* Render target */
IDWriteBitmapRenderTarget *render_target = NULL;
/* FIXME: errors when point_size too high */
error = dwrite_gdi_interop->CreateBitmapRenderTarget(0, raster_target_w, raster_target_h, &render_target);
render_target->SetPixelsPerDip(1.0);
/* Clear the render target */
HDC dc = 0;
{
dc = render_target->GetMemoryDC();
HGDIOBJ original = SelectObject(dc, GetStockObject(DC_PEN));
SetDCPenColor(dc, bg_color);
SelectObject(dc, GetStockObject(DC_BRUSH));
SetDCBrushColor(dc, bg_color);
Rectangle(dc, 0, 0, raster_target_w, raster_target_h);
SelectObject(dc, original);
}
/* Allocate font memory */
struct font_glyph *glyphs = (struct font_glyph *)arena_push_array_zero(arena, struct font_glyph, glyph_count);
/* Allocate (starting) atlas memory
* NOTE: This is unecessary since atlas memory will grow anyway. Could
* just start w/ atlas height 0.
*/
u64 atlas_w = 1024;
u64 atlas_h = 1;
u32 *atlas_memory = arena_push_array_zero(arena, u32, atlas_w * atlas_h);
/* Fill CPU side atlas & metric data */
u32 out_offset_x = 0;
u32 out_offset_y = 0;
u32 row_height = 0;
for (u16 i = 0; i < glyph_count; ++i) {
/* Render glyph to target */
DWRITE_GLYPH_RUN glyph_run = { 0 };
glyph_run.fontFace = font_face;
glyph_run.fontEmSize = pixel_per_em;
glyph_run.glyphCount = 1;
glyph_run.glyphIndices = &i;
RECT bounding_box = { 0 };
error = render_target->DrawGlyphRun(
raster_target_x,
raster_target_y,
DWRITE_MEASURING_MODE_NATURAL,
&glyph_run,
rendering_params,
fg_color,
&bounding_box
);
if (bounding_box.left < 0
|| bounding_box.top < 0
|| bounding_box.right > raster_target_w
|| bounding_box.bottom > raster_target_h) {
/* Skip */
continue;
}
/* Compute glyph metrics */
DWRITE_GLYPH_METRICS glyph_metrics = { 0 };
error = font_face->GetDesignGlyphMetrics(&i, 1, &glyph_metrics, false);
f32 off_x = (f32)bounding_box.left - raster_target_x;
f32 off_y = (f32)bounding_box.top - raster_target_y;
f32 advance = (f32)glyph_metrics.advanceWidth * pixel_per_design_unit;
i32 tex_w = bounding_box.right - bounding_box.left;
i32 tex_h = bounding_box.bottom - bounding_box.top;
struct font_glyph *glyph = &glyphs[i];
glyph->off_x = off_x;
glyph->off_y = off_y;
glyph->advance = round_up(advance);
glyph->width = (f32)tex_w;
glyph->height = (f32)tex_h;
/* Get the bitmap */
HBITMAP bitmap = (HBITMAP)GetCurrentObject(dc, OBJ_BITMAP);
DIBSECTION dib = { 0 };
GetObject(bitmap, sizeof(dib), &dib);
/* Start new row if necessary */
if ((out_offset_x + tex_w) >= atlas_w) {
out_offset_y += row_height;
out_offset_x = 0;
row_height = 0;
}
/* Grow atlas height */
if ((out_offset_y + tex_h) > atlas_h) {
u64 diff = (out_offset_y + tex_h) - atlas_h;
/* NOTE: This allocation must be contiguous with the initial atlas
* allocation (IE: No non-atlas arena PUSHes) */
arena_push_array_zero(arena, u32, diff * atlas_w);
atlas_h += diff;
}
/* Set bounding box metrics (now that we know atlas x & y) */
glyph->atlas_rect = { 0 };
glyph->atlas_rect.x = (f32)out_offset_x;
glyph->atlas_rect.y = (f32)out_offset_y;
glyph->atlas_rect.width = (f32)tex_w;
glyph->atlas_rect.height = (f32)tex_h;
/* Fill atlas */
u64 in_pitch = dib.dsBm.bmWidthBytes / 4;
u32 *in_data = (u32 *)dib.dsBm.bmBits;
u32 *out_data = atlas_memory;
for (i32 y = 0; y < tex_h; ++y) {
u64 out_y = out_offset_y + y;
u64 in_y = bounding_box.top + y;
for (i32 x = 0; x < tex_w; ++x) {
u64 out_x = out_offset_x + x;
u64 in_x = bounding_box.left + x;
u32 *out_pixel = out_data + (out_x + (out_y * atlas_w));
u32 *in_pixel = in_data + (in_x + (in_y * in_pitch));
*out_pixel = RGBA_32(0xFF, 0xFF, 0xFF, *in_pixel & 0xFF);
}
}
out_offset_x += tex_w;
/* Grow row height */
if ((u32)tex_h > row_height) {
row_height = tex_h;
}
/* Clear the render target */
{
HGDIOBJ original = SelectObject(dc, GetStockObject(DC_PEN));
SetDCPenColor(dc, bg_color);
SelectObject(dc, GetStockObject(DC_BRUSH));
SetDCBrushColor(dc, bg_color);
Rectangle(dc, bounding_box.left, bounding_box.top, bounding_box.right, bounding_box.bottom);
SelectObject(dc, original);
}
}
/* Construct indices array */
u16 *cache_indices = NULL;
if (cache_codes_count > 0) {
cache_indices = arena_push_array_zero(arena, u16, cache_codes_count);
font_face->GetGlyphIndices(cache_codes, cache_codes_count, cache_indices);
}
/* Release */
dwrite_gdi_interop->Release();
rendering_params->Release();
default_rendering_params->Release();
// NOTE FROM ALLEN: We don't release font face because we intend to keep the font face around after the baking process
// font_face->Release();
font_file->Release();
//loader->Release();
factory->Release();
/* Return */
struct ttf_decode_result result = { 0 };
result.glyphs = glyphs;
result.glyphs_count = glyph_count;
result.cache_indices = cache_indices;
result.image_data.pixels = (u32 *)atlas_memory;
result.image_data.width = (u32)atlas_w;
result.image_data.height = (u32)atlas_h;
return result;
}