308 lines
10 KiB
C++
308 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"
|
|
}
|
|
|
|
#pragma warning(push, 0)
|
|
# include <dwrite.h>
|
|
# include <dwrite_3.h>
|
|
#pragma warning(pop)
|
|
|
|
#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 != S_OK) {
|
|
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, (UINT32)raster_target_w, (UINT32)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 = (u64)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 = (u64)bounding_box.top + y;
|
|
for (i32 x = 0; x < tex_w; ++x) {
|
|
u64 out_x = out_offset_x + x;
|
|
u64 in_x = (u64)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 = (u32)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;
|
|
}
|