font_outline: remove all 1024/256/128 magic numbers

This fully threads both the real minimum size of the texture and the
dimensions of the texture through to the TA parameters.

This also removes spurious zero-area drawing commands (space
characters).
This commit is contained in:
Zack Buhman 2023-12-22 23:54:39 +08:00
parent 3b7e1eaef8
commit 8f0afc2868
11 changed files with 145 additions and 133 deletions

1
.gitignore vendored
View File

@ -12,3 +12,4 @@ __pycache__
*.gch *.gch
scramble scramble
cdi4dc cdi4dc
tools/ttf_outline

Binary file not shown.

View File

@ -29,60 +29,6 @@ struct vertex {
float v; float v;
}; };
/*
// screen space coordinates
const struct vertex quad_verticies[4] = {
{ 0.f, 64.f, 0.01f, 0.f, 1.f },
{ 0.f, 0.f, 0.01f, 0.f, 0.f },
{ 64.f, 0.f, 0.01f, 1.f, 0.f },
{ 64.f, 64.f, 0.01f, 1.f, 1.f, },
};
uint32_t transform(uint32_t * ta_parameter_buf)
{
auto parameter = ta_parameter_writer(ta_parameter_buf);
uint32_t texture_address = (offsetof (struct texture_memory_alloc, texture));
constexpr uint32_t base_color = 0xffffffff;
auto sprite = global_sprite(base_color);
sprite.parameter_control_word = para_control::para_type::sprite
| para_control::list_type::opaque
| obj_control::col_type::packed_color
| obj_control::texture
| obj_control::_16bit_uv;
sprite.tsp_instruction_word = tsp_instruction_word::src_alpha_instr::one
| tsp_instruction_word::dst_alpha_instr::zero
| tsp_instruction_word::fog_control::no_fog
| tsp_instruction_word::texture_u_size::_8 // 8px
| tsp_instruction_word::texture_v_size::_8; // 8px
sprite.texture_control_word = texture_control_word::pixel_format::_565
| texture_control_word::scan_order::twiddled
| texture_control_word::texture_address(texture_address / 8);
parameter.append<global_sprite>() = sprite;
parameter.append<vertex_sprite_type_1>() =
vertex_sprite_type_1(quad_verticies[0].x,
quad_verticies[0].y,
quad_verticies[0].z,
quad_verticies[1].x,
quad_verticies[1].y,
quad_verticies[1].z,
quad_verticies[2].x,
quad_verticies[2].y,
quad_verticies[2].z,
quad_verticies[3].x,
quad_verticies[3].y,
uv_16bit(quad_verticies[0].u, quad_verticies[0].v),
uv_16bit(quad_verticies[1].u, quad_verticies[1].v),
uv_16bit(quad_verticies[2].u, quad_verticies[2].v));
// curiously, there is no `dz` in vertex_sprite_type_1
// curiously, there is no `du_dv` in vertex_sprite_type_1
parameter.append<global_end_of_list>() = global_end_of_list();
return parameter.offset;
}
*/
const struct vertex strip_vertices[4] = { const struct vertex strip_vertices[4] = {
// [ position ] [ uv coordinates ] // [ position ] [ uv coordinates ]
{ 0.f, 1.f, 0.f, 0.f, 1.f, }, { 0.f, 1.f, 0.f, 0.f, 1.f, },
@ -93,7 +39,9 @@ const struct vertex strip_vertices[4] = {
constexpr uint32_t strip_length = (sizeof (strip_vertices)) / (sizeof (struct vertex)); constexpr uint32_t strip_length = (sizeof (strip_vertices)) / (sizeof (struct vertex));
uint32_t transform(ta_parameter_writer& parameter, uint32_t transform(ta_parameter_writer& parameter,
const uint32_t first_char_code, const glyph * glyphs, const uint32_t first_char_code,
const uint32_t texture_width, uint32_t texture_height,
const glyph * glyphs,
const char * s, const uint32_t len, const char * s, const uint32_t len,
const uint32_t y_offset) const uint32_t y_offset)
{ {
@ -102,6 +50,13 @@ uint32_t transform(ta_parameter_writer& parameter,
uint32_t advance = 0; // in 26.6 fixed-point uint32_t advance = 0; // in 26.6 fixed-point
for (uint32_t string_ix = 0; string_ix < len; string_ix++) { for (uint32_t string_ix = 0; string_ix < len; string_ix++) {
char c = s[string_ix];
auto& glyph = glyphs[c - first_char_code];
if (glyph.bitmap.width == 0 || glyph.bitmap.height == 0) {
advance += glyph.metrics.horiAdvance;
continue;
}
auto polygon = global_polygon_type_0(texture_address); auto polygon = global_polygon_type_0(texture_address);
polygon.parameter_control_word = para_control::para_type::polygon_or_modifier_volume polygon.parameter_control_word = para_control::para_type::polygon_or_modifier_volume
| para_control::list_type::opaque | para_control::list_type::opaque
@ -111,17 +66,14 @@ uint32_t transform(ta_parameter_writer& parameter,
polygon.tsp_instruction_word = tsp_instruction_word::src_alpha_instr::one polygon.tsp_instruction_word = tsp_instruction_word::src_alpha_instr::one
| tsp_instruction_word::dst_alpha_instr::zero | tsp_instruction_word::dst_alpha_instr::zero
| tsp_instruction_word::fog_control::no_fog | tsp_instruction_word::fog_control::no_fog
| tsp_instruction_word::texture_u_size::_128 | tsp_instruction_word::texture_u_size::from_int(texture_width)
| tsp_instruction_word::texture_v_size::_256; | tsp_instruction_word::texture_v_size::from_int(texture_height);
polygon.texture_control_word = texture_control_word::pixel_format::_8bpp_palette polygon.texture_control_word = texture_control_word::pixel_format::_8bpp_palette
| texture_control_word::scan_order::twiddled | texture_control_word::scan_order::twiddled
| texture_control_word::texture_address(texture_address / 8); | texture_control_word::texture_address(texture_address / 8);
parameter.append<global_polygon_type_0>() = polygon; parameter.append<global_polygon_type_0>() = polygon;
char c = s[string_ix];
auto& glyph = glyphs[c - first_char_code];
for (uint32_t i = 0; i < strip_length; i++) { for (uint32_t i = 0; i < strip_length; i++) {
bool end_of_strip = i == strip_length - 1; bool end_of_strip = i == strip_length - 1;
@ -142,8 +94,8 @@ uint32_t transform(ta_parameter_writer& parameter,
v *= glyph.bitmap.height; v *= glyph.bitmap.height;
u += glyph.bitmap.x; u += glyph.bitmap.x;
v += glyph.bitmap.y; v += glyph.bitmap.y;
u = u / 128.f; u = u / static_cast<float>(texture_width);
v = v / 256.f; v = v / static_cast<float>(texture_height);
parameter.append<vertex_polygon_type_3>() = parameter.append<vertex_polygon_type_3>() =
vertex_polygon_type_3(x, y, z, vertex_polygon_type_3(x, y, z,
@ -183,16 +135,22 @@ void inflate_font(const uint32_t * src, const uint32_t size)
} }
} }
template <int C>
void palette_data() void palette_data()
{ {
static_assert(C >= 2);
constexpr int increment = 256 / C;
holly.PAL_RAM_CTRL = pal_ram_ctrl::pixel_format::rgb565; holly.PAL_RAM_CTRL = pal_ram_ctrl::pixel_format::rgb565;
// palette of 256 greys // generate a palette with `C` shades of grey,
for (int i = 0; i < 256; i++) { // ranging in intensity from rgb565(0, 0, 0) to rgb565(31, 63, 31)
holly.PALETTE_RAM[i] = ((i >> 3) << 11) for (int i = 0; i < 256; i += increment) {
| ((i >> 2) << 5) holly.PALETTE_RAM[i / increment] = ((i >> 3) << 11)
| ((i >> 3) << 0); | ((i >> 2) << 5)
| ((i >> 3) << 0);
} }
holly.PALETTE_RAM[255] = 0xffff;
} }
uint32_t _ta_parameter_buf[((32 * 10 * 17) + 32) / 4]; uint32_t _ta_parameter_buf[((32 * 10 * 17) + 32) / 4];
@ -205,6 +163,7 @@ void main()
auto glyphs = reinterpret_cast<const struct glyph *>(&font[1]); auto glyphs = reinterpret_cast<const struct glyph *>(&font[1]);
auto texture = reinterpret_cast<const uint32_t *>(&glyphs[font->glyph_count]); auto texture = reinterpret_cast<const uint32_t *>(&glyphs[font->glyph_count]);
/*
serial::integer<uint32_t>(font->first_char_code); serial::integer<uint32_t>(font->first_char_code);
serial::integer<uint32_t>(font->glyph_count); serial::integer<uint32_t>(font->glyph_count);
serial::integer<uint32_t>(font->glyph_height); serial::integer<uint32_t>(font->glyph_height);
@ -213,10 +172,11 @@ void main()
serial::character('\n'); serial::character('\n');
serial::integer<uint32_t>(((uint32_t)glyphs) - ((uint32_t)font)); serial::integer<uint32_t>(((uint32_t)glyphs) - ((uint32_t)font));
serial::integer<uint32_t>(((uint32_t)texture) - ((uint32_t)font)); serial::integer<uint32_t>(((uint32_t)texture) - ((uint32_t)font));
*/
uint32_t texture_size = font->texture_width * font->texture_height; uint32_t texture_size = font->max_z_curve_ix + 1;
inflate_font(texture, texture_size); inflate_font(texture, texture_size);
palette_data(); palette_data<256>();
// The address of `ta_parameter_buf` must be a multiple of 32 bytes. // The address of `ta_parameter_buf` must be a multiple of 32 bytes.
// This is mandatory for ch2-dma to the ta fifo polygon converter. // This is mandatory for ch2-dma to the ta fifo polygon converter.
@ -256,13 +216,19 @@ void main()
auto parameter = ta_parameter_writer(ta_parameter_buf); auto parameter = ta_parameter_writer(ta_parameter_buf);
transform(parameter, font->first_char_code, glyphs, transform(parameter,
font->first_char_code,
font->texture_width, font->texture_height,
glyphs,
ana, 17, ana, 17,
0); font->glyph_height * 0);
transform(parameter, font->first_char_code, glyphs, transform(parameter,
font->first_char_code,
font->texture_width, font->texture_height,
glyphs,
cabal, 26, cabal, 26,
font->glyph_height); font->glyph_height * 1);
parameter.append<global_end_of_list>() = global_end_of_list(); parameter.append<global_end_of_list>() = global_end_of_list();

View File

@ -34,6 +34,7 @@ struct font {
uint16_t glyph_height; uint16_t glyph_height;
uint16_t texture_width; uint16_t texture_width;
uint16_t texture_height; uint16_t texture_height;
uint32_t max_z_curve_ix;
} __attribute__ ((packed)); } __attribute__ ((packed));
static_assert((sizeof (font)) == ((sizeof (uint32_t)) * 3)); static_assert((sizeof (font)) == ((sizeof (uint32_t)) * 4));

View File

@ -109,6 +109,20 @@ namespace tsp_instruction_word {
constexpr uint32_t _256 = 5 << 3; constexpr uint32_t _256 = 5 << 3;
constexpr uint32_t _512 = 6 << 3; constexpr uint32_t _512 = 6 << 3;
constexpr uint32_t _1024 = 7 << 3; constexpr uint32_t _1024 = 7 << 3;
constexpr uint32_t from_int(uint32_t n) {
switch (n) {
default: [[fallthrough]];
case 8: return _8;
case 16: return _16;
case 32: return _32;
case 64: return _64;
case 128: return _128;
case 256: return _256;
case 512: return _512;
case 1024: return _1024;
}
}
} }
namespace texture_v_size { namespace texture_v_size {
@ -120,6 +134,20 @@ namespace tsp_instruction_word {
constexpr uint32_t _256 = 5 << 0; constexpr uint32_t _256 = 5 << 0;
constexpr uint32_t _512 = 6 << 0; constexpr uint32_t _512 = 6 << 0;
constexpr uint32_t _1024 = 7 << 0; constexpr uint32_t _1024 = 7 << 0;
constexpr uint32_t from_int(uint32_t n) {
switch (n) {
default: [[fallthrough]];
case 8: return _8;
case 16: return _16;
case 32: return _32;
case 64: return _64;
case 128: return _128;
case 256: return _256;
case 512: return _512;
case 1024: return _1024;
}
}
} }
} }

View File

@ -1,5 +0,0 @@
#pragma once
#include <cstdint>
uint32_t pack_all(struct rect * rects, const uint32_t num_rects);

View File

@ -1,15 +1,16 @@
#include <cassert> #include <cassert>
#include <cstdint> #include <cstdint>
#include <compare> #include <compare>
#include <iostream>
#include "insertion_sort.hpp" #include "insertion_sort.hpp"
#include "rect.hpp" #include "rect.hpp"
#include "../twiddle.hpp" #include "../twiddle.hpp"
#include <iostream> #include "2d_pack.hpp"
struct size { struct size {
uint32_t width; uint16_t width;
uint32_t height; uint16_t height;
}; };
constexpr struct size max_texture = {1024, 1024}; constexpr struct size max_texture = {1024, 1024};
@ -36,9 +37,9 @@ inline bool area_valid(const uint8_t texture[max_texture.height][max_texture.wid
return true; return true;
} }
bool pack_into(uint8_t texture[max_texture.height][max_texture.width], uint32_t pack_into(uint8_t texture[max_texture.height][max_texture.width],
struct size& window, struct size& window,
struct rect& rect) struct rect& rect)
{ {
uint32_t z_curve_ix = 0; uint32_t z_curve_ix = 0;
@ -52,7 +53,7 @@ bool pack_into(uint8_t texture[max_texture.height][max_texture.width],
auto [x_offset, y_offset] = twiddle::from_ix(z_curve_ix); auto [x_offset, y_offset] = twiddle::from_ix(z_curve_ix);
if (x_offset >= window.width and y_offset >= window.height) { if (x_offset >= window.width and y_offset >= window.height) {
std::cerr << z_curve_ix << ' ' << window.width << ' ' << window.height << '\n'; //std::cerr << z_curve_ix << ' ' << window.width << ' ' << window.height << '\n';
assert(window.width < max_texture.width || window.height < max_texture.height); assert(window.width < max_texture.width || window.height < max_texture.height);
if (window.width == window.height) { window.height *= 2; } if (window.width == window.height) { window.height *= 2; }
else { window.width *= 2; } else { window.width *= 2; }
@ -76,15 +77,19 @@ bool pack_into(uint8_t texture[max_texture.height][max_texture.width],
rect.x = x_offset; rect.x = x_offset;
rect.y = y_offset; rect.y = y_offset;
return true; return twiddle::from_xy(rect.x + rect.width - 1,
rect.y + rect.height - 1);
} else { } else {
z_curve_ix += 1; z_curve_ix += 1;
continue; continue;
} }
} }
assert(false);
} }
uint32_t pack_all(struct rect * rects, const uint32_t num_rects) struct window_curve_ix
pack_all(struct rect * rects, const uint32_t num_rects)
{ {
uint8_t texture[max_texture.height][max_texture.width] = { 0 }; uint8_t texture[max_texture.height][max_texture.width] = { 0 };
size window = {1, 1}; size window = {1, 1};
@ -92,22 +97,16 @@ uint32_t pack_all(struct rect * rects, const uint32_t num_rects)
// sort all rectangles by size // sort all rectangles by size
insertion_sort(rects, num_rects); insertion_sort(rects, num_rects);
uint32_t max_x = 0; uint32_t max_z_curve_ix = 0;
uint32_t max_y = 0;
for (uint32_t i = 0; i < num_rects; i++) { for (uint32_t i = 0; i < num_rects; i++) {
std::cerr << "pack " << i << '\n'; uint32_t z_curve_ix = pack_into(texture, window, rects[i]);
bool packed = pack_into(texture, window, rects[i]); //std::cerr << "z_curve_ix " << z_curve_ix << '\n';
if (packed) { if (z_curve_ix > max_z_curve_ix)
const uint32_t x = rects[i].x + rects[i].width - 1; max_z_curve_ix = z_curve_ix;
const uint32_t y = rects[i].y + rects[i].height - 1;
if (x > max_x) max_x = x;
if (y > max_y) max_y = y;
}
} }
const uint32_t curve_ix = twiddle::from_xy(max_x, max_y); std::cerr << "window size: " << window.width << ' ' << window.height << '\n';
std::cerr << "max xy " << max_x << ' ' << max_y << '\n'; std::cerr << "max_z_curve_ix: " << max_z_curve_ix << '\n';
std::cerr << "curve_ix " << curve_ix << '\n'; return {window.width, window.height, max_z_curve_ix};
return curve_ix;
} }

14
tools/2d_pack.hpp Normal file
View File

@ -0,0 +1,14 @@
#pragma once
#include <cstdint>
struct window_curve_ix {
struct {
uint16_t width;
uint16_t height;
} window;
uint32_t max_z_curve_ix;
};
struct window_curve_ix
pack_all(struct rect * rects, const uint32_t num_rects);

View File

@ -4,7 +4,7 @@ CXXFLAGS = -std=c++23
CFLAGS += $(shell pkg-config --cflags freetype2) CFLAGS += $(shell pkg-config --cflags freetype2)
LDFLAGS = $(shell pkg-config --libs freetype2) LDFLAGS = $(shell pkg-config --libs freetype2)
all: ttf-outline all: ttf_outline
%.o: %.cpp %.o: %.cpp
$(CXX) $(CFLAGS) $(CXXFLAGS) -c $< -o $@ $(CXX) $(CFLAGS) $(CXXFLAGS) -c $< -o $@
@ -12,10 +12,10 @@ all: ttf-outline
%: %.o %: %.o
$(CXX) $(LDFLAGS) $^ -o $@ $(CXX) $(LDFLAGS) $^ -o $@
ttf-outline: ttf-outline.o 2d-pack.o ttf_outline: ttf_outline.o 2d_pack.o
clean: clean:
rm -f *.o ttf-convert ttf-bitmap rm -f *.o ttf_outline
.SUFFIXES: .SUFFIXES:
.INTERMEDIATE: .INTERMEDIATE:

View File

@ -10,11 +10,14 @@
#include "../font/font.hpp" #include "../font/font.hpp"
#include "rect.hpp" #include "rect.hpp"
#include "2d-pack.hpp" #include "2d_pack.hpp"
#include "../twiddle.hpp" #include "../twiddle.hpp"
std::endian _target_endian; std::endian _target_endian;
constexpr uint32_t max_texture_dim = 1024;
constexpr uint32_t max_texture_size = max_texture_dim * max_texture_dim;
uint32_t byteswap(const uint32_t n) uint32_t byteswap(const uint32_t n)
{ {
if (std::endian::native != _target_endian) { if (std::endian::native != _target_endian) {
@ -63,7 +66,7 @@ load_outline_char(const FT_Face face,
return -1; return -1;
} }
std::cerr << "size " << face->glyph->bitmap.rows << ' ' << face->glyph->bitmap.width << '\n'; //std::cerr << "size " << face->glyph->bitmap.rows << ' ' << face->glyph->bitmap.width << '\n';
//assert(face->glyph->format == FT_GLYPH_FORMAT_OUTLINE); //assert(face->glyph->format == FT_GLYPH_FORMAT_OUTLINE);
@ -81,19 +84,19 @@ load_outline_char(const FT_Face face,
assert(face->glyph->bitmap.width == rect.width); assert(face->glyph->bitmap.width == rect.width);
assert(face->glyph->bitmap.rows == rect.height); assert(face->glyph->bitmap.rows == rect.height);
std::cerr << "num_grays " << face->glyph->bitmap.num_grays << '\n'; //std::cerr << "num_grays " << face->glyph->bitmap.num_grays << '\n';
switch (face->glyph->bitmap.num_grays) { switch (face->glyph->bitmap.num_grays) {
case 2: case 2:
assert(false); assert(false);
break; break;
case 256: case 256:
std::cerr << "rxy " << rect.x << ' ' << rect.y << '\n'; //std::cerr << "rxy " << rect.x << ' ' << rect.y << '\n';
std::cerr << "rwh " << rect.width << ' ' << rect.height << '\n'; //std::cerr << "rwh " << rect.width << ' ' << rect.height << '\n';
for (uint32_t y = 0; y < rect.height; y++) { for (uint32_t y = 0; y < rect.height; y++) {
for (uint32_t x = 0; x < rect.width; x++) { for (uint32_t x = 0; x < rect.width; x++) {
uint32_t texture_ix = (rect.y + y) * 1024 + (rect.x + x); uint32_t texture_ix = (rect.y + y) * max_texture_dim + (rect.x + x);
assert(texture_ix < 1024 * 1024); assert(texture_ix < max_texture_size);
texture[texture_ix] = face->glyph->bitmap.buffer[y * face->glyph->bitmap.pitch + x]; texture[texture_ix] = face->glyph->bitmap.buffer[y * face->glyph->bitmap.pitch + x];
} }
} }
@ -127,17 +130,18 @@ enum {
argv_length = 7 argv_length = 7
}; };
void load_all_positions(const FT_Face face, struct window_curve_ix
const uint32_t start, load_all_positions(const FT_Face face,
const uint32_t end, const uint32_t start,
glyph * glyphs, const uint32_t end,
uint32_t * texture glyph * glyphs,
) uint32_t * texture
)
{ {
const uint32_t num_glyphs = (end - start) + 1; const uint32_t num_glyphs = (end - start) + 1;
struct rect rects[num_glyphs]; struct rect rects[num_glyphs];
uint8_t temp[1024 * 1024]; uint8_t temp[max_texture_size];
// first, load all rectangles // first, load all rectangles
for (uint32_t char_code = start; char_code <= end; char_code++) { for (uint32_t char_code = start; char_code <= end; char_code++) {
@ -145,7 +149,7 @@ void load_all_positions(const FT_Face face,
} }
// calculate a 2-dimensional packing for the rectangles // calculate a 2-dimensional packing for the rectangles
pack_all(rects, num_glyphs); auto window_curve_ix = pack_all(rects, num_glyphs);
// asdf // asdf
for (uint32_t i = 0; i < num_glyphs; i++) { for (uint32_t i = 0; i < num_glyphs; i++) {
@ -159,8 +163,11 @@ void load_all_positions(const FT_Face face,
} }
twiddle::texture2<8>(texture, temp, twiddle::texture2<8>(texture, temp,
128, 256, window_curve_ix.window.width,
1024); window_curve_ix.window.height,
max_texture_dim);
return window_curve_ix;
} }
int main(int argc, char *argv[]) int main(int argc, char *argv[])
@ -210,8 +217,6 @@ int main(int argc, char *argv[])
return -1; return -1;
} }
std::cerr << "here\n";
uint32_t start; uint32_t start;
uint32_t end; uint32_t end;
@ -224,20 +229,24 @@ int main(int argc, char *argv[])
uint32_t num_glyphs = (end - start) + 1; uint32_t num_glyphs = (end - start) + 1;
glyph glyphs[num_glyphs]; glyph glyphs[num_glyphs];
uint32_t texture[(1024 * 1024) / 4]; uint32_t texture[max_texture_size / 4];
memset(texture, 0x00, 1024 * 1024); memset(texture, 0x00, max_texture_size);
auto window_curve_ix = load_all_positions(face, start, end, glyphs, texture);
font font; font font;
font.first_char_code = byteswap(start); font.first_char_code = byteswap(start);
font.glyph_count = byteswap(num_glyphs); font.glyph_count = byteswap(num_glyphs);
font.glyph_height = byteswap(face->size->metrics.height); font.glyph_height = byteswap(face->size->metrics.height);
font.texture_width = 128; font.texture_width = byteswap(window_curve_ix.window.width);
font.texture_height = 256; font.texture_height = byteswap(window_curve_ix.window.height);
font.max_z_curve_ix = byteswap(window_curve_ix.max_z_curve_ix);
load_all_positions(face, start, end, glyphs, texture);
std::cerr << "start: 0x" << std::hex << start << '\n'; std::cerr << "start: 0x" << std::hex << start << '\n';
std::cerr << "end: 0x" << std::hex << end << '\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';
std::cerr << "max_z_curve_ix: " << std::dec << window_curve_ix.max_z_curve_ix << '\n';
FILE * out = fopen(argv[output_file_path], "w"); FILE * out = fopen(argv[output_file_path], "w");
if (out == NULL) { if (out == NULL) {
@ -245,10 +254,9 @@ int main(int argc, char *argv[])
return -1; return -1;
} }
uint32_t texture_size = 128 * 256;
fwrite(reinterpret_cast<void*>(&font), (sizeof (font)), 1, out); fwrite(reinterpret_cast<void*>(&font), (sizeof (font)), 1, out);
fwrite(reinterpret_cast<void*>(&glyphs[0]), (sizeof (glyph)), num_glyphs, out); fwrite(reinterpret_cast<void*>(&glyphs[0]), (sizeof (glyph)), num_glyphs, out);
fwrite(reinterpret_cast<void*>(&texture[0]), (sizeof (uint8_t)), texture_size, out); fwrite(reinterpret_cast<void*>(&texture[0]), (sizeof (uint8_t)), window_curve_ix.max_z_curve_ix + 1, out);
fclose(out); fclose(out);
} }