/* Based on Allen Webster's dwrite rasterizer example - * https://github.com/4th-dimention/examps */ extern "C" { #include "ttf.h" #include "util.h" #include "string.h" #include "memory.h" #include "arena.h" #include "font.h" } #pragma warning(push, 0) # include # include #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 = ZI, 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) { __prof; 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(LIT("Error creating DWrite factory")); } return { 0 }; } struct ttf_decode_result ttf_decode(struct arena *arena, struct string encoded, f32 point_size, u32 *cache_codes, u32 cache_codes_count) { __prof; COLORREF bg_color = RGB32(0,0,0); COLORREF fg_color = RGB32(255,255,255); IDWriteFactory5 *factory = G.factory; /* TODO: handle errors */ HRESULT error = 0; (UNUSED)error; /* File */ IDWriteFontFile *font_file = 0; { /* Create in memory loader */ IDWriteInMemoryFontFileLoader *loader = 0; error = factory->CreateInMemoryFontFileLoader(&loader); error = factory->RegisterFontFileLoader(loader); IDWriteFontSetBuilder1 *builder = 0; error = factory->CreateFontSetBuilder(&builder); error = loader->CreateInMemoryFontFileReference(factory, encoded.text, (u32)encoded.len, 0, &font_file); error = builder->AddFontFile(font_file); } /* Face */ IDWriteFontFace *font_face = 0; error = factory->CreateFontFace(DWRITE_FONT_FACE_TYPE_TRUETYPE, 1, &font_file, 0, DWRITE_FONT_SIMULATIONS_NONE, &font_face); /* Render settings */ IDWriteRenderingParams *default_rendering_params = 0; error = factory->CreateRenderingParams(&default_rendering_params); IDWriteRenderingParams *rendering_params = 0; 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 = 0; error = factory->GetGdiInterop(&dwrite_gdi_interop); /* Get Metrics */ DWRITE_FONT_METRICS metrics = ZI; 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 = 0; /* 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(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(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; { __profn("Build atlas"); for (u16 i = 0; i < glyph_count; ++i) { /* Render glyph to target */ DWRITE_GLYPH_RUN glyph_run = ZI; glyph_run.fontFace = font_face; glyph_run.fontEmSize = pixel_per_em; glyph_run.glyphCount = 1; glyph_run.glyphIndices = &i; RECT bounding_box = ZI; 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 = ZI; error = font_face->GetDesignGlyphMetrics(&i, 1, &glyph_metrics, 0); 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 = ZI; 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(arena, u32, diff * atlas_w); atlas_h += diff; } /* Set bounding box metrics (now that we know atlas x & y) */ glyph->atlas_rect = ZI; 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 = RGBA32(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 = 0; if (cache_codes_count > 0) { cache_indices = arena_push_array(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 = ZI; 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; }