Created
June 8, 2020 12:45
-
-
Save JPGygax68/dd4e573d4aeb61ea0c12b2f7141190d1 to your computer and use it in GitHub Desktop.
(Posted to help debugging a ZLS problem under VSCode)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const std = @import("std"); | |
const c = @cImport({ | |
// @cInclude("imgui/imgui.h"); | |
// @cInclude("imgui/imgui_internal.h"); | |
@cInclude("imgui/imstb_truetype.h"); | |
@cInclude("imgui/imstb_rectpack.h"); | |
}); | |
const assert = std.debug.assert; | |
const Allocator = std.mem.Allocator; | |
const ARCFAST_TESSELLATION_MULTIPLIER = 1; | |
const UNICODE_CODEPOINT_MAX = 0x10FFFF; | |
const UChar = u21; // Unicode character | |
pub const Vec2 = struct { | |
x: f32 = 0, | |
y: f32 = 0, | |
}; | |
pub const Vec4 = struct { | |
x: f32 = 0, | |
y: f32 = 0, | |
z: f32 = 0, | |
w: f32 = 0, | |
}; | |
pub const FontGlyph = packed struct { | |
CodePoint: u31, // 0x0000..0xFFFF | |
Visible: u1, // Flag to allow early out when rendering | |
AdvanceX: f32, // Distance to next character (= data from font + ImFontConfig::GlyphExtraSpacing.x baked in) | |
X0: f32, | |
Y0: f32, | |
X1: f32, | |
Y1: f32, // Glyph corners | |
U0: f32, | |
V0: f32, | |
U1: f32, | |
V1: f32, // Texture coordinates | |
}; | |
pub const GlyphRange = struct { | |
start: UChar, | |
end: UChar, | |
}; | |
pub const FontConfig = struct { | |
FontData: []const u8, // // TTF/OTF data | |
FontDataOwnedByAtlas: bool, // true // TTF/OTF data ownership taken by the container ImFontAtlas (will delete memory itself). | |
FontNo: c_int, // 0 // Index of font within TTF/OTF file | |
SizePixels: f32, // // Size in pixels for rasterizer (more or less maps to the resulting font height). | |
OversampleH: u8, // 3 // Rasterize at higher quality for sub-pixel positioning. Read https://github.com/nothings/stb/blob/master/tests/oversample/README.md for details. | |
OversampleV: u8, // 1 // Rasterize at higher quality for sub-pixel positioning. We don't use sub-pixel positions on the Y axis. | |
PixelSnapH: bool, // false // Align every glyph to pixel boundary. Useful e.g. if you are merging a non-pixel aligned font with the default font. If enabled, you can set OversampleH/V to 1. | |
GlyphExtraSpacing: Vec2, // 0, 0 // Extra spacing (in pixels) between glyphs. Only X axis is supported for now. | |
GlyphOffset: Vec2, // 0, 0 // Offset all glyphs from this font input. | |
GlyphRanges: ?[]const GlyphRange, // NULL // Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. | |
GlyphMinAdvanceX: f32, // 0 // Minimum AdvanceX for glyphs, set Min to align font icons, set both Min/Max to enforce mono-space font | |
GlyphMaxAdvanceX: f32, // FLT_MAX // Maximum AdvanceX for glyphs | |
MergeMode: bool, // false // Merge into previous ImFont, so you can combine multiple inputs font into one ImFont (e.g. ASCII font + icons + Japanese glyphs). You may want to use GlyphOffset.y when merge font of different heights. | |
RasterizerFlags: u32, // 0x00 // Settings for custom font rasterizer (e.g. ImGuiFreeType). Leave as zero if you aren't using one. | |
RasterizerMultiply: f32, // 1.0f // Brighten (>1.0f) or darken (<1.0f) font output. Brightening small fonts may be a good workaround to make them more readable. | |
EllipsisChar: UChar, // -1 // Explicitly specify unicode codepoint of ellipsis character. When fonts are being merged first specified ellipsis will be used. | |
// [Internal] | |
Name: [40]u8, // Name (strictly to ease debugging) | |
DstFont: ?*Font, | |
pub fn init() FontConfig { | |
return FontConfig{ | |
.FontData = &[_]u8{}, | |
.FontDataOwnedByAtlas = true, | |
.FontNo = 0, | |
.SizePixels = 0.0, | |
.OversampleH = 3, // FIXME: 2 may be a better default? | |
.OversampleV = 1, | |
.PixelSnapH = false, | |
.GlyphExtraSpacing = .{ .x = 0, .y = 0 }, | |
.GlyphOffset = .{ .x = 0, .y = 0 }, | |
.GlyphRanges = null, | |
.GlyphMinAdvanceX = 0.0, | |
.GlyphMaxAdvanceX = std.math.f32_max, | |
.MergeMode = false, | |
.RasterizerFlags = 0x00, | |
.RasterizerMultiply = 1.0, | |
.EllipsisChar = std.math.maxInt(UChar), | |
.Name = std.mem.zeroes([40]u8), | |
.DstFont = null, | |
}; | |
} | |
}; | |
pub const FontAtlasFlags = packed struct { | |
NoPowerOfTwoHeight: bool = false, // Don't round the height to next power of two | |
NoMouseCursors: bool = false, // Don't build software mouse cursors into the atlas | |
}; | |
pub const FontAtlasCustomRect = struct { | |
Width: u16, | |
Height: u16, // Input // Desired rectangle dimension | |
X: u16, | |
Y: u16, // Output // Packed position in Atlas | |
GlyphID: u32, // Input // For custom font glyphs only (ID < 0x110000) | |
GlyphAdvanceX: f32, // Input // For custom font glyphs only: glyph xadvance | |
GlyphOffset: Vec2, // Input // For custom font glyphs only: glyph display offset | |
Font: *Font, // Input // For custom font glyphs only: target font | |
pub fn init() FontAtlasCustomRect { | |
return FontAtlasCustomRect{ | |
.Width = 0, | |
.Height = 0, | |
.X = 0, | |
.Y = 0xFFFF, | |
.GlyphID = 0, | |
.GlyphAdvanceX = 0, | |
.GlyphOffset = .{ 0, 0 }, | |
.Font = null, | |
}; | |
} | |
pub fn IsPacked(self: FontAtlasCustomRect) bool { | |
return self.X != 0xFFFF; | |
} | |
}; | |
// Load and rasterize multiple TTF/OTF fonts into a same texture. The font atlas will build a single texture holding: | |
// - One or more fonts. | |
// - Custom graphics data needed to render the shapes needed by Dear ImGui. | |
// - Mouse cursor shapes for software cursor rendering (unless setting 'Flags |= ImFontAtlasFlags_NoMouseCursors' in the font atlas). | |
// It is the user-code responsibility to setup/build the atlas, then upload the pixel data into a texture accessible by your graphics api. | |
// - Optionally, call any of the AddFont*** functions. If you don't call any, the default font embedded in the code will be loaded for you. | |
// - Call GetTexDataAsAlpha8() or GetTexDataAsRGBA32() to build and retrieve pixels data. | |
// - Upload the pixels data into a texture within your graphics system (see imgui_impl_xxxx.cpp examples) | |
// - Call SetTexID(my_tex_id); and pass the pointer/identifier to your texture in a format natural to your graphics API. | |
// This value will be passed back to you during rendering to identify the texture. Read FAQ entry about ImTextureID for more details. | |
// Common pitfalls: | |
// - If you pass a 'glyph_ranges' array to AddFont*** functions, you need to make sure that your array persist up until the | |
// atlas is build (when calling GetTexData*** or Build()). We only copy the pointer, not the data. | |
// - Important: By default, AddFontFromMemoryTTF() takes ownership of the data. Even though we are not writing to it, we will free the pointer on destruction. | |
// You can set font_cfg->FontDataOwnedByAtlas=false to keep ownership of your data and it won't be freed, | |
// - Even though many functions are suffixed with "TTF", OTF data is supported just as well. | |
// - This is an old API and it is currently awkward for those and and various other reasons! We will address them in the future! | |
pub const FontAtlas = struct { | |
const Error = error { | |
FontNotContainedInAtlas, | |
InitFontFailed, | |
}; | |
pub fn init(alltor: *std.mem.Allocator) FontAtlas { | |
var atlas = FontAtlas{ | |
.Locked = false, | |
.Flags = .{}, | |
.TexID = 0, | |
.TexDesiredWidth = 0, | |
.TexGlyphPadding = 1, | |
.TexPixelsAlpha8 = null, | |
.TexPixelsRGBA32 = null, | |
.TexWidth = 0, | |
.TexHeight = 0, | |
.TexUvScale = Vec2{ .x = 0, .y = 0 }, | |
.TexUvWhitePixel = Vec2{ .x = 0, .y = 0 }, | |
.Fonts = std.ArrayList(*Font).init(alltor), | |
.CustomRects = std.ArrayList(FontAtlasCustomRect).init(alltor), | |
.ConfigData = std.ArrayList(FontConfig).init(alltor), | |
.CustomRectIds = .{-1}, | |
}; | |
return atlas; | |
} | |
pub fn AddFont(self: *FontAtlas, font_cfg: FontConfig, alltor: *std.mem.Allocator) anyerror!*Font { | |
assert(!self.Locked); // and "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); | |
assert(font_cfg.SizePixels > 0); | |
// Create new font | |
var font: *Font = &(try alltor.alloc(Font, 1))[0]; | |
if (!font_cfg.MergeMode) | |
try self.Fonts.append(font) | |
else { | |
// When using MergeMode make sure that a font has already been added before. You can use ImGui::GetIO().Fonts->AddFontDefault() to add the default imgui font. | |
assert(self.Fonts.items.len > 0); // and "Cannot use MergeMode for the first font"); | |
font = self.Fonts.items[self.Fonts.items.len - 1]; | |
} | |
try self.ConfigData.append(font_cfg); | |
var new_font_cfg = &self.ConfigData.items[self.ConfigData.items.len - 1]; | |
if (new_font_cfg.DstFont == null) | |
new_font_cfg.DstFont = self.Fonts.items[self.Fonts.items.len - 1]; | |
if (!new_font_cfg.FontDataOwnedByAtlas) { | |
new_font_cfg.FontData = try alltor.alloc(u8, new_font_cfg.FontData.len); | |
new_font_cfg.FontData = font_cfg.FontData; | |
// std.mem.copy([new_font_cfg.FontDataSize]u8, new_font_cfg.FontData, font_cfg.FontData); | |
new_font_cfg.FontDataOwnedByAtlas = true; | |
} | |
if (new_font_cfg.DstFont.?.*.EllipsisChar == std.math.maxInt(UChar)) | |
new_font_cfg.DstFont.?.*.EllipsisChar = font_cfg.EllipsisChar; | |
// Invalidate texture | |
self.ClearTexData(); | |
return new_font_cfg.DstFont.?; | |
} | |
pub fn AddFontDefault(self: FontAtlas, font_cfg_tmpl: ?FontConfig) *Font { | |
var font_cfg: FontConfig = if (font_cfg_tmpl != null) font_cfg_tmpl.? else FontConfig.init(); | |
if (font_cfg_tmpl == null) { | |
font_cfg.OversampleH = 1; font_cfg.OversampleV = 1; | |
font_cfg.PixelSnapH = true; | |
} | |
if (font_cfg.SizePixels <= 0.0) | |
font_cfg.SizePixels = 13.0 * 1.0; | |
if (font_cfg.Name[0] == '\x00') | |
std.fmt.bufPrint(font_cfg.Name[0..], "ProggyClean.ttf, {}px", font_cfg.SizePixels); | |
font_cfg.EllipsisChar = 0x0085; | |
const ttf_compressed_base85 = GetDefaultCompressedFontDataTTFBase85(); | |
const glyph_ranges = if (font_cfg.GlyphRanges != null) font_cfg.GlyphRanges else GetGlyphRangesDefault(); | |
var font = AddFontFromMemoryCompressedBase85TTF(ttf_compressed_base85, font_cfg.SizePixels, &font_cfg, glyph_ranges); | |
font.DisplayOffset.y = 1.0; | |
return font; | |
} | |
pub fn AddFontFromFileTTF( | |
self: *FontAtlas, | |
filename: []const u8, | |
size_pixels: f32, | |
alltor: *Allocator, | |
font_cfg_tmpl: ?FontConfig, | |
glyph_ranges: ?[]const GlyphRange | |
) !*Font { | |
assert(!self.Locked); // "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); | |
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); | |
defer arena.deinit(); | |
var data = try std.fs.cwd().readFileAlloc(&arena.allocator, filename, 10 * 1024 * 1024); | |
var font_cfg = if (font_cfg_tmpl != null) font_cfg_tmpl.? else FontConfig.init(); | |
if (font_cfg.Name[0] == '\x00') | |
std.mem.copy(u8, &font_cfg.Name, filename); | |
return self.*.AddFontFromMemoryTTF(data, size_pixels, alltor, font_cfg, glyph_ranges); | |
} | |
// Note: Transfer ownership of 'ttf_data' to ImFontAtlas! Will be deleted after destruction of the atlas. Set font_cfg->FontDataOwnedByAtlas=false to keep ownership of your data and it won't be freed. | |
pub fn AddFontFromMemoryTTF( | |
self: *FontAtlas, | |
ttf_data: []const u8, | |
size_pixels: f32, | |
alltor: *Allocator, | |
font_cfg_tmpl: ?FontConfig, | |
glyph_ranges: ?[]const GlyphRange | |
) anyerror!*Font | |
{ | |
assert(!self.*.Locked); // && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); | |
var font_cfg = if (font_cfg_tmpl != null) font_cfg_tmpl.? else FontConfig.init(); | |
assert(font_cfg.FontData.len == 0); | |
font_cfg.FontData = ttf_data; | |
font_cfg.SizePixels = size_pixels; | |
font_cfg.GlyphRanges = glyph_ranges; | |
return self.*.AddFont(font_cfg, alltor); | |
} | |
// 'compressed_font_data' still owned by caller. Compress with binary_to_compressed_c.cpp. | |
pub fn AddFontFromMemoryCompressedTTF(self: FontAtlas, compressed_font_data: var, compressed_font_size: i32, size_pixels: f32, font_cfg: ?FontConfig, glyph_ranges: ?[_]UChar) Font { | |
// TODO | |
} | |
// 'compressed_font_data_base85' still owned by caller. Compress with binary_to_compressed_c.cpp with -base85 parameter. | |
pub fn AddFontFromMemoryCompressedBase85TTF(self: FontAtlas, compressed_font_data_base85: [_]u8, size_pixels: f32, font_cfg: ?FontConfig, glyph_ranges: ?[_]UChar) Font { | |
// TODO | |
} | |
// Clear input data (all ImFontConfig structures including sizes, TTF data, glyph ranges, etc.) = all the data used to build the texture and fonts. | |
pub fn ClearInputData(self: FontAtlas) void { | |
// TODO | |
} | |
// Clear output texture data (CPU side). Saves RAM once the texture has been copied to graphics memory. | |
pub fn ClearTexData(self: FontAtlas) void { | |
// TODO | |
} | |
// Clear output font data (glyphs storage, UV coordinates). | |
pub fn ClearFonts(self: FontAtlas) void { | |
// TODO | |
} | |
// Clear all input and output. | |
pub fn Clear() void { | |
// TODO | |
} | |
// Build atlas, retrieve pixel data. | |
// User is in charge of copying the pixels into graphics memory (e.g. create a texture with your engine). Then store your texture handle with SetTexID(). | |
// The pitch is always = Width * BytesPerPixels (1 or 4) | |
// Building in RGBA32 format is provided for convenience and compatibility, but note that unless you manually manipulate or copy color data into | |
// the texture (e.g. when using the AddCustomRect*** api), then the RGB pixels emitted will always be white (~75% of memory/bandwidth waste. | |
// Build pixels data. This is called automatically for you by the GetTexData*** functions. | |
pub fn Build(self: *FontAtlas, alltor: *Allocator) !void { | |
assert(!self.Locked); // "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); | |
return FontAtlasBuildWithStbTruetype(self, alltor); | |
} | |
// 1 byte per-pixel | |
pub fn GetTexDataAsAlpha8( | |
self: *FontAtlas, | |
out_pixels: ?*[]const u8, | |
out_width: *usize, out_height: *usize, | |
out_bytes_per_pixel: ?*usize, | |
alltor: *Allocator | |
) void { | |
// Build atlas on demand | |
if (self.TexPixelsAlpha8 == null) { | |
if (self.ConfigData.items.len == 0) | |
_ = self.AddFontDefault(null); | |
try self.Build(alltor); | |
} | |
out_pixels.? = self.TexPixelsAlpha8.?; | |
if (out_width != null) out_width.? = self.TexWidth; | |
if (out_height != null) out_height.? = self.TexHeight; | |
if (out_bytes_per_pixel != null) out_bytes_per_pixel.? = 1; | |
} | |
// 4 bytes-per-pixel | |
pub fn GetTexDataAsRGBA32(out_pixels: ?*[]const u8, out_width: *usize, out_height: *usize, out_bytes_per_pixel: ?*usize) void { | |
// TODO | |
} | |
pub fn IsBuilt() bool { | |
return Fonts.Size > 0 and (TexPixelsAlpha8 != null or TexPixelsRGBA32 != null); | |
} | |
pub fn SetTexID(id: TextureID) void { | |
TexID = id; | |
} | |
//------------------------------------------- | |
// Glyph Ranges | |
//------------------------------------------- | |
// Helpers to retrieve list of common Unicode ranges (2 values per range, values are inclusive, zero-terminated list) | |
// NB: Make sure that your string are UTF-8 and NOT in your local code page. In C++11, you can create UTF-8 string literal using the u8"Hello world" syntax. See FAQ for details. | |
// NB: Consider using ImFontGlyphRangesBuilder to build glyph ranges from textual data. | |
// Basic Latin, Extended Latin | |
pub fn GetGlyphRangesDefault() []const GlyphRange { | |
return []const GlyphRange { | |
.{ .start = 0x0020, .end = 0x00FF }, // Basic Latin + Latin Supplement | |
}; | |
} | |
// Default + Korean characters | |
pub fn GetGlyphRangesKorean() [:0]UChar {} | |
// Default + Hiragana, Katakana, Half-Width, Selection of 1946 Ideographs | |
pub fn GetGlyphRangesJapanese() [:0]UChar {} | |
// Default + Half-Width + Japanese Hiragana/Katakana + full set of about 21000 CJK Unified Ideographs | |
pub fn GetGlyphRangesChineseFull() [:0]UChar {} | |
// Default + Half-Width + Japanese Hiragana/Katakana + set of 2500 CJK Unified Ideographs for common simplified Chinese | |
pub fn GetGlyphRangesChineseSimplifiedCommon() [:0]UChar {} | |
// Default + about 400 Cyrillic characters | |
pub fn GetGlyphRangesCyrillic() [:0]UChar {} | |
// Default + Thai characters | |
pub fn GetGlyphRangesThai() [:0]UChar {} | |
// Default + Vietnamese characters | |
pub fn GetGlyphRangesVietnamese() [:0]UChar {} | |
//------------------------------------------- | |
// [BETA] Custom Rectangles/Glyphs API | |
//------------------------------------------- | |
// You can request arbitrary rectangles to be packed into the atlas, for your own purposes. | |
// After calling Build(), you can query the rectangle position and render your pixels. | |
// You can also request your rectangles to be mapped as font glyph (given a font + Unicode point), | |
// so you can render e.g. custom colorful icons and use them as regular glyphs. | |
// Read docs/FONTS.txt for more details about using colorful icons. | |
// Note: this API may be redesigned later in order to support multi-monitor varying DPI settings. | |
pub fn AddCustomRectRegular(width, height: u32) i32 { | |
// TODO | |
} | |
pub fn AddCustomRectFontGlyph(font: Font, id: UChar, width, height: u32, advance_x: float, offset: ?Vec2) i32 { | |
// TODO | |
} | |
pub fn GetCustomRectByIndex(index: i32) FontAtlasCustomRect { | |
if (index < 0) return NULL; | |
return &CustomRects[index]; | |
} | |
// [Internal] | |
pub fn CalcCustomRectUV(rect: FontAtlasCustomRect, out_uv_min: *Vec2, out_uv_max: *Vec2) void { | |
// TODO | |
} | |
pub fn GetMouseCursorTexData(cursor: MouseCursor, out_offset: *Vec2, out_size: *Vec2, out_uv_border: *[2]Vec2, out_uv_fill: *[2]Vec2) bool { | |
// TODO | |
} | |
//------------------------------------------- | |
// Members | |
//------------------------------------------- | |
Locked: bool, // Marked as Locked by ImGui::NewFrame() so attempt to modify the atlas will assert. | |
Flags: FontAtlasFlags, // Build flags (see FontAtlasFlags_) | |
TexID: usize, // User data to refer to the texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure. | |
TexDesiredWidth: u32, // Texture width desired by user before Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height. | |
TexGlyphPadding: c_int, // Padding between glyphs within texture in pixels. Defaults to 1. If your rendering method doesn't rely on bilinear filtering you may set this to 0. | |
// [Internal] | |
// NB: Access texture data via GetTexData*() calls! Which will setup a default font for you. | |
TexPixelsAlpha8: ?[]u8, // 1 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight | |
TexPixelsRGBA32: ?[]u32, // 4 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight * 4 | |
TexWidth: u32, // Texture width calculated during Build(). | |
TexHeight: u32, // Texture height calculated during Build(). | |
TexUvScale: Vec2, // = (1.0f/TexWidth, 1.0f/TexHeight) | |
TexUvWhitePixel: Vec2, // Texture coordinates to a white pixel | |
Fonts: std.ArrayList(*Font), // Hold all the fonts returned by AddFont*. Fonts[0] is the default font upon calling ImGui::NewFrame(), use ImGui::PushFont()/PopFont() to change the current font. | |
CustomRects: std.ArrayList(FontAtlasCustomRect), // Rectangles for packing custom texture data into the atlas. | |
ConfigData: std.ArrayList(FontConfig), // Internal data | |
CustomRectIds: [1]i32, // Identifiers of custom texture rectangle used by ImFontAtlas/ImDrawList | |
}; | |
pub const Font = struct { | |
// Members: Hot ~20/24 bytes (for CalcTextSize) | |
IndexAdvanceX: std.ArrayList(f32), // 12-16 // out // // Sparse. Glyphs->AdvanceX in a directly indexable way (cache-friendly for CalcTextSize functions which only this this info, and are often bottleneck in large UI). | |
FallbackAdvanceX: f32, // 4 // out // = FallbackGlyph->AdvanceX | |
FontSize: f32, // 4 // in // // Height of characters/line, set during loading (don't change after loading) | |
// Members: Hot ~36/48 bytes (for CalcTextSize + render loop) | |
IndexLookup: std.ArrayList(UChar), // 12-16 // out // // Sparse. Index glyphs by Unicode code-point. | |
Glyphs: std.ArrayList(FontGlyph), // 12-16 // out // // All glyphs. | |
FallbackGlyph: *FontGlyph, // 4-8 // out // = FindGlyph(FontFallbackChar) | |
DisplayOffset: Vec2, // 8 // in // = (0,0) // Offset font rendering by xx pixels | |
// Members: Cold ~32/40 bytes | |
ContainerAtlas: *FontAtlas, // 4-8 // out // // What we has been loaded into | |
ConfigData: FontConfig, // 4-8 // in // // Pointer within ContainerAtlas->ConfigData | |
ConfigDataCount: u16, // 2 // in // ~ 1 // Number of ImFontConfig involved in creating this font. Bigger than 1 when merging multiple font sources into one ImFont. | |
FallbackChar: UChar, // 2 // in // = '?' // Replacement character if a glyph isn't found. Only set via SetFallbackChar() | |
EllipsisChar: UChar, // 2 // out // = -1 // Character used for ellipsis rendering. | |
DirtyLookupTables: bool, // 1 // out // | |
Scale: f32, // 4 // in // = 1.f // Base font scale, multiplied by the per-window font scale which you can adjust with SetWindowFontScale() | |
Ascent: f32, // 4+4 // out // // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize] | |
Descent: f32, | |
MetricsTotalSurface: u32, // 4 // out // // Total surface in pixels to get an idea of the font rasterization/texture cost (not exact, we approximate the cost of padding between glyphs) | |
Used4kPagesMap: [(UNICODE_CODEPOINT_MAX + 1) / 4096 / 8]u8, // 2 bytes if ImWchar=ImWchar16, 34 bytes if ImWchar==ImWchar32. Store 1-bit for each block of 4K codepoints that has one active glyph. This is mainly used to facilitate iterations across all used codepoints. | |
// Methods | |
// TODO IMGUI_API ImFont(); | |
// TODO: IMGUI_API ~ImFont(); | |
pub fn FindGlyph(self: Font, c: UChar) *FontGlyph { | |
// TODO | |
} | |
pub fn FindGlyphNoFallback(self: Font, c: UChar) *FontGlyph { | |
// TODO | |
} | |
pub fn GetCharAdvance(self: Font, c: UChar) f32 { | |
return if (c < IndexAdvanceX.Size) IndexAdvanceX[c] else FallbackAdvanceX; | |
} | |
pub fn IsLoaded(self: Font) bool { | |
return ContainerAtlas != null; | |
} | |
pub fn GetDebugName(self: Font) [_]u8 { | |
return if (ConfigData) ConfigData.Name else "<unknown>"; | |
} | |
// 'max_width' stops rendering after a certain width (could be turned into a 2d size). FLT_MAX to disable. | |
// 'wrap_width' enable automatic word-wrapping across multiple lines to fit into given width. 0.0f to disable. | |
// utf8 | |
pub fn CalcTextSizeA(size, max_width, wrap_width: f32, text: []const u8) struct { size: Vec2, remaining: []const u8 } { | |
// TODO | |
} | |
pub fn CalcWordWrapPositionA(scale: f32, text: []const u8, wrap_width: f32) []const u8 { | |
// TODO | |
} | |
pub fn RenderChar(draw_list: DrawList, size: f32, pos: Vec2, col: u32, c: UChar) void { | |
// TODO | |
} | |
pub fn RenderText(draw_list: DrawList, size: f32, pos: Vec2, col: u32, clip_rect: Vec4, text: []const u8, wrap_width: ?f32, cpu_fine_clip: ?bool) void { | |
// TODO | |
} | |
// [Internal] Don't use! | |
fn BuildLookupTable() void { | |
// TODO | |
} | |
fn ClearOutputData() void { | |
// TODO | |
} | |
fn GrowIndex(new_size: u32) void { | |
// TODO | |
} | |
fn AddGlyph(self: *Font, codepoint: UChar, x0: f32, y0: f32, x1: f32, y1: f32, _u0: f32, v0: f32, _u1: f32, v1: f32, advance_x: f32) void { | |
_ = try self.Glyphs.resize(self.Glyphs.items.len + 1); | |
//// C code, not yet adapted: | |
//ImFontGlyph& glyph = self.Glyphs.back(); | |
//glyph.Codepoint = codepoint; | |
//glyph.Visible = (x0 != x1) and (y0 != y1); | |
//glyph.X0 = x0; | |
//glyph.Y0 = y0; | |
//glyph.X1 = x1; | |
//glyph.Y1 = y1; | |
//glyph.U0 = u0; | |
//glyph.V0 = v0; | |
//glyph.U1 = u1; | |
//glyph.V1 = v1; | |
//glyph.AdvanceX = advance_x + ConfigData.GlyphExtraSpacing.x; // Bake spacing into AdvanceX | |
// | |
//if (ConfigData.PixelSnapH) | |
// glyph.AdvanceX = std.math.round(glyph.AdvanceX); | |
// | |
//// Compute rough surface usage metrics (+1 to account for average padding, +0.99 to round) | |
//self.DirtyLookupTables = true; | |
//self.MetricsTotalSurface += @floatToInt(u32, (glyph.U1 - glyph.U0) * self.ContainerAtlas.TexWidth + 1.99) * | |
// @floatToInt(u32, (glyph.V1 - glyph.V0) * self.ContainerAtlas.TexHeight + 1.99); | |
} | |
fn AddRemapChar(dst, src: UChar, overwrite_dst: ?bool) void { | |
// TODO | |
} | |
fn SetGlyphVisible(c: UChar, visible: bool) void { | |
// TODO | |
} | |
fn IsGlyphRangeUnused(c_begin, c_end: UChar) bool { | |
// TODO | |
} | |
}; | |
pub const DrawListFlags = enum(u3) { | |
None = 0, | |
AntiAliasedLines = 1 << 0, // Lines are anti-aliased (*2 the number of triangles for 1.0f wide line, otherwise *3 the number of triangles) | |
AntiAliasedFill = 1 << 1, // Filled shapes have anti-aliased edges (*2 the number of vertices) | |
AllowVtxOffset = 1 << 2, // Can emit 'VtxOffset > 0' to allow large meshes. Set when 'ImGuiBackendFlags_RendererHasVtxOffset' is enabled. | |
}; | |
pub const DrawListSharedData = struct { | |
TexUvWhitePixel: Vec2, // UV of white pixel in the atlas | |
Font: ?Font, // Current/default font size (optional, for simplified AddText overload) | |
FontSize: f32, // Current/default font (optional, for simplified AddText overload) | |
CurveTessellationTol: f32, // Tessellation tolerance when using PathBezierCurveTo() | |
CircleSegmentMaxError: f32, // Number of circle segments to use per pixel of radius for AddCircle() etc | |
ClipRectFullscreen: Vec4, // Value for PushClipRectFullscreen() | |
InitialFlags: DrawListFlags, // Initial flags at the beginning of the frame (it is possible to alter flags on a per-drawlist basis afterwards) | |
// [Internal] Lookup tables | |
ArcFastVtx: [12 * ARCFAST_TESSELLATION_MULTIPLIER]Vec2, // FIXME: Bake rounded corners fill/borders in atlas | |
CircleSegmentCounts: [64]u8, // Precomputed segment count for given radius (array index + 1) before we calculate it dynamically (to avoid calculation overhead) | |
pub fn init() DrawListSharedData { | |
var data = DrawListSharedData{ | |
.TexUvWhitePixel = .{ .x = 0, .y = 0 }, | |
.Font = null, | |
.FontSize = 0.0, | |
.CurveTessellationTol = 0.0, | |
.CircleSegmentMaxError = 0.0, | |
.ClipRectFullscreen = Vec4{ .x = -8192, .y = -8192, .z = 8192, .w = 8192 }, | |
.InitialFlags = DrawListFlags.None, // TODO: Zig-style enum | |
.ArcFastVtx = undefined, | |
.CircleSegmentCounts = [_]u8{0} ** 64, | |
}; | |
for (data.ArcFastVtx) |*vtx, i| { | |
const a: f32 = @intToFloat(f32, i) * 2 * std.math.pi / @intToFloat(f32, data.ArcFastVtx.len); | |
vtx.* = Vec2{ .x = std.math.cos(a), .y = std.math.sin(a) }; | |
} | |
return data; | |
} | |
pub fn SetCircleSegmentMaxError(max_error: f32) void {} | |
}; | |
// Register default custom rectangles (this is called/shared by both the stb_truetype and the FreeType builder) | |
fn FontAtlasBuildInit(atlas: FontAtlas) void { | |
if (atlas.CustomRectIds[0] < 0) { | |
if ((atlas.Flags & FontAtlas.Flags.NoMouseCursors) != 0) | |
atlas.CustomRectIds[0] = atlas.AddCustomRectRegular(FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF*2+1, FONT_ATLAS_DEFAULT_TEX_DATA_H) | |
else | |
atlas.CustomRectIds[0] = atlas.AddCustomRectRegular(2, 2); | |
} | |
} | |
fn FontAtlasBuildWithStbTruetype(atlas: *FontAtlas, alltor: *Allocator) !void { | |
// Temporary data for one source font (multiple source fonts can be merged into one destination ImFont) | |
// (C++03 doesn't allow instancing ImVector<> with function-local types so we declare the type here.) | |
const FontBuildSrcData = struct { | |
FontInfo: c.stbtt_fontinfo, | |
PackRange: c.stbtt_pack_range, // Hold the list of codepoints to pack (essentially points to Codepoints.Data) | |
Rects: []c.stbrp_rect, // Rectangles to pack. We first fill in their sizes and the packer will give us their position. | |
PackedChars: []c.stbtt_packedchar, // Output glyphs | |
SrcRanges: []const GlyphRange, // Ranges as requested by user (user is allowed to request too much, e.g. 0x0020..0xFFFF) | |
DstIndex: i32, // Index into atlas->Fonts[] and dst_tmp_array[] | |
GlyphsHighest: usize, // Highest requested codepoint | |
GlyphsCount: usize, // Glyph count (excluding missing glyphs and glyphs already set by an earlier source font) | |
GlyphsSet: std.ArrayList(u1), // Glyph bit map (random access, 1-bit per codepoint. This will be a maximum of 8KB) | |
GlyphsList: std.ArrayList(c_int), // Glyph codepoints list (flattened version of GlyphsMap) | |
}; | |
// Temporary data for one destination ImFont* (multiple source fonts can be merged into one destination ImFont) | |
const FontBuildDstData = struct { | |
SrcCount: i32, // Number of source fonts targeting this destination font. | |
GlyphsHighest: usize, | |
GlyphsCount: usize, | |
GlyphsSet: std.ArrayList(u1), // This is used to resolve collision when multiple sources are merged into a same destination font. | |
}; | |
assert(atlas.ConfigData.items.len > 0); | |
FontAtlasBuildInit(atlas.*); | |
// Clear atlas | |
atlas.TexID = 0; | |
atlas.TexWidth = 0; | |
atlas.TexHeight = 0; | |
atlas.TexUvScale = Vec2{ .x = 0, .y = 0 }; | |
atlas.TexUvWhitePixel = Vec2{ .x = 0, .y = 0 }; | |
atlas.ClearTexData(); | |
// Temporary storage for building | |
var src_tmp_array = std.ArrayList(FontBuildSrcData).init(alltor); | |
var dst_tmp_array = std.ArrayList(FontBuildDstData).init(alltor); | |
_ = try src_tmp_array.resize(atlas.ConfigData.items.len); | |
_ = try dst_tmp_array.resize(atlas.Fonts.items.len); | |
for (src_tmp_array.items) |*item| item.* = std.mem.zeroes(FontBuildSrcData); | |
for (dst_tmp_array.items) |*item| item.* = std.mem.zeroes(FontBuildDstData); | |
// 1. Initialize font loading structure, check font data validity | |
for (atlas.ConfigData.items) |cfg, src_i| { | |
var src_tmp = &src_tmp_array.items[src_i]; | |
assert(cfg.DstFont != null and (!cfg.DstFont.?.IsLoaded() or cfg.DstFont.?.ContainerAtlas == atlas)); | |
// Find index from cfg.DstFont (we allow the user to set cfg.DstFont. Also it makes casual debugging nicer than when storing indices) | |
src_tmp.DstIndex = -1; | |
for (atlas.Fonts.items) |font, index| { | |
if (cfg.DstFont == font) src_tmp.DstIndex = @intCast(i32, index); | |
} | |
assert(src_tmp.DstIndex != -1); // cfg.DstFont not pointing within atlas->Fonts[] array? | |
if (src_tmp.DstIndex == -1) return FontAtlas.Error.FontNotContainedInAtlas; | |
// Initialize helper structure for font loading and verify that the TTF/OTF data is correct | |
const font_offset = c.stbtt_GetFontOffsetForIndex(&cfg.FontData[0], cfg.FontNo); | |
assert(font_offset >= 0); // "FontData is incorrect, or FontNo cannot be found." | |
if (c.stbtt_InitFont(&src_tmp.FontInfo, &cfg.FontData[0], font_offset) == 0) | |
return FontAtlas.Error.InitFontFailed; | |
// Measure highest codepoints | |
var dst_tmp = &dst_tmp_array.items[@intCast(usize, src_tmp.DstIndex)]; | |
src_tmp.SrcRanges = if (cfg.GlyphRanges != null) cfg.GlyphRanges.? else FontAtlas.GetGlyphRangesDefault(); | |
for (src_tmp.SrcRanges) |range| { | |
if (range.end > src_tmp.GlyphsHighest) src_tmp.GlyphsHighest = range.end; | |
} | |
dst_tmp.SrcCount += 1; | |
dst_tmp.GlyphsHighest = std.math.max(dst_tmp.GlyphsHighest, src_tmp.GlyphsHighest); | |
} | |
// 2. For every requested codepoint, check for their presence in the font data, and handle redundancy or overlaps between source fonts to avoid unused glyphs. | |
var total_glyphs_count: usize = 0; | |
for (src_tmp_array.items) |*src_tmp, src_i| { | |
var dst_tmp = &dst_tmp_array.items[@intCast(usize, src_tmp.DstIndex)]; | |
_ = try src_tmp.GlyphsSet.resize(src_tmp.GlyphsHighest + 1); | |
if (dst_tmp.GlyphsSet.items.len == 0) | |
_ = try dst_tmp.GlyphsSet.resize(dst_tmp.GlyphsHighest + 1); | |
for (src_tmp.SrcRanges) |range, i| { | |
var codepoint: UChar = 0; | |
while (codepoint < range.end) { | |
if (dst_tmp.GlyphsSet.items[codepoint] != 0) // Don't overwrite existing glyphs. We could make this an option for MergeMode (e.g. MergeOverwrite==true) | |
continue; | |
if (c.stbtt_FindGlyphIndex(&src_tmp.FontInfo, codepoint) != 0) // It is actually in the font? | |
continue; | |
// Add to avail set/counters | |
src_tmp.GlyphsCount += 1; | |
dst_tmp.GlyphsCount += 1; | |
src_tmp.GlyphsSet.items[codepoint] = 1; | |
dst_tmp.GlyphsSet.items[codepoint] = 1; | |
total_glyphs_count += 1; | |
codepoint += 1; | |
} | |
} | |
} | |
// 3. Unpack our bit map into a flat list (we now have all the Unicode points that we know are requested _and_ available _and_ not overlapping another) | |
for (src_tmp_array.items) |*src_tmp| { | |
try src_tmp.GlyphsList.ensureCapacity(@intCast(usize, src_tmp.GlyphsCount)); | |
UnpackBitVectorToFlatIndexList(src_tmp.GlyphsSet, &src_tmp.GlyphsList); | |
try src_tmp.GlyphsSet.resize(0); | |
assert(src_tmp.GlyphsList.items.len == src_tmp.GlyphsCount); | |
} | |
for (dst_tmp_array.items) |*dst_tmp| try dst_tmp.GlyphsSet.resize(0); | |
try dst_tmp_array.resize(0); | |
// Allocate packing character data and flag packed characters buffer as non-packed (x0=y0=x1=y1=0) | |
// (We technically don't need to zero-clear buf_rects, but let's do it for the sake of sanity) | |
var buf_rects = std.ArrayList(c.stbrp_rect).init(alltor); | |
var buf_packedchars = std.ArrayList(c.stbtt_packedchar).init(alltor); | |
try buf_rects.resize(total_glyphs_count); | |
try buf_packedchars.resize(total_glyphs_count); | |
for (buf_rects.items) |*br| br.* = std.mem.zeroes(c.stbrp_rect); | |
for (buf_packedchars.items) |*bpc| bpc.* = std.mem.zeroes(c.stbtt_packedchar); | |
// 4. Gather glyphs sizes so we can pack them in our virtual canvas. | |
var total_surface: f32 = 0; | |
var buf_rects_out_n: usize = 0; | |
var buf_packedchars_out_n: usize = 0; | |
for (src_tmp_array.items) |*src_tmp, src_i| { | |
if (src_tmp.GlyphsCount == 0) continue; | |
src_tmp.Rects = buf_rects.items[buf_rects_out_n .. buf_rects_out_n + src_tmp.GlyphsList.items.len]; | |
src_tmp.PackedChars = buf_packedchars.items[buf_packedchars_out_n .. buf_packedchars_out_n + src_tmp.GlyphsCount]; | |
buf_rects_out_n += src_tmp.GlyphsCount; | |
buf_packedchars_out_n += src_tmp.GlyphsCount; | |
// Convert our ranges in the format stb_truetype wants | |
const cfg = &atlas.ConfigData.items[src_i]; | |
src_tmp.PackRange.font_size = cfg.SizePixels; | |
src_tmp.PackRange.first_unicode_codepoint_in_range = 0; | |
src_tmp.PackRange.array_of_unicode_codepoints = src_tmp.GlyphsList.items.ptr; | |
src_tmp.PackRange.num_chars = @intCast(c_int, src_tmp.GlyphsList.items.len); | |
src_tmp.PackRange.chardata_for_range = &src_tmp.PackedChars[0]; | |
src_tmp.PackRange.h_oversample = cfg.OversampleH; | |
src_tmp.PackRange.v_oversample = cfg.OversampleV; | |
// Gather the sizes of all rectangles we will need to pack (this loop is based on stbtt_PackFontRangesGatherRects) | |
const scale = if (cfg.SizePixels > 0) c.stbtt_ScaleForPixelHeight(&src_tmp.FontInfo, cfg.SizePixels) else c.stbtt_ScaleForMappingEmToPixels(&src_tmp.FontInfo, -cfg.SizePixels); | |
const padding = atlas.TexGlyphPadding; | |
for (src_tmp.GlyphsList.items) |glyph, glyph_i| { | |
var x0: c_int = 0; var y0: c_int = 0; var x1: c_int = 0; var y1: c_int = 0; | |
const glyph_index_in_font = c.stbtt_FindGlyphIndex(&src_tmp.FontInfo, src_tmp.GlyphsList.items[glyph_i]); | |
assert(glyph_index_in_font != 0); | |
c.stbtt_GetGlyphBitmapBoxSubpixel(&src_tmp.FontInfo, glyph_index_in_font, | |
std.math.floor(scale * @intToFloat(f32, cfg.OversampleH)), std.math.floor(scale * @intToFloat(f32, cfg.OversampleV)), 0, 0, | |
&x0, &y0, &x1, &y1); | |
src_tmp.Rects[glyph_i].w = @intCast(c_ushort, x1 - x0 + padding + @intCast(c_int, cfg.OversampleH) - 1); | |
src_tmp.Rects[glyph_i].h = @intCast(c_ushort, y1 - y0 + padding + @intCast(c_int, cfg.OversampleV) - 1); | |
total_surface += @intToFloat(f32, src_tmp.Rects[glyph_i].w * src_tmp.Rects[glyph_i].h); | |
} | |
} | |
// We need a width for the skyline algorithm, any width! | |
// The exact width doesn't really matter much, but some API/GPU have texture size limitations and increasing width can decrease height. | |
// User can override TexDesiredWidth and TexGlyphPadding if they wish, otherwise we use a simple heuristic to select the width based on expected surface. | |
const surface_sqrt: f32 = std.math.sqrt(total_surface) + 1; | |
atlas.TexHeight = 0; | |
if (atlas.TexDesiredWidth > 0) | |
atlas.TexWidth = atlas.TexDesiredWidth | |
else | |
atlas.TexWidth = if (surface_sqrt >= 4096*0.7) @as(u32, 4096) | |
else if (surface_sqrt >= 2048*0.7) @as(u32, 2048) | |
else if (surface_sqrt >= 1024*0.7) @as(u32, 1024) | |
else @as(u32, 512); | |
// 5. Start packing | |
// Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values). | |
const TEX_HEIGHT_MAX = 1024 * 32; | |
var spc: c.stbtt_pack_context = std.mem.zeroes(c.stbtt_pack_context); | |
_ = c.stbtt_PackBegin(&spc, null, @intCast(c_int, atlas.TexWidth), TEX_HEIGHT_MAX, 0, atlas.TexGlyphPadding, null); | |
FontAtlasBuildPackCustomRects(atlas, spc.pack_info); | |
// 6. Pack each source font. No rendering yet, we are working with rectangles in an infinitely tall texture at this point. | |
for (src_tmp_array.items) |src_tmp| { | |
if (src_tmp.GlyphsCount == 0) continue; | |
_ = c.stbrp_pack_rects( | |
@ptrCast(*c.stbrp_context, @alignCast(@alignOf(*c.stbrp_context), spc.pack_info)), | |
&src_tmp.Rects[0], @intCast(c_int, src_tmp.GlyphsCount)); | |
// Extend texture height and mark missing glyphs as non-packed so we won't render them. | |
// FIXME: We are not handling packing failure here (would happen if we got off TEX_HEIGHT_MAX or if a single if larger than TexWidth?) | |
for (src_tmp.Rects) |rect| { | |
if (rect.was_packed != 0) | |
atlas.TexHeight = std.math.max(atlas.TexHeight, rect.y + rect.h); | |
} | |
} | |
// 7. Allocate texture | |
atlas.TexHeight = if (atlas.Flags.NoPowerOfTwoHeight) (atlas.TexHeight + 1) else try std.math.ceilPowerOfTwo(u32, atlas.TexHeight); | |
atlas.TexUvScale = Vec2{ .x = 1.0 / @intToFloat(f32, atlas.TexWidth), .y = 1.0 / @intToFloat(f32, atlas.TexHeight) }; | |
atlas.TexPixelsAlpha8 = try alltor.alloc(u8, atlas.TexWidth * atlas.TexHeight); | |
for (atlas.TexPixelsAlpha8.?) |*px| px.* = 0; | |
// memset(atlas->TexPixelsAlpha8, 0, atlas->TexWidth * atlas->TexHeight); | |
spc.pixels = atlas.TexPixelsAlpha8.?.ptr; | |
spc.height = @intCast(c_int, atlas.TexHeight); | |
// 8. Render/rasterize font characters into the texture | |
for (src_tmp_array.items) |*src_tmp, src_i| { | |
if (src_tmp.GlyphsCount == 0) continue; | |
var cfg = &atlas.ConfigData.items[src_i]; | |
_ = c.stbtt_PackFontRangesRenderIntoRects(&spc, &src_tmp.FontInfo, &src_tmp.PackRange, 1, src_tmp.Rects.ptr); | |
// Apply multiply operator | |
if (cfg.RasterizerMultiply != 1.0) { | |
var multiply_table: [256]u8 = undefined; | |
FontAtlasBuildMultiplyCalcLookupTable(&multiply_table, cfg.RasterizerMultiply); | |
var glyph_i: usize = 0; | |
while (glyph_i < src_tmp.GlyphsCount) { | |
const r = &src_tmp.Rects[glyph_i]; | |
if (r.was_packed != 0) | |
FontAtlasBuildMultiplyRectAlpha8(multiply_table, atlas.TexPixelsAlpha8.?, | |
r.x, r.y, r.w, r.h, atlas.TexWidth * 1); | |
glyph_i += 1; | |
} | |
} | |
src_tmp.Rects.len = 0; | |
} | |
// End packing | |
c.stbtt_PackEnd(&spc); | |
try buf_rects.resize(0); | |
// 9. Setup ImFont and glyphs for runtime | |
for (src_tmp_array.items) |src_tmp, src_i| { | |
if (src_tmp.GlyphsCount == 0) continue; | |
var cfg = &atlas.ConfigData.items[src_i]; | |
var dst_font = cfg.DstFont.?; // We can have multiple input fonts writing into a same destination font (when using MergeMode=true) | |
const font_scale = c.stbtt_ScaleForPixelHeight(&src_tmp.FontInfo, cfg.SizePixels); | |
var unscaled_ascent: c_int = 0; var unscaled_descent: c_int = 0; var unscaled_line_gap: c_int = 0; | |
c.stbtt_GetFontVMetrics(&src_tmp.FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap); | |
const ascent : f32 = std.math.floor(@intToFloat(f32, unscaled_ascent ) * font_scale + | |
@as(f32, if (unscaled_ascent > 0) 1.0 else -1.0)); | |
const descent: f32 = std.math.floor(@intToFloat(f32, unscaled_descent) * font_scale + | |
@as(f32, if (unscaled_descent > 0) 1.0 else -1.0)); | |
FontAtlasBuildSetupFont(atlas.*, dst_font, cfg.*, ascent, descent); | |
const font_off_x = cfg.GlyphOffset.x; | |
const font_off_y = cfg.GlyphOffset.y + std.math.round(dst_font.Ascent); | |
var glyph_i: usize = 0; | |
while (glyph_i < src_tmp.GlyphsCount) { | |
const codepoint = @intCast(UChar, src_tmp.GlyphsList.items[glyph_i]); | |
const pc = &src_tmp.PackedChars[glyph_i]; | |
const char_advance_x_org = pc.xadvance; | |
const char_advance_x_mod = std.math.clamp(char_advance_x_org, cfg.GlyphMinAdvanceX, cfg.GlyphMaxAdvanceX); | |
var char_off_x = font_off_x; | |
if (char_advance_x_org != char_advance_x_mod) | |
char_off_x += if (cfg.PixelSnapH) | |
std.math.floor((char_advance_x_mod - char_advance_x_org) * 0.5) | |
else (char_advance_x_mod - char_advance_x_org) * 0.5; | |
// Register glyph | |
var q: c.stbtt_aligned_quad = undefined; | |
var dummy_x: f32 = 0.0; var dummy_y: f32 = 0.0; | |
c.stbtt_GetPackedQuad(src_tmp.PackedChars.ptr, @intCast(c_int, atlas.TexWidth), @intCast(c_int, atlas.TexHeight), @intCast(c_int, glyph_i), | |
&dummy_x, &dummy_y, &q, 0); | |
dst_font.AddGlyph(codepoint, q.x0 + char_off_x, q.y0 + font_off_y, q.x1 + char_off_x, q.y1 + font_off_y, q.s0, q.t0, q.s1, q.t1, char_advance_x_mod); | |
glyph_i += 1; | |
} | |
} | |
// Cleanup temporary (ImVector doesn't honor destructor) | |
// for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) | |
// src_tmp_array[src_i].~ImFontBuildSrcData(); | |
FontAtlasBuildFinish(atlas); | |
} | |
// TODO: bit-packed version of ArrayList(u1)! | |
fn UnpackBitVectorToFlatIndexList(in: std.ArrayList(u1), out: *std.ArrayList(c_int)) void { | |
for (in.items) |on, i| { | |
if (on != 0) out.append(@intCast(c_int, i)); | |
} | |
} | |
fn FontAtlasBuildPackCustomRects(atlas: *FontAtlas, stbrp_context_opaque: *c_void) void { | |
var pack_context = @ptrCast(c.stbrp_context, stbrp_context_opaque); | |
assert(pack_context != null); | |
var user_rects = atlas.CustomRects; | |
assert(user_rects.items.len >= 1); // We expect at least the default custom rects to be registered, else something went wrong. | |
var pack_rects = std.ArrayList(c.stbrp_rect); | |
pack_rects.resize(user_rects.items.len); | |
for (pack_rects) |*rect| rect.* = std.mem.zeroes(c.stbrp_rect); | |
var i = 0; | |
while (i < user_rects.items.len) { | |
pack_rects[i].w = user_rects[i].Width; | |
pack_rects[i].h = user_rects[i].Height; | |
i += 1; | |
} | |
c.stbrp_pack_rects(pack_context, &pack_rects.items[0], pack_rects.items.len); | |
i = 0; | |
while (i < pack_rects.items.len) { | |
if (pack_rects[i].was_packed) { | |
user_rects[i].X = pack_rects[i].x; | |
user_rects[i].Y = pack_rects[i].y; | |
assert(pack_rects[i].w == user_rects[i].Width and pack_rects[i].h == user_rects[i].Height); | |
atlas.TexHeight = std.math.max(atlas.TexHeight, pack_rects[i].y + pack_rects[i].h); | |
} | |
} | |
} | |
fn FontAtlasBuildMultiplyCalcLookupTable(out_table: *[256]u8, in_brighten_factor: f32) void { | |
for (out_table) |*value| { | |
var new_val: f32 = @intToFloat(f32, value.*) * in_brighten_factor; | |
value.* = if (new_val > 255) 255 else @floatToInt(u8, new_val); | |
} | |
} | |
fn FontAtlasBuildMultiplyRectAlpha8(table: [256]u8, pixels: []u8, x: i32, y: i32, w: i32, h: i32, stride: u32) void { | |
var data = pixels[@intCast(usize, x + y * stride) ..]; | |
var j = h; | |
while (j > 0) { | |
var i = 0; | |
while (i < w) { | |
data[i] = table[data[i]]; | |
i += 1; | |
} | |
data += stride; | |
j -= 1; | |
} | |
} | |
fn FontAtlasBuildSetupFont(atlas: FontAtlas, font: *Font, font_config: FontConfig, ascent: f32, descent: f32) void { | |
if (!font_config.MergeMode) { | |
font.ClearOutputData(); | |
font.FontSize = font_config.SizePixels; | |
font.ConfigData = font_config; | |
font.ContainerAtlas = atlas; | |
font.Ascent = ascent; | |
font.Descent = descent; | |
} | |
font.ConfigDataCount += 1; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment