static u8 read_8(struct font_buffer *buf) { u8 result = buf->data[buf->offset]; buf->offset += 1; return(result); } static s8 read_8s(struct font_buffer *buf) { s8 result = buf->data[buf->offset]; buf->offset += 1; return(result); } static u16 be16(u8 *buf) { u16 result = buf[0] << 8 | buf[1]; return(result); } static s16 be16s(u8 *buf) { s16 result = buf[0] << 8 | buf[1]; return(result); } static u16 read_be16(struct font_buffer *buf) { u16 result = be16(buf->data + buf->offset); buf->offset += 2; return(result); } static s16 read_be16s(struct font_buffer *buf) { s16 result = be16s(buf->data + buf->offset); buf->offset += 2; return(result); } static u32 be32(u8 *buf) { u32 result = ((u32) buf[0] << 24) | ((u32) buf[1] << 16) | ((u32) buf[2] << 8) | ((u32) buf[3]); return(result); } static u32 read_be32(struct font_buffer *buf) { u32 result = be32(buf->data + buf->offset); buf->offset += 4; return(result); } static f32 read_be214(struct font_buffer *buf) { f32 result = read_be16s(buf) / 16384.0f; return(result); } static struct font_buffer read_file(char *filename) { FILE *file = fopen(filename, "rb"); struct font_buffer result = { 0 }; ASSERT(file); if (file) { fseek(file, 0, SEEK_END); result.size = ftell(file); result.data = malloc(result.size); fseek(file, 0, SEEK_SET); fread(result.data, result.size, 1, file); fclose(file); } return(result); } static u32 get_glyph_index_format12(struct ttf_font *font, u16 codepoint) { u32 ngroups = be32(font->file.data + font->cmap_offset + 12); font->file.offset = font->cmap_offset + 16; // TODO: binsearch for (u32 g = 0; g < ngroups; ++g) { u32 start_charcode = read_be32(&font->file); u32 end_charcode = read_be32(&font->file); u32 start_glyphcode = read_be32(&font->file); if (start_charcode <= codepoint && codepoint <= end_charcode) { u32 glyph_index = start_glyphcode + (codepoint - start_charcode); return(glyph_index); } if (start_charcode > codepoint) { break; } } return(0); } static s16 get_glyph_index_format4(struct ttf_font *font, u16 codepoint) { int segcount_x2 = be16(font->file.data + font->cmap_offset + 6); u16 *end_codes = (u16 *) (font->file.data + font->cmap_offset + 16); u16 *start_codes = (u16 *) (font->file.data + font->cmap_offset + 16 + segcount_x2 + 2); u16 *id_deltas = (u16 *) (font->file.data + font->cmap_offset + 16 + segcount_x2 * 2 + 2); u16 *id_range_offset = (u16 *) (font->file.data + font->cmap_offset + 16 + segcount_x2 * 3 + 2); int index = -1; for (int i = 0; i < segcount_x2 / 2; ++i) { u16 end_code = be16((u8 *) (end_codes + i)); if (end_code >= codepoint) { index = i; break; } } if (index == -1) { return(0); } u16 start_code = be16((u8 *) (start_codes + index)); if (start_code <= codepoint) { u16 ir_offset = be16((u8 *) (id_range_offset + index)); if (ir_offset != 0) { u16 glyph_index = be16((u8 *) (id_range_offset + index + ir_offset / 2 + (codepoint - start_code))); if (!glyph_index) { return(0); } u16 id_delta = be16((u8 *) (id_deltas + index)); u16 result = (u16) (glyph_index + id_delta); return(result); } u16 id_delta = be16((u8 *) (id_deltas + index)); u16 result = (u16) (codepoint + id_delta); return(result); } return(0); } static u16 get_glyph_index_format6(struct ttf_font *font, u16 codepoint) { u16 first_code = be16(font->file.data + font->cmap_offset + 6); u16 entry_count = be16(font->file.data + font->cmap_offset + 8); u16 *glyph_index_array = (u16 *) (font->file.data + font->cmap_offset + 10); if (first_code <= codepoint && codepoint < first_code + entry_count) { u16 glyph_index = be16((u8 *) (glyph_index_array + (codepoint - first_code))); return(glyph_index); } return(0); } static u16 get_glyph_index_format0(struct ttf_font *font, u16 codepoint) { u8 *glyph_index_array = font->file.data + font->cmap_offset + 6; if (codepoint <= 0xFF) { u8 glyph_index = glyph_index_array[codepoint]; return(glyph_index); } return(0); } static u32 get_glyph_index(struct ttf_font *font, u16 codepoint) { u32 result = 0; switch (font->cmap_format) { case 0: { result = get_glyph_index_format0(font, codepoint); break; } case 12: { result = get_glyph_index_format12(font, codepoint); break; } case 6: { result = get_glyph_index_format6(font, codepoint); break; } case 4: { result = get_glyph_index_format4(font, codepoint); break; } default: { ASSERT(false); } } return(result); } static u32 get_glyph_offset(struct ttf_font *font, u32 glyph_index) { u32 offset = 0; u32 offset_next; if (font->head.itl_format == 1) { font->file.offset = font->loca_offset + glyph_index * 4; offset = read_be32(&font->file); offset_next = read_be32(&font->file); } else { font->file.offset = font->loca_offset + glyph_index * 2; offset = read_be16(&font->file); offset *= 2; offset_next = read_be16(&font->file); offset_next *= 2; } if (offset == offset_next) { return(0); } return(offset); } static s16 get_current_coordinate(struct font_buffer *font_file, int flag_combined) { s16 current_coordinate = 0; switch (flag_combined) { case 0: { current_coordinate = read_be16(font_file); break; } case 1: { current_coordinate = 0; break; } case 2: { current_coordinate = read_8(font_file); current_coordinate *= -1; break; } case 3: { current_coordinate = read_8(font_file); break; } } return(current_coordinate); } static void get_simple_glyph_points(struct font_buffer *font_file, u16 number_of_countours, struct glyph *result) { u16 *end_pts_of_contours = calloc(1, number_of_countours * sizeof(u16)); for (int c = 0; c < number_of_countours; ++c) { end_pts_of_contours[c] = read_be16(font_file); } // NOTE: skip instructions u16 instruction_length = read_be16(font_file); font_file->offset += instruction_length; int last_index = end_pts_of_contours[number_of_countours - 1]; union glyph_flag *flags = calloc(1, last_index + 1); struct glyph_point *points = malloc((last_index + 2) * 2 * sizeof(struct v2)); // NOTE: so that we can insert one point at the start if needed // points = points + 1; for (int i = 0; i < last_index + 1; ++i) { flags[i].flag = read_8(font_file); if (flags[i].repeat) { u8 repeat_count = read_8(font_file); while (repeat_count-- > 0) { i++; flags[i] = flags[i - 1]; } } } s16 prev_coordinate = 0; int start = 0; int head = 0; for (int c = 0; c < number_of_countours; ++c) { int end = end_pts_of_contours[c]; struct glyph_point first_point = { 0 }; for (int i = start; i < end + 1; ++i) { // NOTE: read x coordinates int flag_combined = (flags[i].x_short << 1) | flags[i].x_short_pos; s16 current_coordinate = get_current_coordinate(font_file, flag_combined); s16 read_x = current_coordinate + prev_coordinate; int prev = i - 1; if (prev >= start) { //prev = end; if (!flags[i].on_curve && !flags[prev].on_curve) { // NOTE: implicit on-curve point points[head].on_curve = true; points[head].x = (read_x + points[head - 1].x) / 2; ++head; } } points[head].on_curve = flags[i].on_curve; points[head].x = read_x; ++head; if (i == start) { // NOTE: first iteration could not have inserted an implicit point first_point = points[head - 1]; } prev_coordinate = read_x; } if (!flags[start].on_curve && !flags[end].on_curve) { points[head].on_curve = true; points[head].x = (first_point.x + points[head - 1].x) / 2; ++head; } start = end + 1; } prev_coordinate = 0; start = 0; head = 0; int added_points = 0; for (int c = 0; c < number_of_countours; ++c) { int end = end_pts_of_contours[c]; struct glyph_point first_point = { 0 }; for (int i = start; i < end + 1; ++i) { // NOTE: read y coordinates int flag_combined = (flags[i].y_short << 1) | flags[i].y_short_pos; s16 current_coordinate = get_current_coordinate(font_file, flag_combined); s16 read_y = current_coordinate + prev_coordinate; int prev = i - 1; if (prev >= start) { //prev = end; if (!flags[i].on_curve && !flags[prev].on_curve) { // NOTE: implicit on-curve point points[head].on_curve = true; points[head].y = (read_y + points[head - 1].y) / 2; ++head; ++added_points; } } points[head].on_curve = flags[i].on_curve; points[head].y = read_y; ++head; prev_coordinate = read_y; if (i == start) { // NOTE: first iteration could not have inserted an implicit point first_point = points[head - 1]; } } if (!flags[start].on_curve && !flags[end].on_curve) { points[head].on_curve = true; points[head].y = (first_point.y + points[head - 1].y) / 2; ++head; ++added_points; } start = end + 1; end_pts_of_contours[c] += added_points; } result->points = points; result->ncontours = number_of_countours; result->end_pts_of_contours = end_pts_of_contours; } static struct glyph get_glyph_outline(struct ttf_font *font, u32 glyph_index) { u32 glyph_offset = get_glyph_offset(font, glyph_index); struct font_buffer *font_file = &font->file; font_file->offset = font->glyf_offset + glyph_offset; struct glyph result = { 0 }; s16 number_of_countours = read_be16(font_file); result.xmin = read_be16(font_file); result.ymin = read_be16(font_file); result.xmax = read_be16(font_file); result.ymax = read_be16(font_file); if (number_of_countours > 0) { // NOTE: simple glyph get_simple_glyph_points(font_file, number_of_countours, &result); } else if (number_of_countours < 0) { // NOTE: compund glyph int head = 0; result.end_pts_of_contours = malloc(font->maxp.max_component_contours * sizeof(u16)); result.points = malloc(font->maxp.max_component_points * 2 * sizeof(struct glyph_point)); for (;;) { u16 flags = read_be16(font_file); u16 component_index = read_be16(font_file); f32 matrix[6] = {1, 0, 0, 1, 0, 0}; /* NOTE(aolo2): matrix stuff is highjacked from stb */ if (flags & ARG_1_AND_2_ARE_WORDS) { matrix[4] = read_be16(font_file); matrix[5] = read_be16(font_file); } else { matrix[4] = read_8s(font_file); matrix[5] = read_8s(font_file); } if (flags & WE_HAVE_A_SCALE) { f32 scale = read_be214(font_file); matrix[0] = scale; matrix[1] = 0; matrix[2] = 0; matrix[3] = scale; } else if (flags & WE_HAVE_AN_X_AND_Y_SCALE ) { f32 xscale = read_be214(font_file); f32 yscale = read_be214(font_file); matrix[0] = xscale; matrix[1] = 0; matrix[2] = 0; matrix[3] = yscale; } else if (flags & WE_HAVE_A_TWO_BY_TWO) { f32 xscale = read_be214(font_file); f32 scale01 = read_be214(font_file); f32 scale10 = read_be214(font_file); f32 yscale = read_be214(font_file); matrix[0] = xscale; matrix[1] = scale01; matrix[2] = scale10; matrix[3] = yscale; } f32 m = sqrtf(matrix[0] * matrix[0] + matrix[1] * matrix[1]); f32 n = sqrtf(matrix[2] * matrix[2] + matrix[3] * matrix[3]); u32 saved_offset = font_file->offset; struct glyph component = get_glyph_outline(font, component_index); font_file->offset = saved_offset; int startv = 0; for (int c = 0; c < component.ncontours; ++c) { int endv = component.end_pts_of_contours[c] + 1; for (int v = startv; v < endv; ++v) { s16 x = component.points[v].x; s16 y = component.points[v].y; result.points[head].x = m * (matrix[0] * x + matrix[2] * y + matrix[4]); result.points[head].y = n * (matrix[1] * x + matrix[3] * y + matrix[5]); result.points[head].on_curve = component.points[v].on_curve; ++head; } startv = endv; result.end_pts_of_contours[result.ncontours] = head - 1; ++result.ncontours; } if (!(flags & MORE_COMPONENTS)) { break; } } } else { // NOTE: space //advance = 0; // TODO } return(result); } static void get_hmtx(struct ttf_font *font, u32 glyph_index, struct glyph *dest) { struct font_buffer font_file = font->file; // TODO: monospaced font only has one record font_file.offset = font->hmtx_offset + glyph_index * 4; dest->advance = read_be16(&font_file); dest->lsb = read_be16s(&font_file); } static struct glyph get_outline(struct ttf_font *font, u16 codepoint) { u32 glyph_index = get_glyph_index(font, codepoint); struct glyph result = get_glyph_outline(font, glyph_index); get_hmtx(font, glyph_index, &result); #if 1 int ymax = result.ymax; //int ymin = result.ymin; for (int p = 0; p < result.end_pts_of_contours[result.ncontours - 1] + 1; ++p) { struct glyph_point *gp = result.points + p; gp->y = ymax - gp->y; //gp->y += ymin; } #endif return(result); } static u16 read_offset_table(struct font_buffer *file) { u16 num_tables = be16(file->data + 4); return(num_tables); } static struct font_directory read_font_directory(struct font_buffer *file) { struct font_directory result = { 0 }; u16 table_count = read_offset_table(file); for (int i = 0; i < table_count; ++i) { u32 tag = be32(file->data + 12 + 16 * i + 0); u32 offset = be32(file->data + 12 + 16 * i + 8); switch (tag) { case 0x636d6170: { /* cmap */ result.cmap_offset = offset; break; } case 0x6c6f6361: { /* loca */ result.loca_offset = offset; break; } case 0x68656164: { /* head */ result.head_offset = offset; break; } case 0x676c7966: { /* glyf */ result.glyf_offset = offset; break; } case 0x686d7478: { /* hmtx */ result.hmtx_offset = offset; break; } case 0x6d617870: { /* maxp */ result.maxp_offset = offset; break; } case 0x68686561: { /* hhea */ result.hhea_offset = offset; break; } } } return(result); } static void read_head(struct font_directory font_dir, struct ttf_font *font) { font->head.units_per_em = be16(font->file.data + font_dir.head_offset + 18); font->head.itl_format = be16(font->file.data + font_dir.head_offset + 50); } static void read_hhea(struct font_directory font_dir, struct ttf_font *font) { font->hhea.ascent = be16s(font->file.data + font_dir.hhea_offset + 4); font->hhea.descent = be16s(font->file.data + font_dir.hhea_offset + 6); } static void read_cmap(struct font_directory font_dir, struct ttf_font *font) { font->file.offset = font_dir.cmap_offset + 2; u16 num_tables = read_be16(&font->file); for (int st = 0; st < num_tables; ++st) { u16 platform_id = read_be16(&font->file); u16 platform_specific_id = read_be16(&font->file); u32 subtable_offset = read_be32(&font->file); if ((platform_id == 0 && (platform_specific_id == 3 || platform_specific_id == 4)) || (platform_id == 3 && ((platform_specific_id == 1 || platform_specific_id == 10)))) { font->cmap_format = be16(font->file.data + font_dir.cmap_offset + subtable_offset); font->cmap_offset = font_dir.cmap_offset + subtable_offset; } } } static void read_maxp(struct font_directory font_dir, struct ttf_font *font) { font->maxp.max_component_points = be16(font->file.data + font_dir.maxp_offset + 10); font->maxp.max_component_contours = be16(font->file.data + font_dir.maxp_offset + 12); } static struct ttf_font parse_ttf_file(char *filename, char *fontname) { struct ttf_font result = { 0 }; struct font_buffer font_file = read_file(filename); struct font_directory font_dir = read_font_directory(&font_file); result.file = font_file; result.glyf_offset = font_dir.glyf_offset; result.loca_offset = font_dir.loca_offset; result.hmtx_offset = font_dir.hmtx_offset; read_head(font_dir, &result); read_maxp(font_dir, &result); read_cmap(font_dir, &result); read_hhea(font_dir, &result); result.name = fontname; return(result); }