You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
677 lines
18 KiB
677 lines
18 KiB
#include <stdint.h> |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <stdbool.h> |
|
|
|
#include <wchar.h> |
|
|
|
#include <assert.h> |
|
|
|
#include <unistd.h> |
|
#include <fcntl.h> |
|
#include <math.h> |
|
|
|
#include <X11/Xlib.h> |
|
#include <X11/Xutil.h> |
|
|
|
#include <sys/ioctl.h> |
|
#include <sys/mman.h> |
|
#include <sys/types.h> |
|
#include <sys/stat.h> |
|
|
|
#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 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; |
|
|
|
struct intersection *intersections = malloc(lines->from[lines->ncontours] * sizeof(struct intersection)); |
|
|
|
for (int y = 0; y < gheight && (at_y + y < height); ++y) { |
|
u32 ncross = intersect_glyph(lines, y, 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; |
|
|
|
for (int x = x0; x < x1; ++x) { |
|
pixels[(y + at_y) * width + (at_x + x)] = 0x00; |
|
} |
|
} |
|
} |
|
} |
|
|
|
#if DEBUG_SHOW_POINTS |
|
_D_render_glyph_points(g, scale, pixels, width, height); |
|
#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, 0xFFFFFFFF, width * height * 4); |
|
render_utf_string(font, 32, pixels, width, height, L"Привет, батя! Как поживаешь? Hello, Batya. Wazzaaap", 100, 100); |
|
render_utf_string(font, 32, 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); |
|
} |