diff --git a/client/Makefile b/client/Makefile index 2f5b5a0..f200ecf 100644 --- a/client/Makefile +++ b/client/Makefile @@ -1,6 +1,6 @@ ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) CFLAGS = -I $(ROOT_DIR)/include -I/usr/local/include -O0 -g -Wall -Wextra -LDFLAGS = -lz -lglfw -lvulkan -ldl -Xlinker -rpath -Xlinker /opt/homebrew/lib +LDFLAGS = -lfreetype -lz -lglfw -lvulkan -ldl -Xlinker -rpath -Xlinker /opt/homebrew/lib SOURCES = src/main.c src/render.c src/vma.cpp src/pipeline.c src/command.c src/spng.c OBJECTS = $(addsuffix .o, $(basename $(SOURCES))) diff --git a/client/include/pipeline.h b/client/include/pipeline.h index 637602a..bd70e73 100644 --- a/client/include/pipeline.h +++ b/client/include/pipeline.h @@ -6,6 +6,9 @@ #include "vk_mem_alloc.h" #include "command.h" +#include "ft2build.h" +#include FT_FREETYPE_H + typedef struct ComputePipelineStruct { VkPipelineLayout layout; VkPipeline pipeline; @@ -60,21 +63,17 @@ typedef struct FontUniformStruct { uint32_t num_symbols; uint32_t width; uint32_t height; - uint32_t space_width; VkDeviceAddress symbol_list; } FontUniform; typedef struct SymbolInfoStruct { - uint32_t x; - uint32_t top; + int32_t top; + uint32_t left; uint32_t width; + uint32_t height; + uint32_t advance; } SymbolInfo; -typedef struct FontDataStruct { - FontUniform info; - uint16_t* codes; -} FontData; - typedef struct FontDescriptorStruct { VmaAllocation symbol_memory; VmaAllocation uniform_memory; @@ -130,6 +129,6 @@ typedef struct UIContextStruct { VkResult init_pipelines(VkDevice device, VmaAllocator allocator, VkExtent2D swapchain_extent, vec2 window_scale, VkRenderPass render_pass, Queue transfer_queue, VkCommandPool transfer_pool, UIContext* context); -VkResult create_text_descriptor(VkDevice device, VmaAllocator allocator, VkDescriptorSetLayout layout, VkDescriptorPool pool, FontData* font, SymbolInfo* symbols, uint32_t* atlas, VkCommandPool transfer_pool, Queue transfer_queue, FontDescriptor* descriptor); +VkResult load_font(VkDevice device, VmaAllocator allocator, VkDescriptorSetLayout layout, VkDescriptorPool pool,VkCommandPool transfer_pool, Queue transfer_queue, FT_Library library, const char* ttf_file, uint32_t size, uint32_t** charmap, FontDescriptor* descriptor); #endif diff --git a/client/shader_src/ui_text.comp b/client/shader_src/ui_text.comp index d7c8ad2..168021f 100644 --- a/client/shader_src/ui_text.comp +++ b/client/shader_src/ui_text.comp @@ -2,9 +2,11 @@ #extension GL_EXT_buffer_reference : require struct Symbol { - uint x; - uint top; + int top; + uint left; uint width; + uint height; + uint advance; }; struct Character { @@ -42,7 +44,6 @@ layout(set = 0, binding = 0) uniform Font { uint num_symbols; uint width; uint height; - uint space_width; SymbolList symbol_list; } font; @@ -74,8 +75,8 @@ void main() { float x = 0; for(uint i = 0; i < string.len; i++) { Symbol symbol = font.symbol_list.symbols[push.pointers.codes.codes[buffer_pos + i]]; - push.pointers.characters.characters[buffer_pos + i].pos = string.pos + vec3(x, symbol.top/font.height*string.size, 0); - x += symbol.width * string.size / font.height; + push.pointers.characters.characters[buffer_pos + i].pos = string.pos + vec3(x, 0, 0); + x += string.size*symbol.advance/font.width; push.pointers.characters.characters[buffer_pos + i].size = string.size; push.pointers.characters.characters[buffer_pos + i].color = string.color; push.pointers.characters.characters[buffer_pos + i].code = push.pointers.codes.codes[buffer_pos + i]; diff --git a/client/shader_src/ui_text.frag b/client/shader_src/ui_text.frag index 7a0c0d6..ec0421e 100644 --- a/client/shader_src/ui_text.frag +++ b/client/shader_src/ui_text.frag @@ -1,13 +1,14 @@ #version 450 -layout(set = 1, binding = 1) uniform sampler2D font; +layout(set = 1, binding = 1) uniform sampler2DArray font; layout(location = 0) in vec4 fragColor; layout(location = 1) in vec2 fragUV; +layout(location = 2) flat in uint code; layout(location = 0) out vec4 outColor; void main() { - outColor = fragColor * texture(font, fragUV); + outColor = fragColor * texture(font, vec3(fragUV, code)); } diff --git a/client/shader_src/ui_text.vert b/client/shader_src/ui_text.vert index 218a930..247ea52 100644 --- a/client/shader_src/ui_text.vert +++ b/client/shader_src/ui_text.vert @@ -2,9 +2,11 @@ #extension GL_EXT_buffer_reference : require struct Symbol { - uint x; - uint top; + int top; + uint left; uint width; + uint height; + uint advance; }; struct Character { @@ -30,7 +32,6 @@ layout(set = 1, binding = 0) uniform Font { uint num_symbols; uint width; uint height; - uint space_width; SymbolList symbol_list; } font; @@ -47,19 +48,20 @@ layout(location = 0) in vec2 inVertexPosition; layout(location = 0) out vec4 fragColor; layout(location = 1) out vec2 fragUV; +layout(location = 2) out uint code; void main() { Character character = push.pointers.characters.characters[gl_InstanceIndex]; Symbol symbol = font.symbol_list.symbols[character.code]; - float fragU = (inVertexPosition.x*symbol.width + symbol.x) / font.width; - float fragV = inVertexPosition.y + symbol.top / font.height; - float x = inVertexPosition.x * character.size * symbol.width / font.height; - float y = (inVertexPosition.y + float(symbol.top)/float(font.height)) * character.size; + float fragU = inVertexPosition.x * symbol.width/font.width; + float fragV = inVertexPosition.y * symbol.height/font.height; + float x = (inVertexPosition.x * symbol.width + symbol.left) * character.size / font.width; + float y = (inVertexPosition.y * symbol.height + symbol.top) * character.size / font.height; fragUV = vec2(fragU, fragV); - fragColor = character.color; gl_Position = ubo.screen * vec4(vec3(x, y, 0.0) + character.pos, 1.0); + code = character.code; } diff --git a/client/src/main.c b/client/src/main.c index fdc5149..d431677 100644 --- a/client/src/main.c +++ b/client/src/main.c @@ -4,7 +4,9 @@ #include "vk_mem_alloc.h" #include "vulkan/vk_enum_string_helper.h" #include "vulkan/vulkan_core.h" -#include "spng.h" + +#include "ft2build.h" +#include FT_FREETYPE_H ColoredRect colored_rect(float width, float height, float r, float g, float b, float a, float x, float y, float z) { ColoredRect rect = { @@ -15,89 +17,6 @@ ColoredRect colored_rect(float width, float height, float r, float g, float b, f return rect; } -VkResult load_font(const char* atlas_file, const char* metadata_file, FontData* font, SymbolInfo** symbols, uint32_t** image) { - FILE* atlas = fopen(atlas_file, "rb"); - if(atlas == NULL) { - return VK_ERROR_UNKNOWN; - } - - fseek(atlas, 0L, SEEK_END); - size_t buf_size = ftell(atlas); - void* buf = malloc(buf_size); - fseek(atlas, 0L, SEEK_SET); - size_t read = fread(buf, buf_size, 1, atlas); - if(read != 1) { - return VK_ERROR_UNKNOWN; - } - fclose(atlas); - - size_t out_size = 0; - spng_ctx *ctx = spng_ctx_new(0); - - int result = 0; - - result = spng_set_png_buffer(ctx, buf, buf_size); - if(result != SPNG_OK) { - return VK_ERROR_UNKNOWN; - } - result = spng_decoded_image_size(ctx, SPNG_FMT_RGBA8, &out_size); - if(result != SPNG_OK) { - return VK_ERROR_UNKNOWN; - } - - *image = malloc(out_size); - - result = spng_decode_image(ctx, *image, out_size, SPNG_FMT_RGBA8, 0); - if(result != SPNG_OK) { - return VK_ERROR_UNKNOWN; - } - - struct spng_ihdr ihdr; - result = spng_get_ihdr(ctx, &ihdr); - if(result != SPNG_OK) { - return VK_ERROR_UNKNOWN; - } - font->info.height = ihdr.height; - font->info.width = ihdr.width; - - spng_ctx_free(ctx); - free(buf); - - FILE* metadata = fopen(metadata_file, "rb"); - if(atlas == NULL) { - return VK_ERROR_UNKNOWN; - } - uint16_t symbol_count; - uint8_t ascent, descent; - fread(&symbol_count, 2, 1, metadata); - fread(&ascent, 1, 1, metadata); - fread(&descent, 1, 1, metadata); - - font->info.num_symbols = ntohs(symbol_count); - - *symbols = malloc(sizeof(SymbolInfo)*symbol_count); - font->codes = malloc(sizeof(uint16_t)*symbol_count); - - uint8_t width; - uint8_t top; - uint16_t code; - uint32_t x; - for(uint16_t i = 0; i < symbol_count; i++) { - fread(&code, 2, 1, metadata); - font->codes[i] = ntohs(code); - fread(&x, 4, 1, metadata); - (*symbols)[i].x = ntohl(x); - fread(&width, 1, 1, metadata); - (*symbols)[i].width = width; - fread(&top, 1, 1, metadata); - (*symbols)[i].top = top; - } - - fclose(metadata); - - return VK_SUCCESS; -} - typedef struct TextPointersMemoryStruct { VmaAllocation pointers_memory; VmaAllocation draw_memory; @@ -321,16 +240,14 @@ VkResult render_thread(GLFWwindow* window, RenderContext* render_context) { return result; } - FontData test_font = {}; - uint32_t* test_atlas; - SymbolInfo* test_symbols; - result = load_font("tools/test.png", "tools/test.meta", &test_font, &test_symbols, &test_atlas); - if(result != VK_SUCCESS) { - return result; + FT_Library library; + if(FT_Init_FreeType(&library) != FT_Err_Ok) { + return VK_ERROR_UNKNOWN; } - FontDescriptor test_font_descriptor; - result = create_text_descriptor(render_context->device, render_context->allocator, ui_context.font_layout, ui_context.font_pool, &test_font, test_symbols, test_atlas, render_context->transfer_pool, render_context->transfer_queue, &test_font_descriptor); + FontDescriptor test_font; + uint32_t* charmap; + result = load_font(render_context->device, render_context->allocator, ui_context.font_layout, ui_context.font_pool, render_context->transfer_pool, render_context->transfer_queue, library, "test.ttf", 16, &charmap, &test_font); if(result != VK_SUCCESS) { return result; } @@ -382,7 +299,7 @@ VkResult render_thread(GLFWwindow* window, RenderContext* render_context) { String* mapped_string = (String*)mapped; mapped_string->pos[0] = 200.0; mapped_string->pos[1] = 200.0; - mapped_string->pos[1] = 0.5; + mapped_string->pos[2] = 0.5; mapped_string->color[0] = 1.0; mapped_string->color[1] = 1.0; mapped_string->color[2] = 1.0; @@ -420,7 +337,7 @@ VkResult render_thread(GLFWwindow* window, RenderContext* render_context) { .colored_rects = colored_rect_buffer, .colored_rect_count = 3, - .font = test_font_descriptor, + .font = test_font, .chars_size = 10*sizeof(Character), .chars = text_pointers.characters_buffer, .string_count = 1, @@ -428,7 +345,6 @@ VkResult render_thread(GLFWwindow* window, RenderContext* render_context) { .string_draw = text_pointers.draw_buffer, .string_draw_clear = text_pointers.draw_clear_buffer, }; - fprintf(stderr, "GPU Buffer: 0x%llX\n", text_pointers_address); while(glfwWindowShouldClose(window) == 0) { glfwPollEvents(); diff --git a/client/src/pipeline.c b/client/src/pipeline.c index 683167d..3562f14 100644 --- a/client/src/pipeline.c +++ b/client/src/pipeline.c @@ -459,7 +459,79 @@ VkResult create_font_descriptor_pools(VkDevice device, uint32_t max_sets, VkDesc return VK_SUCCESS; } -VkResult create_text_descriptor(VkDevice device, VmaAllocator allocator, VkDescriptorSetLayout layout, VkDescriptorPool pool, FontData* font, SymbolInfo* symbols, uint32_t* atlas, VkCommandPool transfer_pool, Queue transfer_queue, FontDescriptor* descriptor) { +VkResult load_font(VkDevice device, VmaAllocator allocator, VkDescriptorSetLayout layout, VkDescriptorPool pool,VkCommandPool transfer_pool, Queue transfer_queue, FT_Library library, const char* ttf_file, uint32_t size, uint32_t** charmap, FontDescriptor* descriptor) { + FT_Face face; + + int error; + error = FT_New_Face(library, ttf_file, 0, &face); + if(error != FT_Err_Ok) { + return VK_ERROR_UNKNOWN; + } + + error = FT_Set_Pixel_Sizes(face, 0, size); + if(error != FT_Err_Ok) { + return VK_ERROR_UNKNOWN; + } + + uint32_t* tmp_charmap = malloc(sizeof(uint32_t)*face->num_glyphs); + SymbolInfo* symbols = malloc(sizeof(SymbolInfo)*face->num_glyphs); + FontUniform uniform; + + uint32_t glyph_index; + uint32_t max_height = 0; + uint32_t max_width = 0; + uint32_t symbol_count = 0; + uint32_t c; + + // TODO: worry about variants(that's why num_glyphs doesn't match symbol_count) + c = FT_Get_First_Char(face, &glyph_index); + for(uint32_t i = 0; i < face->num_glyphs; i++) { + if(glyph_index == 0) { + break; + } + FT_Load_Glyph(face, glyph_index, FT_LOAD_RENDER | FT_RENDER_MODE_MONO); + uint32_t width = face->glyph->bitmap.width; + uint32_t height = face->glyph->bitmap.rows; + tmp_charmap[i] = c; + symbols[i].width = width; + symbols[i].height = height; + symbols[i].left = face->glyph->bitmap_left; + symbols[i].top = ((face->bbox.yMax*size)/face->units_per_EM) - face->glyph->bitmap_top; + symbols[i].advance = face->glyph->advance.x*16/face->units_per_EM; + max_width = width > max_width ? width : max_width; + max_height = height > max_height ? height : max_height; + symbol_count += 1; + c = FT_Get_Next_Char(face, c, &glyph_index); + } + + uniform.width = max_width; + uniform.height = max_height; + uniform.num_symbols = symbol_count; + uint32_t image_size = max_width*max_height*sizeof(uint32_t); + uint32_t* images = malloc(image_size*symbol_count); + memset(images, 0x00, image_size*symbol_count); + *charmap = malloc(sizeof(uint32_t)*symbol_count); + memcpy(*charmap, tmp_charmap, sizeof(uint32_t)*symbol_count); + free(tmp_charmap); + + for(uint32_t i = 0; i < symbol_count; i++) { + glyph_index = FT_Get_Char_Index(face, (*charmap)[i]); + FT_Load_Glyph(face, glyph_index, FT_LOAD_RENDER | FT_RENDER_MODE_MONO); + for(uint32_t y = 0; y < face->glyph->bitmap.rows; y++) { + for(uint32_t x = 0; x < face->glyph->bitmap.width; x++) { + if(face->glyph->bitmap.buffer[y*face->glyph->bitmap.width+x] != 0) { + images[max_width*max_height*i + max_width*y + x] = 0xFFFFFFFF; + } + } + } + } + + + error = FT_Done_Face(face); + if(error != FT_Err_Ok) { + return VK_ERROR_UNKNOWN; + } + VkResult result; VkDescriptorSetAllocateInfo set_allocate_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, @@ -477,7 +549,7 @@ VkResult create_text_descriptor(VkDevice device, VmaAllocator allocator, VkDescr .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, - .size = sizeof(SymbolInfo)*font->info.num_symbols, + .size = sizeof(SymbolInfo)*uniform.num_symbols, }; VmaAllocationCreateInfo symbol_memory_info = { .usage = VMA_MEMORY_USAGE_GPU_ONLY, @@ -504,10 +576,10 @@ VkResult create_text_descriptor(VkDevice device, VmaAllocator allocator, VkDescr .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, .extent.depth = 1, - .extent.width = font->info.width, - .extent.height = font->info.height, + .extent.width = uniform.width, + .extent.height = uniform.height, .mipLevels = 1, - .arrayLayers = 1, + .arrayLayers = uniform.num_symbols, .format = VK_FORMAT_R8G8B8A8_SRGB, .tiling = VK_IMAGE_TILING_OPTIMAL, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, @@ -524,12 +596,11 @@ VkResult create_text_descriptor(VkDevice device, VmaAllocator allocator, VkDescr return result; } - uint32_t image_size = sizeof(uint32_t)*font->info.width*font->info.height; VkBufferCreateInfo staging_info = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, - .size = sizeof(FontUniform) + image_size + sizeof(SymbolInfo)*font->info.num_symbols, + .size = sizeof(FontUniform) + image_size*uniform.num_symbols + sizeof(SymbolInfo)*uniform.num_symbols, }; VmaAllocationCreateInfo staging_memory_info = { @@ -548,28 +619,30 @@ VkResult create_text_descriptor(VkDevice device, VmaAllocator allocator, VkDescr if(result != VK_SUCCESS) { return result; } - memcpy(mapped_staging + image_size + sizeof(FontUniform), symbols, sizeof(SymbolInfo)*font->info.num_symbols); + memcpy(mapped_staging + image_size*uniform.num_symbols + sizeof(FontUniform), symbols, sizeof(SymbolInfo)*uniform.num_symbols); VkBufferDeviceAddressInfo address_info = { .sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, .buffer = descriptor->symbols, }; - font->info.symbol_list = vkGetBufferDeviceAddress(device, &address_info); - memcpy(mapped_staging + image_size, &font->info, sizeof(FontUniform)); - memcpy(mapped_staging, atlas, image_size); + uniform.symbol_list = vkGetBufferDeviceAddress(device, &address_info); + memcpy(mapped_staging + image_size*uniform.num_symbols, &uniform, sizeof(FontUniform)); + memcpy(mapped_staging, images, image_size*uniform.num_symbols); vmaUnmapMemory(allocator, staging_memory); + free(images); + free(symbols); VkCommandBuffer command_buffer = command_begin_single(device, transfer_pool); VkBufferCopy uniform_copy_info = { .size = sizeof(FontUniform), - .srcOffset = image_size, + .srcOffset = image_size*uniform.num_symbols, .dstOffset = 0, }; vkCmdCopyBuffer(command_buffer, staging_buffer, descriptor->uniform, 1, &uniform_copy_info); VkBufferCopy symbol_copy_info = { - .size = sizeof(SymbolInfo)*font->info.num_symbols, - .srcOffset = image_size + sizeof(FontUniform), + .size = sizeof(SymbolInfo)*uniform.num_symbols, + .srcOffset = image_size*uniform.num_symbols + sizeof(FontUniform), .dstOffset = 0, }; vkCmdCopyBuffer(command_buffer, staging_buffer, descriptor->symbols, 1, &symbol_copy_info); @@ -583,14 +656,14 @@ VkResult create_text_descriptor(VkDevice device, VmaAllocator allocator, VkDescr .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .subresourceRange.levelCount = 1, - .subresourceRange.layerCount = 1, + .subresourceRange.layerCount = uniform.num_symbols, .srcAccessMask = 0, .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, }; vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, &first_barrier); VkBufferImageCopy image_copy = { - .imageSubresource.layerCount = 1, + .imageSubresource.layerCount = uniform.num_symbols, .imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .imageExtent = image_info.extent, }; @@ -605,7 +678,7 @@ VkResult create_text_descriptor(VkDevice device, VmaAllocator allocator, VkDescr .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .subresourceRange.levelCount = 1, - .subresourceRange.layerCount = 1, + .subresourceRange.layerCount = uniform.num_symbols, .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, }; @@ -620,11 +693,11 @@ VkResult create_text_descriptor(VkDevice device, VmaAllocator allocator, VkDescr VkImageViewCreateInfo view_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = descriptor->image, - .viewType = VK_IMAGE_VIEW_TYPE_2D, + .viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY, .format = VK_FORMAT_R8G8B8A8_SRGB, .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .layerCount = 1, + .layerCount = uniform.num_symbols, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, diff --git a/client/tools/ttf.py b/client/tools/ttf.py deleted file mode 100755 index 10e8bee..0000000 --- a/client/tools/ttf.py +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env python3 - -from fontTools.ttLib import TTFont -from fontTools.unicode import Unicode -from PIL import ImageDraw, Image, ImageFont -from os import mkdir -from argparse import ArgumentParser -from struct import pack - -parser = ArgumentParser() -parser.add_argument("ttf_file") -parser.add_argument("atlas") -parser.add_argument("metadata") - -args = parser.parse_args() - -# Add control characters, keep 0x00 for "unknown" -blacklist = set([ -0x08, -0x09, -0x0D, -0x1D, -0x20, -]) - -# Add other control characters -blacklist.update(set(range(0x80, 0xA0))) - -ttfont = TTFont(args.ttf_file) -keycodes = set() -for table in ttfont['cmap'].tables: - for keycode in table.cmap.keys(): - keycodes.add(keycode) - -keycodes = keycodes.difference(blacklist) - -font = ImageFont.truetype(args.ttf_file, size=16) -(ascent, descent) = font.getmetrics() - -widths = dict() -tops = dict() -for keycode in keycodes: - (left, top, right, bottom) = font.getbbox(chr(keycode)) - widths[keycode] = right-left - tops[keycode] = top - -height = ascent - descent -image = Image.new(mode="RGBA", size=(sum(widths.values()), height)) -draw = ImageDraw.Draw(image) - -x = 0 -keycode_list = list(keycodes) -keycode_list.sort() -for keycode in keycode_list: - draw.text((x, -tops[keycode]), chr(keycode), font=font) - x += widths[keycode] - print(f"{chr(keycode)} - {tops[keycode]}") - -image.save(args.atlas) - -metadata = bytearray(pack("!HBB", len(keycode_list), ascent, descent)) -idx = 0 -x = 0 -for keycode in keycode_list: - metadata += pack("!HIBB", keycode, x, widths[keycode], tops[keycode]) - x += widths[keycode] -with open(args.metadata, "wb") as metadata_file: - metadata_file.write(metadata)