Skip to content

Instantly share code, notes, and snippets.

@thebirk
Created February 22, 2018 17:51
Show Gist options
  • Save thebirk/fcd9f7e1ddcec9bf314c8785696c46d8 to your computer and use it in GitHub Desktop.
Save thebirk/fcd9f7e1ddcec9bf314c8785696c46d8 to your computer and use it in GitHub Desktop.
Mostly working packing, just handle memory and check for duplicate packed chars
using import "core:c.odin"
foreign import ftlib "freetype271.lib";
/*
when ODIN_OS == "windows" do foreign import ftlib "freetype271.lib";
else do _ :: compiler_assert(false);
*/
FT_Library_ :: struct {}
FT_Library :: ^FT_Library_;
FT_Size_Internal :: rawptr;
FT_Size_Metrics :: struct {
x_ppem : u16,
y_ppem : u16,
x_scale : c_long,
y_scale : c_long,
ascender : c_long,
descender : c_long,
height : c_long,
max_advance : c_long,
}
FT_Generic_Finalizer :: proc "c" (object: rawptr);
FT_Generic :: struct {
data: rawptr,
finalizer: FT_Generic_Finalizer,
}
FT_SizeRec :: struct {
face : FT_Face,
generic : FT_Generic,
metrics : FT_Size_Metrics,
internal : FT_Size_Internal,
}
FT_Size :: ^FT_SizeRec;
FT_Bitmap_Size :: struct {
height : i16,
width : i16,
size : c_long,
x_ppem : c_long,
y_ppem : c_long,
}
FT_Encoding :: enum i32 #export {
FT_ENCODING_NONE = 0,
FT_ENCODING_MS_SYMBOL = ('s' << 24) | ('y' << 16) | ('m' << 8) | 'b',
FT_ENCODING_UNICODE = ('u' << 24) | ('n' << 16) | ('i' << 8) | 'c',
FT_ENCODING_SJIS = ('s' << 24) | ('j' << 16) | ('i' << 8) | 's',
FT_ENCODING_PRC = ('g' << 24) | ('b' << 16) | (' ' << 8) | ' ',
FT_ENCODING_BIG5 = ('b' << 24) | ('i' << 16) | ('g' << 8) | '5',
FT_ENCODING_WANSUNG = ('w' << 24) | ('a' << 16) | ('n' << 8) | 's',
FT_ENCODING_JOHAB = ('j' << 24) | ('o' << 16) | ('h' << 8) | 'a',
/* for backward compatibility */
FT_ENCODING_GB2312 = FT_ENCODING_PRC,
FT_ENCODING_MS_SJIS = FT_ENCODING_SJIS,
FT_ENCODING_MS_GB2312 = FT_ENCODING_PRC,
FT_ENCODING_MS_BIG5 = FT_ENCODING_BIG5,
FT_ENCODING_MS_WANSUNG = FT_ENCODING_WANSUNG,
FT_ENCODING_MS_JOHAB = FT_ENCODING_JOHAB,
FT_ENCODING_ADOBE_STANDARD = ('A' << 24) | ('D' << 16) | ('O' << 8) | 'B',
FT_ENCODING_ADOBE_EXPERT = ('A' << 24) | ('D' << 16) | ('B' << 8) | 'E',
FT_ENCODING_ADOBE_CUSTOM = ('A' << 24) | ('D' << 16) | ('B' << 8) | 'C',
FT_ENCODING_ADOBE_LATIN_1 = ('l' << 24) | ('a' << 16) | ('t' << 8) | '1',
FT_ENCODING_OLD_LATIN_2 = ('l' << 24) | ('a' << 16) | ('t' << 8) | '2',
FT_ENCODING_APPLE_ROMAN = ('a' << 24) | ('r' << 16) | ('m' << 8) | 'n',
}
FT_CharMapRec :: struct {
face : FT_Face,
encoding : FT_Encoding,
platform_id : u16,
encoding_id : u16,
}
FT_CharMap :: ^FT_CharMapRec;
FT_BBox :: struct {
xMin, yMin: c_long,
xMax, yMax: c_long,
}
FT_Glyph_Metrics :: struct {
width : c_long,
height : c_long,
horiBearingX : c_long,
horiBearingY : c_long,
horiAdvance : c_long,
vertBearingX : c_long,
vertBearingY : c_long,
vertAdvance : c_long,
}
FT_Glyph_Format :: enum i32 #export {
FT_GLYPH_FORMAT_NONE = 0,
FT_GLYPH_FORMAT_COMPOSITE = ('c' << 24) | ('o' << 16) | ('m' << 8) | 'p',
FT_GLYPH_FORMAT_BITMAP = ('b' << 24) | ('i' << 16) | ('t' << 8) | 's',
FT_GLYPH_FORMAT_OUTLINE = ('o' << 24) | ('u' << 16) | ('t' << 8) | 'l',
FT_GLYPH_FORMAT_PLOTTER = ('p' << 24) | ('l' << 16) | ('o' << 8) | 't',
}
FT_Bitmap :: struct {
rows : u32,
width : u32,
pitch : int,
buffer : ^u8,
num_grays : u16,
pixel_mode : u8,
palette_mode : u8,
palette : rawptr,
}
FT_Vector :: struct {
x: c_long,
y: c_long,
}
FT_Outline :: struct {
n_contours : i16,
n_points : i16,
points : ^FT_Vector,
tags : ^u8,
contours : ^i16,
flags : i32,
}
FT_SubGlyph :: rawptr;
FT_Slot_Internal :: rawptr;
FT_GlyphSlotRec :: struct {
library : FT_Library,
face : FT_Face,
next : FT_GlyphSlot,
reserved : u32,
generic : FT_Generic,
metrics : FT_Glyph_Metrics,
linearHoriAdvance : c_long,
linearVertAdvance : c_long,
advance : FT_Vector,
format : FT_Glyph_Format,
bitmap : FT_Bitmap,
bitmap_left : i32,
bitmap_top : i32,
outline : FT_Outline,
num_subglyphs : u32,
subglyphs : FT_SubGlyph,
control_data : rawptr,
control_len : c_long,
lsb_delta : c_long,
rsb_delta : c_long,
other : rawptr,
internal : FT_Slot_Internal,
}
FT_GlyphSlot :: ^FT_GlyphSlotRec;
FT_Driver :: rawptr;
FT_Alloc_Func :: proc"c"(memory: FT_Memory, size: c_long) -> rawptr;
FT_Free_Func :: proc"c"(memory: FT_Memory, block: rawptr);
FT_Realloc_Func :: proc"c"(memory: FT_Memory, cur_size: c_long, new_size: c_long, block: rawptr) -> rawptr;
FT_MemoryRec :: struct {
user : rawptr,
alloc : FT_Alloc_Func,
free : FT_Free_Func,
realloc : FT_Realloc_Func,
}
FT_Memory :: ^FT_MemoryRec;
FT_Stream_IoFunc :: proc"c"(stream: FT_Stream, offset: c_ulong, buffer: ^u8, count: c_ulong) -> c_ulong;
FT_Stream_CloseFunc :: proc"c"(stream: FT_Stream);
FT_StreamDesc :: struct #raw_union {
value: c_long,
pointer: rawptr,
}
FT_StreamRec :: struct {
base : ^u8,
size : c_ulong,
pos : c_ulong,
descriptor : FT_StreamDesc,
pathname : FT_StreamDesc,
read : FT_Stream_IoFunc,
close : FT_Stream_CloseFunc,
memory : FT_Memory,
cursor : ^u8,
limit : ^u8,
}
FT_Stream :: ^FT_StreamRec;
FT_ListNode :: rawptr;
FT_ListRec :: struct {
head : FT_ListNode,
tail : FT_ListNode,
}
FT_List :: ^FT_ListRec;
FT_Face_Internal :: rawptr;
FT_FaceRec :: struct {
num_faces : c_long,
face_index : c_long,
face_flags : c_long,
style_flags : c_long,
num_glyphs : c_long,
family_name : ^u8,
style_name : ^u8,
num_fixed_sizes : i32,
available_sizes : ^FT_Bitmap_Size,
num_charmaps : i32,
charmaps : ^FT_CharMap,
generic : FT_Generic,
/*# The following member variables (down to `underline_thickness') */
/*# are only relevant to scalable outlines; cf. @FT_Bitmap_Size */
/*# for bitmap fonts. */
bbox : FT_BBox,
units_per_EM : u16,
ascender : i16,
descender : i16,
height : i16,
max_advance_width : i16,
max_advance_height : i16,
underline_position : i16,
underline_thickness : i16,
glyph : FT_GlyphSlot,
size : FT_Size,
charmap : FT_CharMap,
/*@private begin */
driver : FT_Driver,
memory : FT_Memory,
stream : FT_Stream,
sizes_list : FT_ListRec,
autohint : FT_Generic,
extensions : rawptr,
internal : FT_Face_Internal,
}
FT_Face :: ^FT_FaceRec;
FT_Load_Flags :: enum i32 #export {
FT_LOAD_DEFAULT = 0x0,
FT_LOAD_NO_SCALE = 1 << 0,
FT_LOAD_NO_HINTING = 1 << 1,
FT_LOAD_RENDER = 1 << 2,
FT_LOAD_NO_BITMAP = 1 << 3,
FT_LOAD_VERTICAL_LAYOUT = 1 << 4,
FT_LOAD_FORCE_AUTOHINT = 1 << 5,
FT_LOAD_CROP_BITMAP = 1 << 6,
FT_LOAD_PEDANTIC = 1 << 7,
FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH = 1 << 9,
FT_LOAD_NO_RECURSE = 1 << 10,
FT_LOAD_IGNORE_TRANSFORM = 1 << 11,
FT_LOAD_MONOCHROME = 1 << 12,
FT_LOAD_LINEAR_DESIGN = 1 << 13,
FT_LOAD_NO_AUTOHINT = 1 << 15,
FT_LOAD_COLOR = 1 << 20,
FT_LOAD_COMPUTE_METRICS = 1 << 21,
FT_LOAD_BITMAP_METRICS_ONLY = 1 << 22,
}
FT_Render_Mode :: enum i32 #export {
FT_RENDER_MODE_NORMAL = 0,
FT_RENDER_MODE_LIGHT,
FT_RENDER_MODE_MONO,
FT_RENDER_MODE_LCD,
FT_RENDER_MODE_LCD_V,
FT_RENDER_MODE_MAX,
}
FT_Kerning_Mode :: enum u32 #export {
FT_KERNING_DEFAULT = 0,
FT_KERNING_UNFITTED,
FT_KERNING_UNSCALED,
}
@(default_calling_convention="c")
foreign ftlib {
FT_Init_FreeType :: proc(lib: ^FT_Library) -> i32 ---;
FT_Done_FreeType :: proc(lib: FT_Library) -> i32 ---;
FT_New_Face :: proc(library: FT_Library, path: ^u8, face_index: c_long, face: ^FT_Face) -> i32 ---;
FT_Done_Face :: proc(face: FT_Face) -> i32 ---;
FT_Set_Pixel_Sizes :: proc(face: FT_Face, pixel_width: u32, pixel_height: u32) -> i32 ---;
FT_Load_Glyph :: proc(face: FT_Face, glyph_index: u32, load_flags: FT_Load_Flags) -> i32 ---;
FT_Load_Char :: proc(face: FT_Face, char_code: c_ulong, load_flags: FT_Load_Flags) -> i32 ---;
FT_Render_Glyph :: proc(slot: FT_GlyphSlot, render_mode: FT_Render_Mode) -> i32 ---;
FT_Get_Char_Index :: proc(face: FT_Face, charcode: c_ulong) -> u32 ---;
FT_Get_Kerning :: proc(face: FT_Face, left_glyph, right_glyph: u32, kern_mode: u32, kerning: ^FT_Vector) -> i32 ---;
FT_Get_Glyph_Name :: proc(face: FT_Face, glyph_index: u32, buffer: ^u8, buffer_max: u32) -> i32 ---;
}
import "core:fmt.odin"
/* face: FT_Face;
path := "fonts/arial.ttf\x00";
if FT_New_Face(lib, &path[0], 0, &face) != 0 {
fmt.println("Failed to create face!\n");
}
FT_Set_Pixel_Sizes(face, 0, 16);
print_rune(face, '£');*/
print_rune :: proc(face: FT_Face, r: rune) {
if FT_Load_Char(face, auto_cast r, FT_LOAD_RENDER) != 0 {
fmt.println("Failed to load char!\n");
}
chars := " .:ioV#@";
bitmap := face.glyph.bitmap;
pixels := mem.slice_ptr(bitmap.buffer, int(bitmap.rows*bitmap.width));
for y in 0..bitmap.rows {
for x in 0..bitmap.width {
v := pixels[x+y*bitmap.width] >> 5;
fmt.printf("%c", chars[v]);
}
fmt.println();
}
fmt.println();
}
using import "shared:odin-stb/stb_rect_pack.odin"
import "core:strings.odin"
import "core:mem.odin"
Glyph :: struct {
bearingX, bearingY: int,
width, height: int,
advanceX: int,
}
Font :: struct {
face: FT_Face,
contex: stbrp_context,
nodes: []stbrp_node,
atlas_w, atlas_h: int,
pixels: []u8,
glyphs: map[rune]Glyph,
}
the_library: FT_Library;
load_font :: proc(path: string, pixel_size: int, atlas_width, atlas_height: int) -> ^Font {
font := new(Font);
font.atlas_w = atlas_width-1;
font.atlas_h = atlas_height-1;
using font;
cpath := strings.new_c_string(path);
defer free(cpath);
if FT_New_Face(the_library, cpath, 0, &face) != 0 {
return nil;
}
FT_Set_Pixel_Sizes(face, 0, auto_cast pixel_size);
pixels = make([]u8, atlas_w*atlas_h);
nodes = make([]stbrp_node, atlas_w);
stbrp_init_target(&contex, i32(atlas_w), i32(atlas_h), &nodes[0], auto_cast len(nodes));
pack_range(font, ' ', '~');
return font;
}
pack_range :: proc(using font: ^Font, first, last: rune) -> (packed_all: bool = true) {
glyph_count := int(last-first+1);
reserve(&glyphs, glyph_count);
rects := make([]stbrp_rect, glyph_count);
defer free(rects);
fmt.printf("glyph_count %v\n", glyph_count);
// Get glyph data and setup packing
for cp in first...last {
if FT_Load_Char(face, auto_cast cp, FT_LOAD_RENDER) != 0 {
assert(false, "Invalid font cp!");
}
glyph := face.glyph;
g: Glyph;
g.width = auto_cast glyph.metrics.width;
g.height = auto_cast glyph.metrics.height;
g.bearingX = auto_cast glyph.metrics.horiBearingX;
g.bearingY = auto_cast glyph.metrics.horiBearingY;
g.advanceX = auto_cast glyph.metrics.horiAdvance;
glyphs[cp] = g;
rects[cp-first].w = auto_cast (glyph.bitmap.width+1);
rects[cp-first].h = auto_cast (glyph.bitmap.rows+1);
fmt.printf("loop\n");
}
stbrp_pack_rects(&contex, &rects[0], i32(len(rects)));
copy_to_atlas :: proc(using font: ^Font, r: ^stbrp_rect, bitmap: []u8) {
for y in 0..r.h {
yy := int(r.y+y);
for x in 0..r.w {
xx := int(r.x+x);
font.pixels[xx+yy*font.atlas_w] = bitmap[x+y*r.w];
}
}
}
// Do packing and writing to atlas
for cp in first...last {
if FT_Load_Char(face, auto_cast cp, FT_LOAD_RENDER) != 0 {
assert(false, "Invalid font cp!!??!?!");
}
r := &rects[cp-first];
fmt.printf("r.was_packed %v %c\n", r.was_packed, cp);
if r.was_packed == 0 {
packed_all = false;
continue;
}
r.x += 1;
r.y += 1;
r.w -= 1;
r.h -= 1;
pixels := mem.slice_ptr(face.glyph.bitmap.buffer, int(face.glyph.bitmap.width*face.glyph.bitmap.rows));
copy_to_atlas(font, r, pixels);
}
return;
}
import stbiw "shared:odin-stb/stb_image_write.odin"
main :: proc() {
FT_Init_FreeType(&the_library);
defer FT_Done_FreeType(the_library);
font := load_font("fonts/arial.ttf", 32, 512, 512);
if font == nil {
assert(false, "failed to init font!");
}
pack_range(font, 0xA0, 0xFF);
stbiw.write_png("test.png\x00", font.atlas_w, font.atlas_h, 1, font.pixels, 0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment