# so many fucking hours wasted man i hate fonts so much # fuck bezier # fuck revo # fuck revel # fuck ttf # fuck lung # fuck me # im like 90% confident this works, but im like 10% confident its fucked fn ttf_mod(value, modulus) -> num do return value - math.floor(value / modulus) * modulus end fn ttf_u8(data, offset) -> num do return mem_read(data + offset, 1) end fn ttf_u16(data, offset) -> num do return mem_read(data + offset, 1) * 256 + mem_read(data + offset + 1, 1) end fn ttf_i16(data, offset) -> num do const value = ttf_u16(data, offset) if (value >= 32768) return value - 65536 return value end fn ttf_u32(data, offset) -> num do return ((mem_read(data + offset, 1) * 256 + mem_read(data + offset + 1, 1)) * 256 + mem_read(data + offset + 2, 1)) * 256 + mem_read(data + offset + 3, 1) end fn ttf_tag_eq(data, offset, tag) -> bool do for i in 0..4 do if (mem_read(data + offset + i, 1) != tag[i]:ascii()) return :false end return :true end fn ttf_table(data, tag) -> num do const count = ttf_u16(data, 4) for i in 0..count do const record = 12 + i * 16 if (ttf_tag_eq(data, record, tag)) return ttf_u32(data, record + 8) end return -1 end fn ttf_cmap_sub(data, cmap_off) -> num do const count = ttf_u16(data, cmap_off + 2) let best = -1 for i in 0..count do const record = cmap_off + 4 + i * 8 const platform = ttf_u16(data, record) const encoding = ttf_u16(data, record + 2) if (platform == 3 and (encoding == 1 or encoding == 0)) or platform == 0 do best = cmap_off + ttf_u32(data, record + 4) end end return best end fn ttf_glyph_of(data, subtable, code) -> num do if (ttf_u16(data, subtable) != 4) return 0 const segment_count = ttf_u16(data, subtable + 6) / 2 const end_base = subtable + 14 const start_base = end_base + segment_count * 2 + 2 const delta_base = start_base + segment_count * 2 const range_base = delta_base + segment_count * 2 for i in 0..segment_count do if code <= ttf_u16(data, end_base + i * 2) do const start_code = ttf_u16(data, start_base + i * 2) if (code < start_code) return 0 const idro = ttf_u16(data, range_base + i * 2) if (idro == 0) return (code + ttf_i16(data, delta_base + i * 2)) band 0xFFFF const glyph_id = ttf_u16(data, range_base + i * 2 + idro + (code - start_code) * 2) if (glyph_id == 0) return 0 return (glyph_id + ttf_i16(data, delta_base + i * 2)) band 0xFFFF end end return 0 end fn ttf_glyph_offset(data, loca, long, glyph_id) -> num do if (long) return ttf_u32(data, loca + glyph_id * 4) return ttf_u16(data, loca + glyph_id * 2) * 2 end fn ttf_advance(data, hmtx, num_hmetrics, glyph_id) -> num do if (glyph_id < num_hmetrics) return ttf_u16(data, hmtx + glyph_id * 4) return ttf_u16(data, hmtx + (num_hmetrics - 1) * 4) end fn ttf_add_line(edges, x0, y0, x1, y1) edges:push({ ax = x0, ay = y0, bx = x1, by = y1 }) fn ttf_add_quad(edges, x0, y0, cx, cy, x1, y1, steps) do let px = x0 let py = y0 for i in 1..(steps + 1) do const t = i / steps const mt = 1 - t const qx = mt * mt * x0 + 2 * mt * t * cx + t * t * x1 const qy = mt * mt * y0 + 2 * mt * t * cy + t * t * y1 ttf_add_line(edges, px, py, qx, qy) px = qx py = qy end end fn ttf_contour(pts, edges, nsub) do const point_count = pts:len() if (point_count < 2) return :nil let expanded = {} for i in 0..point_count do expanded:push(pts[i]) const j = ttf_mod(i + 1, point_count) if pts[i].on == :false and pts[j].on == :false do expanded:push({ x = (pts[i].x + pts[j].x) / 2, y = (pts[i].y + pts[j].y) / 2, on = :true }) end end const expanded_count = expanded:len() let start = -1 for i in 0..expanded_count do if (start < 0 and expanded[i].on) start = i end if (start < 0) return :nil let cx = expanded[start].x let cy = expanded[start].y let k = 1 while (k <= expanded_count) do const point = expanded[ttf_mod(start + k, expanded_count)] if point.on do ttf_add_line(edges, cx, cy, point.x, point.y) cx = point.x cy = point.y k += 1 end else do const endpoint = expanded[ttf_mod(start + k + 1, expanded_count)] ttf_add_quad(edges, cx, cy, point.x, point.y, endpoint.x, endpoint.y, nsub) cx = endpoint.x cy = endpoint.y k += 2 end end end # how about you contour my fucking asshole fn ttf_glyph_edges(data, glyf, loca, long, glyph_id, scale, ox, baseline, edges, nsub) do const offset = ttf_glyph_offset(data, loca, long, glyph_id) const next = ttf_glyph_offset(data, loca, long, glyph_id + 1) # empty glyph if (next <= offset) return :nil const base = glyf + offset const contour_count = ttf_i16(data, base) # skip if (contour_count < 0) return :nil let ends = {} for i in 0..contour_count do ends:push(ttf_u16(data, base + 10 + i * 2)) end const point_count = ends[contour_count - 1] + 1 let pos = base + 10 + contour_count * 2 pos += 2 + ttf_u16(data, pos) # skip instructions let flags = {} while (flags:len() < point_count) do const flag = ttf_u8(data, pos) pos += 1 flags:push(flag) if (flag band 0x08) != 0 do let repeat = ttf_u8(data, pos) pos += 1 while (repeat > 0) do flags:push(flag) repeat -= 1 end end end let xs = {} let x = 0 for i in 0..point_count do const flag = flags[i] if (flag band 0x02) != 0 do const dx = ttf_u8(data, pos) pos += 1 if (flag band 0x10) != 0 do x += dx end else do x -= dx end end else do if (flag band 0x10) == 0 do x += ttf_i16(data, pos) pos += 2 end end xs:push(x) end let ys = {} let y = 0 for i in 0..point_count do const flag = flags[i] if (flag band 0x04) != 0 do const dy = ttf_u8(data, pos) pos += 1 if (flag band 0x20) != 0 do y += dy end else do y -= dy end end else do if (flag band 0x20) == 0 do y += ttf_i16(data, pos) pos += 2 end end ys:push(y) end # split into contours let start = 0 for ci in 0..contour_count do const stop = ends[ci] let pts = {} for i in start..(stop + 1) do pts:push({ x = xs[i] * scale + ox, y = baseline - ys[i] * scale, on = (flags[i] band 0x01) != 0 }) end ttf_contour(pts, edges, nsub) start = stop + 1 end end fn ttf_fill(edges, width, height) -> table do let rows = {} for py in 0..height do const yc = py + 0.5 let cross = {} for i in 0..edges:len() do const edge = edges[i] const y0 = edge.ay const y1 = edge.by if (y0 <= yc and yc < y1) or (y1 <= yc and yc < y0) do const t = (yc - y0) / (y1 - y0) let direction = 1 if y1 < y0 do direction = -1 end cross:push({ x = edge.ax + t * (edge.bx - edge.ax), dir = direction }) end end # sort crossings left to right # insertion sort for i in 1..cross:len() do const key = cross[i] let j = i - 1 while (j >= 0 and cross[j].x > key.x) do cross[j + 1] = cross[j] j -= 1 end cross[j + 1] = key end let bits = 0 let winding = 0 for k in 0..cross:len() do winding += cross[k].dir if (winding != 0 and k + 1 < cross:len()) do const xa = cross[k].x const xb = cross[k + 1].x if xb > xa do let any = :false for px in 0..width do if (px + 0.5 >= xa and px + 0.5 < xb) do bits = bits bor (1 shl px) any = :true end end if any == :false do const midpoint = math.floor((xa + xb) / 2) if (midpoint >= 0 and midpoint < width) bits = bits bor (1 shl midpoint) end end end end rows:push(bits) end return rows end global ttf_io = { buffer = 0, pages = 512 } global load_ttf = fn(path, size) -> { glyphs: table, width: num, height: num } | :nil do const total = fs:size(path) if (total < 12) return :nil if ttf_io.buffer == 0 do ttf_io.buffer = alloc_contig(ttf_io.pages) end if (ttf_io.buffer == 0) return :nil if (total > ttf_io.pages * 4096) return :nil fs:read_into(path, ttf_io.buffer) const data = ttf_io.buffer const head = ttf_table(data, "head") const maxp = ttf_table(data, "maxp") const cmap = ttf_table(data, "cmap") const loca = ttf_table(data, "loca") const glyf = ttf_table(data, "glyf") const hhea = ttf_table(data, "hhea") const hmtx = ttf_table(data, "hmtx") if (head < 0 or cmap < 0 or loca < 0 or glyf < 0 or hhea < 0) return :nil const upm = ttf_u16(data, head + 18) if (upm == 0) return :nil const long = ttf_i16(data, head + 50) == 1 const ascent = ttf_i16(data, hhea + 4) const descent = ttf_i16(data, hhea + 6) const num_hmetrics = ttf_u16(data, hhea + 34) const scale = size / upm const baseline = math.floor(ascent * scale) let height = math.floor((ascent - descent) * scale) + 1 const subtable = ttf_cmap_sub(data, cmap) if (subtable < 0) return :nil const mgate = ttf_glyph_of(data, subtable, 77) # 'M' let width = math.floor(ttf_advance(data, hmtx, num_hmetrics, mgate) * scale) + 1 if (width < 2) width = math.floor(size * 0.6) + 1 const nsub = 5 let glyphs = {} for code in 32..128 do const glyph_id = ttf_glyph_of(data, subtable, code) let edges = {} ttf_glyph_edges(data, glyf, loca, long, glyph_id, scale, 1, baseline, edges, nsub) glyphs:push(ttf_fill(edges, width, height)) end return { glyphs = glyphs, width = width, height = height } end