diff --git a/font.js b/font.js new file mode 100644 index 0000000..af9fe7f --- /dev/null +++ b/font.js @@ -0,0 +1,165 @@ +import { getPath } from "./common.js"; + +class FontRenderer { + createBuffers(device) + { + this.maxGlyphs = 1024; + this.glyphBufferSize = 4 * this.maxGlyphs; + + this.frames = []; + for (let i = 0; i < 2; i++) { + const glyphBuffer = device.createBuffer({ + label: `glyph buffer ${i}`, + size: this.glyphBufferSize, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, + }); + + const bindGroup = device.createBindGroup({ + label: `font bind group ${i}`, + layout: this.bindGroupLayout.glyph, + entries: [{ + binding: 0, + resource: { buffer: glyphBuffer }, + }] + }); + + this.frames.push({ + glyphBuffer: glyphBuffer, + bindGroup: bindGroup, + }); + } + } + + constructor(device, canvasFormat, viewUniformBuffer, wgsl, module) + { + const label = "font"; + + this.module = module; + + ////////////////////////////////////////////////////////////////////// + // shader module + ////////////////////////////////////////////////////////////////////// + + const shaderModule = device.createShaderModule({ + label: `${label} shader`, + code: wgsl, + }); + + ////////////////////////////////////////////////////////////////////// + // index buffer + ////////////////////////////////////////////////////////////////////// + + const array = new Uint16Array(6); + array[0] = 0; + array[1] = 1; + array[2] = 2; + array[3] = 0; + array[4] = 3; + array[5] = 1; + + this.buffer = device.createBuffer({ + label: `${label} renderer buffer`, + size: array.byteLength, + usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST, + }); + + device.queue.writeBuffer(this.buffer, 0, array, 0, array.length); + + ////////////////////////////////////////////////////////////////////// + // pipeline + ////////////////////////////////////////////////////////////////////// + + this.bindGroupLayout = { + view: device.createBindGroupLayout({ + label: `${label} bind group layout view`, + entries: [{ + binding: 0, + visibility: GPUShaderStage.VERTEX, + buffer: { type: "uniform" } + }] + }), + glyph: device.createBindGroupLayout({ + label: `${label} bind group layout glyph`, + entries: [{ // glyphs + binding: 0, + visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, + buffer: { type: "read-only-storage" } + }/*, { // configuration + binding: 1, + visibility: GPUShaderStage.FRAGMENT, + buffer: { type: "uniform" } + }*/] + }), + }; + + const pipelineLayout = device.createPipelineLayout({ + label: `${label} pipeline layout`, + bindGroupLayouts: [ + this.bindGroupLayout.view, + this.bindGroupLayout.glyph + ], + }); + + this.renderPipeline = device.createRenderPipeline({ + label: `${label} pipeline`, + layout: pipelineLayout, + vertex: { + module: shaderModule, + entryPoint: "vertexMain", + }, + fragment: { + module: shaderModule, + entryPoint: "fragmentMain", + targets: [{ + format: canvasFormat, + }] + }, + primitive: { + topology: "triangle-list", + }, + depthStencil: { + depthWriteEnabled: true, + depthCompare: "less", + format: "depth24plus", + }, + }); + + ////////////////////////////////////////////////////////////////////// + // buffers + ////////////////////////////////////////////////////////////////////// + + this.createBuffers(device); + + ////////////////////////////////////////////////////////////////////// + // bind group + ////////////////////////////////////////////////////////////////////// + + this.viewBindGroup = device.createBindGroup({ + label: `${label} bind group`, + layout: this.bindGroupLayout.view, + entries: [{ + binding: 0, + resource: { buffer: viewUniformBuffer }, + }], + }); + } + + render(renderPass, frameNumber) + { + renderPass.setPipeline(this.renderPipeline); + renderPass.setIndexBuffer(this.buffer, "uint16"); + renderPass.setBindGroup(0, this.viewBindGroup); + renderPass.setBindGroup(1, this.frames[frameNumber].bindGroup); + renderPass.drawIndexed(6); + } +} + +async function loadFont(device, canvasFormat, viewUniformBuffer, module) +{ + const fontWgsl = await getPath("font.wgsl"); + + const renderer = new FontRenderer(device, canvasFormat, viewUniformBuffer, fontWgsl, module); + return renderer; +} + +export { loadFont }; diff --git a/font.wgsl b/font.wgsl new file mode 100644 index 0000000..e90427c --- /dev/null +++ b/font.wgsl @@ -0,0 +1,52 @@ +struct Configuration { + viewProj: mat4x4f, + lightViewProj: mat4x4f, + lightPosition: vec4f, + eyePosition: vec4f, + aspect: f32, +}; + +struct GlyphBitmap { + position: vec2f, + velocity: vec2f, +}; + +@group(0) @binding(0) var config: Configuration; +@group(1) @binding(0) var glyphs: array; + +struct VertexInput { + @builtin(vertex_index) vertex_index: u32, +}; + +struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) texture: vec2f, +}; + +const vertices = array( + vec2f(1.0, 0.0), + vec2f(0.0, 1.0), + vec2f(0.0, 0.0), + vec2f(1.0, 1.0), +); + +@vertex +fn vertexMain(input: VertexInput) -> VertexOutput +{ + let texture = vertices[input.vertex_index]; + + let position = texture * 2.0 - 1.0; + + var output: VertexOutput; + output.position = vec4f(position, 0.0, 1.0); + output.texture = position * vec2f(config.aspect, 1); + return output; +} + +@fragment +fn fragmentMain(input: VertexOutput) -> @location(0) vec4f +{ + var color = vec3f(input.texture.xy, 0); + + return vec4f(color, 1); +} diff --git a/include/font.h b/include/font.h new file mode 100644 index 0000000..0aac575 --- /dev/null +++ b/include/font.h @@ -0,0 +1,51 @@ +// this file is designed to be platform-agnostic +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// metrics are 26.6 fixed point +struct glyph_metrics { + int32_t horiBearingX; + int32_t horiBearingY; + int32_t horiAdvance; +} __attribute__ ((packed)); + +static_assert((sizeof (struct glyph_metrics)) == ((sizeof (int32_t)) * 3)); + +struct glyph_bitmap { + uint16_t x; + uint16_t y; + uint16_t width; + uint16_t height; +} __attribute__ ((packed)); + +static_assert((sizeof (struct glyph_bitmap)) == ((sizeof (uint16_t)) * 4)); + +struct glyph { + struct glyph_bitmap bitmap; + struct glyph_metrics metrics; +} __attribute__ ((packed)); + +static_assert((sizeof (struct glyph)) == ((sizeof (struct glyph_bitmap)) + (sizeof (struct glyph_metrics)))); + +struct font { + uint32_t first_char_code; + uint32_t last_char_code; + struct face_metrics { + int32_t height; // 26.6 fixed point + int32_t max_advance; // 26.6 fixed point + } face_metrics; + uint32_t glyph_count; + uint16_t texture_width; + uint16_t texture_height; +} __attribute__ ((packed)); + +static_assert((sizeof (struct font)) == ((sizeof (uint32_t)) * 6)); + +#ifdef __cplusplus +} +#endif diff --git a/src/font_layout/api.cpp b/src/font_layout/api.cpp new file mode 100644 index 0000000..123009b --- /dev/null +++ b/src/font_layout/api.cpp @@ -0,0 +1,47 @@ +#include "new.h" +#include "font_layout.h" + +extern "C" { + FontLayout * font_layout__create(size_t buffer, int layout_buffer_length) + { + FontLayout * font_layout = New(); + font_layout->init(buffer, layout_buffer_length); + return font_layout; + } + + size_t font_layout__glyph_buffer_size(FontLayout * font_layout) + { + return font_layout->font->glyph_count * (sizeof (GlyphBuffer)); + } + + void * font_layout__glyph_buffer_address(FontLayout * font_layout) + { + return font_layout->glyph_buffer; + } + + size_t font_layout__texture_size(FontLayout * font_layout) + { + int const bytes_per_pixel = 1; + return font_layout->font->texture_width * font_layout->font->texture_width * bytes_per_pixel; + } + + void * font_layout__texture_address(FontLayout * font_layout) + { + return font_layout->texture; + } + + size_t font_layout__layout_buffer_size(FontLayout * font_layout) + { + return font_layout->layout_buffer_length * (sizeof (LayoutBuffer)); + } + + void * font_layout__layout_buffer_address(FontLayout * font_layout) + { + return font_layout->layout_buffer; + } + + int font_layout__layout_buffer_index(FontLayout * font_layout) + { + return font_layout->layout_buffer_index; + } +}; diff --git a/src/font_layout/font_layout.cpp b/src/font_layout/font_layout.cpp new file mode 100644 index 0000000..395efac --- /dev/null +++ b/src/font_layout/font_layout.cpp @@ -0,0 +1,28 @@ +#include "new.h" +#include "font_layout.h" + +static inline size_t texture_offset(font * font) +{ + return (sizeof (font)) * (sizeof (glyph)) * font->glyph_count; +} + +void FontLayout::init(size_t buffer, int layout_buffer_length) +{ + this->font = reinterpret_cast(buffer + 0); + this->glyphs = reinterpret_cast(buffer + (sizeof (font))); + + this->texture = reinterpret_cast(buffer + texture_offset(this->font)); + + this->layout_buffer_length = layout_buffer_length; + this->layout_buffer_index = 0; + this->layout_buffer = New(layout_buffer_length); + + this->glyph_buffer = New(this->font->glyph_count); + for (uint16_t i = 0; i < this->font->glyph_count; i++) { + glyph_bitmap const & bitmap = this->glyphs[i].bitmap; + this->glyph_buffer[i].position.x = float(bitmap.x) / float(this->font->texture_width); + this->glyph_buffer[i].position.y = float(bitmap.y) / float(this->font->texture_height); + this->glyph_buffer[i].size.x = float(bitmap.width) / float(this->font->texture_width); + this->glyph_buffer[i].size.y = float(bitmap.height) / float(this->font->texture_height); + } +} diff --git a/src/font_layout/font_layout.h b/src/font_layout/font_layout.h new file mode 100644 index 0000000..0cf8cf3 --- /dev/null +++ b/src/font_layout/font_layout.h @@ -0,0 +1,26 @@ +#pragma once + +#include "font.h" +#include "directxmath/DirectXMath.h" + +struct GlyphBuffer { + XMFLOAT2 position; // in uv units + XMFLOAT2 size; // in uv units +}; + +struct LayoutBuffer { + XMFLOAT3 position; // in local coordinate space units + float glyph_index; +}; + +struct FontLayout { + font * font; + glyph * glyphs; + void * texture; + int layout_buffer_length; // in elements + int layout_buffer_index; + GlyphBuffer * glyph_buffer; + LayoutBuffer * layout_buffer; + + void init(size_t buffer, int layout_buffer_length); +}; diff --git a/tools/ttf_outline/Makefile b/tools/ttf_outline/Makefile new file mode 100644 index 0000000..3dd7b79 --- /dev/null +++ b/tools/ttf_outline/Makefile @@ -0,0 +1,16 @@ +CFLAGS = -Og -g -gdwarf-4 -Wall -Wextra -Wno-error -Wfatal-errors +CFLAGS += -Wno-error=unused-parameter +CFLAGS += -Wno-error=unused-variable +CFLAGS += -Wno-error=unused-but-set-variable +CFLAGS += -I../../include + +CXXFLAGS = -std=c++23 + +FREETYPE_CFLAGS = $(shell pkg-config --cflags freetype2) +FREETYPE_LDFLAGS = $(shell pkg-config --libs freetype2) + +%.o: %.cpp + $(CXX) $(CFLAGS) $(CXXFLAGS) $(FREETYPE_CFLAGS) -c $< -o $@ + +ttf_outline: ttf_outline.o ttf_2d_pack.o + $(CXX) $(LDFLAGS) $(FREETYPE_LDFLAGS) $^ -o $@ diff --git a/tools/ttf_outline/ttf_2d_pack.cpp b/tools/ttf_outline/ttf_2d_pack.cpp new file mode 100644 index 0000000..50378b3 --- /dev/null +++ b/tools/ttf_outline/ttf_2d_pack.cpp @@ -0,0 +1,159 @@ +#include +#include +#include + +#include "ttf_2d_pack.h" + +struct size { + uint16_t width; + uint16_t height; +}; + +constexpr struct size max_texture = {1024, 1024}; + +inline bool area_valid(const uint8_t texture[max_texture.height][max_texture.width], + const uint32_t x_offset, + const uint32_t y_offset, + const struct rect& rect, + const struct size& window) +{ + for (uint32_t yi = 0; yi < rect.height; yi++) { + for (uint32_t xi = 0; xi < rect.width; xi++) { + uint32_t x = x_offset + xi; + uint32_t y = y_offset + yi; + + if (texture[y][x] != 0) + return false; + + if (x >= window.width || y >= window.height) + return false; + } + } + + return true; +} + +constexpr inline std::array +from_ix(uint32_t curve_ix) +{ + std::array x_y = {0, 0}; + uint32_t curve_bit = 0; + + while (curve_ix != 0) { + x_y[(curve_bit + 1) % 2] |= (curve_ix & 1) << (curve_bit / 2); + curve_ix >>= 1; + curve_bit += 1; + } + + return x_y; +} + +static_assert(from_ix(0) == std::array{{0b000, 0b000}}); +static_assert(from_ix(2) == std::array{{0b001, 0b000}}); +static_assert(from_ix(8) == std::array{{0b010, 0b000}}); +static_assert(from_ix(10) == std::array{{0b011, 0b000}}); +static_assert(from_ix(32) == std::array{{0b100, 0b000}}); +static_assert(from_ix(34) == std::array{{0b101, 0b000}}); +static_assert(from_ix(40) == std::array{{0b110, 0b000}}); +static_assert(from_ix(42) == std::array{{0b111, 0b000}}); + +static_assert(from_ix(1) == std::array{{0b000, 0b001}}); +static_assert(from_ix(4) == std::array{{0b000, 0b010}}); +static_assert(from_ix(5) == std::array{{0b000, 0b011}}); +static_assert(from_ix(16) == std::array{{0b000, 0b100}}); +static_assert(from_ix(17) == std::array{{0b000, 0b101}}); +static_assert(from_ix(20) == std::array{{0b000, 0b110}}); +static_assert(from_ix(21) == std::array{{0b000, 0b111}}); + +constexpr inline int log2(uint32_t n) +{ + switch (n) { + default: + case 8: return 3; + case 16: return 4; + case 32: return 5; + case 64: return 6; + case 128: return 7; + case 256: return 8; + case 512: return 9; + case 1024: return 10; + } +} + +static void pack_into(uint8_t texture[max_texture.height][max_texture.width], + size & window, + rect & rect) +{ + uint32_t z_curve_ix = 0; + + if (rect.width == 0 || rect.height == 0) { + rect.x = 0; + rect.y = 0; + return; + } + + while (true) { + auto [x_offset, y_offset] = from_ix(z_curve_ix); + + if (x_offset >= window.width and y_offset >= window.height) { + assert(window.width < max_texture.width || window.height < max_texture.height); + if (window.width == window.height) { window.height *= 2; } + else { window.width *= 2; } + + // when the window changes; start again from the beginning and + // re-check earlier locations that might have been skipped due + // to window size + z_curve_ix = 0; + } + + if (area_valid(texture, x_offset, y_offset, rect, window)) { + for (uint32_t yi = 0; yi < rect.height; yi++) { + for (uint32_t xi = 0; xi < rect.width; xi++) { + uint32_t x = x_offset + xi; + uint32_t y = y_offset + yi; + + texture[y][x] = 1; + } + } + + rect.x = x_offset; + rect.y = y_offset; + + return; + } else { + z_curve_ix += 1; + continue; + } + } + + assert(false); +} + +template +void insertion_sort(T * arr, int len) +{ + int i = 1; + while (i < len) { + int j = i; + while (j > 0 && arr[j - 1] < arr[j]) { + std::swap(arr[j - 1], arr[j]); + j -= 1; + } + i += 1; + } +} + +window_curve_ix pack_all(struct rect * rects, const uint32_t num_rects) +{ + uint8_t texture[max_texture.height][max_texture.width] = { 0 }; + size window = {1, 1}; + + // sort all rectangles by size + insertion_sort(rects, num_rects); + + for (uint32_t i = 0; i < num_rects; i++) { + pack_into(texture, window, rects[i]); + } + + return {window.width, window.height}; +} diff --git a/tools/ttf_outline/ttf_2d_pack.h b/tools/ttf_outline/ttf_2d_pack.h new file mode 100644 index 0000000..0abc970 --- /dev/null +++ b/tools/ttf_outline/ttf_2d_pack.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +struct window_curve_ix { + struct { + uint16_t width; + uint16_t height; + } window; +}; + +window_curve_ix pack_all(struct rect * rects, const uint32_t num_rects); + +struct rect { + uint32_t char_code; + uint32_t width; + uint32_t height; + int32_t x; + int32_t y; + + std::strong_ordering operator<=>(const rect& b) const + { + return (width * height) <=> (b.width * b.height); + } +}; diff --git a/tools/ttf_outline/ttf_outline.cpp b/tools/ttf_outline/ttf_outline.cpp new file mode 100644 index 0000000..9177987 --- /dev/null +++ b/tools/ttf_outline/ttf_outline.cpp @@ -0,0 +1,299 @@ +#include +#include +#include + +#include +#include + +#include +#include FT_FREETYPE_H + +#include "font.h" +#include "ttf_2d_pack.h" + +std::endian _target_endian; + +constexpr uint32_t max_texture_dim = 1024; +constexpr uint32_t max_texture_size = max_texture_dim * max_texture_dim; + +template< class T > +constexpr T byteswap(const T n) +{ + if (std::endian::native != _target_endian) { + return std::byteswap(n); + } else { + return n; + } +} + +int32_t +load_outline_char_bitmap_rect(const FT_Face face, + const FT_Int32 load_flags, + const FT_Render_Mode render_mode, + const FT_ULong char_code, + struct rect& rect) +{ + FT_Error error; + FT_UInt glyph_index = FT_Get_Char_Index(face, char_code); + + error = FT_Load_Glyph(face, glyph_index, load_flags); + if (error) { + std::cerr << "FT_Load_Glyph " << FT_Error_String(error) << '\n'; + return -1; + } + + error = FT_Render_Glyph(face->glyph, render_mode); + if (error) { + std::cerr << "FT_Render_Glyph " << FT_Error_String(error) << '\n'; + return -1; + } + + rect.char_code = char_code; + rect.height = face->glyph->bitmap.rows; + rect.width = face->glyph->bitmap.width; + rect.x = -1; + rect.y = -1; + + return 0; +} + +int32_t +load_outline_char(const FT_Face face, + const FT_Int32 load_flags, + const FT_Render_Mode render_mode, + const uint32_t bits_per_pixel, + const FT_ULong char_code, + glyph * glyph, + uint8_t * texture, + uint32_t texture_width, + struct rect& rect) +{ + FT_Error error; + FT_UInt glyph_index = FT_Get_Char_Index(face, char_code); + + error = FT_Load_Glyph(face, glyph_index, load_flags); + if (error) { + std::cerr << "FT_Load_Glyph " << FT_Error_String(error) << '\n'; + return -1; + } + + //std::cerr << "size " << face->glyph->bitmap.rows << ' ' << face->glyph->bitmap.width << '\n'; + + //assert(face->glyph->format == FT_GLYPH_FORMAT_OUTLINE); + + error = FT_Render_Glyph(face->glyph, render_mode); + if (error) { + std::cerr << "FT_Render_Glyph " << FT_Error_String(error) << '\n'; + return -1; + } + + if (!(face->glyph->bitmap.pitch > 0)) { + assert(face->glyph->bitmap.width == 0); + assert(face->glyph->bitmap.rows == 0); + } + + assert(face->glyph->bitmap.width == rect.width); + assert(face->glyph->bitmap.rows == rect.height); + + assert(bits_per_pixel == 8 || bits_per_pixel == 4 || bits_per_pixel == 2 || bits_per_pixel == 1); + const uint32_t pixels_per_byte = 8 / bits_per_pixel; + const uint32_t texture_stride = texture_width / pixels_per_byte; + //std::cerr << "pixels per byte: " << pixels_per_byte << '\n'; + //std::cerr << "texture stride: " << texture_stride << '\n'; + + for (uint32_t y = 0; y < rect.height; y++) { + for (uint32_t x = 0; x < rect.width; x++) { + const uint32_t texture_ix = (rect.y + y) * texture_stride + (rect.x + x) / pixels_per_byte; + const uint32_t texture_ix_mod = (rect.x + x) % pixels_per_byte; + assert(texture_ix < max_texture_size); + + uint8_t level; + + //std::cerr << "rxy " << rect.x << ' ' << rect.y << '\n'; + //std::cerr << "rwh " << rect.width << ' ' << rect.height << '\n'; + + //std::cerr << "pixel_mode " << (int)face->glyph->bitmap.pixel_mode << '\n'; + switch (face->glyph->bitmap.pixel_mode) { + case FT_PIXEL_MODE_MONO: + // [num_grays] is only used with FT_PIXEL_MODE_GRAY; it gives the number + // of gray levels used in the bitmap. + level = (face->glyph->bitmap.buffer[y * face->glyph->bitmap.pitch + (x / 8)] >> (7 - (x % 8))) & 1; + break; + case FT_PIXEL_MODE_GRAY: + //std::cerr << "num_grays " << face->glyph->bitmap.num_grays << '\n'; + //assert(face->glyph->bitmap.num_grays == 256); + level = face->glyph->bitmap.buffer[y * face->glyph->bitmap.pitch + x]; + level >>= (8 - bits_per_pixel); + break; + default: + assert(false); + break; + } + texture[texture_ix] |= level << (bits_per_pixel * texture_ix_mod); + } + } + + glyph_bitmap& bitmap = glyph->bitmap; + bitmap.x = byteswap(rect.x); + bitmap.y = byteswap(rect.y); + bitmap.width = byteswap(rect.width); + bitmap.height = byteswap(rect.height); + + glyph_metrics& metrics = glyph->metrics; + metrics.horiBearingX = byteswap(face->glyph->metrics.horiBearingX); + metrics.horiBearingY = byteswap(face->glyph->metrics.horiBearingY); + metrics.horiAdvance = byteswap(face->glyph->metrics.horiAdvance); + + return 0; +} + +enum { + start_hex = 1, + end_hex = 2, + pixel_size = 3, + target_endian = 4, + font_file_path = 5, + output_file_path = 6, + argv_length = 7 +}; + +struct window_curve_ix +load_all_positions(const FT_Face face, + const uint32_t start, + const uint32_t end, + glyph * glyphs, + uint8_t * texture + ) +{ + const uint32_t num_glyphs = (end - start) + 1; + struct rect rects[num_glyphs]; + + FT_Int32 load_flags = FT_LOAD_DEFAULT | FT_LOAD_TARGET_MODE(FT_RENDER_MODE_SDF) | FT_LOAD_RENDER; + FT_Render_Mode render_mode = FT_RENDER_MODE_SDF; + + // first, load all rectangles + for (uint32_t char_code = start; char_code <= end; char_code++) { + load_outline_char_bitmap_rect(face, + load_flags, + render_mode, + char_code, + rects[char_code - start]); + } + + // calculate a 2-dimensional packing for the rectangles + auto window_curve_ix = pack_all(rects, num_glyphs); + + const uint32_t bits_per_pixel = 8; + + // render all of the glyphs to the texture; + for (uint32_t i = 0; i < num_glyphs; i++) { + const uint32_t char_code = rects[i].char_code; + int32_t err = load_outline_char(face, + load_flags, + render_mode, + bits_per_pixel, + char_code, + &glyphs[char_code - start], + texture, + window_curve_ix.window.width, + rects[i]); + if (err < 0) assert(false); + } + + return window_curve_ix; +} + +int main(int argc, char *argv[]) +{ + FT_Library library; + FT_Face face; + FT_Error error; + + if (argc != argv_length) { + std::cerr << "usage: " << argv[0] << " [start-hex] [end-hex] [pixel-size] [target-endian] [font-file-path] [output-file-path]\n\n"; + std::cerr << "ex. 1: " << argv[0] << " 3000 30ff 30 0 little ipagp.ttf font.bin\n"; + std::cerr << "ex. 2: " << argv[0] << " 20 7f 30 1 big DejaVuSans.ttf font.bin\n"; + return -1; + } + + error = FT_Init_FreeType(&library); + if (error) { + std::cerr << "FT_Init_FreeType\n"; + return -1; + } + + error = FT_New_Face(library, argv[font_file_path], 0, &face); + if (error) { + std::cerr << "FT_New_Face\n"; + return -1; + } + + std::stringstream ss3; + int font_size; + ss3 << std::dec << argv[pixel_size]; + ss3 >> font_size; + std::cerr << "font_size: " << font_size << '\n'; + std::stringstream ss4; + + error = FT_Set_Pixel_Sizes(face, 0, font_size); + if (error) { + std::cerr << "FT_Set_Pixel_Sizes: " << FT_Error_String(error) << error << '\n'; + return -1; + } + + if (std::string(argv[target_endian]).compare("little") == 0) { + _target_endian = std::endian::little; + } else if (std::string(argv[target_endian]).compare("big") == 0) { + _target_endian = std::endian::big; + } else { + std::cerr << "unknown endian: " << argv[target_endian] << '\n'; + std::cerr << "expected one of: big, little\n"; + return -1; + } + + uint32_t start; + uint32_t end; + + std::stringstream ss1; + ss1 << std::hex << argv[start_hex]; + ss1 >> start; + std::stringstream ss2; + ss2 << std::hex << argv[end_hex]; + ss2 >> end; + + uint32_t num_glyphs = (end - start) + 1; + glyph glyphs[num_glyphs]; + uint8_t texture[max_texture_size]; + memset(texture, 0x00, max_texture_size); + + auto window_curve_ix = load_all_positions(face, start, end, glyphs, texture); + + uint32_t texture_size; + texture_size = window_curve_ix.window.width * window_curve_ix.window.height; + + font font; + font.first_char_code = byteswap(start); + font.last_char_code = byteswap(end); + font.face_metrics.height = byteswap(face->size->metrics.height); + font.face_metrics.max_advance = byteswap(face->size->metrics.max_advance); + font.glyph_count = byteswap(num_glyphs); + font.texture_width = byteswap(window_curve_ix.window.width); + font.texture_height = byteswap(window_curve_ix.window.height); + + std::cerr << "start: 0x" << std::hex << start << '\n'; + std::cerr << "end: 0x" << std::hex << end << '\n'; + std::cerr << "texture_width: " << std::dec << window_curve_ix.window.width << '\n'; + std::cerr << "texture_height: " << std::dec << window_curve_ix.window.height << '\n'; + + FILE * out = fopen(argv[output_file_path], "w"); + if (out == NULL) { + perror("fopen(w)"); + return -1; + } + + fwrite(reinterpret_cast(&font), (sizeof (font)), 1, out); + fwrite(reinterpret_cast(&glyphs[0]), (sizeof (glyph)), num_glyphs, out); + fwrite(reinterpret_cast(&texture[0]), (sizeof (uint8_t)), texture_size, out); + + fclose(out); +}