#include #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 DEBUG_SHOW_POINTS 0 /* 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 intersection { f32 x; int dir; }; enum rgb_channel { RED = 0x000000FF, GREEN = 0x0000FF00, BLUE = 0x00FF0000, }; 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; int line_gap; int max_advance; }; 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 int clamp_u32(u32 val, u32 cap) { u32 result = val; if (val > cap) result = cap; return(result); } 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 y, struct v2f p0, struct v2f p1, f32 lasty, f32 *vx) { 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 ((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); *vx = x1; return(result); } if (0 < t1 && t1 < 1.0f) { f32 x1 = p0.x + (p1.x - p0.x) * t1; *vx = x1; 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 y, struct intersection *intersections) { int nints = 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; f32 vx; int r = scanline_intersects_line(y, lines->data[i].a, lines->data[i].b, lasty, &vx); if (r) { intersections[nints].x = vx; intersections[nints].dir = r; ++nints; } } } return(nints); } 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 sort_intersections(struct intersection *intersections, int size) { bool swapped = true; while (swapped) { swapped = false; for (int i = 0; i < size - 1; ++i) { f32 x1 = intersections[i].x; f32 x2 = intersections[i + 1].x; if (x1 > x2) { struct intersection tmp = intersections[i]; intersections[i] = intersections[i + 1]; intersections[i + 1] = tmp; swapped = true; } } } } static void render_glyph(struct glyph g, struct line_contour *lines, f32 scale, u32 *pixels, int width, int height, int at_x, int at_y) { int gwidth = round_f32((g.xmax - g.xmin) * scale) + 1; int gheight = round_f32((g.ymax - g.ymin) * scale) + 1; int oversample_y = 4; f32 norm = 1.0f / (oversample_y + 1); struct intersection *intersections = malloc(lines->from[lines->ncontours] * sizeof(struct intersection)); for (int y = 0; y < gheight && (at_y + y < height); ++y) { for (int yy = 1; yy <= oversample_y; ++yy) { u32 ncross = intersect_glyph(lines, y + norm * yy, intersections); if (ncross) { sort_intersections(intersections, ncross); int state = 0; for (int i = 0; i < ncross - 1; ++i) { struct intersection inter = intersections[i]; struct intersection next_inter = intersections[i + 1]; state += inter.dir; if (state != 0) { f32 x0 = inter.x; f32 x1 = next_inter.x; int x_from = x0; int x_to = x1; int start_enter = (state - inter.dir == 0); int end_enter = (state + next_inter.dir != 0); u32 start_brightness; u32 end_brightness; if (start_enter) { start_brightness = (x_from + 1 - x0) * (255.99f / oversample_y); } else { start_brightness = (x0 - x_from) * (255.99f / oversample_y); } if (end_enter) { end_brightness = (x_to + 1 - x1) * (255.99f / oversample_y); } else { end_brightness = (x1 - x_to) * (255.99f / oversample_y); } for (int x = x_from; x < x_to; ++x) { pixels[(y + at_y) * width + (at_x + x)] += 255.99f / oversample_y; pixels[(y + at_y) * width + (at_x + x)] = clamp_u32(pixels[(y + at_y) * width + (at_x + x)], 255); } u32 new_start_brightness = clamp_u32(pixels[(y + at_y) * width + (at_x + x_from)] + start_brightness, 255); u32 new_end_brightness = clamp_u32(pixels[(y + at_y) * width + (at_x + x_to)] + end_brightness, 255); pixels[(y + at_y) * width + (at_x + x_from)] = new_start_brightness; pixels[(y + at_y) * width + (at_x + x_to)] = new_end_brightness; } } } } } #if 0 for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { if (pixels[y * width + x] <= 255) { u32 brightness = 255 - pixels[y * width + x]; u32 white = 0xFF000000 | brightness << 16 | brightness << 8 | brightness; pixels[y * width + x] = white; } } } #endif free(intersections); } static void outline_to_lines(struct glyph g, f32 scale, struct line_contour *dest, int *cnt) { int nlines = 0; int points_from = 0; int curve_segments = 5; 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 = round_f32((gp.x - g.xmin) * scale); f32 y1 = round_f32(gp.y * scale); f32 x2 = round_f32((nextgp.x - g.xmin) * scale); f32 y2 = round_f32(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 = round_f32((nextnextgp.x - g.xmin) * scale); f32 y3 = round_f32(nextnextgp.y * scale); /* P(t) = P0*t^2 + P1*2*t*(1-t) + P2*(1-t)^2 */ f32 t_step = 1.0f / curve_segments; f32 x_prev = x1; f32 y_prev = y1; /* s = 1 for exact beginning */ for (int s = 1; s <= curve_segments; ++s) { f32 t_now = t_step * s; f32 x_now; f32 y_now; if (s < curve_segments) { x_now = x3 * t_now * t_now + x2 * 2.0f * t_now * (1.0f - t_now) + x1 * (1.0f - t_now) * (1.0f - t_now); y_now = y3 * t_now * t_now + y2 * 2.0f * t_now * (1.0f - t_now) + y1 * (1.0f - t_now) * (1.0f - t_now); } else { /* For exact match between neighbours */ x_now = x3; y_now = y3; } if (abs_f32(y_now - y_prev) > F32EPS) { if (dest->data) { dest->data[nlines].a = (struct v2f) { x_prev, y_prev }; dest->data[nlines].b = (struct v2f) { x_now, y_now }; } ++nlines; } x_prev = x_now; y_prev = y_now; } ++p; } } dest->from[c + 1] = nlines; points_from = g.end_pts_of_contours[c] + 1; } dest->ncontours = g.ncontours; if (cnt) { *cnt = nlines; } } static void render_utf_string(struct ttf_font font, int px_size, u32 *pixels, u32 width, u32 height, wchar_t *string, int at_x, int at_y) { u32 offset_x = at_x; u32 offset_y = at_y; f32 scale = (f32) px_size / ((f32) (font.hhea.ascent - font.hhea.descent)); u32 len = wcslen(string); for (u32 i = 0; i < len; ++i) { u16 codepoint = string[i]; if (codepoint != ' ') { struct glyph g = get_outline(&font, codepoint); struct line_contour lines = { 0 }; int nlines = 0; outline_to_lines(g, scale, &lines, &nlines); lines.data = malloc(nlines * sizeof(struct line)); outline_to_lines(g, scale, &lines, 0); int baseline_correction = round_f32(scale * g.ymax); render_glyph(g, &lines, scale, pixels, width, height, offset_x + g.lsb * scale, offset_y - baseline_correction); offset_x += scale * g.advance; free(lines.data); } else { offset_x += px_size / 3; } //XPutImage(display, window, default_gc, xwindow_buffer, 0, 0, 0, 0, width, height); //sleep(1); } } 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"); u32 *pixels = hc_vram; int t = 0; for (;;) { memset(pixels, 0x00, width * height * 4); render_utf_string(font, 32, pixels, width, height, L"Привет, батя! Как поживаешь? Hello, Batya. Wazzaaap", 100, 100); render_utf_string(font, 20, pixels, width, height, L"QQQQqqqqq", 100, 200); XPutImage(display, window, default_gc, xwindow_buffer, 0, 0, 0, 0, width, height); //sleep(1); ++t; } return(0); }