From c7bfdc657803b13ac3b8b19adbb79de9db4e942b Mon Sep 17 00:00:00 2001 From: "A.Olokhtonov" Date: Sun, 11 Apr 2021 14:17:48 +0300 Subject: [PATCH] No artifacts! --- main.c | 657 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ ttf2.c | 684 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1341 insertions(+) create mode 100644 main.c create mode 100644 ttf2.c diff --git a/main.c b/main.c new file mode 100644 index 0000000..f501440 --- /dev/null +++ b/main.c @@ -0,0 +1,657 @@ +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#define ASSERT(expr) if (!expr) { fprintf(stderr, "[ASSERT] Assertion fail %s:%d\n", __FILE__, __LINE__); abort(); } + +#define MAX_INTERSECTIONS 16 +#define F32EPS 1e-5f +#define MAX_CONTOURS 16 + +#define PER_PIXEL 0 +#define PER_ROW 0 +#define DEBUG_SHOW_POINTS 1 + +/* +int total_hits = 0; + for (int yy = 1; yy <= oversample_y; ++yy) { + for (int xx = 1; xx <= oversample_x; ++xx) { + u32 ncross = intersect_glyph(g, scale, x + norm_x * xx , y + norm_y * yy, width); + if (ncross) { + ++total_hits; + } + } + } + + if (total_hits) { + u32 brightness = (256 - total_hits * 256 / (oversample_x * oversample_y)) * 0.99f; + pixels[(y - baseline_correction) * width + x] = 0xFF000000 | brightness << 16 | brightness << 8 | brightness; + } +*/ + +typedef int64_t s64; +typedef int32_t s32; +typedef int16_t s16; +typedef int8_t s8; + +typedef uint64_t u64; +typedef uint32_t u32; +typedef uint16_t u16; +typedef uint8_t u8; + +typedef float f32; +typedef double f64; + + +/************* TTF *************/ + +struct v2 { + int x; + int y; +}; + +struct v2f { + f32 x; + f32 y; +}; + +struct line { + struct v2f a; + struct v2f b; +}; + +struct line_contour { + int ncontours; + int from[MAX_CONTOURS]; + struct line *data; +}; + +struct font_buffer { + u8 *data; + u64 offset; + u64 size; +}; + +struct font_directory { + u32 cmap_offset; + u32 head_offset; + u32 hhea_offset; + u32 loca_offset; + u32 glyf_offset; + u32 hmtx_offset; + u32 maxp_offset; +}; + +union glyph_flag { + struct { + u8 on_curve : 1; + u8 x_short : 1; + u8 y_short : 1; + u8 repeat : 1; + u8 x_short_pos : 1; + u8 y_short_pos : 1; + u8 reserved1 : 1; + u8 reserved2 : 1; + }; + u8 flag; +}; + +enum compund_glyph_flag { + ARG_1_AND_2_ARE_WORDS = 0x1, + ARGS_ARE_XY_VALUES = 0x2, + ROUND_XY_TO_GRID = 0x4, + WE_HAVE_A_SCALE = 0x8, + + MORE_COMPONENTS = 0x20, + WE_HAVE_AN_X_AND_Y_SCALE = 0x40, + WE_HAVE_A_TWO_BY_TWO = 0x80, + WE_HAVE_INSTRUCTIONS = 0x100, + USE_MY_METRICS = 0x200, + OVERLAP_COMPOUND = 0x400 +}; + +struct maxp_table { + u16 max_component_points; + u16 max_component_contours; +}; + +struct head_table { + int itl_format; + int units_per_em; +}; + +struct hhea_table { + int ascent; + int descent; +}; + +struct glyph_point { + s16 x; + s16 y; + bool on_curve; +}; + +struct glyph_segment { + bool is_curve; + struct v2f p0; + struct v2f p1; + struct v2f p2; +}; + +struct glyph { + s16 xmin, ymin; + s16 xmax, ymax; + + u16 advance; + s16 lsb; + + u16 ncontours; + u16 *end_pts_of_contours; + struct glyph_point *points; +}; + +struct ttf_font { + struct font_buffer file; + + struct maxp_table maxp; + struct head_table head; + struct hhea_table hhea; + + char *name; + + int cmap_format; + + int glyf_offset; + int loca_offset; + int cmap_offset; + int hmtx_offset; +}; + +/*******************************/ + + +#include "ttf2.c" + +static Display *display; +static Window window; +static GC default_gc; +static XImage* xwindow_buffer; + +static f32 +abs_f32(f32 v) +{ + f32 result = (v > 0 ? v : -v); + return(result); +} + +static f32 +round_f32(f32 v) +{ + return(v); + +#if 0 + int towards_zero = (int) v; + f32 diff = abs_f32(v - towards_zero); + + if (diff >= 0.5f) { + return(v > 0 ? towards_zero + 1 : towards_zero - 1); + } else { + return(towards_zero); + } +#endif +} + +static int +scanline_intersects_line(f32 x, f32 y, struct v2f p0, struct v2f p1, f32 lasty) +{ + bool goes_up = (p0.y > p1.y); + f32 t1 = (y - p0.y) / (p1.y - p0.y); /* NOTE(aolo2): no horizontal lines by design */ + + if (t1 == 0) { + f32 x1 = p0.x + (p1.x - p0.x) * t1; + if (x1 > x) { + if ((lasty < p0.y) && (p0.y > p1.y)) return(0); + if ((lasty > p0.y) && (p0.y < p1.y)) return(0); + int result = (goes_up ? 1 : -1); + return(result); + } + } + + if (0 < t1 && t1 < 1.0f) { + f32 x1 = p0.x + (p1.x - p0.x) * t1; + if (x1 > x) { + int result = (goes_up ? 1 : -1); + return(result); + } + } + + return(0); +} + +static void +render_line(struct v2 from, struct v2 to, u32 *pixels, int width, int height, u32 color) +{ + if (from.x >= width) { + from.x = width - 1; + } + + if (to.x >= width) { + to.x = width - 1; + } + + if (from.y >= height) { + from.y = height - 1; + } + + if (to.y >= height) { + to.y = height - 1; + } + + if (from.y == to.y) { + if (to.x < from.x) { + int tmp = to.x; + to.x = from.x; + from.x = tmp; + } + + u32 *out = pixels + width * from.y + from.x; + // printf("%ld %ld -> %ld %ld\n", from.x, from.y, to.x, to.y); + + for (int x = from.x; x <= to.x; ++x) { + // printf("%d\n", x); + *out = color; + ++out; + } + } else if (from.x == to.x) { + if (to.y < from.y) { + int tmp = to.y; + to.y = from.y; + from.y = tmp; + } + + u32 *out = pixels + width * from.y + from.x; + + //printf("%ld %ld -> %ld %ld\n", from.x, from.y, to.x, to.y); + + for (int y = from.y; y <= to.y; ++y) { + out = pixels + width * y + from.x; + *out = color; + } + } else { + int x0 = from.x; + int y0 = from.y; + int x1 = to.x; + int y1 = to.y; + + int dx = abs(x1 - x0); + int sx = (x0 < x1 ? 1 : -1); + int dy = -abs(y1 - y0); + int sy = (y0 < y1 ? 1 : -1); + int err = dx+dy; + + while (0 <= x0 && x0 < width && 0 <= y0 && y0 < height) { + u32 *out = pixels + width * y0 + x0; + *out = color; + + if (x0 == x1 && y0 == y1) { + break; + } + + int e2 = 2 * err; + + if (e2 >= dy) { + err += dy; + x0 += sx; + } + + if (e2 <= dx) { + err += dx; + y0 += sy; + } + } + } +} + +static void +render_grid(int x_step, int y_step, u32 *pixels, int width, int height) +{ + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (x % x_step == 0 || y % y_step == 0) { + pixels[y * width + x] = 0xFFEEEEE; + } + } + } +} + +static int +intersect_glyph(struct line_contour *lines, f32 x, f32 y) +{ + int result = 0; + + for (int c = 0; c < lines->ncontours; ++c) { + int from = lines->from[c]; + int to = lines->from[c + 1]; + + for (int i = from; i < to; ++i) { + int lasti = (i > from ? i - 1 : to - 1); + f32 lasty = lines->data[lasti].a.y; + int r = scanline_intersects_line(x, y, lines->data[i].a, lines->data[i].b, lasty); + result += r; + } + } + + return(result); +} + +static void +_D_render_glyph_points(struct glyph g, f32 scale, u32 *pixels, int width, int height) +{ + int points_from = 0; + for (int c = 0; c < g.ncontours; ++c) { + for (int p = points_from; p < g.end_pts_of_contours[c] + 1; ++p) { + struct glyph_point gp = g.points[p]; + + int x1 = round_f32((gp.x - g.xmin) * scale); + int y1 = round_f32(gp.y * scale); + + int next = p + 1; + if (p == g.end_pts_of_contours[c]) { + next = points_from; + } + + int x2 = round_f32((g.points[next].x - g.xmin) * scale); + int y2 = round_f32(g.points[next].y * scale); + + render_line((struct v2) {x1, y1}, (struct v2) {x2, y2}, pixels, width, height, 0xFFFF00FF); + //printf("%d %d\n", g.points[p].x, g.points[p].y); + + //XPutImage(display, window, default_gc, xwindow_buffer, 0, 0, 0, 0, width, height); + + if (gp.on_curve) { + pixels[y1 * width + x1] = 0xFF0000FF; + } else { + pixels[y1 * width + x1] = 0xFFFFFF00; + } + } + + points_from = g.end_pts_of_contours[c] + 1; + } +} + +static void +render_glyph(struct glyph g, struct line_contour *lines, + struct ttf_font font, int px_size, u32 *pixels, int width, int height) +{ + f32 scale = (f32) px_size / ((f32) (font.hhea.ascent - font.hhea.descent)); + //int npoints = g.end_pts_of_contours[g.ncontours - 1] + 1; + + if (g.advance < 0) { + g.advance *= -1; + } + + int x0 = 0; // g.xmin * scale; + + int gwidth = (g.xmax - g.xmin) * scale + 2; + int gheight = (g.ymax - g.ymin) * scale + 2; + + pixels = pixels + width * 100 + 100; + +#if 1 + for (int y = 0; y < gheight; ++y) { + + //u32 ncross = intersect_glyph(g, scale, x0 - 999.0f, y + 0.5f, width, intersections, &nints); + + //if (ncross) { + //continue; + //} + + for (int x = x0; x < x0 + gwidth; ++x) { + u32 ncross = intersect_glyph(lines, x + 0.5f, y + 0.5f); + if (ncross) { + pixels[y * width + x] = 0x00; + } else { + pixels[y * width + x] = 0xFFFF0000; + } + +#if PER_PIXEL + XPutImage(display, window, default_gc, xwindow_buffer, 0, 0, 0, 0, width, height); +#endif + } + +#if PER_ROW + XPutImage(display, window, default_gc, xwindow_buffer, 0, 0, 0, 0, width, height); +#endif + } +#else + int oversample_x = 4; + int oversample_y = 4; + f32 norm_x = 1.0f / (oversample_x + 1); + f32 norm_y = 1.0f / (oversample_y + 1); + int nints; + + for (int y = 0; y < gheight; ++y) { + for (int x = x0; x < x0 + gwidth; ++x) { + + int total_hits = 0; + + for (int yy = 1; yy <= oversample_y; ++yy) { + for (int xx = 1; xx <= oversample_x; ++xx) { + u32 ncross = intersect_glyph(lines, nlines, x + norm_x * xx , y + norm_y * yy); + if (ncross) { + ++total_hits; + } + } + } + + if (total_hits) { + u32 brightness = (256 - total_hits * 256 / (oversample_x * oversample_y)) * 0.99f; + pixels[y * width + x] = 0xFF000000 | brightness << 16 | brightness << 8 | brightness; + } else { + //pixels[y * width + x - 1] = 0xFFFF0000; + } + } + } +#endif + +#if DEBUG_SHOW_POINTS + _D_render_glyph_points(g, scale, pixels, width, height); +#endif +} + +static void +outline_to_lines(struct glyph g, f32 scale, struct line_contour *dest, int *cnt) +{ + int nlines = 0; + int points_from = 0; + + for (int c = 0; c < g.ncontours; ++c) { + for (int p = points_from; p < g.end_pts_of_contours[c] + 1; ++p) { + struct glyph_point gp = g.points[p]; + + int nexti = (p + 1 < g.end_pts_of_contours[c] + 1 ? p + 1 : points_from); + struct glyph_point nextgp = g.points[nexti]; + + if (p == points_from && !gp.on_curve) { + continue; + } + + f32 x1 = (gp.x - g.xmin) * scale; + f32 y1 = gp.y * scale; + + f32 x2 = (nextgp.x - g.xmin) * scale; + f32 y2 = nextgp.y * scale; + + if (nextgp.on_curve) { + if (gp.y != nextgp.y) { + if (dest->data) { + dest->data[nlines].a = (struct v2f) { x1, y1 }; + dest->data[nlines].b = (struct v2f) { x2, y2 }; + } + ++nlines; + } + } else { + int nextnexti = (nexti + 1 < g.end_pts_of_contours[c] + 1 ? nexti + 1 : points_from); + struct glyph_point nextnextgp = g.points[nextnexti]; + + f32 x3 = (nextnextgp.x - g.xmin) * scale; + f32 y3 = nextnextgp.y * scale; + + if (gp.y != nextgp.y) { + if (dest->data) { + dest->data[nlines].a = (struct v2f) { x1, y1 }; + dest->data[nlines].b = (struct v2f) { x2, y2 }; + } + ++nlines; + } + + if (nextgp.y != nextnextgp.y) { + if (dest->data) { + dest->data[nlines].a = (struct v2f) { x2, y2 }; + dest->data[nlines].b = (struct v2f) { x3, y3 }; + } + ++nlines; + } + + ++p; + } + } + + dest->from[c + 1] = nlines; + + points_from = g.end_pts_of_contours[c] + 1; + } + + dest->ncontours = g.ncontours; + + if (cnt) { + *cnt = nlines; + } +} + +int +main(int argc, char **argv) +{ + if (argc != 2) { + fprintf(stderr, "Usage: %s filename.ttf\n", argv[0]); + return(1); + } + + display = XOpenDisplay(0); + Window root_window = DefaultRootWindow(display); + + int default_screen = DefaultScreen(display); + int screen_bit_depth = 24; + int width = 1280; + int height = 720; + + XVisualInfo visinfo = { 0 }; + XMatchVisualInfo(display, default_screen, screen_bit_depth, TrueColor, &visinfo); + + XSetWindowAttributes window_attr; + window_attr.bit_gravity = StaticGravity; + window_attr.background_pixel = 0; + window_attr.colormap = XCreateColormap(display, root_window, visinfo.visual, AllocNone); + window_attr.event_mask = StructureNotifyMask | KeyPressMask | KeyReleaseMask; + + unsigned long attribute_mask = CWBitGravity | CWBackPixel | CWColormap | CWEventMask; + + window = XCreateWindow(display, root_window, + 0, 0, + width, height, 0, + visinfo.depth, InputOutput, + visinfo.visual, attribute_mask, &window_attr); + + XGrabPointer(display, window, False, ButtonPressMask | PointerMotionMask, + GrabModeAsync, GrabModeAsync, None, None, CurrentTime); + + XSelectInput(display, window, KeyPress | KeyRelease | ButtonRelease | PointerMotionMask); + + XStoreName(display, window, "ttf"); + XMapWindow(display, window); + XFlush(display); + + Atom WM_DELETE_WINDOW = XInternAtom(display, "WM_DELETE_WINDOW", False); + XSetWMProtocols(display, window, &WM_DELETE_WINDOW, 1); + + int pixel_bits = 32; + int pixel_bytes = pixel_bits / 8; + int window_buffer_size = width * height * pixel_bytes; + void *hc_vram = malloc(window_buffer_size); + + xwindow_buffer = XCreateImage(display, visinfo.visual, visinfo.depth, ZPixmap, 0, hc_vram, width, height, + pixel_bits, 0); + + default_gc = DefaultGC(display, default_screen); + + struct ttf_font font = parse_ttf_file(argv[1], "Inter"); + printf("Loaded font\n"); + + //struct glyph g = get_outline(&font, 0x046C); + int codepoint = 0x0431; + struct glyph g = get_outline(&font, codepoint); + int nlines = 0; + + u32 px_size = 120; + u32 *pixels = hc_vram; + int t = 0; + + f32 scale = (f32) px_size / ((f32) (font.hhea.ascent - font.hhea.descent)); + + struct line_contour lines = { 0 }; + outline_to_lines(g, scale, &lines, &nlines); + lines.data = malloc(nlines * sizeof(struct line)); + outline_to_lines(g, scale, &lines, 0); + + memset(pixels, 0xFFFFFFFF, width * height * 4); + render_grid(10, 10, pixels, width, height); + + for (;;) { + + memset(pixels, 0xFFFFFFFF, width * height * 4); + render_grid(10, 10, pixels, width, height); + render_glyph(g, &lines, font, px_size, pixels, width, height); + XPutImage(display, window, default_gc, xwindow_buffer, 0, 0, 0, 0, width, height); + +#if 0 + //printf("%d\n", px_size); + ++px_size; + + scale = (f32) px_size / ((f32) (font.hhea.ascent - font.hhea.descent)); + free(lines.data); + lines.data = 0; + outline_to_lines(g, scale, &lines, &nlines); + lines.data = malloc(nlines * sizeof(struct line)); + outline_to_lines(g, scale, &lines, 0); + + if (px_size > 200) { + ++codepoint; + g = get_outline(&font, codepoint); + printf("Loaded codepoint %#x\n", codepoint); + px_size = 20; + } +#endif + + //sleep(1); + + ++t; + } + + return(0); +} \ No newline at end of file diff --git a/ttf2.c b/ttf2.c new file mode 100644 index 0000000..bdfb6fa --- /dev/null +++ b/ttf2.c @@ -0,0 +1,684 @@ +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); +} \ No newline at end of file