diff --git a/dlib/alloc.d b/dlib/alloc.d index 729f0ab..bb0f1ce 100644 --- a/dlib/alloc.d +++ b/dlib/alloc.d @@ -7,13 +7,9 @@ import dlib.util; import core.stdc.string; -version(WebAssembly) +static if(NativeTarget) { - import dlib.externdecl; -} -else -{ - import core.memory : malloc = pureMalloc, realloc = pureRealloc, free = pureFree; + public import core.memory : malloc = pureMalloc, realloc = pureRealloc, free = pureFree; } static Scratch g_scratch; @@ -62,14 +58,14 @@ version(WebAssembly) { pragma(LDC_intrinsic, "llvm.wasm.memory.grow.i32") -extern(C) size_t grow(size_t index, size_t size); +extern(C) usize grow(usize index, usize size) nothrow @nogc; pragma(LDC_intrinsic, "llvm.wasm.memory.size.i32") -extern(C) size_t size(size_t index); +extern(C) usize size(usize index) nothrow @nogc; extern(C) u32 -WasmGrow(u32 size) +WasmGrow(u32 size) nothrow @nogc { return grow(0, size); } @@ -81,7 +77,7 @@ WasmGrow2(u32 index, u32 size) } extern(C) u32 -WasmSize() +WasmSize() nothrow @nogc { return size(0); } @@ -373,7 +369,7 @@ AllocAlign(Arena* arena, usize size, usize alignment) usize mem_pos, current, offset; ArenaPool* node = arena.first; - while (true) + while(true) { if(node == null) { @@ -403,7 +399,7 @@ void Reset(Arena* arena) { ArenaPool* node = arena.first; - while (node != null) + while(node != null) { node.pos = 0; node = node.next; @@ -415,7 +411,7 @@ Free(Arena* arena) { ArenaPool* node = arena.first; ArenaPool* next; - while (node != null) + while(node != null) { next = node.next; Free(node); @@ -487,19 +483,457 @@ ScratchAlloc(T)(usize count, T set) return arr; } -version(DLIB_TEST) unittest +version(DLIB_TEST) { + void WriteToArray(T)(T[] arr, T factor) { - u64[5] arr = [1, 2, 3, 4, 5]; - - u64[] copy = Alloc!(u64)(arr); - - assert(arr == copy); + foreach(i; 0 .. arr.length) + { + arr[i] = i*factor; + } } + void AssertArrayValues(T)(T[] arr, T factor) { - Arena a = CreateArena(64); + foreach(i; 0 .. arr.length) + { + assert(arr[i] == i*factor); + } + } - u64[] arr = Alloc!(u64)(&a, 128); + void DLibTestAlloc() + { + { + u64[5] arr0 = [1, 2, 3, 4, 5]; + + u64[] copy = Alloc!(u64)(arr0); + + assert(arr0 == copy); + + u64[] arr1 = Alloc!(u64)(64); + + WriteToArray(arr1, 5); + + assert(arr0 == copy); + AssertArrayValues(arr1, 5); + } + + { + Arena a = CreateArena(64); + + u64[] arr0 = Alloc!(u64)(&a, 128); + u64[] arr1 = Alloc!(u64)(&a, 256); + + WriteToArray(arr0, 2); + WriteToArray(arr1, 3); + WriteToArray(arr0, 2); + + AssertArrayValues(arr0, 2); + AssertArrayValues(arr1, 3); + + Reset(&a); + + arr0 = Alloc!(u64)(&a, 256); + arr1 = Alloc!(u64)(&a, 128); + + WriteToArray(arr0, 4); + WriteToArray(arr1, 5); + WriteToArray(arr0, 4); + + AssertArrayValues(arr0, 4); + AssertArrayValues(arr1, 5); + } + } + + unittest + { + DLibTestAlloc(); } } + +version(WebAssembly) nothrow @nogc: + +struct MemBlock +{ + void* addr; + MemBlock* next; + usize size; +} + +struct Heap +{ + MemBlock* free; + MemBlock* used; + MemBlock* fresh; + usize top; +} + +extern extern(C) u8 __heap_base; +extern extern(C) u8 __data_end; + +__gshared Heap* heap; +__gshared void* heap_limit; +__gshared usize heap_split_thresh; +__gshared usize heap_alignment; +__gshared usize heap_max_blocks; +__gshared usize current_pages; + +enum bool MALLOC_COMPACT = true; +enum bool MALLOC_SPLIT = true; + +usize WasmPageSize(usize x) => x*1024*64; + +/** + * If compaction is enabled, inserts block + * into free list, sorted by addr. + * If disabled, add block has new head of + * the free list. + */ +void +InsertBlock(MemBlock *block) +{ + static if(MALLOC_COMPACT) + { + MemBlock *ptr = heap.free; + MemBlock *prev = null; + while(ptr != null) + { + if(cast(usize)block.addr <= cast(usize)ptr.addr) + { + break; + } + prev = ptr; + ptr = ptr.next; + } + + if(prev != null) + { + prev.next = block; + } + else + { + heap.free = block; + } + block.next = ptr; + } + else + { + block.next = heap.free; + heap.free = block; + } +} + +void +ReleaseBlocks(MemBlock *scan, MemBlock *to) +{ + MemBlock *scan_next; + while(scan != to) + { + scan_next = scan.next; + scan.next = heap.fresh; + heap.fresh = scan; + scan.addr = null; + scan.size = 0; + scan = scan_next; + } +} + +void +Compact() +{ + MemBlock *ptr = heap.free; + MemBlock *prev; + MemBlock *scan; + while(ptr != null) + { + prev = ptr; + scan = ptr.next; + + while(scan != null && cast(usize)prev.addr + prev.size == cast(usize)scan.addr) + { + prev = scan; + scan = scan.next; + } + + if(prev != ptr) + { + usize new_size = cast(usize)prev.addr - cast(usize)ptr.addr + prev.size; + ptr.size = new_size; + MemBlock *next = prev.next; + // make merged blocks available + ReleaseBlocks(ptr.next, prev.next); + // relink + ptr.next = next; + } + + ptr = ptr.next; + } +} + +void +MallocInit(const usize heap_blocks, const usize split_thresh, const usize alignment) +{ + current_pages = WasmSize(); + void* limit = cast(void *)WasmPageSize(current_pages); + + heap = cast(Heap *)&__heap_base; + heap_limit = limit; + heap_split_thresh = split_thresh; + heap_alignment = alignment; + heap_max_blocks = heap_blocks; + + heap.free = null; + heap.used = null; + heap.fresh = cast(MemBlock*)(heap + 1); + heap.top = cast(usize)(heap.fresh + heap_blocks); + + MemBlock *block = heap.fresh; + usize i = heap_max_blocks - 1; + while(i--) + { + block.next = block + 1; + block++; + } + + block.next = null; +} + +void +free(void *ptr) +{ + MemBlock *block = heap.used; + MemBlock *prev = null; + while(block != null) + { + if(ptr == block.addr) + { + if(prev) + { + prev.next = block.next; + } + else + { + heap.used = block.next; + } + InsertBlock(block); + + static if(MALLOC_COMPACT) + { + Compact(); + } + } + prev = block; + block = block.next; + } +} + +usize +PtrInfo(void *ptr) +{ + usize result = 0; + + MemBlock *block = heap.used; + while(block != null) + { + if(ptr == block.addr) + { + result = block.size; + break; + } + } + + return result; +} + +MemBlock* +AllocBlock(usize num) +{ + MemBlock *ptr = heap.free; + MemBlock *prev = null; + usize top = heap.top; + num = (num + heap_alignment - 1) & -heap_alignment; + while(ptr != null) + { + const i32 is_top = (cast(usize)ptr.addr + ptr.size >= top) && (cast(usize)ptr.addr + num <= cast(usize)heap_limit); + if(is_top || ptr.size >= num) + { + if(prev != null) + { + prev.next = ptr.next; + } + else + { + heap.free = ptr.next; + } + ptr.next = heap.used; + heap.used = ptr; + + static if(!MALLOC_SPLIT) + { + if(is_top) + { + ptr.size = num; + heap.top = cast(usize)ptr.addr + num; + static if(MALLOC_COMPACT) + { + Compact(); + } + } + } + else + { + if(is_top) + { + ptr.size = num; + heap.top = cast(usize)ptr.addr + num; + } + else if(heap.fresh != null) + { + usize excess = ptr.size - num; + if(excess >= heap_split_thresh) + { + ptr.size = num; + MemBlock *split = heap.fresh; + heap.fresh = split.next; + split.addr = cast(void *)(cast(usize)ptr.addr + num); + split.size = excess; + InsertBlock(split); + static if(MALLOC_COMPACT) + { + Compact(); + } + } + } + } + + return ptr; + } + + prev = ptr; + ptr = ptr.next; + } + + // no matching free blocks + // see if any other blocks available + usize new_top = top + num; + if(heap.fresh != null && new_top <= cast(usize)heap_limit) + { + ptr = heap.fresh; + heap.fresh = ptr.next; + ptr.addr = cast(void *)top; + ptr.next = heap.used; + ptr.size = num; + heap.used = ptr; + heap.top = new_top; + + return ptr; + } + + return null; +} + +usize +BytesToPages(usize count) +{ + usize pages = count/(1024*64); + return pages > 0 ? pages : 1; +} + +void* +malloc(usize num) +{ + MemBlock *block = AllocBlock(num); + + if(block == null) + { + WasmGrow(BytesToPages(num)); + usize new_pages = WasmSize(); + usize page_diff = new_pages - current_pages; + if(page_diff > 0) + { + current_pages = new_pages; + heap_limit = cast(void*)WasmPageSize(current_pages); + + block = AllocBlock(num); + } + } + + return block != null ? block.addr : null; +} + +void +MemClear(void *ptr, usize num) +{ + usize *ptrw = cast(usize*)ptr; + usize numw = (num & -usize.sizeof) / usize.sizeof; + while(numw--) + { + *ptrw++ = 0; + } + num &= (usize.sizeof - 1); + u8* ptrb = cast(u8*)ptrw; + while(num--) + { + *ptrb++ = 0; + } +} + +void* +calloc(usize num, usize size) +{ + num *= size; + MemBlock *block = AllocBlock(num); + + { + MemClear(block.addr, num); + return block.addr; + } + + return null; +} + +void* +realloc(void *ptr, usize size) +{ + void *new_ptr = malloc(size); + usize prev_size = PtrInfo(ptr); + + memcpy(new_ptr, ptr, prev_size); + + free(ptr); + + return new_ptr; +} + +usize +CountBlocks(MemBlock *ptr) +{ + usize num = 0; + while(ptr != null) + { + num++; + ptr = ptr.next; + } + return num; +} + +usize MallocFreeCount() +{ + return CountBlocks(heap.free); +} + +usize MallocUsedCount() +{ + return CountBlocks(heap.used); +} + +usize MallocMallocFreshCount() +{ + return CountBlocks(heap.fresh); +} + +bool MallocCheck() +{ + return heap_max_blocks == MallocFreeCount() + MallocUsedCount() + MallocMallocFreshCount(); +} + diff --git a/dlib/dlibincludes.c b/dlib/dlibincludes.c index 8e6429e..0bc96c1 100644 --- a/dlib/dlibincludes.c +++ b/dlib/dlibincludes.c @@ -27,7 +27,7 @@ #endif #ifndef NO_STBI -# include "../external/stb/stb_truetype.h" +// # include "../external/stb/stb_truetype.h" #endif #ifdef DLIB_INCLUDE_VULKAN diff --git a/dlib/externdecl.d b/dlib/externdecl.d index d804d3e..9d4433d 100644 --- a/dlib/externdecl.d +++ b/dlib/externdecl.d @@ -2,53 +2,12 @@ module dlib.externdecl; import dlib.aliases; -enum stbtt_curvetype : u8 -{ - none, - vmove, - vline, - vcurve, - vcubic, -} - -alias stbtt_vertex_type = short; - -struct stbtt__buf -{ - u8* data; - i32 cursor; - i32 size; -} - -struct stbtt_vertex -{ - stbtt_vertex_type x, y, cx, cy, cx1, cy1; - stbtt_curvetype type; - u8 padding; -} - -struct stbtt_fontinfo -{ - void* userdata; - u8* data; - i32 fontstart; - - i32 numGlyphs; - - i32 loca, head, glyf, hhea, hmtx, kern, gpos, svg; - i32 index_map; - i32 indexToLocFormat; - - stbtt__buf cff; - stbtt__buf charstrings; - stbtt__buf gsubrs; - stbtt__buf subrs; - stbtt__buf fontdicts; - stbtt__buf fdselect; -} +import stb_truetype; @nogc extern(C): +/* +static if(NativeTarget): // stb_truetype i32 @@ -78,24 +37,4 @@ stbtt_GetGlyphBox(const stbtt_fontinfo* info, i32 glyph_index, i32* x0, i32* y0, i32 stbtt_GetFontOffsetForIndex(const(u8*) data, i32 index); -// tinyalloc - -version(WebAssembly) -{ - void - ta_init(const usize heap_blocks, const usize split_thresh, const usize alignment); - - void* - malloc(usize size) nothrow @nogc; - - void* - calloc(usize size, usize count); - - void - free(void *ptr); - - void* - realloc(void *ptr, usize size); -} - - +*/ diff --git a/dlib/fonts.d b/dlib/fonts.d index 7a91625..170d8fc 100644 --- a/dlib/fonts.d +++ b/dlib/fonts.d @@ -1,18 +1,19 @@ module dlib.fonts; -static if(NativeTarget) -{ - import dlibincludes : FT_Library, FT_Face, FT_Error, FT_Bitmap, FT_GlyphSlot, FT_Int32, FT_ULong, FT_Long, - FT_Set_Pixel_Sizes, FT_Load_Char, FT_Done_Face, FT_New_Memory_Face, FT_Done_FreeType, FT_Init_FreeType, - FT_LOAD_RENDER, FT_PIXEL_MODE_GRAY, FT_PIXEL_MODE_MONO, FT_PIXEL_MODE_BGRA; -} - import dlib.externdecl; import dlib.aliases; import dlib.util; import dlib.alloc; import dlib.math; +import core.math : fabs; + +import std.traits; + +import stb_truetype; + +alias FontFace = stbtt_fontinfo; + const u32 FONT_MAX_BANDS = 8; const u32 FONT_TEX_WIDTH = 4096; @@ -141,68 +142,196 @@ struct SlugBuffer struct FontAtlas { Glyph[128] glyphs; - u8[] data; - u32 size; - UVec2 dimensions; - f32 ascent; - f32 descent; - f32 line_gap; - f32 line_height; - f32 max_advance; + f32 ascent = 0.0; // All unscaled + f32 descent = 0.0; + f32 line_gap = 0.0; + f32 line_height = 0.0; + f32 max_advance = 0.0; +} + +struct GlyphBounds +{ + f32 left = 0.0, bottom = 0.0, right = 0.0, top = 0.0; } struct Glyph { - dchar ch; - f32 advance; - f32 plane_left; - f32 plane_bottom; - f32 plane_right; - f32 plane_top; - f32 atlas_left; - f32 atlas_bottom; - f32 atlas_right; - f32 atlas_top; + dchar ch; + f32 advance; + GlyphBounds plane; + GlyphBounds atlas; } -static if(NativeTarget) +enum MSDFType { + None, + HardMask, + SoftMask, + SDF, + PSDF, + MSDF, + MTSDF, +} -__gshared FT_Library FT_LIB; -alias FontFace = FT_Face; - -void -CloseFreeType() +enum MSDFYOrigin { - if(FT_LIB) + None, + Left, + Bottom, + Right, + Top, +} + +struct MSDFInfo +{ + MSDFGlyph[] glyphs; + MSDFType type; + Vec2 aem_range; + u32 distance_field_range; + u32 distance_field_range_middle; + f32 pixels_per_em; + u32 texture_width; + u32 texture_height; + MSDFYOrigin y_origin; + f32 line_height; // EM + f32 ascender; // EM + f32 descender; // EM + f32 underline_y; // EM + f32 underline_thickness; // EM +} + +struct MSDFGlyph +{ + dchar glyph; + f32 advance = 0.0; + GlyphBounds plane; // EM + GlyphBounds atlas; // Pixels +} + +static MSDFInfo +GenerateMSDF(string file_path)() +{ + import std.json; + + string msdf_json = import(file_path); + + JSONValue jsonv = parseJSON(msdf_json); + + static T GetJSONValue(T)(JSONValue jsonv, string name) { - FT_Done_FreeType(FT_LIB); + T result = Select!(is(T == string), null, cast(T)0); + + if(const(JSONValue)* value = name in jsonv) + { + switch(value.type()) + { + static if(is(T == u32) || is(T == u64) || is(T == f32)) + { + case JSONType.float_: result = cast(T)value.floating; break; + case JSONType.uinteger: result = cast(T)value.uinteger; break; + case JSONType.integer: result = cast(T)value.integer; break; + } + static if(is(T == string)) + { + case JSONType.string: result = cast(T)value.str; break; + } + default: assert(false, "Unable to retrieve value"); + } + } + + return result; } + + MSDFInfo result = { + distance_field_range: GetJSONValue!(u32)(jsonv["atlas"], "distanceRange"), + distance_field_range_middle: GetJSONValue!(u32)(jsonv["atlas"], "distanceRangeMiddle"), + pixels_per_em: GetJSONValue!(f32)(jsonv["atlas"], "size"), + texture_width: GetJSONValue!(u32)(jsonv["atlas"], "width"), + texture_height: GetJSONValue!(u32)(jsonv["atlas"], "height"), + line_height: GetJSONValue!(f32)(jsonv["metrics"], "lineHeight"), + ascender: GetJSONValue!(f32)(jsonv["metrics"], "ascender"), + descender: GetJSONValue!(f32)(jsonv["metrics"], "descender"), + underline_y: GetJSONValue!(f32)(jsonv["metrics"], "underlineY"), + underline_thickness: GetJSONValue!(f32)(jsonv["metrics"], "underlineThickness"), + }; + + result.aem_range = Vec2( + (result.distance_field_range_middle - result.distance_field_range/2) / result.pixels_per_em, + (result.distance_field_range_middle + result.distance_field_range/2) / result.pixels_per_em + ); + + string type_string = GetJSONValue!(string)(jsonv["atlas"], "type"); + string y_origin_string = GetJSONValue!(string)(jsonv["atlas"], "yOrigin"); + + assert(type_string.length); + assert(y_origin_string.length); + + final switch(type_string) with(MSDFType) + { + case "hardmask": result.type = HardMask; break; + case "softmask": result.type = SoftMask; break; + case "sdf": result.type = SDF; break; + case "psdf": result.type = PSDF; break; + case "msdf": result.type = MSDF; break; + case "mtsdf": result.type = MTSDF; break; + } + + final switch(y_origin_string) with(MSDFYOrigin) + { + case "top": result.y_origin = Top; break; + case "left": result.y_origin = Left; break; + case "right": result.y_origin = Right; break; + case "bottom": result.y_origin = Bottom; break; + } + + MSDFGlyph[] glyphs = new MSDFGlyph[128]; + + JSONValue[] glyph_json = jsonv["glyphs"].array; + + foreach(i; 0 .. glyph_json.length) + { + u64 ch = cast(u64)glyph_json[i]["unicode"].integer; + MSDFGlyph glyph = { glyph: cast(dchar)ch }; + glyph.advance = GetJSONValue!(f32)(glyph_json[i], "advance"); + if("planeBounds" in glyph_json[i]) + { + glyph.plane.left = GetJSONValue!(f32)(glyph_json[i]["planeBounds"], "left"); + glyph.plane.right = GetJSONValue!(f32)(glyph_json[i]["planeBounds"], "right"); + glyph.plane.bottom = GetJSONValue!(f32)(glyph_json[i]["planeBounds"], "bottom"); + glyph.plane.top = GetJSONValue!(f32)(glyph_json[i]["planeBounds"], "top"); + + glyph.atlas.left = GetJSONValue!(f32)(glyph_json[i]["atlasBounds"], "left"); + glyph.atlas.right = GetJSONValue!(f32)(glyph_json[i]["atlasBounds"], "right"); + glyph.atlas.bottom = GetJSONValue!(f32)(glyph_json[i]["atlasBounds"], "bottom"); + glyph.atlas.top = GetJSONValue!(f32)(glyph_json[i]["atlasBounds"], "top"); + } + + glyphs[ch] = glyph; + } + + foreach(i; 0 .. glyphs.length) + { + if(glyphs[i].glyph == 0xFFFF) + { + glyphs[i].glyph = 0; + glyphs[i].plane = GlyphBounds(left: 0.0, right: 0.0, top: 0.0, bottom: 0.0); + glyphs[i].atlas = GlyphBounds(left: 0.0, right: 0.0, top: 0.0, bottom: 0.0); + } + } + + result.glyphs = glyphs; + + return result; } FontFace OpenFont(u8[] data) { FontFace font; - FT_New_Memory_Face(FT_LIB, data.ptr, cast(FT_Long)data.length, 0, &font); + stbtt_InitFont(&font, data.ptr, stbtt_GetFontOffsetForIndex(data.ptr, 0)); return font; } -void -CloseFont(FontFace font) -{ - if(font != null) - { - FT_Done_Face(font); - } -} - -i32 -Float26(f32 f) -{ - return cast(i32)round(cast(f32)(1 << 6) * f); -} - u8 DeMultiply(u8 color, u8 alpha) { @@ -217,180 +346,59 @@ TextSize(f32 s) } FontAtlas -CreateAtlas(Arena* arena, FontFace font, u32 size, UVec2 atlas_dim) +PopulateAtlasGlyphInfo(Arena* arena, FontFace* font, MSDFInfo* msdf_info) { - FontAtlas atlas = { - data: Alloc!(u8)(arena, atlas_dim.x * atlas_dim.y * 4), - size: size, - dimensions: atlas_dim, - }; + FontAtlas atlas; // TODO: proper packing algorithm if(font != null) { - FT_Set_Pixel_Sizes(font, 0, TextSize(size)); + i32 ascent, descent, line_gap; + stbtt_GetFontVMetrics(font, &ascent, &descent, &line_gap); - i64 bbox_ymax = font.bbox.yMax >> 6; - i64 bbox_ymin = font.bbox.yMin >> 6; - - u64 baseline = font.size.metrics.ascender >> 6; - u64 line_height = (font.size.metrics.height >> 6)+1; - - atlas.line_height = line_height; - atlas.max_advance = font.size.metrics.max_advance >> 6; - - u32 max_w, max_h, count; - - i32 font_size = Float26(size); - - const u32 PADDING = 2; - foreach(FT_ULong char_code; 0 .. 0x7F) + i32 max_advance; + foreach(i32 ch; 0 .. 128) { - FT_Error res = FT_Load_Char(font, char_code, cast(FT_Int32)FT_LOAD_RENDER); - if(res != 0) + i32 glyph_index = stbtt_FindGlyphIndex(font, ch); + if(glyph_index) { - continue; - } + i32 advance, x0, y0, x1, y1; + stbtt_GetGlyphHMetrics(font, glyph_index, &advance, null); + stbtt_GetGlyphBox(font, glyph_index, &x0, &y0, &x1, &y1); - u32 bmp_w = font.glyph.bitmap.width; - u32 bmp_h = font.glyph.bitmap.rows; - if(max_w + bmp_w > atlas_dim.x) - { - max_h += line_height+PADDING; - max_w = 0; - } + max_advance = max_advance < advance ? advance : max_advance; - if(max_h > atlas_dim.y) - { - Logf("Unable to pack atlas within dimensions provided"); - assert(false); + MSDFGlyph* msdfg; + foreach(j; 0 .. msdf_info.glyphs.length) + { + if(msdf_info.glyphs[j].glyph == ch) + { + msdfg = msdf_info.glyphs.ptr + j; + break; + } + } + + Glyph* g = atlas.glyphs.ptr + ch; + + g.atlas = msdfg.atlas; + + g.plane.left = cast(f32)x0; + g.plane.top = cast(f32)y0; + g.plane.right = cast(f32)x1; + g.plane.bottom = cast(f32)y1; } - - max_w += bmp_w; - count += 1; } - max_w = max_h = count = 0; - - foreach(FT_ULong char_code; 0 .. 0x7F) - { - FT_Error res = FT_Load_Char(font, char_code, cast(FT_Int32)FT_LOAD_RENDER); - if(res != 0) - { - continue; - } - - FT_GlyphSlot glyph = font.glyph; - FT_Bitmap* bmp = &font.glyph.bitmap; - - if(max_w + bmp.rows > atlas_dim.x) - { - max_h += line_height+PADDING; - max_w = PADDING; - } - - i64 base = Max(baseline, font.glyph.bitmap_top); - - switch(bmp.pixel_mode) - { - case FT_PIXEL_MODE_BGRA: - { - u64 x, y; - foreach(r; 0 .. bmp.rows) - { - y = (base - font.glyph.bitmap_top + r) + max_h; - foreach(c; 0 .. bmp.width) - { - x = max_w + c; - u64 offset = (y*atlas_dim.y + x) * 4; - - u8 p_b = bmp.buffer[offset+0]; - u8 p_g = bmp.buffer[offset+1]; - u8 p_r = bmp.buffer[offset+2]; - u8 p_a = bmp.buffer[offset+3]; - - atlas.data[offset .. offset+4] = [DeMultiply(p_r, p_a), DeMultiply(p_b, p_a), DeMultiply(p_g, p_a), p_a]; - } - } - } break; - case FT_PIXEL_MODE_MONO: - { - u32 pitch = bmp.pitch; - u8* buf = bmp.buffer; - u64 x, y; - foreach(r; 0 .. bmp.rows) - { - u8 bits = 0; - u8* buf_ptr = buf; - y = (base - font.glyph.bitmap_top + r) + max_h; - foreach(c; 0 .. bmp.width) - { - if((c & 7) == 0) - { - bits = *buf_ptr; - buf_ptr += 1; - } - - x = max_w + c; - u64 offset = (y*atlas_dim.y + x) * 4; - - atlas.data[offset .. offset+4] = [255, 255, 255, bits & 0x80 ? 255 : 0]; - bits <<= 1; - } - - buf += pitch; - } - } break; - case FT_PIXEL_MODE_GRAY: - { - u64 x, y; - foreach(r; 0 .. bmp.rows) - { - y = (base - font.glyph.bitmap_top + r) + max_h; - foreach(c; 0 .. bmp.width) - { - x = max_w + c; - u64 offset = (y*atlas_dim.y + x) * 4; - - atlas.data[offset .. offset+4] = [255, 255, 255, bmp.buffer[r*bmp.pitch + c]]; - } - } - } break; - default: - assert(false, "Unknown pixel_mode value"); - break; - } - - Glyph* g = atlas.glyphs.ptr + char_code; - - f32 height = glyph.metrics.height >> 6; - f32 width = glyph.metrics.width >> 6; - f32 top = font.glyph.bitmap_top; - f32 left = font.glyph.bitmap_left; - - g.ch = cast(dchar)char_code; - g.advance = cast(f32)(glyph.advance.x >> 6); - g.plane_left = left; - g.plane_right = g.plane_left + bmp.width; - g.plane_top = 0.0; - g.plane_bottom = line_height; - - g.atlas_top = max_h; - g.atlas_left = max_w; - g.atlas_bottom = max_h + line_height; - g.atlas_right = max_w + bmp.width; - - max_w += bmp.width + PADDING; - - count += 1; - } + atlas.ascent = cast(f32)ascent; + atlas.descent = cast(f32)descent; + atlas.line_height = cast(f32)(ascent - descent); // Maybe add line gap? + atlas.line_gap = cast(f32)line_gap; + atlas.max_advance = cast(f32)max_advance; } return atlas; } -} - void BuildFontGlyph(Arena* arena, u32 index, u32 glyph_index, SlugFontInfo* font_info, u32* curve_index, stbtt_fontinfo* stb_font_info) { @@ -424,7 +432,7 @@ BuildFontGlyph(Arena* arena, u32 index, u32 glyph_index, SlugFontInfo* font_info f32 dx = x-cx; f32 dy = y-cy; - if(Abs(dx) < 0.1 && Abs(dy) < 0.1) + if(fabs(dx) < 0.1 && fabs(dy) < 0.1) { cx = x; cy = y; diff --git a/dlib/math.d b/dlib/math.d index 0b7d854..bbed588 100644 --- a/dlib/math.d +++ b/dlib/math.d @@ -947,9 +947,7 @@ align(16) struct Matrix(T, int D) template IsMatrixInstantiation(U) { - static void IsMatrix(T, int D)(Matrix!(T, D) x) {} - - enum bool IsMatrixInstantiation = is(typeof(IsMatrix(U.init))); + enum bool IsMatrixInstantiation = isInstanceOf!(Matrix, U); } } @@ -1556,262 +1554,270 @@ Clamp(T)(T value, T min, T max) if(IsVector!(T)) return value; } -version(DLIB_TEST) unittest +version(DLIB_TEST) { - enum FLOAT_MAX = f32.max; - enum FLOAT_MIN = -f32.max; - - /* - - void PrintMatrix(Mat4 mat) + void DLibTestMath() { - foreach(i; 0 .. mat.N) + enum FLOAT_MAX = f32.max; + enum FLOAT_MIN = -f32.max; + + /* + + void PrintMatrix(Mat4 mat) { - if(i % 4 == 0) + foreach(i; 0 .. mat.N) { - printf("\n"); - } - printf("%.08f ", mat.v[i]); - } - printf("\n"); - } - - srand(cast(u32)time(null)); - - f32 RandomFloat() - { - return cast(f32)(rand())/cast(f32)(RAND_MAX + 1.0); - } - - { // Vec2 arithmetic - Vec2 v1 = Vec2(5.0, 10.0); - Vec2 v2 = Vec2(2.5, 5.0); - - Vec2 result = v1 * v2; - - assert(result == Vec2(12.5, 50.0), "Vec2 mul failure"); - - result = v1 + v2; - - assert(result == Vec2(7.5, 15.0), "Vec2 add failure"); - - result = v1 - v2; - - assert(result == Vec2(2.5, 5.0), "Vec2 sub failure"); - - result = v1 / v2; - - assert(result == Vec2(2.0), "Vec2 div failure"); - } - - { // Vec3 Arithmetic - Vec3 v1 = Vec3(5.0, 10.0, 15.0); - Vec3 v2 = Vec3(2.5, 5.0, 7.5); - - Vec3 result = v1 * v2; - - assert(result == Vec3(12.5, 50.0, 112.5), "Vec3 mul failure"); - - result = v1 + v2; - - assert(result == Vec3(7.5, 15.0, 22.5), "Vec3 add failure"); - - result = v1 - v2; - - assert(result == Vec3(2.5, 5.0, 7.5), "Vec3 sub failure"); - - result = v1 / v2; - - assert(result == Vec3(2.0), "Vec3 div failure"); - } - - { // Vec3 Arithmetic - Vec4 v1 = Vec4(5.0, 10.0, 15.0, 20.0); - Vec4 v2 = Vec4(2.5, 5.0, 7.5, 10.0); - - Vec4 result = v1 * v2; - - assert(result == Vec4(12.5, 50.0, 112.5, 200.0), "Vec4 mul failure"); - - result = v1 + v2; - - assert(result == Vec4(7.5, 15.0, 22.5, 30.0), "Vec4 add failure"); - - result = v1 - v2; - - assert(result == Vec4(2.5, 5.0, 7.5, 10.0), "Vec4 sub failure"); - - result = v1 / v2; - - assert(result == Vec4(2.0), "Vec4 div failure"); - } - - { // Mat4 Arithmetic - Mat4 m1 = RandomMat4(); - Mat4 m2 = RandomMat4(); - Mat4 m3 = m1 * m2; - Mat4 m4; - - MatZero(&m4); - - for(u32 i = 0; i < 4; i += 1) - { - for(u32 j = 0; j < 4; j += 1) - { - for(u32 k = 0; k < 4; k += 1) + if(i % 4 == 0) { - m4.rows[i][j] += m1.rows[k][j] * m2.rows[i][k]; + printf("\n"); + } + printf("%.08f ", mat.v[i]); + } + printf("\n"); + } + + srand(cast(u32)time(null)); + + f32 RandomFloat() + { + return cast(f32)(rand())/cast(f32)(RAND_MAX + 1.0); + } + + { // Vec2 arithmetic + Vec2 v1 = Vec2(5.0, 10.0); + Vec2 v2 = Vec2(2.5, 5.0); + + Vec2 result = v1 * v2; + + assert(result == Vec2(12.5, 50.0), "Vec2 mul failure"); + + result = v1 + v2; + + assert(result == Vec2(7.5, 15.0), "Vec2 add failure"); + + result = v1 - v2; + + assert(result == Vec2(2.5, 5.0), "Vec2 sub failure"); + + result = v1 / v2; + + assert(result == Vec2(2.0), "Vec2 div failure"); + } + + { // Vec3 Arithmetic + Vec3 v1 = Vec3(5.0, 10.0, 15.0); + Vec3 v2 = Vec3(2.5, 5.0, 7.5); + + Vec3 result = v1 * v2; + + assert(result == Vec3(12.5, 50.0, 112.5), "Vec3 mul failure"); + + result = v1 + v2; + + assert(result == Vec3(7.5, 15.0, 22.5), "Vec3 add failure"); + + result = v1 - v2; + + assert(result == Vec3(2.5, 5.0, 7.5), "Vec3 sub failure"); + + result = v1 / v2; + + assert(result == Vec3(2.0), "Vec3 div failure"); + } + + { // Vec3 Arithmetic + Vec4 v1 = Vec4(5.0, 10.0, 15.0, 20.0); + Vec4 v2 = Vec4(2.5, 5.0, 7.5, 10.0); + + Vec4 result = v1 * v2; + + assert(result == Vec4(12.5, 50.0, 112.5, 200.0), "Vec4 mul failure"); + + result = v1 + v2; + + assert(result == Vec4(7.5, 15.0, 22.5, 30.0), "Vec4 add failure"); + + result = v1 - v2; + + assert(result == Vec4(2.5, 5.0, 7.5, 10.0), "Vec4 sub failure"); + + result = v1 / v2; + + assert(result == Vec4(2.0), "Vec4 div failure"); + } + + { // Mat4 Arithmetic + Mat4 m1 = RandomMat4(); + Mat4 m2 = RandomMat4(); + Mat4 m3 = m1 * m2; + Mat4 m4; + + MatZero(&m4); + + for(u32 i = 0; i < 4; i += 1) + { + for(u32 j = 0; j < 4; j += 1) + { + for(u32 k = 0; k < 4; k += 1) + { + m4.rows[i][j] += m1.rows[k][j] * m2.rows[i][k]; + } } } + + assert(m3 == m4, "Mat4 mul failure"); } - assert(m3 == m4, "Mat4 mul failure"); - } + { // Translate + Mat4 mat = Mat4Identity(); + Vec4 vec = Vec4(1.0, 2.0, 3.0, 1.0); - { // Translate - Mat4 mat = Mat4Identity(); - Vec4 vec = Vec4(1.0, 2.0, 3.0, 1.0); + Translate(&mat, Vec3(13.0, 11.0, 7.0)); + Vec4 result = mat * vec; - Translate(&mat, Vec3(13.0, 11.0, 7.0)); - Vec4 result = mat * vec; + assert(result == Vec4(14.0, 13.0, 10.0, 1.0)); - assert(result == Vec4(14.0, 13.0, 10.0, 1.0)); + mat = Mat4Identity(); + Translate(&mat, Vec3(1.0, -1.0, -5.0)); + result = mat * result; - mat = Mat4Identity(); - Translate(&mat, Vec3(1.0, -1.0, -5.0)); - result = mat * result; + assert(result == Vec4(15.0, 12.0, 5.0, 1.0)); + } - assert(result == Vec4(15.0, 12.0, 5.0, 1.0)); - } + { // Identity + Mat4 identity = Mat4( + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0 + ); + Mat4 mat = Mat4Identity(); - { // Identity - Mat4 identity = Mat4( - 1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - 0.0, 0.0, 0.0, 1.0 - ); - Mat4 mat = Mat4Identity(); - - assert(identity == mat); - } + assert(identity == mat); + } - { // Inverse - foreach(i; 0 .. 1000) + { // Inverse + foreach(i; 0 .. 1000) + { + Mat4 m1 = RandomMat4(); + + Mat4 m1_inv = Inverse(m1); + Mat4 m1_reinv = Inverse(m1_inv); + + assert(m1 == m1_reinv, "Inverse test failed"); + } + } + + { // Cross + Vec3 v1 = Vec3(2.0, -3.0, 4.0); + Vec3 v2 = Vec3(12.0, -31.0, 43.0); + + Vec3 v3 = Cross(v1, v2); + + Vec3 v4 = Vec3( + v1.y * v2.z - v1.z * v2.y, + v1.z * v2.x - v1.x * v2.z, + v1.x * v2.y - v1.y * v2.x + ); + + assert(v3 == v4, "Vec3 Cross failure"); + + v3 = CrossN(v1, v2); + + Normalize(&v4); + + assert(v3 == v4, "Vec3 CrossN failure"); + } + */ + + { // Initializers + u32[4] arr = [1, 2, 3, 4]; + Vec4 vec = Vec4(arr); + assert(vec == Vec4(1.0, 2.0, 3.0, 4.0)); + + Mat2 mat = Mat2(1.0, 0.0, 0.0, 1.0); + + f32[16] floats = 22.0; + Mat4 mat4 = Mat4(floats); + assert(mat4.v == floats); + + Quat quat = Quat(1.0, 1.0, 1.0, 1.0); + + Quat quat2; + } + { - Mat4 m1 = RandomMat4(); + Vec3 vec = Vec3(1.0, 2.0, 3.0); + Mat4 mat = Mat4( + 1.0, 1.0, 1.0, 1.0, + 2.0, 2.0, 2.0, 2.0, + 3.0, 3.0, 3.0, 3.0, + 4.0, 4.0, 4.0, 4.0 + ); - Mat4 m1_inv = Inverse(m1); - Mat4 m1_reinv = Inverse(m1_inv); + Transform(&vec, &mat, 1.0); + + assert(vec == Vec3(18.0)); + + Vec4 low = Vec4(-5.0); + Vec4 high = Vec4(+5.0); + Vec4 test = Vec4(-500.0); + + test = Clamp(test, low, high); + + assert(test == low); + + test = Vec4(+500.0); + test = Clamp(test, low, high); + + assert(test == high); - assert(m1 == m1_reinv, "Inverse test failed"); } - } - { // Cross - Vec3 v1 = Vec3(2.0, -3.0, 4.0); - Vec3 v2 = Vec3(12.0, -31.0, 43.0); - - Vec3 v3 = Cross(v1, v2); - - Vec3 v4 = Vec3( - v1.y * v2.z - v1.z * v2.y, - v1.z * v2.x - v1.x * v2.z, - v1.x * v2.y - v1.y * v2.x - ); - - assert(v3 == v4, "Vec3 Cross failure"); - - v3 = CrossN(v1, v2); - - Normalize(&v4); - - assert(v3 == v4, "Vec3 CrossN failure"); - } - */ - - { // Initializers - u32[4] arr = [1, 2, 3, 4]; - Vec4 vec = Vec4(arr); - assert(vec == Vec4(1.0, 2.0, 3.0, 4.0)); - - Mat2 mat = Mat2(1.0, 0.0, 0.0, 1.0); - - f32[16] floats = 22.0; - Mat4 mat4 = Mat4(floats); - assert(mat4.v == floats); - - Quat quat = Quat(1.0, 1.0, 1.0, 1.0); - - Quat quat2; - } - - { - Vec3 vec = Vec3(1.0, 2.0, 3.0); - Mat4 mat = Mat4( - 1.0, 1.0, 1.0, 1.0, - 2.0, 2.0, 2.0, 2.0, - 3.0, 3.0, 3.0, 3.0, - 4.0, 4.0, 4.0, 4.0 - ); - - Transform(&vec, &mat, 1.0); - - assert(vec == Vec3(18.0)); - - Vec4 low = Vec4(-5.0); - Vec4 high = Vec4(+5.0); - Vec4 test = Vec4(-500.0); - - test = Clamp(test, low, high); - - assert(test == low); - - test = Vec4(+500.0); - test = Clamp(test, low, high); - - assert(test == high); - - } - - { - Vec4 m0 = Vec4(0.0); - Vec4 m1 = Vec4(10.0); - - assert(Mix(m0, m1, 0.0) == m0); - assert(Mix(m0, m1, 1.0) == m1); - assert(Mix(m0, m1, 0.5) == Vec4(5.0)); - assert(Mix(m0, m1, 0.75) == Vec4(7.5)); - - void TestModify(Vec4 v) { - v.r = 55; + Vec4 m0 = Vec4(0.0); + Vec4 m1 = Vec4(10.0); + + assert(Mix(m0, m1, 0.0) == m0); + assert(Mix(m0, m1, 1.0) == m1); + assert(Mix(m0, m1, 0.5) == Vec4(5.0)); + assert(Mix(m0, m1, 0.75) == Vec4(7.5)); + + void TestModify(Vec4 v) + { + v.r = 55; + } + } + + { + u32 max = Max(50, 33, 123.0); + assert(max == 123); + } + + { + Vec2 v0 = 50U; + + assert(Abs(v0.x-50.0) < 0.0009 && Abs(v0.y-50.0) < 0.0009); + + U8Vec2 v1 = U8Vec2(55U); + + assert(v1.x == 55 && v1.y == 55); + } + + { + assert(Ceil(10.5) == 11.0); + assert(Floor(100.33) == 100.0); + assert(Abs(-500) == 500); + assert(Abs(-500.0) == 500.0); + + assert(Clamp(55, 10, 40) == 40); + assert(Clamp(-20, -5, 55) == -5); } } + unittest { - u32 max = Max(50, 33, 123.0); - assert(max == 123); - } - - { - Vec2 v0 = 50U; - - assert(Abs(v0.x-50.0) < 0.0009 && Abs(v0.y-50.0) < 0.0009); - - U8Vec2 v1 = U8Vec2(55U); - - assert(v1.x == 55 && v1.y == 55); - } - - { - assert(Ceil(10.5) == 11.0); - assert(Floor(100.33) == 100.0); - assert(Abs(-500) == 500); - assert(Abs(-500.0) == 500.0); - - assert(Clamp(55, 10, 40) == 40); - assert(Clamp(-20, -5, 55) == -5); + DLibTestMath(); } } diff --git a/dlib/package.d b/dlib/package.d index 5f27603..441249c 100644 --- a/dlib/package.d +++ b/dlib/package.d @@ -16,15 +16,3 @@ else public import dlibincludes; } -shared static this() -{ - static if(NativeTarget) - { - FT_Init_FreeType(&FT_LIB); - } - else - { - ta_init(256, 16, (void*).sizeof*2); - } -} - diff --git a/dlib/util.d b/dlib/util.d index e82f35c..1648a5a 100644 --- a/dlib/util.d +++ b/dlib/util.d @@ -17,7 +17,8 @@ static if(NativeTarget) import dlib.platform; } -enum bool StringType(T) = (is(T: string) || is(T: u8[]) || is(T: char[]) || is(T: const(char)[])); +enum bool StringType(T) = (is(T == string) || is(T == u8[]) || is(T == char[]) || is(T == const(char)[])); +enum bool CStringType(T) = (is(T == char*) || is(T == u8*) || is(T == immutable(char)*) || is(T == const(char)*)); pragma(inline) void Int3() @@ -59,6 +60,12 @@ Str(T)(T arr) if(StringType!(T)) return (cast(immutable(char)*)arr.ptr)[0 .. arr.length]; } +string +Str(T)(T ptr) if(CStringType!(T)) +{ + return ptr ? Str(ptr[0 .. cast(usize)strlen(ptr)]) : null; +} + alias ConvToStr = Str; T[] @@ -1120,273 +1127,280 @@ ProcessHash(const(void)* data, ref u64 state_0, ref u64 state_1, ref u64 state_2 state_3 = ProcessSingleHashValue(state_3, block[3]); } -version(DLIB_TEST) unittest +version(DLIB_TEST) { - { // Singly Linked List - alias LT = ListBox!(u32, false); - LinkedList!(LT) list; - LT[5] nodes; - foreach(u32 i, n; nodes) - { - nodes[i].value = i; - SLLPush(&list, &nodes[i], null); - } - - u32 count = 0; - u32[3] res1 = [0, 2, 4]; - - SLLRemove(&list, &nodes[1], &nodes[0], null); - SLLRemove(&list, &nodes[3], &nodes[2], null); - - LT* n = list.first; - - assert(list.first != null && list.last != null); - assert(n != null); - assert(n.next != null); - - void TestSLList(LinkedList!(LT)* list, u32[] result) - { - LT* n = list.first; - foreach(i, v; result) - { - assert(n != null); - assert(v == n.value); - - if(i == result.length-1) - { - assert(n.next == null); - assert(n == list.last); - } - - n = n.next; - } - } - - TestSLList(&list, res1); - - count = 0; - u32[5] res2 = [3, 0, 2, 4, 1]; - - SLLPushFront(&list, &nodes[3], null); - SLLPush(&list, &nodes[1], null); - - TestSLList(&list, res2); - - count = 0; - - SLLRemove(&list, &nodes[3], null, null); - SLLRemove(&list, &nodes[1], &nodes[4], null); - - TestSLList(&list, res1); - } - - { // Doubly Linked List - alias LT = ListBox!(u32, true); - - void TestDLList(T)(T* list, u32[] result) - { - LT* n = list.first; - foreach(i, v; result) - { - assert(n != null); - assert(v == n.value); - - if(i > 0) - { - assert(n.prev != null); - } - - if(i == result.length-1) - { - assert(n.next == null); - assert(n == list.last); - } - - n = n.next; - } - - n = list.last; - foreach_reverse(i, v; result) - { - assert(n != null); - assert(v == n.value); - - if(i == result.length-1) - { - assert(n.next == null); - } - - if(i == 0) - { - assert(n.prev == null); - assert(n == list.first); - } - - n = n.prev; - } - } - - LinkedList!(LT) list; - LT[5] nodes; - foreach(u32 i, n; nodes) - { - nodes[i].value = i; - DLLPush(&list, &nodes[i], null); - } - - assert(list.first != null && list.last != null); - - u32[5] test1 = [0, 1, 2, 3, 4]; - TestDLList(&list, test1); - - u32 count = 0; - u32[3] res1 = [0, 2, 4]; - - DLLRemove(&list, &nodes[1], null); - DLLRemove(&list, &nodes[3], null); - - TestDLList(&list, res1); - - count = 0; - u32[5] res2 = [3, 0, 2, 4, 1]; - - DLLPushFront(&list, &nodes[3], null); - DLLPush(&list, &nodes[1], null); - - TestDLList(&list, res2); - - DLLRemove(&list, &nodes[3], null); - DLLRemove(&list, &nodes[1], null); - - TestDLList(&list, res1); - - DLLInsert(&list, &nodes[1], &nodes[0], null); - } - - { // MemCpy - - u8[777] bytes; - u8[777] test_bytes; - - bytes[0 .. 123] = 123; - bytes[123 .. 333] = 133; - bytes[333 .. 655] = 155; - bytes[655 .. $] = 199; - - test_bytes[0 .. $] = bytes[0 .. $]; - - assert(test_bytes == bytes); - - test_bytes[0 .. $] = 0; - - MemCpy(test_bytes.ptr, bytes.ptr, 777); - - assert(test_bytes == bytes); - - test_bytes[0 .. $] = 0; - - MemCpy(test_bytes.ptr+100, bytes.ptr, 32); - - u32 count = 0; - foreach(i, v; test_bytes[100 .. 132]) - { - if(v != bytes[count]) - { - assert(false); - } - - count += 1; - } - - assert(test_bytes[100 .. 132] == bytes[0 .. 32]); - - test_bytes[0 .. $] = 0; - - MemCpy(test_bytes.ptr, bytes.ptr, 33); - - assert(test_bytes[0 .. 33] == bytes[0 .. 33]); - - test_bytes[0 .. $] = 0; - - MemCpy(test_bytes.ptr, bytes.ptr, 65); - - assert(test_bytes[0 .. 65] == bytes[0 .. 65]); - - test_bytes[0 .. $] = 0; - - MemCpy(test_bytes.ptr, bytes.ptr, 96); - - foreach(i, v; test_bytes[0 .. 96]) - { - if(v != bytes[i]) - { - assert(false); - } - } - - assert(test_bytes[0 .. 96] == bytes[0 .. 96]); - } - - { // Hash Table - auto table = CreateHashTable!(u64, u64)(10); - - table[100] = 100; - } - - { // Hash - u8[10] arr_1 = 5; - u64[10] arr_2 = 555; - u64 val_1 = 555555; - u32 val_2 = 33333; - - u64 v1 = Hash(arr_1); - u64 v2 = Hash(arr_2); - u64 v3 = Hash(&val_1); - - assert(v1 > 0); - assert(v2 > 0); - assert(v3 > 0); - } - - { // Casts - u8[] arr = CastStr!(u8)("Test"); - char[2] char_arr = ['a', 'b']; - arr = CastArr!(u8)(char_arr); - } - + void DLibTestUtil() { - ResetScratch(MB(4)); - string str = Scratchf("%s testing %s", 55, "testing"); - assert(str == "55 testing testing"); - Logf(str); + { // Singly Linked List + alias LT = ListBox!(u32, false); + LinkedList!(LT) list; + LT[5] nodes; + foreach(u32 i, n; nodes) + { + nodes[i].value = i; + SLLPush(&list, &nodes[i], null); + } + + u32 count = 0; + u32[3] res1 = [0, 2, 4]; + + SLLRemove(&list, &nodes[1], &nodes[0], null); + SLLRemove(&list, &nodes[3], &nodes[2], null); + + LT* n = list.first; + + assert(list.first != null && list.last != null); + assert(n != null); + assert(n.next != null); + + void TestSLList(LinkedList!(LT)* list, u32[] result) + { + LT* n = list.first; + foreach(i, v; result) + { + assert(n != null); + assert(v == n.value); + + if(i == result.length-1) + { + assert(n.next == null); + assert(n == list.last); + } + + n = n.next; + } + } + + TestSLList(&list, res1); + + count = 0; + u32[5] res2 = [3, 0, 2, 4, 1]; + + SLLPushFront(&list, &nodes[3], null); + SLLPush(&list, &nodes[1], null); + + TestSLList(&list, res2); + + count = 0; + + SLLRemove(&list, &nodes[3], null, null); + SLLRemove(&list, &nodes[1], &nodes[4], null); + + TestSLList(&list, res1); + } + + { // Doubly Linked List + alias LT = ListBox!(u32, true); + + void TestDLList(T)(T* list, u32[] result) + { + LT* n = list.first; + foreach(i, v; result) + { + assert(n != null); + assert(v == n.value); + + if(i > 0) + { + assert(n.prev != null); + } + + if(i == result.length-1) + { + assert(n.next == null); + assert(n == list.last); + } + + n = n.next; + } + + n = list.last; + foreach_reverse(i, v; result) + { + assert(n != null); + assert(v == n.value); + + if(i == result.length-1) + { + assert(n.next == null); + } + + if(i == 0) + { + assert(n.prev == null); + assert(n == list.first); + } + + n = n.prev; + } + } + + LinkedList!(LT) list; + LT[5] nodes; + foreach(u32 i, n; nodes) + { + nodes[i].value = i; + DLLPush(&list, &nodes[i], null); + } + + assert(list.first != null && list.last != null); + + u32[5] test1 = [0, 1, 2, 3, 4]; + TestDLList(&list, test1); + + u32 count = 0; + u32[3] res1 = [0, 2, 4]; + + DLLRemove(&list, &nodes[1], null); + DLLRemove(&list, &nodes[3], null); + + TestDLList(&list, res1); + + count = 0; + u32[5] res2 = [3, 0, 2, 4, 1]; + + DLLPushFront(&list, &nodes[3], null); + DLLPush(&list, &nodes[1], null); + + TestDLList(&list, res2); + + DLLRemove(&list, &nodes[3], null); + DLLRemove(&list, &nodes[1], null); + + TestDLList(&list, res1); + + DLLInsert(&list, &nodes[1], &nodes[0], null); + } + + { // MemCpy + + u8[777] bytes; + u8[777] test_bytes; + + bytes[0 .. 123] = 123; + bytes[123 .. 333] = 133; + bytes[333 .. 655] = 155; + bytes[655 .. $] = 199; + + test_bytes[0 .. $] = bytes[0 .. $]; + + assert(test_bytes == bytes); + + test_bytes[0 .. $] = 0; + + MemCpy(test_bytes.ptr, bytes.ptr, 777); + + assert(test_bytes == bytes); + + test_bytes[0 .. $] = 0; + + MemCpy(test_bytes.ptr+100, bytes.ptr, 32); + + u32 count = 0; + foreach(i, v; test_bytes[100 .. 132]) + { + if(v != bytes[count]) + { + assert(false); + } + + count += 1; + } + + assert(test_bytes[100 .. 132] == bytes[0 .. 32]); + + test_bytes[0 .. $] = 0; + + MemCpy(test_bytes.ptr, bytes.ptr, 33); + + assert(test_bytes[0 .. 33] == bytes[0 .. 33]); + + test_bytes[0 .. $] = 0; + + MemCpy(test_bytes.ptr, bytes.ptr, 65); + + assert(test_bytes[0 .. 65] == bytes[0 .. 65]); + + test_bytes[0 .. $] = 0; + + MemCpy(test_bytes.ptr, bytes.ptr, 96); + + foreach(i, v; test_bytes[0 .. 96]) + { + if(v != bytes[i]) + { + assert(false); + } + } + + assert(test_bytes[0 .. 96] == bytes[0 .. 96]); + } + + { // Hash Table + auto table = CreateHashTable!(u64, u64)(10); + + table[100] = 100; + } + + { // Hash + u8[10] arr_1 = 5; + u64[10] arr_2 = 555; + u64 val_1 = 555555; + u32 val_2 = 33333; + + u64 v1 = Hash(arr_1); + u64 v2 = Hash(arr_2); + u64 v3 = Hash(&val_1); + + assert(v1 > 0); + assert(v2 > 0); + assert(v3 > 0); + } + + { // Casts + u8[] arr = CastStr!(u8)("Test"); + char[2] char_arr = ['a', 'b']; + arr = CastArr!(u8)(char_arr); + } + + { + ResetScratch(MB(4)); + string str = Scratchf("%s testing %s", 55, "testing"); + assert(str == "55 testing testing"); + } + + { + u64[555] slice = 53321; + + MemSet(slice.ptr, 0, u64.sizeof*slice.length); + + for(usize i = 0; i < slice.length; i += 1) + { + assert(slice[i] == 0); + } + + MemSet(slice.ptr, 5, u64.sizeof*slice.length); + + u64 cmp = cast(u64)( + cast(u64)(5) << 56 | + cast(u64)(5) << 48 | + cast(u64)(5) << 40 | + cast(u64)(5) << 32 | + cast(u64)(5) << 24 | + cast(u64)(5) << 16 | + cast(u64)(5) << 8 | + cast(u64)(5) << 0 + ); + + for(usize i = 0; i < slice.length; i += 1) + { + assert(slice[i] == cmp); + } + } } + unittest { - u64[555] slice = 53321; - - MemSet(slice.ptr, 0, u64.sizeof*slice.length); - - for(usize i = 0; i < slice.length; i += 1) - { - assert(slice[i] == 0); - } - - MemSet(slice.ptr, 5, u64.sizeof*slice.length); - - u64 cmp = cast(u64)( - cast(u64)(5) << 56 | - cast(u64)(5) << 48 | - cast(u64)(5) << 40 | - cast(u64)(5) << 32 | - cast(u64)(5) << 24 | - cast(u64)(5) << 16 | - cast(u64)(5) << 8 | - cast(u64)(5) << 0 - ); - - for(usize i = 0; i < slice.length; i += 1) - { - assert(slice[i] == cmp); - } + DLibTestUtil(); } } diff --git a/stb_truetype.d b/stb_truetype.d new file mode 100644 index 0000000..c72ae51 --- /dev/null +++ b/stb_truetype.d @@ -0,0 +1,4414 @@ +module stb_truetype; + +import std.math.rounding : floor, ceil; +import core.stdc.math : pow, cos, acos; +import core.math : sqrt, fabs; +import dlib.alloc : malloc, free; +import core.stdc.string : strlen, memset, memcpy; + +// TODO: fix security issues +// ======================================================================= +// +// NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES +// +// This library does no range checking of the offsets found in the file, +// meaning an attacker can use it to read arbitrary memory. +// +// ======================================================================= + +int ifloor(T)(T x) if(__traits(isFloating, T)) => cast(int)floor(x); +int iceil(T)(T x) if(__traits(isFloating, T)) => cast(int)ceil(x); + +struct stbtt__buf +{ + ubyte* data; + int cursor; + int size; +} + +struct stbtt_bakedchar +{ + ushort x0, y0, x1, y1; + float xoff = 0.0, yoff = 0.0, xadvance = 0.0; +} + +struct stbtt_aligned_quad +{ + float x0 = 0.0, y0 = 0.0, s0 = 0.0, t0 = 0.0; + float x1 = 0.0, y1 = 0.0, s1 = 0.0, t1 = 0.0; +} + +struct stbtt_packedchar +{ + ushort x0, y0, x1, y1; + float xoff = 0.0, yoff = 0.0, xadvance = 0.0; + float xoff2 = 0.0, yoff2 = 0.0; +} + +struct stbtt_pack_range +{ + float font_size = 0.0; + int first_unicode_codepoint_in_range; + int* array_of_unicode_codepoints; + int num_chars; + stbtt_packedchar* chardata_for_range; + ubyte h_oversample, v_oversample; +} + +T STBTT_POINT_SIZE(T)(T x) if(__traits(isNumeric, T)) => -x; + +struct stbtt_pack_context +{ + void* user_allocator_context; + void* pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + int skip_missing; + uint h_oversample, v_oversample; + ubyte* pixels; + void* nodes; +} + +struct stbtt_fontinfo +{ + ubyte* data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern,gpos,svg; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +} + +struct stbtt_kerningentry +{ + int glyph1; // use stbtt_FindGlyphIndex + int glyph2; + int advance; +} + +enum stbtt_curvetype : ubyte +{ + none, + vmove, + vline, + vcurve, + vcubic, +} + +struct stbtt_vertex +{ + short x, y, cx, cy, cx1, cy1; + ubyte type, padding; +} + +private struct stbtt__bitmap +{ + int w, h, stride; + ubyte* pixels; +}; + +enum STBTT_MACSTYLE_DONTCARE = 0; +enum STBTT_MACSTYLE_BOLD = 1; +enum STBTT_MACSTYLE_ITALIC = 2; +enum STBTT_MACSTYLE_UNDERSCORE = 4; +enum STBTT_MACSTYLE_NONE = 8; // <= not same as 0, this makes us check the bitfield is 0 + +enum STBTT_PLATFORM +{ + ID_UNICODE = 0, + ID_MAC = 1, + ID_ISO = 2, + ID_MICROSOFT = 3, +} + +enum STBTT_UNICODE_EID +{ + UNICODE_1_0 = 0, + UNICODE_1_1 = 1, + ISO_10646 = 2, + UNICODE_2_0_BMP = 3, + UNICODE_2_0_FULL = 4, +} + +enum STBTT_MS_EID +{ + SYMBOL = 0, + UNICODE_BMP = 1, + SHIFTJIS = 2, + UNICODE_FULL = 10, +} + +enum STBTT_MAC_EID +{ + ROMAN = 0, + JAPANESE = 1, + CHINESE_TRAD = 2, + KOREAN = 3, + ARABIC = 4, + HEBREW = 5, + GREEK = 6, + RUSSIAN = 7, +} + +enum STBTT_MS_LANG +{ + ENGLISH = 0x0409, + CHINESE = 0x0804, + DUTCH = 0x0413, + FRENCH = 0x040C, + GERMAN = 0x0407, + HEBREW = 0x040D, + ITALIAN = 0x0410, + JAPANESE = 0x0411, + KOREAN = 0x0412, + RUSSIAN = 0x0419, + SPANISH = 0x0409, + SWEDISH = 0x041D, +} + +enum STBTT_MAC_LANG +{ + ENGLISH = 0, + ARABIC = 12, + DUTCH = 4, + FRENCH = 1, + GERMAN = 2, + HEBREW = 10, + ITALIAN = 3, + JAPANESE = 11, + KOREAN = 23, + RUSSIAN = 32, + SPANISH = 6, + SWEDISH = 5, + CHINESE_SIMPLIFIED = 33, + CHINESE_TRAD = 19, +} + +enum STBTT_MAX_OVERSAMPLE = 8; + +static assert(STBTT_MAX_OVERSAMPLE <= 255); +static assert((STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0); + +enum STBTT_RASTERIZER_VERSION = 2; + +// returns number of contours +static stbtt__point * +stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours) +{ + stbtt__point *points = null; + int num_points = 0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == stbtt_curvetype.vmove) + ++n; + + *num_contours = n; + if (n == 0) return null; + + *contour_lengths = cast(int *)malloc(int.sizeof * n); + + if (*contour_lengths == null) { + *num_contours = 0; + return null; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = cast(stbtt__point *)malloc(num_points * stbtt__point.sizeof); + if (points == null) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case stbtt_curvetype.vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case stbtt_curvetype.vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case stbtt_curvetype.vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case stbtt_curvetype.vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + default: break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + free(points); + free(*contour_lengths); + *contour_lengths = null; + *num_contours = 0; + return null; +} + +void +stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count = 0; + int *winding_lengths = null; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert); + free(winding_lengths); + free(windings); + } +} + +void +stbtt_FreeBitmap(ubyte *bitmap) +{ + free(bitmap); +} + +ubyte * +stbtt_GetGlyphBitmapSubpixel(stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + free(vertices); + return null; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = null; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = cast(ubyte *)malloc(gbm.w * gbm.h); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1); + } + } + free(vertices); + return gbm.pixels; +} + +ubyte * +stbtt_GetGlyphBitmap(stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +void +stbtt_MakeGlyphBitmapSubpixel(stbtt_fontinfo *info, ubyte *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0, &iy0, null, null); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1); + + free(vertices); +} +void +stbtt_MakeGlyphBitmap(stbtt_fontinfo *info, ubyte *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +ubyte * +stbtt_GetCodepointBitmapSubpixel(stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +void +stbtt_MakeCodepointBitmapSubpixelPrefilter(stbtt_fontinfo *info, ubyte *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixelPrefilter( + info, + output, + out_w, + out_h, + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + oversample_x, + oversample_y, + sub_x, + sub_y, + stbtt_FindGlyphIndex(info,codepoint) + ); +} + +void +stbtt_MakeCodepointBitmapSubpixel(stbtt_fontinfo *info, ubyte *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +ubyte * +stbtt_GetCodepointBitmap(stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +void +stbtt_MakeCodepointBitmap(stbtt_fontinfo *info, ubyte *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +void +stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_bakedchar *b = chardata + char_index; + int round_x = ifloor((*xpos + b.xoff) + 0.5f); + int round_y = ifloor((*ypos + b.yoff) + 0.5f); + + q.x0 = round_x + d3d_bias; + q.y0 = round_y + d3d_bias; + q.x1 = round_x + b.x1 - b.x0 + d3d_bias; + q.y1 = round_y + b.y1 - b.y0 + d3d_bias; + + q.s0 = b.x0 * ipw; + q.t0 = b.y0 * iph; + q.s1 = b.x1 * ipw; + q.t1 = b.y1 * iph; + + *xpos += b.xadvance; +} + + +int +stbtt_PackBegin(stbtt_pack_context *spc, ubyte *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = cast(stbrp_context *)malloc(stbrp_context.sizeof); + int num_nodes = pw - padding; + stbrp_node *nodes = cast(stbrp_node *)malloc(stbrp_node.sizeof * num_nodes); + + if (context == null || nodes == null) { + if (context != null) free(context); + if (nodes != null) free(nodes ); + return 0; + } + + spc.user_allocator_context = alloc_context; + spc.width = pw; + spc.height = ph; + spc.pixels = pixels; + spc.pack_info = context; + spc.nodes = nodes; + spc.padding = padding; + spc.stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc.h_oversample = 1; + spc.v_oversample = 1; + spc.skip_missing = 0; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +void +stbtt_PackEnd(stbtt_pack_context *spc) +{ + free(spc.nodes ); + free(spc.pack_info); +} + +void +stbtt_PackSetOversampling(stbtt_pack_context *spc, uint h_oversample, uint v_oversample) +{ + assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc.h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc.v_oversample = v_oversample; +} + +void +stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) +{ + spc.skip_missing = skip; +} + +// rects array must be big enough to accommodate all characters in the given ranges +int +stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + int missing_glyph_added = 0; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = cast(ubyte) spc.h_oversample; + ranges[i].v_oversample = cast(ubyte) spc.v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == null ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + if (glyph == 0 && (spc.skip_missing || missing_glyph_added)) { + rects[k].w = rects[k].h = 0; + } else { + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc.h_oversample, + scale * spc.v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc.padding + spc.h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc.padding + spc.v_oversample-1); + if (glyph == 0) + missing_glyph_added = 1; + } + ++k; + } + } + + return k; +} + +void +stbtt_MakeGlyphBitmapSubpixelPrefilter(stbtt_fontinfo *info, ubyte *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given ranges +int +stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, missing_glyph = -1, return_value = 1; + + // save current values + int old_h_over = spc.h_oversample; + int old_v_over = spc.v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc.h_oversample = ranges[i].h_oversample; + spc.v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc.h_oversample; + recip_v = 1.0f / spc.v_oversample; + sub_x = stbtt__oversample_shift(spc.h_oversample); + sub_y = stbtt__oversample_shift(spc.v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r.was_packed && r.w != 0 && r.h != 0) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == null ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = cast(stbrp_coord) spc.padding; + + // pad on left and top + r.x += pad; + r.y += pad; + r.w -= pad; + r.h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc.h_oversample, + scale * spc.v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc.pixels + r.x + r.y*spc.stride_in_bytes, + r.w - spc.h_oversample+1, + r.h - spc.v_oversample+1, + spc.stride_in_bytes, + scale * spc.h_oversample, + scale * spc.v_oversample, + 0,0, + glyph); + + if (spc.h_oversample > 1) + stbtt__h_prefilter(spc.pixels + r.x + r.y*spc.stride_in_bytes, + r.w, r.h, spc.stride_in_bytes, + spc.h_oversample); + + if (spc.v_oversample > 1) + stbtt__v_prefilter(spc.pixels + r.x + r.y*spc.stride_in_bytes, + r.w, r.h, spc.stride_in_bytes, + spc.v_oversample); + + bc.x0 = cast(short) r.x; + bc.y0 = cast(short) r.y; + bc.x1 = cast(short) (r.x + r.w); + bc.y1 = cast(short) (r.y + r.h); + bc.xadvance = scale * advance; + bc.xoff = cast(float) x0 * recip_h + sub_x; + bc.yoff = cast(float) y0 * recip_v + sub_y; + bc.xoff2 = (x0 + r.w) * recip_h + sub_x; + bc.yoff2 = (y0 + r.h) * recip_v + sub_y; + + if (glyph == 0) + missing_glyph = j; + } else if (spc.skip_missing) { + return_value = 0; + } else if (r.was_packed && r.w == 0 && r.h == 0 && missing_glyph >= 0) { + ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph]; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc.h_oversample = old_h_over; + spc.v_oversample = old_v_over; + + return return_value; +} + +void +stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects(cast(stbrp_context *) spc.pack_info, rects, num_rects); +} + +int +stbtt_PackFontRanges(stbtt_pack_context *spc, const ubyte *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc.pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = cast(stbrp_rect *)malloc(stbrp_rect.sizeof * n); + if (rects == null) + return 0; + + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + free(rects); + return return_value; +} + +int +stbtt_PackFontRange(stbtt_pack_context *spc, const ubyte *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = null; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +void +stbtt_GetScaledFontVMetrics(const ubyte *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) +{ + int i_ascent, i_descent, i_lineGap; + float scale; + stbtt_fontinfo info; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); + scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size); + stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); + *ascent = cast(float) i_ascent * scale; + *descent = cast(float) i_descent * scale; + *lineGap = cast(float) i_lineGap * scale; +} + +void +stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = cast(float) ifloor((*xpos + b.xoff) + 0.5f); + float y = cast(float) ifloor((*ypos + b.yoff) + 0.5f); + q.x0 = x; + q.y0 = y; + q.x1 = x + b.xoff2 - b.xoff; + q.y1 = y + b.yoff2 - b.yoff; + } else { + q.x0 = *xpos + b.xoff; + q.y0 = *ypos + b.yoff; + q.x1 = *xpos + b.xoff2; + q.y1 = *ypos + b.yoff2; + } + + q.s0 = b.x0 * ipw; + q.t0 = b.y0 * iph; + q.s1 = b.x1 * ipw; + q.t1 = b.y1 * iph; + + *xpos += b.xadvance; +} + + +ubyte * +stbtt_GetGlyphSDF(stbtt_fontinfo *info, float scale, int glyph, int padding, ubyte onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + float scale_x = scale, scale_y = scale; + int ix0,iy0,ix1,iy1; + int w,h; + ubyte *data; + + if (scale == 0) return null; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + + // if empty, return null + if (ix0 == ix1 || iy0 == iy1) + return null; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width ) *width = w; + if (height) *height = h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + // invert for y-downwards bitmaps + scale_y = -scale_y; + + { + // distance from singular values (in the same units as the pixel grid) + const float eps = 1./1024, eps2 = eps*eps; + int x,y,i,j; + float *precompute; + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = cast(ubyte *) malloc(w * h); + precompute = cast(float *) malloc(num_verts * float.sizeof); + + for (i=0,j=num_verts-1; i < num_verts; j=i++) { + if (verts[i].type == stbtt_curvetype.vline) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; + float dist = cast(float) sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); + precompute[i] = (dist < eps) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == stbtt_curvetype.vcurve) { + float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; + float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; + float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float len2 = bx*bx + by*by; + if (len2 >= eps2) + precompute[i] = 1.0f / len2; + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float val = 0.0; + float min_dist = 999999.0f; + float sx = cast(float) x + 0.5f; + float sy = cast(float) y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path + + for (i=0; i < num_verts; ++i) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + + if (verts[i].type == stbtt_curvetype.vline && precompute[i] != 0.0f) { + float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + + float dist,dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = cast(float) sqrt(dist2); + + // coarse culling against bbox + //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && + // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) + dist = cast(float) fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; + assert(i != 0); + if (dist < min_dist) { + // check position along line + // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) + // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) + float dx = x1-x0, dy = y1-y0; + float px = x0-sx, py = y0-sy; + // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy + // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve + float t = -(px*dx + py*dy) / (dx*dx + dy*dy); + if (t >= 0.0f && t <= 1.0f) + min_dist = dist; + } + } else if (verts[i].type == stbtt_curvetype.vcurve) { + float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; + float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; + float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); + float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); + float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); + float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); + // coarse culling against bbox to avoid computing cubic unnecessarily + if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { + int num=0; + float ax = x1-x0, ay = y1-y0; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float[3] res = [0.0f, 0.0f, 0.0f]; + float px,py,t,it,dist2; + float a_inv = precompute[i]; + if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula + float a = 3*(ax*bx + ay*by); + float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); + float c = mx*ax+my*ay; + if (fabs(a) < eps2) { // if a is 0, it's linear + if (fabs(b) >= eps2) { + res[num++] = -c/b; + } + } else { + float discriminant = b*b - 4*a*c; + if (discriminant < 0) + num = 0; + else { + float root = cast(float) sqrt(discriminant); + res[0] = (-b - root)/(2*a); + res[1] = (-b + root)/(2*a); + num = 2; // don't bother distinguishing 1-solution case, as code below will still work + } + } + } else { + float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point + float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; + float d = (mx*ax+my*ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res.ptr); + } + dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = cast(float) sqrt(dist2); + + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = cast(float) sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = cast(float) sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = cast(float) sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; // if outside the shape, value is negative + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y-iy0)*w+(x-ix0)] = cast(ubyte) val; + } + } + free(precompute); + free(verts); + } + return data; +} + +ubyte * +stbtt_GetCodepointSDF(stbtt_fontinfo *info, float scale, int codepoint, int padding, ubyte onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); +} + +void +stbtt_FreeSDF(ubyte *bitmap) +{ + free(bitmap); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +const(char)* +stbtt_GetFontNameString(stbtt_fontinfo* font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + int i,count,stringOffset; + ubyte *fc = font.data; + uint offset = font.fontstart; + uint nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return null; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + uint loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return cast(const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return null; +} + +int +stbtt_BakeFontBitmap(const ubyte *data, int offset, + float pixel_height, ubyte *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal(cast(ubyte *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +int +stbtt_GetFontOffsetForIndex(const ubyte *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal(cast(ubyte *) data, index); +} + +int +stbtt_GetNumberOfFonts(const ubyte *data) +{ + return stbtt_GetNumberOfFonts_internal(cast(ubyte *) data); +} + +int +stbtt_InitFont(stbtt_fontinfo *info, const ubyte *data, int offset) +{ + return stbtt_InitFont_internal(info, cast(ubyte *) data, offset); +} + +int +stbtt_FindMatchingFont(const ubyte *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal(cast(ubyte *) fontdata, cast(char *) name, flags); +} + +int +stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal(cast(char *) s1, len1, cast(char *) s2, len2); +} + +int +stbtt_InitFont_internal(stbtt_fontinfo *info, ubyte *data, int fontstart) +{ + uint cmap, t; + int i,numTables; + + info.data = data; + info.fontstart = fontstart; + info.cff = stbtt__new_buf(null, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info.loca = stbtt__find_table(data, fontstart, "loca"); // required + info.head = stbtt__find_table(data, fontstart, "head"); // required + info.glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info.hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info.hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info.kern = stbtt__find_table(data, fontstart, "kern"); // not required + info.gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required + + if (!cmap || !info.head || !info.hhea || !info.hmtx) + return 0; + if (info.glyf) { + // required for truetype + if (!info.loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + uint cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + uint cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info.fontdicts = stbtt__new_buf(null, 0); + info.fdselect = stbtt__new_buf(null, 0); + + // @TODO this should use size from table (not 512MB) + info.cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info.cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info.gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info.subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info.fontdicts = stbtt__cff_get_index(&b); + info.fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info.charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info.numGlyphs = ttUSHORT(data+t+4); + else + info.numGlyphs = 0xffff; + + info.svg = -1; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info.index_map = 0; + for (i=0; i < numTables; ++i) + { + uint encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) + { + case STBTT_PLATFORM.ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID.UNICODE_BMP: + case STBTT_MS_EID.UNICODE_FULL: + // MS/Unicode + info.index_map = cmap + ttULONG(data+encoding_record+4); + break; + default: break; + } + break; + case STBTT_PLATFORM.ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info.index_map = cmap + ttULONG(data+encoding_record+4); + break; + default: break; + } + } + + if (info.index_map == 0) + return 0; + + info.indexToLocFormat = ttUSHORT(data+info.head + 50); + return 1; +} + +int +stbtt_FindGlyphIndex(stbtt_fontinfo *info, int unicode_codepoint) +{ + ubyte *data = info.data; + uint index_map = info.index_map; + + ushort format = ttUSHORT(data + index_map + 0); + if (format == 0) + { // apple byte encoding + int bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } + else if (format == 6) + { + uint first = ttUSHORT(data + index_map + 6); + uint count = ttUSHORT(data + index_map + 8); + if (cast(uint) unicode_codepoint >= first && cast(uint) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } + else if (format == 2) + { + assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } + else if (format == 4) + { // standard mapping for windows fonts: binary search collection of ranges + ushort segcount = ttUSHORT(data+index_map+6) >> 1; + ushort searchRange = ttUSHORT(data+index_map+8) >> 1; + ushort entrySelector = ttUSHORT(data+index_map+10); + ushort rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + uint endCount = index_map + 14; + uint search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + ushort end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + ushort offset, start, last; + ushort item = cast(ushort) ((search - endCount) >> 1); + + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + last = ttUSHORT(data + endCount + 2*item); + if (unicode_codepoint < start || unicode_codepoint > last) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return cast(ushort)(unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + uint ngroups = ttULONG(data+index_map+12); + int low,high; + low = 0; high = cast(int)ngroups; + // Binary search the right group. + while (low < high) { + int mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + uint start_char = ttULONG(data+index_map+16+mid*12); + uint end_char = ttULONG(data+index_map+16+mid*12+4); + if (cast(uint) unicode_codepoint < start_char) + high = mid; + else if (cast(uint) unicode_codepoint > end_char) + low = mid+1; + else { + uint start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + assert(0); + return 0; +} + +int +stbtt_GetCodepointShape(stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} +int +stbtt_GetGlyphBox(stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info.cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info.data + g + 2); + if (y0) *y0 = ttSHORT(info.data + g + 4); + if (x1) *x1 = ttSHORT(info.data + g + 6); + if (y1) *y1 = ttSHORT(info.data + g + 8); + } + return 1; +} + +int +stbtt_GetCodepointBox(stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +int +stbtt_IsGlyphEmpty(stbtt_fontinfo *info, int glyph_index) +{ + short numberOfContours; + int g; + if (info.cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, null, null, null, null) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info.data + g); + return numberOfContours == 0; +} + +int +stbtt_GetGlyphShape(stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info.cff.size) + return stbtt_GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt_GetGlyphShapeT2(info, glyph_index, pvertices); +} + +void +stbtt_GetGlyphHMetrics(stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + ushort numOfLongHorMetrics = ttUSHORT(info.data+info.hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info.data + info.hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info.data + info.hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info.data + info.hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info.data + info.hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +int +stbtt_initfont_internal(stbtt_fontinfo *info, ubyte *data, int fontstart) +{ + uint cmap, t; + int i,numtables; + + info.data = data; + info.fontstart = fontstart; + info.cff = stbtt__new_buf(null, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info.loca = stbtt__find_table(data, fontstart, "loca"); // required + info.head = stbtt__find_table(data, fontstart, "head"); // required + info.glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info.hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info.hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info.kern = stbtt__find_table(data, fontstart, "kern"); // not required + info.gpos = stbtt__find_table(data, fontstart, "gpos"); // not required + + if (!cmap || !info.head || !info.hhea || !info.hmtx) + return 0; + if (info.glyf) { + // required for truetype + if (!info.loca) return 0; + } else { + // initialization for cff / type2 fonts (otf) + stbtt__buf b, topdict, topdictidx; + uint cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + uint cff; + + cff = stbtt__find_table(data, fontstart, "cff "); + if (!cff) return 0; + + info.fontdicts = stbtt__new_buf(null, 0); + info.fdselect = stbtt__new_buf(null, 0); + + // @todo this should use size from table (not 512mb) + info.cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info.cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @todo the name index could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name index + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string index + info.gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info.subrs = stbtt__get_subrs(b, topdict); + + // we only support type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a cid font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info.fontdicts = stbtt__cff_get_index(&b); + info.fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info.charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info.numGlyphs = ttUSHORT(data+t+4); + else + info.numGlyphs = 0xffff; + + info.svg = -1; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numtables = ttUSHORT(data + cmap + 2); + info.index_map = 0; + for (i=0; i < numtables; ++i) + { + uint encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) + { + case STBTT_PLATFORM.ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID.UNICODE_BMP: + case STBTT_MS_EID.UNICODE_FULL: + // ms/unicode + info.index_map = cmap + ttULONG(data+encoding_record+4); + break; + default: break; + } + break; + case STBTT_PLATFORM.ID_UNICODE: + // mac/ios has these + // all the encodingids are unicode, so we don't bother to check it + info.index_map = cmap + ttULONG(data+encoding_record+4); + break; + default: break; + } + } + + if (info.index_map == 0) + return 0; + + info.indexToLocFormat = ttUSHORT(data+info.head + 50); + return 1; +} + +int +stbtt_findglyphindex(stbtt_fontinfo *info, int unicode_codepoint) +{ + ubyte *data = info.data; + uint index_map = info.index_map; + + ushort format = ttUSHORT(data + index_map + 0); + if (format == 0) + { // apple byte encoding + int bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } + else if (format == 6) + { + uint first = ttUSHORT(data + index_map + 6); + uint count = ttUSHORT(data + index_map + 8); + if (cast(uint) unicode_codepoint >= first && cast(uint) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } + else if (format == 2) + { + assert(0); // @todo: high-byte mapping for japanese/chinese/korean + return 0; + } + else if (format == 4) + { // standard mapping for windows fonts: binary search collection of ranges + ushort segcount = ttUSHORT(data+index_map+6) >> 1; + ushort searchrange = ttUSHORT(data+index_map+8) >> 1; + ushort entryselector = ttUSHORT(data+index_map+10); + ushort rangeshift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + uint endcount = index_map + 14; + uint search = endcount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endcount .. endcount + segcount + // but searchrange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeshift*2)) + search += rangeshift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entryselector) { + ushort end; + searchrange >>= 1; + end = ttUSHORT(data + search + searchrange*2); + if (unicode_codepoint > end) + search += searchrange*2; + --entryselector; + } + search += 2; + + { + ushort offset, start, last; + ushort item = cast(ushort) ((search - endcount) >> 1); + + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + last = ttUSHORT(data + endcount + 2*item); + if (unicode_codepoint < start || unicode_codepoint > last) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return cast(ushort)(unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + uint ngroups = ttULONG(data+index_map+12); + int low,high; + low = 0; high = cast(int)ngroups; + // binary search the right group. + while (low < high) { + int mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + uint start_char = ttULONG(data+index_map+16+mid*12); + uint end_char = ttULONG(data+index_map+16+mid*12+4); + if (cast(uint) unicode_codepoint < start_char) + high = mid; + else if (cast(uint) unicode_codepoint > end_char) + low = mid+1; + else { + uint start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @todo + assert(0); + return 0; +} + +int +stbtt_getcodepointshape(stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_getglyphshape(info, stbtt_findglyphindex(info, unicode_codepoint), vertices); +} +int +stbtt_getglyphbox(stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info.cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info.data + g + 2); + if (y0) *y0 = ttSHORT(info.data + g + 4); + if (x1) *x1 = ttSHORT(info.data + g + 6); + if (y1) *y1 = ttSHORT(info.data + g + 8); + } + return 1; +} + +int +stbtt_getcodepointbox(stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_getglyphbox(info, stbtt_findglyphindex(info,codepoint), x0,y0,x1,y1); +} + +int +stbtt_isglyphempty(stbtt_fontinfo *info, int glyph_index) +{ + short numberofcontours; + int g; + if (info.cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, null, null, null, null) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberofcontours = ttSHORT(info.data + g); + return numberofcontours == 0; +} + +int +stbtt_getglyphshape(stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info.cff.size) + return stbtt_GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt_GetGlyphShapeT2(info, glyph_index, pvertices); +} + +void +stbtt_getglyphhmetrics(stbtt_fontinfo *info, int glyph_index, int *advancewidth, int *leftsidebearing) +{ + ushort numoflonghormetrics = ttUSHORT(info.data+info.hhea + 34); + if (glyph_index < numoflonghormetrics) { + if (advancewidth) *advancewidth = ttSHORT(info.data + info.hmtx + 4*glyph_index); + if (leftsidebearing) *leftsidebearing = ttSHORT(info.data + info.hmtx + 4*glyph_index + 2); + } else { + if (advancewidth) *advancewidth = ttSHORT(info.data + info.hmtx + 4*(numoflonghormetrics-1)); + if (leftsidebearing) *leftsidebearing = ttSHORT(info.data + info.hmtx + 4*numoflonghormetrics + 2*(glyph_index - numoflonghormetrics)); + } +} + +int +stbtt_GetKerningTableLength(stbtt_fontinfo *info) +{ + ubyte *data = info.data + info.kern; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info.kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + return ttUSHORT(data+10); +} + +int +stbtt_GetKerningTable(stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length) +{ + ubyte *data = info.data + info.kern; + int k, length; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info.kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + length = ttUSHORT(data+10); + if (table_length < length) + length = table_length; + + for (k = 0; k < length; k++) + { + table[k].glyph1 = ttUSHORT(data+18+(k*6)); + table[k].glyph2 = ttUSHORT(data+20+(k*6)); + table[k].advance = ttSHORT(data+22+(k*6)); + } + + return length; +} + +int +stbtt_GetGlyphKernAdvance(stbtt_fontinfo *info, int g1, int g2) +{ + int xAdvance = 0; + + if (info.gpos) + xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); + else if (info.kern) + xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + + return xAdvance; +} + +int +stbtt_GetCodepointKernAdvance(stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info.kern && !info.gpos) // if no kerning table, don't waste time looking up both codepoint.glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +void +stbtt_GetCodepointHMetrics(stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +void +stbtt_GetFontVMetrics(stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info.data+info.hhea + 4); + if (descent) *descent = ttSHORT(info.data+info.hhea + 6); + if (lineGap) *lineGap = ttSHORT(info.data+info.hhea + 8); +} + +int +stbtt_GetFontVMetricsOS2(stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) +{ + int tab = stbtt__find_table(info.data, info.fontstart, "OS/2"); + if (!tab) + return 0; + if (typoAscent ) *typoAscent = ttSHORT(info.data+tab + 68); + if (typoDescent) *typoDescent = ttSHORT(info.data+tab + 70); + if (typoLineGap) *typoLineGap = ttSHORT(info.data+tab + 72); + return 1; +} + +void +stbtt_GetFontBoundingBox(stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info.data + info.head + 36); + *y0 = ttSHORT(info.data + info.head + 38); + *x1 = ttSHORT(info.data + info.head + 40); + *y1 = ttSHORT(info.data + info.head + 42); +} + +float +stbtt_ScaleForPixelHeight(stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info.data + info.hhea + 4) - ttSHORT(info.data + info.hhea + 6); + return cast(float) height / fheight; +} + +float +stbtt_ScaleForMappingEmToPixels(stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info.data + info.head + 18); + return pixels / unitsPerEm; +} + +void +stbtt_FreeShape(stbtt_fontinfo *info, stbtt_vertex *v) +{ + free(v); +} + +ubyte * +stbtt_FindSVGDoc(stbtt_fontinfo *info, int gl) +{ + int i; + ubyte *data = info.data; + ubyte *svg_doc_list = data + stbtt__get_svg(cast(stbtt_fontinfo *) info); + + int numEntries = ttUSHORT(svg_doc_list); + ubyte *svg_docs = svg_doc_list + 2; + + for(i=0; i= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2))) + return svg_doc; + } + return null; +} + +int +stbtt_GetGlyphSVG(stbtt_fontinfo *info, int gl, char** svg) +{ + ubyte *data = info.data; + ubyte *svg_doc; + + if (info.svg == 0) + return 0; + + svg_doc = stbtt_FindSVGDoc(info, gl); + if (svg_doc != null) { + *svg = cast(char*) data + info.svg + ttULONG(svg_doc + 4); + return ttULONG(svg_doc + 8); + } else { + return 0; + } +} + +int +stbtt_GetCodepointSVG(stbtt_fontinfo *info, int unicode_codepoint, char** svg) +{ + return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg); +} +void +stbtt_GetGlyphBitmapBoxSubpixel(stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = iceil (-y0 * scale_y + shift_y); + } +} + +void +stbtt_GetGlyphBitmapBox(stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +void +stbtt_GetCodepointBitmapBoxSubpixel(stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +void +stbtt_GetCodepointBitmapBox(stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + + +ubyte stbtt__buf_get8(stbtt__buf *b) +{ + if(b.cursor >= b.size) + return 0; + return b.data[b.cursor++]; +} + +ubyte stbtt__buf_peek8(stbtt__buf *b) +{ + if(b.cursor >= b.size) + return 0; + return b.data[b.cursor]; +} + +void stbtt__buf_seek(stbtt__buf *b, int o) +{ + assert(!(o > b.size || o < 0)); + b.cursor = (o > b.size || o < 0) ? b.size : o; +} + +void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b.cursor + o); +} + +uint stbtt__buf_get(stbtt__buf *b, int n) +{ + uint v = 0; + int i; + assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + assert(size < 0x40000000); + r.data = cast(ubyte*) p; + r.size = cast(int) size; + r.cursor = 0; + return r; +} + +uint stbtt__buf_get16(stbtt__buf* b) => stbtt__buf_get(b, 2); +uint stbtt__buf_get32(stbtt__buf* b) => stbtt__buf_get(b, 4); + +stbtt__buf stbtt__buf_range(stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(null, 0); + if (o < 0 || s < 0 || o > b.size || s > b.size - o) return r; + r.data = b.data + o; + r.size = s; + return r; +} + +stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b.cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b.cursor - start); +} + +uint stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + assert(0); + return 0; +} + +void stbtt__cff_skip_operand(stbtt__buf *b) +{ + int v, b0 = stbtt__buf_peek8(b); + assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b.cursor < b.size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b.cursor < b.size) { + int start = b.cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b.cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, uint *_out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + _out[i] = stbtt__cff_int(&operands); +} + +int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + assert(i >= 0 && i < count); + assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +ubyte ttBYTE(T)(T* p) => *(cast(ubyte*)p); +byte ttCHAR(T)(T* p) => *(cast(byte*) p); + +ushort ttUSHORT(const ubyte *p) => cast(ushort)(p[0]*256 + p[1]); +short ttSHORT (const ubyte *p) => cast(short)(p[0]*256 + p[1]); +uint ttULONG (const ubyte *p) => cast(uint)((p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]); +int ttLONG (const ubyte *p) => cast(int)((p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]); + +alias ttFixed = ttLONG; + +bool stbtt_tag4(ubyte* p, char c0, char c1, char c2, char c3) => ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)); +bool stbtt_tag(ubyte* p, string str) => stbtt_tag4(p,str[0],str[1],str[2],str[3]); + +int stbtt__isfont(ubyte *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +uint stbtt__find_table(ubyte *data, uint fontstart, string tag) +{ + int num_tables = ttUSHORT(data+fontstart+4); + uint tabledir = fontstart + 12; + int i; + for (i=0; i < num_tables; ++i) { + uint loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +int stbtt_GetFontOffsetForIndex_internal(ubyte* font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + int n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + + return -1; +} + +int stbtt_GetNumberOfFonts_internal(ubyte *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) + { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) + { + return ttLONG(font_collection+8); + } + } + return 0; +} + +stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + uint subrsoff = 0; + uint[2] private_loc = [ 0, 0 ]; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc.ptr); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(null, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(null, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +// since most people won't use this, find this table the first time it's needed +int stbtt__get_svg(stbtt_fontinfo *info) +{ + uint t; + if (info.svg < 0) + { + t = stbtt__find_table(info.data, info.fontstart, "SVG "); + if (t) + { + uint offset = ttULONG(info.data + t + 2); + info.svg = t + offset; + } + else + { + info.svg = 0; + } + } + return info.svg; +} + +static void stbtt_setvertex(stbtt_vertex *v, ubyte type, int x, int y, int cx, int cy) +{ + v.type = type; + v.x = cast(short) x; + v.y = cast(short) y; + v.cx = cast(short) cx; + v.cy = cast(short) cy; +} + +static int stbtt__GetGlyfOffset(stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + assert(!info.cff.size); + + if (glyph_index >= info.numGlyphs) return -1; // glyph index out of range + if (info.indexToLocFormat >= 2) return -1; // unknown index.glyph map format + + if (info.indexToLocFormat == 0) { + g1 = info.glyf + ttUSHORT(info.data + info.loca + glyph_index * 2) * 2; + g2 = info.glyf + ttUSHORT(info.data + info.loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info.glyf + ttULONG (info.data + info.loca + glyph_index * 4); + g2 = info.glyf + ttULONG (info.data + info.loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + int sx, int sy, int scx, int scy, int cx, int cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], stbtt_curvetype.vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], stbtt_curvetype.vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], stbtt_curvetype.vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], stbtt_curvetype.vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt_GetGlyphShapeTT(stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + short numberOfContours; + ubyte *endPtsOfContours; + ubyte *data = info.data; + stbtt_vertex* vertices; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = null; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + ubyte flags=0,flagcount; + int ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + int x,y,cx,cy,sx,sy, scx,scy; + ubyte *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = cast(stbtt_vertex *)malloc(m * stbtt_vertex.sizeof); + if (vertices == null) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + short dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + cast(short) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = cast(short) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + short dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + cast(short) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = cast(short) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = cast(short) vertices[off+i].x; + y = cast(short) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + cast(int) vertices[off+i+1].x) >> 1; + sy = (y + cast(int) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = cast(int) vertices[off+i+1].x; + sy = cast(int) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], stbtt_curvetype.vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], stbtt_curvetype.vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], stbtt_curvetype.vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], stbtt_curvetype.vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours < 0) { + // Compound shapes. + int more = 1; + ubyte *comp = data + g + 10; + num_vertices = 0; + vertices = null; + while (more) { + ushort flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex* comp_verts, tmp; + float[6] mtx = [1,0,0,1,0,0]; + float m = 0.0, n = 0.0; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = cast(float) sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = cast(float) sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + short x,y; + x=v.x; y=v.y; + v.x = cast(short)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v.y = cast(short)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v.cx; y=v.cy; + v.cx = cast(short)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v.cy = cast(short)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = cast(stbtt_vertex*)malloc((num_vertices+comp_num_verts)*stbtt_vertex.sizeof); + if (!tmp) { + if (vertices) free(vertices); + if (comp_verts) free(comp_verts); + return 0; + } + if (num_vertices > 0 && vertices) memcpy(tmp, vertices, num_vertices*stbtt_vertex.sizeof); + memcpy(tmp+num_vertices, comp_verts, comp_num_verts*stbtt_vertex.sizeof); + if (vertices) free(vertices); + vertices = tmp; + free(comp_verts); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +struct stbtt__csctx +{ + int bounds; + int started; + float first_x = 0.0, first_y = 0.0; + float x = 0.0, y = 0.0; + int min_x, max_x, min_y, max_y; + + stbtt_vertex* pvertices; + int num_vertices; +}; + +void stbtt__track_vertex(stbtt__csctx *c, int x, int y) +{ + if (x > c.max_x || !c.started) c.max_x = x; + if (y > c.max_y || !c.started) c.max_y = y; + if (x < c.min_x || !c.started) c.min_x = x; + if (y < c.min_y || !c.started) c.min_y = y; + c.started = 1; +} + +void stbtt__csctx_v(stbtt__csctx *c, ubyte type, int x, int y, int cx, int cy, int cx1, int cy1) +{ + if (c.bounds) { + stbtt__track_vertex(c, x, y); + if (type == stbtt_curvetype.vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c.pvertices[c.num_vertices], type, x, y, cx, cy); + c.pvertices[c.num_vertices].cx1 = cast(short) cx1; + c.pvertices[c.num_vertices].cy1 = cast(short) cy1; + } + c.num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx.first_x != ctx.x || ctx.first_y != ctx.y) + stbtt__csctx_v(ctx, stbtt_curvetype.vline, cast(int)ctx.first_x, cast(int)ctx.first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx.first_x = ctx.x = ctx.x + dx; + ctx.first_y = ctx.y = ctx.y + dy; + stbtt__csctx_v(ctx, stbtt_curvetype.vmove, cast(int)ctx.x, cast(int)ctx.y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx.x += dx; + ctx.y += dy; + stbtt__csctx_v(ctx, stbtt_curvetype.vline, cast(int)ctx.x, cast(int)ctx.y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx.x + dx1; + float cy1 = ctx.y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx.x = cx2 + dx3; + ctx.y = cy2 + dy3; + stbtt__csctx_v(ctx, stbtt_curvetype.vcubic, cast(int)ctx.x, cast(int)ctx.y, cast(int)cx1, cast(int)cy1, cast(int)cx2, cast(int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(null, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info.fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(null, 0); + return stbtt__get_subrs(info.cff, stbtt__cff_index_get(info.fontdicts, fdselector)); +} + +static int stbtt__run_charstring(stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float[48] s = 0.0; + stbtt__buf[10] subr_stack; + stbtt__buf subrs = info.subrs; + stbtt__buf b; + float f = 0.0; + +// #define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info.charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return 0; // STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return 0; // STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return 0; // STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return 0; // STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return 0; // STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return 0; // STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return 0; // STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return 0; // STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return 0; // STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return 0; // STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return 0; // STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return 0; // STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return 0; // STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return 0; // STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info.fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + goto case 0X1D; + case 0x1D: // callgsubr + if (sp < 1) return 0; // STBTT_CSERR("call(g|)subr stack"); + v = cast(int) s[--sp]; + if (subr_stack_height >= 10) return 0; // STBTT_CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info.gsubrs, v); + if (b.size == 0) return 0; // STBTT_CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return 0; // STBTT_CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1 = 0, dx2 = 0, dx3 = 0, dx4 = 0, dx5 = 0, dx6 = 0, dy1 = 0, dy2 = 0, dy3 = 0, dy4 = 0, dy5 = 0, dy6 = 0; + float dx = 0, dy = 0; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return 0; // STBTT_CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return 0; // STBTT_CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return 0; // STBTT_CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return 0; // STBTT_CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (fabs(dx) > fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return 0; // STBTT_CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && b0 < 32) + return 0; // STBTT_CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = cast(float)cast(int)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = cast(float)cast(short)stbtt__cff_int(&b); + } + if (sp >= 48) return 0; // STBTT_CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return 0; // STBTT_CSERR("no endchar"); + +// #undef STBTT__CSERR +} + +static int stbtt_GetGlyphShapeT2(stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = {bounds: 1, first_x: 0.0, first_y: 0.0, x: 0.0, y: 0.0}; + stbtt__csctx output_ctx = {bounds: 0, first_x: 0.0, first_y: 0.0, x: 0.0, y: 0.0}; + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = cast(stbtt_vertex*)malloc(count_ctx.num_vertices*stbtt_vertex.sizeof); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = null; + return 0; +} + +static int stbtt__GetGlyphInfoT2(stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = {bounds: 1, first_x: 0.0, first_y: 0.0, x: 0.0, y: 0.0}; + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) *x0 = r ? c.min_x : 0; + if (y0) *y0 = r ? c.min_y : 0; + if (x1) *x1 = r ? c.max_x : 0; + if (y1) *y1 = r ? c.max_y : 0; + return r ? c.num_vertices : 0; +} + +static int stbtt__GetGlyphKernInfoAdvance(stbtt_fontinfo *info, int glyph1, int glyph2) +{ + ubyte *data = info.data + info.kern; + uint needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info.kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +static int stbtt__GetCoverageIndex(ubyte *coverageTable, int glyph) +{ + ushort coverageFormat = ttUSHORT(coverageTable); + switch (coverageFormat) { + case 1: { + ushort glyphCount = ttUSHORT(coverageTable + 2); + + // Binary search. + int l=0, r=glyphCount-1, m; + int straw, needle=glyph; + while (l <= r) { + ubyte *glyphArray = coverageTable + 4; + ushort glyphID; + m = (l + r) >> 1; + glyphID = ttUSHORT(glyphArray + 2 * m); + straw = glyphID; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + return m; + } + } + break; + } + + case 2: { + ushort rangeCount = ttUSHORT(coverageTable + 2); + ubyte *rangeArray = coverageTable + 4; + + // Binary search. + int l=0, r=rangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + ubyte *rangeRecord; + m = (l + r) >> 1; + rangeRecord = rangeArray + 6 * m; + strawStart = ttUSHORT(rangeRecord); + strawEnd = ttUSHORT(rangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else { + ushort startCoverageIndex = ttUSHORT(rangeRecord + 4); + return startCoverageIndex + glyph - strawStart; + } + } + break; + } + + default: return -1; // unsupported + } + + return -1; +} + +static int stbtt__GetGlyphClass(ubyte *classDefTable, int glyph) +{ + ushort classDefFormat = ttUSHORT(classDefTable); + switch (classDefFormat) + { + case 1: { + ushort startGlyphID = ttUSHORT(classDefTable + 2); + ushort glyphCount = ttUSHORT(classDefTable + 4); + ubyte *classDef1ValueArray = classDefTable + 6; + + if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) + return cast(int)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); + break; + } + + case 2: { + ushort classRangeCount = ttUSHORT(classDefTable + 2); + ubyte *classRangeRecords = classDefTable + 4; + + // Binary search. + int l=0, r=classRangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + ubyte *classRangeRecord; + m = (l + r) >> 1; + classRangeRecord = classRangeRecords + 6 * m; + strawStart = ttUSHORT(classRangeRecord); + strawEnd = ttUSHORT(classRangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else + return cast(int)ttUSHORT(classRangeRecord + 4); + } + break; + } + + default: + return -1; // Unsupported definition type, return an error. + } + + // "All glyphs not assigned to a class fall into class 0". (OpenType spec) + return 0; +} + +static int stbtt__GetGlyphGPOSInfoAdvance(stbtt_fontinfo *info, int glyph1, int glyph2) +{ + ushort lookupListOffset; + ubyte *lookupList; + ushort lookupCount; + ubyte *data; + int i, sti; + + if (!info.gpos) return 0; + + data = info.data + info.gpos; + + if (ttUSHORT(data+0) != 1) return 0; // Major version 1 + if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 + + lookupListOffset = ttUSHORT(data+8); + lookupList = data + lookupListOffset; + lookupCount = ttUSHORT(lookupList); + + for (i=0; i= pairSetCount) return 0; + + needle=glyph2; + r=pairValueCount-1; + l=0; + + // Binary search. + while (l <= r) { + ushort secondGlyph; + ubyte *pairValue; + m = (l + r) >> 1; + pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; + secondGlyph = ttUSHORT(pairValue); + straw = secondGlyph; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + short xAdvance = ttSHORT(pairValue + 2); + return xAdvance; + } + } + } else + return 0; + break; + } + + case 2: { + ushort valueFormat1 = ttUSHORT(table + 4); + ushort valueFormat2 = ttUSHORT(table + 6); + if (valueFormat1 == 4 && valueFormat2 == 0) { // Support more formats? + ushort classDef1Offset = ttUSHORT(table + 8); + ushort classDef2Offset = ttUSHORT(table + 10); + int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); + int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); + + ushort class1Count = ttUSHORT(table + 12); + ushort class2Count = ttUSHORT(table + 14); + ubyte* class1Records, class2Records; + short xAdvance; + + if (glyph1class < 0 || glyph1class >= class1Count) return 0; // malformed + if (glyph2class < 0 || glyph2class >= class2Count) return 0; // malformed + + class1Records = table + 16; + class2Records = class1Records + 2 * (glyph1class * class2Count); + xAdvance = ttSHORT(class2Records + 2 * glyph2class); + return xAdvance; + } else + return 0; + break; + } + + default: + return 0; // Unsupported position format + } + } + } + + return 0; +} + + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +struct stbtt__hheap_chunk +{ + stbtt__hheap_chunk *next; +}; + +struct stbtt__hheap +{ + stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +}; + +void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size) +{ + if (hh.first_free) { + void *p = hh.first_free; + hh.first_free = * cast(void **) p; + return p; + } else { + if (hh.num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = cast(stbtt__hheap_chunk *)malloc(stbtt__hheap_chunk.sizeof + size * count); + if (c == null) + return null; + c.next = hh.head; + hh.head = c; + hh.num_remaining_in_head_chunk = count; + } + --hh.num_remaining_in_head_chunk; + return cast(char*) (hh.head) + stbtt__hheap_chunk.sizeof + size * hh.num_remaining_in_head_chunk; + } +} + +void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *cast(void **) p = hh.first_free; + hh.first_free = p; +} + +void stbtt__hheap_cleanup(stbtt__hheap *hh) +{ + stbtt__hheap_chunk *c = hh.head; + while (c) { + stbtt__hheap_chunk *n = c.next; + free(c); + c = n; + } +} + +struct stbtt__edge +{ + float x0 = 0.0, y0 = 0.0, x1 = 0.0, y1 = 0.0; + int invert; +} + +struct stbtt__active_edge +{ + stbtt__active_edge *next; + static if(STBTT_RASTERIZER_VERSION == 1) + { + int x,dx; + float ey = 0.0; + int direction; + } + else static if(STBTT_RASTERIZER_VERSION == 2) + { + float fx = 0.0, fdx = 0.0, fdy = 0.0; + float direction = 0.0; + float sy = 0.0; + float ey = 0.0; + } + else static assert(false, "Unrecognized value of STBTT_RASTERIZER_VERSION"); +} + +static if(STBTT_RASTERIZER_VERSION == 1) +{ + enum STBTT_FIXSHIFT = 10; + enum STBTT_FIX = (1 << STBTT_FIXSHIFT); + enum STBTT_FIXMASK = (STBTT_FIX-1); + + + static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point) + { + stbtt__active_edge *z = cast(stbtt__active_edge *) stbtt__hheap_alloc(hh, stbtt__active_edge.sizeof); + float dxdy = (e.x1 - e.x0) / (e.y1 - e.y0); + assert(z != null); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z.dx = -ifloor(STBTT_FIX * -dxdy); + else + z.dx = ifloor(STBTT_FIX * dxdy); + + z.x = ifloor(STBTT_FIX * e.x0 + z.dx * (start_point - e.y0)); // use z.dx so when we offset later it's by the same amount + z.x -= off_x * STBTT_FIX; + + z.ey = e.y1; + z.next = 0; + z.direction = e.invert ? 1 : -1; + return z; + } +} +else static if(STBTT_RASTERIZER_VERSION == 2) +{ + static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point) + { + stbtt__active_edge *z = cast(stbtt__active_edge *) stbtt__hheap_alloc(hh, stbtt__active_edge.sizeof); + float dxdy = (e.x1 - e.x0) / (e.y1 - e.y0); + assert(z != null); + //assert(e.y0 <= start_point); + if (!z) return z; + z.fdx = dxdy; + z.fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z.fx = e.x0 + dxdy * (start_point - e.y0); + z.fx -= off_x; + z.direction = e.invert ? 1.0f : -1.0f; + z.sy = e.y0; + z.ey = e.y1; + z.next = null; + return z; + } +} +else static assert(false, "Unrecognized value of STBTT_RASTERIZER_VERSION"); + + +static if(STBTT_RASTERIZER_VERSION == 1) +{ + static void stbtt__fill_active_edges(ubyte *scanline, int len, stbtt__active_edge *e, int max_weight) + { + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e.x; w += e.direction; + } else { + int x1 = e.x; w += e.direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + cast(ubyte) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + cast(ubyte) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + cast(ubyte) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + cast(ubyte) max_weight; + } + } + } + } + + e = e.next; + } + } + + static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y) + { + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = null; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + ubyte[512] scanline_data; + ubyte* scanline; + + if (result.w > 512) + scanline = cast(ubyte *)malloc(result.w); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result.h) * cast(float) vsubsample + 1; + + while (j < result.h) { + memset(scanline, 0, result.w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z.ey <= scan_y) { + *step = z.next; // delete from list + assert(z.direction); + z.direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z.x += z.dx; // advance to position for current scanline + step = &((*step).next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step).next) { + if ((*step).x > (*step).next.x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t.next; + + t.next = q.next; + q.next = t; + *step = q; + changed = 1; + } + step = &(*step).next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e.y0 <= scan_y) { + if (e.y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y); + if (z != null) { + // find insertion point + if (active == null) + active = z; + else if (z.x < active.x) { + // insert at front + z.next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p.next && p.next.x < z.x) + p = p.next; + // at this point, p.next.x is NOT < z.x + z.next = p.next; + p.next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result.w, active, max_weight); + + ++y; + } + memcpy(result.pixels + j * result.stride, scanline, result.w); + ++j; + } + + stbtt__hheap_cleanup(&hh); + + if (scanline != scanline_data) + free(scanline); + } +} +else static if(STBTT_RASTERIZER_VERSION == 2) +{ + static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) + { + if (y0 == y1) return; + assert(y0 < y1); + assert(e.sy <= e.ey); + if (y0 > e.ey) return; + if (y1 < e.sy) return; + if (y0 < e.sy) { + x0 += (x1-x0) * (e.sy - y0) / (y1-y0); + y0 = e.sy; + } + if (y1 > e.ey) { + x1 += (x1-x0) * (e.ey - y1) / (y1-y0); + y1 = e.ey; + } + + if (x0 == x) + assert(x1 <= x+1); + else if (x0 == x+1) + assert(x1 >= x); + else if (x0 <= x) + assert(x1 <= x); + else if (x0 >= x+1) + assert(x1 >= x+1); + else + assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e.direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + { + + } + else { + assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e.direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } + } + + static float stbtt__sized_trapezoid_area(float height, float top_width, float bottom_width) + { + assert(top_width >= 0); + assert(bottom_width >= 0); + return (top_width + bottom_width) / 2.0f * height; + } + + static float stbtt__position_trapezoid_area(float height, float tx0, float tx1, float bx0, float bx1) + { + return stbtt__sized_trapezoid_area(height, tx1 - tx0, bx1 - bx0); + } + + static float stbtt__sized_triangle_area(float height, float width) + { + return height * width / 2; + } + + static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) + { + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + assert(e.ey >= y_top); + + if (e.fdx == 0) { + float x0 = e.fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,cast(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,cast(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e.fx; + float dx = e.fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e.fdy; + assert(e.sy <= y_bottom && e.ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e.sy > y_top) { + x_top = x0 + dx * (e.sy - y_top); + sy0 = e.sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e.ey < y_bottom) { + x_bottom = x0 + dx * (e.ey - y_top); + sy1 = e.ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if (cast(int) x_top == cast(int) x_bottom) { + float height = 0.0; + // simple case, only spans one pixel + int x = cast(int) x_top; + height = (sy1 - sy0) * e.direction; + assert(x >= 0 && x < len); + scanline[x] += stbtt__position_trapezoid_area(height, x_top, x+1.0f, x_bottom, x+1.0f); + scanline_fill[x] += height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing = 0.0, y_final = 0.0, step = 0.0, sign = 0.0, area = 0.0; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t = 0.0; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + assert(dy >= 0); + assert(dx >= 0); + + x1 = cast(int) x_top; + x2 = cast(int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = y_top + dy * (x1+1 - x0); + + // compute intersection with y axis at x2 + y_final = y_top + dy * (x2 - x0); + + // x1 x_top x2 x_bottom + // y_top +------|-----+------------+------------+--------|---+------------+ + // | | | | | | + // | | | | | | + // sy0 | Txxxxx|............|............|............|............| + // y_crossing | *xxxxx.......|............|............|............| + // | | xxxxx..|............|............|............| + // | | /- xx*xxxx........|............|............| + // | | dy < | xxxxxx..|............|............| + // y_final | | \- | xx*xxx.........|............| + // sy1 | | | | xxxxxB...|............| + // | | | | | | + // | | | | | | + // y_bottom +------------+------------+------------+------------+------------+ + // + // goal is to measure the area covered by '.' in each pixel + + // if x2 is right at the right edge of x1, y_crossing can blow up, github #1057 + // @TODO: maybe test against sy1 rather than y_bottom? + if (y_crossing > y_bottom) + y_crossing = y_bottom; + + sign = e.direction; + + // area of the rectangle covered from sy0..y_crossing + area = sign * (y_crossing-sy0); + + // area of the triangle (x_top,sy0), (x1+1,sy0), (x1+1,y_crossing) + scanline[x1] += stbtt__sized_triangle_area(area, x1+1 - x_top); + + // check if final y_crossing is blown up; no test case for this + if (y_final > y_bottom) { + y_final = y_bottom; + dy = (y_final - y_crossing ) / (x2 - (x1+1)); // if denom=0, y_final = y_crossing, so y_final <= y_bottom + } + + // in second pixel, area covered by line segment found in first pixel + // is always a rectangle 1 wide * the height of that line segment; this + // is exactly what the variable 'area' stores. it also gets a contribution + // from the line segment within it. the THIRD pixel will get the first + // pixel's rectangle contribution, the second pixel's rectangle contribution, + // and its own contribution. the 'own contribution' is the same in every pixel except + // the leftmost and rightmost, a trapezoid that slides down in each pixel. + // the second pixel's contribution to the third pixel will be the + // rectangle 1 wide times the height change in the second pixel, which is dy. + + step = sign * dy * 1; // dy is dy/dx, change in y for every 1 change in x, + // which multiplied by 1-pixel-width is how much pixel area changes for each step in x + // so the area advances by 'step' every time + + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; // area of trapezoid is 1*step/2 + area += step; + } + assert(fabs(area) <= 1.01f); // accumulated error from area += step unless we round step down + assert(sy1 > y_final-0.01f); + + // area covered in the last pixel is the rectangle from all the pixels to the left, + // plus the trapezoid filled by the line segment in this pixel all the way to the right edge + scanline[x2] += area + sign * stbtt__position_trapezoid_area(sy1-y_final, cast(float) x2, x2+1.0f, x_bottom, x2+1.0f); + + // the rest of the line is filled based on the total height of the line segment in this pixel + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + // note though that this does happen some of the time because + // x_top and x_bottom can be extrapolated at the top & bottom of + // the shape and actually lie outside the bounding box + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = cast(float) (x); + float x2 = cast(float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + // x = e.x + e.dx * (y-y_top) + // (y-y_top) = (x - e.x) / e.dx + // y = (x - e.x) / e.dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e.next; + } + } + + // directly AA rasterize edges w/o supersampling + static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y) + { + stbtt__hheap hh; + stbtt__active_edge *active = null; + int y,j=0, i; + float[129] scanline_data = 0.0; + float* scanline, scanline2; + + // STBTT__NOTUSED(vsubsample); + + if (result.w > 64) + scanline = cast(float *)malloc((result.w*2+1) * float.sizeof); + else + scanline = scanline_data.ptr; + + scanline2 = scanline + result.w; + + y = off_y; + e[n].y0 = cast(float) (off_y + result.h) + 1; + + while (j < result.h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + memset(scanline , 0, result.w*float.sizeof); + memset(scanline2, 0, (result.w+1)*float.sizeof); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z.ey <= scan_y_top) { + *step = z.next; // delete from list + assert(z.direction); + z.direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step).next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e.y0 <= scan_y_bottom) { + if (e.y0 != e.y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top); + if (z != null) { + if (j == 0 && off_y != 0) { + if (z.ey < scan_y_top) { + // this can happen due to subpixel positioning and some kind of fp rounding error i think + z.ey = scan_y_top; + } + } + assert(z.ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds + // insert at front + z.next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result.w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result.w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = cast(float) fabs(k)*255 + 0.5f; + m = cast(int) k; + if (m > 255) m = 255; + result.pixels[j*result.stride + i] = cast(ubyte) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z.fx += z.fdx; // advance to position for current scanline + step = &((*step).next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh); + + if (scanline != scanline_data.ptr) + free(scanline); + } +} +else static assert(false, "Unrecognized value of STBTT_RASTERIZER_VERSION"); + +bool STBTT__COMPARE(stbtt__edge* a, stbtt__edge* b) => a.y0 < b.y0; + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) + { + stbtt__edge t = p[i]; + stbtt__edge* a = &t; + j = i; + while (j > 0) + { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t = {x0: 0.0, y0: 0.0, x1: 0.0, y1: 0.0}; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0], &p[m]); + c12 = STBTT__COMPARE(&p[m], &p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && midn => n; 0 0 */ + /* 0n: 0>n => 0; 0 n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +struct stbtt__point +{ + float x = 0.0, y = 0.0; +}; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; + static if(STBTT_RASTERIZER_VERSION == 1) + { + int vsubsample = result.h < 8 ? 15 : 5; + } + else static if(STBTT_RASTERIZER_VERSION == 2) + { + int vsubsample = 1; + } + else static assert(false, "Unrecognized value of STBTT_RASTERIZER_VERSION"); + + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = cast(stbtt__edge *)malloc(stbtt__edge.sizeof * (n+1)); // add an extra one as a sentinel + if (e == null) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y); + + free(e); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = cast(float) (sqrt(dx0*dx0+dy0*dy0)+sqrt(dx1*dx1+dy1*dy1)+sqrt(dx2*dx2+dy2*dy2)); + float shortlen = cast(float) sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(ubyte *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + ubyte *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + assert(x+gw < pw); + assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = cast(short) x; + chardata[i].y0 = cast(short) y; + chardata[i].x1 = cast(short) (x + gw); + chardata[i].y1 = cast(short) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = cast(float) x0; + chardata[i].yoff = cast(float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + + +alias stbrp_coord = int; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +struct stbrp_context +{ + int width,height; + int x,y,bottom_y; +}; + +struct stbrp_node +{ + ubyte x; +}; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con.width = pw; + con.height = ph; + con.x = 0; + con.y = 0; + con.bottom_y = 0; + // STBTT__NOTUSED(nodes); + // STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con.x + rects[i].w > con.width) { + con.x = 0; + con.y = con.bottom_y; + } + if (con.y + rects[i].h > con.height) + break; + rects[i].x = con.x; + rects[i].y = con.y; + rects[i].was_packed = 1; + con.x += rects[i].w; + if (con.y + rects[i].h > con.bottom_y) + con.bottom_y = con.y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. +enum STBTT__OVER_MASK = (STBTT_MAX_OVERSAMPLE-1); + +static void stbtt__h_prefilter(ubyte *pixels, int w, int h, int stride_in_bytes, uint kernel_width) +{ + ubyte[STBTT_MAX_OVERSAMPLE] buffer; + int safe_w = w - kernel_width; + int j; + memset(buffer.ptr, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + uint total; + memset(buffer.ptr, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = cast(ubyte) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = cast(ubyte) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = cast(ubyte) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = cast(ubyte) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = cast(ubyte) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = cast(ubyte) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(ubyte *pixels, int w, int h, int stride_in_bytes, uint kernel_width) +{ + ubyte[STBTT_MAX_OVERSAMPLE] buffer; + int safe_h = h - kernel_width; + int j; + memset(buffer.ptr, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + uint total; + memset(buffer.ptr, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = cast(ubyte) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = cast(ubyte) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = cast(ubyte) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = cast(ubyte) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = cast(ubyte) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = cast(ubyte) (total / kernel_width); + } + + pixels += 1; + } +} + +float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return cast(float)-(oversample - 1) / (2.0f * cast(float)oversample); +} + +////////////////////////////////////////////////////////////////////////////// +// +// sdf computation +// + +T STBTT_min(T)(T a, T b) => a < b ? a : b; +T STBTT_max(T)(T a, T b) => a < b ? b : a; + +int stbtt__ray_intersect_bezier(float[2] orig, float[2] ray, float[2] q0, float[2] q1, float[2] q2, float[2][2] hits) +{ + float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; + float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; + float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; + float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + + float a = q0perp - 2*q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b*b - a*c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = cast(float) sqrt(discr); + s0 = (b+d) * rcpna; + s1 = (b-d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + // 2*b*s + c = 0 + // s = -c / (2*b) + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; + float q1d = q1[0]*rayn_x + q1[1]*rayn_y; + float q2d = q2[0]*rayn_x + q2[1]*rayn_y; + float rod = orig[0]*rayn_x + orig[1]*rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; + hits[0][1] = a*s0+b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; + hits[1][1] = a*s1+b; + return 2; + } else { + return 1; + } + } +} + +static int equal(float[2] a, float[2] b) +{ + return (a[0] == b[0] && a[1] == b[1]); +} + +static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) +{ + int i; + float[2] orig = 0.0; + float[2] ray = [ 1, 0 ]; + float y_frac = 0.0; + int winding = 0; + + // make sure y never passes through a vertex of the shape + y_frac = y % 1.0f; + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + + orig[0] = x; + orig[1] = y; + + // test a ray from (-infinity,y) to (x,y) + for (i=0; i < nverts; ++i) { + if (verts[i].type == stbtt_curvetype.vline) { + int x0 = cast(int) verts[i-1].x, y0 = cast(int) verts[i-1].y; + int x1 = cast(int) verts[i ].x, y1 = cast(int) verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == stbtt_curvetype.vcurve) { + int x0 = cast(int) verts[i-1].x , y0 = cast(int) verts[i-1].y ; + int x1 = cast(int) verts[i ].cx, y1 = cast(int) verts[i ].cy; + int x2 = cast(int) verts[i ].x , y2 = cast(int) verts[i ].y ; + int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); + int by = STBTT_max(y0,STBTT_max(y1,y2)); + if (y > ay && y < by && x > ax) { + float[2] q0 = 0.0, q1 = 0.0, q2 = 0.0; + float[2][2] hits = 0; + q0[0] = cast(float)x0; + q0[1] = cast(float)y0; + q1[0] = cast(float)x1; + q1[1] = cast(float)y1; + q2[0] = cast(float)x2; + q2[1] = cast(float)y2; + if (equal(q0,q1) || equal(q1,q2)) { + x0 = cast(int)verts[i-1].x; + y0 = cast(int)verts[i-1].y; + x1 = cast(int)verts[i ].x; + y1 = cast(int)verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) + winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) + winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot( float x ) +{ + if (x<0) + return -cast(float) pow(-x,1.0f/3.0f); + else + return cast(float) pow( x,1.0f/3.0f); +} + +// x^3 + a*x^2 + b*x + c = 0 +static int stbtt__solve_cubic(float a, float b, float c, float* r) +{ + float s = -a / 3; + float p = b - a*a / 3; + float q = a * (2*a*a - 9*b) / 27 + c; + float p3 = p*p*p; + float d = q*q + 4*p3 / 27; + if (d >= 0) { + float z = cast(float) sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = cast(float) sqrt(-p/3); + float v = cast(float) acos(-sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative + float m = cast(float) cos(v); + float n = cast(float) cos(v-3.141592/2)*1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + //assert( fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? + //assert( fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); + //assert( fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); + return 3; + } +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static int stbtt__CompareUTF8toUTF16_bigendian_prefix(ubyte *s1, int len1, ubyte *s2, int len2) +{ + int i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + ushort ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + uint c; + ushort ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix(cast(ubyte*) s1, len1, cast(ubyte*) s2, len2); +} + +static int stbtt__matchpair(ubyte *fc, uint nm, ubyte *name, int nlen, int target_id, int next_id) +{ + int i; + int count = ttUSHORT(fc+nm+2); + int stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + uint loc = nm + 6 + 12 * i; + int id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + int platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + int slen = ttUSHORT(fc+loc+8); + int off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + int matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal(cast(char*) (name+matchlen), nlen-matchlen, cast(char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(ubyte *fc, uint offset, ubyte *name, int flags) +{ + int nlen = cast(int)strlen(cast(char *)name); + uint nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(ubyte *font_collection, char *name_utf8, int flags) +{ + int i; + for (i=0;;++i) { + int off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches(cast(ubyte *) font_collection, off, cast(ubyte*) name_utf8, flags)) + return off; + } +} + diff --git a/test.sh b/test.sh index a3e9303..61bb525 100755 --- a/test.sh +++ b/test.sh @@ -2,17 +2,17 @@ name="Test_Runner" -shared_src="dlib/package.d dlib/platform.d dlib/fonts.d dlib/aliases.d dlib/math.d dlib/util.d dlib/alloc.d dlib/assets.d dlib/externdecl.d" +shared_src="dlib/package.d dlib/platform.d dlib/fonts.d dlib/aliases.d dlib/math.d dlib/util.d dlib/alloc.d dlib/assets.d dlib/externdecl.d stb_truetype.d" if [ "$1" == "wasm" ]; then - flags="-c -vgc -mtriple=wasm32-unknown-unknown-wasm -d-version=DLIB_TEST -fvisibility=public -dllimport=all --unittest --Xcc=-DBUILD_WASM -Iwasm/runtime -L--no-entry -i=core -i=std -i=. --real-precision=double --of=build/dlibmain.wasm --d-version=inline_concat -verrors=90" - wasm_src="wasm/runtime/object.d" + flags="-vgc -betterC -mtriple=wasm32-unknown-unknown-wasm -d-version=DLIB_TEST -fvisibility=hidden --unittest -Iwasm/runtime -i=core -i=std -i=. --real-precision=double --of=build/dlibmain.wasm --d-version=inline_concat -verrors=90 --of=build/dlib.wasm -gcc=clang" + wasm_src="wasm/runtime/object.d stb_truetype.d" - /bin/bash ./build.sh build wasm + #/bin/bash ./build.sh build wasm ldc2 $flags $shared_src $wasm_src - wasm-ld build/dlibmain.wasm build/dlibincludes.wasm --export=RunTests --error-limit=0 --export-memory -obuild/dlib.wasm + #wasm-ld build/dlibmain.wasm build/dlibincludes.wasm --export=RunTests --error-limit=0 --export-memory -obuild/dlib.wasm cp build/dlib.wasm wasm/dlib.wasm else diff --git a/test/teststbtt.d b/test/teststbtt.d new file mode 100644 index 0000000..3de8f78 --- /dev/null +++ b/test/teststbtt.d @@ -0,0 +1,26 @@ +import stb_truetype; + +import core.stdc.stdio; +import core.stdc.stdlib; + +ubyte[] FONT_DATA = cast(ubyte[])import("pc-9800.ttf"); +stbtt_bakedchar[96] cdata; + +void +main(string[] args) +{ + char arg1 = args.length > 1 ? args[1][0] : 'a'; + + stbtt_fontinfo font; + ubyte* bitmap; + int w,h,i,j,c = (arg1), s = (args.length > 2 ? atoi(args[2].ptr) : 20); + + stbtt_InitFont(&font, FONT_DATA.ptr, stbtt_GetFontOffsetForIndex(FONT_DATA.ptr, 0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0, stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, null, null); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } +} diff --git a/wasm/dlib.wasm b/wasm/dlib.wasm index f85bcfd..84c8c1c 100755 Binary files a/wasm/dlib.wasm and b/wasm/dlib.wasm differ diff --git a/wasm/runtime/core/arsd/objectutils.d b/wasm/runtime/core/arsd/objectutils.d index d7baf51..2f038c4 100644 --- a/wasm/runtime/core/arsd/objectutils.d +++ b/wasm/runtime/core/arsd/objectutils.d @@ -1,5 +1,6 @@ module core.arsd.objectutils; +/* ///Provides only __doPostblit and hasPostblit for making the code simpler. size_t structTypeInfoSize(const TypeInfo ti) pure nothrow @nogc { @@ -11,6 +12,9 @@ size_t structTypeInfoSize(const TypeInfo ti) pure nothrow @nogc } return 0; } +*/ + +/* // strip const/immutable/shared/inout from type info inout(TypeInfo) unqualify(return scope inout(TypeInfo) cti) pure nothrow @nogc { @@ -32,6 +36,7 @@ inout(TypeInfo) unqualify(return scope inout(TypeInfo) cti) pure nothrow @nogc } return ti; } +*/ bool hasPostblit(in TypeInfo ti) nothrow pure { diff --git a/wasm/runtime/core/internal/cast_.d b/wasm/runtime/core/internal/cast_.d index dd2eb03..b19ab4b 100644 --- a/wasm/runtime/core/internal/cast_.d +++ b/wasm/runtime/core/internal/cast_.d @@ -157,10 +157,10 @@ void* _d_cast(To, From)(From o) @trusted { return _d_paint_cast!To(o); } - else static if (is (To : From)) - { - return _d_class_cast!To(o); - } + //else static if (is (To : From)) + //{ + // return _d_class_cast!To(o); + //} else { return null; diff --git a/wasm/runtime/core/stdc/math.d b/wasm/runtime/core/stdc/math.d new file mode 100644 index 0000000..0a1ceac --- /dev/null +++ b/wasm/runtime/core/stdc/math.d @@ -0,0 +1,4 @@ +module core.stdc.math; + +public import wasm : pow, cos, acos; + diff --git a/wasm/runtime/math/package.d b/wasm/runtime/math/package.d index 58431c4..a7b9883 100644 --- a/wasm/runtime/math/package.d +++ b/wasm/runtime/math/package.d @@ -1,10 +1,13 @@ module core.math; -pragma(LDC_intrinsic, "llvm.sqrt.f32") -float llvm_sqrt(float x) pure nothrow @nogc @safe; - -pragma(LDC_intrinsic, "llvm.sqrt.f64") -double llvm_sqrt(double x) pure nothrow @nogc @safe; +pragma(LDC_intrinsic, "llvm.sqrt.f#") T +llvm_sqrt(T)(T x) pure nothrow @nogc @safe; float sqrt(float x ) => x < 0.0f ? float.nan : llvm_sqrt(x); double sqrt(double x) => x < 0.0f ? double.nan : llvm_sqrt(x); + +pragma(LDC_intrinsic, "llvm.fabs.f#") T +llvm_fabs(T)(T x) pure nothrow @nogc @safe; + +float fabs(float x) => llvm_fabs(x); + diff --git a/wasm/runtime/object.d b/wasm/runtime/object.d index a6e613b..e6ceb51 100644 --- a/wasm/runtime/object.d +++ b/wasm/runtime/object.d @@ -12,10 +12,18 @@ alias AliasSeq(TList...) = TList; // import core.arsd.memory_allocation; import core.stdc.string; -import dlib.externdecl; +import dlib.alloc; +import dlib.util; import wasm; +export extern(C) void +__assert(const(char)* msg, const(char)* file, uint line) +{ + + Abortf("%s(%s): Assertion failure %s", Str(file), line, Str(msg)); +} + version(CarelessAlocation) { version = inline_concat; @@ -42,96 +50,96 @@ int __cmp(C1, C2)(C1 lhs, C2 rhs) if ((is(C1 : const(Object)) || (is(C1 == interface) && (__traits(getLinkage, C1) == "D"))) && (is(C2 : const(Object)) || (is(C2 == interface) && (__traits(getLinkage, C2) == "D")))) { - static if (is(C1 == typeof(null)) && is(C2 == typeof(null))) - { - return 0; - } - else static if (is(C1 == typeof(null))) - { - // Regard null references as always being "less than" - return -1; - } - else static if (is(C2 == typeof(null))) - { - return 1; - } - else - { - if (lhs is rhs) - return 0; - if (lhs is null) - return -1; - if (rhs is null) - return 1; - return lhs.opCmp(rhs); - } + static if (is(C1 == typeof(null)) && is(C2 == typeof(null))) + { + return 0; + } + else static if (is(C1 == typeof(null))) + { + // Regard null references as always being "less than" + return -1; + } + else static if (is(C2 == typeof(null))) + { + return 1; + } + else + { + if (lhs is rhs) + return 0; + if (lhs is null) + return -1; + if (rhs is null) + return 1; + return lhs.opCmp(rhs); + } } class TypeInfo_AssociativeArray : TypeInfo { - override string toString() const - { - return MakeString(value.toString(), "[", key.toString(), "]"); - } + override string toString() const + { + return MakeString(value.toString(), "[", key.toString(), "]"); + } - override bool opEquals(Object o) - { - if (this is o) - return true; - auto c = cast(const TypeInfo_AssociativeArray)o; - return c && this.key == c.key && - this.value == c.value; - } + override bool opEquals(Object o) + { + if (this is o) + return true; + auto c = cast(const TypeInfo_AssociativeArray)o; + return c && this.key == c.key && + this.value == c.value; + } - override bool equals(in void* p1, in void* p2) @trusted const - { - return xopEquals(p1, p2); - } + override bool equals(in void* p1, in void* p2) @trusted const + { + return xopEquals(p1, p2); + } - override hash_t getHash(scope const void* p) nothrow @trusted const - { - return xtoHash(p); - } + override hash_t getHash(scope const void* p) nothrow @trusted const + { + return xtoHash(p); + } - // BUG: need to add the rest of the functions + // BUG: need to add the rest of the functions - override @property size_t tsize() nothrow pure const - { - return (char[int]).sizeof; - } + override @property size_t tsize() nothrow pure const + { + return (char[int]).sizeof; + } - override const(void)[] initializer() const @trusted - { - return (cast(void *)null)[0 .. (char[int]).sizeof]; - } + override const(void)[] initializer() const @trusted + { + return (cast(void *)null)[0 .. (char[int]).sizeof]; + } - override @property inout(TypeInfo) next() nothrow pure inout { return value; } - override @property uint flags() nothrow pure const { return 1; } + override @property inout(TypeInfo) next() nothrow pure inout { return value; } + override @property uint flags() nothrow pure const { return 1; } - // TypeInfo entry is generated from the type of this template to help rt/aaA.d - // private static import core.internal.newaa; - // alias Entry(K, V) = core.internal.newaa.Entry!(K, V); + // TypeInfo entry is generated from the type of this template to help rt/aaA.d + // private static import core.internal.newaa; + // alias Entry(K, V) = core.internal.newaa.Entry!(K, V); - TypeInfo value; - TypeInfo key; - TypeInfo entry; + TypeInfo value; + TypeInfo key; + TypeInfo entry; - bool function(scope const void* p1, scope const void* p2) nothrow @safe xopEquals; - hash_t function(scope const void*) nothrow @safe xtoHash; + bool function(scope const void* p1, scope const void* p2) nothrow @safe xopEquals; + hash_t function(scope const void*) nothrow @safe xtoHash; - alias aaOpEqual(K, V) = core.internal.newaa._aaOpEqual!(K, V); - alias aaGetHash(K, V) = core.internal.newaa._aaGetHash!(K, V); + alias aaOpEqual(K, V) = core.internal.newaa._aaOpEqual!(K, V); + alias aaGetHash(K, V) = core.internal.newaa._aaGetHash!(K, V); - override @property size_t talign() nothrow pure const - { - return (char[int]).alignof; - } + override @property size_t talign() nothrow pure const + { + return (char[int]).alignof; + } - version (WithArgTypes) override int argTypes(out TypeInfo arg1, out TypeInfo arg2) - { - arg1 = typeid(void*); - return 0; - } + version (WithArgTypes) override int argTypes(out TypeInfo arg1, out TypeInfo arg2) + { + //arg1 = typeid(void*); + return 0; + } } template RTInfoImpl(size_t[] pointerBitmap) @@ -168,11 +176,12 @@ AllocPtr(size_t size) @trusted nothrow @nogc template _arrayOp(Args...) { - import core.internal.array.operations; - alias _arrayOp = arrayOp!Args; + import core.internal.array.operations; + alias _arrayOp = arrayOp!Args; } -extern(C) void _d_array_slice_copy(void* dst, size_t dstlen, void* src, size_t srclen, size_t elemsz) { +extern(C) void _d_array_slice_copy(void* dst, size_t dstlen, void* src, size_t srclen, size_t elemsz) +{ auto d = cast(ubyte*) dst; auto s = cast(ubyte*) src; auto len = dstlen * elemsz; @@ -183,10 +192,10 @@ extern(C) void _d_array_slice_copy(void* dst, size_t dstlen, void* src, size_t s s++; len--; } - } -void reserve(T)(ref T[] arr, size_t length) @trusted { +void reserve(T)(ref T[] arr, size_t length) @trusted +{ arr = (cast(T*) (malloc(length * T.sizeof)))[0 .. 0]; } @@ -194,37 +203,37 @@ void reserve(T)(ref T[] arr, size_t length) @trusted { extern(C) void _d_arraybounds(string file, size_t line) { - Abort("Range Error"); + Abortf("%s(%s): Range Error", file, line); } /// Called when an out of range slice of an array is created extern(C) void _d_arraybounds_slice(string file, uint line, size_t lwr, size_t upr, size_t length) { - Abort("Range error on slice creation"); + Abortf("%s(%s): Range error on slice creation %s is out of range on [%s .. %s]", file, line, length, lwr, upr); } /// Called when an out of range array index is accessed extern(C) void _d_arraybounds_index(string file, uint line, size_t index, size_t length) { - Abort("Range error on indexing array"); + Abortf("%s(%s): Range error on indexing array %s is out of range of %s", file, line, index, length); } // } extern(C) void _d_assert(string file, uint line) @trusted @nogc { - Abort("Assertion failure"); + Abortf("%s(%s): Assertion failure", file, line); } void _d_assertp(immutable(char)* file, uint line) { - Abort("Assertion failure"); + Abortf("%s(%s): Assertion failure", Str(file), line); } extern(C) void _d_assert_msg(string msg, string file, uint line) @trusted @nogc { - Abort("Assertion failure with msg"); + Abortf("%s(%s): Assertion failure - %s", file, line, msg); } void __switch_error(string file, size_t line) @trusted @nogc @@ -232,7 +241,8 @@ void __switch_error(string file, size_t line) @trusted @nogc _d_assert_msg("final switch error", file, line); } -bool __equals(T1, T2)(scope const T1[] lhs, scope const T2[] rhs) { +bool __equals(T1, T2)(scope const T1[] lhs, scope const T2[] rhs) +{ if (lhs.length != rhs.length) { return false; @@ -252,7 +262,8 @@ bool __equals(T1, T2)(scope const T1[] lhs, scope const T2[] rhs) { // bare basics class support { -extern(C) Object _d_allocclass(TypeInfo_Class ti) { +extern(C) Object _d_allocclass(TypeInfo_Class ti) +{ auto ptr = (cast(byte*)malloc(ti.m_init.length))[0 .. ti.m_init.length]; ptr[0 .. $] = ti.m_init[0 .. $]; return cast(Object) ptr.ptr; @@ -1962,11 +1973,13 @@ extern(C) void[] _d_newarrayiT(const TypeInfo ti, size_t length) } } +/* extern (C) void* _d_newitemU(scope const TypeInfo _ti) { auto ti = unqualify(_ti); return malloc(ti.tsize); } +*/ /// ditto extern (C) T* _d_newitemT(T)() @trusted @@ -1979,6 +1992,7 @@ extern (C) T* _d_newitemT(T)() @trusted return p; } +/* /// Same as above, for item with non-zero initializer. extern (C) void* _d_newitemiT(TypeInfo ti) { @@ -1987,8 +2001,7 @@ extern (C) void* _d_newitemiT(TypeInfo ti) memcpy(p, initializer.ptr, initializer.length); return p; } - - +*/ private void[] _d_newarrayOpT(alias op)(const TypeInfo ti, size_t[] dimensions) { @@ -2133,6 +2146,7 @@ extern (C) byte[] _d_arraycatT(const TypeInfo ti, byte[] x, byte[] y) return p[0 .. x.length + y.length]; } +/* extern (C) void[] _d_arrayappendcd(ref byte[] x, dchar c) { // c could encode into from 1 to 4 characters @@ -2174,6 +2188,7 @@ extern (C) void[] _d_arrayappendcd(ref byte[] x, dchar c) // return _d_arrayappendT(typeid(shared char[]), x, appendthis); } +*/ @@ -3103,26 +3118,28 @@ class Exception : Throwable } } +/* inout(TypeInfo) unqualify(return scope inout(TypeInfo) cti) pure nothrow @nogc { - TypeInfo ti = cast() cti; - while (ti) - { - // avoid dynamic type casts - auto tti = typeid(ti); - if (tti is typeid(TypeInfo_Const)) - ti = (cast(TypeInfo_Const)cast(void*)ti).base; - else if (tti is typeid(TypeInfo_Invariant)) - ti = (cast(TypeInfo_Invariant)cast(void*)ti).base; - else if (tti is typeid(TypeInfo_Shared)) - ti = (cast(TypeInfo_Shared)cast(void*)ti).base; - else if (tti is typeid(TypeInfo_Inout)) - ti = (cast(TypeInfo_Inout)cast(void*)ti).base; - else - break; - } - return ti; + TypeInfo ti = cast() cti; + while (ti) + { + // avoid dynamic type casts + auto tti = typeid(ti); + if (tti is typeid(TypeInfo_Const)) + ti = (cast(TypeInfo_Const)cast(void*)ti).base; + else if (tti is typeid(TypeInfo_Invariant)) + ti = (cast(TypeInfo_Invariant)cast(void*)ti).base; + else if (tti is typeid(TypeInfo_Shared)) + ti = (cast(TypeInfo_Shared)cast(void*)ti).base; + else if (tti is typeid(TypeInfo_Inout)) + ti = (cast(TypeInfo_Inout)cast(void*)ti).base; + else + break; + } + return ti; } +*/ private void _doPostblit(T)(T[] arr) { diff --git a/wasm/runtime/std/format.d b/wasm/runtime/std/format.d index b080860..9ec22b9 100644 --- a/wasm/runtime/std/format.d +++ b/wasm/runtime/std/format.d @@ -5,7 +5,7 @@ import std.traits; import wasm; char[] -sformat(Char, Args...)(scope return char[] buf, scope const(Char)[] fmt, Args args) +sformat(Char, Args...)(scope return char[] buf, scope const(Char)[] fmt, Args args) @nogc { SprintfType type = SprintfType.None; static foreach(i; 0 .. Args.length) @@ -52,10 +52,18 @@ sformat(Char, Args...)(scope return char[] buf, scope const(Char)[] fmt, Args ar else static if(is(Args[i] == double)) type = F64; else static if(is(Args[i] == bool)) type = Bool; else static if(is(Args[i] == char)) type = Char; + else static if(isPointer!(Args[i])) type = Pointer; else static assert(false, "Type unsupported"); } - SprintfLoadValue(&args[i], type); + static if(isPointer!(Args[i])) + { + SprintfLoadValue(args[i], type); + } + else + { + SprintfLoadValue(&args[i], type); + } } } @@ -65,7 +73,7 @@ sformat(Char, Args...)(scope return char[] buf, scope const(Char)[] fmt, Args ar } char[] -sformat(alias fmt, Args...)(char[] buf, Args args) if(isSomeString!(typeof(fmt))) +sformat(alias fmt, Args...)(char[] buf, Args args) @nogc if(isSomeString!(typeof(fmt))) { return .sformat(buf, fmt, args); } diff --git a/wasm/runtime/std/math/rounding.d b/wasm/runtime/std/math/rounding.d index 72a6e6f..1049b98 100644 --- a/wasm/runtime/std/math/rounding.d +++ b/wasm/runtime/std/math/rounding.d @@ -2,7 +2,21 @@ module std.math.rounding; pragma(LDC_intrinsic, "llvm.round.f#") T llvm_round(T)(T val) -if (__traits(isFloating, T)); +if(__traits(isFloating, T)); -float round(float x) => llvm_round(x); +float round(float x) => llvm_round(x); double round(double x) => llvm_round(x); + +pragma(LDC_intrinsic, "llvm.floor.f#") +T llvm_floor(T)(T val) +if(__traits(isFloating, T)); + +float floor(float x) => llvm_floor(x); +double floor(double x) => llvm_floor(x); + +pragma(LDC_intrisic, "llvm.ceil.f#") +T llvm_ceil(T)(T val) +if(__traits(isFloating, T)); + +float ceil(float x) => llvm_ceil(x); +double ceil(double x) => llvm_ceil(x); diff --git a/wasm/runtime/std/traits.d b/wasm/runtime/std/traits.d index ace6baf..2b772c4 100644 --- a/wasm/runtime/std/traits.d +++ b/wasm/runtime/std/traits.d @@ -1,5 +1,7 @@ module std.traits; +public import core.internal.traits; + enum bool isFloatingPoint(T) = __traits(isFloating, T) && is(T : real); template isIntegral(T) @@ -82,3 +84,11 @@ template isAggregateType(T) } enum bool isSomeString(T) = is(immutable T == immutable C[], C) && (is(C == char) || is(C == wchar) || is(C == dchar)); + +template Select(bool condition, T...) +if (T.length == 2) +{ + import std.meta : Alias; + alias Select = Alias!(T[!condition]); +} + diff --git a/wasm/runtime/wasm.d b/wasm/runtime/wasm.d index 3620e1a..00371bc 100644 --- a/wasm/runtime/wasm.d +++ b/wasm/runtime/wasm.d @@ -2,6 +2,9 @@ module wasm; import ldc.attributes; +import dlib.util; +import std.format; + enum SprintfType : size_t { None, @@ -30,9 +33,12 @@ enum SprintfType : size_t F64Array, Char, CharArray, + Pointer, } -extern extern(C) @nogc @llvmAttr("wasm-import-module", "env"): + + +extern extern(C) @llvmAttr("wasm-import-module", "env"): @llvmAttr("wasm-import-name", "Console") void Console(string str, bool write_line); @@ -41,39 +47,60 @@ Console(string str, bool write_line); Console2(size_t length, const(void)* ptr, bool write_line); @llvmAttr("wasm-import-name", "Abort") void -Abort(string message); +Abort(string message) @nogc; @llvmAttr("wasm-import-name", "SprintfLoadValue") void -SprintfLoadValue(const(void)* ptr, SprintfType type); +SprintfLoadValue(const(void)* ptr, SprintfType type) @nogc; @llvmAttr("wasm-import-name", "SprintfLoadArray") void -SprintfLoadArray(size_t length, const(void)* ptr, SprintfType type); +SprintfLoadArray(size_t length, const(void)* ptr, SprintfType type) @nogc; @llvmAttr("wasm-import-name", "SprintfEnd") size_t -SprintfEnd(char[] buffer, string format); +SprintfEnd(char[] buffer, string format) @nogc; + +@llvmAttr("wasm-import-name", "pow") double +pow(double base, double exponent) @nogc; + +@llvmAttr("wasm-import-name", "cos") double +cos(double x) @nogc; + +@llvmAttr("wasm-import-name", "acos") double +acos(double x) @nogc; + +void +Abortf(Args...)(string fmt, Args args) @nogc +{ + char[1024] buffer; + + string abort_message = Str(sformat(buffer, fmt, args)); + + Abort(abort_message); +} export void _start() { import dlib.alloc; import dlib.util; + import std.format; + + MallocInit(256, 16, (void*).sizeof*2); ResetScratch(MB(2)); + + char[100] buffer; + uint[2] arr = [1, 2]; + sformat(buffer, "%s", arr); } -version(DLIB_TEST) -{ - -export void +version(DLIB_TEST) export void RunTests() { - Console("azZ", true); - static foreach(test_fn; __traits(getUnitTests, __traits(parent, _start))) - { - Console("Running test", true); - test_fn(); - } + import dlib; + + DLibTestMath(); + DLibTestUtil(); + DLibTestAlloc(); + Console("Tests succeeded", true); } - -} diff --git a/wasm/wasm.js b/wasm/wasm.js index 0bbb0db..bb3ba6a 100644 --- a/wasm/wasm.js +++ b/wasm/wasm.js @@ -27,6 +27,8 @@ const TYPES = { CharArray: 25, }; +let LoadSize = null; + function assert(condition, message) { if(!condition) @@ -36,260 +38,272 @@ function assert(condition, message) } } -class WasmManager +const wasm_manager = { + memory: null, + data_view: null, + stdout_string: "", + sprintf_values: [], + ptr_size: 4, +}; + +function InitWasmManager(memory, ptr_size = 4) { - constructor(memory, ptr_size = 4) + wasm_manager.ptr_size = ptr_size; + + wasm_manager.sprintf_values = []; + + wasm_manager.stdout_string = ""; + + switch(wasm_manager.ptr_size) { - this.ptr_size = ptr_size; + case 8: LoadSize = LoadU64; break; + case 4: + default: LoadSize = LoadU32; break; + } +} - this.sprintf_values = []; +function SetMemory(memory) +{ + wasm_manager.memory = memory; + wasm_manager.data_view = new DataView(wasm_manager.memory.buffer); +} - this.stdout_string = ""; +function Memory() +{ + return new DataView(wasm_manager.memory.buffer); +} - switch(this.ptr_size) +function LoadBool(addr) { return Boolean(Memory().getUint8(addr, true)); } +function LoadF32(addr) { return Memory().getFloat32(addr, true); } +function LoadF64(addr) { return Memory().getFloat64(addr, true); } +function LoadU8 (addr) { return Memory().getUint8 (addr, true); } +function LoadI8 (addr) { return Memory().getInt8 (addr, true); } +function LoadU16(addr) { return Memory().getUint16 (addr, true); } +function LoadI16(addr) { return Memory().getInt16 (addr, true); } +function LoadU32(addr) { return Memory().getUint32 (addr, true); } +function LoadI32(addr) { return Memory().getInt32 (addr, true); } + +function LoadU64(addr) +{ + const lo = Memory().getUint32(addr+0, true); + const hi = Memory().getUint32(addr+4, true); + return lo + hi*4294967296; +} + +function LoadI64(addr) +{ + const lo = Memory().getUint32(addr+0, true); + const hi = Memory().getInt32 (addr+4, true); + return lo + hi*4294967296; +} + +function LoadArray(array_class, length, addr) +{ + return new array_class(wasm_manager.memory.buffer, addr, length); +} + +function LoadString(length, addr) +{ + return new TextDecoder().decode(LoadArray(Uint8Array, length, addr)); +} + +function LoadCString(ptr) +{ + if(!ptr) return null; + + let length = 0; + for(; LoadU8(ptr+length); length += 1) {} + + return LoadString(ptr, length); +} + +function StoreString(ptr, value) +{ + const src = new TextEncoder().encode(value); + const dst = new Uint8Array(wasm_manager.memory.buffer, ptr, src.length); + + dst.set(src); + + return src.length; +} + +const imports = { + env: { + Abort(string_length, string_ptr) { - case 8: this.LoadSize = this.LoadU64; break; - case 4: - default: this.LoadSize = this.LoadU32; break; - } - } + console.error(LoadString(string_length, string_ptr)); + }, + SprintfLoadValue(ptr, type) + { + let value = null; + switch(type) + { + case TYPES.U8: value = LoadU8(ptr); break; + case TYPES.I8: value = LoadI8(ptr); break; + case TYPES.U16: value = LoadU16(ptr); break; + case TYPES.I16: value = LoadI16(ptr); break; + case TYPES.U32: value = LoadU32(ptr); break; + case TYPES.I32: value = LoadI32(ptr); break; + case TYPES.U64: value = LoadU64(ptr); break; + case TYPES.I64: value = LoadI64(ptr); break; + case TYPES.SizeT: value = LoadSize(ptr); break; + case TYPES.F32: value = LoadF32(ptr); break; + case TYPES.F64: value = LoadF64(ptr); break; + case TYPES.Bool: value = LoadBool(ptr); break; + case TYPES.Char: value = LoadString(1, ptr); break; + default: break; + } - SetMemory(memory) - { - this.memory = memory; - this.data_view = new DataView(this.memory.buffer); - } - - LoadBool = (addr) => Boolean(this.data_view.getUint8(addr, true)); - - LoadF32 = (addr) => this.data_view.getFloat32(addr, true); - LoadF64 = (addr) => this.data_view.getFloat64(addr, true); - - LoadU8 = (addr) => this.data_view.getUint8 (addr, true); - LoadI8 = (addr) => this.data_view.getInt8 (addr, true); - LoadU16 = (addr) => this.data_view.getUint16 (addr, true); - LoadI16 = (addr) => this.data_view.getInt16 (addr, true); - LoadU32 = (addr) => this.data_view.getUint32 (addr, true); - LoadI32 = (addr) => this.data_view.getInt32 (addr, true); - - LoadU64(addr) - { - const lo = this.data_view.getUint32(addr+0, true); - const hi = this.data_view.getUint32(addr+4, true); - return lo + hi*4294967296; - } - - LoadI64(addr) - { - const lo = this.data_view.getUint32(addr+0, true); - const hi = this.data_view.getInt32 (addr+4, true); - return lo + hi*4294967296; - } - - LoadArray = (array_class, length, addr) => new array_class(this.memory.buffer, addr, length); - LoadString = (length, addr) => new TextDecoder().decode(this.LoadArray(Uint8Array, length, addr)); - - LoadCString(ptr) - { - if(!ptr) return null; - - let length = 0; - for(; this.LoadU8(ptr+length); length += 1) {} - - return this.LoadString(ptr, length); - } - - StoreString(ptr, value) - { - const src = new TextEncoder().encode(value); - const dst = new Uint8Array(this.memory.buffer, ptr, src.length); - - dst.set(src); - - return src.length; - } - - Imports() - { - const manager = this; - return { - env: { - Abort(string_length, string_ptr) + if(value != null) + { + if(type === TYPES.Bool) { - console.error(manager.LoadString(string_length, string_ptr)); - }, - SprintfLoadValue(ptr, type) + wasm_manager.sprintf_values.push(value ? 'true' : 'false'); + } + else { - let value = null; - switch(type) - { - case TYPES.U8: value = manager.LoadU8(ptr); break; - case TYPES.I8: value = manager.LoadI8(ptr); break; - case TYPES.U16: value = manager.LoadU16(ptr); break; - case TYPES.I16: value = manager.LoadI16(ptr); break; - case TYPES.U32: value = manager.LoadU32(ptr); break; - case TYPES.I32: value = manager.LoadI32(ptr); break; - case TYPES.U64: value = manager.LoadU64(ptr); break; - case TYPES.I64: value = manager.LoadI64(ptr); break; - case TYPES.SizeT: value = manager.LoadSize(ptr); break; - case TYPES.F32: value = manager.LoadF32(ptr); break; - case TYPES.F64: value = manager.LoadF64(ptr); break; - case TYPES.Bool: value = manager.LoadBool(ptr); break; - case TYPES.Char: value = manager.LoadString(1, ptr); break; - default: break; - } + wasm_manager.sprintf_values.push(''+value); + } + } + }, + SprintfLoadArray(length, ptr, type) + { + let value = null; + switch(type) + { + case TYPES.CharArray: + case TYPES.String: value = LoadString(length, ptr); break; + case TYPES.U8Array: value = LoadArray(Uint8Array, length, ptr); break; + case TYPES.I8Array: value = LoadArray(Int8Array, length, ptr); break; + case TYPES.U16Array: value = LoadArray(Uint16Array, length, ptr); break; + case TYPES.I16Array: value = LoadArray(Int16Array, length, ptr); break; + case TYPES.U32Array: value = LoadArray(Uint32Array, length, ptr); break; + case TYPES.I32Array: value = LoadArray(Int32Array, length, ptr); break; + case TYPES.U64Array: value = LoadArray(BigUint64Array, length, ptr); break; + case TYPES.I64Array: value = LoadArray(BigInt64Array, length, ptr); break; + case TYPES.F32Array: value = LoadArray(Float32Array, length, ptr); break; + case TYPES.F64Array: value = LoadArray(Float64Array, length, ptr); break; + } - if(value) - { - if(type === TYPES.Bool) - { - manager.sprintf_values.push(value ? 'true' : 'false'); - } - else - { - manager.sprintf_values.push(''+value); - } - } - }, - SprintfLoadArray(length, ptr, type) + if(value) + { + if(type !== TYPES.String && type !== TYPES.CharArray) { - let value = null; - switch(type) - { - case TYPES.CharArray: - case TYPES.String: value = manager.LoadString(length, ptr); break; - case TYPES.U8Array: value = manager.LoadArray(Uint8Array, length, ptr); break; - case TYPES.I8Array: value = manager.LoadArray(Int8Array, length, ptr); break; - case TYPES.U16Array: value = manager.LoadArray(Uint16Array, length, ptr); break; - case TYPES.I16Array: value = manager.LoadArray(Int16Array, length, ptr); break; - case TYPES.U32Array: value = manager.LoadArray(Uint32Array, length, ptr); break; - case TYPES.I32Array: value = manager.LoadArray(Int32Array, length, ptr); break; - case TYPES.U64Array: value = manager.LoadArray(BigUint64Array, length, ptr); break; - case TYPES.I64Array: value = manager.LoadArray(BigInt64Array, length, ptr); break; - case TYPES.F32Array: value = manager.LoadArray(Float32Array, length, ptr); break; - case TYPES.F64Array: value = manager.LoadArray(Float64Array, length, ptr); break; - } + value = `[${value.join(", ")}]`; + } - if(value) - { - if(type !== TYPES.String && type !== TYPES.CharArray) - { - value = `[${value.join(", ")}]`; - } + wasm_manager.sprintf_values.push(value); + } + }, + SprintfEnd(buffer_length, buffer_ptr, string_length, string_ptr) + { + let format_string = LoadString(string_length, string_ptr); - manager.sprintf_values.push(value); - } - }, - SprintfEnd(buffer_length, buffer_ptr, string_length, string_ptr) + let index = format_string.indexOf("%"); + + let result = format_string.substring(0, index); + let format = format_string.substring(index); + + function Next() + { + index = format.indexOf("%"); + result += format.substring(0, index); + format = format.substring(index); + } + + let value_index = 0; + for(; value_index < wasm_manager.sprintf_values.length;) + { + assert(format.length); + + const char = format.charAt(1); + assert(char !== " " && char.length, "Invalid format string " + format_string); + + if(char === "%") { - let format_string = manager.LoadString(string_length, string_ptr); - - let index = format_string.indexOf("%"); - - let result = format_string.substring(0, index); - let format = format_string.substring(index); - - function Next() + result += "%"; + format = format.substring(2); + Next(); + continue; + } + else if(char >= '0' && char <= '9') + { + let digit_index = 2; + for(;;) { - index = format.indexOf("%"); - result += format.substring(0, index); - format = format.substring(index); - } + let next_char = format.charAt(digit_index++); - let value_index = 0; - for(; value_index < manager.sprintf_values.length;) - { - assert(format.length); + assert(next_char !== " " && next_char.length, "Invalid format string " + format_string); - const char = format.charAt(1); - assert(char !== " " && char.length, "Invalid format string " + format_string); - - if(char === "%") + if(next_char < '0' || next_char > '9') { - result += "%"; - format = format.substring(2); - Next(); continue; } - else if(char >= '0' && char <= '9') - { - let digit_index = 2; - for(;;) - { - let next_char = format.charAt(digit_index++); - assert(next_char !== " " && next_char.length, "Invalid format string " + format_string); + result += wasm_manager.sprintf_values[value_index++]; + format = format.substring(digit_index); + Next(); - if(next_char < '0' || next_char > '9') - { - continue; - } - - result += manager.sprintf_values[value_index++]; - format = format.substring(digit_index); - Next(); - - break; - } - } - else if((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z')) - { - result += manager.sprintf_values[value_index++]; - format = format.substring(2); - Next(); - } - else assert(false, "Invalid format string " + format_string); + break; } - - result += format; - - const bytes_written = manager.StoreString(buffer_ptr, result); - assert(bytes_written <= buffer_length, "Format string is longer than buffer length"); - - manager.sprintf_values = []; - - return bytes_written; - }, - Console(string_length, string_ptr, write_line) + } + else if((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z')) { - manager.stdout_string += manager.LoadString(string_length, string_ptr); - const arr = manager.LoadArray(Uint8Array, string_length, string_ptr); + result += wasm_manager.sprintf_values[value_index++]; + format = format.substring(2); + Next(); + } + else assert(false, "Invalid format string " + format_string); + } - if(write_line || manager.stdout_string.includes("\n")) - { - console.log(manager.stdout_string); - manager.stdout_string = ""; - } - }, - Console2(string_length, string_ptr, write_line) - { - manager.stdout_string += manager.LoadString(string_length, string_ptr); - const arr = manager.LoadArray(Uint8Array, string_length, string_ptr); + result += format; - if(write_line || manager.stdout_string.includes("\n")) - { - console.log(manager.stdout_string); - manager.stdout_string = ""; - } - }, - }, - } - } + const bytes_written = StoreString(buffer_ptr, result); + assert(bytes_written <= buffer_length, "Format string is longer than buffer length"); + + wasm_manager.sprintf_values = []; + + return bytes_written; + }, + Console(string_length, string_ptr, write_line) + { + wasm_manager.stdout_string += LoadString(string_length, string_ptr); + const arr = LoadArray(Uint8Array, string_length, string_ptr); + + if(write_line || wasm_manager.stdout_string.includes("\n")) + { + console.log(wasm_manager.stdout_string); + wasm_manager.stdout_string = ""; + } + }, + Console2(string_length, string_ptr, write_line) + { + wasm_manager.stdout_string += LoadString(string_length, string_ptr); + const arr = LoadArray(Uint8Array, string_length, string_ptr); + + if(write_line || wasm_manager.stdout_string.includes("\n")) + { + console.log(wasm_manager.stdout_string); + wasm_manager.stdout_string = ""; + } + }, + pow: Math.pow, + cos: Math.cos, + acos: Math.acos, + }, } async function StartWasm(path) { - const manager = new WasmManager(); - const response = await fetch(path); const data = await response.arrayBuffer(); - const wasm = await WebAssembly.instantiate(data, manager.Imports()); + const wasm = await WebAssembly.instantiate(data, imports); const exports = wasm.instance.exports; if(exports.memory) { - manager.SetMemory(exports.memory); + SetMemory(exports.memory); } exports?._start();