aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/font
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-04-04 19:22:04 +0300
committerCan Celik <ogulcancelik@gmail.com>2026-04-05 01:23:06 +0300
commit41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch)
tree3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/font
parent55c138263e190a1e79d4b997bfb9397306c0ec1f (diff)
feat: finish ghostty pane backend integration
Diffstat (limited to 'vendor/libghostty-vt/src/font')
-rw-r--r--vendor/libghostty-vt/src/font/Atlas.zig889
-rw-r--r--vendor/libghostty-vt/src/font/CodepointMap.zig125
-rw-r--r--vendor/libghostty-vt/src/font/CodepointResolver.zig562
-rw-r--r--vendor/libghostty-vt/src/font/Collection.zig1523
-rw-r--r--vendor/libghostty-vt/src/font/DeferredFace.zig463
-rw-r--r--vendor/libghostty-vt/src/font/Glyph.zig19
-rw-r--r--vendor/libghostty-vt/src/font/Metrics.zig816
-rw-r--r--vendor/libghostty-vt/src/font/SharedGrid.zig540
-rw-r--r--vendor/libghostty-vt/src/font/SharedGridSet.zig834
-rw-r--r--vendor/libghostty-vt/src/font/backend.zig118
-rw-r--r--vendor/libghostty-vt/src/font/discovery.zig1049
-rw-r--r--vendor/libghostty-vt/src/font/embedded.zig55
-rw-r--r--vendor/libghostty-vt/src/font/face.zig707
-rw-r--r--vendor/libghostty-vt/src/font/face/coretext.zig1162
-rw-r--r--vendor/libghostty-vt/src/font/face/freetype.zig1439
-rw-r--r--vendor/libghostty-vt/src/font/face/web_canvas.zig561
-rw-r--r--vendor/libghostty-vt/src/font/library.zig63
-rw-r--r--vendor/libghostty-vt/src/font/main.zig82
-rw-r--r--vendor/libghostty-vt/src/font/nerd_font_attributes.zig2774
-rw-r--r--vendor/libghostty-vt/src/font/nerd_font_codegen.py576
-rw-r--r--vendor/libghostty-vt/src/font/nerd_font_codepoint_tables.py10449
-rw-r--r--vendor/libghostty-vt/src/font/opentype.zig17
-rw-r--r--vendor/libghostty-vt/src/font/opentype/head.zig179
-rw-r--r--vendor/libghostty-vt/src/font/opentype/hhea.zig116
-rw-r--r--vendor/libghostty-vt/src/font/opentype/os2.zig583
-rw-r--r--vendor/libghostty-vt/src/font/opentype/post.zig82
-rw-r--r--vendor/libghostty-vt/src/font/opentype/sfnt.zig312
-rw-r--r--vendor/libghostty-vt/src/font/opentype/svg.zig114
-rw-r--r--vendor/libghostty-vt/src/font/shape.zig101
-rw-r--r--vendor/libghostty-vt/src/font/shaper/Cache.zig106
-rw-r--r--vendor/libghostty-vt/src/font/shaper/coretext.zig2678
-rw-r--r--vendor/libghostty-vt/src/font/shaper/feature.zig354
-rw-r--r--vendor/libghostty-vt/src/font/shaper/harfbuzz.zig2172
-rw-r--r--vendor/libghostty-vt/src/font/shaper/noop.zig143
-rw-r--r--vendor/libghostty-vt/src/font/shaper/run.zig409
-rw-r--r--vendor/libghostty-vt/src/font/shaper/testdata/arabic.txt3
-rw-r--r--vendor/libghostty-vt/src/font/shaper/web_canvas.zig305
-rw-r--r--vendor/libghostty-vt/src/font/sprite.zig45
-rw-r--r--vendor/libghostty-vt/src/font/sprite/Face.zig550
-rw-r--r--vendor/libghostty-vt/src/font/sprite/canvas.zig512
-rw-r--r--vendor/libghostty-vt/src/font/sprite/draw/README.md55
-rw-r--r--vendor/libghostty-vt/src/font/sprite/draw/block.zig177
-rw-r--r--vendor/libghostty-vt/src/font/sprite/draw/box.zig929
-rw-r--r--vendor/libghostty-vt/src/font/sprite/draw/braille.zig148
-rw-r--r--vendor/libghostty-vt/src/font/sprite/draw/branch.zig504
-rw-r--r--vendor/libghostty-vt/src/font/sprite/draw/common.zig374
-rw-r--r--vendor/libghostty-vt/src/font/sprite/draw/geometric_shapes.zig198
-rw-r--r--vendor/libghostty-vt/src/font/sprite/draw/octants.txt234
-rw-r--r--vendor/libghostty-vt/src/font/sprite/draw/powerline.zig394
-rw-r--r--vendor/libghostty-vt/src/font/sprite/draw/special.zig372
-rw-r--r--vendor/libghostty-vt/src/font/sprite/draw/symbols_for_legacy_computing.zig1413
-rw-r--r--vendor/libghostty-vt/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig633
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+1CC00...U+1CCFF-11x21+2.pngbin0 -> 1032 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+1CC00...U+1CCFF-12x24+3.pngbin0 -> 1295 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+1CC00...U+1CCFF-18x36+4.pngbin0 -> 2193 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+1CC00...U+1CCFF-9x17+1.pngbin0 -> 794 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+1CD00...U+1CDFF-11x21+2.pngbin0 -> 1280 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+1CD00...U+1CDFF-12x24+3.pngbin0 -> 1870 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+1CD00...U+1CDFF-18x36+4.pngbin0 -> 3411 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+1CD00...U+1CDFF-9x17+1.pngbin0 -> 1103 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+1CE00...U+1CEFF-11x21+2.pngbin0 -> 1006 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+1CE00...U+1CEFF-12x24+3.pngbin0 -> 1255 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+1CE00...U+1CEFF-18x36+4.pngbin0 -> 2247 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+1CE00...U+1CEFF-9x17+1.pngbin0 -> 751 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+1FB00...U+1FBFF-11x21+2.pngbin0 -> 5427 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+1FB00...U+1FBFF-12x24+3.pngbin0 -> 5724 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+1FB00...U+1FBFF-18x36+4.pngbin0 -> 9975 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+1FB00...U+1FBFF-9x17+1.pngbin0 -> 4341 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+2500...U+25FF-11x21+2.pngbin0 -> 2225 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+2500...U+25FF-12x24+3.pngbin0 -> 2635 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+2500...U+25FF-18x36+4.pngbin0 -> 4570 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+2500...U+25FF-9x17+1.pngbin0 -> 1853 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+2800...U+28FF-11x21+2.pngbin0 -> 1022 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+2800...U+28FF-12x24+3.pngbin0 -> 1541 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+2800...U+28FF-18x36+4.pngbin0 -> 2501 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+2800...U+28FF-9x17+1.pngbin0 -> 917 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+E000...U+E0FF-11x21+2.pngbin0 -> 1102 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+E000...U+E0FF-12x24+3.pngbin0 -> 1255 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+E000...U+E0FF-18x36+4.pngbin0 -> 2217 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+E000...U+E0FF-9x17+1.pngbin0 -> 894 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+F500...U+F5FF-11x21+2.pngbin0 -> 1114 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+F500...U+F5FF-12x24+3.pngbin0 -> 1421 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+F500...U+F5FF-18x36+4.pngbin0 -> 2473 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+F500...U+F5FF-9x17+1.pngbin0 -> 872 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+F600...U+F6FF-11x21+2.pngbin0 -> 495 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+F600...U+F6FF-12x24+3.pngbin0 -> 637 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+F600...U+F6FF-18x36+4.pngbin0 -> 1210 bytes
-rw-r--r--vendor/libghostty-vt/src/font/sprite/testdata/U+F600...U+F6FF-9x17+1.pngbin0 -> 393 bytes
88 files changed, 39018 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/font/Atlas.zig b/vendor/libghostty-vt/src/font/Atlas.zig
new file mode 100644
index 00000000..d1206457
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/Atlas.zig
@@ -0,0 +1,889 @@
+//! Implements a texture atlas (https://en.wikipedia.org/wiki/Texture_atlas).
+//!
+//! The implementation is based on "A Thousand Ways to Pack the Bin - A
+//! Practical Approach to Two-Dimensional Rectangle Bin Packing" by Jukka
+//! Jylänki. This specific implementation is based heavily on
+//! Nicolas P. Rougier's freetype-gl project as well as Jukka's C++
+//! implementation: https://github.com/juj/RectangleBinPack
+//!
+//! Limitations that are easy to fix, but I didn't need them:
+//!
+//! * Written data must be packed, no support for custom strides.
+//! * Texture is always a square, no ability to set width != height. Note
+//! that regions written INTO the atlas do not have to be square, only
+//! the full atlas texture itself.
+//!
+const Atlas = @This();
+
+const std = @import("std");
+const assert = @import("../quirks.zig").inlineAssert;
+const Allocator = std.mem.Allocator;
+const testing = std.testing;
+const fastmem = @import("../fastmem.zig");
+const tripwire = @import("../tripwire.zig");
+
+const log = std.log.scoped(.atlas);
+
+/// Data is the raw texture data.
+data: []u8,
+
+/// Width and height of the atlas texture. The current implementation is
+/// always square so this is both the width and the height.
+size: u32 = 0,
+
+/// The nodes (rectangles) of available space.
+nodes: std.ArrayListUnmanaged(Node) = .{},
+
+/// The format of the texture data being written into the Atlas. This must be
+/// uniform for all textures in the Atlas. If you have some textures with
+/// different formats, you must use multiple atlases or convert the textures.
+format: Format = .grayscale,
+
+/// This will be incremented every time the atlas is modified. This is useful
+/// for knowing if the texture data has changed since the last time it was
+/// sent to the GPU. It is up the user of the atlas to read this value atomically
+/// to observe it.
+modified: std.atomic.Value(usize) = .{ .raw = 0 },
+
+/// This will be incremented every time the atlas is resized. This is useful
+/// for knowing if a GPU texture can be updated in-place or if it requires
+/// a resize operation.
+resized: std.atomic.Value(usize) = .{ .raw = 0 },
+
+pub const Format = enum(u8) {
+ /// 1 byte per pixel grayscale.
+ grayscale = 0,
+ /// 3 bytes per pixel BGR.
+ bgr = 1,
+ /// 4 bytes per pixel BGRA.
+ bgra = 2,
+
+ pub fn depth(self: Format) u8 {
+ return switch (self) {
+ .grayscale => 1,
+ .bgr => 3,
+ .bgra => 4,
+ };
+ }
+};
+
+const Node = struct {
+ x: u32,
+ y: u32,
+ width: u32,
+};
+
+pub const Error = error{
+ /// Atlas cannot fit the desired region. You must enlarge the atlas.
+ AtlasFull,
+};
+
+/// A region within the texture atlas. These can be acquired using the
+/// "reserve" function. A region reservation is required to write data.
+pub const Region = extern struct {
+ x: u32,
+ y: u32,
+ width: u32,
+ height: u32,
+};
+
+/// Number of nodes to preallocate in the list on init.
+///
+/// TODO: figure out optimal prealloc based on real world usage
+const node_prealloc: usize = 64;
+
+pub const init_tw = tripwire.module(enum {
+ alloc_data,
+ alloc_nodes,
+}, init);
+
+pub fn init(alloc: Allocator, size: u32, format: Format) Allocator.Error!Atlas {
+ const tw = init_tw;
+
+ try tw.check(.alloc_data);
+ var result = Atlas{
+ .data = try alloc.alloc(u8, size * size * format.depth()),
+ .size = size,
+ .nodes = .{},
+ .format = format,
+ };
+ errdefer result.deinit(alloc);
+
+ // Prealloc some nodes.
+ try tw.check(.alloc_nodes);
+ result.nodes = try .initCapacity(alloc, node_prealloc);
+
+ // This sets up our initial state
+ result.clear();
+
+ return result;
+}
+
+pub fn deinit(self: *Atlas, alloc: Allocator) void {
+ self.nodes.deinit(alloc);
+ alloc.free(self.data);
+ self.* = undefined;
+}
+
+pub const reserve_tw = tripwire.module(enum {
+ insert_node,
+}, reserve);
+
+/// Reserve a region within the atlas with the given width and height.
+///
+/// May allocate to add a new rectangle into the internal list of rectangles.
+/// This will not automatically enlarge the texture if it is full.
+pub fn reserve(
+ self: *Atlas,
+ alloc: Allocator,
+ width: u32,
+ height: u32,
+) (Allocator.Error || Error)!Region {
+ const tw = reserve_tw;
+
+ // x, y are populated within :best_idx below
+ var region: Region = .{ .x = 0, .y = 0, .width = width, .height = height };
+
+ // If our width/height are 0, then we return the region as-is. This
+ // may seem like an error case but it simplifies downstream callers who
+ // might be trying to write empty data.
+ if (width == 0 and height == 0) return region;
+
+ // Find the location in our nodes list to insert the new node for this region.
+ const best_idx: usize = best_idx: {
+ var best_height: u32 = std.math.maxInt(u32);
+ var best_width: u32 = best_height;
+ var chosen: ?usize = null;
+
+ var i: usize = 0;
+ while (i < self.nodes.items.len) : (i += 1) {
+ // Check if our region fits within this node.
+ const y = self.fit(i, width, height) orelse continue;
+
+ const node = self.nodes.items[i];
+ if ((y + height) < best_height or
+ ((y + height) == best_height and
+ (node.width > 0 and node.width < best_width)))
+ {
+ chosen = i;
+ best_width = node.width;
+ best_height = y + height;
+ region.x = node.x;
+ region.y = y;
+ }
+ }
+
+ // If we never found a chosen index, the atlas cannot fit our region.
+ break :best_idx chosen orelse return Error.AtlasFull;
+ };
+
+ // Insert our new node for this rectangle at the exact best index
+ try tw.check(.insert_node);
+ try self.nodes.insert(alloc, best_idx, .{
+ .x = region.x,
+ .y = region.y + height,
+ .width = width,
+ });
+ errdefer comptime unreachable;
+
+ // Optimize our rectangles
+ var i: usize = best_idx + 1;
+ while (i < self.nodes.items.len) : (i += 1) {
+ const node = &self.nodes.items[i];
+ const prev = self.nodes.items[i - 1];
+ if (node.x < (prev.x + prev.width)) {
+ const shrink = prev.x + prev.width - node.x;
+ node.x += shrink;
+ node.width -|= shrink;
+ if (node.width <= 0) {
+ _ = self.nodes.orderedRemove(i);
+ i -= 1;
+ continue;
+ }
+ }
+
+ break;
+ }
+ self.merge();
+
+ return region;
+}
+
+/// Attempts to fit a rectangle of width x height into the node at idx.
+/// The return value is the y within the texture where the rectangle can be
+/// placed. The x is the same as the node.
+fn fit(self: Atlas, idx: usize, width: u32, height: u32) ?u32 {
+ // If the added width exceeds our texture size, it doesn't fit.
+ const node = self.nodes.items[idx];
+ if ((node.x + width) > (self.size - 1)) return null;
+
+ // Go node by node looking for space that can fit our width.
+ var y = node.y;
+ var i = idx;
+ var width_left = width;
+ while (width_left > 0) : (i += 1) {
+ const n = self.nodes.items[i];
+ if (n.y > y) y = n.y;
+
+ // If the added height exceeds our texture size, it doesn't fit.
+ if ((y + height) > (self.size - 1)) return null;
+
+ width_left -|= n.width;
+ }
+
+ return y;
+}
+
+/// Merge adjacent nodes with the same y value.
+fn merge(self: *Atlas) void {
+ var i: usize = 0;
+ while (i < self.nodes.items.len - 1) {
+ const node = &self.nodes.items[i];
+ const next = self.nodes.items[i + 1];
+ if (node.y == next.y) {
+ node.width += next.width;
+ _ = self.nodes.orderedRemove(i + 1);
+ continue;
+ }
+
+ i += 1;
+ }
+}
+
+/// Set the data associated with a reserved region. The data is expected
+/// to fit exactly within the region. The data must be formatted with the
+/// proper bpp configured on init.
+pub fn set(self: *Atlas, reg: Region, data: []const u8) void {
+ assert(reg.x < (self.size - 1));
+ assert((reg.x + reg.width) <= (self.size - 1));
+ assert(reg.y < (self.size - 1));
+ assert((reg.y + reg.height) <= (self.size - 1));
+
+ const depth = self.format.depth();
+ var i: u32 = 0;
+ while (i < reg.height) : (i += 1) {
+ const tex_offset = (((reg.y + i) * self.size) + reg.x) * depth;
+ const data_offset = i * reg.width * depth;
+ fastmem.copy(
+ u8,
+ self.data[tex_offset..],
+ data[data_offset .. data_offset + (reg.width * depth)],
+ );
+ }
+
+ _ = self.modified.fetchAdd(1, .monotonic);
+}
+
+/// Like `set` but allows specifying a width for the source data and an
+/// offset x and y, so that a section of a larger buffer may be copied
+/// in to the atlas.
+pub fn setFromLarger(
+ self: *Atlas,
+ reg: Region,
+ src: []const u8,
+ src_width: u32,
+ src_x: u32,
+ src_y: u32,
+) void {
+ assert(reg.x < (self.size - 1));
+ assert((reg.x + reg.width) <= (self.size - 1));
+ assert(reg.y < (self.size - 1));
+ assert((reg.y + reg.height) <= (self.size - 1));
+
+ const depth = self.format.depth();
+ var i: u32 = 0;
+ while (i < reg.height) : (i += 1) {
+ const tex_offset = (((reg.y + i) * self.size) + reg.x) * depth;
+ const src_offset = (((src_y + i) * src_width) + src_x) * depth;
+ fastmem.copy(
+ u8,
+ self.data[tex_offset..],
+ src[src_offset .. src_offset + (reg.width * depth)],
+ );
+ }
+
+ _ = self.modified.fetchAdd(1, .monotonic);
+}
+
+pub const grow_tw = tripwire.module(enum {
+ ensure_node_capacity,
+ alloc_data,
+}, grow);
+
+// Grow the texture to the new size, preserving all previously written data.
+pub fn grow(self: *Atlas, alloc: Allocator, size_new: u32) Allocator.Error!void {
+ const tw = grow_tw;
+
+ assert(size_new >= self.size);
+ if (size_new == self.size) return;
+
+ // We reserve space ahead of time for the new node, so that we
+ // won't have to handle any errors after allocating our new data.
+ try tw.check(.ensure_node_capacity);
+ try self.nodes.ensureUnusedCapacity(alloc, 1);
+
+ try tw.check(.alloc_data);
+ const data_new = try alloc.alloc(
+ u8,
+ size_new * size_new * self.format.depth(),
+ );
+
+ // Function is infallible from this point.
+ errdefer comptime unreachable;
+
+ // Keep track of our old data so that we can copy it.
+ const data_old = self.data;
+ const size_old = self.size;
+
+ // Update our data and size to our new ones.
+ self.data = data_new;
+ self.size = size_new;
+
+ // Free the old data once we're done with it.
+ defer alloc.free(data_old);
+
+ // Zero the new data out and copy the old data over.
+ @memset(self.data, 0);
+ self.set(.{
+ .x = 0, // don't bother skipping border so we can avoid strides
+ .y = 1, // skip the first border row
+ .width = size_old,
+ .height = size_old - 2, // skip the last border row
+ }, data_old[size_old * self.format.depth() ..]);
+
+ // Add the new rectangle for our added righthand space.
+ self.nodes.appendAssumeCapacity(.{
+ .x = size_old - 1,
+ .y = 1,
+ .width = size_new - size_old,
+ });
+
+ // We are both modified and resized
+ _ = self.modified.fetchAdd(1, .monotonic);
+ _ = self.resized.fetchAdd(1, .monotonic);
+}
+
+// Empty the atlas. This doesn't reclaim any previously allocated memory.
+pub fn clear(self: *Atlas) void {
+ _ = self.modified.fetchAdd(1, .monotonic);
+ @memset(self.data, 0);
+ self.nodes.clearRetainingCapacity();
+
+ // Add our initial rectangle. This is the size of the full texture
+ // and is the initial rectangle we fit our regions in. We keep a 1px border
+ // to avoid artifacting when sampling the texture.
+ self.nodes.appendAssumeCapacity(.{ .x = 1, .y = 1, .width = self.size - 2 });
+}
+
+/// Dump the atlas as a PPM to a writer, for debug purposes.
+/// Only supports grayscale and bgr atlases.
+///
+/// NOTE: BGR atlases will have the red and blue channels
+/// swapped because PPM expects RGB. This would be
+/// easy enough to fix so next time someone needs
+/// to debug a color atlas they should fix it.
+pub fn dump(self: Atlas, writer: *std.Io.Writer) std.Io.Writer.Error!void {
+ try writer.print(
+ \\P{c}
+ \\{d} {d}
+ \\255
+ \\
+ , .{
+ @as(u8, switch (self.format) {
+ .grayscale => '5',
+ .bgr => '6',
+ else => {
+ log.err("Unsupported format for dump: {}", .{self.format});
+ @panic("Cannot dump this atlas format.");
+ },
+ }),
+ self.size,
+ self.size,
+ });
+ try writer.writeAll(self.data);
+}
+
+/// The wasm-compatible API. This lacks documentation unless the API differs
+/// from the standard Zig API. To learn what a function does, just look one
+/// level deeper to what Zig function is called and read the documentation there.
+pub const Wasm = struct {
+ // If you're copying this file (Atlas.zig) out to a separate project,
+ // just replace this with the allocator you want to use.
+ const wasm = @import("../os/wasm.zig");
+ const alloc = wasm.alloc;
+ const js = @import("zig-js");
+
+ export fn atlas_new(size: u32, format: u8) ?*Atlas {
+ const atlas = init(
+ alloc,
+ size,
+ @enumFromInt(format),
+ ) catch return null;
+ const result = alloc.create(Atlas) catch return null;
+ result.* = atlas;
+ return result;
+ }
+
+ export fn atlas_free(ptr: ?*Atlas) void {
+ if (ptr) |v| {
+ v.deinit(alloc);
+ alloc.destroy(v);
+ }
+ }
+
+ /// The return value for this should be freed by the caller with "free".
+ export fn atlas_reserve(self: *Atlas, width: u32, height: u32) ?*Region {
+ return atlas_reserve_(self, width, height) catch return null;
+ }
+
+ fn atlas_reserve_(self: *Atlas, width: u32, height: u32) !*Region {
+ const reg = try self.reserve(alloc, width, height);
+ const result = try alloc.create(Region);
+ errdefer alloc.destroy(result);
+ _ = try wasm.toHostOwned(result);
+ result.* = reg;
+ return result;
+ }
+
+ export fn atlas_set(self: *Atlas, reg: *Region, data: [*]const u8, len: usize) void {
+ self.set(reg.*, data[0..len]);
+ }
+
+ export fn atlas_grow(self: *Atlas, size_new: u32) bool {
+ self.grow(alloc, size_new) catch return false;
+ return true;
+ }
+
+ export fn atlas_clear(self: *Atlas) void {
+ self.clear();
+ }
+
+ /// This creates a Canvas element identified by the id returned that
+ /// the caller can draw into the DOM to visualize the atlas. The returned
+ /// ID must be freed from the JS runtime by calling "zigjs.deleteValue".
+ export fn atlas_debug_canvas(self: *Atlas) u32 {
+ return atlas_debug_canvas_(self) catch |err| {
+ log.warn("error dumping atlas canvas err={}", .{err});
+ return 0;
+ };
+ }
+
+ fn atlas_debug_canvas_(self: *Atlas) !u32 {
+ // Create our canvas
+ const doc = try js.global.get(js.Object, "document");
+ defer doc.deinit();
+ const canvas = try doc.call(js.Object, "createElement", .{js.string("canvas")});
+ errdefer canvas.deinit();
+
+ // Setup our canvas size
+ {
+ try canvas.set("width", self.size);
+ try canvas.set("height", self.size);
+
+ const width_str = try std.fmt.allocPrint(alloc, "{d}px", .{self.size});
+ defer alloc.free(width_str);
+
+ const style = try canvas.get(js.Object, "style");
+ defer style.deinit();
+ try style.set("width", js.string(width_str));
+ try style.set("height", js.string(width_str));
+ }
+
+ // This will return the same context on subsequent calls so it
+ // is important to reset it.
+ const ctx = try canvas.call(js.Object, "getContext", .{js.string("2d")});
+ defer ctx.deinit();
+
+ // We need to draw pixels so this is format dependent.
+ const buf: []u8 = switch (self.format) {
+ .bgra => buf: {
+ // Convert from BGRA to RGBA by swapping every R and B.
+ var buf: []u8 = try alloc.dupe(u8, self.data);
+ errdefer alloc.free(buf);
+ var i: usize = 0;
+ while (i < self.data.len) : (i += 4) {
+ std.mem.swap(u8, &buf[i], &buf[i + 2]);
+ }
+ break :buf buf;
+ },
+
+ .grayscale => buf: {
+ // Convert from A8 to RGBA so every 4th byte is set to a value.
+ var buf: []u8 = try alloc.alloc(u8, self.data.len * 4);
+ errdefer alloc.free(buf);
+ @memset(buf, 0);
+ for (self.data, 0..) |value, i| {
+ buf[(i * 4) + 3] = value;
+ }
+ break :buf buf;
+ },
+
+ else => return error.UnsupportedAtlasFormat,
+ };
+ defer if (buf.ptr != self.data.ptr) alloc.free(buf);
+
+ // Create an ImageData from our buffer and then write it to the canvas
+ const image_data: js.Object = data: {
+ // Get our runtime memory
+ const mem = try js.runtime.get(js.Object, "memory");
+ defer mem.deinit();
+ const mem_buf = try mem.get(js.Object, "buffer");
+ defer mem_buf.deinit();
+
+ // Create an array that points to our buffer
+ const arr = arr: {
+ const Uint8ClampedArray = try js.global.get(js.Object, "Uint8ClampedArray");
+ defer Uint8ClampedArray.deinit();
+ const arr = try Uint8ClampedArray.new(.{ mem_buf, buf.ptr, buf.len });
+ if (!wasm.shared_mem) break :arr arr;
+
+ // If we're sharing memory then we have to copy the data since
+ // we can't set ImageData directly using a SharedArrayBuffer.
+ defer arr.deinit();
+ break :arr try arr.call(js.Object, "slice", .{});
+ };
+ defer arr.deinit();
+
+ // Create the image data from our array
+ const ImageData = try js.global.get(js.Object, "ImageData");
+ defer ImageData.deinit();
+ const data = try ImageData.new(.{ arr, self.size, self.size });
+ errdefer data.deinit();
+
+ break :data data;
+ };
+ defer image_data.deinit();
+
+ // Draw it
+ try ctx.call(void, "putImageData", .{ image_data, 0, 0 });
+
+ const id = @as(js.Ref, @bitCast(@intFromEnum(canvas.value))).id;
+ return id;
+ }
+
+ test "happy path" {
+ const atlas = atlas_new(512, @intFromEnum(Format.grayscale)).?;
+ defer atlas_free(atlas);
+
+ const reg = atlas_reserve(atlas, 2, 2).?;
+ defer alloc.destroy(reg);
+ try testing.expect(wasm.isHostOwned(reg));
+ defer wasm.toModuleOwned(reg);
+ try testing.expect(reg.width > 0);
+
+ const data = &[_]u8{ 1, 2, 3, 4 };
+ try testing.expect(!atlas.modified);
+ atlas_set(atlas, reg, data, data.len);
+ try testing.expect(atlas.modified);
+ }
+};
+
+test "exact fit" {
+ const alloc = testing.allocator;
+ var atlas = try init(alloc, 34, .grayscale); // +2 for 1px border
+ defer atlas.deinit(alloc);
+
+ const modified = atlas.modified.load(.monotonic);
+ _ = try atlas.reserve(alloc, 32, 32);
+ try testing.expectEqual(modified, atlas.modified.load(.monotonic));
+ try testing.expectError(Error.AtlasFull, atlas.reserve(alloc, 1, 1));
+}
+
+test "doesn't fit" {
+ const alloc = testing.allocator;
+ var atlas = try init(alloc, 32, .grayscale);
+ defer atlas.deinit(alloc);
+
+ // doesn't fit due to border
+ try testing.expectError(Error.AtlasFull, atlas.reserve(alloc, 32, 32));
+}
+
+test "fit multiple" {
+ const alloc = testing.allocator;
+ var atlas = try init(alloc, 32, .grayscale);
+ defer atlas.deinit(alloc);
+
+ _ = try atlas.reserve(alloc, 15, 30);
+ _ = try atlas.reserve(alloc, 15, 30);
+ try testing.expectError(Error.AtlasFull, atlas.reserve(alloc, 1, 1));
+}
+
+test "writing data" {
+ const alloc = testing.allocator;
+ var atlas = try init(alloc, 32, .grayscale);
+ defer atlas.deinit(alloc);
+
+ const reg = try atlas.reserve(alloc, 2, 2);
+ const old = atlas.modified.load(.monotonic);
+ atlas.set(reg, &[_]u8{ 1, 2, 3, 4 });
+ const new = atlas.modified.load(.monotonic);
+ try testing.expect(new > old);
+
+ // 33 because of the 1px border and so on
+ try testing.expectEqual(@as(u8, 1), atlas.data[33]);
+ try testing.expectEqual(@as(u8, 2), atlas.data[34]);
+ try testing.expectEqual(@as(u8, 3), atlas.data[65]);
+ try testing.expectEqual(@as(u8, 4), atlas.data[66]);
+}
+
+test "writing data from a larger source" {
+ const alloc = testing.allocator;
+ var atlas = try init(alloc, 32, .grayscale);
+ defer atlas.deinit(alloc);
+
+ const reg = try atlas.reserve(alloc, 2, 2);
+ const old = atlas.modified.load(.monotonic);
+ // zig fmt: off
+ atlas.setFromLarger(reg, &[_]u8{
+ 8, 8, 8, 8, 8,
+ 8, 8, 1, 2, 8,
+ 8, 8, 3, 4, 8,
+ 8, 8, 8, 8, 8,
+ }, 5, 2, 1);
+ // zig fmt: on
+ const new = atlas.modified.load(.monotonic);
+ try testing.expect(new > old);
+
+ // 33 because of the 1px border and so on
+ try testing.expectEqual(@as(u8, 1), atlas.data[33]);
+ try testing.expectEqual(@as(u8, 2), atlas.data[34]);
+ try testing.expectEqual(@as(u8, 3), atlas.data[65]);
+ try testing.expectEqual(@as(u8, 4), atlas.data[66]);
+
+ // None of the `8`s from the source data outside of the
+ // specified region should have made it on to the atlas.
+ try testing.expectEqual(null, std.mem.indexOfScalar(u8, atlas.data, 8));
+}
+
+test "grow" {
+ const alloc = testing.allocator;
+ var atlas = try init(alloc, 4, .grayscale); // +2 for 1px border
+ defer atlas.deinit(alloc);
+
+ const reg = try atlas.reserve(alloc, 2, 2);
+ try testing.expectError(Error.AtlasFull, atlas.reserve(alloc, 1, 1));
+
+ // Write some data so we can verify that growing doesn't mess it up
+ atlas.set(reg, &[_]u8{ 1, 2, 3, 4 });
+ try testing.expectEqual(@as(u8, 1), atlas.data[5]);
+ try testing.expectEqual(@as(u8, 2), atlas.data[6]);
+ try testing.expectEqual(@as(u8, 3), atlas.data[9]);
+ try testing.expectEqual(@as(u8, 4), atlas.data[10]);
+
+ // Expand by exactly 1 should fit our new 1x1 block.
+ const old_modified = atlas.modified.load(.monotonic);
+ const old_resized = atlas.resized.load(.monotonic);
+ try atlas.grow(alloc, atlas.size + 1);
+ const new_modified = atlas.modified.load(.monotonic);
+ const new_resized = atlas.resized.load(.monotonic);
+ try testing.expect(new_modified > old_modified);
+ try testing.expect(new_resized > old_resized);
+ _ = try atlas.reserve(alloc, 1, 1);
+
+ // Ensure our data is still set. Not the offsets change due to size.
+ try testing.expectEqual(@as(u8, 1), atlas.data[atlas.size + 1]);
+ try testing.expectEqual(@as(u8, 2), atlas.data[atlas.size + 2]);
+ try testing.expectEqual(@as(u8, 3), atlas.data[atlas.size * 2 + 1]);
+ try testing.expectEqual(@as(u8, 4), atlas.data[atlas.size * 2 + 2]);
+}
+
+test "writing BGR data" {
+ const alloc = testing.allocator;
+ var atlas = try init(alloc, 32, .bgr);
+ defer atlas.deinit(alloc);
+
+ // This is BGR so its 3 bpp
+ const reg = try atlas.reserve(alloc, 1, 2);
+ atlas.set(reg, &[_]u8{
+ 1, 2, 3,
+ 4, 5, 6,
+ });
+
+ // 33 because of the 1px border and so on
+ const depth = @as(usize, @intCast(atlas.format.depth()));
+ try testing.expectEqual(@as(u8, 1), atlas.data[33 * depth]);
+ try testing.expectEqual(@as(u8, 2), atlas.data[33 * depth + 1]);
+ try testing.expectEqual(@as(u8, 3), atlas.data[33 * depth + 2]);
+ try testing.expectEqual(@as(u8, 4), atlas.data[65 * depth]);
+ try testing.expectEqual(@as(u8, 5), atlas.data[65 * depth + 1]);
+ try testing.expectEqual(@as(u8, 6), atlas.data[65 * depth + 2]);
+}
+
+test "grow BGR" {
+ const alloc = testing.allocator;
+
+ // Atlas is 4x4 so its a 1px border meaning we only have 2x2 available
+ var atlas = try init(alloc, 4, .bgr);
+ defer atlas.deinit(alloc);
+
+ // Get our 2x2, which should be ALL our usable space
+ const reg = try atlas.reserve(alloc, 2, 2);
+ try testing.expectError(Error.AtlasFull, atlas.reserve(alloc, 1, 1));
+
+ // This is BGR so its 3 bpp
+ atlas.set(reg, &[_]u8{
+ 10, 11, 12, // (0, 0) (x, y) from top-left
+ 13, 14, 15, // (1, 0)
+ 20, 21, 22, // (0, 1)
+ 23, 24, 25, // (1, 1)
+ });
+
+ // Our top left skips the first row (size * depth) and the first
+ // column (depth) for the 1px border.
+ const depth = @as(usize, @intCast(atlas.format.depth()));
+ var tl = (atlas.size * depth) + depth;
+ try testing.expectEqual(@as(u8, 10), atlas.data[tl]);
+ try testing.expectEqual(@as(u8, 11), atlas.data[tl + 1]);
+ try testing.expectEqual(@as(u8, 12), atlas.data[tl + 2]);
+ try testing.expectEqual(@as(u8, 13), atlas.data[tl + 3]);
+ try testing.expectEqual(@as(u8, 14), atlas.data[tl + 4]);
+ try testing.expectEqual(@as(u8, 15), atlas.data[tl + 5]);
+ try testing.expectEqual(@as(u8, 0), atlas.data[tl + 6]); // border
+
+ tl += (atlas.size * depth); // next row
+ try testing.expectEqual(@as(u8, 20), atlas.data[tl]);
+ try testing.expectEqual(@as(u8, 21), atlas.data[tl + 1]);
+ try testing.expectEqual(@as(u8, 22), atlas.data[tl + 2]);
+ try testing.expectEqual(@as(u8, 23), atlas.data[tl + 3]);
+ try testing.expectEqual(@as(u8, 24), atlas.data[tl + 4]);
+ try testing.expectEqual(@as(u8, 25), atlas.data[tl + 5]);
+ try testing.expectEqual(@as(u8, 0), atlas.data[tl + 6]); // border
+
+ // Expand by exactly 1 should fit our new 1x1 block.
+ try atlas.grow(alloc, atlas.size + 1);
+
+ // Data should be in same place accounting for the new size
+ tl = (atlas.size * depth) + depth;
+ try testing.expectEqual(@as(u8, 10), atlas.data[tl]);
+ try testing.expectEqual(@as(u8, 11), atlas.data[tl + 1]);
+ try testing.expectEqual(@as(u8, 12), atlas.data[tl + 2]);
+ try testing.expectEqual(@as(u8, 13), atlas.data[tl + 3]);
+ try testing.expectEqual(@as(u8, 14), atlas.data[tl + 4]);
+ try testing.expectEqual(@as(u8, 15), atlas.data[tl + 5]);
+ try testing.expectEqual(@as(u8, 0), atlas.data[tl + 6]); // border
+
+ tl += (atlas.size * depth); // next row
+ try testing.expectEqual(@as(u8, 20), atlas.data[tl]);
+ try testing.expectEqual(@as(u8, 21), atlas.data[tl + 1]);
+ try testing.expectEqual(@as(u8, 22), atlas.data[tl + 2]);
+ try testing.expectEqual(@as(u8, 23), atlas.data[tl + 3]);
+ try testing.expectEqual(@as(u8, 24), atlas.data[tl + 4]);
+ try testing.expectEqual(@as(u8, 25), atlas.data[tl + 5]);
+ try testing.expectEqual(@as(u8, 0), atlas.data[tl + 6]); // border
+
+ // Should fit the new blocks around the edges
+ _ = try atlas.reserve(alloc, 1, 3);
+ _ = try atlas.reserve(alloc, 2, 1);
+ try testing.expectError(Error.AtlasFull, atlas.reserve(alloc, 1, 1));
+}
+
+test "grow OOM" {
+ // We use a fixed buffer allocator so that we can consistently hit OOM.
+ //
+ // We calculate the size to exactly fit the 4x4 pixels and node list.
+ var buf: [
+ 4 * 4 * 1 // 4x4 pixels, each 1 byte.
+ + node_prealloc * @sizeOf(Node) // preallocated nodes.
+ ]u8 = undefined;
+ var fba: std.heap.FixedBufferAllocator = .init(&buf);
+ const alloc = fba.allocator();
+
+ var atlas = try init(alloc, 4, .grayscale); // +2 for 1px border
+ defer atlas.deinit(alloc);
+
+ const reg = try atlas.reserve(alloc, 2, 2);
+ try testing.expectError(
+ Error.AtlasFull,
+ atlas.reserve(alloc, 1, 1),
+ );
+
+ // Write some data so we can verify that attempted growing doesn't mess it up.
+ atlas.set(reg, &[_]u8{ 1, 2, 3, 4 });
+ try testing.expectEqual(@as(u8, 1), atlas.data[5]);
+ try testing.expectEqual(@as(u8, 2), atlas.data[6]);
+ try testing.expectEqual(@as(u8, 3), atlas.data[9]);
+ try testing.expectEqual(@as(u8, 4), atlas.data[10]);
+
+ // Expand by 1, should give OOM, modified and resized should be unchanged.
+ const old_modified = atlas.modified.load(.monotonic);
+ const old_resized = atlas.resized.load(.monotonic);
+ try testing.expectError(
+ Allocator.Error.OutOfMemory,
+ atlas.grow(alloc, atlas.size + 1),
+ );
+ const new_modified = atlas.modified.load(.monotonic);
+ const new_resized = atlas.resized.load(.monotonic);
+ try testing.expectEqual(old_modified, new_modified);
+ try testing.expectEqual(old_resized, new_resized);
+
+ // Ensure our data is still set.
+ try testing.expectEqual(@as(u8, 1), atlas.data[5]);
+ try testing.expectEqual(@as(u8, 2), atlas.data[6]);
+ try testing.expectEqual(@as(u8, 3), atlas.data[9]);
+ try testing.expectEqual(@as(u8, 4), atlas.data[10]);
+}
+
+test "init error" {
+ // Test every failure point in `init` and ensure that we don't
+ // leak memory (testing.allocator verifies) since we're exiting early.
+ for (std.meta.tags(init_tw.FailPoint)) |tag| {
+ const tw = init_tw;
+ defer tw.end(.reset) catch unreachable;
+ tw.errorAlways(tag, error.OutOfMemory);
+ try testing.expectError(
+ error.OutOfMemory,
+ init(testing.allocator, 32, .grayscale),
+ );
+ }
+}
+
+test "reserve error" {
+ // Test every failure point in `reserve` and ensure that we don't
+ // leak memory (testing.allocator verifies) since we're exiting early.
+ for (std.meta.tags(reserve_tw.FailPoint)) |tag| {
+ const tw = reserve_tw;
+ defer tw.end(.reset) catch unreachable;
+
+ var atlas = try init(testing.allocator, 32, .grayscale);
+ defer atlas.deinit(testing.allocator);
+
+ tw.errorAlways(tag, error.OutOfMemory);
+ try testing.expectError(
+ error.OutOfMemory,
+ atlas.reserve(testing.allocator, 2, 2),
+ );
+ }
+}
+
+test "grow error" {
+ // Test every failure point in `grow` and ensure that we don't
+ // leak memory (testing.allocator verifies) since we're exiting early.
+ for (std.meta.tags(grow_tw.FailPoint)) |tag| {
+ const tw = grow_tw;
+ defer tw.end(.reset) catch unreachable;
+
+ var atlas = try init(testing.allocator, 4, .grayscale);
+ defer atlas.deinit(testing.allocator);
+
+ // Write some data to verify it's preserved after failed grow
+ const reg = try atlas.reserve(testing.allocator, 2, 2);
+ atlas.set(reg, &[_]u8{ 1, 2, 3, 4 });
+
+ const old_modified = atlas.modified.load(.monotonic);
+ const old_resized = atlas.resized.load(.monotonic);
+
+ tw.errorAlways(tag, error.OutOfMemory);
+ try testing.expectError(
+ error.OutOfMemory,
+ atlas.grow(testing.allocator, atlas.size + 1),
+ );
+
+ // Verify atlas state is unchanged after failed grow
+ try testing.expectEqual(old_modified, atlas.modified.load(.monotonic));
+ try testing.expectEqual(old_resized, atlas.resized.load(.monotonic));
+ try testing.expectEqual(@as(u8, 1), atlas.data[5]);
+ try testing.expectEqual(@as(u8, 2), atlas.data[6]);
+ try testing.expectEqual(@as(u8, 3), atlas.data[9]);
+ try testing.expectEqual(@as(u8, 4), atlas.data[10]);
+ }
+}
diff --git a/vendor/libghostty-vt/src/font/CodepointMap.zig b/vendor/libghostty-vt/src/font/CodepointMap.zig
new file mode 100644
index 00000000..564bf013
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/CodepointMap.zig
@@ -0,0 +1,125 @@
+/// CodepointMap is a map of codepoints to a discovery descriptor of a font
+/// to use for that codepoint. If the descriptor doesn't return any matching
+/// font, the codepoint is rendered using the default font.
+const CodepointMap = @This();
+
+const std = @import("std");
+const assert = @import("../quirks.zig").inlineAssert;
+const Allocator = std.mem.Allocator;
+const discovery = @import("discovery.zig");
+
+pub const Entry = struct {
+ /// Unicode codepoint range. Asserts range[0] <= range[1].
+ range: [2]u21,
+
+ /// The discovery descriptor of the font to use for this range.
+ descriptor: discovery.Descriptor,
+};
+
+/// The list of entries. We use a multiarraylist because Descriptors are
+/// quite large and we will very rarely match, so we'd rather pack our
+/// ranges together to make everything more cache friendly for lookups.
+///
+/// Note: we just do a linear search because we expect to always have very
+/// few entries, so the overhead of a binary search is not worth it. This is
+/// possible to defeat with some pathological inputs, but there is no realistic
+/// scenario where this will be a problem except people trying to fuck around.
+list: std.MultiArrayList(Entry) = .{},
+
+pub fn deinit(self: *CodepointMap, alloc: Allocator) void {
+ self.list.deinit(alloc);
+}
+
+/// Deep copy of the struct. The given allocator is expected to
+/// be an arena allocator of some sort since the struct itself
+/// doesn't support fine-grained deallocation of fields.
+pub fn clone(self: *const CodepointMap, alloc: Allocator) !CodepointMap {
+ var list = try self.list.clone(alloc);
+ for (list.items(.descriptor)) |*d| {
+ d.* = try d.clone(alloc);
+ }
+
+ return .{ .list = list };
+}
+
+/// Add an entry to the map.
+///
+/// For conflicting codepoints, entries added later take priority over
+/// entries added earlier.
+pub fn add(self: *CodepointMap, alloc: Allocator, entry: Entry) !void {
+ assert(entry.range[0] <= entry.range[1]);
+ try self.list.append(alloc, entry);
+}
+
+/// Get a descriptor for a codepoint.
+pub fn get(self: *const CodepointMap, cp: u21) ?discovery.Descriptor {
+ const items = self.list.items(.range);
+ for (0..items.len) |forward_i| {
+ const i = items.len - forward_i - 1;
+ const range = items[i];
+ if (range[0] <= cp and cp <= range[1]) {
+ const descs = self.list.items(.descriptor);
+ return descs[i];
+ }
+ }
+
+ return null;
+}
+
+/// Hash with the given hasher.
+pub fn hash(self: *const CodepointMap, hasher: anytype) void {
+ const autoHash = std.hash.autoHash;
+ autoHash(hasher, self.list.len);
+ const slice = self.list.slice();
+ for (0..slice.len) |i| {
+ const entry = slice.get(i);
+ autoHash(hasher, entry.range);
+ entry.descriptor.hash(hasher);
+ }
+}
+
+/// Returns a hash code that can be used to uniquely identify this
+/// action.
+pub fn hashcode(self: *const CodepointMap) u64 {
+ var hasher = std.hash.Wyhash.init(0);
+ self.hash(&hasher);
+ return hasher.final();
+}
+
+test "codepointmap" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var m: CodepointMap = .{};
+ defer m.deinit(alloc);
+
+ // Exact range
+ try testing.expect(m.get(1) == null);
+ try m.add(alloc, .{ .range = .{ 1, 1 }, .descriptor = .{ .family = "A" } });
+ {
+ const d = m.get(1).?;
+ try testing.expectEqualStrings("A", d.family.?);
+ }
+
+ // Later entry takes priority
+ try m.add(alloc, .{ .range = .{ 1, 2 }, .descriptor = .{ .family = "B" } });
+ {
+ const d = m.get(1).?;
+ try testing.expectEqualStrings("B", d.family.?);
+ }
+
+ // Non-matching
+ try testing.expect(m.get(0) == null);
+ try testing.expect(m.get(3) == null);
+
+ try m.add(alloc, .{ .range = .{ 3, 4 }, .descriptor = .{ .family = "C" } });
+ try m.add(alloc, .{ .range = .{ 5, 6 }, .descriptor = .{ .family = "D" } });
+ {
+ const d = m.get(3).?;
+ try testing.expectEqualStrings("C", d.family.?);
+ }
+ {
+ const d = m.get(1).?;
+ try testing.expectEqualStrings("B", d.family.?);
+ }
+}
diff --git a/vendor/libghostty-vt/src/font/CodepointResolver.zig b/vendor/libghostty-vt/src/font/CodepointResolver.zig
new file mode 100644
index 00000000..a4f13c29
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/CodepointResolver.zig
@@ -0,0 +1,562 @@
+//! CodepointResolver maps a codepoint to a font. It is more dynamic
+//! than "Collection" since it supports mapping codepoint ranges to
+//! specific fonts, searching for fallback fonts, and more.
+//!
+//! To initialize the codepoint resolver, manually initialize using
+//! Zig initialization syntax: .{}-style. Set the fields you want set,
+//! and begin using the resolver.
+//!
+//! Deinit must still be called on the resolver to free any memory
+//! allocated during use. All functions that take allocators should use
+//! the same allocator.
+const CodepointResolver = @This();
+
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const uucode = @import("uucode");
+const font = @import("main.zig");
+const Atlas = font.Atlas;
+const CodepointMap = font.CodepointMap;
+const Collection = font.Collection;
+const Discover = font.Discover;
+const DiscoveryDescriptor = font.discovery.Descriptor;
+const Face = font.Face;
+const Glyph = font.Glyph;
+const Library = font.Library;
+const Presentation = font.Presentation;
+const RenderOptions = font.face.RenderOptions;
+const SpriteFace = font.SpriteFace;
+const Style = font.Style;
+
+const log = std.log.scoped(.font_codepoint_resolver);
+
+/// The underlying collection of fonts. This will be modified as
+/// new fonts are found via the resolver. The resolver takes ownership
+/// of the collection and will deinit it when it is deinitialized.
+collection: Collection,
+
+/// The set of statuses and whether they're enabled or not. This defaults
+/// to true. This can be changed at runtime with no ill effect.
+styles: StyleStatus = .initFill(true),
+
+/// If discovery is available, we'll look up fonts where we can't find
+/// the codepoint. This can be set after initialization.
+discover: ?*Discover = null,
+
+/// A map of codepoints to font requests for codepoint-level overrides.
+/// The memory associated with the map is owned by the caller and is not
+/// modified or freed by Group.
+codepoint_map: ?CodepointMap = null,
+
+/// The descriptor cache is used to cache the descriptor to font face
+/// mapping for codepoint maps.
+descriptor_cache: DescriptorCache = .{},
+
+/// Set this to a non-null value to enable sprite glyph drawing. If this
+/// isn't enabled we'll just fall through to trying to use regular fonts
+/// to render sprite glyphs. But more than likely, if this isn't set then
+/// terminal rendering will look wrong.
+sprite: ?SpriteFace = null,
+
+pub fn deinit(self: *CodepointResolver, alloc: Allocator) void {
+ self.collection.deinit(alloc);
+ self.descriptor_cache.deinit(alloc);
+}
+
+/// Looks up the font that should be used for a specific codepoint.
+/// The font index is valid as long as font faces aren't removed. This
+/// isn't cached; it is expected that downstream users handle caching if
+/// that is important.
+///
+/// Optionally, a presentation format can be specified. This presentation
+/// format will be preferred but if it can't be found in this format,
+/// any format will be accepted. If presentation is null, the UCD
+/// (Unicode Character Database) will be used to determine the default
+/// presentation for the codepoint.
+/// a code point.
+///
+/// An allocator is required because certain functionality (codepoint
+/// mapping, fallback fonts, etc.) may require memory allocation. Curiously,
+/// this function cannot error! If an error occurs for any reason, including
+/// memory allocation, the associated functionality is ignored and the
+/// resolver attempts to use a different method to satisfy the codepoint.
+/// This behavior is intentional to make the resolver apply best-effort
+/// logic to satisfy the codepoint since its better to render something
+/// than nothing.
+///
+/// This logic is relatively complex so the exact algorithm is documented
+/// here. If this gets out of sync with the code, ask questions.
+///
+/// 1. If a font style is requested that is disabled (i.e. bold),
+/// we start over with the regular font style. The regular font style
+/// cannot be disabled, but it can be replaced with a stylized font
+/// face.
+///
+/// 2. If there is a codepoint override for the codepoint, we satisfy
+/// that requirement if we can, no matter what style or presentation.
+///
+/// 3. If this is a sprite codepoint (such as an underline), then the
+/// sprite font always is the result.
+///
+/// 4. If the exact style and presentation request can be satisfied by
+/// one of our loaded fonts, we return that value. We search loaded
+/// fonts in the order they're added to the group, so the caller must
+/// set the priority order.
+///
+/// 5. If the style isn't regular, we restart this process at this point
+/// but with the regular style. This lets us fall back to regular with
+/// our loaded fonts before trying a fallback. We'd rather show a regular
+/// version of a codepoint from a loaded font than find a new font in
+/// the correct style because styles in other fonts often change
+/// metrics like glyph widths.
+///
+/// 6. If the style is regular, and font discovery is enabled, we look
+/// for a fallback font to satisfy our request.
+///
+/// 7. Finally, as a last resort, we fall back to restarting this whole
+/// process with a regular font face satisfying ANY presentation for
+/// the codepoint. If this fails, we return null.
+///
+pub fn getIndex(
+ self: *CodepointResolver,
+ alloc: Allocator,
+ cp: u32,
+ style: Style,
+ p: ?Presentation,
+) ?Collection.Index {
+ // If we've disabled a font style, then fall back to regular.
+ if (style != .regular and !self.styles.get(style)) {
+ return self.getIndex(alloc, cp, .regular, p);
+ }
+
+ // Codepoint overrides.
+ if (self.getIndexCodepointOverride(alloc, cp)) |idx_| {
+ if (idx_) |idx| return idx;
+ } else |err| {
+ log.warn("codepoint override failed codepoint={} err={}", .{ cp, err });
+ }
+
+ // If we have sprite drawing enabled, check if our sprite face can
+ // handle this.
+ if (self.sprite) |sprite| {
+ if (sprite.hasCodepoint(cp, p)) {
+ return .initSpecial(.sprite);
+ }
+ }
+
+ // Build our presentation mode. If we don't have an explicit presentation
+ // given then we use the UCD (Unicode Character Database) to determine
+ // the default presentation. Note there is some inefficiency here because
+ // we'll do this multiple times if we recurse, but this is a cached function
+ // call higher up (GroupCache) so this should be rare.
+ const p_mode: Collection.PresentationMode = if (p) |v| .{ .explicit = v } else .{
+ .default = if (uucode.get(.is_emoji_presentation, @intCast(cp)))
+ .emoji
+ else
+ .text,
+ };
+
+ // If we can find the exact value, then return that.
+ if (self.collection.getIndex(cp, style, p_mode)) |value| return value;
+
+ // If we're not a regular font style, try looking for a regular font
+ // that will satisfy this request. Blindly looking for unmatched styled
+ // fonts to satisfy one codepoint results in some ugly rendering.
+ if (style != .regular) {
+ if (self.getIndex(alloc, cp, .regular, p)) |value| return value;
+ }
+
+ // If we are regular, try looking for a fallback using discovery.
+ if (style == .regular and font.Discover != void) {
+ log.debug("searching for a fallback font for cp={X}", .{cp});
+ if (self.discover) |disco| discover: {
+ const load_opts = self.collection.load_options orelse
+ break :discover;
+ var disco_it = disco.discoverFallback(alloc, &self.collection, .{
+ .codepoint = cp,
+ .size = load_opts.size.points,
+ .bold = style == .bold or style == .bold_italic,
+ .italic = style == .italic or style == .bold_italic,
+ .monospace = false,
+ }) catch break :discover;
+ defer disco_it.deinit();
+
+ while (true) {
+ var deferred_face = (disco_it.next() catch |err| {
+ log.warn("fallback search failed with error err={}", .{err});
+ break;
+ }) orelse break;
+
+ // Discovery is supposed to only return faces that have our
+ // codepoint but we can't search presentation in discovery so
+ // we have to check it here.
+ const face: Collection.Entry = .{
+ .face = .{ .deferred = deferred_face },
+ .fallback = true,
+ };
+ if (!face.hasCodepoint(cp, p_mode)) {
+ deferred_face.deinit();
+ continue;
+ }
+
+ var buf: [256]u8 = undefined;
+ log.info("found codepoint 0x{X} in fallback face={s}", .{
+ cp,
+ deferred_face.name(&buf) catch "<error>",
+ });
+ return self.collection.addDeferred(alloc, deferred_face, .{
+ .style = style,
+ .fallback = true,
+ .size_adjustment = font.default_fallback_adjustment,
+ }) catch {
+ deferred_face.deinit();
+ break :discover;
+ };
+ }
+
+ log.debug("no fallback face found for cp={X}", .{cp});
+ }
+ }
+
+ // If this is regular with any matching presentation, then we are done
+ // there is nothing more we can do. Otherwise we fall through and do
+ // an any presentation search.
+ if (style == .regular and p_mode == .any) return null;
+
+ // For non-regular fonts, we fall back to regular with any presentation
+ return self.collection.getIndex(cp, .regular, .{ .any = {} });
+}
+
+/// Checks if the codepoint is in the map of codepoint overrides,
+/// finds the override font, and returns it.
+fn getIndexCodepointOverride(
+ self: *CodepointResolver,
+ alloc: Allocator,
+ cp: u32,
+) !?Collection.Index {
+ // If discovery is disabled then we can't do codepoint overrides
+ // since the override is based on discovery to find the font.
+ if (comptime font.Discover == void) return null;
+
+ // Get our codepoint map. If we have no map set then we have no
+ // codepoint overrides and we're done.
+ const map = self.codepoint_map orelse return null;
+
+ // If we have a codepoint too large or isn't in the map, then we
+ // don't have an override. The map returns a descriptor that can be
+ // used with font discovery to search for a matching font.
+ const cp_u21 = std.math.cast(u21, cp) orelse return null;
+ const desc = map.get(cp_u21) orelse return null;
+
+ // Fast path: the descriptor is already loaded. This means that we
+ // already did the search before and we have an exact font for this
+ // codepoint.
+ const idx_: ?Collection.Index = self.descriptor_cache.get(desc) orelse idx: {
+ // Slow path: we have to find this descriptor and load the font
+ const discover = self.discover orelse return null;
+ var disco_it = try discover.discover(alloc, desc);
+ defer disco_it.deinit();
+
+ const face = (try disco_it.next()) orelse {
+ log.warn(
+ "font lookup for codepoint map failed codepoint={} err=FontNotFound",
+ .{cp},
+ );
+
+ // Add null to the cache so we don't do a lookup again later.
+ try self.descriptor_cache.put(alloc, desc, null);
+ return null;
+ };
+
+ // Add the font to our list of fonts so we can get an index for it,
+ // and ensure the index is stored in the descriptor cache for next time.
+ const idx = try self.collection.addDeferred(alloc, face, .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = font.default_fallback_adjustment,
+ });
+ try self.descriptor_cache.put(alloc, desc, idx);
+
+ break :idx idx;
+ };
+
+ // The descriptor cache will populate null if the descriptor is not found
+ // to avoid expensive discoveries later, so if it is null then we already
+ // searched and found nothing.
+ const idx = idx_ orelse return null;
+
+ // We need to verify that this index has the codepoint we want.
+ if (self.collection.hasCodepoint(idx, cp, .{ .any = {} })) {
+ log.debug("codepoint override based on config codepoint={} family={s}", .{
+ cp,
+ desc.family orelse "",
+ });
+
+ return idx;
+ }
+
+ return null;
+}
+
+/// Returns the presentation for a specific font index. This is useful for
+/// determining what atlas is needed.
+pub fn getPresentation(
+ self: *CodepointResolver,
+ index: Collection.Index,
+ glyph_index: u32,
+) !Presentation {
+ if (index.special()) |sp| return switch (sp) {
+ .sprite => .text,
+ };
+
+ const face = try self.collection.getFace(index);
+ return if (face.isColorGlyph(glyph_index)) .emoji else .text;
+}
+
+/// Render a glyph by glyph index into the given font atlas and return
+/// metadata about it.
+///
+/// This performs no caching, it is up to the caller to cache calls to this
+/// if they want. This will also not resize the atlas if it is full.
+///
+/// IMPORTANT: this renders by /glyph index/ and not by /codepoint/. The caller
+/// is expected to translate codepoints to glyph indexes in some way. The most
+/// trivial way to do this is to get the Face and call glyphIndex. If you're
+/// doing text shaping, the text shaping library (i.e. HarfBuzz) will automatically
+/// determine glyph indexes for a text run.
+pub fn renderGlyph(
+ self: *CodepointResolver,
+ alloc: Allocator,
+ atlas: *Atlas,
+ index: Collection.Index,
+ glyph_index: u32,
+ opts: RenderOptions,
+) !Glyph {
+ // Special-case fonts are rendered directly.
+ if (index.special()) |sp| switch (sp) {
+ .sprite => return try self.sprite.?.renderGlyph(
+ alloc,
+ atlas,
+ glyph_index,
+ opts,
+ ),
+ };
+
+ const face = try self.collection.getFace(index);
+ const glyph = try face.renderGlyph(alloc, atlas, glyph_index, opts);
+ // log.warn("GLYPH={}", .{glyph});
+ return glyph;
+}
+
+/// Packed array of booleans to indicate if a style is enabled or not.
+pub const StyleStatus = std.EnumArray(Style, bool);
+
+/// Map of descriptors to faces. This is used with manual codepoint maps
+/// to ensure that we don't load the same font multiple times.
+///
+/// Note that the current implementation will load the same font multiple
+/// times if the font used for a codepoint map is identical to a font used
+/// for a regular style. That's just an inefficient choice made now because
+/// the implementation is simpler and codepoint maps matching a regular
+/// font is a rare case.
+const DescriptorCache = std.HashMapUnmanaged(
+ DiscoveryDescriptor,
+ ?Collection.Index,
+ struct {
+ const KeyType = DiscoveryDescriptor;
+
+ pub fn hash(ctx: @This(), k: KeyType) u64 {
+ _ = ctx;
+ return k.hashcode();
+ }
+
+ pub fn eql(ctx: @This(), a: KeyType, b: KeyType) bool {
+ // Note that this means its possible to have two different
+ // descriptors match when there is a hash collision so we
+ // should button this up later.
+ return ctx.hash(a) == ctx.hash(b);
+ }
+ },
+ std.hash_map.default_max_load_percentage,
+);
+
+test getIndex {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ const testFont = font.embedded.regular;
+ const testEmoji = font.embedded.emoji;
+ const testEmojiText = font.embedded.emoji_text;
+
+ var lib = try Library.init(alloc);
+ defer lib.deinit();
+
+ var c = Collection.init();
+ c.load_options = .{ .library = lib };
+
+ {
+ errdefer c.deinit(alloc);
+ _ = try c.add(alloc, try .init(
+ lib,
+ testFont,
+ .{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
+ ), .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .none,
+ });
+ if (comptime !font.options.backend.hasCoretext()) {
+ // Coretext doesn't support Noto's format
+ _ = try c.add(alloc, try .init(
+ lib,
+ testEmoji,
+ .{ .size = .{ .points = 12 } },
+ ), .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .none,
+ });
+ }
+ _ = try c.add(alloc, try .init(
+ lib,
+ testEmojiText,
+ .{ .size = .{ .points = 12 } },
+ ), .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .none,
+ });
+ }
+
+ var r: CodepointResolver = .{ .collection = c };
+ defer r.deinit(alloc);
+
+ // Should find all visible ASCII
+ var i: u32 = 32;
+ while (i < 127) : (i += 1) {
+ const idx = r.getIndex(alloc, i, .regular, null).?;
+ try testing.expectEqual(Style.regular, idx.style);
+ try testing.expectEqual(@as(Collection.Index.IndexInt, 0), idx.idx);
+ }
+
+ // Try emoji
+ {
+ const idx = r.getIndex(alloc, '🥸', .regular, null).?;
+ try testing.expectEqual(Style.regular, idx.style);
+ try testing.expectEqual(@as(Collection.Index.IndexInt, 1), idx.idx);
+ }
+
+ // Try text emoji
+ {
+ const idx = r.getIndex(alloc, 0x270C, .regular, .text).?;
+ try testing.expectEqual(Style.regular, idx.style);
+ const text_idx = if (comptime font.options.backend.hasCoretext()) 1 else 2;
+ try testing.expectEqual(@as(Collection.Index.IndexInt, text_idx), idx.idx);
+ }
+ {
+ const idx = r.getIndex(alloc, 0x270C, .regular, .emoji).?;
+ try testing.expectEqual(Style.regular, idx.style);
+ try testing.expectEqual(@as(Collection.Index.IndexInt, 1), idx.idx);
+ }
+
+ // Box glyph should be null since we didn't set a box font
+ {
+ try testing.expect(r.getIndex(alloc, 0x1FB00, .regular, null) == null);
+ }
+}
+
+test "getIndex disabled font style" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ const testFont = font.embedded.regular;
+
+ var atlas_grayscale = try font.Atlas.init(alloc, 512, .grayscale);
+ defer atlas_grayscale.deinit(alloc);
+
+ var lib = try Library.init(alloc);
+ defer lib.deinit();
+
+ var c = Collection.init();
+ c.load_options = .{ .library = lib };
+
+ _ = try c.add(alloc, try .init(
+ lib,
+ testFont,
+ .{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
+ ), .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .none,
+ });
+ _ = try c.add(alloc, try .init(
+ lib,
+ testFont,
+ .{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
+ ), .{
+ .style = .bold,
+ .fallback = false,
+ .size_adjustment = .none,
+ });
+ _ = try c.add(alloc, try .init(
+ lib,
+ testFont,
+ .{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
+ ), .{
+ .style = .italic,
+ .fallback = false,
+ .size_adjustment = .none,
+ });
+
+ var r: CodepointResolver = .{ .collection = c };
+ defer r.deinit(alloc);
+ r.styles.set(.bold, false); // Disable bold
+
+ // Regular should work fine
+ {
+ const idx = r.getIndex(alloc, 'A', .regular, null).?;
+ try testing.expectEqual(Style.regular, idx.style);
+ try testing.expectEqual(@as(Collection.Index.IndexInt, 0), idx.idx);
+ }
+
+ // Bold should go to regular
+ {
+ const idx = r.getIndex(alloc, 'A', .bold, null).?;
+ try testing.expectEqual(Style.regular, idx.style);
+ try testing.expectEqual(@as(Collection.Index.IndexInt, 0), idx.idx);
+ }
+
+ // Italic should still work
+ {
+ const idx = r.getIndex(alloc, 'A', .italic, null).?;
+ try testing.expectEqual(Style.italic, idx.style);
+ try testing.expectEqual(@as(Collection.Index.IndexInt, 0), idx.idx);
+ }
+}
+
+test "getIndex box glyph" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var lib = try Library.init(alloc);
+ defer lib.deinit();
+
+ const c = Collection.init();
+
+ var r: CodepointResolver = .{
+ .collection = c,
+ .sprite = .{
+ .metrics = font.Metrics.calc(.{
+ .px_per_em = 30.0,
+ .cell_width = 18.0,
+ .ascent = 30.0,
+ .descent = -6.0,
+ .line_gap = 0.0,
+ }),
+ },
+ };
+ defer r.deinit(alloc);
+
+ // Should find a box glyph
+ const idx = r.getIndex(alloc, 0x2500, .regular, null).?;
+ try testing.expectEqual(Style.regular, idx.style);
+ try testing.expectEqual(@intFromEnum(Collection.Index.Special.sprite), idx.idx);
+}
diff --git a/vendor/libghostty-vt/src/font/Collection.zig b/vendor/libghostty-vt/src/font/Collection.zig
new file mode 100644
index 00000000..5d7bfa51
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/Collection.zig
@@ -0,0 +1,1523 @@
+//! A font collection is a list of faces of different styles. The list is
+//! ordered by priority (per style). All fonts in a collection share the same
+//! size so they can be used interchangeably in cases a glyph is missing in one
+//! and present in another.
+//!
+//! The purpose of a collection is to store a list of fonts by style
+//! and priority order. A collection does not handle searching for font
+//! callbacks, rasterization, etc. For this, see CodepointResolver.
+//!
+//! The collection can contain both loaded and deferred faces. Deferred faces
+//! typically use less memory while still providing some necessary information
+//! such as codepoint support, presentation, etc. This is useful for looking
+//! for fallback fonts as efficiently as possible. For example, when the glyph
+//! "X" is not found, we can quickly search through deferred fonts rather
+//! than loading the font completely.
+const Collection = @This();
+
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const config = @import("../config.zig");
+const comparison = @import("../datastruct/comparison.zig");
+const font = @import("main.zig");
+const options = font.options;
+const DeferredFace = font.DeferredFace;
+const DesiredSize = font.face.DesiredSize;
+const Face = font.Face;
+const Library = font.Library;
+const Metrics = font.Metrics;
+const Presentation = font.Presentation;
+const Style = font.Style;
+
+const log = std.log.scoped(.font_collection);
+
+/// The available faces we have. This shouldn't be modified manually.
+/// Instead, use the functions available on Collection.
+faces: StyleArray,
+
+/// The metric modifiers to use for this collection. The memory
+/// for this is owned by the user and is not freed by the collection.
+///
+/// Call `Collection.updateMetrics` to recompute the
+/// collection's metrics after making changes to these.
+metric_modifiers: Metrics.ModifierSet = .{},
+
+/// Metrics for this collection. Call `Collection.updateMetrics` to (re)compute
+/// these after adding a primary font or making changes to `metric_modifiers`.
+metrics: ?Metrics = null,
+
+/// The face metrics for the primary face in the collection.
+///
+/// We keep this around so we don't need to re-compute it when calculating
+/// the scale factor for additional fonts added to the collection.
+primary_face_metrics: ?Metrics.FaceMetrics = null,
+
+/// The load options for deferred faces in the face list. If this
+/// is not set, then deferred faces will not be loaded. Attempting to
+/// add a deferred face will result in an error.
+load_options: ?LoadOptions = null,
+
+/// Initialize an empty collection.
+pub fn init() Collection {
+ // Initialize our styles array, preallocating some space that is
+ // likely to be used.
+ return .{ .faces = .initFill(.{}) };
+}
+
+pub fn deinit(self: *Collection, alloc: Allocator) void {
+ var it = self.faces.iterator();
+ while (it.next()) |array| {
+ var entry_it = array.value.iterator(0);
+ // Deinit all entries, aliases can be ignored.
+ while (entry_it.next()) |entry_or_alias|
+ switch (entry_or_alias.*) {
+ .entry => |*entry| entry.deinit(),
+ .alias => {},
+ };
+ array.value.deinit(alloc);
+ }
+
+ if (self.load_options) |*v| v.deinit(alloc);
+}
+
+/// Options for adding a face to the collection.
+pub const AddOptions = struct {
+ /// What style this face is.
+ style: Style,
+ /// What size adjustment to use.
+ size_adjustment: SizeAdjustment,
+ /// Whether this is a fallback face.
+ fallback: bool,
+};
+
+pub const AddError =
+ Allocator.Error ||
+ error{
+ /// There's no more room in the collection.
+ CollectionFull,
+ /// The call to `face.setSize` failed.
+ SetSizeFailed,
+ };
+
+/// Add a face to the collection. This face will be added next in priority if
+/// others exist already, i.e. it'll be the _last_ to be searched for a glyph
+/// in that list.
+///
+/// If no error is encountered then the collection takes ownership of the face,
+/// in which case face will be deallocated when the collection is deallocated.
+///
+/// When added, the size of the face will be adjusted to match `load_options`.
+///
+/// Returns the index for the added face.
+pub fn add(
+ self: *Collection,
+ alloc: Allocator,
+ face: Face,
+ opts: AddOptions,
+) AddError!Index {
+ const list = self.faces.getPtr(opts.style);
+
+ // We have some special indexes so we must never pass those.
+ const idx = list.count();
+ if (idx >= Index.Special.start - 1)
+ return error.CollectionFull;
+
+ var owned_face = face;
+
+ // Scale factor to adjust the size of the added face.
+ const scale_factor = self.scaleFactor(
+ owned_face.getMetrics(),
+ opts.size_adjustment,
+ );
+
+ // If we have load options, we update the size to ensure
+ // it's matches and is normalized to the primary if possible.
+ if (self.load_options) |load_opts| {
+ var new_opts = load_opts;
+ new_opts.size.points *= @floatCast(scale_factor);
+ owned_face.setSize(new_opts.faceOptions()) catch return error.SetSizeFailed;
+ }
+
+ try list.append(alloc, .{
+ .entry = .{
+ .face = .{ .loaded = owned_face },
+ .fallback = opts.fallback,
+ .scale_factor = .{ .scale = scale_factor },
+ },
+ });
+
+ return .{ .style = opts.style, .idx = @intCast(idx) };
+}
+
+pub const AddDeferredError =
+ Allocator.Error ||
+ error{
+ /// There's no more room in the collection.
+ CollectionFull,
+ /// `load_options` is null, can't do deferred loading.
+ DeferredLoadingUnavailable,
+ };
+
+/// Add a deferred face to the collection.
+///
+/// Returns the index for the added face.
+pub fn addDeferred(
+ self: *Collection,
+ alloc: Allocator,
+ face: DeferredFace,
+ opts: AddOptions,
+) AddDeferredError!Index {
+ const list = self.faces.getPtr(opts.style);
+
+ if (self.load_options == null) return error.DeferredLoadingUnavailable;
+
+ // We have some special indexes so we must never pass those.
+ const idx = list.count();
+ if (idx >= Index.Special.start - 1)
+ return error.CollectionFull;
+
+ try list.append(alloc, .{
+ .entry = .{
+ .face = .{ .deferred = face },
+ .fallback = opts.fallback,
+ .scale_factor = .{ .adjustment = opts.size_adjustment },
+ },
+ });
+
+ return .{ .style = opts.style, .idx = @intCast(idx) };
+}
+
+/// Return the Face represented by a given Index. The returned pointer
+/// is only valid as long as this collection is not modified.
+///
+/// This will initialize the face if it is deferred and not yet loaded,
+/// which can fail.
+pub fn getFace(self: *Collection, index: Index) !*Face {
+ return try self.getFaceFromEntry(try self.getEntry(index));
+}
+
+pub const EntryError = error{
+ /// Index represents a special font (built-in) and these don't
+ /// have an associated face. This should be caught upstream and use
+ /// alternate logic.
+ SpecialHasNoFace,
+
+ /// Invalid index.
+ IndexOutOfBounds,
+};
+
+/// Get the unaliased entry from an index
+pub fn getEntry(self: *Collection, index: Index) EntryError!*Entry {
+ if (index.special() != null) return error.SpecialHasNoFace;
+ const list = self.faces.getPtr(index.style);
+ if (index.idx >= list.len) return error.IndexOutOfBounds;
+ return list.at(index.idx).getEntry();
+}
+
+/// Get the face from an entry.
+///
+/// This entry must not be an alias.
+fn getFaceFromEntry(
+ self: *Collection,
+ entry: *Entry,
+) !*Face {
+ return switch (entry.face) {
+ .deferred => |*d| deferred: {
+ var opts = self.load_options orelse
+ return error.DeferredLoadingUnavailable;
+
+ // Load the face.
+ var face = try d.load(opts.library, opts.faceOptions());
+ errdefer face.deinit();
+
+ // Calculate the scale factor for this
+ // entry now that we have a loaded face.
+ if (entry.scale_factor == .adjustment) {
+ const factor = self.scaleFactor(
+ face.getMetrics(),
+ entry.scale_factor.adjustment,
+ );
+ entry.scale_factor = .{ .scale = factor };
+ }
+
+ // If our scale factor is something other
+ // than 1.0 then we need to resize the face.
+ if (entry.scale_factor.scale != 1.0) {
+ opts.size.points *= @floatCast(entry.scale_factor.scale);
+ try face.setSize(opts.faceOptions());
+ }
+
+ // Deinit the deferred face now that we have
+ // loaded it and are past any possible errors.
+ errdefer comptime unreachable;
+ d.deinit();
+
+ // Set the loaded face on the entry.
+ entry.face = .{ .loaded = face };
+
+ // Return the pointer to it.
+ break :deferred &entry.face.loaded;
+ },
+
+ .loaded => |*f| f,
+ };
+}
+
+/// Return the index of the font in this collection that contains
+/// the given codepoint, style, and presentation. If no font is found,
+/// null is returned.
+///
+/// This does not trigger font loading; deferred fonts can be
+/// searched for codepoints.
+pub fn getIndex(
+ self: *const Collection,
+ cp: u32,
+ style: Style,
+ p_mode: PresentationMode,
+) ?Index {
+ var i: usize = 0;
+ var it = self.faces.get(style).constIterator(0);
+ while (it.next()) |entry_or_alias| {
+ if (entry_or_alias.getConstEntry().hasCodepoint(cp, p_mode)) {
+ return .{
+ .style = style,
+ .idx = @intCast(i),
+ };
+ }
+
+ i += 1;
+ }
+
+ // Not found
+ return null;
+}
+
+/// Check if a specific font index has a specific codepoint. This does not
+/// necessarily force the font to load. The presentation value "p" will
+/// verify the Emoji representation matches if it is non-null. If "p" is
+/// null then any presentation will be accepted.
+pub fn hasCodepoint(
+ self: *const Collection,
+ index: Index,
+ cp: u32,
+ p_mode: PresentationMode,
+) bool {
+ const list = self.faces.get(index.style);
+ if (index.idx >= list.count()) return false;
+ return list.at(index.idx).getConstEntry().hasCodepoint(cp, p_mode);
+}
+
+pub const CompleteError = Allocator.Error || error{
+ DefaultUnavailable,
+};
+
+/// Ensure we have an option for all styles in the collection, such
+/// as italic and bold by synthesizing them if necessary from the
+/// first regular face that has text glyphs.
+///
+/// If there is no regular face that has text glyphs, then this
+/// does nothing.
+pub fn completeStyles(
+ self: *Collection,
+ alloc: Allocator,
+ synthetic_config: config.FontSyntheticStyle,
+) CompleteError!void {
+ // If every style has at least one entry then we're done!
+ // This is the most common case.
+ empty: {
+ var it = self.faces.iterator();
+ while (it.next()) |entry| {
+ if (entry.value.count() == 0) break :empty;
+ }
+
+ return;
+ }
+
+ // Find the first regular face that has non-colorized text glyphs.
+ // This is the font we want to fallback to. This may not be index zero
+ // if a user configures something like an Emoji font first.
+ const regular_entry: *Entry = entry: {
+ const list = self.faces.getPtr(.regular);
+ if (list.count() == 0) return;
+
+ // Find our first regular face that has text glyphs.
+ var it = list.iterator(0);
+ while (it.next()) |entry_or_alias| {
+ // Load our face. If we fail to load it, we just skip it and
+ // continue on to try the next one.
+ const entry = entry_or_alias.getEntry();
+ const face = self.getFaceFromEntry(entry) catch |err| {
+ log.warn("error loading regular entry={d} err={}", .{
+ it.index - 1,
+ err,
+ });
+
+ continue;
+ };
+
+ // We have two conditionals here. The color check is obvious:
+ // we want to auto-italicize a normal text font. The second
+ // check is less obvious... for mixed color/non-color fonts, we
+ // accept the regular font if it has basic ASCII. This may not
+ // be strictly correct (especially with international fonts) but
+ // it's a reasonable heuristic and the first case will match 99%
+ // of the time.
+ if (!face.hasColor() or face.glyphIndex('A') != null) {
+ break :entry entry;
+ }
+ }
+
+ // No regular text face found. We can't provide any fallback.
+ return error.DefaultUnavailable;
+ };
+
+ // If we don't have italic, attempt to create a synthetic italic face.
+ // If we can't create a synthetic italic face, we'll just use the regular
+ // face for italic.
+ const italic_list = self.faces.getPtr(.italic);
+ const have_italic = italic_list.count() > 0;
+ if (!have_italic) italic: {
+ if (!synthetic_config.italic) {
+ log.info("italic style not available and synthetic italic disabled", .{});
+ try italic_list.append(alloc, .{ .alias = regular_entry });
+ break :italic;
+ }
+
+ const synthetic = self.syntheticItalic(regular_entry) catch |err| {
+ log.warn("failed to create synthetic italic, italic style will not be available err={}", .{err});
+ try italic_list.append(alloc, .{ .alias = regular_entry });
+ break :italic;
+ };
+
+ const synthetic_entry: Entry = .{
+ .face = .{ .loaded = synthetic },
+ .fallback = false,
+ };
+ log.info("synthetic italic face created", .{});
+ try italic_list.append(alloc, .{ .entry = synthetic_entry });
+ }
+
+ // If we don't have bold, use the regular font.
+ const bold_list = self.faces.getPtr(.bold);
+ const have_bold = bold_list.count() > 0;
+ if (!have_bold) bold: {
+ if (!synthetic_config.bold) {
+ log.info("bold style not available and synthetic bold disabled", .{});
+ try bold_list.append(alloc, .{ .alias = regular_entry });
+ break :bold;
+ }
+
+ const synthetic = self.syntheticBold(regular_entry) catch |err| {
+ log.warn("failed to create synthetic bold, bold style will not be available err={}", .{err});
+ try bold_list.append(alloc, .{ .alias = regular_entry });
+ break :bold;
+ };
+
+ const synthetic_entry: Entry = .{
+ .face = .{ .loaded = synthetic },
+ .fallback = false,
+ };
+ log.info("synthetic bold face created", .{});
+ try bold_list.append(alloc, .{ .entry = synthetic_entry });
+ }
+
+ // If we don't have bold italic, we attempt to synthesize a bold variant
+ // of the italic font. If we can't do that, we'll use the italic font.
+ const bold_italic_list = self.faces.getPtr(.bold_italic);
+ if (bold_italic_list.count() == 0) bold_italic: {
+ if (!synthetic_config.@"bold-italic") {
+ log.info("bold italic style not available and synthetic bold italic disabled", .{});
+ try bold_italic_list.append(alloc, .{ .alias = regular_entry });
+ break :bold_italic;
+ }
+
+ // Prefer to synthesize on top of the face we already had. If we
+ // have bold then we try to synthesize italic on top of bold.
+ if (have_bold) {
+ const base_entry: *Entry = bold_list.at(0).getEntry();
+ if (self.syntheticItalic(base_entry)) |synthetic| {
+ log.info("synthetic bold italic face created from bold", .{});
+ const synthetic_entry: Entry = .{
+ .face = .{ .loaded = synthetic },
+ .fallback = false,
+ };
+ try bold_italic_list.append(alloc, .{ .entry = synthetic_entry });
+ break :bold_italic;
+ } else |_| {}
+
+ // If synthesizing italic failed, then we try to synthesize
+ // bold on whatever italic font we have.
+ }
+
+ const base_entry: *Entry = italic_list.at(0).getEntry();
+ if (self.syntheticBold(base_entry)) |synthetic| {
+ log.info("synthetic bold italic face created from italic", .{});
+ const synthetic_entry: Entry = .{
+ .face = .{ .loaded = synthetic },
+ .fallback = false,
+ };
+ try bold_italic_list.append(alloc, .{ .entry = synthetic_entry });
+ break :bold_italic;
+ } else |_| {}
+
+ log.warn("bold italic style not available, using italic font", .{});
+ try bold_italic_list.append(alloc, .{ .alias = base_entry });
+ }
+}
+
+/// Create a synthetic bold font face from the given entry and return it.
+fn syntheticBold(self: *Collection, entry: *Entry) !Face {
+ // Not all font backends support synthetic bold.
+ if (comptime !@hasDecl(Face, "syntheticBold")) return error.SyntheticBoldUnavailable;
+
+ // We require loading options to create a synthetic bold face.
+ const opts = self.load_options orelse return error.DeferredLoadingUnavailable;
+
+ // Try to bold it.
+ const regular = try self.getFaceFromEntry(entry);
+
+ // Inherit size from regular; it may be different than opts.size
+ // due to scaling adjustments
+ var face_opts = opts.faceOptions();
+ face_opts.size = regular.size;
+ const face = try regular.syntheticBold(opts.faceOptions());
+
+ var buf: [256]u8 = undefined;
+ if (face.name(&buf)) |name| {
+ log.info("font synthetic bold created family={s}", .{name});
+ } else |_| {}
+
+ return face;
+}
+
+/// Create a synthetic italic font face from the given entry and return it.
+fn syntheticItalic(self: *Collection, entry: *Entry) !Face {
+ // Not all font backends support synthetic italicization.
+ if (comptime !@hasDecl(Face, "syntheticItalic")) return error.SyntheticItalicUnavailable;
+
+ // We require loading options to create a synthetic italic face.
+ const opts = self.load_options orelse return error.DeferredLoadingUnavailable;
+
+ // Try to italicize it.
+ const regular = try self.getFaceFromEntry(entry);
+
+ // Inherit size from regular; it may be different than opts.size
+ // due to scaling adjustments
+ var face_opts = opts.faceOptions();
+ face_opts.size = regular.size;
+ const face = try regular.syntheticItalic(opts.faceOptions());
+
+ var buf: [256]u8 = undefined;
+ if (face.name(&buf)) |name| {
+ log.info("font synthetic italic created family={s}", .{name});
+ } else |_| {}
+
+ return face;
+}
+
+pub const SetSizeError =
+ UpdateMetricsError ||
+ error{
+ /// `self.load_options` is `null`.
+ DeferredLoadingUnavailable,
+ /// The call to `face.setSize` failed.
+ SetSizeFailed,
+ };
+
+/// Update the size of all faces in the collection. This will
+/// also update the size in the load options for future deferred
+/// face loading.
+///
+/// This requires load options to be set.
+pub fn setSize(
+ self: *Collection,
+ size: DesiredSize,
+) SetSizeError!void {
+ // Get a pointer to our options so we can modify the size.
+ const opts = &(self.load_options orelse return error.DeferredLoadingUnavailable);
+ opts.size = size;
+
+ const primary_entry = self.getEntry(.{ .idx = 0 }) catch null;
+
+ // Resize all our faces that are loaded
+ var it = self.faces.iterator();
+ while (it.next()) |array| {
+ var entry_it = array.value.iterator(0);
+ // Resize all faces. We skip entries that are aliases, since
+ // the underlying face will have a non-alias entry somewhere.
+ while (entry_it.next()) |entry_or_alias| {
+ if (entry_or_alias.* == .alias) continue;
+
+ const entry = entry_or_alias.getEntry();
+
+ if (entry.getLoaded()) |face| {
+ // If this isn't our primary face, we scale
+ // the size appropriately before setting it.
+ //
+ // If we don't have a primary face we also don't.
+ var new_opts = opts.*;
+ if (primary_entry != null and entry != primary_entry) {
+ new_opts.size.points *= @floatCast(entry.scale_factor.scale);
+ }
+ face.setSize(new_opts.faceOptions()) catch return error.SetSizeFailed;
+ }
+ }
+ }
+
+ try self.updateMetrics();
+}
+
+/// Options for adjusting the size of a face relative to the primary face.
+pub const SizeAdjustment = enum {
+ /// Don't adjust the size for this face, use the original point size.
+ none,
+ /// Match ideograph character width with the primary face.
+ ic_width,
+ /// Match ex height with the primary face.
+ ex_height,
+ /// Match cap height with the primary face.
+ cap_height,
+ /// Match line height with the primary face.
+ line_height,
+};
+
+/// Calculate a factor by which to scale the provided face to match
+/// it with the primary face, depending on the specified adjustment.
+///
+/// If this encounters any problems loading the primary face or its
+/// metrics then it just returns `1.0`.
+///
+/// This functions very much like the `font-size-adjust` CSS property.
+/// ref: https://developer.mozilla.org/en-US/docs/Web/CSS/font-size-adjust
+fn scaleFactor(
+ self: *Collection,
+ face_metrics: Metrics.FaceMetrics,
+ adjustment: SizeAdjustment,
+) f64 {
+ // If there's no adjustment, the scale is 1.0
+ if (adjustment == .none) return 1.0;
+
+ // If we haven't calculated our primary face metrics yet, do so now.
+ if (self.primary_face_metrics == null) {
+ @branchHint(.unlikely);
+ // If we can't load the primary face, just use 1.0 as the scale factor.
+ const primary_face = self.getFace(.{ .idx = 0 }) catch return 1.0;
+ self.primary_face_metrics = primary_face.getMetrics();
+ }
+
+ const primary_metrics = self.primary_face_metrics.?;
+
+ // We normalize the metrics values which are expressed in px to instead
+ // be in ems, so that it doesn't matter what size the faces actually are.
+ const primary_scale = 1 / primary_metrics.px_per_em;
+ const face_scale = 1 / face_metrics.px_per_em;
+
+ // We get the target metrics from the primary face and this face depending
+ // on the specified `adjustment`. If this face doesn't explicitly define a
+ // metric, or if the value it defines is invalid, we fall through to other
+ // options in the order below.
+ //
+ // In order to make sure the value is valid, we compare it with the result
+ // of the estimator function, which rules out both null and invalid values.
+ const primary_metric: f64, const face_metric: f64 =
+ normalize_by: switch (adjustment) {
+ .ic_width => {
+ if (face_metrics.ic_width != face_metrics.icWidth())
+ continue :normalize_by .ex_height;
+
+ break :normalize_by .{
+ primary_metrics.icWidth() * primary_scale,
+ face_metrics.icWidth() * face_scale,
+ };
+ },
+
+ .ex_height => {
+ if (face_metrics.ex_height != face_metrics.exHeight())
+ continue :normalize_by .cap_height;
+
+ break :normalize_by .{
+ primary_metrics.exHeight() * primary_scale,
+ face_metrics.exHeight() * face_scale,
+ };
+ },
+
+ .cap_height => {
+ if (face_metrics.cap_height != face_metrics.capHeight())
+ continue :normalize_by .line_height;
+
+ break :normalize_by .{
+ primary_metrics.capHeight() * primary_scale,
+ face_metrics.capHeight() * face_scale,
+ };
+ },
+
+ .line_height => .{
+ primary_metrics.lineHeight() * primary_scale,
+ face_metrics.lineHeight() * face_scale,
+ },
+
+ .none => unreachable,
+ };
+
+ return primary_metric / face_metric;
+}
+
+const UpdateMetricsError = error{
+ CannotLoadPrimaryFont,
+};
+
+/// Update the cell metrics for this collection, based on
+/// the primary font and the modifiers in `metric_modifiers`.
+///
+/// This requires a primary font (index `0`) to be present.
+pub fn updateMetrics(self: *Collection) UpdateMetricsError!void {
+ const primary_face = self.getFace(.{ .idx = 0 }) catch return error.CannotLoadPrimaryFont;
+
+ self.primary_face_metrics = primary_face.getMetrics();
+
+ var metrics = Metrics.calc(self.primary_face_metrics.?);
+
+ metrics.apply(self.metric_modifiers);
+
+ self.metrics = metrics;
+}
+
+/// Packed array of all Style enum cases mapped to a growable list of faces.
+///
+/// We use this data structure because there aren't many styles and all
+/// styles are typically loaded for a terminal session. The overhead per
+/// style even if it is not used or barely used is minimal given the
+/// small style count.
+///
+/// We use a segmented list because the entry values must be pointer-stable
+/// to support aliases.
+///
+/// WARNING: We cannot use any prealloc yet for the segmented list because
+/// the collection is copied around by value and pointers aren't stable.
+const StyleArray = std.EnumArray(Style, std.SegmentedList(EntryOrAlias, 0));
+
+/// Load options are used to configure all the details a Collection
+/// needs to load deferred faces.
+pub const LoadOptions = struct {
+ /// The library to use for loading faces. This is not owned by
+ /// the collection and can be used by multiple collections. When
+ /// deinitializing the collection, the library is not deinitialized.
+ library: Library,
+
+ /// The desired font size for all loaded faces.
+ size: DesiredSize = .{ .points = 12 },
+
+ /// Freetype Load Flags to use when loading glyphs. This is a list of
+ /// bitfield constants that controls operations to perform during glyph
+ /// loading. Only a subset is exposed for configuration, for the whole set
+ /// of flags see `pkg.freetype.face.LoadFlags`.
+ freetype_load_flags: font.face.FreetypeLoadFlags = font.face.freetype_load_flags_default,
+
+ pub fn deinit(self: *LoadOptions, alloc: Allocator) void {
+ _ = self;
+ _ = alloc;
+ }
+
+ /// The options to use for loading faces.
+ pub fn faceOptions(self: *const LoadOptions) font.face.Options {
+ return .{
+ .size = self.size,
+ .freetype_load_flags = self.freetype_load_flags,
+ };
+ }
+};
+
+/// A entry in a collection can be deferred or loaded. A deferred face
+/// is not yet fully loaded and only represents the font descriptor
+/// and usually uses less resources. A loaded face is fully parsed,
+/// ready to rasterize, and usually uses more resources than a
+/// deferred version.
+///
+/// A face can also be a "fallback" variant that is still either
+/// deferred or loaded. Today, there is only one difference between
+/// fallback and non-fallback (or "explicit") faces: the handling
+/// of emoji presentation.
+///
+/// For explicit faces, when an explicit emoji presentation is
+/// not requested, we will use any glyph for that codepoint found
+/// even if the font presentation does not match the UCD
+/// (Unicode Character Database) value. When an explicit presentation
+/// is requested (via either VS15/V16), that is always honored.
+/// The reason we do this is because we assume that if a user
+/// explicitly chosen a font face (hence it is "explicit" and
+/// not "fallback"), they want to use any glyphs possible within that
+/// font face. Fallback fonts on the other hand are picked as a
+/// last resort, so we should prefer exactness if possible.
+pub const Entry = struct {
+ const AnyFace = union(enum) {
+ /// Not yet loaded.
+ deferred: DeferredFace,
+
+ /// Loaded.
+ loaded: Face,
+ };
+
+ face: AnyFace,
+
+ /// Whether this face is a fallback face, see
+ /// main doc comment on Entry for more info.
+ fallback: bool,
+
+ /// Factor to multiply the collection size by for this face, or
+ /// else the size adjustment that should be used to calculate
+ /// once the face is loaded.
+ ///
+ /// This is computed when the face is loaded, based on a scale
+ /// factor computed for an adjustment from the primary face to
+ /// this one, which allows fallback fonts to be harmonized with
+ /// the primary font by matching one of the metrics between them.
+ scale_factor: union(enum) {
+ adjustment: SizeAdjustment,
+ scale: f64,
+ } = .{ .scale = 1.0 },
+
+ pub fn deinit(self: *Entry) void {
+ switch (self.face) {
+ inline .deferred, .loaded => |*v| v.deinit(),
+ }
+ }
+
+ /// If this face is loaded, then this returns the `Face`,
+ /// otherwise returns null.
+ pub fn getLoaded(self: *Entry) ?*Face {
+ return switch (self.face) {
+ .deferred => null,
+ .loaded => |*face| face,
+ };
+ }
+
+ /// True if the entry is deferred.
+ fn isDeferred(self: Entry) bool {
+ return switch (self.face) {
+ .deferred => true,
+ .loaded => false,
+ };
+ }
+
+ /// True if this face satisfies the given codepoint and presentation.
+ pub fn hasCodepoint(
+ self: Entry,
+ cp: u32,
+ p_mode: PresentationMode,
+ ) bool {
+ return mode: switch (p_mode) {
+ .default => |p| if (self.fallback)
+ // Fallback fonts require explicit presentation matching.
+ continue :mode .{ .explicit = p }
+ else
+ // Non-fallback fonts do not.
+ continue :mode .any,
+
+ .explicit => |p| switch (self.face) {
+ .deferred => |v| v.hasCodepoint(cp, p),
+
+ .loaded => |face| explicit: {
+ const index = face.glyphIndex(cp) orelse break :explicit false;
+ break :explicit switch (p) {
+ .text => !face.isColorGlyph(index),
+ .emoji => face.isColorGlyph(index),
+ };
+ },
+ },
+
+ .any => switch (self.face) {
+ .deferred => |v| v.hasCodepoint(cp, null),
+
+ .loaded => |face| face.glyphIndex(cp) != null,
+ },
+ };
+ }
+};
+
+pub const EntryOrAlias = union(enum) {
+ entry: Entry,
+
+ /// An alias to another entry. This is used to share the same face,
+ /// avoid memory duplication. An alias must point to a non-alias entry.
+ alias: *Entry,
+
+ /// Get a pointer to the underlying entry.
+ pub fn getEntry(self: *EntryOrAlias) *Entry {
+ return switch (self.*) {
+ .entry => |*v| v,
+ .alias => |v| v,
+ };
+ }
+
+ /// Get a const pointer to the underlying entry.
+ pub fn getConstEntry(self: *const EntryOrAlias) *const Entry {
+ return switch (self.*) {
+ .entry => |*v| v,
+ .alias => |v| v,
+ };
+ }
+};
+
+/// The requested presentation for a codepoint.
+pub const PresentationMode = union(enum) {
+ /// The codepoint has an explicit presentation that is required,
+ /// i.e. VS15/V16.
+ explicit: Presentation,
+
+ /// The codepoint has no explicit presentation and we should use
+ /// the presentation from the UCD.
+ default: Presentation,
+
+ /// The codepoint can be any presentation.
+ any: void,
+};
+
+/// This represents a specific font in the collection.
+///
+/// The backing size of this packed struct represents the total number
+/// of possible usable fonts in a collection. And the number of bits
+/// used for the index and not the style represents the total number
+/// of possible usable fonts for a given style.
+///
+/// The goal is to keep the size of this struct as small as practical. We
+/// accept the limitations that this imposes so long as they're reasonable.
+/// At the time of writing this comment, this is a 16-bit struct with 13
+/// bits used for the index, supporting up to 8192 fonts per style. This
+/// seems more than reasonable. There are synthetic scenarios where this
+/// could be a limitation but I can't think of any that are practical.
+///
+/// If you somehow need more fonts per style, you can increase the size of
+/// the Backing type and everything should just work fine.
+pub const Index = packed struct(Index.Backing) {
+ const Backing = u16;
+ const backing_bits = @typeInfo(Backing).int.bits;
+
+ /// The number of bits we use for the index.
+ const idx_bits = backing_bits - @typeInfo(@typeInfo(Style).@"enum".tag_type).int.bits;
+ pub const IndexInt = @Type(.{ .int = .{ .signedness = .unsigned, .bits = idx_bits } });
+
+ /// The special-case fonts that we support.
+ pub const Special = enum(IndexInt) {
+ // We start all special fonts at this index so they can be detected.
+ pub const start = std.math.maxInt(IndexInt);
+
+ /// Sprite drawing, this is rendered JIT using 2D graphics APIs.
+ sprite = start,
+ };
+
+ style: Style = .regular,
+ idx: IndexInt = 0,
+
+ /// Initialize a special font index.
+ pub fn initSpecial(v: Special) Index {
+ return .{ .style = .regular, .idx = @intFromEnum(v) };
+ }
+
+ /// Convert to int
+ pub fn int(self: Index) Backing {
+ return @bitCast(self);
+ }
+
+ /// Returns true if this is a "special" index which doesn't map to
+ /// a real font face. We can still render it but there is no face for
+ /// this font.
+ pub fn special(self: Index) ?Special {
+ if (self.idx < Special.start) return null;
+ return @enumFromInt(self.idx);
+ }
+
+ test {
+ // We never want to take up more than a byte since font indexes are
+ // everywhere so if we increase the size of this we'll dramatically
+ // increase our memory usage.
+ try std.testing.expectEqual(@sizeOf(Backing), @sizeOf(Index));
+
+ // Just so we're aware when this changes. The current maximum number
+ // of fonts for a style is 13 bits or 8192 fonts.
+ try std.testing.expectEqual(13, idx_bits);
+ }
+};
+
+test init {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var c = init();
+ defer c.deinit(alloc);
+}
+
+test "add full" {
+ // This test is way too slow to run under Valgrind, unfortunately.
+ if (std.valgrind.runningOnValgrind() > 0) return error.SkipZigTest;
+
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ const testFont = font.embedded.regular;
+
+ var lib = try Library.init(alloc);
+ defer lib.deinit();
+
+ var c = init();
+ defer c.deinit(alloc);
+
+ for (0..Index.Special.start - 1) |_| {
+ _ = try c.add(alloc, try .init(
+ lib,
+ testFont,
+ .{ .size = .{ .points = 12 } },
+ ), .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .none,
+ });
+ }
+
+ var face = try Face.init(
+ lib,
+ testFont,
+ .{ .size = .{ .points = 12 } },
+ );
+ // We have to deinit it manually since the
+ // collection doesn't do it if adding fails.
+ defer face.deinit();
+ try testing.expectError(
+ error.CollectionFull,
+ c.add(alloc, face, .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .none,
+ }),
+ );
+}
+
+test "add deferred without loading options" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var c = init();
+ defer c.deinit(alloc);
+
+ try testing.expectError(error.DeferredLoadingUnavailable, c.addDeferred(
+ alloc,
+ // This can be undefined because it should never be accessed.
+ undefined,
+ .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .none,
+ },
+ ));
+}
+
+test getFace {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ const testFont = font.embedded.regular;
+
+ var lib = try Library.init(alloc);
+ defer lib.deinit();
+
+ var c = init();
+ defer c.deinit(alloc);
+
+ const idx = try c.add(alloc, try .init(
+ lib,
+ testFont,
+ .{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
+ ), .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .none,
+ });
+
+ {
+ const face1 = try c.getFace(idx);
+ const face2 = try c.getFace(idx);
+ try testing.expectEqual(@intFromPtr(face1), @intFromPtr(face2));
+ }
+}
+
+test getIndex {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ const testFont = font.embedded.regular;
+
+ var lib = try Library.init(alloc);
+ defer lib.deinit();
+
+ var c = init();
+ defer c.deinit(alloc);
+
+ _ = try c.add(alloc, try .init(
+ lib,
+ testFont,
+ .{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
+ ), .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .none,
+ });
+
+ // Should find all visible ASCII
+ var i: u32 = 32;
+ while (i < 127) : (i += 1) {
+ const idx = c.getIndex(i, .regular, .{ .any = {} });
+ try testing.expect(idx != null);
+ }
+
+ // Should not find emoji
+ {
+ const idx = c.getIndex('🥸', .regular, .{ .any = {} });
+ try testing.expect(idx == null);
+ }
+}
+
+test completeStyles {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ const testFont = font.embedded.regular;
+
+ var lib = try Library.init(alloc);
+ defer lib.deinit();
+
+ var c = init();
+ defer c.deinit(alloc);
+ c.load_options = .{ .library = lib };
+
+ _ = try c.add(alloc, try .init(
+ lib,
+ testFont,
+ .{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
+ ), .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .none,
+ });
+
+ try testing.expect(c.getIndex('A', .bold, .{ .any = {} }) == null);
+ try testing.expect(c.getIndex('A', .italic, .{ .any = {} }) == null);
+ try testing.expect(c.getIndex('A', .bold_italic, .{ .any = {} }) == null);
+ try c.completeStyles(alloc, .{});
+ try testing.expect(c.getIndex('A', .bold, .{ .any = {} }) != null);
+ try testing.expect(c.getIndex('A', .italic, .{ .any = {} }) != null);
+ try testing.expect(c.getIndex('A', .bold_italic, .{ .any = {} }) != null);
+}
+
+test setSize {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ const testFont = font.embedded.regular;
+
+ var lib = try Library.init(alloc);
+ defer lib.deinit();
+
+ var c = init();
+ defer c.deinit(alloc);
+ c.load_options = .{ .library = lib };
+
+ _ = try c.add(alloc, try .init(
+ lib,
+ testFont,
+ .{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
+ ), .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .none,
+ });
+
+ try testing.expectEqual(@as(u32, 12), c.load_options.?.size.points);
+ try c.setSize(.{ .points = 24 });
+ try testing.expectEqual(@as(u32, 24), c.load_options.?.size.points);
+}
+
+test hasCodepoint {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ const testFont = font.embedded.regular;
+
+ var lib = try Library.init(alloc);
+ defer lib.deinit();
+
+ var c = init();
+ defer c.deinit(alloc);
+ c.load_options = .{ .library = lib };
+
+ const idx = try c.add(alloc, try .init(
+ lib,
+ testFont,
+ .{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
+ ), .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .none,
+ });
+
+ try testing.expect(c.hasCodepoint(idx, 'A', .{ .any = {} }));
+ try testing.expect(!c.hasCodepoint(idx, '🥸', .{ .any = {} }));
+}
+
+test "hasCodepoint emoji default graphical" {
+ if (options.backend != .fontconfig_freetype) return error.SkipZigTest;
+
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ const testEmoji = font.embedded.emoji;
+
+ var lib = try Library.init(alloc);
+ defer lib.deinit();
+
+ var c = init();
+ defer c.deinit(alloc);
+ c.load_options = .{ .library = lib };
+
+ const idx = try c.add(alloc, try .init(
+ lib,
+ testEmoji,
+ .{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
+ ), .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .none,
+ });
+
+ try testing.expect(!c.hasCodepoint(idx, 'A', .{ .any = {} }));
+ try testing.expect(c.hasCodepoint(idx, '🥸', .{ .any = {} }));
+ // TODO(fontmem): test explicit/implicit
+}
+
+test "metrics" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ const testFont = font.embedded.inconsolata;
+
+ var lib = try Library.init(alloc);
+ defer lib.deinit();
+
+ var c = init();
+ defer c.deinit(alloc);
+ const size: DesiredSize = .{ .points = 12, .xdpi = 96, .ydpi = 96 };
+ c.load_options = .{ .library = lib, .size = size };
+
+ _ = try c.add(alloc, try .init(
+ lib,
+ testFont,
+ .{ .size = size },
+ ), .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .none,
+ });
+
+ try c.updateMetrics();
+
+ try comparison.expectApproxEqual(font.Metrics{
+ .cell_width = 8,
+ // The cell height is 17 px because the calculation is
+ //
+ // ascender - descender + gap
+ //
+ // which, for inconsolata is
+ //
+ // 859 - -190 + 0
+ //
+ // font units, at 1000 units per em that works out to 1.049 em,
+ // and 1em should be the point size * dpi scale, so 12 * (96/72)
+ // which is 16, and 16 * 1.049 = 16.784, which finally is rounded
+ // to 17.
+ //
+ // The icon height is (2 * cap_height + face_height) / 3
+ // = (2 * 623 + 1049) / 3 = 765, and 16 * 0.765 = 12.24.
+ .cell_height = 17,
+ .cell_baseline = 3,
+ .underline_position = 17,
+ .underline_thickness = 1,
+ .strikethrough_position = 10,
+ .strikethrough_thickness = 1,
+ .overline_position = 0,
+ .overline_thickness = 1,
+ .box_thickness = 1,
+ .cursor_height = 17,
+ .icon_height = 16.784,
+ .icon_height_single = 12.24,
+ .face_width = 8.0,
+ .face_height = 16.784,
+ .face_y = -0.04,
+ }, c.metrics);
+
+ // Resize should change metrics
+ try c.setSize(.{ .points = 24, .xdpi = 96, .ydpi = 96 });
+ try comparison.expectApproxEqual(font.Metrics{
+ .cell_width = 16,
+ .cell_height = 34,
+ .cell_baseline = 6,
+ .underline_position = 34,
+ .underline_thickness = 2,
+ .strikethrough_position = 19,
+ .strikethrough_thickness = 2,
+ .overline_position = 0,
+ .overline_thickness = 2,
+ .box_thickness = 2,
+ .cursor_height = 34,
+ .icon_height = 33.568,
+ .icon_height_single = 24.48,
+ .face_width = 16.0,
+ .face_height = 33.568,
+ .face_y = -0.08,
+ }, c.metrics);
+}
+
+// TODO: Also test CJK fallback sizing, we don't currently have a CJK test font.
+test "adjusted sizes" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ const testFont = font.embedded.inconsolata;
+ const fallback = font.embedded.monaspace_neon;
+ const symbol = font.embedded.symbols_nerd_font;
+
+ var lib = try Library.init(alloc);
+ defer lib.deinit();
+
+ var c = init();
+ defer c.deinit(alloc);
+ const size: DesiredSize = .{ .points = 12, .xdpi = 96, .ydpi = 96 };
+ c.load_options = .{ .library = lib, .size = size };
+
+ // Add our primary face.
+ _ = try c.add(alloc, try .init(
+ lib,
+ testFont,
+ .{ .size = size },
+ ), .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .none,
+ });
+
+ try c.updateMetrics();
+
+ inline for ([_][]const u8{ "ex_height", "cap_height" }) |metric| {
+ // Add the fallback face with the chosen adjustment metric.
+ const fallback_idx = try c.add(alloc, try .init(
+ lib,
+ fallback,
+ .{ .size = size },
+ ), .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = @field(SizeAdjustment, metric),
+ });
+
+ // The chosen metric should match.
+ {
+ const primary_metrics = (try c.getFace(.{ .idx = 0 })).getMetrics();
+ const fallback_metrics = (try c.getFace(fallback_idx)).getMetrics();
+
+ try std.testing.expectApproxEqAbs(
+ @field(primary_metrics, metric).?,
+ @field(fallback_metrics, metric).?,
+ // We accept anything within half a pixel.
+ 0.5,
+ );
+ }
+
+ // Resize should keep that relationship.
+ try c.setSize(.{ .points = 37, .xdpi = 96, .ydpi = 96 });
+ {
+ const primary_metrics = (try c.getFace(.{ .idx = 0 })).getMetrics();
+ const fallback_metrics = (try c.getFace(fallback_idx)).getMetrics();
+
+ try std.testing.expectApproxEqAbs(
+ @field(primary_metrics, metric).?,
+ @field(fallback_metrics, metric).?,
+ // We accept anything within half a pixel.
+ 0.5,
+ );
+ }
+
+ // Reset size for the next iteration
+ try c.setSize(size);
+ }
+
+ {
+ // A reference metric of "none" should leave the size unchanged.
+ const fallback_idx = try c.add(alloc, try .init(
+ lib,
+ fallback,
+ .{ .size = size },
+ ), .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .none,
+ });
+
+ try std.testing.expectEqual(
+ (try c.getFace(.{ .idx = 0 })).size.points,
+ (try c.getFace(fallback_idx)).size.points,
+ );
+
+ // Resize should keep that.
+ try c.setSize(.{ .points = 37, .xdpi = 96, .ydpi = 96 });
+
+ try std.testing.expectEqual(
+ (try c.getFace(.{ .idx = 0 })).size.points,
+ (try c.getFace(fallback_idx)).size.points,
+ );
+
+ // Reset collection size
+ try c.setSize(size);
+ }
+
+ // Add the symbol face.
+ const symbol_idx = try c.add(alloc, try .init(
+ lib,
+ symbol,
+ .{ .size = size },
+ ), .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .ex_height,
+ });
+
+ // Test fallback to lineHeight() (ex_height and cap_height not defined in symbols font).
+ {
+ const primary_metrics = (try c.getFace(.{ .idx = 0 })).getMetrics();
+ const symbol_metrics = (try c.getFace(symbol_idx)).getMetrics();
+
+ try std.testing.expectApproxEqAbs(
+ primary_metrics.lineHeight(),
+ symbol_metrics.lineHeight(),
+ // We accept anything within half a pixel.
+ 0.5,
+ );
+ }
+}
+
+test "face metrics" {
+ // The web canvas backend doesn't calculate face metrics, only cell metrics
+ if (options.backend != .web_canvas) return error.SkipZigTest;
+
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ const narrowFont = font.embedded.cozette;
+ const wideFont = font.embedded.geist_mono;
+
+ var lib = try Library.init(alloc);
+ defer lib.deinit();
+
+ var c = init();
+ defer c.deinit(alloc);
+ const size: DesiredSize = .{ .points = 12, .xdpi = 96, .ydpi = 96 };
+ c.load_options = .{ .library = lib, .size = size };
+
+ const narrowIndex = try c.add(alloc, try .init(
+ lib,
+ narrowFont,
+ .{ .size = size },
+ ), .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .none,
+ });
+ const wideIndex = try c.add(alloc, try .init(
+ lib,
+ wideFont,
+ .{ .size = size },
+ ), .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .none,
+ });
+
+ const narrowMetrics: font.Metrics.FaceMetrics = (try c.getFace(narrowIndex)).getMetrics();
+ const wideMetrics: font.Metrics.FaceMetrics = (try c.getFace(wideIndex)).getMetrics();
+
+ // Verify provided/measured metrics. Measured
+ // values are backend-dependent due to hinting.
+ const narrowMetricsExpected = font.Metrics.FaceMetrics{
+ .px_per_em = 16.0,
+ .cell_width = switch (options.backend) {
+ .freetype,
+ .fontconfig_freetype,
+ .coretext_freetype,
+ => 8.0,
+ .coretext,
+ .coretext_harfbuzz,
+ .coretext_noshape,
+ => 7.3828125,
+ .web_canvas => unreachable,
+ },
+ .ascent = 12.3046875,
+ .descent = -3.6953125,
+ .line_gap = 0.0,
+ .underline_position = -1.2265625,
+ .underline_thickness = 1.2265625,
+ .strikethrough_position = 6.15625,
+ .strikethrough_thickness = 1.234375,
+ .cap_height = 9.84375,
+ .ex_height = 7.3828125,
+ .ascii_height = switch (options.backend) {
+ .freetype,
+ .fontconfig_freetype,
+ .coretext_freetype,
+ => 18.0625,
+ .coretext,
+ .coretext_harfbuzz,
+ .coretext_noshape,
+ => 16.0,
+ .web_canvas => unreachable,
+ },
+ };
+ const wideMetricsExpected = font.Metrics.FaceMetrics{
+ .px_per_em = 16.0,
+ .cell_width = switch (options.backend) {
+ .freetype,
+ .fontconfig_freetype,
+ .coretext_freetype,
+ => 10.0,
+ .coretext,
+ .coretext_harfbuzz,
+ .coretext_noshape,
+ => 9.6,
+ .web_canvas => unreachable,
+ },
+ .ascent = 14.72,
+ .descent = -3.52,
+ .line_gap = 1.6,
+ .underline_position = -1.6,
+ .underline_thickness = 0.8,
+ .strikethrough_position = 4.24,
+ .strikethrough_thickness = 0.8,
+ .cap_height = 11.36,
+ .ex_height = 8.48,
+ .ascii_height = switch (options.backend) {
+ .freetype,
+ .fontconfig_freetype,
+ .coretext_freetype,
+ => 16.0,
+ .coretext,
+ .coretext_harfbuzz,
+ .coretext_noshape,
+ => 15.472000000000001,
+ .web_canvas => unreachable,
+ },
+ };
+
+ inline for (
+ .{ narrowMetricsExpected, wideMetricsExpected },
+ .{ narrowMetrics, wideMetrics },
+ ) |metricsExpected, metricsActual| {
+ try comparison.expectApproxEqual(metricsExpected, metricsActual);
+ }
+
+ // Verify estimated metrics. icWidth() should equal the smaller of
+ // 2 * cell_width and ascii_height. For a narrow (wide) font, the
+ // smaller quantity is the former (latter).
+ try std.testing.expectEqual(
+ 2 * narrowMetrics.cell_width,
+ narrowMetrics.icWidth(),
+ );
+ try std.testing.expectEqual(
+ wideMetrics.ascii_height,
+ wideMetrics.icWidth(),
+ );
+}
diff --git a/vendor/libghostty-vt/src/font/DeferredFace.zig b/vendor/libghostty-vt/src/font/DeferredFace.zig
new file mode 100644
index 00000000..e818cca3
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/DeferredFace.zig
@@ -0,0 +1,463 @@
+//! A deferred face represents a single font face with all the information
+//! necessary to load it, but defers loading the full face until it is
+//! needed.
+//!
+//! This allows us to have many fallback fonts to look for glyphs, but
+//! only load them if they're really needed.
+const DeferredFace = @This();
+
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const fontconfig = @import("fontconfig");
+const macos = @import("macos");
+const font = @import("main.zig");
+const options = @import("main.zig").options;
+const Library = @import("main.zig").Library;
+const Face = @import("main.zig").Face;
+const Presentation = @import("main.zig").Presentation;
+
+const log = std.log.scoped(.deferred_face);
+
+/// Fontconfig
+fc: if (options.backend == .fontconfig_freetype) ?Fontconfig else void =
+ if (options.backend == .fontconfig_freetype) null else {},
+
+/// CoreText
+ct: if (font.Discover == font.discovery.CoreText) ?CoreText else void =
+ if (font.Discover == font.discovery.CoreText) null else {},
+
+/// Canvas
+wc: if (options.backend == .web_canvas) ?WebCanvas else void =
+ if (options.backend == .web_canvas) null else {},
+
+/// Fontconfig specific data. This is only present if building with fontconfig.
+pub const Fontconfig = struct {
+ /// The pattern for this font. This must be the "render prepared" pattern.
+ /// (i.e. call FcFontRenderPrepare).
+ pattern: *fontconfig.Pattern,
+
+ /// Charset and Langset are used for quick lookup if a codepoint and
+ /// presentation style are supported. They can be derived from pattern
+ /// but are cached since they're frequently used.
+ charset: *const fontconfig.CharSet,
+ langset: *const fontconfig.LangSet,
+
+ /// Variations to apply to this font.
+ variations: []const font.face.Variation,
+
+ pub fn deinit(self: *Fontconfig) void {
+ self.pattern.destroy();
+ self.* = undefined;
+ }
+};
+
+/// CoreText specific data. This is only present when building with CoreText.
+pub const CoreText = struct {
+ /// The initialized font
+ font: *macos.text.Font,
+
+ /// Variations to apply to this font. We apply the variations to the
+ /// search descriptor but sometimes when the font collection is
+ /// made the variation axes are reset so we have to reapply them.
+ variations: []const font.face.Variation,
+
+ pub fn deinit(self: *CoreText) void {
+ self.font.release();
+ self.* = undefined;
+ }
+};
+
+/// WebCanvas specific data. This is only present when building with canvas.
+pub const WebCanvas = struct {
+ /// The allocator to use for fonts
+ alloc: Allocator,
+
+ /// The string to use for the "font" attribute for the canvas
+ font_str: [:0]const u8,
+
+ /// The presentation for this font.
+ presentation: Presentation,
+
+ pub fn deinit(self: *WebCanvas) void {
+ self.alloc.free(self.font_str);
+ self.* = undefined;
+ }
+};
+
+pub fn deinit(self: *DeferredFace) void {
+ switch (options.backend) {
+ .fontconfig_freetype => if (self.fc) |*fc| fc.deinit(),
+ .freetype => {},
+ .web_canvas => if (self.wc) |*wc| wc.deinit(),
+ .coretext,
+ .coretext_freetype,
+ .coretext_harfbuzz,
+ .coretext_noshape,
+ => if (self.ct) |*ct| ct.deinit(),
+ }
+ self.* = undefined;
+}
+
+/// Returns the family name of the font.
+pub fn familyName(self: DeferredFace, buf: []u8) ![]const u8 {
+ switch (options.backend) {
+ .freetype => {},
+
+ .fontconfig_freetype => if (self.fc) |fc|
+ return (try fc.pattern.get(.family, 0)).string,
+
+ .coretext,
+ .coretext_freetype,
+ .coretext_harfbuzz,
+ .coretext_noshape,
+ => if (self.ct) |ct| {
+ const family_name = ct.font.copyAttribute(.family_name) orelse
+ return "unknown";
+ return family_name.cstringPtr(.utf8) orelse unsupported: {
+ break :unsupported family_name.cstring(buf, .utf8) orelse
+ return error.OutOfMemory;
+ };
+ },
+
+ .web_canvas => if (self.wc) |wc| return wc.font_str,
+ }
+
+ return "";
+}
+
+/// Returns the name of this face. The memory is always owned by the
+/// face so it doesn't have to be freed.
+pub fn name(self: DeferredFace, buf: []u8) ![]const u8 {
+ switch (options.backend) {
+ .freetype => {},
+
+ .fontconfig_freetype => if (self.fc) |fc|
+ return (try fc.pattern.get(.fullname, 0)).string,
+
+ .coretext,
+ .coretext_freetype,
+ .coretext_harfbuzz,
+ .coretext_noshape,
+ => if (self.ct) |ct| {
+ const display_name = ct.font.copyDisplayName();
+ return display_name.cstringPtr(.utf8) orelse unsupported: {
+ // "NULL if the internal storage of theString does not allow
+ // this to be returned efficiently." In this case, we need
+ // to allocate. But we can't return an allocated string because
+ // we don't have an allocator. Let's use the stack and log it.
+ break :unsupported display_name.cstring(buf, .utf8) orelse
+ return error.OutOfMemory;
+ };
+ },
+
+ .web_canvas => if (self.wc) |wc| return wc.font_str,
+ }
+
+ return "";
+}
+
+/// Load the deferred font face. This does nothing if the face is loaded.
+pub fn load(
+ self: *DeferredFace,
+ lib: Library,
+ opts: font.face.Options,
+) !Face {
+ return switch (options.backend) {
+ .fontconfig_freetype => try self.loadFontconfig(lib, opts),
+ .coretext, .coretext_harfbuzz, .coretext_noshape => try self.loadCoreText(lib, opts),
+ .coretext_freetype => try self.loadCoreTextFreetype(lib, opts),
+ .web_canvas => try self.loadWebCanvas(opts),
+
+ // Unreachable because we must be already loaded or have the
+ // proper configuration for one of the other deferred mechanisms.
+ .freetype => unreachable,
+ };
+}
+
+fn loadFontconfig(
+ self: *DeferredFace,
+ lib: Library,
+ opts: font.face.Options,
+) !Face {
+ const fc = self.fc.?;
+
+ // Filename and index for our face so we can load it
+ const filename = (try fc.pattern.get(.file, 0)).string;
+ const face_index = (try fc.pattern.get(.index, 0)).integer;
+
+ var face = try Face.initFile(lib, filename, face_index, opts);
+ errdefer face.deinit();
+ try face.setVariations(fc.variations, opts);
+ return face;
+}
+
+fn loadCoreText(
+ self: *DeferredFace,
+ lib: Library,
+ opts: font.face.Options,
+) !Face {
+ _ = lib;
+ const ct = self.ct.?;
+ var face = try Face.initFontCopy(ct.font, opts);
+ errdefer face.deinit();
+ try face.setVariations(ct.variations, opts);
+ return face;
+}
+
+fn loadCoreTextFreetype(
+ self: *DeferredFace,
+ lib: Library,
+ opts: font.face.Options,
+) !Face {
+ const ct = self.ct.?;
+
+ // Get the URL for the font so we can get the filepath
+ const url = ct.font.copyAttribute(.url) orelse
+ return error.FontHasNoFile;
+ defer url.release();
+
+ // Get the path from the URL
+ const path = url.copyPath() orelse return error.FontHasNoFile;
+ defer path.release();
+
+ // URL decode the path
+ const blank = try macos.foundation.String.createWithBytes("", .utf8, false);
+ defer blank.release();
+ const decoded = try macos.foundation.URL.createStringByReplacingPercentEscapes(
+ path,
+ blank,
+ );
+ defer decoded.release();
+
+ // Decode into a c string. 1024 bytes should be enough for anybody.
+ var buf: [1024]u8 = undefined;
+ const path_slice = decoded.cstring(buf[0..1023], .utf8) orelse
+ return error.FontPathCantDecode;
+
+ // Freetype requires null-terminated. We always leave space at
+ // the end for a zero so we set that up here.
+ buf[path_slice.len] = 0;
+
+ // Face index 0 is not always correct. We don't ship this configuration
+ // in a release build. Users should use the pure CoreText builds.
+ //std.log.warn("path={s}", .{path_slice});
+ var face = try Face.initFile(lib, buf[0..path_slice.len :0], 0, opts);
+ errdefer face.deinit();
+ try face.setVariations(ct.variations, opts);
+
+ return face;
+}
+
+fn loadWebCanvas(
+ self: *DeferredFace,
+ opts: font.face.Options,
+) !Face {
+ const wc = self.wc.?;
+ return try .initNamed(wc.alloc, wc.font_str, opts, wc.presentation);
+}
+
+/// Returns true if this face can satisfy the given codepoint and
+/// presentation. If presentation is null, then it just checks if the
+/// codepoint is present at all.
+///
+/// This should not require the face to be loaded IF we're using a
+/// discovery mechanism (i.e. fontconfig). If no discovery is used,
+/// the face is always expected to be loaded.
+pub fn hasCodepoint(self: DeferredFace, cp: u32, p: ?Presentation) bool {
+ switch (options.backend) {
+ .fontconfig_freetype => {
+ // If we are using fontconfig, use the fontconfig metadata to
+ // avoid loading the face.
+ if (self.fc) |fc| {
+ // Check if char exists
+ if (!fc.charset.hasChar(cp)) return false;
+
+ // If we have a presentation, check it matches
+ if (p) |desired| {
+ const emoji_lang = "und-zsye";
+ const actual: Presentation = if (fc.langset.hasLang(emoji_lang))
+ .emoji
+ else
+ .text;
+
+ return desired == actual;
+ }
+
+ return true;
+ }
+ },
+
+ .coretext,
+ .coretext_freetype,
+ .coretext_harfbuzz,
+ .coretext_noshape,
+ => {
+ // If we are using coretext, we check the loaded CT font.
+ if (self.ct) |ct| {
+ // This presentation check isn't as detailed as isColorGlyph
+ // because forced presentation modes are only used for emoji and
+ // emoji should always have color glyphs set. This can be
+ // more correct by using the isColorGlyph logic but I'd want
+ // to find a font that actually requires this so we can write
+ // a test for it before changing it.
+ if (p) |desired_p| {
+ const traits = ct.font.getSymbolicTraits();
+ const actual_p: Presentation = if (traits.color_glyphs) .emoji else .text;
+ if (actual_p != desired_p) return false;
+ }
+
+ // Turn UTF-32 into UTF-16 for CT API
+ var unichars: [2]u16 = undefined;
+ const pair = macos.foundation.stringGetSurrogatePairForLongCharacter(cp, &unichars);
+ const len: usize = if (pair) 2 else 1;
+
+ // Get our glyphs
+ var glyphs = [2]macos.graphics.Glyph{ 0, 0 };
+ return ct.font.getGlyphsForCharacters(unichars[0..len], glyphs[0..len]);
+ }
+ },
+
+ // Canvas always has the codepoint because we have no way of
+ // really checking and we let the browser handle it.
+ .web_canvas => if (self.wc) |wc| {
+ // Fast-path if we have a specific presentation and we
+ // don't match, then it is definitely not this face.
+ if (p) |desired| if (wc.presentation != desired) return false;
+
+ // Slow-path: we initialize the font, render it, and check
+ // if it works and the presentation matches.
+ var face = Face.initNamed(
+ wc.alloc,
+ wc.font_str,
+ .{ .points = 12 },
+ wc.presentation,
+ ) catch |err| {
+ log.warn("failed to init face for codepoint check " ++
+ "face={s} err={}", .{
+ wc.font_str,
+ err,
+ });
+
+ return false;
+ };
+ defer face.deinit();
+ return face.glyphIndex(cp) != null;
+ },
+
+ .freetype => {},
+ }
+
+ // This is unreachable because discovery mechanisms terminate, and
+ // if we're not using a discovery mechanism, the face MUST be loaded.
+ unreachable;
+}
+
+/// The wasm-compatible API.
+pub const Wasm = struct {
+ const wasm = @import("../os/wasm.zig");
+ const alloc = wasm.alloc;
+
+ export fn deferred_face_new(ptr: [*]const u8, len: usize, presentation: u16) ?*DeferredFace {
+ return deferred_face_new_(ptr, len, presentation) catch |err| {
+ log.warn("error creating deferred face err={}", .{err});
+ return null;
+ };
+ }
+
+ fn deferred_face_new_(ptr: [*]const u8, len: usize, presentation: u16) !*DeferredFace {
+ const font_str = try alloc.dupeZ(u8, ptr[0..len]);
+ errdefer alloc.free(font_str);
+
+ var face: DeferredFace = .{
+ .wc = .{
+ .alloc = alloc,
+ .font_str = font_str,
+ .presentation = @enumFromInt(presentation),
+ },
+ };
+ errdefer face.deinit();
+
+ const result = try alloc.create(DeferredFace);
+ errdefer alloc.destroy(result);
+ result.* = face;
+ return result;
+ }
+
+ export fn deferred_face_free(ptr: ?*DeferredFace) void {
+ if (ptr) |v| {
+ v.deinit();
+ alloc.destroy(v);
+ }
+ }
+
+ export fn deferred_face_load(self: *DeferredFace, pts: f32) void {
+ self.load(.{}, .{ .points = pts }) catch |err| {
+ log.warn("error loading deferred face err={}", .{err});
+ return;
+ };
+ }
+};
+
+test "fontconfig" {
+ if (options.backend != .fontconfig_freetype) return error.SkipZigTest;
+
+ const discovery = @import("main.zig").discovery;
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ // Load freetype
+ var lib = try Library.init(alloc);
+ defer lib.deinit();
+
+ // Get a deferred face from fontconfig
+ var def = def: {
+ var fc = discovery.Fontconfig.init();
+ defer fc.deinit();
+ var it = try fc.discover(alloc, .{ .family = "monospace", .size = 12 });
+ defer it.deinit();
+ break :def (try it.next()).?;
+ };
+ defer def.deinit();
+
+ // Verify we can get the name
+ var buf: [1024]u8 = undefined;
+ const n = try def.name(&buf);
+ try testing.expect(n.len > 0);
+
+ // Load it and verify it works
+ var face = try def.load(lib, .{ .size = .{ .points = 12 } });
+ defer face.deinit();
+ try testing.expect(face.glyphIndex(' ') != null);
+}
+
+test "coretext" {
+ if (options.backend != .coretext) return error.SkipZigTest;
+
+ const discovery = @import("main.zig").discovery;
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ // Load freetype
+ var lib = try Library.init(alloc);
+ defer lib.deinit();
+
+ // Get a deferred face from fontconfig
+ var def = def: {
+ var fc = discovery.CoreText.init();
+ var it = try fc.discover(alloc, .{ .family = "Monaco", .size = 12 });
+ defer it.deinit();
+ break :def (try it.next()).?;
+ };
+ defer def.deinit();
+ try testing.expect(def.hasCodepoint(' ', null));
+
+ // Verify we can get the name
+ var buf: [1024]u8 = undefined;
+ const n = try def.name(&buf);
+ try testing.expect(n.len > 0);
+
+ // Load it and verify it works
+ var face = try def.load(lib, .{ .size = .{ .points = 12 } });
+ defer face.deinit();
+ try testing.expect(face.glyphIndex(' ') != null);
+}
diff --git a/vendor/libghostty-vt/src/font/Glyph.zig b/vendor/libghostty-vt/src/font/Glyph.zig
new file mode 100644
index 00000000..f9937027
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/Glyph.zig
@@ -0,0 +1,19 @@
+//! Glyph is a single loaded glyph for a face.
+const Glyph = @This();
+
+/// width of glyph in pixels
+width: u32,
+
+/// height of glyph in pixels
+height: u32,
+
+/// left bearing
+offset_x: i32,
+
+/// top bearing
+offset_y: i32,
+
+/// coordinates in the atlas of the top-left corner. These have to
+/// be normalized to be between 0 and 1 prior to use in shaders.
+atlas_x: u32,
+atlas_y: u32,
diff --git a/vendor/libghostty-vt/src/font/Metrics.zig b/vendor/libghostty-vt/src/font/Metrics.zig
new file mode 100644
index 00000000..a72cb7be
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/Metrics.zig
@@ -0,0 +1,816 @@
+const Metrics = @This();
+
+const std = @import("std");
+const assert = @import("../quirks.zig").inlineAssert;
+
+/// Recommended cell width and height for a monospace grid using this font.
+cell_width: u32,
+cell_height: u32,
+
+/// Distance in pixels from the bottom of the cell to the text baseline.
+cell_baseline: u32,
+
+/// Distance in pixels from the top of the cell to the top of the underline.
+underline_position: u32,
+/// Thickness in pixels of the underline.
+underline_thickness: u32,
+
+/// Distance in pixels from the top of the cell to the top of the strikethrough.
+strikethrough_position: u32,
+/// Thickness in pixels of the strikethrough.
+strikethrough_thickness: u32,
+
+/// Distance in pixels from the top of the cell to the top of the overline.
+/// Can be negative to adjust the position above the top of the cell.
+overline_position: i32,
+/// Thickness in pixels of the overline.
+overline_thickness: u32,
+
+/// Thickness in pixels of box drawing characters.
+box_thickness: u32,
+
+/// The thickness in pixels of the cursor sprite. This has a default value
+/// because it is not determined by fonts but rather by user configuration.
+cursor_thickness: u32 = 1,
+
+/// The height in pixels of the cursor sprite.
+cursor_height: u32,
+
+/// The constraint height for nerd fonts icons.
+icon_height: f64,
+
+/// The constraint height for nerd fonts icons limited to a single cell width.
+icon_height_single: f64,
+
+/// The unrounded face width, used in scaling calculations.
+face_width: f64,
+
+/// The unrounded face height, used in scaling calculations.
+face_height: f64,
+
+/// The offset from the bottom of the cell to the bottom
+/// of the face's bounding box, based on the rounded and
+/// potentially adjusted cell height.
+face_y: f64,
+
+/// Minimum acceptable values for some fields to prevent modifiers
+/// from being able to, for example, cause 0-thickness underlines.
+const Minimums = struct {
+ const cell_width = 1;
+ const cell_height = 1;
+ const underline_thickness = 1;
+ const strikethrough_thickness = 1;
+ const overline_thickness = 1;
+ const box_thickness = 1;
+ const cursor_thickness = 1;
+ const cursor_height = 1;
+ const icon_height = 1.0;
+ const icon_height_single = 1.0;
+ const face_height = 1.0;
+ const face_width = 1.0;
+};
+
+/// Metrics extracted from a font face, based on
+/// the metadata tables and glyph measurements.
+pub const FaceMetrics = struct {
+ /// Pixels per em, dividing the other values in this struct by this should
+ /// yield sizes in ems, to allow comparing metrics from faces of different
+ /// sizes.
+ px_per_em: f64,
+
+ /// The minimum cell width that can contain any glyph in the ASCII range.
+ ///
+ /// Determined by measuring all printable glyphs in the ASCII range.
+ cell_width: f64,
+
+ /// The typographic ascent metric from the font.
+ /// This represents the maximum vertical position of the highest ascender.
+ ///
+ /// Relative to the baseline, in px, +Y=up
+ ascent: f64,
+
+ /// The typographic descent metric from the font.
+ /// This represents the minimum vertical position of the lowest descender.
+ ///
+ /// Relative to the baseline, in px, +Y=up
+ ///
+ /// Note:
+ /// As this value is generally below the baseline, it is typically negative.
+ descent: f64,
+
+ /// The typographic line gap (aka "leading") metric from the font.
+ /// This represents the additional space to be added between lines in
+ /// addition to the space defined by the ascent and descent metrics.
+ ///
+ /// Positive value in px
+ line_gap: f64,
+
+ /// The TOP of the underline stroke.
+ ///
+ /// Relative to the baseline, in px, +Y=up
+ underline_position: ?f64 = null,
+
+ /// The thickness of the underline stroke in px.
+ underline_thickness: ?f64 = null,
+
+ /// The TOP of the strikethrough stroke.
+ ///
+ /// Relative to the baseline, in px, +Y=up
+ strikethrough_position: ?f64 = null,
+
+ /// The thickness of the strikethrough stroke in px.
+ strikethrough_thickness: ?f64 = null,
+
+ /// The height of capital letters in the font, either derived from
+ /// a provided cap height metric or measured from the height of the
+ /// capital H glyph.
+ cap_height: ?f64 = null,
+
+ /// The height of lowercase letters in the font, either derived from
+ /// a provided ex height metric or measured from the height of the
+ /// lowercase x glyph.
+ ex_height: ?f64 = null,
+
+ /// The measured height of the bounding box containing all printable
+ /// ASCII characters. This can be different from ascent - descent for
+ /// two reasons: non-letter symbols like @ and $ often exceed the
+ /// the ascender and descender lines; and fonts often bake the line
+ /// gap into the ascent and descent metrics (as per, e.g., the Google
+ /// Fonts guidelines: https://simoncozens.github.io/gf-docs/metrics.html).
+ ///
+ /// Positive value in px
+ ascii_height: ?f64 = null,
+
+ /// The width of the character "水" (CJK water ideograph, U+6C34),
+ /// if present. This is used for font size adjustment, to normalize
+ /// the width of CJK fonts mixed with latin fonts.
+ ///
+ /// NOTE: IC = Ideograph Character
+ ic_width: ?f64 = null,
+
+ /// Convenience function for getting the line height
+ /// (ascent - descent + line_gap).
+ pub inline fn lineHeight(self: FaceMetrics) f64 {
+ return self.ascent - self.descent + self.line_gap;
+ }
+
+ /// Convenience function for getting the cap height. If this is not
+ /// defined in the font, we estimate it as 75% of the ascent.
+ pub inline fn capHeight(self: FaceMetrics) f64 {
+ if (self.cap_height) |value| if (value > 0) return value;
+ return 0.75 * self.ascent;
+ }
+
+ /// Convenience function for getting the ex height. If this is not
+ /// defined in the font, we estimate it as 75% of the cap height.
+ pub inline fn exHeight(self: FaceMetrics) f64 {
+ if (self.ex_height) |value| if (value > 0) return value;
+ return 0.75 * self.capHeight();
+ }
+
+ /// Convenience function for getting the ASCII height. If we
+ /// couldn't measure this, we use 1.5 * cap_height as our
+ /// estimator, based on measurements across programming fonts.
+ pub inline fn asciiHeight(self: FaceMetrics) f64 {
+ if (self.ascii_height) |value| if (value > 0) return value;
+ return 1.5 * self.capHeight();
+ }
+
+ /// Convenience function for getting the ideograph width. If this is
+ /// not defined in the font, we estimate it as the minimum of the
+ /// ascii height and two cell widths.
+ pub inline fn icWidth(self: FaceMetrics) f64 {
+ if (self.ic_width) |value| if (value > 0) return value;
+ return @min(self.asciiHeight(), 2 * self.cell_width);
+ }
+
+ /// Convenience function for getting the underline thickness. If
+ /// this is not defined in the font, we estimate it as 15% of the ex
+ /// height.
+ pub inline fn underlineThickness(self: FaceMetrics) f64 {
+ if (self.underline_thickness) |value| if (value > 0) return value;
+ return 0.15 * self.exHeight();
+ }
+
+ /// Convenience function for getting the strikethrough thickness. If
+ /// this is not defined in the font, we set it equal to the
+ /// underline thickness.
+ pub inline fn strikethroughThickness(self: FaceMetrics) f64 {
+ if (self.strikethrough_thickness) |value| if (value > 0) return value;
+ return self.underlineThickness();
+ }
+
+ // NOTE: The getters below return positions, not sizes, so both
+ // positive and negative values are valid, hence no sign validation.
+
+ /// Convenience function for getting the underline position. If
+ /// this is not defined in the font, we place it one underline
+ /// thickness below the baseline.
+ pub inline fn underlinePosition(self: FaceMetrics) f64 {
+ return self.underline_position orelse -self.underlineThickness();
+ }
+
+ /// Convenience function for getting the strikethrough position. If
+ /// this is not defined in the font, we center it at half the ex
+ /// height, so that it's perfectly centered on lower case text.
+ pub inline fn strikethroughPosition(self: FaceMetrics) f64 {
+ return self.strikethrough_position orelse (self.exHeight() + self.strikethroughThickness()) * 0.5;
+ }
+};
+
+/// Calculate our metrics based on values extracted from a font.
+///
+/// Try to pass values with as much precision as possible,
+/// do not round them before using them for this function.
+///
+/// For any nullable options that are not provided, estimates will be used.
+pub fn calc(face: FaceMetrics) Metrics {
+ // These are the unrounded advance width and line height values,
+ // which are retained separately from the rounded cell width and
+ // height values (below), for calculations that need to know how
+ // much error there is between the design dimensions of the font
+ // and the pixel dimensions of our cells.
+ const face_width = face.cell_width;
+ const face_height = face.lineHeight();
+
+ // The cell width and height values need to be integers since they
+ // represent pixel dimensions of the grid cells in the terminal.
+ //
+ // We use @round for the cell width to limit the difference from
+ // the "true" width value to no more than 0.5px. This is a better
+ // approximation of the authorial intent of the font than ceiling
+ // would be, and makes the apparent spacing match better between
+ // low and high DPI displays.
+ //
+ // This does mean that it's possible for a glyph to overflow the
+ // edge of the cell by a pixel if it has no side bearings, but in
+ // reality such glyphs are generally meant to connect to adjacent
+ // glyphs in some way so it's not really an issue.
+ //
+ // The same is true for the height. Some fonts are poorly authored
+ // and have a descender on a normal glyph that extends right up to
+ // the descent value of the face, and this can result in the glyph
+ // overflowing the bottom of the cell by a pixel, which isn't good
+ // but if we try to prevent it by increasing the cell height then
+ // we get line heights that are too large for most users and even
+ // more inconsistent across DPIs.
+ //
+ // Users who experience such cell-height overflows should:
+ //
+ // 1. Nag the font author to either redesign the glyph to not go
+ // so low, or else adjust the descent value in the metadata.
+ //
+ // 2. Add an `adjust-cell-height` entry to their config to give
+ // the cell enough room for the glyph.
+ const cell_width = @round(face_width);
+ const cell_height = @round(face_height);
+
+ // We split our line gap in two parts, and put half of it on the top
+ // of the cell and the other half on the bottom, so that our text never
+ // bumps up against either edge of the cell vertically.
+ const half_line_gap = face.line_gap / 2;
+
+ // NOTE: Unlike all our other metrics, `cell_baseline` is
+ // relative to the BOTTOM of the cell rather than the top.
+ const face_baseline = half_line_gap - face.descent;
+ // We calculate the baseline by trying to center the face vertically
+ // in the pixel-rounded cell height, so that before rounding it will
+ // be an even distance from the top and bottom of the cell, meaning
+ // it either sticks out the same amount or is inset the same amount,
+ // depending on whether the cell height was rounded up or down from
+ // the line height. We do this by adding half the difference between
+ // the cell height and the face height.
+ const cell_baseline = @round(face_baseline - (cell_height - face_height) / 2);
+
+ // We keep track of the offset from the bottom of the cell
+ // to the bottom of the face's "true" bounding box, which at
+ // this point, since nothing has been scaled yet, is equivalent
+ // to the offset between the baseline we draw at (cell_baseline)
+ // and the one the font wants (face_baseline).
+ const face_y = cell_baseline - face_baseline;
+
+ // We calculate a top_to_baseline to make following calculations simpler.
+ const top_to_baseline = cell_height - cell_baseline;
+
+ // Get the other font metrics or their estimates. See doc comments
+ // in FaceMetrics for explanations of the estimation heuristics.
+ const cap_height = face.capHeight();
+ const underline_thickness = @max(1, @ceil(face.underlineThickness()));
+ const strikethrough_thickness = @max(1, @ceil(face.strikethroughThickness()));
+ const underline_position = @round(top_to_baseline - face.underlinePosition());
+ const strikethrough_position = @round(top_to_baseline - face.strikethroughPosition());
+
+ // Same heuristic as the font_patcher script. We store icon_height
+ // separately from face_height such that modifiers can apply to the former
+ // without affecting the latter.
+ const icon_height = face_height;
+ const icon_height_single = (2 * cap_height + face_height) / 3;
+
+ var result: Metrics = .{
+ .cell_width = @intFromFloat(cell_width),
+ .cell_height = @intFromFloat(cell_height),
+ .cell_baseline = @intFromFloat(cell_baseline),
+ .underline_position = @intFromFloat(underline_position),
+ .underline_thickness = @intFromFloat(underline_thickness),
+ .strikethrough_position = @intFromFloat(strikethrough_position),
+ .strikethrough_thickness = @intFromFloat(strikethrough_thickness),
+ .overline_position = 0,
+ .overline_thickness = @intFromFloat(underline_thickness),
+ .box_thickness = @intFromFloat(underline_thickness),
+ .cursor_height = @intFromFloat(cell_height),
+ .icon_height = icon_height,
+ .icon_height_single = icon_height_single,
+ .face_width = face_width,
+ .face_height = face_height,
+ .face_y = face_y,
+ };
+
+ // Ensure all metrics are within their allowable range.
+ result.clamp();
+
+ // std.log.debug("metrics={}", .{result});
+
+ return result;
+}
+
+/// Apply a set of modifiers.
+pub fn apply(self: *Metrics, mods: ModifierSet) void {
+ var it = mods.iterator();
+ while (it.next()) |entry| {
+ switch (entry.key_ptr.*) {
+ // We clamp these values to a minimum of 1 to prevent divide-by-zero
+ // in downstream operations.
+ inline .cell_width,
+ .cell_height,
+ => |tag| {
+ // Compute the new value. If it is the same avoid the work.
+ const original = @field(self, @tagName(tag));
+ const new = @max(entry.value_ptr.apply(original), 1);
+ if (new == original) continue;
+
+ // Set the new value
+ @field(self, @tagName(tag)) = new;
+
+ // For cell height, we have to also modify some positions
+ // that are absolute from the top of the cell. The main goal
+ // here is to center the baseline so that text is vertically
+ // centered in the cell.
+ if (comptime tag == .cell_height) {
+ const original_f64: f64 = @floatFromInt(original);
+ const new_f64: f64 = @floatFromInt(new);
+ const diff = new_f64 - original_f64;
+ const half_diff = diff / 2.0;
+
+ // If the diff is even, the number of pixels we add
+ // will be the same for the top and the bottom, but
+ // if the diff is odd then we want to add the extra
+ // pixel to the edge of the cell that needs it most.
+ //
+ // How much the edge "needs it" depends on whether
+ // the face is higher or lower than it should be to
+ // be perfectly centered in the cell.
+ //
+ // If the face were perfectly centered then face_y
+ // would be equal to half of the difference between
+ // the cell height and the face height.
+ const position_with_respect_to_center =
+ self.face_y - (original_f64 - self.face_height) / 2;
+
+ const diff_top, const diff_bottom =
+ if (position_with_respect_to_center > 0)
+ // The baseline is higher than it should be, so we
+ // add the extra to the top, or if it's a negative
+ // diff it gets added to the bottom because of how
+ // floor and ceil work.
+ .{ @ceil(half_diff), @floor(half_diff) }
+ else
+ // The baseline is lower than it should be, so we
+ // add the extra to the bottom, or vice versa for
+ // negative diffs.
+ .{ @floor(half_diff), @ceil(half_diff) };
+
+ // The cell baseline and face_y values are relative to the
+ // bottom of the cell so we add the bottom diff to them.
+ addFloatToInt(&self.cell_baseline, diff_bottom);
+ self.face_y += diff_bottom;
+
+ // These are all relative to the top of the cell.
+ addFloatToInt(&self.underline_position, diff_top);
+ addFloatToInt(&self.strikethrough_position, diff_top);
+ self.overline_position +|= @as(i32, @intFromFloat(diff_top));
+ }
+ },
+ inline .icon_height => {
+ self.icon_height = entry.value_ptr.apply(self.icon_height);
+ self.icon_height_single = entry.value_ptr.apply(self.icon_height_single);
+ },
+
+ inline else => |tag| {
+ @field(self, @tagName(tag)) = entry.value_ptr.apply(@field(self, @tagName(tag)));
+ },
+ }
+ }
+
+ // Prevent modifiers from pushing metrics out of their allowable range.
+ self.clamp();
+}
+
+/// Helper function for adding an f64 to a u32.
+///
+/// Performs saturating addition or subtraction
+/// depending on the sign of the provided float.
+///
+/// The f64 is asserted to have an integer value.
+inline fn addFloatToInt(int: *u32, float: f64) void {
+ assert(@floor(float) == float);
+ int.* =
+ if (float >= 0.0)
+ int.* +| @as(u32, @intFromFloat(float))
+ else
+ int.* -| @as(u32, @intFromFloat(-float));
+}
+
+/// Clamp all metrics to their allowable range.
+fn clamp(self: *Metrics) void {
+ inline for (std.meta.fields(Metrics)) |field| {
+ if (@hasDecl(Minimums, field.name)) {
+ @field(self, field.name) = @max(
+ @field(self, field.name),
+ @field(Minimums, field.name),
+ );
+ }
+ }
+}
+
+/// A set of modifiers to apply to metrics. We use a hash map because
+/// we expect most metrics to be unmodified and want to take up as
+/// little space as possible.
+pub const ModifierSet = std.AutoHashMapUnmanaged(Key, Modifier);
+
+/// A modifier to apply to a metrics value. The modifier value represents
+/// a delta, so percent is a percentage to change, not a percentage of.
+/// For example, "20%" is 20% larger, not 20% of the value. Likewise,
+/// an absolute value of "20" is 20 larger, not literally 20.
+pub const Modifier = union(enum) {
+ percent: f64,
+ absolute: i32,
+
+ /// Parses the modifier value. If the value ends in "%" it is assumed
+ /// to be a percent, otherwise the value is parsed as an integer.
+ pub fn parse(input: []const u8) !Modifier {
+ if (input.len == 0) return error.InvalidFormat;
+
+ if (input[input.len - 1] == '%') {
+ var percent = std.fmt.parseFloat(
+ f64,
+ input[0 .. input.len - 1],
+ ) catch return error.InvalidFormat;
+ percent /= 100;
+
+ if (percent <= -1) return .{ .percent = 0 };
+ if (percent < 0) return .{ .percent = 1 + percent };
+ return .{ .percent = 1 + percent };
+ }
+
+ return .{
+ .absolute = std.fmt.parseInt(i32, input, 10) catch
+ return error.InvalidFormat,
+ };
+ }
+
+ /// So it works with the config framework.
+ pub fn parseCLI(input: ?[]const u8) !Modifier {
+ return try parse(input orelse return error.ValueRequired);
+ }
+
+ /// Used by config formatter
+ pub fn formatEntry(self: Modifier, formatter: anytype) !void {
+ var buf: [1024]u8 = undefined;
+ switch (self) {
+ .percent => |v| {
+ try formatter.formatEntry(
+ []const u8,
+ std.fmt.bufPrint(
+ &buf,
+ "{d}%",
+ .{(v - 1) * 100},
+ ) catch return error.OutOfMemory,
+ );
+ },
+
+ .absolute => |v| {
+ try formatter.formatEntry(
+ []const u8,
+ std.fmt.bufPrint(
+ &buf,
+ "{d}",
+ .{v},
+ ) catch return error.OutOfMemory,
+ );
+ },
+ }
+ }
+
+ /// Apply a modifier to a numeric value.
+ pub fn apply(self: Modifier, v: anytype) @TypeOf(v) {
+ const T = @TypeOf(v);
+ const Tinfo = @typeInfo(T);
+ return switch (comptime Tinfo) {
+ .int, .comptime_int => switch (self) {
+ .percent => |p| percent: {
+ const p_clamped: f64 = @max(0, p);
+ const v_f64: f64 = @floatFromInt(v);
+ const applied_f64: f64 = @round(v_f64 * p_clamped);
+ const applied_T: T = @intFromFloat(applied_f64);
+ break :percent applied_T;
+ },
+
+ .absolute => |abs| absolute: {
+ const v_i64: i64 = @intCast(v);
+ const abs_i64: i64 = @intCast(abs);
+ const applied_i64: i64 = v_i64 +| abs_i64;
+ const clamped_i64: i64 = if (Tinfo.int.signedness == .signed)
+ applied_i64
+ else
+ @max(0, applied_i64);
+ const applied_T: T = std.math.cast(T, clamped_i64) orelse
+ std.math.maxInt(T) * @as(T, @intCast(std.math.sign(clamped_i64)));
+ break :absolute applied_T;
+ },
+ },
+ .float, .comptime_float => return switch (self) {
+ .percent => |p| v * @max(0, p),
+ .absolute => |abs| v + @as(T, @floatFromInt(abs)),
+ },
+ else => {},
+ };
+ }
+
+ /// Hash using the hasher.
+ pub fn hash(self: Modifier, hasher: anytype) void {
+ const autoHash = std.hash.autoHash;
+ autoHash(hasher, std.meta.activeTag(self));
+ switch (self) {
+ // floats can't be hashed directly so we bitcast to i64.
+ // for the purpose of what we're trying to do this seems
+ // good enough but I would prefer value hashing.
+ .percent => |v| autoHash(hasher, @as(i64, @bitCast(v))),
+ .absolute => |v| autoHash(hasher, v),
+ }
+ }
+
+ test "formatConfig percent" {
+ const configpkg = @import("../config.zig");
+ const testing = std.testing;
+ var buf: std.Io.Writer.Allocating = .init(testing.allocator);
+ defer buf.deinit();
+
+ const p = try parseCLI("24%");
+ try p.formatEntry(configpkg.entryFormatter("a", &buf.writer));
+ try std.testing.expectEqualSlices(u8, "a = 24%\n", buf.written());
+ }
+
+ test "formatConfig absolute" {
+ const configpkg = @import("../config.zig");
+ const testing = std.testing;
+ var buf: std.Io.Writer.Allocating = .init(testing.allocator);
+ defer buf.deinit();
+
+ const p = try parseCLI("-30");
+ try p.formatEntry(configpkg.entryFormatter("a", &buf.writer));
+ try std.testing.expectEqualSlices(u8, "a = -30\n", buf.written());
+ }
+};
+
+/// Key is an enum of all the available metrics keys.
+pub const Key = key: {
+ const field_infos = std.meta.fields(Metrics);
+ var enumFields: [field_infos.len]std.builtin.Type.EnumField = undefined;
+ var count: usize = 0;
+ for (field_infos, 0..) |field, i| {
+ if (field.type != u32 and field.type != i32 and field.type != f64) continue;
+ enumFields[i] = .{ .name = field.name, .value = i };
+ count += 1;
+ }
+
+ var decls = [_]std.builtin.Type.Declaration{};
+ break :key @Type(.{
+ .@"enum" = .{
+ .tag_type = std.math.IntFittingRange(0, count - 1),
+ .fields = enumFields[0..count],
+ .decls = &decls,
+ .is_exhaustive = true,
+ },
+ });
+};
+
+// NOTE: This is purposely not pub because we want to force outside callers
+// to use the `.{}` syntax so unused fields are detected by the compiler.
+fn init() Metrics {
+ return .{
+ .cell_width = 0,
+ .cell_height = 0,
+ .cell_baseline = 0,
+ .underline_position = 0,
+ .underline_thickness = 0,
+ .strikethrough_position = 0,
+ .strikethrough_thickness = 0,
+ .overline_position = 0,
+ .overline_thickness = 0,
+ .box_thickness = 0,
+ .cursor_height = 0,
+ .icon_height = 0.0,
+ .icon_height_single = 0.0,
+ .face_width = 0.0,
+ .face_height = 0.0,
+ .face_y = 0.0,
+ };
+}
+
+test "Metrics: apply modifiers" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var set: ModifierSet = .{};
+ defer set.deinit(alloc);
+ try set.put(alloc, .cell_width, .{ .percent = 1.2 });
+
+ var m: Metrics = init();
+ m.cell_width = 100;
+ m.apply(set);
+ try testing.expectEqual(@as(u32, 120), m.cell_width);
+}
+
+test "Metrics: adjust cell height smaller" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var set: ModifierSet = .{};
+ defer set.deinit(alloc);
+ // We choose numbers such that the subtracted number of pixels is odd,
+ // as that's the case that could most easily have off-by-one errors.
+ // Here we're removing 25 pixels: 13 on the bottom, 12 on top, split
+ // that way because we're simulating a face that's 0.33px higher than
+ // it "should" be (due to rounding).
+ try set.put(alloc, .cell_height, .{ .percent = 0.75 });
+
+ var m: Metrics = init();
+ m.face_y = 0.33;
+ m.cell_baseline = 50;
+ m.underline_position = 55;
+ m.strikethrough_position = 30;
+ m.overline_position = 0;
+ m.cell_height = 100;
+ m.face_height = 99.67;
+ m.cursor_height = 100;
+ m.apply(set);
+ try testing.expectEqual(-12.67, m.face_y);
+ try testing.expectEqual(@as(u32, 75), m.cell_height);
+ try testing.expectEqual(@as(u32, 37), m.cell_baseline);
+ try testing.expectEqual(@as(u32, 43), m.underline_position);
+ try testing.expectEqual(@as(u32, 18), m.strikethrough_position);
+ try testing.expectEqual(@as(i32, -12), m.overline_position);
+ // Cursor height is separate from cell height and does not follow it.
+ try testing.expectEqual(@as(u32, 100), m.cursor_height);
+}
+
+test "Metrics: adjust cell height larger" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var set: ModifierSet = .{};
+ defer set.deinit(alloc);
+ // We choose numbers such that the added number of pixels is odd,
+ // as that's the case that could most easily have off-by-one errors.
+ // Here we're adding 75 pixels: 37 on the bottom, 38 on top, split
+ // that way because we're simulating a face that's 0.33px higher
+ // than it "should" be (due to rounding).
+ try set.put(alloc, .cell_height, .{ .percent = 1.75 });
+
+ var m: Metrics = init();
+ m.face_y = 0.33;
+ m.cell_baseline = 50;
+ m.underline_position = 55;
+ m.strikethrough_position = 30;
+ m.overline_position = 0;
+ m.cell_height = 100;
+ m.face_height = 99.67;
+ m.cursor_height = 100;
+ m.apply(set);
+ try testing.expectEqual(37.33, m.face_y);
+ try testing.expectEqual(@as(u32, 175), m.cell_height);
+ try testing.expectEqual(@as(u32, 87), m.cell_baseline);
+ try testing.expectEqual(@as(u32, 93), m.underline_position);
+ try testing.expectEqual(@as(u32, 68), m.strikethrough_position);
+ try testing.expectEqual(@as(i32, 38), m.overline_position);
+ // Cursor height is separate from cell height and does not follow it.
+ try testing.expectEqual(@as(u32, 100), m.cursor_height);
+}
+
+test "Metrics: adjust icon height by percentage" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var set: ModifierSet = .{};
+ defer set.deinit(alloc);
+ try set.put(alloc, .icon_height, .{ .percent = 0.75 });
+
+ var m: Metrics = init();
+ m.icon_height = 100.0;
+ m.icon_height_single = 80.0;
+ m.face_height = 100.0;
+ m.face_y = 1.0;
+ m.apply(set);
+ try testing.expectEqual(75.0, m.icon_height);
+ try testing.expectEqual(60.0, m.icon_height_single);
+ // Face metrics not affected
+ try testing.expectEqual(100.0, m.face_height);
+ try testing.expectEqual(1.0, m.face_y);
+}
+
+test "Metrics: adjust icon height by absolute pixels" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var set: ModifierSet = .{};
+ defer set.deinit(alloc);
+ try set.put(alloc, .icon_height, .{ .absolute = -5 });
+
+ var m: Metrics = init();
+ m.icon_height = 100.0;
+ m.icon_height_single = 80.0;
+ m.face_height = 100.0;
+ m.face_y = 1.0;
+ m.apply(set);
+ try testing.expectEqual(95.0, m.icon_height);
+ try testing.expectEqual(75.0, m.icon_height_single);
+ // Face metrics not affected
+ try testing.expectEqual(100.0, m.face_height);
+ try testing.expectEqual(1.0, m.face_y);
+}
+
+test "Modifier: parse absolute" {
+ const testing = std.testing;
+
+ {
+ const m = try Modifier.parse("100");
+ try testing.expectEqual(Modifier{ .absolute = 100 }, m);
+ }
+
+ {
+ const m = try Modifier.parse("-100");
+ try testing.expectEqual(Modifier{ .absolute = -100 }, m);
+ }
+}
+
+test "Modifier: parse percent" {
+ const testing = std.testing;
+
+ {
+ const m = try Modifier.parse("20%");
+ try testing.expectEqual(Modifier{ .percent = 1.2 }, m);
+ }
+ {
+ const m = try Modifier.parse("-20%");
+ try testing.expectEqual(Modifier{ .percent = 0.8 }, m);
+ }
+ {
+ const m = try Modifier.parse("0%");
+ try testing.expectEqual(Modifier{ .percent = 1 }, m);
+ }
+}
+
+test "Modifier: percent" {
+ const testing = std.testing;
+
+ {
+ const m: Modifier = .{ .percent = 0.8 };
+ const v: u32 = m.apply(@as(u32, 100));
+ try testing.expectEqual(@as(u32, 80), v);
+ }
+ {
+ const m: Modifier = .{ .percent = 1.8 };
+ const v: u32 = m.apply(@as(u32, 100));
+ try testing.expectEqual(@as(u32, 180), v);
+ }
+}
+
+test "Modifier: absolute" {
+ const testing = std.testing;
+
+ {
+ const m: Modifier = .{ .absolute = -100 };
+ const v: u32 = m.apply(@as(u32, 100));
+ try testing.expectEqual(@as(u32, 0), v);
+ }
+ {
+ const m: Modifier = .{ .absolute = -120 };
+ const v: u32 = m.apply(@as(u32, 100));
+ try testing.expectEqual(@as(u32, 0), v);
+ }
+ {
+ const m: Modifier = .{ .absolute = 100 };
+ const v: u32 = m.apply(@as(u32, 100));
+ try testing.expectEqual(@as(u32, 200), v);
+ }
+}
diff --git a/vendor/libghostty-vt/src/font/SharedGrid.zig b/vendor/libghostty-vt/src/font/SharedGrid.zig
new file mode 100644
index 00000000..5fd729b3
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/SharedGrid.zig
@@ -0,0 +1,540 @@
+//! This structure represents the state required to render a terminal
+//! grid using the font subsystem. It is "shared" because it is able to
+//! be shared across multiple surfaces.
+//!
+//! It is desirable for the grid state to be shared because the font
+//! configuration for a set of surfaces is almost always the same and
+//! font data is relatively memory intensive. Further, the font subsystem
+//! should be read-heavy compared to write-heavy, so it handles concurrent
+//! reads well. Going even further, the font subsystem should be very rarely
+//! read at all since it should only be necessary when the grid actively
+//! changes.
+//!
+//! SharedGrid does NOT support resizing, font-family changes, font removals
+//! in collections, etc. Because the Grid is shared this would cause a
+//! major disruption in the rendering of multiple surfaces (i.e. increasing
+//! the font size in one would increase it in all). In many cases this isn't
+//! desirable so to implement configuration changes the grid should be
+//! reinitialized and all surfaces should switch over to using that one.
+const SharedGrid = @This();
+
+const std = @import("std");
+const assert = @import("../quirks.zig").inlineAssert;
+const tripwire = @import("../tripwire.zig");
+const Allocator = std.mem.Allocator;
+const renderer = @import("../renderer.zig");
+const font = @import("main.zig");
+const Atlas = font.Atlas;
+const CodepointResolver = font.CodepointResolver;
+const Collection = font.Collection;
+const Face = font.Face;
+const Glyph = font.Glyph;
+const Library = font.Library;
+const Metrics = font.Metrics;
+const Presentation = font.Presentation;
+const Style = font.Style;
+const RenderOptions = font.face.RenderOptions;
+
+const log = std.log.scoped(.font_shared_grid);
+
+/// Cache for codepoints to font indexes in a group.
+codepoints: std.AutoHashMapUnmanaged(CodepointKey, ?Collection.Index) = .{},
+
+/// Cache for glyph renders into the atlas.
+glyphs: std.HashMapUnmanaged(GlyphKey, Render, GlyphKey.Context, 80) = .{},
+
+/// The texture atlas to store renders in. The Glyph data in the glyphs
+/// cache is dependent on the atlas matching.
+atlas_grayscale: Atlas,
+atlas_color: Atlas,
+
+/// The underlying resolver for font data, fallbacks, etc. The shared
+/// grid takes ownership of the resolver and will free it.
+resolver: CodepointResolver,
+
+/// The currently active grid metrics dictating the layout of the grid.
+/// This is calculated based on the resolver and current fonts.
+metrics: Metrics,
+
+/// The RwLock used to protect the shared grid. Callers are expected to use
+/// this directly if they need to i.e. access the atlas directly. Because
+/// callers can use this lock directly, maintainers need to be extra careful
+/// to review call sites to ensure they are using the lock correctly.
+lock: std.Thread.RwLock,
+
+pub const init_tw = tripwire.module(enum {
+ codepoints_capacity,
+ glyphs_capacity,
+ reload_metrics,
+}, init);
+
+/// Initialize the grid.
+///
+/// The resolver must have a collection that supports deferred loading
+/// (collection.load_options != null). This is because we need the load
+/// options data to determine grid metrics and setup our sprite font.
+///
+/// SharedGrid always configures the sprite font. This struct is expected to be
+/// used with a terminal grid and therefore the sprite font is always
+/// necessary for correct rendering.
+pub fn init(
+ alloc: Allocator,
+ resolver: CodepointResolver,
+) !SharedGrid {
+ const tw = init_tw;
+
+ // We need to support loading options since we use the size data
+ assert(resolver.collection.load_options != null);
+
+ var atlas_grayscale = try Atlas.init(alloc, 512, .grayscale);
+ errdefer atlas_grayscale.deinit(alloc);
+ var atlas_color = try Atlas.init(alloc, 512, .bgra);
+ errdefer atlas_color.deinit(alloc);
+
+ var result: SharedGrid = .{
+ .resolver = resolver,
+ .atlas_grayscale = atlas_grayscale,
+ .atlas_color = atlas_color,
+ .lock = .{},
+ .metrics = undefined, // Loaded below
+ };
+
+ // We set an initial capacity that can fit a good number of characters.
+ // This number was picked empirically based on my own terminal usage.
+ try tw.check(.codepoints_capacity);
+ try result.codepoints.ensureTotalCapacity(alloc, 128);
+ errdefer result.codepoints.deinit(alloc);
+ try tw.check(.glyphs_capacity);
+ try result.glyphs.ensureTotalCapacity(alloc, 128);
+ errdefer result.glyphs.deinit(alloc);
+
+ // Initialize our metrics.
+ try tw.check(.reload_metrics);
+ try result.reloadMetrics();
+
+ return result;
+}
+
+/// Deinit. Assumes no concurrent access so no lock is taken.
+pub fn deinit(self: *SharedGrid, alloc: Allocator) void {
+ self.codepoints.deinit(alloc);
+ self.glyphs.deinit(alloc);
+ self.atlas_grayscale.deinit(alloc);
+ self.atlas_color.deinit(alloc);
+ self.resolver.deinit(alloc);
+}
+
+fn reloadMetrics(self: *SharedGrid) !void {
+ const collection = &self.resolver.collection;
+ try collection.updateMetrics();
+
+ self.metrics = collection.metrics.?;
+
+ // Setup our sprite font.
+ self.resolver.sprite = .{ .metrics = self.metrics };
+}
+
+/// Returns the grid cell size.
+///
+/// This is not thread safe.
+pub fn cellSize(self: *SharedGrid) renderer.CellSize {
+ return .{
+ .width = self.metrics.cell_width,
+ .height = self.metrics.cell_height,
+ };
+}
+
+/// Get the font index for a given codepoint. This is cached.
+///
+/// This always forces loading any deferred fonts since we assume that if
+/// you're looking up an index that the caller plans to use the font. By
+/// loading the font in this function we can ensure thread-safety on the
+/// load without complicating future calls.
+pub fn getIndex(
+ self: *SharedGrid,
+ alloc: Allocator,
+ cp: u32,
+ style: Style,
+ p: ?Presentation,
+) !?Collection.Index {
+ const key: CodepointKey = .{ .style = style, .codepoint = cp, .presentation = p };
+
+ // Fast path: the cache has the value. This is almost always true and
+ // only requires a read lock.
+ {
+ self.lock.lockShared();
+ defer self.lock.unlockShared();
+ if (self.codepoints.get(key)) |v| return v;
+ }
+
+ // Slow path: we need to search this codepoint
+ self.lock.lock();
+ defer self.lock.unlock();
+
+ // Try to get it, if it is now in the cache another thread beat us to it.
+ const gop = try self.codepoints.getOrPut(alloc, key);
+ if (gop.found_existing) return gop.value_ptr.*;
+ errdefer self.codepoints.removeByPtr(gop.key_ptr);
+
+ // Load a value and cache it. This even caches negative matches.
+ const value = self.resolver.getIndex(alloc, cp, style, p);
+ gop.value_ptr.* = value;
+
+ if (value) |idx| preload: {
+ // If the font is a sprite font then we don't need to preload
+ // because getFace doesn't work with special fonts.
+ if (idx.special() != null) break :preload;
+
+ // Load the face in case its deferred. If this fails then we would've
+ // failed to load it in the future anyways so we want to undo all
+ // the caching we did.
+ _ = try self.resolver.collection.getFace(idx);
+ }
+
+ return value;
+}
+
+/// Returns true if the given font index has the codepoint and presentation.
+pub fn hasCodepoint(
+ self: *SharedGrid,
+ idx: Collection.Index,
+ cp: u32,
+ p: ?Presentation,
+) bool {
+ self.lock.lockShared();
+ defer self.lock.unlockShared();
+ return self.resolver.collection.hasCodepoint(
+ idx,
+ cp,
+ if (p) |v| .{ .explicit = v } else .{ .any = {} },
+ );
+}
+
+pub const Render = struct {
+ glyph: Glyph,
+ presentation: Presentation,
+};
+
+/// Render a codepoint. This uses the first font index that has the codepoint
+/// and matches the presentation requested. If the codepoint cannot be found
+/// in any font, an null render is returned.
+pub fn renderCodepoint(
+ self: *SharedGrid,
+ alloc: Allocator,
+ cp: u32,
+ style: Style,
+ p: ?Presentation,
+ opts: RenderOptions,
+) !?Render {
+ // Note: we could optimize the below to use way less locking, but
+ // at the time of writing this codepath is only called for preedit
+ // text which is relatively rare and almost non-existent in multiple
+ // surfaces at the same time.
+
+ // Get the font that has the codepoint
+ const index = try self.getIndex(alloc, cp, style, p) orelse return null;
+
+ // Get the glyph for the font
+ const glyph_index = glyph_index: {
+ self.lock.lockShared();
+ defer self.lock.unlockShared();
+ const face = try self.resolver.collection.getFace(index);
+ break :glyph_index face.glyphIndex(cp) orelse return null;
+ };
+
+ // Render
+ return try self.renderGlyph(alloc, index, glyph_index, opts);
+}
+
+pub const renderGlyph_tw = tripwire.module(enum {
+ get_presentation,
+}, renderGlyph);
+
+/// Render a glyph index. This automatically determines the correct texture
+/// atlas to use and caches the result.
+pub fn renderGlyph(
+ self: *SharedGrid,
+ alloc: Allocator,
+ index: Collection.Index,
+ glyph_index: u32,
+ opts: RenderOptions,
+) !Render {
+ const tw = renderGlyph_tw;
+
+ const key: GlyphKey = .{ .index = index, .glyph = glyph_index, .opts = opts };
+
+ // Fast path: the cache has the value. This is almost always true and
+ // only requires a read lock.
+ {
+ self.lock.lockShared();
+ defer self.lock.unlockShared();
+ if (self.glyphs.get(key)) |v| return v;
+ }
+
+ // Slow path: we need to search this codepoint
+ self.lock.lock();
+ defer self.lock.unlock();
+
+ const gop = try self.glyphs.getOrPut(alloc, key);
+ if (gop.found_existing) return gop.value_ptr.*;
+ errdefer self.glyphs.removeByPtr(gop.key_ptr);
+
+ // Get the presentation to determine what atlas to use
+ try tw.check(.get_presentation);
+ const p = try self.resolver.getPresentation(index, glyph_index);
+ const atlas: *font.Atlas = switch (p) {
+ .text => &self.atlas_grayscale,
+ .emoji => &self.atlas_color,
+ };
+
+ var render_opts = opts;
+
+ // Always use these constraints for emoji.
+ if (p == .emoji) {
+ render_opts.constraint = .{
+ // Scale emoji to be as large as possible
+ // while preserving their aspect ratio.
+ .size = .cover,
+
+ // Center the emoji in its cells.
+ .align_horizontal = .center,
+ .align_vertical = .center,
+
+ // Add a small bit of padding so the emoji
+ // doesn't quite touch the edges of the cells.
+ .pad_left = 0.025,
+ .pad_right = 0.025,
+ };
+ }
+
+ // Render into the atlas
+ const glyph = self.resolver.renderGlyph(
+ alloc,
+ atlas,
+ index,
+ glyph_index,
+ render_opts,
+ ) catch |err| switch (err) {
+ // If the atlas is full, we resize it
+ error.AtlasFull => blk: {
+ try atlas.grow(alloc, atlas.size * 2);
+ break :blk try self.resolver.renderGlyph(
+ alloc,
+ atlas,
+ index,
+ glyph_index,
+ render_opts,
+ );
+ },
+
+ else => return err,
+ };
+
+ // Cache and return
+ gop.value_ptr.* = .{
+ .glyph = glyph,
+ .presentation = p,
+ };
+
+ return gop.value_ptr.*;
+}
+
+const CodepointKey = struct {
+ style: Style,
+ codepoint: u32,
+ presentation: ?Presentation,
+};
+
+const GlyphKey = struct {
+ index: Collection.Index,
+ glyph: u32,
+ opts: RenderOptions,
+
+ const Context = struct {
+ pub fn hash(_: Context, key: GlyphKey) u64 {
+ // Packed is a u64 but std.hash.int improves uniformity and
+ // avoids collisions in our hashmap.
+ const packed_key = Packed.from(key);
+ return std.hash.int(@as(u64, @bitCast(packed_key)));
+ }
+
+ pub fn eql(_: Context, a: GlyphKey, b: GlyphKey) bool {
+ // Packed checks glyphs but in most cases the glyphs are NOT
+ // equal so the first check leads to increased throughput.
+ return a.glyph == b.glyph and Packed.from(a) == Packed.from(b);
+ }
+ };
+
+ const Packed = packed struct(u64) {
+ index: Collection.Index,
+ glyph: u32,
+ opts: packed struct(u16) {
+ cell_width: u2,
+ thicken: bool,
+ thicken_strength: u8,
+ constraint_width: u2,
+ _padding: u3 = 0,
+ },
+
+ inline fn from(key: GlyphKey) Packed {
+ return .{
+ .index = key.index,
+ .glyph = key.glyph,
+ .opts = .{
+ .cell_width = key.opts.cell_width orelse 0,
+ .thicken = key.opts.thicken,
+ .thicken_strength = key.opts.thicken_strength,
+ .constraint_width = key.opts.constraint_width,
+ },
+ };
+ }
+ };
+};
+
+const TestMode = enum { normal };
+
+fn testGrid(mode: TestMode, alloc: Allocator, lib: Library) !SharedGrid {
+ const testFont = font.embedded.regular;
+
+ var c = Collection.init();
+ c.load_options = .{ .library = lib };
+
+ switch (mode) {
+ .normal => {
+ _ = try c.add(alloc, try .init(
+ lib,
+ testFont,
+ .{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
+ ), .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .none,
+ });
+ },
+ }
+
+ var r: CodepointResolver = .{ .collection = c };
+ errdefer r.deinit(alloc);
+
+ return try init(alloc, r);
+}
+
+test getIndex {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ // const testEmoji = @import("test.zig").fontEmoji;
+
+ var lib = try Library.init(alloc);
+ defer lib.deinit();
+
+ var grid = try testGrid(.normal, alloc, lib);
+ defer grid.deinit(alloc);
+
+ // Visible ASCII.
+ for (32..127) |i| {
+ const idx = (try grid.getIndex(alloc, @intCast(i), .regular, null)).?;
+ try testing.expectEqual(Style.regular, idx.style);
+ try testing.expectEqual(@as(Collection.Index.IndexInt, 0), idx.idx);
+ try testing.expect(grid.hasCodepoint(idx, @intCast(i), null));
+ }
+
+ // Do it again without a resolver set to ensure we only hit the cache
+ const old_resolver = grid.resolver;
+ grid.resolver = undefined;
+ defer grid.resolver = old_resolver;
+ for (32..127) |i| {
+ const idx = (try grid.getIndex(alloc, @intCast(i), .regular, null)).?;
+ try testing.expectEqual(Style.regular, idx.style);
+ try testing.expectEqual(@as(Collection.Index.IndexInt, 0), idx.idx);
+ }
+}
+
+test "renderGlyph error after cache insert rolls back cache entry" {
+ // This test verifies that when renderGlyph fails after inserting a cache
+ // entry (via getOrPut), the errdefer properly removes the entry, preventing
+ // corrupted/uninitialized data from remaining in the cache.
+
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var lib = try Library.init(alloc);
+ defer lib.deinit();
+
+ var grid = try testGrid(.normal, alloc, lib);
+ defer grid.deinit(alloc);
+
+ // Get the font index for 'A'
+ const idx = (try grid.getIndex(alloc, 'A', .regular, null)).?;
+
+ // Get the glyph index for 'A'
+ const glyph_index = glyph_index: {
+ grid.lock.lockShared();
+ defer grid.lock.unlockShared();
+ const face = try grid.resolver.collection.getFace(idx);
+ break :glyph_index face.glyphIndex('A').?;
+ };
+
+ const render_opts: RenderOptions = .{ .grid_metrics = grid.metrics };
+ const key: GlyphKey = .{ .index = idx, .glyph = glyph_index, .opts = render_opts };
+
+ // Verify the cache is empty for this glyph
+ try testing.expect(grid.glyphs.get(key) == null);
+
+ // Set up tripwire to fail after cache insert.
+ // We use OutOfMemory as it's a valid error in the renderGlyph error set.
+ const tw = renderGlyph_tw;
+ defer tw.end(.reset) catch {};
+ tw.errorAlways(.get_presentation, error.OutOfMemory);
+
+ // This should fail due to the tripwire
+ try testing.expectError(
+ error.OutOfMemory,
+ grid.renderGlyph(alloc, idx, glyph_index, render_opts),
+ );
+
+ // The errdefer should have removed the cache entry, leaving the cache clean.
+ // Without the errdefer fix, this would contain garbage/uninitialized data.
+ try testing.expect(grid.glyphs.get(key) == null);
+}
+
+test "init error" {
+ // Test every failure point in `init` and ensure that we don't
+ // leak memory (testing.allocator verifies) since we're exiting early.
+ //
+ // BUG: Currently this test will fail because init() is missing errdefer
+ // cleanup for codepoints and glyphs when late operations fail
+ // (ensureTotalCapacity, reloadMetrics).
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ for (std.meta.tags(init_tw.FailPoint)) |tag| {
+ const tw = init_tw;
+ defer tw.end(.reset) catch unreachable;
+ tw.errorAlways(tag, error.OutOfMemory);
+
+ // Create a resolver for testing - we need to set up a minimal one.
+ // The caller is responsible for cleaning up the resolver if init fails.
+ var lib = try Library.init(alloc);
+ defer lib.deinit();
+
+ var c = Collection.init();
+ c.load_options = .{ .library = lib };
+ _ = try c.add(alloc, try .init(
+ lib,
+ font.embedded.regular,
+ .{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
+ ), .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .none,
+ });
+
+ var resolver: CodepointResolver = .{ .collection = c };
+ defer resolver.deinit(alloc); // Caller cleans up on init failure
+
+ try testing.expectError(
+ error.OutOfMemory,
+ init(alloc, resolver),
+ );
+ }
+}
diff --git a/vendor/libghostty-vt/src/font/SharedGridSet.zig b/vendor/libghostty-vt/src/font/SharedGridSet.zig
new file mode 100644
index 00000000..b832139b
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/SharedGridSet.zig
@@ -0,0 +1,834 @@
+//! This structure contains a set of SharedGrid structures keyed by
+//! unique font configuration.
+//!
+//! Most terminals (surfaces) will share the same font configuration.
+//! This structure allows expensive font information such as
+//! the font atlas, glyph cache, font faces, etc. to be shared.
+//!
+//! This structure is thread-safe when the operations are documented
+//! as thread-safe.
+const SharedGridSet = @This();
+
+const std = @import("std");
+const builtin = @import("builtin");
+const assert = @import("../quirks.zig").inlineAssert;
+const Allocator = std.mem.Allocator;
+const ArenaAllocator = std.heap.ArenaAllocator;
+const font = @import("main.zig");
+const CodepointResolver = font.CodepointResolver;
+const Collection = font.Collection;
+const Discover = font.Discover;
+const Style = font.Style;
+const Library = font.Library;
+const Metrics = font.Metrics;
+const CodepointMap = font.CodepointMap;
+const DesiredSize = font.face.DesiredSize;
+const Face = font.Face;
+const SharedGrid = font.SharedGrid;
+const discovery = @import("discovery.zig");
+const configpkg = @import("../config.zig");
+const Config = configpkg.Config;
+
+const log = std.log.scoped(.font_shared_grid_set);
+
+/// The allocator to use for all heap allocations.
+alloc: Allocator,
+
+/// The map of font configurations to SharedGrid instances.
+map: Map = .{},
+
+/// The font library that is used for all font groups.
+font_lib: Library,
+
+/// Font discovery mechanism.
+font_discover: ?Discover = null,
+
+/// Lock to protect multi-threaded access to the map.
+lock: std.Thread.Mutex = .{},
+
+pub const InitError = Library.InitError;
+
+/// Initialize a new SharedGridSet.
+pub fn init(alloc: Allocator) InitError!SharedGridSet {
+ var font_lib = try Library.init(alloc);
+ errdefer font_lib.deinit();
+
+ return .{
+ .alloc = alloc,
+ .map = .{},
+ .font_lib = font_lib,
+ };
+}
+
+pub fn deinit(self: *SharedGridSet) void {
+ var it = self.map.iterator();
+ while (it.next()) |entry| {
+ entry.key_ptr.deinit();
+ const v = entry.value_ptr.*;
+ v.grid.deinit(self.alloc);
+ self.alloc.destroy(v.grid);
+ }
+ self.map.deinit(self.alloc);
+
+ if (comptime Discover != void) {
+ if (self.font_discover) |*v| v.deinit();
+ }
+
+ self.font_lib.deinit();
+}
+
+/// Returns the number of cached grids.
+pub fn count(self: *SharedGridSet) usize {
+ self.lock.lock();
+ defer self.lock.unlock();
+ return self.map.count();
+}
+
+/// Initialize a SharedGrid for the given font configuration. If the
+/// SharedGrid is not present it will be initialized with a ref count of
+/// 1. If it is present, the ref count will be incremented.
+///
+/// This is NOT thread-safe.
+///
+/// The returned data (key and grid) should never be freed. The memory is
+/// owned by the set and will be freed when the ref count reaches zero.
+pub fn ref(
+ self: *SharedGridSet,
+ config: *const DerivedConfig,
+ font_size: DesiredSize,
+) !struct { Key, *SharedGrid } {
+ var key = try Key.init(self.alloc, config, font_size);
+ errdefer key.deinit();
+
+ self.lock.lock();
+ defer self.lock.unlock();
+
+ const gop = try self.map.getOrPut(self.alloc, key);
+ if (gop.found_existing) {
+ log.debug("found cached grid for font config", .{});
+
+ // We can deinit the key because we found a cached value.
+ key.deinit();
+
+ // Increment our ref count and return the cache
+ gop.value_ptr.ref += 1;
+ return .{ gop.key_ptr.*, gop.value_ptr.grid };
+ }
+ errdefer self.map.removeByPtr(gop.key_ptr);
+
+ log.debug("initializing new grid for font config", .{});
+
+ // A new font config, initialize the cache.
+ const grid = try self.alloc.create(SharedGrid);
+ errdefer self.alloc.destroy(grid);
+ gop.value_ptr.* = .{
+ .grid = grid,
+ .ref = 1,
+ };
+
+ grid.* = try .init(self.alloc, resolver: {
+ // Build our collection. This is the expensive operation that
+ // involves finding fonts, loading them (maybe, some are deferred),
+ // etc.
+ var c = try self.collection(&key, font_size, config);
+ errdefer c.deinit(self.alloc);
+
+ // Setup our enabled/disabled styles
+ var styles = CodepointResolver.StyleStatus.initFill(true);
+ styles.set(.bold, config.@"font-style-bold" != .false);
+ styles.set(.italic, config.@"font-style-italic" != .false);
+ styles.set(.bold_italic, config.@"font-style-bold-italic" != .false);
+
+ // Init our resolver which just requires setting fields.
+ break :resolver .{
+ .collection = c,
+ .styles = styles,
+ .discover = try self.discover(),
+ .codepoint_map = key.codepoint_map,
+ };
+ });
+ errdefer grid.deinit(self.alloc);
+
+ return .{ gop.key_ptr.*, gop.value_ptr.grid };
+}
+
+/// Builds the Collection for the given configuration key and
+/// initial font size.
+fn collection(
+ self: *SharedGridSet,
+ key: *const Key,
+ size: DesiredSize,
+ config: *const DerivedConfig,
+) !Collection {
+ // A quick note on memory management:
+ // - font_lib is owned by the SharedGridSet
+ // - metric_modifiers is owned by the key which is freed only when
+ // the ref count for this grid reaches zero.
+ const load_options: Collection.LoadOptions = .{
+ .library = self.font_lib,
+ .size = size,
+ .freetype_load_flags = key.freetype_load_flags,
+ };
+
+ var c = Collection.init();
+ errdefer c.deinit(self.alloc);
+ c.load_options = load_options;
+ c.metric_modifiers = key.metric_modifiers;
+
+ // Search for fonts
+ if (Discover != void) discover: {
+ const disco = try self.discover() orelse {
+ log.warn(
+ "font discovery not available, cannot search for fonts",
+ .{},
+ );
+ break :discover;
+ };
+
+ // A buffer we use to store the font names for logging.
+ var name_buf: [256]u8 = undefined;
+
+ inline for (@typeInfo(Style).@"enum".fields) |field| {
+ const style = @field(Style, field.name);
+ for (key.descriptorsForStyle(style)) |desc| {
+ {
+ var disco_it = try disco.discover(self.alloc, desc);
+ defer disco_it.deinit();
+ if (try disco_it.next()) |face| {
+ log.info("font {s}: {s}", .{
+ field.name,
+ try face.name(&name_buf),
+ });
+
+ _ = try c.addDeferred(self.alloc, face, .{
+ .style = style,
+ .fallback = false,
+ // No size adjustment for primary fonts.
+ .size_adjustment = .none,
+ });
+
+ continue;
+ }
+ }
+
+ // If there are variation configurations and we didn't find
+ // the font, then we retry the discovery with all stylistic
+ // bits set to false. This is because some fonts may not
+ // set the stylistic bit in their table but still support
+ // axes to mimic the style. At the time of writing, Berkeley
+ // Mono Variable is like this. See #2140.
+ if (style != .regular and desc.variations.len > 0) {
+ var disco_it = try disco.discover(self.alloc, desc: {
+ var copy = desc;
+ copy.bold = false;
+ copy.italic = false;
+ break :desc copy;
+ });
+ defer disco_it.deinit();
+ if (try disco_it.next()) |face| {
+ log.info("font {s}: {s}", .{
+ field.name,
+ try face.name(&name_buf),
+ });
+
+ _ = try c.addDeferred(self.alloc, face, .{
+ .style = style,
+ .fallback = false,
+ // No size adjustment for primary fonts.
+ .size_adjustment = .none,
+ });
+
+ continue;
+ }
+ }
+
+ log.warn("font-family {s} not found: {s}", .{
+ field.name,
+ desc.family.?,
+ });
+ }
+ }
+ }
+
+ // Complete our styles to ensure we have something to satisfy every
+ // possible style request. We do this before adding our built-in font
+ // because we want to ensure our built-in styles are fallbacks to
+ // the configured styles.
+ try c.completeStyles(self.alloc, config.@"font-synthetic-style");
+
+ // Our built-in font will be used as a backup
+ _ = try c.add(
+ self.alloc,
+ try .init(
+ self.font_lib,
+ font.embedded.variable,
+ load_options.faceOptions(),
+ ),
+ .{
+ .style = .regular,
+ .fallback = true,
+ .size_adjustment = font.default_fallback_adjustment,
+ },
+ );
+ try (try c.getFace(try c.add(
+ self.alloc,
+ try .init(
+ self.font_lib,
+ font.embedded.variable,
+ load_options.faceOptions(),
+ ),
+ .{
+ .style = .bold,
+ .fallback = true,
+ .size_adjustment = font.default_fallback_adjustment,
+ },
+ ))).setVariations(
+ &.{.{ .id = .init("wght"), .value = 700 }},
+ load_options.faceOptions(),
+ );
+ _ = try c.add(
+ self.alloc,
+ try .init(
+ self.font_lib,
+ font.embedded.variable_italic,
+ load_options.faceOptions(),
+ ),
+ .{
+ .style = .italic,
+ .fallback = true,
+ .size_adjustment = font.default_fallback_adjustment,
+ },
+ );
+ try (try c.getFace(try c.add(
+ self.alloc,
+ try .init(
+ self.font_lib,
+ font.embedded.variable_italic,
+ load_options.faceOptions(),
+ ),
+ .{
+ .style = .bold_italic,
+ .fallback = true,
+ .size_adjustment = font.default_fallback_adjustment,
+ },
+ ))).setVariations(
+ &.{.{ .id = .init("wght"), .value = 700 }},
+ load_options.faceOptions(),
+ );
+
+ // Nerd-font symbols fallback.
+ _ = try c.add(
+ self.alloc,
+ try .init(
+ self.font_lib,
+ font.embedded.symbols_nerd_font,
+ load_options.faceOptions(),
+ ),
+ .{
+ .style = .regular,
+ .fallback = true,
+ // No size adjustment for the symbols font.
+ .size_adjustment = .none,
+ },
+ );
+
+ // On macOS, always search for and add the Apple Emoji font
+ // as our preferred emoji font for fallback. We do this in case
+ // people add other emoji fonts to their system, we always want to
+ // prefer the official one. Users can override this by explicitly
+ // specifying a font-family for emoji.
+ if (comptime builtin.target.os.tag.isDarwin() and Discover != void) apple_emoji: {
+ const disco = try self.discover() orelse break :apple_emoji;
+ var disco_it = try disco.discover(self.alloc, .{
+ .family = "Apple Color Emoji",
+ });
+ defer disco_it.deinit();
+ if (try disco_it.next()) |face| {
+ _ = try c.addDeferred(self.alloc, face, .{
+ .style = .regular,
+ .fallback = true,
+ // No size adjustment for emojis.
+ .size_adjustment = .none,
+ });
+ }
+ }
+
+ // Emoji fallback. We don't include this on Mac since Mac is expected
+ // to always have the Apple Emoji available on the system.
+ if (comptime !builtin.target.os.tag.isDarwin() or Discover == void) {
+ _ = try c.add(
+ self.alloc,
+ try .init(
+ self.font_lib,
+ font.embedded.emoji,
+ load_options.faceOptions(),
+ ),
+ .{
+ .style = .regular,
+ .fallback = true,
+ // No size adjustment for emojis.
+ .size_adjustment = .none,
+ },
+ );
+ _ = try c.add(
+ self.alloc,
+ try .init(
+ self.font_lib,
+ font.embedded.emoji_text,
+ load_options.faceOptions(),
+ ),
+ .{
+ .style = .regular,
+ .fallback = true,
+ // No size adjustment for emojis.
+ .size_adjustment = .none,
+ },
+ );
+ }
+
+ return c;
+}
+
+/// Decrement the ref count for the given key. If the ref count is zero,
+/// the grid will be deinitialized and removed from the map.j:w
+pub fn deref(self: *SharedGridSet, key: Key) void {
+ self.lock.lock();
+ defer self.lock.unlock();
+
+ const entry = self.map.getEntry(key) orelse return;
+ assert(entry.value_ptr.ref >= 1);
+
+ // If we have more than one reference, decrement and return.
+ if (entry.value_ptr.ref > 1) {
+ entry.value_ptr.ref -= 1;
+ return;
+ }
+
+ // We are at a zero ref count so deinit the group and remove.
+ entry.key_ptr.deinit();
+ entry.value_ptr.grid.deinit(self.alloc);
+ self.alloc.destroy(entry.value_ptr.grid);
+ self.map.removeByPtr(entry.key_ptr);
+}
+
+/// Map of font configurations to grid instances. The grid
+/// instances are pointers that are heap allocated so that they're
+/// stable pointers across hash map resizes.
+pub const Map = std.HashMapUnmanaged(
+ Key,
+ ReffedGrid,
+ struct {
+ const KeyType = Key;
+
+ pub fn hash(ctx: @This(), k: KeyType) u64 {
+ _ = ctx;
+ return k.hashcode();
+ }
+
+ pub fn eql(ctx: @This(), a: KeyType, b: KeyType) bool {
+ return ctx.hash(a) == ctx.hash(b);
+ }
+ },
+ std.hash_map.default_max_load_percentage,
+);
+
+/// Initialize once and return the font discovery mechanism. This remains
+/// initialized throughout the lifetime of the application because some
+/// font discovery mechanisms (i.e. fontconfig) are unsafe to reinit.
+fn discover(self: *SharedGridSet) !?*Discover {
+ // If we're built without a font discovery mechanism, return null
+ if (comptime Discover == void) return null;
+
+ // If we initialized, use it
+ if (self.font_discover) |*v| return v;
+
+ self.font_discover = .init();
+ return &self.font_discover.?;
+}
+
+/// Ref-counted SharedGrid.
+const ReffedGrid = struct {
+ grid: *SharedGrid,
+ ref: u32 = 0,
+};
+
+/// This is the configuration required to create a key without having
+/// to keep the full Ghostty configuration around.
+pub const DerivedConfig = struct {
+ arena: ArenaAllocator,
+
+ @"font-family": configpkg.RepeatableString,
+ @"font-family-bold": configpkg.RepeatableString,
+ @"font-family-italic": configpkg.RepeatableString,
+ @"font-family-bold-italic": configpkg.RepeatableString,
+ @"font-style": configpkg.FontStyle,
+ @"font-style-bold": configpkg.FontStyle,
+ @"font-style-italic": configpkg.FontStyle,
+ @"font-style-bold-italic": configpkg.FontStyle,
+ @"font-variation": configpkg.RepeatableFontVariation,
+ @"font-variation-bold": configpkg.RepeatableFontVariation,
+ @"font-variation-italic": configpkg.RepeatableFontVariation,
+ @"font-variation-bold-italic": configpkg.RepeatableFontVariation,
+ @"font-codepoint-map": configpkg.RepeatableCodepointMap,
+ @"font-synthetic-style": configpkg.FontSyntheticStyle,
+ @"adjust-cell-width": ?Metrics.Modifier,
+ @"adjust-cell-height": ?Metrics.Modifier,
+ @"adjust-font-baseline": ?Metrics.Modifier,
+ @"adjust-underline-position": ?Metrics.Modifier,
+ @"adjust-underline-thickness": ?Metrics.Modifier,
+ @"adjust-strikethrough-position": ?Metrics.Modifier,
+ @"adjust-strikethrough-thickness": ?Metrics.Modifier,
+ @"adjust-overline-position": ?Metrics.Modifier,
+ @"adjust-overline-thickness": ?Metrics.Modifier,
+ @"adjust-cursor-thickness": ?Metrics.Modifier,
+ @"adjust-cursor-height": ?Metrics.Modifier,
+ @"adjust-box-thickness": ?Metrics.Modifier,
+ @"adjust-icon-height": ?Metrics.Modifier,
+ @"freetype-load-flags": font.face.FreetypeLoadFlags,
+
+ /// Initialize a DerivedConfig. The config should be either a
+ /// config.Config or another DerivedConfig to clone from.
+ pub fn init(
+ alloc_gpa: Allocator,
+ config: anytype,
+ ) Allocator.Error!DerivedConfig {
+ var arena = ArenaAllocator.init(alloc_gpa);
+ errdefer arena.deinit();
+ const alloc = arena.allocator();
+
+ return .{
+ .@"font-family" = try config.@"font-family".clone(alloc),
+ .@"font-family-bold" = try config.@"font-family-bold".clone(alloc),
+ .@"font-family-italic" = try config.@"font-family-italic".clone(alloc),
+ .@"font-family-bold-italic" = try config.@"font-family-bold-italic".clone(alloc),
+ .@"font-style" = try config.@"font-style".clone(alloc),
+ .@"font-style-bold" = try config.@"font-style-bold".clone(alloc),
+ .@"font-style-italic" = try config.@"font-style-italic".clone(alloc),
+ .@"font-style-bold-italic" = try config.@"font-style-bold-italic".clone(alloc),
+ .@"font-variation" = try config.@"font-variation".clone(alloc),
+ .@"font-variation-bold" = try config.@"font-variation-bold".clone(alloc),
+ .@"font-variation-italic" = try config.@"font-variation-italic".clone(alloc),
+ .@"font-variation-bold-italic" = try config.@"font-variation-bold-italic".clone(alloc),
+ .@"font-codepoint-map" = try config.@"font-codepoint-map".clone(alloc),
+ .@"font-synthetic-style" = config.@"font-synthetic-style",
+ .@"adjust-cell-width" = config.@"adjust-cell-width",
+ .@"adjust-cell-height" = config.@"adjust-cell-height",
+ .@"adjust-font-baseline" = config.@"adjust-font-baseline",
+ .@"adjust-underline-position" = config.@"adjust-underline-position",
+ .@"adjust-underline-thickness" = config.@"adjust-underline-thickness",
+ .@"adjust-strikethrough-position" = config.@"adjust-strikethrough-position",
+ .@"adjust-strikethrough-thickness" = config.@"adjust-strikethrough-thickness",
+ .@"adjust-overline-position" = config.@"adjust-overline-position",
+ .@"adjust-overline-thickness" = config.@"adjust-overline-thickness",
+ .@"adjust-cursor-thickness" = config.@"adjust-cursor-thickness",
+ .@"adjust-cursor-height" = config.@"adjust-cursor-height",
+ .@"adjust-box-thickness" = config.@"adjust-box-thickness",
+ .@"adjust-icon-height" = config.@"adjust-icon-height",
+ .@"freetype-load-flags" = if (font.face.FreetypeLoadFlags != void) config.@"freetype-load-flags" else {},
+
+ // This must be last so the arena contains all our allocations
+ // from above since Zig does assignment in order.
+ .arena = arena,
+ };
+ }
+
+ pub fn deinit(self: *DerivedConfig) void {
+ self.arena.deinit();
+ }
+};
+
+/// The key used to uniquely identify a font configuration.
+pub const Key = struct {
+ arena: ArenaAllocator,
+
+ /// The descriptors used for all the fonts added to the
+ /// initial group, including all styles. This is hashed
+ /// in order so the order matters. All users of the struct
+ /// should ensure that the order is consistent.
+ descriptors: []const discovery.Descriptor = &.{},
+
+ /// These are the offsets into the descriptors array for
+ /// each style. For example, bold is from
+ /// offsets[@intFromEnum(.bold) - 1] to
+ /// offsets[@intFromEnum(.bold)].
+ style_offsets: StyleOffsets = @splat(0),
+
+ /// The codepoint map configuration.
+ codepoint_map: CodepointMap = .{},
+
+ /// The metric modifier set configuration.
+ metric_modifiers: Metrics.ModifierSet = .{},
+
+ /// The configured font size for this key. We don't use this
+ /// directly but it is used as part of the hash for the
+ /// font grid.
+ font_size: DesiredSize = .{ .points = 12 },
+
+ /// The freetype load flags configuration, only non-void if the
+ /// freetype backend is enabled.
+ freetype_load_flags: font.face.FreetypeLoadFlags = font.face.freetype_load_flags_default,
+
+ const style_offsets_len = std.enums.directEnumArrayLen(Style, 0);
+ const StyleOffsets = [style_offsets_len]usize;
+
+ comptime {
+ // We assume this throughout this structure. If this changes
+ // we may need to change this structure.
+ assert(@intFromEnum(Style.regular) == 0);
+ assert(@intFromEnum(Style.bold) == 1);
+ assert(@intFromEnum(Style.italic) == 2);
+ assert(@intFromEnum(Style.bold_italic) == 3);
+ }
+
+ pub fn init(
+ alloc_gpa: Allocator,
+ config_src: *const DerivedConfig,
+ font_size: DesiredSize,
+ ) Allocator.Error!Key {
+ var arena = ArenaAllocator.init(alloc_gpa);
+ errdefer arena.deinit();
+ const alloc = arena.allocator();
+
+ // Clone our configuration. We need to do this because the lifetime
+ // of the derived config is usually shorter than that of a key
+ // and we use pointers into the derived config for the key. We
+ // can remove this if we wanted by dupe-ing the memory we use
+ // from DerivedConfig below.
+ var config = try DerivedConfig.init(alloc, config_src);
+
+ var descriptors: std.ArrayList(discovery.Descriptor) = .empty;
+ defer descriptors.deinit(alloc);
+ for (config.@"font-family".list.items) |family| {
+ try descriptors.append(alloc, .{
+ .family = family,
+ .style = config.@"font-style".nameValue(),
+ .size = font_size.points,
+ .variations = config.@"font-variation".list.items,
+ });
+ }
+
+ // In all the styled cases below, we prefer to specify an exact
+ // style via the `font-style` configuration. If a style is not
+ // specified, we use the discovery mechanism to search for a
+ // style category such as bold, italic, etc. We can't specify both
+ // because the latter will restrict the search to only that. If
+ // a user says `font-style = italic` for the bold face for example,
+ // no results would be found if we restrict to ALSO searching for
+ // italic.
+ for (config.@"font-family-bold".list.items) |family| {
+ const style = config.@"font-style-bold".nameValue();
+ try descriptors.append(alloc, .{
+ .family = family,
+ .style = style,
+ .size = font_size.points,
+ .bold = style == null,
+ .variations = config.@"font-variation-bold".list.items,
+ });
+ }
+ for (config.@"font-family-italic".list.items) |family| {
+ const style = config.@"font-style-italic".nameValue();
+ try descriptors.append(alloc, .{
+ .family = family,
+ .style = style,
+ .size = font_size.points,
+ .italic = style == null,
+ .variations = config.@"font-variation-italic".list.items,
+ });
+ }
+ for (config.@"font-family-bold-italic".list.items) |family| {
+ const style = config.@"font-style-bold-italic".nameValue();
+ try descriptors.append(alloc, .{
+ .family = family,
+ .style = style,
+ .size = font_size.points,
+ .bold = style == null,
+ .italic = style == null,
+ .variations = config.@"font-variation-bold-italic".list.items,
+ });
+ }
+
+ // Setup the codepoint map
+ const codepoint_map: CodepointMap = map: {
+ const map = config.@"font-codepoint-map";
+ if (map.map.list.len == 0) break :map .{};
+ const clone = try config.@"font-codepoint-map".clone(alloc);
+ break :map clone.map;
+ };
+
+ // Metric modifiers
+ const metric_modifiers: Metrics.ModifierSet = set: {
+ var set: Metrics.ModifierSet = .{};
+ if (config.@"adjust-cell-width") |m| try set.put(alloc, .cell_width, m);
+ if (config.@"adjust-cell-height") |m| try set.put(alloc, .cell_height, m);
+ if (config.@"adjust-font-baseline") |m| try set.put(alloc, .cell_baseline, m);
+ if (config.@"adjust-underline-position") |m| try set.put(alloc, .underline_position, m);
+ if (config.@"adjust-underline-thickness") |m| try set.put(alloc, .underline_thickness, m);
+ if (config.@"adjust-strikethrough-position") |m| try set.put(alloc, .strikethrough_position, m);
+ if (config.@"adjust-strikethrough-thickness") |m| try set.put(alloc, .strikethrough_thickness, m);
+ if (config.@"adjust-overline-position") |m| try set.put(alloc, .overline_position, m);
+ if (config.@"adjust-overline-thickness") |m| try set.put(alloc, .overline_thickness, m);
+ if (config.@"adjust-cursor-thickness") |m| try set.put(alloc, .cursor_thickness, m);
+ if (config.@"adjust-cursor-height") |m| try set.put(alloc, .cursor_height, m);
+ if (config.@"adjust-box-thickness") |m| try set.put(alloc, .box_thickness, m);
+ if (config.@"adjust-icon-height") |m| try set.put(alloc, .icon_height, m);
+ break :set set;
+ };
+
+ const regular_offset = config.@"font-family".list.items.len;
+ const bold_offset = regular_offset + config.@"font-family-bold".list.items.len;
+ const italic_offset = bold_offset + config.@"font-family-italic".list.items.len;
+ const bold_italic_offset = italic_offset + config.@"font-family-bold-italic".list.items.len;
+
+ return .{
+ .arena = arena,
+ .descriptors = try descriptors.toOwnedSlice(alloc),
+ .style_offsets = .{
+ regular_offset,
+ bold_offset,
+ italic_offset,
+ bold_italic_offset,
+ },
+ .codepoint_map = codepoint_map,
+ .metric_modifiers = metric_modifiers,
+ .font_size = font_size,
+ .freetype_load_flags = if (font.face.FreetypeLoadFlags != void)
+ config.@"freetype-load-flags"
+ else
+ font.face.freetype_load_flags_default,
+ };
+ }
+
+ pub fn deinit(self: *Key) void {
+ self.arena.deinit();
+ }
+
+ /// Get the descriptors for the given font style that can be
+ /// used with discovery.
+ pub fn descriptorsForStyle(
+ self: Key,
+ style: Style,
+ ) []const discovery.Descriptor {
+ const idx = @intFromEnum(style);
+ const start: usize = if (idx == 0) 0 else self.style_offsets[idx - 1];
+ const end = self.style_offsets[idx];
+ return self.descriptors[start..end];
+ }
+
+ /// Hash the key with the given hasher.
+ pub fn hash(self: Key, hasher: anytype) void {
+ const autoHash = std.hash.autoHash;
+ autoHash(hasher, @as(u32, @bitCast(self.font_size.points)));
+ autoHash(hasher, self.font_size.xdpi);
+ autoHash(hasher, self.font_size.ydpi);
+ autoHash(hasher, self.descriptors.len);
+ for (self.descriptors) |d| d.hash(hasher);
+ self.codepoint_map.hash(hasher);
+ autoHash(hasher, self.metric_modifiers.count());
+ autoHash(hasher, self.freetype_load_flags);
+ if (self.metric_modifiers.count() > 0) {
+ inline for (@typeInfo(Metrics.Key).@"enum".fields) |field| {
+ const key = @field(Metrics.Key, field.name);
+ if (self.metric_modifiers.get(key)) |value| {
+ autoHash(hasher, key);
+ value.hash(hasher);
+ }
+ }
+ }
+ }
+
+ /// Returns a hash code that can be used to uniquely identify this
+ /// action.
+ pub fn hashcode(self: Key) u64 {
+ var hasher = std.hash.Wyhash.init(0);
+ self.hash(&hasher);
+ return hasher.final();
+ }
+};
+
+test "Key" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ var cfg = try Config.default(alloc);
+ defer cfg.deinit();
+
+ var keycfg = try DerivedConfig.init(alloc, &cfg);
+ defer keycfg.deinit();
+
+ var k = try Key.init(alloc, &keycfg, .{ .points = 12 });
+ defer k.deinit();
+
+ var k2 = try Key.init(alloc, &keycfg, .{ .points = 12 });
+ defer k2.deinit();
+
+ try testing.expect(k.hashcode() > 0);
+ try testing.expectEqual(k.hashcode(), k2.hashcode());
+}
+
+test "Key different font points" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ var cfg = try Config.default(alloc);
+ defer cfg.deinit();
+
+ var keycfg = try DerivedConfig.init(alloc, &cfg);
+ defer keycfg.deinit();
+
+ var k = try Key.init(alloc, &keycfg, .{ .points = 12 });
+ defer k.deinit();
+
+ var k2 = try Key.init(alloc, &keycfg, .{ .points = 16 });
+ defer k2.deinit();
+
+ try testing.expect(k.hashcode() != k2.hashcode());
+}
+
+test "Key different font DPI" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ var cfg = try Config.default(alloc);
+ defer cfg.deinit();
+
+ var keycfg = try DerivedConfig.init(alloc, &cfg);
+ defer keycfg.deinit();
+
+ var k = try Key.init(alloc, &keycfg, .{ .points = 12, .xdpi = 1 });
+ defer k.deinit();
+
+ var k2 = try Key.init(alloc, &keycfg, .{ .points = 12, .xdpi = 2 });
+ defer k2.deinit();
+
+ try testing.expect(k.hashcode() != k2.hashcode());
+}
+
+test SharedGridSet {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var set = try SharedGridSet.init(alloc);
+ defer set.deinit();
+
+ var cfg = try Config.default(alloc);
+ defer cfg.deinit();
+
+ var keycfg = try DerivedConfig.init(alloc, &cfg);
+ defer keycfg.deinit();
+
+ // Get a grid for the given config
+ const key1, const grid1 = try set.ref(&keycfg, .{ .points = 12 });
+ try testing.expectEqual(@as(usize, 1), set.count());
+
+ // Get another
+ const key2, const grid2 = try set.ref(&keycfg, .{ .points = 12 });
+ try testing.expectEqual(@as(usize, 1), set.count());
+
+ // They should be pointer equivalent
+ try testing.expectEqual(@intFromPtr(grid1), @intFromPtr(grid2));
+
+ // If I deref grid2 then we should still have a count of 1
+ set.deref(key2);
+ try testing.expectEqual(@as(usize, 1), set.count());
+
+ // If I deref grid1 then we should have a count of 0
+ set.deref(key1);
+ try testing.expectEqual(@as(usize, 0), set.count());
+}
diff --git a/vendor/libghostty-vt/src/font/backend.zig b/vendor/libghostty-vt/src/font/backend.zig
new file mode 100644
index 00000000..d24edec7
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/backend.zig
@@ -0,0 +1,118 @@
+const std = @import("std");
+
+pub const Backend = enum {
+ const WasmTarget = @import("../os/wasm/target.zig").Target;
+
+ /// FreeType for font rendering with no font discovery enabled.
+ freetype,
+
+ /// Fontconfig for font discovery and FreeType for font rendering.
+ fontconfig_freetype,
+
+ /// CoreText for font discovery, rendering, and shaping (macOS).
+ coretext,
+
+ /// CoreText for font discovery, FreeType for rendering, and
+ /// HarfBuzz for shaping (macOS).
+ coretext_freetype,
+
+ /// CoreText for font discovery and rendering, HarfBuzz for shaping
+ coretext_harfbuzz,
+
+ /// CoreText for font discovery and rendering, no shaping.
+ coretext_noshape,
+
+ /// Use the browser font system and the Canvas API (wasm). This limits
+ /// the available fonts to browser fonts (anything Canvas natively
+ /// supports).
+ web_canvas,
+
+ /// Returns the default backend for a build environment. This is
+ /// meant to be called at comptime by the build.zig script. To get the
+ /// backend look at build_options.
+ pub fn default(
+ target: std.Target,
+ wasm_target: WasmTarget,
+ ) Backend {
+ if (target.cpu.arch == .wasm32) {
+ return switch (wasm_target) {
+ .browser => .web_canvas,
+ };
+ }
+
+ if (target.os.tag == .windows) {
+ // Avoid fontconfig on Windows because its libxml2 dependency
+ // may not unpack due to symlinks. Use plain freetype for now
+ // which means no font discovery. Full solution would likely use
+ // DirectWrite which has its own discovery API.
+ return .freetype;
+ }
+
+ // macOS also supports "coretext_freetype" but there is no scenario
+ // that is the default. It is only used by people who want to
+ // self-compile Ghostty and prefer the freetype aesthetic.
+ return if (target.os.tag.isDarwin()) .coretext else .fontconfig_freetype;
+ }
+
+ // All the functions below can be called at comptime or runtime to
+ // determine if we have a certain dependency.
+
+ pub fn hasFreetype(self: Backend) bool {
+ return switch (self) {
+ .freetype,
+ .fontconfig_freetype,
+ .coretext_freetype,
+ => true,
+
+ .coretext,
+ .coretext_harfbuzz,
+ .coretext_noshape,
+ .web_canvas,
+ => false,
+ };
+ }
+
+ pub fn hasCoretext(self: Backend) bool {
+ return switch (self) {
+ .coretext,
+ .coretext_freetype,
+ .coretext_harfbuzz,
+ .coretext_noshape,
+ => true,
+
+ .freetype,
+ .fontconfig_freetype,
+ .web_canvas,
+ => false,
+ };
+ }
+
+ pub fn hasFontconfig(self: Backend) bool {
+ return switch (self) {
+ .fontconfig_freetype => true,
+
+ .freetype,
+ .coretext,
+ .coretext_freetype,
+ .coretext_harfbuzz,
+ .coretext_noshape,
+ .web_canvas,
+ => false,
+ };
+ }
+
+ pub fn hasHarfbuzz(self: Backend) bool {
+ return switch (self) {
+ .freetype,
+ .fontconfig_freetype,
+ .coretext_freetype,
+ .coretext_harfbuzz,
+ => true,
+
+ .coretext,
+ .coretext_noshape,
+ .web_canvas,
+ => false,
+ };
+ }
+};
diff --git a/vendor/libghostty-vt/src/font/discovery.zig b/vendor/libghostty-vt/src/font/discovery.zig
new file mode 100644
index 00000000..c419d36a
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/discovery.zig
@@ -0,0 +1,1049 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const assert = @import("../quirks.zig").inlineAssert;
+const fontconfig = @import("fontconfig");
+const macos = @import("macos");
+const opentype = @import("opentype.zig");
+const options = @import("main.zig").options;
+const Collection = @import("main.zig").Collection;
+const DeferredFace = @import("main.zig").DeferredFace;
+const Variation = @import("main.zig").face.Variation;
+
+const log = std.log.scoped(.discovery);
+
+/// Discover implementation for the compile options.
+pub const Discover = switch (options.backend) {
+ .freetype => void, // no discovery
+ .fontconfig_freetype => Fontconfig,
+ .web_canvas => void, // no discovery
+ .coretext,
+ .coretext_freetype,
+ .coretext_harfbuzz,
+ .coretext_noshape,
+ => CoreText,
+};
+
+/// Descriptor is used to search for fonts. The only required field
+/// is "family". The rest are ignored unless they're set to a non-zero
+/// value.
+pub const Descriptor = struct {
+ /// Font family to search for. This can be a fully qualified font
+ /// name such as "Fira Code", "monospace", "serif", etc. Memory is
+ /// owned by the caller and should be freed when this descriptor
+ /// is no longer in use. The discovery structs will never store the
+ /// descriptor.
+ ///
+ /// On systems that use fontconfig (Linux), this can be a full
+ /// fontconfig pattern, such as "Fira Code-14:bold".
+ family: ?[:0]const u8 = null,
+
+ /// Specific font style to search for. This will filter the style
+ /// string the font advertises. The "bold/italic" booleans later in this
+ /// struct filter by the style trait the font has, not the string, so
+ /// these can be used in conjunction or not.
+ style: ?[:0]const u8 = null,
+
+ /// A codepoint that this font must be able to render.
+ codepoint: u32 = 0,
+
+ /// Font size in points that the font should support. For conversion
+ /// to pixels, we will use 72 DPI for Mac and 96 DPI for everything else.
+ /// (If pixel conversion is necessary, i.e. emoji fonts)
+ size: f32 = 0,
+
+ /// True if we want to search specifically for a font that supports
+ /// specific styles.
+ bold: bool = false,
+ italic: bool = false,
+ monospace: bool = false,
+
+ /// Variation axes to apply to the font. This also impacts searching
+ /// for fonts since fonts with the ability to set these variations
+ /// will be preferred, but not guaranteed.
+ variations: []const Variation = &.{},
+
+ /// Hash the descriptor with the given hasher.
+ pub fn hash(self: Descriptor, hasher: anytype) void {
+ const autoHash = std.hash.autoHash;
+ const autoHashStrat = std.hash.autoHashStrat;
+ autoHashStrat(hasher, self.family, .Deep);
+ autoHashStrat(hasher, self.style, .Deep);
+ autoHash(hasher, self.codepoint);
+ autoHash(hasher, @as(u32, @bitCast(self.size)));
+ autoHash(hasher, self.bold);
+ autoHash(hasher, self.italic);
+ autoHash(hasher, self.monospace);
+ autoHash(hasher, self.variations.len);
+ for (self.variations) |variation| {
+ autoHash(hasher, variation.id);
+
+ // This is not correct, but we don't currently depend on the
+ // hash value being different based on decimal values of variations.
+ autoHash(hasher, @as(i64, @intFromFloat(variation.value)));
+ }
+ }
+
+ /// Returns a hash code that can be used to uniquely identify this
+ /// action.
+ pub fn hashcode(self: Descriptor) u64 {
+ var hasher = std.hash.Wyhash.init(0);
+ self.hash(&hasher);
+ return hasher.final();
+ }
+
+ /// Deep copy of the struct. The given allocator is expected to
+ /// be an arena allocator of some sort since the descriptor
+ /// itself doesn't support fine-grained deallocation of fields.
+ pub fn clone(self: *const Descriptor, alloc: Allocator) !Descriptor {
+ // We can't do any errdefer cleanup in here. As documented we
+ // expect the allocator to be an arena so any errors should be
+ // cleaned up somewhere else.
+
+ var copy = self.*;
+ copy.family = if (self.family) |src| try alloc.dupeZ(u8, src) else null;
+ copy.style = if (self.style) |src| try alloc.dupeZ(u8, src) else null;
+ copy.variations = try alloc.dupe(Variation, self.variations);
+ return copy;
+ }
+
+ /// Convert to Fontconfig pattern to use for lookup. The pattern does
+ /// not have defaults filled/substituted (Fontconfig thing) so callers
+ /// must still do this.
+ pub fn toFcPattern(self: Descriptor) *fontconfig.Pattern {
+ const pat = fontconfig.Pattern.create();
+ if (self.family) |family| {
+ assert(pat.add(.family, .{ .string = family }, false));
+ }
+ if (self.style) |style| {
+ assert(pat.add(.style, .{ .string = style }, false));
+ }
+ if (self.codepoint > 0) {
+ const cs = fontconfig.CharSet.create();
+ defer cs.destroy();
+ assert(cs.addChar(self.codepoint));
+ assert(pat.add(.charset, .{ .char_set = cs }, false));
+ }
+ if (self.size > 0) assert(pat.add(
+ .size,
+ .{ .integer = @intFromFloat(@round(self.size)) },
+ false,
+ ));
+ if (self.bold) assert(pat.add(
+ .weight,
+ .{ .integer = @intFromEnum(fontconfig.Weight.bold) },
+ false,
+ ));
+ if (self.italic) assert(pat.add(
+ .slant,
+ .{ .integer = @intFromEnum(fontconfig.Slant.italic) },
+ false,
+ ));
+
+ // For fontconfig, we always add monospace in the pattern. Since
+ // fontconfig sorts by closeness to the pattern, this doesn't fully
+ // exclude non-monospace but helps prefer it.
+ assert(pat.add(
+ .spacing,
+ .{ .integer = @intFromEnum(fontconfig.Spacing.mono) },
+ false,
+ ));
+
+ return pat;
+ }
+
+ /// Convert to Core Text font descriptor to use for lookup or
+ /// conversion to a specific font.
+ pub fn toCoreTextDescriptor(self: Descriptor) !*macos.text.FontDescriptor {
+ const attrs = try macos.foundation.MutableDictionary.create(0);
+ defer attrs.release();
+
+ // Family
+ if (self.family) |family_bytes| {
+ const family = try macos.foundation.String.createWithBytes(family_bytes, .utf8, false);
+ defer family.release();
+ attrs.setValue(
+ macos.text.FontAttribute.family_name.key(),
+ family,
+ );
+ }
+
+ // Style
+ if (self.style) |style_bytes| {
+ const style = try macos.foundation.String.createWithBytes(style_bytes, .utf8, false);
+ defer style.release();
+ attrs.setValue(
+ macos.text.FontAttribute.style_name.key(),
+ style,
+ );
+ }
+
+ // Codepoint support
+ if (self.codepoint > 0) {
+ const cs = try macos.foundation.CharacterSet.createWithCharactersInRange(.{
+ .location = self.codepoint,
+ .length = 1,
+ });
+ defer cs.release();
+ attrs.setValue(
+ macos.text.FontAttribute.character_set.key(),
+ cs,
+ );
+ }
+
+ // Set our size attribute if set
+ if (self.size > 0) {
+ const size32: i32 = @intFromFloat(@round(self.size));
+ const size = try macos.foundation.Number.create(
+ .sint32,
+ &size32,
+ );
+ defer size.release();
+ attrs.setValue(
+ macos.text.FontAttribute.size.key(),
+ size,
+ );
+ }
+
+ // Build our traits. If we set any, then we store it in the attributes
+ // otherwise we do nothing. We determine this by setting up the packed
+ // struct, converting to an int, and checking if it is non-zero.
+ const traits: macos.text.FontSymbolicTraits = .{
+ .bold = self.bold,
+ .italic = self.italic,
+ .monospace = self.monospace,
+ };
+ const traits_cval: u32 = @bitCast(traits);
+ if (traits_cval > 0) {
+ // Setting traits is a pain. We have to create a nested dictionary
+ // of the symbolic traits value, and set that in our attributes.
+ const traits_num = try macos.foundation.Number.create(
+ .sint32,
+ @as(*const i32, @ptrCast(&traits_cval)),
+ );
+ defer traits_num.release();
+
+ const traits_dict = try macos.foundation.MutableDictionary.create(0);
+ defer traits_dict.release();
+ traits_dict.setValue(
+ macos.text.FontTraitKey.symbolic.key(),
+ traits_num,
+ );
+
+ attrs.setValue(
+ macos.text.FontAttribute.traits.key(),
+ traits_dict,
+ );
+ }
+
+ return try macos.text.FontDescriptor.createWithAttributes(@ptrCast(attrs));
+ }
+};
+
+pub const Fontconfig = struct {
+ fc_config: *fontconfig.Config,
+
+ pub fn init() Fontconfig {
+ // safe to call multiple times and concurrently
+ _ = fontconfig.init();
+ return .{ .fc_config = fontconfig.initLoadConfigAndFonts() };
+ }
+
+ pub fn deinit(self: *Fontconfig) void {
+ self.fc_config.destroy();
+ }
+
+ /// Discover fonts from a descriptor. This returns an iterator that can
+ /// be used to build up the deferred fonts.
+ pub fn discover(
+ self: *const Fontconfig,
+ alloc: Allocator,
+ desc: Descriptor,
+ ) !DiscoverIterator {
+ _ = alloc;
+
+ // Build our pattern that we'll search for
+ const pat = desc.toFcPattern();
+ errdefer pat.destroy();
+ assert(self.fc_config.substituteWithPat(pat, .pattern));
+ pat.defaultSubstitute();
+
+ // Search
+ const res = self.fc_config.fontSort(pat, false, null);
+ if (res.result != .match) return error.FontConfigFailed;
+ errdefer res.fs.destroy();
+
+ return .{
+ .config = self.fc_config,
+ .pattern = pat,
+ .set = res.fs,
+ .fonts = res.fs.fonts(),
+ .variations = desc.variations,
+ .i = 0,
+ };
+ }
+
+ pub fn discoverFallback(
+ self: *const Fontconfig,
+ alloc: Allocator,
+ collection: *Collection,
+ desc: Descriptor,
+ ) !DiscoverIterator {
+ _ = collection;
+ return try self.discover(alloc, desc);
+ }
+
+ pub const DiscoverIterator = struct {
+ config: *fontconfig.Config,
+ pattern: *fontconfig.Pattern,
+ set: *fontconfig.FontSet,
+ fonts: []*fontconfig.Pattern,
+ variations: []const Variation,
+ i: usize,
+
+ pub fn deinit(self: *DiscoverIterator) void {
+ self.set.destroy();
+ self.pattern.destroy();
+ self.* = undefined;
+ }
+
+ pub fn next(self: *DiscoverIterator) fontconfig.Error!?DeferredFace {
+ if (self.i >= self.fonts.len) return null;
+
+ // Get the copied pattern from our fontset that has the
+ // attributes configured for rendering.
+ const font_pattern = try self.config.fontRenderPrepare(
+ self.pattern,
+ self.fonts[self.i],
+ );
+ errdefer font_pattern.destroy();
+
+ // Increment after we return
+ defer self.i += 1;
+
+ return DeferredFace{
+ .fc = .{
+ .pattern = font_pattern,
+ .charset = (try font_pattern.get(.charset, 0)).char_set,
+ .langset = (try font_pattern.get(.lang, 0)).lang_set,
+ .variations = self.variations,
+ },
+ };
+ }
+ };
+};
+
+pub const CoreText = struct {
+ pub fn init() CoreText {
+ // Required for the "interface" but does nothing for CoreText.
+ return .{};
+ }
+
+ pub fn deinit(self: *CoreText) void {
+ _ = self;
+ }
+
+ /// Discover fonts from a descriptor. This returns an iterator that can
+ /// be used to build up the deferred fonts.
+ pub fn discover(self: *const CoreText, alloc: Allocator, desc: Descriptor) !DiscoverIterator {
+ _ = self;
+
+ // Build our pattern that we'll search for
+ const ct_desc = try desc.toCoreTextDescriptor();
+ defer ct_desc.release();
+
+ // Our descriptors have to be in an array
+ var ct_desc_arr = [_]*const macos.text.FontDescriptor{ct_desc};
+ const desc_arr = try macos.foundation.Array.create(macos.text.FontDescriptor, &ct_desc_arr);
+ defer desc_arr.release();
+
+ // Build our collection
+ const set = try macos.text.FontCollection.createWithFontDescriptors(desc_arr);
+ defer set.release();
+ const list = set.createMatchingFontDescriptors();
+ defer list.release();
+
+ // Sort our descriptors
+ const zig_list = try copyMatchingDescriptors(alloc, list);
+ errdefer alloc.free(zig_list);
+ sortMatchingDescriptors(&desc, zig_list);
+
+ return DiscoverIterator{
+ .alloc = alloc,
+ .list = zig_list,
+ .variations = desc.variations,
+ .i = 0,
+ };
+ }
+
+ pub fn discoverFallback(
+ self: *const CoreText,
+ alloc: Allocator,
+ collection: *Collection,
+ desc: Descriptor,
+ ) !DiscoverIterator {
+ // If we have a codepoint within the CJK unified ideographs block
+ // then we fallback to macOS to find a font that supports it because
+ // there isn't a better way manually with CoreText that I can find that
+ // properly takes into account system locale.
+ //
+ // References:
+ // - http://unicode.org/charts/PDF/U4E00.pdf
+ // - https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/platform/fonts/LocaleInFonts.md#unified-han-ideographs
+ if (desc.codepoint >= 0x4E00 and
+ desc.codepoint <= 0x9FFF)
+ han: {
+ const han = try self.discoverCodepoint(
+ collection,
+ desc,
+ ) orelse break :han;
+
+ // This is silly but our discover iterator needs a slice so
+ // we allocate here. This isn't a performance bottleneck but
+ // this is something we can optimize very easily...
+ const list = try alloc.alloc(*macos.text.FontDescriptor, 1);
+ errdefer alloc.free(list);
+ list[0] = han;
+
+ return DiscoverIterator{
+ .alloc = alloc,
+ .list = list,
+ .variations = desc.variations,
+ .i = 0,
+ };
+ }
+
+ const it = try self.discover(alloc, desc);
+
+ // If our normal discovery doesn't find anything and we have a specific
+ // codepoint, then fallback to using CTFontCreateForString to find a
+ // matching font CoreText wants to use. See:
+ // https://github.com/ghostty-org/ghostty/issues/2499
+ if (it.list.len == 0 and desc.codepoint > 0) codepoint: {
+ const ct_desc = try self.discoverCodepoint(
+ collection,
+ desc,
+ ) orelse break :codepoint;
+
+ const list = try alloc.alloc(*macos.text.FontDescriptor, 1);
+ errdefer alloc.free(list);
+ list[0] = ct_desc;
+
+ return DiscoverIterator{
+ .alloc = alloc,
+ .list = list,
+ .variations = desc.variations,
+ .i = 0,
+ };
+ }
+
+ return it;
+ }
+
+ /// Discover a font for a specific codepoint using the CoreText
+ /// CTFontCreateForString API.
+ fn discoverCodepoint(
+ self: *const CoreText,
+ collection: *Collection,
+ desc: Descriptor,
+ ) !?*macos.text.FontDescriptor {
+ _ = self;
+
+ if (comptime options.backend.hasFreetype()) {
+ // If we have freetype, we can't use CoreText to find a font
+ // that supports a specific codepoint because we need to
+ // have a CoreText font to be able to do so.
+ return null;
+ }
+
+ assert(desc.codepoint > 0);
+
+ // Get our original font. This is dependent on the requested style
+ // from the descriptor.
+ const original = original: {
+ // In all the styles below, we try to match it but if we don't
+ // we always fall back to some other option. The order matters
+ // here.
+
+ if (desc.bold and desc.italic) {
+ const entries = collection.faces.get(.bold_italic);
+ if (entries.count() > 0) {
+ break :original try collection.getFace(.{ .style = .bold_italic });
+ }
+ }
+
+ if (desc.bold) {
+ const entries = collection.faces.get(.bold);
+ if (entries.count() > 0) {
+ break :original try collection.getFace(.{ .style = .bold });
+ }
+ }
+
+ if (desc.italic) {
+ const entries = collection.faces.get(.italic);
+ if (entries.count() > 0) {
+ break :original try collection.getFace(.{ .style = .italic });
+ }
+ }
+
+ break :original try collection.getFace(.{ .style = .regular });
+ };
+
+ // We need it in utf8 format
+ var buf: [4]u8 = undefined;
+ const len = try std.unicode.utf8Encode(
+ @intCast(desc.codepoint),
+ &buf,
+ );
+
+ // We need a CFString
+ const str = try macos.foundation.String.createWithBytes(
+ buf[0..len],
+ .utf8,
+ false,
+ );
+ defer str.release();
+
+ // Get our range length for CTFontCreateForString. It looks like
+ // the range uses UTF-16 codepoints and not UTF-32 codepoints.
+ const range_len: usize = range_len: {
+ var unichars: [2]u16 = undefined;
+ const pair = macos.foundation.stringGetSurrogatePairForLongCharacter(
+ desc.codepoint,
+ &unichars,
+ );
+ break :range_len if (pair) 2 else 1;
+ };
+
+ // Get our font
+ const font = original.font.createForString(
+ str,
+ macos.foundation.Range.init(0, range_len),
+ ) orelse return null;
+ defer font.release();
+
+ // Do not allow the last resort font to go through. This is the
+ // last font used by CoreText if it can't find anything else and
+ // only contains replacement characters.
+ last_resort: {
+ const name_str = font.copyPostScriptName();
+ defer name_str.release();
+
+ // If the name doesn't fit in our buffer, then it can't
+ // be the last resort font so we break out.
+ var name_buf: [64]u8 = undefined;
+ const name: []const u8 = name_str.cstring(&name_buf, .utf8) orelse
+ break :last_resort;
+
+ // If the name is "LastResort" then we don't want to use it.
+ if (std.mem.eql(u8, "LastResort", name)) return null;
+ }
+
+ // Get the descriptor
+ return font.copyDescriptor();
+ }
+
+ fn copyMatchingDescriptors(
+ alloc: Allocator,
+ list: *macos.foundation.Array,
+ ) ![]*macos.text.FontDescriptor {
+ var result = try alloc.alloc(*macos.text.FontDescriptor, list.getCount());
+ errdefer alloc.free(result);
+ for (0..result.len) |i| {
+ result[i] = list.getValueAtIndex(macos.text.FontDescriptor, i);
+
+ // We need to retain because once the list is freed it will
+ // release all its members.
+ result[i].retain();
+ }
+ return result;
+ }
+
+ fn sortMatchingDescriptors(
+ desc: *const Descriptor,
+ list: []*macos.text.FontDescriptor,
+ ) void {
+ std.mem.sortUnstable(*macos.text.FontDescriptor, list, desc, struct {
+ fn lessThan(
+ desc_inner: *const Descriptor,
+ lhs: *macos.text.FontDescriptor,
+ rhs: *macos.text.FontDescriptor,
+ ) bool {
+ const lhs_score: Score = .score(desc_inner, lhs);
+ const rhs_score: Score = .score(desc_inner, rhs);
+ // Higher score is "less" (earlier)
+ return lhs_score.int() > rhs_score.int();
+ }
+ }.lessThan);
+ }
+
+ /// We represent our sorting score as a packed struct so that we
+ /// can compare scores numerically but build scores symbolically.
+ ///
+ /// Note that packed structs store their fields from least to most
+ /// significant, so the fields here are defined in increasing order
+ /// of precedence.
+ const Score = packed struct {
+ const Backing = @typeInfo(@This()).@"struct".backing_integer.?;
+
+ /// Number of glyphs in the font, if two fonts have identical
+ /// scores otherwise then we prefer the one with more glyphs.
+ ///
+ /// (Number of glyphs clamped at u16 intmax)
+ glyph_count: u16 = 0,
+ /// A fuzzy match on the style string, less important than
+ /// an exact match, and less important than trait matches.
+ fuzzy_style: u8 = 0,
+ /// Whether the bold-ness of the font matches the descriptor.
+ /// This is less important than italic because a font that's italic
+ /// when it shouldn't be or not italic when it should be is a bigger
+ /// problem (subjectively) than being the wrong weight.
+ bold: bool = false,
+ /// Whether the italic-ness of the font matches the descriptor.
+ /// This is less important than an exact match on the style string
+ /// because we want users to be allowed to override trait matching
+ /// for the bold/italic/bold italic styles if they want.
+ italic: bool = false,
+ /// An exact (case-insensitive) match on the style string.
+ exact_style: bool = false,
+ /// Whether the font is monospace, this is more important than any of
+ /// the other fields unless we're looking for a specific codepoint,
+ /// in which case that is the most important thing.
+ monospace: bool = false,
+ /// If we're looking for a codepoint, whether this font has it.
+ codepoint: bool = false,
+
+ pub fn int(self: Score) Backing {
+ return @bitCast(self);
+ }
+
+ fn score(desc: *const Descriptor, ct_desc: *const macos.text.FontDescriptor) Score {
+ var self: Score = .{};
+
+ // We always load the font if we can since some things can only be
+ // inspected on the font itself. Fonts that can't be loaded score
+ // 0 automatically because we don't want a font we can't load.
+ const font: *macos.text.Font = macos.text.Font.createWithFontDescriptor(
+ ct_desc,
+ 12,
+ ) catch return self;
+ defer font.release();
+
+ // We prefer fonts with more glyphs, all else being equal.
+ {
+ const Type = @TypeOf(self.glyph_count);
+ self.glyph_count = std.math.cast(
+ Type,
+ font.getGlyphCount(),
+ ) orelse std.math.maxInt(Type);
+ }
+
+ // If we're searching for a codepoint, then we
+ // prioritize fonts that have that codepoint.
+ if (desc.codepoint > 0) {
+ // Turn UTF-32 into UTF-16 for CT API
+ var unichars: [2]u16 = undefined;
+ const pair = macos.foundation.stringGetSurrogatePairForLongCharacter(
+ desc.codepoint,
+ &unichars,
+ );
+ const len: usize = if (pair) 2 else 1;
+
+ // Get our glyphs
+ var glyphs = [2]macos.graphics.Glyph{ 0, 0 };
+ self.codepoint = font.getGlyphsForCharacters(
+ unichars[0..len],
+ glyphs[0..len],
+ );
+ }
+
+ // Get our symbolic traits for the descriptor so we can
+ // compare boolean attributes like bold, monospace, etc.
+ const symbolic_traits: macos.text.FontSymbolicTraits = traits: {
+ const traits = ct_desc.copyAttribute(.traits) orelse break :traits .{};
+ defer traits.release();
+
+ const key = macos.text.FontTraitKey.symbolic.key();
+ const symbolic = traits.getValue(macos.foundation.Number, key) orelse
+ break :traits .{};
+
+ break :traits macos.text.FontSymbolicTraits.init(symbolic);
+ };
+
+ self.monospace = symbolic_traits.monospace;
+
+ // We try to derived data from the font itself, which is generally
+ // more reliable than only using the symbolic traits for this.
+ const is_bold: bool, const is_italic: bool = derived: {
+ // We start with initial guesses based on the symbolic traits,
+ // but refine these with more information if we can get it.
+ var is_italic = symbolic_traits.italic;
+ var is_bold = symbolic_traits.bold;
+
+ // Read the 'head' table out of the font data if it's available.
+ if (head: {
+ const tag = macos.text.FontTableTag.init("head");
+ const data = font.copyTable(tag) orelse break :head null;
+ defer data.release();
+ const ptr = data.getPointer();
+ const len = data.getLength();
+ break :head opentype.Head.init(ptr[0..len]) catch |err| {
+ log.warn("error parsing head table: {}", .{err});
+ break :head null;
+ };
+ }) |head_| {
+ const head: opentype.Head = head_;
+ is_bold = is_bold or (head.macStyle & 1 == 1);
+ is_italic = is_italic or (head.macStyle & 2 == 2);
+ }
+
+ // Read the 'OS/2' table out of the font data if it's available.
+ if (os2: {
+ const tag = macos.text.FontTableTag.init("OS/2");
+ const data = font.copyTable(tag) orelse break :os2 null;
+ defer data.release();
+ const ptr = data.getPointer();
+ const len = data.getLength();
+ break :os2 opentype.OS2.init(ptr[0..len]) catch |err| {
+ log.warn("error parsing OS/2 table: {}", .{err});
+ break :os2 null;
+ };
+ }) |os2| {
+ is_bold = is_bold or os2.fsSelection.bold;
+ is_italic = is_italic or os2.fsSelection.italic;
+ }
+
+ // Check if we have variation axes in our descriptor, if we
+ // do then we can derive weight italic-ness or both from them.
+ if (font.copyAttribute(.variation_axes)) |axes| variations: {
+ defer axes.release();
+
+ // Copy the variation values for this instance of the font.
+ // if there are none then we just break out immediately.
+ const values: *macos.foundation.Dictionary =
+ font.copyAttribute(.variation) orelse break :variations;
+ defer values.release();
+
+ var buf: [1024]u8 = undefined;
+
+ // If we see the 'ital' value then we ignore 'slnt'.
+ var ital_seen = false;
+
+ const len = axes.getCount();
+ for (0..len) |i| {
+ const dict = axes.getValueAtIndex(macos.foundation.Dictionary, i);
+ const Key = macos.text.FontVariationAxisKey;
+ const cf_id = dict.getValue(Key.identifier.Value(), Key.identifier.key()).?;
+ const cf_name = dict.getValue(Key.name.Value(), Key.name.key()).?;
+ const cf_def = dict.getValue(Key.default_value.Value(), Key.default_value.key()).?;
+
+ const name_str = cf_name.cstring(&buf, .utf8) orelse "";
+
+ // Default value
+ var def: f64 = 0;
+ _ = cf_def.getValue(.double, &def);
+ // Value in this font
+ var val: f64 = def;
+ if (values.getValue(
+ macos.foundation.Number,
+ cf_id,
+ )) |cf_val| _ = cf_val.getValue(.double, &val);
+
+ if (std.mem.eql(u8, "wght", name_str)) {
+ // Somewhat subjective threshold, we consider fonts
+ // bold if they have a 'wght' set greater than 600.
+ is_bold = val > 600;
+ continue;
+ }
+ if (std.mem.eql(u8, "ital", name_str)) {
+ is_italic = val > 0.5;
+ ital_seen = true;
+ continue;
+ }
+ if (!ital_seen and std.mem.eql(u8, "slnt", name_str)) {
+ // Arbitrary threshold of anything more than a 5
+ // degree clockwise slant is considered italic.
+ is_italic = val <= -5.0;
+ continue;
+ }
+ }
+ }
+
+ break :derived .{ is_bold, is_italic };
+ };
+
+ self.bold = desc.bold == is_bold;
+ self.italic = desc.italic == is_italic;
+
+ // Get the style string from the font.
+ var style_str_buf: [128]u8 = undefined;
+ const style_str: []const u8 = style_str: {
+ const style = ct_desc.copyAttribute(.style_name) orelse
+ break :style_str "";
+ defer style.release();
+
+ break :style_str style.cstring(&style_str_buf, .utf8) orelse "";
+ };
+
+ // The first string in this slice will be used for the exact match,
+ // and for the fuzzy match, all matching substrings will increase
+ // the rank.
+ const desired_styles: []const [:0]const u8 = desired: {
+ if (desc.style) |s| break :desired &.{s};
+
+ // If we don't have an explicitly desired style name, we base
+ // it on the bold and italic properties, this isn't ideal since
+ // fonts may use style names other than these, but it helps in
+ // some edge cases.
+ if (desc.bold) {
+ if (desc.italic) break :desired &.{ "bold italic", "bold", "italic", "oblique" };
+ break :desired &.{ "bold", "upright" };
+ } else if (desc.italic) {
+ break :desired &.{ "italic", "regular", "oblique" };
+ }
+ break :desired &.{ "regular", "upright" };
+ };
+
+ self.exact_style = std.ascii.eqlIgnoreCase(
+ style_str,
+ desired_styles[0],
+ );
+ // Our "fuzzy match" score is 0 if the desired style isn't present
+ // in the string, otherwise we give higher priority for styles that
+ // have fewer characters not in the desired_styles list.
+ const fuzzy_type = @TypeOf(self.fuzzy_style);
+ self.fuzzy_style = @intCast(style_str.len);
+ for (desired_styles) |s| {
+ if (std.ascii.indexOfIgnoreCase(style_str, s) != null) {
+ self.fuzzy_style -|= @intCast(s.len);
+ }
+ }
+ self.fuzzy_style = std.math.maxInt(fuzzy_type) -| self.fuzzy_style;
+
+ return self;
+ }
+ };
+
+ pub const DiscoverIterator = struct {
+ alloc: Allocator,
+ list: []const *macos.text.FontDescriptor,
+ variations: []const Variation,
+ i: usize,
+
+ pub fn deinit(self: *DiscoverIterator) void {
+ for (self.list) |desc| {
+ desc.release();
+ }
+ self.alloc.free(self.list);
+ self.* = undefined;
+ }
+
+ pub fn next(self: *DiscoverIterator) !?DeferredFace {
+ if (self.i >= self.list.len) return null;
+
+ // Get our descriptor. We need to remove the character set
+ // limitation because we may have used that to filter but we
+ // don't want it anymore because it'll restrict the characters
+ // available.
+ const desc = desc: {
+ // We create a copy, overwriting the character set attribute.
+ const attrs = try macos.foundation.MutableDictionary.create(0);
+ defer attrs.release();
+
+ attrs.setValue(
+ macos.text.FontAttribute.character_set.key(),
+ macos.c.kCFNull,
+ );
+
+ break :desc try macos.text.FontDescriptor.createCopyWithAttributes(
+ self.list[self.i],
+ @ptrCast(attrs),
+ );
+ };
+ defer desc.release();
+
+ // Create our font. We need a size to initialize it so we use size
+ // 12 but we will alter the size later.
+ const font = try macos.text.Font.createWithFontDescriptor(desc, 12);
+ errdefer font.release();
+
+ // Increment after we return
+ defer self.i += 1;
+
+ return DeferredFace{
+ .ct = .{
+ .font = font,
+ .variations = self.variations,
+ },
+ };
+ }
+ };
+};
+
+test "descriptor hash" {
+ const testing = std.testing;
+
+ var d: Descriptor = .{};
+ try testing.expect(d.hashcode() != 0);
+}
+
+test "descriptor hash family names" {
+ const testing = std.testing;
+
+ var d1: Descriptor = .{ .family = "A" };
+ var d2: Descriptor = .{ .family = "B" };
+ try testing.expect(d1.hashcode() != d2.hashcode());
+}
+
+test "fontconfig" {
+ if (options.backend != .fontconfig_freetype) return error.SkipZigTest;
+
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var fc = Fontconfig.init();
+ defer fc.deinit();
+ var it = try fc.discover(alloc, .{ .family = "monospace", .size = 12 });
+ defer it.deinit();
+}
+
+test "fontconfig codepoint" {
+ if (options.backend != .fontconfig_freetype) return error.SkipZigTest;
+
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var fc = Fontconfig.init();
+ defer fc.deinit();
+ var it = try fc.discover(alloc, .{ .codepoint = 'A', .size = 12 });
+ defer it.deinit();
+
+ // The first result should have the codepoint. Later ones may not
+ // because fontconfig returns all fonts sorted.
+ var face = (try it.next()).?;
+ defer face.deinit();
+ try testing.expect(face.hasCodepoint('A', null));
+
+ // Should have other codepoints too
+ try testing.expect(face.hasCodepoint('B', null));
+}
+
+test "coretext" {
+ if (options.backend != .coretext and options.backend != .coretext_freetype)
+ return error.SkipZigTest;
+
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var ct = CoreText.init();
+ defer ct.deinit();
+ var it = try ct.discover(alloc, .{ .family = "Monaco", .size = 12 });
+ defer it.deinit();
+ var count: usize = 0;
+ while (try it.next()) |_| {
+ count += 1;
+ }
+ try testing.expect(count > 0);
+}
+
+test "coretext codepoint" {
+ if (options.backend != .coretext and options.backend != .coretext_freetype)
+ return error.SkipZigTest;
+
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var ct = CoreText.init();
+ defer ct.deinit();
+ var it = try ct.discover(alloc, .{ .codepoint = 'A', .size = 12 });
+ defer it.deinit();
+
+ // The first result should have the codepoint. Later ones may not
+ // because fontconfig returns all fonts sorted.
+ const face = (try it.next()).?;
+ try testing.expect(face.hasCodepoint('A', null));
+
+ // Should have other codepoints too
+ try testing.expect(face.hasCodepoint('B', null));
+}
+
+test "coretext sorting" {
+ if (options.backend != .coretext and options.backend != .coretext_freetype)
+ return error.SkipZigTest;
+
+ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!//
+ // FIXME: Disabled for now because SF Pro is not available in CI
+ // The solution likely involves directly testing that the
+ // `sortMatchingDescriptors` function sorts a bundled test
+ // font correctly, instead of relying on the system fonts.
+ if (true) return error.SkipZigTest;
+ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!//
+
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var ct = CoreText.init();
+ defer ct.deinit();
+
+ // We try to get a Regular, Italic, Bold, & Bold Italic version of SF Pro,
+ // which should be installed on all Macs, and has many styles which makes
+ // it a good test, since there will be many results for each discovery.
+
+ // Regular
+ {
+ var it = try ct.discover(alloc, .{
+ .family = "SF Pro",
+ .size = 12,
+ });
+ defer it.deinit();
+ const res = (try it.next()).?;
+ var buf: [1024]u8 = undefined;
+ const name = try res.name(&buf);
+ try testing.expectEqualStrings("SF Pro Regular", name);
+ }
+
+ // Regular Italic
+ //
+ // NOTE: This makes sure that we don't accidentally prefer "Thin Italic",
+ // which we previously did, because it has a shorter name.
+ {
+ var it = try ct.discover(alloc, .{
+ .family = "SF Pro",
+ .size = 12,
+ .italic = true,
+ });
+ defer it.deinit();
+ const res = (try it.next()).?;
+ var buf: [1024]u8 = undefined;
+ const name = try res.name(&buf);
+ try testing.expectEqualStrings("SF Pro Regular Italic", name);
+ }
+
+ // Bold
+ {
+ var it = try ct.discover(alloc, .{
+ .family = "SF Pro",
+ .size = 12,
+ .bold = true,
+ });
+ defer it.deinit();
+ const res = (try it.next()).?;
+ var buf: [1024]u8 = undefined;
+ const name = try res.name(&buf);
+ try testing.expectEqualStrings("SF Pro Bold", name);
+ }
+
+ // Bold Italic
+ {
+ var it = try ct.discover(alloc, .{
+ .family = "SF Pro",
+ .size = 12,
+ .bold = true,
+ .italic = true,
+ });
+ defer it.deinit();
+ const res = (try it.next()).?;
+ var buf: [1024]u8 = undefined;
+ const name = try res.name(&buf);
+ try testing.expectEqualStrings("SF Pro Bold Italic", name);
+ }
+}
diff --git a/vendor/libghostty-vt/src/font/embedded.zig b/vendor/libghostty-vt/src/font/embedded.zig
new file mode 100644
index 00000000..8e0ae33b
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/embedded.zig
@@ -0,0 +1,55 @@
+//! Fonts that can be embedded with Ghostty. Note they are only actually
+//! embedded in the binary if they are referenced by the code, so fonts
+//! used for tests will not result in the final binary being larger.
+//!
+//! Be careful to ensure that any fonts you embed are licensed for
+//! redistribution and include their license as necessary.
+
+/// Default fonts that we prefer for Ghostty.
+pub const variable = @embedFile("jetbrains_mono_variable");
+pub const variable_italic = @embedFile("jetbrains_mono_variable_italic");
+
+/// Symbols-only nerd font.
+pub const symbols_nerd_font = @embedFile("nerd_fonts_symbols_only");
+
+/// Static jetbrains mono faces, currently unused.
+pub const regular = @embedFile("jetbrains_mono_regular");
+pub const bold = @embedFile("jetbrains_mono_bold");
+pub const italic = @embedFile("jetbrains_mono_italic");
+pub const bold_italic = @embedFile("jetbrains_mono_bold_italic");
+
+/// Emoji fonts
+pub const emoji = @embedFile("res/NotoColorEmoji.ttf");
+pub const emoji_text = @embedFile("res/NotoEmoji-Regular.ttf");
+
+// Fonts below are ONLY used for testing.
+
+/// Fonts with general properties
+pub const arabic = @embedFile("res/KawkabMono-Regular.ttf");
+
+/// A font for testing which is patched with nerd font symbols.
+pub const test_nerd_font = @embedFile("res/JetBrainsMonoNerdFont-Regular.ttf");
+
+/// Specific font families below:
+pub const code_new_roman = @embedFile("res/CodeNewRoman-Regular.otf");
+pub const inconsolata = @embedFile("res/Inconsolata-Regular.ttf");
+pub const geist_mono = @embedFile("res/GeistMono-Regular.ttf");
+pub const jetbrains_mono = @embedFile("res/JetBrainsMonoNoNF-Regular.ttf");
+pub const julia_mono = @embedFile("res/JuliaMono-Regular.ttf");
+
+/// Cozette is a unique font because it embeds some emoji characters
+/// but has a text presentation.
+pub const cozette = @embedFile("res/CozetteVector.ttf");
+
+/// Monaspace has weird ligature behaviors we want to test in our shapers
+/// so we embed it here.
+pub const monaspace_neon = @embedFile("res/MonaspaceNeon-Regular.otf");
+
+/// Terminus TTF is a scalable font with bitmap glyphs at various sizes.
+pub const terminus_ttf = @embedFile("res/TerminusTTF-Regular.ttf");
+
+/// Spleen is a monospaced bitmap font available in multiple formats.
+/// Used for testing bitmap font support across different file formats.
+pub const spleen_bdf = @embedFile("res/spleen-8x16.bdf");
+pub const spleen_pcf = @embedFile("res/spleen-8x16.pcf");
+pub const spleen_otb = @embedFile("res/spleen-8x16.otb");
diff --git a/vendor/libghostty-vt/src/font/face.zig b/vendor/libghostty-vt/src/font/face.zig
new file mode 100644
index 00000000..a1312c45
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/face.zig
@@ -0,0 +1,707 @@
+const std = @import("std");
+const builtin = @import("builtin");
+const build_config = @import("../build_config.zig");
+const options = @import("main.zig").options;
+const Metrics = @import("main.zig").Metrics;
+const config = @import("../config.zig");
+const freetype = @import("face/freetype.zig");
+const coretext = @import("face/coretext.zig");
+pub const web_canvas = @import("face/web_canvas.zig");
+
+/// Face implementation for the compile options.
+pub const Face = switch (options.backend) {
+ .freetype,
+ .fontconfig_freetype,
+ .coretext_freetype,
+ => freetype.Face,
+
+ .coretext,
+ .coretext_harfbuzz,
+ .coretext_noshape,
+ => coretext.Face,
+
+ .web_canvas => web_canvas.Face,
+};
+
+/// If a DPI can't be calculated, this DPI is used. This is probably
+/// wrong on modern devices so it is highly recommended you get the DPI
+/// using whatever platform method you can.
+pub const default_dpi = if (builtin.os.tag == .macos) 72 else 96;
+
+/// These are the flags to customize how freetype loads fonts. This is
+/// only non-void if the freetype backend is enabled.
+pub const FreetypeLoadFlags = if (options.backend.hasFreetype())
+ config.FreetypeLoadFlags
+else
+ void;
+pub const freetype_load_flags_default: FreetypeLoadFlags = if (FreetypeLoadFlags != void) .{} else {};
+
+/// Options for initializing a font face.
+pub const Options = struct {
+ size: DesiredSize,
+ freetype_load_flags: FreetypeLoadFlags = freetype_load_flags_default,
+};
+
+/// The desired size for loading a font.
+pub const DesiredSize = struct {
+ // Desired size in points
+ points: f32,
+
+ // The DPI of the screen so we can convert points to pixels.
+ xdpi: u16 = default_dpi,
+ ydpi: u16 = default_dpi,
+
+ // Converts points to pixels
+ pub fn pixels(self: DesiredSize) f32 {
+ // 1 point = 1/72 inch
+ return (self.points * @as(f32, @floatFromInt(self.ydpi))) / 72;
+ }
+
+ /// Make this a valid gobject if we're in a GTK environment.
+ pub const getGObjectType = switch (build_config.app_runtime) {
+ .gtk => @import("gobject").ext.defineBoxed(
+ DesiredSize,
+ .{ .name = "GhosttyFontDesiredSize" },
+ ),
+
+ .none => void,
+ };
+};
+
+/// A font variation setting. The best documentation for this I know of
+/// is actually the CSS font-variation-settings property on MDN:
+/// https://developer.mozilla.org/en-US/docs/Web/CSS/font-variation-settings
+pub const Variation = struct {
+ id: Id,
+ value: f64,
+
+ pub const Id = packed struct(u32) {
+ d: u8,
+ c: u8,
+ b: u8,
+ a: u8,
+
+ pub fn init(v: *const [4]u8) Id {
+ return .{ .a = v[0], .b = v[1], .c = v[2], .d = v[3] };
+ }
+
+ /// Converts the ID to a string. The return value is only valid
+ /// for the lifetime of the self pointer.
+ pub fn str(self: Id) [4]u8 {
+ return .{ self.a, self.b, self.c, self.d };
+ }
+ };
+};
+
+/// The size and position of a glyph.
+pub const GlyphSize = struct {
+ width: f64,
+ height: f64,
+ x: f64,
+ y: f64,
+};
+
+/// Additional options for rendering glyphs.
+pub const RenderOptions = struct {
+ /// The metrics that are defining the grid layout. These are usually
+ /// the metrics of the primary font face. The grid metrics are used
+ /// by the font face to better layout the glyph in situations where
+ /// the font is not exactly the same size as the grid.
+ grid_metrics: Metrics,
+
+ /// The number of grid cells this glyph will take up. This can be used
+ /// optionally by the rasterizer to better layout the glyph.
+ cell_width: ?u2 = null,
+
+ /// Constraint and alignment properties for the glyph. The rasterizer
+ /// should call the `constrain` function on this with the original size
+ /// and bearings of the glyph to get remapped values that the glyph
+ /// should be scaled/moved to.
+ constraint: Constraint = .none,
+
+ /// The number of cells, horizontally that the glyph is free to take up
+ /// when resized and aligned by `constraint`. This is usually 1, but if
+ /// there's whitespace to the right of the cell then it can be 2.
+ constraint_width: u2 = 1,
+
+ /// Thicken the glyph. This draws the glyph with a thicker stroke width.
+ /// This is purely an aesthetic setting.
+ ///
+ /// This only works with CoreText currently.
+ thicken: bool = false,
+
+ /// "Strength" of the thickening, between `0` and `255`.
+ /// Only has an effect when `thicken` is enabled.
+ ///
+ /// `0` does not correspond to *no* thickening,
+ /// just the *lightest* thickening available.
+ ///
+ /// CoreText only.
+ thicken_strength: u8 = 255,
+
+ /// See the `constraint` field.
+ pub const Constraint = struct {
+ /// Don't constrain the glyph in any way.
+ pub const none: Constraint = .{};
+
+ /// Sizing rule.
+ size: Size = .none,
+
+ /// Vertical alignment rule.
+ align_vertical: Align = .none,
+ /// Horizontal alignment rule.
+ align_horizontal: Align = .none,
+
+ /// Top padding when resizing.
+ pad_top: f64 = 0.0,
+ /// Left padding when resizing.
+ pad_left: f64 = 0.0,
+ /// Right padding when resizing.
+ pad_right: f64 = 0.0,
+ /// Bottom padding when resizing.
+ pad_bottom: f64 = 0.0,
+
+ // Size and bearings of the glyph relative
+ // to the bounding box of its scale group.
+ relative_width: f64 = 1.0,
+ relative_height: f64 = 1.0,
+ relative_x: f64 = 0.0,
+ relative_y: f64 = 0.0,
+
+ /// Maximum aspect ratio (width/height) to allow when stretching.
+ max_xy_ratio: ?f64 = null,
+
+ /// Maximum number of cells horizontally to use.
+ max_constraint_width: u2 = 2,
+
+ /// What to use as the height metric when constraining the glyph and
+ /// the constraint width is 1,
+ height: Height = .cell,
+
+ pub const Size = enum {
+ /// Don't change the size of this glyph.
+ none,
+ /// Scale the glyph down if needed to fit within the bounds,
+ /// preserving aspect ratio.
+ fit,
+ /// Scale the glyph up or down to exactly match the bounds,
+ /// preserving aspect ratio.
+ cover,
+ /// Scale the glyph down if needed to fit within the bounds,
+ /// preserving aspect ratio. If the glyph doesn't cover a
+ /// single cell, scale up. If the glyph exceeds a single
+ /// cell but is within the bounds, do nothing.
+ /// (Nerd Font specific rule.)
+ fit_cover1,
+ /// Stretch the glyph to exactly fit the bounds in both
+ /// directions, disregarding aspect ratio.
+ stretch,
+ };
+
+ pub const Align = enum {
+ /// Don't move the glyph on this axis.
+ none,
+ /// Move the glyph so that its leading (bottom/left)
+ /// edge aligns with the leading edge of the axis.
+ start,
+ /// Move the glyph so that its trailing (top/right)
+ /// edge aligns with the trailing edge of the axis.
+ end,
+ /// Move the glyph so that it is centered on this axis.
+ center,
+ /// Move the glyph so that it is centered on this axis,
+ /// but always with respect to the first cell even for
+ /// multi-cell constraints. (Nerd Font specific rule.)
+ center1,
+ };
+
+ pub const Height = enum {
+ /// Use the full line height of the primary face for
+ /// constraining this glyph.
+ cell,
+ /// Use the icon height from the grid metrics for
+ /// constraining this glyph. Unlike `cell`, the value of
+ /// this height depends on both the constraint width and the
+ /// affected by the `adjust-icon-height` config option.
+ icon,
+ };
+
+ /// Returns true if the constraint does anything. If it doesn't,
+ /// because it neither sizes nor positions the glyph, then this
+ /// returns false.
+ pub inline fn doesAnything(self: Constraint) bool {
+ return self.size != .none or
+ self.align_horizontal != .none or
+ self.align_vertical != .none;
+ }
+
+ /// Apply this constraint to the provided glyph
+ /// size, given the available width and height.
+ pub fn constrain(
+ self: Constraint,
+ glyph: GlyphSize,
+ metrics: Metrics,
+ /// Number of cells horizontally available for this glyph.
+ constraint_width: u2,
+ ) GlyphSize {
+ if (!self.doesAnything()) return glyph;
+
+ switch (self.size) {
+ .stretch => {
+ // Stretched glyphs are usually meant to align across cell
+ // boundaries, which works best if they're scaled and
+ // aligned to the grid rather than the face. This is most
+ // easily done by inserting this little fib in the metrics.
+ var m = metrics;
+ m.face_width = @floatFromInt(m.cell_width);
+ m.face_height = @floatFromInt(m.cell_height);
+ m.face_y = 0.0;
+
+ // Negative padding for stretched glyphs is a band-aid to
+ // avoid gaps due to pixel rounding, but at the cost of
+ // unsightly overlap artifacts. Since we scale and align to
+ // the grid rather than the face, we don't need it.
+ var c = self;
+ c.pad_bottom = @max(0, c.pad_bottom);
+ c.pad_top = @max(0, c.pad_top);
+ c.pad_left = @max(0, c.pad_left);
+ c.pad_right = @max(0, c.pad_right);
+
+ return c.constrainInner(glyph, m, constraint_width);
+ },
+ else => return self.constrainInner(glyph, metrics, constraint_width),
+ }
+ }
+
+ fn constrainInner(
+ self: Constraint,
+ glyph: GlyphSize,
+ metrics: Metrics,
+ constraint_width: u2,
+ ) GlyphSize {
+ // For extra wide font faces, never stretch glyphs across two cells.
+ // This mirrors font_patcher.
+ const min_constraint_width: u2 = if ((self.size == .stretch) and (metrics.face_width > 0.9 * metrics.face_height))
+ 1
+ else
+ @min(self.max_constraint_width, constraint_width);
+
+ // The bounding box for the glyph's scale group.
+ // Scaling and alignment rules are calculated for
+ // this box and then applied to the glyph.
+ var group: GlyphSize = group: {
+ const group_width = glyph.width / self.relative_width;
+ const group_height = glyph.height / self.relative_height;
+ break :group .{
+ .width = group_width,
+ .height = group_height,
+ .x = glyph.x - (group_width * self.relative_x),
+ .y = glyph.y - (group_height * self.relative_y),
+ };
+ };
+
+ // Apply prescribed scaling, preserving the
+ // center bearings of the group bounding box
+ const width_factor, const height_factor = self.scale_factors(group, metrics, min_constraint_width);
+ const center_x = group.x + (group.width / 2);
+ const center_y = group.y + (group.height / 2);
+ group.width *= width_factor;
+ group.height *= height_factor;
+ group.x = center_x - (group.width / 2);
+ group.y = center_y - (group.height / 2);
+
+ // NOTE: font_patcher jumps through a lot of hoops at this
+ // point to ensure that the glyph remains within the target
+ // bounding box after rounding to font definition units.
+ // This is irrelevant here as we're not rounding, we're
+ // staying in f64 and heading straight to rendering.
+
+ // Apply prescribed alignment
+ group.y = self.aligned_y(group, metrics);
+ group.x = self.aligned_x(group, metrics, min_constraint_width);
+
+ // Transfer the scaling and alignment back to the glyph and return.
+ return .{
+ .width = width_factor * glyph.width,
+ .height = height_factor * glyph.height,
+ .x = group.x + (group.width * self.relative_x),
+ .y = group.y + (group.height * self.relative_y),
+ };
+ }
+
+ /// Return width and height scaling factors for this scaling group.
+ fn scale_factors(
+ self: Constraint,
+ group: GlyphSize,
+ metrics: Metrics,
+ min_constraint_width: u2,
+ ) struct { f64, f64 } {
+ if (self.size == .none) {
+ return .{ 1.0, 1.0 };
+ }
+
+ const multi_cell = (min_constraint_width > 1);
+
+ const pad_width_factor = @as(f64, @floatFromInt(min_constraint_width)) - (self.pad_left + self.pad_right);
+ const pad_height_factor = 1 - (self.pad_bottom + self.pad_top);
+
+ const target_width = pad_width_factor * metrics.face_width;
+ const target_height = pad_height_factor * switch (self.height) {
+ .cell => metrics.face_height,
+ // Like font-patcher, the icon constraint height depends on the
+ // constraint width. Unlike font-patcher, the multi-cell
+ // icon_height may be different from face_height due to the
+ // `adjust-icon-height` config option.
+ .icon => if (multi_cell)
+ metrics.icon_height
+ else
+ metrics.icon_height_single,
+ };
+
+ var width_factor = target_width / group.width;
+ var height_factor = target_height / group.height;
+
+ switch (self.size) {
+ .none => unreachable,
+ .fit => {
+ // Scale down to fit if needed
+ height_factor = @min(1, width_factor, height_factor);
+ width_factor = height_factor;
+ },
+ .cover => {
+ // Scale to cover
+ height_factor = @min(width_factor, height_factor);
+ width_factor = height_factor;
+ },
+ .fit_cover1 => {
+ // Scale down to fit or up to cover at least one cell
+ // NOTE: This is similar to font_patcher's "pa" mode,
+ // however, font_patcher will only do the upscaling
+ // part if the constraint width is 1, resulting in
+ // some icons becoming smaller when the constraint
+ // width increases. You'd see icons shrinking when
+ // opening up a space after them. This makes no
+ // sense, so we've fixed the rule such that these
+ // icons are scaled to the same size for multi-cell
+ // constraints as they would be for single-cell.
+ height_factor = @min(width_factor, height_factor);
+ if (multi_cell and (height_factor > 1)) {
+ // Call back into this function with
+ // constraint width 1 to get single-cell scale
+ // factors. We use the height factor as width
+ // could have been modified by max_xy_ratio.
+ _, const single_height_factor = self.scale_factors(group, metrics, 1);
+ height_factor = @max(1, single_height_factor);
+ }
+ width_factor = height_factor;
+ },
+ .stretch => {},
+ }
+
+ // Reduce aspect ratio if required
+ if (self.max_xy_ratio) |ratio| {
+ if (group.width * width_factor > group.height * height_factor * ratio) {
+ width_factor = group.height * height_factor * ratio / group.width;
+ }
+ }
+
+ return .{ width_factor, height_factor };
+ }
+
+ /// Return vertical bearing for aligning this group
+ fn aligned_y(
+ self: Constraint,
+ group: GlyphSize,
+ metrics: Metrics,
+ ) f64 {
+ if ((self.size == .none) and (self.align_vertical == .none)) {
+ // If we don't have any constraints affecting the vertical axis,
+ // we don't touch vertical alignment.
+ return group.y;
+ }
+ // We use face_height and offset by face_y, rather than
+ // using cell_height directly, to account for the asymmetry
+ // of the pixel cell around the face (a consequence of
+ // aligning the baseline with a pixel boundary rather than
+ // vertically centering the face).
+ const pad_bottom_dy = self.pad_bottom * metrics.face_height;
+ const pad_top_dy = self.pad_top * metrics.face_height;
+ const start_y = metrics.face_y + pad_bottom_dy;
+ const end_y = metrics.face_y + (metrics.face_height - group.height - pad_top_dy);
+ const center_y = (start_y + end_y) / 2;
+ return switch (self.align_vertical) {
+ // NOTE: Even if there is no prescribed alignment, we ensure
+ // that the group doesn't protrude outside the padded cell,
+ // since this is implied by every available size constraint. If
+ // the group is too high we fall back to centering, though if we
+ // hit the .none prong we always have self.size != .none, so
+ // this should never happen.
+ .none => if (end_y < start_y)
+ center_y
+ else
+ @max(start_y, @min(group.y, end_y)),
+ .start => start_y,
+ .end => end_y,
+ .center, .center1 => center_y,
+ };
+ }
+
+ /// Return horizontal bearing for aligning this group
+ fn aligned_x(
+ self: Constraint,
+ group: GlyphSize,
+ metrics: Metrics,
+ min_constraint_width: u2,
+ ) f64 {
+ if ((self.size == .none) and (self.align_horizontal == .none)) {
+ // If we don't have any constraints affecting the horizontal
+ // axis, we don't touch horizontal alignment.
+ return group.x;
+ }
+ // For multi-cell constraints, we align relative to the span
+ // from the left edge of the first cell to the right edge of
+ // the last face cell assuming it's left-aligned within the
+ // rounded and adjusted pixel cell. Any horizontal offset to
+ // center the face within the grid cell is the responsibility
+ // of the backend-specific rendering code, and should be done
+ // after applying constraints.
+ const full_face_span = metrics.face_width + @as(f64, @floatFromInt((min_constraint_width - 1) * metrics.cell_width));
+ const pad_left_dx = self.pad_left * metrics.face_width;
+ const pad_right_dx = self.pad_right * metrics.face_width;
+ const start_x = pad_left_dx;
+ const end_x = full_face_span - group.width - pad_right_dx;
+ return switch (self.align_horizontal) {
+ // NOTE: Even if there is no prescribed alignment, we ensure
+ // that the glyph doesn't protrude outside the padded cell,
+ // since this is implied by every available size constraint. The
+ // left-side bound has priority if the group is too wide, though
+ // if we hit the .none prong we always have self.size != .none,
+ // so this should never happen.
+ .none => @max(start_x, @min(group.x, end_x)),
+ .start => start_x,
+ .end => @max(start_x, end_x),
+ .center => @max(start_x, (start_x + end_x) / 2),
+ // NOTE: .center1 implements the font_patcher rule of centering
+ // in the first cell even for multi-cell constraints. Since glyphs
+ // are not allowed to protrude to the left, this results in the
+ // left-alignment like .start when the glyph is wider than a cell.
+ .center1 => center1: {
+ const end1_x = metrics.face_width - group.width - pad_right_dx;
+ break :center1 @max(start_x, (start_x + end1_x) / 2);
+ },
+ };
+ }
+ };
+};
+
+test {
+ @import("std").testing.refAllDecls(@This());
+}
+
+test "Variation.Id: wght should be 2003265652" {
+ const testing = std.testing;
+ const id = Variation.Id.init("wght");
+ try testing.expectEqual(@as(u32, 2003265652), @as(u32, @bitCast(id)));
+ try testing.expectEqualStrings("wght", &(id.str()));
+}
+
+test "Variation.Id: slnt should be 1936486004" {
+ const testing = std.testing;
+ const id: Variation.Id = .{ .a = 's', .b = 'l', .c = 'n', .d = 't' };
+ try testing.expectEqual(@as(u32, 1936486004), @as(u32, @bitCast(id)));
+ try testing.expectEqualStrings("slnt", &(id.str()));
+}
+
+test "Constraints" {
+ const comparison = @import("../datastruct/comparison.zig");
+ const getConstraint = @import("nerd_font_attributes.zig").getConstraint;
+
+ // Hardcoded data matches metrics from CoreText at size 12 and DPI 96.
+
+ // Define grid metrics (matches font-family = JetBrains Mono)
+ const metrics: Metrics = .{
+ .cell_width = 10,
+ .cell_height = 22,
+ .cell_baseline = 5,
+ .underline_position = 19,
+ .underline_thickness = 1,
+ .strikethrough_position = 12,
+ .strikethrough_thickness = 1,
+ .overline_position = 0,
+ .overline_thickness = 1,
+ .box_thickness = 1,
+ .cursor_thickness = 1,
+ .cursor_height = 22,
+ .icon_height = 21.12,
+ .icon_height_single = 44.48 / 3.0,
+ .face_width = 9.6,
+ .face_height = 21.12,
+ .face_y = 0.2,
+ };
+
+ // ASCII (no constraint).
+ {
+ const constraint: RenderOptions.Constraint = .none;
+
+ // BBox of 'x' from JetBrains Mono.
+ const glyph_x: GlyphSize = .{
+ .width = 6.784,
+ .height = 15.28,
+ .x = 1.408,
+ .y = 4.84,
+ };
+
+ // Any constraint width: do nothing.
+ inline for (.{ 1, 2 }) |constraint_width| {
+ try comparison.expectApproxEqual(
+ glyph_x,
+ constraint.constrain(glyph_x, metrics, constraint_width),
+ );
+ }
+ }
+
+ // Symbol (same constraint as hardcoded in Renderer.addGlyph).
+ {
+ const constraint: RenderOptions.Constraint = .{ .size = .fit };
+
+ // BBox of '■' (0x25A0 black square) from Iosevka.
+ // NOTE: This glyph is designed to span two cells.
+ const glyph_25A0: GlyphSize = .{
+ .width = 10.272,
+ .height = 10.272,
+ .x = 2.864,
+ .y = 5.304,
+ };
+
+ // Constraint width 1: scale down and shift to fit a single cell.
+ try comparison.expectApproxEqual(
+ GlyphSize{
+ .width = metrics.face_width,
+ .height = metrics.face_width,
+ .x = 0,
+ .y = 5.64,
+ },
+ constraint.constrain(glyph_25A0, metrics, 1),
+ );
+
+ // Constraint width 2: do nothing.
+ try comparison.expectApproxEqual(
+ glyph_25A0,
+ constraint.constrain(glyph_25A0, metrics, 2),
+ );
+ }
+
+ // Emoji (same constraint as hardcoded in SharedGrid.renderGlyph).
+ {
+ const constraint: RenderOptions.Constraint = .{
+ .size = .cover,
+ .align_horizontal = .center,
+ .align_vertical = .center,
+ .pad_left = 0.025,
+ .pad_right = 0.025,
+ };
+
+ // BBox of '🥸' (0x1F978) from Apple Color Emoji.
+ const glyph_1F978: GlyphSize = .{
+ .width = 20,
+ .height = 20,
+ .x = 0.46,
+ .y = 1,
+ };
+
+ // Constraint width 2: scale to cover two cells with padding, center;
+ try comparison.expectApproxEqual(
+ GlyphSize{
+ .width = 18.72,
+ .height = 18.72,
+ .x = 0.44,
+ .y = 1.4,
+ },
+ constraint.constrain(glyph_1F978, metrics, 2),
+ );
+ }
+
+ // Nerd Font default.
+ {
+ const constraint = getConstraint(0xea61).?;
+
+ // Verify that this is the constraint we expect.
+ try std.testing.expectEqual(.fit_cover1, constraint.size);
+ try std.testing.expectEqual(.icon, constraint.height);
+ try std.testing.expectEqual(.center1, constraint.align_horizontal);
+ try std.testing.expectEqual(.center1, constraint.align_vertical);
+
+ // BBox of '' (0xEA61 nf-cod-lightbulb) from Symbols Only.
+ // NOTE: This icon is part of a group, so the
+ // constraint applies to a larger bounding box.
+ const glyph_EA61: GlyphSize = .{
+ .width = 9.015625,
+ .height = 13.015625,
+ .x = 3.015625,
+ .y = 3.76525,
+ };
+
+ // Constraint width 1: scale and shift group to fit a single cell.
+ try comparison.expectApproxEqual(
+ GlyphSize{
+ .width = 7.2125,
+ .height = 10.4125,
+ .x = 0.8125,
+ .y = 5.950695224719102,
+ },
+ constraint.constrain(glyph_EA61, metrics, 1),
+ );
+
+ // Constraint width 2: no scaling; left-align and vertically center group.
+ try comparison.expectApproxEqual(
+ GlyphSize{
+ .width = glyph_EA61.width,
+ .height = glyph_EA61.height,
+ .x = 1.015625,
+ .y = 4.7483690308988775,
+ },
+ constraint.constrain(glyph_EA61, metrics, 2),
+ );
+ }
+
+ // Nerd Font stretch.
+ {
+ const constraint = getConstraint(0xe0c0).?;
+
+ // Verify that this is the constraint we expect.
+ try std.testing.expectEqual(.stretch, constraint.size);
+ try std.testing.expectEqual(.cell, constraint.height);
+ try std.testing.expectEqual(.start, constraint.align_horizontal);
+ try std.testing.expectEqual(.center1, constraint.align_vertical);
+
+ // BBox of ' ' (0xE0C0 nf-ple-flame_thick) from Symbols Only.
+ const glyph_E0C0: GlyphSize = .{
+ .width = 16.796875,
+ .height = 16.46875,
+ .x = -0.796875,
+ .y = 1.7109375,
+ };
+
+ // Constraint width 1: stretch and position to exactly cover one cell.
+ try comparison.expectApproxEqual(
+ GlyphSize{
+ .width = @floatFromInt(metrics.cell_width),
+ .height = @floatFromInt(metrics.cell_height),
+ .x = 0,
+ .y = 0,
+ },
+ constraint.constrain(glyph_E0C0, metrics, 1),
+ );
+
+ // Constraint width 1: stretch and position to exactly cover two cells.
+ try comparison.expectApproxEqual(
+ GlyphSize{
+ .width = @floatFromInt(2 * metrics.cell_width),
+ .height = @floatFromInt(metrics.cell_height),
+ .x = 0,
+ .y = 0,
+ },
+ constraint.constrain(glyph_E0C0, metrics, 2),
+ );
+ }
+}
diff --git a/vendor/libghostty-vt/src/font/face/coretext.zig b/vendor/libghostty-vt/src/font/face/coretext.zig
new file mode 100644
index 00000000..1d133388
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/face/coretext.zig
@@ -0,0 +1,1162 @@
+const std = @import("std");
+const builtin = @import("builtin");
+const assert = @import("../../quirks.zig").inlineAssert;
+const Allocator = std.mem.Allocator;
+const macos = @import("macos");
+const harfbuzz = @import("harfbuzz");
+const font = @import("../main.zig");
+const opentype = @import("../opentype.zig");
+const quirks = @import("../../quirks.zig");
+
+const log = std.log.scoped(.font_face);
+
+pub const Face = struct {
+ /// Our font face
+ font: *macos.text.Font,
+
+ /// Harfbuzz font corresponding to this face. We only use this
+ /// if we're using Harfbuzz.
+ hb_font: if (harfbuzz_shaper) harfbuzz.Font else void,
+
+ /// Set quirks.disableDefaultFontFeatures
+ quirks_disable_default_font_features: bool = false,
+
+ /// True if this font face should be rasterized with a synthetic bold
+ /// effect. This is used for fonts that don't have a bold variant.
+ synthetic_bold: ?f64 = null,
+
+ /// If the face can possibly be colored, then this is the state
+ /// used to check for color information. This is null if the font
+ /// can't possibly be colored (i.e. doesn't have SVG, sbix, etc
+ /// tables).
+ color: ?ColorState = null,
+
+ /// The current size this font is set to.
+ size: font.face.DesiredSize,
+
+ /// True if our build is using Harfbuzz. If we're not, we can avoid
+ /// some Harfbuzz-specific code paths.
+ const harfbuzz_shaper = font.options.backend.hasHarfbuzz();
+
+ /// The matrix applied to a regular font to auto-italicize it.
+ pub const italic_skew = macos.graphics.AffineTransform{
+ .a = 1,
+ .b = 0,
+ .c = 0.267949, // approx. tan(15)
+ .d = 1,
+ .tx = 0,
+ .ty = 0,
+ };
+
+ /// Initialize a CoreText-based font from a TTF/TTC in memory.
+ pub fn init(
+ lib: font.Library,
+ source: [:0]const u8,
+ opts: font.face.Options,
+ ) !Face {
+ _ = lib;
+
+ const data = try macos.foundation.Data.createWithBytesNoCopy(source);
+ defer data.release();
+
+ const desc = macos.text.createFontDescriptorFromData(data) orelse
+ return error.FontInitFailure;
+ defer desc.release();
+
+ const ct_font = try macos.text.Font.createWithFontDescriptor(desc, 12);
+ defer ct_font.release();
+
+ return try initFontCopy(ct_font, opts);
+ }
+
+ /// Initialize a CoreText-based face from another initialized font face
+ /// but with a new size. This is often how CoreText fonts are initialized
+ /// because the font is loaded at a default size during discovery, and then
+ /// adjusted to the final size for final load.
+ pub fn initFontCopy(base: *macos.text.Font, opts: font.face.Options) !Face {
+ // Create a copy. The copyWithAttributes docs say the size is in points,
+ // but we need to scale the points by the DPI and to do that we use our
+ // function called "pixels".
+ const ct_font = try base.copyWithAttributes(
+ opts.size.pixels(),
+ null,
+ null,
+ );
+ errdefer ct_font.release();
+
+ return try initFont(ct_font, opts);
+ }
+
+ /// Initialize a face with a CTFont. This will take ownership over
+ /// the CTFont. This does NOT copy or retain the CTFont.
+ pub fn initFont(ct_font: *macos.text.Font, opts: font.face.Options) !Face {
+ const traits = ct_font.getSymbolicTraits();
+
+ var hb_font = if (comptime harfbuzz_shaper) font: {
+ var hb_font = try harfbuzz.coretext.createFont(ct_font);
+ const pixels: opentype.sfnt.F26Dot6 = .from(opts.size.pixels());
+ hb_font.setScale(@bitCast(pixels), @bitCast(pixels));
+ break :font hb_font;
+ } else {};
+ errdefer if (comptime harfbuzz_shaper) hb_font.destroy();
+
+ const color: ?ColorState = if (traits.color_glyphs)
+ try .init(ct_font)
+ else
+ null;
+ errdefer if (color) |v| v.deinit();
+
+ var result: Face = .{
+ .font = ct_font,
+ .hb_font = hb_font,
+ .color = color,
+ .size = opts.size,
+ };
+ result.quirks_disable_default_font_features = quirks.disableDefaultFontFeatures(&result);
+
+ // In debug mode, we output information about available variation axes,
+ // if they exist.
+ if (comptime builtin.mode == .Debug) {
+ if (ct_font.copyAttribute(.variation_axes)) |axes| {
+ defer axes.release();
+
+ var buf: [1024]u8 = undefined;
+ log.debug("variation axes font={s}", .{try result.name(&buf)});
+
+ const len = axes.getCount();
+ for (0..len) |i| {
+ const dict = axes.getValueAtIndex(macos.foundation.Dictionary, i);
+ const Key = macos.text.FontVariationAxisKey;
+ const cf_name = dict.getValue(Key.name.Value(), Key.name.key()).?;
+ const cf_id = dict.getValue(Key.identifier.Value(), Key.identifier.key()).?;
+ const cf_min = dict.getValue(Key.minimum_value.Value(), Key.minimum_value.key()).?;
+ const cf_max = dict.getValue(Key.maximum_value.Value(), Key.maximum_value.key()).?;
+ const cf_def = dict.getValue(Key.default_value.Value(), Key.default_value.key()).?;
+
+ const namestr = cf_name.cstring(&buf, .utf8) orelse "";
+
+ var id_raw: c_int = 0;
+ _ = cf_id.getValue(.int, &id_raw);
+ const id: font.face.Variation.Id = @bitCast(id_raw);
+
+ var min: f64 = 0;
+ _ = cf_min.getValue(.double, &min);
+
+ var max: f64 = 0;
+ _ = cf_max.getValue(.double, &max);
+
+ var def: f64 = 0;
+ _ = cf_def.getValue(.double, &def);
+
+ log.debug("variation axis: name={s} id={s} min={} max={} def={}", .{
+ namestr,
+ id.str(),
+ min,
+ max,
+ def,
+ });
+ }
+ }
+ }
+
+ return result;
+ }
+
+ pub fn deinit(self: *Face) void {
+ self.font.release();
+ if (comptime harfbuzz_shaper) self.hb_font.destroy();
+ if (self.color) |v| v.deinit();
+ self.* = undefined;
+ }
+
+ /// Return a new face that is the same as this but has a transformation
+ /// matrix applied to italicize it.
+ pub fn syntheticItalic(self: *const Face, opts: font.face.Options) !Face {
+ const ct_font = try self.font.copyWithAttributes(0.0, &italic_skew, null);
+ errdefer ct_font.release();
+ return try initFont(ct_font, opts);
+ }
+
+ /// Return a new face that is the same as this but applies a synthetic
+ /// bold effect to it. This is useful for fonts that don't have a bold
+ /// variant.
+ pub fn syntheticBold(self: *const Face, opts: font.face.Options) !Face {
+ const ct_font = try self.font.copyWithAttributes(0.0, null, null);
+ errdefer ct_font.release();
+ var face = try initFont(ct_font, opts);
+
+ // To determine our synthetic bold line width we get a multiplier
+ // from the font size in points. This is a heuristic that is based
+ // on the fact that a line width of 1 looks good to me at a certain
+ // point size. We want to scale that up roughly linearly with the
+ // font size.
+ const points_f64: f64 = @floatCast(opts.size.points);
+ const line_width = @max(points_f64 / 14.0, 1);
+ // log.debug("synthetic bold line width={}", .{line_width});
+ face.synthetic_bold = line_width;
+
+ return face;
+ }
+
+ /// Returns the font name. If allocation is required, buf will be used,
+ /// but sometimes allocation isn't required and a static string is
+ /// returned.
+ pub fn name(self: *const Face, buf: []u8) Allocator.Error![]const u8 {
+ const family_name = self.font.copyFamilyName();
+ if (family_name.cstringPtr(.utf8)) |str| return str;
+
+ // "NULL if the internal storage of theString does not allow
+ // this to be returned efficiently." In this case, we need
+ // to allocate.
+ return family_name.cstring(buf, .utf8) orelse error.OutOfMemory;
+ }
+
+ /// Resize the font in-place. If this succeeds, the caller is responsible
+ /// for clearing any glyph caches, font atlas data, etc.
+ pub fn setSize(self: *Face, opts: font.face.Options) !void {
+ // We just create a copy and replace ourself
+ const face = try initFontCopy(self.font, opts);
+ self.deinit();
+ self.* = face;
+ }
+
+ /// Set the variation axes for this font. This will modify this font
+ /// in-place.
+ pub fn setVariations(
+ self: *Face,
+ vs: []const font.face.Variation,
+ opts: font.face.Options,
+ ) !void {
+ // If we have no variations, we don't need to do anything.
+ if (vs.len == 0) return;
+
+ // Create a new font descriptor with all the variations set.
+ var desc = self.font.copyDescriptor();
+ defer desc.release();
+ for (vs) |v| {
+ const id = try macos.foundation.Number.create(.int, @ptrCast(&v.id));
+ defer id.release();
+ const next = try desc.createCopyWithVariation(id, v.value);
+ desc.release();
+ desc = next;
+ }
+
+ // Put our current size in the opts so that we don't change size.
+ var new_opts = opts;
+ new_opts.size = self.size;
+
+ // Initialize a font based on these attributes.
+ const ct_font = try self.font.copyWithAttributes(0, null, desc);
+ errdefer ct_font.release();
+ const face = try initFont(ct_font, new_opts);
+ self.deinit();
+ self.* = face;
+ }
+
+ /// Returns true if the face has any glyphs that are colorized.
+ /// To determine if an individual glyph is colorized you must use
+ /// isColorGlyph.
+ pub fn hasColor(self: *const Face) bool {
+ return self.color != null;
+ }
+
+ /// Returns true if the given glyph ID is colorized.
+ pub fn isColorGlyph(self: *const Face, glyph_id: u32) bool {
+ const c = self.color orelse return false;
+ return c.isColorGlyph(glyph_id);
+ }
+
+ /// Returns the glyph index for the given Unicode code point. If this
+ /// face doesn't support this glyph, null is returned.
+ pub fn glyphIndex(self: Face, cp: u32) ?u32 {
+ // Turn UTF-32 into UTF-16 for CT API
+ var unichars: [2]u16 = undefined;
+ const pair = macos.foundation.stringGetSurrogatePairForLongCharacter(cp, &unichars);
+ const len: usize = if (pair) 2 else 1;
+
+ // Get our glyphs
+ var glyphs = [2]macos.graphics.Glyph{ 0, 0 };
+ if (!self.font.getGlyphsForCharacters(unichars[0..len], glyphs[0..len]))
+ return null;
+
+ // We can have pairs due to chars like emoji but we expect all of them
+ // to decode down into exactly one glyph ID.
+ if (pair) assert(glyphs[1] == 0);
+
+ return @intCast(glyphs[0]);
+ }
+
+ pub fn renderGlyph(
+ self: Face,
+ alloc: Allocator,
+ atlas: *font.Atlas,
+ glyph_index: u32,
+ opts: font.face.RenderOptions,
+ ) !font.Glyph {
+ var glyphs = [_]macos.graphics.Glyph{@intCast(glyph_index)};
+
+ // Get the bounding rect for rendering this glyph.
+ // This is in a coordinate space with (0.0, 0.0)
+ // in the bottom left and +Y pointing up.
+ var rect = self.font.getBoundingRectsForGlyphs(.horizontal, &glyphs, null);
+
+ // Determine whether this is a color glyph.
+ const is_color = self.isColorGlyph(glyph_index);
+ // And whether it's (probably) a bitmap (sbix).
+ const sbix = is_color and self.color != null and self.color.?.sbix;
+
+ // If we're rendering a synthetic bold then we will gain 50% of
+ // the line width on every edge, which means we should increase
+ // our width and height by the line width and subtract half from
+ // our origin points.
+ //
+ // We don't add extra size if it's a sbix color font though,
+ // since bitmaps aren't affected by synthetic bold.
+ if (!sbix) if (self.synthetic_bold) |line_width| {
+ rect.size.width += line_width;
+ rect.size.height += line_width;
+ rect.origin.x -= line_width / 2;
+ rect.origin.y -= line_width / 2;
+ };
+
+ // If our rect is smaller than a quarter pixel in either axis
+ // then it has no outlines or they're too small to render.
+ //
+ // In this case we just return 0-sized glyph struct.
+ if (rect.size.width < 0.25 or rect.size.height < 0.25)
+ return font.Glyph{
+ .width = 0,
+ .height = 0,
+ .offset_x = 0,
+ .offset_y = 0,
+ .atlas_x = 0,
+ .atlas_y = 0,
+ };
+
+ const metrics = opts.grid_metrics;
+ const cell_width: f64 = @floatFromInt(metrics.cell_width);
+ const cell_height: f64 = @floatFromInt(metrics.cell_height);
+
+ // Next we apply any constraints to get the final size of the glyph.
+ const constraint = opts.constraint;
+
+ // We need to add the baseline position before passing to the constrain
+ // function since it operates on cell-relative positions, not baseline.
+ const cell_baseline: f64 = @floatFromInt(metrics.cell_baseline);
+
+ const glyph_size = constraint.constrain(
+ .{
+ .width = rect.size.width,
+ .height = rect.size.height,
+ .x = rect.origin.x,
+ .y = rect.origin.y + cell_baseline,
+ },
+ metrics,
+ opts.constraint_width,
+ );
+
+ var x = glyph_size.x;
+ var y = glyph_size.y;
+ var width = glyph_size.width;
+ var height = glyph_size.height;
+
+ // We center all glyphs within the pixel-rounded and adjusted
+ // cell width if it's larger than the face width, so that they
+ // aren't weirdly off to the left.
+ //
+ // We don't do this if the glyph has a stretch constraint,
+ // since in that case the position was already calculated with the
+ // new cell width in mind.
+ if (constraint.size != .stretch) {
+ // We add half the difference to re-center.
+ const dx = (cell_width - metrics.face_width) / 2;
+ x += dx;
+ if (dx < 0) {
+ // For negative diff (cell narrower than advance), we remove the
+ // integer part and only keep the fractional adjustment needed
+ // for consistent subpixel positioning.
+ x -= @trunc(dx);
+ }
+ }
+
+ // If this is a bitmap glyph, it will always render as full pixels,
+ // not fractional pixels, so we need to quantize its position and
+ // size accordingly to align to full pixels so we get good results.
+ if (sbix) {
+ width = cell_width - @round(cell_width - width - x) - @round(x);
+ height = cell_height - @round(cell_height - height - y) - @round(y);
+ x = @round(x);
+ y = @round(y);
+ }
+
+ // We make an assumption that font smoothing ("thicken")
+ // adds no more than 1 extra pixel to any edge. We don't
+ // add extra size if it's a sbix color font though, since
+ // bitmaps aren't affected by smoothing.
+ const canvas_padding: u32 = if (opts.thicken and !sbix) 1 else 0;
+
+ // Our whole-pixel bearings for the final glyph.
+ // The fractional portion will be included in the rasterized position.
+ const px_x = @as(i32, @intFromFloat(@floor(x))) - @as(i32, @intCast(canvas_padding));
+ const px_y = @as(i32, @intFromFloat(@floor(y))) - @as(i32, @intCast(canvas_padding));
+
+ // We keep track of the fractional part of the pixel bearings, which
+ // we will add as an offset when rasterizing to make sure we get the
+ // correct sub-pixel position.
+ const frac_x = x - @floor(x);
+ const frac_y = y - @floor(y);
+
+ // Add the fractional pixel to the width and height and take
+ // the ceiling to get a canvas size that will definitely fit
+ // our drawn glyph, including the fractional offset and font smoothing.
+ const px_width = @as(u32, @intFromFloat(@ceil(width + frac_x))) + (2 * canvas_padding);
+ const px_height = @as(u32, @intFromFloat(@ceil(height + frac_y))) + (2 * canvas_padding);
+
+ // Settings that are specific to if we are rendering text or emoji.
+ const color: struct {
+ color: bool,
+ depth: u32,
+ space: *macos.graphics.ColorSpace,
+ context_opts: c_uint,
+ } = if (!is_color) .{
+ .color = false,
+ .depth = 1,
+ .space = try macos.graphics.ColorSpace.createNamed(.linearGray),
+ .context_opts = @intFromEnum(macos.graphics.ImageAlphaInfo.only),
+ } else .{
+ .color = true,
+ .depth = 4,
+ .space = try macos.graphics.ColorSpace.createNamed(.displayP3),
+ .context_opts = @intFromEnum(macos.graphics.BitmapInfo.byte_order_32_little) |
+ @intFromEnum(macos.graphics.ImageAlphaInfo.premultiplied_first),
+ };
+ defer color.space.release();
+
+ // This is just a safety check.
+ if (atlas.format.depth() != color.depth) {
+ log.warn("font atlas color depth doesn't equal font color depth atlas={} font={}", .{
+ atlas.format.depth(),
+ color.depth,
+ });
+ return error.InvalidAtlasFormat;
+ }
+
+ // Our buffer for rendering. We could cache this but glyph rasterization
+ // usually stabilizes pretty quickly and is very infrequent so I think
+ // the allocation overhead is acceptable compared to the cost of
+ // caching it forever or having to deal with a cache lifetime.
+ const buf = try alloc.alloc(u8, px_width * px_height * color.depth);
+ defer alloc.free(buf);
+ @memset(buf, 0);
+
+ const context = macos.graphics.BitmapContext.context;
+ const ctx = try macos.graphics.BitmapContext.create(
+ buf,
+ px_width,
+ px_height,
+ 8,
+ px_width * color.depth,
+ color.space,
+ color.context_opts,
+ );
+ defer context.release(ctx);
+
+ // Perform an initial fill. This ensures that we don't have any
+ // uninitialized pixels in the bitmap.
+ if (color.color)
+ context.setRGBFillColor(ctx, 0, 0, 0, 0)
+ else
+ context.setGrayFillColor(ctx, 0, 0);
+ context.fillRect(ctx, .{
+ .origin = .{ .x = 0, .y = 0 },
+ .size = .{
+ .width = @floatFromInt(px_width),
+ .height = @floatFromInt(px_height),
+ },
+ });
+
+ // "Font smoothing" is what we call "thickening", it's an attempt
+ // to compensate for optical thinning of fonts, but at this point
+ // it's just something that makes the text look closer to system
+ // applications if users want that.
+ context.setAllowsFontSmoothing(ctx, true);
+ context.setShouldSmoothFonts(ctx, opts.thicken);
+
+ // Subpixel positioning allows glyphs to be placed at non-integer
+ // coordinates. We need this for our alignment.
+ context.setAllowsFontSubpixelPositioning(ctx, true);
+ context.setShouldSubpixelPositionFonts(ctx, true);
+
+ // We don't want subpixel quantization, since we very carefully
+ // manage the position of our glyphs ourselves, and dont want to
+ // mess that up.
+ context.setAllowsFontSubpixelQuantization(ctx, false);
+ context.setShouldSubpixelQuantizeFonts(ctx, false);
+
+ // Anti-aliasing is self explanatory.
+ context.setAllowsAntialiasing(ctx, true);
+ context.setShouldAntialias(ctx, true);
+
+ // Set our color for drawing
+ if (color.color) {
+ context.setRGBFillColor(ctx, 1, 1, 1, 1);
+ context.setRGBStrokeColor(ctx, 1, 1, 1, 1);
+ } else {
+ const strength: f64 = @floatFromInt(opts.thicken_strength);
+ context.setGrayFillColor(ctx, strength / 255.0, 1);
+ context.setGrayStrokeColor(ctx, strength / 255.0, 1);
+ }
+
+ // If we are drawing with synthetic bold then use a fill stroke
+ // which strokes the outlines of the glyph making a more bold look.
+ if (self.synthetic_bold) |line_width| {
+ context.setTextDrawingMode(ctx, .fill_stroke);
+ context.setLineWidth(ctx, line_width);
+ }
+
+ // Translate our drawing context so that when we draw our
+ // glyph the bottom/left edge is at the correct sub-pixel
+ // position. The bottom/left edges are guaranteed to be at
+ // exactly [0, 0] relative to this because when we call to
+ // `drawGlyphs`, we pass the negated bearings.
+ context.translateCTM(
+ ctx,
+ frac_x + @as(f64, @floatFromInt(canvas_padding)),
+ frac_y + @as(f64, @floatFromInt(canvas_padding)),
+ );
+
+ // Scale the drawing context so that when we draw
+ // our glyph it's stretched to the constrained size.
+ context.scaleCTM(
+ ctx,
+ width / rect.size.width,
+ height / rect.size.height,
+ );
+
+ // Draw our glyph.
+ //
+ // We offset the position by the negated bearings so that the
+ // glyph is drawn at exactly [0, 0], which is then offset to
+ // the appropriate fractional position by the translation we
+ // did before scaling.
+ self.font.drawGlyphs(&glyphs, &.{.{
+ .x = -rect.origin.x,
+ .y = -rect.origin.y,
+ }}, ctx);
+
+ // Write our rasterized glyph to the atlas.
+ const region = try atlas.reserve(alloc, px_width, px_height);
+ atlas.set(region, buf);
+
+ // This should be the distance from the bottom of
+ // the cell to the top of the glyph's bounding box.
+ const offset_y: i32 = px_y + @as(i32, @intCast(px_height));
+
+ // This should be the distance from the left of
+ // the cell to the left of the glyph's bounding box.
+ const offset_x: i32 = px_x;
+
+ return .{
+ .width = px_width,
+ .height = px_height,
+ .offset_x = offset_x,
+ .offset_y = offset_y,
+ .atlas_x = region.x,
+ .atlas_y = region.y,
+ };
+ }
+
+ /// Get the `FaceMetrics` for this face.
+ pub fn getMetrics(self: *Face) font.Metrics.FaceMetrics {
+ const ct_font = self.font;
+
+ // Read the 'head' table out of the font data.
+ const head_: ?opentype.Head = head: {
+ // macOS bitmap-only fonts use a 'bhed' tag rather than 'head', but
+ // the table format is byte-identical to the 'head' table, so if we
+ // can't find 'head' we try 'bhed' instead before failing.
+ //
+ // ref: https://fontforge.org/docs/techref/bitmaponlysfnt.html
+ const head_tag = macos.text.FontTableTag.init("head");
+ const bhed_tag = macos.text.FontTableTag.init("bhed");
+ const data =
+ ct_font.copyTable(head_tag) orelse
+ ct_font.copyTable(bhed_tag) orelse
+ break :head null;
+ defer data.release();
+ const ptr = data.getPointer();
+ const len = data.getLength();
+ break :head opentype.Head.init(ptr[0..len]) catch |err| {
+ log.warn("error parsing head table: {}", .{err});
+ break :head null;
+ };
+ };
+
+ // Read the 'post' table out of the font data.
+ const post_: ?opentype.Post = post: {
+ const tag = macos.text.FontTableTag.init("post");
+ const data = ct_font.copyTable(tag) orelse break :post null;
+ defer data.release();
+ const ptr = data.getPointer();
+ const len = data.getLength();
+ break :post opentype.Post.init(ptr[0..len]) catch |err| {
+ log.warn("error parsing post table: {}", .{err});
+ break :post null;
+ };
+ };
+
+ // Read the 'OS/2' table out of the font data if it's available.
+ const os2_: ?opentype.OS2 = os2: {
+ const tag = macos.text.FontTableTag.init("OS/2");
+ const data = ct_font.copyTable(tag) orelse break :os2 null;
+ defer data.release();
+ const ptr = data.getPointer();
+ const len = data.getLength();
+ break :os2 opentype.OS2.init(ptr[0..len]) catch |err| {
+ log.warn("error parsing OS/2 table: {}", .{err});
+ break :os2 null;
+ };
+ };
+
+ // Read the 'hhea' table out of the font data.
+ const hhea_: ?opentype.Hhea = hhea: {
+ const tag = macos.text.FontTableTag.init("hhea");
+ const data = ct_font.copyTable(tag) orelse break :hhea null;
+ defer data.release();
+ const ptr = data.getPointer();
+ const len = data.getLength();
+ break :hhea opentype.Hhea.init(ptr[0..len]) catch |err| {
+ log.warn("error parsing hhea table: {}", .{err});
+ break :hhea null;
+ };
+ };
+
+ const units_per_em: f64 =
+ if (head_) |head|
+ @floatFromInt(head.unitsPerEm)
+ else
+ @floatFromInt(self.font.getUnitsPerEm());
+ const px_per_em: f64 = ct_font.getSize();
+ const px_per_unit: f64 = px_per_em / units_per_em;
+
+ const ascent: f64, const descent: f64, const line_gap: f64 = vertical_metrics: {
+ // If we couldn't get the hhea table, rely on metrics from CoreText.
+ const hhea = hhea_ orelse break :vertical_metrics .{
+ self.font.getAscent(),
+ -self.font.getDescent(),
+ self.font.getLeading(),
+ };
+
+ const hhea_ascent: f64 = @floatFromInt(hhea.ascender);
+ const hhea_descent: f64 = @floatFromInt(hhea.descender);
+ const hhea_line_gap: f64 = @floatFromInt(hhea.lineGap);
+
+ // If our font has no OS/2 table, then we just
+ // blindly use the metrics from the hhea table.
+ const os2 = os2_ orelse break :vertical_metrics .{
+ hhea_ascent * px_per_unit,
+ hhea_descent * px_per_unit,
+ hhea_line_gap * px_per_unit,
+ };
+
+ const os2_ascent: f64 = @floatFromInt(os2.sTypoAscender);
+ const os2_descent: f64 = @floatFromInt(os2.sTypoDescender);
+ const os2_line_gap: f64 = @floatFromInt(os2.sTypoLineGap);
+
+ // If the font says to use typo metrics, trust it.
+ if (os2.fsSelection.use_typo_metrics) break :vertical_metrics .{
+ os2_ascent * px_per_unit,
+ os2_descent * px_per_unit,
+ os2_line_gap * px_per_unit,
+ };
+
+ // Otherwise we prefer the height metrics from 'hhea' if they
+ // are available, or else OS/2 sTypo* metrics, and if all else
+ // fails then we use OS/2 usWin* metrics.
+ //
+ // This is not "standard" behavior, but it's our best bet to
+ // account for fonts being... just weird. It's pretty much what
+ // FreeType does to get its generic ascent and descent metrics.
+
+ if (hhea.ascender != 0 or hhea.descender != 0) break :vertical_metrics .{
+ hhea_ascent * px_per_unit,
+ hhea_descent * px_per_unit,
+ hhea_line_gap * px_per_unit,
+ };
+
+ if (os2_ascent != 0 or os2_descent != 0) break :vertical_metrics .{
+ os2_ascent * px_per_unit,
+ os2_descent * px_per_unit,
+ os2_line_gap * px_per_unit,
+ };
+
+ const win_ascent: f64 = @floatFromInt(os2.usWinAscent);
+ const win_descent: f64 = @floatFromInt(os2.usWinDescent);
+ break :vertical_metrics .{
+ win_ascent * px_per_unit,
+ // usWinDescent is *positive* -> down unlike sTypoDescender
+ // and hhea.Descender, so we flip its sign to fix this.
+ -win_descent * px_per_unit,
+ 0.0,
+ };
+ };
+
+ const underline_position, const underline_thickness = ul: {
+ const post = post_ orelse break :ul .{ null, null };
+
+ // Some fonts have degenerate 'post' tables where the underline
+ // thickness (and often position) are 0. We consider them null
+ // if this is the case and use our own fallbacks when we calculate.
+ const has_broken_underline = post.underlineThickness == 0;
+
+ // If the underline position isn't 0 then we do use it,
+ // even if the thickness is't properly specified.
+ const pos: ?f64 = if (has_broken_underline and post.underlinePosition == 0)
+ null
+ else
+ @as(f64, @floatFromInt(post.underlinePosition)) * px_per_unit;
+
+ const thick: ?f64 = if (has_broken_underline)
+ null
+ else
+ @as(f64, @floatFromInt(post.underlineThickness)) * px_per_unit;
+
+ break :ul .{ pos, thick };
+ };
+
+ // Similar logic to the underline above.
+ const strikethrough_position, const strikethrough_thickness = st: {
+ const os2 = os2_ orelse break :st .{ null, null };
+
+ const has_broken_strikethrough = os2.yStrikeoutSize == 0;
+
+ const pos: ?f64 = if (has_broken_strikethrough and os2.yStrikeoutPosition == 0)
+ null
+ else
+ @as(f64, @floatFromInt(os2.yStrikeoutPosition)) * px_per_unit;
+
+ const thick: ?f64 = if (has_broken_strikethrough)
+ null
+ else
+ @as(f64, @floatFromInt(os2.yStrikeoutSize)) * px_per_unit;
+
+ break :st .{ pos, thick };
+ };
+
+ // We fall back to whatever CoreText does if the
+ // OS/2 table doesn't specify a cap or ex height.
+ const cap_height: f64, const ex_height: f64 = heights: {
+ const os2 = os2_ orelse break :heights .{
+ ct_font.getCapHeight(),
+ ct_font.getXHeight(),
+ };
+
+ break :heights .{
+ if (os2.sCapHeight) |sCapHeight|
+ @as(f64, @floatFromInt(sCapHeight)) * px_per_unit
+ else
+ ct_font.getCapHeight(),
+
+ if (os2.sxHeight) |sxHeight|
+ @as(f64, @floatFromInt(sxHeight)) * px_per_unit
+ else
+ ct_font.getXHeight(),
+ };
+ };
+
+ // Cell width is calculated by calculating the widest width of the
+ // visible ASCII characters. Usually 'M' is widest but we just take
+ // whatever is widest.
+ //
+ // ASCII height is calculated as the height of the overall bounding
+ // box of the same characters.
+ const cell_width: f64, const ascii_height: f64 = measurements: {
+ // Build a comptime array of all the ASCII chars
+ const unichars = comptime unichars: {
+ const len = 127 - 32;
+ var result: [len]u16 = undefined;
+ var i: u16 = 32;
+ while (i < 127) : (i += 1) {
+ result[i - 32] = i;
+ }
+
+ break :unichars result;
+ };
+
+ // Get our glyph IDs for the ASCII chars
+ var glyphs: [unichars.len]macos.graphics.Glyph = undefined;
+ _ = ct_font.getGlyphsForCharacters(&unichars, &glyphs);
+
+ // Get all our advances
+ var advances: [unichars.len]macos.graphics.Size = undefined;
+ _ = ct_font.getAdvancesForGlyphs(.horizontal, &glyphs, &advances);
+
+ // Find the maximum advance
+ var max: f64 = 0;
+ var i: usize = 0;
+ while (i < advances.len) : (i += 1) {
+ max = @max(advances[i].width, max);
+ }
+
+ // Get the overall bounding rect for the glyphs
+ const rect = ct_font.getBoundingRectsForGlyphs(.horizontal, &glyphs, null);
+
+ break :measurements .{ max, rect.size.height };
+ };
+
+ // Measure "水" (CJK water ideograph, U+6C34) for our ic width.
+ const ic_width: ?f64 = ic_width: {
+ const glyph = self.glyphIndex('水') orelse break :ic_width null;
+
+ const advance = ct_font.getAdvancesForGlyphs(
+ .horizontal,
+ &.{@intCast(glyph)},
+ null,
+ );
+
+ const bounds = ct_font.getBoundingRectsForGlyphs(
+ .horizontal,
+ &.{@intCast(glyph)},
+ null,
+ );
+
+ // If the advance of the glyph is less than the width of the actual
+ // glyph then we just treat it as invalid since it's probably wrong
+ // and using it for size normalization will instead make the font
+ // way too big.
+ //
+ // This can sometimes happen if there's a CJK font that has been
+ // patched with the nerd fonts patcher and it butchers the advance
+ // values so the advance ends up half the width of the actual glyph.
+ if (bounds.size.width > advance) {
+ var buf: [1024]u8 = undefined;
+ const font_name = self.name(&buf) catch "<Error getting font name>";
+ log.warn(
+ "(getMetrics) Width of glyph '水' for font \"{s}\" is greater than its advance ({d} > {d}), discarding ic_width metric.",
+ .{
+ font_name,
+ bounds.size.width,
+ advance,
+ },
+ );
+ break :ic_width null;
+ }
+
+ break :ic_width advance;
+ };
+
+ return .{
+ .px_per_em = px_per_em,
+
+ .cell_width = cell_width,
+
+ .ascent = ascent,
+ .descent = descent,
+ .line_gap = line_gap,
+
+ .underline_position = underline_position,
+ .underline_thickness = underline_thickness,
+
+ .strikethrough_position = strikethrough_position,
+ .strikethrough_thickness = strikethrough_thickness,
+
+ .cap_height = cap_height,
+ .ex_height = ex_height,
+ .ascii_height = ascii_height,
+ .ic_width = ic_width,
+ };
+ }
+
+ /// Copy the font table data for the given tag.
+ pub fn copyTable(
+ self: Face,
+ alloc: Allocator,
+ tag: *const [4]u8,
+ ) Allocator.Error!?[]u8 {
+ const data = self.font.copyTable(macos.text.FontTableTag.init(tag)) orelse
+ return null;
+ defer data.release();
+
+ const buf = try alloc.alloc(u8, data.getLength());
+ errdefer alloc.free(buf);
+
+ const ptr = data.getPointer();
+ @memcpy(buf, ptr[0..buf.len]);
+
+ return buf;
+ }
+};
+
+/// The state associated with a font face that may have colorized glyphs.
+/// This is used to determine if a specific glyph ID is colorized.
+const ColorState = struct {
+ /// True if there is an sbix font table. For now, the mere presence
+ /// of an sbix font table causes us to assume the glyph is colored.
+ /// We can improve this later.
+ sbix: bool,
+
+ /// The SVG font table data (if any), which we can use to determine
+ /// if a glyph is present in the SVG table.
+ svg: ?opentype.SVG,
+ svg_data: ?*macos.foundation.Data,
+
+ pub const Error = error{InvalidSVGTable};
+
+ pub fn init(f: *macos.text.Font) Error!ColorState {
+ // sbix is true if the table exists in the font data at all.
+ // In the future we probably want to actually parse it and
+ // check for glyphs.
+ const sbix: bool = sbix: {
+ const tag = macos.text.FontTableTag.init("sbix");
+ const data = f.copyTable(tag) orelse break :sbix false;
+ data.release();
+ break :sbix data.getLength() > 0;
+ };
+
+ // Read the SVG table out of the font data.
+ const svg: ?struct {
+ svg: opentype.SVG,
+ data: *macos.foundation.Data,
+ } = svg: {
+ const tag = macos.text.FontTableTag.init("SVG ");
+ const data = f.copyTable(tag) orelse break :svg null;
+ errdefer data.release();
+ const ptr = data.getPointer();
+ const len = data.getLength();
+ const svg = opentype.SVG.init(ptr[0..len]) catch |err| {
+ return switch (err) {
+ error.EndOfStream,
+ error.SVGVersionNotSupported,
+ => error.InvalidSVGTable,
+ };
+ };
+
+ break :svg .{
+ .svg = svg,
+ .data = data,
+ };
+ };
+
+ return .{
+ .sbix = sbix,
+ .svg = if (svg) |v| v.svg else null,
+ .svg_data = if (svg) |v| v.data else null,
+ };
+ }
+
+ pub fn deinit(self: *const ColorState) void {
+ if (self.svg_data) |v| v.release();
+ }
+
+ /// Returns true if the given glyph ID is colored.
+ pub fn isColorGlyph(self: *const ColorState, glyph_id: u32) bool {
+ // Our font system uses 32-bit glyph IDs for special values but
+ // actual fonts only contain 16-bit glyph IDs so if we can't cast
+ // into it it must be false.
+ const glyph_u16 = std.math.cast(u16, glyph_id) orelse return false;
+
+ // sbix is always true for now
+ if (self.sbix) return true;
+
+ // if we have svg data, check it
+ if (self.svg) |svg| {
+ if (svg.hasGlyph(glyph_u16)) return true;
+ }
+
+ return false;
+ }
+};
+
+test {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var atlas = try font.Atlas.init(alloc, 512, .grayscale);
+ defer atlas.deinit(alloc);
+
+ const name = try macos.foundation.String.createWithBytes("Monaco", .utf8, false);
+ defer name.release();
+ const desc = try macos.text.FontDescriptor.createWithNameAndSize(name, 12);
+ defer desc.release();
+ const ct_font = try macos.text.Font.createWithFontDescriptor(desc, 12);
+ defer ct_font.release();
+
+ var face = try Face.initFontCopy(ct_font, .{ .size = .{ .points = 12 } });
+ defer face.deinit();
+
+ // Generate all visible ASCII
+ var i: u8 = 32;
+ while (i < 127) : (i += 1) {
+ try testing.expect(face.glyphIndex(i) != null);
+ _ = try face.renderGlyph(
+ alloc,
+ &atlas,
+ face.glyphIndex(i).?,
+ .{ .grid_metrics = font.Metrics.calc(face.getMetrics()) },
+ );
+ }
+}
+
+test "name" {
+ const testing = std.testing;
+
+ const name = try macos.foundation.String.createWithBytes("Menlo", .utf8, false);
+ defer name.release();
+ const desc = try macos.text.FontDescriptor.createWithNameAndSize(name, 12);
+ defer desc.release();
+ const ct_font = try macos.text.Font.createWithFontDescriptor(desc, 12);
+ defer ct_font.release();
+
+ var face = try Face.initFontCopy(ct_font, .{ .size = .{ .points = 12 } });
+ defer face.deinit();
+
+ var buf: [1024]u8 = undefined;
+ const font_name = try face.name(&buf);
+ try testing.expectEqualStrings(font_name, "Menlo");
+}
+
+test "emoji" {
+ const testing = std.testing;
+
+ const name = try macos.foundation.String.createWithBytes("Apple Color Emoji", .utf8, false);
+ defer name.release();
+ const desc = try macos.text.FontDescriptor.createWithNameAndSize(name, 12);
+ defer desc.release();
+ const ct_font = try macos.text.Font.createWithFontDescriptor(desc, 12);
+ defer ct_font.release();
+
+ var face = try Face.initFontCopy(ct_font, .{ .size = .{ .points = 18 } });
+ defer face.deinit();
+
+ // Glyph index check
+ {
+ const id = face.glyphIndex('🥸').?;
+ try testing.expect(face.isColorGlyph(id));
+ }
+}
+
+test "in-memory" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ const testFont = font.embedded.regular;
+
+ var atlas = try font.Atlas.init(alloc, 512, .grayscale);
+ defer atlas.deinit(alloc);
+
+ var lib = try font.Library.init(alloc);
+ defer lib.deinit();
+
+ var face = try Face.init(lib, testFont, .{ .size = .{ .points = 12 } });
+ defer face.deinit();
+
+ // Generate all visible ASCII
+ var i: u8 = 32;
+ while (i < 127) : (i += 1) {
+ try testing.expect(face.glyphIndex(i) != null);
+ _ = try face.renderGlyph(
+ alloc,
+ &atlas,
+ face.glyphIndex(i).?,
+ .{ .grid_metrics = font.Metrics.calc(face.getMetrics()) },
+ );
+ }
+}
+
+test "variable" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ const testFont = font.embedded.variable;
+
+ var atlas = try font.Atlas.init(alloc, 512, .grayscale);
+ defer atlas.deinit(alloc);
+
+ var lib = try font.Library.init(alloc);
+ defer lib.deinit();
+
+ var face = try Face.init(lib, testFont, .{ .size = .{ .points = 12 } });
+ defer face.deinit();
+
+ // Generate all visible ASCII
+ var i: u8 = 32;
+ while (i < 127) : (i += 1) {
+ try testing.expect(face.glyphIndex(i) != null);
+ _ = try face.renderGlyph(
+ alloc,
+ &atlas,
+ face.glyphIndex(i).?,
+ .{ .grid_metrics = font.Metrics.calc(face.getMetrics()) },
+ );
+ }
+}
+
+test "variable set variation" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ const testFont = font.embedded.variable;
+
+ var atlas = try font.Atlas.init(alloc, 512, .grayscale);
+ defer atlas.deinit(alloc);
+
+ var lib = try font.Library.init(alloc);
+ defer lib.deinit();
+
+ var face = try Face.init(lib, testFont, .{ .size = .{ .points = 12 } });
+ defer face.deinit();
+
+ try face.setVariations(&.{
+ .{ .id = font.face.Variation.Id.init("wght"), .value = 400 },
+ }, .{ .size = .{ .points = 12 } });
+
+ // Generate all visible ASCII
+ var i: u8 = 32;
+ while (i < 127) : (i += 1) {
+ try testing.expect(face.glyphIndex(i) != null);
+ _ = try face.renderGlyph(
+ alloc,
+ &atlas,
+ face.glyphIndex(i).?,
+ .{ .grid_metrics = font.Metrics.calc(face.getMetrics()) },
+ );
+ }
+}
+
+test "svg font table" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ const testFont = font.embedded.julia_mono;
+
+ var lib = try font.Library.init(alloc);
+ defer lib.deinit();
+
+ var face = try Face.init(lib, testFont, .{ .size = .{ .points = 12 } });
+ defer face.deinit();
+
+ const table = (try face.copyTable(alloc, "SVG ")).?;
+ defer alloc.free(table);
+
+ try testing.expect(table.len > 0);
+}
+
+test "glyphIndex colored vs text" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ const testFont = font.embedded.julia_mono;
+
+ var lib = try font.Library.init(alloc);
+ defer lib.deinit();
+
+ var face = try Face.init(lib, testFont, .{ .size = .{ .points = 12 } });
+ defer face.deinit();
+
+ {
+ const glyph = face.glyphIndex('A').?;
+ try testing.expectEqual(4, glyph);
+ try testing.expect(!face.isColorGlyph(glyph));
+ }
+
+ {
+ const glyph = face.glyphIndex(0xE800).?;
+ try testing.expectEqual(11482, glyph);
+ try testing.expect(face.isColorGlyph(glyph));
+ }
+}
diff --git a/vendor/libghostty-vt/src/font/face/freetype.zig b/vendor/libghostty-vt/src/font/face/freetype.zig
new file mode 100644
index 00000000..528f72d5
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/face/freetype.zig
@@ -0,0 +1,1439 @@
+//! Face represents a single font face. A single font face has a single set
+//! of properties associated with it such as style, weight, etc.
+//!
+//! A Face isn't typically meant to be used directly. It is usually used
+//! via a Family in order to store it in an Atlas.
+
+const std = @import("std");
+const builtin = @import("builtin");
+const freetype = @import("freetype");
+const harfbuzz = @import("harfbuzz");
+const stb = @import("../../stb/main.zig");
+const assert = @import("../../quirks.zig").inlineAssert;
+const testing = std.testing;
+const Allocator = std.mem.Allocator;
+const font = @import("../main.zig");
+const Glyph = font.Glyph;
+const Library = font.Library;
+const opentype = @import("../opentype.zig");
+const quirks = @import("../../quirks.zig");
+const config = @import("../../config.zig");
+
+const F26Dot6 = opentype.sfnt.F26Dot6;
+
+const log = std.log.scoped(.font_face);
+
+pub const Face = struct {
+ comptime {
+ // If we have the freetype backend, we should have load flags.
+ assert(font.face.FreetypeLoadFlags != void);
+ }
+
+ /// Our Library
+ lib: Library,
+
+ /// Our font face.
+ face: freetype.Face,
+
+ /// This mutex MUST be held while doing anything with the
+ /// glyph slot on the freetype face, because this struct
+ /// may be shared across multiple surfaces.
+ ///
+ /// This means that anywhere where `self.face.loadGlyph`
+ /// is called, this mutex must be held.
+ ft_mutex: *std.Thread.Mutex,
+
+ /// Harfbuzz font corresponding to this face.
+ hb_font: harfbuzz.Font,
+
+ /// Freetype load flags for this font face.
+ load_flags: font.face.FreetypeLoadFlags,
+
+ /// Set quirks.disableDefaultFontFeatures
+ quirks_disable_default_font_features: bool = false,
+
+ /// Set to true to apply a synthetic italic to the face.
+ synthetic: packed struct {
+ italic: bool = false,
+ bold: bool = false,
+ } = .{},
+
+ /// The current size this font is set to.
+ size: font.face.DesiredSize,
+
+ /// Initialize a new font face with the given source in-memory.
+ pub fn initFile(
+ lib: Library,
+ path: [:0]const u8,
+ index: i32,
+ opts: font.face.Options,
+ ) !Face {
+ lib.mutex.lock();
+ defer lib.mutex.unlock();
+ const face = try lib.lib.initFace(path, index);
+ errdefer face.deinit();
+ return try initFace(lib, face, opts);
+ }
+
+ /// Initialize a new font face with the given source in-memory.
+ pub fn init(
+ lib: Library,
+ source: [:0]const u8,
+ opts: font.face.Options,
+ ) !Face {
+ lib.mutex.lock();
+ defer lib.mutex.unlock();
+ const face = try lib.lib.initMemoryFace(source, 0);
+ errdefer face.deinit();
+ return try initFace(lib, face, opts);
+ }
+
+ fn initFace(
+ lib: Library,
+ face: freetype.Face,
+ opts: font.face.Options,
+ ) !Face {
+ try face.selectCharmap(.unicode);
+ try setSize_(face, opts.size);
+
+ var hb_font = try harfbuzz.freetype.createFont(face.handle);
+ errdefer hb_font.destroy();
+
+ const ft_mutex = try lib.alloc.create(std.Thread.Mutex);
+ errdefer lib.alloc.destroy(ft_mutex);
+ ft_mutex.* = .{};
+
+ var result: Face = .{
+ .lib = lib,
+ .face = face,
+ .hb_font = hb_font,
+ .ft_mutex = ft_mutex,
+ .load_flags = opts.freetype_load_flags,
+ .size = opts.size,
+ };
+ result.quirks_disable_default_font_features = quirks.disableDefaultFontFeatures(&result);
+
+ // In debug mode, we output information about available variation axes,
+ // if they exist.
+ if (comptime builtin.mode == .Debug) mm: {
+ if (!face.hasMultipleMasters()) break :mm;
+ var buf: [1024]u8 = undefined;
+ log.debug("variation axes font={s}", .{try result.name(&buf)});
+
+ const mm = try face.getMMVar();
+ defer lib.lib.doneMMVar(mm);
+ for (0..mm.num_axis) |i| {
+ const axis = mm.axis[i];
+ const id_raw = std.math.cast(c_int, axis.tag) orelse continue;
+ const id: font.face.Variation.Id = @bitCast(id_raw);
+ log.debug("variation axis: name={s} id={s} min={} max={} def={}", .{
+ std.mem.sliceTo(axis.name, 0),
+ id.str(),
+ axis.minimum >> 16,
+ axis.maximum >> 16,
+ axis.def >> 16,
+ });
+ }
+ }
+
+ return result;
+ }
+
+ pub fn deinit(self: *Face) void {
+ self.lib.alloc.destroy(self.ft_mutex);
+ {
+ self.lib.mutex.lock();
+ defer self.lib.mutex.unlock();
+
+ self.face.deinit();
+ }
+ self.hb_font.destroy();
+ self.* = undefined;
+ }
+
+ /// Returns the font name. If allocation is required, buf will be used,
+ /// but sometimes allocation isn't required and a static string is
+ /// returned.
+ pub fn name(self: *const Face, buf: []u8) Allocator.Error![]const u8 {
+ const count = self.face.getSfntNameCount();
+
+ // We look for the font family entry.
+ for (0..count) |i| {
+ const entry = self.face.getSfntName(i) catch continue;
+ if (entry.name_id == freetype.c.TT_NAME_ID_FONT_FAMILY) {
+ const string = entry.string[0..entry.string_len];
+ // There are other encodings that are something other than UTF-8
+ // but this is one we've seen "in the wild" so far.
+ if (entry.platform_id == freetype.c.TT_PLATFORM_MICROSOFT and entry.encoding_id == freetype.c.TT_MS_ID_UNICODE_CS) skip: {
+ if (string.len % 2 != 0) break :skip;
+ if (string.len > 1024) break :skip;
+ var tmp: [512]u16 = undefined;
+ const max = string.len / 2;
+ for (@as([]const u16, @ptrCast(@alignCast(string))), 0..) |c, j| tmp[j] = @byteSwap(c);
+ const len = std.unicode.utf16LeToUtf8(buf, tmp[0..max]) catch return string;
+ return buf[0..len];
+ }
+ return string;
+ }
+ }
+
+ return "";
+ }
+
+ test "face name" {
+ const embedded = @import("../embedded.zig");
+
+ var lib: Library = try .init(testing.allocator);
+ defer lib.deinit();
+
+ {
+ var face: Face = try .init(lib, embedded.variable, .{ .size = .{ .points = 14 } });
+ defer face.deinit();
+
+ var buf: [1024]u8 = undefined;
+ const actual = try face.name(&buf);
+
+ try testing.expectEqualStrings("JetBrains Mono", actual);
+ }
+
+ {
+ var face: Face = try .init(lib, embedded.inconsolata, .{ .size = .{ .points = 14 } });
+ defer face.deinit();
+
+ var buf: [1024]u8 = undefined;
+ const actual = try face.name(&buf);
+
+ try testing.expectEqualStrings("Inconsolata", actual);
+ }
+ }
+
+ /// Return a new face that is the same as this but also has synthetic
+ /// bold applied.
+ pub fn syntheticBold(self: *const Face, opts: font.face.Options) !Face {
+ // Increase face ref count
+ self.face.ref();
+ errdefer self.face.deinit();
+
+ var f = try initFace(self.lib, self.face, opts);
+ errdefer f.deinit();
+ f.synthetic = self.synthetic;
+ f.synthetic.bold = true;
+
+ return f;
+ }
+
+ /// Return a new face that is the same as this but has a transformation
+ /// matrix applied to italicize it.
+ pub fn syntheticItalic(self: *const Face, opts: font.face.Options) !Face {
+ // Increase face ref count
+ self.face.ref();
+ errdefer self.face.deinit();
+
+ var f = try initFace(self.lib, self.face, opts);
+ errdefer f.deinit();
+ f.synthetic = self.synthetic;
+ f.synthetic.italic = true;
+
+ return f;
+ }
+
+ /// Resize the font in-place. If this succeeds, the caller is responsible
+ /// for clearing any glyph caches, font atlas data, etc.
+ pub fn setSize(self: *Face, opts: font.face.Options) !void {
+ try setSize_(self.face, opts.size);
+ self.size = opts.size;
+ }
+
+ fn setSize_(face: freetype.Face, size: font.face.DesiredSize) !void {
+ // If we have fixed sizes, we just have to try to pick the one closest
+ // to what the user requested. Otherwise, we can choose an arbitrary
+ // pixel size.
+ if (face.isScalable()) {
+ const size_26dot6: i32 = @intFromFloat(@round(size.points * 64));
+ try face.setCharSize(0, size_26dot6, size.xdpi, size.ydpi);
+ } else try selectSizeNearest(face, @intFromFloat(@round(size.pixels())));
+ }
+
+ /// Selects the fixed size in the loaded face that is closest to the
+ /// requested pixel size.
+ fn selectSizeNearest(face: freetype.Face, size: u32) !void {
+ var i: i32 = 0;
+ var best_i: i32 = 0;
+ var best_diff: i32 = 0;
+ while (i < face.handle.*.num_fixed_sizes) : (i += 1) {
+ const width = face.handle.*.available_sizes[@intCast(i)].width;
+ const diff = @as(i32, @intCast(size)) - @as(i32, @intCast(width));
+ if (i == 0 or diff < best_diff) {
+ best_diff = diff;
+ best_i = i;
+ }
+ }
+
+ try face.selectSize(best_i);
+ }
+
+ /// Set the variation axes for this font. This will modify this font
+ /// in-place.
+ pub fn setVariations(
+ self: *Face,
+ vs: []const font.face.Variation,
+ opts: font.face.Options,
+ ) !void {
+ _ = opts;
+
+ // If this font doesn't support variations, we can't do anything.
+ if (!self.face.hasMultipleMasters() or vs.len == 0) return;
+
+ // Freetype requires that we send ALL coordinates in at once so the
+ // first thing we have to do is get all the vars and put them into
+ // an array.
+ const mm = try self.face.getMMVar();
+ defer self.lib.lib.doneMMVar(mm);
+
+ // To avoid allocations, we cap the number of variation axes we can
+ // support. This is arbitrary but Firefox caps this at 16 so I
+ // feel like that's probably safe... and we do double cause its
+ // cheap.
+ var coords_buf: [32]freetype.c.FT_Fixed = undefined;
+ var coords = coords_buf[0..@min(coords_buf.len, mm.num_axis)];
+ try self.face.getVarDesignCoordinates(coords);
+
+ // Now we go through each axis and see if its set. This is slow
+ // but there usually aren't many axes and usually not many set
+ // variations, either.
+ for (0..mm.num_axis) |i| {
+ const axis = mm.axis[i];
+ const id = std.math.cast(u32, axis.tag) orelse continue;
+ for (vs) |v| {
+ if (id == @as(u32, @bitCast(v.id))) {
+ coords[i] = @intFromFloat(v.value * 65536);
+ break;
+ }
+ }
+ }
+
+ // Set them!
+ try self.face.setVarDesignCoordinates(coords);
+ }
+
+ /// Returns the glyph index for the given Unicode code point. If this
+ /// face doesn't support this glyph, null is returned.
+ pub fn glyphIndex(self: Face, cp: u32) ?u32 {
+ return self.face.getCharIndex(cp);
+ }
+
+ /// Returns true if this font is colored. This can be used by callers to
+ /// determine what kind of atlas to pass in.
+ pub fn hasColor(self: Face) bool {
+ return self.face.hasColor();
+ }
+
+ /// Returns true if the given glyph ID is colorized.
+ pub fn isColorGlyph(self: *const Face, glyph_id: u32) bool {
+ self.ft_mutex.lock();
+ defer self.ft_mutex.unlock();
+
+ // Load the glyph and see what pixel mode it renders with.
+ // All modes other than BGRA are non-color.
+ // If the glyph fails to load, just return false.
+ self.face.loadGlyph(glyph_id, .{
+ .render = true,
+ .color = self.face.hasColor(),
+ // NO_SVG set to true because we don't currently support rendering
+ // SVG glyphs under FreeType, since that requires bundling another
+ // dependency to handle rendering the SVG.
+ .no_svg = true,
+ }) catch return false;
+
+ const glyph = self.face.handle.*.glyph;
+
+ return glyph.*.bitmap.pixel_mode == freetype.c.FT_PIXEL_MODE_BGRA;
+ }
+
+ /// Set the load flags to use when loading a glyph for measurement or
+ /// rendering.
+ fn glyphLoadFlags(self: Face, constrained: bool) freetype.LoadFlags {
+ // Hinting should only be enabled if the configured load flags specify
+ // it and the provided constraint doesn't actually do anything, since
+ // if it does, then it'll mess up the hinting anyway when it moves or
+ // resizes the glyph.
+ const do_hinting = self.load_flags.hinting and !constrained;
+
+ return .{
+ // If our glyph has color, we want to render the color
+ .color = self.face.hasColor(),
+
+ // We don't render, because we'll invoke the render
+ // manually after applying constraints further down.
+ .render = false,
+
+ // use options from config
+ .no_hinting = !do_hinting,
+ .force_autohint = self.load_flags.@"force-autohint",
+ .no_autohint = !self.load_flags.autohint,
+
+ // If we're gonna be rendering this glyph in monochrome,
+ // then we should use the monochrome hinter as well, or
+ // else it won't look very good at all.
+ //
+ // Otherwise if the user asked for light hinting we
+ // use that, otherwise we just use the normal target.
+ .target = if (self.load_flags.monochrome)
+ .mono
+ else if (self.load_flags.light)
+ .light
+ else
+ .normal,
+
+ // NO_SVG set to true because we don't currently support rendering
+ // SVG glyphs under FreeType, since that requires bundling another
+ // dependency to handle rendering the SVG.
+ .no_svg = true,
+ };
+ }
+
+ /// Get a rect that represents the position and size of the loaded glyph.
+ fn getGlyphSize(glyph: freetype.c.FT_GlyphSlot) font.face.GlyphSize {
+ // If we're dealing with an outline glyph then we get the
+ // outline's bounding box instead of using the built-in
+ // metrics, since that's more precise and allows better
+ // cell-fitting.
+ if (glyph.*.format == freetype.c.FT_GLYPH_FORMAT_OUTLINE) {
+ // Get the glyph's bounding box before we transform it at all.
+ // We use this rather than the metrics, since it's more precise.
+ var bbox: freetype.c.FT_BBox = undefined;
+ _ = freetype.c.FT_Outline_Get_BBox(&glyph.*.outline, &bbox);
+
+ return .{
+ .x = f26dot6ToF64(bbox.xMin),
+ .y = f26dot6ToF64(bbox.yMin),
+ .width = f26dot6ToF64(bbox.xMax - bbox.xMin),
+ .height = f26dot6ToF64(bbox.yMax - bbox.yMin),
+ };
+ }
+
+ return .{
+ .x = f26dot6ToF64(glyph.*.metrics.horiBearingX),
+ .y = f26dot6ToF64(glyph.*.metrics.horiBearingY - glyph.*.metrics.height),
+ .width = f26dot6ToF64(glyph.*.metrics.width),
+ .height = f26dot6ToF64(glyph.*.metrics.height),
+ };
+ }
+
+ /// Render a glyph using the glyph index. The rendered glyph is stored in the
+ /// given texture atlas.
+ pub fn renderGlyph(
+ self: Face,
+ alloc: Allocator,
+ atlas: *font.Atlas,
+ glyph_index: u32,
+ opts: font.face.RenderOptions,
+ ) !Glyph {
+ self.ft_mutex.lock();
+ defer self.ft_mutex.unlock();
+
+ // Load the glyph.
+ try self.face.loadGlyph(glyph_index, self.glyphLoadFlags(opts.constraint.doesAnything()));
+ const glyph = self.face.handle.*.glyph;
+
+ // For synthetic bold, we embolden the glyph.
+ if (self.synthetic.bold) {
+ // We need to scale the embolden amount based on the font size.
+ // This is a heuristic I found worked well across a variety of
+ // founts: 1 pixel per 64 units of height.
+ const font_height: f64 = @floatFromInt(self.face.handle.*.size.*.metrics.height);
+ const ratio: f64 = 64.0 / 2048.0;
+ const amount = @ceil(font_height * ratio);
+ _ = freetype.c.FT_Outline_Embolden(&glyph.*.outline, @intFromFloat(amount));
+ }
+
+ // We get a rect that represents the position
+ // and size of the glyph before constraints.
+ const rect = getGlyphSize(glyph);
+
+ // If our glyph is smaller than a quarter pixel in either axis
+ // then it has no outlines or they're too small to render.
+ //
+ // In this case we just return 0-sized glyph struct.
+ if (rect.width < 0.25 or rect.height < 0.25)
+ return font.Glyph{
+ .width = 0,
+ .height = 0,
+ .offset_x = 0,
+ .offset_y = 0,
+ .atlas_x = 0,
+ .atlas_y = 0,
+ };
+
+ const metrics = opts.grid_metrics;
+ const cell_width: f64 = @floatFromInt(metrics.cell_width);
+ const cell_height: f64 = @floatFromInt(metrics.cell_height);
+
+ // Next we apply any constraints to get the final size of the glyph.
+ const constraint = opts.constraint;
+
+ // We need to add the baseline position before passing to the constrain
+ // function since it operates on cell-relative positions, not baseline.
+ const cell_baseline: f64 = @floatFromInt(metrics.cell_baseline);
+
+ const glyph_size = constraint.constrain(
+ .{
+ .width = rect.width,
+ .height = rect.height,
+ .x = rect.x,
+ .y = rect.y + cell_baseline,
+ },
+ metrics,
+ opts.constraint_width,
+ );
+
+ var width = glyph_size.width;
+ var height = glyph_size.height;
+ var x = glyph_size.x;
+ var y = glyph_size.y;
+
+ // We center all glyphs within the pixel-rounded and adjusted
+ // cell width if it's larger than the face width, so that they
+ // aren't weirdly off to the left.
+ //
+ // We don't do this if the glyph has a stretch constraint,
+ // since in that case the position was already calculated with the
+ // new cell width in mind.
+ if ((constraint.size != .stretch) and (metrics.face_width < cell_width)) {
+ // We add half the difference to re-center.
+ //
+ // NOTE: We round this to a whole-pixel amount because under
+ // FreeType, the outlines will be hinted, which isn't
+ // the case under CoreText. If we move the outlines by
+ // a non-whole-pixel amount, it completely ruins the
+ // hinting.
+ x += @round((cell_width - metrics.face_width) / 2);
+ }
+
+ // If this is a bitmap glyph, it will always render as full pixels,
+ // not fractional pixels, so we need to quantize its position and
+ // size accordingly to align to full pixels so we get good results.
+ if (glyph.*.format == freetype.c.FT_GLYPH_FORMAT_BITMAP) {
+ width = cell_width - @round(cell_width - width - x) - @round(x);
+ height = cell_height - @round(cell_height - height - y) - @round(y);
+ x = @round(x);
+ y = @round(y);
+ }
+
+ // Now we can render the glyph.
+ var bitmap: freetype.c.FT_Bitmap = undefined;
+ _ = freetype.c.FT_Bitmap_Init(&bitmap);
+ defer _ = freetype.c.FT_Bitmap_Done(self.lib.lib.handle, &bitmap);
+ switch (glyph.*.format) {
+ freetype.c.FT_GLYPH_FORMAT_OUTLINE => {
+ // Manually adjust the glyph outline with this transform.
+ //
+ // This offers better precision than using the freetype transform
+ // matrix, since that has 16.16 coefficients, and also I was having
+ // weird issues that I can only assume where due to freetype doing
+ // some bad caching or something when I did this using the matrix.
+ const scale_x = width / rect.width;
+ const scale_y = height / rect.height;
+ const skew: f64 =
+ if (self.synthetic.italic)
+ // We skew by 12 degrees to synthesize italics.
+ @tan(std.math.degreesToRadians(12))
+ else
+ 0.0;
+
+ const outline = &glyph.*.outline;
+ for (outline.points[0..@intCast(outline.n_points)]) |*p| {
+ // Convert to f64 for processing
+ var px = f26dot6ToF64(p.x);
+ var py = f26dot6ToF64(p.y);
+
+ // Subtract original bearings
+ px -= rect.x;
+ py -= rect.y;
+
+ // Scale
+ px *= scale_x;
+ py *= scale_y;
+
+ // Add new bearings
+ px += x;
+ py += y - cell_baseline;
+
+ // Skew
+ px += py * skew;
+
+ // Convert back and store
+ p.x = @as(i32, @bitCast(F26Dot6.from(px)));
+ p.y = @as(i32, @bitCast(F26Dot6.from(py)));
+ }
+
+ try self.face.renderGlyph(
+ if (self.load_flags.monochrome)
+ .mono
+ else
+ .normal,
+ );
+
+ // Copy the glyph's bitmap, making sure
+ // that it's 8bpp and densely packed.
+ if (freetype.c.FT_Bitmap_Convert(
+ self.lib.lib.handle,
+ &glyph.*.bitmap,
+ &bitmap,
+ 1,
+ ) != 0) {
+ return error.BitmapHandlingError;
+ }
+ },
+
+ freetype.c.FT_GLYPH_FORMAT_BITMAP => {
+ // If our glyph has a non-color bitmap, we need
+ // to convert it to dense 8bpp so that the scale
+ // operation works correctly.
+ switch (glyph.*.bitmap.pixel_mode) {
+ freetype.c.FT_PIXEL_MODE_BGRA,
+ freetype.c.FT_PIXEL_MODE_GRAY,
+ => {},
+ else => {
+ // Make sure the slot owns its bitmap,
+ // since we'll be modifying it here.
+ if (freetype.c.FT_GlyphSlot_Own_Bitmap(glyph) != 0) {
+ return error.BitmapHandlingError;
+ }
+
+ var converted: freetype.c.FT_Bitmap = undefined;
+ freetype.c.FT_Bitmap_Init(&converted);
+ if (freetype.c.FT_Bitmap_Convert(
+ self.lib.lib.handle,
+ &glyph.*.bitmap,
+ &converted,
+ 1,
+ ) != 0) {
+ return error.BitmapHandlingError;
+ }
+ // Free the existing glyph bitmap and
+ // replace it with the converted one.
+ _ = freetype.c.FT_Bitmap_Done(
+ self.lib.lib.handle,
+ &glyph.*.bitmap,
+ );
+ glyph.*.bitmap = converted;
+ },
+ }
+
+ const glyph_bitmap = glyph.*.bitmap;
+
+ // Round our target width and height
+ // as the size for our scaled bitmap.
+ const w: u32 = @intFromFloat(@round(width));
+ const h: u32 = @intFromFloat(@round(height));
+ const pitch = w * atlas.format.depth();
+
+ // Allocate a buffer for our scaled bitmap.
+ //
+ // We'll copy this to the original bitmap once we're
+ // done so we can free it at the end of this scope.
+ const buf = try alloc.alloc(u8, pitch * h);
+ defer alloc.free(buf);
+
+ // Resize
+ if (stb.stbir_resize_uint8(
+ glyph_bitmap.buffer,
+ @intCast(glyph_bitmap.width),
+ @intCast(glyph_bitmap.rows),
+ glyph_bitmap.pitch,
+ buf.ptr,
+ @intCast(w),
+ @intCast(h),
+ @intCast(pitch),
+ atlas.format.depth(),
+ ) == 0) {
+ // This should never fail because this is a
+ // fairly straightforward in-memory operation...
+ return error.GlyphResizeFailed;
+ }
+
+ const scaled_bitmap: freetype.c.FT_Bitmap = .{
+ .buffer = buf.ptr,
+ .width = @intCast(w),
+ .rows = @intCast(h),
+ .pitch = @intCast(pitch),
+ .pixel_mode = glyph_bitmap.pixel_mode,
+ .num_grays = glyph_bitmap.num_grays,
+ };
+
+ // Replace the bitmap's buffer and size info.
+ if (freetype.c.FT_Bitmap_Copy(
+ self.lib.lib.handle,
+ &scaled_bitmap,
+ &bitmap,
+ ) != 0) {
+ return error.BitmapHandlingError;
+ }
+
+ // Update the bearings to account for the new positioning.
+ glyph.*.bitmap_top = @intFromFloat(@floor(y - cell_baseline + height));
+ glyph.*.bitmap_left = @intFromFloat(@floor(x));
+ },
+
+ else => |f| {
+ // Glyph formats are tags, so we can
+ // output a semi-readable error here.
+ // Use @bitCast to u32 because MSVC translates C enums
+ // as signed int, while GCC/Clang uses unsigned int.
+ const tag: u32 = @bitCast(f);
+ log.err(
+ "Can't render glyph with unsupported glyph format \"{s}\"",
+ .{[4]u8{
+ @truncate(tag >> 24),
+ @truncate(tag >> 16),
+ @truncate(tag >> 8),
+ @truncate(tag >> 0),
+ }},
+ );
+ return error.UnsupportedGlyphFormat;
+ },
+ }
+
+ // If this is a color glyph but we're trying to render it to the
+ // grayscale atlas, or vice versa, then we throw and error. Maybe
+ // in the future we could convert, but for now it should be fine.
+ switch (bitmap.pixel_mode) {
+ freetype.c.FT_PIXEL_MODE_GRAY => if (atlas.format != .grayscale) {
+ return error.WrongAtlas;
+ },
+ freetype.c.FT_PIXEL_MODE_BGRA => if (atlas.format != .bgra) {
+ return error.WrongAtlas;
+ },
+ else => {
+ log.warn("glyph={} pixel mode={}", .{ glyph_index, bitmap.pixel_mode });
+ @panic("unsupported pixel mode");
+ },
+ }
+
+ // Our whole-pixel bearings for the final glyph.
+ // The fractional portion will be included in the rasterized position.
+ //
+ // For the Y position, FreeType's `bitmap_top` is the distance from the
+ // baseline to the top of the glyph, but we need the distance from the
+ // bottom of the cell to the bottom of the glyph, so first we add the
+ // baseline to get the distance from the bottom of the cell to the top
+ // of the glyph, then we subtract the height of the glyph to get the
+ // bottom.
+ const px_x: i32 = glyph.*.bitmap_left;
+ const px_y: i32 = glyph.*.bitmap_top +
+ @as(i32, @intCast(metrics.cell_baseline)) -
+ @as(i32, @intCast(bitmap.rows));
+
+ const px_width = bitmap.width;
+ const px_height = bitmap.rows;
+ const len: usize = @intCast(
+ @as(c_uint, @intCast(@abs(bitmap.pitch))) * bitmap.rows,
+ );
+
+ // If our bitmap is grayscale, make sure to multiply all pixel
+ // values by the right factor to bring `num_grays` up to 256.
+ //
+ // This is necessary because FT_Bitmap_Convert doesn't do this,
+ // it just sets num_grays to the correct number and uses the
+ // original smaller pixel values.
+ if (bitmap.pixel_mode == freetype.c.FT_PIXEL_MODE_GRAY and
+ bitmap.num_grays < 256)
+ {
+ const factor: u8 = @intCast(255 / (bitmap.num_grays - 1));
+ for (bitmap.buffer[0..len]) |*p| {
+ p.* *= factor;
+ }
+ bitmap.num_grays = 256;
+ }
+
+ // Must have non-empty bitmap because we return earlier if zero.
+ // We assume the rest of this that it is non-zero so this is important.
+ assert(px_width > 0 and px_height > 0);
+
+ // If this doesn't match then something is wrong.
+ assert(px_width * atlas.format.depth() == bitmap.pitch);
+
+ // Allocate our texture atlas region and copy our bitmap in to it.
+ const region = try atlas.reserve(alloc, px_width, px_height);
+ atlas.set(region, bitmap.buffer[0..len]);
+
+ // This should be the distance from the bottom of
+ // the cell to the top of the glyph's bounding box.
+ const offset_y: i32 = px_y + @as(i32, @intCast(px_height));
+
+ // This should be the distance from the left of
+ // the cell to the left of the glyph's bounding box.
+ const offset_x: i32 = px_x;
+
+ return Glyph{
+ .width = px_width,
+ .height = px_height,
+ .offset_x = offset_x,
+ .offset_y = offset_y,
+ .atlas_x = region.x,
+ .atlas_y = region.y,
+ };
+ }
+
+ /// Convert 16.6 pixel format to pixels based on the scale factor of the
+ /// current font size.
+ fn unitsToPxY(self: Face, units: i32) i32 {
+ return @intCast(freetype.mulFix(
+ units,
+ @intCast(self.face.handle.*.size.*.metrics.y_scale),
+ ) >> 6);
+ }
+
+ /// Convert 26.6 pixel format to f32
+ fn f26dot6ToFloat(v: freetype.c.FT_F26Dot6) f32 {
+ return @floatFromInt(v >> 6);
+ }
+
+ fn f26dot6ToF64(v: freetype.c.FT_F26Dot6) f64 {
+ return @as(F26Dot6, @bitCast(@as(i32, @intCast(v)))).to(f64);
+ }
+
+ /// Get the `FaceMetrics` for this face.
+ pub fn getMetrics(self: *Face) font.Metrics.FaceMetrics {
+ const face = self.face;
+
+ const size_metrics = face.handle.*.size.*.metrics;
+
+ // This code relies on this assumption, and it should always be
+ // true since we don't do any non-uniform scaling on the font ever.
+ assert(size_metrics.x_ppem == size_metrics.y_ppem);
+
+ // Read the 'head' table out of the font data.
+ const head_ = face.getSfntTable(.head);
+
+ // Read the 'post' table out of the font data.
+ const post_ = face.getSfntTable(.post);
+
+ // Read the 'OS/2' table out of the font data.
+ const os2_: ?*freetype.c.TT_OS2 = os2: {
+ const os2 = face.getSfntTable(.os2) orelse break :os2 null;
+ if (os2.version == 0xFFFF) break :os2 null;
+ break :os2 os2;
+ };
+
+ // Read the 'hhea' table out of the font data.
+ const hhea_ = face.getSfntTable(.hhea);
+
+ // Whether the font is in a scalable format. We need to know this
+ // because many of the metrics provided by FreeType are invalid for
+ // non-scalable fonts.
+ const is_scalable = face.handle.*.face_flags & freetype.c.FT_FACE_FLAG_SCALABLE != 0;
+
+ // We get the UPM from the head table.
+ //
+ // If we have no head, but it is a scalable face, take the UPM from
+ // FreeType's units_per_EM, otherwise we'll assume that UPM == PPEM.
+ const units_per_em: freetype.c.FT_UShort =
+ if (head_) |head|
+ head.Units_Per_EM
+ else if (is_scalable)
+ face.handle.*.units_per_EM
+ else
+ size_metrics.y_ppem;
+ const px_per_em: f64 = @floatFromInt(size_metrics.y_ppem);
+ const px_per_unit = px_per_em / @as(f64, @floatFromInt(units_per_em));
+
+ const ascent: f64, const descent: f64, const line_gap: f64 = vertical_metrics: {
+ const hhea = hhea_ orelse {
+ // If we couldn't get the hhea table, rely on metrics from FreeType.
+ const ascender = f26dot6ToF64(size_metrics.ascender);
+ const descender = f26dot6ToF64(size_metrics.descender);
+ const height = f26dot6ToF64(size_metrics.height);
+ break :vertical_metrics .{
+ ascender,
+ descender,
+ // We compute the line gap by adding the (negative) descender
+ // and subtracting the (positive) ascender from the line height
+ // to get the remaining gap size.
+ //
+ // NOTE: This might always be 0... but it doesn't hurt to do.
+ height + descender - ascender,
+ };
+ };
+
+ const hhea_ascent: f64 = @floatFromInt(hhea.Ascender);
+ const hhea_descent: f64 = @floatFromInt(hhea.Descender);
+ const hhea_line_gap: f64 = @floatFromInt(hhea.Line_Gap);
+
+ // If our font has no OS/2 table, then we just
+ // blindly use the metrics from the hhea table.
+ const os2 = os2_ orelse break :vertical_metrics .{
+ hhea_ascent * px_per_unit,
+ hhea_descent * px_per_unit,
+ hhea_line_gap * px_per_unit,
+ };
+
+ const os2_ascent: f64 = @floatFromInt(os2.sTypoAscender);
+ const os2_descent: f64 = @floatFromInt(os2.sTypoDescender);
+ const os2_line_gap: f64 = @floatFromInt(os2.sTypoLineGap);
+
+ // If the font says to use typo metrics, trust it.
+ // (The USE_TYPO_METRICS bit is bit 7)
+ if (os2.fsSelection & (1 << 7) != 0) {
+ break :vertical_metrics .{
+ os2_ascent * px_per_unit,
+ os2_descent * px_per_unit,
+ os2_line_gap * px_per_unit,
+ };
+ }
+
+ // Otherwise we prefer the height metrics from 'hhea' if they
+ // are available, or else OS/2 sTypo* metrics, and if all else
+ // fails then we use OS/2 usWin* metrics.
+ //
+ // This is not "standard" behavior, but it's our best bet to
+ // account for fonts being... just weird. It's pretty much what
+ // FreeType does to get its generic ascent and descent metrics.
+
+ if (hhea.Ascender != 0 or hhea.Descender != 0) {
+ break :vertical_metrics .{
+ hhea_ascent * px_per_unit,
+ hhea_descent * px_per_unit,
+ hhea_line_gap * px_per_unit,
+ };
+ }
+
+ if (os2_ascent != 0 or os2_descent != 0) {
+ break :vertical_metrics .{
+ os2_ascent * px_per_unit,
+ os2_descent * px_per_unit,
+ os2_line_gap * px_per_unit,
+ };
+ }
+
+ const win_ascent: f64 = @floatFromInt(os2.usWinAscent);
+ const win_descent: f64 = @floatFromInt(os2.usWinDescent);
+ break :vertical_metrics .{
+ win_ascent * px_per_unit,
+ // usWinDescent is *positive* -> down unlike sTypoDescender
+ // and hhea.Descender, so we flip its sign to fix this.
+ -win_descent * px_per_unit,
+ 0.0,
+ };
+ };
+
+ const underline_position: ?f64, const underline_thickness: ?f64 = ul: {
+ const post = post_ orelse break :ul .{ null, null };
+
+ // Some fonts have degenerate 'post' tables where the underline
+ // thickness (and often position) are 0. We consider them null
+ // if this is the case and use our own fallbacks when we calculate.
+ const has_broken_underline = post.underlineThickness == 0;
+
+ // If the underline position isn't 0 then we do use it,
+ // even if the thickness is't properly specified.
+ const pos: ?f64 = if (has_broken_underline and post.underlinePosition == 0)
+ null
+ else
+ @as(f64, @floatFromInt(post.underlinePosition)) * px_per_unit;
+
+ const thick: ?f64 = if (has_broken_underline)
+ null
+ else
+ @as(f64, @floatFromInt(post.underlineThickness)) * px_per_unit;
+
+ break :ul .{ pos, thick };
+ };
+
+ // Similar logic to the underline above.
+ const strikethrough_position, const strikethrough_thickness = st: {
+ const os2 = os2_ orelse break :st .{ null, null };
+
+ const has_broken_strikethrough = os2.yStrikeoutSize == 0;
+
+ const pos: ?f64 = if (has_broken_strikethrough and os2.yStrikeoutPosition == 0)
+ null
+ else
+ @as(f64, @floatFromInt(os2.yStrikeoutPosition)) * px_per_unit;
+
+ const thick: ?f64 = if (has_broken_strikethrough)
+ null
+ else
+ @as(f64, @floatFromInt(os2.yStrikeoutSize)) * px_per_unit;
+
+ break :st .{ pos, thick };
+ };
+
+ // Cell width is calculated by calculating the widest width of the
+ // visible ASCII characters. Usually 'M' is widest but we just take
+ // whatever is widest.
+ //
+ // ASCII height is calculated as the height of the overall bounding
+ // box of the same characters.
+ //
+ // If we fail to load any visible ASCII we just use max_advance from
+ // the metrics provided by FreeType, and set ascii_height to null as
+ // it's optional.
+ const cell_width: f64, const ascii_height: ?f64 = measurements: {
+ self.ft_mutex.lock();
+ defer self.ft_mutex.unlock();
+
+ var max: f64 = 0.0;
+ var top: f64 = 0.0;
+ var bottom: f64 = 0.0;
+ var c: u8 = ' ';
+ while (c < 127) : (c += 1) {
+ if (face.getCharIndex(c)) |glyph_index| {
+ if (face.loadGlyph(glyph_index, self.glyphLoadFlags(false))) {
+ const glyph = face.handle.*.glyph;
+ max = @max(
+ f26dot6ToF64(glyph.*.advance.x),
+ max,
+ );
+ const rect = getGlyphSize(glyph);
+ top = @max(rect.y + rect.height, top);
+ bottom = @min(rect.y, bottom);
+ } else |_| {}
+ }
+ }
+
+ // If we couldn't get valid measurements, just use
+ // FreeType's max_advance and null, respectively.
+ if (max == 0.0) {
+ max = f26dot6ToF64(size_metrics.max_advance);
+ }
+ const rect_height: ?f64 = rect_height: {
+ const estimate = top - bottom;
+ if (estimate <= 0.0) {
+ break :rect_height null;
+ }
+ break :rect_height estimate;
+ };
+
+ break :measurements .{ max, rect_height };
+ };
+
+ // We use the cap and ex heights specified by the font if they're
+ // available, otherwise we try to measure the `H` and `x` glyphs.
+ const cap_height: ?f64, const ex_height: ?f64 = heights: {
+ if (os2_) |os2| {
+ // The OS/2 table does not include these metrics in version 1.
+ if (os2.version >= 2) {
+ break :heights .{
+ @as(f64, @floatFromInt(os2.sCapHeight)) * px_per_unit,
+ @as(f64, @floatFromInt(os2.sxHeight)) * px_per_unit,
+ };
+ }
+ }
+
+ break :heights .{
+ cap: {
+ self.ft_mutex.lock();
+ defer self.ft_mutex.unlock();
+ if (face.getCharIndex('H')) |glyph_index| {
+ if (face.loadGlyph(glyph_index, self.glyphLoadFlags(false))) {
+ break :cap getGlyphSize(face.handle.*.glyph).height;
+ } else |_| {}
+ }
+ break :cap null;
+ },
+ ex: {
+ self.ft_mutex.lock();
+ defer self.ft_mutex.unlock();
+ if (face.getCharIndex('x')) |glyph_index| {
+ if (face.loadGlyph(glyph_index, self.glyphLoadFlags(false))) {
+ break :ex getGlyphSize(face.handle.*.glyph).height;
+ } else |_| {}
+ }
+ break :ex null;
+ },
+ };
+ };
+
+ // Measure "水" (CJK water ideograph, U+6C34) for our ic width.
+ const ic_width: ?f64 = ic_width: {
+ self.ft_mutex.lock();
+ defer self.ft_mutex.unlock();
+
+ const glyph = face.getCharIndex('水') orelse break :ic_width null;
+
+ face.loadGlyph(glyph, self.glyphLoadFlags(false)) catch break :ic_width null;
+
+ const ft_glyph = face.handle.*.glyph;
+
+ // If the advance of the glyph is less than the width of the actual
+ // glyph then we just treat it as invalid since it's probably wrong
+ // and using it for size normalization will instead make the font
+ // way too big.
+ //
+ // This can sometimes happen if there's a CJK font that has been
+ // patched with the nerd fonts patcher and it butchers the advance
+ // values so the advance ends up half the width of the actual glyph.
+ const ft_glyph_width = getGlyphSize(ft_glyph).width;
+ const advance = f26dot6ToF64(ft_glyph.*.advance.x);
+ if (ft_glyph_width > advance) {
+ var buf: [1024]u8 = undefined;
+ const font_name = self.name(&buf) catch "<Error getting font name>";
+ log.warn(
+ "(getMetrics) Width of glyph '水' for font \"{s}\" is greater than its advance ({d} > {d}), discarding ic_width metric.",
+ .{ font_name, ft_glyph_width, advance },
+ );
+ break :ic_width null;
+ }
+
+ break :ic_width advance;
+ };
+
+ return .{
+ .px_per_em = px_per_em,
+
+ .cell_width = cell_width,
+
+ .ascent = ascent,
+ .descent = descent,
+ .line_gap = line_gap,
+
+ .underline_position = underline_position,
+ .underline_thickness = underline_thickness,
+
+ .strikethrough_position = strikethrough_position,
+ .strikethrough_thickness = strikethrough_thickness,
+
+ .cap_height = cap_height,
+ .ex_height = ex_height,
+ .ascii_height = ascii_height,
+ .ic_width = ic_width,
+ };
+ }
+
+ /// Copy the font table data for the given tag.
+ pub fn copyTable(self: Face, alloc: Allocator, tag: *const [4]u8) !?[]u8 {
+ return try self.face.loadSfntTable(alloc, freetype.Tag.init(tag));
+ }
+};
+
+test {
+ const testFont = font.embedded.inconsolata;
+ const alloc = testing.allocator;
+
+ var lib = try Library.init(alloc);
+ defer lib.deinit();
+
+ var atlas = try font.Atlas.init(alloc, 512, .grayscale);
+ defer atlas.deinit(alloc);
+
+ var ft_font = try Face.init(
+ lib,
+ testFont,
+ .{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
+ );
+ defer ft_font.deinit();
+
+ // Generate all visible ASCII
+ var i: u8 = 32;
+ while (i < 127) : (i += 1) {
+ _ = try ft_font.renderGlyph(
+ alloc,
+ &atlas,
+ ft_font.glyphIndex(i).?,
+ .{ .grid_metrics = font.Metrics.calc(ft_font.getMetrics()) },
+ );
+ }
+
+ // Test resizing
+ {
+ const g1 = try ft_font.renderGlyph(
+ alloc,
+ &atlas,
+ ft_font.glyphIndex('A').?,
+ .{ .grid_metrics = font.Metrics.calc(ft_font.getMetrics()) },
+ );
+ try testing.expectEqual(@as(u32, 11), g1.height);
+
+ try ft_font.setSize(.{ .size = .{ .points = 24, .xdpi = 96, .ydpi = 96 } });
+ const g2 = try ft_font.renderGlyph(
+ alloc,
+ &atlas,
+ ft_font.glyphIndex('A').?,
+ .{ .grid_metrics = font.Metrics.calc(ft_font.getMetrics()) },
+ );
+ try testing.expectEqual(@as(u32, 21), g2.height);
+ }
+}
+
+test "color emoji" {
+ const alloc = testing.allocator;
+ const testFont = font.embedded.emoji;
+
+ var lib = try Library.init(alloc);
+ defer lib.deinit();
+
+ var atlas = try font.Atlas.init(alloc, 512, .bgra);
+ defer atlas.deinit(alloc);
+
+ var ft_font = try Face.init(
+ lib,
+ testFont,
+ .{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
+ );
+ defer ft_font.deinit();
+
+ _ = try ft_font.renderGlyph(
+ alloc,
+ &atlas,
+ ft_font.glyphIndex('🥸').?,
+ .{ .grid_metrics = font.Metrics.calc(ft_font.getMetrics()) },
+ );
+
+ // Make sure this glyph has color
+ {
+ try testing.expect(ft_font.hasColor());
+ const glyph_id = ft_font.glyphIndex('🥸').?;
+ try testing.expect(ft_font.isColorGlyph(glyph_id));
+ }
+}
+
+test "mono to bgra" {
+ const alloc = testing.allocator;
+ const testFont = font.embedded.emoji;
+
+ var lib = try Library.init(alloc);
+ defer lib.deinit();
+
+ var atlas = try font.Atlas.init(alloc, 512, .bgra);
+ defer atlas.deinit(alloc);
+
+ var ft_font = try Face.init(lib, testFont, .{ .size = .{ .points = 12, .xdpi = 72, .ydpi = 72 } });
+ defer ft_font.deinit();
+
+ // glyph 3 is mono in Noto
+ _ = try ft_font.renderGlyph(
+ alloc,
+ &atlas,
+ 3,
+ .{ .grid_metrics = font.Metrics.calc(ft_font.getMetrics()) },
+ );
+}
+
+test "svg font table" {
+ const alloc = testing.allocator;
+ const testFont = font.embedded.julia_mono;
+
+ var lib = try font.Library.init(alloc);
+ defer lib.deinit();
+
+ var face = try Face.init(lib, testFont, .{ .size = .{ .points = 12, .xdpi = 72, .ydpi = 72 } });
+ defer face.deinit();
+
+ const table = (try face.copyTable(alloc, "SVG ")).?;
+ defer alloc.free(table);
+
+ try testing.expectEqual(430, table.len);
+}
+
+const terminus_i =
+ \\........
+ \\........
+ \\...#....
+ \\...#....
+ \\........
+ \\..##....
+ \\...#....
+ \\...#....
+ \\...#....
+ \\...#....
+ \\...#....
+ \\..###...
+ \\........
+ \\........
+ \\........
+ \\........
+;
+// Including the newline
+const terminus_i_pitch = 9;
+
+test "bitmap glyph" {
+ const alloc = testing.allocator;
+ const testFont = font.embedded.terminus_ttf;
+
+ var lib = try Library.init(alloc);
+ defer lib.deinit();
+
+ var atlas = try font.Atlas.init(alloc, 512, .grayscale);
+ defer atlas.deinit(alloc);
+
+ // Any glyph at 12pt @ 96 DPI is a bitmap
+ var ft_font = try Face.init(lib, testFont, .{ .size = .{
+ .points = 12,
+ .xdpi = 96,
+ .ydpi = 96,
+ } });
+ defer ft_font.deinit();
+
+ // glyph 77 = 'i'
+ const glyph = try ft_font.renderGlyph(
+ alloc,
+ &atlas,
+ 77,
+ .{ .grid_metrics = font.Metrics.calc(ft_font.getMetrics()) },
+ );
+
+ // should render crisp
+ try testing.expectEqual(8, glyph.width);
+ try testing.expectEqual(16, glyph.height);
+ for (0..glyph.height) |y| {
+ for (0..glyph.width) |x| {
+ const pixel = terminus_i[y * terminus_i_pitch + x];
+ try testing.expectEqual(
+ @as(u8, if (pixel == '#') 255 else 0),
+ atlas.data[(glyph.atlas_y + y) * atlas.size + (glyph.atlas_x + x)],
+ );
+ }
+ }
+}
+
+// Expected pixel pattern for Spleen 8x16 'A' (glyph index from char 'A')
+// Derived from BDF BITMAP data: 00,00,7C,C6,C6,C6,FE,C6,C6,C6,C6,C6,00,00,00,00
+const spleen_A =
+ \\........
+ \\........
+ \\.#####..
+ \\##...##.
+ \\##...##.
+ \\##...##.
+ \\#######.
+ \\##...##.
+ \\##...##.
+ \\##...##.
+ \\##...##.
+ \\##...##.
+ \\........
+ \\........
+ \\........
+ \\........
+;
+// Including the newline
+const spleen_A_pitch = 9;
+// Test parameters for bitmap font tests
+const spleen_test_point_size = 12;
+const spleen_test_dpi = 96;
+
+test "bitmap glyph BDF" {
+ const alloc = testing.allocator;
+ const testFont = font.embedded.spleen_bdf;
+
+ var lib = try Library.init(alloc);
+ defer lib.deinit();
+
+ var atlas = try font.Atlas.init(alloc, 512, .grayscale);
+ defer atlas.deinit(alloc);
+
+ // Spleen 8x16 is a pure bitmap font at 16px height
+ var ft_font = try Face.init(lib, testFont, .{ .size = .{
+ .points = spleen_test_point_size,
+ .xdpi = spleen_test_dpi,
+ .ydpi = spleen_test_dpi,
+ } });
+ defer ft_font.deinit();
+
+ // Get glyph index for 'A'
+ const glyph_index = ft_font.glyphIndex('A') orelse return error.GlyphNotFound;
+
+ const glyph = try ft_font.renderGlyph(
+ alloc,
+ &atlas,
+ glyph_index,
+ .{ .grid_metrics = font.Metrics.calc(ft_font.getMetrics()) },
+ );
+
+ // Verify dimensions match Spleen 8x16
+ try testing.expectEqual(8, glyph.width);
+ try testing.expectEqual(16, glyph.height);
+
+ // Verify pixel-perfect rendering
+ for (0..glyph.height) |y| {
+ for (0..glyph.width) |x| {
+ const pixel = spleen_A[y * spleen_A_pitch + x];
+ try testing.expectEqual(
+ @as(u8, if (pixel == '#') 255 else 0),
+ atlas.data[(glyph.atlas_y + y) * atlas.size + (glyph.atlas_x + x)],
+ );
+ }
+ }
+}
+
+test "bitmap glyph PCF" {
+ const alloc = testing.allocator;
+ const testFont = font.embedded.spleen_pcf;
+
+ var lib = try Library.init(alloc);
+ defer lib.deinit();
+
+ var atlas = try font.Atlas.init(alloc, 512, .grayscale);
+ defer atlas.deinit(alloc);
+
+ var ft_font = try Face.init(lib, testFont, .{ .size = .{
+ .points = spleen_test_point_size,
+ .xdpi = spleen_test_dpi,
+ .ydpi = spleen_test_dpi,
+ } });
+ defer ft_font.deinit();
+
+ const glyph_index = ft_font.glyphIndex('A') orelse return error.GlyphNotFound;
+
+ const glyph = try ft_font.renderGlyph(
+ alloc,
+ &atlas,
+ glyph_index,
+ .{ .grid_metrics = font.Metrics.calc(ft_font.getMetrics()) },
+ );
+
+ try testing.expectEqual(8, glyph.width);
+ try testing.expectEqual(16, glyph.height);
+
+ for (0..glyph.height) |y| {
+ for (0..glyph.width) |x| {
+ const pixel = spleen_A[y * spleen_A_pitch + x];
+ try testing.expectEqual(
+ @as(u8, if (pixel == '#') 255 else 0),
+ atlas.data[(glyph.atlas_y + y) * atlas.size + (glyph.atlas_x + x)],
+ );
+ }
+ }
+}
+
+test "bitmap glyph OTB" {
+ const alloc = testing.allocator;
+ const testFont = font.embedded.spleen_otb;
+
+ var lib = try Library.init(alloc);
+ defer lib.deinit();
+
+ var atlas = try font.Atlas.init(alloc, 512, .grayscale);
+ defer atlas.deinit(alloc);
+
+ var ft_font = try Face.init(lib, testFont, .{ .size = .{
+ .points = spleen_test_point_size,
+ .xdpi = spleen_test_dpi,
+ .ydpi = spleen_test_dpi,
+ } });
+ defer ft_font.deinit();
+
+ const glyph_index = ft_font.glyphIndex('A') orelse return error.GlyphNotFound;
+
+ const glyph = try ft_font.renderGlyph(
+ alloc,
+ &atlas,
+ glyph_index,
+ .{ .grid_metrics = font.Metrics.calc(ft_font.getMetrics()) },
+ );
+
+ try testing.expectEqual(8, glyph.width);
+ try testing.expectEqual(16, glyph.height);
+
+ for (0..glyph.height) |y| {
+ for (0..glyph.width) |x| {
+ const pixel = spleen_A[y * spleen_A_pitch + x];
+ try testing.expectEqual(
+ @as(u8, if (pixel == '#') 255 else 0),
+ atlas.data[(glyph.atlas_y + y) * atlas.size + (glyph.atlas_x + x)],
+ );
+ }
+ }
+}
diff --git a/vendor/libghostty-vt/src/font/face/web_canvas.zig b/vendor/libghostty-vt/src/font/face/web_canvas.zig
new file mode 100644
index 00000000..b4f9f5d5
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/face/web_canvas.zig
@@ -0,0 +1,561 @@
+const std = @import("std");
+const assert = @import("../../quirks.zig").inlineAssert;
+const testing = std.testing;
+const Allocator = std.mem.Allocator;
+const js = @import("zig-js");
+const font = @import("../main.zig");
+
+const log = std.log.scoped(.font_face);
+
+pub const Face = struct {
+ /// See graphemes field for more details.
+ const grapheme_start: u32 = 0x10FFFF + 1;
+ const grapheme_end: u32 = std.math.maxInt(u32);
+
+ /// The web canvas face makes use of an allocator when interacting
+ /// with the JS environment.
+ alloc: Allocator,
+
+ /// The CSS "font" attribute, excluding size.
+ font_str: []const u8,
+
+ /// The size we currently have set.
+ size: font.face.DesiredSize,
+
+ /// The presentation for this font.
+ presentation: font.Presentation,
+
+ /// Metrics for this font face. These are useful for renderers.
+ metrics: font.Metrics,
+
+ /// The canvas element that we will reuse to render glyphs
+ canvas: js.Object,
+
+ /// The map to store multi-codepoint grapheme clusters that are rendered.
+ /// We use 1 above the maximum unicode codepoint up to the max 32-bit
+ /// unsigned integer to store the "glyph index" for graphemes.
+ grapheme_to_glyph: std.StringHashMapUnmanaged(u32) = .{},
+ glyph_to_grapheme: std.AutoHashMapUnmanaged(u32, []u8) = .{},
+ grapheme_next: u32 = grapheme_start,
+
+ /// Initialize a web canvas font with a "raw" value. The "raw" value can
+ /// be any valid value for a CSS "font" property EXCLUDING the size. The
+ /// size is always added via the `size` parameter.
+ ///
+ /// The raw value is copied so the caller can free it after it is gone.
+ ///
+ /// The presentation is given here directly because the browser gives
+ /// us no easy way to determine the presentation we want for this font.
+ /// Callers should just tell us what to expect.
+ pub fn initNamed(
+ alloc: Allocator,
+ raw: []const u8,
+ size: font.face.DesiredSize,
+ presentation: font.Presentation,
+ ) !Face {
+ // Copy our font string because we're going to have to reuse it.
+ const font_str = try alloc.dupe(u8, raw);
+ errdefer alloc.free(font_str);
+
+ // Create our canvas that we're going to continue to reuse.
+ const doc = try js.global.get(js.Object, "document");
+ defer doc.deinit();
+ const canvas = try doc.call(js.Object, "createElement", .{js.string("canvas")});
+ errdefer canvas.deinit();
+
+ var result = Face{
+ .alloc = alloc,
+ .font_str = font_str,
+ .size = size,
+ .presentation = presentation,
+
+ .canvas = canvas,
+
+ // We're going to calculate these right after initialization.
+ .metrics = undefined,
+ };
+ try result.calcMetrics();
+
+ log.debug("face initialized: {s}", .{raw});
+ return result;
+ }
+
+ pub fn deinit(self: *Face) void {
+ self.alloc.free(self.font_str);
+ self.grapheme_to_glyph.deinit(self.alloc);
+ {
+ var it = self.glyph_to_grapheme.valueIterator();
+ while (it.next()) |value| self.alloc.free(value.*);
+ self.glyph_to_grapheme.deinit(self.alloc);
+ }
+ self.canvas.deinit();
+ self.* = undefined;
+ }
+
+ /// Resize the font in-place. If this succeeds, the caller is responsible
+ /// for clearing any glyph caches, font atlas data, etc.
+ pub fn setSize(self: *Face, size: font.face.DesiredSize) !void {
+ const old = self.size;
+ self.size = size;
+ errdefer self.size = old;
+ try self.calcMetrics();
+ }
+
+ /// Returns the glyph index for the given Unicode code point. For canvas,
+ /// we support every glyph and the ID is just the codepoint since we don't
+ /// have access to the underlying tables anyways. We let the browser deal
+ /// with bad codepoints.
+ pub fn glyphIndex(self: Face, cp: u32) ?u32 {
+ // If this is a multi-codepoint grapheme then we only check if
+ // we actually know about it.
+ if (cp >= grapheme_start) {
+ if (!self.glyph_to_grapheme.contains(cp)) return null;
+ }
+
+ // Render the glyph to determine if it is colored or not. We
+ // have to do this because the browser will always try to render
+ // whatever we give it and we have no API to determine color.
+ //
+ // We don't want to say yes to the wrong presentation because
+ // it will go into the wrong Atlas.
+ const p: font.Presentation = if (cp <= 255) .text else p: {
+ break :p self.glyphPresentation(cp) catch {
+ // In this case, we assume we are unable to render
+ // this glyph and therefore just say we don't support it.
+ return null;
+ };
+ };
+ if (p != self.presentation) return null;
+
+ return cp;
+ }
+
+ /// This determines the presentation of the glyph by literally
+ /// inspecting the image data to look for any color. This isn't
+ /// super performant but we don't have a better choice given the
+ /// canvas APIs.
+ fn glyphPresentation(
+ self: Face,
+ cp: u32,
+ ) !font.Presentation {
+ // Render the glyph
+ var render = try self.renderGlyphInternal(self.alloc, cp);
+ defer render.deinit();
+
+ // Inspect the image data for any non-zeros in the RGB value.
+ // NOTE(perf): this is an easy candidate for SIMD.
+ var i: usize = 0;
+ while (i < render.bitmap.len) : (i += 4) {
+ if (render.bitmap[i] > 0 or
+ render.bitmap[i + 1] > 0 or
+ render.bitmap[i + 2] > 0) return .emoji;
+ }
+
+ return .text;
+ }
+
+ /// Returns the glyph index for the given grapheme cluster. The same
+ /// cluster will always map to the same glyph index. This does not render
+ /// the grapheme at this time, only reserves the index.
+ pub fn graphemeGlyphIndex(self: *Face, cluster: []const u8) error{OutOfMemory}!u32 {
+ // If we already have this stored then return it
+ const gop = try self.grapheme_to_glyph.getOrPut(self.alloc, cluster);
+ if (gop.found_existing) return gop.value_ptr.*;
+ errdefer _ = self.grapheme_to_glyph.remove(cluster);
+
+ // We don't have it stored. Ensure we have space to store. The
+ // next will be "0" if we're out of space due to unsigned int wrapping.
+ if (self.grapheme_next == 0) return error.OutOfMemory;
+
+ // Copy the cluster for our reverse mapping
+ const copy = try self.alloc.dupe(u8, cluster);
+ errdefer self.alloc.free(copy);
+
+ // Grow space for the reverse mapping
+ try self.glyph_to_grapheme.ensureUnusedCapacity(self.alloc, 1);
+
+ // Store it
+ gop.value_ptr.* = self.grapheme_next;
+ self.glyph_to_grapheme.putAssumeCapacity(self.grapheme_next, copy);
+
+ self.grapheme_next +%= 1;
+ return gop.value_ptr.*;
+ }
+
+ /// Render a glyph using the glyph index. The rendered glyph is stored
+ /// in the given texture atlas.
+ pub fn renderGlyph(
+ self: Face,
+ alloc: Allocator,
+ atlas: *font.Atlas,
+ glyph_index: u32,
+ opts: font.face.RenderOptions,
+ ) !font.Glyph {
+ _ = opts;
+
+ var render = try self.renderGlyphInternal(alloc, glyph_index);
+ defer render.deinit();
+
+ // Convert the format of the bitmap if necessary
+ const bitmap_formatted: []u8 = switch (atlas.format) {
+ // Bitmap is already in RGBA
+ .rgba => render.bitmap,
+
+ // Convert down to A8
+ .grayscale => a8: {
+ assert(@mod(render.bitmap.len, 4) == 0);
+ var bitmap_a8 = try alloc.alloc(u8, render.bitmap.len / 4);
+ errdefer alloc.free(bitmap_a8);
+ var i: usize = 0;
+ while (i < bitmap_a8.len) : (i += 1) {
+ bitmap_a8[i] = render.bitmap[(i * 4) + 3];
+ }
+
+ break :a8 bitmap_a8;
+ },
+
+ else => return error.UnsupportedAtlasFormat,
+ };
+ defer if (bitmap_formatted.ptr != render.bitmap.ptr) {
+ alloc.free(bitmap_formatted);
+ };
+
+ // Put it in our atlas
+ const region = try atlas.reserve(alloc, render.width, render.height);
+ if (region.width > 0 and region.height > 0) {
+ atlas.set(region, bitmap_formatted);
+ }
+
+ return font.Glyph{
+ .width = render.width,
+ .height = render.height,
+ // TODO: this can't be right
+ .offset_x = 0,
+ .offset_y = 0,
+ .atlas_x = region.x,
+ .atlas_y = region.y,
+ };
+ }
+
+ /// Calculate the metrics associated with a given face.
+ fn calcMetrics(self: *Face) !void {
+ const ctx = try self.context();
+ defer ctx.deinit();
+
+ // Cell width is the width of our M text
+ const cell_width: f32 = cell_width: {
+ const metrics = try ctx.call(js.Object, "measureText", .{js.string("M")});
+ defer metrics.deinit();
+
+ // We prefer the bounding box since it is tighter but certain
+ // text such as emoji do not have a bounding box set so we use
+ // the full run width instead.
+ const bounding_right = try metrics.get(f32, "actualBoundingBoxRight");
+ if (bounding_right > 0) break :cell_width bounding_right;
+ break :cell_width try metrics.get(f32, "width");
+ };
+
+ // To get the cell height we render a high and low character and get
+ // the total of the ascent and descent. This should equal our
+ // pixel height but this is a more surefire way to get it.
+ const height_metrics = try ctx.call(js.Object, "measureText", .{js.string("M_")});
+ defer height_metrics.deinit();
+ const asc = try height_metrics.get(f32, "actualBoundingBoxAscent");
+ const desc = try height_metrics.get(f32, "actualBoundingBoxDescent");
+ const cell_height = asc + desc;
+ const cell_baseline = desc;
+
+ // There isn't a declared underline position for canvas measurements
+ // so we just go 1 under the cell height to match freetype logic
+ // at this time (our freetype logic).
+ const underline_position = cell_height - 1;
+ const underline_thickness: f32 = 1;
+
+ const result = font.Metrics{
+ .cell_width = @intFromFloat(cell_width),
+ .cell_height = @intFromFloat(cell_height),
+ .cell_baseline = @intFromFloat(cell_baseline),
+ .underline_position = @intFromFloat(underline_position),
+ .underline_thickness = @intFromFloat(underline_thickness),
+ .strikethrough_position = @intFromFloat(underline_position),
+ .strikethrough_thickness = @intFromFloat(underline_thickness),
+ };
+
+ self.metrics = result;
+ log.debug("metrics font={s} value={}", .{ self.font_str, self.metrics });
+ }
+
+ /// Returns the 2d context configured for drawing
+ fn context(self: Face) !js.Object {
+ // This will return the same context on subsequent calls so it
+ // is important to reset it.
+ const ctx = try self.canvas.call(js.Object, "getContext", .{js.string("2d")});
+ errdefer ctx.deinit();
+
+ // Clear the canvas
+ {
+ const width = try self.canvas.get(f64, "width");
+ const height = try self.canvas.get(f64, "height");
+ try ctx.call(void, "clearRect", .{ 0, 0, width, height });
+ }
+
+ // Set our context font
+ const font_val = try std.fmt.allocPrint(
+ self.alloc,
+ "{d}px {s}",
+ .{ self.size.points, self.font_str },
+ );
+ defer self.alloc.free(font_val);
+ try ctx.set("font", js.string(font_val));
+
+ // If the font property didn't change, then the font set didn't work.
+ // We do this check because it is very easy to put an invalid font
+ // in and this at least makes it show up in the logs.
+ const check = try ctx.getAlloc(js.String, self.alloc, "font");
+ defer self.alloc.free(check);
+ if (!std.mem.eql(u8, font_val, check)) {
+ log.warn("canvas font didn't set, fonts may be broken, expected={s} got={s}", .{
+ font_val,
+ check,
+ });
+ }
+
+ return ctx;
+ }
+
+ /// An internal (web-canvas-only) format for rendered glyphs
+ /// since we do render passes in multiple different situations.
+ const RenderedGlyph = struct {
+ alloc: Allocator,
+ metrics: js.Object,
+ width: u32,
+ height: u32,
+ bitmap: []u8,
+
+ pub fn deinit(self: *RenderedGlyph) void {
+ self.metrics.deinit();
+ self.alloc.free(self.bitmap);
+ self.* = undefined;
+ }
+ };
+
+ /// Shared logic for rendering a glyph.
+ fn renderGlyphInternal(
+ self: Face,
+ alloc: Allocator,
+ glyph_index: u32,
+ ) !RenderedGlyph {
+ // Encode our glyph to UTF-8 so we can build a JS string out of it.
+ var utf8: [4]u8 = undefined;
+ const glyph_str = glyph_str: {
+ // If we are a normal glyph then we are a single codepoint and
+ // we just UTF8 encode it as-is.
+ if (glyph_index < grapheme_start) {
+ const utf8_len = try std.unicode.utf8Encode(@intCast(glyph_index), &utf8);
+ break :glyph_str js.string(utf8[0..utf8_len]);
+ }
+
+ // We are a multi-codepoint glyph so we have to read the glyph
+ // from the map and it is already utf8 encoded.
+ const slice = self.glyph_to_grapheme.get(glyph_index) orelse
+ return error.UnknownGraphemeCluster;
+ break :glyph_str js.string(slice);
+ };
+
+ // Get our drawing context
+ const measure_ctx = try self.context();
+ defer measure_ctx.deinit();
+
+ // Get the width and height of the render
+ const metrics = try measure_ctx.call(js.Object, "measureText", .{glyph_str});
+ errdefer metrics.deinit();
+ const width: u32 = @as(u32, @intFromFloat(@ceil(width: {
+ // We prefer the bounding box since it is tighter but certain
+ // text such as emoji do not have a bounding box set so we use
+ // the full run width instead.
+ const bounding_right = try metrics.get(f32, "actualBoundingBoxRight");
+ if (bounding_right > 0) break :width bounding_right;
+ break :width try metrics.get(f32, "width");
+ }))) + 1;
+
+ const left = try metrics.get(f32, "actualBoundingBoxLeft");
+ const asc = try metrics.get(f32, "actualBoundingBoxAscent");
+ const desc = try metrics.get(f32, "actualBoundingBoxDescent");
+
+ // On Firefox on Linux, the bounding box is broken in some cases for
+ // ideographic glyphs (such as emoji). We detect this and behave
+ // differently.
+ const broken_bbox = asc + desc < 0.001;
+
+ // Height is our ascender + descender for this char
+ const height = if (!broken_bbox) @as(u32, @intFromFloat(@ceil(asc + desc))) + 1 else width;
+
+ // Note: width and height both get "+ 1" added to them above. This
+ // is important so that there is a 1px border around the glyph to avoid
+ // any clipping in the atlas.
+
+ // Resize canvas to match the glyph size exactly
+ {
+ try self.canvas.set("width", width);
+ try self.canvas.set("height", height);
+
+ const width_str = try std.fmt.allocPrint(alloc, "{d}px", .{width});
+ defer alloc.free(width_str);
+ const height_str = try std.fmt.allocPrint(alloc, "{d}px", .{height});
+ defer alloc.free(height_str);
+
+ const style = try self.canvas.get(js.Object, "style");
+ defer style.deinit();
+ try style.set("width", js.string(width_str));
+ try style.set("height", js.string(height_str));
+ }
+
+ // Reload our context since we resized the canvas
+ const ctx = try self.context();
+ defer ctx.deinit();
+
+ // For the broken bounding box case we render ideographic baselines
+ // so that we just render the glyph fully in the box with no offsets.
+ if (broken_bbox) {
+ try ctx.set("textBaseline", js.string("ideographic"));
+ }
+
+ // Draw background
+ try ctx.set("fillStyle", js.string("transparent"));
+ try ctx.call(void, "fillRect", .{
+ 0,
+ 0,
+ width,
+ height,
+ });
+
+ // Draw glyph
+ try ctx.set("fillStyle", js.string("black"));
+ try ctx.call(void, "fillText", .{
+ glyph_str,
+ left + 1,
+ if (!broken_bbox) asc + 1 else @as(f32, @floatFromInt(height)),
+ });
+
+ // Read the image data and get it into a []u8 on our side
+ const bitmap: []u8 = bitmap: {
+ // Read the raw bitmap data and get the "data" value which is a
+ // Uint8ClampedArray.
+ const data = try ctx.call(js.Object, "getImageData", .{ 0, 0, width, height });
+ defer data.deinit();
+ const src_array = try data.get(js.Object, "data");
+ defer src_array.deinit();
+
+ // Allocate our local memory to copy the data to.
+ const len = try src_array.get(u32, "length");
+ const bitmap = try alloc.alloc(u8, @intCast(len));
+ errdefer alloc.free(bitmap);
+
+ // Create our target Uint8Array that we can use to copy from src.
+ const mem_array = mem_array: {
+ // Get our runtime memory
+ const mem = try js.runtime.get(js.Object, "memory");
+ defer mem.deinit();
+ const buf = try mem.get(js.Object, "buffer");
+ defer buf.deinit();
+
+ // Construct our array to peer into our memory
+ const Uint8Array = try js.global.get(js.Object, "Uint8Array");
+ defer Uint8Array.deinit();
+ const mem_array = try Uint8Array.new(.{ buf, bitmap.ptr });
+ errdefer mem_array.deinit();
+
+ break :mem_array mem_array;
+ };
+ defer mem_array.deinit();
+
+ // Copy
+ try mem_array.call(void, "set", .{src_array});
+
+ break :bitmap bitmap;
+ };
+ errdefer alloc.free(bitmap);
+
+ return RenderedGlyph{
+ .alloc = alloc,
+ .metrics = metrics,
+ .width = width,
+ .height = height,
+ .bitmap = bitmap,
+ };
+ }
+};
+
+/// The wasm-compatible API.
+pub const Wasm = struct {
+ const wasm = @import("../../os/wasm.zig");
+ const alloc = wasm.alloc;
+
+ export fn face_new(ptr: [*]const u8, len: usize, pts: u16, p: u16) ?*Face {
+ return face_new_(ptr, len, pts, p) catch null;
+ }
+
+ fn face_new_(ptr: [*]const u8, len: usize, pts: f32, presentation: u16) !*Face {
+ var face = try Face.initNamed(
+ alloc,
+ ptr[0..len],
+ .{ .points = pts },
+ @enumFromInt(presentation),
+ );
+ errdefer face.deinit();
+
+ const result = try alloc.create(Face);
+ errdefer alloc.destroy(result);
+ result.* = face;
+ return result;
+ }
+
+ export fn face_free(ptr: ?*Face) void {
+ if (ptr) |v| {
+ v.deinit();
+ alloc.destroy(v);
+ }
+ }
+
+ /// Resulting pointer must be freed using the global "free".
+ export fn face_render_glyph(
+ face: *Face,
+ atlas: *font.Atlas,
+ codepoint: u32,
+ ) ?*font.Glyph {
+ return face_render_glyph_(face, atlas, codepoint) catch |err| {
+ log.warn("error rendering glyph err={}", .{err});
+ return null;
+ };
+ }
+
+ export fn face_debug_canvas(face: *Face) void {
+ face_debug_canvas_(face) catch |err| {
+ log.warn("error adding debug canvas err={}", .{err});
+ };
+ }
+
+ fn face_debug_canvas_(face: *Face) !void {
+ const doc = try js.global.get(js.Object, "document");
+ defer doc.deinit();
+
+ const elem = try doc.call(
+ ?js.Object,
+ "getElementById",
+ .{js.string("face-canvas")},
+ ) orelse return error.CanvasContainerNotFound;
+ defer elem.deinit();
+
+ try elem.call(void, "append", .{face.canvas});
+ }
+
+ fn face_render_glyph_(face: *Face, atlas: *font.Atlas, codepoint: u32) !*font.Glyph {
+ const glyph = try face.renderGlyph(alloc, atlas, codepoint, .{});
+
+ const result = try alloc.create(font.Glyph);
+ errdefer alloc.destroy(result);
+ _ = try wasm.toHostOwned(result);
+ result.* = glyph;
+ return result;
+ }
+};
diff --git a/vendor/libghostty-vt/src/font/library.zig b/vendor/libghostty-vt/src/font/library.zig
new file mode 100644
index 00000000..dce6dbd5
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/library.zig
@@ -0,0 +1,63 @@
+//! A library represents the shared state that the underlying font
+//! library implementation(s) require per-process.
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const options = @import("main.zig").options;
+const freetype = @import("freetype");
+const font = @import("main.zig");
+
+/// Library implementation for the compile options.
+pub const Library = switch (options.backend) {
+ // Freetype requires a state library
+ .freetype,
+ .fontconfig_freetype,
+ .coretext_freetype,
+ => FreetypeLibrary,
+
+ // Some backends such as CT and Canvas don't have a "library"
+ .coretext,
+ .coretext_harfbuzz,
+ .coretext_noshape,
+ .web_canvas,
+ => NoopLibrary,
+};
+
+pub const FreetypeLibrary = struct {
+ lib: freetype.Library,
+
+ alloc: Allocator,
+
+ /// Mutex to be held any time the library is
+ /// being used to create or destroy a face.
+ mutex: *std.Thread.Mutex,
+
+ pub const InitError = freetype.Error || Allocator.Error;
+
+ pub fn init(alloc: Allocator) InitError!Library {
+ const lib = try freetype.Library.init();
+ errdefer lib.deinit();
+
+ const mutex = try alloc.create(std.Thread.Mutex);
+ mutex.* = .{};
+
+ return Library{ .lib = lib, .alloc = alloc, .mutex = mutex };
+ }
+
+ pub fn deinit(self: *Library) void {
+ self.alloc.destroy(self.mutex);
+ self.lib.deinit();
+ }
+};
+
+pub const NoopLibrary = struct {
+ pub const InitError = error{};
+
+ pub fn init(alloc: Allocator) InitError!Library {
+ _ = alloc;
+ return Library{};
+ }
+
+ pub fn deinit(self: *Library) void {
+ _ = self;
+ }
+};
diff --git a/vendor/libghostty-vt/src/font/main.zig b/vendor/libghostty-vt/src/font/main.zig
new file mode 100644
index 00000000..a8522afe
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/main.zig
@@ -0,0 +1,82 @@
+const std = @import("std");
+const builtin = @import("builtin");
+const build_config = @import("../build_config.zig");
+
+const library = @import("library.zig");
+
+pub const Atlas = @import("Atlas.zig");
+pub const Backend = @import("backend.zig").Backend;
+pub const discovery = @import("discovery.zig");
+pub const embedded = @import("embedded.zig");
+pub const face = @import("face.zig");
+pub const CodepointMap = @import("CodepointMap.zig");
+pub const CodepointResolver = @import("CodepointResolver.zig");
+pub const Collection = @import("Collection.zig");
+pub const DeferredFace = @import("DeferredFace.zig");
+pub const Face = face.Face;
+pub const Glyph = @import("Glyph.zig");
+pub const Metrics = @import("Metrics.zig");
+pub const opentype = @import("opentype.zig");
+pub const shape = @import("shape.zig");
+pub const Shaper = shape.Shaper;
+pub const ShaperCache = shape.Cache;
+pub const SharedGrid = @import("SharedGrid.zig");
+pub const SharedGridSet = @import("SharedGridSet.zig");
+pub const sprite = @import("sprite.zig");
+pub const Sprite = sprite.Sprite;
+pub const SpriteFace = sprite.Face;
+pub const Descriptor = discovery.Descriptor;
+pub const Discover = discovery.Discover;
+pub const Library = library.Library;
+
+// If we're targeting wasm then we export some wasm APIs.
+comptime {
+ if (builtin.target.cpu.arch.isWasm()) {
+ _ = Atlas.Wasm;
+ _ = DeferredFace.Wasm;
+ _ = face.web_canvas.Wasm;
+ _ = shape.web_canvas.Wasm;
+ }
+}
+
+/// Build options
+pub const options: struct {
+ backend: Backend,
+} = .{
+ // TODO: we need to modify the build config for wasm builds. the issue
+ // is we're sharing the build config options between all exes in build.zig.
+ // We need to construct it per target.
+ .backend = if (builtin.target.cpu.arch.isWasm()) .web_canvas else build_config.font_backend,
+};
+
+/// The styles that a family can take.
+pub const Style = enum(u3) {
+ regular = 0,
+ bold = 1,
+ italic = 2,
+ bold_italic = 3,
+};
+
+/// The presentation for an emoji.
+pub const Presentation = enum(u1) {
+ text = 0, // U+FE0E
+ emoji = 1, // U+FEOF
+};
+
+/// A FontIndex that can be used to use the sprite font directly.
+pub const sprite_index = Collection.Index.initSpecial(.sprite);
+
+/// The default font size adjustment we use when loading fallback fonts.
+///
+/// TODO: Add user configuration for this instead of hard-coding it.
+pub const default_fallback_adjustment: Collection.SizeAdjustment = .ic_width;
+
+test {
+ // For non-wasm we want to test everything we can
+ if (!comptime builtin.target.cpu.arch.isWasm()) {
+ @import("std").testing.refAllDecls(@This());
+ return;
+ }
+
+ _ = Atlas;
+}
diff --git a/vendor/libghostty-vt/src/font/nerd_font_attributes.zig b/vendor/libghostty-vt/src/font/nerd_font_attributes.zig
new file mode 100644
index 00000000..f4a19d96
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/nerd_font_attributes.zig
@@ -0,0 +1,2774 @@
+//! This is a generated file, produced by nerd_font_codegen.py
+//! DO NOT EDIT BY HAND!
+//!
+//! This file provides info extracted from the nerd fonts patcher script,
+//! specifying the scaling/positioning attributes of various glyphs.
+
+const Constraint = @import("face.zig").RenderOptions.Constraint;
+
+/// Get the constraints for the provided codepoint.
+pub fn getConstraint(cp: u21) ?Constraint {
+ return switch (cp) {
+ 0x2630,
+ => .{
+ .size = .cover,
+ .height = .icon,
+ .max_constraint_width = 1,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .pad_left = 0.05,
+ .pad_right = 0.05,
+ .pad_top = 0.05,
+ .pad_bottom = 0.05,
+ },
+ 0x276c...0x276d,
+ => .{
+ .size = .cover,
+ .max_constraint_width = 1,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.7142857142857143,
+ .relative_height = 0.8910614525139665,
+ .relative_x = 0.1428571428571428,
+ .relative_y = 0.0349162011173184,
+ .pad_top = 0.15,
+ .pad_bottom = 0.15,
+ },
+ 0x276e...0x276f,
+ => .{
+ .size = .cover,
+ .max_constraint_width = 1,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.9885714285714285,
+ .relative_height = 0.8910614525139665,
+ .relative_x = 0.0057142857142857,
+ .relative_y = 0.0125698324022346,
+ .pad_top = 0.15,
+ .pad_bottom = 0.15,
+ },
+ 0x2770...0x2771,
+ => .{
+ .size = .cover,
+ .max_constraint_width = 1,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .pad_top = 0.15,
+ .pad_bottom = 0.15,
+ },
+ 0xe0a0...0xe0a3,
+ 0xe0cf,
+ => .{
+ .size = .fit_cover1,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ },
+ 0xe0b0,
+ => .{
+ .size = .stretch,
+ .max_constraint_width = 1,
+ .align_horizontal = .start,
+ .align_vertical = .center1,
+ .pad_left = -0.03,
+ .pad_right = -0.03,
+ .pad_top = -0.005,
+ .pad_bottom = -0.005,
+ .max_xy_ratio = 0.7,
+ },
+ 0xe0b1,
+ => .{
+ .size = .stretch,
+ .max_constraint_width = 1,
+ .align_horizontal = .start,
+ .align_vertical = .center1,
+ .max_xy_ratio = 0.7,
+ },
+ 0xe0b2,
+ => .{
+ .size = .stretch,
+ .max_constraint_width = 1,
+ .align_horizontal = .end,
+ .align_vertical = .center1,
+ .pad_left = -0.03,
+ .pad_right = -0.03,
+ .pad_top = -0.005,
+ .pad_bottom = -0.005,
+ .max_xy_ratio = 0.7,
+ },
+ 0xe0b3,
+ => .{
+ .size = .stretch,
+ .max_constraint_width = 1,
+ .align_horizontal = .end,
+ .align_vertical = .center1,
+ .max_xy_ratio = 0.7,
+ },
+ 0xe0b4,
+ => .{
+ .size = .stretch,
+ .max_constraint_width = 1,
+ .align_horizontal = .start,
+ .align_vertical = .center1,
+ .pad_left = -0.03,
+ .pad_right = -0.03,
+ .pad_top = -0.005,
+ .pad_bottom = -0.005,
+ .max_xy_ratio = 0.59,
+ },
+ 0xe0b5,
+ => .{
+ .size = .stretch,
+ .max_constraint_width = 1,
+ .align_horizontal = .start,
+ .align_vertical = .center1,
+ .max_xy_ratio = 0.5,
+ },
+ 0xe0b6,
+ => .{
+ .size = .stretch,
+ .max_constraint_width = 1,
+ .align_horizontal = .end,
+ .align_vertical = .center1,
+ .pad_left = -0.03,
+ .pad_right = -0.03,
+ .pad_top = -0.005,
+ .pad_bottom = -0.005,
+ .max_xy_ratio = 0.59,
+ },
+ 0xe0b7,
+ => .{
+ .size = .stretch,
+ .max_constraint_width = 1,
+ .align_horizontal = .end,
+ .align_vertical = .center1,
+ .max_xy_ratio = 0.5,
+ },
+ 0xe0b8,
+ 0xe0bc,
+ => .{
+ .size = .stretch,
+ .max_constraint_width = 1,
+ .align_horizontal = .start,
+ .align_vertical = .center1,
+ .pad_left = -0.025,
+ .pad_right = -0.025,
+ .pad_top = -0.005,
+ .pad_bottom = -0.005,
+ },
+ 0xe0b9,
+ 0xe0bd,
+ => .{
+ .size = .stretch,
+ .max_constraint_width = 1,
+ .align_horizontal = .start,
+ .align_vertical = .center1,
+ },
+ 0xe0ba,
+ 0xe0be,
+ => .{
+ .size = .stretch,
+ .max_constraint_width = 1,
+ .align_horizontal = .end,
+ .align_vertical = .center1,
+ .pad_left = -0.025,
+ .pad_right = -0.025,
+ .pad_top = -0.005,
+ .pad_bottom = -0.005,
+ },
+ 0xe0bb,
+ 0xe0bf,
+ => .{
+ .size = .stretch,
+ .max_constraint_width = 1,
+ .align_horizontal = .end,
+ .align_vertical = .center1,
+ },
+ 0xe0c0,
+ 0xe0c8,
+ => .{
+ .size = .stretch,
+ .align_horizontal = .start,
+ .align_vertical = .center1,
+ .pad_left = -0.025,
+ .pad_right = -0.025,
+ .pad_top = -0.005,
+ .pad_bottom = -0.005,
+ },
+ 0xe0c1,
+ => .{
+ .size = .stretch,
+ .align_horizontal = .start,
+ .align_vertical = .center1,
+ },
+ 0xe0c2,
+ 0xe0ca,
+ => .{
+ .size = .stretch,
+ .align_horizontal = .end,
+ .align_vertical = .center1,
+ .pad_left = -0.025,
+ .pad_right = -0.025,
+ .pad_top = -0.005,
+ .pad_bottom = -0.005,
+ },
+ 0xe0c3,
+ => .{
+ .size = .stretch,
+ .align_horizontal = .end,
+ .align_vertical = .center1,
+ },
+ 0xe0c4,
+ => .{
+ .size = .stretch,
+ .align_horizontal = .start,
+ .align_vertical = .center1,
+ .pad_left = 0.015,
+ .pad_right = 0.015,
+ .pad_top = 0.015,
+ .pad_bottom = 0.015,
+ .max_xy_ratio = 0.86,
+ },
+ 0xe0c5,
+ => .{
+ .size = .stretch,
+ .align_horizontal = .end,
+ .align_vertical = .center1,
+ .pad_left = 0.015,
+ .pad_right = 0.015,
+ .pad_top = 0.015,
+ .pad_bottom = 0.015,
+ .max_xy_ratio = 0.86,
+ },
+ 0xe0c6,
+ => .{
+ .size = .stretch,
+ .align_horizontal = .start,
+ .align_vertical = .center1,
+ .pad_left = 0.015,
+ .pad_right = 0.015,
+ .pad_top = 0.015,
+ .pad_bottom = 0.015,
+ .max_xy_ratio = 0.78,
+ },
+ 0xe0c7,
+ => .{
+ .size = .stretch,
+ .align_horizontal = .end,
+ .align_vertical = .center1,
+ .pad_left = 0.015,
+ .pad_right = 0.015,
+ .pad_top = 0.015,
+ .pad_bottom = 0.015,
+ .max_xy_ratio = 0.78,
+ },
+ 0xe0cc,
+ => .{
+ .size = .stretch,
+ .align_horizontal = .start,
+ .align_vertical = .center1,
+ .pad_left = -0.01,
+ .pad_right = -0.01,
+ .pad_top = -0.005,
+ .pad_bottom = -0.005,
+ .max_xy_ratio = 0.85,
+ },
+ 0xe0cd,
+ => .{
+ .size = .stretch,
+ .align_horizontal = .start,
+ .align_vertical = .center1,
+ .max_xy_ratio = 0.865,
+ },
+ 0xe0ce,
+ 0xe0d0...0xe0d1,
+ => .{
+ .size = .fit_cover1,
+ .align_horizontal = .start,
+ .align_vertical = .center1,
+ },
+ 0xe0d2,
+ => .{
+ .size = .stretch,
+ .max_constraint_width = 1,
+ .align_horizontal = .start,
+ .align_vertical = .center1,
+ .pad_left = -0.01,
+ .pad_right = -0.01,
+ .pad_top = -0.005,
+ .pad_bottom = -0.005,
+ .max_xy_ratio = 0.7,
+ },
+ 0xe0d4,
+ => .{
+ .size = .stretch,
+ .max_constraint_width = 1,
+ .align_horizontal = .end,
+ .align_vertical = .center1,
+ .pad_left = -0.01,
+ .pad_right = -0.01,
+ .pad_top = -0.005,
+ .pad_bottom = -0.005,
+ .max_xy_ratio = 0.7,
+ },
+ 0xe0d6,
+ => .{
+ .size = .stretch,
+ .max_constraint_width = 1,
+ .align_horizontal = .start,
+ .align_vertical = .center1,
+ .pad_left = -0.025,
+ .pad_right = -0.025,
+ .pad_top = -0.005,
+ .pad_bottom = -0.005,
+ .max_xy_ratio = 0.7,
+ },
+ 0xe0d7,
+ => .{
+ .size = .stretch,
+ .max_constraint_width = 1,
+ .align_horizontal = .end,
+ .align_vertical = .center1,
+ .pad_left = -0.025,
+ .pad_right = -0.025,
+ .pad_top = -0.005,
+ .pad_bottom = -0.005,
+ .max_xy_ratio = 0.7,
+ },
+ 0xe300,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8984375000000000,
+ .relative_y = 0.0986328125000000,
+ },
+ 0xe301,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8798828125000000,
+ .relative_y = 0.1171875000000000,
+ },
+ 0xe302,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7646484375000000,
+ .relative_y = 0.2314453125000000,
+ },
+ 0xe303,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8789062500000000,
+ .relative_y = 0.1171875000000000,
+ },
+ 0xe304,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9755859375000000,
+ .relative_y = 0.0244140625000000,
+ },
+ 0xe305,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9960937500000000,
+ .relative_y = 0.0019531250000000,
+ },
+ 0xe306,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9863281250000000,
+ .relative_y = 0.0097656250000000,
+ },
+ 0xe307,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9951171875000000,
+ .relative_y = 0.0039062500000000,
+ },
+ 0xe308,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9785156250000000,
+ .relative_y = 0.0195312500000000,
+ },
+ 0xe309,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9736328125000000,
+ .relative_y = 0.0214843750000000,
+ },
+ 0xe30a,
+ 0xe35f,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9648437500000000,
+ .relative_y = 0.0302734375000000,
+ },
+ 0xe30b,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8437500000000000,
+ .relative_y = 0.1513671875000000,
+ },
+ 0xe30c,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8027343750000000,
+ .relative_y = 0.1835937500000000,
+ },
+ 0xe30d,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7753906250000000,
+ .relative_y = 0.1083984375000000,
+ },
+ 0xe30e,
+ 0xe365,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9833984375000000,
+ .relative_y = 0.0166015625000000,
+ },
+ 0xe30f,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9716796875000000,
+ .relative_y = 0.0263671875000000,
+ },
+ 0xe310,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.6621093750000000,
+ .relative_y = 0.0986328125000000,
+ },
+ 0xe311,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.6425781250000000,
+ .relative_y = 0.1171875000000000,
+ },
+ 0xe312,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.5322265625000000,
+ .relative_y = 0.2314453125000000,
+ },
+ 0xe313,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.6416015625000000,
+ .relative_y = 0.1181640625000000,
+ },
+ 0xe314,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7382812500000000,
+ .relative_y = 0.0195312500000000,
+ },
+ 0xe315,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.6787109375000000,
+ .relative_y = 0.1357421875000000,
+ },
+ 0xe316,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7480468750000000,
+ .relative_y = 0.0097656250000000,
+ },
+ 0xe317,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7529296875000000,
+ .relative_y = 0.0048828125000000,
+ },
+ 0xe318,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7314453125000000,
+ .relative_y = 0.0263671875000000,
+ },
+ 0xe319,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7402343750000000,
+ .relative_y = 0.0195312500000000,
+ },
+ 0xe31a,
+ 0xe35e,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7294921875000000,
+ .relative_y = 0.0283203125000000,
+ },
+ 0xe31b,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.6074218750000000,
+ .relative_y = 0.1503906250000000,
+ },
+ 0xe31c,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7363281250000000,
+ .relative_y = 0.0224609375000000,
+ },
+ 0xe31d,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7460937500000000,
+ .relative_y = 0.0126953125000000,
+ },
+ 0xe31e,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.2675781250000000,
+ .relative_y = 0.3310546875000000,
+ },
+ 0xe31f,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7363281250000000,
+ .relative_y = 0.0986328125000000,
+ },
+ 0xe320,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7177734375000000,
+ .relative_y = 0.1171875000000000,
+ },
+ 0xe321,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8085937500000000,
+ .relative_y = 0.0253906250000000,
+ },
+ 0xe322,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7509765625000000,
+ .relative_y = 0.0839843750000000,
+ },
+ 0xe323,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8281250000000000,
+ .relative_y = 0.0097656250000000,
+ },
+ 0xe324,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8349609375000000,
+ },
+ 0xe325,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8154296875000000,
+ .relative_y = 0.0214843750000000,
+ },
+ 0xe326,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8144531250000000,
+ .relative_y = 0.0195312500000000,
+ },
+ 0xe327,
+ 0xe361,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8076171875000000,
+ .relative_y = 0.0273437500000000,
+ },
+ 0xe328,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.6845703125000000,
+ .relative_y = 0.1503906250000000,
+ },
+ 0xe329,
+ 0xe367,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8173828125000000,
+ .relative_y = 0.0175781250000000,
+ },
+ 0xe32a,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8105468750000000,
+ .relative_y = 0.0263671875000000,
+ },
+ 0xe32b,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.5175781250000000,
+ .relative_y = 0.2421875000000000,
+ },
+ 0xe32c,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.6992187500000000,
+ .relative_y = 0.1005859375000000,
+ },
+ 0xe32d,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.6787109375000000,
+ .relative_y = 0.1201171875000000,
+ },
+ 0xe32e,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.5654296875000000,
+ .relative_y = 0.2324218750000000,
+ },
+ 0xe32f,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7714843750000000,
+ .relative_y = 0.0273437500000000,
+ },
+ 0xe330,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7148437500000000,
+ .relative_y = 0.0830078125000000,
+ },
+ 0xe331,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7919921875000000,
+ .relative_y = 0.0097656250000000,
+ },
+ 0xe332,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7871093750000000,
+ .relative_y = 0.0126953125000000,
+ },
+ 0xe333,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7714843750000000,
+ .relative_y = 0.0263671875000000,
+ },
+ 0xe334,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7773437500000000,
+ .relative_y = 0.0195312500000000,
+ },
+ 0xe335,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7714843750000000,
+ .relative_y = 0.0283203125000000,
+ },
+ 0xe336,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.6503906250000000,
+ .relative_y = 0.1503906250000000,
+ },
+ 0xe337,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7753906250000000,
+ .relative_y = 0.0234375000000000,
+ },
+ 0xe338,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7792968750000000,
+ .relative_y = 0.0185546875000000,
+ },
+ 0xe339,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8445945945945946,
+ },
+ 0xe33a,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.5283203125000000,
+ .relative_y = 0.2324218750000000,
+ },
+ 0xe33b,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.5449218750000000,
+ .relative_y = 0.2148437500000000,
+ },
+ 0xe33c...0xe33d,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.5273437500000000,
+ .relative_y = 0.2324218750000000,
+ },
+ 0xe33e,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.3293918918918919,
+ .relative_y = 0.6706081081081081,
+ },
+ 0xe33f,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.5200000000000000,
+ .relative_y = 0.2707692307692308,
+ },
+ 0xe340,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8307692307692308,
+ .relative_y = 0.0861538461538462,
+ },
+ 0xe341,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8327702702702703,
+ .relative_y = 0.0050675675675676,
+ },
+ 0xe344,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.5307692307692308,
+ .relative_y = 0.2092307692307692,
+ },
+ 0xe345,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.5332112630208333,
+ .relative_y = 0.2040934244791667,
+ },
+ 0xe347,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8307692307692308,
+ .relative_y = 0.1246153846153846,
+ },
+ 0xe349,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.5307967032967034,
+ .relative_y = 0.2615384615384616,
+ },
+ 0xe34c,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8659995118379302,
+ .relative_y = 0.1340004881620698,
+ },
+ 0xe34d,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9890163534293386,
+ .relative_y = 0.0002440810349036,
+ },
+ 0xe34f,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.5751953125000000,
+ .relative_y = 0.1142578125000000,
+ },
+ 0xe351,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.6533203125000000,
+ .relative_y = 0.1328125000000000,
+ },
+ 0xe352,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.5215384615384615,
+ .relative_y = 0.2846153846153846,
+ },
+ 0xe353,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8308012820512821,
+ .relative_y = 0.1230448717948718,
+ },
+ 0xe354...0xe356,
+ 0xe358...0xe359,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9935233160621761,
+ .relative_y = 0.0025906735751295,
+ },
+ 0xe357,
+ 0xe3a9,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9961139896373057,
+ },
+ 0xe35a,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9935233160621761,
+ .relative_y = 0.0012953367875648,
+ },
+ 0xe35b,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9987046632124352,
+ .relative_y = 0.0012953367875648,
+ },
+ 0xe360,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7695312500000000,
+ .relative_y = 0.0302734375000000,
+ },
+ 0xe362,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9902343750000000,
+ .relative_y = 0.0097656250000000,
+ },
+ 0xe363,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7900390625000000,
+ .relative_y = 0.0097656250000000,
+ },
+ 0xe364,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8251953125000000,
+ .relative_y = 0.0097656250000000,
+ },
+ 0xe366,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7832031250000000,
+ .relative_y = 0.0166015625000000,
+ },
+ 0xe369,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.4902343750000000,
+ .relative_y = 0.2548828125000000,
+ },
+ 0xe36b,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9333658774713205,
+ .relative_y = 0.0266048328044911,
+ },
+ 0xe36c,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7076171875000000,
+ .relative_y = 0.1083984375000000,
+ },
+ 0xe36d,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8427734375000000,
+ .relative_y = 0.0625000000000000,
+ },
+ 0xe36e,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7529721467391304,
+ .relative_y = 0.0956606657608696,
+ },
+ 0xe36f,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.6835937500000000,
+ .relative_y = 0.1250000000000000,
+ },
+ 0xe370,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8642578125000000,
+ .relative_y = 0.0625000000000000,
+ },
+ 0xe371,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.6103515625000000,
+ .relative_y = 0.1933593750000000,
+ },
+ 0xe372,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7949218750000000,
+ .relative_y = 0.0576171875000000,
+ },
+ 0xe373,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8652343750000000,
+ .relative_y = 0.0058593750000000,
+ },
+ 0xe374,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.3154296875000000,
+ .relative_y = 0.2861328125000000,
+ },
+ 0xe375,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.6772460937500000,
+ .relative_y = 0.1303710937500000,
+ },
+ 0xe376,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.6992187500000000,
+ .relative_y = 0.1337890625000000,
+ },
+ 0xe377,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7314453125000000,
+ .relative_y = 0.1552734375000000,
+ },
+ 0xe378,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7314453125000000,
+ .relative_y = 0.1542968750000000,
+ },
+ 0xe379,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.5751953125000000,
+ .relative_y = 0.1826171875000000,
+ },
+ 0xe37a,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.5263671875000000,
+ .relative_y = 0.2285156250000000,
+ },
+ 0xe37b,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.5751953125000000,
+ .relative_y = 0.1835937500000000,
+ },
+ 0xe37d,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9003906250000000,
+ .relative_y = 0.0957031250000000,
+ },
+ 0xe37e,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.6015625000000000,
+ .relative_y = 0.2324218750000000,
+ },
+ 0xe37f,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.5200000000000000,
+ .relative_y = 0.2784615384615385,
+ },
+ 0xe380,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.5200000000000000,
+ .relative_y = 0.2630769230769231,
+ },
+ 0xe38e...0xe391,
+ 0xe394,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.4990253411306043,
+ .relative_height = 0.9987012987012988,
+ .relative_x = 0.4996751137102014,
+ },
+ 0xe392...0xe393,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.4996751137102014,
+ .relative_height = 0.9987012987012988,
+ .relative_x = 0.4990253411306043,
+ },
+ 0xe395,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.5471085120207927,
+ .relative_height = 0.9987012987012988,
+ .relative_x = 0.4515919428200130,
+ },
+ 0xe396,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.5945419103313840,
+ .relative_height = 0.9987012987012988,
+ .relative_x = 0.4041585445094217,
+ },
+ 0xe397,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.6426250812215725,
+ .relative_x = 0.3573749187784275,
+ },
+ 0xe398,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.6900584795321637,
+ .relative_x = 0.3099415204678362,
+ },
+ 0xe399,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.7381416504223521,
+ .relative_x = 0.2618583495776478,
+ },
+ 0xe39a,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.7855750487329435,
+ .relative_x = 0.2144249512670565,
+ },
+ 0xe39b,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.9987004548408057,
+ .relative_height = 0.9987012987012988,
+ },
+ 0xe39c,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.8323586744639376,
+ .relative_height = 0.9935064935064936,
+ },
+ 0xe39d,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.7855750487329435,
+ .relative_height = 0.9948051948051948,
+ },
+ 0xe39e,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.7381416504223521,
+ .relative_height = 0.9961038961038962,
+ },
+ 0xe39f,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.6907082521117609,
+ .relative_height = 0.9961038961038962,
+ },
+ 0xe3a0,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.6426250812215725,
+ .relative_height = 0.9961038961038962,
+ },
+ 0xe3a1,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.5945419103313840,
+ .relative_height = 0.9974025974025974,
+ },
+ 0xe3a2...0xe3a3,
+ 0xe3a5,
+ 0xe3a7...0xe3a8,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.4990253411306043,
+ .relative_height = 0.9987012987012988,
+ },
+ 0xe3a4,
+ 0xe3a6,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.4996751137102014,
+ .relative_height = 0.9987012987012988,
+ },
+ 0xe3aa,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9902343750000000,
+ .relative_y = 0.0078125000000000,
+ },
+ 0xe3ab,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7900390625000000,
+ .relative_y = 0.0058593750000000,
+ },
+ 0xe3ac,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8251953125000000,
+ .relative_y = 0.0078125000000000,
+ },
+ 0xe3ad,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7519531250000000,
+ .relative_y = 0.0068359375000000,
+ },
+ 0xe3ae,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.6152343750000000,
+ .relative_y = 0.2324218750000000,
+ },
+ 0xe3af,
+ 0xe3b3,
+ 0xe3b5...0xe3bb,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9986072423398329,
+ .relative_y = 0.0013927576601671,
+ },
+ 0xe3b0...0xe3b2,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9958217270194986,
+ .relative_y = 0.0041782729805014,
+ },
+ 0xe3c1,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.6590187942396876,
+ .relative_y = 0.1349768123016842,
+ },
+ 0xe3c2,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7939956065413717,
+ },
+ 0x23fb...0x23fe,
+ 0x2665,
+ 0x26a1,
+ 0x2b58,
+ 0xe000...0xe00a,
+ 0xe200...0xe2a9,
+ 0xe342...0xe343,
+ 0xe346,
+ 0xe348,
+ 0xe34a...0xe34b,
+ 0xe34e,
+ 0xe350,
+ 0xe35c...0xe35d,
+ 0xe368,
+ 0xe36a,
+ 0xe37c,
+ 0xe381...0xe38d,
+ 0xe3b4,
+ 0xe3bc...0xe3c0,
+ 0xe3c3...0xe3e3,
+ 0xe5fa...0xe6b8,
+ 0xe700...0xe8ef,
+ 0xea60,
+ 0xea62...0xea7c,
+ 0xea7e...0xea88,
+ 0xea8a...0xea8c,
+ 0xea8f...0xea98,
+ 0xeaa3...0xeab3,
+ 0xeab8...0xeac7,
+ 0xeac9,
+ 0xeacc...0xead3,
+ 0xead7...0xeb09,
+ 0xeb0b...0xeb42,
+ 0xeb44...0xeb4e,
+ 0xeb50...0xeb6d,
+ 0xeb72...0xeb89,
+ 0xeb8b...0xeb99,
+ 0xeb9b...0xebd4,
+ 0xebd7...0xec06,
+ 0xec08...0xec0a,
+ 0xec0d...0xec1e,
+ 0xed00...0xedff,
+ 0xee0c...0xefce,
+ 0xf000...0xf004,
+ 0xf006...0xf025,
+ 0xf028...0xf02a,
+ 0xf02c...0xf030,
+ 0xf034,
+ 0xf036...0xf043,
+ 0xf045,
+ 0xf047,
+ 0xf053...0xf05f,
+ 0xf062,
+ 0xf064...0xf076,
+ 0xf079...0xf07d,
+ 0xf07f...0xf088,
+ 0xf08a...0xf0a3,
+ 0xf0a6...0xf0d6,
+ 0xf0db,
+ 0xf0df...0xf0ff,
+ 0xf108...0xf12f,
+ 0xf131...0xf140,
+ 0xf142...0xf152,
+ 0xf155,
+ 0xf15a...0xf174,
+ 0xf176,
+ 0xf179...0xf181,
+ 0xf183...0xf220,
+ 0xf223,
+ 0xf22e...0xf254,
+ 0xf259,
+ 0xf25c...0xf381,
+ 0xf400...0xf415,
+ 0xf417...0xf423,
+ 0xf425...0xf430,
+ 0xf435...0xf437,
+ 0xf439...0xf43d,
+ 0xf43f...0xf442,
+ 0xf446...0xf449,
+ 0xf44c...0xf45b,
+ 0xf45d...0xf45f,
+ 0xf462...0xf466,
+ 0xf468...0xf46b,
+ 0xf46d...0xf46f,
+ 0xf471...0xf475,
+ 0xf477...0xf479,
+ 0xf47f...0xf48a,
+ 0xf48c...0xf492,
+ 0xf494...0xf499,
+ 0xf49b...0xf4c2,
+ 0xf4c4...0xf4ee,
+ 0xf4f3...0xf51c,
+ 0xf51e...0xf533,
+ 0xf0001...0xf1af0,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ },
+ 0xea61,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.7513020833333334,
+ .relative_height = 0.9291573452647278,
+ .relative_x = 0.0846354166666667,
+ .relative_y = 0.0708426547352722,
+ },
+ 0xea7d,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.8394854586129754,
+ .relative_height = 0.8751387347391787,
+ .relative_x = 0.0917225950782998,
+ .relative_y = 0.0416204217536071,
+ },
+ 0xea99,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.9395973154362416,
+ .relative_height = 0.4778024417314096,
+ .relative_x = 0.0302013422818792,
+ .relative_y = 0.2269700332963374,
+ },
+ 0xea9a,
+ 0xeaa1,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.7673378076062640,
+ .relative_height = 0.8523862375138734,
+ .relative_x = 0.1526845637583893,
+ .relative_y = 0.0754716981132075,
+ },
+ 0xea9b,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.8590604026845637,
+ .relative_height = 0.7613762486126526,
+ .relative_x = 0.0721476510067114,
+ .relative_y = 0.0871254162042175,
+ },
+ 0xea9c,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.8590604026845637,
+ .relative_height = 0.7574916759156493,
+ .relative_x = 0.0721476510067114,
+ .relative_y = 0.0832408435072142,
+ },
+ 0xea9d,
+ 0xeaa0,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.4082774049217002,
+ .relative_height = 0.5077691453940066,
+ .relative_x = 0.2863534675615212,
+ .relative_y = 0.2763596004439512,
+ },
+ 0xea9e...0xea9f,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.5117449664429530,
+ .relative_height = 0.4051054384017758,
+ .relative_x = 0.2136465324384788,
+ .relative_y = 0.3068812430632630,
+ },
+ 0xeaa2,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.8061116965226555,
+ .relative_height = 0.9438247156716689,
+ .relative_x = 0.0679662802950474,
+ .relative_y = 0.0147523709167545,
+ },
+ 0xeab4,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.9945482866043613,
+ .relative_height = 0.5264797507788161,
+ .relative_y = 0.2024922118380062,
+ },
+ 0xeab5,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.5264797507788161,
+ .relative_height = 0.9945482866043613,
+ .relative_x = 0.2024922118380062,
+ .relative_y = 0.0054517133956386,
+ },
+ 0xeab6,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.5264797507788161,
+ .relative_height = 0.9945482866043613,
+ .relative_x = 0.2710280373831775,
+ },
+ 0xeab7,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.9945482866043613,
+ .relative_height = 0.5264797507788161,
+ .relative_x = 0.0054517133956386,
+ .relative_y = 0.2710280373831775,
+ },
+ 0xead4...0xead5,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.7069825436408977,
+ .relative_x = 0.1483790523690773,
+ },
+ 0xead6,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8780760626398211,
+ .relative_y = 0.0687919463087248,
+ },
+ 0xeb43,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.7335766423357665,
+ .relative_height = 0.9996188152778837,
+ .relative_x = 0.1991657977059437,
+ .relative_y = 0.0003811847221163,
+ },
+ 0xeb6e,
+ 0xeb71,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.4954604409857328,
+ .relative_y = 0.2522697795071336,
+ },
+ 0xeb6f,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.4973958333333333,
+ .relative_x = 0.2493489583333333,
+ },
+ 0xeb70,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.4973958333333333,
+ .relative_height = 0.9961089494163424,
+ .relative_x = 0.2493489583333333,
+ .relative_y = 0.0038910505836576,
+ },
+ 0xeb8a,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.3468834688346883,
+ .relative_height = 0.3353615785256410,
+ .relative_x = 0.2642276422764228,
+ .relative_y = 0.3313050881410256,
+ },
+ 0xeb9a,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.8740779768177028,
+ .relative_height = 0.9438247156716689,
+ .relative_x = 0.0679662802950474,
+ .relative_y = 0.0147523709167545,
+ },
+ 0xebd5,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.9322210636079249,
+ .relative_height = 0.9318897917604415,
+ .relative_y = 0.0681102082395584,
+ },
+ 0xebd6,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9996446423917936,
+ .relative_y = 0.0003553576082064,
+ },
+ 0xec07,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.3495911047345768,
+ .relative_height = 0.3355179398148149,
+ .relative_x = 0.2615335565120357,
+ .relative_y = 0.3311487268518519,
+ },
+ 0xec0b,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.9327424400417101,
+ .relative_height = 0.9996188152778837,
+ .relative_y = 0.0003811847221163,
+ },
+ 0xec0c,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.8008342022940563,
+ .relative_x = 0.1991657977059437,
+ },
+ 0xee00,
+ 0xee03,
+ => .{
+ .size = .stretch,
+ .max_constraint_width = 1,
+ .align_horizontal = .end,
+ .align_vertical = .center1,
+ .relative_width = 0.8681172291296625,
+ .relative_height = 0.8626692456479691,
+ .relative_x = 0.1314387211367673,
+ .relative_y = 0.0686653771760155,
+ .pad_left = -0.025,
+ .pad_right = -0.025,
+ .pad_top = -0.005,
+ .pad_bottom = -0.005,
+ },
+ 0xee01,
+ 0xee04,
+ => .{
+ .size = .stretch,
+ .max_constraint_width = 1,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8626692456479691,
+ .relative_y = 0.0686653771760155,
+ .pad_left = -0.05,
+ .pad_right = -0.05,
+ .pad_top = -0.005,
+ .pad_bottom = -0.005,
+ },
+ 0xee02,
+ 0xee05,
+ => .{
+ .size = .stretch,
+ .max_constraint_width = 1,
+ .align_horizontal = .start,
+ .align_vertical = .center1,
+ .relative_width = 0.8685612788632326,
+ .relative_height = 0.8626692456479691,
+ .relative_y = 0.0686653771760155,
+ .pad_left = -0.025,
+ .pad_right = -0.025,
+ .pad_top = -0.005,
+ .pad_bottom = -0.005,
+ },
+ 0xee06,
+ => .{
+ .size = .cover,
+ .max_constraint_width = 1,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.7059415911379657,
+ .relative_height = 0.2234524408656266,
+ .relative_x = 0.1470292044310171,
+ .relative_y = 0.7765475591343735,
+ .pad_left = 0.015,
+ .pad_right = 0.015,
+ .pad_top = 0.015,
+ .pad_bottom = 0.015,
+ },
+ 0xee07,
+ => .{
+ .size = .cover,
+ .max_constraint_width = 1,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.5000000000000000,
+ .relative_height = 0.7498741821841973,
+ .relative_x = 0.5000000000000000,
+ .relative_y = 0.2501258178158027,
+ .pad_left = 0.015,
+ .pad_right = 0.015,
+ .pad_top = 0.015,
+ .pad_bottom = 0.015,
+ },
+ 0xee08,
+ => .{
+ .size = .cover,
+ .max_constraint_width = 1,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.6299093655589124,
+ .relative_height = 0.8535480624056366,
+ .relative_x = 0.3700906344410876,
+ .pad_left = 0.015,
+ .pad_right = 0.015,
+ .pad_top = 0.015,
+ .pad_bottom = 0.015,
+ },
+ 0xee09,
+ => .{
+ .size = .cover,
+ .max_constraint_width = 1,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.4997483643683945,
+ .pad_left = 0.015,
+ .pad_right = 0.015,
+ .pad_top = 0.015,
+ .pad_bottom = 0.015,
+ },
+ 0xee0a,
+ => .{
+ .size = .cover,
+ .max_constraint_width = 1,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.6299093655589124,
+ .relative_height = 0.8535480624056366,
+ .pad_left = 0.015,
+ .pad_right = 0.015,
+ .pad_top = 0.015,
+ .pad_bottom = 0.015,
+ },
+ 0xee0b,
+ => .{
+ .size = .cover,
+ .max_constraint_width = 1,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.5000000000000000,
+ .relative_height = 0.7498741821841973,
+ .relative_y = 0.2501258178158027,
+ .pad_left = 0.015,
+ .pad_right = 0.015,
+ .pad_top = 0.015,
+ .pad_bottom = 0.015,
+ },
+ 0xf005,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9999664113932554,
+ .relative_y = 0.0000335886067446,
+ },
+ 0xf026...0xf027,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9786184354605580,
+ .relative_y = 0.0103951316192896,
+ },
+ 0xf02b,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9758052740827267,
+ .relative_y = 0.0238869355863696,
+ },
+ 0xf031...0xf033,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9987922705314010,
+ .relative_y = 0.0006038647342995,
+ },
+ 0xf035,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9989935587761675,
+ .relative_y = 0.0004025764895330,
+ },
+ 0xf044,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9925925925925926,
+ },
+ 0xf046,
+ 0xf153...0xf154,
+ 0xf158,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8751322751322751,
+ .relative_y = 0.0624338624338624,
+ },
+ 0xf048,
+ 0xf04a,
+ 0xf04e,
+ 0xf051,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8577706898990622,
+ .relative_y = 0.0711892586341537,
+ },
+ 0xf049,
+ 0xf050,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8579450878868969,
+ .relative_y = 0.0710148606463189,
+ },
+ 0xf04b,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9997041418532618,
+ .relative_y = 0.0002958581467381,
+ },
+ 0xf04c...0xf04d,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8572940020656472,
+ .relative_y = 0.0713404035569438,
+ },
+ 0xf04f,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7138835298072554,
+ .relative_y = 0.1433479295317200,
+ },
+ 0xf052,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9999748091795350,
+ },
+ 0xf060...0xf061,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8567975830815709,
+ .relative_y = 0.0719033232628399,
+ },
+ 0xf063,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9987915407854985,
+ .relative_y = 0.0006042296072508,
+ },
+ 0xf077,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.5700483091787439,
+ .relative_y = 0.2862318840579710,
+ },
+ 0xf078,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.5700483091787439,
+ .relative_y = 0.1437198067632850,
+ },
+ 0xf07e,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.4989429175475687,
+ .relative_y = 0.2505285412262157,
+ },
+ 0xf089,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9998488512696494,
+ .relative_y = 0.0001511487303507,
+ },
+ 0xf0a4...0xf0a5,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7502645502645503,
+ .relative_y = 0.1248677248677249,
+ },
+ 0xf0d7,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.4281400966183575,
+ .relative_y = 0.2053140096618357,
+ },
+ 0xf0d8,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.4281400966183575,
+ .relative_y = 0.3472222222222222,
+ },
+ 0xf0d9,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7140772371750631,
+ .relative_y = 0.1333462732919255,
+ },
+ 0xf0da,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7140396210163651,
+ .relative_y = 0.1333838894506235,
+ },
+ 0xf0dc,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ },
+ 0xf0dd,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .relative_height = 0.4275362318840580,
+ .relative_y = 0.0012077294685990,
+ },
+ 0xf0de,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .relative_height = 0.4287439613526570,
+ .relative_y = 0.5712560386473430,
+ },
+ 0xf100...0xf101,
+ 0xf104...0xf105,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8573155985489722,
+ .relative_y = 0.0713422007255139,
+ },
+ 0xf102,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9286577992744861,
+ .relative_y = 0.0713422007255139,
+ },
+ 0xf103,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9286577992744861,
+ },
+ 0xf106...0xf107,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.5000000000000000,
+ .relative_y = 0.2853688029020556,
+ },
+ 0xf130,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9998602571268865,
+ },
+ 0xf141,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.2593984962406015,
+ .relative_y = 0.3696741854636592,
+ },
+ 0xf156,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8752505446623093,
+ .relative_y = 0.0623155929038282,
+ },
+ 0xf157,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8756468797564688,
+ .relative_y = 0.0624338624338624,
+ },
+ 0xf159,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8756067947646895,
+ .relative_y = 0.0623492063492063,
+ },
+ 0xf175,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9989423585404548,
+ .relative_y = 0.0005288207297726,
+ },
+ 0xf177...0xf178,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.6250661025912215,
+ .relative_y = 0.1877313590692755,
+ },
+ 0xf182,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.9998046921689268,
+ },
+ 0xf221,
+ 0xf224...0xf226,
+ 0xf228,
+ 0xf22a,
+ 0xf22c,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9994854643684076,
+ },
+ 0xf222,
+ 0xf227,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.8746819883943630,
+ .relative_y = 0.0624017379870223,
+ },
+ 0xf229,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9370837263813853,
+ .relative_y = 0.0624017379870223,
+ },
+ 0xf22b,
+ 0xf22d,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.6874767744332962,
+ .relative_y = 0.1560043449675557,
+ },
+ 0xf255...0xf256,
+ 0xf25a,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9993997599039616,
+ },
+ 0xf257,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7810124049619848,
+ .relative_y = 0.0935945806894186,
+ },
+ 0xf258,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.7498142113988452,
+ .relative_y = 0.1247927742525582,
+ },
+ 0xf25b,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.9975006099019084,
+ },
+ 0xf416,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_height = 0.6090604026845637,
+ .relative_y = 0.2119686800894855,
+ },
+ 0xf424,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.5019531250000000,
+ .relative_height = 0.5755033557046980,
+ .relative_x = 0.2480468750000000,
+ .relative_y = 0.2108501118568233,
+ },
+ 0xf431,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.6240234375000000,
+ .relative_height = 0.7695749440715883,
+ .relative_x = 0.2031250000000000,
+ .relative_y = 0.1420581655480984,
+ },
+ 0xf432,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.6718750000000000,
+ .relative_height = 0.7147651006711410,
+ .relative_x = 0.1875000000000000,
+ .relative_y = 0.1610738255033557,
+ },
+ 0xf433,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.6240234375000000,
+ .relative_height = 0.7695749440715883,
+ .relative_x = 0.2041015625000000,
+ .relative_y = 0.0883668903803132,
+ },
+ 0xf434,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.6718750000000000,
+ .relative_height = 0.7147651006711410,
+ .relative_x = 0.1406250000000000,
+ .relative_y = 0.1599552572706935,
+ },
+ 0xf438,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.2436523437500000,
+ .relative_height = 0.4560546875000000,
+ .relative_x = 0.3813476562500000,
+ .relative_y = 0.2719726562500000,
+ },
+ 0xf43e,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.5029296875000000,
+ .relative_height = 0.5755033557046980,
+ .relative_x = 0.2500000000000000,
+ .relative_y = 0.2136465324384788,
+ },
+ 0xf443,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.7500000000000000,
+ .relative_x = 0.1250000000000000,
+ },
+ 0xf444...0xf445,
+ 0xf4c3,
+ 0xf51d,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.5000000000000000,
+ .relative_height = 0.5000000000000000,
+ .relative_x = 0.2500000000000000,
+ .relative_y = 0.2500000000000000,
+ },
+ 0xf44a,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.2436523437500000,
+ .relative_height = 0.4560546875000000,
+ .relative_x = 0.3750000000000000,
+ .relative_y = 0.2719726562500000,
+ },
+ 0xf44b,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.4560546875000000,
+ .relative_height = 0.2436523437500000,
+ .relative_x = 0.2719726562500000,
+ .relative_y = 0.3188476562500000,
+ },
+ 0xf45c,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.5019531250000000,
+ .relative_height = 0.5749440715883669,
+ .relative_x = 0.2480468750000000,
+ .relative_y = 0.2114093959731544,
+ },
+ 0xf460,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.3593750000000000,
+ .relative_height = 0.6240234375000000,
+ .relative_x = 0.3750000000000000,
+ .relative_y = 0.1884765625000000,
+ },
+ 0xf461,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.6237816764132553,
+ .relative_height = 0.9988851727982163,
+ .relative_x = 0.1881091617933723,
+ },
+ 0xf467,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.5639648437500000,
+ .relative_height = 0.5649414062500000,
+ .relative_x = 0.2187500000000000,
+ .relative_y = 0.2177734375000000,
+ },
+ 0xf46c,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.5039062500000000,
+ .relative_height = 0.5771812080536913,
+ .relative_x = 0.2490234375000000,
+ .relative_y = 0.2091722595078300,
+ },
+ 0xf470,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.9926757812500000,
+ .relative_height = 0.2690429687500000,
+ .relative_y = 0.6865234375000000,
+ },
+ 0xf476,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.8732325694783033,
+ .relative_x = 0.0633837152608484,
+ },
+ 0xf47a,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.5843079922027290,
+ .relative_height = 0.9509476031215162,
+ .relative_x = 0.2066276803118908,
+ .relative_y = 0.0234113712374582,
+ },
+ 0xf47b...0xf47c,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.6250000000000000,
+ .relative_height = 0.3593750000000000,
+ .relative_x = 0.1875000000000000,
+ .relative_y = 0.3281250000000000,
+ },
+ 0xf47d,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.3593750000000000,
+ .relative_height = 0.6240234375000000,
+ .relative_x = 0.2656250000000000,
+ .relative_y = 0.1875000000000000,
+ },
+ 0xf47e,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.4560546875000000,
+ .relative_height = 0.2436523437500000,
+ .relative_x = 0.2719726562500000,
+ .relative_y = 0.3750000000000000,
+ },
+ 0xf48b,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.7187500000000000,
+ .relative_height = 0.0937500000000000,
+ .relative_x = 0.1250000000000000,
+ .relative_y = 0.4687500000000000,
+ },
+ 0xf493,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.8313840155945419,
+ .relative_height = 0.9509476031215162,
+ .relative_x = 0.0843079922027290,
+ .relative_y = 0.0234113712374582,
+ },
+ 0xf49a,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.8727450024378351,
+ .relative_x = 0.0633837152608484,
+ },
+ 0xf4ef,
+ 0xf4f2,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.7142857142857143,
+ .relative_x = 0.1428571428571428,
+ },
+ 0xf4f0,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.9642857142857143,
+ .relative_height = 0.7407407407407407,
+ .relative_y = 0.1111111111111111,
+ },
+ 0xf4f1,
+ => .{
+ .size = .fit_cover1,
+ .height = .icon,
+ .align_horizontal = .center1,
+ .align_vertical = .center1,
+ .relative_width = 0.9642857142857143,
+ .relative_height = 0.7407407407407407,
+ .relative_x = 0.0357142857142857,
+ .relative_y = 0.1111111111111111,
+ },
+ else => null,
+ };
+}
diff --git a/vendor/libghostty-vt/src/font/nerd_font_codegen.py b/vendor/libghostty-vt/src/font/nerd_font_codegen.py
new file mode 100644
index 00000000..664555a3
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/nerd_font_codegen.py
@@ -0,0 +1,576 @@
+"""
+This file extracts the patch sets from the nerd fonts font patcher file in order to
+extract scaling rules and attributes for different codepoint ranges which it then
+codegens in to a Zig file with a function that switches over codepoints and returns the
+attributes and scaling rules.
+
+This does include an `eval` call! This is spooky, but we trust the nerd fonts code to
+be safe and not malicious or anything.
+
+This script requires Python 3.12 or greater, requires that the `fontTools`
+python module is installed, and requires that the path to a copy of the
+SymbolsNerdFont (not Mono!) font is passed as the first argument to it.
+"""
+
+import ast
+import sys
+import math
+from fontTools.ttLib import TTFont, TTLibError
+from fontTools.pens.boundsPen import BoundsPen
+from collections import defaultdict
+from contextlib import suppress
+from pathlib import Path
+from types import SimpleNamespace
+from typing import Literal, TypedDict, cast
+from urllib.request import urlretrieve
+
+type PatchSetAttributes = dict[Literal["default"] | int, PatchSetAttributeEntry]
+type AttributeHash = tuple[
+ str | None,
+ str | None,
+ str,
+ float,
+ float,
+ float,
+ float,
+ float,
+ float,
+ float,
+]
+type ResolvedSymbol = PatchSetAttributes | PatchSetScaleRules | int | None
+
+
+class PatchSetScaleRules(TypedDict):
+ ShiftMode: str
+ ScaleGroups: list[list[int] | range]
+
+
+class PatchSetAttributeEntry(TypedDict):
+ align: str
+ valign: str
+ stretch: str
+ params: dict[str, float | bool]
+
+ relative_x: float
+ relative_y: float
+ relative_width: float
+ relative_height: float
+
+
+class PatchSet(TypedDict):
+ Name: str
+ Filename: str
+ Exact: bool
+ SymStart: int
+ SymEnd: int
+ SrcStart: int | None
+ ScaleRules: PatchSetScaleRules | None
+ Attributes: PatchSetAttributes
+
+
+class PatchSetExtractor(ast.NodeVisitor):
+ def __init__(self) -> None:
+ self.symbol_table: dict[str, ast.expr] = {}
+ self.patch_set_values: list[PatchSet] = []
+ self.nf_version: str = ""
+
+ def visit_Assign(self, node):
+ if (
+ node.col_offset == 0 # top-level assignment
+ and len(node.targets) == 1 # no funny destructuring business
+ and isinstance(node.targets[0], ast.Name) # no setitem et cetera
+ and node.targets[0].id == "version" # it's the version string!
+ ):
+ self.nf_version = ast.literal_eval(node.value)
+ else:
+ return self.generic_visit(node)
+
+ def visit_ClassDef(self, node: ast.ClassDef) -> None:
+ if node.name != "font_patcher":
+ return
+ for item in node.body:
+ if isinstance(item, ast.FunctionDef) and item.name == "setup_patch_set":
+ self.visit_setup_patch_set(item)
+
+ def visit_setup_patch_set(self, node: ast.FunctionDef) -> None:
+ # First pass: gather variable assignments
+ for stmt in node.body:
+ match stmt:
+ case ast.Assign(targets=[ast.Name(id=symbol)]):
+ # Store simple variable assignments in the symbol table
+ self.symbol_table[symbol] = stmt.value
+
+ # Second pass: process self.patch_set
+ for stmt in node.body:
+ if not isinstance(stmt, ast.Assign):
+ continue
+ for target in stmt.targets:
+ if (
+ isinstance(target, ast.Attribute)
+ and target.attr == "patch_set"
+ and isinstance(stmt.value, ast.List)
+ ):
+ for elt in stmt.value.elts:
+ if isinstance(elt, ast.Dict):
+ self.process_patch_entry(elt)
+
+ def resolve_symbol(self, node: ast.expr) -> ResolvedSymbol:
+ """Resolve named variables to their actual values from the symbol table."""
+ if isinstance(node, ast.Name) and node.id in self.symbol_table:
+ return self.safe_literal_eval(self.symbol_table[node.id])
+ return self.safe_literal_eval(node)
+
+ def safe_literal_eval(self, node: ast.expr) -> ResolvedSymbol:
+ """Try to evaluate or stringify an AST node."""
+ try:
+ return ast.literal_eval(node)
+ except ValueError:
+ # Spooky eval! But we trust nerd fonts to be safe...
+ if hasattr(ast, "unparse"):
+ return eval(
+ ast.unparse(node),
+ {"box_enabled": False, "box_keep": False},
+ {
+ "self": SimpleNamespace(
+ args=SimpleNamespace(
+ careful=False,
+ custom=False,
+ fontawesome=True,
+ fontawesomeextension=True,
+ fontlogos=True,
+ octicons=True,
+ codicons=True,
+ powersymbols=True,
+ pomicons=True,
+ powerline=True,
+ powerlineextra=True,
+ material=True,
+ weather=True,
+ )
+ ),
+ },
+ )
+ msg = f"<cannot eval: {type(node).__name__}>"
+ raise ValueError(msg) from None
+
+ def process_patch_entry(self, dict_node: ast.Dict) -> None:
+ entry = {}
+ for key_node, value_node in zip(dict_node.keys, dict_node.values):
+ if isinstance(key_node, ast.Constant):
+ if key_node.value == "Enabled":
+ if self.safe_literal_eval(value_node):
+ continue # This patch set is enabled, continue to next key
+ else:
+ return # This patch set is disabled, skip
+ key = ast.literal_eval(cast("ast.Constant", key_node))
+ entry[key] = self.resolve_symbol(value_node)
+ self.patch_set_values.append(cast("PatchSet", entry))
+
+
+def extract_patch_set_values(source_code: str) -> tuple[list[PatchSet], str]:
+ tree = ast.parse(source_code)
+ extractor = PatchSetExtractor()
+ extractor.visit(tree)
+ return extractor.patch_set_values, extractor.nf_version
+
+
+def parse_alignment(val: str) -> str | None:
+ return {
+ "l": ".start",
+ "r": ".end",
+ "c": ".center1", # font-patcher specific centering rule, see face.zig
+ "": None,
+ }.get(val, ".none")
+
+
+def attr_key(attr: PatchSetAttributeEntry) -> AttributeHash:
+ """Convert attributes to a hashable key for grouping."""
+ params = attr.get("params", {})
+ return (
+ parse_alignment(attr.get("align", "")),
+ parse_alignment(attr.get("valign", "")),
+ attr.get("stretch", ""),
+ float(params.get("overlap", 0.0)),
+ float(params.get("xy-ratio", -1.0)),
+ float(params.get("ypadding", 0.0)),
+ float(attr.get("relative_x", 0.0)),
+ float(attr.get("relative_y", 0.0)),
+ float(attr.get("relative_width", 1.0)),
+ float(attr.get("relative_height", 1.0)),
+ )
+
+
+def coalesce_codepoints_to_ranges(codepoints: list[int]) -> list[tuple[int, int]]:
+ """Convert a sorted list of integers to a list of single values and ranges."""
+ ranges: list[tuple[int, int]] = []
+ cp_iter = iter(sorted(codepoints))
+ with suppress(StopIteration):
+ start = prev = next(cp_iter)
+ for cp in cp_iter:
+ if cp == prev + 1:
+ prev = cp
+ else:
+ ranges.append((start, prev))
+ start = prev = cp
+ ranges.append((start, prev))
+ return ranges
+
+
+def emit_zig_entry_multikey(codepoints: list[int], attr: PatchSetAttributeEntry) -> str:
+ align = parse_alignment(attr.get("align", ""))
+ valign = parse_alignment(attr.get("valign", ""))
+ stretch = attr.get("stretch", "")
+ params = attr.get("params", {})
+
+ relative_x = attr.get("relative_x", 0.0)
+ relative_y = attr.get("relative_y", 0.0)
+ relative_width = attr.get("relative_width", 1.0)
+ relative_height = attr.get("relative_height", 1.0)
+
+ overlap = params.get("overlap", 0.0)
+ xy_ratio = params.get("xy-ratio", -1.0)
+ y_padding = params.get("ypadding", 0.0)
+
+ ranges = coalesce_codepoints_to_ranges(codepoints)
+ keys = "\n".join(
+ f" {start:#x}...{end:#x}," if start != end else f" {start:#x},"
+ for start, end in ranges
+ )
+
+ s = f"{keys}\n => .{{\n"
+
+ # This maps the font_patcher stretch rules to a Constrain instance
+ # NOTE: some comments in font_patcher indicate that only x or y
+ # would also be a valid spec, but no icons use it, so we won't
+ # support it until we have to.
+ if "pa" in stretch:
+ if "!" in stretch or overlap:
+ s += " .size = .cover,\n"
+ else:
+ s += " .size = .fit_cover1,\n"
+ elif "xy" in stretch:
+ s += " .size = .stretch,\n"
+ else:
+ print(f"Warning: Unknown stretch rule {stretch}")
+
+ # `^` indicates that scaling should use the
+ # full cell height, not just the icon height,
+ # even when the constraint width is 1
+ if "^" not in stretch:
+ s += " .height = .icon,\n"
+
+ # There are two cases where we want to limit the constraint width to 1:
+ # - If there's a `1` in the stretch mode string.
+ # - If the stretch mode is not `pa` and there's not an explicit `2`.
+ if "1" in stretch or ("pa" not in stretch and "2" not in stretch):
+ s += " .max_constraint_width = 1,\n"
+
+ if align is not None:
+ s += f" .align_horizontal = {align},\n"
+ if valign is not None:
+ s += f" .align_vertical = {valign},\n"
+
+ if relative_width != 1.0:
+ s += f" .relative_width = {relative_width:.16f},\n"
+ if relative_height != 1.0:
+ s += f" .relative_height = {relative_height:.16f},\n"
+ if relative_x != 0.0:
+ s += f" .relative_x = {relative_x:.16f},\n"
+ if relative_y != 0.0:
+ s += f" .relative_y = {relative_y:.16f},\n"
+
+ # `overlap` and `ypadding` are mutually exclusive,
+ # this is asserted in the nerd fonts patcher itself.
+ if overlap:
+ pad = -overlap / 2
+ s += f" .pad_left = {pad},\n"
+ s += f" .pad_right = {pad},\n"
+ # In the nerd fonts patcher, overlap values
+ # are capped at 0.01 in the vertical direction.
+ v_pad = -min(0.01, overlap) / 2
+ s += f" .pad_top = {v_pad},\n"
+ s += f" .pad_bottom = {v_pad},\n"
+ elif y_padding:
+ s += f" .pad_top = {y_padding / 2},\n"
+ s += f" .pad_bottom = {y_padding / 2},\n"
+
+ if xy_ratio > 0:
+ s += f" .max_xy_ratio = {xy_ratio},\n"
+
+ s += " },"
+ return s
+
+
+def generate_codepoint_tables(
+ patch_sets: list[PatchSet],
+ nerd_font: TTFont,
+ nf_version: str,
+) -> dict[str, dict[int, int]]:
+ # We may already have the table saved from a previous run.
+ if Path("nerd_font_codepoint_tables.py").exists():
+ import nerd_font_codepoint_tables
+
+ if nerd_font_codepoint_tables.version == nf_version:
+ return nerd_font_codepoint_tables.cp_tables
+
+ cp_tables: dict[str, dict[int, int]] = {}
+ cp_nerdfont_used: set[int] = set()
+ cmap = nerd_font.getBestCmap()
+ for entry in patch_sets:
+ patch_set_name = entry["Name"]
+ print(f"Info: Extracting codepoint table from patch set '{patch_set_name}'")
+
+ # Extract codepoint map from original font file; download if needed
+ source_filename = entry["Filename"]
+ target_folder = Path("nerd_font_symbol_fonts")
+ target_folder.mkdir(exist_ok=True)
+ target_file = target_folder / Path(source_filename).name
+ if not target_file.exists():
+ print(f"Info: Downloading '{source_filename}'")
+ urlretrieve(
+ f"https://github.com/ryanoasis/nerd-fonts/raw/refs/tags/v{nf_version}/src/glyphs/{source_filename}",
+ target_file,
+ )
+ try:
+ with TTFont(target_file) as patchfont:
+ patch_cmap = patchfont.getBestCmap()
+ except TTLibError:
+ # Not a TTF/OTF font. This is OK if this patch set is exact, so we
+ # let if pass. If there's a problem, later checks will catch it.
+ patch_cmap = None
+
+ # A glyph's scale rules are specified using its codepoint in
+ # the original font, which is sometimes different from its
+ # Nerd Font codepoint. If entry["Exact"] is False, the codepoints are
+ # mapped according to the following rules:
+ # * entry["SymStart"] and entry["SymEnd"] denote the patch set's codepoint
+ # range in the original font.
+ # * entry["SrcStart"] is the starting point of the patch set's mapped
+ # codepoint range. It must not be None if entry["Exact"] is False.
+ # * The destination codepoint range is packed; that is, while there may be
+ # gaps without glyphs in the original font's codepoint range, there are
+ # none in the Nerd Font range. Hence there is no constant codepoint
+ # offset; instead we must iterate through the range and increment the
+ # destination codepoint every time we encounter a glyph in the original
+ # font.
+ # If entry["Exact"] is True, the origin and Nerd Font codepoints are the
+ # same, gaps included, and entry["SrcStart"] must be None.
+ if entry["Exact"]:
+ assert entry["SrcStart"] is None
+ cp_nerdfont = 0
+ else:
+ assert entry["SrcStart"]
+ assert patch_cmap is not None
+ cp_nerdfont = entry["SrcStart"] - 1
+
+ if patch_set_name not in cp_tables:
+ # There are several patch sets with the same name, representing
+ # different codepoint ranges within the same original font. Merging
+ # these into a single table is OK. However, we need to keep separate
+ # tables for the different fonts to correctly deal with cases where
+ # they fill in each other's gaps.
+ cp_tables[patch_set_name] = {}
+ for cp_original in range(entry["SymStart"], entry["SymEnd"] + 1):
+ if patch_cmap and cp_original not in patch_cmap:
+ continue
+ if not entry["Exact"]:
+ cp_nerdfont += 1
+ else:
+ cp_nerdfont = cp_original
+ if cp_nerdfont not in cmap:
+ raise ValueError(
+ f"Missing codepoint in Symbols Only Font: {hex(cp_nerdfont)} in patch set '{patch_set_name}'"
+ )
+ elif cp_nerdfont in cp_nerdfont_used:
+ raise ValueError(
+ f"Overlap for codepoint {hex(cp_nerdfont)} in patch set '{patch_set_name}'"
+ )
+ cp_tables[patch_set_name][cp_original] = cp_nerdfont
+ cp_nerdfont_used.add(cp_nerdfont)
+
+ # Store the table and corresponding Nerd Fonts version together in a module.
+ with open("nerd_font_codepoint_tables.py", "w") as f:
+ print(
+ """#! This is a generated file, produced by nerd_font_codegen.py
+#! DO NOT EDIT BY HAND!
+#!
+#! This file specifies the mapping of codepoints in the original symbol
+#! fonts to codepoints in a patched Nerd Font. This is extracted from
+#! the nerd fonts patcher script and the symbol font files.""",
+ file=f,
+ )
+ print(f'version = "{nf_version}"', file=f)
+ print("cp_tables = {", file=f)
+ for name, table in cp_tables.items():
+ print(f' "{name}": {{', file=f)
+ for key, value in table.items():
+ print(f" {hex(key)}: {hex(value)},", file=f)
+ print(" },", file=f)
+ print("}", file=f)
+
+ return cp_tables
+
+
+def generate_zig_switch_arms(
+ patch_sets: list[PatchSet],
+ nerd_font: TTFont,
+ nf_version: str,
+) -> str:
+ cmap = nerd_font.getBestCmap()
+ glyphs = nerd_font.getGlyphSet()
+ cp_tables = generate_codepoint_tables(patch_sets, nerd_font, nf_version)
+
+ entries: dict[int, PatchSetAttributeEntry] = {}
+ for entry in patch_sets:
+ patch_set_name = entry["Name"]
+ print(f"Info: Extracting rules from patch set '{patch_set_name}'")
+
+ attributes = entry["Attributes"]
+ patch_set_entries: dict[int, PatchSetAttributeEntry] = {}
+
+ cp_table = cp_tables[patch_set_name]
+ for cp_original in range(entry["SymStart"], entry["SymEnd"] + 1):
+ if cp_original not in cp_table:
+ continue
+ cp_nerdfont = cp_table[cp_original]
+ if cp_nerdfont in entries:
+ raise ValueError(
+ f"Overlap for codepoint {hex(cp_nerdfont)} in patch set '{patch_set_name}'"
+ )
+ if cp_original in attributes:
+ patch_set_entries[cp_nerdfont] = attributes[cp_original].copy()
+ else:
+ patch_set_entries[cp_nerdfont] = attributes["default"].copy()
+
+ if entry["ScaleRules"] is not None:
+ for group in entry["ScaleRules"]["ScaleGroups"]:
+ xMin = math.inf
+ yMin = math.inf
+ xMax = -math.inf
+ yMax = -math.inf
+ individual_bounds: dict[int, tuple[int, int, int, int]] = {}
+ individual_advances: set[float] = set()
+ for cp_original in group:
+ if cp_original not in cp_table:
+ # There is one special case where a scale group includes
+ # a glyph from the original font that's not in any patch
+ # set, and hence not in the Symbols Only font. The point
+ # of this glyph is to add extra vertical padding to a
+ # stretched (^xy) scale group, which means that its
+ # scaled and aligned position would span the line height
+ # plus overlap. Thus, we can use any other stretched
+ # glyph with overlap as stand-in to get the vertical
+ # bounds, such as 0xE0B0 (powerline left hard divider).
+ # We don't worry about the horizontal bounds, as they by
+ # design should not affect the group's bounding box.
+ if (
+ patch_set_name == "Progress Indicators"
+ and cp_original == 0xEDFF
+ ):
+ glyph = glyphs[cmap[0xE0B0]]
+ bounds = BoundsPen(glyphSet=glyphs)
+ glyph.draw(bounds)
+ yMin = min(bounds.bounds[1], yMin)
+ yMax = max(bounds.bounds[3], yMax)
+ else:
+ # Other cases are due to lazily specified scale
+ # groups with gaps in the codepoint range.
+ print(
+ f"Info: Skipping scale group codepoint {hex(cp_original)}, which does not exist in patch set '{patch_set_name}'"
+ )
+ continue
+
+ cp_nerdfont = cp_table[cp_original]
+ glyph = glyphs[cmap[cp_nerdfont]]
+ individual_advances.add(glyph.width)
+ bounds = BoundsPen(glyphSet=glyphs)
+ glyph.draw(bounds)
+ individual_bounds[cp_nerdfont] = bounds.bounds
+ xMin = min(bounds.bounds[0], xMin)
+ yMin = min(bounds.bounds[1], yMin)
+ xMax = max(bounds.bounds[2], xMax)
+ yMax = max(bounds.bounds[3], yMax)
+ group_width = xMax - xMin
+ group_height = yMax - yMin
+ group_is_monospace = (len(individual_bounds) > 1) and (
+ len(individual_advances) == 1
+ )
+ for cp_original in group:
+ if cp_original not in cp_table:
+ continue
+ cp_nerdfont = cp_table[cp_original]
+ if (
+ # Scale groups may cut across patch sets, but we're only
+ # updating a single patch set at a time, so we skip
+ # codepoints not in it.
+ cp_nerdfont not in patch_set_entries
+ # Codepoints may contribute to the bounding box of multiple groups,
+ # but should be scaled according to the first group they are found
+ # in. Hence, to avoid overwriting, we need to skip codepoints that
+ # have already been assigned a scale group.
+ or "relative_height" in patch_set_entries[cp_nerdfont]
+ ):
+ continue
+ this_bounds = individual_bounds[cp_nerdfont]
+ this_height = this_bounds[3] - this_bounds[1]
+ patch_set_entries[cp_nerdfont]["relative_height"] = (
+ this_height / group_height
+ )
+ patch_set_entries[cp_nerdfont]["relative_y"] = (
+ this_bounds[1] - yMin
+ ) / group_height
+ # Horizontal alignment should only be grouped if the group is monospace,
+ # that is, if all glyphs in the group have the same advance width.
+ if group_is_monospace:
+ this_width = this_bounds[2] - this_bounds[0]
+ patch_set_entries[cp_nerdfont]["relative_width"] = (
+ this_width / group_width
+ )
+ patch_set_entries[cp_nerdfont]["relative_x"] = (
+ this_bounds[0] - xMin
+ ) / group_width
+ entries |= patch_set_entries
+
+ # Group codepoints by attribute key
+ grouped = defaultdict[AttributeHash, list[int]](list)
+ for cp, attr in entries.items():
+ grouped[attr_key(attr)].append(cp)
+
+ # Emit zig switch arms
+ result: list[str] = []
+ for codepoints in sorted(grouped.values()):
+ # Use one of the attrs in the group to emit the value
+ attr = entries[codepoints[0]]
+ result.append(emit_zig_entry_multikey(codepoints, attr))
+
+ return "\n".join(result)
+
+
+if __name__ == "__main__":
+ project_root = Path(__file__).resolve().parents[2]
+
+ nf_path = sys.argv[1]
+
+ nerd_font = TTFont(nf_path)
+
+ patcher_path = project_root / "vendor" / "nerd-fonts" / "font-patcher.py"
+ source = patcher_path.read_text(encoding="utf-8")
+ patch_set, nf_version = extract_patch_set_values(source)
+
+ out_path = project_root / "src" / "font" / "nerd_font_attributes.zig"
+
+ with out_path.open("w", encoding="utf-8") as f:
+ f.write("""//! This is a generated file, produced by nerd_font_codegen.py
+//! DO NOT EDIT BY HAND!
+//!
+//! This file provides info extracted from the nerd fonts patcher script,
+//! specifying the scaling/positioning attributes of various glyphs.
+
+const Constraint = @import("face.zig").RenderOptions.Constraint;
+
+/// Get the constraints for the provided codepoint.
+pub fn getConstraint(cp: u21) ?Constraint {
+ return switch (cp) {
+""")
+ f.write(generate_zig_switch_arms(patch_set, nerd_font, nf_version))
+ f.write("\n else => null,\n };\n}\n")
diff --git a/vendor/libghostty-vt/src/font/nerd_font_codepoint_tables.py b/vendor/libghostty-vt/src/font/nerd_font_codepoint_tables.py
new file mode 100644
index 00000000..89a623f1
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/nerd_font_codepoint_tables.py
@@ -0,0 +1,10449 @@
+#! This is a generated file, produced by nerd_font_codegen.py
+#! DO NOT EDIT BY HAND!
+#!
+#! This file specifies the mapping of codepoints in the original symbol
+#! fonts to codepoints in a patched Nerd Font. This is extracted from
+#! the nerd fonts patcher script and the symbol font files.
+version = "3.4.0"
+cp_tables = {
+ "Seti-UI + Custom": {
+ 0xe4fa: 0xe5fa,
+ 0xe4fb: 0xe5fb,
+ 0xe4fc: 0xe5fc,
+ 0xe4fd: 0xe5fd,
+ 0xe4fe: 0xe5fe,
+ 0xe4ff: 0xe5ff,
+ 0xe500: 0xe600,
+ 0xe501: 0xe601,
+ 0xe502: 0xe602,
+ 0xe503: 0xe603,
+ 0xe504: 0xe604,
+ 0xe505: 0xe605,
+ 0xe506: 0xe606,
+ 0xe507: 0xe607,
+ 0xe508: 0xe608,
+ 0xe509: 0xe609,
+ 0xe50a: 0xe60a,
+ 0xe50b: 0xe60b,
+ 0xe50c: 0xe60c,
+ 0xe50d: 0xe60d,
+ 0xe50e: 0xe60e,
+ 0xe50f: 0xe60f,
+ 0xe510: 0xe610,
+ 0xe511: 0xe611,
+ 0xe512: 0xe612,
+ 0xe513: 0xe613,
+ 0xe514: 0xe614,
+ 0xe515: 0xe615,
+ 0xe516: 0xe616,
+ 0xe517: 0xe617,
+ 0xe518: 0xe618,
+ 0xe519: 0xe619,
+ 0xe51a: 0xe61a,
+ 0xe51b: 0xe61b,
+ 0xe51c: 0xe61c,
+ 0xe51d: 0xe61d,
+ 0xe51e: 0xe61e,
+ 0xe51f: 0xe61f,
+ 0xe520: 0xe620,
+ 0xe521: 0xe621,
+ 0xe522: 0xe622,
+ 0xe523: 0xe623,
+ 0xe524: 0xe624,
+ 0xe525: 0xe625,
+ 0xe526: 0xe626,
+ 0xe527: 0xe627,
+ 0xe528: 0xe628,
+ 0xe529: 0xe629,
+ 0xe52a: 0xe62a,
+ 0xe52b: 0xe62b,
+ 0xe52c: 0xe62c,
+ 0xe52d: 0xe62d,
+ 0xe52e: 0xe62e,
+ 0xe52f: 0xe62f,
+ 0xe530: 0xe630,
+ 0xe531: 0xe631,
+ 0xe532: 0xe632,
+ 0xe533: 0xe633,
+ 0xe534: 0xe634,
+ 0xe535: 0xe635,
+ 0xe536: 0xe636,
+ 0xe537: 0xe637,
+ 0xe538: 0xe638,
+ 0xe539: 0xe639,
+ 0xe53a: 0xe63a,
+ 0xe53b: 0xe63b,
+ 0xe53c: 0xe63c,
+ 0xe53d: 0xe63d,
+ 0xe53e: 0xe63e,
+ 0xe53f: 0xe63f,
+ 0xe540: 0xe640,
+ 0xe541: 0xe641,
+ 0xe542: 0xe642,
+ 0xe543: 0xe643,
+ 0xe544: 0xe644,
+ 0xe545: 0xe645,
+ 0xe546: 0xe646,
+ 0xe547: 0xe647,
+ 0xe548: 0xe648,
+ 0xe549: 0xe649,
+ 0xe54a: 0xe64a,
+ 0xe54b: 0xe64b,
+ 0xe54c: 0xe64c,
+ 0xe54d: 0xe64d,
+ 0xe54e: 0xe64e,
+ 0xe54f: 0xe64f,
+ 0xe550: 0xe650,
+ 0xe551: 0xe651,
+ 0xe552: 0xe652,
+ 0xe553: 0xe653,
+ 0xe554: 0xe654,
+ 0xe555: 0xe655,
+ 0xe556: 0xe656,
+ 0xe557: 0xe657,
+ 0xe558: 0xe658,
+ 0xe559: 0xe659,
+ 0xe55a: 0xe65a,
+ 0xe55b: 0xe65b,
+ 0xe55c: 0xe65c,
+ 0xe55d: 0xe65d,
+ 0xe55e: 0xe65e,
+ 0xe55f: 0xe65f,
+ 0xe560: 0xe660,
+ 0xe561: 0xe661,
+ 0xe562: 0xe662,
+ 0xe563: 0xe663,
+ 0xe564: 0xe664,
+ 0xe565: 0xe665,
+ 0xe566: 0xe666,
+ 0xe567: 0xe667,
+ 0xe568: 0xe668,
+ 0xe569: 0xe669,
+ 0xe56a: 0xe66a,
+ 0xe56b: 0xe66b,
+ 0xe56c: 0xe66c,
+ 0xe56d: 0xe66d,
+ 0xe56e: 0xe66e,
+ 0xe56f: 0xe66f,
+ 0xe570: 0xe670,
+ 0xe571: 0xe671,
+ 0xe572: 0xe672,
+ 0xe573: 0xe673,
+ 0xe574: 0xe674,
+ 0xe575: 0xe675,
+ 0xe576: 0xe676,
+ 0xe577: 0xe677,
+ 0xe578: 0xe678,
+ 0xe579: 0xe679,
+ 0xe57a: 0xe67a,
+ 0xe57b: 0xe67b,
+ 0xe57c: 0xe67c,
+ 0xe57d: 0xe67d,
+ 0xe57e: 0xe67e,
+ 0xe57f: 0xe67f,
+ 0xe580: 0xe680,
+ 0xe581: 0xe681,
+ 0xe582: 0xe682,
+ 0xe583: 0xe683,
+ 0xe584: 0xe684,
+ 0xe585: 0xe685,
+ 0xe586: 0xe686,
+ 0xe587: 0xe687,
+ 0xe588: 0xe688,
+ 0xe589: 0xe689,
+ 0xe58a: 0xe68a,
+ 0xe58b: 0xe68b,
+ 0xe58c: 0xe68c,
+ 0xe58d: 0xe68d,
+ 0xe58e: 0xe68e,
+ 0xe58f: 0xe68f,
+ 0xe590: 0xe690,
+ 0xe591: 0xe691,
+ 0xe592: 0xe692,
+ 0xe593: 0xe693,
+ 0xe594: 0xe694,
+ 0xe595: 0xe695,
+ 0xe596: 0xe696,
+ 0xe597: 0xe697,
+ 0xe598: 0xe698,
+ 0xe599: 0xe699,
+ 0xe59a: 0xe69a,
+ 0xe59b: 0xe69b,
+ 0xe59c: 0xe69c,
+ 0xe59d: 0xe69d,
+ 0xe59e: 0xe69e,
+ 0xe59f: 0xe69f,
+ 0xe5a0: 0xe6a0,
+ 0xe5a1: 0xe6a1,
+ 0xe5a2: 0xe6a2,
+ 0xe5a3: 0xe6a3,
+ 0xe5a4: 0xe6a4,
+ 0xe5a5: 0xe6a5,
+ 0xe5a6: 0xe6a6,
+ 0xe5a7: 0xe6a7,
+ 0xe5a8: 0xe6a8,
+ 0xe5a9: 0xe6a9,
+ 0xe5aa: 0xe6aa,
+ 0xe5ab: 0xe6ab,
+ 0xe5ac: 0xe6ac,
+ 0xe5ad: 0xe6ad,
+ 0xe5ae: 0xe6ae,
+ 0xe5af: 0xe6af,
+ 0xe5b0: 0xe6b0,
+ 0xe5b1: 0xe6b1,
+ 0xe5b2: 0xe6b2,
+ 0xe5b3: 0xe6b3,
+ 0xe5b4: 0xe6b4,
+ 0xe5b5: 0xe6b5,
+ 0xe5b6: 0xe6b6,
+ 0xe5b7: 0xe6b7,
+ 0xe5b8: 0xe6b8,
+ },
+ "Heavy Angle Brackets": {
+ 0x276c: 0x276c,
+ 0x276d: 0x276d,
+ 0x276e: 0x276e,
+ 0x276f: 0x276f,
+ 0x2770: 0x2770,
+ 0x2771: 0x2771,
+ },
+ "Progress Indicators": {
+ 0xee00: 0xee00,
+ 0xee01: 0xee01,
+ 0xee02: 0xee02,
+ 0xee03: 0xee03,
+ 0xee04: 0xee04,
+ 0xee05: 0xee05,
+ 0xee06: 0xee06,
+ 0xee07: 0xee07,
+ 0xee08: 0xee08,
+ 0xee09: 0xee09,
+ 0xee0a: 0xee0a,
+ 0xee0b: 0xee0b,
+ },
+ "Devicons": {
+ 0xe600: 0xe700,
+ 0xe601: 0xe701,
+ 0xe602: 0xe702,
+ 0xe603: 0xe703,
+ 0xe604: 0xe704,
+ 0xe605: 0xe705,
+ 0xe606: 0xe706,
+ 0xe607: 0xe707,
+ 0xe608: 0xe708,
+ 0xe609: 0xe709,
+ 0xe60a: 0xe70a,
+ 0xe60b: 0xe70b,
+ 0xe60c: 0xe70c,
+ 0xe60d: 0xe70d,
+ 0xe60e: 0xe70e,
+ 0xe60f: 0xe70f,
+ 0xe610: 0xe710,
+ 0xe611: 0xe711,
+ 0xe612: 0xe712,
+ 0xe613: 0xe713,
+ 0xe614: 0xe714,
+ 0xe615: 0xe715,
+ 0xe616: 0xe716,
+ 0xe617: 0xe717,
+ 0xe618: 0xe718,
+ 0xe619: 0xe719,
+ 0xe61a: 0xe71a,
+ 0xe61b: 0xe71b,
+ 0xe61c: 0xe71c,
+ 0xe61d: 0xe71d,
+ 0xe61e: 0xe71e,
+ 0xe61f: 0xe71f,
+ 0xe620: 0xe720,
+ 0xe621: 0xe721,
+ 0xe622: 0xe722,
+ 0xe623: 0xe723,
+ 0xe624: 0xe724,
+ 0xe625: 0xe725,
+ 0xe626: 0xe726,
+ 0xe627: 0xe727,
+ 0xe628: 0xe728,
+ 0xe629: 0xe729,
+ 0xe62a: 0xe72a,
+ 0xe62b: 0xe72b,
+ 0xe62c: 0xe72c,
+ 0xe62d: 0xe72d,
+ 0xe62e: 0xe72e,
+ 0xe62f: 0xe72f,
+ 0xe630: 0xe730,
+ 0xe631: 0xe731,
+ 0xe632: 0xe732,
+ 0xe633: 0xe733,
+ 0xe634: 0xe734,
+ 0xe635: 0xe735,
+ 0xe636: 0xe736,
+ 0xe637: 0xe737,
+ 0xe638: 0xe738,
+ 0xe639: 0xe739,
+ 0xe63a: 0xe73a,
+ 0xe63b: 0xe73b,
+ 0xe63c: 0xe73c,
+ 0xe63d: 0xe73d,
+ 0xe63e: 0xe73e,
+ 0xe63f: 0xe73f,
+ 0xe640: 0xe740,
+ 0xe641: 0xe741,
+ 0xe642: 0xe742,
+ 0xe643: 0xe743,
+ 0xe644: 0xe744,
+ 0xe645: 0xe745,
+ 0xe646: 0xe746,
+ 0xe647: 0xe747,
+ 0xe648: 0xe748,
+ 0xe649: 0xe749,
+ 0xe64a: 0xe74a,
+ 0xe64b: 0xe74b,
+ 0xe64c: 0xe74c,
+ 0xe64d: 0xe74d,
+ 0xe64e: 0xe74e,
+ 0xe64f: 0xe74f,
+ 0xe650: 0xe750,
+ 0xe651: 0xe751,
+ 0xe652: 0xe752,
+ 0xe653: 0xe753,
+ 0xe654: 0xe754,
+ 0xe655: 0xe755,
+ 0xe656: 0xe756,
+ 0xe657: 0xe757,
+ 0xe658: 0xe758,
+ 0xe659: 0xe759,
+ 0xe65a: 0xe75a,
+ 0xe65b: 0xe75b,
+ 0xe65c: 0xe75c,
+ 0xe65d: 0xe75d,
+ 0xe65e: 0xe75e,
+ 0xe65f: 0xe75f,
+ 0xe660: 0xe760,
+ 0xe661: 0xe761,
+ 0xe662: 0xe762,
+ 0xe663: 0xe763,
+ 0xe664: 0xe764,
+ 0xe665: 0xe765,
+ 0xe666: 0xe766,
+ 0xe667: 0xe767,
+ 0xe668: 0xe768,
+ 0xe669: 0xe769,
+ 0xe66a: 0xe76a,
+ 0xe66b: 0xe76b,
+ 0xe66c: 0xe76c,
+ 0xe66d: 0xe76d,
+ 0xe66e: 0xe76e,
+ 0xe66f: 0xe76f,
+ 0xe670: 0xe770,
+ 0xe671: 0xe771,
+ 0xe672: 0xe772,
+ 0xe673: 0xe773,
+ 0xe674: 0xe774,
+ 0xe675: 0xe775,
+ 0xe676: 0xe776,
+ 0xe677: 0xe777,
+ 0xe678: 0xe778,
+ 0xe679: 0xe779,
+ 0xe67a: 0xe77a,
+ 0xe67b: 0xe77b,
+ 0xe67c: 0xe77c,
+ 0xe67d: 0xe77d,
+ 0xe67e: 0xe77e,
+ 0xe67f: 0xe77f,
+ 0xe680: 0xe780,
+ 0xe681: 0xe781,
+ 0xe682: 0xe782,
+ 0xe683: 0xe783,
+ 0xe684: 0xe784,
+ 0xe685: 0xe785,
+ 0xe686: 0xe786,
+ 0xe687: 0xe787,
+ 0xe688: 0xe788,
+ 0xe689: 0xe789,
+ 0xe68a: 0xe78a,
+ 0xe68b: 0xe78b,
+ 0xe68c: 0xe78c,
+ 0xe68d: 0xe78d,
+ 0xe68e: 0xe78e,
+ 0xe68f: 0xe78f,
+ 0xe690: 0xe790,
+ 0xe691: 0xe791,
+ 0xe692: 0xe792,
+ 0xe693: 0xe793,
+ 0xe694: 0xe794,
+ 0xe695: 0xe795,
+ 0xe696: 0xe796,
+ 0xe697: 0xe797,
+ 0xe698: 0xe798,
+ 0xe699: 0xe799,
+ 0xe69a: 0xe79a,
+ 0xe69b: 0xe79b,
+ 0xe69c: 0xe79c,
+ 0xe69d: 0xe79d,
+ 0xe69e: 0xe79e,
+ 0xe69f: 0xe79f,
+ 0xe6a0: 0xe7a0,
+ 0xe6a1: 0xe7a1,
+ 0xe6a2: 0xe7a2,
+ 0xe6a3: 0xe7a3,
+ 0xe6a4: 0xe7a4,
+ 0xe6a5: 0xe7a5,
+ 0xe6a6: 0xe7a6,
+ 0xe6a7: 0xe7a7,
+ 0xe6a8: 0xe7a8,
+ 0xe6a9: 0xe7a9,
+ 0xe6aa: 0xe7aa,
+ 0xe6ab: 0xe7ab,
+ 0xe6ac: 0xe7ac,
+ 0xe6ad: 0xe7ad,
+ 0xe6ae: 0xe7ae,
+ 0xe6af: 0xe7af,
+ 0xe6b0: 0xe7b0,
+ 0xe6b1: 0xe7b1,
+ 0xe6b2: 0xe7b2,
+ 0xe6b3: 0xe7b3,
+ 0xe6b4: 0xe7b4,
+ 0xe6b5: 0xe7b5,
+ 0xe6b6: 0xe7b6,
+ 0xe6b7: 0xe7b7,
+ 0xe6b8: 0xe7b8,
+ 0xe6b9: 0xe7b9,
+ 0xe6ba: 0xe7ba,
+ 0xe6bb: 0xe7bb,
+ 0xe6bc: 0xe7bc,
+ 0xe6bd: 0xe7bd,
+ 0xe6be: 0xe7be,
+ 0xe6bf: 0xe7bf,
+ 0xe6c0: 0xe7c0,
+ 0xe6c1: 0xe7c1,
+ 0xe6c2: 0xe7c2,
+ 0xe6c3: 0xe7c3,
+ 0xe6c4: 0xe7c4,
+ 0xe6c5: 0xe7c5,
+ 0xe6c6: 0xe7c6,
+ 0xe6c7: 0xe7c7,
+ 0xe6c8: 0xe7c8,
+ 0xe6c9: 0xe7c9,
+ 0xe6ca: 0xe7ca,
+ 0xe6cb: 0xe7cb,
+ 0xe6cc: 0xe7cc,
+ 0xe6cd: 0xe7cd,
+ 0xe6ce: 0xe7ce,
+ 0xe6cf: 0xe7cf,
+ 0xe6d0: 0xe7d0,
+ 0xe6d1: 0xe7d1,
+ 0xe6d2: 0xe7d2,
+ 0xe6d3: 0xe7d3,
+ 0xe6d4: 0xe7d4,
+ 0xe6d5: 0xe7d5,
+ 0xe6d6: 0xe7d6,
+ 0xe6d7: 0xe7d7,
+ 0xe6d8: 0xe7d8,
+ 0xe6d9: 0xe7d9,
+ 0xe6da: 0xe7da,
+ 0xe6db: 0xe7db,
+ 0xe6dc: 0xe7dc,
+ 0xe6dd: 0xe7dd,
+ 0xe6de: 0xe7de,
+ 0xe6df: 0xe7df,
+ 0xe6e0: 0xe7e0,
+ 0xe6e1: 0xe7e1,
+ 0xe6e2: 0xe7e2,
+ 0xe6e3: 0xe7e3,
+ 0xe6e4: 0xe7e4,
+ 0xe6e5: 0xe7e5,
+ 0xe6e6: 0xe7e6,
+ 0xe6e7: 0xe7e7,
+ 0xe6e8: 0xe7e8,
+ 0xe6e9: 0xe7e9,
+ 0xe6ea: 0xe7ea,
+ 0xe6eb: 0xe7eb,
+ 0xe6ec: 0xe7ec,
+ 0xe6ed: 0xe7ed,
+ 0xe6ee: 0xe7ee,
+ 0xe6ef: 0xe7ef,
+ 0xe6f0: 0xe7f0,
+ 0xe6f1: 0xe7f1,
+ 0xe6f2: 0xe7f2,
+ 0xe6f3: 0xe7f3,
+ 0xe6f4: 0xe7f4,
+ 0xe6f5: 0xe7f5,
+ 0xe6f6: 0xe7f6,
+ 0xe6f7: 0xe7f7,
+ 0xe6f8: 0xe7f8,
+ 0xe6f9: 0xe7f9,
+ 0xe6fa: 0xe7fa,
+ 0xe6fb: 0xe7fb,
+ 0xe6fc: 0xe7fc,
+ 0xe6fd: 0xe7fd,
+ 0xe6fe: 0xe7fe,
+ 0xe6ff: 0xe7ff,
+ 0xe700: 0xe800,
+ 0xe701: 0xe801,
+ 0xe702: 0xe802,
+ 0xe703: 0xe803,
+ 0xe704: 0xe804,
+ 0xe705: 0xe805,
+ 0xe706: 0xe806,
+ 0xe707: 0xe807,
+ 0xe708: 0xe808,
+ 0xe709: 0xe809,
+ 0xe70a: 0xe80a,
+ 0xe70b: 0xe80b,
+ 0xe70c: 0xe80c,
+ 0xe70d: 0xe80d,
+ 0xe70e: 0xe80e,
+ 0xe70f: 0xe80f,
+ 0xe710: 0xe810,
+ 0xe711: 0xe811,
+ 0xe712: 0xe812,
+ 0xe713: 0xe813,
+ 0xe714: 0xe814,
+ 0xe715: 0xe815,
+ 0xe716: 0xe816,
+ 0xe717: 0xe817,
+ 0xe718: 0xe818,
+ 0xe719: 0xe819,
+ 0xe71a: 0xe81a,
+ 0xe71b: 0xe81b,
+ 0xe71c: 0xe81c,
+ 0xe71d: 0xe81d,
+ 0xe71e: 0xe81e,
+ 0xe71f: 0xe81f,
+ 0xe720: 0xe820,
+ 0xe721: 0xe821,
+ 0xe722: 0xe822,
+ 0xe723: 0xe823,
+ 0xe724: 0xe824,
+ 0xe725: 0xe825,
+ 0xe726: 0xe826,
+ 0xe727: 0xe827,
+ 0xe728: 0xe828,
+ 0xe729: 0xe829,
+ 0xe72a: 0xe82a,
+ 0xe72b: 0xe82b,
+ 0xe72c: 0xe82c,
+ 0xe72d: 0xe82d,
+ 0xe72e: 0xe82e,
+ 0xe72f: 0xe82f,
+ 0xe730: 0xe830,
+ 0xe731: 0xe831,
+ 0xe732: 0xe832,
+ 0xe733: 0xe833,
+ 0xe734: 0xe834,
+ 0xe735: 0xe835,
+ 0xe736: 0xe836,
+ 0xe737: 0xe837,
+ 0xe738: 0xe838,
+ 0xe739: 0xe839,
+ 0xe73a: 0xe83a,
+ 0xe73b: 0xe83b,
+ 0xe73c: 0xe83c,
+ 0xe73d: 0xe83d,
+ 0xe73e: 0xe83e,
+ 0xe73f: 0xe83f,
+ 0xe740: 0xe840,
+ 0xe741: 0xe841,
+ 0xe742: 0xe842,
+ 0xe743: 0xe843,
+ 0xe744: 0xe844,
+ 0xe745: 0xe845,
+ 0xe746: 0xe846,
+ 0xe747: 0xe847,
+ 0xe748: 0xe848,
+ 0xe749: 0xe849,
+ 0xe74a: 0xe84a,
+ 0xe74b: 0xe84b,
+ 0xe74c: 0xe84c,
+ 0xe74d: 0xe84d,
+ 0xe74e: 0xe84e,
+ 0xe74f: 0xe84f,
+ 0xe750: 0xe850,
+ 0xe751: 0xe851,
+ 0xe752: 0xe852,
+ 0xe753: 0xe853,
+ 0xe754: 0xe854,
+ 0xe755: 0xe855,
+ 0xe756: 0xe856,
+ 0xe757: 0xe857,
+ 0xe758: 0xe858,
+ 0xe759: 0xe859,
+ 0xe75a: 0xe85a,
+ 0xe75b: 0xe85b,
+ 0xe75c: 0xe85c,
+ 0xe75d: 0xe85d,
+ 0xe75e: 0xe85e,
+ 0xe75f: 0xe85f,
+ 0xe760: 0xe860,
+ 0xe761: 0xe861,
+ 0xe762: 0xe862,
+ 0xe763: 0xe863,
+ 0xe764: 0xe864,
+ 0xe765: 0xe865,
+ 0xe766: 0xe866,
+ 0xe767: 0xe867,
+ 0xe768: 0xe868,
+ 0xe769: 0xe869,
+ 0xe76a: 0xe86a,
+ 0xe76b: 0xe86b,
+ 0xe76c: 0xe86c,
+ 0xe76d: 0xe86d,
+ 0xe76e: 0xe86e,
+ 0xe76f: 0xe86f,
+ 0xe770: 0xe870,
+ 0xe771: 0xe871,
+ 0xe772: 0xe872,
+ 0xe773: 0xe873,
+ 0xe774: 0xe874,
+ 0xe775: 0xe875,
+ 0xe776: 0xe876,
+ 0xe777: 0xe877,
+ 0xe778: 0xe878,
+ 0xe779: 0xe879,
+ 0xe77a: 0xe87a,
+ 0xe77b: 0xe87b,
+ 0xe77c: 0xe87c,
+ 0xe77d: 0xe87d,
+ 0xe77e: 0xe87e,
+ 0xe77f: 0xe87f,
+ 0xe780: 0xe880,
+ 0xe781: 0xe881,
+ 0xe782: 0xe882,
+ 0xe783: 0xe883,
+ 0xe784: 0xe884,
+ 0xe785: 0xe885,
+ 0xe786: 0xe886,
+ 0xe787: 0xe887,
+ 0xe788: 0xe888,
+ 0xe789: 0xe889,
+ 0xe78a: 0xe88a,
+ 0xe78b: 0xe88b,
+ 0xe78c: 0xe88c,
+ 0xe78d: 0xe88d,
+ 0xe78e: 0xe88e,
+ 0xe78f: 0xe88f,
+ 0xe790: 0xe890,
+ 0xe791: 0xe891,
+ 0xe792: 0xe892,
+ 0xe793: 0xe893,
+ 0xe794: 0xe894,
+ 0xe795: 0xe895,
+ 0xe796: 0xe896,
+ 0xe797: 0xe897,
+ 0xe798: 0xe898,
+ 0xe799: 0xe899,
+ 0xe79a: 0xe89a,
+ 0xe79b: 0xe89b,
+ 0xe79c: 0xe89c,
+ 0xe79d: 0xe89d,
+ 0xe79e: 0xe89e,
+ 0xe79f: 0xe89f,
+ 0xe7a0: 0xe8a0,
+ 0xe7a1: 0xe8a1,
+ 0xe7a2: 0xe8a2,
+ 0xe7a3: 0xe8a3,
+ 0xe7a4: 0xe8a4,
+ 0xe7a5: 0xe8a5,
+ 0xe7a6: 0xe8a6,
+ 0xe7a7: 0xe8a7,
+ 0xe7a8: 0xe8a8,
+ 0xe7a9: 0xe8a9,
+ 0xe7aa: 0xe8aa,
+ 0xe7ab: 0xe8ab,
+ 0xe7ac: 0xe8ac,
+ 0xe7ad: 0xe8ad,
+ 0xe7ae: 0xe8ae,
+ 0xe7af: 0xe8af,
+ 0xe7b0: 0xe8b0,
+ 0xe7b1: 0xe8b1,
+ 0xe7b2: 0xe8b2,
+ 0xe7b3: 0xe8b3,
+ 0xe7b4: 0xe8b4,
+ 0xe7b5: 0xe8b5,
+ 0xe7b6: 0xe8b6,
+ 0xe7b7: 0xe8b7,
+ 0xe7b8: 0xe8b8,
+ 0xe7b9: 0xe8b9,
+ 0xe7ba: 0xe8ba,
+ 0xe7bb: 0xe8bb,
+ 0xe7bc: 0xe8bc,
+ 0xe7bd: 0xe8bd,
+ 0xe7be: 0xe8be,
+ 0xe7bf: 0xe8bf,
+ 0xe7c0: 0xe8c0,
+ 0xe7c1: 0xe8c1,
+ 0xe7c2: 0xe8c2,
+ 0xe7c3: 0xe8c3,
+ 0xe7c4: 0xe8c4,
+ 0xe7c5: 0xe8c5,
+ 0xe7c6: 0xe8c6,
+ 0xe7c7: 0xe8c7,
+ 0xe7c8: 0xe8c8,
+ 0xe7c9: 0xe8c9,
+ 0xe7ca: 0xe8ca,
+ 0xe7cb: 0xe8cb,
+ 0xe7cc: 0xe8cc,
+ 0xe7cd: 0xe8cd,
+ 0xe7ce: 0xe8ce,
+ 0xe7cf: 0xe8cf,
+ 0xe7d0: 0xe8d0,
+ 0xe7d1: 0xe8d1,
+ 0xe7d2: 0xe8d2,
+ 0xe7d3: 0xe8d3,
+ 0xe7d4: 0xe8d4,
+ 0xe7d5: 0xe8d5,
+ 0xe7d6: 0xe8d6,
+ 0xe7d7: 0xe8d7,
+ 0xe7d8: 0xe8d8,
+ 0xe7d9: 0xe8d9,
+ 0xe7da: 0xe8da,
+ 0xe7db: 0xe8db,
+ 0xe7dc: 0xe8dc,
+ 0xe7dd: 0xe8dd,
+ 0xe7de: 0xe8de,
+ 0xe7df: 0xe8df,
+ 0xe7e0: 0xe8e0,
+ 0xe7e1: 0xe8e1,
+ 0xe7e2: 0xe8e2,
+ 0xe7e3: 0xe8e3,
+ 0xe7e4: 0xe8e4,
+ 0xe7e5: 0xe8e5,
+ 0xe7e6: 0xe8e6,
+ 0xe7e7: 0xe8e7,
+ 0xe7e8: 0xe8e8,
+ 0xe7e9: 0xe8e9,
+ 0xe7ea: 0xe8ea,
+ 0xe7eb: 0xe8eb,
+ 0xe7ec: 0xe8ec,
+ 0xe7ed: 0xe8ed,
+ 0xe7ee: 0xe8ee,
+ 0xe7ef: 0xe8ef,
+ },
+ "Powerline Symbols": {
+ 0xe0a0: 0xe0a0,
+ 0xe0a1: 0xe0a1,
+ 0xe0a2: 0xe0a2,
+ 0xe0b0: 0xe0b0,
+ 0xe0b1: 0xe0b1,
+ 0xe0b2: 0xe0b2,
+ 0xe0b3: 0xe0b3,
+ },
+ "Powerline Extra Symbols": {
+ 0xe0a3: 0xe0a3,
+ 0xe0b4: 0xe0b4,
+ 0xe0b5: 0xe0b5,
+ 0xe0b6: 0xe0b6,
+ 0xe0b7: 0xe0b7,
+ 0xe0b8: 0xe0b8,
+ 0xe0b9: 0xe0b9,
+ 0xe0ba: 0xe0ba,
+ 0xe0bb: 0xe0bb,
+ 0xe0bc: 0xe0bc,
+ 0xe0bd: 0xe0bd,
+ 0xe0be: 0xe0be,
+ 0xe0bf: 0xe0bf,
+ 0xe0c0: 0xe0c0,
+ 0xe0c1: 0xe0c1,
+ 0xe0c2: 0xe0c2,
+ 0xe0c3: 0xe0c3,
+ 0xe0c4: 0xe0c4,
+ 0xe0c5: 0xe0c5,
+ 0xe0c6: 0xe0c6,
+ 0xe0c7: 0xe0c7,
+ 0xe0c8: 0xe0c8,
+ 0xe0ca: 0xe0ca,
+ 0xe0cc: 0xe0cc,
+ 0xe0cd: 0xe0cd,
+ 0xe0ce: 0xe0ce,
+ 0xe0cf: 0xe0cf,
+ 0xe0d0: 0xe0d0,
+ 0xe0d1: 0xe0d1,
+ 0xe0d2: 0xe0d2,
+ 0xe0d4: 0xe0d4,
+ 0xe0d6: 0xe0d6,
+ 0xe0d7: 0xe0d7,
+ 0x2630: 0x2630,
+ },
+ "Pomicons": {
+ 0xe000: 0xe000,
+ 0xe001: 0xe001,
+ 0xe002: 0xe002,
+ 0xe003: 0xe003,
+ 0xe004: 0xe004,
+ 0xe005: 0xe005,
+ 0xe006: 0xe006,
+ 0xe007: 0xe007,
+ 0xe008: 0xe008,
+ 0xe009: 0xe009,
+ 0xe00a: 0xe00a,
+ },
+ "Font Awesome": {
+ 0xed00: 0xed00,
+ 0xed01: 0xed01,
+ 0xed02: 0xed02,
+ 0xed03: 0xed03,
+ 0xed04: 0xed04,
+ 0xed05: 0xed05,
+ 0xed06: 0xed06,
+ 0xed07: 0xed07,
+ 0xed08: 0xed08,
+ 0xed09: 0xed09,
+ 0xed0a: 0xed0a,
+ 0xed0b: 0xed0b,
+ 0xed0c: 0xed0c,
+ 0xed0d: 0xed0d,
+ 0xed0e: 0xed0e,
+ 0xed0f: 0xed0f,
+ 0xed10: 0xed10,
+ 0xed11: 0xed11,
+ 0xed12: 0xed12,
+ 0xed13: 0xed13,
+ 0xed14: 0xed14,
+ 0xed15: 0xed15,
+ 0xed16: 0xed16,
+ 0xed17: 0xed17,
+ 0xed18: 0xed18,
+ 0xed19: 0xed19,
+ 0xed1a: 0xed1a,
+ 0xed1b: 0xed1b,
+ 0xed1c: 0xed1c,
+ 0xed1d: 0xed1d,
+ 0xed1e: 0xed1e,
+ 0xed1f: 0xed1f,
+ 0xed20: 0xed20,
+ 0xed21: 0xed21,
+ 0xed22: 0xed22,
+ 0xed23: 0xed23,
+ 0xed24: 0xed24,
+ 0xed25: 0xed25,
+ 0xed26: 0xed26,
+ 0xed27: 0xed27,
+ 0xed28: 0xed28,
+ 0xed29: 0xed29,
+ 0xed2a: 0xed2a,
+ 0xed2b: 0xed2b,
+ 0xed2c: 0xed2c,
+ 0xed2d: 0xed2d,
+ 0xed2e: 0xed2e,
+ 0xed2f: 0xed2f,
+ 0xed30: 0xed30,
+ 0xed31: 0xed31,
+ 0xed32: 0xed32,
+ 0xed33: 0xed33,
+ 0xed34: 0xed34,
+ 0xed35: 0xed35,
+ 0xed36: 0xed36,
+ 0xed37: 0xed37,
+ 0xed38: 0xed38,
+ 0xed39: 0xed39,
+ 0xed3a: 0xed3a,
+ 0xed3b: 0xed3b,
+ 0xed3c: 0xed3c,
+ 0xed3d: 0xed3d,
+ 0xed3e: 0xed3e,
+ 0xed3f: 0xed3f,
+ 0xed40: 0xed40,
+ 0xed41: 0xed41,
+ 0xed42: 0xed42,
+ 0xed43: 0xed43,
+ 0xed44: 0xed44,
+ 0xed45: 0xed45,
+ 0xed46: 0xed46,
+ 0xed47: 0xed47,
+ 0xed48: 0xed48,
+ 0xed49: 0xed49,
+ 0xed4a: 0xed4a,
+ 0xed4b: 0xed4b,
+ 0xed4c: 0xed4c,
+ 0xed4d: 0xed4d,
+ 0xed4e: 0xed4e,
+ 0xed4f: 0xed4f,
+ 0xed50: 0xed50,
+ 0xed51: 0xed51,
+ 0xed52: 0xed52,
+ 0xed53: 0xed53,
+ 0xed54: 0xed54,
+ 0xed55: 0xed55,
+ 0xed56: 0xed56,
+ 0xed57: 0xed57,
+ 0xed58: 0xed58,
+ 0xed59: 0xed59,
+ 0xed5a: 0xed5a,
+ 0xed5b: 0xed5b,
+ 0xed5c: 0xed5c,
+ 0xed5d: 0xed5d,
+ 0xed5e: 0xed5e,
+ 0xed5f: 0xed5f,
+ 0xed60: 0xed60,
+ 0xed61: 0xed61,
+ 0xed62: 0xed62,
+ 0xed63: 0xed63,
+ 0xed64: 0xed64,
+ 0xed65: 0xed65,
+ 0xed66: 0xed66,
+ 0xed67: 0xed67,
+ 0xed68: 0xed68,
+ 0xed69: 0xed69,
+ 0xed6a: 0xed6a,
+ 0xed6b: 0xed6b,
+ 0xed6c: 0xed6c,
+ 0xed6d: 0xed6d,
+ 0xed6e: 0xed6e,
+ 0xed6f: 0xed6f,
+ 0xed70: 0xed70,
+ 0xed71: 0xed71,
+ 0xed72: 0xed72,
+ 0xed73: 0xed73,
+ 0xed74: 0xed74,
+ 0xed75: 0xed75,
+ 0xed76: 0xed76,
+ 0xed77: 0xed77,
+ 0xed78: 0xed78,
+ 0xed79: 0xed79,
+ 0xed7a: 0xed7a,
+ 0xed7b: 0xed7b,
+ 0xed7c: 0xed7c,
+ 0xed7d: 0xed7d,
+ 0xed7e: 0xed7e,
+ 0xed7f: 0xed7f,
+ 0xed80: 0xed80,
+ 0xed81: 0xed81,
+ 0xed82: 0xed82,
+ 0xed83: 0xed83,
+ 0xed84: 0xed84,
+ 0xed85: 0xed85,
+ 0xed86: 0xed86,
+ 0xed87: 0xed87,
+ 0xed88: 0xed88,
+ 0xed89: 0xed89,
+ 0xed8a: 0xed8a,
+ 0xed8b: 0xed8b,
+ 0xed8c: 0xed8c,
+ 0xed8d: 0xed8d,
+ 0xed8e: 0xed8e,
+ 0xed8f: 0xed8f,
+ 0xed90: 0xed90,
+ 0xed91: 0xed91,
+ 0xed92: 0xed92,
+ 0xed93: 0xed93,
+ 0xed94: 0xed94,
+ 0xed95: 0xed95,
+ 0xed96: 0xed96,
+ 0xed97: 0xed97,
+ 0xed98: 0xed98,
+ 0xed99: 0xed99,
+ 0xed9a: 0xed9a,
+ 0xed9b: 0xed9b,
+ 0xed9c: 0xed9c,
+ 0xed9d: 0xed9d,
+ 0xed9e: 0xed9e,
+ 0xed9f: 0xed9f,
+ 0xeda0: 0xeda0,
+ 0xeda1: 0xeda1,
+ 0xeda2: 0xeda2,
+ 0xeda3: 0xeda3,
+ 0xeda4: 0xeda4,
+ 0xeda5: 0xeda5,
+ 0xeda6: 0xeda6,
+ 0xeda7: 0xeda7,
+ 0xeda8: 0xeda8,
+ 0xeda9: 0xeda9,
+ 0xedaa: 0xedaa,
+ 0xedab: 0xedab,
+ 0xedac: 0xedac,
+ 0xedad: 0xedad,
+ 0xedae: 0xedae,
+ 0xedaf: 0xedaf,
+ 0xedb0: 0xedb0,
+ 0xedb1: 0xedb1,
+ 0xedb2: 0xedb2,
+ 0xedb3: 0xedb3,
+ 0xedb4: 0xedb4,
+ 0xedb5: 0xedb5,
+ 0xedb6: 0xedb6,
+ 0xedb7: 0xedb7,
+ 0xedb8: 0xedb8,
+ 0xedb9: 0xedb9,
+ 0xedba: 0xedba,
+ 0xedbb: 0xedbb,
+ 0xedbc: 0xedbc,
+ 0xedbd: 0xedbd,
+ 0xedbe: 0xedbe,
+ 0xedbf: 0xedbf,
+ 0xedc0: 0xedc0,
+ 0xedc1: 0xedc1,
+ 0xedc2: 0xedc2,
+ 0xedc3: 0xedc3,
+ 0xedc4: 0xedc4,
+ 0xedc5: 0xedc5,
+ 0xedc6: 0xedc6,
+ 0xedc7: 0xedc7,
+ 0xedc8: 0xedc8,
+ 0xedc9: 0xedc9,
+ 0xedca: 0xedca,
+ 0xedcb: 0xedcb,
+ 0xedcc: 0xedcc,
+ 0xedcd: 0xedcd,
+ 0xedce: 0xedce,
+ 0xedcf: 0xedcf,
+ 0xedd0: 0xedd0,
+ 0xedd1: 0xedd1,
+ 0xedd2: 0xedd2,
+ 0xedd3: 0xedd3,
+ 0xedd4: 0xedd4,
+ 0xedd5: 0xedd5,
+ 0xedd6: 0xedd6,
+ 0xedd7: 0xedd7,
+ 0xedd8: 0xedd8,
+ 0xedd9: 0xedd9,
+ 0xedda: 0xedda,
+ 0xeddb: 0xeddb,
+ 0xeddc: 0xeddc,
+ 0xeddd: 0xeddd,
+ 0xedde: 0xedde,
+ 0xeddf: 0xeddf,
+ 0xede0: 0xede0,
+ 0xede1: 0xede1,
+ 0xede2: 0xede2,
+ 0xede3: 0xede3,
+ 0xede4: 0xede4,
+ 0xede5: 0xede5,
+ 0xede6: 0xede6,
+ 0xede7: 0xede7,
+ 0xede8: 0xede8,
+ 0xede9: 0xede9,
+ 0xedea: 0xedea,
+ 0xedeb: 0xedeb,
+ 0xedec: 0xedec,
+ 0xeded: 0xeded,
+ 0xedee: 0xedee,
+ 0xedef: 0xedef,
+ 0xedf0: 0xedf0,
+ 0xedf1: 0xedf1,
+ 0xedf2: 0xedf2,
+ 0xedf3: 0xedf3,
+ 0xedf4: 0xedf4,
+ 0xedf5: 0xedf5,
+ 0xedf6: 0xedf6,
+ 0xedf7: 0xedf7,
+ 0xedf8: 0xedf8,
+ 0xedf9: 0xedf9,
+ 0xedfa: 0xedfa,
+ 0xedfb: 0xedfb,
+ 0xedfc: 0xedfc,
+ 0xedfd: 0xedfd,
+ 0xedfe: 0xedfe,
+ 0xedff: 0xedff,
+ 0xee0c: 0xee0c,
+ 0xee0d: 0xee0d,
+ 0xee0e: 0xee0e,
+ 0xee0f: 0xee0f,
+ 0xee10: 0xee10,
+ 0xee11: 0xee11,
+ 0xee12: 0xee12,
+ 0xee13: 0xee13,
+ 0xee14: 0xee14,
+ 0xee15: 0xee15,
+ 0xee16: 0xee16,
+ 0xee17: 0xee17,
+ 0xee18: 0xee18,
+ 0xee19: 0xee19,
+ 0xee1a: 0xee1a,
+ 0xee1b: 0xee1b,
+ 0xee1c: 0xee1c,
+ 0xee1d: 0xee1d,
+ 0xee1e: 0xee1e,
+ 0xee1f: 0xee1f,
+ 0xee20: 0xee20,
+ 0xee21: 0xee21,
+ 0xee22: 0xee22,
+ 0xee23: 0xee23,
+ 0xee24: 0xee24,
+ 0xee25: 0xee25,
+ 0xee26: 0xee26,
+ 0xee27: 0xee27,
+ 0xee28: 0xee28,
+ 0xee29: 0xee29,
+ 0xee2a: 0xee2a,
+ 0xee2b: 0xee2b,
+ 0xee2c: 0xee2c,
+ 0xee2d: 0xee2d,
+ 0xee2e: 0xee2e,
+ 0xee2f: 0xee2f,
+ 0xee30: 0xee30,
+ 0xee31: 0xee31,
+ 0xee32: 0xee32,
+ 0xee33: 0xee33,
+ 0xee34: 0xee34,
+ 0xee35: 0xee35,
+ 0xee36: 0xee36,
+ 0xee37: 0xee37,
+ 0xee38: 0xee38,
+ 0xee39: 0xee39,
+ 0xee3a: 0xee3a,
+ 0xee3b: 0xee3b,
+ 0xee3c: 0xee3c,
+ 0xee3d: 0xee3d,
+ 0xee3e: 0xee3e,
+ 0xee3f: 0xee3f,
+ 0xee40: 0xee40,
+ 0xee41: 0xee41,
+ 0xee42: 0xee42,
+ 0xee43: 0xee43,
+ 0xee44: 0xee44,
+ 0xee45: 0xee45,
+ 0xee46: 0xee46,
+ 0xee47: 0xee47,
+ 0xee48: 0xee48,
+ 0xee49: 0xee49,
+ 0xee4a: 0xee4a,
+ 0xee4b: 0xee4b,
+ 0xee4c: 0xee4c,
+ 0xee4d: 0xee4d,
+ 0xee4e: 0xee4e,
+ 0xee4f: 0xee4f,
+ 0xee50: 0xee50,
+ 0xee51: 0xee51,
+ 0xee52: 0xee52,
+ 0xee53: 0xee53,
+ 0xee54: 0xee54,
+ 0xee55: 0xee55,
+ 0xee56: 0xee56,
+ 0xee57: 0xee57,
+ 0xee58: 0xee58,
+ 0xee59: 0xee59,
+ 0xee5a: 0xee5a,
+ 0xee5b: 0xee5b,
+ 0xee5c: 0xee5c,
+ 0xee5d: 0xee5d,
+ 0xee5e: 0xee5e,
+ 0xee5f: 0xee5f,
+ 0xee60: 0xee60,
+ 0xee61: 0xee61,
+ 0xee62: 0xee62,
+ 0xee63: 0xee63,
+ 0xee64: 0xee64,
+ 0xee65: 0xee65,
+ 0xee66: 0xee66,
+ 0xee67: 0xee67,
+ 0xee68: 0xee68,
+ 0xee69: 0xee69,
+ 0xee6a: 0xee6a,
+ 0xee6b: 0xee6b,
+ 0xee6c: 0xee6c,
+ 0xee6d: 0xee6d,
+ 0xee6e: 0xee6e,
+ 0xee6f: 0xee6f,
+ 0xee70: 0xee70,
+ 0xee71: 0xee71,
+ 0xee72: 0xee72,
+ 0xee73: 0xee73,
+ 0xee74: 0xee74,
+ 0xee75: 0xee75,
+ 0xee76: 0xee76,
+ 0xee77: 0xee77,
+ 0xee78: 0xee78,
+ 0xee79: 0xee79,
+ 0xee7a: 0xee7a,
+ 0xee7b: 0xee7b,
+ 0xee7c: 0xee7c,
+ 0xee7d: 0xee7d,
+ 0xee7e: 0xee7e,
+ 0xee7f: 0xee7f,
+ 0xee80: 0xee80,
+ 0xee81: 0xee81,
+ 0xee82: 0xee82,
+ 0xee83: 0xee83,
+ 0xee84: 0xee84,
+ 0xee85: 0xee85,
+ 0xee86: 0xee86,
+ 0xee87: 0xee87,
+ 0xee88: 0xee88,
+ 0xee89: 0xee89,
+ 0xee8a: 0xee8a,
+ 0xee8b: 0xee8b,
+ 0xee8c: 0xee8c,
+ 0xee8d: 0xee8d,
+ 0xee8e: 0xee8e,
+ 0xee8f: 0xee8f,
+ 0xee90: 0xee90,
+ 0xee91: 0xee91,
+ 0xee92: 0xee92,
+ 0xee93: 0xee93,
+ 0xee94: 0xee94,
+ 0xee95: 0xee95,
+ 0xee96: 0xee96,
+ 0xee97: 0xee97,
+ 0xee98: 0xee98,
+ 0xee99: 0xee99,
+ 0xee9a: 0xee9a,
+ 0xee9b: 0xee9b,
+ 0xee9c: 0xee9c,
+ 0xee9d: 0xee9d,
+ 0xee9e: 0xee9e,
+ 0xee9f: 0xee9f,
+ 0xeea0: 0xeea0,
+ 0xeea1: 0xeea1,
+ 0xeea2: 0xeea2,
+ 0xeea3: 0xeea3,
+ 0xeea4: 0xeea4,
+ 0xeea5: 0xeea5,
+ 0xeea6: 0xeea6,
+ 0xeea7: 0xeea7,
+ 0xeea8: 0xeea8,
+ 0xeea9: 0xeea9,
+ 0xeeaa: 0xeeaa,
+ 0xeeab: 0xeeab,
+ 0xeeac: 0xeeac,
+ 0xeead: 0xeead,
+ 0xeeae: 0xeeae,
+ 0xeeaf: 0xeeaf,
+ 0xeeb0: 0xeeb0,
+ 0xeeb1: 0xeeb1,
+ 0xeeb2: 0xeeb2,
+ 0xeeb3: 0xeeb3,
+ 0xeeb4: 0xeeb4,
+ 0xeeb5: 0xeeb5,
+ 0xeeb6: 0xeeb6,
+ 0xeeb7: 0xeeb7,
+ 0xeeb8: 0xeeb8,
+ 0xeeb9: 0xeeb9,
+ 0xeeba: 0xeeba,
+ 0xeebb: 0xeebb,
+ 0xeebc: 0xeebc,
+ 0xeebd: 0xeebd,
+ 0xeebe: 0xeebe,
+ 0xeebf: 0xeebf,
+ 0xeec0: 0xeec0,
+ 0xeec1: 0xeec1,
+ 0xeec2: 0xeec2,
+ 0xeec3: 0xeec3,
+ 0xeec4: 0xeec4,
+ 0xeec5: 0xeec5,
+ 0xeec6: 0xeec6,
+ 0xeec7: 0xeec7,
+ 0xeec8: 0xeec8,
+ 0xeec9: 0xeec9,
+ 0xeeca: 0xeeca,
+ 0xeecb: 0xeecb,
+ 0xeecc: 0xeecc,
+ 0xeecd: 0xeecd,
+ 0xeece: 0xeece,
+ 0xeecf: 0xeecf,
+ 0xeed0: 0xeed0,
+ 0xeed1: 0xeed1,
+ 0xeed2: 0xeed2,
+ 0xeed3: 0xeed3,
+ 0xeed4: 0xeed4,
+ 0xeed5: 0xeed5,
+ 0xeed6: 0xeed6,
+ 0xeed7: 0xeed7,
+ 0xeed8: 0xeed8,
+ 0xeed9: 0xeed9,
+ 0xeeda: 0xeeda,
+ 0xeedb: 0xeedb,
+ 0xeedc: 0xeedc,
+ 0xeedd: 0xeedd,
+ 0xeede: 0xeede,
+ 0xeedf: 0xeedf,
+ 0xeee0: 0xeee0,
+ 0xeee1: 0xeee1,
+ 0xeee2: 0xeee2,
+ 0xeee3: 0xeee3,
+ 0xeee4: 0xeee4,
+ 0xeee5: 0xeee5,
+ 0xeee6: 0xeee6,
+ 0xeee7: 0xeee7,
+ 0xeee8: 0xeee8,
+ 0xeee9: 0xeee9,
+ 0xeeea: 0xeeea,
+ 0xeeeb: 0xeeeb,
+ 0xeeec: 0xeeec,
+ 0xeeed: 0xeeed,
+ 0xeeee: 0xeeee,
+ 0xeeef: 0xeeef,
+ 0xeef0: 0xeef0,
+ 0xeef1: 0xeef1,
+ 0xeef2: 0xeef2,
+ 0xeef3: 0xeef3,
+ 0xeef4: 0xeef4,
+ 0xeef5: 0xeef5,
+ 0xeef6: 0xeef6,
+ 0xeef7: 0xeef7,
+ 0xeef8: 0xeef8,
+ 0xeef9: 0xeef9,
+ 0xeefa: 0xeefa,
+ 0xeefb: 0xeefb,
+ 0xeefc: 0xeefc,
+ 0xeefd: 0xeefd,
+ 0xeefe: 0xeefe,
+ 0xeeff: 0xeeff,
+ 0xef00: 0xef00,
+ 0xef01: 0xef01,
+ 0xef02: 0xef02,
+ 0xef03: 0xef03,
+ 0xef04: 0xef04,
+ 0xef05: 0xef05,
+ 0xef06: 0xef06,
+ 0xef07: 0xef07,
+ 0xef08: 0xef08,
+ 0xef09: 0xef09,
+ 0xef0a: 0xef0a,
+ 0xef0b: 0xef0b,
+ 0xef0c: 0xef0c,
+ 0xef0d: 0xef0d,
+ 0xef0e: 0xef0e,
+ 0xef0f: 0xef0f,
+ 0xef10: 0xef10,
+ 0xef11: 0xef11,
+ 0xef12: 0xef12,
+ 0xef13: 0xef13,
+ 0xef14: 0xef14,
+ 0xef15: 0xef15,
+ 0xef16: 0xef16,
+ 0xef17: 0xef17,
+ 0xef18: 0xef18,
+ 0xef19: 0xef19,
+ 0xef1a: 0xef1a,
+ 0xef1b: 0xef1b,
+ 0xef1c: 0xef1c,
+ 0xef1d: 0xef1d,
+ 0xef1e: 0xef1e,
+ 0xef1f: 0xef1f,
+ 0xef20: 0xef20,
+ 0xef21: 0xef21,
+ 0xef22: 0xef22,
+ 0xef23: 0xef23,
+ 0xef24: 0xef24,
+ 0xef25: 0xef25,
+ 0xef26: 0xef26,
+ 0xef27: 0xef27,
+ 0xef28: 0xef28,
+ 0xef29: 0xef29,
+ 0xef2a: 0xef2a,
+ 0xef2b: 0xef2b,
+ 0xef2c: 0xef2c,
+ 0xef2d: 0xef2d,
+ 0xef2e: 0xef2e,
+ 0xef2f: 0xef2f,
+ 0xef30: 0xef30,
+ 0xef31: 0xef31,
+ 0xef32: 0xef32,
+ 0xef33: 0xef33,
+ 0xef34: 0xef34,
+ 0xef35: 0xef35,
+ 0xef36: 0xef36,
+ 0xef37: 0xef37,
+ 0xef38: 0xef38,
+ 0xef39: 0xef39,
+ 0xef3a: 0xef3a,
+ 0xef3b: 0xef3b,
+ 0xef3c: 0xef3c,
+ 0xef3d: 0xef3d,
+ 0xef3e: 0xef3e,
+ 0xef3f: 0xef3f,
+ 0xef40: 0xef40,
+ 0xef41: 0xef41,
+ 0xef42: 0xef42,
+ 0xef43: 0xef43,
+ 0xef44: 0xef44,
+ 0xef45: 0xef45,
+ 0xef46: 0xef46,
+ 0xef47: 0xef47,
+ 0xef48: 0xef48,
+ 0xef49: 0xef49,
+ 0xef4a: 0xef4a,
+ 0xef4b: 0xef4b,
+ 0xef4c: 0xef4c,
+ 0xef4d: 0xef4d,
+ 0xef4e: 0xef4e,
+ 0xef4f: 0xef4f,
+ 0xef50: 0xef50,
+ 0xef51: 0xef51,
+ 0xef52: 0xef52,
+ 0xef53: 0xef53,
+ 0xef54: 0xef54,
+ 0xef55: 0xef55,
+ 0xef56: 0xef56,
+ 0xef57: 0xef57,
+ 0xef58: 0xef58,
+ 0xef59: 0xef59,
+ 0xef5a: 0xef5a,
+ 0xef5b: 0xef5b,
+ 0xef5c: 0xef5c,
+ 0xef5d: 0xef5d,
+ 0xef5e: 0xef5e,
+ 0xef5f: 0xef5f,
+ 0xef60: 0xef60,
+ 0xef61: 0xef61,
+ 0xef62: 0xef62,
+ 0xef63: 0xef63,
+ 0xef64: 0xef64,
+ 0xef65: 0xef65,
+ 0xef66: 0xef66,
+ 0xef67: 0xef67,
+ 0xef68: 0xef68,
+ 0xef69: 0xef69,
+ 0xef6a: 0xef6a,
+ 0xef6b: 0xef6b,
+ 0xef6c: 0xef6c,
+ 0xef6d: 0xef6d,
+ 0xef6e: 0xef6e,
+ 0xef6f: 0xef6f,
+ 0xef70: 0xef70,
+ 0xef71: 0xef71,
+ 0xef72: 0xef72,
+ 0xef73: 0xef73,
+ 0xef74: 0xef74,
+ 0xef75: 0xef75,
+ 0xef76: 0xef76,
+ 0xef77: 0xef77,
+ 0xef78: 0xef78,
+ 0xef79: 0xef79,
+ 0xef7a: 0xef7a,
+ 0xef7b: 0xef7b,
+ 0xef7c: 0xef7c,
+ 0xef7d: 0xef7d,
+ 0xef7e: 0xef7e,
+ 0xef7f: 0xef7f,
+ 0xef80: 0xef80,
+ 0xef81: 0xef81,
+ 0xef82: 0xef82,
+ 0xef83: 0xef83,
+ 0xef84: 0xef84,
+ 0xef85: 0xef85,
+ 0xef86: 0xef86,
+ 0xef87: 0xef87,
+ 0xef88: 0xef88,
+ 0xef89: 0xef89,
+ 0xef8a: 0xef8a,
+ 0xef8b: 0xef8b,
+ 0xef8c: 0xef8c,
+ 0xef8d: 0xef8d,
+ 0xef8e: 0xef8e,
+ 0xef8f: 0xef8f,
+ 0xef90: 0xef90,
+ 0xef91: 0xef91,
+ 0xef92: 0xef92,
+ 0xef93: 0xef93,
+ 0xef94: 0xef94,
+ 0xef95: 0xef95,
+ 0xef96: 0xef96,
+ 0xef97: 0xef97,
+ 0xef98: 0xef98,
+ 0xef99: 0xef99,
+ 0xef9a: 0xef9a,
+ 0xef9b: 0xef9b,
+ 0xef9c: 0xef9c,
+ 0xef9d: 0xef9d,
+ 0xef9e: 0xef9e,
+ 0xef9f: 0xef9f,
+ 0xefa0: 0xefa0,
+ 0xefa1: 0xefa1,
+ 0xefa2: 0xefa2,
+ 0xefa3: 0xefa3,
+ 0xefa4: 0xefa4,
+ 0xefa5: 0xefa5,
+ 0xefa6: 0xefa6,
+ 0xefa7: 0xefa7,
+ 0xefa8: 0xefa8,
+ 0xefa9: 0xefa9,
+ 0xefaa: 0xefaa,
+ 0xefab: 0xefab,
+ 0xefac: 0xefac,
+ 0xefad: 0xefad,
+ 0xefae: 0xefae,
+ 0xefaf: 0xefaf,
+ 0xefb0: 0xefb0,
+ 0xefb1: 0xefb1,
+ 0xefb2: 0xefb2,
+ 0xefb3: 0xefb3,
+ 0xefb4: 0xefb4,
+ 0xefb5: 0xefb5,
+ 0xefb6: 0xefb6,
+ 0xefb7: 0xefb7,
+ 0xefb8: 0xefb8,
+ 0xefb9: 0xefb9,
+ 0xefba: 0xefba,
+ 0xefbb: 0xefbb,
+ 0xefbc: 0xefbc,
+ 0xefbd: 0xefbd,
+ 0xefbe: 0xefbe,
+ 0xefbf: 0xefbf,
+ 0xefc0: 0xefc0,
+ 0xefc1: 0xefc1,
+ 0xefc2: 0xefc2,
+ 0xefc3: 0xefc3,
+ 0xefc4: 0xefc4,
+ 0xefc5: 0xefc5,
+ 0xefc6: 0xefc6,
+ 0xefc7: 0xefc7,
+ 0xefc8: 0xefc8,
+ 0xefc9: 0xefc9,
+ 0xefca: 0xefca,
+ 0xefcb: 0xefcb,
+ 0xefcc: 0xefcc,
+ 0xefcd: 0xefcd,
+ 0xefce: 0xefce,
+ 0xf000: 0xf000,
+ 0xf001: 0xf001,
+ 0xf002: 0xf002,
+ 0xf003: 0xf003,
+ 0xf004: 0xf004,
+ 0xf005: 0xf005,
+ 0xf006: 0xf006,
+ 0xf007: 0xf007,
+ 0xf008: 0xf008,
+ 0xf009: 0xf009,
+ 0xf00a: 0xf00a,
+ 0xf00b: 0xf00b,
+ 0xf00c: 0xf00c,
+ 0xf00d: 0xf00d,
+ 0xf00e: 0xf00e,
+ 0xf00f: 0xf00f,
+ 0xf010: 0xf010,
+ 0xf011: 0xf011,
+ 0xf012: 0xf012,
+ 0xf013: 0xf013,
+ 0xf014: 0xf014,
+ 0xf015: 0xf015,
+ 0xf016: 0xf016,
+ 0xf017: 0xf017,
+ 0xf018: 0xf018,
+ 0xf019: 0xf019,
+ 0xf01a: 0xf01a,
+ 0xf01b: 0xf01b,
+ 0xf01c: 0xf01c,
+ 0xf01d: 0xf01d,
+ 0xf01e: 0xf01e,
+ 0xf01f: 0xf01f,
+ 0xf020: 0xf020,
+ 0xf021: 0xf021,
+ 0xf022: 0xf022,
+ 0xf023: 0xf023,
+ 0xf024: 0xf024,
+ 0xf025: 0xf025,
+ 0xf026: 0xf026,
+ 0xf027: 0xf027,
+ 0xf028: 0xf028,
+ 0xf029: 0xf029,
+ 0xf02a: 0xf02a,
+ 0xf02b: 0xf02b,
+ 0xf02c: 0xf02c,
+ 0xf02d: 0xf02d,
+ 0xf02e: 0xf02e,
+ 0xf02f: 0xf02f,
+ 0xf030: 0xf030,
+ 0xf031: 0xf031,
+ 0xf032: 0xf032,
+ 0xf033: 0xf033,
+ 0xf034: 0xf034,
+ 0xf035: 0xf035,
+ 0xf036: 0xf036,
+ 0xf037: 0xf037,
+ 0xf038: 0xf038,
+ 0xf039: 0xf039,
+ 0xf03a: 0xf03a,
+ 0xf03b: 0xf03b,
+ 0xf03c: 0xf03c,
+ 0xf03d: 0xf03d,
+ 0xf03e: 0xf03e,
+ 0xf03f: 0xf03f,
+ 0xf040: 0xf040,
+ 0xf041: 0xf041,
+ 0xf042: 0xf042,
+ 0xf043: 0xf043,
+ 0xf044: 0xf044,
+ 0xf045: 0xf045,
+ 0xf046: 0xf046,
+ 0xf047: 0xf047,
+ 0xf048: 0xf048,
+ 0xf049: 0xf049,
+ 0xf04a: 0xf04a,
+ 0xf04b: 0xf04b,
+ 0xf04c: 0xf04c,
+ 0xf04d: 0xf04d,
+ 0xf04e: 0xf04e,
+ 0xf04f: 0xf04f,
+ 0xf050: 0xf050,
+ 0xf051: 0xf051,
+ 0xf052: 0xf052,
+ 0xf053: 0xf053,
+ 0xf054: 0xf054,
+ 0xf055: 0xf055,
+ 0xf056: 0xf056,
+ 0xf057: 0xf057,
+ 0xf058: 0xf058,
+ 0xf059: 0xf059,
+ 0xf05a: 0xf05a,
+ 0xf05b: 0xf05b,
+ 0xf05c: 0xf05c,
+ 0xf05d: 0xf05d,
+ 0xf05e: 0xf05e,
+ 0xf05f: 0xf05f,
+ 0xf060: 0xf060,
+ 0xf061: 0xf061,
+ 0xf062: 0xf062,
+ 0xf063: 0xf063,
+ 0xf064: 0xf064,
+ 0xf065: 0xf065,
+ 0xf066: 0xf066,
+ 0xf067: 0xf067,
+ 0xf068: 0xf068,
+ 0xf069: 0xf069,
+ 0xf06a: 0xf06a,
+ 0xf06b: 0xf06b,
+ 0xf06c: 0xf06c,
+ 0xf06d: 0xf06d,
+ 0xf06e: 0xf06e,
+ 0xf06f: 0xf06f,
+ 0xf070: 0xf070,
+ 0xf071: 0xf071,
+ 0xf072: 0xf072,
+ 0xf073: 0xf073,
+ 0xf074: 0xf074,
+ 0xf075: 0xf075,
+ 0xf076: 0xf076,
+ 0xf077: 0xf077,
+ 0xf078: 0xf078,
+ 0xf079: 0xf079,
+ 0xf07a: 0xf07a,
+ 0xf07b: 0xf07b,
+ 0xf07c: 0xf07c,
+ 0xf07d: 0xf07d,
+ 0xf07e: 0xf07e,
+ 0xf07f: 0xf07f,
+ 0xf080: 0xf080,
+ 0xf081: 0xf081,
+ 0xf082: 0xf082,
+ 0xf083: 0xf083,
+ 0xf084: 0xf084,
+ 0xf085: 0xf085,
+ 0xf086: 0xf086,
+ 0xf087: 0xf087,
+ 0xf088: 0xf088,
+ 0xf089: 0xf089,
+ 0xf08a: 0xf08a,
+ 0xf08b: 0xf08b,
+ 0xf08c: 0xf08c,
+ 0xf08d: 0xf08d,
+ 0xf08e: 0xf08e,
+ 0xf08f: 0xf08f,
+ 0xf090: 0xf090,
+ 0xf091: 0xf091,
+ 0xf092: 0xf092,
+ 0xf093: 0xf093,
+ 0xf094: 0xf094,
+ 0xf095: 0xf095,
+ 0xf096: 0xf096,
+ 0xf097: 0xf097,
+ 0xf098: 0xf098,
+ 0xf099: 0xf099,
+ 0xf09a: 0xf09a,
+ 0xf09b: 0xf09b,
+ 0xf09c: 0xf09c,
+ 0xf09d: 0xf09d,
+ 0xf09e: 0xf09e,
+ 0xf09f: 0xf09f,
+ 0xf0a0: 0xf0a0,
+ 0xf0a1: 0xf0a1,
+ 0xf0a2: 0xf0a2,
+ 0xf0a3: 0xf0a3,
+ 0xf0a4: 0xf0a4,
+ 0xf0a5: 0xf0a5,
+ 0xf0a6: 0xf0a6,
+ 0xf0a7: 0xf0a7,
+ 0xf0a8: 0xf0a8,
+ 0xf0a9: 0xf0a9,
+ 0xf0aa: 0xf0aa,
+ 0xf0ab: 0xf0ab,
+ 0xf0ac: 0xf0ac,
+ 0xf0ad: 0xf0ad,
+ 0xf0ae: 0xf0ae,
+ 0xf0af: 0xf0af,
+ 0xf0b0: 0xf0b0,
+ 0xf0b1: 0xf0b1,
+ 0xf0b2: 0xf0b2,
+ 0xf0b3: 0xf0b3,
+ 0xf0b4: 0xf0b4,
+ 0xf0b5: 0xf0b5,
+ 0xf0b6: 0xf0b6,
+ 0xf0b7: 0xf0b7,
+ 0xf0b8: 0xf0b8,
+ 0xf0b9: 0xf0b9,
+ 0xf0ba: 0xf0ba,
+ 0xf0bb: 0xf0bb,
+ 0xf0bc: 0xf0bc,
+ 0xf0bd: 0xf0bd,
+ 0xf0be: 0xf0be,
+ 0xf0bf: 0xf0bf,
+ 0xf0c0: 0xf0c0,
+ 0xf0c1: 0xf0c1,
+ 0xf0c2: 0xf0c2,
+ 0xf0c3: 0xf0c3,
+ 0xf0c4: 0xf0c4,
+ 0xf0c5: 0xf0c5,
+ 0xf0c6: 0xf0c6,
+ 0xf0c7: 0xf0c7,
+ 0xf0c8: 0xf0c8,
+ 0xf0c9: 0xf0c9,
+ 0xf0ca: 0xf0ca,
+ 0xf0cb: 0xf0cb,
+ 0xf0cc: 0xf0cc,
+ 0xf0cd: 0xf0cd,
+ 0xf0ce: 0xf0ce,
+ 0xf0cf: 0xf0cf,
+ 0xf0d0: 0xf0d0,
+ 0xf0d1: 0xf0d1,
+ 0xf0d2: 0xf0d2,
+ 0xf0d3: 0xf0d3,
+ 0xf0d4: 0xf0d4,
+ 0xf0d5: 0xf0d5,
+ 0xf0d6: 0xf0d6,
+ 0xf0d7: 0xf0d7,
+ 0xf0d8: 0xf0d8,
+ 0xf0d9: 0xf0d9,
+ 0xf0da: 0xf0da,
+ 0xf0db: 0xf0db,
+ 0xf0dc: 0xf0dc,
+ 0xf0dd: 0xf0dd,
+ 0xf0de: 0xf0de,
+ 0xf0df: 0xf0df,
+ 0xf0e0: 0xf0e0,
+ 0xf0e1: 0xf0e1,
+ 0xf0e2: 0xf0e2,
+ 0xf0e3: 0xf0e3,
+ 0xf0e4: 0xf0e4,
+ 0xf0e5: 0xf0e5,
+ 0xf0e6: 0xf0e6,
+ 0xf0e7: 0xf0e7,
+ 0xf0e8: 0xf0e8,
+ 0xf0e9: 0xf0e9,
+ 0xf0ea: 0xf0ea,
+ 0xf0eb: 0xf0eb,
+ 0xf0ec: 0xf0ec,
+ 0xf0ed: 0xf0ed,
+ 0xf0ee: 0xf0ee,
+ 0xf0ef: 0xf0ef,
+ 0xf0f0: 0xf0f0,
+ 0xf0f1: 0xf0f1,
+ 0xf0f2: 0xf0f2,
+ 0xf0f3: 0xf0f3,
+ 0xf0f4: 0xf0f4,
+ 0xf0f5: 0xf0f5,
+ 0xf0f6: 0xf0f6,
+ 0xf0f7: 0xf0f7,
+ 0xf0f8: 0xf0f8,
+ 0xf0f9: 0xf0f9,
+ 0xf0fa: 0xf0fa,
+ 0xf0fb: 0xf0fb,
+ 0xf0fc: 0xf0fc,
+ 0xf0fd: 0xf0fd,
+ 0xf0fe: 0xf0fe,
+ 0xf0ff: 0xf0ff,
+ 0xf100: 0xf100,
+ 0xf101: 0xf101,
+ 0xf102: 0xf102,
+ 0xf103: 0xf103,
+ 0xf104: 0xf104,
+ 0xf105: 0xf105,
+ 0xf106: 0xf106,
+ 0xf107: 0xf107,
+ 0xf108: 0xf108,
+ 0xf109: 0xf109,
+ 0xf10a: 0xf10a,
+ 0xf10b: 0xf10b,
+ 0xf10c: 0xf10c,
+ 0xf10d: 0xf10d,
+ 0xf10e: 0xf10e,
+ 0xf10f: 0xf10f,
+ 0xf110: 0xf110,
+ 0xf111: 0xf111,
+ 0xf112: 0xf112,
+ 0xf113: 0xf113,
+ 0xf114: 0xf114,
+ 0xf115: 0xf115,
+ 0xf116: 0xf116,
+ 0xf117: 0xf117,
+ 0xf118: 0xf118,
+ 0xf119: 0xf119,
+ 0xf11a: 0xf11a,
+ 0xf11b: 0xf11b,
+ 0xf11c: 0xf11c,
+ 0xf11d: 0xf11d,
+ 0xf11e: 0xf11e,
+ 0xf11f: 0xf11f,
+ 0xf120: 0xf120,
+ 0xf121: 0xf121,
+ 0xf122: 0xf122,
+ 0xf123: 0xf123,
+ 0xf124: 0xf124,
+ 0xf125: 0xf125,
+ 0xf126: 0xf126,
+ 0xf127: 0xf127,
+ 0xf128: 0xf128,
+ 0xf129: 0xf129,
+ 0xf12a: 0xf12a,
+ 0xf12b: 0xf12b,
+ 0xf12c: 0xf12c,
+ 0xf12d: 0xf12d,
+ 0xf12e: 0xf12e,
+ 0xf12f: 0xf12f,
+ 0xf130: 0xf130,
+ 0xf131: 0xf131,
+ 0xf132: 0xf132,
+ 0xf133: 0xf133,
+ 0xf134: 0xf134,
+ 0xf135: 0xf135,
+ 0xf136: 0xf136,
+ 0xf137: 0xf137,
+ 0xf138: 0xf138,
+ 0xf139: 0xf139,
+ 0xf13a: 0xf13a,
+ 0xf13b: 0xf13b,
+ 0xf13c: 0xf13c,
+ 0xf13d: 0xf13d,
+ 0xf13e: 0xf13e,
+ 0xf13f: 0xf13f,
+ 0xf140: 0xf140,
+ 0xf141: 0xf141,
+ 0xf142: 0xf142,
+ 0xf143: 0xf143,
+ 0xf144: 0xf144,
+ 0xf145: 0xf145,
+ 0xf146: 0xf146,
+ 0xf147: 0xf147,
+ 0xf148: 0xf148,
+ 0xf149: 0xf149,
+ 0xf14a: 0xf14a,
+ 0xf14b: 0xf14b,
+ 0xf14c: 0xf14c,
+ 0xf14d: 0xf14d,
+ 0xf14e: 0xf14e,
+ 0xf14f: 0xf14f,
+ 0xf150: 0xf150,
+ 0xf151: 0xf151,
+ 0xf152: 0xf152,
+ 0xf153: 0xf153,
+ 0xf154: 0xf154,
+ 0xf155: 0xf155,
+ 0xf156: 0xf156,
+ 0xf157: 0xf157,
+ 0xf158: 0xf158,
+ 0xf159: 0xf159,
+ 0xf15a: 0xf15a,
+ 0xf15b: 0xf15b,
+ 0xf15c: 0xf15c,
+ 0xf15d: 0xf15d,
+ 0xf15e: 0xf15e,
+ 0xf15f: 0xf15f,
+ 0xf160: 0xf160,
+ 0xf161: 0xf161,
+ 0xf162: 0xf162,
+ 0xf163: 0xf163,
+ 0xf164: 0xf164,
+ 0xf165: 0xf165,
+ 0xf166: 0xf166,
+ 0xf167: 0xf167,
+ 0xf168: 0xf168,
+ 0xf169: 0xf169,
+ 0xf16a: 0xf16a,
+ 0xf16b: 0xf16b,
+ 0xf16c: 0xf16c,
+ 0xf16d: 0xf16d,
+ 0xf16e: 0xf16e,
+ 0xf16f: 0xf16f,
+ 0xf170: 0xf170,
+ 0xf171: 0xf171,
+ 0xf172: 0xf172,
+ 0xf173: 0xf173,
+ 0xf174: 0xf174,
+ 0xf175: 0xf175,
+ 0xf176: 0xf176,
+ 0xf177: 0xf177,
+ 0xf178: 0xf178,
+ 0xf179: 0xf179,
+ 0xf17a: 0xf17a,
+ 0xf17b: 0xf17b,
+ 0xf17c: 0xf17c,
+ 0xf17d: 0xf17d,
+ 0xf17e: 0xf17e,
+ 0xf17f: 0xf17f,
+ 0xf180: 0xf180,
+ 0xf181: 0xf181,
+ 0xf182: 0xf182,
+ 0xf183: 0xf183,
+ 0xf184: 0xf184,
+ 0xf185: 0xf185,
+ 0xf186: 0xf186,
+ 0xf187: 0xf187,
+ 0xf188: 0xf188,
+ 0xf189: 0xf189,
+ 0xf18a: 0xf18a,
+ 0xf18b: 0xf18b,
+ 0xf18c: 0xf18c,
+ 0xf18d: 0xf18d,
+ 0xf18e: 0xf18e,
+ 0xf18f: 0xf18f,
+ 0xf190: 0xf190,
+ 0xf191: 0xf191,
+ 0xf192: 0xf192,
+ 0xf193: 0xf193,
+ 0xf194: 0xf194,
+ 0xf195: 0xf195,
+ 0xf196: 0xf196,
+ 0xf197: 0xf197,
+ 0xf198: 0xf198,
+ 0xf199: 0xf199,
+ 0xf19a: 0xf19a,
+ 0xf19b: 0xf19b,
+ 0xf19c: 0xf19c,
+ 0xf19d: 0xf19d,
+ 0xf19e: 0xf19e,
+ 0xf19f: 0xf19f,
+ 0xf1a0: 0xf1a0,
+ 0xf1a1: 0xf1a1,
+ 0xf1a2: 0xf1a2,
+ 0xf1a3: 0xf1a3,
+ 0xf1a4: 0xf1a4,
+ 0xf1a5: 0xf1a5,
+ 0xf1a6: 0xf1a6,
+ 0xf1a7: 0xf1a7,
+ 0xf1a8: 0xf1a8,
+ 0xf1a9: 0xf1a9,
+ 0xf1aa: 0xf1aa,
+ 0xf1ab: 0xf1ab,
+ 0xf1ac: 0xf1ac,
+ 0xf1ad: 0xf1ad,
+ 0xf1ae: 0xf1ae,
+ 0xf1af: 0xf1af,
+ 0xf1b0: 0xf1b0,
+ 0xf1b1: 0xf1b1,
+ 0xf1b2: 0xf1b2,
+ 0xf1b3: 0xf1b3,
+ 0xf1b4: 0xf1b4,
+ 0xf1b5: 0xf1b5,
+ 0xf1b6: 0xf1b6,
+ 0xf1b7: 0xf1b7,
+ 0xf1b8: 0xf1b8,
+ 0xf1b9: 0xf1b9,
+ 0xf1ba: 0xf1ba,
+ 0xf1bb: 0xf1bb,
+ 0xf1bc: 0xf1bc,
+ 0xf1bd: 0xf1bd,
+ 0xf1be: 0xf1be,
+ 0xf1bf: 0xf1bf,
+ 0xf1c0: 0xf1c0,
+ 0xf1c1: 0xf1c1,
+ 0xf1c2: 0xf1c2,
+ 0xf1c3: 0xf1c3,
+ 0xf1c4: 0xf1c4,
+ 0xf1c5: 0xf1c5,
+ 0xf1c6: 0xf1c6,
+ 0xf1c7: 0xf1c7,
+ 0xf1c8: 0xf1c8,
+ 0xf1c9: 0xf1c9,
+ 0xf1ca: 0xf1ca,
+ 0xf1cb: 0xf1cb,
+ 0xf1cc: 0xf1cc,
+ 0xf1cd: 0xf1cd,
+ 0xf1ce: 0xf1ce,
+ 0xf1cf: 0xf1cf,
+ 0xf1d0: 0xf1d0,
+ 0xf1d1: 0xf1d1,
+ 0xf1d2: 0xf1d2,
+ 0xf1d3: 0xf1d3,
+ 0xf1d4: 0xf1d4,
+ 0xf1d5: 0xf1d5,
+ 0xf1d6: 0xf1d6,
+ 0xf1d7: 0xf1d7,
+ 0xf1d8: 0xf1d8,
+ 0xf1d9: 0xf1d9,
+ 0xf1da: 0xf1da,
+ 0xf1db: 0xf1db,
+ 0xf1dc: 0xf1dc,
+ 0xf1dd: 0xf1dd,
+ 0xf1de: 0xf1de,
+ 0xf1df: 0xf1df,
+ 0xf1e0: 0xf1e0,
+ 0xf1e1: 0xf1e1,
+ 0xf1e2: 0xf1e2,
+ 0xf1e3: 0xf1e3,
+ 0xf1e4: 0xf1e4,
+ 0xf1e5: 0xf1e5,
+ 0xf1e6: 0xf1e6,
+ 0xf1e7: 0xf1e7,
+ 0xf1e8: 0xf1e8,
+ 0xf1e9: 0xf1e9,
+ 0xf1ea: 0xf1ea,
+ 0xf1eb: 0xf1eb,
+ 0xf1ec: 0xf1ec,
+ 0xf1ed: 0xf1ed,
+ 0xf1ee: 0xf1ee,
+ 0xf1ef: 0xf1ef,
+ 0xf1f0: 0xf1f0,
+ 0xf1f1: 0xf1f1,
+ 0xf1f2: 0xf1f2,
+ 0xf1f3: 0xf1f3,
+ 0xf1f4: 0xf1f4,
+ 0xf1f5: 0xf1f5,
+ 0xf1f6: 0xf1f6,
+ 0xf1f7: 0xf1f7,
+ 0xf1f8: 0xf1f8,
+ 0xf1f9: 0xf1f9,
+ 0xf1fa: 0xf1fa,
+ 0xf1fb: 0xf1fb,
+ 0xf1fc: 0xf1fc,
+ 0xf1fd: 0xf1fd,
+ 0xf1fe: 0xf1fe,
+ 0xf1ff: 0xf1ff,
+ 0xf200: 0xf200,
+ 0xf201: 0xf201,
+ 0xf202: 0xf202,
+ 0xf203: 0xf203,
+ 0xf204: 0xf204,
+ 0xf205: 0xf205,
+ 0xf206: 0xf206,
+ 0xf207: 0xf207,
+ 0xf208: 0xf208,
+ 0xf209: 0xf209,
+ 0xf20a: 0xf20a,
+ 0xf20b: 0xf20b,
+ 0xf20c: 0xf20c,
+ 0xf20d: 0xf20d,
+ 0xf20e: 0xf20e,
+ 0xf20f: 0xf20f,
+ 0xf210: 0xf210,
+ 0xf211: 0xf211,
+ 0xf212: 0xf212,
+ 0xf213: 0xf213,
+ 0xf214: 0xf214,
+ 0xf215: 0xf215,
+ 0xf216: 0xf216,
+ 0xf217: 0xf217,
+ 0xf218: 0xf218,
+ 0xf219: 0xf219,
+ 0xf21a: 0xf21a,
+ 0xf21b: 0xf21b,
+ 0xf21c: 0xf21c,
+ 0xf21d: 0xf21d,
+ 0xf21e: 0xf21e,
+ 0xf21f: 0xf21f,
+ 0xf220: 0xf220,
+ 0xf221: 0xf221,
+ 0xf222: 0xf222,
+ 0xf223: 0xf223,
+ 0xf224: 0xf224,
+ 0xf225: 0xf225,
+ 0xf226: 0xf226,
+ 0xf227: 0xf227,
+ 0xf228: 0xf228,
+ 0xf229: 0xf229,
+ 0xf22a: 0xf22a,
+ 0xf22b: 0xf22b,
+ 0xf22c: 0xf22c,
+ 0xf22d: 0xf22d,
+ 0xf22e: 0xf22e,
+ 0xf22f: 0xf22f,
+ 0xf230: 0xf230,
+ 0xf231: 0xf231,
+ 0xf232: 0xf232,
+ 0xf233: 0xf233,
+ 0xf234: 0xf234,
+ 0xf235: 0xf235,
+ 0xf236: 0xf236,
+ 0xf237: 0xf237,
+ 0xf238: 0xf238,
+ 0xf239: 0xf239,
+ 0xf23a: 0xf23a,
+ 0xf23b: 0xf23b,
+ 0xf23c: 0xf23c,
+ 0xf23d: 0xf23d,
+ 0xf23e: 0xf23e,
+ 0xf23f: 0xf23f,
+ 0xf240: 0xf240,
+ 0xf241: 0xf241,
+ 0xf242: 0xf242,
+ 0xf243: 0xf243,
+ 0xf244: 0xf244,
+ 0xf245: 0xf245,
+ 0xf246: 0xf246,
+ 0xf247: 0xf247,
+ 0xf248: 0xf248,
+ 0xf249: 0xf249,
+ 0xf24a: 0xf24a,
+ 0xf24b: 0xf24b,
+ 0xf24c: 0xf24c,
+ 0xf24d: 0xf24d,
+ 0xf24e: 0xf24e,
+ 0xf24f: 0xf24f,
+ 0xf250: 0xf250,
+ 0xf251: 0xf251,
+ 0xf252: 0xf252,
+ 0xf253: 0xf253,
+ 0xf254: 0xf254,
+ 0xf255: 0xf255,
+ 0xf256: 0xf256,
+ 0xf257: 0xf257,
+ 0xf258: 0xf258,
+ 0xf259: 0xf259,
+ 0xf25a: 0xf25a,
+ 0xf25b: 0xf25b,
+ 0xf25c: 0xf25c,
+ 0xf25d: 0xf25d,
+ 0xf25e: 0xf25e,
+ 0xf25f: 0xf25f,
+ 0xf260: 0xf260,
+ 0xf261: 0xf261,
+ 0xf262: 0xf262,
+ 0xf263: 0xf263,
+ 0xf264: 0xf264,
+ 0xf265: 0xf265,
+ 0xf266: 0xf266,
+ 0xf267: 0xf267,
+ 0xf268: 0xf268,
+ 0xf269: 0xf269,
+ 0xf26a: 0xf26a,
+ 0xf26b: 0xf26b,
+ 0xf26c: 0xf26c,
+ 0xf26d: 0xf26d,
+ 0xf26e: 0xf26e,
+ 0xf26f: 0xf26f,
+ 0xf270: 0xf270,
+ 0xf271: 0xf271,
+ 0xf272: 0xf272,
+ 0xf273: 0xf273,
+ 0xf274: 0xf274,
+ 0xf275: 0xf275,
+ 0xf276: 0xf276,
+ 0xf277: 0xf277,
+ 0xf278: 0xf278,
+ 0xf279: 0xf279,
+ 0xf27a: 0xf27a,
+ 0xf27b: 0xf27b,
+ 0xf27c: 0xf27c,
+ 0xf27d: 0xf27d,
+ 0xf27e: 0xf27e,
+ 0xf27f: 0xf27f,
+ 0xf280: 0xf280,
+ 0xf281: 0xf281,
+ 0xf282: 0xf282,
+ 0xf283: 0xf283,
+ 0xf284: 0xf284,
+ 0xf285: 0xf285,
+ 0xf286: 0xf286,
+ 0xf287: 0xf287,
+ 0xf288: 0xf288,
+ 0xf289: 0xf289,
+ 0xf28a: 0xf28a,
+ 0xf28b: 0xf28b,
+ 0xf28c: 0xf28c,
+ 0xf28d: 0xf28d,
+ 0xf28e: 0xf28e,
+ 0xf28f: 0xf28f,
+ 0xf290: 0xf290,
+ 0xf291: 0xf291,
+ 0xf292: 0xf292,
+ 0xf293: 0xf293,
+ 0xf294: 0xf294,
+ 0xf295: 0xf295,
+ 0xf296: 0xf296,
+ 0xf297: 0xf297,
+ 0xf298: 0xf298,
+ 0xf299: 0xf299,
+ 0xf29a: 0xf29a,
+ 0xf29b: 0xf29b,
+ 0xf29c: 0xf29c,
+ 0xf29d: 0xf29d,
+ 0xf29e: 0xf29e,
+ 0xf29f: 0xf29f,
+ 0xf2a0: 0xf2a0,
+ 0xf2a1: 0xf2a1,
+ 0xf2a2: 0xf2a2,
+ 0xf2a3: 0xf2a3,
+ 0xf2a4: 0xf2a4,
+ 0xf2a5: 0xf2a5,
+ 0xf2a6: 0xf2a6,
+ 0xf2a7: 0xf2a7,
+ 0xf2a8: 0xf2a8,
+ 0xf2a9: 0xf2a9,
+ 0xf2aa: 0xf2aa,
+ 0xf2ab: 0xf2ab,
+ 0xf2ac: 0xf2ac,
+ 0xf2ad: 0xf2ad,
+ 0xf2ae: 0xf2ae,
+ 0xf2af: 0xf2af,
+ 0xf2b0: 0xf2b0,
+ 0xf2b1: 0xf2b1,
+ 0xf2b2: 0xf2b2,
+ 0xf2b3: 0xf2b3,
+ 0xf2b4: 0xf2b4,
+ 0xf2b5: 0xf2b5,
+ 0xf2b6: 0xf2b6,
+ 0xf2b7: 0xf2b7,
+ 0xf2b8: 0xf2b8,
+ 0xf2b9: 0xf2b9,
+ 0xf2ba: 0xf2ba,
+ 0xf2bb: 0xf2bb,
+ 0xf2bc: 0xf2bc,
+ 0xf2bd: 0xf2bd,
+ 0xf2be: 0xf2be,
+ 0xf2bf: 0xf2bf,
+ 0xf2c0: 0xf2c0,
+ 0xf2c1: 0xf2c1,
+ 0xf2c2: 0xf2c2,
+ 0xf2c3: 0xf2c3,
+ 0xf2c4: 0xf2c4,
+ 0xf2c5: 0xf2c5,
+ 0xf2c6: 0xf2c6,
+ 0xf2c7: 0xf2c7,
+ 0xf2c8: 0xf2c8,
+ 0xf2c9: 0xf2c9,
+ 0xf2ca: 0xf2ca,
+ 0xf2cb: 0xf2cb,
+ 0xf2cc: 0xf2cc,
+ 0xf2cd: 0xf2cd,
+ 0xf2ce: 0xf2ce,
+ 0xf2cf: 0xf2cf,
+ 0xf2d0: 0xf2d0,
+ 0xf2d1: 0xf2d1,
+ 0xf2d2: 0xf2d2,
+ 0xf2d3: 0xf2d3,
+ 0xf2d4: 0xf2d4,
+ 0xf2d5: 0xf2d5,
+ 0xf2d6: 0xf2d6,
+ 0xf2d7: 0xf2d7,
+ 0xf2d8: 0xf2d8,
+ 0xf2d9: 0xf2d9,
+ 0xf2da: 0xf2da,
+ 0xf2db: 0xf2db,
+ 0xf2dc: 0xf2dc,
+ 0xf2dd: 0xf2dd,
+ 0xf2de: 0xf2de,
+ 0xf2df: 0xf2df,
+ 0xf2e0: 0xf2e0,
+ 0xf2e1: 0xf2e1,
+ 0xf2e2: 0xf2e2,
+ 0xf2e3: 0xf2e3,
+ 0xf2e4: 0xf2e4,
+ 0xf2e5: 0xf2e5,
+ 0xf2e6: 0xf2e6,
+ 0xf2e7: 0xf2e7,
+ 0xf2e8: 0xf2e8,
+ 0xf2e9: 0xf2e9,
+ 0xf2ea: 0xf2ea,
+ 0xf2eb: 0xf2eb,
+ 0xf2ec: 0xf2ec,
+ 0xf2ed: 0xf2ed,
+ 0xf2ee: 0xf2ee,
+ 0xf2ef: 0xf2ef,
+ 0xf2f0: 0xf2f0,
+ 0xf2f1: 0xf2f1,
+ 0xf2f2: 0xf2f2,
+ 0xf2f3: 0xf2f3,
+ 0xf2f4: 0xf2f4,
+ 0xf2f5: 0xf2f5,
+ 0xf2f6: 0xf2f6,
+ 0xf2f7: 0xf2f7,
+ 0xf2f8: 0xf2f8,
+ 0xf2f9: 0xf2f9,
+ 0xf2fa: 0xf2fa,
+ 0xf2fb: 0xf2fb,
+ 0xf2fc: 0xf2fc,
+ 0xf2fd: 0xf2fd,
+ 0xf2fe: 0xf2fe,
+ 0xf2ff: 0xf2ff,
+ },
+ "Font Awesome Extension": {
+ 0xe000: 0xe200,
+ 0xe001: 0xe201,
+ 0xe002: 0xe202,
+ 0xe003: 0xe203,
+ 0xe004: 0xe204,
+ 0xe005: 0xe205,
+ 0xe006: 0xe206,
+ 0xe007: 0xe207,
+ 0xe008: 0xe208,
+ 0xe009: 0xe209,
+ 0xe00a: 0xe20a,
+ 0xe00b: 0xe20b,
+ 0xe00c: 0xe20c,
+ 0xe00d: 0xe20d,
+ 0xe00e: 0xe20e,
+ 0xe00f: 0xe20f,
+ 0xe010: 0xe210,
+ 0xe011: 0xe211,
+ 0xe012: 0xe212,
+ 0xe013: 0xe213,
+ 0xe014: 0xe214,
+ 0xe015: 0xe215,
+ 0xe016: 0xe216,
+ 0xe017: 0xe217,
+ 0xe018: 0xe218,
+ 0xe019: 0xe219,
+ 0xe01a: 0xe21a,
+ 0xe01b: 0xe21b,
+ 0xe01c: 0xe21c,
+ 0xe01d: 0xe21d,
+ 0xe01e: 0xe21e,
+ 0xe01f: 0xe21f,
+ 0xe020: 0xe220,
+ 0xe021: 0xe221,
+ 0xe022: 0xe222,
+ 0xe023: 0xe223,
+ 0xe024: 0xe224,
+ 0xe025: 0xe225,
+ 0xe026: 0xe226,
+ 0xe027: 0xe227,
+ 0xe028: 0xe228,
+ 0xe029: 0xe229,
+ 0xe02a: 0xe22a,
+ 0xe02b: 0xe22b,
+ 0xe02c: 0xe22c,
+ 0xe02d: 0xe22d,
+ 0xe02e: 0xe22e,
+ 0xe02f: 0xe22f,
+ 0xe030: 0xe230,
+ 0xe031: 0xe231,
+ 0xe032: 0xe232,
+ 0xe033: 0xe233,
+ 0xe034: 0xe234,
+ 0xe035: 0xe235,
+ 0xe036: 0xe236,
+ 0xe037: 0xe237,
+ 0xe038: 0xe238,
+ 0xe039: 0xe239,
+ 0xe03a: 0xe23a,
+ 0xe03b: 0xe23b,
+ 0xe03c: 0xe23c,
+ 0xe03d: 0xe23d,
+ 0xe03e: 0xe23e,
+ 0xe03f: 0xe23f,
+ 0xe040: 0xe240,
+ 0xe041: 0xe241,
+ 0xe042: 0xe242,
+ 0xe043: 0xe243,
+ 0xe044: 0xe244,
+ 0xe045: 0xe245,
+ 0xe046: 0xe246,
+ 0xe047: 0xe247,
+ 0xe048: 0xe248,
+ 0xe049: 0xe249,
+ 0xe04a: 0xe24a,
+ 0xe04b: 0xe24b,
+ 0xe04c: 0xe24c,
+ 0xe04d: 0xe24d,
+ 0xe04e: 0xe24e,
+ 0xe04f: 0xe24f,
+ 0xe050: 0xe250,
+ 0xe051: 0xe251,
+ 0xe052: 0xe252,
+ 0xe053: 0xe253,
+ 0xe054: 0xe254,
+ 0xe055: 0xe255,
+ 0xe056: 0xe256,
+ 0xe057: 0xe257,
+ 0xe058: 0xe258,
+ 0xe059: 0xe259,
+ 0xe05a: 0xe25a,
+ 0xe05b: 0xe25b,
+ 0xe05c: 0xe25c,
+ 0xe05d: 0xe25d,
+ 0xe05e: 0xe25e,
+ 0xe05f: 0xe25f,
+ 0xe060: 0xe260,
+ 0xe061: 0xe261,
+ 0xe062: 0xe262,
+ 0xe063: 0xe263,
+ 0xe064: 0xe264,
+ 0xe065: 0xe265,
+ 0xe066: 0xe266,
+ 0xe067: 0xe267,
+ 0xe068: 0xe268,
+ 0xe069: 0xe269,
+ 0xe06a: 0xe26a,
+ 0xe06b: 0xe26b,
+ 0xe06c: 0xe26c,
+ 0xe06d: 0xe26d,
+ 0xe06e: 0xe26e,
+ 0xe06f: 0xe26f,
+ 0xe070: 0xe270,
+ 0xe071: 0xe271,
+ 0xe072: 0xe272,
+ 0xe073: 0xe273,
+ 0xe074: 0xe274,
+ 0xe075: 0xe275,
+ 0xe076: 0xe276,
+ 0xe077: 0xe277,
+ 0xe078: 0xe278,
+ 0xe079: 0xe279,
+ 0xe07a: 0xe27a,
+ 0xe07b: 0xe27b,
+ 0xe07c: 0xe27c,
+ 0xe07d: 0xe27d,
+ 0xe07e: 0xe27e,
+ 0xe07f: 0xe27f,
+ 0xe080: 0xe280,
+ 0xe081: 0xe281,
+ 0xe082: 0xe282,
+ 0xe083: 0xe283,
+ 0xe084: 0xe284,
+ 0xe085: 0xe285,
+ 0xe086: 0xe286,
+ 0xe087: 0xe287,
+ 0xe088: 0xe288,
+ 0xe089: 0xe289,
+ 0xe08a: 0xe28a,
+ 0xe08b: 0xe28b,
+ 0xe08c: 0xe28c,
+ 0xe08d: 0xe28d,
+ 0xe08e: 0xe28e,
+ 0xe08f: 0xe28f,
+ 0xe090: 0xe290,
+ 0xe091: 0xe291,
+ 0xe092: 0xe292,
+ 0xe093: 0xe293,
+ 0xe094: 0xe294,
+ 0xe095: 0xe295,
+ 0xe096: 0xe296,
+ 0xe097: 0xe297,
+ 0xe098: 0xe298,
+ 0xe099: 0xe299,
+ 0xe09a: 0xe29a,
+ 0xe09b: 0xe29b,
+ 0xe09c: 0xe29c,
+ 0xe09d: 0xe29d,
+ 0xe09e: 0xe29e,
+ 0xe09f: 0xe29f,
+ 0xe0a0: 0xe2a0,
+ 0xe0a1: 0xe2a1,
+ 0xe0a2: 0xe2a2,
+ 0xe0a3: 0xe2a3,
+ 0xe0a4: 0xe2a4,
+ 0xe0a5: 0xe2a5,
+ 0xe0a6: 0xe2a6,
+ 0xe0a7: 0xe2a7,
+ 0xe0a8: 0xe2a8,
+ 0xe0a9: 0xe2a9,
+ },
+ "Power Symbols": {
+ 0x23fb: 0x23fb,
+ 0x23fc: 0x23fc,
+ 0x23fd: 0x23fd,
+ 0x23fe: 0x23fe,
+ 0x2b58: 0x2b58,
+ },
+ "Material": {
+ 0xf0001: 0xf0001,
+ 0xf0002: 0xf0002,
+ 0xf0003: 0xf0003,
+ 0xf0004: 0xf0004,
+ 0xf0005: 0xf0005,
+ 0xf0006: 0xf0006,
+ 0xf0007: 0xf0007,
+ 0xf0008: 0xf0008,
+ 0xf0009: 0xf0009,
+ 0xf000a: 0xf000a,
+ 0xf000b: 0xf000b,
+ 0xf000c: 0xf000c,
+ 0xf000d: 0xf000d,
+ 0xf000e: 0xf000e,
+ 0xf000f: 0xf000f,
+ 0xf0010: 0xf0010,
+ 0xf0011: 0xf0011,
+ 0xf0012: 0xf0012,
+ 0xf0013: 0xf0013,
+ 0xf0014: 0xf0014,
+ 0xf0015: 0xf0015,
+ 0xf0016: 0xf0016,
+ 0xf0017: 0xf0017,
+ 0xf0018: 0xf0018,
+ 0xf0019: 0xf0019,
+ 0xf001a: 0xf001a,
+ 0xf001b: 0xf001b,
+ 0xf001c: 0xf001c,
+ 0xf001d: 0xf001d,
+ 0xf001e: 0xf001e,
+ 0xf001f: 0xf001f,
+ 0xf0020: 0xf0020,
+ 0xf0021: 0xf0021,
+ 0xf0022: 0xf0022,
+ 0xf0023: 0xf0023,
+ 0xf0024: 0xf0024,
+ 0xf0025: 0xf0025,
+ 0xf0026: 0xf0026,
+ 0xf0027: 0xf0027,
+ 0xf0028: 0xf0028,
+ 0xf0029: 0xf0029,
+ 0xf002a: 0xf002a,
+ 0xf002b: 0xf002b,
+ 0xf002c: 0xf002c,
+ 0xf002d: 0xf002d,
+ 0xf002e: 0xf002e,
+ 0xf002f: 0xf002f,
+ 0xf0030: 0xf0030,
+ 0xf0031: 0xf0031,
+ 0xf0032: 0xf0032,
+ 0xf0033: 0xf0033,
+ 0xf0034: 0xf0034,
+ 0xf0035: 0xf0035,
+ 0xf0036: 0xf0036,
+ 0xf0037: 0xf0037,
+ 0xf0038: 0xf0038,
+ 0xf0039: 0xf0039,
+ 0xf003a: 0xf003a,
+ 0xf003b: 0xf003b,
+ 0xf003c: 0xf003c,
+ 0xf003d: 0xf003d,
+ 0xf003e: 0xf003e,
+ 0xf003f: 0xf003f,
+ 0xf0040: 0xf0040,
+ 0xf0041: 0xf0041,
+ 0xf0042: 0xf0042,
+ 0xf0043: 0xf0043,
+ 0xf0044: 0xf0044,
+ 0xf0045: 0xf0045,
+ 0xf0046: 0xf0046,
+ 0xf0047: 0xf0047,
+ 0xf0048: 0xf0048,
+ 0xf0049: 0xf0049,
+ 0xf004a: 0xf004a,
+ 0xf004b: 0xf004b,
+ 0xf004c: 0xf004c,
+ 0xf004d: 0xf004d,
+ 0xf004e: 0xf004e,
+ 0xf004f: 0xf004f,
+ 0xf0050: 0xf0050,
+ 0xf0051: 0xf0051,
+ 0xf0052: 0xf0052,
+ 0xf0053: 0xf0053,
+ 0xf0054: 0xf0054,
+ 0xf0055: 0xf0055,
+ 0xf0056: 0xf0056,
+ 0xf0057: 0xf0057,
+ 0xf0058: 0xf0058,
+ 0xf0059: 0xf0059,
+ 0xf005a: 0xf005a,
+ 0xf005b: 0xf005b,
+ 0xf005c: 0xf005c,
+ 0xf005d: 0xf005d,
+ 0xf005e: 0xf005e,
+ 0xf005f: 0xf005f,
+ 0xf0060: 0xf0060,
+ 0xf0061: 0xf0061,
+ 0xf0062: 0xf0062,
+ 0xf0063: 0xf0063,
+ 0xf0064: 0xf0064,
+ 0xf0065: 0xf0065,
+ 0xf0066: 0xf0066,
+ 0xf0067: 0xf0067,
+ 0xf0068: 0xf0068,
+ 0xf0069: 0xf0069,
+ 0xf006a: 0xf006a,
+ 0xf006b: 0xf006b,
+ 0xf006c: 0xf006c,
+ 0xf006d: 0xf006d,
+ 0xf006e: 0xf006e,
+ 0xf006f: 0xf006f,
+ 0xf0070: 0xf0070,
+ 0xf0071: 0xf0071,
+ 0xf0072: 0xf0072,
+ 0xf0073: 0xf0073,
+ 0xf0074: 0xf0074,
+ 0xf0075: 0xf0075,
+ 0xf0076: 0xf0076,
+ 0xf0077: 0xf0077,
+ 0xf0078: 0xf0078,
+ 0xf0079: 0xf0079,
+ 0xf007a: 0xf007a,
+ 0xf007b: 0xf007b,
+ 0xf007c: 0xf007c,
+ 0xf007d: 0xf007d,
+ 0xf007e: 0xf007e,
+ 0xf007f: 0xf007f,
+ 0xf0080: 0xf0080,
+ 0xf0081: 0xf0081,
+ 0xf0082: 0xf0082,
+ 0xf0083: 0xf0083,
+ 0xf0084: 0xf0084,
+ 0xf0085: 0xf0085,
+ 0xf0086: 0xf0086,
+ 0xf0087: 0xf0087,
+ 0xf0088: 0xf0088,
+ 0xf0089: 0xf0089,
+ 0xf008a: 0xf008a,
+ 0xf008b: 0xf008b,
+ 0xf008c: 0xf008c,
+ 0xf008d: 0xf008d,
+ 0xf008e: 0xf008e,
+ 0xf008f: 0xf008f,
+ 0xf0090: 0xf0090,
+ 0xf0091: 0xf0091,
+ 0xf0092: 0xf0092,
+ 0xf0093: 0xf0093,
+ 0xf0094: 0xf0094,
+ 0xf0095: 0xf0095,
+ 0xf0096: 0xf0096,
+ 0xf0097: 0xf0097,
+ 0xf0098: 0xf0098,
+ 0xf0099: 0xf0099,
+ 0xf009a: 0xf009a,
+ 0xf009b: 0xf009b,
+ 0xf009c: 0xf009c,
+ 0xf009d: 0xf009d,
+ 0xf009e: 0xf009e,
+ 0xf009f: 0xf009f,
+ 0xf00a0: 0xf00a0,
+ 0xf00a1: 0xf00a1,
+ 0xf00a2: 0xf00a2,
+ 0xf00a3: 0xf00a3,
+ 0xf00a4: 0xf00a4,
+ 0xf00a5: 0xf00a5,
+ 0xf00a6: 0xf00a6,
+ 0xf00a7: 0xf00a7,
+ 0xf00a8: 0xf00a8,
+ 0xf00a9: 0xf00a9,
+ 0xf00aa: 0xf00aa,
+ 0xf00ab: 0xf00ab,
+ 0xf00ac: 0xf00ac,
+ 0xf00ad: 0xf00ad,
+ 0xf00ae: 0xf00ae,
+ 0xf00af: 0xf00af,
+ 0xf00b0: 0xf00b0,
+ 0xf00b1: 0xf00b1,
+ 0xf00b2: 0xf00b2,
+ 0xf00b3: 0xf00b3,
+ 0xf00b4: 0xf00b4,
+ 0xf00b5: 0xf00b5,
+ 0xf00b6: 0xf00b6,
+ 0xf00b7: 0xf00b7,
+ 0xf00b8: 0xf00b8,
+ 0xf00b9: 0xf00b9,
+ 0xf00ba: 0xf00ba,
+ 0xf00bb: 0xf00bb,
+ 0xf00bc: 0xf00bc,
+ 0xf00bd: 0xf00bd,
+ 0xf00be: 0xf00be,
+ 0xf00bf: 0xf00bf,
+ 0xf00c0: 0xf00c0,
+ 0xf00c1: 0xf00c1,
+ 0xf00c2: 0xf00c2,
+ 0xf00c3: 0xf00c3,
+ 0xf00c4: 0xf00c4,
+ 0xf00c5: 0xf00c5,
+ 0xf00c6: 0xf00c6,
+ 0xf00c7: 0xf00c7,
+ 0xf00c8: 0xf00c8,
+ 0xf00c9: 0xf00c9,
+ 0xf00ca: 0xf00ca,
+ 0xf00cb: 0xf00cb,
+ 0xf00cc: 0xf00cc,
+ 0xf00cd: 0xf00cd,
+ 0xf00ce: 0xf00ce,
+ 0xf00cf: 0xf00cf,
+ 0xf00d0: 0xf00d0,
+ 0xf00d1: 0xf00d1,
+ 0xf00d2: 0xf00d2,
+ 0xf00d3: 0xf00d3,
+ 0xf00d4: 0xf00d4,
+ 0xf00d5: 0xf00d5,
+ 0xf00d6: 0xf00d6,
+ 0xf00d7: 0xf00d7,
+ 0xf00d8: 0xf00d8,
+ 0xf00d9: 0xf00d9,
+ 0xf00da: 0xf00da,
+ 0xf00db: 0xf00db,
+ 0xf00dc: 0xf00dc,
+ 0xf00dd: 0xf00dd,
+ 0xf00de: 0xf00de,
+ 0xf00df: 0xf00df,
+ 0xf00e0: 0xf00e0,
+ 0xf00e1: 0xf00e1,
+ 0xf00e2: 0xf00e2,
+ 0xf00e3: 0xf00e3,
+ 0xf00e4: 0xf00e4,
+ 0xf00e5: 0xf00e5,
+ 0xf00e6: 0xf00e6,
+ 0xf00e7: 0xf00e7,
+ 0xf00e8: 0xf00e8,
+ 0xf00e9: 0xf00e9,
+ 0xf00ea: 0xf00ea,
+ 0xf00eb: 0xf00eb,
+ 0xf00ec: 0xf00ec,
+ 0xf00ed: 0xf00ed,
+ 0xf00ee: 0xf00ee,
+ 0xf00ef: 0xf00ef,
+ 0xf00f0: 0xf00f0,
+ 0xf00f1: 0xf00f1,
+ 0xf00f2: 0xf00f2,
+ 0xf00f3: 0xf00f3,
+ 0xf00f4: 0xf00f4,
+ 0xf00f5: 0xf00f5,
+ 0xf00f6: 0xf00f6,
+ 0xf00f7: 0xf00f7,
+ 0xf00f8: 0xf00f8,
+ 0xf00f9: 0xf00f9,
+ 0xf00fa: 0xf00fa,
+ 0xf00fb: 0xf00fb,
+ 0xf00fc: 0xf00fc,
+ 0xf00fd: 0xf00fd,
+ 0xf00fe: 0xf00fe,
+ 0xf00ff: 0xf00ff,
+ 0xf0100: 0xf0100,
+ 0xf0101: 0xf0101,
+ 0xf0102: 0xf0102,
+ 0xf0103: 0xf0103,
+ 0xf0104: 0xf0104,
+ 0xf0105: 0xf0105,
+ 0xf0106: 0xf0106,
+ 0xf0107: 0xf0107,
+ 0xf0108: 0xf0108,
+ 0xf0109: 0xf0109,
+ 0xf010a: 0xf010a,
+ 0xf010b: 0xf010b,
+ 0xf010c: 0xf010c,
+ 0xf010d: 0xf010d,
+ 0xf010e: 0xf010e,
+ 0xf010f: 0xf010f,
+ 0xf0110: 0xf0110,
+ 0xf0111: 0xf0111,
+ 0xf0112: 0xf0112,
+ 0xf0113: 0xf0113,
+ 0xf0114: 0xf0114,
+ 0xf0115: 0xf0115,
+ 0xf0116: 0xf0116,
+ 0xf0117: 0xf0117,
+ 0xf0118: 0xf0118,
+ 0xf0119: 0xf0119,
+ 0xf011a: 0xf011a,
+ 0xf011b: 0xf011b,
+ 0xf011c: 0xf011c,
+ 0xf011d: 0xf011d,
+ 0xf011e: 0xf011e,
+ 0xf011f: 0xf011f,
+ 0xf0120: 0xf0120,
+ 0xf0121: 0xf0121,
+ 0xf0122: 0xf0122,
+ 0xf0123: 0xf0123,
+ 0xf0124: 0xf0124,
+ 0xf0125: 0xf0125,
+ 0xf0126: 0xf0126,
+ 0xf0127: 0xf0127,
+ 0xf0128: 0xf0128,
+ 0xf0129: 0xf0129,
+ 0xf012a: 0xf012a,
+ 0xf012b: 0xf012b,
+ 0xf012c: 0xf012c,
+ 0xf012d: 0xf012d,
+ 0xf012e: 0xf012e,
+ 0xf012f: 0xf012f,
+ 0xf0130: 0xf0130,
+ 0xf0131: 0xf0131,
+ 0xf0132: 0xf0132,
+ 0xf0133: 0xf0133,
+ 0xf0134: 0xf0134,
+ 0xf0135: 0xf0135,
+ 0xf0136: 0xf0136,
+ 0xf0137: 0xf0137,
+ 0xf0138: 0xf0138,
+ 0xf0139: 0xf0139,
+ 0xf013a: 0xf013a,
+ 0xf013b: 0xf013b,
+ 0xf013c: 0xf013c,
+ 0xf013d: 0xf013d,
+ 0xf013e: 0xf013e,
+ 0xf013f: 0xf013f,
+ 0xf0140: 0xf0140,
+ 0xf0141: 0xf0141,
+ 0xf0142: 0xf0142,
+ 0xf0143: 0xf0143,
+ 0xf0144: 0xf0144,
+ 0xf0145: 0xf0145,
+ 0xf0146: 0xf0146,
+ 0xf0147: 0xf0147,
+ 0xf0148: 0xf0148,
+ 0xf0149: 0xf0149,
+ 0xf014a: 0xf014a,
+ 0xf014b: 0xf014b,
+ 0xf014c: 0xf014c,
+ 0xf014d: 0xf014d,
+ 0xf014e: 0xf014e,
+ 0xf014f: 0xf014f,
+ 0xf0150: 0xf0150,
+ 0xf0151: 0xf0151,
+ 0xf0152: 0xf0152,
+ 0xf0153: 0xf0153,
+ 0xf0154: 0xf0154,
+ 0xf0155: 0xf0155,
+ 0xf0156: 0xf0156,
+ 0xf0157: 0xf0157,
+ 0xf0158: 0xf0158,
+ 0xf0159: 0xf0159,
+ 0xf015a: 0xf015a,
+ 0xf015b: 0xf015b,
+ 0xf015c: 0xf015c,
+ 0xf015d: 0xf015d,
+ 0xf015e: 0xf015e,
+ 0xf015f: 0xf015f,
+ 0xf0160: 0xf0160,
+ 0xf0161: 0xf0161,
+ 0xf0162: 0xf0162,
+ 0xf0163: 0xf0163,
+ 0xf0164: 0xf0164,
+ 0xf0165: 0xf0165,
+ 0xf0166: 0xf0166,
+ 0xf0167: 0xf0167,
+ 0xf0168: 0xf0168,
+ 0xf0169: 0xf0169,
+ 0xf016a: 0xf016a,
+ 0xf016b: 0xf016b,
+ 0xf016c: 0xf016c,
+ 0xf016d: 0xf016d,
+ 0xf016e: 0xf016e,
+ 0xf016f: 0xf016f,
+ 0xf0170: 0xf0170,
+ 0xf0171: 0xf0171,
+ 0xf0172: 0xf0172,
+ 0xf0173: 0xf0173,
+ 0xf0174: 0xf0174,
+ 0xf0175: 0xf0175,
+ 0xf0176: 0xf0176,
+ 0xf0177: 0xf0177,
+ 0xf0178: 0xf0178,
+ 0xf0179: 0xf0179,
+ 0xf017a: 0xf017a,
+ 0xf017b: 0xf017b,
+ 0xf017c: 0xf017c,
+ 0xf017d: 0xf017d,
+ 0xf017e: 0xf017e,
+ 0xf017f: 0xf017f,
+ 0xf0180: 0xf0180,
+ 0xf0181: 0xf0181,
+ 0xf0182: 0xf0182,
+ 0xf0183: 0xf0183,
+ 0xf0184: 0xf0184,
+ 0xf0185: 0xf0185,
+ 0xf0186: 0xf0186,
+ 0xf0187: 0xf0187,
+ 0xf0188: 0xf0188,
+ 0xf0189: 0xf0189,
+ 0xf018a: 0xf018a,
+ 0xf018b: 0xf018b,
+ 0xf018c: 0xf018c,
+ 0xf018d: 0xf018d,
+ 0xf018e: 0xf018e,
+ 0xf018f: 0xf018f,
+ 0xf0190: 0xf0190,
+ 0xf0191: 0xf0191,
+ 0xf0192: 0xf0192,
+ 0xf0193: 0xf0193,
+ 0xf0194: 0xf0194,
+ 0xf0195: 0xf0195,
+ 0xf0196: 0xf0196,
+ 0xf0197: 0xf0197,
+ 0xf0198: 0xf0198,
+ 0xf0199: 0xf0199,
+ 0xf019a: 0xf019a,
+ 0xf019b: 0xf019b,
+ 0xf019c: 0xf019c,
+ 0xf019d: 0xf019d,
+ 0xf019e: 0xf019e,
+ 0xf019f: 0xf019f,
+ 0xf01a0: 0xf01a0,
+ 0xf01a1: 0xf01a1,
+ 0xf01a2: 0xf01a2,
+ 0xf01a3: 0xf01a3,
+ 0xf01a4: 0xf01a4,
+ 0xf01a5: 0xf01a5,
+ 0xf01a6: 0xf01a6,
+ 0xf01a7: 0xf01a7,
+ 0xf01a8: 0xf01a8,
+ 0xf01a9: 0xf01a9,
+ 0xf01aa: 0xf01aa,
+ 0xf01ab: 0xf01ab,
+ 0xf01ac: 0xf01ac,
+ 0xf01ad: 0xf01ad,
+ 0xf01ae: 0xf01ae,
+ 0xf01af: 0xf01af,
+ 0xf01b0: 0xf01b0,
+ 0xf01b1: 0xf01b1,
+ 0xf01b2: 0xf01b2,
+ 0xf01b3: 0xf01b3,
+ 0xf01b4: 0xf01b4,
+ 0xf01b5: 0xf01b5,
+ 0xf01b6: 0xf01b6,
+ 0xf01b7: 0xf01b7,
+ 0xf01b8: 0xf01b8,
+ 0xf01b9: 0xf01b9,
+ 0xf01ba: 0xf01ba,
+ 0xf01bb: 0xf01bb,
+ 0xf01bc: 0xf01bc,
+ 0xf01bd: 0xf01bd,
+ 0xf01be: 0xf01be,
+ 0xf01bf: 0xf01bf,
+ 0xf01c0: 0xf01c0,
+ 0xf01c1: 0xf01c1,
+ 0xf01c2: 0xf01c2,
+ 0xf01c3: 0xf01c3,
+ 0xf01c4: 0xf01c4,
+ 0xf01c5: 0xf01c5,
+ 0xf01c6: 0xf01c6,
+ 0xf01c7: 0xf01c7,
+ 0xf01c8: 0xf01c8,
+ 0xf01c9: 0xf01c9,
+ 0xf01ca: 0xf01ca,
+ 0xf01cb: 0xf01cb,
+ 0xf01cc: 0xf01cc,
+ 0xf01cd: 0xf01cd,
+ 0xf01ce: 0xf01ce,
+ 0xf01cf: 0xf01cf,
+ 0xf01d0: 0xf01d0,
+ 0xf01d1: 0xf01d1,
+ 0xf01d2: 0xf01d2,
+ 0xf01d3: 0xf01d3,
+ 0xf01d4: 0xf01d4,
+ 0xf01d5: 0xf01d5,
+ 0xf01d6: 0xf01d6,
+ 0xf01d7: 0xf01d7,
+ 0xf01d8: 0xf01d8,
+ 0xf01d9: 0xf01d9,
+ 0xf01da: 0xf01da,
+ 0xf01db: 0xf01db,
+ 0xf01dc: 0xf01dc,
+ 0xf01dd: 0xf01dd,
+ 0xf01de: 0xf01de,
+ 0xf01df: 0xf01df,
+ 0xf01e0: 0xf01e0,
+ 0xf01e1: 0xf01e1,
+ 0xf01e2: 0xf01e2,
+ 0xf01e3: 0xf01e3,
+ 0xf01e4: 0xf01e4,
+ 0xf01e5: 0xf01e5,
+ 0xf01e6: 0xf01e6,
+ 0xf01e7: 0xf01e7,
+ 0xf01e8: 0xf01e8,
+ 0xf01e9: 0xf01e9,
+ 0xf01ea: 0xf01ea,
+ 0xf01eb: 0xf01eb,
+ 0xf01ec: 0xf01ec,
+ 0xf01ed: 0xf01ed,
+ 0xf01ee: 0xf01ee,
+ 0xf01ef: 0xf01ef,
+ 0xf01f0: 0xf01f0,
+ 0xf01f1: 0xf01f1,
+ 0xf01f2: 0xf01f2,
+ 0xf01f3: 0xf01f3,
+ 0xf01f4: 0xf01f4,
+ 0xf01f5: 0xf01f5,
+ 0xf01f6: 0xf01f6,
+ 0xf01f7: 0xf01f7,
+ 0xf01f8: 0xf01f8,
+ 0xf01f9: 0xf01f9,
+ 0xf01fa: 0xf01fa,
+ 0xf01fb: 0xf01fb,
+ 0xf01fc: 0xf01fc,
+ 0xf01fd: 0xf01fd,
+ 0xf01fe: 0xf01fe,
+ 0xf01ff: 0xf01ff,
+ 0xf0200: 0xf0200,
+ 0xf0201: 0xf0201,
+ 0xf0202: 0xf0202,
+ 0xf0203: 0xf0203,
+ 0xf0204: 0xf0204,
+ 0xf0205: 0xf0205,
+ 0xf0206: 0xf0206,
+ 0xf0207: 0xf0207,
+ 0xf0208: 0xf0208,
+ 0xf0209: 0xf0209,
+ 0xf020a: 0xf020a,
+ 0xf020b: 0xf020b,
+ 0xf020c: 0xf020c,
+ 0xf020d: 0xf020d,
+ 0xf020e: 0xf020e,
+ 0xf020f: 0xf020f,
+ 0xf0210: 0xf0210,
+ 0xf0211: 0xf0211,
+ 0xf0212: 0xf0212,
+ 0xf0213: 0xf0213,
+ 0xf0214: 0xf0214,
+ 0xf0215: 0xf0215,
+ 0xf0216: 0xf0216,
+ 0xf0217: 0xf0217,
+ 0xf0218: 0xf0218,
+ 0xf0219: 0xf0219,
+ 0xf021a: 0xf021a,
+ 0xf021b: 0xf021b,
+ 0xf021c: 0xf021c,
+ 0xf021d: 0xf021d,
+ 0xf021e: 0xf021e,
+ 0xf021f: 0xf021f,
+ 0xf0220: 0xf0220,
+ 0xf0221: 0xf0221,
+ 0xf0222: 0xf0222,
+ 0xf0223: 0xf0223,
+ 0xf0224: 0xf0224,
+ 0xf0225: 0xf0225,
+ 0xf0226: 0xf0226,
+ 0xf0227: 0xf0227,
+ 0xf0228: 0xf0228,
+ 0xf0229: 0xf0229,
+ 0xf022a: 0xf022a,
+ 0xf022b: 0xf022b,
+ 0xf022c: 0xf022c,
+ 0xf022d: 0xf022d,
+ 0xf022e: 0xf022e,
+ 0xf022f: 0xf022f,
+ 0xf0230: 0xf0230,
+ 0xf0231: 0xf0231,
+ 0xf0232: 0xf0232,
+ 0xf0233: 0xf0233,
+ 0xf0234: 0xf0234,
+ 0xf0235: 0xf0235,
+ 0xf0236: 0xf0236,
+ 0xf0237: 0xf0237,
+ 0xf0238: 0xf0238,
+ 0xf0239: 0xf0239,
+ 0xf023a: 0xf023a,
+ 0xf023b: 0xf023b,
+ 0xf023c: 0xf023c,
+ 0xf023d: 0xf023d,
+ 0xf023e: 0xf023e,
+ 0xf023f: 0xf023f,
+ 0xf0240: 0xf0240,
+ 0xf0241: 0xf0241,
+ 0xf0242: 0xf0242,
+ 0xf0243: 0xf0243,
+ 0xf0244: 0xf0244,
+ 0xf0245: 0xf0245,
+ 0xf0246: 0xf0246,
+ 0xf0247: 0xf0247,
+ 0xf0248: 0xf0248,
+ 0xf0249: 0xf0249,
+ 0xf024a: 0xf024a,
+ 0xf024b: 0xf024b,
+ 0xf024c: 0xf024c,
+ 0xf024d: 0xf024d,
+ 0xf024e: 0xf024e,
+ 0xf024f: 0xf024f,
+ 0xf0250: 0xf0250,
+ 0xf0251: 0xf0251,
+ 0xf0252: 0xf0252,
+ 0xf0253: 0xf0253,
+ 0xf0254: 0xf0254,
+ 0xf0255: 0xf0255,
+ 0xf0256: 0xf0256,
+ 0xf0257: 0xf0257,
+ 0xf0258: 0xf0258,
+ 0xf0259: 0xf0259,
+ 0xf025a: 0xf025a,
+ 0xf025b: 0xf025b,
+ 0xf025c: 0xf025c,
+ 0xf025d: 0xf025d,
+ 0xf025e: 0xf025e,
+ 0xf025f: 0xf025f,
+ 0xf0260: 0xf0260,
+ 0xf0261: 0xf0261,
+ 0xf0262: 0xf0262,
+ 0xf0263: 0xf0263,
+ 0xf0264: 0xf0264,
+ 0xf0265: 0xf0265,
+ 0xf0266: 0xf0266,
+ 0xf0267: 0xf0267,
+ 0xf0268: 0xf0268,
+ 0xf0269: 0xf0269,
+ 0xf026a: 0xf026a,
+ 0xf026b: 0xf026b,
+ 0xf026c: 0xf026c,
+ 0xf026d: 0xf026d,
+ 0xf026e: 0xf026e,
+ 0xf026f: 0xf026f,
+ 0xf0270: 0xf0270,
+ 0xf0271: 0xf0271,
+ 0xf0272: 0xf0272,
+ 0xf0273: 0xf0273,
+ 0xf0274: 0xf0274,
+ 0xf0275: 0xf0275,
+ 0xf0276: 0xf0276,
+ 0xf0277: 0xf0277,
+ 0xf0278: 0xf0278,
+ 0xf0279: 0xf0279,
+ 0xf027a: 0xf027a,
+ 0xf027b: 0xf027b,
+ 0xf027c: 0xf027c,
+ 0xf027d: 0xf027d,
+ 0xf027e: 0xf027e,
+ 0xf027f: 0xf027f,
+ 0xf0280: 0xf0280,
+ 0xf0281: 0xf0281,
+ 0xf0282: 0xf0282,
+ 0xf0283: 0xf0283,
+ 0xf0284: 0xf0284,
+ 0xf0285: 0xf0285,
+ 0xf0286: 0xf0286,
+ 0xf0287: 0xf0287,
+ 0xf0288: 0xf0288,
+ 0xf0289: 0xf0289,
+ 0xf028a: 0xf028a,
+ 0xf028b: 0xf028b,
+ 0xf028c: 0xf028c,
+ 0xf028d: 0xf028d,
+ 0xf028e: 0xf028e,
+ 0xf028f: 0xf028f,
+ 0xf0290: 0xf0290,
+ 0xf0291: 0xf0291,
+ 0xf0292: 0xf0292,
+ 0xf0293: 0xf0293,
+ 0xf0294: 0xf0294,
+ 0xf0295: 0xf0295,
+ 0xf0296: 0xf0296,
+ 0xf0297: 0xf0297,
+ 0xf0298: 0xf0298,
+ 0xf0299: 0xf0299,
+ 0xf029a: 0xf029a,
+ 0xf029b: 0xf029b,
+ 0xf029c: 0xf029c,
+ 0xf029d: 0xf029d,
+ 0xf029e: 0xf029e,
+ 0xf029f: 0xf029f,
+ 0xf02a0: 0xf02a0,
+ 0xf02a1: 0xf02a1,
+ 0xf02a2: 0xf02a2,
+ 0xf02a3: 0xf02a3,
+ 0xf02a4: 0xf02a4,
+ 0xf02a5: 0xf02a5,
+ 0xf02a6: 0xf02a6,
+ 0xf02a7: 0xf02a7,
+ 0xf02a8: 0xf02a8,
+ 0xf02a9: 0xf02a9,
+ 0xf02aa: 0xf02aa,
+ 0xf02ab: 0xf02ab,
+ 0xf02ac: 0xf02ac,
+ 0xf02ad: 0xf02ad,
+ 0xf02ae: 0xf02ae,
+ 0xf02af: 0xf02af,
+ 0xf02b0: 0xf02b0,
+ 0xf02b1: 0xf02b1,
+ 0xf02b2: 0xf02b2,
+ 0xf02b3: 0xf02b3,
+ 0xf02b4: 0xf02b4,
+ 0xf02b5: 0xf02b5,
+ 0xf02b6: 0xf02b6,
+ 0xf02b7: 0xf02b7,
+ 0xf02b8: 0xf02b8,
+ 0xf02b9: 0xf02b9,
+ 0xf02ba: 0xf02ba,
+ 0xf02bb: 0xf02bb,
+ 0xf02bc: 0xf02bc,
+ 0xf02bd: 0xf02bd,
+ 0xf02be: 0xf02be,
+ 0xf02bf: 0xf02bf,
+ 0xf02c0: 0xf02c0,
+ 0xf02c1: 0xf02c1,
+ 0xf02c2: 0xf02c2,
+ 0xf02c3: 0xf02c3,
+ 0xf02c4: 0xf02c4,
+ 0xf02c5: 0xf02c5,
+ 0xf02c6: 0xf02c6,
+ 0xf02c7: 0xf02c7,
+ 0xf02c8: 0xf02c8,
+ 0xf02c9: 0xf02c9,
+ 0xf02ca: 0xf02ca,
+ 0xf02cb: 0xf02cb,
+ 0xf02cc: 0xf02cc,
+ 0xf02cd: 0xf02cd,
+ 0xf02ce: 0xf02ce,
+ 0xf02cf: 0xf02cf,
+ 0xf02d0: 0xf02d0,
+ 0xf02d1: 0xf02d1,
+ 0xf02d2: 0xf02d2,
+ 0xf02d3: 0xf02d3,
+ 0xf02d4: 0xf02d4,
+ 0xf02d5: 0xf02d5,
+ 0xf02d6: 0xf02d6,
+ 0xf02d7: 0xf02d7,
+ 0xf02d8: 0xf02d8,
+ 0xf02d9: 0xf02d9,
+ 0xf02da: 0xf02da,
+ 0xf02db: 0xf02db,
+ 0xf02dc: 0xf02dc,
+ 0xf02dd: 0xf02dd,
+ 0xf02de: 0xf02de,
+ 0xf02df: 0xf02df,
+ 0xf02e0: 0xf02e0,
+ 0xf02e1: 0xf02e1,
+ 0xf02e2: 0xf02e2,
+ 0xf02e3: 0xf02e3,
+ 0xf02e4: 0xf02e4,
+ 0xf02e5: 0xf02e5,
+ 0xf02e6: 0xf02e6,
+ 0xf02e7: 0xf02e7,
+ 0xf02e8: 0xf02e8,
+ 0xf02e9: 0xf02e9,
+ 0xf02ea: 0xf02ea,
+ 0xf02eb: 0xf02eb,
+ 0xf02ec: 0xf02ec,
+ 0xf02ed: 0xf02ed,
+ 0xf02ee: 0xf02ee,
+ 0xf02ef: 0xf02ef,
+ 0xf02f0: 0xf02f0,
+ 0xf02f1: 0xf02f1,
+ 0xf02f2: 0xf02f2,
+ 0xf02f3: 0xf02f3,
+ 0xf02f4: 0xf02f4,
+ 0xf02f5: 0xf02f5,
+ 0xf02f6: 0xf02f6,
+ 0xf02f7: 0xf02f7,
+ 0xf02f8: 0xf02f8,
+ 0xf02f9: 0xf02f9,
+ 0xf02fa: 0xf02fa,
+ 0xf02fb: 0xf02fb,
+ 0xf02fc: 0xf02fc,
+ 0xf02fd: 0xf02fd,
+ 0xf02fe: 0xf02fe,
+ 0xf02ff: 0xf02ff,
+ 0xf0300: 0xf0300,
+ 0xf0301: 0xf0301,
+ 0xf0302: 0xf0302,
+ 0xf0303: 0xf0303,
+ 0xf0304: 0xf0304,
+ 0xf0305: 0xf0305,
+ 0xf0306: 0xf0306,
+ 0xf0307: 0xf0307,
+ 0xf0308: 0xf0308,
+ 0xf0309: 0xf0309,
+ 0xf030a: 0xf030a,
+ 0xf030b: 0xf030b,
+ 0xf030c: 0xf030c,
+ 0xf030d: 0xf030d,
+ 0xf030e: 0xf030e,
+ 0xf030f: 0xf030f,
+ 0xf0310: 0xf0310,
+ 0xf0311: 0xf0311,
+ 0xf0312: 0xf0312,
+ 0xf0313: 0xf0313,
+ 0xf0314: 0xf0314,
+ 0xf0315: 0xf0315,
+ 0xf0316: 0xf0316,
+ 0xf0317: 0xf0317,
+ 0xf0318: 0xf0318,
+ 0xf0319: 0xf0319,
+ 0xf031a: 0xf031a,
+ 0xf031b: 0xf031b,
+ 0xf031c: 0xf031c,
+ 0xf031d: 0xf031d,
+ 0xf031e: 0xf031e,
+ 0xf031f: 0xf031f,
+ 0xf0320: 0xf0320,
+ 0xf0321: 0xf0321,
+ 0xf0322: 0xf0322,
+ 0xf0323: 0xf0323,
+ 0xf0324: 0xf0324,
+ 0xf0325: 0xf0325,
+ 0xf0326: 0xf0326,
+ 0xf0327: 0xf0327,
+ 0xf0328: 0xf0328,
+ 0xf0329: 0xf0329,
+ 0xf032a: 0xf032a,
+ 0xf032b: 0xf032b,
+ 0xf032c: 0xf032c,
+ 0xf032d: 0xf032d,
+ 0xf032e: 0xf032e,
+ 0xf032f: 0xf032f,
+ 0xf0330: 0xf0330,
+ 0xf0331: 0xf0331,
+ 0xf0332: 0xf0332,
+ 0xf0333: 0xf0333,
+ 0xf0334: 0xf0334,
+ 0xf0335: 0xf0335,
+ 0xf0336: 0xf0336,
+ 0xf0337: 0xf0337,
+ 0xf0338: 0xf0338,
+ 0xf0339: 0xf0339,
+ 0xf033a: 0xf033a,
+ 0xf033b: 0xf033b,
+ 0xf033c: 0xf033c,
+ 0xf033d: 0xf033d,
+ 0xf033e: 0xf033e,
+ 0xf033f: 0xf033f,
+ 0xf0340: 0xf0340,
+ 0xf0341: 0xf0341,
+ 0xf0342: 0xf0342,
+ 0xf0343: 0xf0343,
+ 0xf0344: 0xf0344,
+ 0xf0345: 0xf0345,
+ 0xf0346: 0xf0346,
+ 0xf0347: 0xf0347,
+ 0xf0348: 0xf0348,
+ 0xf0349: 0xf0349,
+ 0xf034a: 0xf034a,
+ 0xf034b: 0xf034b,
+ 0xf034c: 0xf034c,
+ 0xf034d: 0xf034d,
+ 0xf034e: 0xf034e,
+ 0xf034f: 0xf034f,
+ 0xf0350: 0xf0350,
+ 0xf0351: 0xf0351,
+ 0xf0352: 0xf0352,
+ 0xf0353: 0xf0353,
+ 0xf0354: 0xf0354,
+ 0xf0355: 0xf0355,
+ 0xf0356: 0xf0356,
+ 0xf0357: 0xf0357,
+ 0xf0358: 0xf0358,
+ 0xf0359: 0xf0359,
+ 0xf035a: 0xf035a,
+ 0xf035b: 0xf035b,
+ 0xf035c: 0xf035c,
+ 0xf035d: 0xf035d,
+ 0xf035e: 0xf035e,
+ 0xf035f: 0xf035f,
+ 0xf0360: 0xf0360,
+ 0xf0361: 0xf0361,
+ 0xf0362: 0xf0362,
+ 0xf0363: 0xf0363,
+ 0xf0364: 0xf0364,
+ 0xf0365: 0xf0365,
+ 0xf0366: 0xf0366,
+ 0xf0367: 0xf0367,
+ 0xf0368: 0xf0368,
+ 0xf0369: 0xf0369,
+ 0xf036a: 0xf036a,
+ 0xf036b: 0xf036b,
+ 0xf036c: 0xf036c,
+ 0xf036d: 0xf036d,
+ 0xf036e: 0xf036e,
+ 0xf036f: 0xf036f,
+ 0xf0370: 0xf0370,
+ 0xf0371: 0xf0371,
+ 0xf0372: 0xf0372,
+ 0xf0373: 0xf0373,
+ 0xf0374: 0xf0374,
+ 0xf0375: 0xf0375,
+ 0xf0376: 0xf0376,
+ 0xf0377: 0xf0377,
+ 0xf0378: 0xf0378,
+ 0xf0379: 0xf0379,
+ 0xf037a: 0xf037a,
+ 0xf037b: 0xf037b,
+ 0xf037c: 0xf037c,
+ 0xf037d: 0xf037d,
+ 0xf037e: 0xf037e,
+ 0xf037f: 0xf037f,
+ 0xf0380: 0xf0380,
+ 0xf0381: 0xf0381,
+ 0xf0382: 0xf0382,
+ 0xf0383: 0xf0383,
+ 0xf0384: 0xf0384,
+ 0xf0385: 0xf0385,
+ 0xf0386: 0xf0386,
+ 0xf0387: 0xf0387,
+ 0xf0388: 0xf0388,
+ 0xf0389: 0xf0389,
+ 0xf038a: 0xf038a,
+ 0xf038b: 0xf038b,
+ 0xf038c: 0xf038c,
+ 0xf038d: 0xf038d,
+ 0xf038e: 0xf038e,
+ 0xf038f: 0xf038f,
+ 0xf0390: 0xf0390,
+ 0xf0391: 0xf0391,
+ 0xf0392: 0xf0392,
+ 0xf0393: 0xf0393,
+ 0xf0394: 0xf0394,
+ 0xf0395: 0xf0395,
+ 0xf0396: 0xf0396,
+ 0xf0397: 0xf0397,
+ 0xf0398: 0xf0398,
+ 0xf0399: 0xf0399,
+ 0xf039a: 0xf039a,
+ 0xf039b: 0xf039b,
+ 0xf039c: 0xf039c,
+ 0xf039d: 0xf039d,
+ 0xf039e: 0xf039e,
+ 0xf039f: 0xf039f,
+ 0xf03a0: 0xf03a0,
+ 0xf03a1: 0xf03a1,
+ 0xf03a2: 0xf03a2,
+ 0xf03a3: 0xf03a3,
+ 0xf03a4: 0xf03a4,
+ 0xf03a5: 0xf03a5,
+ 0xf03a6: 0xf03a6,
+ 0xf03a7: 0xf03a7,
+ 0xf03a8: 0xf03a8,
+ 0xf03a9: 0xf03a9,
+ 0xf03aa: 0xf03aa,
+ 0xf03ab: 0xf03ab,
+ 0xf03ac: 0xf03ac,
+ 0xf03ad: 0xf03ad,
+ 0xf03ae: 0xf03ae,
+ 0xf03af: 0xf03af,
+ 0xf03b0: 0xf03b0,
+ 0xf03b1: 0xf03b1,
+ 0xf03b2: 0xf03b2,
+ 0xf03b3: 0xf03b3,
+ 0xf03b4: 0xf03b4,
+ 0xf03b5: 0xf03b5,
+ 0xf03b6: 0xf03b6,
+ 0xf03b7: 0xf03b7,
+ 0xf03b8: 0xf03b8,
+ 0xf03b9: 0xf03b9,
+ 0xf03ba: 0xf03ba,
+ 0xf03bb: 0xf03bb,
+ 0xf03bc: 0xf03bc,
+ 0xf03bd: 0xf03bd,
+ 0xf03be: 0xf03be,
+ 0xf03bf: 0xf03bf,
+ 0xf03c0: 0xf03c0,
+ 0xf03c1: 0xf03c1,
+ 0xf03c2: 0xf03c2,
+ 0xf03c3: 0xf03c3,
+ 0xf03c4: 0xf03c4,
+ 0xf03c5: 0xf03c5,
+ 0xf03c6: 0xf03c6,
+ 0xf03c7: 0xf03c7,
+ 0xf03c8: 0xf03c8,
+ 0xf03c9: 0xf03c9,
+ 0xf03ca: 0xf03ca,
+ 0xf03cb: 0xf03cb,
+ 0xf03cc: 0xf03cc,
+ 0xf03cd: 0xf03cd,
+ 0xf03ce: 0xf03ce,
+ 0xf03cf: 0xf03cf,
+ 0xf03d0: 0xf03d0,
+ 0xf03d1: 0xf03d1,
+ 0xf03d2: 0xf03d2,
+ 0xf03d3: 0xf03d3,
+ 0xf03d4: 0xf03d4,
+ 0xf03d5: 0xf03d5,
+ 0xf03d6: 0xf03d6,
+ 0xf03d7: 0xf03d7,
+ 0xf03d8: 0xf03d8,
+ 0xf03d9: 0xf03d9,
+ 0xf03da: 0xf03da,
+ 0xf03db: 0xf03db,
+ 0xf03dc: 0xf03dc,
+ 0xf03dd: 0xf03dd,
+ 0xf03de: 0xf03de,
+ 0xf03df: 0xf03df,
+ 0xf03e0: 0xf03e0,
+ 0xf03e1: 0xf03e1,
+ 0xf03e2: 0xf03e2,
+ 0xf03e3: 0xf03e3,
+ 0xf03e4: 0xf03e4,
+ 0xf03e5: 0xf03e5,
+ 0xf03e6: 0xf03e6,
+ 0xf03e7: 0xf03e7,
+ 0xf03e8: 0xf03e8,
+ 0xf03e9: 0xf03e9,
+ 0xf03ea: 0xf03ea,
+ 0xf03eb: 0xf03eb,
+ 0xf03ec: 0xf03ec,
+ 0xf03ed: 0xf03ed,
+ 0xf03ee: 0xf03ee,
+ 0xf03ef: 0xf03ef,
+ 0xf03f0: 0xf03f0,
+ 0xf03f1: 0xf03f1,
+ 0xf03f2: 0xf03f2,
+ 0xf03f3: 0xf03f3,
+ 0xf03f4: 0xf03f4,
+ 0xf03f5: 0xf03f5,
+ 0xf03f6: 0xf03f6,
+ 0xf03f7: 0xf03f7,
+ 0xf03f8: 0xf03f8,
+ 0xf03f9: 0xf03f9,
+ 0xf03fa: 0xf03fa,
+ 0xf03fb: 0xf03fb,
+ 0xf03fc: 0xf03fc,
+ 0xf03fd: 0xf03fd,
+ 0xf03fe: 0xf03fe,
+ 0xf03ff: 0xf03ff,
+ 0xf0400: 0xf0400,
+ 0xf0401: 0xf0401,
+ 0xf0402: 0xf0402,
+ 0xf0403: 0xf0403,
+ 0xf0404: 0xf0404,
+ 0xf0405: 0xf0405,
+ 0xf0406: 0xf0406,
+ 0xf0407: 0xf0407,
+ 0xf0408: 0xf0408,
+ 0xf0409: 0xf0409,
+ 0xf040a: 0xf040a,
+ 0xf040b: 0xf040b,
+ 0xf040c: 0xf040c,
+ 0xf040d: 0xf040d,
+ 0xf040e: 0xf040e,
+ 0xf040f: 0xf040f,
+ 0xf0410: 0xf0410,
+ 0xf0411: 0xf0411,
+ 0xf0412: 0xf0412,
+ 0xf0413: 0xf0413,
+ 0xf0414: 0xf0414,
+ 0xf0415: 0xf0415,
+ 0xf0416: 0xf0416,
+ 0xf0417: 0xf0417,
+ 0xf0418: 0xf0418,
+ 0xf0419: 0xf0419,
+ 0xf041a: 0xf041a,
+ 0xf041b: 0xf041b,
+ 0xf041c: 0xf041c,
+ 0xf041d: 0xf041d,
+ 0xf041e: 0xf041e,
+ 0xf041f: 0xf041f,
+ 0xf0420: 0xf0420,
+ 0xf0421: 0xf0421,
+ 0xf0422: 0xf0422,
+ 0xf0423: 0xf0423,
+ 0xf0424: 0xf0424,
+ 0xf0425: 0xf0425,
+ 0xf0426: 0xf0426,
+ 0xf0427: 0xf0427,
+ 0xf0428: 0xf0428,
+ 0xf0429: 0xf0429,
+ 0xf042a: 0xf042a,
+ 0xf042b: 0xf042b,
+ 0xf042c: 0xf042c,
+ 0xf042d: 0xf042d,
+ 0xf042e: 0xf042e,
+ 0xf042f: 0xf042f,
+ 0xf0430: 0xf0430,
+ 0xf0431: 0xf0431,
+ 0xf0432: 0xf0432,
+ 0xf0433: 0xf0433,
+ 0xf0434: 0xf0434,
+ 0xf0435: 0xf0435,
+ 0xf0436: 0xf0436,
+ 0xf0437: 0xf0437,
+ 0xf0438: 0xf0438,
+ 0xf0439: 0xf0439,
+ 0xf043a: 0xf043a,
+ 0xf043b: 0xf043b,
+ 0xf043c: 0xf043c,
+ 0xf043d: 0xf043d,
+ 0xf043e: 0xf043e,
+ 0xf043f: 0xf043f,
+ 0xf0440: 0xf0440,
+ 0xf0441: 0xf0441,
+ 0xf0442: 0xf0442,
+ 0xf0443: 0xf0443,
+ 0xf0444: 0xf0444,
+ 0xf0445: 0xf0445,
+ 0xf0446: 0xf0446,
+ 0xf0447: 0xf0447,
+ 0xf0448: 0xf0448,
+ 0xf0449: 0xf0449,
+ 0xf044a: 0xf044a,
+ 0xf044b: 0xf044b,
+ 0xf044c: 0xf044c,
+ 0xf044d: 0xf044d,
+ 0xf044e: 0xf044e,
+ 0xf044f: 0xf044f,
+ 0xf0450: 0xf0450,
+ 0xf0451: 0xf0451,
+ 0xf0452: 0xf0452,
+ 0xf0453: 0xf0453,
+ 0xf0454: 0xf0454,
+ 0xf0455: 0xf0455,
+ 0xf0456: 0xf0456,
+ 0xf0457: 0xf0457,
+ 0xf0458: 0xf0458,
+ 0xf0459: 0xf0459,
+ 0xf045a: 0xf045a,
+ 0xf045b: 0xf045b,
+ 0xf045c: 0xf045c,
+ 0xf045d: 0xf045d,
+ 0xf045e: 0xf045e,
+ 0xf045f: 0xf045f,
+ 0xf0460: 0xf0460,
+ 0xf0461: 0xf0461,
+ 0xf0462: 0xf0462,
+ 0xf0463: 0xf0463,
+ 0xf0464: 0xf0464,
+ 0xf0465: 0xf0465,
+ 0xf0466: 0xf0466,
+ 0xf0467: 0xf0467,
+ 0xf0468: 0xf0468,
+ 0xf0469: 0xf0469,
+ 0xf046a: 0xf046a,
+ 0xf046b: 0xf046b,
+ 0xf046c: 0xf046c,
+ 0xf046d: 0xf046d,
+ 0xf046e: 0xf046e,
+ 0xf046f: 0xf046f,
+ 0xf0470: 0xf0470,
+ 0xf0471: 0xf0471,
+ 0xf0472: 0xf0472,
+ 0xf0473: 0xf0473,
+ 0xf0474: 0xf0474,
+ 0xf0475: 0xf0475,
+ 0xf0476: 0xf0476,
+ 0xf0477: 0xf0477,
+ 0xf0478: 0xf0478,
+ 0xf0479: 0xf0479,
+ 0xf047a: 0xf047a,
+ 0xf047b: 0xf047b,
+ 0xf047c: 0xf047c,
+ 0xf047d: 0xf047d,
+ 0xf047e: 0xf047e,
+ 0xf047f: 0xf047f,
+ 0xf0480: 0xf0480,
+ 0xf0481: 0xf0481,
+ 0xf0482: 0xf0482,
+ 0xf0483: 0xf0483,
+ 0xf0484: 0xf0484,
+ 0xf0485: 0xf0485,
+ 0xf0486: 0xf0486,
+ 0xf0487: 0xf0487,
+ 0xf0488: 0xf0488,
+ 0xf0489: 0xf0489,
+ 0xf048a: 0xf048a,
+ 0xf048b: 0xf048b,
+ 0xf048c: 0xf048c,
+ 0xf048d: 0xf048d,
+ 0xf048e: 0xf048e,
+ 0xf048f: 0xf048f,
+ 0xf0490: 0xf0490,
+ 0xf0491: 0xf0491,
+ 0xf0492: 0xf0492,
+ 0xf0493: 0xf0493,
+ 0xf0494: 0xf0494,
+ 0xf0495: 0xf0495,
+ 0xf0496: 0xf0496,
+ 0xf0497: 0xf0497,
+ 0xf0498: 0xf0498,
+ 0xf0499: 0xf0499,
+ 0xf049a: 0xf049a,
+ 0xf049b: 0xf049b,
+ 0xf049c: 0xf049c,
+ 0xf049d: 0xf049d,
+ 0xf049e: 0xf049e,
+ 0xf049f: 0xf049f,
+ 0xf04a0: 0xf04a0,
+ 0xf04a1: 0xf04a1,
+ 0xf04a2: 0xf04a2,
+ 0xf04a3: 0xf04a3,
+ 0xf04a4: 0xf04a4,
+ 0xf04a5: 0xf04a5,
+ 0xf04a6: 0xf04a6,
+ 0xf04a7: 0xf04a7,
+ 0xf04a8: 0xf04a8,
+ 0xf04a9: 0xf04a9,
+ 0xf04aa: 0xf04aa,
+ 0xf04ab: 0xf04ab,
+ 0xf04ac: 0xf04ac,
+ 0xf04ad: 0xf04ad,
+ 0xf04ae: 0xf04ae,
+ 0xf04af: 0xf04af,
+ 0xf04b0: 0xf04b0,
+ 0xf04b1: 0xf04b1,
+ 0xf04b2: 0xf04b2,
+ 0xf04b3: 0xf04b3,
+ 0xf04b4: 0xf04b4,
+ 0xf04b5: 0xf04b5,
+ 0xf04b6: 0xf04b6,
+ 0xf04b7: 0xf04b7,
+ 0xf04b8: 0xf04b8,
+ 0xf04b9: 0xf04b9,
+ 0xf04ba: 0xf04ba,
+ 0xf04bb: 0xf04bb,
+ 0xf04bc: 0xf04bc,
+ 0xf04bd: 0xf04bd,
+ 0xf04be: 0xf04be,
+ 0xf04bf: 0xf04bf,
+ 0xf04c0: 0xf04c0,
+ 0xf04c1: 0xf04c1,
+ 0xf04c2: 0xf04c2,
+ 0xf04c3: 0xf04c3,
+ 0xf04c4: 0xf04c4,
+ 0xf04c5: 0xf04c5,
+ 0xf04c6: 0xf04c6,
+ 0xf04c7: 0xf04c7,
+ 0xf04c8: 0xf04c8,
+ 0xf04c9: 0xf04c9,
+ 0xf04ca: 0xf04ca,
+ 0xf04cb: 0xf04cb,
+ 0xf04cc: 0xf04cc,
+ 0xf04cd: 0xf04cd,
+ 0xf04ce: 0xf04ce,
+ 0xf04cf: 0xf04cf,
+ 0xf04d0: 0xf04d0,
+ 0xf04d1: 0xf04d1,
+ 0xf04d2: 0xf04d2,
+ 0xf04d3: 0xf04d3,
+ 0xf04d4: 0xf04d4,
+ 0xf04d5: 0xf04d5,
+ 0xf04d6: 0xf04d6,
+ 0xf04d7: 0xf04d7,
+ 0xf04d8: 0xf04d8,
+ 0xf04d9: 0xf04d9,
+ 0xf04da: 0xf04da,
+ 0xf04db: 0xf04db,
+ 0xf04dc: 0xf04dc,
+ 0xf04dd: 0xf04dd,
+ 0xf04de: 0xf04de,
+ 0xf04df: 0xf04df,
+ 0xf04e0: 0xf04e0,
+ 0xf04e1: 0xf04e1,
+ 0xf04e2: 0xf04e2,
+ 0xf04e3: 0xf04e3,
+ 0xf04e4: 0xf04e4,
+ 0xf04e5: 0xf04e5,
+ 0xf04e6: 0xf04e6,
+ 0xf04e7: 0xf04e7,
+ 0xf04e8: 0xf04e8,
+ 0xf04e9: 0xf04e9,
+ 0xf04ea: 0xf04ea,
+ 0xf04eb: 0xf04eb,
+ 0xf04ec: 0xf04ec,
+ 0xf04ed: 0xf04ed,
+ 0xf04ee: 0xf04ee,
+ 0xf04ef: 0xf04ef,
+ 0xf04f0: 0xf04f0,
+ 0xf04f1: 0xf04f1,
+ 0xf04f2: 0xf04f2,
+ 0xf04f3: 0xf04f3,
+ 0xf04f4: 0xf04f4,
+ 0xf04f5: 0xf04f5,
+ 0xf04f6: 0xf04f6,
+ 0xf04f7: 0xf04f7,
+ 0xf04f8: 0xf04f8,
+ 0xf04f9: 0xf04f9,
+ 0xf04fa: 0xf04fa,
+ 0xf04fb: 0xf04fb,
+ 0xf04fc: 0xf04fc,
+ 0xf04fd: 0xf04fd,
+ 0xf04fe: 0xf04fe,
+ 0xf04ff: 0xf04ff,
+ 0xf0500: 0xf0500,
+ 0xf0501: 0xf0501,
+ 0xf0502: 0xf0502,
+ 0xf0503: 0xf0503,
+ 0xf0504: 0xf0504,
+ 0xf0505: 0xf0505,
+ 0xf0506: 0xf0506,
+ 0xf0507: 0xf0507,
+ 0xf0508: 0xf0508,
+ 0xf0509: 0xf0509,
+ 0xf050a: 0xf050a,
+ 0xf050b: 0xf050b,
+ 0xf050c: 0xf050c,
+ 0xf050d: 0xf050d,
+ 0xf050e: 0xf050e,
+ 0xf050f: 0xf050f,
+ 0xf0510: 0xf0510,
+ 0xf0511: 0xf0511,
+ 0xf0512: 0xf0512,
+ 0xf0513: 0xf0513,
+ 0xf0514: 0xf0514,
+ 0xf0515: 0xf0515,
+ 0xf0516: 0xf0516,
+ 0xf0517: 0xf0517,
+ 0xf0518: 0xf0518,
+ 0xf0519: 0xf0519,
+ 0xf051a: 0xf051a,
+ 0xf051b: 0xf051b,
+ 0xf051c: 0xf051c,
+ 0xf051d: 0xf051d,
+ 0xf051e: 0xf051e,
+ 0xf051f: 0xf051f,
+ 0xf0520: 0xf0520,
+ 0xf0521: 0xf0521,
+ 0xf0522: 0xf0522,
+ 0xf0523: 0xf0523,
+ 0xf0524: 0xf0524,
+ 0xf0525: 0xf0525,
+ 0xf0526: 0xf0526,
+ 0xf0527: 0xf0527,
+ 0xf0528: 0xf0528,
+ 0xf0529: 0xf0529,
+ 0xf052a: 0xf052a,
+ 0xf052b: 0xf052b,
+ 0xf052c: 0xf052c,
+ 0xf052d: 0xf052d,
+ 0xf052e: 0xf052e,
+ 0xf052f: 0xf052f,
+ 0xf0530: 0xf0530,
+ 0xf0531: 0xf0531,
+ 0xf0532: 0xf0532,
+ 0xf0533: 0xf0533,
+ 0xf0534: 0xf0534,
+ 0xf0535: 0xf0535,
+ 0xf0536: 0xf0536,
+ 0xf0537: 0xf0537,
+ 0xf0538: 0xf0538,
+ 0xf0539: 0xf0539,
+ 0xf053a: 0xf053a,
+ 0xf053b: 0xf053b,
+ 0xf053c: 0xf053c,
+ 0xf053d: 0xf053d,
+ 0xf053e: 0xf053e,
+ 0xf053f: 0xf053f,
+ 0xf0540: 0xf0540,
+ 0xf0541: 0xf0541,
+ 0xf0542: 0xf0542,
+ 0xf0543: 0xf0543,
+ 0xf0544: 0xf0544,
+ 0xf0545: 0xf0545,
+ 0xf0546: 0xf0546,
+ 0xf0547: 0xf0547,
+ 0xf0548: 0xf0548,
+ 0xf0549: 0xf0549,
+ 0xf054a: 0xf054a,
+ 0xf054b: 0xf054b,
+ 0xf054c: 0xf054c,
+ 0xf054d: 0xf054d,
+ 0xf054e: 0xf054e,
+ 0xf054f: 0xf054f,
+ 0xf0550: 0xf0550,
+ 0xf0551: 0xf0551,
+ 0xf0552: 0xf0552,
+ 0xf0553: 0xf0553,
+ 0xf0554: 0xf0554,
+ 0xf0555: 0xf0555,
+ 0xf0556: 0xf0556,
+ 0xf0557: 0xf0557,
+ 0xf0558: 0xf0558,
+ 0xf0559: 0xf0559,
+ 0xf055a: 0xf055a,
+ 0xf055b: 0xf055b,
+ 0xf055c: 0xf055c,
+ 0xf055d: 0xf055d,
+ 0xf055e: 0xf055e,
+ 0xf055f: 0xf055f,
+ 0xf0560: 0xf0560,
+ 0xf0561: 0xf0561,
+ 0xf0562: 0xf0562,
+ 0xf0563: 0xf0563,
+ 0xf0564: 0xf0564,
+ 0xf0565: 0xf0565,
+ 0xf0566: 0xf0566,
+ 0xf0567: 0xf0567,
+ 0xf0568: 0xf0568,
+ 0xf0569: 0xf0569,
+ 0xf056a: 0xf056a,
+ 0xf056b: 0xf056b,
+ 0xf056c: 0xf056c,
+ 0xf056d: 0xf056d,
+ 0xf056e: 0xf056e,
+ 0xf056f: 0xf056f,
+ 0xf0570: 0xf0570,
+ 0xf0571: 0xf0571,
+ 0xf0572: 0xf0572,
+ 0xf0573: 0xf0573,
+ 0xf0574: 0xf0574,
+ 0xf0575: 0xf0575,
+ 0xf0576: 0xf0576,
+ 0xf0577: 0xf0577,
+ 0xf0578: 0xf0578,
+ 0xf0579: 0xf0579,
+ 0xf057a: 0xf057a,
+ 0xf057b: 0xf057b,
+ 0xf057c: 0xf057c,
+ 0xf057d: 0xf057d,
+ 0xf057e: 0xf057e,
+ 0xf057f: 0xf057f,
+ 0xf0580: 0xf0580,
+ 0xf0581: 0xf0581,
+ 0xf0582: 0xf0582,
+ 0xf0583: 0xf0583,
+ 0xf0584: 0xf0584,
+ 0xf0585: 0xf0585,
+ 0xf0586: 0xf0586,
+ 0xf0587: 0xf0587,
+ 0xf0588: 0xf0588,
+ 0xf0589: 0xf0589,
+ 0xf058a: 0xf058a,
+ 0xf058b: 0xf058b,
+ 0xf058c: 0xf058c,
+ 0xf058d: 0xf058d,
+ 0xf058e: 0xf058e,
+ 0xf058f: 0xf058f,
+ 0xf0590: 0xf0590,
+ 0xf0591: 0xf0591,
+ 0xf0592: 0xf0592,
+ 0xf0593: 0xf0593,
+ 0xf0594: 0xf0594,
+ 0xf0595: 0xf0595,
+ 0xf0596: 0xf0596,
+ 0xf0597: 0xf0597,
+ 0xf0598: 0xf0598,
+ 0xf0599: 0xf0599,
+ 0xf059a: 0xf059a,
+ 0xf059b: 0xf059b,
+ 0xf059c: 0xf059c,
+ 0xf059d: 0xf059d,
+ 0xf059e: 0xf059e,
+ 0xf059f: 0xf059f,
+ 0xf05a0: 0xf05a0,
+ 0xf05a1: 0xf05a1,
+ 0xf05a2: 0xf05a2,
+ 0xf05a3: 0xf05a3,
+ 0xf05a4: 0xf05a4,
+ 0xf05a5: 0xf05a5,
+ 0xf05a6: 0xf05a6,
+ 0xf05a7: 0xf05a7,
+ 0xf05a8: 0xf05a8,
+ 0xf05a9: 0xf05a9,
+ 0xf05aa: 0xf05aa,
+ 0xf05ab: 0xf05ab,
+ 0xf05ac: 0xf05ac,
+ 0xf05ad: 0xf05ad,
+ 0xf05ae: 0xf05ae,
+ 0xf05af: 0xf05af,
+ 0xf05b0: 0xf05b0,
+ 0xf05b1: 0xf05b1,
+ 0xf05b2: 0xf05b2,
+ 0xf05b3: 0xf05b3,
+ 0xf05b4: 0xf05b4,
+ 0xf05b5: 0xf05b5,
+ 0xf05b6: 0xf05b6,
+ 0xf05b7: 0xf05b7,
+ 0xf05b8: 0xf05b8,
+ 0xf05b9: 0xf05b9,
+ 0xf05ba: 0xf05ba,
+ 0xf05bb: 0xf05bb,
+ 0xf05bc: 0xf05bc,
+ 0xf05bd: 0xf05bd,
+ 0xf05be: 0xf05be,
+ 0xf05bf: 0xf05bf,
+ 0xf05c0: 0xf05c0,
+ 0xf05c1: 0xf05c1,
+ 0xf05c2: 0xf05c2,
+ 0xf05c3: 0xf05c3,
+ 0xf05c4: 0xf05c4,
+ 0xf05c5: 0xf05c5,
+ 0xf05c6: 0xf05c6,
+ 0xf05c7: 0xf05c7,
+ 0xf05c8: 0xf05c8,
+ 0xf05c9: 0xf05c9,
+ 0xf05ca: 0xf05ca,
+ 0xf05cb: 0xf05cb,
+ 0xf05cc: 0xf05cc,
+ 0xf05cd: 0xf05cd,
+ 0xf05ce: 0xf05ce,
+ 0xf05cf: 0xf05cf,
+ 0xf05d0: 0xf05d0,
+ 0xf05d1: 0xf05d1,
+ 0xf05d2: 0xf05d2,
+ 0xf05d3: 0xf05d3,
+ 0xf05d4: 0xf05d4,
+ 0xf05d5: 0xf05d5,
+ 0xf05d6: 0xf05d6,
+ 0xf05d7: 0xf05d7,
+ 0xf05d8: 0xf05d8,
+ 0xf05d9: 0xf05d9,
+ 0xf05da: 0xf05da,
+ 0xf05db: 0xf05db,
+ 0xf05dc: 0xf05dc,
+ 0xf05dd: 0xf05dd,
+ 0xf05de: 0xf05de,
+ 0xf05df: 0xf05df,
+ 0xf05e0: 0xf05e0,
+ 0xf05e1: 0xf05e1,
+ 0xf05e2: 0xf05e2,
+ 0xf05e3: 0xf05e3,
+ 0xf05e4: 0xf05e4,
+ 0xf05e5: 0xf05e5,
+ 0xf05e6: 0xf05e6,
+ 0xf05e7: 0xf05e7,
+ 0xf05e8: 0xf05e8,
+ 0xf05e9: 0xf05e9,
+ 0xf05ea: 0xf05ea,
+ 0xf05eb: 0xf05eb,
+ 0xf05ec: 0xf05ec,
+ 0xf05ed: 0xf05ed,
+ 0xf05ee: 0xf05ee,
+ 0xf05ef: 0xf05ef,
+ 0xf05f0: 0xf05f0,
+ 0xf05f1: 0xf05f1,
+ 0xf05f2: 0xf05f2,
+ 0xf05f3: 0xf05f3,
+ 0xf05f4: 0xf05f4,
+ 0xf05f5: 0xf05f5,
+ 0xf05f6: 0xf05f6,
+ 0xf05f7: 0xf05f7,
+ 0xf05f8: 0xf05f8,
+ 0xf05f9: 0xf05f9,
+ 0xf05fa: 0xf05fa,
+ 0xf05fb: 0xf05fb,
+ 0xf05fc: 0xf05fc,
+ 0xf05fd: 0xf05fd,
+ 0xf05fe: 0xf05fe,
+ 0xf05ff: 0xf05ff,
+ 0xf0600: 0xf0600,
+ 0xf0601: 0xf0601,
+ 0xf0602: 0xf0602,
+ 0xf0603: 0xf0603,
+ 0xf0604: 0xf0604,
+ 0xf0605: 0xf0605,
+ 0xf0606: 0xf0606,
+ 0xf0607: 0xf0607,
+ 0xf0608: 0xf0608,
+ 0xf0609: 0xf0609,
+ 0xf060a: 0xf060a,
+ 0xf060b: 0xf060b,
+ 0xf060c: 0xf060c,
+ 0xf060d: 0xf060d,
+ 0xf060e: 0xf060e,
+ 0xf060f: 0xf060f,
+ 0xf0610: 0xf0610,
+ 0xf0611: 0xf0611,
+ 0xf0612: 0xf0612,
+ 0xf0613: 0xf0613,
+ 0xf0614: 0xf0614,
+ 0xf0615: 0xf0615,
+ 0xf0616: 0xf0616,
+ 0xf0617: 0xf0617,
+ 0xf0618: 0xf0618,
+ 0xf0619: 0xf0619,
+ 0xf061a: 0xf061a,
+ 0xf061b: 0xf061b,
+ 0xf061c: 0xf061c,
+ 0xf061d: 0xf061d,
+ 0xf061e: 0xf061e,
+ 0xf061f: 0xf061f,
+ 0xf0620: 0xf0620,
+ 0xf0621: 0xf0621,
+ 0xf0622: 0xf0622,
+ 0xf0623: 0xf0623,
+ 0xf0624: 0xf0624,
+ 0xf0625: 0xf0625,
+ 0xf0626: 0xf0626,
+ 0xf0627: 0xf0627,
+ 0xf0628: 0xf0628,
+ 0xf0629: 0xf0629,
+ 0xf062a: 0xf062a,
+ 0xf062b: 0xf062b,
+ 0xf062c: 0xf062c,
+ 0xf062d: 0xf062d,
+ 0xf062e: 0xf062e,
+ 0xf062f: 0xf062f,
+ 0xf0630: 0xf0630,
+ 0xf0631: 0xf0631,
+ 0xf0632: 0xf0632,
+ 0xf0633: 0xf0633,
+ 0xf0634: 0xf0634,
+ 0xf0635: 0xf0635,
+ 0xf0636: 0xf0636,
+ 0xf0637: 0xf0637,
+ 0xf0638: 0xf0638,
+ 0xf0639: 0xf0639,
+ 0xf063a: 0xf063a,
+ 0xf063b: 0xf063b,
+ 0xf063c: 0xf063c,
+ 0xf063d: 0xf063d,
+ 0xf063e: 0xf063e,
+ 0xf063f: 0xf063f,
+ 0xf0640: 0xf0640,
+ 0xf0641: 0xf0641,
+ 0xf0642: 0xf0642,
+ 0xf0643: 0xf0643,
+ 0xf0644: 0xf0644,
+ 0xf0645: 0xf0645,
+ 0xf0646: 0xf0646,
+ 0xf0647: 0xf0647,
+ 0xf0648: 0xf0648,
+ 0xf0649: 0xf0649,
+ 0xf064a: 0xf064a,
+ 0xf064b: 0xf064b,
+ 0xf064c: 0xf064c,
+ 0xf064d: 0xf064d,
+ 0xf064e: 0xf064e,
+ 0xf064f: 0xf064f,
+ 0xf0650: 0xf0650,
+ 0xf0651: 0xf0651,
+ 0xf0652: 0xf0652,
+ 0xf0653: 0xf0653,
+ 0xf0654: 0xf0654,
+ 0xf0655: 0xf0655,
+ 0xf0656: 0xf0656,
+ 0xf0657: 0xf0657,
+ 0xf0658: 0xf0658,
+ 0xf0659: 0xf0659,
+ 0xf065a: 0xf065a,
+ 0xf065b: 0xf065b,
+ 0xf065c: 0xf065c,
+ 0xf065d: 0xf065d,
+ 0xf065e: 0xf065e,
+ 0xf065f: 0xf065f,
+ 0xf0660: 0xf0660,
+ 0xf0661: 0xf0661,
+ 0xf0662: 0xf0662,
+ 0xf0663: 0xf0663,
+ 0xf0664: 0xf0664,
+ 0xf0665: 0xf0665,
+ 0xf0666: 0xf0666,
+ 0xf0667: 0xf0667,
+ 0xf0668: 0xf0668,
+ 0xf0669: 0xf0669,
+ 0xf066a: 0xf066a,
+ 0xf066b: 0xf066b,
+ 0xf066c: 0xf066c,
+ 0xf066d: 0xf066d,
+ 0xf066e: 0xf066e,
+ 0xf066f: 0xf066f,
+ 0xf0670: 0xf0670,
+ 0xf0671: 0xf0671,
+ 0xf0672: 0xf0672,
+ 0xf0673: 0xf0673,
+ 0xf0674: 0xf0674,
+ 0xf0675: 0xf0675,
+ 0xf0676: 0xf0676,
+ 0xf0677: 0xf0677,
+ 0xf0678: 0xf0678,
+ 0xf0679: 0xf0679,
+ 0xf067a: 0xf067a,
+ 0xf067b: 0xf067b,
+ 0xf067c: 0xf067c,
+ 0xf067d: 0xf067d,
+ 0xf067e: 0xf067e,
+ 0xf067f: 0xf067f,
+ 0xf0680: 0xf0680,
+ 0xf0681: 0xf0681,
+ 0xf0682: 0xf0682,
+ 0xf0683: 0xf0683,
+ 0xf0684: 0xf0684,
+ 0xf0685: 0xf0685,
+ 0xf0686: 0xf0686,
+ 0xf0687: 0xf0687,
+ 0xf0688: 0xf0688,
+ 0xf0689: 0xf0689,
+ 0xf068a: 0xf068a,
+ 0xf068b: 0xf068b,
+ 0xf068c: 0xf068c,
+ 0xf068d: 0xf068d,
+ 0xf068e: 0xf068e,
+ 0xf068f: 0xf068f,
+ 0xf0690: 0xf0690,
+ 0xf0691: 0xf0691,
+ 0xf0692: 0xf0692,
+ 0xf0693: 0xf0693,
+ 0xf0694: 0xf0694,
+ 0xf0695: 0xf0695,
+ 0xf0696: 0xf0696,
+ 0xf0697: 0xf0697,
+ 0xf0698: 0xf0698,
+ 0xf0699: 0xf0699,
+ 0xf069a: 0xf069a,
+ 0xf069b: 0xf069b,
+ 0xf069c: 0xf069c,
+ 0xf069d: 0xf069d,
+ 0xf069e: 0xf069e,
+ 0xf069f: 0xf069f,
+ 0xf06a0: 0xf06a0,
+ 0xf06a1: 0xf06a1,
+ 0xf06a2: 0xf06a2,
+ 0xf06a3: 0xf06a3,
+ 0xf06a4: 0xf06a4,
+ 0xf06a5: 0xf06a5,
+ 0xf06a6: 0xf06a6,
+ 0xf06a7: 0xf06a7,
+ 0xf06a8: 0xf06a8,
+ 0xf06a9: 0xf06a9,
+ 0xf06aa: 0xf06aa,
+ 0xf06ab: 0xf06ab,
+ 0xf06ac: 0xf06ac,
+ 0xf06ad: 0xf06ad,
+ 0xf06ae: 0xf06ae,
+ 0xf06af: 0xf06af,
+ 0xf06b0: 0xf06b0,
+ 0xf06b1: 0xf06b1,
+ 0xf06b2: 0xf06b2,
+ 0xf06b3: 0xf06b3,
+ 0xf06b4: 0xf06b4,
+ 0xf06b5: 0xf06b5,
+ 0xf06b6: 0xf06b6,
+ 0xf06b7: 0xf06b7,
+ 0xf06b8: 0xf06b8,
+ 0xf06b9: 0xf06b9,
+ 0xf06ba: 0xf06ba,
+ 0xf06bb: 0xf06bb,
+ 0xf06bc: 0xf06bc,
+ 0xf06bd: 0xf06bd,
+ 0xf06be: 0xf06be,
+ 0xf06bf: 0xf06bf,
+ 0xf06c0: 0xf06c0,
+ 0xf06c1: 0xf06c1,
+ 0xf06c2: 0xf06c2,
+ 0xf06c3: 0xf06c3,
+ 0xf06c4: 0xf06c4,
+ 0xf06c5: 0xf06c5,
+ 0xf06c6: 0xf06c6,
+ 0xf06c7: 0xf06c7,
+ 0xf06c8: 0xf06c8,
+ 0xf06c9: 0xf06c9,
+ 0xf06ca: 0xf06ca,
+ 0xf06cb: 0xf06cb,
+ 0xf06cc: 0xf06cc,
+ 0xf06cd: 0xf06cd,
+ 0xf06ce: 0xf06ce,
+ 0xf06cf: 0xf06cf,
+ 0xf06d0: 0xf06d0,
+ 0xf06d1: 0xf06d1,
+ 0xf06d2: 0xf06d2,
+ 0xf06d3: 0xf06d3,
+ 0xf06d4: 0xf06d4,
+ 0xf06d5: 0xf06d5,
+ 0xf06d6: 0xf06d6,
+ 0xf06d7: 0xf06d7,
+ 0xf06d8: 0xf06d8,
+ 0xf06d9: 0xf06d9,
+ 0xf06da: 0xf06da,
+ 0xf06db: 0xf06db,
+ 0xf06dc: 0xf06dc,
+ 0xf06dd: 0xf06dd,
+ 0xf06de: 0xf06de,
+ 0xf06df: 0xf06df,
+ 0xf06e0: 0xf06e0,
+ 0xf06e1: 0xf06e1,
+ 0xf06e2: 0xf06e2,
+ 0xf06e3: 0xf06e3,
+ 0xf06e4: 0xf06e4,
+ 0xf06e5: 0xf06e5,
+ 0xf06e6: 0xf06e6,
+ 0xf06e7: 0xf06e7,
+ 0xf06e8: 0xf06e8,
+ 0xf06e9: 0xf06e9,
+ 0xf06ea: 0xf06ea,
+ 0xf06eb: 0xf06eb,
+ 0xf06ec: 0xf06ec,
+ 0xf06ed: 0xf06ed,
+ 0xf06ee: 0xf06ee,
+ 0xf06ef: 0xf06ef,
+ 0xf06f0: 0xf06f0,
+ 0xf06f1: 0xf06f1,
+ 0xf06f2: 0xf06f2,
+ 0xf06f3: 0xf06f3,
+ 0xf06f4: 0xf06f4,
+ 0xf06f5: 0xf06f5,
+ 0xf06f6: 0xf06f6,
+ 0xf06f7: 0xf06f7,
+ 0xf06f8: 0xf06f8,
+ 0xf06f9: 0xf06f9,
+ 0xf06fa: 0xf06fa,
+ 0xf06fb: 0xf06fb,
+ 0xf06fc: 0xf06fc,
+ 0xf06fd: 0xf06fd,
+ 0xf06fe: 0xf06fe,
+ 0xf06ff: 0xf06ff,
+ 0xf0700: 0xf0700,
+ 0xf0701: 0xf0701,
+ 0xf0702: 0xf0702,
+ 0xf0703: 0xf0703,
+ 0xf0704: 0xf0704,
+ 0xf0705: 0xf0705,
+ 0xf0706: 0xf0706,
+ 0xf0707: 0xf0707,
+ 0xf0708: 0xf0708,
+ 0xf0709: 0xf0709,
+ 0xf070a: 0xf070a,
+ 0xf070b: 0xf070b,
+ 0xf070c: 0xf070c,
+ 0xf070d: 0xf070d,
+ 0xf070e: 0xf070e,
+ 0xf070f: 0xf070f,
+ 0xf0710: 0xf0710,
+ 0xf0711: 0xf0711,
+ 0xf0712: 0xf0712,
+ 0xf0713: 0xf0713,
+ 0xf0714: 0xf0714,
+ 0xf0715: 0xf0715,
+ 0xf0716: 0xf0716,
+ 0xf0717: 0xf0717,
+ 0xf0718: 0xf0718,
+ 0xf0719: 0xf0719,
+ 0xf071a: 0xf071a,
+ 0xf071b: 0xf071b,
+ 0xf071c: 0xf071c,
+ 0xf071d: 0xf071d,
+ 0xf071e: 0xf071e,
+ 0xf071f: 0xf071f,
+ 0xf0720: 0xf0720,
+ 0xf0721: 0xf0721,
+ 0xf0722: 0xf0722,
+ 0xf0723: 0xf0723,
+ 0xf0724: 0xf0724,
+ 0xf0725: 0xf0725,
+ 0xf0726: 0xf0726,
+ 0xf0727: 0xf0727,
+ 0xf0728: 0xf0728,
+ 0xf0729: 0xf0729,
+ 0xf072a: 0xf072a,
+ 0xf072b: 0xf072b,
+ 0xf072c: 0xf072c,
+ 0xf072d: 0xf072d,
+ 0xf072e: 0xf072e,
+ 0xf072f: 0xf072f,
+ 0xf0730: 0xf0730,
+ 0xf0731: 0xf0731,
+ 0xf0732: 0xf0732,
+ 0xf0733: 0xf0733,
+ 0xf0734: 0xf0734,
+ 0xf0735: 0xf0735,
+ 0xf0736: 0xf0736,
+ 0xf0737: 0xf0737,
+ 0xf0738: 0xf0738,
+ 0xf0739: 0xf0739,
+ 0xf073a: 0xf073a,
+ 0xf073b: 0xf073b,
+ 0xf073c: 0xf073c,
+ 0xf073d: 0xf073d,
+ 0xf073e: 0xf073e,
+ 0xf073f: 0xf073f,
+ 0xf0740: 0xf0740,
+ 0xf0741: 0xf0741,
+ 0xf0742: 0xf0742,
+ 0xf0743: 0xf0743,
+ 0xf0744: 0xf0744,
+ 0xf0745: 0xf0745,
+ 0xf0746: 0xf0746,
+ 0xf0747: 0xf0747,
+ 0xf0748: 0xf0748,
+ 0xf0749: 0xf0749,
+ 0xf074a: 0xf074a,
+ 0xf074b: 0xf074b,
+ 0xf074c: 0xf074c,
+ 0xf074d: 0xf074d,
+ 0xf074e: 0xf074e,
+ 0xf074f: 0xf074f,
+ 0xf0750: 0xf0750,
+ 0xf0751: 0xf0751,
+ 0xf0752: 0xf0752,
+ 0xf0753: 0xf0753,
+ 0xf0754: 0xf0754,
+ 0xf0755: 0xf0755,
+ 0xf0756: 0xf0756,
+ 0xf0757: 0xf0757,
+ 0xf0758: 0xf0758,
+ 0xf0759: 0xf0759,
+ 0xf075a: 0xf075a,
+ 0xf075b: 0xf075b,
+ 0xf075c: 0xf075c,
+ 0xf075d: 0xf075d,
+ 0xf075e: 0xf075e,
+ 0xf075f: 0xf075f,
+ 0xf0760: 0xf0760,
+ 0xf0761: 0xf0761,
+ 0xf0762: 0xf0762,
+ 0xf0763: 0xf0763,
+ 0xf0764: 0xf0764,
+ 0xf0765: 0xf0765,
+ 0xf0766: 0xf0766,
+ 0xf0767: 0xf0767,
+ 0xf0768: 0xf0768,
+ 0xf0769: 0xf0769,
+ 0xf076a: 0xf076a,
+ 0xf076b: 0xf076b,
+ 0xf076c: 0xf076c,
+ 0xf076d: 0xf076d,
+ 0xf076e: 0xf076e,
+ 0xf076f: 0xf076f,
+ 0xf0770: 0xf0770,
+ 0xf0771: 0xf0771,
+ 0xf0772: 0xf0772,
+ 0xf0773: 0xf0773,
+ 0xf0774: 0xf0774,
+ 0xf0775: 0xf0775,
+ 0xf0776: 0xf0776,
+ 0xf0777: 0xf0777,
+ 0xf0778: 0xf0778,
+ 0xf0779: 0xf0779,
+ 0xf077a: 0xf077a,
+ 0xf077b: 0xf077b,
+ 0xf077c: 0xf077c,
+ 0xf077d: 0xf077d,
+ 0xf077e: 0xf077e,
+ 0xf077f: 0xf077f,
+ 0xf0780: 0xf0780,
+ 0xf0781: 0xf0781,
+ 0xf0782: 0xf0782,
+ 0xf0783: 0xf0783,
+ 0xf0784: 0xf0784,
+ 0xf0785: 0xf0785,
+ 0xf0786: 0xf0786,
+ 0xf0787: 0xf0787,
+ 0xf0788: 0xf0788,
+ 0xf0789: 0xf0789,
+ 0xf078a: 0xf078a,
+ 0xf078b: 0xf078b,
+ 0xf078c: 0xf078c,
+ 0xf078d: 0xf078d,
+ 0xf078e: 0xf078e,
+ 0xf078f: 0xf078f,
+ 0xf0790: 0xf0790,
+ 0xf0791: 0xf0791,
+ 0xf0792: 0xf0792,
+ 0xf0793: 0xf0793,
+ 0xf0794: 0xf0794,
+ 0xf0795: 0xf0795,
+ 0xf0796: 0xf0796,
+ 0xf0797: 0xf0797,
+ 0xf0798: 0xf0798,
+ 0xf0799: 0xf0799,
+ 0xf079a: 0xf079a,
+ 0xf079b: 0xf079b,
+ 0xf079c: 0xf079c,
+ 0xf079d: 0xf079d,
+ 0xf079e: 0xf079e,
+ 0xf079f: 0xf079f,
+ 0xf07a0: 0xf07a0,
+ 0xf07a1: 0xf07a1,
+ 0xf07a2: 0xf07a2,
+ 0xf07a3: 0xf07a3,
+ 0xf07a4: 0xf07a4,
+ 0xf07a5: 0xf07a5,
+ 0xf07a6: 0xf07a6,
+ 0xf07a7: 0xf07a7,
+ 0xf07a8: 0xf07a8,
+ 0xf07a9: 0xf07a9,
+ 0xf07aa: 0xf07aa,
+ 0xf07ab: 0xf07ab,
+ 0xf07ac: 0xf07ac,
+ 0xf07ad: 0xf07ad,
+ 0xf07ae: 0xf07ae,
+ 0xf07af: 0xf07af,
+ 0xf07b0: 0xf07b0,
+ 0xf07b1: 0xf07b1,
+ 0xf07b2: 0xf07b2,
+ 0xf07b3: 0xf07b3,
+ 0xf07b4: 0xf07b4,
+ 0xf07b5: 0xf07b5,
+ 0xf07b6: 0xf07b6,
+ 0xf07b7: 0xf07b7,
+ 0xf07b8: 0xf07b8,
+ 0xf07b9: 0xf07b9,
+ 0xf07ba: 0xf07ba,
+ 0xf07bb: 0xf07bb,
+ 0xf07bc: 0xf07bc,
+ 0xf07bd: 0xf07bd,
+ 0xf07be: 0xf07be,
+ 0xf07bf: 0xf07bf,
+ 0xf07c0: 0xf07c0,
+ 0xf07c1: 0xf07c1,
+ 0xf07c2: 0xf07c2,
+ 0xf07c3: 0xf07c3,
+ 0xf07c4: 0xf07c4,
+ 0xf07c5: 0xf07c5,
+ 0xf07c6: 0xf07c6,
+ 0xf07c7: 0xf07c7,
+ 0xf07c8: 0xf07c8,
+ 0xf07c9: 0xf07c9,
+ 0xf07ca: 0xf07ca,
+ 0xf07cb: 0xf07cb,
+ 0xf07cc: 0xf07cc,
+ 0xf07cd: 0xf07cd,
+ 0xf07ce: 0xf07ce,
+ 0xf07cf: 0xf07cf,
+ 0xf07d0: 0xf07d0,
+ 0xf07d1: 0xf07d1,
+ 0xf07d2: 0xf07d2,
+ 0xf07d3: 0xf07d3,
+ 0xf07d4: 0xf07d4,
+ 0xf07d5: 0xf07d5,
+ 0xf07d6: 0xf07d6,
+ 0xf07d7: 0xf07d7,
+ 0xf07d8: 0xf07d8,
+ 0xf07d9: 0xf07d9,
+ 0xf07da: 0xf07da,
+ 0xf07db: 0xf07db,
+ 0xf07dc: 0xf07dc,
+ 0xf07dd: 0xf07dd,
+ 0xf07de: 0xf07de,
+ 0xf07df: 0xf07df,
+ 0xf07e0: 0xf07e0,
+ 0xf07e1: 0xf07e1,
+ 0xf07e2: 0xf07e2,
+ 0xf07e3: 0xf07e3,
+ 0xf07e4: 0xf07e4,
+ 0xf07e5: 0xf07e5,
+ 0xf07e6: 0xf07e6,
+ 0xf07e7: 0xf07e7,
+ 0xf07e8: 0xf07e8,
+ 0xf07e9: 0xf07e9,
+ 0xf07ea: 0xf07ea,
+ 0xf07eb: 0xf07eb,
+ 0xf07ec: 0xf07ec,
+ 0xf07ed: 0xf07ed,
+ 0xf07ee: 0xf07ee,
+ 0xf07ef: 0xf07ef,
+ 0xf07f0: 0xf07f0,
+ 0xf07f1: 0xf07f1,
+ 0xf07f2: 0xf07f2,
+ 0xf07f3: 0xf07f3,
+ 0xf07f4: 0xf07f4,
+ 0xf07f5: 0xf07f5,
+ 0xf07f6: 0xf07f6,
+ 0xf07f7: 0xf07f7,
+ 0xf07f8: 0xf07f8,
+ 0xf07f9: 0xf07f9,
+ 0xf07fa: 0xf07fa,
+ 0xf07fb: 0xf07fb,
+ 0xf07fc: 0xf07fc,
+ 0xf07fd: 0xf07fd,
+ 0xf07fe: 0xf07fe,
+ 0xf07ff: 0xf07ff,
+ 0xf0800: 0xf0800,
+ 0xf0801: 0xf0801,
+ 0xf0802: 0xf0802,
+ 0xf0803: 0xf0803,
+ 0xf0804: 0xf0804,
+ 0xf0805: 0xf0805,
+ 0xf0806: 0xf0806,
+ 0xf0807: 0xf0807,
+ 0xf0808: 0xf0808,
+ 0xf0809: 0xf0809,
+ 0xf080a: 0xf080a,
+ 0xf080b: 0xf080b,
+ 0xf080c: 0xf080c,
+ 0xf080d: 0xf080d,
+ 0xf080e: 0xf080e,
+ 0xf080f: 0xf080f,
+ 0xf0810: 0xf0810,
+ 0xf0811: 0xf0811,
+ 0xf0812: 0xf0812,
+ 0xf0813: 0xf0813,
+ 0xf0814: 0xf0814,
+ 0xf0815: 0xf0815,
+ 0xf0816: 0xf0816,
+ 0xf0817: 0xf0817,
+ 0xf0818: 0xf0818,
+ 0xf0819: 0xf0819,
+ 0xf081a: 0xf081a,
+ 0xf081b: 0xf081b,
+ 0xf081c: 0xf081c,
+ 0xf081d: 0xf081d,
+ 0xf081e: 0xf081e,
+ 0xf081f: 0xf081f,
+ 0xf0820: 0xf0820,
+ 0xf0821: 0xf0821,
+ 0xf0822: 0xf0822,
+ 0xf0823: 0xf0823,
+ 0xf0824: 0xf0824,
+ 0xf0825: 0xf0825,
+ 0xf0826: 0xf0826,
+ 0xf0827: 0xf0827,
+ 0xf0828: 0xf0828,
+ 0xf0829: 0xf0829,
+ 0xf082a: 0xf082a,
+ 0xf082b: 0xf082b,
+ 0xf082c: 0xf082c,
+ 0xf082d: 0xf082d,
+ 0xf082e: 0xf082e,
+ 0xf082f: 0xf082f,
+ 0xf0830: 0xf0830,
+ 0xf0831: 0xf0831,
+ 0xf0832: 0xf0832,
+ 0xf0833: 0xf0833,
+ 0xf0834: 0xf0834,
+ 0xf0835: 0xf0835,
+ 0xf0836: 0xf0836,
+ 0xf0837: 0xf0837,
+ 0xf0838: 0xf0838,
+ 0xf0839: 0xf0839,
+ 0xf083a: 0xf083a,
+ 0xf083b: 0xf083b,
+ 0xf083c: 0xf083c,
+ 0xf083d: 0xf083d,
+ 0xf083e: 0xf083e,
+ 0xf083f: 0xf083f,
+ 0xf0840: 0xf0840,
+ 0xf0841: 0xf0841,
+ 0xf0842: 0xf0842,
+ 0xf0843: 0xf0843,
+ 0xf0844: 0xf0844,
+ 0xf0845: 0xf0845,
+ 0xf0846: 0xf0846,
+ 0xf0847: 0xf0847,
+ 0xf0848: 0xf0848,
+ 0xf0849: 0xf0849,
+ 0xf084a: 0xf084a,
+ 0xf084b: 0xf084b,
+ 0xf084c: 0xf084c,
+ 0xf084d: 0xf084d,
+ 0xf084e: 0xf084e,
+ 0xf084f: 0xf084f,
+ 0xf0850: 0xf0850,
+ 0xf0851: 0xf0851,
+ 0xf0852: 0xf0852,
+ 0xf0853: 0xf0853,
+ 0xf0854: 0xf0854,
+ 0xf0855: 0xf0855,
+ 0xf0856: 0xf0856,
+ 0xf0857: 0xf0857,
+ 0xf0858: 0xf0858,
+ 0xf0859: 0xf0859,
+ 0xf085a: 0xf085a,
+ 0xf085b: 0xf085b,
+ 0xf085c: 0xf085c,
+ 0xf085d: 0xf085d,
+ 0xf085e: 0xf085e,
+ 0xf085f: 0xf085f,
+ 0xf0860: 0xf0860,
+ 0xf0861: 0xf0861,
+ 0xf0862: 0xf0862,
+ 0xf0863: 0xf0863,
+ 0xf0864: 0xf0864,
+ 0xf0865: 0xf0865,
+ 0xf0866: 0xf0866,
+ 0xf0867: 0xf0867,
+ 0xf0868: 0xf0868,
+ 0xf0869: 0xf0869,
+ 0xf086a: 0xf086a,
+ 0xf086b: 0xf086b,
+ 0xf086c: 0xf086c,
+ 0xf086d: 0xf086d,
+ 0xf086e: 0xf086e,
+ 0xf086f: 0xf086f,
+ 0xf0870: 0xf0870,
+ 0xf0871: 0xf0871,
+ 0xf0872: 0xf0872,
+ 0xf0873: 0xf0873,
+ 0xf0874: 0xf0874,
+ 0xf0875: 0xf0875,
+ 0xf0876: 0xf0876,
+ 0xf0877: 0xf0877,
+ 0xf0878: 0xf0878,
+ 0xf0879: 0xf0879,
+ 0xf087a: 0xf087a,
+ 0xf087b: 0xf087b,
+ 0xf087c: 0xf087c,
+ 0xf087d: 0xf087d,
+ 0xf087e: 0xf087e,
+ 0xf087f: 0xf087f,
+ 0xf0880: 0xf0880,
+ 0xf0881: 0xf0881,
+ 0xf0882: 0xf0882,
+ 0xf0883: 0xf0883,
+ 0xf0884: 0xf0884,
+ 0xf0885: 0xf0885,
+ 0xf0886: 0xf0886,
+ 0xf0887: 0xf0887,
+ 0xf0888: 0xf0888,
+ 0xf0889: 0xf0889,
+ 0xf088a: 0xf088a,
+ 0xf088b: 0xf088b,
+ 0xf088c: 0xf088c,
+ 0xf088d: 0xf088d,
+ 0xf088e: 0xf088e,
+ 0xf088f: 0xf088f,
+ 0xf0890: 0xf0890,
+ 0xf0891: 0xf0891,
+ 0xf0892: 0xf0892,
+ 0xf0893: 0xf0893,
+ 0xf0894: 0xf0894,
+ 0xf0895: 0xf0895,
+ 0xf0896: 0xf0896,
+ 0xf0897: 0xf0897,
+ 0xf0898: 0xf0898,
+ 0xf0899: 0xf0899,
+ 0xf089a: 0xf089a,
+ 0xf089b: 0xf089b,
+ 0xf089c: 0xf089c,
+ 0xf089d: 0xf089d,
+ 0xf089e: 0xf089e,
+ 0xf089f: 0xf089f,
+ 0xf08a0: 0xf08a0,
+ 0xf08a1: 0xf08a1,
+ 0xf08a2: 0xf08a2,
+ 0xf08a3: 0xf08a3,
+ 0xf08a4: 0xf08a4,
+ 0xf08a5: 0xf08a5,
+ 0xf08a6: 0xf08a6,
+ 0xf08a7: 0xf08a7,
+ 0xf08a8: 0xf08a8,
+ 0xf08a9: 0xf08a9,
+ 0xf08aa: 0xf08aa,
+ 0xf08ab: 0xf08ab,
+ 0xf08ac: 0xf08ac,
+ 0xf08ad: 0xf08ad,
+ 0xf08ae: 0xf08ae,
+ 0xf08af: 0xf08af,
+ 0xf08b0: 0xf08b0,
+ 0xf08b1: 0xf08b1,
+ 0xf08b2: 0xf08b2,
+ 0xf08b3: 0xf08b3,
+ 0xf08b4: 0xf08b4,
+ 0xf08b5: 0xf08b5,
+ 0xf08b6: 0xf08b6,
+ 0xf08b7: 0xf08b7,
+ 0xf08b8: 0xf08b8,
+ 0xf08b9: 0xf08b9,
+ 0xf08ba: 0xf08ba,
+ 0xf08bb: 0xf08bb,
+ 0xf08bc: 0xf08bc,
+ 0xf08bd: 0xf08bd,
+ 0xf08be: 0xf08be,
+ 0xf08bf: 0xf08bf,
+ 0xf08c0: 0xf08c0,
+ 0xf08c1: 0xf08c1,
+ 0xf08c2: 0xf08c2,
+ 0xf08c3: 0xf08c3,
+ 0xf08c4: 0xf08c4,
+ 0xf08c5: 0xf08c5,
+ 0xf08c6: 0xf08c6,
+ 0xf08c7: 0xf08c7,
+ 0xf08c8: 0xf08c8,
+ 0xf08c9: 0xf08c9,
+ 0xf08ca: 0xf08ca,
+ 0xf08cb: 0xf08cb,
+ 0xf08cc: 0xf08cc,
+ 0xf08cd: 0xf08cd,
+ 0xf08ce: 0xf08ce,
+ 0xf08cf: 0xf08cf,
+ 0xf08d0: 0xf08d0,
+ 0xf08d1: 0xf08d1,
+ 0xf08d2: 0xf08d2,
+ 0xf08d3: 0xf08d3,
+ 0xf08d4: 0xf08d4,
+ 0xf08d5: 0xf08d5,
+ 0xf08d6: 0xf08d6,
+ 0xf08d7: 0xf08d7,
+ 0xf08d8: 0xf08d8,
+ 0xf08d9: 0xf08d9,
+ 0xf08da: 0xf08da,
+ 0xf08db: 0xf08db,
+ 0xf08dc: 0xf08dc,
+ 0xf08dd: 0xf08dd,
+ 0xf08de: 0xf08de,
+ 0xf08df: 0xf08df,
+ 0xf08e0: 0xf08e0,
+ 0xf08e1: 0xf08e1,
+ 0xf08e2: 0xf08e2,
+ 0xf08e3: 0xf08e3,
+ 0xf08e4: 0xf08e4,
+ 0xf08e5: 0xf08e5,
+ 0xf08e6: 0xf08e6,
+ 0xf08e7: 0xf08e7,
+ 0xf08e8: 0xf08e8,
+ 0xf08e9: 0xf08e9,
+ 0xf08ea: 0xf08ea,
+ 0xf08eb: 0xf08eb,
+ 0xf08ec: 0xf08ec,
+ 0xf08ed: 0xf08ed,
+ 0xf08ee: 0xf08ee,
+ 0xf08ef: 0xf08ef,
+ 0xf08f0: 0xf08f0,
+ 0xf08f1: 0xf08f1,
+ 0xf08f2: 0xf08f2,
+ 0xf08f3: 0xf08f3,
+ 0xf08f4: 0xf08f4,
+ 0xf08f5: 0xf08f5,
+ 0xf08f6: 0xf08f6,
+ 0xf08f7: 0xf08f7,
+ 0xf08f8: 0xf08f8,
+ 0xf08f9: 0xf08f9,
+ 0xf08fa: 0xf08fa,
+ 0xf08fb: 0xf08fb,
+ 0xf08fc: 0xf08fc,
+ 0xf08fd: 0xf08fd,
+ 0xf08fe: 0xf08fe,
+ 0xf08ff: 0xf08ff,
+ 0xf0900: 0xf0900,
+ 0xf0901: 0xf0901,
+ 0xf0902: 0xf0902,
+ 0xf0903: 0xf0903,
+ 0xf0904: 0xf0904,
+ 0xf0905: 0xf0905,
+ 0xf0906: 0xf0906,
+ 0xf0907: 0xf0907,
+ 0xf0908: 0xf0908,
+ 0xf0909: 0xf0909,
+ 0xf090a: 0xf090a,
+ 0xf090b: 0xf090b,
+ 0xf090c: 0xf090c,
+ 0xf090d: 0xf090d,
+ 0xf090e: 0xf090e,
+ 0xf090f: 0xf090f,
+ 0xf0910: 0xf0910,
+ 0xf0911: 0xf0911,
+ 0xf0912: 0xf0912,
+ 0xf0913: 0xf0913,
+ 0xf0914: 0xf0914,
+ 0xf0915: 0xf0915,
+ 0xf0916: 0xf0916,
+ 0xf0917: 0xf0917,
+ 0xf0918: 0xf0918,
+ 0xf0919: 0xf0919,
+ 0xf091a: 0xf091a,
+ 0xf091b: 0xf091b,
+ 0xf091c: 0xf091c,
+ 0xf091d: 0xf091d,
+ 0xf091e: 0xf091e,
+ 0xf091f: 0xf091f,
+ 0xf0920: 0xf0920,
+ 0xf0921: 0xf0921,
+ 0xf0922: 0xf0922,
+ 0xf0923: 0xf0923,
+ 0xf0924: 0xf0924,
+ 0xf0925: 0xf0925,
+ 0xf0926: 0xf0926,
+ 0xf0927: 0xf0927,
+ 0xf0928: 0xf0928,
+ 0xf0929: 0xf0929,
+ 0xf092a: 0xf092a,
+ 0xf092b: 0xf092b,
+ 0xf092c: 0xf092c,
+ 0xf092d: 0xf092d,
+ 0xf092e: 0xf092e,
+ 0xf092f: 0xf092f,
+ 0xf0930: 0xf0930,
+ 0xf0931: 0xf0931,
+ 0xf0932: 0xf0932,
+ 0xf0933: 0xf0933,
+ 0xf0934: 0xf0934,
+ 0xf0935: 0xf0935,
+ 0xf0936: 0xf0936,
+ 0xf0937: 0xf0937,
+ 0xf0938: 0xf0938,
+ 0xf0939: 0xf0939,
+ 0xf093a: 0xf093a,
+ 0xf093b: 0xf093b,
+ 0xf093c: 0xf093c,
+ 0xf093d: 0xf093d,
+ 0xf093e: 0xf093e,
+ 0xf093f: 0xf093f,
+ 0xf0940: 0xf0940,
+ 0xf0941: 0xf0941,
+ 0xf0942: 0xf0942,
+ 0xf0943: 0xf0943,
+ 0xf0944: 0xf0944,
+ 0xf0945: 0xf0945,
+ 0xf0946: 0xf0946,
+ 0xf0947: 0xf0947,
+ 0xf0948: 0xf0948,
+ 0xf0949: 0xf0949,
+ 0xf094a: 0xf094a,
+ 0xf094b: 0xf094b,
+ 0xf094c: 0xf094c,
+ 0xf094d: 0xf094d,
+ 0xf094e: 0xf094e,
+ 0xf094f: 0xf094f,
+ 0xf0950: 0xf0950,
+ 0xf0951: 0xf0951,
+ 0xf0952: 0xf0952,
+ 0xf0953: 0xf0953,
+ 0xf0954: 0xf0954,
+ 0xf0955: 0xf0955,
+ 0xf0956: 0xf0956,
+ 0xf0957: 0xf0957,
+ 0xf0958: 0xf0958,
+ 0xf0959: 0xf0959,
+ 0xf095a: 0xf095a,
+ 0xf095b: 0xf095b,
+ 0xf095c: 0xf095c,
+ 0xf095d: 0xf095d,
+ 0xf095e: 0xf095e,
+ 0xf095f: 0xf095f,
+ 0xf0960: 0xf0960,
+ 0xf0961: 0xf0961,
+ 0xf0962: 0xf0962,
+ 0xf0963: 0xf0963,
+ 0xf0964: 0xf0964,
+ 0xf0965: 0xf0965,
+ 0xf0966: 0xf0966,
+ 0xf0967: 0xf0967,
+ 0xf0968: 0xf0968,
+ 0xf0969: 0xf0969,
+ 0xf096a: 0xf096a,
+ 0xf096b: 0xf096b,
+ 0xf096c: 0xf096c,
+ 0xf096d: 0xf096d,
+ 0xf096e: 0xf096e,
+ 0xf096f: 0xf096f,
+ 0xf0970: 0xf0970,
+ 0xf0971: 0xf0971,
+ 0xf0972: 0xf0972,
+ 0xf0973: 0xf0973,
+ 0xf0974: 0xf0974,
+ 0xf0975: 0xf0975,
+ 0xf0976: 0xf0976,
+ 0xf0977: 0xf0977,
+ 0xf0978: 0xf0978,
+ 0xf0979: 0xf0979,
+ 0xf097a: 0xf097a,
+ 0xf097b: 0xf097b,
+ 0xf097c: 0xf097c,
+ 0xf097d: 0xf097d,
+ 0xf097e: 0xf097e,
+ 0xf097f: 0xf097f,
+ 0xf0980: 0xf0980,
+ 0xf0981: 0xf0981,
+ 0xf0982: 0xf0982,
+ 0xf0983: 0xf0983,
+ 0xf0984: 0xf0984,
+ 0xf0985: 0xf0985,
+ 0xf0986: 0xf0986,
+ 0xf0987: 0xf0987,
+ 0xf0988: 0xf0988,
+ 0xf0989: 0xf0989,
+ 0xf098a: 0xf098a,
+ 0xf098b: 0xf098b,
+ 0xf098c: 0xf098c,
+ 0xf098d: 0xf098d,
+ 0xf098e: 0xf098e,
+ 0xf098f: 0xf098f,
+ 0xf0990: 0xf0990,
+ 0xf0991: 0xf0991,
+ 0xf0992: 0xf0992,
+ 0xf0993: 0xf0993,
+ 0xf0994: 0xf0994,
+ 0xf0995: 0xf0995,
+ 0xf0996: 0xf0996,
+ 0xf0997: 0xf0997,
+ 0xf0998: 0xf0998,
+ 0xf0999: 0xf0999,
+ 0xf099a: 0xf099a,
+ 0xf099b: 0xf099b,
+ 0xf099c: 0xf099c,
+ 0xf099d: 0xf099d,
+ 0xf099e: 0xf099e,
+ 0xf099f: 0xf099f,
+ 0xf09a0: 0xf09a0,
+ 0xf09a1: 0xf09a1,
+ 0xf09a2: 0xf09a2,
+ 0xf09a3: 0xf09a3,
+ 0xf09a4: 0xf09a4,
+ 0xf09a5: 0xf09a5,
+ 0xf09a6: 0xf09a6,
+ 0xf09a7: 0xf09a7,
+ 0xf09a8: 0xf09a8,
+ 0xf09a9: 0xf09a9,
+ 0xf09aa: 0xf09aa,
+ 0xf09ab: 0xf09ab,
+ 0xf09ac: 0xf09ac,
+ 0xf09ad: 0xf09ad,
+ 0xf09ae: 0xf09ae,
+ 0xf09af: 0xf09af,
+ 0xf09b0: 0xf09b0,
+ 0xf09b1: 0xf09b1,
+ 0xf09b2: 0xf09b2,
+ 0xf09b3: 0xf09b3,
+ 0xf09b4: 0xf09b4,
+ 0xf09b5: 0xf09b5,
+ 0xf09b6: 0xf09b6,
+ 0xf09b7: 0xf09b7,
+ 0xf09b8: 0xf09b8,
+ 0xf09b9: 0xf09b9,
+ 0xf09ba: 0xf09ba,
+ 0xf09bb: 0xf09bb,
+ 0xf09bc: 0xf09bc,
+ 0xf09bd: 0xf09bd,
+ 0xf09be: 0xf09be,
+ 0xf09bf: 0xf09bf,
+ 0xf09c0: 0xf09c0,
+ 0xf09c1: 0xf09c1,
+ 0xf09c2: 0xf09c2,
+ 0xf09c3: 0xf09c3,
+ 0xf09c4: 0xf09c4,
+ 0xf09c5: 0xf09c5,
+ 0xf09c6: 0xf09c6,
+ 0xf09c7: 0xf09c7,
+ 0xf09c8: 0xf09c8,
+ 0xf09c9: 0xf09c9,
+ 0xf09ca: 0xf09ca,
+ 0xf09cb: 0xf09cb,
+ 0xf09cc: 0xf09cc,
+ 0xf09cd: 0xf09cd,
+ 0xf09ce: 0xf09ce,
+ 0xf09cf: 0xf09cf,
+ 0xf09d0: 0xf09d0,
+ 0xf09d1: 0xf09d1,
+ 0xf09d2: 0xf09d2,
+ 0xf09d3: 0xf09d3,
+ 0xf09d4: 0xf09d4,
+ 0xf09d5: 0xf09d5,
+ 0xf09d6: 0xf09d6,
+ 0xf09d7: 0xf09d7,
+ 0xf09d8: 0xf09d8,
+ 0xf09d9: 0xf09d9,
+ 0xf09da: 0xf09da,
+ 0xf09db: 0xf09db,
+ 0xf09dc: 0xf09dc,
+ 0xf09dd: 0xf09dd,
+ 0xf09de: 0xf09de,
+ 0xf09df: 0xf09df,
+ 0xf09e0: 0xf09e0,
+ 0xf09e1: 0xf09e1,
+ 0xf09e2: 0xf09e2,
+ 0xf09e3: 0xf09e3,
+ 0xf09e4: 0xf09e4,
+ 0xf09e5: 0xf09e5,
+ 0xf09e6: 0xf09e6,
+ 0xf09e7: 0xf09e7,
+ 0xf09e8: 0xf09e8,
+ 0xf09e9: 0xf09e9,
+ 0xf09ea: 0xf09ea,
+ 0xf09eb: 0xf09eb,
+ 0xf09ec: 0xf09ec,
+ 0xf09ed: 0xf09ed,
+ 0xf09ee: 0xf09ee,
+ 0xf09ef: 0xf09ef,
+ 0xf09f0: 0xf09f0,
+ 0xf09f1: 0xf09f1,
+ 0xf09f2: 0xf09f2,
+ 0xf09f3: 0xf09f3,
+ 0xf09f4: 0xf09f4,
+ 0xf09f5: 0xf09f5,
+ 0xf09f6: 0xf09f6,
+ 0xf09f7: 0xf09f7,
+ 0xf09f8: 0xf09f8,
+ 0xf09f9: 0xf09f9,
+ 0xf09fa: 0xf09fa,
+ 0xf09fb: 0xf09fb,
+ 0xf09fc: 0xf09fc,
+ 0xf09fd: 0xf09fd,
+ 0xf09fe: 0xf09fe,
+ 0xf09ff: 0xf09ff,
+ 0xf0a00: 0xf0a00,
+ 0xf0a01: 0xf0a01,
+ 0xf0a02: 0xf0a02,
+ 0xf0a03: 0xf0a03,
+ 0xf0a04: 0xf0a04,
+ 0xf0a05: 0xf0a05,
+ 0xf0a06: 0xf0a06,
+ 0xf0a07: 0xf0a07,
+ 0xf0a08: 0xf0a08,
+ 0xf0a09: 0xf0a09,
+ 0xf0a0a: 0xf0a0a,
+ 0xf0a0b: 0xf0a0b,
+ 0xf0a0c: 0xf0a0c,
+ 0xf0a0d: 0xf0a0d,
+ 0xf0a0e: 0xf0a0e,
+ 0xf0a0f: 0xf0a0f,
+ 0xf0a10: 0xf0a10,
+ 0xf0a11: 0xf0a11,
+ 0xf0a12: 0xf0a12,
+ 0xf0a13: 0xf0a13,
+ 0xf0a14: 0xf0a14,
+ 0xf0a15: 0xf0a15,
+ 0xf0a16: 0xf0a16,
+ 0xf0a17: 0xf0a17,
+ 0xf0a18: 0xf0a18,
+ 0xf0a19: 0xf0a19,
+ 0xf0a1a: 0xf0a1a,
+ 0xf0a1b: 0xf0a1b,
+ 0xf0a1c: 0xf0a1c,
+ 0xf0a1d: 0xf0a1d,
+ 0xf0a1e: 0xf0a1e,
+ 0xf0a1f: 0xf0a1f,
+ 0xf0a20: 0xf0a20,
+ 0xf0a21: 0xf0a21,
+ 0xf0a22: 0xf0a22,
+ 0xf0a23: 0xf0a23,
+ 0xf0a24: 0xf0a24,
+ 0xf0a25: 0xf0a25,
+ 0xf0a26: 0xf0a26,
+ 0xf0a27: 0xf0a27,
+ 0xf0a28: 0xf0a28,
+ 0xf0a29: 0xf0a29,
+ 0xf0a2a: 0xf0a2a,
+ 0xf0a2b: 0xf0a2b,
+ 0xf0a2c: 0xf0a2c,
+ 0xf0a2d: 0xf0a2d,
+ 0xf0a2e: 0xf0a2e,
+ 0xf0a2f: 0xf0a2f,
+ 0xf0a30: 0xf0a30,
+ 0xf0a31: 0xf0a31,
+ 0xf0a32: 0xf0a32,
+ 0xf0a33: 0xf0a33,
+ 0xf0a34: 0xf0a34,
+ 0xf0a35: 0xf0a35,
+ 0xf0a36: 0xf0a36,
+ 0xf0a37: 0xf0a37,
+ 0xf0a38: 0xf0a38,
+ 0xf0a39: 0xf0a39,
+ 0xf0a3a: 0xf0a3a,
+ 0xf0a3b: 0xf0a3b,
+ 0xf0a3c: 0xf0a3c,
+ 0xf0a3d: 0xf0a3d,
+ 0xf0a3e: 0xf0a3e,
+ 0xf0a3f: 0xf0a3f,
+ 0xf0a40: 0xf0a40,
+ 0xf0a41: 0xf0a41,
+ 0xf0a42: 0xf0a42,
+ 0xf0a43: 0xf0a43,
+ 0xf0a44: 0xf0a44,
+ 0xf0a45: 0xf0a45,
+ 0xf0a46: 0xf0a46,
+ 0xf0a47: 0xf0a47,
+ 0xf0a48: 0xf0a48,
+ 0xf0a49: 0xf0a49,
+ 0xf0a4a: 0xf0a4a,
+ 0xf0a4b: 0xf0a4b,
+ 0xf0a4c: 0xf0a4c,
+ 0xf0a4d: 0xf0a4d,
+ 0xf0a4e: 0xf0a4e,
+ 0xf0a4f: 0xf0a4f,
+ 0xf0a50: 0xf0a50,
+ 0xf0a51: 0xf0a51,
+ 0xf0a52: 0xf0a52,
+ 0xf0a53: 0xf0a53,
+ 0xf0a54: 0xf0a54,
+ 0xf0a55: 0xf0a55,
+ 0xf0a56: 0xf0a56,
+ 0xf0a57: 0xf0a57,
+ 0xf0a58: 0xf0a58,
+ 0xf0a59: 0xf0a59,
+ 0xf0a5a: 0xf0a5a,
+ 0xf0a5b: 0xf0a5b,
+ 0xf0a5c: 0xf0a5c,
+ 0xf0a5d: 0xf0a5d,
+ 0xf0a5e: 0xf0a5e,
+ 0xf0a5f: 0xf0a5f,
+ 0xf0a60: 0xf0a60,
+ 0xf0a61: 0xf0a61,
+ 0xf0a62: 0xf0a62,
+ 0xf0a63: 0xf0a63,
+ 0xf0a64: 0xf0a64,
+ 0xf0a65: 0xf0a65,
+ 0xf0a66: 0xf0a66,
+ 0xf0a67: 0xf0a67,
+ 0xf0a68: 0xf0a68,
+ 0xf0a69: 0xf0a69,
+ 0xf0a6a: 0xf0a6a,
+ 0xf0a6b: 0xf0a6b,
+ 0xf0a6c: 0xf0a6c,
+ 0xf0a6d: 0xf0a6d,
+ 0xf0a6e: 0xf0a6e,
+ 0xf0a6f: 0xf0a6f,
+ 0xf0a70: 0xf0a70,
+ 0xf0a71: 0xf0a71,
+ 0xf0a72: 0xf0a72,
+ 0xf0a73: 0xf0a73,
+ 0xf0a74: 0xf0a74,
+ 0xf0a75: 0xf0a75,
+ 0xf0a76: 0xf0a76,
+ 0xf0a77: 0xf0a77,
+ 0xf0a78: 0xf0a78,
+ 0xf0a79: 0xf0a79,
+ 0xf0a7a: 0xf0a7a,
+ 0xf0a7b: 0xf0a7b,
+ 0xf0a7c: 0xf0a7c,
+ 0xf0a7d: 0xf0a7d,
+ 0xf0a7e: 0xf0a7e,
+ 0xf0a7f: 0xf0a7f,
+ 0xf0a80: 0xf0a80,
+ 0xf0a81: 0xf0a81,
+ 0xf0a82: 0xf0a82,
+ 0xf0a83: 0xf0a83,
+ 0xf0a84: 0xf0a84,
+ 0xf0a85: 0xf0a85,
+ 0xf0a86: 0xf0a86,
+ 0xf0a87: 0xf0a87,
+ 0xf0a88: 0xf0a88,
+ 0xf0a89: 0xf0a89,
+ 0xf0a8a: 0xf0a8a,
+ 0xf0a8b: 0xf0a8b,
+ 0xf0a8c: 0xf0a8c,
+ 0xf0a8d: 0xf0a8d,
+ 0xf0a8e: 0xf0a8e,
+ 0xf0a8f: 0xf0a8f,
+ 0xf0a90: 0xf0a90,
+ 0xf0a91: 0xf0a91,
+ 0xf0a92: 0xf0a92,
+ 0xf0a93: 0xf0a93,
+ 0xf0a94: 0xf0a94,
+ 0xf0a95: 0xf0a95,
+ 0xf0a96: 0xf0a96,
+ 0xf0a97: 0xf0a97,
+ 0xf0a98: 0xf0a98,
+ 0xf0a99: 0xf0a99,
+ 0xf0a9a: 0xf0a9a,
+ 0xf0a9b: 0xf0a9b,
+ 0xf0a9c: 0xf0a9c,
+ 0xf0a9d: 0xf0a9d,
+ 0xf0a9e: 0xf0a9e,
+ 0xf0a9f: 0xf0a9f,
+ 0xf0aa0: 0xf0aa0,
+ 0xf0aa1: 0xf0aa1,
+ 0xf0aa2: 0xf0aa2,
+ 0xf0aa3: 0xf0aa3,
+ 0xf0aa4: 0xf0aa4,
+ 0xf0aa5: 0xf0aa5,
+ 0xf0aa6: 0xf0aa6,
+ 0xf0aa7: 0xf0aa7,
+ 0xf0aa8: 0xf0aa8,
+ 0xf0aa9: 0xf0aa9,
+ 0xf0aaa: 0xf0aaa,
+ 0xf0aab: 0xf0aab,
+ 0xf0aac: 0xf0aac,
+ 0xf0aad: 0xf0aad,
+ 0xf0aae: 0xf0aae,
+ 0xf0aaf: 0xf0aaf,
+ 0xf0ab0: 0xf0ab0,
+ 0xf0ab1: 0xf0ab1,
+ 0xf0ab2: 0xf0ab2,
+ 0xf0ab3: 0xf0ab3,
+ 0xf0ab4: 0xf0ab4,
+ 0xf0ab5: 0xf0ab5,
+ 0xf0ab6: 0xf0ab6,
+ 0xf0ab7: 0xf0ab7,
+ 0xf0ab8: 0xf0ab8,
+ 0xf0ab9: 0xf0ab9,
+ 0xf0aba: 0xf0aba,
+ 0xf0abb: 0xf0abb,
+ 0xf0abc: 0xf0abc,
+ 0xf0abd: 0xf0abd,
+ 0xf0abe: 0xf0abe,
+ 0xf0abf: 0xf0abf,
+ 0xf0ac0: 0xf0ac0,
+ 0xf0ac1: 0xf0ac1,
+ 0xf0ac2: 0xf0ac2,
+ 0xf0ac3: 0xf0ac3,
+ 0xf0ac4: 0xf0ac4,
+ 0xf0ac5: 0xf0ac5,
+ 0xf0ac6: 0xf0ac6,
+ 0xf0ac7: 0xf0ac7,
+ 0xf0ac8: 0xf0ac8,
+ 0xf0ac9: 0xf0ac9,
+ 0xf0aca: 0xf0aca,
+ 0xf0acb: 0xf0acb,
+ 0xf0acc: 0xf0acc,
+ 0xf0acd: 0xf0acd,
+ 0xf0ace: 0xf0ace,
+ 0xf0acf: 0xf0acf,
+ 0xf0ad0: 0xf0ad0,
+ 0xf0ad1: 0xf0ad1,
+ 0xf0ad2: 0xf0ad2,
+ 0xf0ad3: 0xf0ad3,
+ 0xf0ad4: 0xf0ad4,
+ 0xf0ad5: 0xf0ad5,
+ 0xf0ad6: 0xf0ad6,
+ 0xf0ad7: 0xf0ad7,
+ 0xf0ad8: 0xf0ad8,
+ 0xf0ad9: 0xf0ad9,
+ 0xf0ada: 0xf0ada,
+ 0xf0adb: 0xf0adb,
+ 0xf0adc: 0xf0adc,
+ 0xf0add: 0xf0add,
+ 0xf0ade: 0xf0ade,
+ 0xf0adf: 0xf0adf,
+ 0xf0ae0: 0xf0ae0,
+ 0xf0ae1: 0xf0ae1,
+ 0xf0ae2: 0xf0ae2,
+ 0xf0ae3: 0xf0ae3,
+ 0xf0ae4: 0xf0ae4,
+ 0xf0ae5: 0xf0ae5,
+ 0xf0ae6: 0xf0ae6,
+ 0xf0ae7: 0xf0ae7,
+ 0xf0ae8: 0xf0ae8,
+ 0xf0ae9: 0xf0ae9,
+ 0xf0aea: 0xf0aea,
+ 0xf0aeb: 0xf0aeb,
+ 0xf0aec: 0xf0aec,
+ 0xf0aed: 0xf0aed,
+ 0xf0aee: 0xf0aee,
+ 0xf0aef: 0xf0aef,
+ 0xf0af0: 0xf0af0,
+ 0xf0af1: 0xf0af1,
+ 0xf0af2: 0xf0af2,
+ 0xf0af3: 0xf0af3,
+ 0xf0af4: 0xf0af4,
+ 0xf0af5: 0xf0af5,
+ 0xf0af6: 0xf0af6,
+ 0xf0af7: 0xf0af7,
+ 0xf0af8: 0xf0af8,
+ 0xf0af9: 0xf0af9,
+ 0xf0afa: 0xf0afa,
+ 0xf0afb: 0xf0afb,
+ 0xf0afc: 0xf0afc,
+ 0xf0afd: 0xf0afd,
+ 0xf0afe: 0xf0afe,
+ 0xf0aff: 0xf0aff,
+ 0xf0b00: 0xf0b00,
+ 0xf0b01: 0xf0b01,
+ 0xf0b02: 0xf0b02,
+ 0xf0b03: 0xf0b03,
+ 0xf0b04: 0xf0b04,
+ 0xf0b05: 0xf0b05,
+ 0xf0b06: 0xf0b06,
+ 0xf0b07: 0xf0b07,
+ 0xf0b08: 0xf0b08,
+ 0xf0b09: 0xf0b09,
+ 0xf0b0a: 0xf0b0a,
+ 0xf0b0b: 0xf0b0b,
+ 0xf0b0c: 0xf0b0c,
+ 0xf0b0d: 0xf0b0d,
+ 0xf0b0e: 0xf0b0e,
+ 0xf0b0f: 0xf0b0f,
+ 0xf0b10: 0xf0b10,
+ 0xf0b11: 0xf0b11,
+ 0xf0b12: 0xf0b12,
+ 0xf0b13: 0xf0b13,
+ 0xf0b14: 0xf0b14,
+ 0xf0b15: 0xf0b15,
+ 0xf0b16: 0xf0b16,
+ 0xf0b17: 0xf0b17,
+ 0xf0b18: 0xf0b18,
+ 0xf0b19: 0xf0b19,
+ 0xf0b1a: 0xf0b1a,
+ 0xf0b1b: 0xf0b1b,
+ 0xf0b1c: 0xf0b1c,
+ 0xf0b1d: 0xf0b1d,
+ 0xf0b1e: 0xf0b1e,
+ 0xf0b1f: 0xf0b1f,
+ 0xf0b20: 0xf0b20,
+ 0xf0b21: 0xf0b21,
+ 0xf0b22: 0xf0b22,
+ 0xf0b23: 0xf0b23,
+ 0xf0b24: 0xf0b24,
+ 0xf0b25: 0xf0b25,
+ 0xf0b26: 0xf0b26,
+ 0xf0b27: 0xf0b27,
+ 0xf0b28: 0xf0b28,
+ 0xf0b29: 0xf0b29,
+ 0xf0b2a: 0xf0b2a,
+ 0xf0b2b: 0xf0b2b,
+ 0xf0b2c: 0xf0b2c,
+ 0xf0b2d: 0xf0b2d,
+ 0xf0b2e: 0xf0b2e,
+ 0xf0b2f: 0xf0b2f,
+ 0xf0b30: 0xf0b30,
+ 0xf0b31: 0xf0b31,
+ 0xf0b32: 0xf0b32,
+ 0xf0b33: 0xf0b33,
+ 0xf0b34: 0xf0b34,
+ 0xf0b35: 0xf0b35,
+ 0xf0b36: 0xf0b36,
+ 0xf0b37: 0xf0b37,
+ 0xf0b38: 0xf0b38,
+ 0xf0b39: 0xf0b39,
+ 0xf0b3a: 0xf0b3a,
+ 0xf0b3b: 0xf0b3b,
+ 0xf0b3c: 0xf0b3c,
+ 0xf0b3d: 0xf0b3d,
+ 0xf0b3e: 0xf0b3e,
+ 0xf0b3f: 0xf0b3f,
+ 0xf0b40: 0xf0b40,
+ 0xf0b41: 0xf0b41,
+ 0xf0b42: 0xf0b42,
+ 0xf0b43: 0xf0b43,
+ 0xf0b44: 0xf0b44,
+ 0xf0b45: 0xf0b45,
+ 0xf0b46: 0xf0b46,
+ 0xf0b47: 0xf0b47,
+ 0xf0b48: 0xf0b48,
+ 0xf0b49: 0xf0b49,
+ 0xf0b4a: 0xf0b4a,
+ 0xf0b4b: 0xf0b4b,
+ 0xf0b4c: 0xf0b4c,
+ 0xf0b4d: 0xf0b4d,
+ 0xf0b4e: 0xf0b4e,
+ 0xf0b4f: 0xf0b4f,
+ 0xf0b50: 0xf0b50,
+ 0xf0b51: 0xf0b51,
+ 0xf0b52: 0xf0b52,
+ 0xf0b53: 0xf0b53,
+ 0xf0b54: 0xf0b54,
+ 0xf0b55: 0xf0b55,
+ 0xf0b56: 0xf0b56,
+ 0xf0b57: 0xf0b57,
+ 0xf0b58: 0xf0b58,
+ 0xf0b59: 0xf0b59,
+ 0xf0b5a: 0xf0b5a,
+ 0xf0b5b: 0xf0b5b,
+ 0xf0b5c: 0xf0b5c,
+ 0xf0b5d: 0xf0b5d,
+ 0xf0b5e: 0xf0b5e,
+ 0xf0b5f: 0xf0b5f,
+ 0xf0b60: 0xf0b60,
+ 0xf0b61: 0xf0b61,
+ 0xf0b62: 0xf0b62,
+ 0xf0b63: 0xf0b63,
+ 0xf0b64: 0xf0b64,
+ 0xf0b65: 0xf0b65,
+ 0xf0b66: 0xf0b66,
+ 0xf0b67: 0xf0b67,
+ 0xf0b68: 0xf0b68,
+ 0xf0b69: 0xf0b69,
+ 0xf0b6a: 0xf0b6a,
+ 0xf0b6b: 0xf0b6b,
+ 0xf0b6c: 0xf0b6c,
+ 0xf0b6d: 0xf0b6d,
+ 0xf0b6e: 0xf0b6e,
+ 0xf0b6f: 0xf0b6f,
+ 0xf0b70: 0xf0b70,
+ 0xf0b71: 0xf0b71,
+ 0xf0b72: 0xf0b72,
+ 0xf0b73: 0xf0b73,
+ 0xf0b74: 0xf0b74,
+ 0xf0b75: 0xf0b75,
+ 0xf0b76: 0xf0b76,
+ 0xf0b77: 0xf0b77,
+ 0xf0b78: 0xf0b78,
+ 0xf0b79: 0xf0b79,
+ 0xf0b7a: 0xf0b7a,
+ 0xf0b7b: 0xf0b7b,
+ 0xf0b7c: 0xf0b7c,
+ 0xf0b7d: 0xf0b7d,
+ 0xf0b7e: 0xf0b7e,
+ 0xf0b7f: 0xf0b7f,
+ 0xf0b80: 0xf0b80,
+ 0xf0b81: 0xf0b81,
+ 0xf0b82: 0xf0b82,
+ 0xf0b83: 0xf0b83,
+ 0xf0b84: 0xf0b84,
+ 0xf0b85: 0xf0b85,
+ 0xf0b86: 0xf0b86,
+ 0xf0b87: 0xf0b87,
+ 0xf0b88: 0xf0b88,
+ 0xf0b89: 0xf0b89,
+ 0xf0b8a: 0xf0b8a,
+ 0xf0b8b: 0xf0b8b,
+ 0xf0b8c: 0xf0b8c,
+ 0xf0b8d: 0xf0b8d,
+ 0xf0b8e: 0xf0b8e,
+ 0xf0b8f: 0xf0b8f,
+ 0xf0b90: 0xf0b90,
+ 0xf0b91: 0xf0b91,
+ 0xf0b92: 0xf0b92,
+ 0xf0b93: 0xf0b93,
+ 0xf0b94: 0xf0b94,
+ 0xf0b95: 0xf0b95,
+ 0xf0b96: 0xf0b96,
+ 0xf0b97: 0xf0b97,
+ 0xf0b98: 0xf0b98,
+ 0xf0b99: 0xf0b99,
+ 0xf0b9a: 0xf0b9a,
+ 0xf0b9b: 0xf0b9b,
+ 0xf0b9c: 0xf0b9c,
+ 0xf0b9d: 0xf0b9d,
+ 0xf0b9e: 0xf0b9e,
+ 0xf0b9f: 0xf0b9f,
+ 0xf0ba0: 0xf0ba0,
+ 0xf0ba1: 0xf0ba1,
+ 0xf0ba2: 0xf0ba2,
+ 0xf0ba3: 0xf0ba3,
+ 0xf0ba4: 0xf0ba4,
+ 0xf0ba5: 0xf0ba5,
+ 0xf0ba6: 0xf0ba6,
+ 0xf0ba7: 0xf0ba7,
+ 0xf0ba8: 0xf0ba8,
+ 0xf0ba9: 0xf0ba9,
+ 0xf0baa: 0xf0baa,
+ 0xf0bab: 0xf0bab,
+ 0xf0bac: 0xf0bac,
+ 0xf0bad: 0xf0bad,
+ 0xf0bae: 0xf0bae,
+ 0xf0baf: 0xf0baf,
+ 0xf0bb0: 0xf0bb0,
+ 0xf0bb1: 0xf0bb1,
+ 0xf0bb2: 0xf0bb2,
+ 0xf0bb3: 0xf0bb3,
+ 0xf0bb4: 0xf0bb4,
+ 0xf0bb5: 0xf0bb5,
+ 0xf0bb6: 0xf0bb6,
+ 0xf0bb7: 0xf0bb7,
+ 0xf0bb8: 0xf0bb8,
+ 0xf0bb9: 0xf0bb9,
+ 0xf0bba: 0xf0bba,
+ 0xf0bbb: 0xf0bbb,
+ 0xf0bbc: 0xf0bbc,
+ 0xf0bbd: 0xf0bbd,
+ 0xf0bbe: 0xf0bbe,
+ 0xf0bbf: 0xf0bbf,
+ 0xf0bc0: 0xf0bc0,
+ 0xf0bc1: 0xf0bc1,
+ 0xf0bc2: 0xf0bc2,
+ 0xf0bc3: 0xf0bc3,
+ 0xf0bc4: 0xf0bc4,
+ 0xf0bc5: 0xf0bc5,
+ 0xf0bc6: 0xf0bc6,
+ 0xf0bc7: 0xf0bc7,
+ 0xf0bc8: 0xf0bc8,
+ 0xf0bc9: 0xf0bc9,
+ 0xf0bca: 0xf0bca,
+ 0xf0bcb: 0xf0bcb,
+ 0xf0bcc: 0xf0bcc,
+ 0xf0bcd: 0xf0bcd,
+ 0xf0bce: 0xf0bce,
+ 0xf0bcf: 0xf0bcf,
+ 0xf0bd0: 0xf0bd0,
+ 0xf0bd1: 0xf0bd1,
+ 0xf0bd2: 0xf0bd2,
+ 0xf0bd3: 0xf0bd3,
+ 0xf0bd4: 0xf0bd4,
+ 0xf0bd5: 0xf0bd5,
+ 0xf0bd6: 0xf0bd6,
+ 0xf0bd7: 0xf0bd7,
+ 0xf0bd8: 0xf0bd8,
+ 0xf0bd9: 0xf0bd9,
+ 0xf0bda: 0xf0bda,
+ 0xf0bdb: 0xf0bdb,
+ 0xf0bdc: 0xf0bdc,
+ 0xf0bdd: 0xf0bdd,
+ 0xf0bde: 0xf0bde,
+ 0xf0bdf: 0xf0bdf,
+ 0xf0be0: 0xf0be0,
+ 0xf0be1: 0xf0be1,
+ 0xf0be2: 0xf0be2,
+ 0xf0be3: 0xf0be3,
+ 0xf0be4: 0xf0be4,
+ 0xf0be5: 0xf0be5,
+ 0xf0be6: 0xf0be6,
+ 0xf0be7: 0xf0be7,
+ 0xf0be8: 0xf0be8,
+ 0xf0be9: 0xf0be9,
+ 0xf0bea: 0xf0bea,
+ 0xf0beb: 0xf0beb,
+ 0xf0bec: 0xf0bec,
+ 0xf0bed: 0xf0bed,
+ 0xf0bee: 0xf0bee,
+ 0xf0bef: 0xf0bef,
+ 0xf0bf0: 0xf0bf0,
+ 0xf0bf1: 0xf0bf1,
+ 0xf0bf2: 0xf0bf2,
+ 0xf0bf3: 0xf0bf3,
+ 0xf0bf4: 0xf0bf4,
+ 0xf0bf5: 0xf0bf5,
+ 0xf0bf6: 0xf0bf6,
+ 0xf0bf7: 0xf0bf7,
+ 0xf0bf8: 0xf0bf8,
+ 0xf0bf9: 0xf0bf9,
+ 0xf0bfa: 0xf0bfa,
+ 0xf0bfb: 0xf0bfb,
+ 0xf0bfc: 0xf0bfc,
+ 0xf0bfd: 0xf0bfd,
+ 0xf0bfe: 0xf0bfe,
+ 0xf0bff: 0xf0bff,
+ 0xf0c00: 0xf0c00,
+ 0xf0c01: 0xf0c01,
+ 0xf0c02: 0xf0c02,
+ 0xf0c03: 0xf0c03,
+ 0xf0c04: 0xf0c04,
+ 0xf0c05: 0xf0c05,
+ 0xf0c06: 0xf0c06,
+ 0xf0c07: 0xf0c07,
+ 0xf0c08: 0xf0c08,
+ 0xf0c09: 0xf0c09,
+ 0xf0c0a: 0xf0c0a,
+ 0xf0c0b: 0xf0c0b,
+ 0xf0c0c: 0xf0c0c,
+ 0xf0c0d: 0xf0c0d,
+ 0xf0c0e: 0xf0c0e,
+ 0xf0c0f: 0xf0c0f,
+ 0xf0c10: 0xf0c10,
+ 0xf0c11: 0xf0c11,
+ 0xf0c12: 0xf0c12,
+ 0xf0c13: 0xf0c13,
+ 0xf0c14: 0xf0c14,
+ 0xf0c15: 0xf0c15,
+ 0xf0c16: 0xf0c16,
+ 0xf0c17: 0xf0c17,
+ 0xf0c18: 0xf0c18,
+ 0xf0c19: 0xf0c19,
+ 0xf0c1a: 0xf0c1a,
+ 0xf0c1b: 0xf0c1b,
+ 0xf0c1c: 0xf0c1c,
+ 0xf0c1d: 0xf0c1d,
+ 0xf0c1e: 0xf0c1e,
+ 0xf0c1f: 0xf0c1f,
+ 0xf0c20: 0xf0c20,
+ 0xf0c21: 0xf0c21,
+ 0xf0c22: 0xf0c22,
+ 0xf0c23: 0xf0c23,
+ 0xf0c24: 0xf0c24,
+ 0xf0c25: 0xf0c25,
+ 0xf0c26: 0xf0c26,
+ 0xf0c27: 0xf0c27,
+ 0xf0c28: 0xf0c28,
+ 0xf0c29: 0xf0c29,
+ 0xf0c2a: 0xf0c2a,
+ 0xf0c2b: 0xf0c2b,
+ 0xf0c2c: 0xf0c2c,
+ 0xf0c2d: 0xf0c2d,
+ 0xf0c2e: 0xf0c2e,
+ 0xf0c2f: 0xf0c2f,
+ 0xf0c30: 0xf0c30,
+ 0xf0c31: 0xf0c31,
+ 0xf0c32: 0xf0c32,
+ 0xf0c33: 0xf0c33,
+ 0xf0c34: 0xf0c34,
+ 0xf0c35: 0xf0c35,
+ 0xf0c36: 0xf0c36,
+ 0xf0c37: 0xf0c37,
+ 0xf0c38: 0xf0c38,
+ 0xf0c39: 0xf0c39,
+ 0xf0c3a: 0xf0c3a,
+ 0xf0c3b: 0xf0c3b,
+ 0xf0c3c: 0xf0c3c,
+ 0xf0c3d: 0xf0c3d,
+ 0xf0c3e: 0xf0c3e,
+ 0xf0c3f: 0xf0c3f,
+ 0xf0c40: 0xf0c40,
+ 0xf0c41: 0xf0c41,
+ 0xf0c42: 0xf0c42,
+ 0xf0c43: 0xf0c43,
+ 0xf0c44: 0xf0c44,
+ 0xf0c45: 0xf0c45,
+ 0xf0c46: 0xf0c46,
+ 0xf0c47: 0xf0c47,
+ 0xf0c48: 0xf0c48,
+ 0xf0c49: 0xf0c49,
+ 0xf0c4a: 0xf0c4a,
+ 0xf0c4b: 0xf0c4b,
+ 0xf0c4c: 0xf0c4c,
+ 0xf0c4d: 0xf0c4d,
+ 0xf0c4e: 0xf0c4e,
+ 0xf0c4f: 0xf0c4f,
+ 0xf0c50: 0xf0c50,
+ 0xf0c51: 0xf0c51,
+ 0xf0c52: 0xf0c52,
+ 0xf0c53: 0xf0c53,
+ 0xf0c54: 0xf0c54,
+ 0xf0c55: 0xf0c55,
+ 0xf0c56: 0xf0c56,
+ 0xf0c57: 0xf0c57,
+ 0xf0c58: 0xf0c58,
+ 0xf0c59: 0xf0c59,
+ 0xf0c5a: 0xf0c5a,
+ 0xf0c5b: 0xf0c5b,
+ 0xf0c5c: 0xf0c5c,
+ 0xf0c5d: 0xf0c5d,
+ 0xf0c5e: 0xf0c5e,
+ 0xf0c5f: 0xf0c5f,
+ 0xf0c60: 0xf0c60,
+ 0xf0c61: 0xf0c61,
+ 0xf0c62: 0xf0c62,
+ 0xf0c63: 0xf0c63,
+ 0xf0c64: 0xf0c64,
+ 0xf0c65: 0xf0c65,
+ 0xf0c66: 0xf0c66,
+ 0xf0c67: 0xf0c67,
+ 0xf0c68: 0xf0c68,
+ 0xf0c69: 0xf0c69,
+ 0xf0c6a: 0xf0c6a,
+ 0xf0c6b: 0xf0c6b,
+ 0xf0c6c: 0xf0c6c,
+ 0xf0c6d: 0xf0c6d,
+ 0xf0c6e: 0xf0c6e,
+ 0xf0c6f: 0xf0c6f,
+ 0xf0c70: 0xf0c70,
+ 0xf0c71: 0xf0c71,
+ 0xf0c72: 0xf0c72,
+ 0xf0c73: 0xf0c73,
+ 0xf0c74: 0xf0c74,
+ 0xf0c75: 0xf0c75,
+ 0xf0c76: 0xf0c76,
+ 0xf0c77: 0xf0c77,
+ 0xf0c78: 0xf0c78,
+ 0xf0c79: 0xf0c79,
+ 0xf0c7a: 0xf0c7a,
+ 0xf0c7b: 0xf0c7b,
+ 0xf0c7c: 0xf0c7c,
+ 0xf0c7d: 0xf0c7d,
+ 0xf0c7e: 0xf0c7e,
+ 0xf0c7f: 0xf0c7f,
+ 0xf0c80: 0xf0c80,
+ 0xf0c81: 0xf0c81,
+ 0xf0c82: 0xf0c82,
+ 0xf0c83: 0xf0c83,
+ 0xf0c84: 0xf0c84,
+ 0xf0c85: 0xf0c85,
+ 0xf0c86: 0xf0c86,
+ 0xf0c87: 0xf0c87,
+ 0xf0c88: 0xf0c88,
+ 0xf0c89: 0xf0c89,
+ 0xf0c8a: 0xf0c8a,
+ 0xf0c8b: 0xf0c8b,
+ 0xf0c8c: 0xf0c8c,
+ 0xf0c8d: 0xf0c8d,
+ 0xf0c8e: 0xf0c8e,
+ 0xf0c8f: 0xf0c8f,
+ 0xf0c90: 0xf0c90,
+ 0xf0c91: 0xf0c91,
+ 0xf0c92: 0xf0c92,
+ 0xf0c93: 0xf0c93,
+ 0xf0c94: 0xf0c94,
+ 0xf0c95: 0xf0c95,
+ 0xf0c96: 0xf0c96,
+ 0xf0c97: 0xf0c97,
+ 0xf0c98: 0xf0c98,
+ 0xf0c99: 0xf0c99,
+ 0xf0c9a: 0xf0c9a,
+ 0xf0c9b: 0xf0c9b,
+ 0xf0c9c: 0xf0c9c,
+ 0xf0c9d: 0xf0c9d,
+ 0xf0c9e: 0xf0c9e,
+ 0xf0c9f: 0xf0c9f,
+ 0xf0ca0: 0xf0ca0,
+ 0xf0ca1: 0xf0ca1,
+ 0xf0ca2: 0xf0ca2,
+ 0xf0ca3: 0xf0ca3,
+ 0xf0ca4: 0xf0ca4,
+ 0xf0ca5: 0xf0ca5,
+ 0xf0ca6: 0xf0ca6,
+ 0xf0ca7: 0xf0ca7,
+ 0xf0ca8: 0xf0ca8,
+ 0xf0ca9: 0xf0ca9,
+ 0xf0caa: 0xf0caa,
+ 0xf0cab: 0xf0cab,
+ 0xf0cac: 0xf0cac,
+ 0xf0cad: 0xf0cad,
+ 0xf0cae: 0xf0cae,
+ 0xf0caf: 0xf0caf,
+ 0xf0cb0: 0xf0cb0,
+ 0xf0cb1: 0xf0cb1,
+ 0xf0cb2: 0xf0cb2,
+ 0xf0cb3: 0xf0cb3,
+ 0xf0cb4: 0xf0cb4,
+ 0xf0cb5: 0xf0cb5,
+ 0xf0cb6: 0xf0cb6,
+ 0xf0cb7: 0xf0cb7,
+ 0xf0cb8: 0xf0cb8,
+ 0xf0cb9: 0xf0cb9,
+ 0xf0cba: 0xf0cba,
+ 0xf0cbb: 0xf0cbb,
+ 0xf0cbc: 0xf0cbc,
+ 0xf0cbd: 0xf0cbd,
+ 0xf0cbe: 0xf0cbe,
+ 0xf0cbf: 0xf0cbf,
+ 0xf0cc0: 0xf0cc0,
+ 0xf0cc1: 0xf0cc1,
+ 0xf0cc2: 0xf0cc2,
+ 0xf0cc3: 0xf0cc3,
+ 0xf0cc4: 0xf0cc4,
+ 0xf0cc5: 0xf0cc5,
+ 0xf0cc6: 0xf0cc6,
+ 0xf0cc7: 0xf0cc7,
+ 0xf0cc8: 0xf0cc8,
+ 0xf0cc9: 0xf0cc9,
+ 0xf0cca: 0xf0cca,
+ 0xf0ccb: 0xf0ccb,
+ 0xf0ccc: 0xf0ccc,
+ 0xf0ccd: 0xf0ccd,
+ 0xf0cce: 0xf0cce,
+ 0xf0ccf: 0xf0ccf,
+ 0xf0cd0: 0xf0cd0,
+ 0xf0cd1: 0xf0cd1,
+ 0xf0cd2: 0xf0cd2,
+ 0xf0cd3: 0xf0cd3,
+ 0xf0cd4: 0xf0cd4,
+ 0xf0cd5: 0xf0cd5,
+ 0xf0cd6: 0xf0cd6,
+ 0xf0cd7: 0xf0cd7,
+ 0xf0cd8: 0xf0cd8,
+ 0xf0cd9: 0xf0cd9,
+ 0xf0cda: 0xf0cda,
+ 0xf0cdb: 0xf0cdb,
+ 0xf0cdc: 0xf0cdc,
+ 0xf0cdd: 0xf0cdd,
+ 0xf0cde: 0xf0cde,
+ 0xf0cdf: 0xf0cdf,
+ 0xf0ce0: 0xf0ce0,
+ 0xf0ce1: 0xf0ce1,
+ 0xf0ce2: 0xf0ce2,
+ 0xf0ce3: 0xf0ce3,
+ 0xf0ce4: 0xf0ce4,
+ 0xf0ce5: 0xf0ce5,
+ 0xf0ce6: 0xf0ce6,
+ 0xf0ce7: 0xf0ce7,
+ 0xf0ce8: 0xf0ce8,
+ 0xf0ce9: 0xf0ce9,
+ 0xf0cea: 0xf0cea,
+ 0xf0ceb: 0xf0ceb,
+ 0xf0cec: 0xf0cec,
+ 0xf0ced: 0xf0ced,
+ 0xf0cee: 0xf0cee,
+ 0xf0cef: 0xf0cef,
+ 0xf0cf0: 0xf0cf0,
+ 0xf0cf1: 0xf0cf1,
+ 0xf0cf2: 0xf0cf2,
+ 0xf0cf3: 0xf0cf3,
+ 0xf0cf4: 0xf0cf4,
+ 0xf0cf5: 0xf0cf5,
+ 0xf0cf6: 0xf0cf6,
+ 0xf0cf7: 0xf0cf7,
+ 0xf0cf8: 0xf0cf8,
+ 0xf0cf9: 0xf0cf9,
+ 0xf0cfa: 0xf0cfa,
+ 0xf0cfb: 0xf0cfb,
+ 0xf0cfc: 0xf0cfc,
+ 0xf0cfd: 0xf0cfd,
+ 0xf0cfe: 0xf0cfe,
+ 0xf0cff: 0xf0cff,
+ 0xf0d00: 0xf0d00,
+ 0xf0d01: 0xf0d01,
+ 0xf0d02: 0xf0d02,
+ 0xf0d03: 0xf0d03,
+ 0xf0d04: 0xf0d04,
+ 0xf0d05: 0xf0d05,
+ 0xf0d06: 0xf0d06,
+ 0xf0d07: 0xf0d07,
+ 0xf0d08: 0xf0d08,
+ 0xf0d09: 0xf0d09,
+ 0xf0d0a: 0xf0d0a,
+ 0xf0d0b: 0xf0d0b,
+ 0xf0d0c: 0xf0d0c,
+ 0xf0d0d: 0xf0d0d,
+ 0xf0d0e: 0xf0d0e,
+ 0xf0d0f: 0xf0d0f,
+ 0xf0d10: 0xf0d10,
+ 0xf0d11: 0xf0d11,
+ 0xf0d12: 0xf0d12,
+ 0xf0d13: 0xf0d13,
+ 0xf0d14: 0xf0d14,
+ 0xf0d15: 0xf0d15,
+ 0xf0d16: 0xf0d16,
+ 0xf0d17: 0xf0d17,
+ 0xf0d18: 0xf0d18,
+ 0xf0d19: 0xf0d19,
+ 0xf0d1a: 0xf0d1a,
+ 0xf0d1b: 0xf0d1b,
+ 0xf0d1c: 0xf0d1c,
+ 0xf0d1d: 0xf0d1d,
+ 0xf0d1e: 0xf0d1e,
+ 0xf0d1f: 0xf0d1f,
+ 0xf0d20: 0xf0d20,
+ 0xf0d21: 0xf0d21,
+ 0xf0d22: 0xf0d22,
+ 0xf0d23: 0xf0d23,
+ 0xf0d24: 0xf0d24,
+ 0xf0d25: 0xf0d25,
+ 0xf0d26: 0xf0d26,
+ 0xf0d27: 0xf0d27,
+ 0xf0d28: 0xf0d28,
+ 0xf0d29: 0xf0d29,
+ 0xf0d2a: 0xf0d2a,
+ 0xf0d2b: 0xf0d2b,
+ 0xf0d2c: 0xf0d2c,
+ 0xf0d2d: 0xf0d2d,
+ 0xf0d2e: 0xf0d2e,
+ 0xf0d2f: 0xf0d2f,
+ 0xf0d30: 0xf0d30,
+ 0xf0d31: 0xf0d31,
+ 0xf0d32: 0xf0d32,
+ 0xf0d33: 0xf0d33,
+ 0xf0d34: 0xf0d34,
+ 0xf0d35: 0xf0d35,
+ 0xf0d36: 0xf0d36,
+ 0xf0d37: 0xf0d37,
+ 0xf0d38: 0xf0d38,
+ 0xf0d39: 0xf0d39,
+ 0xf0d3a: 0xf0d3a,
+ 0xf0d3b: 0xf0d3b,
+ 0xf0d3c: 0xf0d3c,
+ 0xf0d3d: 0xf0d3d,
+ 0xf0d3e: 0xf0d3e,
+ 0xf0d3f: 0xf0d3f,
+ 0xf0d40: 0xf0d40,
+ 0xf0d41: 0xf0d41,
+ 0xf0d42: 0xf0d42,
+ 0xf0d43: 0xf0d43,
+ 0xf0d44: 0xf0d44,
+ 0xf0d45: 0xf0d45,
+ 0xf0d46: 0xf0d46,
+ 0xf0d47: 0xf0d47,
+ 0xf0d48: 0xf0d48,
+ 0xf0d49: 0xf0d49,
+ 0xf0d4a: 0xf0d4a,
+ 0xf0d4b: 0xf0d4b,
+ 0xf0d4c: 0xf0d4c,
+ 0xf0d4d: 0xf0d4d,
+ 0xf0d4e: 0xf0d4e,
+ 0xf0d4f: 0xf0d4f,
+ 0xf0d50: 0xf0d50,
+ 0xf0d51: 0xf0d51,
+ 0xf0d52: 0xf0d52,
+ 0xf0d53: 0xf0d53,
+ 0xf0d54: 0xf0d54,
+ 0xf0d55: 0xf0d55,
+ 0xf0d56: 0xf0d56,
+ 0xf0d57: 0xf0d57,
+ 0xf0d58: 0xf0d58,
+ 0xf0d59: 0xf0d59,
+ 0xf0d5a: 0xf0d5a,
+ 0xf0d5b: 0xf0d5b,
+ 0xf0d5c: 0xf0d5c,
+ 0xf0d5d: 0xf0d5d,
+ 0xf0d5e: 0xf0d5e,
+ 0xf0d5f: 0xf0d5f,
+ 0xf0d60: 0xf0d60,
+ 0xf0d61: 0xf0d61,
+ 0xf0d62: 0xf0d62,
+ 0xf0d63: 0xf0d63,
+ 0xf0d64: 0xf0d64,
+ 0xf0d65: 0xf0d65,
+ 0xf0d66: 0xf0d66,
+ 0xf0d67: 0xf0d67,
+ 0xf0d68: 0xf0d68,
+ 0xf0d69: 0xf0d69,
+ 0xf0d6a: 0xf0d6a,
+ 0xf0d6b: 0xf0d6b,
+ 0xf0d6c: 0xf0d6c,
+ 0xf0d6d: 0xf0d6d,
+ 0xf0d6e: 0xf0d6e,
+ 0xf0d6f: 0xf0d6f,
+ 0xf0d70: 0xf0d70,
+ 0xf0d71: 0xf0d71,
+ 0xf0d72: 0xf0d72,
+ 0xf0d73: 0xf0d73,
+ 0xf0d74: 0xf0d74,
+ 0xf0d75: 0xf0d75,
+ 0xf0d76: 0xf0d76,
+ 0xf0d77: 0xf0d77,
+ 0xf0d78: 0xf0d78,
+ 0xf0d79: 0xf0d79,
+ 0xf0d7a: 0xf0d7a,
+ 0xf0d7b: 0xf0d7b,
+ 0xf0d7c: 0xf0d7c,
+ 0xf0d7d: 0xf0d7d,
+ 0xf0d7e: 0xf0d7e,
+ 0xf0d7f: 0xf0d7f,
+ 0xf0d80: 0xf0d80,
+ 0xf0d81: 0xf0d81,
+ 0xf0d82: 0xf0d82,
+ 0xf0d83: 0xf0d83,
+ 0xf0d84: 0xf0d84,
+ 0xf0d85: 0xf0d85,
+ 0xf0d86: 0xf0d86,
+ 0xf0d87: 0xf0d87,
+ 0xf0d88: 0xf0d88,
+ 0xf0d89: 0xf0d89,
+ 0xf0d8a: 0xf0d8a,
+ 0xf0d8b: 0xf0d8b,
+ 0xf0d8c: 0xf0d8c,
+ 0xf0d8d: 0xf0d8d,
+ 0xf0d8e: 0xf0d8e,
+ 0xf0d8f: 0xf0d8f,
+ 0xf0d90: 0xf0d90,
+ 0xf0d91: 0xf0d91,
+ 0xf0d92: 0xf0d92,
+ 0xf0d93: 0xf0d93,
+ 0xf0d94: 0xf0d94,
+ 0xf0d95: 0xf0d95,
+ 0xf0d96: 0xf0d96,
+ 0xf0d97: 0xf0d97,
+ 0xf0d98: 0xf0d98,
+ 0xf0d99: 0xf0d99,
+ 0xf0d9a: 0xf0d9a,
+ 0xf0d9b: 0xf0d9b,
+ 0xf0d9c: 0xf0d9c,
+ 0xf0d9d: 0xf0d9d,
+ 0xf0d9e: 0xf0d9e,
+ 0xf0d9f: 0xf0d9f,
+ 0xf0da0: 0xf0da0,
+ 0xf0da1: 0xf0da1,
+ 0xf0da2: 0xf0da2,
+ 0xf0da3: 0xf0da3,
+ 0xf0da4: 0xf0da4,
+ 0xf0da5: 0xf0da5,
+ 0xf0da6: 0xf0da6,
+ 0xf0da7: 0xf0da7,
+ 0xf0da8: 0xf0da8,
+ 0xf0da9: 0xf0da9,
+ 0xf0daa: 0xf0daa,
+ 0xf0dab: 0xf0dab,
+ 0xf0dac: 0xf0dac,
+ 0xf0dad: 0xf0dad,
+ 0xf0dae: 0xf0dae,
+ 0xf0daf: 0xf0daf,
+ 0xf0db0: 0xf0db0,
+ 0xf0db1: 0xf0db1,
+ 0xf0db2: 0xf0db2,
+ 0xf0db3: 0xf0db3,
+ 0xf0db4: 0xf0db4,
+ 0xf0db5: 0xf0db5,
+ 0xf0db6: 0xf0db6,
+ 0xf0db7: 0xf0db7,
+ 0xf0db8: 0xf0db8,
+ 0xf0db9: 0xf0db9,
+ 0xf0dba: 0xf0dba,
+ 0xf0dbb: 0xf0dbb,
+ 0xf0dbc: 0xf0dbc,
+ 0xf0dbd: 0xf0dbd,
+ 0xf0dbe: 0xf0dbe,
+ 0xf0dbf: 0xf0dbf,
+ 0xf0dc0: 0xf0dc0,
+ 0xf0dc1: 0xf0dc1,
+ 0xf0dc2: 0xf0dc2,
+ 0xf0dc3: 0xf0dc3,
+ 0xf0dc4: 0xf0dc4,
+ 0xf0dc5: 0xf0dc5,
+ 0xf0dc6: 0xf0dc6,
+ 0xf0dc7: 0xf0dc7,
+ 0xf0dc8: 0xf0dc8,
+ 0xf0dc9: 0xf0dc9,
+ 0xf0dca: 0xf0dca,
+ 0xf0dcb: 0xf0dcb,
+ 0xf0dcc: 0xf0dcc,
+ 0xf0dcd: 0xf0dcd,
+ 0xf0dce: 0xf0dce,
+ 0xf0dcf: 0xf0dcf,
+ 0xf0dd0: 0xf0dd0,
+ 0xf0dd1: 0xf0dd1,
+ 0xf0dd2: 0xf0dd2,
+ 0xf0dd3: 0xf0dd3,
+ 0xf0dd4: 0xf0dd4,
+ 0xf0dd5: 0xf0dd5,
+ 0xf0dd6: 0xf0dd6,
+ 0xf0dd7: 0xf0dd7,
+ 0xf0dd8: 0xf0dd8,
+ 0xf0dd9: 0xf0dd9,
+ 0xf0dda: 0xf0dda,
+ 0xf0ddb: 0xf0ddb,
+ 0xf0ddc: 0xf0ddc,
+ 0xf0ddd: 0xf0ddd,
+ 0xf0dde: 0xf0dde,
+ 0xf0ddf: 0xf0ddf,
+ 0xf0de0: 0xf0de0,
+ 0xf0de1: 0xf0de1,
+ 0xf0de2: 0xf0de2,
+ 0xf0de3: 0xf0de3,
+ 0xf0de4: 0xf0de4,
+ 0xf0de5: 0xf0de5,
+ 0xf0de6: 0xf0de6,
+ 0xf0de7: 0xf0de7,
+ 0xf0de8: 0xf0de8,
+ 0xf0de9: 0xf0de9,
+ 0xf0dea: 0xf0dea,
+ 0xf0deb: 0xf0deb,
+ 0xf0dec: 0xf0dec,
+ 0xf0ded: 0xf0ded,
+ 0xf0dee: 0xf0dee,
+ 0xf0def: 0xf0def,
+ 0xf0df0: 0xf0df0,
+ 0xf0df1: 0xf0df1,
+ 0xf0df2: 0xf0df2,
+ 0xf0df3: 0xf0df3,
+ 0xf0df4: 0xf0df4,
+ 0xf0df5: 0xf0df5,
+ 0xf0df6: 0xf0df6,
+ 0xf0df7: 0xf0df7,
+ 0xf0df8: 0xf0df8,
+ 0xf0df9: 0xf0df9,
+ 0xf0dfa: 0xf0dfa,
+ 0xf0dfb: 0xf0dfb,
+ 0xf0dfc: 0xf0dfc,
+ 0xf0dfd: 0xf0dfd,
+ 0xf0dfe: 0xf0dfe,
+ 0xf0dff: 0xf0dff,
+ 0xf0e00: 0xf0e00,
+ 0xf0e01: 0xf0e01,
+ 0xf0e02: 0xf0e02,
+ 0xf0e03: 0xf0e03,
+ 0xf0e04: 0xf0e04,
+ 0xf0e05: 0xf0e05,
+ 0xf0e06: 0xf0e06,
+ 0xf0e07: 0xf0e07,
+ 0xf0e08: 0xf0e08,
+ 0xf0e09: 0xf0e09,
+ 0xf0e0a: 0xf0e0a,
+ 0xf0e0b: 0xf0e0b,
+ 0xf0e0c: 0xf0e0c,
+ 0xf0e0d: 0xf0e0d,
+ 0xf0e0e: 0xf0e0e,
+ 0xf0e0f: 0xf0e0f,
+ 0xf0e10: 0xf0e10,
+ 0xf0e11: 0xf0e11,
+ 0xf0e12: 0xf0e12,
+ 0xf0e13: 0xf0e13,
+ 0xf0e14: 0xf0e14,
+ 0xf0e15: 0xf0e15,
+ 0xf0e16: 0xf0e16,
+ 0xf0e17: 0xf0e17,
+ 0xf0e18: 0xf0e18,
+ 0xf0e19: 0xf0e19,
+ 0xf0e1a: 0xf0e1a,
+ 0xf0e1b: 0xf0e1b,
+ 0xf0e1c: 0xf0e1c,
+ 0xf0e1d: 0xf0e1d,
+ 0xf0e1e: 0xf0e1e,
+ 0xf0e1f: 0xf0e1f,
+ 0xf0e20: 0xf0e20,
+ 0xf0e21: 0xf0e21,
+ 0xf0e22: 0xf0e22,
+ 0xf0e23: 0xf0e23,
+ 0xf0e24: 0xf0e24,
+ 0xf0e25: 0xf0e25,
+ 0xf0e26: 0xf0e26,
+ 0xf0e27: 0xf0e27,
+ 0xf0e28: 0xf0e28,
+ 0xf0e29: 0xf0e29,
+ 0xf0e2a: 0xf0e2a,
+ 0xf0e2b: 0xf0e2b,
+ 0xf0e2c: 0xf0e2c,
+ 0xf0e2d: 0xf0e2d,
+ 0xf0e2e: 0xf0e2e,
+ 0xf0e2f: 0xf0e2f,
+ 0xf0e30: 0xf0e30,
+ 0xf0e31: 0xf0e31,
+ 0xf0e32: 0xf0e32,
+ 0xf0e33: 0xf0e33,
+ 0xf0e34: 0xf0e34,
+ 0xf0e35: 0xf0e35,
+ 0xf0e36: 0xf0e36,
+ 0xf0e37: 0xf0e37,
+ 0xf0e38: 0xf0e38,
+ 0xf0e39: 0xf0e39,
+ 0xf0e3a: 0xf0e3a,
+ 0xf0e3b: 0xf0e3b,
+ 0xf0e3c: 0xf0e3c,
+ 0xf0e3d: 0xf0e3d,
+ 0xf0e3e: 0xf0e3e,
+ 0xf0e3f: 0xf0e3f,
+ 0xf0e40: 0xf0e40,
+ 0xf0e41: 0xf0e41,
+ 0xf0e42: 0xf0e42,
+ 0xf0e43: 0xf0e43,
+ 0xf0e44: 0xf0e44,
+ 0xf0e45: 0xf0e45,
+ 0xf0e46: 0xf0e46,
+ 0xf0e47: 0xf0e47,
+ 0xf0e48: 0xf0e48,
+ 0xf0e49: 0xf0e49,
+ 0xf0e4a: 0xf0e4a,
+ 0xf0e4b: 0xf0e4b,
+ 0xf0e4c: 0xf0e4c,
+ 0xf0e4d: 0xf0e4d,
+ 0xf0e4e: 0xf0e4e,
+ 0xf0e4f: 0xf0e4f,
+ 0xf0e50: 0xf0e50,
+ 0xf0e51: 0xf0e51,
+ 0xf0e52: 0xf0e52,
+ 0xf0e53: 0xf0e53,
+ 0xf0e54: 0xf0e54,
+ 0xf0e55: 0xf0e55,
+ 0xf0e56: 0xf0e56,
+ 0xf0e57: 0xf0e57,
+ 0xf0e58: 0xf0e58,
+ 0xf0e59: 0xf0e59,
+ 0xf0e5a: 0xf0e5a,
+ 0xf0e5b: 0xf0e5b,
+ 0xf0e5c: 0xf0e5c,
+ 0xf0e5d: 0xf0e5d,
+ 0xf0e5e: 0xf0e5e,
+ 0xf0e5f: 0xf0e5f,
+ 0xf0e60: 0xf0e60,
+ 0xf0e61: 0xf0e61,
+ 0xf0e62: 0xf0e62,
+ 0xf0e63: 0xf0e63,
+ 0xf0e64: 0xf0e64,
+ 0xf0e65: 0xf0e65,
+ 0xf0e66: 0xf0e66,
+ 0xf0e67: 0xf0e67,
+ 0xf0e68: 0xf0e68,
+ 0xf0e69: 0xf0e69,
+ 0xf0e6a: 0xf0e6a,
+ 0xf0e6b: 0xf0e6b,
+ 0xf0e6c: 0xf0e6c,
+ 0xf0e6d: 0xf0e6d,
+ 0xf0e6e: 0xf0e6e,
+ 0xf0e6f: 0xf0e6f,
+ 0xf0e70: 0xf0e70,
+ 0xf0e71: 0xf0e71,
+ 0xf0e72: 0xf0e72,
+ 0xf0e73: 0xf0e73,
+ 0xf0e74: 0xf0e74,
+ 0xf0e75: 0xf0e75,
+ 0xf0e76: 0xf0e76,
+ 0xf0e77: 0xf0e77,
+ 0xf0e78: 0xf0e78,
+ 0xf0e79: 0xf0e79,
+ 0xf0e7a: 0xf0e7a,
+ 0xf0e7b: 0xf0e7b,
+ 0xf0e7c: 0xf0e7c,
+ 0xf0e7d: 0xf0e7d,
+ 0xf0e7e: 0xf0e7e,
+ 0xf0e7f: 0xf0e7f,
+ 0xf0e80: 0xf0e80,
+ 0xf0e81: 0xf0e81,
+ 0xf0e82: 0xf0e82,
+ 0xf0e83: 0xf0e83,
+ 0xf0e84: 0xf0e84,
+ 0xf0e85: 0xf0e85,
+ 0xf0e86: 0xf0e86,
+ 0xf0e87: 0xf0e87,
+ 0xf0e88: 0xf0e88,
+ 0xf0e89: 0xf0e89,
+ 0xf0e8a: 0xf0e8a,
+ 0xf0e8b: 0xf0e8b,
+ 0xf0e8c: 0xf0e8c,
+ 0xf0e8d: 0xf0e8d,
+ 0xf0e8e: 0xf0e8e,
+ 0xf0e8f: 0xf0e8f,
+ 0xf0e90: 0xf0e90,
+ 0xf0e91: 0xf0e91,
+ 0xf0e92: 0xf0e92,
+ 0xf0e93: 0xf0e93,
+ 0xf0e94: 0xf0e94,
+ 0xf0e95: 0xf0e95,
+ 0xf0e96: 0xf0e96,
+ 0xf0e97: 0xf0e97,
+ 0xf0e98: 0xf0e98,
+ 0xf0e99: 0xf0e99,
+ 0xf0e9a: 0xf0e9a,
+ 0xf0e9b: 0xf0e9b,
+ 0xf0e9c: 0xf0e9c,
+ 0xf0e9d: 0xf0e9d,
+ 0xf0e9e: 0xf0e9e,
+ 0xf0e9f: 0xf0e9f,
+ 0xf0ea0: 0xf0ea0,
+ 0xf0ea1: 0xf0ea1,
+ 0xf0ea2: 0xf0ea2,
+ 0xf0ea3: 0xf0ea3,
+ 0xf0ea4: 0xf0ea4,
+ 0xf0ea5: 0xf0ea5,
+ 0xf0ea6: 0xf0ea6,
+ 0xf0ea7: 0xf0ea7,
+ 0xf0ea8: 0xf0ea8,
+ 0xf0ea9: 0xf0ea9,
+ 0xf0eaa: 0xf0eaa,
+ 0xf0eab: 0xf0eab,
+ 0xf0eac: 0xf0eac,
+ 0xf0ead: 0xf0ead,
+ 0xf0eae: 0xf0eae,
+ 0xf0eaf: 0xf0eaf,
+ 0xf0eb0: 0xf0eb0,
+ 0xf0eb1: 0xf0eb1,
+ 0xf0eb2: 0xf0eb2,
+ 0xf0eb3: 0xf0eb3,
+ 0xf0eb4: 0xf0eb4,
+ 0xf0eb5: 0xf0eb5,
+ 0xf0eb6: 0xf0eb6,
+ 0xf0eb7: 0xf0eb7,
+ 0xf0eb8: 0xf0eb8,
+ 0xf0eb9: 0xf0eb9,
+ 0xf0eba: 0xf0eba,
+ 0xf0ebb: 0xf0ebb,
+ 0xf0ebc: 0xf0ebc,
+ 0xf0ebd: 0xf0ebd,
+ 0xf0ebe: 0xf0ebe,
+ 0xf0ebf: 0xf0ebf,
+ 0xf0ec0: 0xf0ec0,
+ 0xf0ec1: 0xf0ec1,
+ 0xf0ec2: 0xf0ec2,
+ 0xf0ec3: 0xf0ec3,
+ 0xf0ec4: 0xf0ec4,
+ 0xf0ec5: 0xf0ec5,
+ 0xf0ec6: 0xf0ec6,
+ 0xf0ec7: 0xf0ec7,
+ 0xf0ec8: 0xf0ec8,
+ 0xf0ec9: 0xf0ec9,
+ 0xf0eca: 0xf0eca,
+ 0xf0ecb: 0xf0ecb,
+ 0xf0ecc: 0xf0ecc,
+ 0xf0ecd: 0xf0ecd,
+ 0xf0ece: 0xf0ece,
+ 0xf0ecf: 0xf0ecf,
+ 0xf0ed0: 0xf0ed0,
+ 0xf0ed1: 0xf0ed1,
+ 0xf0ed2: 0xf0ed2,
+ 0xf0ed3: 0xf0ed3,
+ 0xf0ed4: 0xf0ed4,
+ 0xf0ed5: 0xf0ed5,
+ 0xf0ed6: 0xf0ed6,
+ 0xf0ed7: 0xf0ed7,
+ 0xf0ed8: 0xf0ed8,
+ 0xf0ed9: 0xf0ed9,
+ 0xf0eda: 0xf0eda,
+ 0xf0edb: 0xf0edb,
+ 0xf0edc: 0xf0edc,
+ 0xf0edd: 0xf0edd,
+ 0xf0ede: 0xf0ede,
+ 0xf0edf: 0xf0edf,
+ 0xf0ee0: 0xf0ee0,
+ 0xf0ee1: 0xf0ee1,
+ 0xf0ee2: 0xf0ee2,
+ 0xf0ee3: 0xf0ee3,
+ 0xf0ee4: 0xf0ee4,
+ 0xf0ee5: 0xf0ee5,
+ 0xf0ee6: 0xf0ee6,
+ 0xf0ee7: 0xf0ee7,
+ 0xf0ee8: 0xf0ee8,
+ 0xf0ee9: 0xf0ee9,
+ 0xf0eea: 0xf0eea,
+ 0xf0eeb: 0xf0eeb,
+ 0xf0eec: 0xf0eec,
+ 0xf0eed: 0xf0eed,
+ 0xf0eee: 0xf0eee,
+ 0xf0eef: 0xf0eef,
+ 0xf0ef0: 0xf0ef0,
+ 0xf0ef1: 0xf0ef1,
+ 0xf0ef2: 0xf0ef2,
+ 0xf0ef3: 0xf0ef3,
+ 0xf0ef4: 0xf0ef4,
+ 0xf0ef5: 0xf0ef5,
+ 0xf0ef6: 0xf0ef6,
+ 0xf0ef7: 0xf0ef7,
+ 0xf0ef8: 0xf0ef8,
+ 0xf0ef9: 0xf0ef9,
+ 0xf0efa: 0xf0efa,
+ 0xf0efb: 0xf0efb,
+ 0xf0efc: 0xf0efc,
+ 0xf0efd: 0xf0efd,
+ 0xf0efe: 0xf0efe,
+ 0xf0eff: 0xf0eff,
+ 0xf0f00: 0xf0f00,
+ 0xf0f01: 0xf0f01,
+ 0xf0f02: 0xf0f02,
+ 0xf0f03: 0xf0f03,
+ 0xf0f04: 0xf0f04,
+ 0xf0f05: 0xf0f05,
+ 0xf0f06: 0xf0f06,
+ 0xf0f07: 0xf0f07,
+ 0xf0f08: 0xf0f08,
+ 0xf0f09: 0xf0f09,
+ 0xf0f0a: 0xf0f0a,
+ 0xf0f0b: 0xf0f0b,
+ 0xf0f0c: 0xf0f0c,
+ 0xf0f0d: 0xf0f0d,
+ 0xf0f0e: 0xf0f0e,
+ 0xf0f0f: 0xf0f0f,
+ 0xf0f10: 0xf0f10,
+ 0xf0f11: 0xf0f11,
+ 0xf0f12: 0xf0f12,
+ 0xf0f13: 0xf0f13,
+ 0xf0f14: 0xf0f14,
+ 0xf0f15: 0xf0f15,
+ 0xf0f16: 0xf0f16,
+ 0xf0f17: 0xf0f17,
+ 0xf0f18: 0xf0f18,
+ 0xf0f19: 0xf0f19,
+ 0xf0f1a: 0xf0f1a,
+ 0xf0f1b: 0xf0f1b,
+ 0xf0f1c: 0xf0f1c,
+ 0xf0f1d: 0xf0f1d,
+ 0xf0f1e: 0xf0f1e,
+ 0xf0f1f: 0xf0f1f,
+ 0xf0f20: 0xf0f20,
+ 0xf0f21: 0xf0f21,
+ 0xf0f22: 0xf0f22,
+ 0xf0f23: 0xf0f23,
+ 0xf0f24: 0xf0f24,
+ 0xf0f25: 0xf0f25,
+ 0xf0f26: 0xf0f26,
+ 0xf0f27: 0xf0f27,
+ 0xf0f28: 0xf0f28,
+ 0xf0f29: 0xf0f29,
+ 0xf0f2a: 0xf0f2a,
+ 0xf0f2b: 0xf0f2b,
+ 0xf0f2c: 0xf0f2c,
+ 0xf0f2d: 0xf0f2d,
+ 0xf0f2e: 0xf0f2e,
+ 0xf0f2f: 0xf0f2f,
+ 0xf0f30: 0xf0f30,
+ 0xf0f31: 0xf0f31,
+ 0xf0f32: 0xf0f32,
+ 0xf0f33: 0xf0f33,
+ 0xf0f34: 0xf0f34,
+ 0xf0f35: 0xf0f35,
+ 0xf0f36: 0xf0f36,
+ 0xf0f37: 0xf0f37,
+ 0xf0f38: 0xf0f38,
+ 0xf0f39: 0xf0f39,
+ 0xf0f3a: 0xf0f3a,
+ 0xf0f3b: 0xf0f3b,
+ 0xf0f3c: 0xf0f3c,
+ 0xf0f3d: 0xf0f3d,
+ 0xf0f3e: 0xf0f3e,
+ 0xf0f3f: 0xf0f3f,
+ 0xf0f40: 0xf0f40,
+ 0xf0f41: 0xf0f41,
+ 0xf0f42: 0xf0f42,
+ 0xf0f43: 0xf0f43,
+ 0xf0f44: 0xf0f44,
+ 0xf0f45: 0xf0f45,
+ 0xf0f46: 0xf0f46,
+ 0xf0f47: 0xf0f47,
+ 0xf0f48: 0xf0f48,
+ 0xf0f49: 0xf0f49,
+ 0xf0f4a: 0xf0f4a,
+ 0xf0f4b: 0xf0f4b,
+ 0xf0f4c: 0xf0f4c,
+ 0xf0f4d: 0xf0f4d,
+ 0xf0f4e: 0xf0f4e,
+ 0xf0f4f: 0xf0f4f,
+ 0xf0f50: 0xf0f50,
+ 0xf0f51: 0xf0f51,
+ 0xf0f52: 0xf0f52,
+ 0xf0f53: 0xf0f53,
+ 0xf0f54: 0xf0f54,
+ 0xf0f55: 0xf0f55,
+ 0xf0f56: 0xf0f56,
+ 0xf0f57: 0xf0f57,
+ 0xf0f58: 0xf0f58,
+ 0xf0f59: 0xf0f59,
+ 0xf0f5a: 0xf0f5a,
+ 0xf0f5b: 0xf0f5b,
+ 0xf0f5c: 0xf0f5c,
+ 0xf0f5d: 0xf0f5d,
+ 0xf0f5e: 0xf0f5e,
+ 0xf0f5f: 0xf0f5f,
+ 0xf0f60: 0xf0f60,
+ 0xf0f61: 0xf0f61,
+ 0xf0f62: 0xf0f62,
+ 0xf0f63: 0xf0f63,
+ 0xf0f64: 0xf0f64,
+ 0xf0f65: 0xf0f65,
+ 0xf0f66: 0xf0f66,
+ 0xf0f67: 0xf0f67,
+ 0xf0f68: 0xf0f68,
+ 0xf0f69: 0xf0f69,
+ 0xf0f6a: 0xf0f6a,
+ 0xf0f6b: 0xf0f6b,
+ 0xf0f6c: 0xf0f6c,
+ 0xf0f6d: 0xf0f6d,
+ 0xf0f6e: 0xf0f6e,
+ 0xf0f6f: 0xf0f6f,
+ 0xf0f70: 0xf0f70,
+ 0xf0f71: 0xf0f71,
+ 0xf0f72: 0xf0f72,
+ 0xf0f73: 0xf0f73,
+ 0xf0f74: 0xf0f74,
+ 0xf0f75: 0xf0f75,
+ 0xf0f76: 0xf0f76,
+ 0xf0f77: 0xf0f77,
+ 0xf0f78: 0xf0f78,
+ 0xf0f79: 0xf0f79,
+ 0xf0f7a: 0xf0f7a,
+ 0xf0f7b: 0xf0f7b,
+ 0xf0f7c: 0xf0f7c,
+ 0xf0f7d: 0xf0f7d,
+ 0xf0f7e: 0xf0f7e,
+ 0xf0f7f: 0xf0f7f,
+ 0xf0f80: 0xf0f80,
+ 0xf0f81: 0xf0f81,
+ 0xf0f82: 0xf0f82,
+ 0xf0f83: 0xf0f83,
+ 0xf0f84: 0xf0f84,
+ 0xf0f85: 0xf0f85,
+ 0xf0f86: 0xf0f86,
+ 0xf0f87: 0xf0f87,
+ 0xf0f88: 0xf0f88,
+ 0xf0f89: 0xf0f89,
+ 0xf0f8a: 0xf0f8a,
+ 0xf0f8b: 0xf0f8b,
+ 0xf0f8c: 0xf0f8c,
+ 0xf0f8d: 0xf0f8d,
+ 0xf0f8e: 0xf0f8e,
+ 0xf0f8f: 0xf0f8f,
+ 0xf0f90: 0xf0f90,
+ 0xf0f91: 0xf0f91,
+ 0xf0f92: 0xf0f92,
+ 0xf0f93: 0xf0f93,
+ 0xf0f94: 0xf0f94,
+ 0xf0f95: 0xf0f95,
+ 0xf0f96: 0xf0f96,
+ 0xf0f97: 0xf0f97,
+ 0xf0f98: 0xf0f98,
+ 0xf0f99: 0xf0f99,
+ 0xf0f9a: 0xf0f9a,
+ 0xf0f9b: 0xf0f9b,
+ 0xf0f9c: 0xf0f9c,
+ 0xf0f9d: 0xf0f9d,
+ 0xf0f9e: 0xf0f9e,
+ 0xf0f9f: 0xf0f9f,
+ 0xf0fa0: 0xf0fa0,
+ 0xf0fa1: 0xf0fa1,
+ 0xf0fa2: 0xf0fa2,
+ 0xf0fa3: 0xf0fa3,
+ 0xf0fa4: 0xf0fa4,
+ 0xf0fa5: 0xf0fa5,
+ 0xf0fa6: 0xf0fa6,
+ 0xf0fa7: 0xf0fa7,
+ 0xf0fa8: 0xf0fa8,
+ 0xf0fa9: 0xf0fa9,
+ 0xf0faa: 0xf0faa,
+ 0xf0fab: 0xf0fab,
+ 0xf0fac: 0xf0fac,
+ 0xf0fad: 0xf0fad,
+ 0xf0fae: 0xf0fae,
+ 0xf0faf: 0xf0faf,
+ 0xf0fb0: 0xf0fb0,
+ 0xf0fb1: 0xf0fb1,
+ 0xf0fb2: 0xf0fb2,
+ 0xf0fb3: 0xf0fb3,
+ 0xf0fb4: 0xf0fb4,
+ 0xf0fb5: 0xf0fb5,
+ 0xf0fb6: 0xf0fb6,
+ 0xf0fb7: 0xf0fb7,
+ 0xf0fb8: 0xf0fb8,
+ 0xf0fb9: 0xf0fb9,
+ 0xf0fba: 0xf0fba,
+ 0xf0fbb: 0xf0fbb,
+ 0xf0fbc: 0xf0fbc,
+ 0xf0fbd: 0xf0fbd,
+ 0xf0fbe: 0xf0fbe,
+ 0xf0fbf: 0xf0fbf,
+ 0xf0fc0: 0xf0fc0,
+ 0xf0fc1: 0xf0fc1,
+ 0xf0fc2: 0xf0fc2,
+ 0xf0fc3: 0xf0fc3,
+ 0xf0fc4: 0xf0fc4,
+ 0xf0fc5: 0xf0fc5,
+ 0xf0fc6: 0xf0fc6,
+ 0xf0fc7: 0xf0fc7,
+ 0xf0fc8: 0xf0fc8,
+ 0xf0fc9: 0xf0fc9,
+ 0xf0fca: 0xf0fca,
+ 0xf0fcb: 0xf0fcb,
+ 0xf0fcc: 0xf0fcc,
+ 0xf0fcd: 0xf0fcd,
+ 0xf0fce: 0xf0fce,
+ 0xf0fcf: 0xf0fcf,
+ 0xf0fd0: 0xf0fd0,
+ 0xf0fd1: 0xf0fd1,
+ 0xf0fd2: 0xf0fd2,
+ 0xf0fd3: 0xf0fd3,
+ 0xf0fd4: 0xf0fd4,
+ 0xf0fd5: 0xf0fd5,
+ 0xf0fd6: 0xf0fd6,
+ 0xf0fd7: 0xf0fd7,
+ 0xf0fd8: 0xf0fd8,
+ 0xf0fd9: 0xf0fd9,
+ 0xf0fda: 0xf0fda,
+ 0xf0fdb: 0xf0fdb,
+ 0xf0fdc: 0xf0fdc,
+ 0xf0fdd: 0xf0fdd,
+ 0xf0fde: 0xf0fde,
+ 0xf0fdf: 0xf0fdf,
+ 0xf0fe0: 0xf0fe0,
+ 0xf0fe1: 0xf0fe1,
+ 0xf0fe2: 0xf0fe2,
+ 0xf0fe3: 0xf0fe3,
+ 0xf0fe4: 0xf0fe4,
+ 0xf0fe5: 0xf0fe5,
+ 0xf0fe6: 0xf0fe6,
+ 0xf0fe7: 0xf0fe7,
+ 0xf0fe8: 0xf0fe8,
+ 0xf0fe9: 0xf0fe9,
+ 0xf0fea: 0xf0fea,
+ 0xf0feb: 0xf0feb,
+ 0xf0fec: 0xf0fec,
+ 0xf0fed: 0xf0fed,
+ 0xf0fee: 0xf0fee,
+ 0xf0fef: 0xf0fef,
+ 0xf0ff0: 0xf0ff0,
+ 0xf0ff1: 0xf0ff1,
+ 0xf0ff2: 0xf0ff2,
+ 0xf0ff3: 0xf0ff3,
+ 0xf0ff4: 0xf0ff4,
+ 0xf0ff5: 0xf0ff5,
+ 0xf0ff6: 0xf0ff6,
+ 0xf0ff7: 0xf0ff7,
+ 0xf0ff8: 0xf0ff8,
+ 0xf0ff9: 0xf0ff9,
+ 0xf0ffa: 0xf0ffa,
+ 0xf0ffb: 0xf0ffb,
+ 0xf0ffc: 0xf0ffc,
+ 0xf0ffd: 0xf0ffd,
+ 0xf0ffe: 0xf0ffe,
+ 0xf0fff: 0xf0fff,
+ 0xf1000: 0xf1000,
+ 0xf1001: 0xf1001,
+ 0xf1002: 0xf1002,
+ 0xf1003: 0xf1003,
+ 0xf1004: 0xf1004,
+ 0xf1005: 0xf1005,
+ 0xf1006: 0xf1006,
+ 0xf1007: 0xf1007,
+ 0xf1008: 0xf1008,
+ 0xf1009: 0xf1009,
+ 0xf100a: 0xf100a,
+ 0xf100b: 0xf100b,
+ 0xf100c: 0xf100c,
+ 0xf100d: 0xf100d,
+ 0xf100e: 0xf100e,
+ 0xf100f: 0xf100f,
+ 0xf1010: 0xf1010,
+ 0xf1011: 0xf1011,
+ 0xf1012: 0xf1012,
+ 0xf1013: 0xf1013,
+ 0xf1014: 0xf1014,
+ 0xf1015: 0xf1015,
+ 0xf1016: 0xf1016,
+ 0xf1017: 0xf1017,
+ 0xf1018: 0xf1018,
+ 0xf1019: 0xf1019,
+ 0xf101a: 0xf101a,
+ 0xf101b: 0xf101b,
+ 0xf101c: 0xf101c,
+ 0xf101d: 0xf101d,
+ 0xf101e: 0xf101e,
+ 0xf101f: 0xf101f,
+ 0xf1020: 0xf1020,
+ 0xf1021: 0xf1021,
+ 0xf1022: 0xf1022,
+ 0xf1023: 0xf1023,
+ 0xf1024: 0xf1024,
+ 0xf1025: 0xf1025,
+ 0xf1026: 0xf1026,
+ 0xf1027: 0xf1027,
+ 0xf1028: 0xf1028,
+ 0xf1029: 0xf1029,
+ 0xf102a: 0xf102a,
+ 0xf102b: 0xf102b,
+ 0xf102c: 0xf102c,
+ 0xf102d: 0xf102d,
+ 0xf102e: 0xf102e,
+ 0xf102f: 0xf102f,
+ 0xf1030: 0xf1030,
+ 0xf1031: 0xf1031,
+ 0xf1032: 0xf1032,
+ 0xf1033: 0xf1033,
+ 0xf1034: 0xf1034,
+ 0xf1035: 0xf1035,
+ 0xf1036: 0xf1036,
+ 0xf1037: 0xf1037,
+ 0xf1038: 0xf1038,
+ 0xf1039: 0xf1039,
+ 0xf103a: 0xf103a,
+ 0xf103b: 0xf103b,
+ 0xf103c: 0xf103c,
+ 0xf103d: 0xf103d,
+ 0xf103e: 0xf103e,
+ 0xf103f: 0xf103f,
+ 0xf1040: 0xf1040,
+ 0xf1041: 0xf1041,
+ 0xf1042: 0xf1042,
+ 0xf1043: 0xf1043,
+ 0xf1044: 0xf1044,
+ 0xf1045: 0xf1045,
+ 0xf1046: 0xf1046,
+ 0xf1047: 0xf1047,
+ 0xf1048: 0xf1048,
+ 0xf1049: 0xf1049,
+ 0xf104a: 0xf104a,
+ 0xf104b: 0xf104b,
+ 0xf104c: 0xf104c,
+ 0xf104d: 0xf104d,
+ 0xf104e: 0xf104e,
+ 0xf104f: 0xf104f,
+ 0xf1050: 0xf1050,
+ 0xf1051: 0xf1051,
+ 0xf1052: 0xf1052,
+ 0xf1053: 0xf1053,
+ 0xf1054: 0xf1054,
+ 0xf1055: 0xf1055,
+ 0xf1056: 0xf1056,
+ 0xf1057: 0xf1057,
+ 0xf1058: 0xf1058,
+ 0xf1059: 0xf1059,
+ 0xf105a: 0xf105a,
+ 0xf105b: 0xf105b,
+ 0xf105c: 0xf105c,
+ 0xf105d: 0xf105d,
+ 0xf105e: 0xf105e,
+ 0xf105f: 0xf105f,
+ 0xf1060: 0xf1060,
+ 0xf1061: 0xf1061,
+ 0xf1062: 0xf1062,
+ 0xf1063: 0xf1063,
+ 0xf1064: 0xf1064,
+ 0xf1065: 0xf1065,
+ 0xf1066: 0xf1066,
+ 0xf1067: 0xf1067,
+ 0xf1068: 0xf1068,
+ 0xf1069: 0xf1069,
+ 0xf106a: 0xf106a,
+ 0xf106b: 0xf106b,
+ 0xf106c: 0xf106c,
+ 0xf106d: 0xf106d,
+ 0xf106e: 0xf106e,
+ 0xf106f: 0xf106f,
+ 0xf1070: 0xf1070,
+ 0xf1071: 0xf1071,
+ 0xf1072: 0xf1072,
+ 0xf1073: 0xf1073,
+ 0xf1074: 0xf1074,
+ 0xf1075: 0xf1075,
+ 0xf1076: 0xf1076,
+ 0xf1077: 0xf1077,
+ 0xf1078: 0xf1078,
+ 0xf1079: 0xf1079,
+ 0xf107a: 0xf107a,
+ 0xf107b: 0xf107b,
+ 0xf107c: 0xf107c,
+ 0xf107d: 0xf107d,
+ 0xf107e: 0xf107e,
+ 0xf107f: 0xf107f,
+ 0xf1080: 0xf1080,
+ 0xf1081: 0xf1081,
+ 0xf1082: 0xf1082,
+ 0xf1083: 0xf1083,
+ 0xf1084: 0xf1084,
+ 0xf1085: 0xf1085,
+ 0xf1086: 0xf1086,
+ 0xf1087: 0xf1087,
+ 0xf1088: 0xf1088,
+ 0xf1089: 0xf1089,
+ 0xf108a: 0xf108a,
+ 0xf108b: 0xf108b,
+ 0xf108c: 0xf108c,
+ 0xf108d: 0xf108d,
+ 0xf108e: 0xf108e,
+ 0xf108f: 0xf108f,
+ 0xf1090: 0xf1090,
+ 0xf1091: 0xf1091,
+ 0xf1092: 0xf1092,
+ 0xf1093: 0xf1093,
+ 0xf1094: 0xf1094,
+ 0xf1095: 0xf1095,
+ 0xf1096: 0xf1096,
+ 0xf1097: 0xf1097,
+ 0xf1098: 0xf1098,
+ 0xf1099: 0xf1099,
+ 0xf109a: 0xf109a,
+ 0xf109b: 0xf109b,
+ 0xf109c: 0xf109c,
+ 0xf109d: 0xf109d,
+ 0xf109e: 0xf109e,
+ 0xf109f: 0xf109f,
+ 0xf10a0: 0xf10a0,
+ 0xf10a1: 0xf10a1,
+ 0xf10a2: 0xf10a2,
+ 0xf10a3: 0xf10a3,
+ 0xf10a4: 0xf10a4,
+ 0xf10a5: 0xf10a5,
+ 0xf10a6: 0xf10a6,
+ 0xf10a7: 0xf10a7,
+ 0xf10a8: 0xf10a8,
+ 0xf10a9: 0xf10a9,
+ 0xf10aa: 0xf10aa,
+ 0xf10ab: 0xf10ab,
+ 0xf10ac: 0xf10ac,
+ 0xf10ad: 0xf10ad,
+ 0xf10ae: 0xf10ae,
+ 0xf10af: 0xf10af,
+ 0xf10b0: 0xf10b0,
+ 0xf10b1: 0xf10b1,
+ 0xf10b2: 0xf10b2,
+ 0xf10b3: 0xf10b3,
+ 0xf10b4: 0xf10b4,
+ 0xf10b5: 0xf10b5,
+ 0xf10b6: 0xf10b6,
+ 0xf10b7: 0xf10b7,
+ 0xf10b8: 0xf10b8,
+ 0xf10b9: 0xf10b9,
+ 0xf10ba: 0xf10ba,
+ 0xf10bb: 0xf10bb,
+ 0xf10bc: 0xf10bc,
+ 0xf10bd: 0xf10bd,
+ 0xf10be: 0xf10be,
+ 0xf10bf: 0xf10bf,
+ 0xf10c0: 0xf10c0,
+ 0xf10c1: 0xf10c1,
+ 0xf10c2: 0xf10c2,
+ 0xf10c3: 0xf10c3,
+ 0xf10c4: 0xf10c4,
+ 0xf10c5: 0xf10c5,
+ 0xf10c6: 0xf10c6,
+ 0xf10c7: 0xf10c7,
+ 0xf10c8: 0xf10c8,
+ 0xf10c9: 0xf10c9,
+ 0xf10ca: 0xf10ca,
+ 0xf10cb: 0xf10cb,
+ 0xf10cc: 0xf10cc,
+ 0xf10cd: 0xf10cd,
+ 0xf10ce: 0xf10ce,
+ 0xf10cf: 0xf10cf,
+ 0xf10d0: 0xf10d0,
+ 0xf10d1: 0xf10d1,
+ 0xf10d2: 0xf10d2,
+ 0xf10d3: 0xf10d3,
+ 0xf10d4: 0xf10d4,
+ 0xf10d5: 0xf10d5,
+ 0xf10d6: 0xf10d6,
+ 0xf10d7: 0xf10d7,
+ 0xf10d8: 0xf10d8,
+ 0xf10d9: 0xf10d9,
+ 0xf10da: 0xf10da,
+ 0xf10db: 0xf10db,
+ 0xf10dc: 0xf10dc,
+ 0xf10dd: 0xf10dd,
+ 0xf10de: 0xf10de,
+ 0xf10df: 0xf10df,
+ 0xf10e0: 0xf10e0,
+ 0xf10e1: 0xf10e1,
+ 0xf10e2: 0xf10e2,
+ 0xf10e3: 0xf10e3,
+ 0xf10e4: 0xf10e4,
+ 0xf10e5: 0xf10e5,
+ 0xf10e6: 0xf10e6,
+ 0xf10e7: 0xf10e7,
+ 0xf10e8: 0xf10e8,
+ 0xf10e9: 0xf10e9,
+ 0xf10ea: 0xf10ea,
+ 0xf10eb: 0xf10eb,
+ 0xf10ec: 0xf10ec,
+ 0xf10ed: 0xf10ed,
+ 0xf10ee: 0xf10ee,
+ 0xf10ef: 0xf10ef,
+ 0xf10f0: 0xf10f0,
+ 0xf10f1: 0xf10f1,
+ 0xf10f2: 0xf10f2,
+ 0xf10f3: 0xf10f3,
+ 0xf10f4: 0xf10f4,
+ 0xf10f5: 0xf10f5,
+ 0xf10f6: 0xf10f6,
+ 0xf10f7: 0xf10f7,
+ 0xf10f8: 0xf10f8,
+ 0xf10f9: 0xf10f9,
+ 0xf10fa: 0xf10fa,
+ 0xf10fb: 0xf10fb,
+ 0xf10fc: 0xf10fc,
+ 0xf10fd: 0xf10fd,
+ 0xf10fe: 0xf10fe,
+ 0xf10ff: 0xf10ff,
+ 0xf1100: 0xf1100,
+ 0xf1101: 0xf1101,
+ 0xf1102: 0xf1102,
+ 0xf1103: 0xf1103,
+ 0xf1104: 0xf1104,
+ 0xf1105: 0xf1105,
+ 0xf1106: 0xf1106,
+ 0xf1107: 0xf1107,
+ 0xf1108: 0xf1108,
+ 0xf1109: 0xf1109,
+ 0xf110a: 0xf110a,
+ 0xf110b: 0xf110b,
+ 0xf110c: 0xf110c,
+ 0xf110d: 0xf110d,
+ 0xf110e: 0xf110e,
+ 0xf110f: 0xf110f,
+ 0xf1110: 0xf1110,
+ 0xf1111: 0xf1111,
+ 0xf1112: 0xf1112,
+ 0xf1113: 0xf1113,
+ 0xf1114: 0xf1114,
+ 0xf1115: 0xf1115,
+ 0xf1116: 0xf1116,
+ 0xf1117: 0xf1117,
+ 0xf1118: 0xf1118,
+ 0xf1119: 0xf1119,
+ 0xf111a: 0xf111a,
+ 0xf111b: 0xf111b,
+ 0xf111c: 0xf111c,
+ 0xf111d: 0xf111d,
+ 0xf111e: 0xf111e,
+ 0xf111f: 0xf111f,
+ 0xf1120: 0xf1120,
+ 0xf1121: 0xf1121,
+ 0xf1122: 0xf1122,
+ 0xf1123: 0xf1123,
+ 0xf1124: 0xf1124,
+ 0xf1125: 0xf1125,
+ 0xf1126: 0xf1126,
+ 0xf1127: 0xf1127,
+ 0xf1128: 0xf1128,
+ 0xf1129: 0xf1129,
+ 0xf112a: 0xf112a,
+ 0xf112b: 0xf112b,
+ 0xf112c: 0xf112c,
+ 0xf112d: 0xf112d,
+ 0xf112e: 0xf112e,
+ 0xf112f: 0xf112f,
+ 0xf1130: 0xf1130,
+ 0xf1131: 0xf1131,
+ 0xf1132: 0xf1132,
+ 0xf1133: 0xf1133,
+ 0xf1134: 0xf1134,
+ 0xf1135: 0xf1135,
+ 0xf1136: 0xf1136,
+ 0xf1137: 0xf1137,
+ 0xf1138: 0xf1138,
+ 0xf1139: 0xf1139,
+ 0xf113a: 0xf113a,
+ 0xf113b: 0xf113b,
+ 0xf113c: 0xf113c,
+ 0xf113d: 0xf113d,
+ 0xf113e: 0xf113e,
+ 0xf113f: 0xf113f,
+ 0xf1140: 0xf1140,
+ 0xf1141: 0xf1141,
+ 0xf1142: 0xf1142,
+ 0xf1143: 0xf1143,
+ 0xf1144: 0xf1144,
+ 0xf1145: 0xf1145,
+ 0xf1146: 0xf1146,
+ 0xf1147: 0xf1147,
+ 0xf1148: 0xf1148,
+ 0xf1149: 0xf1149,
+ 0xf114a: 0xf114a,
+ 0xf114b: 0xf114b,
+ 0xf114c: 0xf114c,
+ 0xf114d: 0xf114d,
+ 0xf114e: 0xf114e,
+ 0xf114f: 0xf114f,
+ 0xf1150: 0xf1150,
+ 0xf1151: 0xf1151,
+ 0xf1152: 0xf1152,
+ 0xf1153: 0xf1153,
+ 0xf1154: 0xf1154,
+ 0xf1155: 0xf1155,
+ 0xf1156: 0xf1156,
+ 0xf1157: 0xf1157,
+ 0xf1158: 0xf1158,
+ 0xf1159: 0xf1159,
+ 0xf115a: 0xf115a,
+ 0xf115b: 0xf115b,
+ 0xf115c: 0xf115c,
+ 0xf115d: 0xf115d,
+ 0xf115e: 0xf115e,
+ 0xf115f: 0xf115f,
+ 0xf1160: 0xf1160,
+ 0xf1161: 0xf1161,
+ 0xf1162: 0xf1162,
+ 0xf1163: 0xf1163,
+ 0xf1164: 0xf1164,
+ 0xf1165: 0xf1165,
+ 0xf1166: 0xf1166,
+ 0xf1167: 0xf1167,
+ 0xf1168: 0xf1168,
+ 0xf1169: 0xf1169,
+ 0xf116a: 0xf116a,
+ 0xf116b: 0xf116b,
+ 0xf116c: 0xf116c,
+ 0xf116d: 0xf116d,
+ 0xf116e: 0xf116e,
+ 0xf116f: 0xf116f,
+ 0xf1170: 0xf1170,
+ 0xf1171: 0xf1171,
+ 0xf1172: 0xf1172,
+ 0xf1173: 0xf1173,
+ 0xf1174: 0xf1174,
+ 0xf1175: 0xf1175,
+ 0xf1176: 0xf1176,
+ 0xf1177: 0xf1177,
+ 0xf1178: 0xf1178,
+ 0xf1179: 0xf1179,
+ 0xf117a: 0xf117a,
+ 0xf117b: 0xf117b,
+ 0xf117c: 0xf117c,
+ 0xf117d: 0xf117d,
+ 0xf117e: 0xf117e,
+ 0xf117f: 0xf117f,
+ 0xf1180: 0xf1180,
+ 0xf1181: 0xf1181,
+ 0xf1182: 0xf1182,
+ 0xf1183: 0xf1183,
+ 0xf1184: 0xf1184,
+ 0xf1185: 0xf1185,
+ 0xf1186: 0xf1186,
+ 0xf1187: 0xf1187,
+ 0xf1188: 0xf1188,
+ 0xf1189: 0xf1189,
+ 0xf118a: 0xf118a,
+ 0xf118b: 0xf118b,
+ 0xf118c: 0xf118c,
+ 0xf118d: 0xf118d,
+ 0xf118e: 0xf118e,
+ 0xf118f: 0xf118f,
+ 0xf1190: 0xf1190,
+ 0xf1191: 0xf1191,
+ 0xf1192: 0xf1192,
+ 0xf1193: 0xf1193,
+ 0xf1194: 0xf1194,
+ 0xf1195: 0xf1195,
+ 0xf1196: 0xf1196,
+ 0xf1197: 0xf1197,
+ 0xf1198: 0xf1198,
+ 0xf1199: 0xf1199,
+ 0xf119a: 0xf119a,
+ 0xf119b: 0xf119b,
+ 0xf119c: 0xf119c,
+ 0xf119d: 0xf119d,
+ 0xf119e: 0xf119e,
+ 0xf119f: 0xf119f,
+ 0xf11a0: 0xf11a0,
+ 0xf11a1: 0xf11a1,
+ 0xf11a2: 0xf11a2,
+ 0xf11a3: 0xf11a3,
+ 0xf11a4: 0xf11a4,
+ 0xf11a5: 0xf11a5,
+ 0xf11a6: 0xf11a6,
+ 0xf11a7: 0xf11a7,
+ 0xf11a8: 0xf11a8,
+ 0xf11a9: 0xf11a9,
+ 0xf11aa: 0xf11aa,
+ 0xf11ab: 0xf11ab,
+ 0xf11ac: 0xf11ac,
+ 0xf11ad: 0xf11ad,
+ 0xf11ae: 0xf11ae,
+ 0xf11af: 0xf11af,
+ 0xf11b0: 0xf11b0,
+ 0xf11b1: 0xf11b1,
+ 0xf11b2: 0xf11b2,
+ 0xf11b3: 0xf11b3,
+ 0xf11b4: 0xf11b4,
+ 0xf11b5: 0xf11b5,
+ 0xf11b6: 0xf11b6,
+ 0xf11b7: 0xf11b7,
+ 0xf11b8: 0xf11b8,
+ 0xf11b9: 0xf11b9,
+ 0xf11ba: 0xf11ba,
+ 0xf11bb: 0xf11bb,
+ 0xf11bc: 0xf11bc,
+ 0xf11bd: 0xf11bd,
+ 0xf11be: 0xf11be,
+ 0xf11bf: 0xf11bf,
+ 0xf11c0: 0xf11c0,
+ 0xf11c1: 0xf11c1,
+ 0xf11c2: 0xf11c2,
+ 0xf11c3: 0xf11c3,
+ 0xf11c4: 0xf11c4,
+ 0xf11c5: 0xf11c5,
+ 0xf11c6: 0xf11c6,
+ 0xf11c7: 0xf11c7,
+ 0xf11c8: 0xf11c8,
+ 0xf11c9: 0xf11c9,
+ 0xf11ca: 0xf11ca,
+ 0xf11cb: 0xf11cb,
+ 0xf11cc: 0xf11cc,
+ 0xf11cd: 0xf11cd,
+ 0xf11ce: 0xf11ce,
+ 0xf11cf: 0xf11cf,
+ 0xf11d0: 0xf11d0,
+ 0xf11d1: 0xf11d1,
+ 0xf11d2: 0xf11d2,
+ 0xf11d3: 0xf11d3,
+ 0xf11d4: 0xf11d4,
+ 0xf11d5: 0xf11d5,
+ 0xf11d6: 0xf11d6,
+ 0xf11d7: 0xf11d7,
+ 0xf11d8: 0xf11d8,
+ 0xf11d9: 0xf11d9,
+ 0xf11da: 0xf11da,
+ 0xf11db: 0xf11db,
+ 0xf11dc: 0xf11dc,
+ 0xf11dd: 0xf11dd,
+ 0xf11de: 0xf11de,
+ 0xf11df: 0xf11df,
+ 0xf11e0: 0xf11e0,
+ 0xf11e1: 0xf11e1,
+ 0xf11e2: 0xf11e2,
+ 0xf11e3: 0xf11e3,
+ 0xf11e4: 0xf11e4,
+ 0xf11e5: 0xf11e5,
+ 0xf11e6: 0xf11e6,
+ 0xf11e7: 0xf11e7,
+ 0xf11e8: 0xf11e8,
+ 0xf11e9: 0xf11e9,
+ 0xf11ea: 0xf11ea,
+ 0xf11eb: 0xf11eb,
+ 0xf11ec: 0xf11ec,
+ 0xf11ed: 0xf11ed,
+ 0xf11ee: 0xf11ee,
+ 0xf11ef: 0xf11ef,
+ 0xf11f0: 0xf11f0,
+ 0xf11f1: 0xf11f1,
+ 0xf11f2: 0xf11f2,
+ 0xf11f3: 0xf11f3,
+ 0xf11f4: 0xf11f4,
+ 0xf11f5: 0xf11f5,
+ 0xf11f6: 0xf11f6,
+ 0xf11f7: 0xf11f7,
+ 0xf11f8: 0xf11f8,
+ 0xf11f9: 0xf11f9,
+ 0xf11fa: 0xf11fa,
+ 0xf11fb: 0xf11fb,
+ 0xf11fc: 0xf11fc,
+ 0xf11fd: 0xf11fd,
+ 0xf11fe: 0xf11fe,
+ 0xf11ff: 0xf11ff,
+ 0xf1200: 0xf1200,
+ 0xf1201: 0xf1201,
+ 0xf1202: 0xf1202,
+ 0xf1203: 0xf1203,
+ 0xf1204: 0xf1204,
+ 0xf1205: 0xf1205,
+ 0xf1206: 0xf1206,
+ 0xf1207: 0xf1207,
+ 0xf1208: 0xf1208,
+ 0xf1209: 0xf1209,
+ 0xf120a: 0xf120a,
+ 0xf120b: 0xf120b,
+ 0xf120c: 0xf120c,
+ 0xf120d: 0xf120d,
+ 0xf120e: 0xf120e,
+ 0xf120f: 0xf120f,
+ 0xf1210: 0xf1210,
+ 0xf1211: 0xf1211,
+ 0xf1212: 0xf1212,
+ 0xf1213: 0xf1213,
+ 0xf1214: 0xf1214,
+ 0xf1215: 0xf1215,
+ 0xf1216: 0xf1216,
+ 0xf1217: 0xf1217,
+ 0xf1218: 0xf1218,
+ 0xf1219: 0xf1219,
+ 0xf121a: 0xf121a,
+ 0xf121b: 0xf121b,
+ 0xf121c: 0xf121c,
+ 0xf121d: 0xf121d,
+ 0xf121e: 0xf121e,
+ 0xf121f: 0xf121f,
+ 0xf1220: 0xf1220,
+ 0xf1221: 0xf1221,
+ 0xf1222: 0xf1222,
+ 0xf1223: 0xf1223,
+ 0xf1224: 0xf1224,
+ 0xf1225: 0xf1225,
+ 0xf1226: 0xf1226,
+ 0xf1227: 0xf1227,
+ 0xf1228: 0xf1228,
+ 0xf1229: 0xf1229,
+ 0xf122a: 0xf122a,
+ 0xf122b: 0xf122b,
+ 0xf122c: 0xf122c,
+ 0xf122d: 0xf122d,
+ 0xf122e: 0xf122e,
+ 0xf122f: 0xf122f,
+ 0xf1230: 0xf1230,
+ 0xf1231: 0xf1231,
+ 0xf1232: 0xf1232,
+ 0xf1233: 0xf1233,
+ 0xf1234: 0xf1234,
+ 0xf1235: 0xf1235,
+ 0xf1236: 0xf1236,
+ 0xf1237: 0xf1237,
+ 0xf1238: 0xf1238,
+ 0xf1239: 0xf1239,
+ 0xf123a: 0xf123a,
+ 0xf123b: 0xf123b,
+ 0xf123c: 0xf123c,
+ 0xf123d: 0xf123d,
+ 0xf123e: 0xf123e,
+ 0xf123f: 0xf123f,
+ 0xf1240: 0xf1240,
+ 0xf1241: 0xf1241,
+ 0xf1242: 0xf1242,
+ 0xf1243: 0xf1243,
+ 0xf1244: 0xf1244,
+ 0xf1245: 0xf1245,
+ 0xf1246: 0xf1246,
+ 0xf1247: 0xf1247,
+ 0xf1248: 0xf1248,
+ 0xf1249: 0xf1249,
+ 0xf124a: 0xf124a,
+ 0xf124b: 0xf124b,
+ 0xf124c: 0xf124c,
+ 0xf124d: 0xf124d,
+ 0xf124e: 0xf124e,
+ 0xf124f: 0xf124f,
+ 0xf1250: 0xf1250,
+ 0xf1251: 0xf1251,
+ 0xf1252: 0xf1252,
+ 0xf1253: 0xf1253,
+ 0xf1254: 0xf1254,
+ 0xf1255: 0xf1255,
+ 0xf1256: 0xf1256,
+ 0xf1257: 0xf1257,
+ 0xf1258: 0xf1258,
+ 0xf1259: 0xf1259,
+ 0xf125a: 0xf125a,
+ 0xf125b: 0xf125b,
+ 0xf125c: 0xf125c,
+ 0xf125d: 0xf125d,
+ 0xf125e: 0xf125e,
+ 0xf125f: 0xf125f,
+ 0xf1260: 0xf1260,
+ 0xf1261: 0xf1261,
+ 0xf1262: 0xf1262,
+ 0xf1263: 0xf1263,
+ 0xf1264: 0xf1264,
+ 0xf1265: 0xf1265,
+ 0xf1266: 0xf1266,
+ 0xf1267: 0xf1267,
+ 0xf1268: 0xf1268,
+ 0xf1269: 0xf1269,
+ 0xf126a: 0xf126a,
+ 0xf126b: 0xf126b,
+ 0xf126c: 0xf126c,
+ 0xf126d: 0xf126d,
+ 0xf126e: 0xf126e,
+ 0xf126f: 0xf126f,
+ 0xf1270: 0xf1270,
+ 0xf1271: 0xf1271,
+ 0xf1272: 0xf1272,
+ 0xf1273: 0xf1273,
+ 0xf1274: 0xf1274,
+ 0xf1275: 0xf1275,
+ 0xf1276: 0xf1276,
+ 0xf1277: 0xf1277,
+ 0xf1278: 0xf1278,
+ 0xf1279: 0xf1279,
+ 0xf127a: 0xf127a,
+ 0xf127b: 0xf127b,
+ 0xf127c: 0xf127c,
+ 0xf127d: 0xf127d,
+ 0xf127e: 0xf127e,
+ 0xf127f: 0xf127f,
+ 0xf1280: 0xf1280,
+ 0xf1281: 0xf1281,
+ 0xf1282: 0xf1282,
+ 0xf1283: 0xf1283,
+ 0xf1284: 0xf1284,
+ 0xf1285: 0xf1285,
+ 0xf1286: 0xf1286,
+ 0xf1287: 0xf1287,
+ 0xf1288: 0xf1288,
+ 0xf1289: 0xf1289,
+ 0xf128a: 0xf128a,
+ 0xf128b: 0xf128b,
+ 0xf128c: 0xf128c,
+ 0xf128d: 0xf128d,
+ 0xf128e: 0xf128e,
+ 0xf128f: 0xf128f,
+ 0xf1290: 0xf1290,
+ 0xf1291: 0xf1291,
+ 0xf1292: 0xf1292,
+ 0xf1293: 0xf1293,
+ 0xf1294: 0xf1294,
+ 0xf1295: 0xf1295,
+ 0xf1296: 0xf1296,
+ 0xf1297: 0xf1297,
+ 0xf1298: 0xf1298,
+ 0xf1299: 0xf1299,
+ 0xf129a: 0xf129a,
+ 0xf129b: 0xf129b,
+ 0xf129c: 0xf129c,
+ 0xf129d: 0xf129d,
+ 0xf129e: 0xf129e,
+ 0xf129f: 0xf129f,
+ 0xf12a0: 0xf12a0,
+ 0xf12a1: 0xf12a1,
+ 0xf12a2: 0xf12a2,
+ 0xf12a3: 0xf12a3,
+ 0xf12a4: 0xf12a4,
+ 0xf12a5: 0xf12a5,
+ 0xf12a6: 0xf12a6,
+ 0xf12a7: 0xf12a7,
+ 0xf12a8: 0xf12a8,
+ 0xf12a9: 0xf12a9,
+ 0xf12aa: 0xf12aa,
+ 0xf12ab: 0xf12ab,
+ 0xf12ac: 0xf12ac,
+ 0xf12ad: 0xf12ad,
+ 0xf12ae: 0xf12ae,
+ 0xf12af: 0xf12af,
+ 0xf12b0: 0xf12b0,
+ 0xf12b1: 0xf12b1,
+ 0xf12b2: 0xf12b2,
+ 0xf12b3: 0xf12b3,
+ 0xf12b4: 0xf12b4,
+ 0xf12b5: 0xf12b5,
+ 0xf12b6: 0xf12b6,
+ 0xf12b7: 0xf12b7,
+ 0xf12b8: 0xf12b8,
+ 0xf12b9: 0xf12b9,
+ 0xf12ba: 0xf12ba,
+ 0xf12bb: 0xf12bb,
+ 0xf12bc: 0xf12bc,
+ 0xf12bd: 0xf12bd,
+ 0xf12be: 0xf12be,
+ 0xf12bf: 0xf12bf,
+ 0xf12c0: 0xf12c0,
+ 0xf12c1: 0xf12c1,
+ 0xf12c2: 0xf12c2,
+ 0xf12c3: 0xf12c3,
+ 0xf12c4: 0xf12c4,
+ 0xf12c5: 0xf12c5,
+ 0xf12c6: 0xf12c6,
+ 0xf12c7: 0xf12c7,
+ 0xf12c8: 0xf12c8,
+ 0xf12c9: 0xf12c9,
+ 0xf12ca: 0xf12ca,
+ 0xf12cb: 0xf12cb,
+ 0xf12cc: 0xf12cc,
+ 0xf12cd: 0xf12cd,
+ 0xf12ce: 0xf12ce,
+ 0xf12cf: 0xf12cf,
+ 0xf12d0: 0xf12d0,
+ 0xf12d1: 0xf12d1,
+ 0xf12d2: 0xf12d2,
+ 0xf12d3: 0xf12d3,
+ 0xf12d4: 0xf12d4,
+ 0xf12d5: 0xf12d5,
+ 0xf12d6: 0xf12d6,
+ 0xf12d7: 0xf12d7,
+ 0xf12d8: 0xf12d8,
+ 0xf12d9: 0xf12d9,
+ 0xf12da: 0xf12da,
+ 0xf12db: 0xf12db,
+ 0xf12dc: 0xf12dc,
+ 0xf12dd: 0xf12dd,
+ 0xf12de: 0xf12de,
+ 0xf12df: 0xf12df,
+ 0xf12e0: 0xf12e0,
+ 0xf12e1: 0xf12e1,
+ 0xf12e2: 0xf12e2,
+ 0xf12e3: 0xf12e3,
+ 0xf12e4: 0xf12e4,
+ 0xf12e5: 0xf12e5,
+ 0xf12e6: 0xf12e6,
+ 0xf12e7: 0xf12e7,
+ 0xf12e8: 0xf12e8,
+ 0xf12e9: 0xf12e9,
+ 0xf12ea: 0xf12ea,
+ 0xf12eb: 0xf12eb,
+ 0xf12ec: 0xf12ec,
+ 0xf12ed: 0xf12ed,
+ 0xf12ee: 0xf12ee,
+ 0xf12ef: 0xf12ef,
+ 0xf12f0: 0xf12f0,
+ 0xf12f1: 0xf12f1,
+ 0xf12f2: 0xf12f2,
+ 0xf12f3: 0xf12f3,
+ 0xf12f4: 0xf12f4,
+ 0xf12f5: 0xf12f5,
+ 0xf12f6: 0xf12f6,
+ 0xf12f7: 0xf12f7,
+ 0xf12f8: 0xf12f8,
+ 0xf12f9: 0xf12f9,
+ 0xf12fa: 0xf12fa,
+ 0xf12fb: 0xf12fb,
+ 0xf12fc: 0xf12fc,
+ 0xf12fd: 0xf12fd,
+ 0xf12fe: 0xf12fe,
+ 0xf12ff: 0xf12ff,
+ 0xf1300: 0xf1300,
+ 0xf1301: 0xf1301,
+ 0xf1302: 0xf1302,
+ 0xf1303: 0xf1303,
+ 0xf1304: 0xf1304,
+ 0xf1305: 0xf1305,
+ 0xf1306: 0xf1306,
+ 0xf1307: 0xf1307,
+ 0xf1308: 0xf1308,
+ 0xf1309: 0xf1309,
+ 0xf130a: 0xf130a,
+ 0xf130b: 0xf130b,
+ 0xf130c: 0xf130c,
+ 0xf130d: 0xf130d,
+ 0xf130e: 0xf130e,
+ 0xf130f: 0xf130f,
+ 0xf1310: 0xf1310,
+ 0xf1311: 0xf1311,
+ 0xf1312: 0xf1312,
+ 0xf1313: 0xf1313,
+ 0xf1314: 0xf1314,
+ 0xf1315: 0xf1315,
+ 0xf1316: 0xf1316,
+ 0xf1317: 0xf1317,
+ 0xf1318: 0xf1318,
+ 0xf1319: 0xf1319,
+ 0xf131a: 0xf131a,
+ 0xf131b: 0xf131b,
+ 0xf131c: 0xf131c,
+ 0xf131d: 0xf131d,
+ 0xf131e: 0xf131e,
+ 0xf131f: 0xf131f,
+ 0xf1320: 0xf1320,
+ 0xf1321: 0xf1321,
+ 0xf1322: 0xf1322,
+ 0xf1323: 0xf1323,
+ 0xf1324: 0xf1324,
+ 0xf1325: 0xf1325,
+ 0xf1326: 0xf1326,
+ 0xf1327: 0xf1327,
+ 0xf1328: 0xf1328,
+ 0xf1329: 0xf1329,
+ 0xf132a: 0xf132a,
+ 0xf132b: 0xf132b,
+ 0xf132c: 0xf132c,
+ 0xf132d: 0xf132d,
+ 0xf132e: 0xf132e,
+ 0xf132f: 0xf132f,
+ 0xf1330: 0xf1330,
+ 0xf1331: 0xf1331,
+ 0xf1332: 0xf1332,
+ 0xf1333: 0xf1333,
+ 0xf1334: 0xf1334,
+ 0xf1335: 0xf1335,
+ 0xf1336: 0xf1336,
+ 0xf1337: 0xf1337,
+ 0xf1338: 0xf1338,
+ 0xf1339: 0xf1339,
+ 0xf133a: 0xf133a,
+ 0xf133b: 0xf133b,
+ 0xf133c: 0xf133c,
+ 0xf133d: 0xf133d,
+ 0xf133e: 0xf133e,
+ 0xf133f: 0xf133f,
+ 0xf1340: 0xf1340,
+ 0xf1341: 0xf1341,
+ 0xf1342: 0xf1342,
+ 0xf1343: 0xf1343,
+ 0xf1344: 0xf1344,
+ 0xf1345: 0xf1345,
+ 0xf1346: 0xf1346,
+ 0xf1347: 0xf1347,
+ 0xf1348: 0xf1348,
+ 0xf1349: 0xf1349,
+ 0xf134a: 0xf134a,
+ 0xf134b: 0xf134b,
+ 0xf134c: 0xf134c,
+ 0xf134d: 0xf134d,
+ 0xf134e: 0xf134e,
+ 0xf134f: 0xf134f,
+ 0xf1350: 0xf1350,
+ 0xf1351: 0xf1351,
+ 0xf1352: 0xf1352,
+ 0xf1353: 0xf1353,
+ 0xf1354: 0xf1354,
+ 0xf1355: 0xf1355,
+ 0xf1356: 0xf1356,
+ 0xf1357: 0xf1357,
+ 0xf1358: 0xf1358,
+ 0xf1359: 0xf1359,
+ 0xf135a: 0xf135a,
+ 0xf135b: 0xf135b,
+ 0xf135c: 0xf135c,
+ 0xf135d: 0xf135d,
+ 0xf135e: 0xf135e,
+ 0xf135f: 0xf135f,
+ 0xf1360: 0xf1360,
+ 0xf1361: 0xf1361,
+ 0xf1362: 0xf1362,
+ 0xf1363: 0xf1363,
+ 0xf1364: 0xf1364,
+ 0xf1365: 0xf1365,
+ 0xf1366: 0xf1366,
+ 0xf1367: 0xf1367,
+ 0xf1368: 0xf1368,
+ 0xf1369: 0xf1369,
+ 0xf136a: 0xf136a,
+ 0xf136b: 0xf136b,
+ 0xf136c: 0xf136c,
+ 0xf136d: 0xf136d,
+ 0xf136e: 0xf136e,
+ 0xf136f: 0xf136f,
+ 0xf1370: 0xf1370,
+ 0xf1371: 0xf1371,
+ 0xf1372: 0xf1372,
+ 0xf1373: 0xf1373,
+ 0xf1374: 0xf1374,
+ 0xf1375: 0xf1375,
+ 0xf1376: 0xf1376,
+ 0xf1377: 0xf1377,
+ 0xf1378: 0xf1378,
+ 0xf1379: 0xf1379,
+ 0xf137a: 0xf137a,
+ 0xf137b: 0xf137b,
+ 0xf137c: 0xf137c,
+ 0xf137d: 0xf137d,
+ 0xf137e: 0xf137e,
+ 0xf137f: 0xf137f,
+ 0xf1380: 0xf1380,
+ 0xf1381: 0xf1381,
+ 0xf1382: 0xf1382,
+ 0xf1383: 0xf1383,
+ 0xf1384: 0xf1384,
+ 0xf1385: 0xf1385,
+ 0xf1386: 0xf1386,
+ 0xf1387: 0xf1387,
+ 0xf1388: 0xf1388,
+ 0xf1389: 0xf1389,
+ 0xf138a: 0xf138a,
+ 0xf138b: 0xf138b,
+ 0xf138c: 0xf138c,
+ 0xf138d: 0xf138d,
+ 0xf138e: 0xf138e,
+ 0xf138f: 0xf138f,
+ 0xf1390: 0xf1390,
+ 0xf1391: 0xf1391,
+ 0xf1392: 0xf1392,
+ 0xf1393: 0xf1393,
+ 0xf1394: 0xf1394,
+ 0xf1395: 0xf1395,
+ 0xf1396: 0xf1396,
+ 0xf1397: 0xf1397,
+ 0xf1398: 0xf1398,
+ 0xf1399: 0xf1399,
+ 0xf139a: 0xf139a,
+ 0xf139b: 0xf139b,
+ 0xf139c: 0xf139c,
+ 0xf139d: 0xf139d,
+ 0xf139e: 0xf139e,
+ 0xf139f: 0xf139f,
+ 0xf13a0: 0xf13a0,
+ 0xf13a1: 0xf13a1,
+ 0xf13a2: 0xf13a2,
+ 0xf13a3: 0xf13a3,
+ 0xf13a4: 0xf13a4,
+ 0xf13a5: 0xf13a5,
+ 0xf13a6: 0xf13a6,
+ 0xf13a7: 0xf13a7,
+ 0xf13a8: 0xf13a8,
+ 0xf13a9: 0xf13a9,
+ 0xf13aa: 0xf13aa,
+ 0xf13ab: 0xf13ab,
+ 0xf13ac: 0xf13ac,
+ 0xf13ad: 0xf13ad,
+ 0xf13ae: 0xf13ae,
+ 0xf13af: 0xf13af,
+ 0xf13b0: 0xf13b0,
+ 0xf13b1: 0xf13b1,
+ 0xf13b2: 0xf13b2,
+ 0xf13b3: 0xf13b3,
+ 0xf13b4: 0xf13b4,
+ 0xf13b5: 0xf13b5,
+ 0xf13b6: 0xf13b6,
+ 0xf13b7: 0xf13b7,
+ 0xf13b8: 0xf13b8,
+ 0xf13b9: 0xf13b9,
+ 0xf13ba: 0xf13ba,
+ 0xf13bb: 0xf13bb,
+ 0xf13bc: 0xf13bc,
+ 0xf13bd: 0xf13bd,
+ 0xf13be: 0xf13be,
+ 0xf13bf: 0xf13bf,
+ 0xf13c0: 0xf13c0,
+ 0xf13c1: 0xf13c1,
+ 0xf13c2: 0xf13c2,
+ 0xf13c3: 0xf13c3,
+ 0xf13c4: 0xf13c4,
+ 0xf13c5: 0xf13c5,
+ 0xf13c6: 0xf13c6,
+ 0xf13c7: 0xf13c7,
+ 0xf13c8: 0xf13c8,
+ 0xf13c9: 0xf13c9,
+ 0xf13ca: 0xf13ca,
+ 0xf13cb: 0xf13cb,
+ 0xf13cc: 0xf13cc,
+ 0xf13cd: 0xf13cd,
+ 0xf13ce: 0xf13ce,
+ 0xf13cf: 0xf13cf,
+ 0xf13d0: 0xf13d0,
+ 0xf13d1: 0xf13d1,
+ 0xf13d2: 0xf13d2,
+ 0xf13d3: 0xf13d3,
+ 0xf13d4: 0xf13d4,
+ 0xf13d5: 0xf13d5,
+ 0xf13d6: 0xf13d6,
+ 0xf13d7: 0xf13d7,
+ 0xf13d8: 0xf13d8,
+ 0xf13d9: 0xf13d9,
+ 0xf13da: 0xf13da,
+ 0xf13db: 0xf13db,
+ 0xf13dc: 0xf13dc,
+ 0xf13dd: 0xf13dd,
+ 0xf13de: 0xf13de,
+ 0xf13df: 0xf13df,
+ 0xf13e0: 0xf13e0,
+ 0xf13e1: 0xf13e1,
+ 0xf13e2: 0xf13e2,
+ 0xf13e3: 0xf13e3,
+ 0xf13e4: 0xf13e4,
+ 0xf13e5: 0xf13e5,
+ 0xf13e6: 0xf13e6,
+ 0xf13e7: 0xf13e7,
+ 0xf13e8: 0xf13e8,
+ 0xf13e9: 0xf13e9,
+ 0xf13ea: 0xf13ea,
+ 0xf13eb: 0xf13eb,
+ 0xf13ec: 0xf13ec,
+ 0xf13ed: 0xf13ed,
+ 0xf13ee: 0xf13ee,
+ 0xf13ef: 0xf13ef,
+ 0xf13f0: 0xf13f0,
+ 0xf13f1: 0xf13f1,
+ 0xf13f2: 0xf13f2,
+ 0xf13f3: 0xf13f3,
+ 0xf13f4: 0xf13f4,
+ 0xf13f5: 0xf13f5,
+ 0xf13f6: 0xf13f6,
+ 0xf13f7: 0xf13f7,
+ 0xf13f8: 0xf13f8,
+ 0xf13f9: 0xf13f9,
+ 0xf13fa: 0xf13fa,
+ 0xf13fb: 0xf13fb,
+ 0xf13fc: 0xf13fc,
+ 0xf13fd: 0xf13fd,
+ 0xf13fe: 0xf13fe,
+ 0xf13ff: 0xf13ff,
+ 0xf1400: 0xf1400,
+ 0xf1401: 0xf1401,
+ 0xf1402: 0xf1402,
+ 0xf1403: 0xf1403,
+ 0xf1404: 0xf1404,
+ 0xf1405: 0xf1405,
+ 0xf1406: 0xf1406,
+ 0xf1407: 0xf1407,
+ 0xf1408: 0xf1408,
+ 0xf1409: 0xf1409,
+ 0xf140a: 0xf140a,
+ 0xf140b: 0xf140b,
+ 0xf140c: 0xf140c,
+ 0xf140d: 0xf140d,
+ 0xf140e: 0xf140e,
+ 0xf140f: 0xf140f,
+ 0xf1410: 0xf1410,
+ 0xf1411: 0xf1411,
+ 0xf1412: 0xf1412,
+ 0xf1413: 0xf1413,
+ 0xf1414: 0xf1414,
+ 0xf1415: 0xf1415,
+ 0xf1416: 0xf1416,
+ 0xf1417: 0xf1417,
+ 0xf1418: 0xf1418,
+ 0xf1419: 0xf1419,
+ 0xf141a: 0xf141a,
+ 0xf141b: 0xf141b,
+ 0xf141c: 0xf141c,
+ 0xf141d: 0xf141d,
+ 0xf141e: 0xf141e,
+ 0xf141f: 0xf141f,
+ 0xf1420: 0xf1420,
+ 0xf1421: 0xf1421,
+ 0xf1422: 0xf1422,
+ 0xf1423: 0xf1423,
+ 0xf1424: 0xf1424,
+ 0xf1425: 0xf1425,
+ 0xf1426: 0xf1426,
+ 0xf1427: 0xf1427,
+ 0xf1428: 0xf1428,
+ 0xf1429: 0xf1429,
+ 0xf142a: 0xf142a,
+ 0xf142b: 0xf142b,
+ 0xf142c: 0xf142c,
+ 0xf142d: 0xf142d,
+ 0xf142e: 0xf142e,
+ 0xf142f: 0xf142f,
+ 0xf1430: 0xf1430,
+ 0xf1431: 0xf1431,
+ 0xf1432: 0xf1432,
+ 0xf1433: 0xf1433,
+ 0xf1434: 0xf1434,
+ 0xf1435: 0xf1435,
+ 0xf1436: 0xf1436,
+ 0xf1437: 0xf1437,
+ 0xf1438: 0xf1438,
+ 0xf1439: 0xf1439,
+ 0xf143a: 0xf143a,
+ 0xf143b: 0xf143b,
+ 0xf143c: 0xf143c,
+ 0xf143d: 0xf143d,
+ 0xf143e: 0xf143e,
+ 0xf143f: 0xf143f,
+ 0xf1440: 0xf1440,
+ 0xf1441: 0xf1441,
+ 0xf1442: 0xf1442,
+ 0xf1443: 0xf1443,
+ 0xf1444: 0xf1444,
+ 0xf1445: 0xf1445,
+ 0xf1446: 0xf1446,
+ 0xf1447: 0xf1447,
+ 0xf1448: 0xf1448,
+ 0xf1449: 0xf1449,
+ 0xf144a: 0xf144a,
+ 0xf144b: 0xf144b,
+ 0xf144c: 0xf144c,
+ 0xf144d: 0xf144d,
+ 0xf144e: 0xf144e,
+ 0xf144f: 0xf144f,
+ 0xf1450: 0xf1450,
+ 0xf1451: 0xf1451,
+ 0xf1452: 0xf1452,
+ 0xf1453: 0xf1453,
+ 0xf1454: 0xf1454,
+ 0xf1455: 0xf1455,
+ 0xf1456: 0xf1456,
+ 0xf1457: 0xf1457,
+ 0xf1458: 0xf1458,
+ 0xf1459: 0xf1459,
+ 0xf145a: 0xf145a,
+ 0xf145b: 0xf145b,
+ 0xf145c: 0xf145c,
+ 0xf145d: 0xf145d,
+ 0xf145e: 0xf145e,
+ 0xf145f: 0xf145f,
+ 0xf1460: 0xf1460,
+ 0xf1461: 0xf1461,
+ 0xf1462: 0xf1462,
+ 0xf1463: 0xf1463,
+ 0xf1464: 0xf1464,
+ 0xf1465: 0xf1465,
+ 0xf1466: 0xf1466,
+ 0xf1467: 0xf1467,
+ 0xf1468: 0xf1468,
+ 0xf1469: 0xf1469,
+ 0xf146a: 0xf146a,
+ 0xf146b: 0xf146b,
+ 0xf146c: 0xf146c,
+ 0xf146d: 0xf146d,
+ 0xf146e: 0xf146e,
+ 0xf146f: 0xf146f,
+ 0xf1470: 0xf1470,
+ 0xf1471: 0xf1471,
+ 0xf1472: 0xf1472,
+ 0xf1473: 0xf1473,
+ 0xf1474: 0xf1474,
+ 0xf1475: 0xf1475,
+ 0xf1476: 0xf1476,
+ 0xf1477: 0xf1477,
+ 0xf1478: 0xf1478,
+ 0xf1479: 0xf1479,
+ 0xf147a: 0xf147a,
+ 0xf147b: 0xf147b,
+ 0xf147c: 0xf147c,
+ 0xf147d: 0xf147d,
+ 0xf147e: 0xf147e,
+ 0xf147f: 0xf147f,
+ 0xf1480: 0xf1480,
+ 0xf1481: 0xf1481,
+ 0xf1482: 0xf1482,
+ 0xf1483: 0xf1483,
+ 0xf1484: 0xf1484,
+ 0xf1485: 0xf1485,
+ 0xf1486: 0xf1486,
+ 0xf1487: 0xf1487,
+ 0xf1488: 0xf1488,
+ 0xf1489: 0xf1489,
+ 0xf148a: 0xf148a,
+ 0xf148b: 0xf148b,
+ 0xf148c: 0xf148c,
+ 0xf148d: 0xf148d,
+ 0xf148e: 0xf148e,
+ 0xf148f: 0xf148f,
+ 0xf1490: 0xf1490,
+ 0xf1491: 0xf1491,
+ 0xf1492: 0xf1492,
+ 0xf1493: 0xf1493,
+ 0xf1494: 0xf1494,
+ 0xf1495: 0xf1495,
+ 0xf1496: 0xf1496,
+ 0xf1497: 0xf1497,
+ 0xf1498: 0xf1498,
+ 0xf1499: 0xf1499,
+ 0xf149a: 0xf149a,
+ 0xf149b: 0xf149b,
+ 0xf149c: 0xf149c,
+ 0xf149d: 0xf149d,
+ 0xf149e: 0xf149e,
+ 0xf149f: 0xf149f,
+ 0xf14a0: 0xf14a0,
+ 0xf14a1: 0xf14a1,
+ 0xf14a2: 0xf14a2,
+ 0xf14a3: 0xf14a3,
+ 0xf14a4: 0xf14a4,
+ 0xf14a5: 0xf14a5,
+ 0xf14a6: 0xf14a6,
+ 0xf14a7: 0xf14a7,
+ 0xf14a8: 0xf14a8,
+ 0xf14a9: 0xf14a9,
+ 0xf14aa: 0xf14aa,
+ 0xf14ab: 0xf14ab,
+ 0xf14ac: 0xf14ac,
+ 0xf14ad: 0xf14ad,
+ 0xf14ae: 0xf14ae,
+ 0xf14af: 0xf14af,
+ 0xf14b0: 0xf14b0,
+ 0xf14b1: 0xf14b1,
+ 0xf14b2: 0xf14b2,
+ 0xf14b3: 0xf14b3,
+ 0xf14b4: 0xf14b4,
+ 0xf14b5: 0xf14b5,
+ 0xf14b6: 0xf14b6,
+ 0xf14b7: 0xf14b7,
+ 0xf14b8: 0xf14b8,
+ 0xf14b9: 0xf14b9,
+ 0xf14ba: 0xf14ba,
+ 0xf14bb: 0xf14bb,
+ 0xf14bc: 0xf14bc,
+ 0xf14bd: 0xf14bd,
+ 0xf14be: 0xf14be,
+ 0xf14bf: 0xf14bf,
+ 0xf14c0: 0xf14c0,
+ 0xf14c1: 0xf14c1,
+ 0xf14c2: 0xf14c2,
+ 0xf14c3: 0xf14c3,
+ 0xf14c4: 0xf14c4,
+ 0xf14c5: 0xf14c5,
+ 0xf14c6: 0xf14c6,
+ 0xf14c7: 0xf14c7,
+ 0xf14c8: 0xf14c8,
+ 0xf14c9: 0xf14c9,
+ 0xf14ca: 0xf14ca,
+ 0xf14cb: 0xf14cb,
+ 0xf14cc: 0xf14cc,
+ 0xf14cd: 0xf14cd,
+ 0xf14ce: 0xf14ce,
+ 0xf14cf: 0xf14cf,
+ 0xf14d0: 0xf14d0,
+ 0xf14d1: 0xf14d1,
+ 0xf14d2: 0xf14d2,
+ 0xf14d3: 0xf14d3,
+ 0xf14d4: 0xf14d4,
+ 0xf14d5: 0xf14d5,
+ 0xf14d6: 0xf14d6,
+ 0xf14d7: 0xf14d7,
+ 0xf14d8: 0xf14d8,
+ 0xf14d9: 0xf14d9,
+ 0xf14da: 0xf14da,
+ 0xf14db: 0xf14db,
+ 0xf14dc: 0xf14dc,
+ 0xf14dd: 0xf14dd,
+ 0xf14de: 0xf14de,
+ 0xf14df: 0xf14df,
+ 0xf14e0: 0xf14e0,
+ 0xf14e1: 0xf14e1,
+ 0xf14e2: 0xf14e2,
+ 0xf14e3: 0xf14e3,
+ 0xf14e4: 0xf14e4,
+ 0xf14e5: 0xf14e5,
+ 0xf14e6: 0xf14e6,
+ 0xf14e7: 0xf14e7,
+ 0xf14e8: 0xf14e8,
+ 0xf14e9: 0xf14e9,
+ 0xf14ea: 0xf14ea,
+ 0xf14eb: 0xf14eb,
+ 0xf14ec: 0xf14ec,
+ 0xf14ed: 0xf14ed,
+ 0xf14ee: 0xf14ee,
+ 0xf14ef: 0xf14ef,
+ 0xf14f0: 0xf14f0,
+ 0xf14f1: 0xf14f1,
+ 0xf14f2: 0xf14f2,
+ 0xf14f3: 0xf14f3,
+ 0xf14f4: 0xf14f4,
+ 0xf14f5: 0xf14f5,
+ 0xf14f6: 0xf14f6,
+ 0xf14f7: 0xf14f7,
+ 0xf14f8: 0xf14f8,
+ 0xf14f9: 0xf14f9,
+ 0xf14fa: 0xf14fa,
+ 0xf14fb: 0xf14fb,
+ 0xf14fc: 0xf14fc,
+ 0xf14fd: 0xf14fd,
+ 0xf14fe: 0xf14fe,
+ 0xf14ff: 0xf14ff,
+ 0xf1500: 0xf1500,
+ 0xf1501: 0xf1501,
+ 0xf1502: 0xf1502,
+ 0xf1503: 0xf1503,
+ 0xf1504: 0xf1504,
+ 0xf1505: 0xf1505,
+ 0xf1506: 0xf1506,
+ 0xf1507: 0xf1507,
+ 0xf1508: 0xf1508,
+ 0xf1509: 0xf1509,
+ 0xf150a: 0xf150a,
+ 0xf150b: 0xf150b,
+ 0xf150c: 0xf150c,
+ 0xf150d: 0xf150d,
+ 0xf150e: 0xf150e,
+ 0xf150f: 0xf150f,
+ 0xf1510: 0xf1510,
+ 0xf1511: 0xf1511,
+ 0xf1512: 0xf1512,
+ 0xf1513: 0xf1513,
+ 0xf1514: 0xf1514,
+ 0xf1515: 0xf1515,
+ 0xf1516: 0xf1516,
+ 0xf1517: 0xf1517,
+ 0xf1518: 0xf1518,
+ 0xf1519: 0xf1519,
+ 0xf151a: 0xf151a,
+ 0xf151b: 0xf151b,
+ 0xf151c: 0xf151c,
+ 0xf151d: 0xf151d,
+ 0xf151e: 0xf151e,
+ 0xf151f: 0xf151f,
+ 0xf1520: 0xf1520,
+ 0xf1521: 0xf1521,
+ 0xf1522: 0xf1522,
+ 0xf1523: 0xf1523,
+ 0xf1524: 0xf1524,
+ 0xf1525: 0xf1525,
+ 0xf1526: 0xf1526,
+ 0xf1527: 0xf1527,
+ 0xf1528: 0xf1528,
+ 0xf1529: 0xf1529,
+ 0xf152a: 0xf152a,
+ 0xf152b: 0xf152b,
+ 0xf152c: 0xf152c,
+ 0xf152d: 0xf152d,
+ 0xf152e: 0xf152e,
+ 0xf152f: 0xf152f,
+ 0xf1530: 0xf1530,
+ 0xf1531: 0xf1531,
+ 0xf1532: 0xf1532,
+ 0xf1533: 0xf1533,
+ 0xf1534: 0xf1534,
+ 0xf1535: 0xf1535,
+ 0xf1536: 0xf1536,
+ 0xf1537: 0xf1537,
+ 0xf1538: 0xf1538,
+ 0xf1539: 0xf1539,
+ 0xf153a: 0xf153a,
+ 0xf153b: 0xf153b,
+ 0xf153c: 0xf153c,
+ 0xf153d: 0xf153d,
+ 0xf153e: 0xf153e,
+ 0xf153f: 0xf153f,
+ 0xf1540: 0xf1540,
+ 0xf1541: 0xf1541,
+ 0xf1542: 0xf1542,
+ 0xf1543: 0xf1543,
+ 0xf1544: 0xf1544,
+ 0xf1545: 0xf1545,
+ 0xf1546: 0xf1546,
+ 0xf1547: 0xf1547,
+ 0xf1548: 0xf1548,
+ 0xf1549: 0xf1549,
+ 0xf154a: 0xf154a,
+ 0xf154b: 0xf154b,
+ 0xf154c: 0xf154c,
+ 0xf154d: 0xf154d,
+ 0xf154e: 0xf154e,
+ 0xf154f: 0xf154f,
+ 0xf1550: 0xf1550,
+ 0xf1551: 0xf1551,
+ 0xf1552: 0xf1552,
+ 0xf1553: 0xf1553,
+ 0xf1554: 0xf1554,
+ 0xf1555: 0xf1555,
+ 0xf1556: 0xf1556,
+ 0xf1557: 0xf1557,
+ 0xf1558: 0xf1558,
+ 0xf1559: 0xf1559,
+ 0xf155a: 0xf155a,
+ 0xf155b: 0xf155b,
+ 0xf155c: 0xf155c,
+ 0xf155d: 0xf155d,
+ 0xf155e: 0xf155e,
+ 0xf155f: 0xf155f,
+ 0xf1560: 0xf1560,
+ 0xf1561: 0xf1561,
+ 0xf1562: 0xf1562,
+ 0xf1563: 0xf1563,
+ 0xf1564: 0xf1564,
+ 0xf1565: 0xf1565,
+ 0xf1566: 0xf1566,
+ 0xf1567: 0xf1567,
+ 0xf1568: 0xf1568,
+ 0xf1569: 0xf1569,
+ 0xf156a: 0xf156a,
+ 0xf156b: 0xf156b,
+ 0xf156c: 0xf156c,
+ 0xf156d: 0xf156d,
+ 0xf156e: 0xf156e,
+ 0xf156f: 0xf156f,
+ 0xf1570: 0xf1570,
+ 0xf1571: 0xf1571,
+ 0xf1572: 0xf1572,
+ 0xf1573: 0xf1573,
+ 0xf1574: 0xf1574,
+ 0xf1575: 0xf1575,
+ 0xf1576: 0xf1576,
+ 0xf1577: 0xf1577,
+ 0xf1578: 0xf1578,
+ 0xf1579: 0xf1579,
+ 0xf157a: 0xf157a,
+ 0xf157b: 0xf157b,
+ 0xf157c: 0xf157c,
+ 0xf157d: 0xf157d,
+ 0xf157e: 0xf157e,
+ 0xf157f: 0xf157f,
+ 0xf1580: 0xf1580,
+ 0xf1581: 0xf1581,
+ 0xf1582: 0xf1582,
+ 0xf1583: 0xf1583,
+ 0xf1584: 0xf1584,
+ 0xf1585: 0xf1585,
+ 0xf1586: 0xf1586,
+ 0xf1587: 0xf1587,
+ 0xf1588: 0xf1588,
+ 0xf1589: 0xf1589,
+ 0xf158a: 0xf158a,
+ 0xf158b: 0xf158b,
+ 0xf158c: 0xf158c,
+ 0xf158d: 0xf158d,
+ 0xf158e: 0xf158e,
+ 0xf158f: 0xf158f,
+ 0xf1590: 0xf1590,
+ 0xf1591: 0xf1591,
+ 0xf1592: 0xf1592,
+ 0xf1593: 0xf1593,
+ 0xf1594: 0xf1594,
+ 0xf1595: 0xf1595,
+ 0xf1596: 0xf1596,
+ 0xf1597: 0xf1597,
+ 0xf1598: 0xf1598,
+ 0xf1599: 0xf1599,
+ 0xf159a: 0xf159a,
+ 0xf159b: 0xf159b,
+ 0xf159c: 0xf159c,
+ 0xf159d: 0xf159d,
+ 0xf159e: 0xf159e,
+ 0xf159f: 0xf159f,
+ 0xf15a0: 0xf15a0,
+ 0xf15a1: 0xf15a1,
+ 0xf15a2: 0xf15a2,
+ 0xf15a3: 0xf15a3,
+ 0xf15a4: 0xf15a4,
+ 0xf15a5: 0xf15a5,
+ 0xf15a6: 0xf15a6,
+ 0xf15a7: 0xf15a7,
+ 0xf15a8: 0xf15a8,
+ 0xf15a9: 0xf15a9,
+ 0xf15aa: 0xf15aa,
+ 0xf15ab: 0xf15ab,
+ 0xf15ac: 0xf15ac,
+ 0xf15ad: 0xf15ad,
+ 0xf15ae: 0xf15ae,
+ 0xf15af: 0xf15af,
+ 0xf15b0: 0xf15b0,
+ 0xf15b1: 0xf15b1,
+ 0xf15b2: 0xf15b2,
+ 0xf15b3: 0xf15b3,
+ 0xf15b4: 0xf15b4,
+ 0xf15b5: 0xf15b5,
+ 0xf15b6: 0xf15b6,
+ 0xf15b7: 0xf15b7,
+ 0xf15b8: 0xf15b8,
+ 0xf15b9: 0xf15b9,
+ 0xf15ba: 0xf15ba,
+ 0xf15bb: 0xf15bb,
+ 0xf15bc: 0xf15bc,
+ 0xf15bd: 0xf15bd,
+ 0xf15be: 0xf15be,
+ 0xf15bf: 0xf15bf,
+ 0xf15c0: 0xf15c0,
+ 0xf15c1: 0xf15c1,
+ 0xf15c2: 0xf15c2,
+ 0xf15c3: 0xf15c3,
+ 0xf15c4: 0xf15c4,
+ 0xf15c5: 0xf15c5,
+ 0xf15c6: 0xf15c6,
+ 0xf15c7: 0xf15c7,
+ 0xf15c8: 0xf15c8,
+ 0xf15c9: 0xf15c9,
+ 0xf15ca: 0xf15ca,
+ 0xf15cb: 0xf15cb,
+ 0xf15cc: 0xf15cc,
+ 0xf15cd: 0xf15cd,
+ 0xf15ce: 0xf15ce,
+ 0xf15cf: 0xf15cf,
+ 0xf15d0: 0xf15d0,
+ 0xf15d1: 0xf15d1,
+ 0xf15d2: 0xf15d2,
+ 0xf15d3: 0xf15d3,
+ 0xf15d4: 0xf15d4,
+ 0xf15d5: 0xf15d5,
+ 0xf15d6: 0xf15d6,
+ 0xf15d7: 0xf15d7,
+ 0xf15d8: 0xf15d8,
+ 0xf15d9: 0xf15d9,
+ 0xf15da: 0xf15da,
+ 0xf15db: 0xf15db,
+ 0xf15dc: 0xf15dc,
+ 0xf15dd: 0xf15dd,
+ 0xf15de: 0xf15de,
+ 0xf15df: 0xf15df,
+ 0xf15e0: 0xf15e0,
+ 0xf15e1: 0xf15e1,
+ 0xf15e2: 0xf15e2,
+ 0xf15e3: 0xf15e3,
+ 0xf15e4: 0xf15e4,
+ 0xf15e5: 0xf15e5,
+ 0xf15e6: 0xf15e6,
+ 0xf15e7: 0xf15e7,
+ 0xf15e8: 0xf15e8,
+ 0xf15e9: 0xf15e9,
+ 0xf15ea: 0xf15ea,
+ 0xf15eb: 0xf15eb,
+ 0xf15ec: 0xf15ec,
+ 0xf15ed: 0xf15ed,
+ 0xf15ee: 0xf15ee,
+ 0xf15ef: 0xf15ef,
+ 0xf15f0: 0xf15f0,
+ 0xf15f1: 0xf15f1,
+ 0xf15f2: 0xf15f2,
+ 0xf15f3: 0xf15f3,
+ 0xf15f4: 0xf15f4,
+ 0xf15f5: 0xf15f5,
+ 0xf15f6: 0xf15f6,
+ 0xf15f7: 0xf15f7,
+ 0xf15f8: 0xf15f8,
+ 0xf15f9: 0xf15f9,
+ 0xf15fa: 0xf15fa,
+ 0xf15fb: 0xf15fb,
+ 0xf15fc: 0xf15fc,
+ 0xf15fd: 0xf15fd,
+ 0xf15fe: 0xf15fe,
+ 0xf15ff: 0xf15ff,
+ 0xf1600: 0xf1600,
+ 0xf1601: 0xf1601,
+ 0xf1602: 0xf1602,
+ 0xf1603: 0xf1603,
+ 0xf1604: 0xf1604,
+ 0xf1605: 0xf1605,
+ 0xf1606: 0xf1606,
+ 0xf1607: 0xf1607,
+ 0xf1608: 0xf1608,
+ 0xf1609: 0xf1609,
+ 0xf160a: 0xf160a,
+ 0xf160b: 0xf160b,
+ 0xf160c: 0xf160c,
+ 0xf160d: 0xf160d,
+ 0xf160e: 0xf160e,
+ 0xf160f: 0xf160f,
+ 0xf1610: 0xf1610,
+ 0xf1611: 0xf1611,
+ 0xf1612: 0xf1612,
+ 0xf1613: 0xf1613,
+ 0xf1614: 0xf1614,
+ 0xf1615: 0xf1615,
+ 0xf1616: 0xf1616,
+ 0xf1617: 0xf1617,
+ 0xf1618: 0xf1618,
+ 0xf1619: 0xf1619,
+ 0xf161a: 0xf161a,
+ 0xf161b: 0xf161b,
+ 0xf161c: 0xf161c,
+ 0xf161d: 0xf161d,
+ 0xf161e: 0xf161e,
+ 0xf161f: 0xf161f,
+ 0xf1620: 0xf1620,
+ 0xf1621: 0xf1621,
+ 0xf1622: 0xf1622,
+ 0xf1623: 0xf1623,
+ 0xf1624: 0xf1624,
+ 0xf1625: 0xf1625,
+ 0xf1626: 0xf1626,
+ 0xf1627: 0xf1627,
+ 0xf1628: 0xf1628,
+ 0xf1629: 0xf1629,
+ 0xf162a: 0xf162a,
+ 0xf162b: 0xf162b,
+ 0xf162c: 0xf162c,
+ 0xf162d: 0xf162d,
+ 0xf162e: 0xf162e,
+ 0xf162f: 0xf162f,
+ 0xf1630: 0xf1630,
+ 0xf1631: 0xf1631,
+ 0xf1632: 0xf1632,
+ 0xf1633: 0xf1633,
+ 0xf1634: 0xf1634,
+ 0xf1635: 0xf1635,
+ 0xf1636: 0xf1636,
+ 0xf1637: 0xf1637,
+ 0xf1638: 0xf1638,
+ 0xf1639: 0xf1639,
+ 0xf163a: 0xf163a,
+ 0xf163b: 0xf163b,
+ 0xf163c: 0xf163c,
+ 0xf163d: 0xf163d,
+ 0xf163e: 0xf163e,
+ 0xf163f: 0xf163f,
+ 0xf1640: 0xf1640,
+ 0xf1641: 0xf1641,
+ 0xf1642: 0xf1642,
+ 0xf1643: 0xf1643,
+ 0xf1644: 0xf1644,
+ 0xf1645: 0xf1645,
+ 0xf1646: 0xf1646,
+ 0xf1647: 0xf1647,
+ 0xf1648: 0xf1648,
+ 0xf1649: 0xf1649,
+ 0xf164a: 0xf164a,
+ 0xf164b: 0xf164b,
+ 0xf164c: 0xf164c,
+ 0xf164d: 0xf164d,
+ 0xf164e: 0xf164e,
+ 0xf164f: 0xf164f,
+ 0xf1650: 0xf1650,
+ 0xf1651: 0xf1651,
+ 0xf1652: 0xf1652,
+ 0xf1653: 0xf1653,
+ 0xf1654: 0xf1654,
+ 0xf1655: 0xf1655,
+ 0xf1656: 0xf1656,
+ 0xf1657: 0xf1657,
+ 0xf1658: 0xf1658,
+ 0xf1659: 0xf1659,
+ 0xf165a: 0xf165a,
+ 0xf165b: 0xf165b,
+ 0xf165c: 0xf165c,
+ 0xf165d: 0xf165d,
+ 0xf165e: 0xf165e,
+ 0xf165f: 0xf165f,
+ 0xf1660: 0xf1660,
+ 0xf1661: 0xf1661,
+ 0xf1662: 0xf1662,
+ 0xf1663: 0xf1663,
+ 0xf1664: 0xf1664,
+ 0xf1665: 0xf1665,
+ 0xf1666: 0xf1666,
+ 0xf1667: 0xf1667,
+ 0xf1668: 0xf1668,
+ 0xf1669: 0xf1669,
+ 0xf166a: 0xf166a,
+ 0xf166b: 0xf166b,
+ 0xf166c: 0xf166c,
+ 0xf166d: 0xf166d,
+ 0xf166e: 0xf166e,
+ 0xf166f: 0xf166f,
+ 0xf1670: 0xf1670,
+ 0xf1671: 0xf1671,
+ 0xf1672: 0xf1672,
+ 0xf1673: 0xf1673,
+ 0xf1674: 0xf1674,
+ 0xf1675: 0xf1675,
+ 0xf1676: 0xf1676,
+ 0xf1677: 0xf1677,
+ 0xf1678: 0xf1678,
+ 0xf1679: 0xf1679,
+ 0xf167a: 0xf167a,
+ 0xf167b: 0xf167b,
+ 0xf167c: 0xf167c,
+ 0xf167d: 0xf167d,
+ 0xf167e: 0xf167e,
+ 0xf167f: 0xf167f,
+ 0xf1680: 0xf1680,
+ 0xf1681: 0xf1681,
+ 0xf1682: 0xf1682,
+ 0xf1683: 0xf1683,
+ 0xf1684: 0xf1684,
+ 0xf1685: 0xf1685,
+ 0xf1686: 0xf1686,
+ 0xf1687: 0xf1687,
+ 0xf1688: 0xf1688,
+ 0xf1689: 0xf1689,
+ 0xf168a: 0xf168a,
+ 0xf168b: 0xf168b,
+ 0xf168c: 0xf168c,
+ 0xf168d: 0xf168d,
+ 0xf168e: 0xf168e,
+ 0xf168f: 0xf168f,
+ 0xf1690: 0xf1690,
+ 0xf1691: 0xf1691,
+ 0xf1692: 0xf1692,
+ 0xf1693: 0xf1693,
+ 0xf1694: 0xf1694,
+ 0xf1695: 0xf1695,
+ 0xf1696: 0xf1696,
+ 0xf1697: 0xf1697,
+ 0xf1698: 0xf1698,
+ 0xf1699: 0xf1699,
+ 0xf169a: 0xf169a,
+ 0xf169b: 0xf169b,
+ 0xf169c: 0xf169c,
+ 0xf169d: 0xf169d,
+ 0xf169e: 0xf169e,
+ 0xf169f: 0xf169f,
+ 0xf16a0: 0xf16a0,
+ 0xf16a1: 0xf16a1,
+ 0xf16a2: 0xf16a2,
+ 0xf16a3: 0xf16a3,
+ 0xf16a4: 0xf16a4,
+ 0xf16a5: 0xf16a5,
+ 0xf16a6: 0xf16a6,
+ 0xf16a7: 0xf16a7,
+ 0xf16a8: 0xf16a8,
+ 0xf16a9: 0xf16a9,
+ 0xf16aa: 0xf16aa,
+ 0xf16ab: 0xf16ab,
+ 0xf16ac: 0xf16ac,
+ 0xf16ad: 0xf16ad,
+ 0xf16ae: 0xf16ae,
+ 0xf16af: 0xf16af,
+ 0xf16b0: 0xf16b0,
+ 0xf16b1: 0xf16b1,
+ 0xf16b2: 0xf16b2,
+ 0xf16b3: 0xf16b3,
+ 0xf16b4: 0xf16b4,
+ 0xf16b5: 0xf16b5,
+ 0xf16b6: 0xf16b6,
+ 0xf16b7: 0xf16b7,
+ 0xf16b8: 0xf16b8,
+ 0xf16b9: 0xf16b9,
+ 0xf16ba: 0xf16ba,
+ 0xf16bb: 0xf16bb,
+ 0xf16bc: 0xf16bc,
+ 0xf16bd: 0xf16bd,
+ 0xf16be: 0xf16be,
+ 0xf16bf: 0xf16bf,
+ 0xf16c0: 0xf16c0,
+ 0xf16c1: 0xf16c1,
+ 0xf16c2: 0xf16c2,
+ 0xf16c3: 0xf16c3,
+ 0xf16c4: 0xf16c4,
+ 0xf16c5: 0xf16c5,
+ 0xf16c6: 0xf16c6,
+ 0xf16c7: 0xf16c7,
+ 0xf16c8: 0xf16c8,
+ 0xf16c9: 0xf16c9,
+ 0xf16ca: 0xf16ca,
+ 0xf16cb: 0xf16cb,
+ 0xf16cc: 0xf16cc,
+ 0xf16cd: 0xf16cd,
+ 0xf16ce: 0xf16ce,
+ 0xf16cf: 0xf16cf,
+ 0xf16d0: 0xf16d0,
+ 0xf16d1: 0xf16d1,
+ 0xf16d2: 0xf16d2,
+ 0xf16d3: 0xf16d3,
+ 0xf16d4: 0xf16d4,
+ 0xf16d5: 0xf16d5,
+ 0xf16d6: 0xf16d6,
+ 0xf16d7: 0xf16d7,
+ 0xf16d8: 0xf16d8,
+ 0xf16d9: 0xf16d9,
+ 0xf16da: 0xf16da,
+ 0xf16db: 0xf16db,
+ 0xf16dc: 0xf16dc,
+ 0xf16dd: 0xf16dd,
+ 0xf16de: 0xf16de,
+ 0xf16df: 0xf16df,
+ 0xf16e0: 0xf16e0,
+ 0xf16e1: 0xf16e1,
+ 0xf16e2: 0xf16e2,
+ 0xf16e3: 0xf16e3,
+ 0xf16e4: 0xf16e4,
+ 0xf16e5: 0xf16e5,
+ 0xf16e6: 0xf16e6,
+ 0xf16e7: 0xf16e7,
+ 0xf16e8: 0xf16e8,
+ 0xf16e9: 0xf16e9,
+ 0xf16ea: 0xf16ea,
+ 0xf16eb: 0xf16eb,
+ 0xf16ec: 0xf16ec,
+ 0xf16ed: 0xf16ed,
+ 0xf16ee: 0xf16ee,
+ 0xf16ef: 0xf16ef,
+ 0xf16f0: 0xf16f0,
+ 0xf16f1: 0xf16f1,
+ 0xf16f2: 0xf16f2,
+ 0xf16f3: 0xf16f3,
+ 0xf16f4: 0xf16f4,
+ 0xf16f5: 0xf16f5,
+ 0xf16f6: 0xf16f6,
+ 0xf16f7: 0xf16f7,
+ 0xf16f8: 0xf16f8,
+ 0xf16f9: 0xf16f9,
+ 0xf16fa: 0xf16fa,
+ 0xf16fb: 0xf16fb,
+ 0xf16fc: 0xf16fc,
+ 0xf16fd: 0xf16fd,
+ 0xf16fe: 0xf16fe,
+ 0xf16ff: 0xf16ff,
+ 0xf1700: 0xf1700,
+ 0xf1701: 0xf1701,
+ 0xf1702: 0xf1702,
+ 0xf1703: 0xf1703,
+ 0xf1704: 0xf1704,
+ 0xf1705: 0xf1705,
+ 0xf1706: 0xf1706,
+ 0xf1707: 0xf1707,
+ 0xf1708: 0xf1708,
+ 0xf1709: 0xf1709,
+ 0xf170a: 0xf170a,
+ 0xf170b: 0xf170b,
+ 0xf170c: 0xf170c,
+ 0xf170d: 0xf170d,
+ 0xf170e: 0xf170e,
+ 0xf170f: 0xf170f,
+ 0xf1710: 0xf1710,
+ 0xf1711: 0xf1711,
+ 0xf1712: 0xf1712,
+ 0xf1713: 0xf1713,
+ 0xf1714: 0xf1714,
+ 0xf1715: 0xf1715,
+ 0xf1716: 0xf1716,
+ 0xf1717: 0xf1717,
+ 0xf1718: 0xf1718,
+ 0xf1719: 0xf1719,
+ 0xf171a: 0xf171a,
+ 0xf171b: 0xf171b,
+ 0xf171c: 0xf171c,
+ 0xf171d: 0xf171d,
+ 0xf171e: 0xf171e,
+ 0xf171f: 0xf171f,
+ 0xf1720: 0xf1720,
+ 0xf1721: 0xf1721,
+ 0xf1722: 0xf1722,
+ 0xf1723: 0xf1723,
+ 0xf1724: 0xf1724,
+ 0xf1725: 0xf1725,
+ 0xf1726: 0xf1726,
+ 0xf1727: 0xf1727,
+ 0xf1728: 0xf1728,
+ 0xf1729: 0xf1729,
+ 0xf172a: 0xf172a,
+ 0xf172b: 0xf172b,
+ 0xf172c: 0xf172c,
+ 0xf172d: 0xf172d,
+ 0xf172e: 0xf172e,
+ 0xf172f: 0xf172f,
+ 0xf1730: 0xf1730,
+ 0xf1731: 0xf1731,
+ 0xf1732: 0xf1732,
+ 0xf1733: 0xf1733,
+ 0xf1734: 0xf1734,
+ 0xf1735: 0xf1735,
+ 0xf1736: 0xf1736,
+ 0xf1737: 0xf1737,
+ 0xf1738: 0xf1738,
+ 0xf1739: 0xf1739,
+ 0xf173a: 0xf173a,
+ 0xf173b: 0xf173b,
+ 0xf173c: 0xf173c,
+ 0xf173d: 0xf173d,
+ 0xf173e: 0xf173e,
+ 0xf173f: 0xf173f,
+ 0xf1740: 0xf1740,
+ 0xf1741: 0xf1741,
+ 0xf1742: 0xf1742,
+ 0xf1743: 0xf1743,
+ 0xf1744: 0xf1744,
+ 0xf1745: 0xf1745,
+ 0xf1746: 0xf1746,
+ 0xf1747: 0xf1747,
+ 0xf1748: 0xf1748,
+ 0xf1749: 0xf1749,
+ 0xf174a: 0xf174a,
+ 0xf174b: 0xf174b,
+ 0xf174c: 0xf174c,
+ 0xf174d: 0xf174d,
+ 0xf174e: 0xf174e,
+ 0xf174f: 0xf174f,
+ 0xf1750: 0xf1750,
+ 0xf1751: 0xf1751,
+ 0xf1752: 0xf1752,
+ 0xf1753: 0xf1753,
+ 0xf1754: 0xf1754,
+ 0xf1755: 0xf1755,
+ 0xf1756: 0xf1756,
+ 0xf1757: 0xf1757,
+ 0xf1758: 0xf1758,
+ 0xf1759: 0xf1759,
+ 0xf175a: 0xf175a,
+ 0xf175b: 0xf175b,
+ 0xf175c: 0xf175c,
+ 0xf175d: 0xf175d,
+ 0xf175e: 0xf175e,
+ 0xf175f: 0xf175f,
+ 0xf1760: 0xf1760,
+ 0xf1761: 0xf1761,
+ 0xf1762: 0xf1762,
+ 0xf1763: 0xf1763,
+ 0xf1764: 0xf1764,
+ 0xf1765: 0xf1765,
+ 0xf1766: 0xf1766,
+ 0xf1767: 0xf1767,
+ 0xf1768: 0xf1768,
+ 0xf1769: 0xf1769,
+ 0xf176a: 0xf176a,
+ 0xf176b: 0xf176b,
+ 0xf176c: 0xf176c,
+ 0xf176d: 0xf176d,
+ 0xf176e: 0xf176e,
+ 0xf176f: 0xf176f,
+ 0xf1770: 0xf1770,
+ 0xf1771: 0xf1771,
+ 0xf1772: 0xf1772,
+ 0xf1773: 0xf1773,
+ 0xf1774: 0xf1774,
+ 0xf1775: 0xf1775,
+ 0xf1776: 0xf1776,
+ 0xf1777: 0xf1777,
+ 0xf1778: 0xf1778,
+ 0xf1779: 0xf1779,
+ 0xf177a: 0xf177a,
+ 0xf177b: 0xf177b,
+ 0xf177c: 0xf177c,
+ 0xf177d: 0xf177d,
+ 0xf177e: 0xf177e,
+ 0xf177f: 0xf177f,
+ 0xf1780: 0xf1780,
+ 0xf1781: 0xf1781,
+ 0xf1782: 0xf1782,
+ 0xf1783: 0xf1783,
+ 0xf1784: 0xf1784,
+ 0xf1785: 0xf1785,
+ 0xf1786: 0xf1786,
+ 0xf1787: 0xf1787,
+ 0xf1788: 0xf1788,
+ 0xf1789: 0xf1789,
+ 0xf178a: 0xf178a,
+ 0xf178b: 0xf178b,
+ 0xf178c: 0xf178c,
+ 0xf178d: 0xf178d,
+ 0xf178e: 0xf178e,
+ 0xf178f: 0xf178f,
+ 0xf1790: 0xf1790,
+ 0xf1791: 0xf1791,
+ 0xf1792: 0xf1792,
+ 0xf1793: 0xf1793,
+ 0xf1794: 0xf1794,
+ 0xf1795: 0xf1795,
+ 0xf1796: 0xf1796,
+ 0xf1797: 0xf1797,
+ 0xf1798: 0xf1798,
+ 0xf1799: 0xf1799,
+ 0xf179a: 0xf179a,
+ 0xf179b: 0xf179b,
+ 0xf179c: 0xf179c,
+ 0xf179d: 0xf179d,
+ 0xf179e: 0xf179e,
+ 0xf179f: 0xf179f,
+ 0xf17a0: 0xf17a0,
+ 0xf17a1: 0xf17a1,
+ 0xf17a2: 0xf17a2,
+ 0xf17a3: 0xf17a3,
+ 0xf17a4: 0xf17a4,
+ 0xf17a5: 0xf17a5,
+ 0xf17a6: 0xf17a6,
+ 0xf17a7: 0xf17a7,
+ 0xf17a8: 0xf17a8,
+ 0xf17a9: 0xf17a9,
+ 0xf17aa: 0xf17aa,
+ 0xf17ab: 0xf17ab,
+ 0xf17ac: 0xf17ac,
+ 0xf17ad: 0xf17ad,
+ 0xf17ae: 0xf17ae,
+ 0xf17af: 0xf17af,
+ 0xf17b0: 0xf17b0,
+ 0xf17b1: 0xf17b1,
+ 0xf17b2: 0xf17b2,
+ 0xf17b3: 0xf17b3,
+ 0xf17b4: 0xf17b4,
+ 0xf17b5: 0xf17b5,
+ 0xf17b6: 0xf17b6,
+ 0xf17b7: 0xf17b7,
+ 0xf17b8: 0xf17b8,
+ 0xf17b9: 0xf17b9,
+ 0xf17ba: 0xf17ba,
+ 0xf17bb: 0xf17bb,
+ 0xf17bc: 0xf17bc,
+ 0xf17bd: 0xf17bd,
+ 0xf17be: 0xf17be,
+ 0xf17bf: 0xf17bf,
+ 0xf17c0: 0xf17c0,
+ 0xf17c1: 0xf17c1,
+ 0xf17c2: 0xf17c2,
+ 0xf17c3: 0xf17c3,
+ 0xf17c4: 0xf17c4,
+ 0xf17c5: 0xf17c5,
+ 0xf17c6: 0xf17c6,
+ 0xf17c7: 0xf17c7,
+ 0xf17c8: 0xf17c8,
+ 0xf17c9: 0xf17c9,
+ 0xf17ca: 0xf17ca,
+ 0xf17cb: 0xf17cb,
+ 0xf17cc: 0xf17cc,
+ 0xf17cd: 0xf17cd,
+ 0xf17ce: 0xf17ce,
+ 0xf17cf: 0xf17cf,
+ 0xf17d0: 0xf17d0,
+ 0xf17d1: 0xf17d1,
+ 0xf17d2: 0xf17d2,
+ 0xf17d3: 0xf17d3,
+ 0xf17d4: 0xf17d4,
+ 0xf17d5: 0xf17d5,
+ 0xf17d6: 0xf17d6,
+ 0xf17d7: 0xf17d7,
+ 0xf17d8: 0xf17d8,
+ 0xf17d9: 0xf17d9,
+ 0xf17da: 0xf17da,
+ 0xf17db: 0xf17db,
+ 0xf17dc: 0xf17dc,
+ 0xf17dd: 0xf17dd,
+ 0xf17de: 0xf17de,
+ 0xf17df: 0xf17df,
+ 0xf17e0: 0xf17e0,
+ 0xf17e1: 0xf17e1,
+ 0xf17e2: 0xf17e2,
+ 0xf17e3: 0xf17e3,
+ 0xf17e4: 0xf17e4,
+ 0xf17e5: 0xf17e5,
+ 0xf17e6: 0xf17e6,
+ 0xf17e7: 0xf17e7,
+ 0xf17e8: 0xf17e8,
+ 0xf17e9: 0xf17e9,
+ 0xf17ea: 0xf17ea,
+ 0xf17eb: 0xf17eb,
+ 0xf17ec: 0xf17ec,
+ 0xf17ed: 0xf17ed,
+ 0xf17ee: 0xf17ee,
+ 0xf17ef: 0xf17ef,
+ 0xf17f0: 0xf17f0,
+ 0xf17f1: 0xf17f1,
+ 0xf17f2: 0xf17f2,
+ 0xf17f3: 0xf17f3,
+ 0xf17f4: 0xf17f4,
+ 0xf17f5: 0xf17f5,
+ 0xf17f6: 0xf17f6,
+ 0xf17f7: 0xf17f7,
+ 0xf17f8: 0xf17f8,
+ 0xf17f9: 0xf17f9,
+ 0xf17fa: 0xf17fa,
+ 0xf17fb: 0xf17fb,
+ 0xf17fc: 0xf17fc,
+ 0xf17fd: 0xf17fd,
+ 0xf17fe: 0xf17fe,
+ 0xf17ff: 0xf17ff,
+ 0xf1800: 0xf1800,
+ 0xf1801: 0xf1801,
+ 0xf1802: 0xf1802,
+ 0xf1803: 0xf1803,
+ 0xf1804: 0xf1804,
+ 0xf1805: 0xf1805,
+ 0xf1806: 0xf1806,
+ 0xf1807: 0xf1807,
+ 0xf1808: 0xf1808,
+ 0xf1809: 0xf1809,
+ 0xf180a: 0xf180a,
+ 0xf180b: 0xf180b,
+ 0xf180c: 0xf180c,
+ 0xf180d: 0xf180d,
+ 0xf180e: 0xf180e,
+ 0xf180f: 0xf180f,
+ 0xf1810: 0xf1810,
+ 0xf1811: 0xf1811,
+ 0xf1812: 0xf1812,
+ 0xf1813: 0xf1813,
+ 0xf1814: 0xf1814,
+ 0xf1815: 0xf1815,
+ 0xf1816: 0xf1816,
+ 0xf1817: 0xf1817,
+ 0xf1818: 0xf1818,
+ 0xf1819: 0xf1819,
+ 0xf181a: 0xf181a,
+ 0xf181b: 0xf181b,
+ 0xf181c: 0xf181c,
+ 0xf181d: 0xf181d,
+ 0xf181e: 0xf181e,
+ 0xf181f: 0xf181f,
+ 0xf1820: 0xf1820,
+ 0xf1821: 0xf1821,
+ 0xf1822: 0xf1822,
+ 0xf1823: 0xf1823,
+ 0xf1824: 0xf1824,
+ 0xf1825: 0xf1825,
+ 0xf1826: 0xf1826,
+ 0xf1827: 0xf1827,
+ 0xf1828: 0xf1828,
+ 0xf1829: 0xf1829,
+ 0xf182a: 0xf182a,
+ 0xf182b: 0xf182b,
+ 0xf182c: 0xf182c,
+ 0xf182d: 0xf182d,
+ 0xf182e: 0xf182e,
+ 0xf182f: 0xf182f,
+ 0xf1830: 0xf1830,
+ 0xf1831: 0xf1831,
+ 0xf1832: 0xf1832,
+ 0xf1833: 0xf1833,
+ 0xf1834: 0xf1834,
+ 0xf1835: 0xf1835,
+ 0xf1836: 0xf1836,
+ 0xf1837: 0xf1837,
+ 0xf1838: 0xf1838,
+ 0xf1839: 0xf1839,
+ 0xf183a: 0xf183a,
+ 0xf183b: 0xf183b,
+ 0xf183c: 0xf183c,
+ 0xf183d: 0xf183d,
+ 0xf183e: 0xf183e,
+ 0xf183f: 0xf183f,
+ 0xf1840: 0xf1840,
+ 0xf1841: 0xf1841,
+ 0xf1842: 0xf1842,
+ 0xf1843: 0xf1843,
+ 0xf1844: 0xf1844,
+ 0xf1845: 0xf1845,
+ 0xf1846: 0xf1846,
+ 0xf1847: 0xf1847,
+ 0xf1848: 0xf1848,
+ 0xf1849: 0xf1849,
+ 0xf184a: 0xf184a,
+ 0xf184b: 0xf184b,
+ 0xf184c: 0xf184c,
+ 0xf184d: 0xf184d,
+ 0xf184e: 0xf184e,
+ 0xf184f: 0xf184f,
+ 0xf1850: 0xf1850,
+ 0xf1851: 0xf1851,
+ 0xf1852: 0xf1852,
+ 0xf1853: 0xf1853,
+ 0xf1854: 0xf1854,
+ 0xf1855: 0xf1855,
+ 0xf1856: 0xf1856,
+ 0xf1857: 0xf1857,
+ 0xf1858: 0xf1858,
+ 0xf1859: 0xf1859,
+ 0xf185a: 0xf185a,
+ 0xf185b: 0xf185b,
+ 0xf185c: 0xf185c,
+ 0xf185d: 0xf185d,
+ 0xf185e: 0xf185e,
+ 0xf185f: 0xf185f,
+ 0xf1860: 0xf1860,
+ 0xf1861: 0xf1861,
+ 0xf1862: 0xf1862,
+ 0xf1863: 0xf1863,
+ 0xf1864: 0xf1864,
+ 0xf1865: 0xf1865,
+ 0xf1866: 0xf1866,
+ 0xf1867: 0xf1867,
+ 0xf1868: 0xf1868,
+ 0xf1869: 0xf1869,
+ 0xf186a: 0xf186a,
+ 0xf186b: 0xf186b,
+ 0xf186c: 0xf186c,
+ 0xf186d: 0xf186d,
+ 0xf186e: 0xf186e,
+ 0xf186f: 0xf186f,
+ 0xf1870: 0xf1870,
+ 0xf1871: 0xf1871,
+ 0xf1872: 0xf1872,
+ 0xf1873: 0xf1873,
+ 0xf1874: 0xf1874,
+ 0xf1875: 0xf1875,
+ 0xf1876: 0xf1876,
+ 0xf1877: 0xf1877,
+ 0xf1878: 0xf1878,
+ 0xf1879: 0xf1879,
+ 0xf187a: 0xf187a,
+ 0xf187b: 0xf187b,
+ 0xf187c: 0xf187c,
+ 0xf187d: 0xf187d,
+ 0xf187e: 0xf187e,
+ 0xf187f: 0xf187f,
+ 0xf1880: 0xf1880,
+ 0xf1881: 0xf1881,
+ 0xf1882: 0xf1882,
+ 0xf1883: 0xf1883,
+ 0xf1884: 0xf1884,
+ 0xf1885: 0xf1885,
+ 0xf1886: 0xf1886,
+ 0xf1887: 0xf1887,
+ 0xf1888: 0xf1888,
+ 0xf1889: 0xf1889,
+ 0xf188a: 0xf188a,
+ 0xf188b: 0xf188b,
+ 0xf188c: 0xf188c,
+ 0xf188d: 0xf188d,
+ 0xf188e: 0xf188e,
+ 0xf188f: 0xf188f,
+ 0xf1890: 0xf1890,
+ 0xf1891: 0xf1891,
+ 0xf1892: 0xf1892,
+ 0xf1893: 0xf1893,
+ 0xf1894: 0xf1894,
+ 0xf1895: 0xf1895,
+ 0xf1896: 0xf1896,
+ 0xf1897: 0xf1897,
+ 0xf1898: 0xf1898,
+ 0xf1899: 0xf1899,
+ 0xf189a: 0xf189a,
+ 0xf189b: 0xf189b,
+ 0xf189c: 0xf189c,
+ 0xf189d: 0xf189d,
+ 0xf189e: 0xf189e,
+ 0xf189f: 0xf189f,
+ 0xf18a0: 0xf18a0,
+ 0xf18a1: 0xf18a1,
+ 0xf18a2: 0xf18a2,
+ 0xf18a3: 0xf18a3,
+ 0xf18a4: 0xf18a4,
+ 0xf18a5: 0xf18a5,
+ 0xf18a6: 0xf18a6,
+ 0xf18a7: 0xf18a7,
+ 0xf18a8: 0xf18a8,
+ 0xf18a9: 0xf18a9,
+ 0xf18aa: 0xf18aa,
+ 0xf18ab: 0xf18ab,
+ 0xf18ac: 0xf18ac,
+ 0xf18ad: 0xf18ad,
+ 0xf18ae: 0xf18ae,
+ 0xf18af: 0xf18af,
+ 0xf18b0: 0xf18b0,
+ 0xf18b1: 0xf18b1,
+ 0xf18b2: 0xf18b2,
+ 0xf18b3: 0xf18b3,
+ 0xf18b4: 0xf18b4,
+ 0xf18b5: 0xf18b5,
+ 0xf18b6: 0xf18b6,
+ 0xf18b7: 0xf18b7,
+ 0xf18b8: 0xf18b8,
+ 0xf18b9: 0xf18b9,
+ 0xf18ba: 0xf18ba,
+ 0xf18bb: 0xf18bb,
+ 0xf18bc: 0xf18bc,
+ 0xf18bd: 0xf18bd,
+ 0xf18be: 0xf18be,
+ 0xf18bf: 0xf18bf,
+ 0xf18c0: 0xf18c0,
+ 0xf18c1: 0xf18c1,
+ 0xf18c2: 0xf18c2,
+ 0xf18c3: 0xf18c3,
+ 0xf18c4: 0xf18c4,
+ 0xf18c5: 0xf18c5,
+ 0xf18c6: 0xf18c6,
+ 0xf18c7: 0xf18c7,
+ 0xf18c8: 0xf18c8,
+ 0xf18c9: 0xf18c9,
+ 0xf18ca: 0xf18ca,
+ 0xf18cb: 0xf18cb,
+ 0xf18cc: 0xf18cc,
+ 0xf18cd: 0xf18cd,
+ 0xf18ce: 0xf18ce,
+ 0xf18cf: 0xf18cf,
+ 0xf18d0: 0xf18d0,
+ 0xf18d1: 0xf18d1,
+ 0xf18d2: 0xf18d2,
+ 0xf18d3: 0xf18d3,
+ 0xf18d4: 0xf18d4,
+ 0xf18d5: 0xf18d5,
+ 0xf18d6: 0xf18d6,
+ 0xf18d7: 0xf18d7,
+ 0xf18d8: 0xf18d8,
+ 0xf18d9: 0xf18d9,
+ 0xf18da: 0xf18da,
+ 0xf18db: 0xf18db,
+ 0xf18dc: 0xf18dc,
+ 0xf18dd: 0xf18dd,
+ 0xf18de: 0xf18de,
+ 0xf18df: 0xf18df,
+ 0xf18e0: 0xf18e0,
+ 0xf18e1: 0xf18e1,
+ 0xf18e2: 0xf18e2,
+ 0xf18e3: 0xf18e3,
+ 0xf18e4: 0xf18e4,
+ 0xf18e5: 0xf18e5,
+ 0xf18e6: 0xf18e6,
+ 0xf18e7: 0xf18e7,
+ 0xf18e8: 0xf18e8,
+ 0xf18e9: 0xf18e9,
+ 0xf18ea: 0xf18ea,
+ 0xf18eb: 0xf18eb,
+ 0xf18ec: 0xf18ec,
+ 0xf18ed: 0xf18ed,
+ 0xf18ee: 0xf18ee,
+ 0xf18ef: 0xf18ef,
+ 0xf18f0: 0xf18f0,
+ 0xf18f1: 0xf18f1,
+ 0xf18f2: 0xf18f2,
+ 0xf18f3: 0xf18f3,
+ 0xf18f4: 0xf18f4,
+ 0xf18f5: 0xf18f5,
+ 0xf18f6: 0xf18f6,
+ 0xf18f7: 0xf18f7,
+ 0xf18f8: 0xf18f8,
+ 0xf18f9: 0xf18f9,
+ 0xf18fa: 0xf18fa,
+ 0xf18fb: 0xf18fb,
+ 0xf18fc: 0xf18fc,
+ 0xf18fd: 0xf18fd,
+ 0xf18fe: 0xf18fe,
+ 0xf18ff: 0xf18ff,
+ 0xf1900: 0xf1900,
+ 0xf1901: 0xf1901,
+ 0xf1902: 0xf1902,
+ 0xf1903: 0xf1903,
+ 0xf1904: 0xf1904,
+ 0xf1905: 0xf1905,
+ 0xf1906: 0xf1906,
+ 0xf1907: 0xf1907,
+ 0xf1908: 0xf1908,
+ 0xf1909: 0xf1909,
+ 0xf190a: 0xf190a,
+ 0xf190b: 0xf190b,
+ 0xf190c: 0xf190c,
+ 0xf190d: 0xf190d,
+ 0xf190e: 0xf190e,
+ 0xf190f: 0xf190f,
+ 0xf1910: 0xf1910,
+ 0xf1911: 0xf1911,
+ 0xf1912: 0xf1912,
+ 0xf1913: 0xf1913,
+ 0xf1914: 0xf1914,
+ 0xf1915: 0xf1915,
+ 0xf1916: 0xf1916,
+ 0xf1917: 0xf1917,
+ 0xf1918: 0xf1918,
+ 0xf1919: 0xf1919,
+ 0xf191a: 0xf191a,
+ 0xf191b: 0xf191b,
+ 0xf191c: 0xf191c,
+ 0xf191d: 0xf191d,
+ 0xf191e: 0xf191e,
+ 0xf191f: 0xf191f,
+ 0xf1920: 0xf1920,
+ 0xf1921: 0xf1921,
+ 0xf1922: 0xf1922,
+ 0xf1923: 0xf1923,
+ 0xf1924: 0xf1924,
+ 0xf1925: 0xf1925,
+ 0xf1926: 0xf1926,
+ 0xf1927: 0xf1927,
+ 0xf1928: 0xf1928,
+ 0xf1929: 0xf1929,
+ 0xf192a: 0xf192a,
+ 0xf192b: 0xf192b,
+ 0xf192c: 0xf192c,
+ 0xf192d: 0xf192d,
+ 0xf192e: 0xf192e,
+ 0xf192f: 0xf192f,
+ 0xf1930: 0xf1930,
+ 0xf1931: 0xf1931,
+ 0xf1932: 0xf1932,
+ 0xf1933: 0xf1933,
+ 0xf1934: 0xf1934,
+ 0xf1935: 0xf1935,
+ 0xf1936: 0xf1936,
+ 0xf1937: 0xf1937,
+ 0xf1938: 0xf1938,
+ 0xf1939: 0xf1939,
+ 0xf193a: 0xf193a,
+ 0xf193b: 0xf193b,
+ 0xf193c: 0xf193c,
+ 0xf193d: 0xf193d,
+ 0xf193e: 0xf193e,
+ 0xf193f: 0xf193f,
+ 0xf1940: 0xf1940,
+ 0xf1941: 0xf1941,
+ 0xf1942: 0xf1942,
+ 0xf1943: 0xf1943,
+ 0xf1944: 0xf1944,
+ 0xf1945: 0xf1945,
+ 0xf1946: 0xf1946,
+ 0xf1947: 0xf1947,
+ 0xf1948: 0xf1948,
+ 0xf1949: 0xf1949,
+ 0xf194a: 0xf194a,
+ 0xf194b: 0xf194b,
+ 0xf194c: 0xf194c,
+ 0xf194d: 0xf194d,
+ 0xf194e: 0xf194e,
+ 0xf194f: 0xf194f,
+ 0xf1950: 0xf1950,
+ 0xf1951: 0xf1951,
+ 0xf1952: 0xf1952,
+ 0xf1953: 0xf1953,
+ 0xf1954: 0xf1954,
+ 0xf1955: 0xf1955,
+ 0xf1956: 0xf1956,
+ 0xf1957: 0xf1957,
+ 0xf1958: 0xf1958,
+ 0xf1959: 0xf1959,
+ 0xf195a: 0xf195a,
+ 0xf195b: 0xf195b,
+ 0xf195c: 0xf195c,
+ 0xf195d: 0xf195d,
+ 0xf195e: 0xf195e,
+ 0xf195f: 0xf195f,
+ 0xf1960: 0xf1960,
+ 0xf1961: 0xf1961,
+ 0xf1962: 0xf1962,
+ 0xf1963: 0xf1963,
+ 0xf1964: 0xf1964,
+ 0xf1965: 0xf1965,
+ 0xf1966: 0xf1966,
+ 0xf1967: 0xf1967,
+ 0xf1968: 0xf1968,
+ 0xf1969: 0xf1969,
+ 0xf196a: 0xf196a,
+ 0xf196b: 0xf196b,
+ 0xf196c: 0xf196c,
+ 0xf196d: 0xf196d,
+ 0xf196e: 0xf196e,
+ 0xf196f: 0xf196f,
+ 0xf1970: 0xf1970,
+ 0xf1971: 0xf1971,
+ 0xf1972: 0xf1972,
+ 0xf1973: 0xf1973,
+ 0xf1974: 0xf1974,
+ 0xf1975: 0xf1975,
+ 0xf1976: 0xf1976,
+ 0xf1977: 0xf1977,
+ 0xf1978: 0xf1978,
+ 0xf1979: 0xf1979,
+ 0xf197a: 0xf197a,
+ 0xf197b: 0xf197b,
+ 0xf197c: 0xf197c,
+ 0xf197d: 0xf197d,
+ 0xf197e: 0xf197e,
+ 0xf197f: 0xf197f,
+ 0xf1980: 0xf1980,
+ 0xf1981: 0xf1981,
+ 0xf1982: 0xf1982,
+ 0xf1983: 0xf1983,
+ 0xf1984: 0xf1984,
+ 0xf1985: 0xf1985,
+ 0xf1986: 0xf1986,
+ 0xf1987: 0xf1987,
+ 0xf1988: 0xf1988,
+ 0xf1989: 0xf1989,
+ 0xf198a: 0xf198a,
+ 0xf198b: 0xf198b,
+ 0xf198c: 0xf198c,
+ 0xf198d: 0xf198d,
+ 0xf198e: 0xf198e,
+ 0xf198f: 0xf198f,
+ 0xf1990: 0xf1990,
+ 0xf1991: 0xf1991,
+ 0xf1992: 0xf1992,
+ 0xf1993: 0xf1993,
+ 0xf1994: 0xf1994,
+ 0xf1995: 0xf1995,
+ 0xf1996: 0xf1996,
+ 0xf1997: 0xf1997,
+ 0xf1998: 0xf1998,
+ 0xf1999: 0xf1999,
+ 0xf199a: 0xf199a,
+ 0xf199b: 0xf199b,
+ 0xf199c: 0xf199c,
+ 0xf199d: 0xf199d,
+ 0xf199e: 0xf199e,
+ 0xf199f: 0xf199f,
+ 0xf19a0: 0xf19a0,
+ 0xf19a1: 0xf19a1,
+ 0xf19a2: 0xf19a2,
+ 0xf19a3: 0xf19a3,
+ 0xf19a4: 0xf19a4,
+ 0xf19a5: 0xf19a5,
+ 0xf19a6: 0xf19a6,
+ 0xf19a7: 0xf19a7,
+ 0xf19a8: 0xf19a8,
+ 0xf19a9: 0xf19a9,
+ 0xf19aa: 0xf19aa,
+ 0xf19ab: 0xf19ab,
+ 0xf19ac: 0xf19ac,
+ 0xf19ad: 0xf19ad,
+ 0xf19ae: 0xf19ae,
+ 0xf19af: 0xf19af,
+ 0xf19b0: 0xf19b0,
+ 0xf19b1: 0xf19b1,
+ 0xf19b2: 0xf19b2,
+ 0xf19b3: 0xf19b3,
+ 0xf19b4: 0xf19b4,
+ 0xf19b5: 0xf19b5,
+ 0xf19b6: 0xf19b6,
+ 0xf19b7: 0xf19b7,
+ 0xf19b8: 0xf19b8,
+ 0xf19b9: 0xf19b9,
+ 0xf19ba: 0xf19ba,
+ 0xf19bb: 0xf19bb,
+ 0xf19bc: 0xf19bc,
+ 0xf19bd: 0xf19bd,
+ 0xf19be: 0xf19be,
+ 0xf19bf: 0xf19bf,
+ 0xf19c0: 0xf19c0,
+ 0xf19c1: 0xf19c1,
+ 0xf19c2: 0xf19c2,
+ 0xf19c3: 0xf19c3,
+ 0xf19c4: 0xf19c4,
+ 0xf19c5: 0xf19c5,
+ 0xf19c6: 0xf19c6,
+ 0xf19c7: 0xf19c7,
+ 0xf19c8: 0xf19c8,
+ 0xf19c9: 0xf19c9,
+ 0xf19ca: 0xf19ca,
+ 0xf19cb: 0xf19cb,
+ 0xf19cc: 0xf19cc,
+ 0xf19cd: 0xf19cd,
+ 0xf19ce: 0xf19ce,
+ 0xf19cf: 0xf19cf,
+ 0xf19d0: 0xf19d0,
+ 0xf19d1: 0xf19d1,
+ 0xf19d2: 0xf19d2,
+ 0xf19d3: 0xf19d3,
+ 0xf19d4: 0xf19d4,
+ 0xf19d5: 0xf19d5,
+ 0xf19d6: 0xf19d6,
+ 0xf19d7: 0xf19d7,
+ 0xf19d8: 0xf19d8,
+ 0xf19d9: 0xf19d9,
+ 0xf19da: 0xf19da,
+ 0xf19db: 0xf19db,
+ 0xf19dc: 0xf19dc,
+ 0xf19dd: 0xf19dd,
+ 0xf19de: 0xf19de,
+ 0xf19df: 0xf19df,
+ 0xf19e0: 0xf19e0,
+ 0xf19e1: 0xf19e1,
+ 0xf19e2: 0xf19e2,
+ 0xf19e3: 0xf19e3,
+ 0xf19e4: 0xf19e4,
+ 0xf19e5: 0xf19e5,
+ 0xf19e6: 0xf19e6,
+ 0xf19e7: 0xf19e7,
+ 0xf19e8: 0xf19e8,
+ 0xf19e9: 0xf19e9,
+ 0xf19ea: 0xf19ea,
+ 0xf19eb: 0xf19eb,
+ 0xf19ec: 0xf19ec,
+ 0xf19ed: 0xf19ed,
+ 0xf19ee: 0xf19ee,
+ 0xf19ef: 0xf19ef,
+ 0xf19f0: 0xf19f0,
+ 0xf19f1: 0xf19f1,
+ 0xf19f2: 0xf19f2,
+ 0xf19f3: 0xf19f3,
+ 0xf19f4: 0xf19f4,
+ 0xf19f5: 0xf19f5,
+ 0xf19f6: 0xf19f6,
+ 0xf19f7: 0xf19f7,
+ 0xf19f8: 0xf19f8,
+ 0xf19f9: 0xf19f9,
+ 0xf19fa: 0xf19fa,
+ 0xf19fb: 0xf19fb,
+ 0xf19fc: 0xf19fc,
+ 0xf19fd: 0xf19fd,
+ 0xf19fe: 0xf19fe,
+ 0xf19ff: 0xf19ff,
+ 0xf1a00: 0xf1a00,
+ 0xf1a01: 0xf1a01,
+ 0xf1a02: 0xf1a02,
+ 0xf1a03: 0xf1a03,
+ 0xf1a04: 0xf1a04,
+ 0xf1a05: 0xf1a05,
+ 0xf1a06: 0xf1a06,
+ 0xf1a07: 0xf1a07,
+ 0xf1a08: 0xf1a08,
+ 0xf1a09: 0xf1a09,
+ 0xf1a0a: 0xf1a0a,
+ 0xf1a0b: 0xf1a0b,
+ 0xf1a0c: 0xf1a0c,
+ 0xf1a0d: 0xf1a0d,
+ 0xf1a0e: 0xf1a0e,
+ 0xf1a0f: 0xf1a0f,
+ 0xf1a10: 0xf1a10,
+ 0xf1a11: 0xf1a11,
+ 0xf1a12: 0xf1a12,
+ 0xf1a13: 0xf1a13,
+ 0xf1a14: 0xf1a14,
+ 0xf1a15: 0xf1a15,
+ 0xf1a16: 0xf1a16,
+ 0xf1a17: 0xf1a17,
+ 0xf1a18: 0xf1a18,
+ 0xf1a19: 0xf1a19,
+ 0xf1a1a: 0xf1a1a,
+ 0xf1a1b: 0xf1a1b,
+ 0xf1a1c: 0xf1a1c,
+ 0xf1a1d: 0xf1a1d,
+ 0xf1a1e: 0xf1a1e,
+ 0xf1a1f: 0xf1a1f,
+ 0xf1a20: 0xf1a20,
+ 0xf1a21: 0xf1a21,
+ 0xf1a22: 0xf1a22,
+ 0xf1a23: 0xf1a23,
+ 0xf1a24: 0xf1a24,
+ 0xf1a25: 0xf1a25,
+ 0xf1a26: 0xf1a26,
+ 0xf1a27: 0xf1a27,
+ 0xf1a28: 0xf1a28,
+ 0xf1a29: 0xf1a29,
+ 0xf1a2a: 0xf1a2a,
+ 0xf1a2b: 0xf1a2b,
+ 0xf1a2c: 0xf1a2c,
+ 0xf1a2d: 0xf1a2d,
+ 0xf1a2e: 0xf1a2e,
+ 0xf1a2f: 0xf1a2f,
+ 0xf1a30: 0xf1a30,
+ 0xf1a31: 0xf1a31,
+ 0xf1a32: 0xf1a32,
+ 0xf1a33: 0xf1a33,
+ 0xf1a34: 0xf1a34,
+ 0xf1a35: 0xf1a35,
+ 0xf1a36: 0xf1a36,
+ 0xf1a37: 0xf1a37,
+ 0xf1a38: 0xf1a38,
+ 0xf1a39: 0xf1a39,
+ 0xf1a3a: 0xf1a3a,
+ 0xf1a3b: 0xf1a3b,
+ 0xf1a3c: 0xf1a3c,
+ 0xf1a3d: 0xf1a3d,
+ 0xf1a3e: 0xf1a3e,
+ 0xf1a3f: 0xf1a3f,
+ 0xf1a40: 0xf1a40,
+ 0xf1a41: 0xf1a41,
+ 0xf1a42: 0xf1a42,
+ 0xf1a43: 0xf1a43,
+ 0xf1a44: 0xf1a44,
+ 0xf1a45: 0xf1a45,
+ 0xf1a46: 0xf1a46,
+ 0xf1a47: 0xf1a47,
+ 0xf1a48: 0xf1a48,
+ 0xf1a49: 0xf1a49,
+ 0xf1a4a: 0xf1a4a,
+ 0xf1a4b: 0xf1a4b,
+ 0xf1a4c: 0xf1a4c,
+ 0xf1a4d: 0xf1a4d,
+ 0xf1a4e: 0xf1a4e,
+ 0xf1a4f: 0xf1a4f,
+ 0xf1a50: 0xf1a50,
+ 0xf1a51: 0xf1a51,
+ 0xf1a52: 0xf1a52,
+ 0xf1a53: 0xf1a53,
+ 0xf1a54: 0xf1a54,
+ 0xf1a55: 0xf1a55,
+ 0xf1a56: 0xf1a56,
+ 0xf1a57: 0xf1a57,
+ 0xf1a58: 0xf1a58,
+ 0xf1a59: 0xf1a59,
+ 0xf1a5a: 0xf1a5a,
+ 0xf1a5b: 0xf1a5b,
+ 0xf1a5c: 0xf1a5c,
+ 0xf1a5d: 0xf1a5d,
+ 0xf1a5e: 0xf1a5e,
+ 0xf1a5f: 0xf1a5f,
+ 0xf1a60: 0xf1a60,
+ 0xf1a61: 0xf1a61,
+ 0xf1a62: 0xf1a62,
+ 0xf1a63: 0xf1a63,
+ 0xf1a64: 0xf1a64,
+ 0xf1a65: 0xf1a65,
+ 0xf1a66: 0xf1a66,
+ 0xf1a67: 0xf1a67,
+ 0xf1a68: 0xf1a68,
+ 0xf1a69: 0xf1a69,
+ 0xf1a6a: 0xf1a6a,
+ 0xf1a6b: 0xf1a6b,
+ 0xf1a6c: 0xf1a6c,
+ 0xf1a6d: 0xf1a6d,
+ 0xf1a6e: 0xf1a6e,
+ 0xf1a6f: 0xf1a6f,
+ 0xf1a70: 0xf1a70,
+ 0xf1a71: 0xf1a71,
+ 0xf1a72: 0xf1a72,
+ 0xf1a73: 0xf1a73,
+ 0xf1a74: 0xf1a74,
+ 0xf1a75: 0xf1a75,
+ 0xf1a76: 0xf1a76,
+ 0xf1a77: 0xf1a77,
+ 0xf1a78: 0xf1a78,
+ 0xf1a79: 0xf1a79,
+ 0xf1a7a: 0xf1a7a,
+ 0xf1a7b: 0xf1a7b,
+ 0xf1a7c: 0xf1a7c,
+ 0xf1a7d: 0xf1a7d,
+ 0xf1a7e: 0xf1a7e,
+ 0xf1a7f: 0xf1a7f,
+ 0xf1a80: 0xf1a80,
+ 0xf1a81: 0xf1a81,
+ 0xf1a82: 0xf1a82,
+ 0xf1a83: 0xf1a83,
+ 0xf1a84: 0xf1a84,
+ 0xf1a85: 0xf1a85,
+ 0xf1a86: 0xf1a86,
+ 0xf1a87: 0xf1a87,
+ 0xf1a88: 0xf1a88,
+ 0xf1a89: 0xf1a89,
+ 0xf1a8a: 0xf1a8a,
+ 0xf1a8b: 0xf1a8b,
+ 0xf1a8c: 0xf1a8c,
+ 0xf1a8d: 0xf1a8d,
+ 0xf1a8e: 0xf1a8e,
+ 0xf1a8f: 0xf1a8f,
+ 0xf1a90: 0xf1a90,
+ 0xf1a91: 0xf1a91,
+ 0xf1a92: 0xf1a92,
+ 0xf1a93: 0xf1a93,
+ 0xf1a94: 0xf1a94,
+ 0xf1a95: 0xf1a95,
+ 0xf1a96: 0xf1a96,
+ 0xf1a97: 0xf1a97,
+ 0xf1a98: 0xf1a98,
+ 0xf1a99: 0xf1a99,
+ 0xf1a9a: 0xf1a9a,
+ 0xf1a9b: 0xf1a9b,
+ 0xf1a9c: 0xf1a9c,
+ 0xf1a9d: 0xf1a9d,
+ 0xf1a9e: 0xf1a9e,
+ 0xf1a9f: 0xf1a9f,
+ 0xf1aa0: 0xf1aa0,
+ 0xf1aa1: 0xf1aa1,
+ 0xf1aa2: 0xf1aa2,
+ 0xf1aa3: 0xf1aa3,
+ 0xf1aa4: 0xf1aa4,
+ 0xf1aa5: 0xf1aa5,
+ 0xf1aa6: 0xf1aa6,
+ 0xf1aa7: 0xf1aa7,
+ 0xf1aa8: 0xf1aa8,
+ 0xf1aa9: 0xf1aa9,
+ 0xf1aaa: 0xf1aaa,
+ 0xf1aab: 0xf1aab,
+ 0xf1aac: 0xf1aac,
+ 0xf1aad: 0xf1aad,
+ 0xf1aae: 0xf1aae,
+ 0xf1aaf: 0xf1aaf,
+ 0xf1ab0: 0xf1ab0,
+ 0xf1ab1: 0xf1ab1,
+ 0xf1ab2: 0xf1ab2,
+ 0xf1ab3: 0xf1ab3,
+ 0xf1ab4: 0xf1ab4,
+ 0xf1ab5: 0xf1ab5,
+ 0xf1ab6: 0xf1ab6,
+ 0xf1ab7: 0xf1ab7,
+ 0xf1ab8: 0xf1ab8,
+ 0xf1ab9: 0xf1ab9,
+ 0xf1aba: 0xf1aba,
+ 0xf1abb: 0xf1abb,
+ 0xf1abc: 0xf1abc,
+ 0xf1abd: 0xf1abd,
+ 0xf1abe: 0xf1abe,
+ 0xf1abf: 0xf1abf,
+ 0xf1ac0: 0xf1ac0,
+ 0xf1ac1: 0xf1ac1,
+ 0xf1ac2: 0xf1ac2,
+ 0xf1ac3: 0xf1ac3,
+ 0xf1ac4: 0xf1ac4,
+ 0xf1ac5: 0xf1ac5,
+ 0xf1ac6: 0xf1ac6,
+ 0xf1ac7: 0xf1ac7,
+ 0xf1ac8: 0xf1ac8,
+ 0xf1ac9: 0xf1ac9,
+ 0xf1aca: 0xf1aca,
+ 0xf1acb: 0xf1acb,
+ 0xf1acc: 0xf1acc,
+ 0xf1acd: 0xf1acd,
+ 0xf1ace: 0xf1ace,
+ 0xf1acf: 0xf1acf,
+ 0xf1ad0: 0xf1ad0,
+ 0xf1ad1: 0xf1ad1,
+ 0xf1ad2: 0xf1ad2,
+ 0xf1ad3: 0xf1ad3,
+ 0xf1ad4: 0xf1ad4,
+ 0xf1ad5: 0xf1ad5,
+ 0xf1ad6: 0xf1ad6,
+ 0xf1ad7: 0xf1ad7,
+ 0xf1ad8: 0xf1ad8,
+ 0xf1ad9: 0xf1ad9,
+ 0xf1ada: 0xf1ada,
+ 0xf1adb: 0xf1adb,
+ 0xf1adc: 0xf1adc,
+ 0xf1add: 0xf1add,
+ 0xf1ade: 0xf1ade,
+ 0xf1adf: 0xf1adf,
+ 0xf1ae0: 0xf1ae0,
+ 0xf1ae1: 0xf1ae1,
+ 0xf1ae2: 0xf1ae2,
+ 0xf1ae3: 0xf1ae3,
+ 0xf1ae4: 0xf1ae4,
+ 0xf1ae5: 0xf1ae5,
+ 0xf1ae6: 0xf1ae6,
+ 0xf1ae7: 0xf1ae7,
+ 0xf1ae8: 0xf1ae8,
+ 0xf1ae9: 0xf1ae9,
+ 0xf1aea: 0xf1aea,
+ 0xf1aeb: 0xf1aeb,
+ 0xf1aec: 0xf1aec,
+ 0xf1aed: 0xf1aed,
+ 0xf1aee: 0xf1aee,
+ 0xf1aef: 0xf1aef,
+ 0xf1af0: 0xf1af0,
+ },
+ "Weather Icons": {
+ 0xf000: 0xe300,
+ 0xf001: 0xe301,
+ 0xf002: 0xe302,
+ 0xf003: 0xe303,
+ 0xf004: 0xe304,
+ 0xf005: 0xe305,
+ 0xf006: 0xe306,
+ 0xf007: 0xe307,
+ 0xf008: 0xe308,
+ 0xf009: 0xe309,
+ 0xf00a: 0xe30a,
+ 0xf00b: 0xe30b,
+ 0xf00c: 0xe30c,
+ 0xf00d: 0xe30d,
+ 0xf00e: 0xe30e,
+ 0xf010: 0xe30f,
+ 0xf011: 0xe310,
+ 0xf012: 0xe311,
+ 0xf013: 0xe312,
+ 0xf014: 0xe313,
+ 0xf015: 0xe314,
+ 0xf016: 0xe315,
+ 0xf017: 0xe316,
+ 0xf018: 0xe317,
+ 0xf019: 0xe318,
+ 0xf01a: 0xe319,
+ 0xf01b: 0xe31a,
+ 0xf01c: 0xe31b,
+ 0xf01d: 0xe31c,
+ 0xf01e: 0xe31d,
+ 0xf021: 0xe31e,
+ 0xf022: 0xe31f,
+ 0xf023: 0xe320,
+ 0xf024: 0xe321,
+ 0xf025: 0xe322,
+ 0xf026: 0xe323,
+ 0xf027: 0xe324,
+ 0xf028: 0xe325,
+ 0xf029: 0xe326,
+ 0xf02a: 0xe327,
+ 0xf02b: 0xe328,
+ 0xf02c: 0xe329,
+ 0xf02d: 0xe32a,
+ 0xf02e: 0xe32b,
+ 0xf02f: 0xe32c,
+ 0xf030: 0xe32d,
+ 0xf031: 0xe32e,
+ 0xf032: 0xe32f,
+ 0xf033: 0xe330,
+ 0xf034: 0xe331,
+ 0xf035: 0xe332,
+ 0xf036: 0xe333,
+ 0xf037: 0xe334,
+ 0xf038: 0xe335,
+ 0xf039: 0xe336,
+ 0xf03a: 0xe337,
+ 0xf03b: 0xe338,
+ 0xf03c: 0xe339,
+ 0xf03d: 0xe33a,
+ 0xf03e: 0xe33b,
+ 0xf040: 0xe33c,
+ 0xf041: 0xe33d,
+ 0xf042: 0xe33e,
+ 0xf043: 0xe33f,
+ 0xf044: 0xe340,
+ 0xf045: 0xe341,
+ 0xf046: 0xe342,
+ 0xf047: 0xe343,
+ 0xf048: 0xe344,
+ 0xf049: 0xe345,
+ 0xf04a: 0xe346,
+ 0xf04b: 0xe347,
+ 0xf04c: 0xe348,
+ 0xf04d: 0xe349,
+ 0xf04e: 0xe34a,
+ 0xf050: 0xe34b,
+ 0xf051: 0xe34c,
+ 0xf052: 0xe34d,
+ 0xf053: 0xe34e,
+ 0xf054: 0xe34f,
+ 0xf055: 0xe350,
+ 0xf056: 0xe351,
+ 0xf057: 0xe352,
+ 0xf058: 0xe353,
+ 0xf059: 0xe354,
+ 0xf05a: 0xe355,
+ 0xf05b: 0xe356,
+ 0xf05c: 0xe357,
+ 0xf05d: 0xe358,
+ 0xf05e: 0xe359,
+ 0xf060: 0xe35a,
+ 0xf061: 0xe35b,
+ 0xf062: 0xe35c,
+ 0xf063: 0xe35d,
+ 0xf064: 0xe35e,
+ 0xf065: 0xe35f,
+ 0xf066: 0xe360,
+ 0xf067: 0xe361,
+ 0xf068: 0xe362,
+ 0xf069: 0xe363,
+ 0xf06a: 0xe364,
+ 0xf06b: 0xe365,
+ 0xf06c: 0xe366,
+ 0xf06d: 0xe367,
+ 0xf06e: 0xe368,
+ 0xf070: 0xe369,
+ 0xf071: 0xe36a,
+ 0xf072: 0xe36b,
+ 0xf073: 0xe36c,
+ 0xf074: 0xe36d,
+ 0xf075: 0xe36e,
+ 0xf076: 0xe36f,
+ 0xf077: 0xe370,
+ 0xf078: 0xe371,
+ 0xf079: 0xe372,
+ 0xf07a: 0xe373,
+ 0xf07b: 0xe374,
+ 0xf07c: 0xe375,
+ 0xf07d: 0xe376,
+ 0xf07e: 0xe377,
+ 0xf080: 0xe378,
+ 0xf081: 0xe379,
+ 0xf082: 0xe37a,
+ 0xf083: 0xe37b,
+ 0xf084: 0xe37c,
+ 0xf085: 0xe37d,
+ 0xf086: 0xe37e,
+ 0xf087: 0xe37f,
+ 0xf088: 0xe380,
+ 0xf089: 0xe381,
+ 0xf08a: 0xe382,
+ 0xf08b: 0xe383,
+ 0xf08c: 0xe384,
+ 0xf08d: 0xe385,
+ 0xf08e: 0xe386,
+ 0xf08f: 0xe387,
+ 0xf090: 0xe388,
+ 0xf091: 0xe389,
+ 0xf092: 0xe38a,
+ 0xf093: 0xe38b,
+ 0xf094: 0xe38c,
+ 0xf095: 0xe38d,
+ 0xf096: 0xe38e,
+ 0xf097: 0xe38f,
+ 0xf098: 0xe390,
+ 0xf099: 0xe391,
+ 0xf09a: 0xe392,
+ 0xf09b: 0xe393,
+ 0xf09c: 0xe394,
+ 0xf09d: 0xe395,
+ 0xf09e: 0xe396,
+ 0xf09f: 0xe397,
+ 0xf0a0: 0xe398,
+ 0xf0a1: 0xe399,
+ 0xf0a2: 0xe39a,
+ 0xf0a3: 0xe39b,
+ 0xf0a4: 0xe39c,
+ 0xf0a5: 0xe39d,
+ 0xf0a6: 0xe39e,
+ 0xf0a7: 0xe39f,
+ 0xf0a8: 0xe3a0,
+ 0xf0a9: 0xe3a1,
+ 0xf0aa: 0xe3a2,
+ 0xf0ab: 0xe3a3,
+ 0xf0ac: 0xe3a4,
+ 0xf0ad: 0xe3a5,
+ 0xf0ae: 0xe3a6,
+ 0xf0af: 0xe3a7,
+ 0xf0b0: 0xe3a8,
+ 0xf0b1: 0xe3a9,
+ 0xf0b2: 0xe3aa,
+ 0xf0b3: 0xe3ab,
+ 0xf0b4: 0xe3ac,
+ 0xf0b5: 0xe3ad,
+ 0xf0b6: 0xe3ae,
+ 0xf0b7: 0xe3af,
+ 0xf0b8: 0xe3b0,
+ 0xf0b9: 0xe3b1,
+ 0xf0ba: 0xe3b2,
+ 0xf0bb: 0xe3b3,
+ 0xf0bc: 0xe3b4,
+ 0xf0bd: 0xe3b5,
+ 0xf0be: 0xe3b6,
+ 0xf0bf: 0xe3b7,
+ 0xf0c0: 0xe3b8,
+ 0xf0c1: 0xe3b9,
+ 0xf0c2: 0xe3ba,
+ 0xf0c3: 0xe3bb,
+ 0xf0c4: 0xe3bc,
+ 0xf0c5: 0xe3bd,
+ 0xf0c6: 0xe3be,
+ 0xf0c7: 0xe3bf,
+ 0xf0c8: 0xe3c0,
+ 0xf0c9: 0xe3c1,
+ 0xf0ca: 0xe3c2,
+ 0xf0cb: 0xe3c3,
+ 0xf0cc: 0xe3c4,
+ 0xf0cd: 0xe3c5,
+ 0xf0ce: 0xe3c6,
+ 0xf0cf: 0xe3c7,
+ 0xf0d0: 0xe3c8,
+ 0xf0d1: 0xe3c9,
+ 0xf0d2: 0xe3ca,
+ 0xf0d3: 0xe3cb,
+ 0xf0d4: 0xe3cc,
+ 0xf0d5: 0xe3cd,
+ 0xf0d6: 0xe3ce,
+ 0xf0d7: 0xe3cf,
+ 0xf0d8: 0xe3d0,
+ 0xf0d9: 0xe3d1,
+ 0xf0da: 0xe3d2,
+ 0xf0db: 0xe3d3,
+ 0xf0dc: 0xe3d4,
+ 0xf0dd: 0xe3d5,
+ 0xf0de: 0xe3d6,
+ 0xf0df: 0xe3d7,
+ 0xf0e0: 0xe3d8,
+ 0xf0e1: 0xe3d9,
+ 0xf0e2: 0xe3da,
+ 0xf0e3: 0xe3db,
+ 0xf0e4: 0xe3dc,
+ 0xf0e5: 0xe3dd,
+ 0xf0e6: 0xe3de,
+ 0xf0e7: 0xe3df,
+ 0xf0e8: 0xe3e0,
+ 0xf0e9: 0xe3e1,
+ 0xf0ea: 0xe3e2,
+ 0xf0eb: 0xe3e3,
+ },
+ "Font Logos": {
+ 0xf300: 0xf300,
+ 0xf301: 0xf301,
+ 0xf302: 0xf302,
+ 0xf303: 0xf303,
+ 0xf304: 0xf304,
+ 0xf305: 0xf305,
+ 0xf306: 0xf306,
+ 0xf307: 0xf307,
+ 0xf308: 0xf308,
+ 0xf309: 0xf309,
+ 0xf30a: 0xf30a,
+ 0xf30b: 0xf30b,
+ 0xf30c: 0xf30c,
+ 0xf30d: 0xf30d,
+ 0xf30e: 0xf30e,
+ 0xf30f: 0xf30f,
+ 0xf310: 0xf310,
+ 0xf311: 0xf311,
+ 0xf312: 0xf312,
+ 0xf313: 0xf313,
+ 0xf314: 0xf314,
+ 0xf315: 0xf315,
+ 0xf316: 0xf316,
+ 0xf317: 0xf317,
+ 0xf318: 0xf318,
+ 0xf319: 0xf319,
+ 0xf31a: 0xf31a,
+ 0xf31b: 0xf31b,
+ 0xf31c: 0xf31c,
+ 0xf31d: 0xf31d,
+ 0xf31e: 0xf31e,
+ 0xf31f: 0xf31f,
+ 0xf320: 0xf320,
+ 0xf321: 0xf321,
+ 0xf322: 0xf322,
+ 0xf323: 0xf323,
+ 0xf324: 0xf324,
+ 0xf325: 0xf325,
+ 0xf326: 0xf326,
+ 0xf327: 0xf327,
+ 0xf328: 0xf328,
+ 0xf329: 0xf329,
+ 0xf32a: 0xf32a,
+ 0xf32b: 0xf32b,
+ 0xf32c: 0xf32c,
+ 0xf32d: 0xf32d,
+ 0xf32e: 0xf32e,
+ 0xf32f: 0xf32f,
+ 0xf330: 0xf330,
+ 0xf331: 0xf331,
+ 0xf332: 0xf332,
+ 0xf333: 0xf333,
+ 0xf334: 0xf334,
+ 0xf335: 0xf335,
+ 0xf336: 0xf336,
+ 0xf337: 0xf337,
+ 0xf338: 0xf338,
+ 0xf339: 0xf339,
+ 0xf33a: 0xf33a,
+ 0xf33b: 0xf33b,
+ 0xf33c: 0xf33c,
+ 0xf33d: 0xf33d,
+ 0xf33e: 0xf33e,
+ 0xf33f: 0xf33f,
+ 0xf340: 0xf340,
+ 0xf341: 0xf341,
+ 0xf342: 0xf342,
+ 0xf343: 0xf343,
+ 0xf344: 0xf344,
+ 0xf345: 0xf345,
+ 0xf346: 0xf346,
+ 0xf347: 0xf347,
+ 0xf348: 0xf348,
+ 0xf349: 0xf349,
+ 0xf34a: 0xf34a,
+ 0xf34b: 0xf34b,
+ 0xf34c: 0xf34c,
+ 0xf34d: 0xf34d,
+ 0xf34e: 0xf34e,
+ 0xf34f: 0xf34f,
+ 0xf350: 0xf350,
+ 0xf351: 0xf351,
+ 0xf352: 0xf352,
+ 0xf353: 0xf353,
+ 0xf354: 0xf354,
+ 0xf355: 0xf355,
+ 0xf356: 0xf356,
+ 0xf357: 0xf357,
+ 0xf358: 0xf358,
+ 0xf359: 0xf359,
+ 0xf35a: 0xf35a,
+ 0xf35b: 0xf35b,
+ 0xf35c: 0xf35c,
+ 0xf35d: 0xf35d,
+ 0xf35e: 0xf35e,
+ 0xf35f: 0xf35f,
+ 0xf360: 0xf360,
+ 0xf361: 0xf361,
+ 0xf362: 0xf362,
+ 0xf363: 0xf363,
+ 0xf364: 0xf364,
+ 0xf365: 0xf365,
+ 0xf366: 0xf366,
+ 0xf367: 0xf367,
+ 0xf368: 0xf368,
+ 0xf369: 0xf369,
+ 0xf36a: 0xf36a,
+ 0xf36b: 0xf36b,
+ 0xf36c: 0xf36c,
+ 0xf36d: 0xf36d,
+ 0xf36e: 0xf36e,
+ 0xf36f: 0xf36f,
+ 0xf370: 0xf370,
+ 0xf371: 0xf371,
+ 0xf372: 0xf372,
+ 0xf373: 0xf373,
+ 0xf374: 0xf374,
+ 0xf375: 0xf375,
+ 0xf376: 0xf376,
+ 0xf377: 0xf377,
+ 0xf378: 0xf378,
+ 0xf379: 0xf379,
+ 0xf37a: 0xf37a,
+ 0xf37b: 0xf37b,
+ 0xf37c: 0xf37c,
+ 0xf37d: 0xf37d,
+ 0xf37e: 0xf37e,
+ 0xf37f: 0xf37f,
+ 0xf380: 0xf380,
+ 0xf381: 0xf381,
+ },
+ "Octicons": {
+ 0xf000: 0xf400,
+ 0xf001: 0xf401,
+ 0xf002: 0xf402,
+ 0xf005: 0xf403,
+ 0xf006: 0xf404,
+ 0xf007: 0xf405,
+ 0xf008: 0xf406,
+ 0xf009: 0xf407,
+ 0xf00a: 0xf408,
+ 0xf00b: 0xf409,
+ 0xf00c: 0xf40a,
+ 0xf00d: 0xf40b,
+ 0xf00e: 0xf40c,
+ 0xf010: 0xf40d,
+ 0xf011: 0xf40e,
+ 0xf012: 0xf40f,
+ 0xf013: 0xf410,
+ 0xf014: 0xf411,
+ 0xf015: 0xf412,
+ 0xf016: 0xf413,
+ 0xf017: 0xf414,
+ 0xf018: 0xf415,
+ 0xf019: 0xf416,
+ 0xf01f: 0xf417,
+ 0xf020: 0xf418,
+ 0xf023: 0xf419,
+ 0xf024: 0xf41a,
+ 0xf026: 0xf41b,
+ 0xf027: 0xf41c,
+ 0xf028: 0xf41d,
+ 0xf02a: 0xf41e,
+ 0xf02b: 0xf41f,
+ 0xf02c: 0xf420,
+ 0xf02d: 0xf421,
+ 0xf02e: 0xf422,
+ 0xf02f: 0xf423,
+ 0xf030: 0xf424,
+ 0xf031: 0xf425,
+ 0xf032: 0xf426,
+ 0xf033: 0xf427,
+ 0xf034: 0xf428,
+ 0xf035: 0xf429,
+ 0xf036: 0xf42a,
+ 0xf037: 0xf42b,
+ 0xf038: 0xf42c,
+ 0xf039: 0xf42d,
+ 0xf03a: 0xf42e,
+ 0xf03b: 0xf42f,
+ 0xf03c: 0xf430,
+ 0xf03d: 0xf431,
+ 0xf03e: 0xf432,
+ 0xf03f: 0xf433,
+ 0xf040: 0xf434,
+ 0xf041: 0xf435,
+ 0xf042: 0xf436,
+ 0xf043: 0xf437,
+ 0xf044: 0xf438,
+ 0xf045: 0xf439,
+ 0xf046: 0xf43a,
+ 0xf047: 0xf43b,
+ 0xf048: 0xf43c,
+ 0xf049: 0xf43d,
+ 0xf04a: 0xf43e,
+ 0xf04c: 0xf43f,
+ 0xf04d: 0xf440,
+ 0xf04e: 0xf441,
+ 0xf04f: 0xf442,
+ 0xf051: 0xf443,
+ 0xf052: 0xf444,
+ 0xf053: 0xf445,
+ 0xf056: 0xf446,
+ 0xf057: 0xf447,
+ 0xf058: 0xf448,
+ 0xf059: 0xf449,
+ 0xf05a: 0xf44a,
+ 0xf05b: 0xf44b,
+ 0xf05c: 0xf44c,
+ 0xf05d: 0xf44d,
+ 0xf05e: 0xf44e,
+ 0xf05f: 0xf44f,
+ 0xf060: 0xf450,
+ 0xf061: 0xf451,
+ 0xf062: 0xf452,
+ 0xf063: 0xf453,
+ 0xf064: 0xf454,
+ 0xf068: 0xf455,
+ 0xf06a: 0xf456,
+ 0xf06b: 0xf457,
+ 0xf06c: 0xf458,
+ 0xf06d: 0xf459,
+ 0xf06e: 0xf45a,
+ 0xf070: 0xf45b,
+ 0xf071: 0xf45c,
+ 0xf075: 0xf45d,
+ 0xf076: 0xf45e,
+ 0xf077: 0xf45f,
+ 0xf078: 0xf460,
+ 0xf07b: 0xf461,
+ 0xf07c: 0xf462,
+ 0xf07d: 0xf463,
+ 0xf07e: 0xf464,
+ 0xf07f: 0xf465,
+ 0xf080: 0xf466,
+ 0xf081: 0xf467,
+ 0xf084: 0xf468,
+ 0xf085: 0xf469,
+ 0xf087: 0xf46a,
+ 0xf088: 0xf46b,
+ 0xf08c: 0xf46c,
+ 0xf08d: 0xf46d,
+ 0xf08f: 0xf46e,
+ 0xf091: 0xf46f,
+ 0xf092: 0xf470,
+ 0xf094: 0xf471,
+ 0xf096: 0xf472,
+ 0xf097: 0xf473,
+ 0xf099: 0xf474,
+ 0xf09a: 0xf475,
+ 0xf09c: 0xf476,
+ 0xf09d: 0xf477,
+ 0xf09f: 0xf478,
+ 0xf0a0: 0xf479,
+ 0xf0a1: 0xf47a,
+ 0xf0a2: 0xf47b,
+ 0xf0a3: 0xf47c,
+ 0xf0a4: 0xf47d,
+ 0xf0aa: 0xf47e,
+ 0xf0ac: 0xf47f,
+ 0xf0ad: 0xf480,
+ 0xf0b0: 0xf481,
+ 0xf0b1: 0xf482,
+ 0xf0b2: 0xf483,
+ 0xf0b6: 0xf484,
+ 0xf0ba: 0xf485,
+ 0xf0be: 0xf486,
+ 0xf0c4: 0xf487,
+ 0xf0c5: 0xf488,
+ 0xf0c8: 0xf489,
+ 0xf0c9: 0xf48a,
+ 0xf0ca: 0xf48b,
+ 0xf0cc: 0xf48c,
+ 0xf0cf: 0xf48d,
+ 0xf0d0: 0xf48e,
+ 0xf0d1: 0xf48f,
+ 0xf0d2: 0xf490,
+ 0xf0d3: 0xf491,
+ 0xf0d4: 0xf492,
+ 0xf0d6: 0xf493,
+ 0xf0d7: 0xf494,
+ 0xf0d8: 0xf495,
+ 0xf0da: 0xf496,
+ 0xf0db: 0xf497,
+ 0xf0dc: 0xf498,
+ 0xf0dd: 0xf499,
+ 0xf0de: 0xf49a,
+ 0xf0e0: 0xf49b,
+ 0xf0e1: 0xf49c,
+ 0xf0e2: 0xf49d,
+ 0xf0e3: 0xf49e,
+ 0xf0e4: 0xf49f,
+ 0xf0e5: 0xf4a0,
+ 0xf0e6: 0xf4a1,
+ 0xf0e7: 0xf4a2,
+ 0xf0e8: 0xf4a3,
+ 0xf101: 0xf4a4,
+ 0xf102: 0xf4a5,
+ 0xf103: 0xf4a6,
+ 0xf104: 0xf4a7,
+ 0xf105: 0xf4a8,
+ 0x2665: 0x2665,
+ 0x26a1: 0x26a1,
+ 0xf27c: 0xf4a9,
+ 0xf27d: 0xf4aa,
+ 0xf27e: 0xf4ab,
+ 0xf27f: 0xf4ac,
+ 0xf280: 0xf4ad,
+ 0xf281: 0xf4ae,
+ 0xf282: 0xf4af,
+ 0xf283: 0xf4b0,
+ 0xf284: 0xf4b1,
+ 0xf285: 0xf4b2,
+ 0xf286: 0xf4b3,
+ 0xf287: 0xf4b4,
+ 0xf288: 0xf4b5,
+ 0xf289: 0xf4b6,
+ 0xf28a: 0xf4b7,
+ 0xf28b: 0xf4b8,
+ 0xf28c: 0xf4b9,
+ 0xf28d: 0xf4ba,
+ 0xf28e: 0xf4bb,
+ 0xf28f: 0xf4bc,
+ 0xf290: 0xf4bd,
+ 0xf291: 0xf4be,
+ 0xf292: 0xf4bf,
+ 0xf293: 0xf4c0,
+ 0xf294: 0xf4c1,
+ 0xf295: 0xf4c2,
+ 0xf296: 0xf4c3,
+ 0xf297: 0xf4c4,
+ 0xf298: 0xf4c5,
+ 0xf299: 0xf4c6,
+ 0xf29a: 0xf4c7,
+ 0xf29b: 0xf4c8,
+ 0xf29c: 0xf4c9,
+ 0xf29d: 0xf4ca,
+ 0xf29e: 0xf4cb,
+ 0xf29f: 0xf4cc,
+ 0xf2a0: 0xf4cd,
+ 0xf2a1: 0xf4ce,
+ 0xf2a2: 0xf4cf,
+ 0xf2a3: 0xf4d0,
+ 0xf2a4: 0xf4d1,
+ 0xf2a5: 0xf4d2,
+ 0xf2a6: 0xf4d3,
+ 0xf2a7: 0xf4d4,
+ 0xf2a8: 0xf4d5,
+ 0xf2a9: 0xf4d6,
+ 0xf2aa: 0xf4d7,
+ 0xf2ab: 0xf4d8,
+ 0xf2ac: 0xf4d9,
+ 0xf2ad: 0xf4da,
+ 0xf2ae: 0xf4db,
+ 0xf2af: 0xf4dc,
+ 0xf2b0: 0xf4dd,
+ 0xf2b1: 0xf4de,
+ 0xf2b2: 0xf4df,
+ 0xf2b3: 0xf4e0,
+ 0xf2b4: 0xf4e1,
+ 0xf2b5: 0xf4e2,
+ 0xf2b6: 0xf4e3,
+ 0xf2b7: 0xf4e4,
+ 0xf2b8: 0xf4e5,
+ 0xf2b9: 0xf4e6,
+ 0xf2ba: 0xf4e7,
+ 0xf2bb: 0xf4e8,
+ 0xf2bc: 0xf4e9,
+ 0xf2bd: 0xf4ea,
+ 0xf2be: 0xf4eb,
+ 0xf2bf: 0xf4ec,
+ 0xf2c0: 0xf4ed,
+ 0xf2c1: 0xf4ee,
+ 0xf2c2: 0xf4ef,
+ 0xf2c3: 0xf4f0,
+ 0xf2c4: 0xf4f1,
+ 0xf2c5: 0xf4f2,
+ 0xf2c6: 0xf4f3,
+ 0xf2c7: 0xf4f4,
+ 0xf2c8: 0xf4f5,
+ 0xf2c9: 0xf4f6,
+ 0xf2ca: 0xf4f7,
+ 0xf2cb: 0xf4f8,
+ 0xf2cc: 0xf4f9,
+ 0xf2cd: 0xf4fa,
+ 0xf2ce: 0xf4fb,
+ 0xf2cf: 0xf4fc,
+ 0xf2d0: 0xf4fd,
+ 0xf2d1: 0xf4fe,
+ 0xf2d2: 0xf4ff,
+ 0xf2d3: 0xf500,
+ 0xf2d4: 0xf501,
+ 0xf2d5: 0xf502,
+ 0xf2d6: 0xf503,
+ 0xf2d7: 0xf504,
+ 0xf2d8: 0xf505,
+ 0xf2d9: 0xf506,
+ 0xf2da: 0xf507,
+ 0xf2db: 0xf508,
+ 0xf2dc: 0xf509,
+ 0xf2dd: 0xf50a,
+ 0xf2de: 0xf50b,
+ 0xf2df: 0xf50c,
+ 0xf2e0: 0xf50d,
+ 0xf2e1: 0xf50e,
+ 0xf2e2: 0xf50f,
+ 0xf2e3: 0xf510,
+ 0xf2e4: 0xf511,
+ 0xf2e5: 0xf512,
+ 0xf2e6: 0xf513,
+ 0xf2e7: 0xf514,
+ 0xf2e8: 0xf515,
+ 0xf2e9: 0xf516,
+ 0xf2ea: 0xf517,
+ 0xf2eb: 0xf518,
+ 0xf2ec: 0xf519,
+ 0xf2ed: 0xf51a,
+ 0xf2ee: 0xf51b,
+ 0xf2ef: 0xf51c,
+ 0xf2f0: 0xf51d,
+ 0xf2f1: 0xf51e,
+ 0xf2f2: 0xf51f,
+ 0xf2f3: 0xf520,
+ 0xf2f4: 0xf521,
+ 0xf2f5: 0xf522,
+ 0xf2f6: 0xf523,
+ 0xf2f7: 0xf524,
+ 0xf2f8: 0xf525,
+ 0xf2f9: 0xf526,
+ 0xf2fa: 0xf527,
+ 0xf2fb: 0xf528,
+ 0xf2fc: 0xf529,
+ 0xf2fd: 0xf52a,
+ 0xf2fe: 0xf52b,
+ 0xf2ff: 0xf52c,
+ 0xf300: 0xf52d,
+ 0xf301: 0xf52e,
+ 0xf302: 0xf52f,
+ 0xf303: 0xf530,
+ 0xf304: 0xf531,
+ 0xf305: 0xf532,
+ 0xf306: 0xf533,
+ },
+ "Codicons": {
+ 0xea60: 0xea60,
+ 0xea61: 0xea61,
+ 0xea62: 0xea62,
+ 0xea63: 0xea63,
+ 0xea64: 0xea64,
+ 0xea65: 0xea65,
+ 0xea66: 0xea66,
+ 0xea67: 0xea67,
+ 0xea68: 0xea68,
+ 0xea69: 0xea69,
+ 0xea6a: 0xea6a,
+ 0xea6b: 0xea6b,
+ 0xea6c: 0xea6c,
+ 0xea6d: 0xea6d,
+ 0xea6e: 0xea6e,
+ 0xea6f: 0xea6f,
+ 0xea70: 0xea70,
+ 0xea71: 0xea71,
+ 0xea72: 0xea72,
+ 0xea73: 0xea73,
+ 0xea74: 0xea74,
+ 0xea75: 0xea75,
+ 0xea76: 0xea76,
+ 0xea77: 0xea77,
+ 0xea78: 0xea78,
+ 0xea79: 0xea79,
+ 0xea7a: 0xea7a,
+ 0xea7b: 0xea7b,
+ 0xea7c: 0xea7c,
+ 0xea7d: 0xea7d,
+ 0xea7e: 0xea7e,
+ 0xea7f: 0xea7f,
+ 0xea80: 0xea80,
+ 0xea81: 0xea81,
+ 0xea82: 0xea82,
+ 0xea83: 0xea83,
+ 0xea84: 0xea84,
+ 0xea85: 0xea85,
+ 0xea86: 0xea86,
+ 0xea87: 0xea87,
+ 0xea88: 0xea88,
+ 0xea8a: 0xea8a,
+ 0xea8b: 0xea8b,
+ 0xea8c: 0xea8c,
+ 0xea8f: 0xea8f,
+ 0xea90: 0xea90,
+ 0xea91: 0xea91,
+ 0xea92: 0xea92,
+ 0xea93: 0xea93,
+ 0xea94: 0xea94,
+ 0xea95: 0xea95,
+ 0xea96: 0xea96,
+ 0xea97: 0xea97,
+ 0xea98: 0xea98,
+ 0xea99: 0xea99,
+ 0xea9a: 0xea9a,
+ 0xea9b: 0xea9b,
+ 0xea9c: 0xea9c,
+ 0xea9d: 0xea9d,
+ 0xea9e: 0xea9e,
+ 0xea9f: 0xea9f,
+ 0xeaa0: 0xeaa0,
+ 0xeaa1: 0xeaa1,
+ 0xeaa2: 0xeaa2,
+ 0xeaa3: 0xeaa3,
+ 0xeaa4: 0xeaa4,
+ 0xeaa5: 0xeaa5,
+ 0xeaa6: 0xeaa6,
+ 0xeaa7: 0xeaa7,
+ 0xeaa8: 0xeaa8,
+ 0xeaa9: 0xeaa9,
+ 0xeaaa: 0xeaaa,
+ 0xeaab: 0xeaab,
+ 0xeaac: 0xeaac,
+ 0xeaad: 0xeaad,
+ 0xeaae: 0xeaae,
+ 0xeaaf: 0xeaaf,
+ 0xeab0: 0xeab0,
+ 0xeab1: 0xeab1,
+ 0xeab2: 0xeab2,
+ 0xeab3: 0xeab3,
+ 0xeab4: 0xeab4,
+ 0xeab5: 0xeab5,
+ 0xeab6: 0xeab6,
+ 0xeab7: 0xeab7,
+ 0xeab8: 0xeab8,
+ 0xeab9: 0xeab9,
+ 0xeaba: 0xeaba,
+ 0xeabb: 0xeabb,
+ 0xeabc: 0xeabc,
+ 0xeabd: 0xeabd,
+ 0xeabe: 0xeabe,
+ 0xeabf: 0xeabf,
+ 0xeac0: 0xeac0,
+ 0xeac1: 0xeac1,
+ 0xeac2: 0xeac2,
+ 0xeac3: 0xeac3,
+ 0xeac4: 0xeac4,
+ 0xeac5: 0xeac5,
+ 0xeac6: 0xeac6,
+ 0xeac7: 0xeac7,
+ 0xeac9: 0xeac9,
+ 0xeacc: 0xeacc,
+ 0xeacd: 0xeacd,
+ 0xeace: 0xeace,
+ 0xeacf: 0xeacf,
+ 0xead0: 0xead0,
+ 0xead1: 0xead1,
+ 0xead2: 0xead2,
+ 0xead3: 0xead3,
+ 0xead4: 0xead4,
+ 0xead5: 0xead5,
+ 0xead6: 0xead6,
+ 0xead7: 0xead7,
+ 0xead8: 0xead8,
+ 0xead9: 0xead9,
+ 0xeada: 0xeada,
+ 0xeadb: 0xeadb,
+ 0xeadc: 0xeadc,
+ 0xeadd: 0xeadd,
+ 0xeade: 0xeade,
+ 0xeadf: 0xeadf,
+ 0xeae0: 0xeae0,
+ 0xeae1: 0xeae1,
+ 0xeae2: 0xeae2,
+ 0xeae3: 0xeae3,
+ 0xeae4: 0xeae4,
+ 0xeae5: 0xeae5,
+ 0xeae6: 0xeae6,
+ 0xeae7: 0xeae7,
+ 0xeae8: 0xeae8,
+ 0xeae9: 0xeae9,
+ 0xeaea: 0xeaea,
+ 0xeaeb: 0xeaeb,
+ 0xeaec: 0xeaec,
+ 0xeaed: 0xeaed,
+ 0xeaee: 0xeaee,
+ 0xeaef: 0xeaef,
+ 0xeaf0: 0xeaf0,
+ 0xeaf1: 0xeaf1,
+ 0xeaf2: 0xeaf2,
+ 0xeaf3: 0xeaf3,
+ 0xeaf4: 0xeaf4,
+ 0xeaf5: 0xeaf5,
+ 0xeaf6: 0xeaf6,
+ 0xeaf7: 0xeaf7,
+ 0xeaf8: 0xeaf8,
+ 0xeaf9: 0xeaf9,
+ 0xeafa: 0xeafa,
+ 0xeafb: 0xeafb,
+ 0xeafc: 0xeafc,
+ 0xeafd: 0xeafd,
+ 0xeafe: 0xeafe,
+ 0xeaff: 0xeaff,
+ 0xeb00: 0xeb00,
+ 0xeb01: 0xeb01,
+ 0xeb02: 0xeb02,
+ 0xeb03: 0xeb03,
+ 0xeb04: 0xeb04,
+ 0xeb05: 0xeb05,
+ 0xeb06: 0xeb06,
+ 0xeb07: 0xeb07,
+ 0xeb08: 0xeb08,
+ 0xeb09: 0xeb09,
+ 0xeb0b: 0xeb0b,
+ 0xeb0c: 0xeb0c,
+ 0xeb0d: 0xeb0d,
+ 0xeb0e: 0xeb0e,
+ 0xeb0f: 0xeb0f,
+ 0xeb10: 0xeb10,
+ 0xeb11: 0xeb11,
+ 0xeb12: 0xeb12,
+ 0xeb13: 0xeb13,
+ 0xeb14: 0xeb14,
+ 0xeb15: 0xeb15,
+ 0xeb16: 0xeb16,
+ 0xeb17: 0xeb17,
+ 0xeb18: 0xeb18,
+ 0xeb19: 0xeb19,
+ 0xeb1a: 0xeb1a,
+ 0xeb1b: 0xeb1b,
+ 0xeb1c: 0xeb1c,
+ 0xeb1d: 0xeb1d,
+ 0xeb1e: 0xeb1e,
+ 0xeb1f: 0xeb1f,
+ 0xeb20: 0xeb20,
+ 0xeb21: 0xeb21,
+ 0xeb22: 0xeb22,
+ 0xeb23: 0xeb23,
+ 0xeb24: 0xeb24,
+ 0xeb25: 0xeb25,
+ 0xeb26: 0xeb26,
+ 0xeb27: 0xeb27,
+ 0xeb28: 0xeb28,
+ 0xeb29: 0xeb29,
+ 0xeb2a: 0xeb2a,
+ 0xeb2b: 0xeb2b,
+ 0xeb2c: 0xeb2c,
+ 0xeb2d: 0xeb2d,
+ 0xeb2e: 0xeb2e,
+ 0xeb2f: 0xeb2f,
+ 0xeb30: 0xeb30,
+ 0xeb31: 0xeb31,
+ 0xeb32: 0xeb32,
+ 0xeb33: 0xeb33,
+ 0xeb34: 0xeb34,
+ 0xeb35: 0xeb35,
+ 0xeb36: 0xeb36,
+ 0xeb37: 0xeb37,
+ 0xeb38: 0xeb38,
+ 0xeb39: 0xeb39,
+ 0xeb3a: 0xeb3a,
+ 0xeb3b: 0xeb3b,
+ 0xeb3c: 0xeb3c,
+ 0xeb3d: 0xeb3d,
+ 0xeb3e: 0xeb3e,
+ 0xeb3f: 0xeb3f,
+ 0xeb40: 0xeb40,
+ 0xeb41: 0xeb41,
+ 0xeb42: 0xeb42,
+ 0xeb43: 0xeb43,
+ 0xeb44: 0xeb44,
+ 0xeb45: 0xeb45,
+ 0xeb46: 0xeb46,
+ 0xeb47: 0xeb47,
+ 0xeb48: 0xeb48,
+ 0xeb49: 0xeb49,
+ 0xeb4a: 0xeb4a,
+ 0xeb4b: 0xeb4b,
+ 0xeb4c: 0xeb4c,
+ 0xeb4d: 0xeb4d,
+ 0xeb4e: 0xeb4e,
+ 0xeb50: 0xeb50,
+ 0xeb51: 0xeb51,
+ 0xeb52: 0xeb52,
+ 0xeb53: 0xeb53,
+ 0xeb54: 0xeb54,
+ 0xeb55: 0xeb55,
+ 0xeb56: 0xeb56,
+ 0xeb57: 0xeb57,
+ 0xeb58: 0xeb58,
+ 0xeb59: 0xeb59,
+ 0xeb5a: 0xeb5a,
+ 0xeb5b: 0xeb5b,
+ 0xeb5c: 0xeb5c,
+ 0xeb5d: 0xeb5d,
+ 0xeb5e: 0xeb5e,
+ 0xeb5f: 0xeb5f,
+ 0xeb60: 0xeb60,
+ 0xeb61: 0xeb61,
+ 0xeb62: 0xeb62,
+ 0xeb63: 0xeb63,
+ 0xeb64: 0xeb64,
+ 0xeb65: 0xeb65,
+ 0xeb66: 0xeb66,
+ 0xeb67: 0xeb67,
+ 0xeb68: 0xeb68,
+ 0xeb69: 0xeb69,
+ 0xeb6a: 0xeb6a,
+ 0xeb6b: 0xeb6b,
+ 0xeb6c: 0xeb6c,
+ 0xeb6d: 0xeb6d,
+ 0xeb6e: 0xeb6e,
+ 0xeb6f: 0xeb6f,
+ 0xeb70: 0xeb70,
+ 0xeb71: 0xeb71,
+ 0xeb72: 0xeb72,
+ 0xeb73: 0xeb73,
+ 0xeb74: 0xeb74,
+ 0xeb75: 0xeb75,
+ 0xeb76: 0xeb76,
+ 0xeb77: 0xeb77,
+ 0xeb78: 0xeb78,
+ 0xeb79: 0xeb79,
+ 0xeb7a: 0xeb7a,
+ 0xeb7b: 0xeb7b,
+ 0xeb7c: 0xeb7c,
+ 0xeb7d: 0xeb7d,
+ 0xeb7e: 0xeb7e,
+ 0xeb7f: 0xeb7f,
+ 0xeb80: 0xeb80,
+ 0xeb81: 0xeb81,
+ 0xeb82: 0xeb82,
+ 0xeb83: 0xeb83,
+ 0xeb84: 0xeb84,
+ 0xeb85: 0xeb85,
+ 0xeb86: 0xeb86,
+ 0xeb87: 0xeb87,
+ 0xeb88: 0xeb88,
+ 0xeb89: 0xeb89,
+ 0xeb8a: 0xeb8a,
+ 0xeb8b: 0xeb8b,
+ 0xeb8c: 0xeb8c,
+ 0xeb8d: 0xeb8d,
+ 0xeb8e: 0xeb8e,
+ 0xeb8f: 0xeb8f,
+ 0xeb90: 0xeb90,
+ 0xeb91: 0xeb91,
+ 0xeb92: 0xeb92,
+ 0xeb93: 0xeb93,
+ 0xeb94: 0xeb94,
+ 0xeb95: 0xeb95,
+ 0xeb96: 0xeb96,
+ 0xeb97: 0xeb97,
+ 0xeb98: 0xeb98,
+ 0xeb99: 0xeb99,
+ 0xeb9a: 0xeb9a,
+ 0xeb9b: 0xeb9b,
+ 0xeb9c: 0xeb9c,
+ 0xeb9d: 0xeb9d,
+ 0xeb9e: 0xeb9e,
+ 0xeb9f: 0xeb9f,
+ 0xeba0: 0xeba0,
+ 0xeba1: 0xeba1,
+ 0xeba2: 0xeba2,
+ 0xeba3: 0xeba3,
+ 0xeba4: 0xeba4,
+ 0xeba5: 0xeba5,
+ 0xeba6: 0xeba6,
+ 0xeba7: 0xeba7,
+ 0xeba8: 0xeba8,
+ 0xeba9: 0xeba9,
+ 0xebaa: 0xebaa,
+ 0xebab: 0xebab,
+ 0xebac: 0xebac,
+ 0xebad: 0xebad,
+ 0xebae: 0xebae,
+ 0xebaf: 0xebaf,
+ 0xebb0: 0xebb0,
+ 0xebb1: 0xebb1,
+ 0xebb2: 0xebb2,
+ 0xebb3: 0xebb3,
+ 0xebb4: 0xebb4,
+ 0xebb5: 0xebb5,
+ 0xebb6: 0xebb6,
+ 0xebb7: 0xebb7,
+ 0xebb8: 0xebb8,
+ 0xebb9: 0xebb9,
+ 0xebba: 0xebba,
+ 0xebbb: 0xebbb,
+ 0xebbc: 0xebbc,
+ 0xebbd: 0xebbd,
+ 0xebbe: 0xebbe,
+ 0xebbf: 0xebbf,
+ 0xebc0: 0xebc0,
+ 0xebc1: 0xebc1,
+ 0xebc2: 0xebc2,
+ 0xebc3: 0xebc3,
+ 0xebc4: 0xebc4,
+ 0xebc5: 0xebc5,
+ 0xebc6: 0xebc6,
+ 0xebc7: 0xebc7,
+ 0xebc8: 0xebc8,
+ 0xebc9: 0xebc9,
+ 0xebca: 0xebca,
+ 0xebcb: 0xebcb,
+ 0xebcc: 0xebcc,
+ 0xebcd: 0xebcd,
+ 0xebce: 0xebce,
+ 0xebcf: 0xebcf,
+ 0xebd0: 0xebd0,
+ 0xebd1: 0xebd1,
+ 0xebd2: 0xebd2,
+ 0xebd3: 0xebd3,
+ 0xebd4: 0xebd4,
+ 0xebd5: 0xebd5,
+ 0xebd6: 0xebd6,
+ 0xebd7: 0xebd7,
+ 0xebd8: 0xebd8,
+ 0xebd9: 0xebd9,
+ 0xebda: 0xebda,
+ 0xebdb: 0xebdb,
+ 0xebdc: 0xebdc,
+ 0xebdd: 0xebdd,
+ 0xebde: 0xebde,
+ 0xebdf: 0xebdf,
+ 0xebe0: 0xebe0,
+ 0xebe1: 0xebe1,
+ 0xebe2: 0xebe2,
+ 0xebe3: 0xebe3,
+ 0xebe4: 0xebe4,
+ 0xebe5: 0xebe5,
+ 0xebe6: 0xebe6,
+ 0xebe7: 0xebe7,
+ 0xebe8: 0xebe8,
+ 0xebe9: 0xebe9,
+ 0xebea: 0xebea,
+ 0xebeb: 0xebeb,
+ 0xebec: 0xebec,
+ 0xebed: 0xebed,
+ 0xebee: 0xebee,
+ 0xebef: 0xebef,
+ 0xebf0: 0xebf0,
+ 0xebf1: 0xebf1,
+ 0xebf2: 0xebf2,
+ 0xebf3: 0xebf3,
+ 0xebf4: 0xebf4,
+ 0xebf5: 0xebf5,
+ 0xebf6: 0xebf6,
+ 0xebf7: 0xebf7,
+ 0xebf8: 0xebf8,
+ 0xebf9: 0xebf9,
+ 0xebfa: 0xebfa,
+ 0xebfb: 0xebfb,
+ 0xebfc: 0xebfc,
+ 0xebfd: 0xebfd,
+ 0xebfe: 0xebfe,
+ 0xebff: 0xebff,
+ 0xec00: 0xec00,
+ 0xec01: 0xec01,
+ 0xec02: 0xec02,
+ 0xec03: 0xec03,
+ 0xec04: 0xec04,
+ 0xec05: 0xec05,
+ 0xec06: 0xec06,
+ 0xec07: 0xec07,
+ 0xec08: 0xec08,
+ 0xec09: 0xec09,
+ 0xec0a: 0xec0a,
+ 0xec0b: 0xec0b,
+ 0xec0c: 0xec0c,
+ 0xec0d: 0xec0d,
+ 0xec0e: 0xec0e,
+ 0xec0f: 0xec0f,
+ 0xec10: 0xec10,
+ 0xec11: 0xec11,
+ 0xec12: 0xec12,
+ 0xec13: 0xec13,
+ 0xec14: 0xec14,
+ 0xec15: 0xec15,
+ 0xec16: 0xec16,
+ 0xec17: 0xec17,
+ 0xec18: 0xec18,
+ 0xec19: 0xec19,
+ 0xec1a: 0xec1a,
+ 0xec1b: 0xec1b,
+ 0xec1c: 0xec1c,
+ 0xec1d: 0xec1d,
+ 0xec1e: 0xec1e,
+ },
+}
diff --git a/vendor/libghostty-vt/src/font/opentype.zig b/vendor/libghostty-vt/src/font/opentype.zig
new file mode 100644
index 00000000..dd02efeb
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/opentype.zig
@@ -0,0 +1,17 @@
+pub const sfnt = @import("opentype/sfnt.zig");
+
+const svg = @import("opentype/svg.zig");
+const os2 = @import("opentype/os2.zig");
+const post = @import("opentype/post.zig");
+const hhea = @import("opentype/hhea.zig");
+const head = @import("opentype/head.zig");
+
+pub const SVG = svg.SVG;
+pub const OS2 = os2.OS2;
+pub const Post = post.Post;
+pub const Hhea = hhea.Hhea;
+pub const Head = head.Head;
+
+test {
+ @import("std").testing.refAllDecls(@This());
+}
diff --git a/vendor/libghostty-vt/src/font/opentype/head.zig b/vendor/libghostty-vt/src/font/opentype/head.zig
new file mode 100644
index 00000000..38284d9c
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/opentype/head.zig
@@ -0,0 +1,179 @@
+const std = @import("std");
+const sfnt = @import("sfnt.zig");
+
+/// Font Header Table
+///
+/// References:
+/// - https://learn.microsoft.com/en-us/typography/opentype/spec/head
+///
+/// Field names are in camelCase to match names in spec.
+pub const Head = extern struct {
+ /// Major version number of the font header table — set to 1.
+ majorVersion: sfnt.uint16 align(1),
+
+ /// Minor version number of the font header table — set to 0.
+ minorVersion: sfnt.uint16 align(1),
+
+ /// Set by font manufacturer.
+ fontRevision: sfnt.Fixed align(1),
+
+ /// To compute: set it to 0, sum the entire font as uint32, then store
+ /// 0xB1B0AFBA - sum. If the font is used as a component in a font
+ /// collection file, the value of this field will be invalidated by
+ /// changes to the file structure and font table directory, and must
+ /// be ignored.
+ checksumAdjustment: sfnt.uint32 align(1),
+
+ /// Set to 0x5F0F3CF5.
+ magicNumber: sfnt.uint32 align(1),
+
+ /// Bit 0: Baseline for font at y=0.
+ ///
+ /// Bit 1: Left sidebearing point at x=0
+ /// (relevant only for TrueType rasterizers)
+ ///
+ /// Bit 2: Instructions may depend on point size.
+ ///
+ /// Bit 3: Force ppem to integer values for all internal scaler math; may
+ /// use fractional ppem sizes if this bit is clear. It is strongly
+ /// recommended that this be set in hinted fonts.
+ ///
+ /// Bit 4: Instructions may alter advance width
+ /// (the advance widths might not scale linearly).
+ ///
+ /// Bit 5: This bit is not used in OpenType, and should not be set in order
+ /// to ensure compatible behavior on all platforms. If set, it may
+ /// result in different behavior for vertical layout in some
+ /// platforms.
+ ///
+ /// (See Apple’s specification for details
+ /// regarding behavior in Apple platforms.)
+ ///
+ /// Bits 6 – 10: These bits are not used in OpenType and should always be
+ /// cleared.
+ ///
+ /// (See Apple’s specification for details
+ /// regarding legacy use in Apple platforms.)
+ ///
+ /// Bit 11: Font data is “lossless” as a result of having been
+ /// subjected to optimizing transformation and/or compression
+ /// (such as compression mechanisms defined by ISO/IEC 14496-18,
+ /// MicroType® Express, WOFF 2.0, or similar) where the original
+ /// font functionality and features are retained but the binary
+ /// compatibility between input and output font files is not
+ /// guaranteed. As a result of the applied transform, the DSIG
+ /// table may also be invalidated.
+ ///
+ /// Bit 12: Font converted (produce compatible metrics).
+ ///
+ /// Bit 13: Font optimized for ClearType®. Note, fonts that rely on embedded
+ /// bitmaps (EBDT) for rendering should not be considered optimized
+ /// for ClearType, and therefore should keep this bit cleared.
+ ///
+ /// Bit 14: Last Resort font. If set, indicates that the glyphs encoded in
+ /// the 'cmap' subtables are simply generic symbolic representations
+ /// of code point ranges and do not truly represent support for
+ /// those code points. If unset, indicates that the glyphs encoded
+ /// in the 'cmap' subtables represent proper support for those code
+ /// points.
+ ///
+ /// Bit 15: Reserved, set to 0.
+ flags: sfnt.uint16 align(1),
+
+ /// Set to a value from 16 to 16384. Any value in this range is valid.
+ ///
+ /// In fonts that have TrueType outlines, a power of 2 is recommended
+ /// as this allows performance optimization in some rasterizers.
+ unitsPerEm: sfnt.uint16 align(1),
+
+ /// Number of seconds since 12:00 midnight that started
+ /// January 1st, 1904, in GMT/UTC time zone.
+ created: sfnt.LONGDATETIME align(1),
+
+ /// Number of seconds since 12:00 midnight that started
+ /// January 1st, 1904, in GMT/UTC time zone.
+ modified: sfnt.LONGDATETIME align(1),
+
+ /// Minimum x coordinate across all glyph bounding boxes.
+ xMin: sfnt.int16 align(1),
+
+ /// Minimum y coordinate across all glyph bounding boxes.
+ yMin: sfnt.int16 align(1),
+
+ /// Maximum x coordinate across all glyph bounding boxes.
+ xMax: sfnt.int16 align(1),
+
+ /// Maximum y coordinate across all glyph bounding boxes.
+ yMax: sfnt.int16 align(1),
+
+ /// Bit 0: Bold (if set to 1);
+ /// Bit 1: Italic (if set to 1)
+ /// Bit 2: Underline (if set to 1)
+ /// Bit 3: Outline (if set to 1)
+ /// Bit 4: Shadow (if set to 1)
+ /// Bit 5: Condensed (if set to 1)
+ /// Bit 6: Extended (if set to 1)
+ /// Bits 7 – 15: Reserved (set to 0).
+ macStyle: sfnt.uint16 align(1),
+
+ /// Smallest readable size in pixels.
+ lowestRecPPEM: sfnt.uint16 align(1),
+
+ /// Deprecated (Set to 2).
+ /// 0: Fully mixed directional glyphs;
+ /// 1: Only strongly left to right;
+ /// 2: Like 1 but also contains neutrals;
+ /// -1: Only strongly right to left;
+ /// -2: Like -1 but also contains neutrals.
+ fontDirectionHint: sfnt.int16 align(1),
+
+ /// 0 for short offsets (Offset16), 1 for long (Offset32).
+ indexToLocFormat: sfnt.int16 align(1),
+
+ /// 0 for current format.
+ glyphDataFormat: sfnt.int16 align(1),
+
+ /// Parse the table from raw data.
+ pub fn init(data: []const u8) error{EndOfStream}!Head {
+ var fbs = std.io.fixedBufferStream(data);
+ const reader = fbs.reader();
+ return try reader.readStructEndian(Head, .big);
+ }
+};
+
+test "head" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ const test_font = @import("../embedded.zig").julia_mono;
+
+ const font = try sfnt.SFNT.init(test_font, alloc);
+ defer font.deinit(alloc);
+
+ const table = font.getTable("head").?;
+
+ const head = try Head.init(table);
+
+ try testing.expectEqualDeep(
+ Head{
+ .majorVersion = 1,
+ .minorVersion = 0,
+ .fontRevision = sfnt.Fixed.from(0.05499267578125),
+ .checksumAdjustment = 1007668681,
+ .magicNumber = 1594834165,
+ .flags = 7,
+ .unitsPerEm = 2000,
+ .created = 3797757830,
+ .modified = 3797760444,
+ .xMin = -1000,
+ .yMin = -1058,
+ .xMax = 3089,
+ .yMax = 2400,
+ .macStyle = 0,
+ .lowestRecPPEM = 7,
+ .fontDirectionHint = 2,
+ .indexToLocFormat = 1,
+ .glyphDataFormat = 0,
+ },
+ head,
+ );
+}
diff --git a/vendor/libghostty-vt/src/font/opentype/hhea.zig b/vendor/libghostty-vt/src/font/opentype/hhea.zig
new file mode 100644
index 00000000..b2b3f3e2
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/opentype/hhea.zig
@@ -0,0 +1,116 @@
+const std = @import("std");
+const sfnt = @import("sfnt.zig");
+
+/// Horizontal Header Table
+///
+/// References:
+/// - https://learn.microsoft.com/en-us/typography/opentype/spec/hhea
+///
+/// Field names are in camelCase to match names in spec.
+pub const Hhea = extern struct {
+ /// Major version number of the horizontal header table — set to 1.
+ majorVersion: sfnt.uint16 align(1),
+
+ /// Minor version number of the horizontal header table — set to 0.
+ minorVersion: sfnt.uint16 align(1),
+
+ /// Typographic ascent—see remarks below.
+ ascender: sfnt.FWORD align(1),
+
+ /// Typographic descent—see remarks below.
+ descender: sfnt.FWORD align(1),
+
+ /// Typographic line gap.
+ ///
+ /// Negative lineGap values are treated as zero
+ /// in some legacy platform implementations.
+ lineGap: sfnt.FWORD align(1),
+
+ /// Maximum advance width value in 'hmtx' table.
+ advanceWidthMax: sfnt.UFWORD align(1),
+
+ /// Minimum left sidebearing value in 'hmtx' table for
+ /// glyphs with contours (empty glyphs should be ignored).
+ minLeftSideBearing: sfnt.FWORD align(1),
+
+ /// Minimum right sidebearing value; calculated as
+ /// min(aw - (lsb + xMax - xMin)) for glyphs with
+ /// contours (empty glyphs should be ignored).
+ minRightSideBearing: sfnt.FWORD align(1),
+
+ /// Max(lsb + (xMax - xMin)).
+ xMaxExtent: sfnt.FWORD align(1),
+
+ /// Used to calculate the slope of the cursor (rise/run); 1 for vertical.
+ caretSlopeRise: sfnt.int16 align(1),
+
+ /// 0 for vertical.
+ caretSlopeRun: sfnt.int16 align(1),
+
+ /// The amount by which a slanted highlight on a glyph needs to be shifted
+ /// to produce the best appearance. Set to 0 for non-slanted fonts
+ caretOffset: sfnt.int16 align(1),
+
+ /// set to 0
+ _reserved0: sfnt.int16 align(1),
+
+ /// set to 0
+ _reserved1: sfnt.int16 align(1),
+
+ /// set to 0
+ _reserved2: sfnt.int16 align(1),
+
+ /// set to 0
+ _reserved3: sfnt.int16 align(1),
+
+ /// 0 for current format.
+ metricDataFormat: sfnt.int16 align(1),
+
+ /// Number of hMetric entries in 'hmtx' table
+ numberOfHMetrics: sfnt.uint16 align(1),
+
+ /// Parse the table from raw data.
+ pub fn init(data: []const u8) !Hhea {
+ var fbs = std.io.fixedBufferStream(data);
+ const reader = fbs.reader();
+
+ return try reader.readStructEndian(Hhea, .big);
+ }
+};
+
+test "hhea" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ const test_font = @import("../embedded.zig").julia_mono;
+
+ const font = try sfnt.SFNT.init(test_font, alloc);
+ defer font.deinit(alloc);
+
+ const table = font.getTable("hhea").?;
+
+ const hhea = try Hhea.init(table);
+
+ try testing.expectEqualDeep(
+ Hhea{
+ .majorVersion = 1,
+ .minorVersion = 0,
+ .ascender = 1900,
+ .descender = -450,
+ .lineGap = 0,
+ .advanceWidthMax = 1200,
+ .minLeftSideBearing = -1000,
+ .minRightSideBearing = -1889,
+ .xMaxExtent = 3089,
+ .caretSlopeRise = 1,
+ .caretSlopeRun = 0,
+ .caretOffset = 0,
+ ._reserved0 = 0,
+ ._reserved1 = 0,
+ ._reserved2 = 0,
+ ._reserved3 = 0,
+ .metricDataFormat = 0,
+ .numberOfHMetrics = 2,
+ },
+ hhea,
+ );
+}
diff --git a/vendor/libghostty-vt/src/font/opentype/os2.zig b/vendor/libghostty-vt/src/font/opentype/os2.zig
new file mode 100644
index 00000000..1cd11f35
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/opentype/os2.zig
@@ -0,0 +1,583 @@
+const std = @import("std");
+const sfnt = @import("sfnt.zig");
+
+pub const FSSelection = packed struct(sfnt.uint16) {
+ /// Font contains italic or oblique glyphs, otherwise they are upright.
+ italic: bool = false,
+
+ /// Glyphs are underscored.
+ underscore: bool = false,
+
+ /// Glyphs have their foreground and background reversed.
+ negative: bool = false,
+
+ /// Outline (hollow) glyphs, otherwise they are solid.
+ outlined: bool = false,
+
+ /// Glyphs are overstruck.
+ strikeout: bool = false,
+
+ /// Glyphs are emboldened.
+ bold: bool = false,
+
+ /// Glyphs are in the standard weight/style for the font.
+ regular: bool = false,
+
+ /// If set, it is strongly recommended that applications use
+ /// OS/2.sTypoAscender - OS/2.sTypoDescender + OS/2.sTypoLineGap
+ /// as the default line spacing for this font.
+ use_typo_metrics: bool = false,
+
+ /// The font has 'name' table strings consistent with a weight/width/slope
+ /// family without requiring use of name IDs 21 and 22.
+ wws: bool = false,
+
+ /// Font contains oblique glyphs.
+ oblique: bool = false,
+
+ _reserved: u6 = 0,
+};
+
+/// OS/2 and Windows Metrics Table
+///
+/// References:
+/// - https://learn.microsoft.com/en-us/typography/opentype/spec/os2
+///
+/// Field names are in camelCase to match names in spec.
+pub const OS2v5 = extern struct {
+ version: sfnt.uint16 align(1),
+ xAvgCharWidth: sfnt.FWORD align(1),
+ usWeightClass: sfnt.uint16 align(1),
+ usWidthClass: sfnt.uint16 align(1),
+ fsType: sfnt.uint16 align(1),
+ ySubscriptXSize: sfnt.FWORD align(1),
+ ySubscriptYSize: sfnt.FWORD align(1),
+ ySubscriptXOffset: sfnt.FWORD align(1),
+ ySubscriptYOffset: sfnt.FWORD align(1),
+ ySuperscriptXSize: sfnt.FWORD align(1),
+ ySuperscriptYSize: sfnt.FWORD align(1),
+ ySuperscriptXOffset: sfnt.FWORD align(1),
+ ySuperscriptYOffset: sfnt.FWORD align(1),
+ yStrikeoutSize: sfnt.FWORD align(1),
+ yStrikeoutPosition: sfnt.FWORD align(1),
+ sFamilyClass: sfnt.int16 align(1),
+ panose: [10]sfnt.uint8 align(1),
+ ulUnicodeRange1: sfnt.uint32 align(1),
+ ulUnicodeRange2: sfnt.uint32 align(1),
+ ulUnicodeRange3: sfnt.uint32 align(1),
+ ulUnicodeRange4: sfnt.uint32 align(1),
+ achVendID: sfnt.Tag align(1),
+ fsSelection: FSSelection align(1),
+ usFirstCharIndex: sfnt.uint16 align(1),
+ usLastCharIndex: sfnt.uint16 align(1),
+ sTypoAscender: sfnt.FWORD align(1),
+ sTypoDescender: sfnt.FWORD align(1),
+ sTypoLineGap: sfnt.FWORD align(1),
+ usWinAscent: sfnt.UFWORD align(1),
+ usWinDescent: sfnt.UFWORD align(1),
+ ulCodePageRange1: sfnt.uint32 align(1),
+ ulCodePageRange2: sfnt.uint32 align(1),
+ sxHeight: sfnt.FWORD align(1),
+ sCapHeight: sfnt.FWORD align(1),
+ usDefaultChar: sfnt.uint16 align(1),
+ usBreakChar: sfnt.uint16 align(1),
+ usMaxContext: sfnt.uint16 align(1),
+ usLowerOpticalPointSize: sfnt.uint16 align(1),
+ usUpperOpticalPointSize: sfnt.uint16 align(1),
+};
+
+pub const OS2v4_3_2 = extern struct {
+ version: sfnt.uint16 align(1),
+ xAvgCharWidth: sfnt.FWORD align(1),
+ usWeightClass: sfnt.uint16 align(1),
+ usWidthClass: sfnt.uint16 align(1),
+ fsType: sfnt.uint16 align(1),
+ ySubscriptXSize: sfnt.FWORD align(1),
+ ySubscriptYSize: sfnt.FWORD align(1),
+ ySubscriptXOffset: sfnt.FWORD align(1),
+ ySubscriptYOffset: sfnt.FWORD align(1),
+ ySuperscriptXSize: sfnt.FWORD align(1),
+ ySuperscriptYSize: sfnt.FWORD align(1),
+ ySuperscriptXOffset: sfnt.FWORD align(1),
+ ySuperscriptYOffset: sfnt.FWORD align(1),
+ yStrikeoutSize: sfnt.FWORD align(1),
+ yStrikeoutPosition: sfnt.FWORD align(1),
+ sFamilyClass: sfnt.int16 align(1),
+ panose: [10]sfnt.uint8 align(1),
+ ulUnicodeRange1: sfnt.uint32 align(1),
+ ulUnicodeRange2: sfnt.uint32 align(1),
+ ulUnicodeRange3: sfnt.uint32 align(1),
+ ulUnicodeRange4: sfnt.uint32 align(1),
+ achVendID: sfnt.Tag align(1),
+ fsSelection: FSSelection align(1),
+ usFirstCharIndex: sfnt.uint16 align(1),
+ usLastCharIndex: sfnt.uint16 align(1),
+ sTypoAscender: sfnt.FWORD align(1),
+ sTypoDescender: sfnt.FWORD align(1),
+ sTypoLineGap: sfnt.FWORD align(1),
+ usWinAscent: sfnt.UFWORD align(1),
+ usWinDescent: sfnt.UFWORD align(1),
+ ulCodePageRange1: sfnt.uint32 align(1),
+ ulCodePageRange2: sfnt.uint32 align(1),
+ sxHeight: sfnt.FWORD align(1),
+ sCapHeight: sfnt.FWORD align(1),
+ usDefaultChar: sfnt.uint16 align(1),
+ usBreakChar: sfnt.uint16 align(1),
+ usMaxContext: sfnt.uint16 align(1),
+};
+
+pub const OS2v1 = extern struct {
+ version: sfnt.uint16 align(1),
+ xAvgCharWidth: sfnt.FWORD align(1),
+ usWeightClass: sfnt.uint16 align(1),
+ usWidthClass: sfnt.uint16 align(1),
+ fsType: sfnt.uint16 align(1),
+ ySubscriptXSize: sfnt.FWORD align(1),
+ ySubscriptYSize: sfnt.FWORD align(1),
+ ySubscriptXOffset: sfnt.FWORD align(1),
+ ySubscriptYOffset: sfnt.FWORD align(1),
+ ySuperscriptXSize: sfnt.FWORD align(1),
+ ySuperscriptYSize: sfnt.FWORD align(1),
+ ySuperscriptXOffset: sfnt.FWORD align(1),
+ ySuperscriptYOffset: sfnt.FWORD align(1),
+ yStrikeoutSize: sfnt.FWORD align(1),
+ yStrikeoutPosition: sfnt.FWORD align(1),
+ sFamilyClass: sfnt.int16 align(1),
+ panose: [10]sfnt.uint8 align(1),
+ ulUnicodeRange1: sfnt.uint32 align(1),
+ ulUnicodeRange2: sfnt.uint32 align(1),
+ ulUnicodeRange3: sfnt.uint32 align(1),
+ ulUnicodeRange4: sfnt.uint32 align(1),
+ achVendID: sfnt.Tag align(1),
+ fsSelection: FSSelection align(1),
+ usFirstCharIndex: sfnt.uint16 align(1),
+ usLastCharIndex: sfnt.uint16 align(1),
+ sTypoAscender: sfnt.FWORD align(1),
+ sTypoDescender: sfnt.FWORD align(1),
+ sTypoLineGap: sfnt.FWORD align(1),
+ usWinAscent: sfnt.UFWORD align(1),
+ usWinDescent: sfnt.UFWORD align(1),
+ ulCodePageRange1: sfnt.uint32 align(1),
+ ulCodePageRange2: sfnt.uint32 align(1),
+};
+
+pub const OS2v0 = extern struct {
+ version: sfnt.uint16 align(1),
+ xAvgCharWidth: sfnt.FWORD align(1),
+ usWeightClass: sfnt.uint16 align(1),
+ usWidthClass: sfnt.uint16 align(1),
+ fsType: sfnt.uint16 align(1),
+ ySubscriptXSize: sfnt.FWORD align(1),
+ ySubscriptYSize: sfnt.FWORD align(1),
+ ySubscriptXOffset: sfnt.FWORD align(1),
+ ySubscriptYOffset: sfnt.FWORD align(1),
+ ySuperscriptXSize: sfnt.FWORD align(1),
+ ySuperscriptYSize: sfnt.FWORD align(1),
+ ySuperscriptXOffset: sfnt.FWORD align(1),
+ ySuperscriptYOffset: sfnt.FWORD align(1),
+ yStrikeoutSize: sfnt.FWORD align(1),
+ yStrikeoutPosition: sfnt.FWORD align(1),
+ sFamilyClass: sfnt.int16 align(1),
+ panose: [10]sfnt.uint8 align(1),
+ ulUnicodeRange1: sfnt.uint32 align(1),
+ ulUnicodeRange2: sfnt.uint32 align(1),
+ ulUnicodeRange3: sfnt.uint32 align(1),
+ ulUnicodeRange4: sfnt.uint32 align(1),
+ achVendID: sfnt.Tag align(1),
+ fsSelection: FSSelection align(1),
+ usFirstCharIndex: sfnt.uint16 align(1),
+ usLastCharIndex: sfnt.uint16 align(1),
+ sTypoAscender: sfnt.FWORD align(1),
+ sTypoDescender: sfnt.FWORD align(1),
+ sTypoLineGap: sfnt.FWORD align(1),
+ usWinAscent: sfnt.UFWORD align(1),
+ usWinDescent: sfnt.UFWORD align(1),
+};
+
+/// Generic OS/2 table with optional fields
+/// for those that don't exist in all versions.
+///
+/// References:
+/// - https://learn.microsoft.com/en-us/typography/opentype/spec/os2
+///
+/// Field names are in camelCase to match names in spec.
+pub const OS2 = struct {
+ /// The version number for the OS/2 table: 0x0000 to 0x0005.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2#version
+ version: u16,
+ /// The Average Character Width field specifies the arithmetic average of the escapement (width) of all non-zero width glyphs in the font.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#xavgcharwidth
+ xAvgCharWidth: i16,
+ /// Indicates the visual weight (degree of blackness or thickness of strokes) of the characters in the font. Values from 1 to 1000 are valid.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#usweightclass
+ usWeightClass: u16,
+ /// Indicates a relative change from the normal aspect ratio (width to height ratio) as specified by a font designer for the glyphs in a font.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#uswidthclass
+ usWidthClass: u16,
+ /// Indicates font embedding licensing rights for the font.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#fstype
+ fsType: u16,
+ /// The recommended horizontal size in font design units for subscripts for this font.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ysubscriptxsize
+ ySubscriptXSize: i16,
+ /// The recommended vertical size in font design units for subscripts for this font.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ysubscriptysize
+ ySubscriptYSize: i16,
+ /// The recommended horizontal offset in font design units for subscripts for this font.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ysubscriptxoffset
+ ySubscriptXOffset: i16,
+ /// The recommended vertical offset in font design units from the baseline for subscripts for this font.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ysubscriptyoffset
+ ySubscriptYOffset: i16,
+ /// The recommended horizontal size in font design units for superscripts for this font.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ysuperscriptxsize
+ ySuperscriptXSize: i16,
+ /// The recommended vertical size in font design units for superscripts for this font.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ysuperscriptysize
+ ySuperscriptYSize: i16,
+ /// The recommended horizontal offset in font design units for superscripts for this font.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ysuperscriptxoffset
+ ySuperscriptXOffset: i16,
+ /// The recommended vertical offset in font design units from the baseline for superscripts for this font.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ysuperscriptyoffset
+ ySuperscriptYOffset: i16,
+ /// Thickness of the strikeout stroke in font design units. Should be > 0.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ystrikeoutsize
+ yStrikeoutSize: i16,
+ /// The position of the top of the strikeout stroke relative to the baseline in font design units.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ystrikeoutposition
+ yStrikeoutPosition: i16,
+ /// This field provides a classification of font-family design.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#sfamilyclass
+ sFamilyClass: i16,
+ /// This 10-byte array of numbers is used to describe the visual characteristics of a given typeface.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#panose
+ panose: [10]u8,
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ulunicoderange
+ ulUnicodeRange1: u32,
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ulunicoderange
+ ulUnicodeRange2: u32,
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ulunicoderange
+ ulUnicodeRange3: u32,
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ulunicoderange
+ ulUnicodeRange4: u32,
+ /// The four character identifier for the vendor of the given type face.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#achvendid
+ achVendID: [4]u8,
+ /// Contains information concerning the nature of the font patterns.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#fsselection
+ fsSelection: FSSelection,
+ /// The minimum Unicode index (character code) in this font, according to the 'cmap' subtable for platform ID 3 and platform-specific encoding ID 0 or 1.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#usfirstcharindex
+ usFirstCharIndex: u16,
+ /// The maximum Unicode index (character code) in this font, according to the 'cmap' subtable for platform ID 3 and encoding ID 0 or 1.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#uslastcharindex
+ usLastCharIndex: u16,
+ /// The typographic ascender for this font. This field should be combined with the sTypoDescender and sTypoLineGap values to determine default line spacing.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#stypoascender
+ sTypoAscender: i16,
+ /// The typographic descender for this font. This field should be combined with the sTypoAscender and sTypoLineGap values to determine default line spacing.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#stypodescender
+ sTypoDescender: i16,
+ /// The typographic line gap for this font. This field should be combined with the sTypoAscender and sTypoDescender values to determine default line spacing.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#stypolinegap
+ sTypoLineGap: i16,
+ /// The “Windows ascender” metric. This should be used to specify the height above the baseline for a clipping region.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#uswinascent
+ usWinAscent: u16,
+ /// The “Windows descender” metric. This should be used to specify the vertical extent below the baseline for a clipping region.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#uswindescent
+ usWinDescent: u16,
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ulcodepagerange
+ ulCodePageRange1: ?u32 = null,
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ulcodepagerange
+ ulCodePageRange2: ?u32 = null,
+ /// This metric specifies the distance between the baseline and the approximate height of non-ascending lowercase letters measured in font design units.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#sxheight
+ sxHeight: ?i16 = null,
+ /// This metric specifies the distance between the baseline and the approximate height of uppercase letters measured in font design units.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#scapheight
+ sCapHeight: ?i16 = null,
+ /// This is the Unicode code point, in UTF-16 encoding, of a character that can be used for a default glyph if a requested character is not supported in the font.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#usdefaultchar
+ usDefaultChar: ?u16 = null,
+ /// This is the Unicode code point, in UTF-16 encoding, of a character that can be used as a default break character.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#usbreakchar
+ usBreakChar: ?u16 = null,
+ /// The maximum length of a target glyph context for any feature in this font. For example, a font which has only a pair kerning feature should set this field to 2.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#usmaxcontext
+ usMaxContext: ?u16 = null,
+ /// This field is used for fonts with multiple optical styles.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#usloweropticalpointsize
+ usLowerOpticalPointSize: ?u16 = null,
+ /// This field is used for fonts with multiple optical styles.
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#usupperopticalpointsize
+ usUpperOpticalPointSize: ?u16 = null,
+
+ /// Parse the table from raw data.
+ pub fn init(data: []const u8) error{
+ EndOfStream,
+ OS2VersionNotSupported,
+ }!OS2 {
+ var fbs = std.io.fixedBufferStream(data);
+ const reader = fbs.reader();
+
+ const version = try reader.readInt(sfnt.uint16, .big);
+
+ // Return to the start, cause the version is part of the struct.
+ try fbs.seekTo(0);
+
+ switch (version) {
+ 5 => {
+ const table = try reader.readStructEndian(OS2v5, .big);
+ return .{
+ .version = table.version,
+ .xAvgCharWidth = table.xAvgCharWidth,
+ .usWeightClass = table.usWeightClass,
+ .usWidthClass = table.usWidthClass,
+ .fsType = table.fsType,
+ .ySubscriptXSize = table.ySubscriptXSize,
+ .ySubscriptYSize = table.ySubscriptYSize,
+ .ySubscriptXOffset = table.ySubscriptXOffset,
+ .ySubscriptYOffset = table.ySubscriptYOffset,
+ .ySuperscriptXSize = table.ySuperscriptXSize,
+ .ySuperscriptYSize = table.ySuperscriptYSize,
+ .ySuperscriptXOffset = table.ySuperscriptXOffset,
+ .ySuperscriptYOffset = table.ySuperscriptYOffset,
+ .yStrikeoutSize = table.yStrikeoutSize,
+ .yStrikeoutPosition = table.yStrikeoutPosition,
+ .sFamilyClass = table.sFamilyClass,
+ .panose = table.panose,
+ .ulUnicodeRange1 = table.ulUnicodeRange1,
+ .ulUnicodeRange2 = table.ulUnicodeRange2,
+ .ulUnicodeRange3 = table.ulUnicodeRange3,
+ .ulUnicodeRange4 = table.ulUnicodeRange4,
+ .achVendID = table.achVendID,
+ .fsSelection = table.fsSelection,
+ .usFirstCharIndex = table.usFirstCharIndex,
+ .usLastCharIndex = table.usLastCharIndex,
+ .sTypoAscender = table.sTypoAscender,
+ .sTypoDescender = table.sTypoDescender,
+ .sTypoLineGap = table.sTypoLineGap,
+ .usWinAscent = table.usWinAscent,
+ .usWinDescent = table.usWinDescent,
+ .ulCodePageRange1 = table.ulCodePageRange1,
+ .ulCodePageRange2 = table.ulCodePageRange2,
+ .sxHeight = table.sxHeight,
+ .sCapHeight = table.sCapHeight,
+ .usDefaultChar = table.usDefaultChar,
+ .usBreakChar = table.usBreakChar,
+ .usMaxContext = table.usMaxContext,
+ .usLowerOpticalPointSize = table.usLowerOpticalPointSize,
+ .usUpperOpticalPointSize = table.usUpperOpticalPointSize,
+ };
+ },
+ 4, 3, 2 => {
+ const table = try reader.readStructEndian(OS2v4_3_2, .big);
+ return .{
+ .version = table.version,
+ .xAvgCharWidth = table.xAvgCharWidth,
+ .usWeightClass = table.usWeightClass,
+ .usWidthClass = table.usWidthClass,
+ .fsType = table.fsType,
+ .ySubscriptXSize = table.ySubscriptXSize,
+ .ySubscriptYSize = table.ySubscriptYSize,
+ .ySubscriptXOffset = table.ySubscriptXOffset,
+ .ySubscriptYOffset = table.ySubscriptYOffset,
+ .ySuperscriptXSize = table.ySuperscriptXSize,
+ .ySuperscriptYSize = table.ySuperscriptYSize,
+ .ySuperscriptXOffset = table.ySuperscriptXOffset,
+ .ySuperscriptYOffset = table.ySuperscriptYOffset,
+ .yStrikeoutSize = table.yStrikeoutSize,
+ .yStrikeoutPosition = table.yStrikeoutPosition,
+ .sFamilyClass = table.sFamilyClass,
+ .panose = table.panose,
+ .ulUnicodeRange1 = table.ulUnicodeRange1,
+ .ulUnicodeRange2 = table.ulUnicodeRange2,
+ .ulUnicodeRange3 = table.ulUnicodeRange3,
+ .ulUnicodeRange4 = table.ulUnicodeRange4,
+ .achVendID = table.achVendID,
+ .fsSelection = table.fsSelection,
+ .usFirstCharIndex = table.usFirstCharIndex,
+ .usLastCharIndex = table.usLastCharIndex,
+ .sTypoAscender = table.sTypoAscender,
+ .sTypoDescender = table.sTypoDescender,
+ .sTypoLineGap = table.sTypoLineGap,
+ .usWinAscent = table.usWinAscent,
+ .usWinDescent = table.usWinDescent,
+ .ulCodePageRange1 = table.ulCodePageRange1,
+ .ulCodePageRange2 = table.ulCodePageRange2,
+ .sxHeight = table.sxHeight,
+ .sCapHeight = table.sCapHeight,
+ .usDefaultChar = table.usDefaultChar,
+ .usBreakChar = table.usBreakChar,
+ .usMaxContext = table.usMaxContext,
+ };
+ },
+ 1 => {
+ const table = try reader.readStructEndian(OS2v1, .big);
+ return .{
+ .version = table.version,
+ .xAvgCharWidth = table.xAvgCharWidth,
+ .usWeightClass = table.usWeightClass,
+ .usWidthClass = table.usWidthClass,
+ .fsType = table.fsType,
+ .ySubscriptXSize = table.ySubscriptXSize,
+ .ySubscriptYSize = table.ySubscriptYSize,
+ .ySubscriptXOffset = table.ySubscriptXOffset,
+ .ySubscriptYOffset = table.ySubscriptYOffset,
+ .ySuperscriptXSize = table.ySuperscriptXSize,
+ .ySuperscriptYSize = table.ySuperscriptYSize,
+ .ySuperscriptXOffset = table.ySuperscriptXOffset,
+ .ySuperscriptYOffset = table.ySuperscriptYOffset,
+ .yStrikeoutSize = table.yStrikeoutSize,
+ .yStrikeoutPosition = table.yStrikeoutPosition,
+ .sFamilyClass = table.sFamilyClass,
+ .panose = table.panose,
+ .ulUnicodeRange1 = table.ulUnicodeRange1,
+ .ulUnicodeRange2 = table.ulUnicodeRange2,
+ .ulUnicodeRange3 = table.ulUnicodeRange3,
+ .ulUnicodeRange4 = table.ulUnicodeRange4,
+ .achVendID = table.achVendID,
+ .fsSelection = table.fsSelection,
+ .usFirstCharIndex = table.usFirstCharIndex,
+ .usLastCharIndex = table.usLastCharIndex,
+ .sTypoAscender = table.sTypoAscender,
+ .sTypoDescender = table.sTypoDescender,
+ .sTypoLineGap = table.sTypoLineGap,
+ .usWinAscent = table.usWinAscent,
+ .usWinDescent = table.usWinDescent,
+ .ulCodePageRange1 = table.ulCodePageRange1,
+ .ulCodePageRange2 = table.ulCodePageRange2,
+ };
+ },
+ 0 => {
+ const table = try reader.readStructEndian(OS2v0, .big);
+ return .{
+ .version = table.version,
+ .xAvgCharWidth = table.xAvgCharWidth,
+ .usWeightClass = table.usWeightClass,
+ .usWidthClass = table.usWidthClass,
+ .fsType = table.fsType,
+ .ySubscriptXSize = table.ySubscriptXSize,
+ .ySubscriptYSize = table.ySubscriptYSize,
+ .ySubscriptXOffset = table.ySubscriptXOffset,
+ .ySubscriptYOffset = table.ySubscriptYOffset,
+ .ySuperscriptXSize = table.ySuperscriptXSize,
+ .ySuperscriptYSize = table.ySuperscriptYSize,
+ .ySuperscriptXOffset = table.ySuperscriptXOffset,
+ .ySuperscriptYOffset = table.ySuperscriptYOffset,
+ .yStrikeoutSize = table.yStrikeoutSize,
+ .yStrikeoutPosition = table.yStrikeoutPosition,
+ .sFamilyClass = table.sFamilyClass,
+ .panose = table.panose,
+ .ulUnicodeRange1 = table.ulUnicodeRange1,
+ .ulUnicodeRange2 = table.ulUnicodeRange2,
+ .ulUnicodeRange3 = table.ulUnicodeRange3,
+ .ulUnicodeRange4 = table.ulUnicodeRange4,
+ .achVendID = table.achVendID,
+ .fsSelection = table.fsSelection,
+ .usFirstCharIndex = table.usFirstCharIndex,
+ .usLastCharIndex = table.usLastCharIndex,
+ .sTypoAscender = table.sTypoAscender,
+ .sTypoDescender = table.sTypoDescender,
+ .sTypoLineGap = table.sTypoLineGap,
+ .usWinAscent = table.usWinAscent,
+ .usWinDescent = table.usWinDescent,
+ };
+ },
+ else => return error.OS2VersionNotSupported,
+ }
+ }
+};
+
+test "OS/2" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ const test_font = @import("../embedded.zig").julia_mono;
+
+ const font = try sfnt.SFNT.init(test_font, alloc);
+ defer font.deinit(alloc);
+
+ const table = font.getTable("OS/2").?;
+
+ const os2 = try OS2.init(table);
+
+ try testing.expectEqualDeep(OS2{
+ .version = 4,
+ .xAvgCharWidth = 1200,
+ .usWeightClass = 400,
+ .usWidthClass = 5,
+ .fsType = 0,
+ .ySubscriptXSize = 1300,
+ .ySubscriptYSize = 1200,
+ .ySubscriptXOffset = 0,
+ .ySubscriptYOffset = 150,
+ .ySuperscriptXSize = 1300,
+ .ySuperscriptYSize = 1200,
+ .ySuperscriptXOffset = 0,
+ .ySuperscriptYOffset = 700,
+ .yStrikeoutSize = 100,
+ .yStrikeoutPosition = 550,
+ .sFamilyClass = 0,
+ .panose = .{ 2, 11, 6, 9, 6, 3, 0, 2, 0, 4 },
+ .ulUnicodeRange1 = 3843162111,
+ .ulUnicodeRange2 = 3603300351,
+ .ulUnicodeRange3 = 117760229,
+ .ulUnicodeRange4 = 96510060,
+ .achVendID = "corm".*,
+ .fsSelection = .{
+ .regular = true,
+ .use_typo_metrics = true,
+ },
+ .usFirstCharIndex = 13,
+ .usLastCharIndex = 65535,
+ .sTypoAscender = 1900,
+ .sTypoDescender = -450,
+ .sTypoLineGap = 0,
+ .usWinAscent = 2400,
+ .usWinDescent = 450,
+ .ulCodePageRange1 = 1613234687,
+ .ulCodePageRange2 = 0,
+ .sxHeight = 1100,
+ .sCapHeight = 1450,
+ .usDefaultChar = 0,
+ .usBreakChar = 32,
+ .usMaxContext = 126,
+ .usLowerOpticalPointSize = null,
+ .usUpperOpticalPointSize = null,
+ }, os2);
+}
diff --git a/vendor/libghostty-vt/src/font/opentype/post.zig b/vendor/libghostty-vt/src/font/opentype/post.zig
new file mode 100644
index 00000000..8031a0a4
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/opentype/post.zig
@@ -0,0 +1,82 @@
+const std = @import("std");
+const sfnt = @import("sfnt.zig");
+
+/// PostScript Table
+///
+/// This implementation doesn't parse the
+/// extra fields in versions 2.0 and 2.5.
+///
+/// References:
+/// - https://learn.microsoft.com/en-us/typography/opentype/spec/post
+///
+/// Field names are in camelCase to match names in spec.
+pub const Post = extern struct {
+ version: sfnt.Version16Dot16 align(1),
+
+ /// Italic angle in counter-clockwise degrees from the vertical.
+ /// Zero for upright text, negative for text that leans to the
+ /// right (forward).
+ italicAngle: sfnt.Fixed align(1),
+
+ /// Suggested y-coordinate of the top of the underline.
+ underlinePosition: sfnt.FWORD align(1),
+
+ /// Suggested values for the underline thickness.
+ /// In general, the underline thickness should match the thickness of
+ /// the underscore character (U+005F LOW LINE), and should also match
+ /// the strikeout thickness, which is specified in the OS/2 table.
+ underlineThickness: sfnt.FWORD align(1),
+
+ /// Set to 0 if the font is proportionally spaced, non-zero if
+ /// the font is not proportionally spaced (i.e. monospaced).
+ isFixedPitch: sfnt.uint32 align(1),
+
+ /// Minimum memory usage when an OpenType font is downloaded.
+ minMemType42: sfnt.uint32 align(1),
+
+ /// Maximum memory usage when an OpenType font is downloaded.
+ maxMemType42: sfnt.uint32 align(1),
+
+ /// Minimum memory usage when an OpenType
+ /// font is downloaded as a Type 1 font.
+ minMemType1: sfnt.uint32 align(1),
+
+ /// Maximum memory usage when an OpenType
+ /// font is downloaded as a Type 1 font.
+ maxMemType1: sfnt.uint32 align(1),
+
+ /// Parse the table from raw data.
+ pub fn init(data: []const u8) error{EndOfStream}!Post {
+ var fbs = std.io.fixedBufferStream(data);
+ const reader = fbs.reader();
+ return try reader.readStructEndian(Post, .big);
+ }
+};
+
+test "post" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ const test_font = @import("../embedded.zig").julia_mono;
+
+ const font = try sfnt.SFNT.init(test_font, alloc);
+ defer font.deinit(alloc);
+
+ const table = font.getTable("post").?;
+
+ const post = try Post.init(table);
+
+ try testing.expectEqualDeep(
+ Post{
+ .version = sfnt.Version16Dot16{ .minor = 0, .major = 2 },
+ .italicAngle = sfnt.Fixed.from(0.0),
+ .underlinePosition = -200,
+ .underlineThickness = 100,
+ .isFixedPitch = 1,
+ .minMemType42 = 0,
+ .maxMemType42 = 0,
+ .minMemType1 = 0,
+ .maxMemType1 = 0,
+ },
+ post,
+ );
+}
diff --git a/vendor/libghostty-vt/src/font/opentype/sfnt.zig b/vendor/libghostty-vt/src/font/opentype/sfnt.zig
new file mode 100644
index 00000000..9373cda0
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/opentype/sfnt.zig
@@ -0,0 +1,312 @@
+const std = @import("std");
+const assert = @import("../../quirks.zig").inlineAssert;
+const Allocator = std.mem.Allocator;
+
+/// 8-bit unsigned integer.
+pub const uint8 = u8;
+
+/// 8-bit signed integer.
+pub const int8 = i8;
+
+/// 16-bit unsigned integer.
+pub const uint16 = u16;
+
+/// 16-bit signed integer.
+pub const int16 = i16;
+
+/// 24-bit unsigned integer.
+pub const uint24 = u24;
+
+/// 32-bit unsigned integer.
+pub const uint32 = u32;
+
+/// 32-bit signed integer.
+pub const int32 = i32;
+
+/// 32-bit signed fixed-point number (16.16)
+pub const Fixed = FixedPoint(i32, 16, 16);
+
+/// int16 that describes a quantity in font design units.
+pub const FWORD = i16;
+
+/// uint16 that describes a quantity in font design units.
+pub const UFWORD = u16;
+
+/// 16-bit signed fixed number with the low 14 bits of fraction (2.14).
+pub const F2DOT14 = FixedPoint(i16, 2, 14);
+
+/// Date and time represented in number of seconds since 12:00 midnight, January 1, 1904, UTC. The value is represented as a signed 64-bit integer.
+pub const LONGDATETIME = i64;
+
+/// Array of four uint8s (length = 32 bits) used to identify a table,
+/// design-variation axis, script, language system, feature, or baseline.
+pub const Tag = [4]u8;
+
+/// 8-bit offset to a table, same as uint8, NULL offset = 0x00
+pub const Offset8 = u8;
+
+/// Short offset to a table, same as uint16, NULL offset = 0x0000
+pub const Offset16 = u16;
+
+/// 24-bit offset to a table, same as uint24, NULL offset = 0x000000
+pub const Offset24 = u24;
+
+/// Long offset to a table, same as uint32, NULL offset = 0x00000000
+pub const Offset32 = u32;
+
+/// Packed 32-bit value with major and minor version numbers
+pub const Version16Dot16 = packed struct(u32) {
+ minor: u16,
+ major: u16,
+};
+
+/// 32-bit signed 26.6 fixed point numbers.
+pub const F26Dot6 = FixedPoint(i32, 26, 6);
+
+fn FixedPoint(comptime T: type, int_bits: u64, frac_bits: u64) type {
+ const type_info: std.builtin.Type.Int = @typeInfo(T).int;
+ comptime assert(int_bits + frac_bits == type_info.bits);
+
+ return packed struct(T) {
+ const Self = FixedPoint(T, int_bits, frac_bits);
+ const frac_factor: comptime_float = @floatFromInt(std.math.pow(
+ u64,
+ 2,
+ frac_bits,
+ ));
+ const half = @as(T, 1) << @intCast(frac_bits - 1);
+
+ const Frac = std.meta.Int(.unsigned, frac_bits);
+ const Int = std.meta.Int(type_info.signedness, int_bits);
+
+ frac: Frac,
+ int: Int,
+
+ pub fn to(self: Self, comptime FloatType: type) FloatType {
+ return @as(FloatType, @floatFromInt(
+ @as(T, @bitCast(self)),
+ )) / frac_factor;
+ }
+
+ pub fn from(float: anytype) Self {
+ return @bitCast(
+ @as(T, @intFromFloat(@round(float * frac_factor))),
+ );
+ }
+
+ /// Round to the nearest integer, .5 rounds away from 0.
+ pub fn round(self: Self) T {
+ if (self.frac & half != 0)
+ return self.int + 1
+ else
+ return self.int;
+ }
+
+ pub fn format(
+ self: Self,
+ comptime fmt: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: *std.Io.Writer,
+ ) !void {
+ _ = fmt;
+ _ = options;
+
+ try writer.print("{d}", .{self.to(f64)});
+ }
+ };
+}
+
+test FixedPoint {
+ const testing = std.testing;
+
+ const p26d6 = F26Dot6.from(26.6);
+ try testing.expectEqual(F26Dot6{
+ .int = 26,
+ .frac = 38,
+ }, p26d6);
+ try testing.expectEqual(26.59375, p26d6.to(f64));
+ try testing.expectEqual(27, p26d6.round());
+
+ const n26d6 = F26Dot6.from(-26.6);
+ try testing.expectEqual(F26Dot6{
+ .int = -27,
+ .frac = 26,
+ }, n26d6);
+ try testing.expectEqual(-26.59375, n26d6.to(f64));
+ try testing.expectEqual(-27, n26d6.round());
+}
+
+/// Wrapper for parsing a SFNT font and accessing its tables.
+///
+/// References:
+/// - https://learn.microsoft.com/en-us/typography/opentype/spec/otff
+/// - https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6.html
+pub const SFNT = struct {
+ const Directory = struct {
+ offset: OffsetSubtable,
+ records: []TableRecord,
+
+ /// The static (fixed-sized) portion of the table directory
+ ///
+ /// This struct matches the memory layout of the TrueType/OpenType
+ /// TableDirectory, but does not include the TableRecord array, since
+ /// that is dynamically sized, so we parse it separately.
+ ///
+ /// In the TrueType reference manual this
+ /// is referred to as the "offset subtable".
+ ///
+ /// https://learn.microsoft.com/en-us/typography/opentype/spec/otff#table-directory
+ const OffsetSubtable = extern struct {
+ /// Indicates the type of font file we're reading.
+ /// - 0x00_01_00_00 ---- TrueType
+ /// - 0x74_72_75_65 'true' TrueType
+ /// - 0x4F_54_54_4F 'OTTO' OpenType
+ /// - 0x74_79_70_31 'typ1' PostScript
+ sfnt_version: uint32 align(1),
+ /// Number of tables.
+ num_tables: uint16 align(1),
+ /// Maximum power of 2 less than or equal to numTables, times 16 ((2**floor(log2(numTables))) * 16, where “**” is an exponentiation operator).
+ search_range: uint16 align(1),
+ /// Log2 of the maximum power of 2 less than or equal to numTables (log2(searchRange/16), which is equal to floor(log2(numTables))).
+ entry_selector: uint16 align(1),
+ /// numTables times 16, minus searchRange ((numTables * 16) - searchRange).
+ range_shift: uint16 align(1),
+
+ pub fn format(
+ self: OffsetSubtable,
+ comptime fmt: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: *std.Io.Writer,
+ ) !void {
+ _ = fmt;
+ _ = options;
+
+ try writer.print(
+ "OffsetSubtable('{s}'){{ .num_tables = {} }}",
+ .{
+ if (self.sfnt_version == 0x00_01_00_00)
+ &@as([10]u8, "0x00010000".*)
+ else
+ &@as([4]u8, @bitCast(
+ std.mem.nativeToBig(u32, self.sfnt_version),
+ )),
+ self.num_tables,
+ },
+ );
+ }
+ };
+
+ const TableRecord = extern struct {
+ /// Table identifier.
+ tag: Tag align(1),
+ /// Checksum for this table.
+ checksum: uint32 align(1),
+ /// Offset from beginning of font file.
+ offset: Offset32 align(1),
+ /// Length of this table.
+ length: uint32 align(1),
+
+ pub fn format(
+ self: TableRecord,
+ comptime fmt: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: *std.Io.Writer,
+ ) !void {
+ _ = fmt;
+ _ = options;
+
+ try writer.print(
+ "TableRecord(\"{s}\"){{ .checksum = {}, .offset = {}, .length = {} }}",
+ .{
+ self.tag,
+ self.checksum,
+ self.offset,
+ self.length,
+ },
+ );
+ }
+ };
+ };
+
+ directory: Directory,
+
+ data: []const u8,
+
+ /// Parse a font from raw data. The struct will keep a
+ /// reference to `data` and use it for future operations.
+ pub fn init(data: []const u8, alloc: Allocator) !SFNT {
+ var fbs = std.io.fixedBufferStream(data);
+ const reader = fbs.reader();
+
+ // SFNT files use big endian, if our native endian is
+ // not big we'll need to byte swap the values we read.
+ const byte_swap = native_endian != .big;
+
+ var directory: Directory = undefined;
+
+ try reader.readNoEof(std.mem.asBytes(&directory.offset));
+ if (byte_swap) std.mem.byteSwapAllFields(
+ Directory.OffsetSubtable,
+ &directory.offset,
+ );
+
+ directory.records = try alloc.alloc(Directory.TableRecord, directory.offset.num_tables);
+
+ try reader.readNoEof(std.mem.sliceAsBytes(directory.records));
+ if (byte_swap) for (directory.records) |*record| {
+ std.mem.byteSwapAllFields(
+ Directory.TableRecord,
+ record,
+ );
+ };
+
+ return .{
+ .directory = directory,
+ .data = data,
+ };
+ }
+
+ pub fn deinit(self: SFNT, alloc: Allocator) void {
+ alloc.free(self.directory.records);
+ }
+
+ /// Returns the bytes of the table with the provided tag if present.
+ pub fn getTable(self: SFNT, tag: *const [4]u8) ?[]const u8 {
+ for (self.directory.records) |record| {
+ if (std.mem.eql(u8, tag, &record.tag)) {
+ return self.data[record.offset..][0..record.length];
+ }
+ }
+
+ return null;
+ }
+};
+
+const native_endian = @import("builtin").target.cpu.arch.endian();
+
+test "parse font" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ const test_font = @import("../embedded.zig").julia_mono;
+
+ const sfnt = try SFNT.init(&test_font.*, alloc);
+ defer sfnt.deinit(alloc);
+
+ try testing.expectEqual(19, sfnt.directory.offset.num_tables);
+ try testing.expectEqualStrings("prep", &sfnt.directory.records[18].tag);
+}
+
+test "get table" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ const test_font = @import("../embedded.zig").julia_mono;
+
+ const sfnt = try SFNT.init(&test_font.*, alloc);
+ defer sfnt.deinit(alloc);
+
+ const svg = sfnt.getTable("SVG ").?;
+
+ try testing.expectEqual(430, svg.len);
+}
diff --git a/vendor/libghostty-vt/src/font/opentype/svg.zig b/vendor/libghostty-vt/src/font/opentype/svg.zig
new file mode 100644
index 00000000..b4d9ccaa
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/opentype/svg.zig
@@ -0,0 +1,114 @@
+const std = @import("std");
+const font = @import("../main.zig");
+
+/// SVG glyphs description table.
+///
+/// This struct is focused purely on the operations we need for Ghostty,
+/// namely to be able to look up whether an glyph ID is present in the SVG
+/// table or not. This struct isn't meant to be a general purpose SVG table
+/// reader.
+///
+/// References:
+/// - https://www.w3.org/2013/10/SVG_in_OpenType/#thesvg
+/// - https://learn.microsoft.com/en-us/typography/opentype/spec/svg
+pub const SVG = struct {
+ /// The start and end glyph IDs (inclusive) that are present in the
+ /// table. This is used to very quickly include/exclude a glyph from
+ /// the table.
+ start_glyph_id: u16,
+ end_glyph_id: u16,
+
+ /// All records in the table.
+ records: []const [12]u8,
+
+ pub fn init(data: []const u8) error{
+ EndOfStream,
+ SVGVersionNotSupported,
+ }!SVG {
+ var fbs = std.io.fixedBufferStream(data);
+ const reader = fbs.reader();
+
+ // Version
+ if (try reader.readInt(u16, .big) != 0) {
+ return error.SVGVersionNotSupported;
+ }
+
+ // Offset
+ const offset = try reader.readInt(u32, .big);
+
+ // Seek to the offset to get our document list
+ try fbs.seekTo(offset);
+
+ // Get our document records along with the start/end glyph range.
+ const len = try reader.readInt(u16, .big);
+ const records: [*]const [12]u8 = @ptrCast(data[try fbs.getPos()..]);
+ const start_range = try glyphRange(&records[0]);
+ const end_range = if (len == 1) start_range else try glyphRange(&records[(len - 1)]);
+
+ return .{
+ .start_glyph_id = start_range[0],
+ .end_glyph_id = end_range[1],
+ .records = records[0..len],
+ };
+ }
+
+ pub fn hasGlyph(self: SVG, glyph_id: u16) bool {
+ // Fast path: outside the table range
+ if (glyph_id < self.start_glyph_id or glyph_id > self.end_glyph_id) {
+ return false;
+ }
+
+ // Fast path, matches the start/end glyph IDs
+ if (glyph_id == self.start_glyph_id or glyph_id == self.end_glyph_id) {
+ return true;
+ }
+
+ // Slow path: binary search our records
+ return std.sort.binarySearch(
+ [12]u8,
+ self.records,
+ glyph_id,
+ compareGlyphId,
+ ) != null;
+ }
+
+ fn compareGlyphId(glyph_id: u16, record: [12]u8) std.math.Order {
+ const start, const end = glyphRange(&record) catch return .lt;
+ if (glyph_id < start) {
+ return .lt;
+ } else if (glyph_id > end) {
+ return .gt;
+ } else {
+ return .eq;
+ }
+ }
+
+ fn glyphRange(record: []const u8) !struct { u16, u16 } {
+ var fbs = std.io.fixedBufferStream(record);
+ const reader = fbs.reader();
+ return .{
+ try reader.readInt(u16, .big),
+ try reader.readInt(u16, .big),
+ };
+ }
+};
+
+test "SVG" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ const testFont = font.embedded.julia_mono;
+
+ var lib = try font.Library.init(alloc);
+ defer lib.deinit();
+
+ var face = try font.Face.init(lib, testFont, .{ .size = .{ .points = 12 } });
+ defer face.deinit();
+
+ const table = (try face.copyTable(alloc, "SVG ")).?;
+ defer alloc.free(table);
+
+ const svg = try SVG.init(table);
+ try testing.expectEqual(11482, svg.start_glyph_id);
+ try testing.expectEqual(11482, svg.end_glyph_id);
+ try testing.expect(svg.hasGlyph(11482));
+}
diff --git a/vendor/libghostty-vt/src/font/shape.zig b/vendor/libghostty-vt/src/font/shape.zig
new file mode 100644
index 00000000..864c0f01
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/shape.zig
@@ -0,0 +1,101 @@
+const std = @import("std");
+const options = @import("main.zig").options;
+const run = @import("shaper/run.zig");
+const feature = @import("shaper/feature.zig");
+const configpkg = @import("../config.zig");
+const terminal = @import("../terminal/main.zig");
+const SharedGrid = @import("main.zig").SharedGrid;
+pub const noop = @import("shaper/noop.zig");
+pub const harfbuzz = @import("shaper/harfbuzz.zig");
+pub const coretext = @import("shaper/coretext.zig");
+pub const web_canvas = @import("shaper/web_canvas.zig");
+pub const Cache = @import("shaper/Cache.zig");
+pub const TextRun = run.TextRun;
+pub const RunIterator = run.RunIterator;
+pub const Feature = feature.Feature;
+pub const FeatureList = feature.FeatureList;
+pub const default_features = feature.default_features;
+
+/// Shaper implementation for our compile options.
+pub const Shaper = switch (options.backend) {
+ .freetype,
+ .fontconfig_freetype,
+ .coretext_freetype,
+ .coretext_harfbuzz,
+ => harfbuzz.Shaper,
+
+ // Note that coretext_freetype cannot use the coretext
+ // shaper because the coretext shaper requests CoreText
+ // font faces.
+ .coretext => coretext.Shaper,
+
+ .coretext_noshape => noop.Shaper,
+
+ .web_canvas => web_canvas.Shaper,
+};
+
+/// A cell is a single glyph within a terminal that should be rendered
+/// for a shaping call. Not all terminal cells may be present; only
+/// cells that have a glyph that needs to be rendered.
+pub const Cell = struct {
+ /// The X position of this shaper cell relative to the offset of the
+ /// run. Because runs are always within a single row, it is expected
+ /// that the caller can reconstruct the full position of the cell by
+ /// using the known Y position of the cell and adding the X position
+ /// to the run offset.
+ x: u16,
+
+ /// An additional offset to apply to the rendering.
+ x_offset: i16 = 0,
+ y_offset: i16 = 0,
+
+ /// The glyph index for this cell. The font index to use alongside
+ /// this cell is available in the text run. This glyph index is only
+ /// valid for a given GroupCache and FontIndex that was used to create
+ /// the runs.
+ glyph_index: u32,
+};
+
+/// Options for shapers.
+pub const Options = struct {
+ /// Font features to use when shaping.
+ ///
+ /// Note: eventually, this will move to font.Face probably as we may
+ /// want to support per-face feature configuration. For now, we only
+ /// support applying features globally.
+ features: []const []const u8 = &.{},
+};
+
+/// Options for runIterator.
+pub const RunOptions = struct {
+ /// The font state for the terminal screen. This is mutable because
+ /// cached values may be updated during shaping.
+ grid: *SharedGrid,
+
+ /// The cells for the row to shape.
+ cells: std.MultiArrayList(terminal.RenderState.Cell).Slice = .empty,
+
+ /// The x boundaries of the selection in this row.
+ selection: ?[2]u16 = null,
+
+ /// The cursor position within this row. This is used to break shaping
+ /// on cursor boundaries. This can be disabled by setting this to
+ /// null.
+ cursor_x: ?usize = null,
+
+ /// Apply the font break configuration to the run.
+ pub fn applyBreakConfig(
+ self: *RunOptions,
+ config: configpkg.FontShapingBreak,
+ ) void {
+ if (!config.cursor) self.cursor_x = null;
+ }
+};
+
+test {
+ _ = Cache;
+ _ = Shaper;
+
+ // Always test noop
+ _ = noop;
+}
diff --git a/vendor/libghostty-vt/src/font/shaper/Cache.zig b/vendor/libghostty-vt/src/font/shaper/Cache.zig
new file mode 100644
index 00000000..2696985a
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/shaper/Cache.zig
@@ -0,0 +1,106 @@
+//! This structure caches the shaped cells for a given text run.
+//!
+//! At one point, shaping was the most expensive part of rendering text
+//! (accounting for 96% of frame time on my machine). To speed it up, this
+//! was introduced so that shaping results can be cached depending on the
+//! run.
+//!
+//! The cache key is the text run. The text run builds its own hash value
+//! based on the font, style, codepoint, etc. This just utilizes the hash that
+//! the text run provides.
+pub const Cache = @This();
+
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const font = @import("../main.zig");
+const CacheTable = @import("../../datastruct/main.zig").CacheTable;
+
+const log = std.log.scoped(.font_shaper_cache);
+
+/// Context for cache table.
+const CellCacheTableContext = struct {
+ pub fn hash(self: *const CellCacheTableContext, key: u64) u64 {
+ _ = self;
+ return key;
+ }
+ pub fn eql(self: *const CellCacheTableContext, a: u64, b: u64) bool {
+ _ = self;
+ return a == b;
+ }
+};
+
+/// Cache table for run hash -> shaped cells.
+const CellCacheTable = CacheTable(
+ u64,
+ []font.shape.Cell,
+ CellCacheTableContext,
+
+ // Capacity is slightly arbitrary. These numbers are guesses.
+ //
+ // I'd expect then an average of 256 frequently cached runs is a
+ // safe guess most terminal screens.
+ 256,
+ // 8 items per bucket to give decent resiliency to important runs.
+ 8,
+);
+
+/// The cache table of shaped cells.
+map: CellCacheTable,
+
+pub fn init() Cache {
+ return .{ .map = .{ .context = .{} } };
+}
+
+pub fn deinit(self: *Cache, alloc: Allocator) void {
+ self.clear(alloc);
+}
+
+/// Get the shaped cells for the given text run,
+/// or null if they are not in the cache.
+pub fn get(self: *Cache, run: font.shape.TextRun) ?[]const font.shape.Cell {
+ return self.map.get(run.hash);
+}
+
+/// Insert the shaped cells for the given text run into the cache.
+///
+/// The cells will be duplicated.
+pub fn put(
+ self: *Cache,
+ alloc: Allocator,
+ run: font.shape.TextRun,
+ cells: []const font.shape.Cell,
+) Allocator.Error!void {
+ const copy = try alloc.dupe(font.shape.Cell, cells);
+ const evicted = self.map.put(run.hash, copy);
+ if (evicted) |kv| {
+ alloc.free(kv.value);
+ }
+}
+
+fn clear(self: *Cache, alloc: Allocator) void {
+ for (self.map.buckets, self.map.lengths) |b, l| {
+ for (b[0..l]) |kv| {
+ alloc.free(kv.value);
+ }
+ }
+ self.map.clear();
+}
+
+test Cache {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var c = Cache.init();
+ defer c.deinit(alloc);
+
+ var run: font.shape.TextRun = undefined;
+ run.hash = 1;
+ try testing.expect(c.get(run) == null);
+ try c.put(alloc, run, &.{
+ .{ .x = 0, .glyph_index = 0 },
+ .{ .x = 1, .glyph_index = 1 },
+ });
+
+ const actual = c.get(run).?;
+ try testing.expect(actual.len == 2);
+}
diff --git a/vendor/libghostty-vt/src/font/shaper/coretext.zig b/vendor/libghostty-vt/src/font/shaper/coretext.zig
new file mode 100644
index 00000000..ff7c6d9d
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/shaper/coretext.zig
@@ -0,0 +1,2678 @@
+const std = @import("std");
+const builtin = @import("builtin");
+const assert = @import("../../quirks.zig").inlineAssert;
+const Allocator = std.mem.Allocator;
+const macos = @import("macos");
+const font = @import("../main.zig");
+const os = @import("../../os/main.zig");
+const terminal = @import("../../terminal/main.zig");
+const unicode = @import("../../unicode/main.zig");
+const Feature = font.shape.Feature;
+const FeatureList = font.shape.FeatureList;
+const default_features = font.shape.default_features;
+const Face = font.Face;
+const Collection = font.Collection;
+const DeferredFace = font.DeferredFace;
+const Group = font.Group;
+const GroupCache = font.GroupCache;
+const Library = font.Library;
+const SharedGrid = font.SharedGrid;
+const Style = font.Style;
+const Presentation = font.Presentation;
+const CFReleaseThread = os.CFReleaseThread;
+
+const log = std.log.scoped(.font_shaper);
+
+/// Shaper that uses CoreText.
+///
+/// CoreText shaping differs in subtle ways from HarfBuzz so it may result
+/// in inconsistent rendering across platforms. But it also fixes many
+/// issues (some macOS specific):
+///
+/// - Theta hat offset is incorrect in HarfBuzz but correct by default
+/// on macOS applications using CoreText. (See:
+/// https://github.com/harfbuzz/harfbuzz/discussions/4525)
+///
+/// - Hyphens (U+2010) can be synthesized by CoreText but not by HarfBuzz.
+/// See: https://github.com/mitchellh/ghostty/issues/1643
+///
+pub const Shaper = struct {
+ /// The allocated used for the feature list, font cache, and cell buf.
+ alloc: Allocator,
+
+ /// The string used for shaping the current run.
+ run_state: RunState,
+
+ /// CoreFoundation Dictionary which represents our font feature settings.
+ features: *macos.foundation.Dictionary,
+ /// A version of the features dictionary with the default features excluded.
+ features_no_default: *macos.foundation.Dictionary,
+
+ /// The shared memory used for shaping results.
+ cell_buf: CellBuf,
+
+ /// Cached attributes dict for creating CTTypesetter objects.
+ /// The values in this never change so we can avoid overhead
+ /// by just creating it once and saving it for reuse.
+ typesetter_attr_dict: *macos.foundation.Dictionary,
+
+ /// List where we cache fonts, so we don't have to remake them for
+ /// every single shaping operation.
+ ///
+ /// Fonts are cached as attribute dictionaries to be applied directly to
+ /// attributed strings.
+ cached_fonts: std.ArrayListUnmanaged(?*macos.foundation.Dictionary),
+
+ /// The grid that our cached fonts correspond to.
+ /// If the grid changes then we need to reset our cache.
+ cached_font_grid: usize,
+
+ /// The list of CoreFoundation objects to release on the dedicated
+ /// release thread. This is built up over the course of shaping and
+ /// sent to the release thread when endFrame is called.
+ cf_release_pool: std.ArrayListUnmanaged(*anyopaque),
+
+ /// Dedicated thread for releasing CoreFoundation objects. Some objects,
+ /// such as those produced by CoreText, have excessively slow release
+ /// callback logic.
+ cf_release_thread: *CFReleaseThread,
+ cf_release_thr: std.Thread,
+
+ const CellBuf = std.ArrayListUnmanaged(font.shape.Cell);
+ const CodepointList = std.ArrayListUnmanaged(Codepoint);
+ const Codepoint = struct {
+ codepoint: u32,
+ cluster: u32,
+ };
+
+ const RunState = struct {
+ codepoints: CodepointList,
+ unichars: std.ArrayListUnmanaged(u16),
+
+ fn init() RunState {
+ return .{ .codepoints = .{}, .unichars = .{} };
+ }
+
+ fn deinit(self: *RunState, alloc: Allocator) void {
+ self.codepoints.deinit(alloc);
+ self.unichars.deinit(alloc);
+ }
+
+ fn reset(self: *RunState) void {
+ self.codepoints.clearRetainingCapacity();
+ self.unichars.clearRetainingCapacity();
+ }
+ };
+
+ const Offset = struct {
+ cluster: u32 = 0,
+ x: f64 = 0,
+ };
+
+ /// Create a CoreFoundation Dictionary suitable for
+ /// settings the font features of a CoreText font.
+ fn makeFeaturesDict(feats: []const Feature) !*macos.foundation.Dictionary {
+ const list = try macos.foundation.MutableArray.create();
+ // The list will be retained by the dict once we add it to it.
+ defer list.release();
+
+ for (feats) |feat| {
+ const value_num: c_int = @intCast(feat.value);
+
+ // Keys can only be ASCII.
+ var key = try macos.foundation.String.createWithBytes(&feat.tag, .ascii, false);
+ defer key.release();
+ var value = try macos.foundation.Number.create(.int, &value_num);
+ defer value.release();
+
+ const dict = try macos.foundation.Dictionary.create(
+ &[_]?*const anyopaque{
+ macos.text.c.kCTFontOpenTypeFeatureTag,
+ macos.text.c.kCTFontOpenTypeFeatureValue,
+ },
+ &[_]?*const anyopaque{ key, value },
+ );
+ defer dict.release();
+
+ list.appendValue(macos.foundation.Dictionary, dict);
+ }
+
+ var dict = try macos.foundation.Dictionary.create(
+ &[_]?*const anyopaque{macos.text.c.kCTFontFeatureSettingsAttribute},
+ &[_]?*const anyopaque{list},
+ );
+ errdefer dict.release();
+
+ return dict;
+ }
+
+ /// The cell_buf argument is the buffer to use for storing shaped results.
+ /// This should be at least the number of columns in the terminal.
+ pub fn init(alloc: Allocator, opts: font.shape.Options) !Shaper {
+ var feature_list: FeatureList = .{};
+ defer feature_list.deinit(alloc);
+ for (opts.features) |feature_str| {
+ try feature_list.appendFromString(alloc, feature_str);
+ }
+
+ // We need to construct two attrs dictionaries for font features;
+ // one without the default features included, and one with them.
+ const feats = feature_list.features.items;
+ const feats_df = try alloc.alloc(Feature, feats.len + default_features.len);
+ defer alloc.free(feats_df);
+
+ @memcpy(feats_df[0..default_features.len], &default_features);
+ @memcpy(feats_df[default_features.len..], feats);
+
+ const features = try makeFeaturesDict(feats_df);
+ errdefer features.release();
+ const features_no_default = try makeFeaturesDict(feats);
+ errdefer features_no_default.release();
+
+ var run_state = RunState.init();
+ errdefer run_state.deinit(alloc);
+
+ // For now we only support LTR text. If we shape RTL text then
+ // rendering will be very wrong so we need to explicitly force
+ // LTR no matter what.
+ //
+ // See: https://github.com/mitchellh/ghostty/issues/1737
+ // See: https://github.com/mitchellh/ghostty/issues/1442
+ //
+ // We used to do this by setting the writing direction attribute
+ // on the attributed string we used, but it seems like that will
+ // still allow some weird results, for example a single space at
+ // the end of a line composed of RTL characters will be cause it
+ // to output a run containing just that space, BEFORE it outputs
+ // the rest of the line as a separate run, very weirdly with the
+ // "right to left" flag set in the single space run's run status...
+ //
+ // So instead what we do is use a CTTypesetter to create our line,
+ // using the kCTTypesetterOptionForcedEmbeddingLevel attribute to
+ // force CoreText not to try doing any sort of BiDi, instead just
+ // treat all text as embedding level 0 (left to right).
+ const typesetter_attr_dict = dict: {
+ const num = try macos.foundation.Number.create(.int, &0);
+ defer num.release();
+ break :dict try macos.foundation.Dictionary.create(
+ &.{macos.c.kCTTypesetterOptionForcedEmbeddingLevel},
+ &.{num},
+ );
+ };
+ errdefer typesetter_attr_dict.release();
+
+ // Create the CF release thread.
+ var cf_release_thread = try alloc.create(CFReleaseThread);
+ errdefer alloc.destroy(cf_release_thread);
+ cf_release_thread.* = try .init(alloc);
+ errdefer cf_release_thread.deinit();
+
+ // Start the CF release thread.
+ var cf_release_thr = try std.Thread.spawn(
+ .{},
+ CFReleaseThread.threadMain,
+ .{cf_release_thread},
+ );
+ cf_release_thr.setName("cf_release") catch {};
+
+ return .{
+ .alloc = alloc,
+ .cell_buf = .{},
+ .run_state = run_state,
+ .features = features,
+ .features_no_default = features_no_default,
+ .typesetter_attr_dict = typesetter_attr_dict,
+ .cached_fonts = .{},
+ .cached_font_grid = 0,
+ .cf_release_pool = .{},
+ .cf_release_thread = cf_release_thread,
+ .cf_release_thr = cf_release_thr,
+ };
+ }
+
+ pub fn deinit(self: *Shaper) void {
+ self.cell_buf.deinit(self.alloc);
+ self.run_state.deinit(self.alloc);
+ self.features.release();
+ self.features_no_default.release();
+ self.typesetter_attr_dict.release();
+
+ {
+ for (self.cached_fonts.items) |ft| {
+ if (ft) |f| f.release();
+ }
+ self.cached_fonts.deinit(self.alloc);
+ }
+
+ if (self.cf_release_pool.items.len > 0) {
+ for (self.cf_release_pool.items) |ref| macos.foundation.CFRelease(ref);
+
+ // For tests this logic is normal because we don't want to
+ // wait for a release thread. But in production this is a bug
+ // and we should warn.
+ if (comptime !builtin.is_test) log.warn(
+ "BUG: CFRelease pool was not empty, releasing remaining objects",
+ .{},
+ );
+ }
+ self.cf_release_pool.deinit(self.alloc);
+
+ // Stop the CF release thread
+ {
+ self.cf_release_thread.stop.notify() catch |err|
+ log.err("error notifying cf release thread to stop, may stall err={}", .{err});
+ self.cf_release_thr.join();
+ }
+ self.cf_release_thread.deinit();
+ self.alloc.destroy(self.cf_release_thread);
+ }
+
+ pub fn endFrame(self: *Shaper) void {
+ if (self.cf_release_pool.items.len == 0) return;
+
+ // Get all the items in the pool as an owned slice so we can
+ // send it to the dedicated release thread.
+ const items = self.cf_release_pool.toOwnedSlice(self.alloc) catch |err| {
+ log.warn("error converting release pool to owned slice, slow release err={}", .{err});
+ for (self.cf_release_pool.items) |ref| macos.foundation.CFRelease(ref);
+ self.cf_release_pool.clearRetainingCapacity();
+ return;
+ };
+
+ // Send the items. If the send succeeds then we wake up the
+ // thread to process the items. If the send fails then do a manual
+ // cleanup.
+ if (self.cf_release_thread.mailbox.push(.{ .release = .{
+ .refs = items,
+ .alloc = self.alloc,
+ } }, .{ .forever = {} }) != 0) {
+ self.cf_release_thread.wakeup.notify() catch |err| {
+ log.warn(
+ "error notifying cf release thread to wake up, may stall err={}",
+ .{err},
+ );
+ };
+ return;
+ }
+
+ for (items) |ref| macos.foundation.CFRelease(ref);
+ self.alloc.free(items);
+ }
+
+ pub fn runIterator(
+ self: *Shaper,
+ opts: font.shape.RunOptions,
+ ) font.shape.RunIterator {
+ return .{
+ .hooks = .{ .shaper = self },
+ .opts = opts,
+ };
+ }
+
+ /// Note that this will accumulate garbage in the release pool. The
+ /// caller must ensure you're properly calling endFrame to release
+ /// all the objects.
+ pub fn shape(
+ self: *Shaper,
+ run: font.shape.TextRun,
+ ) ![]const font.shape.Cell {
+ const state = &self.run_state;
+
+ // {
+ // log.debug("shape -----------------------------------", .{});
+ // for (state.codepoints.items) |entry| {
+ // log.debug("cp={X} cluster={}", .{ entry.codepoint, entry.cluster });
+ // }
+ // log.debug("shape end -------------------------------", .{});
+ // }
+
+ // Special fonts aren't shaped and their codepoint == glyph so we
+ // can just return the codepoints as-is.
+ if (run.font_index.special() != null) {
+ self.cell_buf.clearRetainingCapacity();
+ try self.cell_buf.ensureTotalCapacity(self.alloc, state.codepoints.items.len);
+ for (state.codepoints.items) |entry| {
+ // We use null codepoints to pad out our list so indices match
+ // the UTF-16 string we constructed for CoreText. We don't want
+ // to emit these if this is a special font, since they're not
+ // part of the original run.
+ if (entry.codepoint == 0) continue;
+
+ self.cell_buf.appendAssumeCapacity(.{
+ .x = @intCast(entry.cluster),
+ .glyph_index = @intCast(entry.codepoint),
+ });
+ }
+
+ return self.cell_buf.items;
+ }
+
+ // Create an arena for any Zig-based allocations we do
+ var arena = std.heap.ArenaAllocator.init(self.alloc);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ const attr_dict: *macos.foundation.Dictionary = try self.getFont(
+ run.grid,
+ run.font_index,
+ );
+
+ // Make room for the attributed string, CTTypesetter, and CTLine.
+ try self.cf_release_pool.ensureUnusedCapacity(self.alloc, 4);
+
+ const str = macos.foundation.String.createWithCharactersNoCopy(state.unichars.items);
+ self.cf_release_pool.appendAssumeCapacity(str);
+
+ // Create an attributed string from our string
+ const attr_str = try macos.foundation.AttributedString.create(
+ str,
+ attr_dict,
+ );
+ self.cf_release_pool.appendAssumeCapacity(attr_str);
+
+ // Create a typesetter from the attributed string and the cached
+ // attr dict. (See comment in init for more info on the attr dict.)
+ const typesetter =
+ try macos.text.Typesetter.createWithAttributedStringAndOptions(
+ attr_str,
+ self.typesetter_attr_dict,
+ );
+ self.cf_release_pool.appendAssumeCapacity(typesetter);
+
+ // Create a line from the typesetter
+ const line = typesetter.createLine(.{ .location = 0, .length = 0 });
+ self.cf_release_pool.appendAssumeCapacity(line);
+
+ // This keeps track of the current x offset (sum of advance.width) and
+ // the furthest cluster we've seen so far (max).
+ var run_offset: Offset = .{};
+
+ // This keeps track of the cell starting x and cluster.
+ var cell_offset: Offset = .{};
+
+ // For debugging positions, turn this on:
+ //var run_offset_y: f64 = 0.0;
+ //var cell_offset_y: f64 = 0.0;
+
+ // Clear our cell buf and make sure we have enough room for the whole
+ // line of glyphs, so that we can just assume capacity when appending
+ // instead of maybe allocating.
+ self.cell_buf.clearRetainingCapacity();
+ try self.cell_buf.ensureTotalCapacity(self.alloc, line.getGlyphCount());
+
+ // CoreText, despite our insistence with an enforced embedding level,
+ // may sometimes output runs that are non-monotonic. In order to fix
+ // this, we check the run status for each run and if any aren't ltr
+ // we set this to true, which indicates that we must sort our buffer.
+ var non_ltr: bool = false;
+
+ // CoreText may generate multiple runs even though our input to
+ // CoreText is already split into runs by our own run iterator.
+ // The runs as far as I can tell are always sequential to each
+ // other so we can iterate over them and just append to our
+ // cell buffer.
+ const runs = line.getGlyphRuns();
+ for (0..runs.getCount()) |run_i| {
+ const ctrun = runs.getValueAtIndex(macos.text.Run, run_i);
+
+ const status = ctrun.getStatus();
+ if (status.non_monotonic or status.right_to_left) non_ltr = true;
+
+ // Get our glyphs and positions
+ const glyphs = ctrun.getGlyphsPtr() orelse try ctrun.getGlyphs(alloc);
+ const advances = ctrun.getAdvancesPtr() orelse try ctrun.getAdvances(alloc);
+ const positions = ctrun.getPositionsPtr() orelse try ctrun.getPositions(alloc);
+ const indices = ctrun.getStringIndicesPtr() orelse try ctrun.getStringIndices(alloc);
+ assert(glyphs.len == advances.len);
+ assert(glyphs.len == positions.len);
+ assert(glyphs.len == indices.len);
+
+ for (
+ glyphs,
+ advances,
+ positions,
+ indices,
+ ) |glyph, advance, position, index| {
+ // Our cluster is also our cell X position. If the cluster changes
+ // then we need to reset our current cell offsets.
+ const cluster = state.codepoints.items[index].cluster;
+ if (cell_offset.cluster != cluster) {
+ // We previously asserted that the new cluster is greater
+ // than cell_offset.cluster, but this isn't always true.
+ // See e.g. the "shape Chakma vowel sign with ligature
+ // (vowel sign renders first)" test.
+
+ const is_after_glyph_from_current_or_next_clusters =
+ cluster <= run_offset.cluster;
+
+ const is_first_codepoint_in_cluster = blk: {
+ var i = index;
+ while (i > 0) {
+ i -= 1;
+ const codepoint = state.codepoints.items[i];
+
+ // Skip surrogate pair padding
+ if (codepoint.codepoint == 0) continue;
+ break :blk codepoint.cluster != cluster;
+ } else break :blk true;
+ };
+
+ // We need to reset the `cell_offset` at the start of a new
+ // cluster, but we do that conditionally if the codepoint
+ // `is_first_codepoint_in_cluster` and the cluster is not
+ // `is_after_glyph_from_current_or_next_clusters`, which is
+ // a heuristic to detect ligatures and avoid positioning
+ // glyphs that mark ligatures incorrectly. The idea is that
+ // if the first codepoint in a cluster doesn't appear in
+ // the stream, it's very likely that it combined with
+ // codepoints from a previous cluster into a ligature.
+ // Then, the subsequent codepoints are very likely marking
+ // glyphs that are placed relative to that ligature, so if
+ // we were to reset the `cell_offset` to align it with the
+ // grid, the positions would be off. The
+ // `!is_after_glyph_from_current_or_next_clusters` check is
+ // needed in case these marking glyphs come from a later
+ // cluster but are rendered first (see the Chakma and
+ // Bengali tests). In that case when we get to the
+ // codepoint that `is_first_codepoint_in_cluster`, but in a
+ // cluster that
+ // `is_after_glyph_from_current_or_next_clusters`, we don't
+ // want to reset to the grid and cause the positions to be
+ // off. (Note that we could go back and align the cells to
+ // the grid starting from the one from the cluster that
+ // rendered out of order, but that is more complicated so
+ // we don't do that for now. Also, it's TBD if there are
+ // exceptions to this heuristic for detecting ligatures,
+ // but using the logging below seems to show it works
+ // well.)
+ if (is_first_codepoint_in_cluster and
+ !is_after_glyph_from_current_or_next_clusters)
+ {
+ cell_offset = .{
+ .cluster = cluster,
+ .x = run_offset.x,
+ };
+
+ // For debugging positions, turn this on:
+ //cell_offset_y = run_offset_y;
+ }
+ }
+
+ // For debugging positions, turn this on:
+ //try self.debugPositions(alloc, run_offset, run_offset_y, cell_offset, cell_offset_y, position, index);
+
+ const x_offset = position.x - cell_offset.x;
+
+ self.cell_buf.appendAssumeCapacity(.{
+ .x = @intCast(cell_offset.cluster),
+ .x_offset = @intFromFloat(@round(x_offset)),
+ .y_offset = @intFromFloat(@round(position.y)),
+ .glyph_index = glyph,
+ });
+
+ // Add our advances to keep track of our run offsets.
+ // Advances apply to the NEXT cell.
+ run_offset.x += advance.width;
+ run_offset.cluster = @max(run_offset.cluster, cluster);
+
+ // For debugging positions, turn this on:
+ //run_offset_y += advance.height;
+ }
+ }
+
+ // If our buffer contains some non-ltr sections we need to sort it :/
+ if (non_ltr) {
+ // This is EXCEPTIONALLY rare. Only happens for languages with
+ // complex shaping which we don't even really support properly
+ // right now, so are very unlikely to be used heavily by users
+ // of Ghostty.
+ @branchHint(.cold);
+ std.mem.sort(
+ font.shape.Cell,
+ self.cell_buf.items,
+ {},
+ struct {
+ fn lt(_: void, a: font.shape.Cell, b: font.shape.Cell) bool {
+ return a.x < b.x;
+ }
+ }.lt,
+ );
+ }
+
+ return self.cell_buf.items;
+ }
+
+ /// Get an attr dict for a font from a specific index.
+ /// These items are cached, do not retain or release them.
+ fn getFont(
+ self: *Shaper,
+ grid: *font.SharedGrid,
+ index: font.Collection.Index,
+ ) !*macos.foundation.Dictionary {
+ // If this grid doesn't match the one we've cached fonts for,
+ // then we reset the cache list since it's no longer valid.
+ // We use an intFromPtr rather than direct pointer comparison
+ // because we don't want anyone to inadvertently use the pointer.
+ const grid_id: usize = @intFromPtr(grid);
+ if (grid_id != self.cached_font_grid) {
+ if (self.cached_font_grid > 0) {
+ // Put all the currently cached fonts in to
+ // the release pool before clearing the list.
+ try self.cf_release_pool.ensureUnusedCapacity(
+ self.alloc,
+ self.cached_fonts.items.len,
+ );
+ for (self.cached_fonts.items) |ft| {
+ if (ft) |f| {
+ self.cf_release_pool.appendAssumeCapacity(f);
+ }
+ }
+ self.cached_fonts.clearRetainingCapacity();
+ }
+
+ self.cached_font_grid = grid_id;
+ }
+
+ const index_int = index.int();
+
+ // The cached fonts are indexed directly by the font index, since
+ // this number is usually low. Therefore, we set any index we haven't
+ // seen to null.
+ if (self.cached_fonts.items.len <= index_int) {
+ try self.cached_fonts.ensureTotalCapacity(self.alloc, index_int + 1);
+ while (self.cached_fonts.items.len <= index_int) {
+ self.cached_fonts.appendAssumeCapacity(null);
+ }
+ }
+
+ // If we have it, return the cached attr dict.
+ if (self.cached_fonts.items[index_int]) |cached| return cached;
+
+ // Font descriptor, font
+ try self.cf_release_pool.ensureUnusedCapacity(self.alloc, 2);
+
+ const run_font = font: {
+ // The CoreText shaper relies on CoreText and CoreText claims
+ // that CTFonts are threadsafe. See:
+ // https://developer.apple.com/documentation/coretext/
+ //
+ // Quote:
+ // All individual functions in Core Text are thread-safe. Font
+ // objects (CTFont, CTFontDescriptor, and associated objects) can
+ // be used simultaneously by multiple operations, work queues, or
+ // threads. However, the layout objects (CTTypesetter,
+ // CTFramesetter, CTRun, CTLine, CTFrame, and associated objects)
+ // should be used in a single operation, work queue, or thread.
+ //
+ // Because of this, we only acquire the read lock to grab the
+ // face and set it up, then release it.
+ grid.lock.lockShared();
+ defer grid.lock.unlockShared();
+
+ const face = try grid.resolver.collection.getFace(index);
+ const original = face.font;
+
+ const attrs = if (face.quirks_disable_default_font_features)
+ self.features_no_default
+ else
+ self.features;
+
+ const desc = try macos.text.FontDescriptor.createWithAttributes(attrs);
+ self.cf_release_pool.appendAssumeCapacity(desc);
+
+ const copied = try original.copyWithAttributes(0, null, desc);
+ errdefer copied.release();
+
+ break :font copied;
+ };
+ self.cf_release_pool.appendAssumeCapacity(run_font);
+
+ // Get our font and use that get the attributes to set for the
+ // attributed string so the whole string uses the same font.
+ const attr_dict = dict: {
+ break :dict try macos.foundation.Dictionary.create(
+ &.{macos.text.StringAttribute.font.key()},
+ &.{run_font},
+ );
+ };
+
+ self.cached_fonts.items[index_int] = attr_dict;
+ return attr_dict;
+ }
+
+ /// The hooks for RunIterator.
+ pub const RunIteratorHook = struct {
+ shaper: *Shaper,
+
+ pub fn prepare(self: *RunIteratorHook) void {
+ self.shaper.run_state.reset();
+ // log.warn("----------- run reset -------------", .{});
+ }
+
+ pub fn addCodepoint(self: RunIteratorHook, cp: u32, cluster: u32) !void {
+ const state = &self.shaper.run_state;
+
+ // Build our UTF-16 string for CoreText
+ try state.unichars.ensureUnusedCapacity(self.shaper.alloc, 2);
+
+ state.unichars.appendNTimesAssumeCapacity(0, 2);
+
+ const pair = macos.foundation.stringGetSurrogatePairForLongCharacter(
+ cp,
+ state.unichars.items[state.unichars.items.len - 2 ..][0..2],
+ );
+ if (!pair) {
+ state.unichars.items.len -= 1;
+ }
+
+ // Build our reverse lookup table for codepoints to clusters
+ try state.codepoints.append(self.shaper.alloc, .{
+ .codepoint = cp,
+ .cluster = cluster,
+ });
+ // log.warn("run cp={X}", .{cp});
+
+ // If the UTF-16 codepoint is a pair then we need to insert
+ // a dummy entry so that the CTRunGetStringIndices() function
+ // maps correctly.
+ if (pair) try state.codepoints.append(self.shaper.alloc, .{
+ .codepoint = 0,
+ .cluster = cluster,
+ });
+ }
+
+ pub fn finalize(self: RunIteratorHook) void {
+ _ = self;
+ }
+ };
+
+ fn debugPositions(
+ self: *Shaper,
+ alloc: Allocator,
+ run_offset: Offset,
+ run_offset_y: f64,
+ cell_offset: Offset,
+ cell_offset_y: f64,
+ position: macos.graphics.Point,
+ index: usize,
+ ) !void {
+ const state = &self.run_state;
+ const x_offset = position.x - cell_offset.x;
+ const advance_x_offset = run_offset.x - cell_offset.x;
+ const advance_y_offset = run_offset_y - cell_offset_y;
+ const x_offset_diff = x_offset - advance_x_offset;
+ const y_offset_diff = position.y - advance_y_offset;
+ const positions_differ = @abs(x_offset_diff) > 0.0001 or @abs(y_offset_diff) > 0.0001;
+ const old_offset_y = position.y - cell_offset_y;
+ const position_y_differs = @abs(cell_offset_y) > 0.0001;
+ const cluster = state.codepoints.items[index].cluster;
+ const cluster_differs = cluster != cell_offset.cluster;
+
+ // To debug every loop, flip this to true:
+ const extra_debugging = false;
+
+ const is_previous_codepoint_prepend = if (cluster_differs or
+ extra_debugging)
+ blk: {
+ var i = index;
+ while (i > 0) {
+ i -= 1;
+ const codepoint = state.codepoints.items[i];
+
+ // Skip surrogate pair padding
+ if (codepoint.codepoint == 0) continue;
+
+ break :blk unicode.table.get(@intCast(codepoint.codepoint)).grapheme_boundary_class == .prepend;
+ }
+ break :blk false;
+ } else false;
+
+ const formatted_cps = if (positions_differ or
+ position_y_differs or
+ cluster_differs or
+ extra_debugging)
+ blk: {
+ var allocating = std.Io.Writer.Allocating.init(alloc);
+ const writer = &allocating.writer;
+ const codepoints = state.codepoints.items;
+ var last_cluster: ?u32 = null;
+ for (codepoints, 0..) |cp, i| {
+ if ((@as(i32, @intCast(cp.cluster)) >= @as(i32, @intCast(cell_offset.cluster)) - 1 and
+ cp.cluster <= cluster + 1) and
+ cp.codepoint != 0 // Skip surrogate pair padding
+ ) {
+ if (last_cluster) |last| {
+ if (cp.cluster != last) {
+ try writer.writeAll(" ");
+ }
+ }
+ if (i == index) {
+ try writer.writeAll("▸");
+ }
+ // Using Python syntax for easier debugging
+ if (cp.codepoint > 0xFFFF) {
+ try writer.print("\\U{x:0>8}", .{cp.codepoint});
+ } else {
+ try writer.print("\\u{x:0>4}", .{cp.codepoint});
+ }
+ last_cluster = cp.cluster;
+ }
+ }
+ try writer.writeAll(" → ");
+ for (codepoints) |cp| {
+ if ((@as(i32, @intCast(cp.cluster)) >= @as(i32, @intCast(cell_offset.cluster)) - 1 and
+ cp.cluster <= cluster + 1) and
+ cp.codepoint != 0 // Skip surrogate pair padding
+ ) {
+ try writer.print("{u}", .{@as(u21, @intCast(cp.codepoint))});
+ }
+ }
+ break :blk try allocating.toOwnedSlice();
+ } else "";
+
+ if (extra_debugging) {
+ log.warn("extra debugging of positions index={d} cell_offset.cluster={d} cluster={d} run_offset.cluster={d} diff={d} pos=({d:.2},{d:.2}) run_offset=({d:.2},{d:.2}) cell_offset=({d:.2},{d:.2}) is_prev_prepend={} cps = {s}", .{
+ index,
+ cell_offset.cluster,
+ cluster,
+ run_offset.cluster,
+ @as(isize, @intCast(cluster)) - @as(isize, @intCast(cell_offset.cluster)),
+ x_offset,
+ position.y,
+ run_offset.x,
+ run_offset_y,
+ cell_offset.x,
+ cell_offset_y,
+ is_previous_codepoint_prepend,
+ formatted_cps,
+ });
+ }
+
+ if (positions_differ) {
+ log.warn("position differs from advance: cluster={d} pos=({d:.2},{d:.2}) adv=({d:.2},{d:.2}) diff=({d:.2},{d:.2}) cps = {s}", .{
+ cluster,
+ x_offset,
+ position.y,
+ advance_x_offset,
+ advance_y_offset,
+ x_offset_diff,
+ y_offset_diff,
+ formatted_cps,
+ });
+ }
+
+ if (position_y_differs) {
+ log.warn("position.y differs from old offset.y: cluster={d} pos=({d:.2},{d:.2}) run_offset=({d:.2},{d:.2}) cell_offset=({d:.2},{d:.2}) old offset.y={d:.2} cps = {s}", .{
+ cluster,
+ x_offset,
+ position.y,
+ run_offset.x,
+ run_offset_y,
+ cell_offset.x,
+ cell_offset_y,
+ old_offset_y,
+ formatted_cps,
+ });
+ }
+
+ if (cluster_differs) {
+ log.warn("cell_offset.cluster differs from cluster (potential ligature detected) cell_offset.cluster={d} cluster={d} run_offset.cluster={d} diff={d} pos=({d:.2},{d:.2}) run_offset=({d:.2},{d:.2}) cell_offset=({d:.2},{d:.2}) is_prev_prepend={} cps = {s}", .{
+ cell_offset.cluster,
+ cluster,
+ run_offset.cluster,
+ @as(isize, @intCast(cluster)) - @as(isize, @intCast(cell_offset.cluster)),
+ x_offset,
+ position.y,
+ run_offset.x,
+ run_offset_y,
+ cell_offset.x,
+ cell_offset_y,
+ is_previous_codepoint_prepend,
+ formatted_cps,
+ });
+ }
+ }
+};
+
+test "run iterator" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ {
+ var t: terminal.Terminal = try .init(alloc, .{
+ .cols = 5,
+ .rows = 3,
+ });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice("ABCD");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |_| count += 1;
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+
+ // Spaces should be part of a run
+ {
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 10, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice("ABCD EFG");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |_| count += 1;
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+
+ {
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 5, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice("A😃D");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |_| count += 1;
+ try testing.expectEqual(@as(usize, 3), count);
+ }
+
+ // Bad ligatures
+ for (&[_][]const u8{ "fl", "fi", "st" }) |bad| {
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 5, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice(bad);
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |_| count += 1;
+ try testing.expectEqual(@as(usize, 2), count);
+ }
+}
+
+test "run iterator: empty cells with background set" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ {
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 5, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ // Set red background
+ s.nextSlice("\x1b[48;2;255;0;0m");
+ s.nextSlice("A");
+
+ // Get our first row
+ {
+ const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = 1 } }).?;
+ const cell = list_cell.cell;
+ cell.* = .{
+ .content_tag = .bg_color_rgb,
+ .content = .{ .color_rgb = .{ .r = 0xFF, .g = 0, .b = 0 } },
+ };
+ }
+ {
+ const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = 2 } }).?;
+ const cell = list_cell.cell;
+ cell.* = .{
+ .content_tag = .bg_color_rgb,
+ .content = .{ .color_rgb = .{ .r = 0xFF, .g = 0, .b = 0 } },
+ };
+ }
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ {
+ const run = (try it.next(alloc)).?;
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 3), cells.len);
+ }
+ try testing.expect(try it.next(alloc) == null);
+ }
+}
+
+test "shape" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ var buf: [32]u8 = undefined;
+ var buf_idx: usize = 0;
+ buf_idx += try std.unicode.utf8Encode(0x1F44D, buf[buf_idx..]); // Thumbs up plain
+ buf_idx += try std.unicode.utf8Encode(0x1F44D, buf[buf_idx..]); // Thumbs up plain
+ buf_idx += try std.unicode.utf8Encode(0x1F3FD, buf[buf_idx..]); // Medium skin tone
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 10, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice(buf[0..buf_idx]);
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+}
+
+test "shape nerd fonts" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaperWithFont(alloc, .nerd_font);
+ defer testdata.deinit();
+
+ var buf: [32]u8 = undefined;
+ var buf_idx: usize = 0;
+ buf_idx += try std.unicode.utf8Encode(' ', buf[buf_idx..]); // space
+ buf_idx += try std.unicode.utf8Encode(0xF024B, buf[buf_idx..]); // nf-md-folder
+ buf_idx += try std.unicode.utf8Encode(' ', buf[buf_idx..]); // space
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 10, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice(buf[0..buf_idx]);
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+}
+
+test "shape inconsolata ligs" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ {
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 5, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice(">=");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+
+ try testing.expectEqual(@as(usize, 2), run.cells);
+
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 1), cells.len);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+
+ {
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 5, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice("===");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+
+ try testing.expectEqual(@as(usize, 3), run.cells);
+
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 1), cells.len);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+}
+
+test "shape monaspace ligs" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaperWithFont(alloc, .monaspace_neon);
+ defer testdata.deinit();
+
+ {
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 5, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice("===");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+
+ try testing.expectEqual(@as(usize, 3), run.cells);
+
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 1), cells.len);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+}
+
+// https://github.com/mitchellh/ghostty/issues/1708
+test "shape left-replaced lig in last run" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaperWithFont(alloc, .geist_mono);
+ defer testdata.deinit();
+
+ {
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 5, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice("!==");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+
+ try testing.expectEqual(@as(usize, 3), run.cells);
+
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 1), cells.len);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+}
+
+// https://github.com/mitchellh/ghostty/issues/1708
+test "shape left-replaced lig in early run" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaperWithFont(alloc, .geist_mono);
+ defer testdata.deinit();
+
+ {
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 5, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice("!==X");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+
+ const run = (try it.next(alloc)).?;
+
+ try testing.expectEqual(@as(usize, 4), run.cells);
+
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 2), cells.len);
+ }
+}
+
+// https://github.com/mitchellh/ghostty/issues/1664
+test "shape U+3C9 with JB Mono" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaperWithFont(alloc, .jetbrains_mono);
+ defer testdata.deinit();
+
+ {
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 10, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice("\u{03C9} foo");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+
+ var run_count: usize = 0;
+ var cell_count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ run_count += 1;
+ const cells = try shaper.shape(run);
+ cell_count += cells.len;
+ }
+ try testing.expectEqual(@as(usize, 1), run_count);
+ try testing.expectEqual(@as(usize, 5), cell_count);
+ }
+}
+
+test "shape emoji width" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ {
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 5, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice("👍");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 1), cells.len);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+}
+
+test "shape emoji width long" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ // Make a screen and add a long emoji sequence to it.
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 30, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var page = t.screens.active.pages.pages.first.?.data;
+ var row = page.getRow(1);
+ const cell = &row.cells.ptr(page.memory)[0];
+ cell.* = .{
+ .content_tag = .codepoint,
+ .content = .{ .codepoint = 0x1F9D4 }, // Person with beard
+ };
+ var graphemes = [_]u21{
+ 0x1F3FB, // Light skin tone (Fitz 1-2)
+ 0x200D, // ZWJ
+ 0x2642, // Male sign
+ 0xFE0F, // Emoji presentation selector
+ };
+ try page.setGraphemes(
+ row,
+ cell,
+ graphemes[0..],
+ );
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(1).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ const cells = try shaper.shape(run);
+
+ try testing.expectEqual(@as(usize, 1), cells.len);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+}
+
+test "shape variation selector VS15" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ var buf: [32]u8 = undefined;
+ var buf_idx: usize = 0;
+ buf_idx += try std.unicode.utf8Encode(0x270C, buf[buf_idx..]); // Victory sign (default text)
+ buf_idx += try std.unicode.utf8Encode(0xFE0E, buf[buf_idx..]); // ZWJ to force text
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 10, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice(buf[0..buf_idx]);
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 1), cells.len);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+}
+
+test "shape variation selector VS16" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ var buf: [32]u8 = undefined;
+ var buf_idx: usize = 0;
+ buf_idx += try std.unicode.utf8Encode(0x270C, buf[buf_idx..]); // Victory sign (default text)
+ buf_idx += try std.unicode.utf8Encode(0xFE0F, buf[buf_idx..]); // ZWJ to force color
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 10, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice(buf[0..buf_idx]);
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 1), cells.len);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+}
+
+test "shape with empty cells in between" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 30, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice("A");
+ s.nextSlice("\x1b[5C"); // 5 spaces forward
+ s.nextSlice("B");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 1), count);
+ try testing.expectEqual(@as(usize, 7), cells.len);
+ }
+}
+
+test "shape Combining characters" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ var buf: [32]u8 = undefined;
+ var buf_idx: usize = 0;
+ buf_idx += try std.unicode.utf8Encode('n', buf[buf_idx..]); // Combining
+ buf_idx += try std.unicode.utf8Encode(0x0308, buf[buf_idx..]); // Combining
+ buf_idx += try std.unicode.utf8Encode(0x0308, buf[buf_idx..]);
+ buf_idx += try std.unicode.utf8Encode('a', buf[buf_idx..]);
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 30, .rows = 3 });
+ defer t.deinit(alloc);
+
+ // Enable grapheme clustering
+ t.modes.set(.grapheme_cluster, true);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice(buf[0..buf_idx]);
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 4), cells.len);
+ try testing.expectEqual(@as(u16, 0), cells[0].x);
+ try testing.expectEqual(@as(u16, 0), cells[1].x);
+ try testing.expectEqual(@as(u16, 0), cells[2].x);
+ try testing.expectEqual(@as(u16, 1), cells[3].x);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+}
+
+// This test exists because the string it uses causes CoreText to output a
+// non-monotonic run, which we need to handle by sorting the resulting buffer.
+test "shape Devanagari string" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ // We need a font that supports devanagari for this to work, if we can't
+ // find Arial Unicode MS, which is a system font on macOS, we just skip
+ // the test.
+ var testdata = testShaperWithDiscoveredFont(
+ alloc,
+ "Arial Unicode MS",
+ ) catch return error.SkipZigTest;
+ defer testdata.deinit();
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 30, .rows = 3 });
+ defer t.deinit(alloc);
+
+ // Disable grapheme clustering
+ t.modes.set(.grapheme_cluster, false);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice("अपार्टमेंट");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+
+ const run = try it.next(alloc);
+ try testing.expect(run != null);
+ const cells = try shaper.shape(run.?);
+
+ // To understand the `x`/`cluster` assertions here, run with the "For
+ // debugging positions" code turned on and `extra_debugging` set to true.
+ try testing.expectEqual(@as(usize, 8), cells.len);
+ try testing.expectEqual(@as(u16, 0), cells[0].x);
+ try testing.expectEqual(@as(u16, 1), cells[1].x);
+ try testing.expectEqual(@as(u16, 2), cells[2].x);
+ try testing.expectEqual(@as(u16, 4), cells[3].x);
+ try testing.expectEqual(@as(u16, 4), cells[4].x);
+ try testing.expectEqual(@as(u16, 5), cells[5].x);
+ try testing.expectEqual(@as(u16, 5), cells[6].x);
+ try testing.expectEqual(@as(u16, 6), cells[7].x);
+
+ try testing.expect(try it.next(alloc) == null);
+}
+
+test "shape Tai Tham vowels (position differs from advance)" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ // We need a font that supports Tai Tham for this to work, if we can't find
+ // Noto Sans Tai Tham, which is a system font on macOS, we just skip the
+ // test.
+ var testdata = testShaperWithDiscoveredFont(
+ alloc,
+ "Noto Sans Tai Tham",
+ ) catch return error.SkipZigTest;
+ defer testdata.deinit();
+
+ var buf: [32]u8 = undefined;
+ var buf_idx: usize = 0;
+ buf_idx += try std.unicode.utf8Encode(0x1a2F, buf[buf_idx..]); // ᨯ
+ buf_idx += try std.unicode.utf8Encode(0x1a70, buf[buf_idx..]); // ᩰ
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 30, .rows = 3 });
+ defer t.deinit(alloc);
+
+ // Enable grapheme clustering
+ t.modes.set(.grapheme_cluster, true);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice(buf[0..buf_idx]);
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+
+ const cells = try shaper.shape(run);
+ const cell_width = run.grid.metrics.cell_width;
+ try testing.expectEqual(@as(usize, 2), cells.len);
+ try testing.expectEqual(@as(u16, 0), cells[0].x);
+ try testing.expectEqual(@as(u16, 0), cells[1].x);
+
+ // The first glyph renders in the next cell
+ try testing.expectEqual(@as(i16, @intCast(cell_width)), cells[0].x_offset);
+ try testing.expectEqual(@as(i16, 0), cells[1].x_offset);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+}
+
+test "shape Tai Tham letters (position.y differs from advance)" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ // We need a font that supports Tai Tham for this to work, if we can't find
+ // Noto Sans Tai Tham, which is a system font on macOS, we just skip the
+ // test.
+ var testdata = testShaperWithDiscoveredFont(
+ alloc,
+ "Noto Sans Tai Tham",
+ ) catch return error.SkipZigTest;
+ defer testdata.deinit();
+
+ var buf: [32]u8 = undefined;
+ var buf_idx: usize = 0;
+
+ // First grapheme cluster:
+ buf_idx += try std.unicode.utf8Encode(0x1a49, buf[buf_idx..]); // HA
+ buf_idx += try std.unicode.utf8Encode(0x1a60, buf[buf_idx..]); // SAKOT
+ // Second grapheme cluster, combining with the first in a ligature:
+ buf_idx += try std.unicode.utf8Encode(0x1a3f, buf[buf_idx..]); // YA
+ buf_idx += try std.unicode.utf8Encode(0x1a69, buf[buf_idx..]); // U
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 30, .rows = 3 });
+ defer t.deinit(alloc);
+
+ // Enable grapheme clustering
+ t.modes.set(.grapheme_cluster, true);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice(buf[0..buf_idx]);
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 3), cells.len);
+ try testing.expectEqual(@as(u16, 0), cells[0].x);
+ try testing.expectEqual(@as(u16, 0), cells[1].x);
+ try testing.expectEqual(@as(u16, 0), cells[2].x); // U from second grapheme
+
+ // The U glyph renders at a y below zero
+ try testing.expectEqual(@as(i16, -3), cells[2].y_offset);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+}
+
+test "shape Javanese ligatures" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ // We need a font that supports Javanese for this to work, if we can't find
+ // Noto Sans Javanese Regular, which is a system font on macOS, we just
+ // skip the test.
+ var testdata = testShaperWithDiscoveredFont(
+ alloc,
+ "Noto Sans Javanese",
+ ) catch return error.SkipZigTest;
+ defer testdata.deinit();
+
+ var buf: [32]u8 = undefined;
+ var buf_idx: usize = 0;
+
+ // First grapheme cluster:
+ buf_idx += try std.unicode.utf8Encode(0xa9a4, buf[buf_idx..]); // NA
+ buf_idx += try std.unicode.utf8Encode(0xa9c0, buf[buf_idx..]); // PANGKON
+ // Second grapheme cluster, combining with the first in a ligature:
+ buf_idx += try std.unicode.utf8Encode(0xa9b2, buf[buf_idx..]); // HA
+ buf_idx += try std.unicode.utf8Encode(0xa9b8, buf[buf_idx..]); // Vowel sign SUKU
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 30, .rows = 3 });
+ defer t.deinit(alloc);
+
+ // Enable grapheme clustering
+ t.modes.set(.grapheme_cluster, true);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice(buf[0..buf_idx]);
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+
+ const cells = try shaper.shape(run);
+ const cell_width = run.grid.metrics.cell_width;
+ try testing.expectEqual(@as(usize, 3), cells.len);
+ try testing.expectEqual(@as(u16, 0), cells[0].x);
+ try testing.expectEqual(@as(u16, 0), cells[1].x);
+ try testing.expectEqual(@as(u16, 0), cells[2].x);
+
+ // The vowel sign SUKU renders with correct x_offset
+ try testing.expect(cells[2].x_offset > 3 * cell_width);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+}
+
+test "shape Chakma vowel sign with ligature (vowel sign renders first)" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ // We need a font that supports Chakma for this to work, if we can't find
+ // Noto Sans Chakma Regular, which is a system font on macOS, we just skip
+ // the test.
+ var testdata = testShaperWithDiscoveredFont(
+ alloc,
+ "Noto Sans Chakma",
+ ) catch return error.SkipZigTest;
+ defer testdata.deinit();
+
+ var buf: [32]u8 = undefined;
+ var buf_idx: usize = 0;
+
+ // First grapheme cluster:
+ buf_idx += try std.unicode.utf8Encode(0x1111d, buf[buf_idx..]); // BAA
+ // Second grapheme cluster:
+ buf_idx += try std.unicode.utf8Encode(0x11116, buf[buf_idx..]); // TAA
+ buf_idx += try std.unicode.utf8Encode(0x11133, buf[buf_idx..]); // Virama
+ // Third grapheme cluster, combining with the second in a ligature:
+ buf_idx += try std.unicode.utf8Encode(0x11120, buf[buf_idx..]); // YYAA
+ buf_idx += try std.unicode.utf8Encode(0x1112c, buf[buf_idx..]); // Vowel Sign U
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 30, .rows = 3 });
+ defer t.deinit(alloc);
+
+ // Enable grapheme clustering
+ t.modes.set(.grapheme_cluster, true);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice(buf[0..buf_idx]);
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 4), cells.len);
+ try testing.expectEqual(@as(u16, 0), cells[0].x);
+ // See the giant "We need to reset the `cell_offset`" comment, but here
+ // we should technically have the rest of these be `x` of 1, but that
+ // would require going back in the stream to adjust past cells, and
+ // we don't take on that complexity.
+ try testing.expectEqual(@as(u16, 0), cells[1].x);
+ try testing.expectEqual(@as(u16, 0), cells[2].x);
+ try testing.expectEqual(@as(u16, 0), cells[3].x);
+
+ // The vowel sign U renders before the TAA:
+ try testing.expect(cells[1].x_offset < cells[2].x_offset);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+}
+
+test "shape Bengali ligatures with out of order vowels" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ // We need a font that supports Bengali for this to work, if we can't find
+ // Arial Unicode MS, which is a system font on macOS, we just skip the
+ // test.
+ var testdata = testShaperWithDiscoveredFont(
+ alloc,
+ "Arial Unicode MS",
+ ) catch return error.SkipZigTest;
+ defer testdata.deinit();
+
+ var buf: [32]u8 = undefined;
+ var buf_idx: usize = 0;
+
+ // First grapheme cluster:
+ buf_idx += try std.unicode.utf8Encode(0x09b0, buf[buf_idx..]); // RA
+ buf_idx += try std.unicode.utf8Encode(0x09be, buf[buf_idx..]); // Vowel sign AA
+ // Second grapheme cluster:
+ buf_idx += try std.unicode.utf8Encode(0x09b7, buf[buf_idx..]); // SSA
+ buf_idx += try std.unicode.utf8Encode(0x09cd, buf[buf_idx..]); // Virama
+ // Third grapheme cluster, combining with the second in a ligature:
+ buf_idx += try std.unicode.utf8Encode(0x099f, buf[buf_idx..]); // TTA
+ buf_idx += try std.unicode.utf8Encode(0x09cd, buf[buf_idx..]); // Virama
+ // Fourth grapheme cluster, combining with the previous two in a ligature:
+ buf_idx += try std.unicode.utf8Encode(0x09b0, buf[buf_idx..]); // RA
+ buf_idx += try std.unicode.utf8Encode(0x09c7, buf[buf_idx..]); // Vowel sign E
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 30, .rows = 3 });
+ defer t.deinit(alloc);
+
+ // Enable grapheme clustering
+ t.modes.set(.grapheme_cluster, true);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice(buf[0..buf_idx]);
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 8), cells.len);
+ try testing.expectEqual(@as(u16, 0), cells[0].x);
+ try testing.expectEqual(@as(u16, 0), cells[1].x);
+ // See the giant "We need to reset the `cell_offset`" comment, but here
+ // we should technically have the rest of these be `x` of 2, but that
+ // would require going back in the stream to adjust past cells, and
+ // we don't take on that complexity.
+ try testing.expectEqual(@as(u16, 0), cells[2].x);
+ try testing.expectEqual(@as(u16, 0), cells[3].x);
+ try testing.expectEqual(@as(u16, 0), cells[4].x);
+ try testing.expectEqual(@as(u16, 0), cells[5].x);
+ try testing.expectEqual(@as(u16, 0), cells[6].x);
+ try testing.expectEqual(@as(u16, 0), cells[7].x);
+
+ // The vowel sign E renders before the SSA:
+ try testing.expect(cells[2].x_offset < cells[3].x_offset);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+}
+
+test "shape box glyphs" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ var buf: [32]u8 = undefined;
+ var buf_idx: usize = 0;
+ buf_idx += try std.unicode.utf8Encode(0x2500, buf[buf_idx..]); // horiz line
+ buf_idx += try std.unicode.utf8Encode(0x2501, buf[buf_idx..]); //
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 10, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice(buf[0..buf_idx]);
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 2), cells.len);
+ try testing.expectEqual(@as(u32, 0x2500), cells[0].glyph_index);
+ try testing.expectEqual(@as(u16, 0), cells[0].x);
+ try testing.expectEqual(@as(u32, 0x2501), cells[1].glyph_index);
+ try testing.expectEqual(@as(u16, 1), cells[1].x);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+}
+
+test "shape selection boundary" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 10, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice("a1b2c3d4e5");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Full line selection
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ .selection = .{ 0, @intCast(t.cols - 1) },
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+
+ // Offset x, goes to end of line selection
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ .selection = .{ 2, @intCast(t.cols - 1) },
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 2), count);
+ }
+
+ // Offset x, starts at beginning of line
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ .selection = .{ 0, 3 },
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 2), count);
+ }
+
+ // Selection only subset of line
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ .selection = .{ 1, 3 },
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 3), count);
+ }
+
+ // Selection only one character
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ .selection = .{ 1, 1 },
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 3), count);
+ }
+}
+
+test "shape cursor boundary" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 10, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice("a1b2c3d4e5");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // No cursor is full line
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+
+ {
+ // Cursor at index 0 is two runs
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ .cursor_x = 0,
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 2), count);
+ }
+ // And without cursor splitting remains one
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+ }
+
+ {
+ // Cursor at index 1 is three runs
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ .cursor_x = 1,
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 3), count);
+ }
+ // And without cursor splitting remains one
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+ }
+ {
+ // Cursor at last col is two runs
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ .cursor_x = 9,
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 2), count);
+ }
+ // And without cursor splitting remains one
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+ }
+}
+
+test "shape cursor boundary and colored emoji" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 3, .rows = 10 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice("👍🏼");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // No cursor is full line
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+
+ // Cursor on emoji does not split it
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ .cursor_x = 0,
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ .cursor_x = 1,
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+}
+
+test "shape cell attribute change" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ // Plain >= should shape into 1 run
+ {
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 10, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice(">=");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+
+ // Bold vs regular should split
+ {
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 3, .rows = 10 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice(">");
+ s.nextSlice("\x1b[1m"); // Bold
+ s.nextSlice("=");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 2), count);
+ }
+
+ // Changing fg color should split
+ {
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 3, .rows = 10 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ // RGB 1, 2, 3
+ s.nextSlice("\x1b[38;2;1;2;3m");
+ s.nextSlice(">");
+ // RGB 3, 2, 1
+ s.nextSlice("\x1b[38;2;3;2;1m");
+ s.nextSlice("=");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 2), count);
+ }
+
+ // Changing bg color should NOT split
+ {
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 3, .rows = 10 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ // RGB 1, 2, 3 bg
+ s.nextSlice("\x1b[48;2;1;2;3m");
+ s.nextSlice(">");
+ // RGB 3, 2, 1 bg
+ s.nextSlice("\x1b[48;2;3;2;1m");
+ s.nextSlice("=");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+
+ // Same bg color should not split
+ {
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 3, .rows = 10 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ // RGB 1, 2, 3 bg
+ s.nextSlice("\x1b[48;2;1;2;3m");
+ s.nextSlice(">");
+ s.nextSlice("=");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+}
+
+test "shape high plane sprite font codepoint" {
+ // While creating runs, the CoreText shaper uses `0` codepoints to
+ // pad its codepoint list to account for high plane characters which
+ // use two UTF-16 code units. This is so that, after shaping, the string
+ // indices can be used to find the originating codepoint / cluster.
+ //
+ // This is a problem for special (sprite) fonts, which need to be "shaped"
+ // by simply returning the input codepoints verbatim. We include logic to
+ // skip `0` codepoints when constructing the shaped run for sprite fonts,
+ // this test verifies that it works correctly.
+
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 10, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ // U+1FB70: Vertical One Eighth Block-2
+ s.nextSlice("\u{1FB70}");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ // We should get one run
+ const run = (try it.next(alloc)).?;
+ // The run state should have the UTF-16 encoding of the character.
+ try testing.expectEqualSlices(
+ u16,
+ &.{ 0xD83E, 0xDF70 },
+ shaper.run_state.unichars.items,
+ );
+ // The codepoint list should be padded.
+ try testing.expectEqualSlices(
+ Shaper.Codepoint,
+ &.{
+ .{ .codepoint = 0x1FB70, .cluster = 0 },
+ .{ .codepoint = 0, .cluster = 0 },
+ },
+ shaper.run_state.codepoints.items,
+ );
+ // And when shape it
+ const cells = try shaper.shape(run);
+ // we should have
+ // - 1 cell
+ try testing.expectEqual(1, run.cells);
+ // - at position 0
+ try testing.expectEqual(0, run.offset);
+ // - with 1 glyph in it
+ try testing.expectEqual(1, cells.len);
+ // - at position 0
+ try testing.expectEqual(0, cells[0].x);
+ // - the glyph index should be equal to the codepoint
+ try testing.expectEqual(0x1FB70, cells[0].glyph_index);
+ // - it should be a sprite font
+ try testing.expect(run.font_index.special() != null);
+ // And we should get a null run after that
+ try testing.expectEqual(null, try it.next(alloc));
+}
+
+const TestShaper = struct {
+ alloc: Allocator,
+ shaper: Shaper,
+ grid: *SharedGrid,
+ lib: Library,
+
+ pub fn deinit(self: *TestShaper) void {
+ self.shaper.deinit();
+ self.grid.deinit(self.alloc);
+ self.alloc.destroy(self.grid);
+ self.lib.deinit();
+ }
+};
+
+const TestFont = enum {
+ code_new_roman,
+ geist_mono,
+ inconsolata,
+ jetbrains_mono,
+ monaspace_neon,
+ nerd_font,
+};
+
+/// Helper to return a fully initialized shaper.
+fn testShaper(alloc: Allocator) !TestShaper {
+ return try testShaperWithFont(alloc, .inconsolata);
+}
+
+fn testShaperWithFont(alloc: Allocator, font_req: TestFont) !TestShaper {
+ const testEmoji = font.embedded.emoji;
+ const testEmojiText = font.embedded.emoji_text;
+ const testFont = switch (font_req) {
+ .code_new_roman => font.embedded.code_new_roman,
+ .inconsolata => font.embedded.inconsolata,
+ .geist_mono => font.embedded.geist_mono,
+ .jetbrains_mono => font.embedded.jetbrains_mono,
+ .monaspace_neon => font.embedded.monaspace_neon,
+ .nerd_font => font.embedded.test_nerd_font,
+ };
+
+ var lib = try Library.init(alloc);
+ errdefer lib.deinit();
+
+ var c = Collection.init();
+ c.load_options = .{ .library = lib };
+
+ // Setup group
+ _ = try c.add(alloc, try .init(
+ lib,
+ testFont,
+ .{ .size = .{ .points = 12 } },
+ ), .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .none,
+ });
+
+ if (font.options.backend != .coretext) {
+ // Coretext doesn't support Noto's format
+ _ = try c.add(alloc, try .init(
+ lib,
+ testEmoji,
+ .{ .size = .{ .points = 12 } },
+ ), .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .none,
+ });
+ } else {
+ // On CoreText we want to load Apple Emoji, we should have it.
+ var disco = font.Discover.init();
+ defer disco.deinit();
+ var disco_it = try disco.discover(alloc, .{
+ .family = "Apple Color Emoji",
+ .size = 12,
+ .monospace = false,
+ });
+ defer disco_it.deinit();
+ var face = (try disco_it.next()).?;
+ errdefer face.deinit();
+ _ = try c.addDeferred(alloc, face, .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .none,
+ });
+ }
+ _ = try c.add(alloc, try .init(
+ lib,
+ testEmojiText,
+ .{ .size = .{ .points = 12 } },
+ ), .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .none,
+ });
+
+ const grid_ptr = try alloc.create(SharedGrid);
+ errdefer alloc.destroy(grid_ptr);
+ grid_ptr.* = try .init(alloc, .{ .collection = c });
+ errdefer grid_ptr.*.deinit(alloc);
+
+ var shaper = try Shaper.init(alloc, .{
+ // Some of our tests rely on dlig being enabled by default
+ .features = &.{"dlig"},
+ });
+ errdefer shaper.deinit();
+
+ return TestShaper{
+ .alloc = alloc,
+ .shaper = shaper,
+ .grid = grid_ptr,
+ .lib = lib,
+ };
+}
+
+/// Return a fully initialized shaper by discovering a named font on the system.
+fn testShaperWithDiscoveredFont(alloc: Allocator, font_req: [:0]const u8) !TestShaper {
+ var lib = try Library.init(alloc);
+ errdefer lib.deinit();
+
+ var c = Collection.init();
+ c.load_options = .{ .library = lib };
+
+ // Discover and add our font to the collection.
+ {
+ var disco = font.Discover.init();
+ defer disco.deinit();
+ var disco_it = try disco.discover(alloc, .{
+ .family = font_req,
+ .size = 12,
+ .monospace = false,
+ });
+ defer disco_it.deinit();
+ var face: font.DeferredFace = (try disco_it.next()) orelse return error.FontNotFound;
+ errdefer face.deinit();
+ _ = try c.add(
+ alloc,
+ try face.load(lib, .{ .size = .{ .points = 12 } }),
+ .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .none,
+ },
+ );
+ }
+
+ const grid_ptr = try alloc.create(SharedGrid);
+ errdefer alloc.destroy(grid_ptr);
+ grid_ptr.* = try .init(alloc, .{ .collection = c });
+ errdefer grid_ptr.*.deinit(alloc);
+
+ var shaper = try Shaper.init(alloc, .{});
+ errdefer shaper.deinit();
+
+ return TestShaper{
+ .alloc = alloc,
+ .shaper = shaper,
+ .grid = grid_ptr,
+ .lib = lib,
+ };
+}
diff --git a/vendor/libghostty-vt/src/font/shaper/feature.zig b/vendor/libghostty-vt/src/font/shaper/feature.zig
new file mode 100644
index 00000000..5bd73f97
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/shaper/feature.zig
@@ -0,0 +1,354 @@
+const std = @import("std");
+const assert = @import("../../quirks.zig").inlineAssert;
+const Allocator = std.mem.Allocator;
+
+const log = std.log.scoped(.font_shaper);
+
+/// Represents an OpenType font feature setting, which consists of a tag and
+/// a numeric parameter >= 0. Most features are boolean, so only parameters
+/// of 0 and 1 make sense for them, but some (e.g. 'cv01'..'cv99') can take
+/// parameters to choose between multiple variants of a given character or
+/// characters.
+///
+/// Ref:
+/// - https://learn.microsoft.com/en-us/typography/opentype/spec/chapter2#features-and-lookups
+/// - https://harfbuzz.github.io/shaping-opentype-features.html
+pub const Feature = struct {
+ tag: [4]u8,
+ value: u32,
+
+ pub fn fromString(str: []const u8) ?Feature {
+ var fbs = std.io.fixedBufferStream(str);
+ const reader = fbs.reader();
+ return .fromReader(reader);
+ }
+
+ /// Parse a single font feature setting from a std.io.Reader, with a version
+ /// of the syntax of HarfBuzz's font feature strings. Stops at end of stream
+ /// or when a ',' is encountered.
+ ///
+ /// This parsing aims to be as error-tolerant as possible while avoiding any
+ /// assumptions in ambiguous scenarios. When invalid syntax is encountered,
+ /// the reader is advanced to the next boundary (end-of-stream or ',') so
+ /// that further features may be read.
+ ///
+ /// Ref: https://harfbuzz.github.io/harfbuzz-hb-common.html#hb-feature-from-string
+ pub fn fromReader(reader: anytype) ?Feature {
+ var tag_buf: [4]u8 = undefined;
+ var tag: []u8 = tag_buf[0..0];
+ var value: ?u32 = null;
+
+ state: switch ((enum {
+ /// Initial state.
+ start,
+ /// Parsing the tag.
+ tag,
+ /// In the space between the tag and the value.
+ space,
+ /// Parsing an integer parameter directly in to `value`.
+ int,
+ /// Parsing a boolean keyword parameter ("on"/"off").
+ bool,
+ /// Encountered an unrecoverable syntax error, advancing to boundary.
+ err,
+ /// Done parsing feature, skip whitespace until end.
+ done,
+ }).start) {
+ // If we're done then we skip whitespace until we see a ','.
+ .done => while (true) switch (reader.readByte() catch ',') {
+ ' ', '\t' => continue,
+ ',' => break,
+ // If we see something other than whitespace or a ','
+ // then this is an error since the intent is unclear.
+ else => continue :state .err,
+ },
+
+ // If we're fast-forwarding from an error we just wanna
+ // stop at the first boundary and ignore all other bytes.
+ .err => {
+ reader.skipUntilDelimiterOrEof(',') catch {};
+ return null;
+ },
+
+ .start => while (true) switch (reader.readByte() catch ',') {
+ // Ignore leading whitespace.
+ ' ', '\t' => continue,
+ // Empty feature string.
+ ',' => return null,
+ // '+' prefix to explicitly enable feature.
+ '+' => {
+ value = 1;
+ continue :state .tag;
+ },
+ // '-' prefix to explicitly disable feature.
+ '-' => {
+ value = 0;
+ continue :state .tag;
+ },
+ // Quote mark introducing a tag.
+ '"', '\'' => {
+ continue :state .tag;
+ },
+ // First letter of tag.
+ else => |byte| {
+ tag.len = 1;
+ tag[0] = byte;
+ continue :state .tag;
+ },
+ },
+
+ .tag => while (true) switch (reader.readByte() catch ',') {
+ // If the tag is interrupted by a comma it's invalid.
+ ',' => return null,
+ // Ignore quote marks. This does technically ignore cases like
+ // "'k'e'r'n' = 0", but it's unambiguous so if someone really
+ // wants to do that in their config then... sure why not.
+ '"', '\'' => continue,
+ // In all other cases we add the byte to our tag.
+ else => |byte| {
+ tag.len += 1;
+ tag[tag.len - 1] = byte;
+ if (tag.len == 4) continue :state .space;
+ },
+ },
+
+ .space => while (true) switch (reader.readByte() catch ',') {
+ ' ', '\t' => continue,
+ // Ignore quote marks since we might have a
+ // closing quote from the tag still ahead.
+ '"', '\'' => continue,
+ // Allow an '=' (which we can safely ignore)
+ // only if we don't already have a value due
+ // to a '+' or '-' prefix.
+ '=' => if (value != null) continue :state .err,
+ ',' => {
+ // Specifying only a tag turns a feature on.
+ if (value == null) value = 1;
+ break;
+ },
+ '0'...'9' => |byte| {
+ // If we already have value because of a
+ // '+' or '-' prefix then this is an error.
+ if (value != null) continue :state .err;
+ value = byte - '0';
+ continue :state .int;
+ },
+ 'o', 'O' => {
+ // If we already have value because of a
+ // '+' or '-' prefix then this is an error.
+ if (value != null) continue :state .err;
+ continue :state .bool;
+ },
+ else => continue :state .err,
+ },
+
+ .int => while (true) switch (reader.readByte() catch ',') {
+ ',' => break,
+ '0'...'9' => |byte| {
+ // If our value gets too big while
+ // parsing we consider it an error.
+ value = std.math.mul(u32, value.?, 10) catch {
+ continue :state .err;
+ };
+ value.? += byte - '0';
+ },
+ else => continue :state .err,
+ },
+
+ .bool => while (true) switch (reader.readByte() catch ',') {
+ ',' => return null,
+ 'n', 'N' => {
+ // "ofn"
+ if (value != null) {
+ assert(value == 0);
+ continue :state .err;
+ }
+ value = 1;
+ continue :state .done;
+ },
+ 'f', 'F' => {
+ // To make sure we consume two 'f's.
+ if (value == null) {
+ value = 0;
+ } else {
+ assert(value == 0);
+ continue :state .done;
+ }
+ },
+ else => continue :state .err,
+ },
+ }
+
+ assert(value != null);
+ assert(tag.len == 4);
+
+ return .{
+ .tag = tag_buf,
+ .value = value.?,
+ };
+ }
+
+ /// Serialize this feature to the provided buffer.
+ /// The string that this produces should be valid to parse.
+ pub fn toString(self: *const Feature, buf: []u8) !void {
+ var fbs = std.io.fixedBufferStream(buf);
+ try self.format("", .{}, fbs.writer());
+ }
+
+ /// Formatter for logging
+ pub fn format(
+ self: Feature,
+ comptime layout: []const u8,
+ opts: std.fmt.FormatOptions,
+ writer: *std.Io.Writer,
+ ) !void {
+ _ = layout;
+ _ = opts;
+ if (self.value <= 1) {
+ // Format boolean options as "+tag" for on and "-tag" for off.
+ try std.fmt.format(writer, "{c}{s}", .{
+ "-+"[self.value],
+ self.tag,
+ });
+ } else {
+ // Format non-boolean tags as "tag=value".
+ try std.fmt.format(writer, "{s}={d}", .{
+ self.tag,
+ self.value,
+ });
+ }
+ }
+};
+
+/// A list of font feature settings (see `Feature` for more documentation).
+pub const FeatureList = struct {
+ features: std.ArrayListUnmanaged(Feature) = .{},
+
+ pub fn deinit(self: *FeatureList, alloc: Allocator) void {
+ self.features.deinit(alloc);
+ }
+
+ /// Parse a comma separated list of features.
+ /// See `Feature.fromReader` for more docs.
+ pub fn fromString(alloc: Allocator, str: []const u8) !FeatureList {
+ var self: FeatureList = .{};
+ try self.appendFromString(alloc, str);
+ return self;
+ }
+
+ /// Append features to this list from a string with a comma separated list.
+ /// See `Feature.fromReader` for more docs.
+ pub fn appendFromString(
+ self: *FeatureList,
+ alloc: Allocator,
+ str: []const u8,
+ ) !void {
+ var fbs = std.io.fixedBufferStream(str);
+ const reader = fbs.reader();
+ while (fbs.pos < fbs.buffer.len) {
+ const i = fbs.pos;
+ if (Feature.fromReader(reader)) |feature| {
+ try self.features.append(alloc, feature);
+ } else log.warn(
+ "failed to parse font feature setting: \"{s}\"",
+ .{fbs.buffer[i..fbs.pos]},
+ );
+ }
+ }
+
+ /// Formatter for logging
+ pub fn format(
+ self: FeatureList,
+ comptime layout: []const u8,
+ opts: std.fmt.FormatOptions,
+ writer: *std.Io.Writer,
+ ) !void {
+ for (self.features.items, 0..) |feature, i| {
+ try feature.format(layout, opts, writer);
+ if (i != std.features.items.len - 1) try writer.writeAll(", ");
+ }
+ if (self.value <= 1) {
+ // Format boolean options as "+tag" for on and "-tag" for off.
+ try std.fmt.format(writer, "{c}{s}", .{
+ "-+"[self.value],
+ self.tag,
+ });
+ } else {
+ // Format non-boolean tags as "tag=value".
+ try std.fmt.format(writer, "{s}={d}", .{
+ self.tag,
+ self.value,
+ });
+ }
+ }
+};
+
+/// These features are hardcoded to always be on by default. Users
+/// can turn them off by setting the features to "-liga" for example.
+pub const default_features = [_]Feature{
+ .{ .tag = "liga".*, .value = 1 },
+};
+
+test "Feature.fromString" {
+ const testing = std.testing;
+
+ // This is not *complete* coverage of every possible
+ // combination of syntax, but it covers quite a few.
+
+ // Boolean settings (on)
+ const kern_on = Feature{ .tag = "kern".*, .value = 1 };
+ try testing.expectEqual(kern_on, Feature.fromString("kern"));
+ try testing.expectEqual(kern_on, Feature.fromString("kern, "));
+ try testing.expectEqual(kern_on, Feature.fromString("kern on"));
+ try testing.expectEqual(kern_on, Feature.fromString("kern on, "));
+ try testing.expectEqual(kern_on, Feature.fromString("+kern"));
+ try testing.expectEqual(kern_on, Feature.fromString("+kern, "));
+ try testing.expectEqual(kern_on, Feature.fromString("\"kern\" = 1"));
+ try testing.expectEqual(kern_on, Feature.fromString("\"kern\" = 1, "));
+
+ // Boolean settings (off)
+ const kern_off = Feature{ .tag = "kern".*, .value = 0 };
+ try testing.expectEqual(kern_off, Feature.fromString("kern off"));
+ try testing.expectEqual(kern_off, Feature.fromString("kern off, "));
+ try testing.expectEqual(kern_off, Feature.fromString("-'kern'"));
+ try testing.expectEqual(kern_off, Feature.fromString("-'kern', "));
+ try testing.expectEqual(kern_off, Feature.fromString("\"kern\" = 0"));
+ try testing.expectEqual(kern_off, Feature.fromString("\"kern\" = 0, "));
+
+ // Non-boolean settings
+ const aalt_2 = Feature{ .tag = "aalt".*, .value = 2 };
+ try testing.expectEqual(aalt_2, Feature.fromString("aalt=2"));
+ try testing.expectEqual(aalt_2, Feature.fromString("aalt=2, "));
+ try testing.expectEqual(aalt_2, Feature.fromString("'aalt' 2"));
+ try testing.expectEqual(aalt_2, Feature.fromString("'aalt' 2, "));
+
+ // Various ambiguous/error cases which should be null
+ try testing.expectEqual(null, Feature.fromString("aalt=2x")); // bad number
+ try testing.expectEqual(null, Feature.fromString("toolong")); // tag too long
+ try testing.expectEqual(null, Feature.fromString("sht")); // tag too short
+ try testing.expectEqual(null, Feature.fromString("-kern 1")); // redundant/conflicting
+ try testing.expectEqual(null, Feature.fromString("-kern on")); // redundant/conflicting
+ try testing.expectEqual(null, Feature.fromString("aalt=o,")); // bad keyword
+ try testing.expectEqual(null, Feature.fromString("aalt=ofn,")); // bad keyword
+}
+
+test "FeatureList.fromString" {
+ const testing = std.testing;
+
+ const str =
+ " kern, kern on , +kern, \"kern\" = 1," ++ // Boolean settings (on)
+ "kern off, -'kern' , \"kern\"=0," ++ // Boolean settings (off)
+ "aalt=2, 'aalt'\t2," ++ // Non-boolean settings
+ "aalt=2x, toolong, sht, -kern 1, -kern on, aalt=o, aalt=ofn," ++ // Invalid cases
+ "last"; // To ensure final element is included correctly.
+ var feats = try FeatureList.fromString(testing.allocator, str);
+ defer feats.deinit(testing.allocator);
+ try testing.expectEqualSlices(
+ Feature,
+ &(.{Feature{ .tag = "kern".*, .value = 1 }} ** 4 ++
+ .{Feature{ .tag = "kern".*, .value = 0 }} ** 3 ++
+ .{Feature{ .tag = "aalt".*, .value = 2 }} ** 2 ++
+ .{Feature{ .tag = "last".*, .value = 1 }}),
+ feats.features.items,
+ );
+}
diff --git a/vendor/libghostty-vt/src/font/shaper/harfbuzz.zig b/vendor/libghostty-vt/src/font/shaper/harfbuzz.zig
new file mode 100644
index 00000000..9c78d9de
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/shaper/harfbuzz.zig
@@ -0,0 +1,2172 @@
+const std = @import("std");
+const assert = @import("../../quirks.zig").inlineAssert;
+const Allocator = std.mem.Allocator;
+const harfbuzz = @import("harfbuzz");
+const font = @import("../main.zig");
+const terminal = @import("../../terminal/main.zig");
+const unicode = @import("../../unicode/main.zig");
+const Feature = font.shape.Feature;
+const FeatureList = font.shape.FeatureList;
+const default_features = font.shape.default_features;
+const Face = font.Face;
+const Collection = font.Collection;
+const DeferredFace = font.DeferredFace;
+const Library = font.Library;
+const SharedGrid = font.SharedGrid;
+const Style = font.Style;
+const Presentation = font.Presentation;
+
+const log = std.log.scoped(.font_shaper);
+
+/// Shaper that uses Harfbuzz.
+pub const Shaper = struct {
+ /// The allocated used for the feature list, cell buf, and codepoints.
+ alloc: Allocator,
+
+ /// The buffer used for text shaping. We reuse it across multiple shaping
+ /// calls to prevent allocations.
+ hb_buf: harfbuzz.Buffer,
+
+ /// The shared memory used for shaping results.
+ cell_buf: CellBuf,
+
+ /// The features to use for shaping.
+ hb_feats: []harfbuzz.Feature,
+
+ /// The codepoints added to the buffer before shaping. We need to keep
+ /// these separately because after shaping, HarfBuzz replaces codepoints
+ /// with glyph indices in the buffer.
+ codepoints: std.ArrayListUnmanaged(Codepoint) = .{},
+
+ const Codepoint = struct {
+ cluster: u32,
+ codepoint: u32,
+ };
+
+ const CellBuf = std.ArrayListUnmanaged(font.shape.Cell);
+
+ const RunOffset = struct {
+ cluster: u32 = 0,
+ x: i32 = 0,
+ y: i32 = 0,
+ };
+
+ const CellOffset = struct {
+ cluster: u32 = 0,
+ x: i32 = 0,
+ };
+
+ /// The cell_buf argument is the buffer to use for storing shaped results.
+ /// This should be at least the number of columns in the terminal.
+ pub fn init(alloc: Allocator, opts: font.shape.Options) !Shaper {
+ // Parse all the features we want to use.
+ const hb_feats = hb_feats: {
+ var feature_list: FeatureList = .{};
+ defer feature_list.deinit(alloc);
+ try feature_list.features.appendSlice(alloc, &default_features);
+ for (opts.features) |feature_str| {
+ try feature_list.appendFromString(alloc, feature_str);
+ }
+
+ var list = try alloc.alloc(harfbuzz.Feature, feature_list.features.items.len);
+ errdefer alloc.free(list);
+
+ for (feature_list.features.items, 0..) |feature, i| {
+ list[i] = .{
+ .tag = std.mem.nativeToBig(u32, @bitCast(feature.tag)),
+ .value = feature.value,
+ .start = harfbuzz.c.HB_FEATURE_GLOBAL_START,
+ .end = harfbuzz.c.HB_FEATURE_GLOBAL_END,
+ };
+ }
+
+ break :hb_feats list;
+ };
+ errdefer alloc.free(hb_feats);
+
+ return Shaper{
+ .alloc = alloc,
+ .hb_buf = try harfbuzz.Buffer.create(),
+ .cell_buf = .{},
+ .hb_feats = hb_feats,
+ };
+ }
+
+ pub fn deinit(self: *Shaper) void {
+ self.hb_buf.destroy();
+ self.cell_buf.deinit(self.alloc);
+ self.alloc.free(self.hb_feats);
+ self.codepoints.deinit(self.alloc);
+ }
+
+ pub fn endFrame(self: *const Shaper) void {
+ _ = self;
+ }
+
+ /// Returns an iterator that returns one text run at a time for the
+ /// given terminal row. Note that text runs are only valid one at a time
+ /// for a Shaper struct since they share state.
+ ///
+ /// The selection must be a row-only selection (height = 1). See
+ /// Selection.containedRow. The run iterator will ONLY look at X values
+ /// and assume the y value matches.
+ pub fn runIterator(
+ self: *Shaper,
+ opts: font.shape.RunOptions,
+ ) font.shape.RunIterator {
+ return .{
+ .hooks = .{ .shaper = self },
+ .opts = opts,
+ };
+ }
+
+ /// Shape the given text run. The text run must be the immediately previous
+ /// text run that was iterated since the text run does share state with the
+ /// Shaper struct.
+ ///
+ /// The return value is only valid until the next shape call is called.
+ ///
+ /// If there is not enough space in the cell buffer, an error is returned.
+ pub fn shape(self: *Shaper, run: font.shape.TextRun) ![]const font.shape.Cell {
+ // We only do shaping if the font is not a special-case. For special-case
+ // fonts, the codepoint == glyph_index so we don't need to run any shaping.
+ if (run.font_index.special() == null) {
+ // We have to lock the grid to get the face and unfortunately
+ // freetype faces (typically used with harfbuzz) are not thread
+ // safe so this has to be an exclusive lock.
+ run.grid.lock.lock();
+ defer run.grid.lock.unlock();
+
+ const face = try run.grid.resolver.collection.getFace(run.font_index);
+ const i = if (!face.quirks_disable_default_font_features) 0 else i: {
+ // If we are disabling default font features we just offset
+ // our features by the hardcoded items because always
+ // add those at the beginning.
+ break :i default_features.len;
+ };
+
+ harfbuzz.shape(face.hb_font, self.hb_buf, self.hb_feats[i..]);
+ }
+
+ // If our buffer is empty, we short-circuit the rest of the work
+ // return nothing.
+ if (self.hb_buf.getLength() == 0) return self.cell_buf.items[0..0];
+ const info = self.hb_buf.getGlyphInfos();
+ const pos = self.hb_buf.getGlyphPositions() orelse return error.HarfbuzzFailed;
+
+ // This is perhaps not true somewhere, but we currently assume it is true.
+ // If it isn't true, I'd like to catch it and learn more.
+ assert(info.len == pos.len);
+
+ // This keeps track of the current x and y offsets (sum of advances)
+ // and the furthest cluster we've seen so far (max).
+ var run_offset: RunOffset = .{};
+
+ // This keeps track of the cell starting x and cluster.
+ var cell_offset: CellOffset = .{};
+
+ // Convert all our info/pos to cells and set it.
+ self.cell_buf.clearRetainingCapacity();
+ for (info, pos) |info_v, pos_v| {
+ // info_v.cluster is the index into our codepoints array. We use it
+ // to get the original cluster.
+ const index = info_v.cluster;
+ // Our cluster is also our cell X position. If the cluster changes
+ // then we need to reset our current cell offsets.
+ const cluster = self.codepoints.items[index].cluster;
+ if (cell_offset.cluster != cluster) {
+ const is_after_glyph_from_current_or_next_clusters =
+ cluster <= run_offset.cluster;
+
+ const is_first_codepoint_in_cluster = blk: {
+ var i = index;
+ while (i > 0) {
+ i -= 1;
+ const codepoint = self.codepoints.items[i];
+ break :blk codepoint.cluster != cluster;
+ } else break :blk true;
+ };
+
+ // We need to reset the `cell_offset` at the start of a new
+ // cluster, but we do that conditionally if the codepoint
+ // `is_first_codepoint_in_cluster` and the cluster is not
+ // `is_after_glyph_from_current_or_next_clusters`, which is
+ // a heuristic to detect ligatures and avoid positioning
+ // glyphs that mark ligatures incorrectly. The idea is that
+ // if the first codepoint in a cluster doesn't appear in
+ // the stream, it's very likely that it combined with
+ // codepoints from a previous cluster into a ligature.
+ // Then, the subsequent codepoints are very likely marking
+ // glyphs that are placed relative to that ligature, so if
+ // we were to reset the `cell_offset` to align it with the
+ // grid, the positions would be off. The
+ // `!is_after_glyph_from_current_or_next_clusters` check is
+ // needed in case these marking glyphs come from a later
+ // cluster but are rendered first (see the Chakma and
+ // Bengali tests). In that case when we get to the
+ // codepoint that `is_first_codepoint_in_cluster`, but in a
+ // cluster that
+ // `is_after_glyph_from_current_or_next_clusters`, we don't
+ // want to reset to the grid and cause the positions to be
+ // off. (Note that we could go back and align the cells to
+ // the grid starting from the one from the cluster that
+ // rendered out of order, but that is more complicated so
+ // we don't do that for now. Also, it's TBD if there are
+ // exceptions to this heuristic for detecting ligatures,
+ // but using the logging below seems to show it works
+ // well.)
+ if (is_first_codepoint_in_cluster and
+ !is_after_glyph_from_current_or_next_clusters)
+ {
+ cell_offset = .{
+ .cluster = cluster,
+ .x = run_offset.x,
+ };
+ }
+ }
+
+ // Under both FreeType and CoreText the harfbuzz scale is
+ // in 26.6 fixed point units, so we round to the nearest
+ // whole value here.
+ const x_offset = run_offset.x - cell_offset.x + ((pos_v.x_offset + 0b100_000) >> 6);
+ const y_offset = run_offset.y + ((pos_v.y_offset + 0b100_000) >> 6);
+
+ // For debugging positions, turn this on:
+ //try self.debugPositions(run_offset, cell_offset, pos_v, index);
+
+ try self.cell_buf.append(self.alloc, .{
+ .x = @intCast(cell_offset.cluster),
+ .x_offset = @intCast(x_offset),
+ .y_offset = @intCast(y_offset),
+ .glyph_index = info_v.codepoint,
+ });
+
+ // Add our advances to keep track of our run offsets.
+ // Advances apply to the NEXT cell.
+ // Under both FreeType and CoreText the harfbuzz scale is
+ // in 26.6 fixed point units, so we round to the nearest
+ // whole value here.
+ run_offset.x += (pos_v.x_advance + 0b100_000) >> 6;
+ run_offset.y += (pos_v.y_advance + 0b100_000) >> 6;
+ run_offset.cluster = @max(run_offset.cluster, cluster);
+
+ // const i = self.cell_buf.items.len - 1;
+ // log.warn("i={} info={} pos={} cell={}", .{ i, info_v, pos_v, self.cell_buf.items[i] });
+ }
+ //log.warn("----------------", .{});
+
+ return self.cell_buf.items;
+ }
+
+ /// The hooks for RunIterator.
+ pub const RunIteratorHook = struct {
+ shaper: *Shaper,
+
+ pub fn prepare(self: RunIteratorHook) void {
+ // Reset the buffer for our current run
+ self.shaper.hb_buf.reset();
+ self.shaper.hb_buf.setContentType(.unicode);
+
+ // We set the cluster level to `characters` to give us the most
+ // granularity, matching the CoreText shaper, and allowing us
+ // to use our same ligature detection heuristics.
+ self.shaper.hb_buf.setClusterLevel(.characters);
+
+ self.shaper.codepoints.clearRetainingCapacity();
+
+ // We don't support RTL text because RTL in terminals is messy.
+ // Its something we want to improve. For now, we force LTR because
+ // our renderers assume a strictly increasing X value.
+ self.shaper.hb_buf.setDirection(.ltr);
+ }
+
+ pub fn addCodepoint(self: RunIteratorHook, cp: u32, cluster: u32) !void {
+ // log.warn("cluster={} cp={x}", .{ cluster, cp });
+ // We pass the index into codepoints as the cluster value to HarfBuzz.
+ // After shaping, we use info.cluster to get back the index, which
+ // lets us look up the original cluster value from codepoints.
+ const index: u32 = @intCast(self.shaper.codepoints.items.len);
+ self.shaper.hb_buf.add(cp, index);
+ try self.shaper.codepoints.append(self.shaper.alloc, .{
+ .cluster = cluster,
+ .codepoint = cp,
+ });
+ }
+
+ pub fn finalize(self: RunIteratorHook) void {
+ self.shaper.hb_buf.guessSegmentProperties();
+ }
+ };
+
+ fn debugPositions(
+ self: *Shaper,
+ run_offset: RunOffset,
+ cell_offset: CellOffset,
+ pos_v: harfbuzz.GlyphPosition,
+ index: u32,
+ ) !void {
+ const x_offset = run_offset.x - cell_offset.x + ((pos_v.x_offset + 0b100_000) >> 6);
+ const y_offset = run_offset.y + ((pos_v.y_offset + 0b100_000) >> 6);
+ const advance_x_offset = run_offset.x - cell_offset.x;
+ const advance_y_offset = run_offset.y;
+ const x_offset_diff = x_offset - advance_x_offset;
+ const y_offset_diff = y_offset - advance_y_offset;
+ const positions_differ = @abs(x_offset_diff) > 0 or @abs(y_offset_diff) > 0;
+ const y_offset_differs = run_offset.y != 0;
+ const cluster = self.codepoints.items[index].cluster;
+ const cluster_differs = cluster != cell_offset.cluster;
+
+ // To debug every loop, flip this to true:
+ const extra_debugging = false;
+
+ const is_previous_codepoint_prepend = if (cluster_differs or
+ extra_debugging)
+ blk: {
+ var i = index;
+ while (i > 0) {
+ i -= 1;
+ const codepoint = self.codepoints.items[i];
+ break :blk unicode.table.get(@intCast(codepoint.codepoint)).grapheme_boundary_class == .prepend;
+ }
+ break :blk false;
+ } else false;
+
+ const formatted_cps: ?[]u8 = if (positions_differ or
+ y_offset_differs or
+ cluster_differs or
+ extra_debugging)
+ blk: {
+ var allocating = std.Io.Writer.Allocating.init(self.alloc);
+ defer allocating.deinit();
+ const writer = &allocating.writer;
+ const codepoints = self.codepoints.items;
+ var last_cluster: ?u32 = null;
+ for (codepoints, 0..) |cp, i| {
+ if (@as(i32, @intCast(cp.cluster)) >= @as(i32, @intCast(cell_offset.cluster)) - 1 and
+ cp.cluster <= cluster + 1)
+ {
+ if (last_cluster) |last| {
+ if (cp.cluster != last) {
+ try writer.writeAll(" ");
+ }
+ }
+ if (i == index) {
+ try writer.writeAll("▸");
+ }
+ // Using Python syntax for easier debugging
+ if (cp.codepoint > 0xFFFF) {
+ try writer.print("\\U{x:0>8}", .{cp.codepoint});
+ } else {
+ try writer.print("\\u{x:0>4}", .{cp.codepoint});
+ }
+ last_cluster = cp.cluster;
+ }
+ }
+ try writer.writeAll(" → ");
+ for (codepoints) |cp| {
+ if (@as(i32, @intCast(cp.cluster)) >= @as(i32, @intCast(cell_offset.cluster)) - 1 and
+ cp.cluster <= cluster + 1)
+ {
+ try writer.print("{u}", .{@as(u21, @intCast(cp.codepoint))});
+ }
+ }
+ break :blk try allocating.toOwnedSlice();
+ } else null;
+ defer if (formatted_cps) |cps| self.alloc.free(cps);
+
+ if (extra_debugging) {
+ log.warn("extra debugging of positions index={d} cell_offset.cluster={d} cluster={d} run_offset.cluster={d} diff={d} pos=({d},{d}) run_offset=({d},{d}) cell_offset.x={d} is_prev_prepend={} cps = {s}", .{
+ index,
+ cell_offset.cluster,
+ cluster,
+ run_offset.cluster,
+ @as(isize, @intCast(cluster)) - @as(isize, @intCast(cell_offset.cluster)),
+ x_offset,
+ y_offset,
+ run_offset.x,
+ run_offset.y,
+ cell_offset.x,
+ is_previous_codepoint_prepend,
+ formatted_cps.?,
+ });
+ }
+
+ if (positions_differ) {
+ log.warn("position differs from advance: cluster={d} pos=({d},{d}) adv=({d},{d}) diff=({d},{d}) cps = {s}", .{
+ cluster,
+ x_offset,
+ y_offset,
+ advance_x_offset,
+ advance_y_offset,
+ x_offset_diff,
+ y_offset_diff,
+ formatted_cps.?,
+ });
+ }
+
+ if (y_offset_differs) {
+ log.warn("run_offset.y differs from zero: cluster={d} pos=({d},{d}) run_offset=({d},{d}) cell_offset.x={d} cps = {s}", .{
+ cluster,
+ x_offset,
+ y_offset,
+ run_offset.x,
+ run_offset.y,
+ cell_offset.x,
+ formatted_cps.?,
+ });
+ }
+
+ if (cluster_differs) {
+ log.warn("cell_offset.cluster differs from cluster (potential ligature detected) cell_offset.cluster={d} cluster={d} run_offset.cluster={d} diff={d} pos=({d},{d}) run_offset=({d},{d}) cell_offset.x={d} is_prev_prepend={} cps = {s}", .{
+ cell_offset.cluster,
+ cluster,
+ run_offset.cluster,
+ @as(isize, @intCast(cluster)) - @as(isize, @intCast(cell_offset.cluster)),
+ x_offset,
+ y_offset,
+ run_offset.x,
+ run_offset.y,
+ cell_offset.x,
+ is_previous_codepoint_prepend,
+ formatted_cps.?,
+ });
+ }
+ }
+};
+
+test "run iterator" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ {
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 5, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice("ABCD");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |_| count += 1;
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+
+ // Spaces should be part of a run
+ {
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 10, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice("ABCD EFG");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |_| count += 1;
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+
+ {
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 5, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice("A😃D");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |_| {
+ count += 1;
+
+ // All runs should be exactly length 1
+ try testing.expectEqual(@as(u32, 1), shaper.hb_buf.getLength());
+ }
+ try testing.expectEqual(@as(usize, 3), count);
+ }
+}
+
+test "run iterator: empty cells with background set" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ {
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 5, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ // Set red background and write A
+ s.nextSlice("\x1b[48;2;255;0;0mA");
+
+ // Get our first row
+ {
+ const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = 1 } }).?;
+ const cell = list_cell.cell;
+ cell.* = .{
+ .content_tag = .bg_color_rgb,
+ .content = .{ .color_rgb = .{ .r = 0xFF, .g = 0, .b = 0 } },
+ };
+ }
+ {
+ const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = 2 } }).?;
+ const cell = list_cell.cell;
+ cell.* = .{
+ .content_tag = .bg_color_rgb,
+ .content = .{ .color_rgb = .{ .r = 0xFF, .g = 0, .b = 0 } },
+ };
+ }
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ {
+ const run = (try it.next(alloc)).?;
+ try testing.expectEqual(@as(u32, 3), shaper.hb_buf.getLength());
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 3), cells.len);
+ }
+ try testing.expect(try it.next(alloc) == null);
+ }
+}
+
+test "shape" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ var buf: [32]u8 = undefined;
+ var buf_idx: usize = 0;
+ buf_idx += try std.unicode.utf8Encode(0x1F44D, buf[buf_idx..]); // Thumbs up plain
+ buf_idx += try std.unicode.utf8Encode(0x1F44D, buf[buf_idx..]); // Thumbs up plain
+ buf_idx += try std.unicode.utf8Encode(0x1F3FD, buf[buf_idx..]); // Medium skin tone
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 10, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice(buf[0..buf_idx]);
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ try testing.expectEqual(@as(u32, 3), shaper.hb_buf.getLength());
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+}
+
+test "shape inconsolata ligs" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ {
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 5, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice(">=");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+
+ try testing.expectEqual(@as(usize, 2), run.cells);
+
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 1), cells.len);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+
+ {
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 5, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice("===");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+
+ try testing.expectEqual(@as(usize, 3), run.cells);
+
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 1), cells.len);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+}
+
+test "shape monaspace ligs" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaperWithFont(alloc, .monaspace_neon);
+ defer testdata.deinit();
+
+ {
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 5, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice("===");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+
+ try testing.expectEqual(@as(usize, 3), run.cells);
+
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 1), cells.len);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+}
+
+// Ghostty doesn't currently support RTL and our renderers assume
+// that cells are in strict LTR order. This means that we need to
+// force RTL text to be LTR for rendering. This test ensures that
+// we are correctly forcing RTL text to be LTR.
+test "shape arabic forced LTR" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaperWithFont(alloc, .arabic);
+ defer testdata.deinit();
+
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 120, .rows = 30 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice(@embedFile("testdata/arabic.txt"));
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ try testing.expectEqual(@as(usize, 25), run.cells);
+
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 25), cells.len);
+
+ var x: u16 = cells[0].x;
+ for (cells[1..]) |cell| {
+ try testing.expectEqual(x + 1, cell.x);
+ x = cell.x;
+ }
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+}
+
+test "shape emoji width" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ {
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 5, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice("👍");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+
+ try testing.expectEqual(@as(usize, 2), run.cells);
+
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 1), cells.len);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+}
+
+test "shape emoji width long" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ // Make a screen and add a long emoji sequence to it.
+ var t = try terminal.Terminal.init(
+ alloc,
+ .{ .cols = 30, .rows = 3 },
+ );
+ defer t.deinit(alloc);
+
+ var page = t.screens.active.pages.pages.first.?.data;
+ var row = page.getRow(1);
+ const cell = &row.cells.ptr(page.memory)[0];
+ cell.* = .{
+ .content_tag = .codepoint,
+ .content = .{ .codepoint = 0x1F9D4 }, // Person with beard
+ };
+ var graphemes = [_]u21{
+ 0x1F3FB, // Light skin tone (Fitz 1-2)
+ 0x200D, // ZWJ
+ 0x2642, // Male sign
+ 0xFE0F, // Emoji presentation selector
+ };
+ try page.setGraphemes(
+ row,
+ cell,
+ graphemes[0..],
+ );
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(1).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ try testing.expectEqual(@as(u32, 4), shaper.hb_buf.getLength());
+
+ const cells = try shaper.shape(run);
+
+ try testing.expectEqual(@as(usize, 1), cells.len);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+}
+
+test "shape variation selector VS15" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ var buf: [32]u8 = undefined;
+ var buf_idx: usize = 0;
+ buf_idx += try std.unicode.utf8Encode(0x270C, buf[buf_idx..]); // Victory sign (default text)
+ buf_idx += try std.unicode.utf8Encode(0xFE0E, buf[buf_idx..]); // ZWJ to force text
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 10, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice(buf[0..buf_idx]);
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ try testing.expectEqual(@as(u32, 1), shaper.hb_buf.getLength());
+
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 1), cells.len);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+}
+
+test "shape variation selector VS16" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ var buf: [32]u8 = undefined;
+ var buf_idx: usize = 0;
+ buf_idx += try std.unicode.utf8Encode(0x270C, buf[buf_idx..]); // Victory sign (default text)
+ buf_idx += try std.unicode.utf8Encode(0xFE0F, buf[buf_idx..]); // ZWJ to force color
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 10, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice(buf[0..buf_idx]);
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ try testing.expectEqual(@as(u32, 1), shaper.hb_buf.getLength());
+
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 1), cells.len);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+}
+
+test "shape with empty cells in between" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(
+ alloc,
+ .{ .cols = 30, .rows = 3 },
+ );
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice("A");
+ s.nextSlice("\x1b[5C");
+ s.nextSlice("B");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 1), count);
+ try testing.expectEqual(@as(usize, 7), cells.len);
+ }
+}
+
+test "shape Combining characters" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ var buf: [32]u8 = undefined;
+ var buf_idx: usize = 0;
+ buf_idx += try std.unicode.utf8Encode('n', buf[buf_idx..]); // Combining
+ buf_idx += try std.unicode.utf8Encode(0x0308, buf[buf_idx..]); // Combining
+ buf_idx += try std.unicode.utf8Encode(0x0308, buf[buf_idx..]);
+ buf_idx += try std.unicode.utf8Encode('a', buf[buf_idx..]);
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(
+ alloc,
+ .{ .cols = 30, .rows = 3 },
+ );
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice(buf[0..buf_idx]);
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 4), cells.len);
+ try testing.expectEqual(@as(u16, 0), cells[0].x);
+ try testing.expectEqual(@as(u16, 0), cells[1].x);
+ try testing.expectEqual(@as(u16, 0), cells[2].x);
+ try testing.expectEqual(@as(u16, 1), cells[3].x);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+}
+
+// This test exists because the string it uses causes HarfBuzz to output a
+// non-monotonic run with our cluster level set to `characters`, which we need
+// to handle by tracking the max cluster for the run.
+test "shape Devanagari string" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ // We need a font that supports devanagari for this to work, if we can't
+ // find Arial Unicode MS, which is a system font on macOS, we just skip
+ // the test.
+ var testdata = testShaperWithDiscoveredFont(
+ alloc,
+ "Arial Unicode MS",
+ ) catch return error.SkipZigTest;
+ defer testdata.deinit();
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 30, .rows = 3 });
+ defer t.deinit(alloc);
+
+ // Disable grapheme clustering
+ t.modes.set(.grapheme_cluster, false);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice("अपार्टमेंट");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+
+ const run = try it.next(alloc);
+ try testing.expect(run != null);
+ const cells = try shaper.shape(run.?);
+
+ try testing.expectEqual(@as(usize, 8), cells.len);
+ try testing.expectEqual(@as(u16, 0), cells[0].x);
+ try testing.expectEqual(@as(u16, 1), cells[1].x);
+ try testing.expectEqual(@as(u16, 2), cells[2].x);
+ try testing.expectEqual(@as(u16, 4), cells[3].x);
+ try testing.expectEqual(@as(u16, 4), cells[4].x);
+ try testing.expectEqual(@as(u16, 5), cells[5].x);
+ try testing.expectEqual(@as(u16, 5), cells[6].x);
+ try testing.expectEqual(@as(u16, 6), cells[7].x);
+
+ try testing.expect(try it.next(alloc) == null);
+}
+
+// This test fails on Linux if you have the "Noto Sans Tai Tham" font installed
+// locally. Disabling this test until it can be fixed.
+test "shape Tai Tham vowels (position differs from advance)" {
+ return error.SkipZigTest;
+ // // Note that while this test was necessary for CoreText, the old logic was
+ // // working for HarfBuzz. Still we keep it to ensure it has the correct
+ // // behavior.
+ // const testing = std.testing;
+ // const alloc = testing.allocator;
+
+ // // We need a font that supports Tai Tham for this to work, if we can't find
+ // // Noto Sans Tai Tham, which is a system font on macOS, we just skip the
+ // // test.
+ // var testdata = testShaperWithDiscoveredFont(
+ // alloc,
+ // "Noto Sans Tai Tham",
+ // ) catch return error.SkipZigTest;
+ // defer testdata.deinit();
+
+ // var buf: [32]u8 = undefined;
+ // var buf_idx: usize = 0;
+ // buf_idx += try std.unicode.utf8Encode(0x1a2F, buf[buf_idx..]); // ᨯ
+ // buf_idx += try std.unicode.utf8Encode(0x1a70, buf[buf_idx..]); // ᩰ
+
+ // // Make a screen with some data
+ // var t = try terminal.Terminal.init(alloc, .{ .cols = 30, .rows = 3 });
+ // defer t.deinit(alloc);
+
+ // // Enable grapheme clustering
+ // t.modes.set(.grapheme_cluster, true);
+
+ // var s = t.vtStream();
+ // defer s.deinit();
+ // s.nextSlice(buf[0..buf_idx]);
+
+ // var state: terminal.RenderState = .empty;
+ // defer state.deinit(alloc);
+ // try state.update(alloc, &t);
+
+ // // Get our run iterator
+ // var shaper = &testdata.shaper;
+ // var it = shaper.runIterator(.{
+ // .grid = testdata.grid,
+ // .cells = state.row_data.get(0).cells.slice(),
+ // });
+ // var count: usize = 0;
+ // while (try it.next(alloc)) |run| {
+ // count += 1;
+
+ // const cells = try shaper.shape(run);
+ // try testing.expectEqual(@as(usize, 2), cells.len);
+ // try testing.expectEqual(@as(u16, 0), cells[0].x);
+ // try testing.expectEqual(@as(u16, 0), cells[1].x);
+
+ // // The first glyph renders in the next cell. We expect the x_offset
+ // // to equal the cell width. However, with FreeType the cell_width is
+ // // computed from ASCII glyphs, and Noto Sans Tai Tham only has the
+ // // space character in ASCII (with a 3px advance), so the cell_width
+ // // metric doesn't match the actual Tai Tham glyph positioning.
+ // const expected_x_offset: i16 = if (comptime font.options.backend.hasFreetype()) 7 else @intCast(run.grid.metrics.cell_width);
+ // try testing.expectEqual(expected_x_offset, cells[0].x_offset);
+ // try testing.expectEqual(@as(i16, 0), cells[1].x_offset);
+ // }
+ // try testing.expectEqual(@as(usize, 1), count);
+}
+
+test "shape Tibetan characters" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ // We need a font that has multiple glyphs for this codepoint to reproduce
+ // the old broken behavior, and Noto Serif Tibetan is one of them. It's not
+ // a default Mac font, and if we can't find it we just skip the test.
+ var testdata = testShaperWithDiscoveredFont(
+ alloc,
+ "Noto Serif Tibetan",
+ ) catch return error.SkipZigTest;
+ defer testdata.deinit();
+
+ var buf: [32]u8 = undefined;
+ var buf_idx: usize = 0;
+ buf_idx += try std.unicode.utf8Encode(0x0f00, buf[buf_idx..]); // ༀ
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 30, .rows = 3 });
+ defer t.deinit(alloc);
+
+ // Enable grapheme clustering
+ t.modes.set(.grapheme_cluster, true);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice(buf[0..buf_idx]);
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 2), cells.len);
+ try testing.expectEqual(@as(u16, 0), cells[0].x);
+ try testing.expectEqual(@as(u16, 0), cells[1].x);
+
+ // The second glyph renders at the correct location
+ try testing.expect(cells[1].x_offset < 2);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+}
+
+// This test fails on Linux if you have the "Noto Sans Tai Tham" font installed
+// locally. Disabling this test until it can be fixed.
+test "shape Tai Tham letters (run_offset.y differs from zero)" {
+ return error.SkipZigTest;
+ // const testing = std.testing;
+ // const alloc = testing.allocator;
+
+ // // We need a font that supports Tai Tham for this to work, if we can't find
+ // // Noto Sans Tai Tham, which is a system font on macOS, we just skip the
+ // // test.
+ // var testdata = testShaperWithDiscoveredFont(
+ // alloc,
+ // "Noto Sans Tai Tham",
+ // ) catch return error.SkipZigTest;
+ // defer testdata.deinit();
+
+ // var buf: [32]u8 = undefined;
+ // var buf_idx: usize = 0;
+
+ // // First grapheme cluster:
+ // buf_idx += try std.unicode.utf8Encode(0x1a49, buf[buf_idx..]); // HA
+ // buf_idx += try std.unicode.utf8Encode(0x1a60, buf[buf_idx..]); // SAKOT
+ // // Second grapheme cluster, combining with the first in a ligature:
+ // buf_idx += try std.unicode.utf8Encode(0x1a3f, buf[buf_idx..]); // YA
+ // buf_idx += try std.unicode.utf8Encode(0x1a69, buf[buf_idx..]); // U
+
+ // // Make a screen with some data
+ // var t = try terminal.Terminal.init(alloc, .{ .cols = 30, .rows = 3 });
+ // defer t.deinit(alloc);
+
+ // // Enable grapheme clustering
+ // t.modes.set(.grapheme_cluster, true);
+
+ // var s = t.vtStream();
+ // defer s.deinit();
+ // s.nextSlice(buf[0..buf_idx]);
+
+ // var state: terminal.RenderState = .empty;
+ // defer state.deinit(alloc);
+ // try state.update(alloc, &t);
+
+ // // Get our run iterator
+ // var shaper = &testdata.shaper;
+ // var it = shaper.runIterator(.{
+ // .grid = testdata.grid,
+ // .cells = state.row_data.get(0).cells.slice(),
+ // });
+ // var count: usize = 0;
+ // while (try it.next(alloc)) |run| {
+ // count += 1;
+
+ // const cells = try shaper.shape(run);
+ // try testing.expectEqual(@as(usize, 3), cells.len);
+ // try testing.expectEqual(@as(u16, 0), cells[0].x);
+ // try testing.expectEqual(@as(u16, 0), cells[1].x);
+ // try testing.expectEqual(@as(u16, 0), cells[2].x); // U from second grapheme
+
+ // // The U glyph renders at a y below zero
+ // try testing.expectEqual(@as(i16, -3), cells[2].y_offset);
+ // }
+ // try testing.expectEqual(@as(usize, 1), count);
+}
+
+// This test fails on Linux if you have the "Noto Sans Javanese" font installed
+// locally. Disabling this test until it can be fixed.
+test "shape Javanese ligatures" {
+ return error.SkipZigTest;
+ // const testing = std.testing;
+ // const alloc = testing.allocator;
+
+ // // We need a font that supports Javanese for this to work, if we can't find
+ // // Noto Sans Javanese Regular, which is a system font on macOS, we just
+ // // skip the test.
+ // var testdata = testShaperWithDiscoveredFont(
+ // alloc,
+ // "Noto Sans Javanese",
+ // ) catch return error.SkipZigTest;
+ // defer testdata.deinit();
+
+ // var buf: [32]u8 = undefined;
+ // var buf_idx: usize = 0;
+
+ // // First grapheme cluster:
+ // buf_idx += try std.unicode.utf8Encode(0xa9a4, buf[buf_idx..]); // NA
+ // buf_idx += try std.unicode.utf8Encode(0xa9c0, buf[buf_idx..]); // PANGKON
+ // // Second grapheme cluster, combining with the first in a ligature:
+ // buf_idx += try std.unicode.utf8Encode(0xa9b2, buf[buf_idx..]); // HA
+ // buf_idx += try std.unicode.utf8Encode(0xa9b8, buf[buf_idx..]); // Vowel sign SUKU
+
+ // // Make a screen with some data
+ // var t = try terminal.Terminal.init(alloc, .{ .cols = 30, .rows = 3 });
+ // defer t.deinit(alloc);
+
+ // // Enable grapheme clustering
+ // t.modes.set(.grapheme_cluster, true);
+
+ // var s = t.vtStream();
+ // defer s.deinit();
+ // s.nextSlice(buf[0..buf_idx]);
+
+ // var state: terminal.RenderState = .empty;
+ // defer state.deinit(alloc);
+ // try state.update(alloc, &t);
+
+ // // Get our run iterator
+ // var shaper = &testdata.shaper;
+ // var it = shaper.runIterator(.{
+ // .grid = testdata.grid,
+ // .cells = state.row_data.get(0).cells.slice(),
+ // });
+ // var count: usize = 0;
+ // while (try it.next(alloc)) |run| {
+ // count += 1;
+
+ // const cells = try shaper.shape(run);
+ // const cell_width = run.grid.metrics.cell_width;
+ // try testing.expectEqual(@as(usize, 3), cells.len);
+ // try testing.expectEqual(@as(u16, 0), cells[0].x);
+ // try testing.expectEqual(@as(u16, 0), cells[1].x);
+ // try testing.expectEqual(@as(u16, 0), cells[2].x);
+
+ // // The vowel sign SUKU renders with correct x_offset
+ // try testing.expect(cells[2].x_offset > 3 * cell_width);
+ // }
+ // try testing.expectEqual(@as(usize, 1), count);
+}
+
+test "shape Chakma vowel sign with ligature (vowel sign renders first)" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ // We need a font that supports Chakma for this to work, if we can't find
+ // Noto Sans Chakma Regular, which is a system font on macOS, we just skip
+ // the test.
+ var testdata = testShaperWithDiscoveredFont(
+ alloc,
+ "Noto Sans Chakma",
+ ) catch return error.SkipZigTest;
+ defer testdata.deinit();
+
+ var buf: [32]u8 = undefined;
+ var buf_idx: usize = 0;
+
+ // First grapheme cluster:
+ buf_idx += try std.unicode.utf8Encode(0x1111d, buf[buf_idx..]); // BAA
+ // Second grapheme cluster:
+ buf_idx += try std.unicode.utf8Encode(0x11116, buf[buf_idx..]); // TAA
+ buf_idx += try std.unicode.utf8Encode(0x11133, buf[buf_idx..]); // Virama
+ // Third grapheme cluster, combining with the second in a ligature:
+ buf_idx += try std.unicode.utf8Encode(0x11120, buf[buf_idx..]); // YYAA
+ buf_idx += try std.unicode.utf8Encode(0x1112c, buf[buf_idx..]); // Vowel Sign U
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 30, .rows = 3 });
+ defer t.deinit(alloc);
+
+ // Enable grapheme clustering
+ t.modes.set(.grapheme_cluster, true);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice(buf[0..buf_idx]);
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 4), cells.len);
+ try testing.expectEqual(@as(u16, 0), cells[0].x);
+ // See the giant "We need to reset the `cell_offset`" comment, but here
+ // we should technically have the rest of these be `x` of 1, but that
+ // would require going back in the stream to adjust past cells, and
+ // we don't take on that complexity.
+ try testing.expectEqual(@as(u16, 0), cells[1].x);
+ try testing.expectEqual(@as(u16, 0), cells[2].x);
+ try testing.expectEqual(@as(u16, 0), cells[3].x);
+
+ // The vowel sign U renders before the TAA:
+ try testing.expect(cells[1].x_offset < cells[2].x_offset);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+}
+
+test "shape Bengali ligatures with out of order vowels" {
+ // Whereas this test in CoreText had everything shaping into one giant
+ // ligature, HarfBuzz splits it into a few clusters. It still looks okay
+ // (see #10332).
+
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ // We need a font that supports Bengali for this to work, if we can't find
+ // Arial Unicode MS, which is a system font on macOS, we just skip the
+ // test.
+ var testdata = testShaperWithDiscoveredFont(
+ alloc,
+ "Arial Unicode MS",
+ ) catch return error.SkipZigTest;
+ defer testdata.deinit();
+
+ var buf: [32]u8 = undefined;
+ var buf_idx: usize = 0;
+
+ // First grapheme cluster:
+ buf_idx += try std.unicode.utf8Encode(0x09b0, buf[buf_idx..]); // RA
+ buf_idx += try std.unicode.utf8Encode(0x09be, buf[buf_idx..]); // Vowel sign AA
+ // Second grapheme cluster:
+ buf_idx += try std.unicode.utf8Encode(0x09b7, buf[buf_idx..]); // SSA
+ buf_idx += try std.unicode.utf8Encode(0x09cd, buf[buf_idx..]); // Virama
+ // Third grapheme cluster:
+ buf_idx += try std.unicode.utf8Encode(0x099f, buf[buf_idx..]); // TTA
+ buf_idx += try std.unicode.utf8Encode(0x09cd, buf[buf_idx..]); // Virama
+ // Fourth grapheme cluster:
+ buf_idx += try std.unicode.utf8Encode(0x09b0, buf[buf_idx..]); // RA
+ buf_idx += try std.unicode.utf8Encode(0x09c7, buf[buf_idx..]); // Vowel sign E
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 30, .rows = 3 });
+ defer t.deinit(alloc);
+
+ // Enable grapheme clustering
+ t.modes.set(.grapheme_cluster, true);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice(buf[0..buf_idx]);
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 8), cells.len);
+ try testing.expectEqual(@as(u16, 0), cells[0].x);
+ try testing.expectEqual(@as(u16, 0), cells[1].x);
+
+ // Whereas CoreText puts everything all into the first cell (see the
+ // corresponding test), HarfBuzz splits into two clusters.
+ try testing.expectEqual(@as(u16, 2), cells[2].x);
+ try testing.expectEqual(@as(u16, 2), cells[3].x);
+ try testing.expectEqual(@as(u16, 2), cells[4].x);
+ try testing.expectEqual(@as(u16, 2), cells[5].x);
+ try testing.expectEqual(@as(u16, 2), cells[6].x);
+ try testing.expectEqual(@as(u16, 2), cells[7].x);
+
+ // The vowel sign E renders before the SSA:
+ try testing.expect(cells[2].x_offset < cells[3].x_offset);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+}
+
+test "shape box glyphs" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ var buf: [32]u8 = undefined;
+ var buf_idx: usize = 0;
+ buf_idx += try std.unicode.utf8Encode(0x2500, buf[buf_idx..]); // horiz line
+ buf_idx += try std.unicode.utf8Encode(0x2501, buf[buf_idx..]); //
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 10, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice(buf[0..buf_idx]);
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ try testing.expectEqual(@as(u32, 2), shaper.hb_buf.getLength());
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 2), cells.len);
+ try testing.expectEqual(@as(u32, 0x2500), cells[0].glyph_index);
+ try testing.expectEqual(@as(u16, 0), cells[0].x);
+ try testing.expectEqual(@as(u32, 0x2501), cells[1].glyph_index);
+ try testing.expectEqual(@as(u16, 1), cells[1].x);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+}
+
+test "shape selection boundary" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 10, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice("a1b2c3d4e5");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Full line selection
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ .selection = .{ 0, 9 },
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+
+ // Offset x, goes to end of line selection
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ .selection = .{ 2, 9 },
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 2), count);
+ }
+
+ // Offset x, starts at beginning of line
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ .selection = .{ 0, 3 },
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 2), count);
+ }
+
+ // Selection only subset of line
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ .selection = .{ 1, 3 },
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 3), count);
+ }
+
+ // Selection only one character
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ .selection = .{ 1, 1 },
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 3), count);
+ }
+}
+
+test "shape cursor boundary" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 10, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice("a1b2c3d4e5");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // No cursor is full line
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+
+ {
+ // Cursor at index 0 is two runs
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ .cursor_x = 0,
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 2), count);
+ }
+ // And without cursor splitting remains one
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+ }
+
+ {
+ // Cursor at index 1 is three runs
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ .cursor_x = 1,
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 3), count);
+ }
+ // And without cursor splitting remains one
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+ }
+ {
+ // Cursor at last col is two runs
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ .cursor_x = 9,
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 2), count);
+ }
+ // And without cursor splitting remains one
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+ }
+}
+
+test "shape cursor boundary and colored emoji" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(
+ alloc,
+ .{ .cols = 3, .rows = 10 },
+ );
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice("👍🏼");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // No cursor is full line
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+
+ // Cursor on emoji does not split it
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ .cursor_x = 0,
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ .cursor_x = 1,
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+ {
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+}
+
+test "shape cell attribute change" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var testdata = try testShaper(alloc);
+ defer testdata.deinit();
+
+ // Plain >= should shape into 1 run
+ {
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 10, .rows = 3 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice(">=");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+
+ // Bold vs regular should split
+ {
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 3, .rows = 10 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ s.nextSlice(">");
+ s.nextSlice("\x1b[1m");
+ s.nextSlice("=");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 2), count);
+ }
+
+ // Changing fg color should split
+ {
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 3, .rows = 10 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ // RGB 1, 2, 3
+ s.nextSlice("\x1b[38;2;1;2;3m");
+ s.nextSlice(">");
+ // RGB 3, 2, 1
+ s.nextSlice("\x1b[38;2;3;2;1m");
+ s.nextSlice("=");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 2), count);
+ }
+
+ // Changing bg color should not split
+ {
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 3, .rows = 10 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ // RGB 1, 2, 3 bg
+ s.nextSlice("\x1b[48;2;1;2;3m");
+ s.nextSlice(">");
+ // RGB 3, 2, 1 bg
+ s.nextSlice("\x1b[48;2;3;2;1m");
+ s.nextSlice("=");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+
+ // Same bg color should not split
+ {
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 3, .rows = 10 });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ // RGB 1, 2, 3 bg
+ s.nextSlice("\x1b[48;2;1;2;3m");
+ s.nextSlice(">");
+ s.nextSlice("=");
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+ _ = try shaper.shape(run);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+ }
+}
+
+const TestShaper = struct {
+ alloc: Allocator,
+ shaper: Shaper,
+ grid: *SharedGrid,
+ lib: Library,
+
+ pub fn deinit(self: *TestShaper) void {
+ self.shaper.deinit();
+ self.grid.deinit(self.alloc);
+ self.alloc.destroy(self.grid);
+ self.lib.deinit();
+ }
+};
+
+const TestFont = enum {
+ inconsolata,
+ monaspace_neon,
+ arabic,
+};
+
+/// Helper to return a fully initialized shaper.
+fn testShaper(alloc: Allocator) !TestShaper {
+ return try testShaperWithFont(alloc, .inconsolata);
+}
+
+fn testShaperWithFont(alloc: Allocator, font_req: TestFont) !TestShaper {
+ const testEmoji = font.embedded.emoji;
+ const testEmojiText = font.embedded.emoji_text;
+ const testFont = switch (font_req) {
+ .inconsolata => font.embedded.inconsolata,
+ .monaspace_neon => font.embedded.monaspace_neon,
+ .arabic => font.embedded.arabic,
+ };
+
+ var lib = try Library.init(alloc);
+ errdefer lib.deinit();
+
+ var c = Collection.init();
+ c.load_options = .{ .library = lib };
+
+ // Setup group
+ _ = try c.add(alloc, try .init(
+ lib,
+ testFont,
+ .{ .size = .{ .points = 12 } },
+ ), .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .none,
+ });
+
+ if (comptime !font.options.backend.hasCoretext()) {
+ // Coretext doesn't support Noto's format
+ _ = try c.add(alloc, try .init(
+ lib,
+ testEmoji,
+ .{ .size = .{ .points = 12 } },
+ ), .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .none,
+ });
+ } else {
+ // On CoreText we want to load Apple Emoji, we should have it.
+ var disco = font.Discover.init();
+ defer disco.deinit();
+ var disco_it = try disco.discover(alloc, .{
+ .family = "Apple Color Emoji",
+ .size = 12,
+ .monospace = false,
+ });
+ defer disco_it.deinit();
+ var face = (try disco_it.next()).?;
+ errdefer face.deinit();
+ _ = try c.addDeferred(alloc, face, .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .none,
+ });
+ }
+ _ = try c.add(alloc, try .init(
+ lib,
+ testEmojiText,
+ .{ .size = .{ .points = 12 } },
+ ), .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .none,
+ });
+
+ const grid_ptr = try alloc.create(SharedGrid);
+ errdefer alloc.destroy(grid_ptr);
+ grid_ptr.* = try .init(alloc, .{ .collection = c });
+ errdefer grid_ptr.*.deinit(alloc);
+
+ var shaper = try Shaper.init(alloc, .{
+ // Some of our tests rely on dlig being enabled by default
+ .features = &.{"dlig"},
+ });
+ errdefer shaper.deinit();
+
+ return TestShaper{
+ .alloc = alloc,
+ .shaper = shaper,
+ .grid = grid_ptr,
+ .lib = lib,
+ };
+}
+
+fn testShaperWithDiscoveredFont(alloc: Allocator, font_req: [:0]const u8) !TestShaper {
+ if (font.Discover == void) return error.SkipZigTest;
+ var lib = try Library.init(alloc);
+ errdefer lib.deinit();
+
+ var c = Collection.init();
+ c.load_options = .{ .library = lib };
+
+ // Discover and add our font to the collection.
+ {
+ var disco = font.Discover.init();
+ defer disco.deinit();
+ var disco_it = try disco.discover(alloc, .{
+ .family = font_req,
+ .size = 12,
+ .monospace = false,
+ });
+ defer disco_it.deinit();
+ var face: font.DeferredFace = (try disco_it.next()) orelse return error.FontNotFound;
+ errdefer face.deinit();
+
+ // Check which font was discovered - skip if it doesn't match the request
+ var name_buf: [256]u8 = undefined;
+ const face_name = face.name(&name_buf) catch "(unknown)";
+ if (std.mem.indexOf(u8, face_name, font_req) == null) {
+ return error.SkipZigTest;
+ }
+
+ _ = try c.add(
+ alloc,
+ try face.load(lib, .{ .size = .{ .points = 12 } }),
+ .{
+ .style = .regular,
+ .fallback = false,
+ .size_adjustment = .none,
+ },
+ );
+ }
+
+ const grid_ptr = try alloc.create(SharedGrid);
+ errdefer alloc.destroy(grid_ptr);
+ grid_ptr.* = try .init(alloc, .{ .collection = c });
+ errdefer grid_ptr.*.deinit(alloc);
+
+ var shaper = try Shaper.init(alloc, .{});
+ errdefer shaper.deinit();
+
+ return TestShaper{
+ .alloc = alloc,
+ .shaper = shaper,
+ .grid = grid_ptr,
+ .lib = lib,
+ };
+}
diff --git a/vendor/libghostty-vt/src/font/shaper/noop.zig b/vendor/libghostty-vt/src/font/shaper/noop.zig
new file mode 100644
index 00000000..e5a08653
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/shaper/noop.zig
@@ -0,0 +1,143 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const font = @import("../main.zig");
+const Face = font.Face;
+const Collection = font.Collection;
+const DeferredFace = font.DeferredFace;
+const Group = font.Group;
+const GroupCache = font.GroupCache;
+const Library = font.Library;
+const SharedGrid = font.SharedGrid;
+const Style = font.Style;
+const Presentation = font.Presentation;
+const terminal = @import("../../terminal/main.zig");
+
+const log = std.log.scoped(.font_shaper);
+
+/// Shaper that doesn't do any shaping. Each individual codepoint is mapped
+/// directly to the detected text run font's glyph index.
+pub const Shaper = struct {
+ /// The allocated used for the feature list and cell buf.
+ alloc: Allocator,
+
+ /// The string used for shaping the current run.
+ run_state: RunState,
+
+ /// The shared memory used for shaping results.
+ cell_buf: CellBuf,
+
+ const CellBuf = std.ArrayListUnmanaged(font.shape.Cell);
+ const CodepointList = std.ArrayListUnmanaged(Codepoint);
+ const Codepoint = struct {
+ codepoint: u32,
+ cluster: u32,
+ };
+
+ const RunState = struct {
+ codepoints: CodepointList = .{},
+
+ fn deinit(self: *RunState, alloc: Allocator) void {
+ self.codepoints.deinit(alloc);
+ }
+
+ fn reset(self: *RunState) !void {
+ self.codepoints.clearRetainingCapacity();
+ }
+ };
+
+ /// The cell_buf argument is the buffer to use for storing shaped results.
+ /// This should be at least the number of columns in the terminal.
+ pub fn init(alloc: Allocator, opts: font.shape.Options) !Shaper {
+ _ = opts;
+
+ return Shaper{
+ .alloc = alloc,
+ .cell_buf = .{},
+ .run_state = .{},
+ };
+ }
+
+ pub fn deinit(self: *Shaper) void {
+ self.cell_buf.deinit(self.alloc);
+ self.run_state.deinit(self.alloc);
+ }
+
+ pub fn endFrame(self: *const Shaper) void {
+ _ = self;
+ }
+
+ pub fn runIterator(
+ self: *Shaper,
+ opts: font.shape.RunOptions,
+ ) font.shape.RunIterator {
+ return .{
+ .hooks = .{ .shaper = self },
+ .opts = opts,
+ };
+ }
+
+ pub fn shape(self: *Shaper, run: font.shape.TextRun) ![]const font.shape.Cell {
+ const state = &self.run_state;
+
+ // Special fonts aren't shaped and their codepoint == glyph so we
+ // can just return the codepoints as-is.
+ if (run.font_index.special() != null) {
+ self.cell_buf.clearRetainingCapacity();
+ try self.cell_buf.ensureTotalCapacity(self.alloc, state.codepoints.items.len);
+ for (state.codepoints.items) |entry| {
+ self.cell_buf.appendAssumeCapacity(.{
+ .x = @intCast(entry.cluster),
+ .glyph_index = @intCast(entry.codepoint),
+ });
+ }
+
+ return self.cell_buf.items;
+ }
+
+ // Go through the run and map each codepoint to a glyph index.
+ self.cell_buf.clearRetainingCapacity();
+
+ // Note: this is digging into some internal details, we should maybe
+ // expose a public API for this.
+ const face = try run.grid.resolver.collection.getFace(run.font_index);
+ for (state.codepoints.items) |entry| {
+ const glyph_index = face.glyphIndex(entry.codepoint) orelse {
+ // The run iterator shared logic should guarantee that
+ // there is a glyph index for all codepoints in the run.
+ // This is not well tested because we don't use the noop
+ // shaper in any release builds.
+ unreachable;
+ };
+ try self.cell_buf.append(self.alloc, .{
+ .x = @intCast(entry.cluster),
+ .glyph_index = glyph_index,
+ });
+ }
+
+ return self.cell_buf.items;
+ }
+
+ /// The hooks for RunIterator.
+ pub const RunIteratorHook = struct {
+ shaper: *Shaper,
+
+ pub fn prepare(self: *RunIteratorHook) !void {
+ try self.shaper.run_state.reset();
+ }
+
+ pub fn addCodepoint(self: RunIteratorHook, cp: u32, cluster: u32) !void {
+ try self.shaper.run_state.codepoints.append(self.shaper.alloc, .{
+ .codepoint = cp,
+ .cluster = cluster,
+ });
+ }
+
+ pub fn finalize(self: RunIteratorHook) !void {
+ _ = self;
+ }
+ };
+};
+
+test {
+ @import("std").testing.refAllDecls(@This());
+}
diff --git a/vendor/libghostty-vt/src/font/shaper/run.zig b/vendor/libghostty-vt/src/font/shaper/run.zig
new file mode 100644
index 00000000..45c5d38c
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/shaper/run.zig
@@ -0,0 +1,409 @@
+const std = @import("std");
+const assert = @import("../../quirks.zig").inlineAssert;
+const Allocator = std.mem.Allocator;
+const font = @import("../main.zig");
+const shape = @import("../shape.zig");
+const terminal = @import("../../terminal/main.zig");
+const autoHash = std.hash.autoHash;
+const Hasher = std.hash.Wyhash;
+
+/// A single text run. A text run is only valid for one Shaper instance and
+/// until the next run is created. A text run never goes across multiple
+/// rows in a terminal, so it is guaranteed to always be one line.
+pub const TextRun = struct {
+ /// A unique hash for this run. This can be used to cache the shaping
+ /// results. We don't provide a means to compare actual values if the
+ /// hash is the same, so we should continue to improve this hash to
+ /// lower the chance of hash collisions if they become a problem. If
+ /// there are hash collisions, it would result in rendering issues but
+ /// the core data would be correct.
+ ///
+ /// The hash is position-independent within the row by using relative
+ /// cluster positions. This allows identical runs in different positions
+ /// to share the same cache entry, improving cache efficiency.
+ hash: u64,
+
+ /// The offset in the row where this run started. This is added to the
+ /// X position of the final shaped cells to get the absolute position
+ /// in the row where they belong.
+ offset: u16,
+
+ /// The total number of cells produced by this run.
+ cells: u16,
+
+ /// The font grid that built this run.
+ grid: *font.SharedGrid,
+
+ /// The font index to use for the glyphs of this run.
+ font_index: font.Collection.Index,
+};
+
+/// RunIterator is an iterator that yields text runs.
+pub const RunIterator = struct {
+ hooks: font.Shaper.RunIteratorHook,
+ opts: shape.RunOptions,
+ i: usize = 0,
+
+ pub fn next(self: *RunIterator, alloc: Allocator) !?TextRun {
+ const slice = &self.opts.cells;
+ const cells: []const terminal.page.Cell = slice.items(.raw);
+ const graphemes: []const []const u21 = slice.items(.grapheme);
+ const styles: []const terminal.Style = slice.items(.style);
+
+ // Trim the right side of a row that might be empty
+ const max: usize = max: {
+ for (0..cells.len) |i| {
+ const rev_i = cells.len - i - 1;
+ if (!cells[rev_i].isEmpty()) break :max rev_i + 1;
+ }
+
+ break :max 0;
+ };
+
+ // Invisible cells don't have any glyphs rendered,
+ // so we explicitly skip them in the shaping process.
+ while (self.i < max and
+ (cells[self.i].hasStyling() and
+ styles[self.i].flags.invisible)) self.i += 1;
+
+ // We're over at the max
+ if (self.i >= max) return null;
+
+ // Track the font for our current run
+ var current_font: font.Collection.Index = .{};
+
+ // Allow the hook to prepare
+ self.hooks.prepare();
+
+ // Initialize our hash for this run.
+ var hasher = Hasher.init(0);
+
+ // Let's get our style that we'll expect for the run.
+ const style: terminal.Style = if (cells[self.i].hasStyling()) styles[self.i] else .{};
+
+ // Go through cell by cell and accumulate while we build our run.
+ var j: usize = self.i;
+ while (j < max) : (j += 1) {
+ // Use relative cluster positions (offset from run start) to make
+ // the shaping cache position-independent. This ensures that runs
+ // with identical content but different starting positions in the
+ // row produce the same hash, enabling cache reuse.
+ const cluster = j - self.i;
+ const cell: *const terminal.page.Cell = &cells[j];
+
+ // If we have a selection and we're at a boundary point, then
+ // we break the run here.
+ if (self.opts.selection) |bounds| {
+ if (j > self.i) {
+ if (bounds[0] > 0 and j == bounds[0]) break;
+ if (bounds[1] > 0 and j == bounds[1] + 1) break;
+ }
+ }
+
+ // If we're a spacer, then we ignore it
+ switch (cell.wide) {
+ .narrow, .wide => {},
+ .spacer_head, .spacer_tail => continue,
+ }
+
+ // If our cell attributes are changing, then we split the run.
+ // This prevents a single glyph for ">=" to be rendered with
+ // one color when the two components have different styling.
+ if (j > self.i) style: {
+ const prev_cell = cells[j - 1];
+
+ // If the prev cell and this cell are both plain
+ // codepoints then we check if they are commonly "bad"
+ // ligatures and spit the run if they are.
+ if (prev_cell.content_tag == .codepoint and
+ cell.content_tag == .codepoint)
+ {
+ const prev_cp = prev_cell.codepoint();
+ switch (prev_cp) {
+ // fl, fi
+ 'f' => {
+ const cp = cell.codepoint();
+ if (cp == 'l' or cp == 'i') break;
+ },
+
+ // st
+ 's' => {
+ const cp = cell.codepoint();
+ if (cp == 't') break;
+ },
+
+ else => {},
+ }
+ }
+
+ // If the style is exactly the change then fast path out.
+ if (prev_cell.style_id == cell.style_id) break :style;
+
+ // The style is different. We allow differing background
+ // styles but any other change results in a new run.
+ const c1 = comparableStyle(style);
+ const c2 = comparableStyle(if (cell.hasStyling()) styles[j] else .{});
+ if (!c1.eql(c2)) break;
+ }
+
+ // Text runs break when font styles change so we need to get
+ // the proper style.
+ const font_style: font.Style = style: {
+ if (style.flags.bold) {
+ if (style.flags.italic) break :style .bold_italic;
+ break :style .bold;
+ }
+
+ if (style.flags.italic) break :style .italic;
+ break :style .regular;
+ };
+
+ // Determine the presentation format for this glyph.
+ const presentation: ?font.Presentation = if (cell.hasGrapheme()) p: {
+ // We only check the FIRST codepoint because I believe the
+ // presentation format must be directly adjacent to the codepoint.
+ const cps = graphemes[j];
+ assert(cps.len > 0);
+ if (cps[0] == 0xFE0E) break :p .text;
+ if (cps[0] == 0xFE0F) break :p .emoji;
+ break :p null;
+ } else emoji: {
+ // If we're not a grapheme, our individual char could be
+ // an emoji so we want to check if we expect emoji presentation.
+ // The font grid indexForCodepoint we use below will do this
+ // automatically.
+ break :emoji null;
+ };
+
+ // If our cursor is on this line then we break the run around the
+ // cursor. This means that any row with a cursor has at least
+ // three breaks: before, exactly the cursor, and after.
+ //
+ // We do not break a cell that is exactly the grapheme. If there
+ // are cells following that contain joiners, we allow those to
+ // break. This creates an effect where hovering over an emoji
+ // such as a skin-tone emoji is fine, but hovering over the
+ // joiners will show the joiners allowing you to modify the
+ // emoji.
+ if (!cell.hasGrapheme()) {
+ if (self.opts.cursor_x) |cursor_x| {
+ // Exactly: self.i is the cursor and we iterated once. This
+ // means that we started exactly at the cursor and did at
+ // exactly one iteration. Why exactly one? Because we may
+ // start at our cursor but do many if our cursor is exactly
+ // on an emoji.
+ if (self.i == cursor_x and j == self.i + 1) break;
+
+ // Before: up to and not including the cursor. This means
+ // that we started before the cursor (self.i < cursor_x)
+ // and j is now at the cursor meaning we haven't yet processed
+ // the cursor.
+ if (self.i < cursor_x and j == cursor_x) {
+ assert(j > 0);
+ break;
+ }
+
+ // After: after the cursor. We don't need to do anything
+ // special, we just let the run complete.
+ }
+ }
+
+ // We need to find a font that supports this character. If
+ // there are additional zero-width codepoints (to form a single
+ // grapheme, i.e. combining characters), we need to find a font
+ // that supports all of them.
+ const font_info: struct {
+ idx: font.Collection.Index,
+ fallback: ?u32 = null,
+ } = font_info: {
+ // If we find a font that supports this entire grapheme
+ // then we use that.
+ if (try self.indexForCell(
+ alloc,
+ cell,
+ graphemes[j],
+ font_style,
+ presentation,
+ )) |idx| break :font_info .{ .idx = idx };
+
+ // Otherwise we need a fallback character. Prefer the
+ // official replacement character.
+ if (try self.opts.grid.getIndex(
+ alloc,
+ 0xFFFD, // replacement char
+ font_style,
+ presentation,
+ )) |idx| break :font_info .{ .idx = idx, .fallback = 0xFFFD };
+
+ // Fallback to space
+ if (try self.opts.grid.getIndex(
+ alloc,
+ ' ',
+ font_style,
+ presentation,
+ )) |idx| break :font_info .{ .idx = idx, .fallback = ' ' };
+
+ // We can't render at all. This is a bug, we should always
+ // have a font that can render a space.
+ unreachable;
+ };
+
+ //log.warn("char={x} info={}", .{ cell.char, font_info });
+ if (j == self.i) current_font = font_info.idx;
+
+ // If our fonts are not equal, then we're done with our run.
+ if (font_info.idx != current_font) break;
+
+ // If we're a fallback character, add that and continue; we
+ // don't want to add the entire grapheme.
+ if (font_info.fallback) |cp| {
+ try self.addCodepoint(&hasher, cp, @intCast(cluster));
+ continue;
+ }
+
+ // If we're a Kitty unicode placeholder then we add a blank.
+ if (cell.codepoint() == terminal.kitty.graphics.unicode.placeholder) {
+ try self.addCodepoint(&hasher, ' ', @intCast(cluster));
+ continue;
+ }
+
+ // Add all the codepoints for our grapheme
+ try self.addCodepoint(
+ &hasher,
+ if (cell.codepoint() == 0) ' ' else cell.codepoint(),
+ @intCast(cluster),
+ );
+ if (cell.hasGrapheme()) {
+ for (graphemes[j]) |cp| {
+ // Do not send presentation modifiers
+ if (cp == 0xFE0E or cp == 0xFE0F) continue;
+ try self.addCodepoint(&hasher, cp, @intCast(cluster));
+ }
+ }
+ }
+
+ // Finalize our buffer
+ self.hooks.finalize();
+
+ // Add our length to the hash as an additional mechanism to avoid collisions
+ autoHash(&hasher, j - self.i);
+
+ // Add our font index
+ autoHash(&hasher, current_font);
+
+ // Move our cursor. Must defer since we use self.i below.
+ defer self.i = j;
+
+ return .{
+ .hash = hasher.final(),
+ .offset = @intCast(self.i),
+ .cells = @intCast(j - self.i),
+ .grid = self.opts.grid,
+ .font_index = current_font,
+ };
+ }
+
+ fn addCodepoint(self: *RunIterator, hasher: anytype, cp: u32, cluster: u32) !void {
+ autoHash(hasher, cp);
+ autoHash(hasher, cluster);
+ try self.hooks.addCodepoint(cp, cluster);
+ }
+
+ /// Find a font index that supports the grapheme for the given cell,
+ /// or null if no such font exists.
+ ///
+ /// This is used to find a font that supports the entire grapheme.
+ /// We look for fonts that support each individual codepoint and then
+ /// find the common font amongst all candidates.
+ fn indexForCell(
+ self: *RunIterator,
+ alloc: Allocator,
+ cell: *const terminal.Cell,
+ graphemes: []const u21,
+ style: font.Style,
+ presentation: ?font.Presentation,
+ ) !?font.Collection.Index {
+ if (cell.isEmpty() or
+ cell.codepoint() == 0 or
+ cell.codepoint() == terminal.kitty.graphics.unicode.placeholder)
+ {
+ return try self.opts.grid.getIndex(
+ alloc,
+ ' ',
+ style,
+ presentation,
+ );
+ }
+
+ // Get the font index for the primary codepoint.
+ const primary_cp: u32 = cell.codepoint();
+ const primary = try self.opts.grid.getIndex(
+ alloc,
+ primary_cp,
+ style,
+ presentation,
+ ) orelse return null;
+
+ // Easy, and common: we aren't a multi-codepoint grapheme, so
+ // we just return whatever index for the cell codepoint.
+ if (!cell.hasGrapheme()) return primary;
+
+ // If this is a grapheme, we need to find a font that supports
+ // all of the codepoints in the grapheme.
+ var candidates: std.ArrayList(font.Collection.Index) = try .initCapacity(
+ alloc,
+ graphemes.len + 1,
+ );
+ defer candidates.deinit(alloc);
+ candidates.appendAssumeCapacity(primary);
+
+ for (graphemes) |cp| {
+ // Ignore Emoji ZWJs
+ if (cp == 0xFE0E or cp == 0xFE0F or cp == 0x200D) continue;
+
+ // Find a font that supports this codepoint. If none support this
+ // then the whole grapheme can't be rendered so we return null.
+ //
+ // We explicitly do not require the additional grapheme components
+ // to support the base presentation, since it is common for emoji
+ // fonts to support the base emoji with emoji presentation but not
+ // certain ZWJ-combined characters like the male and female signs.
+ const idx = try self.opts.grid.getIndex(
+ alloc,
+ cp,
+ style,
+ null,
+ ) orelse return null;
+ candidates.appendAssumeCapacity(idx);
+ }
+
+ // We need to find a candidate that has ALL of our codepoints
+ for (candidates.items) |idx| {
+ if (!self.opts.grid.hasCodepoint(idx, primary_cp, presentation)) continue;
+ for (graphemes) |cp| {
+ // Ignore Emoji ZWJs
+ if (cp == 0xFE0E or cp == 0xFE0F or cp == 0x200D) continue;
+ if (!self.opts.grid.hasCodepoint(idx, cp, null)) break;
+ } else {
+ // If the while completed, then we have a candidate that
+ // supports all of our codepoints.
+ return idx;
+ }
+ }
+
+ return null;
+ }
+};
+
+/// Returns a style that when compared must be identical for a run to
+/// continue.
+fn comparableStyle(style: terminal.Style) terminal.Style {
+ var s = style;
+
+ // We allow background colors to differ because we'll just paint the
+ // cell background whatever the style is, and wherever the glyph
+ // lands on top of it will be the color of the glyph.
+ s.bg_color = .none;
+
+ return s;
+}
diff --git a/vendor/libghostty-vt/src/font/shaper/testdata/arabic.txt b/vendor/libghostty-vt/src/font/shaper/testdata/arabic.txt
new file mode 100644
index 00000000..d450c762
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/shaper/testdata/arabic.txt
@@ -0,0 +1,3 @@
+غريبه لاني عربي أبا عن جد
+واتكلم الانجليزية بطلاقة اكثر من ٢٥ سنه
+ومع هذا اجد العربيه افضل لان فيها الكثير من المفردات الاكثر دقه بالوصف
diff --git a/vendor/libghostty-vt/src/font/shaper/web_canvas.zig b/vendor/libghostty-vt/src/font/shaper/web_canvas.zig
new file mode 100644
index 00000000..d16bd968
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/shaper/web_canvas.zig
@@ -0,0 +1,305 @@
+const std = @import("std");
+const assert = @import("../../quirks.zig").inlineAssert;
+const Allocator = std.mem.Allocator;
+const font = @import("../main.zig");
+const terminal = @import("../../terminal/main.zig");
+const unicode = @import("../../unicode/main.zig");
+const uucode = @import("uucode");
+
+const log = std.log.scoped(.font_shaper);
+
+pub const Shaper = struct {
+ const RunBuf = std.MultiArrayList(struct {
+ /// The codepoint for this cell. This must be used in conjunction
+ /// with cluster to find the total set of codepoints for a given
+ /// cell. See cluster for more information.
+ codepoint: u32,
+
+ /// Cluster is set to the X value of the cell that this codepoint
+ /// is part of. Note that a cell can have multiple codepoints
+ /// with zero-width joiners (ZWJ) and such. Note that terminals
+ /// do NOT handle full extended grapheme clustering well so it
+ /// is possible a single grapheme extends multiple clusters.
+ /// For example, skin tone emoji thumbs up may show up as two
+ /// clusters: one with thumbs up and the ZWJ, and a second
+ /// cluster with the tone block. It is up to the shaper to handle
+ /// shaping these together into a single glyph, if it wishes.
+ cluster: u32,
+ });
+
+ /// The allocator used for run_buf.
+ alloc: Allocator,
+
+ /// The shared memory used for shaping results.
+ cell_buf: []font.shape.Cell,
+
+ /// The shared memory used for storing information about a run.
+ run_buf: RunBuf,
+
+ /// The cell_buf argument is the buffer to use for storing shaped results.
+ /// This should be at least the number of columns in the terminal.
+ pub fn init(alloc: Allocator, opts: font.shape.Options) !Shaper {
+ // Note: we do not support opts.font_features
+
+ return Shaper{
+ .alloc = alloc,
+ .cell_buf = opts.cell_buf,
+ .run_buf = .{},
+ };
+ }
+
+ pub fn deinit(self: *Shaper) void {
+ self.run_buf.deinit(self.alloc);
+ self.* = undefined;
+ }
+
+ pub fn endFrame(self: *const Shaper) void {
+ _ = self;
+ }
+
+ /// Returns an iterator that returns one text run at a time for the
+ /// given terminal row. Note that text runs are only valid one at a time
+ /// for a Shaper struct since they share state.
+ pub fn runIterator(
+ self: *Shaper,
+ opts: font.shape.RunOptions,
+ ) font.shape.RunIterator {
+ return .{
+ .hooks = .{ .shaper = self },
+ .opts = opts,
+ };
+ }
+
+ /// Shape the given text run. The text run must be the immediately
+ /// previous text run that was iterated since the text run does share
+ /// state with the Shaper struct.
+ ///
+ /// The return value is only valid until the next shape call is called.
+ ///
+ /// If there is not enough space in the cell buffer, an error is
+ /// returned.
+ pub fn shape(self: *Shaper, run: font.shape.TextRun) ![]font.shape.Cell {
+ // TODO: memory check that cell_buf can fit results
+
+ const codepoints = self.run_buf.items(.codepoint);
+ const clusters = self.run_buf.items(.cluster);
+ assert(codepoints.len == clusters.len);
+
+ switch (codepoints.len) {
+ // Special cases: if we have no codepoints (is this possible?)
+ // then our result is also an empty cell run.
+ 0 => return self.cell_buf[0..0],
+
+ // If we have only 1 codepoint, then we assume that it is
+ // a single grapheme and just let it through. At this point,
+ // we can't have any more information to do anything else.
+ 1 => {
+ self.cell_buf[0] = .{
+ .x = @intCast(clusters[0]),
+ .glyph_index = codepoints[0],
+ };
+
+ return self.cell_buf[0..1];
+ },
+
+ else => {},
+ }
+
+ // We know we have at least two codepoints, so we now go through
+ // each and perform grapheme clustering.
+ //
+ // Note that due to limitations of canvas, we can NOT support
+ // font ligatures. However, we do support grapheme clustering.
+ // This means we can render things like skin tone emoji but
+ // we can't render things like single glyph "=>".
+ var break_state: uucode.grapheme.BreakState = .default;
+ var cp1: u21 = @intCast(codepoints[0]);
+
+ var start: usize = 0;
+ var i: usize = 1;
+ var cur: usize = 0;
+ while (i <= codepoints.len) : (i += 1) {
+ // We loop to codepoints.len so that we can handle the end
+ // case. In the end case, we always assume it is a grapheme
+ // break. This isn't strictly true but its how terminals
+ // work today.
+ const grapheme_break = i == codepoints.len or blk: {
+ const cp2: u21 = @intCast(codepoints[i]);
+ defer cp1 = cp2;
+
+ break :blk unicode.graphemeBreak(
+ cp1,
+ cp2,
+ &break_state,
+ );
+ };
+
+ // If this is NOT a grapheme break, cp2 is part of a single
+ // grapheme cluster and we expect there could be more. We
+ // move on to the next codepoint to try again.
+ if (!grapheme_break) continue;
+
+ // This IS a grapheme break, meaning that cp2 is NOT part
+ // of cp1. So we need to render the prior grapheme.
+ const len = i - start;
+ assert(len > 0);
+ switch (len) {
+ // If we have only a single codepoint then just render it
+ // as-is.
+ 1 => self.cell_buf[cur] = .{
+ .x = @intCast(clusters[start]),
+ .glyph_index = codepoints[start],
+ },
+
+ // We must have multiple codepoints (see assert above). In
+ // this case we UTF-8 encode the codepoints and send them
+ // to the face to reserve a private glyph index.
+ else => {
+ // UTF-8 encode the codepoints in this cluster.
+ const cluster = cluster: {
+ const cluster_points = codepoints[start..i];
+ assert(cluster_points.len == len);
+
+ const buf_len = buf_len: {
+ var acc: usize = 0;
+ for (cluster_points) |cp| {
+ acc += try std.unicode.utf8CodepointSequenceLength(
+ @intCast(cp),
+ );
+ }
+
+ break :buf_len acc;
+ };
+
+ var buf = try self.alloc.alloc(u8, buf_len);
+ errdefer self.alloc.free(buf);
+ var buf_i: usize = 0;
+ for (cluster_points) |cp| {
+ buf_i += try std.unicode.utf8Encode(
+ @intCast(cp),
+ buf[buf_i..],
+ );
+ }
+
+ break :cluster buf;
+ };
+ defer self.alloc.free(cluster);
+
+ var face = try run.group.group.faceFromIndex(run.font_index);
+ const index = try face.graphemeGlyphIndex(cluster);
+
+ self.cell_buf[cur] = .{
+ .x = @intCast(clusters[start]),
+ .glyph_index = index,
+ };
+ },
+ }
+
+ start = i;
+ cur += 1;
+ }
+
+ return self.cell_buf[0..cur];
+ }
+
+ /// The hooks for RunIterator.
+ pub const RunIteratorHook = struct {
+ shaper: *Shaper,
+
+ pub fn prepare(self: RunIteratorHook) !void {
+ // Reset the buffer for our current run
+ self.shaper.run_buf.shrinkRetainingCapacity(0);
+ }
+
+ pub fn addCodepoint(
+ self: RunIteratorHook,
+ cp: u32,
+ cluster: u32,
+ ) !void {
+ try self.shaper.run_buf.append(self.shaper.alloc, .{
+ .codepoint = cp,
+ .cluster = cluster,
+ });
+ }
+
+ pub fn finalize(self: RunIteratorHook) !void {
+ _ = self;
+ }
+ };
+};
+
+/// The wasm-compatible API.
+pub const Wasm = struct {
+ const wasm = @import("../../os/wasm.zig");
+ const alloc = wasm.alloc;
+
+ export fn shaper_new(cap: usize) ?*Shaper {
+ return shaper_new_(cap) catch null;
+ }
+
+ fn shaper_new_(cap: usize) !*Shaper {
+ const cell_buf = try alloc.alloc(font.shape.Cell, cap);
+ errdefer alloc.free(cell_buf);
+
+ var shaper = try Shaper.init(alloc, .{ .cell_buf = cell_buf });
+ errdefer shaper.deinit();
+
+ const result = try alloc.create(Shaper);
+ errdefer alloc.destroy(result);
+ result.* = shaper;
+ return result;
+ }
+
+ export fn shaper_free(ptr: ?*Shaper) void {
+ if (ptr) |v| {
+ alloc.free(v.cell_buf);
+ v.deinit();
+ alloc.destroy(v);
+ }
+ }
+
+ /// Runs a test to verify shaping works properly.
+ export fn shaper_test(
+ self: *Shaper,
+ group: *font.GroupCache,
+ str: [*]const u8,
+ len: usize,
+ ) void {
+ shaper_test_(self, group, str[0..len]) catch |err| {
+ log.warn("error during shaper test err={}", .{err});
+ };
+ }
+
+ fn shaper_test_(self: *Shaper, group: *font.GroupCache, str: []const u8) !void {
+ // Create a terminal and print all our characters into it.
+ var term = try terminal.Terminal.init(alloc, self.cell_buf.len, 80);
+ defer term.deinit(alloc);
+
+ // Iterate over unicode codepoints and add to terminal
+ {
+ const view = try std.unicode.Utf8View.init(str);
+ var iter = view.iterator();
+ while (iter.nextCodepoint()) |c| {
+ try term.print(c);
+ }
+ }
+
+ // Iterate over the rows and print out all the runs we get.
+ var rowIter = term.screen.rowIterator(.viewport);
+ var y: usize = 0;
+ while (rowIter.next()) |row| {
+ defer y += 1;
+
+ var iter = self.runIterator(group, row, null, null);
+ while (try iter.next(alloc)) |run| {
+ const cells = try self.shape(run);
+ log.info("y={} run={d} shape={any} idx={}", .{
+ y,
+ run.cells,
+ cells,
+ run.font_index,
+ });
+ }
+ }
+ }
+};
diff --git a/vendor/libghostty-vt/src/font/sprite.zig b/vendor/libghostty-vt/src/font/sprite.zig
new file mode 100644
index 00000000..cf86fa6d
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite.zig
@@ -0,0 +1,45 @@
+const std = @import("std");
+const canvas = @import("sprite/canvas.zig");
+pub const Face = @import("sprite/Face.zig");
+
+pub const Box = canvas.Box;
+pub const Point = canvas.Point;
+pub const Canvas = canvas.Canvas;
+pub const Color = canvas.Color;
+
+/// Sprites are represented as special codepoints outside of the Unicode
+/// codepoint range. Unicode maxes out at U+10FFFF (21 bits), and we use the
+/// high 11 bits to hide our special characters.
+///
+/// These characters are ONLY used for rendering and NEVER used written to
+/// text files or any other exported format, so we don't use the Private Use
+/// Area of Unicode.
+pub const Sprite = enum(u32) {
+ // Start 1 above the maximum Unicode codepoint.
+ pub const start: u32 = std.math.maxInt(u21) + 1;
+ pub const end: u32 = std.math.maxInt(u32);
+
+ underline = start,
+ underline_double,
+ underline_dotted,
+ underline_dashed,
+ underline_curly,
+
+ strikethrough,
+
+ overline,
+
+ cursor_rect,
+ cursor_hollow_rect,
+ cursor_bar,
+ cursor_underline,
+
+ test {
+ const testing = std.testing;
+ try testing.expectEqual(start, @intFromEnum(Sprite.underline));
+ }
+};
+
+test {
+ @import("std").testing.refAllDecls(@This());
+}
diff --git a/vendor/libghostty-vt/src/font/sprite/Face.zig b/vendor/libghostty-vt/src/font/sprite/Face.zig
new file mode 100644
index 00000000..596a9204
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/Face.zig
@@ -0,0 +1,550 @@
+//! This implements the built-in "sprite face". This font renders
+//! the built-in glyphs for the terminal, such as box drawing fonts, as well
+//! as specific sprites that are part of our rendering model such as
+//! text decorations (underlines).
+//!
+//! This isn't really a "font face" so much as it is quacks like a font
+//! face with regards to how it works with font.Group. We don't use any
+//! dynamic dispatch so it isn't truly an interface but the functions
+//! and behaviors are close enough to a system face that it makes it easy
+//! to integrate with font.Group. This is desirable so that higher level
+//! processes such as GroupCache, Shaper, etc. don't need to be aware of
+//! special sprite handling and just treat it like a normal font face.
+const Face = @This();
+
+const std = @import("std");
+const assert = @import("../../quirks.zig").inlineAssert;
+const Allocator = std.mem.Allocator;
+const wuffs = @import("wuffs");
+const z2d = @import("z2d");
+const font = @import("../main.zig");
+const Sprite = font.sprite.Sprite;
+
+const special = @import("draw/special.zig");
+
+const log = std.log.scoped(.font_sprite);
+
+/// Grid metrics for rendering sprites.
+metrics: font.Metrics,
+
+pub const DrawFnError =
+ Allocator.Error ||
+ z2d.Path.Error ||
+ z2d.painter.FillError ||
+ z2d.painter.StrokeError ||
+ error{
+ /// Something went wrong while doing math.
+ MathError,
+ };
+
+/// A function that draws a glyph on the provided canvas.
+pub const DrawFn = fn (
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) DrawFnError!void;
+
+const Range = struct {
+ min: u32,
+ max: u32,
+ draw: DrawFn,
+};
+
+/// Automatically collect ranges for functions with names
+/// in the format `draw<CP>` or `draw<MIN>_<MAX>`.
+const ranges: []const Range = ranges: {
+ @setEvalBranchQuota(1_000_000);
+
+ // Structs containing drawing functions for codepoint ranges.
+ const structs = [_]type{
+ @import("draw/block.zig"),
+ @import("draw/box.zig"),
+ @import("draw/braille.zig"),
+ @import("draw/branch.zig"),
+ @import("draw/geometric_shapes.zig"),
+ @import("draw/powerline.zig"),
+ @import("draw/symbols_for_legacy_computing.zig"),
+ @import("draw/symbols_for_legacy_computing_supplement.zig"),
+ };
+
+ // Count how many draw fns we have
+ var range_count = 0;
+ for (structs) |s| {
+ for (@typeInfo(s).@"struct".decls) |decl| {
+ if (!@hasDecl(s, decl.name)) continue;
+ if (!std.mem.startsWith(u8, decl.name, "draw")) continue;
+ range_count += 1;
+ }
+ }
+
+ // Make an array and collect ranges for each function.
+ var r: [range_count]Range = undefined;
+ var names: [range_count][:0]const u8 = undefined;
+ var i = 0;
+ for (structs) |s| {
+ for (@typeInfo(s).@"struct".decls) |decl| {
+ if (!@hasDecl(s, decl.name)) continue;
+ if (!std.mem.startsWith(u8, decl.name, "draw")) continue;
+
+ const sep = std.mem.indexOfScalar(u8, decl.name, '_') orelse decl.name.len;
+
+ const min = std.fmt.parseInt(u21, decl.name[4..sep], 16) catch unreachable;
+
+ const max = if (sep == decl.name.len)
+ min
+ else
+ std.fmt.parseInt(u21, decl.name[sep + 1 ..], 16) catch unreachable;
+
+ r[i] = .{
+ .min = min,
+ .max = max,
+ .draw = @field(s, decl.name),
+ };
+ names[i] = decl.name;
+ i += 1;
+ }
+ }
+
+ // Sort ranges in ascending order
+ std.mem.sortUnstableContext(0, r.len, struct {
+ r: []Range,
+ names: [][:0]const u8,
+ pub fn lessThan(self: @This(), a: usize, b: usize) bool {
+ return self.r[a].min < self.r[b].min;
+ }
+ pub fn swap(self: @This(), a: usize, b: usize) void {
+ std.mem.swap(Range, &self.r[a], &self.r[b]);
+ std.mem.swap([:0]const u8, &self.names[a], &self.names[b]);
+ }
+ }{
+ .r = &r,
+ .names = &names,
+ });
+
+ // Ensure there's no overlapping ranges
+ i = 0;
+ for (r, 0..) |n, k| {
+ if (n.min <= i) {
+ @compileError(
+ std.fmt.comptimePrint(
+ "Codepoint range for {s}(...) overlaps range for {s}(...), {X} <= {X} <= {X}",
+ .{ names[k], names[k - 1], r[k - 1].min, n.min, r[k - 1].max },
+ ),
+ );
+ }
+ i = n.max;
+ }
+
+ // We need to copy in to a const rather than a var in order to take
+ // the reference at comptime so that we can break with a slice here.
+ const fixed = r;
+
+ break :ranges &fixed;
+};
+
+fn getDrawFn(cp: u32) ?*const DrawFn {
+ // For special sprites (cursors, underlines, etc.) all sprites are drawn
+ // by functions from `Special` that share the name of the enum field.
+ if (cp >= Sprite.start) switch (@as(Sprite, @enumFromInt(cp))) {
+ inline else => |sprite| {
+ return @field(special, @tagName(sprite));
+ },
+ };
+
+ // Pray that the compiler is smart enough to
+ // turn this in to a jump table or something...
+ inline for (ranges) |range| {
+ if (cp >= range.min and cp <= range.max) return range.draw;
+ }
+ return null;
+}
+
+/// Returns true if the codepoint exists in our sprite font.
+pub fn hasCodepoint(self: Face, cp: u32, p: ?font.Presentation) bool {
+ // We ignore presentation. No matter what presentation is
+ // requested we always provide glyphs for our codepoints.
+ _ = p;
+ _ = self;
+ return getDrawFn(cp) != null;
+}
+
+/// Render the glyph.
+pub fn renderGlyph(
+ self: Face,
+ alloc: Allocator,
+ atlas: *font.Atlas,
+ cp: u32,
+ opts: font.face.RenderOptions,
+) !font.Glyph {
+ if (std.debug.runtime_safety) {
+ if (!self.hasCodepoint(cp, null)) {
+ log.err("invalid codepoint cp={x}", .{cp});
+ unreachable; // crash
+ }
+ }
+
+ // It should be impossible for this to be null and we assert that
+ // in runtime safety modes but in case it is its not worth memory
+ // corruption so we return a valid, blank glyph.
+ const draw = getDrawFn(cp) orelse return .{
+ .width = 0,
+ .height = 0,
+ .offset_x = 0,
+ .offset_y = 0,
+ .atlas_x = 0,
+ .atlas_y = 0,
+ };
+
+ const metrics = self.metrics;
+
+ // We adjust our sprite width based on the cell width.
+ const width = switch (opts.cell_width orelse 1) {
+ 0, 1 => metrics.cell_width,
+ else => |width| metrics.cell_width * width,
+ };
+
+ const height = metrics.cell_height;
+
+ const padding_x = width / 4;
+ const padding_y = height / 4;
+
+ // Make a canvas of the desired size
+ var canvas = try font.sprite.Canvas.init(alloc, width, height, padding_x, padding_y);
+ defer canvas.deinit();
+
+ try draw(cp, &canvas, width, height, metrics);
+
+ // Write the drawing to the atlas
+ const region = try canvas.writeAtlas(alloc, atlas);
+
+ return .{
+ .width = region.width,
+ .height = region.height,
+ .offset_x = @as(i32, @intCast(canvas.clip_left)) - @as(i32, @intCast(padding_x)),
+ .offset_y = @as(i32, @intCast(region.height +| canvas.clip_bottom)) - @as(i32, @intCast(padding_y)),
+ .atlas_x = region.x,
+ .atlas_y = region.y,
+ };
+}
+
+/// Used in `testDrawRanges`, checks for diff between the provided atlas
+/// and the reference file for the range, returns true if there is a diff.
+fn testDiffAtlas(
+ alloc: Allocator,
+ atlas: *z2d.Surface,
+ path: []const u8,
+ i: u32,
+ width: u32,
+ height: u32,
+ thickness: u32,
+) !bool {
+ // Get the file contents, we compare the PNG data first in
+ // order to ensure that no one smuggles arbitrary binary
+ // data in to the reference PNGs.
+ const test_file = try std.fs.openFileAbsolute(path, .{ .mode = .read_only });
+ defer test_file.close();
+ const test_bytes = try test_file.readToEndAlloc(
+ alloc,
+ std.math.maxInt(usize),
+ );
+ defer alloc.free(test_bytes);
+
+ const cwd_absolute = try std.fs.cwd().realpathAlloc(alloc, ".");
+ defer alloc.free(cwd_absolute);
+
+ // Get the reference file contents to compare.
+ const ref_path = try std.fmt.allocPrint(
+ alloc,
+ "./src/font/sprite/testdata/U+{X}...U+{X}-{d}x{d}+{d}.png",
+ .{ i, i + 0xFF, width, height, thickness },
+ );
+ defer alloc.free(ref_path);
+ const ref_file =
+ std.fs.cwd().openFile(ref_path, .{ .mode = .read_only }) catch |err| {
+ log.err("Can't open reference file {s}: {}\n", .{
+ ref_path,
+ err,
+ });
+
+ // Copy the test PNG in to the CWD so it isn't
+ // cleaned up with the rest of the tmp dir files.
+ const test_path = try std.fmt.allocPrint(
+ alloc,
+ "{s}/sprite_face_test-U+{X}...U+{X}-{d}x{d}+{d}.png",
+ .{ cwd_absolute, i, i + 0xFF, width, height, thickness },
+ );
+ defer alloc.free(test_path);
+ try std.fs.copyFileAbsolute(path, test_path, .{});
+
+ return true;
+ };
+ defer ref_file.close();
+ const ref_bytes = try ref_file.readToEndAlloc(
+ alloc,
+ std.math.maxInt(usize),
+ );
+ defer alloc.free(ref_bytes);
+
+ // Do our PNG bytes comparison, if it's the same then we can
+ // move on, otherwise we'll decode the reference file and do
+ // a pixel-for-pixel diff.
+ if (std.mem.eql(u8, test_bytes, ref_bytes)) return false;
+
+ // Copy the test PNG in to the CWD so it isn't
+ // cleaned up with the rest of the tmp dir files.
+ const test_path = try std.fmt.allocPrint(
+ alloc,
+ "{s}/sprite_face_test-U+{X}...U+{X}-{d}x{d}+{d}.png",
+ .{ cwd_absolute, i, i + 0xFF, width, height, thickness },
+ );
+ defer alloc.free(test_path);
+ try std.fs.copyFileAbsolute(path, test_path, .{});
+
+ // Use wuffs to decode the reference PNG to raw pixels.
+ // These will be RGBA, so when diffing we can just compare
+ // every fourth byte.
+ const ref_rgba = try wuffs.png.decode(alloc, ref_bytes);
+ defer alloc.free(ref_rgba.data);
+
+ assert(ref_rgba.width == atlas.getWidth());
+ assert(ref_rgba.height == atlas.getHeight());
+
+ // We'll make a visual representation of the diff using
+ // red for removed pixels and green for added. We make
+ // a z2d surface for that here.
+ var diff = try z2d.Surface.init(
+ .image_surface_rgb,
+ alloc,
+ atlas.getWidth(),
+ atlas.getHeight(),
+ );
+ defer diff.deinit(alloc);
+ const diff_pix = diff.image_surface_rgb.buf;
+
+ const test_gray = std.mem.sliceAsBytes(atlas.image_surface_alpha8.buf);
+
+ var differs: bool = false;
+ for (0..test_gray.len) |j| {
+ const t = test_gray[j];
+ const r = ref_rgba.data[j * 4];
+ if (t == r) {
+ // If the pixels match, write it as a faded gray.
+ diff_pix[j].r = t / 3;
+ diff_pix[j].g = t / 3;
+ diff_pix[j].b = t / 3;
+ } else {
+ differs = true;
+ // Otherwise put the reference value in the red
+ // channel and the new value in the green channel.
+ diff_pix[j].r = r;
+ diff_pix[j].g = t;
+ }
+ }
+
+ // If the PNG data differs but not the raw pixels, that's
+ // a big red flag, since it could mean someone is trying to
+ // smuggle binary data in to the test files.
+ if (!differs) {
+ log.err(
+ "!!! Test PNG data does not match reference, but pixels do match! " ++
+ "Either z2d's PNG exporter changed or someone is " ++
+ "trying to smuggle binary data in the test files!\n" ++
+ "test={s}, reference={s}",
+ .{ test_path, ref_path },
+ );
+ return true;
+ }
+
+ // Drop the diff image as a PNG in the cwd.
+ const diff_path = try std.fmt.allocPrint(
+ alloc,
+ "./sprite_face_diff-U+{X}...U+{X}-{d}x{d}+{d}.png",
+ .{ i, i + 0xFF, width, height, thickness },
+ );
+ defer alloc.free(diff_path);
+ try z2d.png_exporter.writeToPNGFile(diff, diff_path, .{});
+ log.err(
+ "One or more glyphs differ from reference file in range U+{X}...U+{X}! " ++
+ "test={s}, reference={s}, diff={s}",
+ .{ i, i + 0xFF, test_path, ref_path, diff_path },
+ );
+
+ return true;
+}
+
+/// Draws all ranges in to a set of 16x16 glyph atlases, checks for regressions
+/// against reference files, logs errors and exposes a diff for any difference
+/// between the reference and test atlas.
+///
+/// Returns true if there was a diff.
+fn testDrawRanges(
+ width: u32,
+ ascent: u32,
+ descent: u32,
+ thickness: u32,
+) !bool {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ const metrics: font.Metrics = .calc(.{
+ // Fudged number, not used in anything we care about here.
+ .px_per_em = 16,
+
+ .cell_width = @floatFromInt(width),
+ .ascent = @floatFromInt(ascent),
+ .descent = -@as(f64, @floatFromInt(descent)),
+ .line_gap = 0.0,
+ .underline_thickness = @floatFromInt(thickness),
+ .strikethrough_thickness = @floatFromInt(thickness),
+ });
+
+ const height = ascent + descent;
+
+ const padding_x = width / 4;
+ const padding_y = height / 4;
+
+ // Canvas to draw glyphs on, we'll reuse this for all glyphs.
+ var canvas = try font.sprite.Canvas.init(
+ alloc,
+ width,
+ height,
+ padding_x,
+ padding_y,
+ );
+ defer canvas.deinit();
+
+ // We render glyphs in batches of 256, which we copy (including padding) to
+ // a 16 by 16 surface to be compared with the reference file for that range.
+ const stride_x = width + 2 * padding_x;
+ const stride_y = height + 2 * padding_y;
+ var atlas = try z2d.Surface.init(
+ .image_surface_alpha8,
+ alloc,
+ @intCast(stride_x * 16),
+ @intCast(stride_y * 16),
+ );
+ defer atlas.deinit(alloc);
+
+ var i: u32 = std.mem.alignBackward(u32, ranges[0].min, 0x100);
+
+ // Try to make the sprite_face_test folder if it doesn't already exist.
+ var dir = testing.tmpDir(.{});
+ defer dir.cleanup();
+ const tmp_dir = try dir.dir.realpathAlloc(alloc, ".");
+ defer alloc.free(tmp_dir);
+
+ // We set this to true if we have any fails so we can
+ // return an error after we're done comparing all glyphs.
+ var fail: bool = false;
+
+ inline for (ranges) |range| {
+ for (range.min..range.max + 1) |cp| {
+ // If we've moved to a new batch of 256, check the
+ // current one and clear the surface for the next one.
+ if (cp - i >= 0x100) {
+ // Export to our tmp dir.
+ const path = try std.fmt.allocPrint(
+ alloc,
+ "{s}/U+{X}...U+{X}-{d}x{d}+{d}.png",
+ .{ tmp_dir, i, i + 0xFF, width, height, thickness },
+ );
+ defer alloc.free(path);
+ try z2d.png_exporter.writeToPNGFile(atlas, path, .{});
+
+ if (try testDiffAtlas(
+ alloc,
+ &atlas,
+ path,
+ i,
+ width,
+ height,
+ thickness,
+ )) fail = true;
+
+ i = std.mem.alignBackward(u32, @intCast(cp), 0x100);
+ @memset(std.mem.sliceAsBytes(atlas.image_surface_alpha8.buf), 0);
+ }
+
+ try getDrawFn(@intCast(cp)).?(
+ @intCast(cp),
+ &canvas,
+ width,
+ height,
+ metrics,
+ );
+ canvas.clearClippingRegions();
+ atlas.composite(
+ &canvas.sfc,
+ .src,
+ @intCast(stride_x * ((cp - i) % 16)),
+ @intCast(stride_y * ((cp - i) / 16)),
+ .{},
+ );
+ @memset(std.mem.sliceAsBytes(canvas.sfc.image_surface_alpha8.buf), 0);
+ canvas.clip_top = 0;
+ canvas.clip_left = 0;
+ canvas.clip_right = 0;
+ canvas.clip_bottom = 0;
+ }
+ }
+
+ const path = try std.fmt.allocPrint(
+ alloc,
+ "{s}/U+{X}...U+{X}-{d}x{d}+{d}.png",
+ .{ tmp_dir, i, i + 0xFF, width, height, thickness },
+ );
+ defer alloc.free(path);
+ try z2d.png_exporter.writeToPNGFile(atlas, path, .{});
+ if (try testDiffAtlas(
+ alloc,
+ &atlas,
+ path,
+ i,
+ width,
+ height,
+ thickness,
+ )) fail = true;
+
+ return fail;
+}
+
+test "sprite face render all sprites" {
+ // This test is way too slow to run under Valgrind, unfortunately.
+ if (std.valgrind.runningOnValgrind() > 0) return error.SkipZigTest;
+
+ // Renders all sprites to an atlas and compares
+ // it to a ground truth for regression testing.
+
+ var diff: bool = false;
+
+ // testDrawRanges(width, ascent, descent, thickness):
+ //
+ // We compare 4 different sets of metrics;
+ // - even cell size / even thickness
+ // - even cell size / odd thickness
+ // - odd cell size / even thickness
+ // - odd cell size / odd thickness
+ // (Also a decreasing range of sizes.)
+ if (try testDrawRanges(18, 30, 6, 4)) diff = true;
+ if (try testDrawRanges(12, 20, 4, 3)) diff = true;
+ if (try testDrawRanges(11, 19, 2, 2)) diff = true;
+ if (try testDrawRanges(9, 15, 2, 1)) diff = true;
+
+ try std.testing.expect(!diff); // There should be no diffs from reference.
+}
+
+// test "sprite face print all sprites" {
+// std.debug.print("\n\n", .{});
+// inline for (ranges) |range| {
+// for (range.min..range.max + 1) |cp| {
+// std.debug.print("{u}", .{ @as(u21, @intCast(cp)) });
+// }
+// }
+// std.debug.print("\n\n", .{});
+// }
+
+test {
+ std.testing.refAllDecls(@This());
+}
diff --git a/vendor/libghostty-vt/src/font/sprite/canvas.zig b/vendor/libghostty-vt/src/font/sprite/canvas.zig
new file mode 100644
index 00000000..7904f20a
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/canvas.zig
@@ -0,0 +1,512 @@
+//! This exposes primitives to draw 2D graphics and export the graphic to
+//! a font atlas.
+const std = @import("std");
+const assert = @import("../../quirks.zig").inlineAssert;
+const Allocator = std.mem.Allocator;
+const z2d = @import("z2d");
+const font = @import("../main.zig");
+
+pub fn Point(comptime T: type) type {
+ return struct {
+ x: T,
+ y: T,
+ };
+}
+
+pub fn Line(comptime T: type) type {
+ return struct {
+ p0: Point(T),
+ p1: Point(T),
+ };
+}
+
+pub fn Box(comptime T: type) type {
+ return struct {
+ p0: Point(T),
+ p1: Point(T),
+
+ pub fn rect(self: Box(T)) Rect(T) {
+ const tl_x = @min(self.p0.x, self.p1.x);
+ const tl_y = @min(self.p0.y, self.p1.y);
+ const br_x = @max(self.p0.x, self.p1.x);
+ const br_y = @max(self.p0.y, self.p1.y);
+
+ return .{
+ .x = tl_x,
+ .y = tl_y,
+ .width = br_x - tl_x,
+ .height = br_y - tl_y,
+ };
+ }
+ };
+}
+
+pub fn Rect(comptime T: type) type {
+ return struct {
+ x: T,
+ y: T,
+ width: T,
+ height: T,
+ };
+}
+
+pub fn Triangle(comptime T: type) type {
+ return struct {
+ p0: Point(T),
+ p1: Point(T),
+ p2: Point(T),
+ };
+}
+
+pub fn Quad(comptime T: type) type {
+ return struct {
+ p0: Point(T),
+ p1: Point(T),
+ p2: Point(T),
+ p3: Point(T),
+ };
+}
+
+/// We only use alpha-channel so a pixel can only be "on" or "off".
+pub const Color = enum(u8) {
+ on = 255,
+ off = 0,
+ _,
+};
+
+/// This is a managed struct, it keeps a reference to the allocator that is
+/// used to initialize it, and the same allocator is used for any further
+/// necessary allocations when drawing.
+pub const Canvas = struct {
+ /// The underlying z2d surface.
+ sfc: z2d.Surface,
+
+ padding_x: u32,
+ padding_y: u32,
+
+ clip_top: u32 = 0,
+ clip_left: u32 = 0,
+ clip_right: u32 = 0,
+ clip_bottom: u32 = 0,
+
+ alloc: Allocator,
+
+ pub fn init(
+ alloc: Allocator,
+ width: u32,
+ height: u32,
+ padding_x: u32,
+ padding_y: u32,
+ ) !Canvas {
+ // Create the surface we'll be using.
+ // We add padding to both sides (hence `2 *`)
+ const sfc = try z2d.Surface.initPixel(
+ .{ .alpha8 = .{ .a = 0 } },
+ alloc,
+ @intCast(width + 2 * padding_x),
+ @intCast(height + 2 * padding_y),
+ );
+ errdefer sfc.deinit(alloc);
+
+ return .{
+ .sfc = sfc,
+ .padding_x = padding_x,
+ .padding_y = padding_y,
+ .alloc = alloc,
+ };
+ }
+
+ pub fn deinit(self: *Canvas) void {
+ self.sfc.deinit(self.alloc);
+ self.* = undefined;
+ }
+
+ /// Write the data in this drawing to the atlas.
+ pub fn writeAtlas(
+ self: *Canvas,
+ alloc: Allocator,
+ atlas: *font.Atlas,
+ ) (Allocator.Error || font.Atlas.Error)!font.Atlas.Region {
+ assert(atlas.format == .grayscale);
+
+ self.trim();
+
+ const sfc_width: u32 = @intCast(self.sfc.getWidth());
+ const sfc_height: u32 = @intCast(self.sfc.getHeight());
+
+ // Subtract our clip margins from the
+ // width and height to get region size.
+ const region_width = sfc_width -| self.clip_left -| self.clip_right;
+ const region_height = sfc_height -| self.clip_top -| self.clip_bottom;
+
+ // Allocate our texture atlas region
+ const region = try atlas.reserve(alloc, region_width, region_height);
+
+ if (region.width > 0 and region.height > 0) {
+ const buffer: []u8 = @ptrCast(self.sfc.image_surface_alpha8.buf);
+
+ // Write the glyph information into the atlas
+ assert(region.width == region_width);
+ assert(region.height == region_height);
+ atlas.setFromLarger(
+ region,
+ buffer,
+ sfc_width,
+ self.clip_left,
+ self.clip_top,
+ );
+ }
+
+ return region;
+ }
+
+ // Adjust clip boundaries to trim off any fully transparent rows or columns.
+ // This circumvents abstractions from z2d so that it can be performant.
+ fn trim(self: *Canvas) void {
+ const width: u32 = @intCast(self.sfc.getWidth());
+ const height: u32 = @intCast(self.sfc.getHeight());
+
+ const buf = std.mem.sliceAsBytes(self.sfc.image_surface_alpha8.buf);
+
+ top: while (self.clip_top < height - self.clip_bottom) {
+ const y = self.clip_top;
+ const x0 = self.clip_left;
+ const x1 = width - self.clip_right;
+ for (buf[y * width ..][x0..x1]) |v| {
+ if (v != 0) break :top;
+ }
+ self.clip_top += 1;
+ }
+
+ bottom: while (self.clip_bottom < height - self.clip_top) {
+ const y = height - self.clip_bottom -| 1;
+ const x0 = self.clip_left;
+ const x1 = width - self.clip_right;
+ for (buf[y * width ..][x0..x1]) |v| {
+ if (v != 0) break :bottom;
+ }
+ self.clip_bottom += 1;
+ }
+
+ left: while (self.clip_left < width - self.clip_right) {
+ const x = self.clip_left;
+ const y0 = self.clip_top;
+ const y1 = height - self.clip_bottom;
+ for (y0..y1) |y| {
+ if (buf[y * width + x] != 0) break :left;
+ }
+ self.clip_left += 1;
+ }
+
+ right: while (self.clip_right < width - self.clip_left) {
+ const x = width - self.clip_right -| 1;
+ const y0 = self.clip_top;
+ const y1 = height - self.clip_bottom;
+ for (y0..y1) |y| {
+ if (buf[y * width + x] != 0) break :right;
+ }
+ self.clip_right += 1;
+ }
+ }
+
+ /// Only really useful for test purposes, since the clipping region is
+ /// automatically excluded when writing to an atlas with `writeAtlas`.
+ pub fn clearClippingRegions(self: *Canvas) void {
+ const buf = std.mem.sliceAsBytes(self.sfc.image_surface_alpha8.buf);
+ const width: usize = @intCast(self.sfc.getWidth());
+ const height: usize = @intCast(self.sfc.getHeight());
+
+ for (0..height) |y| {
+ for (0..self.clip_left) |x| {
+ buf[y * width + x] = 0;
+ }
+ }
+
+ for (0..height) |y| {
+ for (width - self.clip_right..width) |x| {
+ buf[y * width + x] = 0;
+ }
+ }
+
+ for (0..self.clip_top) |y| {
+ for (0..width) |x| {
+ buf[y * width + x] = 0;
+ }
+ }
+
+ for (height - self.clip_bottom..height) |y| {
+ for (0..width) |x| {
+ buf[y * width + x] = 0;
+ }
+ }
+ }
+
+ /// Return a transformation representing the translation for our padding.
+ pub fn transformation(self: Canvas) z2d.Transformation {
+ return .{
+ .ax = 1,
+ .by = 0,
+ .cx = 0,
+ .dy = 1,
+ .tx = @as(f64, @floatFromInt(self.padding_x)),
+ .ty = @as(f64, @floatFromInt(self.padding_y)),
+ };
+ }
+
+ /// Acquires a z2d drawing context, caller MUST deinit context.
+ pub fn getContext(self: *Canvas) z2d.Context {
+ var ctx = z2d.Context.init(self.alloc, &self.sfc);
+ // Offset by our padding to keep
+ // coordinates relative to the cell.
+ ctx.setTransformation(self.transformation());
+ return ctx;
+ }
+
+ /// Draw and fill a single pixel
+ pub fn pixel(self: *Canvas, x: i32, y: i32, color: Color) void {
+ self.sfc.putPixel(
+ x + @as(i32, @intCast(self.padding_x)),
+ y + @as(i32, @intCast(self.padding_y)),
+ .{ .alpha8 = .{ .a = @intFromEnum(color) } },
+ );
+ }
+
+ /// Draw and fill a rectangle. This is the main primitive for drawing
+ /// lines as well (which are just generally skinny rectangles...)
+ pub fn rect(self: *Canvas, v: Rect(i32), color: Color) void {
+ var y = v.y;
+ while (y < v.y + v.height) : (y += 1) {
+ var x = v.x;
+ while (x < v.x + v.width) : (x += 1) {
+ self.pixel(
+ @intCast(x),
+ @intCast(y),
+ color,
+ );
+ }
+ }
+ }
+
+ /// Convenience wrapper for `Canvas.rect`
+ pub fn box(
+ self: *Canvas,
+ x0: i32,
+ y0: i32,
+ x1: i32,
+ y1: i32,
+ color: Color,
+ ) void {
+ self.rect((Box(i32){
+ .p0 = .{ .x = x0, .y = y0 },
+ .p1 = .{ .x = x1, .y = y1 },
+ }).rect(), color);
+ }
+
+ /// Draw and fill a quad.
+ pub fn quad(self: *Canvas, q: Quad(f64), color: Color) !void {
+ var path = self.staticPath(6); // nodes.len = 0
+ path.moveTo(q.p0.x, q.p0.y); // +1, nodes.len = 1
+ path.lineTo(q.p1.x, q.p1.y); // +1, nodes.len = 2
+ path.lineTo(q.p2.x, q.p2.y); // +1, nodes.len = 3
+ path.lineTo(q.p3.x, q.p3.y); // +1, nodes.len = 4
+ path.close(); // +2, nodes.len = 6
+ try self.fillPath(path.wrapped_path, .{}, color);
+ }
+
+ /// Draw and fill a triangle.
+ pub fn triangle(self: *Canvas, t: Triangle(f64), color: Color) !void {
+ var path = self.staticPath(5); // nodes.len = 0
+ path.moveTo(t.p0.x, t.p0.y); // +1, nodes.len = 1
+ path.lineTo(t.p1.x, t.p1.y); // +1, nodes.len = 2
+ path.lineTo(t.p2.x, t.p2.y); // +1, nodes.len = 3
+ path.close(); // +2, nodes.len = 5
+ try self.fillPath(path.wrapped_path, .{}, color);
+ }
+
+ /// Stroke a line.
+ pub fn line(
+ self: *Canvas,
+ l: Line(f64),
+ thickness: f64,
+ color: Color,
+ ) !void {
+ var path = self.staticPath(2); // nodes.len = 0
+ path.moveTo(l.p0.x, l.p0.y); // +1, nodes.len = 1
+ path.lineTo(l.p1.x, l.p1.y); // +1, nodes.len = 2
+ try self.strokePath(
+ path.wrapped_path,
+ .{
+ .line_cap_mode = .butt,
+ .line_width = thickness,
+ },
+ color,
+ );
+ }
+
+ /// Create a static path of the provided len and initialize it.
+ /// Use this function instead of making the path manually since
+ /// it ensures that the transform is applied.
+ pub inline fn staticPath(
+ self: *Canvas,
+ comptime len: usize,
+ ) z2d.StaticPath(len) {
+ var path: z2d.StaticPath(len) = .{};
+ path.init();
+ path.wrapped_path.transformation = self.transformation();
+ return path;
+ }
+
+ /// Stroke a z2d path.
+ pub fn strokePath(
+ self: *Canvas,
+ path: z2d.Path,
+ opts: z2d.painter.StrokeOptions,
+ color: Color,
+ ) z2d.painter.StrokeError!void {
+ try z2d.painter.stroke(
+ self.alloc,
+ &self.sfc,
+ &.{ .opaque_pattern = .{
+ .pixel = .{ .alpha8 = .{ .a = @intFromEnum(color) } },
+ } },
+ path.nodes.items,
+ opts,
+ );
+ }
+
+ /// Do an inner stroke on a z2d path, right now this involves a pretty
+ /// heavy workaround that uses two extra surfaces; in the future, z2d
+ /// should add inner and outer strokes natively.
+ pub fn innerStrokePath(
+ self: *Canvas,
+ path: z2d.Path,
+ opts: z2d.painter.StrokeOptions,
+ color: Color,
+ ) (z2d.painter.StrokeError || z2d.painter.FillError)!void {
+ // On one surface we fill the shape, this will be a mask we
+ // multiply with the double-width stroke so that only the
+ // part inside is used.
+ var fill_sfc: z2d.Surface = try .init(
+ .image_surface_alpha8,
+ self.alloc,
+ self.sfc.getWidth(),
+ self.sfc.getHeight(),
+ );
+ defer fill_sfc.deinit(self.alloc);
+
+ // On the other we'll do the double width stroke.
+ var stroke_sfc: z2d.Surface = try .init(
+ .image_surface_alpha8,
+ self.alloc,
+ self.sfc.getWidth(),
+ self.sfc.getHeight(),
+ );
+ defer stroke_sfc.deinit(self.alloc);
+
+ // Make a closed version of the path for our fill, so
+ // that we can support open paths for inner stroke.
+ var closed_path = path;
+ closed_path.nodes = try path.nodes.clone(self.alloc);
+ defer closed_path.deinit(self.alloc);
+ try closed_path.close(self.alloc);
+
+ // Fill the shape in white to the fill surface, we use
+ // white because this is a mask that we'll multiply with
+ // the stroke, we want everything inside to be the stroke
+ // color.
+ try z2d.painter.fill(
+ self.alloc,
+ &fill_sfc,
+ &.{ .opaque_pattern = .{
+ .pixel = .{ .alpha8 = .{ .a = 255 } },
+ } },
+ closed_path.nodes.items,
+ .{},
+ );
+
+ // Stroke the shape with double the desired width.
+ var mut_opts = opts;
+ mut_opts.line_width *= 2;
+ try z2d.painter.stroke(
+ self.alloc,
+ &stroke_sfc,
+ &.{ .opaque_pattern = .{
+ .pixel = .{ .alpha8 = .{ .a = @intFromEnum(color) } },
+ } },
+ path.nodes.items,
+ mut_opts,
+ );
+
+ // We multiply the stroke sfc on to the fill surface.
+ // The z2d composite operation doesn't seem to work for
+ // this with alpha8 surfaces, so we have to do it manually.
+ for (
+ std.mem.sliceAsBytes(fill_sfc.image_surface_alpha8.buf),
+ std.mem.sliceAsBytes(stroke_sfc.image_surface_alpha8.buf),
+ ) |*d, s| {
+ d.* = @intFromFloat(@round(
+ 255.0 *
+ (@as(f64, @floatFromInt(s)) / 255.0) *
+ (@as(f64, @floatFromInt(d.*)) / 255.0),
+ ));
+ }
+
+ // Then we composite the result on to the main surface.
+ self.sfc.composite(&fill_sfc, .src_over, 0, 0, .{});
+ }
+
+ /// Fill a z2d path.
+ pub fn fillPath(
+ self: *Canvas,
+ path: z2d.Path,
+ opts: z2d.painter.FillOptions,
+ color: Color,
+ ) z2d.painter.FillError!void {
+ try z2d.painter.fill(
+ self.alloc,
+ &self.sfc,
+ &.{ .opaque_pattern = .{
+ .pixel = .{ .alpha8 = .{ .a = @intFromEnum(color) } },
+ } },
+ path.nodes.items,
+ opts,
+ );
+ }
+
+ /// Invert all pixels on the canvas.
+ pub fn invert(self: *Canvas) void {
+ for (std.mem.sliceAsBytes(self.sfc.image_surface_alpha8.buf)) |*v| {
+ v.* = 255 - v.*;
+ }
+ }
+
+ /// Mirror the canvas horizontally.
+ pub fn flipHorizontal(self: *Canvas) Allocator.Error!void {
+ const buf = std.mem.sliceAsBytes(self.sfc.image_surface_alpha8.buf);
+ const clone = try self.alloc.dupe(u8, buf);
+ defer self.alloc.free(clone);
+ const width: usize = @intCast(self.sfc.getWidth());
+ const height: usize = @intCast(self.sfc.getHeight());
+ for (0..height) |y| {
+ for (0..width) |x| {
+ buf[y * width + x] = clone[y * width + width - x - 1];
+ }
+ }
+ std.mem.swap(u32, &self.clip_left, &self.clip_right);
+ }
+
+ /// Mirror the canvas vertically.
+ pub fn flipVertical(self: *Canvas) Allocator.Error!void {
+ const buf = std.mem.sliceAsBytes(self.sfc.image_surface_alpha8.buf);
+ const clone = try self.alloc.dupe(u8, buf);
+ defer self.alloc.free(clone);
+ const width: usize = @intCast(self.sfc.getWidth());
+ const height: usize = @intCast(self.sfc.getHeight());
+ for (0..height) |y| {
+ for (0..width) |x| {
+ buf[y * width + x] = clone[(height - y - 1) * width + x];
+ }
+ }
+ std.mem.swap(u32, &self.clip_top, &self.clip_bottom);
+ }
+};
diff --git a/vendor/libghostty-vt/src/font/sprite/draw/README.md b/vendor/libghostty-vt/src/font/sprite/draw/README.md
new file mode 100644
index 00000000..d1603599
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/draw/README.md
@@ -0,0 +1,55 @@
+# This is a _special_ directory.
+
+The files in this directory are imported by `../Face.zig` and scanned for pub
+functions with names matching a specific format, which are then used to handle
+drawing specified codepoints.
+
+## IMPORTANT
+
+When you add a new file here, you need to add the corresponding import in
+`../Face.zig` for its draw functions to be picked up. I tried dynamically
+listing these files to do this automatically but it was more pain than it
+was worth.
+
+## `draw*` functions
+
+Any function named `draw<CODEPOINT>` or `draw<MIN>_<MAX>` will be used to
+draw the codepoint or range of codepoints specified in the name. These are
+hex-encoded values with upper case letters.
+
+`draw*` functions are provided with these arguments:
+
+```zig
+/// The codepoint being drawn. For single-codepoint draw functions this can
+/// just be discarded, but it's needed for range draw functions to determine
+/// which value in the range needs to be drawn.
+cp: u32,
+/// The canvas on which to draw the codepoint.
+////
+/// This canvas has been prepared with an extra quarter of the width/height on
+/// each edge, and its transform has been set so that [0, 0] is still the upper
+/// left of the cell and [width, height] is still the bottom right; in order to
+/// draw above or to the left, use negative values, and to draw below or to the
+/// right use values greater than the width or the height.
+///
+/// Because the canvas has been prepared this way, it's possible to draw glyphs
+/// that exit the cell bounds by some amount- an example of when this is useful
+/// is in drawing box-drawing diagonals, with enough overlap so that they can
+/// seamlessly connect across corners of cells.
+canvas: *font.sprite.Canvas,
+/// The width of the cell to draw for.
+width: u32,
+/// The height of the cell to draw for.
+height: u32,
+/// The font grid metrics.
+metrics: font.Metrics,
+```
+
+`draw*` functions may only return `DrawFnError!void` (defined in `../Face.zig`).
+
+## `special.zig`
+
+The functions in `special.zig` are not for drawing unicode codepoints,
+rather their names match the enum tag names in the `Sprite` enum from
+`src/font/sprite.zig`. They are called with the same arguments as the
+other `draw*` functions.
diff --git a/vendor/libghostty-vt/src/font/sprite/draw/block.zig b/vendor/libghostty-vt/src/font/sprite/draw/block.zig
new file mode 100644
index 00000000..1731d2f5
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/draw/block.zig
@@ -0,0 +1,177 @@
+//! Block Elements | U+2580...U+259F
+//! https://en.wikipedia.org/wiki/Block_Elements
+//!
+//! ▀▁▂▃▄▅▆▇█▉▊▋▌▍▎▏
+//! ▐░▒▓▔▕▖▗▘▙▚▛▜▝▞▟
+//!
+
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+
+const common = @import("common.zig");
+const Shade = common.Shade;
+const Quads = common.Quads;
+const Alignment = common.Alignment;
+const fill = common.fill;
+
+const font = @import("../../main.zig");
+
+// Utility names for common fractions
+const one_eighth: f64 = 0.125;
+const one_quarter: f64 = 0.25;
+const one_third: f64 = (1.0 / 3.0);
+const three_eighths: f64 = 0.375;
+const half: f64 = 0.5;
+const five_eighths: f64 = 0.625;
+const two_thirds: f64 = (2.0 / 3.0);
+const three_quarters: f64 = 0.75;
+const seven_eighths: f64 = 0.875;
+
+pub fn draw2580_259F(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = width;
+ _ = height;
+
+ switch (cp) {
+ // '▀' UPPER HALF BLOCK
+ 0x2580 => block(metrics, canvas, .upper, 1, half),
+ // '▁' LOWER ONE EIGHTH BLOCK
+ 0x2581 => block(metrics, canvas, .lower, 1, one_eighth),
+ // '▂' LOWER ONE QUARTER BLOCK
+ 0x2582 => block(metrics, canvas, .lower, 1, one_quarter),
+ // '▃' LOWER THREE EIGHTHS BLOCK
+ 0x2583 => block(metrics, canvas, .lower, 1, three_eighths),
+ // '▄' LOWER HALF BLOCK
+ 0x2584 => block(metrics, canvas, .lower, 1, half),
+ // '▅' LOWER FIVE EIGHTHS BLOCK
+ 0x2585 => block(metrics, canvas, .lower, 1, five_eighths),
+ // '▆' LOWER THREE QUARTERS BLOCK
+ 0x2586 => block(metrics, canvas, .lower, 1, three_quarters),
+ // '▇' LOWER SEVEN EIGHTHS BLOCK
+ 0x2587 => block(metrics, canvas, .lower, 1, seven_eighths),
+ // '█' FULL BLOCK
+ 0x2588 => fullBlockShade(metrics, canvas, .on),
+ // '▉' LEFT SEVEN EIGHTHS BLOCK
+ 0x2589 => block(metrics, canvas, .left, seven_eighths, 1),
+ // '▊' LEFT THREE QUARTERS BLOCK
+ 0x258a => block(metrics, canvas, .left, three_quarters, 1),
+ // '▋' LEFT FIVE EIGHTHS BLOCK
+ 0x258b => block(metrics, canvas, .left, five_eighths, 1),
+ // '▌' LEFT HALF BLOCK
+ 0x258c => block(metrics, canvas, .left, half, 1),
+ // '▍' LEFT THREE EIGHTHS BLOCK
+ 0x258d => block(metrics, canvas, .left, three_eighths, 1),
+ // '▎' LEFT ONE QUARTER BLOCK
+ 0x258e => block(metrics, canvas, .left, one_quarter, 1),
+ // '▏' LEFT ONE EIGHTH BLOCK
+ 0x258f => block(metrics, canvas, .left, one_eighth, 1),
+
+ // '▐' RIGHT HALF BLOCK
+ 0x2590 => block(metrics, canvas, .right, half, 1),
+ // '░'
+ 0x2591 => fullBlockShade(metrics, canvas, .light),
+ // '▒'
+ 0x2592 => fullBlockShade(metrics, canvas, .medium),
+ // '▓'
+ 0x2593 => fullBlockShade(metrics, canvas, .dark),
+ // '▔' UPPER ONE EIGHTH BLOCK
+ 0x2594 => block(metrics, canvas, .upper, 1, one_eighth),
+ // '▕' RIGHT ONE EIGHTH BLOCK
+ 0x2595 => block(metrics, canvas, .right, one_eighth, 1),
+ // '▖'
+ 0x2596 => quadrant(metrics, canvas, .{ .bl = true }),
+ // '▗'
+ 0x2597 => quadrant(metrics, canvas, .{ .br = true }),
+ // '▘'
+ 0x2598 => quadrant(metrics, canvas, .{ .tl = true }),
+ // '▙'
+ 0x2599 => quadrant(metrics, canvas, .{ .tl = true, .bl = true, .br = true }),
+ // '▚'
+ 0x259a => quadrant(metrics, canvas, .{ .tl = true, .br = true }),
+ // '▛'
+ 0x259b => quadrant(metrics, canvas, .{ .tl = true, .tr = true, .bl = true }),
+ // '▜'
+ 0x259c => quadrant(metrics, canvas, .{ .tl = true, .tr = true, .br = true }),
+ // '▝'
+ 0x259d => quadrant(metrics, canvas, .{ .tr = true }),
+ // '▞'
+ 0x259e => quadrant(metrics, canvas, .{ .tr = true, .bl = true }),
+ // '▟'
+ 0x259f => quadrant(metrics, canvas, .{ .tr = true, .bl = true, .br = true }),
+
+ else => unreachable,
+ }
+}
+
+pub fn block(
+ metrics: font.Metrics,
+ canvas: *font.sprite.Canvas,
+ comptime alignment: Alignment,
+ comptime width: f64,
+ comptime height: f64,
+) void {
+ blockShade(metrics, canvas, alignment, width, height, .on);
+}
+
+pub fn blockShade(
+ metrics: font.Metrics,
+ canvas: *font.sprite.Canvas,
+ comptime alignment: Alignment,
+ comptime width: f64,
+ comptime height: f64,
+ comptime shade: Shade,
+) void {
+ const float_width: f64 = @floatFromInt(metrics.cell_width);
+ const float_height: f64 = @floatFromInt(metrics.cell_height);
+
+ const w: u32 = @intFromFloat(@round(float_width * width));
+ const h: u32 = @intFromFloat(@round(float_height * height));
+
+ const x = switch (alignment.horizontal) {
+ .left => 0,
+ .right => metrics.cell_width - w,
+ .center => (metrics.cell_width - w) / 2,
+ };
+ const y = switch (alignment.vertical) {
+ .top => 0,
+ .bottom => metrics.cell_height - h,
+ .middle => (metrics.cell_height - h) / 2,
+ };
+
+ canvas.rect(.{
+ .x = @intCast(x),
+ .y = @intCast(y),
+ .width = @intCast(w),
+ .height = @intCast(h),
+ }, @as(font.sprite.Color, @enumFromInt(@intFromEnum(shade))));
+}
+
+pub fn fullBlockShade(
+ metrics: font.Metrics,
+ canvas: *font.sprite.Canvas,
+ shade: Shade,
+) void {
+ canvas.box(
+ 0,
+ 0,
+ @intCast(metrics.cell_width),
+ @intCast(metrics.cell_height),
+ @as(font.sprite.Color, @enumFromInt(@intFromEnum(shade))),
+ );
+}
+
+fn quadrant(
+ metrics: font.Metrics,
+ canvas: *font.sprite.Canvas,
+ comptime quads: Quads,
+) void {
+ if (quads.tl) fill(metrics, canvas, .zero, .half, .zero, .half);
+ if (quads.tr) fill(metrics, canvas, .half, .full, .zero, .half);
+ if (quads.bl) fill(metrics, canvas, .zero, .half, .half, .full);
+ if (quads.br) fill(metrics, canvas, .half, .full, .half, .full);
+}
diff --git a/vendor/libghostty-vt/src/font/sprite/draw/box.zig b/vendor/libghostty-vt/src/font/sprite/draw/box.zig
new file mode 100644
index 00000000..da3edb51
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/draw/box.zig
@@ -0,0 +1,929 @@
+//! Box Drawing | U+2500...U+257F
+//! https://en.wikipedia.org/wiki/Box_Drawing
+//!
+//! ─━│┃┄┅┆┇┈┉┊┋┌┍┎┏
+//! ┐┑┒┓└┕┖┗┘┙┚┛├┝┞┟
+//! ┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯
+//! ┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿
+//! ╀╁╂╃╄╅╆╇╈╉╊╋╌╍╎╏
+//! ═║╒╓╔╕╖╗╘╙╚╛╜╝╞╟
+//! ╠╡╢╣╤╥╦╧╨╩╪╫╬╭╮╯
+//! ╰╱╲╳╴╵╶╷╸╹╺╻╼╽╾╿
+//!
+
+const std = @import("std");
+const assert = @import("../../../quirks.zig").inlineAssert;
+const Allocator = std.mem.Allocator;
+
+const common = @import("common.zig");
+const Thickness = common.Thickness;
+const Shade = common.Shade;
+const Quads = common.Quads;
+const Corner = common.Corner;
+const Edge = common.Edge;
+const Alignment = common.Alignment;
+const hline = common.hline;
+const vline = common.vline;
+const hlineMiddle = common.hlineMiddle;
+const vlineMiddle = common.vlineMiddle;
+
+const font = @import("../../main.zig");
+
+/// Specification of a traditional intersection-style line/box-drawing char,
+/// which can have a different style of line from each edge to the center.
+pub const Lines = packed struct(u8) {
+ up: Style = .none,
+ right: Style = .none,
+ down: Style = .none,
+ left: Style = .none,
+
+ const Style = enum(u2) {
+ none,
+ light,
+ heavy,
+ double,
+ };
+};
+
+pub fn draw2500_257F(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = width;
+ _ = height;
+
+ switch (cp) {
+ // '─'
+ 0x2500 => linesChar(metrics, canvas, .{ .left = .light, .right = .light }),
+ // '━'
+ 0x2501 => linesChar(metrics, canvas, .{ .left = .heavy, .right = .heavy }),
+ // '│'
+ 0x2502 => linesChar(metrics, canvas, .{ .up = .light, .down = .light }),
+ // '┃'
+ 0x2503 => linesChar(metrics, canvas, .{ .up = .heavy, .down = .heavy }),
+ // '┄'
+ 0x2504 => dashHorizontal(
+ metrics,
+ canvas,
+ 3,
+ Thickness.light.height(metrics.box_thickness),
+ @max(4, Thickness.light.height(metrics.box_thickness)),
+ ),
+ // '┅'
+ 0x2505 => dashHorizontal(
+ metrics,
+ canvas,
+ 3,
+ Thickness.heavy.height(metrics.box_thickness),
+ @max(4, Thickness.light.height(metrics.box_thickness)),
+ ),
+ // '┆'
+ 0x2506 => dashVertical(
+ metrics,
+ canvas,
+ 3,
+ Thickness.light.height(metrics.box_thickness),
+ @max(4, Thickness.light.height(metrics.box_thickness)),
+ ),
+ // '┇'
+ 0x2507 => dashVertical(
+ metrics,
+ canvas,
+ 3,
+ Thickness.heavy.height(metrics.box_thickness),
+ @max(4, Thickness.light.height(metrics.box_thickness)),
+ ),
+ // '┈'
+ 0x2508 => dashHorizontal(
+ metrics,
+ canvas,
+ 4,
+ Thickness.light.height(metrics.box_thickness),
+ @max(4, Thickness.light.height(metrics.box_thickness)),
+ ),
+ // '┉'
+ 0x2509 => dashHorizontal(
+ metrics,
+ canvas,
+ 4,
+ Thickness.heavy.height(metrics.box_thickness),
+ @max(4, Thickness.light.height(metrics.box_thickness)),
+ ),
+ // '┊'
+ 0x250a => dashVertical(
+ metrics,
+ canvas,
+ 4,
+ Thickness.light.height(metrics.box_thickness),
+ @max(4, Thickness.light.height(metrics.box_thickness)),
+ ),
+ // '┋'
+ 0x250b => dashVertical(
+ metrics,
+ canvas,
+ 4,
+ Thickness.heavy.height(metrics.box_thickness),
+ @max(4, Thickness.light.height(metrics.box_thickness)),
+ ),
+ // '┌'
+ 0x250c => linesChar(metrics, canvas, .{ .down = .light, .right = .light }),
+ // '┍'
+ 0x250d => linesChar(metrics, canvas, .{ .down = .light, .right = .heavy }),
+ // '┎'
+ 0x250e => linesChar(metrics, canvas, .{ .down = .heavy, .right = .light }),
+ // '┏'
+ 0x250f => linesChar(metrics, canvas, .{ .down = .heavy, .right = .heavy }),
+
+ // '┐'
+ 0x2510 => linesChar(metrics, canvas, .{ .down = .light, .left = .light }),
+ // '┑'
+ 0x2511 => linesChar(metrics, canvas, .{ .down = .light, .left = .heavy }),
+ // '┒'
+ 0x2512 => linesChar(metrics, canvas, .{ .down = .heavy, .left = .light }),
+ // '┓'
+ 0x2513 => linesChar(metrics, canvas, .{ .down = .heavy, .left = .heavy }),
+ // '└'
+ 0x2514 => linesChar(metrics, canvas, .{ .up = .light, .right = .light }),
+ // '┕'
+ 0x2515 => linesChar(metrics, canvas, .{ .up = .light, .right = .heavy }),
+ // '┖'
+ 0x2516 => linesChar(metrics, canvas, .{ .up = .heavy, .right = .light }),
+ // '┗'
+ 0x2517 => linesChar(metrics, canvas, .{ .up = .heavy, .right = .heavy }),
+ // '┘'
+ 0x2518 => linesChar(metrics, canvas, .{ .up = .light, .left = .light }),
+ // '┙'
+ 0x2519 => linesChar(metrics, canvas, .{ .up = .light, .left = .heavy }),
+ // '┚'
+ 0x251a => linesChar(metrics, canvas, .{ .up = .heavy, .left = .light }),
+ // '┛'
+ 0x251b => linesChar(metrics, canvas, .{ .up = .heavy, .left = .heavy }),
+ // '├'
+ 0x251c => linesChar(metrics, canvas, .{ .up = .light, .down = .light, .right = .light }),
+ // '┝'
+ 0x251d => linesChar(metrics, canvas, .{ .up = .light, .down = .light, .right = .heavy }),
+ // '┞'
+ 0x251e => linesChar(metrics, canvas, .{ .up = .heavy, .right = .light, .down = .light }),
+ // '┟'
+ 0x251f => linesChar(metrics, canvas, .{ .down = .heavy, .right = .light, .up = .light }),
+
+ // '┠'
+ 0x2520 => linesChar(metrics, canvas, .{ .up = .heavy, .down = .heavy, .right = .light }),
+ // '┡'
+ 0x2521 => linesChar(metrics, canvas, .{ .down = .light, .right = .heavy, .up = .heavy }),
+ // '┢'
+ 0x2522 => linesChar(metrics, canvas, .{ .up = .light, .right = .heavy, .down = .heavy }),
+ // '┣'
+ 0x2523 => linesChar(metrics, canvas, .{ .up = .heavy, .down = .heavy, .right = .heavy }),
+ // '┤'
+ 0x2524 => linesChar(metrics, canvas, .{ .up = .light, .down = .light, .left = .light }),
+ // '┥'
+ 0x2525 => linesChar(metrics, canvas, .{ .up = .light, .down = .light, .left = .heavy }),
+ // '┦'
+ 0x2526 => linesChar(metrics, canvas, .{ .up = .heavy, .left = .light, .down = .light }),
+ // '┧'
+ 0x2527 => linesChar(metrics, canvas, .{ .down = .heavy, .left = .light, .up = .light }),
+ // '┨'
+ 0x2528 => linesChar(metrics, canvas, .{ .up = .heavy, .down = .heavy, .left = .light }),
+ // '┩'
+ 0x2529 => linesChar(metrics, canvas, .{ .down = .light, .left = .heavy, .up = .heavy }),
+ // '┪'
+ 0x252a => linesChar(metrics, canvas, .{ .up = .light, .left = .heavy, .down = .heavy }),
+ // '┫'
+ 0x252b => linesChar(metrics, canvas, .{ .up = .heavy, .down = .heavy, .left = .heavy }),
+ // '┬'
+ 0x252c => linesChar(metrics, canvas, .{ .down = .light, .left = .light, .right = .light }),
+ // '┭'
+ 0x252d => linesChar(metrics, canvas, .{ .left = .heavy, .right = .light, .down = .light }),
+ // '┮'
+ 0x252e => linesChar(metrics, canvas, .{ .right = .heavy, .left = .light, .down = .light }),
+ // '┯'
+ 0x252f => linesChar(metrics, canvas, .{ .down = .light, .left = .heavy, .right = .heavy }),
+
+ // '┰'
+ 0x2530 => linesChar(metrics, canvas, .{ .down = .heavy, .left = .light, .right = .light }),
+ // '┱'
+ 0x2531 => linesChar(metrics, canvas, .{ .right = .light, .left = .heavy, .down = .heavy }),
+ // '┲'
+ 0x2532 => linesChar(metrics, canvas, .{ .left = .light, .right = .heavy, .down = .heavy }),
+ // '┳'
+ 0x2533 => linesChar(metrics, canvas, .{ .down = .heavy, .left = .heavy, .right = .heavy }),
+ // '┴'
+ 0x2534 => linesChar(metrics, canvas, .{ .up = .light, .left = .light, .right = .light }),
+ // '┵'
+ 0x2535 => linesChar(metrics, canvas, .{ .left = .heavy, .right = .light, .up = .light }),
+ // '┶'
+ 0x2536 => linesChar(metrics, canvas, .{ .right = .heavy, .left = .light, .up = .light }),
+ // '┷'
+ 0x2537 => linesChar(metrics, canvas, .{ .up = .light, .left = .heavy, .right = .heavy }),
+ // '┸'
+ 0x2538 => linesChar(metrics, canvas, .{ .up = .heavy, .left = .light, .right = .light }),
+ // '┹'
+ 0x2539 => linesChar(metrics, canvas, .{ .right = .light, .left = .heavy, .up = .heavy }),
+ // '┺'
+ 0x253a => linesChar(metrics, canvas, .{ .left = .light, .right = .heavy, .up = .heavy }),
+ // '┻'
+ 0x253b => linesChar(metrics, canvas, .{ .up = .heavy, .left = .heavy, .right = .heavy }),
+ // '┼'
+ 0x253c => linesChar(metrics, canvas, .{ .up = .light, .down = .light, .left = .light, .right = .light }),
+ // '┽'
+ 0x253d => linesChar(metrics, canvas, .{ .left = .heavy, .right = .light, .up = .light, .down = .light }),
+ // '┾'
+ 0x253e => linesChar(metrics, canvas, .{ .right = .heavy, .left = .light, .up = .light, .down = .light }),
+ // '┿'
+ 0x253f => linesChar(metrics, canvas, .{ .up = .light, .down = .light, .left = .heavy, .right = .heavy }),
+
+ // '╀'
+ 0x2540 => linesChar(metrics, canvas, .{ .up = .heavy, .down = .light, .left = .light, .right = .light }),
+ // '╁'
+ 0x2541 => linesChar(metrics, canvas, .{ .down = .heavy, .up = .light, .left = .light, .right = .light }),
+ // '╂'
+ 0x2542 => linesChar(metrics, canvas, .{ .up = .heavy, .down = .heavy, .left = .light, .right = .light }),
+ // '╃'
+ 0x2543 => linesChar(metrics, canvas, .{ .left = .heavy, .up = .heavy, .right = .light, .down = .light }),
+ // '╄'
+ 0x2544 => linesChar(metrics, canvas, .{ .right = .heavy, .up = .heavy, .left = .light, .down = .light }),
+ // '╅'
+ 0x2545 => linesChar(metrics, canvas, .{ .left = .heavy, .down = .heavy, .right = .light, .up = .light }),
+ // '╆'
+ 0x2546 => linesChar(metrics, canvas, .{ .right = .heavy, .down = .heavy, .left = .light, .up = .light }),
+ // '╇'
+ 0x2547 => linesChar(metrics, canvas, .{ .down = .light, .up = .heavy, .left = .heavy, .right = .heavy }),
+ // '╈'
+ 0x2548 => linesChar(metrics, canvas, .{ .up = .light, .down = .heavy, .left = .heavy, .right = .heavy }),
+ // '╉'
+ 0x2549 => linesChar(metrics, canvas, .{ .right = .light, .left = .heavy, .up = .heavy, .down = .heavy }),
+ // '╊'
+ 0x254a => linesChar(metrics, canvas, .{ .left = .light, .right = .heavy, .up = .heavy, .down = .heavy }),
+ // '╋'
+ 0x254b => linesChar(metrics, canvas, .{ .up = .heavy, .down = .heavy, .left = .heavy, .right = .heavy }),
+ // '╌'
+ 0x254c => dashHorizontal(
+ metrics,
+ canvas,
+ 2,
+ Thickness.light.height(metrics.box_thickness),
+ Thickness.light.height(metrics.box_thickness),
+ ),
+ // '╍'
+ 0x254d => dashHorizontal(
+ metrics,
+ canvas,
+ 2,
+ Thickness.heavy.height(metrics.box_thickness),
+ Thickness.heavy.height(metrics.box_thickness),
+ ),
+ // '╎'
+ 0x254e => dashVertical(
+ metrics,
+ canvas,
+ 2,
+ Thickness.light.height(metrics.box_thickness),
+ Thickness.heavy.height(metrics.box_thickness),
+ ),
+ // '╏'
+ 0x254f => dashVertical(
+ metrics,
+ canvas,
+ 2,
+ Thickness.heavy.height(metrics.box_thickness),
+ Thickness.heavy.height(metrics.box_thickness),
+ ),
+
+ // '═'
+ 0x2550 => linesChar(metrics, canvas, .{ .left = .double, .right = .double }),
+ // '║'
+ 0x2551 => linesChar(metrics, canvas, .{ .up = .double, .down = .double }),
+ // '╒'
+ 0x2552 => linesChar(metrics, canvas, .{ .down = .light, .right = .double }),
+ // '╓'
+ 0x2553 => linesChar(metrics, canvas, .{ .down = .double, .right = .light }),
+ // '╔'
+ 0x2554 => linesChar(metrics, canvas, .{ .down = .double, .right = .double }),
+ // '╕'
+ 0x2555 => linesChar(metrics, canvas, .{ .down = .light, .left = .double }),
+ // '╖'
+ 0x2556 => linesChar(metrics, canvas, .{ .down = .double, .left = .light }),
+ // '╗'
+ 0x2557 => linesChar(metrics, canvas, .{ .down = .double, .left = .double }),
+ // '╘'
+ 0x2558 => linesChar(metrics, canvas, .{ .up = .light, .right = .double }),
+ // '╙'
+ 0x2559 => linesChar(metrics, canvas, .{ .up = .double, .right = .light }),
+ // '╚'
+ 0x255a => linesChar(metrics, canvas, .{ .up = .double, .right = .double }),
+ // '╛'
+ 0x255b => linesChar(metrics, canvas, .{ .up = .light, .left = .double }),
+ // '╜'
+ 0x255c => linesChar(metrics, canvas, .{ .up = .double, .left = .light }),
+ // '╝'
+ 0x255d => linesChar(metrics, canvas, .{ .up = .double, .left = .double }),
+ // '╞'
+ 0x255e => linesChar(metrics, canvas, .{ .up = .light, .down = .light, .right = .double }),
+ // '╟'
+ 0x255f => linesChar(metrics, canvas, .{ .up = .double, .down = .double, .right = .light }),
+
+ // '╠'
+ 0x2560 => linesChar(metrics, canvas, .{ .up = .double, .down = .double, .right = .double }),
+ // '╡'
+ 0x2561 => linesChar(metrics, canvas, .{ .up = .light, .down = .light, .left = .double }),
+ // '╢'
+ 0x2562 => linesChar(metrics, canvas, .{ .up = .double, .down = .double, .left = .light }),
+ // '╣'
+ 0x2563 => linesChar(metrics, canvas, .{ .up = .double, .down = .double, .left = .double }),
+ // '╤'
+ 0x2564 => linesChar(metrics, canvas, .{ .down = .light, .left = .double, .right = .double }),
+ // '╥'
+ 0x2565 => linesChar(metrics, canvas, .{ .down = .double, .left = .light, .right = .light }),
+ // '╦'
+ 0x2566 => linesChar(metrics, canvas, .{ .down = .double, .left = .double, .right = .double }),
+ // '╧'
+ 0x2567 => linesChar(metrics, canvas, .{ .up = .light, .left = .double, .right = .double }),
+ // '╨'
+ 0x2568 => linesChar(metrics, canvas, .{ .up = .double, .left = .light, .right = .light }),
+ // '╩'
+ 0x2569 => linesChar(metrics, canvas, .{ .up = .double, .left = .double, .right = .double }),
+ // '╪'
+ 0x256a => linesChar(metrics, canvas, .{ .up = .light, .down = .light, .left = .double, .right = .double }),
+ // '╫'
+ 0x256b => linesChar(metrics, canvas, .{ .up = .double, .down = .double, .left = .light, .right = .light }),
+ // '╬'
+ 0x256c => linesChar(metrics, canvas, .{ .up = .double, .down = .double, .left = .double, .right = .double }),
+ // '╭'
+ 0x256d => try arc(metrics, canvas, .br, .light),
+ // '╮'
+ 0x256e => try arc(metrics, canvas, .bl, .light),
+ // '╯'
+ 0x256f => try arc(metrics, canvas, .tl, .light),
+
+ // '╰'
+ 0x2570 => try arc(metrics, canvas, .tr, .light),
+ // '╱'
+ 0x2571 => lightDiagonalUpperRightToLowerLeft(metrics, canvas),
+ // '╲'
+ 0x2572 => lightDiagonalUpperLeftToLowerRight(metrics, canvas),
+ // '╳'
+ 0x2573 => lightDiagonalCross(metrics, canvas),
+ // '╴'
+ 0x2574 => linesChar(metrics, canvas, .{ .left = .light }),
+ // '╵'
+ 0x2575 => linesChar(metrics, canvas, .{ .up = .light }),
+ // '╶'
+ 0x2576 => linesChar(metrics, canvas, .{ .right = .light }),
+ // '╷'
+ 0x2577 => linesChar(metrics, canvas, .{ .down = .light }),
+ // '╸'
+ 0x2578 => linesChar(metrics, canvas, .{ .left = .heavy }),
+ // '╹'
+ 0x2579 => linesChar(metrics, canvas, .{ .up = .heavy }),
+ // '╺'
+ 0x257a => linesChar(metrics, canvas, .{ .right = .heavy }),
+ // '╻'
+ 0x257b => linesChar(metrics, canvas, .{ .down = .heavy }),
+ // '╼'
+ 0x257c => linesChar(metrics, canvas, .{ .left = .light, .right = .heavy }),
+ // '╽'
+ 0x257d => linesChar(metrics, canvas, .{ .up = .light, .down = .heavy }),
+ // '╾'
+ 0x257e => linesChar(metrics, canvas, .{ .left = .heavy, .right = .light }),
+ // '╿'
+ 0x257f => linesChar(metrics, canvas, .{ .up = .heavy, .down = .light }),
+
+ else => unreachable,
+ }
+}
+
+pub fn linesChar(
+ metrics: font.Metrics,
+ canvas: *font.sprite.Canvas,
+ lines: Lines,
+) void {
+ const light_px = Thickness.light.height(metrics.box_thickness);
+ const heavy_px = Thickness.heavy.height(metrics.box_thickness);
+
+ // Top of light horizontal strokes
+ const h_light_top = (metrics.cell_height -| light_px) / 2;
+ // Bottom of light horizontal strokes
+ const h_light_bottom = h_light_top +| light_px;
+
+ // Top of heavy horizontal strokes
+ const h_heavy_top = (metrics.cell_height -| heavy_px) / 2;
+ // Bottom of heavy horizontal strokes
+ const h_heavy_bottom = h_heavy_top +| heavy_px;
+
+ // Top of the top doubled horizontal stroke (bottom is `h_light_top`)
+ const h_double_top = h_light_top -| light_px;
+ // Bottom of the bottom doubled horizontal stroke (top is `h_light_bottom`)
+ const h_double_bottom = h_light_bottom +| light_px;
+
+ // Left of light vertical strokes
+ const v_light_left = (metrics.cell_width -| light_px) / 2;
+ // Right of light vertical strokes
+ const v_light_right = v_light_left +| light_px;
+
+ // Left of heavy vertical strokes
+ const v_heavy_left = (metrics.cell_width -| heavy_px) / 2;
+ // Right of heavy vertical strokes
+ const v_heavy_right = v_heavy_left +| heavy_px;
+
+ // Left of the left doubled vertical stroke (right is `v_light_left`)
+ const v_double_left = v_light_left -| light_px;
+ // Right of the right doubled vertical stroke (left is `v_light_right`)
+ const v_double_right = v_light_right +| light_px;
+
+ // The bottom of the up line
+ const up_bottom = if (lines.left == .heavy or lines.right == .heavy)
+ h_heavy_bottom
+ else if (lines.left != lines.right or lines.down == lines.up)
+ if (lines.left == .double or lines.right == .double)
+ h_double_bottom
+ else
+ h_light_bottom
+ else if (lines.left == .none and lines.right == .none)
+ h_light_bottom
+ else
+ h_light_top;
+
+ // The top of the down line
+ const down_top = if (lines.left == .heavy or lines.right == .heavy)
+ h_heavy_top
+ else if (lines.left != lines.right or lines.up == lines.down)
+ if (lines.left == .double or lines.right == .double)
+ h_double_top
+ else
+ h_light_top
+ else if (lines.left == .none and lines.right == .none)
+ h_light_top
+ else
+ h_light_bottom;
+
+ // The right of the left line
+ const left_right = if (lines.up == .heavy or lines.down == .heavy)
+ v_heavy_right
+ else if (lines.up != lines.down or lines.left == lines.right)
+ if (lines.up == .double or lines.down == .double)
+ v_double_right
+ else
+ v_light_right
+ else if (lines.up == .none and lines.down == .none)
+ v_light_right
+ else
+ v_light_left;
+
+ // The left of the right line
+ const right_left = if (lines.up == .heavy or lines.down == .heavy)
+ v_heavy_left
+ else if (lines.up != lines.down or lines.right == lines.left)
+ if (lines.up == .double or lines.down == .double)
+ v_double_left
+ else
+ v_light_left
+ else if (lines.up == .none and lines.down == .none)
+ v_light_left
+ else
+ v_light_right;
+
+ switch (lines.up) {
+ .none => {},
+ .light => canvas.box(
+ @intCast(v_light_left),
+ 0,
+ @intCast(v_light_right),
+ @intCast(up_bottom),
+ .on,
+ ),
+ .heavy => canvas.box(
+ @intCast(v_heavy_left),
+ 0,
+ @intCast(v_heavy_right),
+ @intCast(up_bottom),
+ .on,
+ ),
+ .double => {
+ const left_bottom = if (lines.left == .double) h_light_top else up_bottom;
+ const right_bottom = if (lines.right == .double) h_light_top else up_bottom;
+
+ canvas.box(
+ @intCast(v_double_left),
+ 0,
+ @intCast(v_light_left),
+ @intCast(left_bottom),
+ .on,
+ );
+ canvas.box(
+ @intCast(v_light_right),
+ 0,
+ @intCast(v_double_right),
+ @intCast(right_bottom),
+ .on,
+ );
+ },
+ }
+
+ switch (lines.right) {
+ .none => {},
+ .light => canvas.box(
+ @intCast(right_left),
+ @intCast(h_light_top),
+ @intCast(metrics.cell_width),
+ @intCast(h_light_bottom),
+ .on,
+ ),
+ .heavy => canvas.box(
+ @intCast(right_left),
+ @intCast(h_heavy_top),
+ @intCast(metrics.cell_width),
+ @intCast(h_heavy_bottom),
+ .on,
+ ),
+ .double => {
+ const top_left = if (lines.up == .double) v_light_right else right_left;
+ const bottom_left = if (lines.down == .double) v_light_right else right_left;
+
+ canvas.box(
+ @intCast(top_left),
+ @intCast(h_double_top),
+ @intCast(metrics.cell_width),
+ @intCast(h_light_top),
+ .on,
+ );
+ canvas.box(
+ @intCast(bottom_left),
+ @intCast(h_light_bottom),
+ @intCast(metrics.cell_width),
+ @intCast(h_double_bottom),
+ .on,
+ );
+ },
+ }
+
+ switch (lines.down) {
+ .none => {},
+ .light => canvas.box(
+ @intCast(v_light_left),
+ @intCast(down_top),
+ @intCast(v_light_right),
+ @intCast(metrics.cell_height),
+ .on,
+ ),
+ .heavy => canvas.box(
+ @intCast(v_heavy_left),
+ @intCast(down_top),
+ @intCast(v_heavy_right),
+ @intCast(metrics.cell_height),
+ .on,
+ ),
+ .double => {
+ const left_top = if (lines.left == .double) h_light_bottom else down_top;
+ const right_top = if (lines.right == .double) h_light_bottom else down_top;
+
+ canvas.box(
+ @intCast(v_double_left),
+ @intCast(left_top),
+ @intCast(v_light_left),
+ @intCast(metrics.cell_height),
+ .on,
+ );
+ canvas.box(
+ @intCast(v_light_right),
+ @intCast(right_top),
+ @intCast(v_double_right),
+ @intCast(metrics.cell_height),
+ .on,
+ );
+ },
+ }
+
+ switch (lines.left) {
+ .none => {},
+ .light => canvas.box(
+ 0,
+ @intCast(h_light_top),
+ @intCast(left_right),
+ @intCast(h_light_bottom),
+ .on,
+ ),
+ .heavy => canvas.box(
+ 0,
+ @intCast(h_heavy_top),
+ @intCast(left_right),
+ @intCast(h_heavy_bottom),
+ .on,
+ ),
+ .double => {
+ const top_right = if (lines.up == .double) v_light_left else left_right;
+ const bottom_right = if (lines.down == .double) v_light_left else left_right;
+
+ canvas.box(
+ 0,
+ @intCast(h_double_top),
+ @intCast(top_right),
+ @intCast(h_light_top),
+ .on,
+ );
+ canvas.box(
+ 0,
+ @intCast(h_light_bottom),
+ @intCast(bottom_right),
+ @intCast(h_double_bottom),
+ .on,
+ );
+ },
+ }
+}
+
+pub fn lightDiagonalUpperRightToLowerLeft(
+ metrics: font.Metrics,
+ canvas: *font.sprite.Canvas,
+) void {
+ const float_width: f64 = @floatFromInt(metrics.cell_width);
+ const float_height: f64 = @floatFromInt(metrics.cell_height);
+
+ // We overshoot the corners by a tiny bit, but we need to
+ // maintain the correct slope, so we calculate that here.
+ const slope_x: f64 = @min(1.0, float_width / float_height);
+ const slope_y: f64 = @min(1.0, float_height / float_width);
+
+ canvas.line(.{
+ .p0 = .{
+ .x = float_width + 0.5 * slope_x,
+ .y = -0.5 * slope_y,
+ },
+ .p1 = .{
+ .x = -0.5 * slope_x,
+ .y = float_height + 0.5 * slope_y,
+ },
+ }, @floatFromInt(Thickness.light.height(metrics.box_thickness)), .on) catch {};
+}
+
+pub fn lightDiagonalUpperLeftToLowerRight(
+ metrics: font.Metrics,
+ canvas: *font.sprite.Canvas,
+) void {
+ const float_width: f64 = @floatFromInt(metrics.cell_width);
+ const float_height: f64 = @floatFromInt(metrics.cell_height);
+
+ // We overshoot the corners by a tiny bit, but we need to
+ // maintain the correct slope, so we calculate that here.
+ const slope_x: f64 = @min(1.0, float_width / float_height);
+ const slope_y: f64 = @min(1.0, float_height / float_width);
+
+ canvas.line(.{
+ .p0 = .{
+ .x = -0.5 * slope_x,
+ .y = -0.5 * slope_y,
+ },
+ .p1 = .{
+ .x = float_width + 0.5 * slope_x,
+ .y = float_height + 0.5 * slope_y,
+ },
+ }, @floatFromInt(Thickness.light.height(metrics.box_thickness)), .on) catch {};
+}
+
+pub fn lightDiagonalCross(
+ metrics: font.Metrics,
+ canvas: *font.sprite.Canvas,
+) void {
+ lightDiagonalUpperRightToLowerLeft(metrics, canvas);
+ lightDiagonalUpperLeftToLowerRight(metrics, canvas);
+}
+
+pub fn arc(
+ metrics: font.Metrics,
+ canvas: *font.sprite.Canvas,
+ comptime corner: Corner,
+ comptime thickness: Thickness,
+) !void {
+ const thick_px = thickness.height(metrics.box_thickness);
+ const float_width: f64 = @floatFromInt(metrics.cell_width);
+ const float_height: f64 = @floatFromInt(metrics.cell_height);
+ const float_thick: f64 = @floatFromInt(thick_px);
+ const center_x: f64 = @as(f64, @floatFromInt((metrics.cell_width -| thick_px) / 2)) + float_thick / 2;
+ const center_y: f64 = @as(f64, @floatFromInt((metrics.cell_height -| thick_px) / 2)) + float_thick / 2;
+
+ const r = @min(float_width, float_height) / 2;
+
+ // Fraction away from the center to place the middle control points,
+ const s: f64 = 0.25;
+
+ var path = canvas.staticPath(4);
+
+ switch (corner) {
+ .tl => {
+ path.moveTo(center_x, 0);
+ path.lineTo(center_x, center_y - r);
+ path.curveTo(
+ center_x,
+ center_y - s * r,
+ center_x - s * r,
+ center_y,
+ center_x - r,
+ center_y,
+ );
+ path.lineTo(0, center_y);
+ },
+ .tr => {
+ path.moveTo(center_x, 0);
+ path.lineTo(center_x, center_y - r);
+ path.curveTo(
+ center_x,
+ center_y - s * r,
+ center_x + s * r,
+ center_y,
+ center_x + r,
+ center_y,
+ );
+ path.lineTo(float_width, center_y);
+ },
+ .bl => {
+ path.moveTo(center_x, float_height);
+ path.lineTo(center_x, center_y + r);
+ path.curveTo(
+ center_x,
+ center_y + s * r,
+ center_x - s * r,
+ center_y,
+ center_x - r,
+ center_y,
+ );
+ path.lineTo(0, center_y);
+ },
+ .br => {
+ path.moveTo(center_x, float_height);
+ path.lineTo(center_x, center_y + r);
+ path.curveTo(
+ center_x,
+ center_y + s * r,
+ center_x + s * r,
+ center_y,
+ center_x + r,
+ center_y,
+ );
+ path.lineTo(float_width, center_y);
+ },
+ }
+
+ try canvas.strokePath(
+ path.wrapped_path,
+ .{
+ .line_cap_mode = .butt,
+ .line_width = float_thick,
+ },
+ .on,
+ );
+}
+
+fn dashHorizontal(
+ metrics: font.Metrics,
+ canvas: *font.sprite.Canvas,
+ count: u8,
+ thick_px: u32,
+ desired_gap: u32,
+) void {
+ assert(count >= 2 and count <= 4);
+
+ // +------------+
+ // | |
+ // | |
+ // | |
+ // | |
+ // | -- -- -- |
+ // | |
+ // | |
+ // | |
+ // | |
+ // +------------+
+ // Our dashed line should be made such that when tiled horizontally
+ // it creates one consistent line with no uneven gap or segment sizes.
+ // In order to make sure this is the case, we should have half-sized
+ // gaps on the left and right so that it is centered properly.
+
+ // For N dashes, there are N - 1 gaps between them, but we also have
+ // half-sized gaps on either side, adding up to N total gaps.
+ const gap_count = count;
+
+ // We need at least 1 pixel for each gap and each dash, if we don't
+ // have that then we can't draw our dashed line correctly so we just
+ // draw a solid line and return.
+ if (metrics.cell_width < count + gap_count) {
+ hlineMiddle(metrics, canvas, .light);
+ return;
+ }
+
+ // We never want the gaps to take up more than 50% of the space,
+ // because if they do the dashes are too small and look wrong.
+ const gap_width: i32 = @intCast(@min(desired_gap, metrics.cell_width / (2 * count)));
+ const total_gap_width: i32 = gap_count * gap_width;
+ const total_dash_width: i32 = @as(i32, @intCast(metrics.cell_width)) - total_gap_width;
+ const dash_width: i32 = @divFloor(total_dash_width, count);
+ const remaining: i32 = @mod(total_dash_width, count);
+
+ assert(dash_width * count + gap_width * gap_count + remaining == metrics.cell_width);
+
+ // Our dashes should be centered vertically.
+ const y: i32 = @intCast((metrics.cell_height -| thick_px) / 2);
+
+ // We start at half a gap from the left edge, in order to center
+ // our dashes properly.
+ var x: i32 = @divFloor(gap_width, 2);
+
+ // We'll distribute the extra space in to dash widths, 1px at a
+ // time. We prefer this to making gaps larger since that is much
+ // more visually obvious.
+ var extra: i32 = remaining;
+
+ for (0..count) |_| {
+ var x1 = x + dash_width;
+ // We distribute left-over size in to dash widths,
+ // since it's less obvious there than in the gaps.
+ if (extra > 0) {
+ extra -= 1;
+ x1 += 1;
+ }
+ hline(canvas, x, x1, y, thick_px);
+ // Advance by the width of the dash we drew and the width
+ // of a gap to get the start of the next dash.
+ x = x1 + gap_width;
+ }
+}
+
+fn dashVertical(
+ metrics: font.Metrics,
+ canvas: *font.sprite.Canvas,
+ comptime count: u8,
+ thick_px: u32,
+ desired_gap: u32,
+) void {
+ assert(count >= 2 and count <= 4);
+
+ // +-----------+
+ // | | |
+ // | | |
+ // | |
+ // | | |
+ // | | |
+ // | |
+ // | | |
+ // | | |
+ // | |
+ // +-----------+
+ // Our dashed line should be made such that when tiled vertically it
+ // it creates one consistent line with no uneven gap or segment sizes.
+ // In order to make sure this is the case, we should have an extra gap
+ // gap at the bottom.
+ //
+ // A single full-sized extra gap is preferred to two half-sized ones for
+ // vertical to allow better joining to solid characters without creating
+ // visible half-sized gaps. Unlike horizontal, centering is a lot less
+ // important, visually.
+
+ // Because of the extra gap at the bottom, there are as many gaps as
+ // there are dashes.
+ const gap_count = count;
+
+ // We need at least 1 pixel for each gap and each dash, if we don't
+ // have that then we can't draw our dashed line correctly so we just
+ // draw a solid line and return.
+ if (metrics.cell_height < count + gap_count) {
+ vlineMiddle(metrics, canvas, .light);
+ return;
+ }
+
+ // We never want the gaps to take up more than 50% of the space,
+ // because if they do the dashes are too small and look wrong.
+ const gap_height: i32 = @intCast(@min(desired_gap, metrics.cell_height / (2 * count)));
+ const total_gap_height: i32 = gap_count * gap_height;
+ const total_dash_height: i32 = @as(i32, @intCast(metrics.cell_height)) - total_gap_height;
+ const dash_height: i32 = @divFloor(total_dash_height, count);
+ const remaining: i32 = @mod(total_dash_height, count);
+
+ assert(dash_height * count + gap_height * gap_count + remaining == metrics.cell_height);
+
+ // Our dashes should be centered horizontally.
+ const x: i32 = @intCast((metrics.cell_width -| thick_px) / 2);
+
+ // We start at the top of the cell.
+ var y: i32 = 0;
+
+ // We'll distribute the extra space in to dash heights, 1px at a
+ // time. We prefer this to making gaps larger since that is much
+ // more visually obvious.
+ var extra: i32 = remaining;
+
+ inline for (0..count) |_| {
+ var y1 = y + dash_height;
+ // We distribute left-over size in to dash widths,
+ // since it's less obvious there than in the gaps.
+ if (extra > 0) {
+ extra -= 1;
+ y1 += 1;
+ }
+ vline(canvas, y, y1, x, thick_px);
+ // Advance by the height of the dash we drew and the height
+ // of a gap to get the start of the next dash.
+ y = y1 + gap_height;
+ }
+}
diff --git a/vendor/libghostty-vt/src/font/sprite/draw/braille.zig b/vendor/libghostty-vt/src/font/sprite/draw/braille.zig
new file mode 100644
index 00000000..fb2d5474
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/draw/braille.zig
@@ -0,0 +1,148 @@
+//! Braille Patterns | U+2800...U+28FF
+//! https://en.wikipedia.org/wiki/Braille_Patterns
+//!
+//! (6 dot patterns)
+//! ⠀ ⠁ ⠂ ⠃ ⠄ ⠅ ⠆ ⠇ ⠈ ⠉ ⠊ ⠋ ⠌ ⠍ ⠎ ⠏
+//! ⠐ ⠑ ⠒ ⠓ ⠔ ⠕ ⠖ ⠗ ⠘ ⠙ ⠚ ⠛ ⠜ ⠝ ⠞ ⠟
+//! ⠠ ⠡ ⠢ ⠣ ⠤ ⠥ ⠦ ⠧ ⠨ ⠩ ⠪ ⠫ ⠬ ⠭ ⠮ ⠯
+//! ⠰ ⠱ ⠲ ⠳ ⠴ ⠵ ⠶ ⠷ ⠸ ⠹ ⠺ ⠻ ⠼ ⠽ ⠾ ⠿
+//!
+//! (8 dot patterns)
+//! ⡀ ⡁ ⡂ ⡃ ⡄ ⡅ ⡆ ⡇ ⡈ ⡉ ⡊ ⡋ ⡌ ⡍ ⡎ ⡏
+//! ⡐ ⡑ ⡒ ⡓ ⡔ ⡕ ⡖ ⡗ ⡘ ⡙ ⡚ ⡛ ⡜ ⡝ ⡞ ⡟
+//! ⡠ ⡡ ⡢ ⡣ ⡤ ⡥ ⡦ ⡧ ⡨ ⡩ ⡪ ⡫ ⡬ ⡭ ⡮ ⡯
+//! ⡰ ⡱ ⡲ ⡳ ⡴ ⡵ ⡶ ⡷ ⡸ ⡹ ⡺ ⡻ ⡼ ⡽ ⡾ ⡿
+//! ⢀ ⢁ ⢂ ⢃ ⢄ ⢅ ⢆ ⢇ ⢈ ⢉ ⢊ ⢋ ⢌ ⢍ ⢎ ⢏
+//! ⢐ ⢑ ⢒ ⢓ ⢔ ⢕ ⢖ ⢗ ⢘ ⢙ ⢚ ⢛ ⢜ ⢝ ⢞ ⢟
+//! ⢠ ⢡ ⢢ ⢣ ⢤ ⢥ ⢦ ⢧ ⢨ ⢩ ⢪ ⢫ ⢬ ⢭ ⢮ ⢯
+//! ⢰ ⢱ ⢲ ⢳ ⢴ ⢵ ⢶ ⢷ ⢸ ⢹ ⢺ ⢻ ⢼ ⢽ ⢾ ⢿
+//! ⣀ ⣁ ⣂ ⣃ ⣄ ⣅ ⣆ ⣇ ⣈ ⣉ ⣊ ⣋ ⣌ ⣍ ⣎ ⣏
+//! ⣐ ⣑ ⣒ ⣓ ⣔ ⣕ ⣖ ⣗ ⣘ ⣙ ⣚ ⣛ ⣜ ⣝ ⣞ ⣟
+//! ⣠ ⣡ ⣢ ⣣ ⣤ ⣥ ⣦ ⣧ ⣨ ⣩ ⣪ ⣫ ⣬ ⣭ ⣮ ⣯
+//! ⣰ ⣱ ⣲ ⣳ ⣴ ⣵ ⣶ ⣷ ⣸ ⣹ ⣺ ⣻ ⣼ ⣽ ⣾ ⣿
+//!
+
+const std = @import("std");
+const assert = @import("../../../quirks.zig").inlineAssert;
+const Allocator = std.mem.Allocator;
+
+const font = @import("../../main.zig");
+
+/// A braille pattern.
+///
+/// Mnemonic:
+/// [t]op - . .
+/// [u]pper - . .
+/// [l]ower - . .
+/// [b]ottom - . .
+/// | |
+/// [l]eft, [r]ight
+///
+/// Struct layout matches bit patterns of unicode codepoints.
+const Pattern = packed struct(u8) {
+ tl: bool,
+ ul: bool,
+ ll: bool,
+ tr: bool,
+ ur: bool,
+ lr: bool,
+ bl: bool,
+ br: bool,
+
+ fn from(cp: u32) Pattern {
+ return @bitCast(@as(u8, @truncate(cp)));
+ }
+};
+
+pub fn draw2800_28FF(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = metrics;
+
+ var w: i32 = @intCast(@min(width / 4, height / 8));
+ var x_spacing: i32 = @intCast(width / 4);
+ var y_spacing: i32 = @intCast(height / 8);
+ var x_margin: i32 = @divFloor(x_spacing, 2);
+ var y_margin: i32 = @divFloor(y_spacing, 2);
+
+ var x_px_left: i32 =
+ @as(i32, @intCast(width)) - 2 * x_margin - x_spacing - 2 * w;
+
+ var y_px_left: i32 =
+ @as(i32, @intCast(height)) - 2 * y_margin - 3 * y_spacing - 4 * w;
+
+ // First, try hard to ensure the DOT width is non-zero
+ if (x_px_left >= 2 and y_px_left >= 4 and w == 0) {
+ w += 1;
+ x_px_left -= 2;
+ y_px_left -= 4;
+ }
+
+ // Second, prefer a non-zero margin
+ if (x_px_left >= 2 and x_margin == 0) {
+ x_margin = 1;
+ x_px_left -= 2;
+ }
+ if (y_px_left >= 2 and y_margin == 0) {
+ y_margin = 1;
+ y_px_left -= 2;
+ }
+
+ // Third, increase spacing
+ if (x_px_left >= 1) {
+ x_spacing += 1;
+ x_px_left -= 1;
+ }
+ if (y_px_left >= 3) {
+ y_spacing += 1;
+ y_px_left -= 3;
+ }
+
+ // Fourth, margins (“spacing”, but on the sides)
+ if (x_px_left >= 2) {
+ x_margin += 1;
+ x_px_left -= 2;
+ }
+ if (y_px_left >= 2) {
+ y_margin += 1;
+ y_px_left -= 2;
+ }
+
+ // Last - increase dot width
+ if (x_px_left >= 2 and y_px_left >= 4) {
+ w += 1;
+ x_px_left -= 2;
+ y_px_left -= 4;
+ }
+
+ assert(x_px_left <= 1 or y_px_left <= 1);
+ assert(2 * x_margin + 2 * w + x_spacing <= width);
+ assert(2 * y_margin + 4 * w + 3 * y_spacing <= height);
+
+ const x = [2]i32{ x_margin, x_margin + w + x_spacing };
+ const y = y: {
+ var y: [4]i32 = undefined;
+ y[0] = y_margin;
+ y[1] = y[0] + w + y_spacing;
+ y[2] = y[1] + w + y_spacing;
+ y[3] = y[2] + w + y_spacing;
+ break :y y;
+ };
+
+ assert(cp >= 0x2800);
+ assert(cp <= 0x28ff);
+ const p: Pattern = .from(cp);
+
+ if (p.tl) canvas.box(x[0], y[0], x[0] + w, y[0] + w, .on);
+ if (p.ul) canvas.box(x[0], y[1], x[0] + w, y[1] + w, .on);
+ if (p.ll) canvas.box(x[0], y[2], x[0] + w, y[2] + w, .on);
+ if (p.bl) canvas.box(x[0], y[3], x[0] + w, y[3] + w, .on);
+ if (p.tr) canvas.box(x[1], y[0], x[1] + w, y[0] + w, .on);
+ if (p.ur) canvas.box(x[1], y[1], x[1] + w, y[1] + w, .on);
+ if (p.lr) canvas.box(x[1], y[2], x[1] + w, y[2] + w, .on);
+ if (p.br) canvas.box(x[1], y[3], x[1] + w, y[3] + w, .on);
+}
diff --git a/vendor/libghostty-vt/src/font/sprite/draw/branch.zig b/vendor/libghostty-vt/src/font/sprite/draw/branch.zig
new file mode 100644
index 00000000..034f1e39
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/draw/branch.zig
@@ -0,0 +1,504 @@
+//! Branch Drawing Characters | U+F5D0...U+F60D
+//!
+//! Branch drawing character set, used for drawing git-like
+//! graphs in the terminal. Originally implemented in Kitty.
+//! Ref:
+//! - https://github.com/kovidgoyal/kitty/pull/7681
+//! - https://github.com/kovidgoyal/kitty/pull/7805
+//! NOTE: Kitty is GPL licensed, and its code was not referenced
+//! for these characters, only the loose specification of
+//! the character set in the pull request descriptions.
+//!
+//!                
+//!                
+//!                
+//!              
+//!
+
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+
+const common = @import("common.zig");
+const Thickness = common.Thickness;
+const Shade = common.Shade;
+const Edge = common.Edge;
+const hlineMiddle = common.hlineMiddle;
+const vlineMiddle = common.vlineMiddle;
+
+const arc = @import("box.zig").arc;
+
+const font = @import("../../main.zig");
+
+/// Specification of a branch drawing node, which consists of a
+/// circle which is either empty or filled, and lines connecting
+/// optionally between the circle and each of the 4 edges.
+const BranchNode = packed struct(u5) {
+ up: bool = false,
+ right: bool = false,
+ down: bool = false,
+ left: bool = false,
+ filled: bool = false,
+};
+
+pub fn drawF5D0_F60D(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = width;
+ _ = height;
+
+ switch (cp) {
+ // ''
+ 0x0f5d0 => hlineMiddle(metrics, canvas, .light),
+ // ''
+ 0x0f5d1 => vlineMiddle(metrics, canvas, .light),
+ // ''
+ 0x0f5d2 => fadingLine(metrics, canvas, .right, .light),
+ // ''
+ 0x0f5d3 => fadingLine(metrics, canvas, .left, .light),
+ // ''
+ 0x0f5d4 => fadingLine(metrics, canvas, .bottom, .light),
+ // ''
+ 0x0f5d5 => fadingLine(metrics, canvas, .top, .light),
+ // ''
+ 0x0f5d6 => try arc(metrics, canvas, .br, .light),
+ // ''
+ 0x0f5d7 => try arc(metrics, canvas, .bl, .light),
+ // ''
+ 0x0f5d8 => try arc(metrics, canvas, .tr, .light),
+ // ''
+ 0x0f5d9 => try arc(metrics, canvas, .tl, .light),
+ // ''
+ 0x0f5da => {
+ vlineMiddle(metrics, canvas, .light);
+ try arc(metrics, canvas, .tr, .light);
+ },
+ // ''
+ 0x0f5db => {
+ vlineMiddle(metrics, canvas, .light);
+ try arc(metrics, canvas, .br, .light);
+ },
+ // ''
+ 0x0f5dc => {
+ try arc(metrics, canvas, .tr, .light);
+ try arc(metrics, canvas, .br, .light);
+ },
+ // ''
+ 0x0f5dd => {
+ vlineMiddle(metrics, canvas, .light);
+ try arc(metrics, canvas, .tl, .light);
+ },
+ // ''
+ 0x0f5de => {
+ vlineMiddle(metrics, canvas, .light);
+ try arc(metrics, canvas, .bl, .light);
+ },
+ // ''
+ 0x0f5df => {
+ try arc(metrics, canvas, .tl, .light);
+ try arc(metrics, canvas, .bl, .light);
+ },
+
+ // ''
+ 0x0f5e0 => {
+ try arc(metrics, canvas, .bl, .light);
+ hlineMiddle(metrics, canvas, .light);
+ },
+ // ''
+ 0x0f5e1 => {
+ try arc(metrics, canvas, .br, .light);
+ hlineMiddle(metrics, canvas, .light);
+ },
+ // ''
+ 0x0f5e2 => {
+ try arc(metrics, canvas, .br, .light);
+ try arc(metrics, canvas, .bl, .light);
+ },
+ // ''
+ 0x0f5e3 => {
+ try arc(metrics, canvas, .tl, .light);
+ hlineMiddle(metrics, canvas, .light);
+ },
+ // ''
+ 0x0f5e4 => {
+ try arc(metrics, canvas, .tr, .light);
+ hlineMiddle(metrics, canvas, .light);
+ },
+ // ''
+ 0x0f5e5 => {
+ try arc(metrics, canvas, .tr, .light);
+ try arc(metrics, canvas, .tl, .light);
+ },
+ // ''
+ 0x0f5e6 => {
+ vlineMiddle(metrics, canvas, .light);
+ try arc(metrics, canvas, .tl, .light);
+ try arc(metrics, canvas, .tr, .light);
+ },
+ // ''
+ 0x0f5e7 => {
+ vlineMiddle(metrics, canvas, .light);
+ try arc(metrics, canvas, .bl, .light);
+ try arc(metrics, canvas, .br, .light);
+ },
+ // ''
+ 0x0f5e8 => {
+ hlineMiddle(metrics, canvas, .light);
+ try arc(metrics, canvas, .bl, .light);
+ try arc(metrics, canvas, .tl, .light);
+ },
+ // ''
+ 0x0f5e9 => {
+ hlineMiddle(metrics, canvas, .light);
+ try arc(metrics, canvas, .tr, .light);
+ try arc(metrics, canvas, .br, .light);
+ },
+ // ''
+ 0x0f5ea => {
+ vlineMiddle(metrics, canvas, .light);
+ try arc(metrics, canvas, .tl, .light);
+ try arc(metrics, canvas, .br, .light);
+ },
+ // ''
+ 0x0f5eb => {
+ vlineMiddle(metrics, canvas, .light);
+ try arc(metrics, canvas, .tr, .light);
+ try arc(metrics, canvas, .bl, .light);
+ },
+ // ''
+ 0x0f5ec => {
+ hlineMiddle(metrics, canvas, .light);
+ try arc(metrics, canvas, .tl, .light);
+ try arc(metrics, canvas, .br, .light);
+ },
+ // ''
+ 0x0f5ed => {
+ hlineMiddle(metrics, canvas, .light);
+ try arc(metrics, canvas, .tr, .light);
+ try arc(metrics, canvas, .bl, .light);
+ },
+ // ''
+ 0x0f5ee => branchNode(metrics, canvas, .{ .filled = true }, .light),
+ // ''
+ 0x0f5ef => branchNode(metrics, canvas, .{}, .light),
+
+ // ''
+ 0x0f5f0 => branchNode(metrics, canvas, .{
+ .right = true,
+ .filled = true,
+ }, .light),
+ // ''
+ 0x0f5f1 => branchNode(metrics, canvas, .{
+ .right = true,
+ }, .light),
+ // ''
+ 0x0f5f2 => branchNode(metrics, canvas, .{
+ .left = true,
+ .filled = true,
+ }, .light),
+ // ''
+ 0x0f5f3 => branchNode(metrics, canvas, .{
+ .left = true,
+ }, .light),
+ // ''
+ 0x0f5f4 => branchNode(metrics, canvas, .{
+ .left = true,
+ .right = true,
+ .filled = true,
+ }, .light),
+ // ''
+ 0x0f5f5 => branchNode(metrics, canvas, .{
+ .left = true,
+ .right = true,
+ }, .light),
+ // ''
+ 0x0f5f6 => branchNode(metrics, canvas, .{
+ .down = true,
+ .filled = true,
+ }, .light),
+ // ''
+ 0x0f5f7 => branchNode(metrics, canvas, .{
+ .down = true,
+ }, .light),
+ // ''
+ 0x0f5f8 => branchNode(metrics, canvas, .{
+ .up = true,
+ .filled = true,
+ }, .light),
+ // ''
+ 0x0f5f9 => branchNode(metrics, canvas, .{
+ .up = true,
+ }, .light),
+ // ''
+ 0x0f5fa => branchNode(metrics, canvas, .{
+ .up = true,
+ .down = true,
+ .filled = true,
+ }, .light),
+ // ''
+ 0x0f5fb => branchNode(metrics, canvas, .{
+ .up = true,
+ .down = true,
+ }, .light),
+ // ''
+ 0x0f5fc => branchNode(metrics, canvas, .{
+ .right = true,
+ .down = true,
+ .filled = true,
+ }, .light),
+ // ''
+ 0x0f5fd => branchNode(metrics, canvas, .{
+ .right = true,
+ .down = true,
+ }, .light),
+ // ''
+ 0x0f5fe => branchNode(metrics, canvas, .{
+ .left = true,
+ .down = true,
+ .filled = true,
+ }, .light),
+ // ''
+ 0x0f5ff => branchNode(metrics, canvas, .{
+ .left = true,
+ .down = true,
+ }, .light),
+
+ // ''
+ 0x0f600 => branchNode(metrics, canvas, .{
+ .up = true,
+ .right = true,
+ .filled = true,
+ }, .light),
+ // ''
+ 0x0f601 => branchNode(metrics, canvas, .{
+ .up = true,
+ .right = true,
+ }, .light),
+ // ''
+ 0x0f602 => branchNode(metrics, canvas, .{
+ .up = true,
+ .left = true,
+ .filled = true,
+ }, .light),
+ // ''
+ 0x0f603 => branchNode(metrics, canvas, .{
+ .up = true,
+ .left = true,
+ }, .light),
+ // ''
+ 0x0f604 => branchNode(metrics, canvas, .{
+ .up = true,
+ .down = true,
+ .right = true,
+ .filled = true,
+ }, .light),
+ // ''
+ 0x0f605 => branchNode(metrics, canvas, .{
+ .up = true,
+ .down = true,
+ .right = true,
+ }, .light),
+ // ''
+ 0x0f606 => branchNode(metrics, canvas, .{
+ .up = true,
+ .down = true,
+ .left = true,
+ .filled = true,
+ }, .light),
+ // ''
+ 0x0f607 => branchNode(metrics, canvas, .{
+ .up = true,
+ .down = true,
+ .left = true,
+ }, .light),
+ // ''
+ 0x0f608 => branchNode(metrics, canvas, .{
+ .down = true,
+ .left = true,
+ .right = true,
+ .filled = true,
+ }, .light),
+ // ''
+ 0x0f609 => branchNode(metrics, canvas, .{
+ .down = true,
+ .left = true,
+ .right = true,
+ }, .light),
+ // ''
+ 0x0f60a => branchNode(metrics, canvas, .{
+ .up = true,
+ .left = true,
+ .right = true,
+ .filled = true,
+ }, .light),
+ // ''
+ 0x0f60b => branchNode(metrics, canvas, .{
+ .up = true,
+ .left = true,
+ .right = true,
+ }, .light),
+ // ''
+ 0x0f60c => branchNode(metrics, canvas, .{
+ .up = true,
+ .down = true,
+ .left = true,
+ .right = true,
+ .filled = true,
+ }, .light),
+ // ''
+ 0x0f60d => branchNode(metrics, canvas, .{
+ .up = true,
+ .down = true,
+ .left = true,
+ .right = true,
+ }, .light),
+
+ else => unreachable,
+ }
+}
+
+fn branchNode(
+ metrics: font.Metrics,
+ canvas: *font.sprite.Canvas,
+ node: BranchNode,
+ comptime thickness: Thickness,
+) void {
+ const thick_px = thickness.height(metrics.box_thickness);
+ const float_width: f64 = @floatFromInt(metrics.cell_width);
+ const float_height: f64 = @floatFromInt(metrics.cell_height);
+ const float_thick: f64 = @floatFromInt(thick_px);
+
+ // Top of horizontal strokes
+ const h_top = (metrics.cell_height -| thick_px) / 2;
+ // Bottom of horizontal strokes
+ const h_bottom = h_top +| thick_px;
+ // Left of vertical strokes
+ const v_left = (metrics.cell_width -| thick_px) / 2;
+ // Right of vertical strokes
+ const v_right = v_left +| thick_px;
+
+ // We calculate the center of the circle this way
+ // to ensure it aligns with box drawing characters
+ // since the lines are sometimes off center to
+ // make sure they aren't split between pixels.
+ const cx: f64 = @as(f64, @floatFromInt(v_left)) + float_thick / 2;
+ const cy: f64 = @as(f64, @floatFromInt(h_top)) + float_thick / 2;
+ // The radius needs to be the smallest distance from the center to an edge.
+ const r: f64 = @min(
+ @min(cx, cy),
+ @min(float_width - cx, float_height - cy),
+ );
+
+ var ctx = canvas.getContext();
+ defer ctx.deinit();
+ ctx.setSource(.{ .opaque_pattern = .{
+ .pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } },
+ } });
+ ctx.setLineWidth(float_thick);
+
+ // These @intFromFloat casts shouldn't ever fail since r can never
+ // be greater than cx or cy, so when subtracting it from them the
+ // result can never be negative.
+ if (node.up) canvas.box(
+ @intCast(v_left),
+ 0,
+ @intCast(v_right),
+ @intFromFloat(@ceil(cy - r + float_thick / 2)),
+ .on,
+ );
+ if (node.right) canvas.box(
+ @intFromFloat(@floor(cx + r - float_thick / 2)),
+ @intCast(h_top),
+ @intCast(metrics.cell_width),
+ @intCast(h_bottom),
+ .on,
+ );
+ if (node.down) canvas.box(
+ @intCast(v_left),
+ @intFromFloat(@floor(cy + r - float_thick / 2)),
+ @intCast(v_right),
+ @intCast(metrics.cell_height),
+ .on,
+ );
+ if (node.left) canvas.box(
+ 0,
+ @intCast(h_top),
+ @intFromFloat(@ceil(cx - r + float_thick / 2)),
+ @intCast(h_bottom),
+ .on,
+ );
+
+ if (node.filled) {
+ ctx.arc(cx, cy, r, 0, std.math.pi * 2) catch return;
+ ctx.closePath() catch return;
+ ctx.fill() catch return;
+ } else {
+ ctx.arc(cx, cy, r - float_thick / 2, 0, std.math.pi * 2) catch return;
+ ctx.closePath() catch return;
+ ctx.stroke() catch return;
+ }
+}
+
+fn fadingLine(
+ metrics: font.Metrics,
+ canvas: *font.sprite.Canvas,
+ comptime to: Edge,
+ comptime thickness: Thickness,
+) void {
+ const thick_px = thickness.height(metrics.box_thickness);
+ const float_width: f64 = @floatFromInt(metrics.cell_width);
+ const float_height: f64 = @floatFromInt(metrics.cell_height);
+
+ // Top of horizontal strokes
+ const h_top = (metrics.cell_height -| thick_px) / 2;
+ // Bottom of horizontal strokes
+ const h_bottom = h_top +| thick_px;
+ // Left of vertical strokes
+ const v_left = (metrics.cell_width -| thick_px) / 2;
+ // Right of vertical strokes
+ const v_right = v_left +| thick_px;
+
+ // If we're fading to the top or left, we start with 0.0
+ // and increment up as we progress, otherwise we start
+ // at 255.0 and increment down (negative).
+ var color: f64 = switch (to) {
+ .top, .left => 0.0,
+ .bottom, .right => 255.0,
+ };
+ const inc: f64 = 255.0 / switch (to) {
+ .top => float_height,
+ .bottom => -float_height,
+ .left => float_width,
+ .right => -float_width,
+ };
+
+ switch (to) {
+ .top, .bottom => {
+ for (0..metrics.cell_height) |y| {
+ for (v_left..v_right) |x| {
+ canvas.pixel(
+ @intCast(x),
+ @intCast(y),
+ @enumFromInt(@as(u8, @intFromFloat(@round(color)))),
+ );
+ }
+ color += inc;
+ }
+ },
+ .left, .right => {
+ for (0..metrics.cell_width) |x| {
+ for (h_top..h_bottom) |y| {
+ canvas.pixel(
+ @intCast(x),
+ @intCast(y),
+ @enumFromInt(@as(u8, @intFromFloat(@round(color)))),
+ );
+ }
+ color += inc;
+ }
+ },
+ }
+}
diff --git a/vendor/libghostty-vt/src/font/sprite/draw/common.zig b/vendor/libghostty-vt/src/font/sprite/draw/common.zig
new file mode 100644
index 00000000..290c4496
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/draw/common.zig
@@ -0,0 +1,374 @@
+//! This file contains a set of useful helper functions
+//! and types for drawing our sprite font glyphs. These
+//! are generally applicable to multiple sets of glyphs
+//! rather than being single-use.
+
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+
+const font = @import("../../main.zig");
+
+const log = std.log.scoped(.sprite_font);
+
+// Utility names for common fractions
+pub const one_eighth: f64 = 0.125;
+pub const one_quarter: f64 = 0.25;
+pub const one_third: f64 = (1.0 / 3.0);
+pub const three_eighths: f64 = 0.375;
+pub const half: f64 = 0.5;
+pub const five_eighths: f64 = 0.625;
+pub const two_thirds: f64 = (2.0 / 3.0);
+pub const three_quarters: f64 = 0.75;
+pub const seven_eighths: f64 = 0.875;
+
+/// The thickness of a line.
+pub const Thickness = enum {
+ super_light,
+ light,
+ heavy,
+
+ /// Calculate the real height of a line based on its
+ /// thickness and a base thickness value. The base
+ /// thickness value is expected to be in pixels.
+ pub fn height(self: Thickness, base: u32) u32 {
+ return switch (self) {
+ .super_light => @max(base / 2, 1),
+ .light => base,
+ .heavy => base * 2,
+ };
+ }
+};
+
+/// Shades.
+pub const Shade = enum(u8) {
+ off = 0x00,
+ light = 0x40,
+ medium = 0x80,
+ dark = 0xc0,
+ on = 0xff,
+
+ _,
+};
+
+/// Applicable to any set of glyphs with features
+/// that may be present or not in each quadrant.
+pub const Quads = packed struct(u4) {
+ tl: bool = false,
+ tr: bool = false,
+ bl: bool = false,
+ br: bool = false,
+};
+
+/// A corner of a cell.
+pub const Corner = enum(u2) {
+ tl,
+ tr,
+ bl,
+ br,
+};
+
+/// An edge of a cell.
+pub const Edge = enum(u2) {
+ top,
+ left,
+ bottom,
+ right,
+};
+
+/// Alignment of a figure within a cell.
+pub const Alignment = struct {
+ horizontal: enum {
+ left,
+ right,
+ center,
+ } = .center,
+
+ vertical: enum {
+ top,
+ bottom,
+ middle,
+ } = .middle,
+
+ pub const upper: Alignment = .{ .vertical = .top };
+ pub const lower: Alignment = .{ .vertical = .bottom };
+ pub const left: Alignment = .{ .horizontal = .left };
+ pub const right: Alignment = .{ .horizontal = .right };
+
+ pub const upper_left: Alignment = .{ .vertical = .top, .horizontal = .left };
+ pub const upper_right: Alignment = .{ .vertical = .top, .horizontal = .right };
+ pub const lower_left: Alignment = .{ .vertical = .bottom, .horizontal = .left };
+ pub const lower_right: Alignment = .{ .vertical = .bottom, .horizontal = .right };
+
+ pub const center: Alignment = .{};
+
+ pub const upper_center = upper;
+ pub const lower_center = lower;
+ pub const middle_left = left;
+ pub const middle_right = right;
+ pub const middle_center: Alignment = center;
+
+ pub const top = upper;
+ pub const bottom = lower;
+ pub const center_top = top;
+ pub const center_bottom = bottom;
+
+ pub const top_left = upper_left;
+ pub const top_right = upper_right;
+ pub const bottom_left = lower_left;
+ pub const bottom_right = lower_right;
+};
+
+/// A value that indicates some fraction across
+/// the cell either horizontally or vertically.
+///
+/// This has some redundant names in it so that you can
+/// use whichever one feels most semantically appropriate.
+pub const Fraction = enum {
+ // Names for the min edge
+ start,
+ left,
+ top,
+ zero,
+
+ // Names based on eighths
+ eighth,
+ one_eighth,
+ two_eighths,
+ three_eighths,
+ four_eighths,
+ five_eighths,
+ six_eighths,
+ seven_eighths,
+
+ // Names based on quarters
+ quarter,
+ one_quarter,
+ two_quarters,
+ three_quarters,
+
+ // Names based on thirds
+ third,
+ one_third,
+ two_thirds,
+
+ // Names based on halves
+ half,
+ one_half,
+
+ // Alternative names for 1/2
+ center,
+ middle,
+
+ // Names for the max edge
+ end,
+ right,
+ bottom,
+ one,
+ full,
+
+ /// This can be indexed to get the fraction for `i/8`.
+ pub const eighths: [9]Fraction = .{
+ .zero,
+ .one_eighth,
+ .two_eighths,
+ .three_eighths,
+ .four_eighths,
+ .five_eighths,
+ .six_eighths,
+ .seven_eighths,
+ .one,
+ };
+
+ /// This can be indexed to get the fraction for `i/4`.
+ pub const quarters: [5]Fraction = .{
+ .zero,
+ .one_quarter,
+ .two_quarters,
+ .three_quarters,
+ .one,
+ };
+
+ /// This can be indexed to get the fraction for `i/3`.
+ pub const thirds: [4]Fraction = .{
+ .zero,
+ .one_third,
+ .two_thirds,
+ .one,
+ };
+
+ /// This can be indexed to get the fraction for `i/2`.
+ pub const halves: [3]Fraction = .{
+ .zero,
+ .one_half,
+ .one,
+ };
+
+ /// Get the x position for this fraction across a particular
+ /// size (width or height), assuming it will be used as the
+ /// min (left/top) coordinate for a block.
+ ///
+ /// `size` can be any integer type, since it will be coerced
+ pub inline fn min(self: Fraction, size: anytype) i32 {
+ const s: f64 = @as(f64, @floatFromInt(size));
+ // For min coordinates, we want to align with the complementary
+ // fraction taken from the end, this ensures that rounding evens
+ // out, so that for example, if `size` is `7`, and we're looking
+ // at the `half` line, `size - round((1 - 0.5) * size)` => `3`;
+ // whereas the max coordinate directly rounds, which means that
+ // both `start` -> `half` and `half` -> `end` will be 4px, from
+ // `0` -> `4` and `3` -> `7`.
+ return @intFromFloat(s - @round((1.0 - self.fraction()) * s));
+ }
+
+ /// Get the x position for this fraction across a particular
+ /// size (width or height), assuming it will be used as the
+ /// max (right/bottom) coordinate for a block.
+ ///
+ /// `size` can be any integer type, since it will be coerced
+ /// with `@floatFromInt`.
+ pub inline fn max(self: Fraction, size: anytype) i32 {
+ const s: f64 = @as(f64, @floatFromInt(size));
+ // See explanation of why these are different in `min`.
+ return @intFromFloat(@round(self.fraction() * s));
+ }
+
+ /// Get this fraction across a particular size (width/height).
+ /// If you need an integer, use `min` or `max` instead, since
+ /// they contain special logic for consistent alignment. This
+ /// is for when you're drawing with paths and don't care about
+ /// pixel alignment.
+ ///
+ /// `size` can be any integer type, since it will be coerced
+ /// with `@floatFromInt`.
+ pub inline fn float(self: Fraction, size: anytype) f64 {
+ return self.fraction() * @as(f64, @floatFromInt(size));
+ }
+
+ /// Get a float for the fraction this represents.
+ pub inline fn fraction(self: Fraction) f64 {
+ return switch (self) {
+ .start,
+ .left,
+ .top,
+ .zero,
+ => 0.0,
+
+ .eighth,
+ .one_eighth,
+ => 0.125,
+
+ .quarter,
+ .one_quarter,
+ .two_eighths,
+ => 0.25,
+
+ .third,
+ .one_third,
+ => 1.0 / 3.0,
+
+ .three_eighths,
+ => 0.375,
+
+ .half,
+ .one_half,
+ .two_quarters,
+ .four_eighths,
+ .center,
+ .middle,
+ => 0.5,
+
+ .five_eighths,
+ => 0.625,
+
+ .two_thirds,
+ => 2.0 / 3.0,
+
+ .three_quarters,
+ .six_eighths,
+ => 0.75,
+
+ .seven_eighths,
+ => 0.875,
+
+ .end,
+ .right,
+ .bottom,
+ .one,
+ .full,
+ => 1.0,
+ };
+ }
+};
+
+/// Fill a section of the cell, specified by a
+/// horizontal and vertical pair of fraction lines.
+pub fn fill(
+ metrics: font.Metrics,
+ canvas: *font.sprite.Canvas,
+ x0: Fraction,
+ x1: Fraction,
+ y0: Fraction,
+ y1: Fraction,
+) void {
+ canvas.box(
+ x0.min(metrics.cell_width),
+ y0.min(metrics.cell_height),
+ x1.max(metrics.cell_width),
+ y1.max(metrics.cell_height),
+ .on,
+ );
+}
+
+/// Centered vertical line of the provided thickness.
+pub fn vlineMiddle(
+ metrics: font.Metrics,
+ canvas: *font.sprite.Canvas,
+ thickness: Thickness,
+) void {
+ const thick_px = thickness.height(metrics.box_thickness);
+ vline(
+ canvas,
+ 0,
+ @intCast(metrics.cell_height),
+ @intCast((metrics.cell_width -| thick_px) / 2),
+ thick_px,
+ );
+}
+
+/// Centered horizontal line of the provided thickness.
+pub fn hlineMiddle(
+ metrics: font.Metrics,
+ canvas: *font.sprite.Canvas,
+ thickness: Thickness,
+) void {
+ const thick_px = thickness.height(metrics.box_thickness);
+ hline(
+ canvas,
+ 0,
+ @intCast(metrics.cell_width),
+ @intCast((metrics.cell_height -| thick_px) / 2),
+ thick_px,
+ );
+}
+
+/// Vertical line with the left edge at `x`, between `y1` and `y2`.
+pub fn vline(
+ canvas: *font.sprite.Canvas,
+ y1: i32,
+ y2: i32,
+ x: i32,
+ thickness_px: u32,
+) void {
+ canvas.box(x, y1, x + @as(i32, @intCast(thickness_px)), y2, .on);
+}
+
+/// Horizontal line with the top edge at `y`, between `x1` and `x2`.
+pub fn hline(
+ canvas: *font.sprite.Canvas,
+ x1: i32,
+ x2: i32,
+ y: i32,
+ thickness_px: u32,
+) void {
+ canvas.box(x1, y, x2, y + @as(i32, @intCast(thickness_px)), .on);
+}
diff --git a/vendor/libghostty-vt/src/font/sprite/draw/geometric_shapes.zig b/vendor/libghostty-vt/src/font/sprite/draw/geometric_shapes.zig
new file mode 100644
index 00000000..f6402cf0
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/draw/geometric_shapes.zig
@@ -0,0 +1,198 @@
+//! Geometric Shapes | U+25A0...U+25FF
+//! https://en.wikipedia.org/wiki/Geometric_Shapes_(Unicode_block)
+//!
+//! ■ □ ▢ ▣ ▤ ▥ ▦ ▧ ▨ ▩ ▪ ▫ ▬ ▭ ▮ ▯
+//! ▰ ▱ ▲ △ ▴ ▵ ▶ ▷ ▸ ▹ ► ▻ ▼ ▽ ▾ ▿
+//! ◀ ◁ ◂ ◃ ◄ ◅ ◆ ◇ ◈ ◉ ◊ ○ ◌ ◍ ◎ ●
+//! ◐ ◑ ◒ ◓ ◔ ◕ ◖ ◗ ◘ ◙ ◚ ◛ ◜ ◝ ◞ ◟
+//! ◠ ◡ ◢ ◣ ◤ ◥ ◦ ◧ ◨ ◩ ◪ ◫ ◬ ◭ ◮ ◯
+//! ◰ ◱ ◲ ◳ ◴ ◵ ◶ ◷ ◸ ◹ ◺ ◻ ◼ ◽︎◾︎◿
+//!
+//! Only a subset of this block is viable for sprite drawing; filling
+//! out this file to have full coverage of this block is not the goal.
+//!
+
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+
+const common = @import("common.zig");
+const Thickness = common.Thickness;
+const Corner = common.Corner;
+const Shade = common.Shade;
+
+const font = @import("../../main.zig");
+
+/// ◢ ◣ ◤ ◥
+pub fn draw25E2_25E5(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = width;
+ _ = height;
+ switch (cp) {
+ // ◢
+ 0x25e2 => try cornerTriangleShade(metrics, canvas, .br, .on),
+ // ◣
+ 0x25e3 => try cornerTriangleShade(metrics, canvas, .bl, .on),
+ // ◤
+ 0x25e4 => try cornerTriangleShade(metrics, canvas, .tl, .on),
+ // ◥
+ 0x25e5 => try cornerTriangleShade(metrics, canvas, .tr, .on),
+
+ else => unreachable,
+ }
+}
+
+/// ◸ ◹ ◺
+pub fn draw25F8_25FA(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = width;
+ _ = height;
+ switch (cp) {
+ // ◸
+ 0x25f8 => try cornerTriangleOutline(metrics, canvas, .tl),
+ // ◹
+ 0x25f9 => try cornerTriangleOutline(metrics, canvas, .tr),
+ // ◺
+ 0x25fa => try cornerTriangleOutline(metrics, canvas, .bl),
+
+ else => unreachable,
+ }
+}
+
+/// ◿
+pub fn draw25FF(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+ _ = width;
+ _ = height;
+ try cornerTriangleOutline(metrics, canvas, .br);
+}
+
+pub fn cornerTriangleShade(
+ metrics: font.Metrics,
+ canvas: *font.sprite.Canvas,
+ comptime corner: Corner,
+ comptime shade: Shade,
+) !void {
+ const float_width: f64 = @floatFromInt(metrics.cell_width);
+ const float_height: f64 = @floatFromInt(metrics.cell_height);
+
+ const x0, const y0, const x1, const y1, const x2, const y2 =
+ switch (corner) {
+ .tl => .{
+ 0,
+ 0,
+ 0,
+ float_height,
+ float_width,
+ 0,
+ },
+ .tr => .{
+ 0,
+ 0,
+ float_width,
+ float_height,
+ float_width,
+ 0,
+ },
+ .bl => .{
+ 0,
+ 0,
+ 0,
+ float_height,
+ float_width,
+ float_height,
+ },
+ .br => .{
+ 0,
+ float_height,
+ float_width,
+ float_height,
+ float_width,
+ 0,
+ },
+ };
+
+ var path = canvas.staticPath(5); // nodes.len = 0
+ path.moveTo(x0, y0); // +1, nodes.len = 1
+ path.lineTo(x1, y1); // +1, nodes.len = 2
+ path.lineTo(x2, y2); // +1, nodes.len = 3
+ path.close(); // +2, nodes.len = 5
+
+ try canvas.fillPath(
+ path.wrapped_path,
+ .{},
+ @enumFromInt(@intFromEnum(shade)),
+ );
+}
+
+pub fn cornerTriangleOutline(
+ metrics: font.Metrics,
+ canvas: *font.sprite.Canvas,
+ comptime corner: Corner,
+) !void {
+ const float_thick: f64 = @floatFromInt(Thickness.light.height(metrics.box_thickness));
+ const float_width: f64 = @floatFromInt(metrics.cell_width);
+ const float_height: f64 = @floatFromInt(metrics.cell_height);
+
+ const x0, const y0, const x1, const y1, const x2, const y2 =
+ switch (corner) {
+ .tl => .{
+ 0,
+ 0,
+ 0,
+ float_height,
+ float_width,
+ 0,
+ },
+ .tr => .{
+ 0,
+ 0,
+ float_width,
+ float_height,
+ float_width,
+ 0,
+ },
+ .bl => .{
+ 0,
+ 0,
+ 0,
+ float_height,
+ float_width,
+ float_height,
+ },
+ .br => .{
+ 0,
+ float_height,
+ float_width,
+ float_height,
+ float_width,
+ 0,
+ },
+ };
+
+ var path = canvas.staticPath(5); // nodes.len = 0
+ path.moveTo(x0, y0); // +1, nodes.len = 1
+ path.lineTo(x1, y1); // +1, nodes.len = 2
+ path.lineTo(x2, y2); // +1, nodes.len = 3
+ path.close(); // +2, nodes.len = 5
+
+ try canvas.innerStrokePath(path.wrapped_path, .{
+ .line_cap_mode = .butt,
+ .line_width = float_thick,
+ }, .on);
+}
diff --git a/vendor/libghostty-vt/src/font/sprite/draw/octants.txt b/vendor/libghostty-vt/src/font/sprite/draw/octants.txt
new file mode 100644
index 00000000..db79aa2c
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/draw/octants.txt
@@ -0,0 +1,234 @@
+# This is the list of all the octants for the Symbols for Legacy
+# Computing block. It is used at comptime to generate the lookup
+# table for drawing them since we weren't able to discern a
+# mathematical pattern for them.
+BLOCK OCTANT-3
+BLOCK OCTANT-23
+BLOCK OCTANT-123
+BLOCK OCTANT-4
+BLOCK OCTANT-14
+BLOCK OCTANT-124
+BLOCK OCTANT-34
+BLOCK OCTANT-134
+BLOCK OCTANT-234
+BLOCK OCTANT-5
+BLOCK OCTANT-15
+BLOCK OCTANT-25
+BLOCK OCTANT-125
+BLOCK OCTANT-135
+BLOCK OCTANT-235
+BLOCK OCTANT-1235
+BLOCK OCTANT-45
+BLOCK OCTANT-145
+BLOCK OCTANT-245
+BLOCK OCTANT-1245
+BLOCK OCTANT-345
+BLOCK OCTANT-1345
+BLOCK OCTANT-2345
+BLOCK OCTANT-12345
+BLOCK OCTANT-6
+BLOCK OCTANT-16
+BLOCK OCTANT-26
+BLOCK OCTANT-126
+BLOCK OCTANT-36
+BLOCK OCTANT-136
+BLOCK OCTANT-236
+BLOCK OCTANT-1236
+BLOCK OCTANT-146
+BLOCK OCTANT-246
+BLOCK OCTANT-1246
+BLOCK OCTANT-346
+BLOCK OCTANT-1346
+BLOCK OCTANT-2346
+BLOCK OCTANT-12346
+BLOCK OCTANT-56
+BLOCK OCTANT-156
+BLOCK OCTANT-256
+BLOCK OCTANT-1256
+BLOCK OCTANT-356
+BLOCK OCTANT-1356
+BLOCK OCTANT-2356
+BLOCK OCTANT-12356
+BLOCK OCTANT-456
+BLOCK OCTANT-1456
+BLOCK OCTANT-2456
+BLOCK OCTANT-12456
+BLOCK OCTANT-3456
+BLOCK OCTANT-13456
+BLOCK OCTANT-23456
+BLOCK OCTANT-17
+BLOCK OCTANT-27
+BLOCK OCTANT-127
+BLOCK OCTANT-37
+BLOCK OCTANT-137
+BLOCK OCTANT-237
+BLOCK OCTANT-1237
+BLOCK OCTANT-47
+BLOCK OCTANT-147
+BLOCK OCTANT-247
+BLOCK OCTANT-1247
+BLOCK OCTANT-347
+BLOCK OCTANT-1347
+BLOCK OCTANT-2347
+BLOCK OCTANT-12347
+BLOCK OCTANT-157
+BLOCK OCTANT-257
+BLOCK OCTANT-1257
+BLOCK OCTANT-357
+BLOCK OCTANT-2357
+BLOCK OCTANT-12357
+BLOCK OCTANT-457
+BLOCK OCTANT-1457
+BLOCK OCTANT-12457
+BLOCK OCTANT-3457
+BLOCK OCTANT-13457
+BLOCK OCTANT-23457
+BLOCK OCTANT-67
+BLOCK OCTANT-167
+BLOCK OCTANT-267
+BLOCK OCTANT-1267
+BLOCK OCTANT-367
+BLOCK OCTANT-1367
+BLOCK OCTANT-2367
+BLOCK OCTANT-12367
+BLOCK OCTANT-467
+BLOCK OCTANT-1467
+BLOCK OCTANT-2467
+BLOCK OCTANT-12467
+BLOCK OCTANT-3467
+BLOCK OCTANT-13467
+BLOCK OCTANT-23467
+BLOCK OCTANT-123467
+BLOCK OCTANT-567
+BLOCK OCTANT-1567
+BLOCK OCTANT-2567
+BLOCK OCTANT-12567
+BLOCK OCTANT-3567
+BLOCK OCTANT-13567
+BLOCK OCTANT-23567
+BLOCK OCTANT-123567
+BLOCK OCTANT-4567
+BLOCK OCTANT-14567
+BLOCK OCTANT-24567
+BLOCK OCTANT-124567
+BLOCK OCTANT-34567
+BLOCK OCTANT-134567
+BLOCK OCTANT-234567
+BLOCK OCTANT-1234567
+BLOCK OCTANT-18
+BLOCK OCTANT-28
+BLOCK OCTANT-128
+BLOCK OCTANT-38
+BLOCK OCTANT-138
+BLOCK OCTANT-238
+BLOCK OCTANT-1238
+BLOCK OCTANT-48
+BLOCK OCTANT-148
+BLOCK OCTANT-248
+BLOCK OCTANT-1248
+BLOCK OCTANT-348
+BLOCK OCTANT-1348
+BLOCK OCTANT-2348
+BLOCK OCTANT-12348
+BLOCK OCTANT-58
+BLOCK OCTANT-158
+BLOCK OCTANT-258
+BLOCK OCTANT-1258
+BLOCK OCTANT-358
+BLOCK OCTANT-1358
+BLOCK OCTANT-2358
+BLOCK OCTANT-12358
+BLOCK OCTANT-458
+BLOCK OCTANT-1458
+BLOCK OCTANT-2458
+BLOCK OCTANT-12458
+BLOCK OCTANT-3458
+BLOCK OCTANT-13458
+BLOCK OCTANT-23458
+BLOCK OCTANT-123458
+BLOCK OCTANT-168
+BLOCK OCTANT-268
+BLOCK OCTANT-1268
+BLOCK OCTANT-368
+BLOCK OCTANT-2368
+BLOCK OCTANT-12368
+BLOCK OCTANT-468
+BLOCK OCTANT-1468
+BLOCK OCTANT-12468
+BLOCK OCTANT-3468
+BLOCK OCTANT-13468
+BLOCK OCTANT-23468
+BLOCK OCTANT-568
+BLOCK OCTANT-1568
+BLOCK OCTANT-2568
+BLOCK OCTANT-12568
+BLOCK OCTANT-3568
+BLOCK OCTANT-13568
+BLOCK OCTANT-23568
+BLOCK OCTANT-123568
+BLOCK OCTANT-4568
+BLOCK OCTANT-14568
+BLOCK OCTANT-24568
+BLOCK OCTANT-124568
+BLOCK OCTANT-34568
+BLOCK OCTANT-134568
+BLOCK OCTANT-234568
+BLOCK OCTANT-1234568
+BLOCK OCTANT-178
+BLOCK OCTANT-278
+BLOCK OCTANT-1278
+BLOCK OCTANT-378
+BLOCK OCTANT-1378
+BLOCK OCTANT-2378
+BLOCK OCTANT-12378
+BLOCK OCTANT-478
+BLOCK OCTANT-1478
+BLOCK OCTANT-2478
+BLOCK OCTANT-12478
+BLOCK OCTANT-3478
+BLOCK OCTANT-13478
+BLOCK OCTANT-23478
+BLOCK OCTANT-123478
+BLOCK OCTANT-578
+BLOCK OCTANT-1578
+BLOCK OCTANT-2578
+BLOCK OCTANT-12578
+BLOCK OCTANT-3578
+BLOCK OCTANT-13578
+BLOCK OCTANT-23578
+BLOCK OCTANT-123578
+BLOCK OCTANT-4578
+BLOCK OCTANT-14578
+BLOCK OCTANT-24578
+BLOCK OCTANT-124578
+BLOCK OCTANT-34578
+BLOCK OCTANT-134578
+BLOCK OCTANT-234578
+BLOCK OCTANT-1234578
+BLOCK OCTANT-678
+BLOCK OCTANT-1678
+BLOCK OCTANT-2678
+BLOCK OCTANT-12678
+BLOCK OCTANT-3678
+BLOCK OCTANT-13678
+BLOCK OCTANT-23678
+BLOCK OCTANT-123678
+BLOCK OCTANT-4678
+BLOCK OCTANT-14678
+BLOCK OCTANT-24678
+BLOCK OCTANT-124678
+BLOCK OCTANT-34678
+BLOCK OCTANT-134678
+BLOCK OCTANT-234678
+BLOCK OCTANT-1234678
+BLOCK OCTANT-15678
+BLOCK OCTANT-25678
+BLOCK OCTANT-125678
+BLOCK OCTANT-35678
+BLOCK OCTANT-235678
+BLOCK OCTANT-1235678
+BLOCK OCTANT-45678
+BLOCK OCTANT-145678
+BLOCK OCTANT-1245678
+BLOCK OCTANT-1345678
+BLOCK OCTANT-2345678
diff --git a/vendor/libghostty-vt/src/font/sprite/draw/powerline.zig b/vendor/libghostty-vt/src/font/sprite/draw/powerline.zig
new file mode 100644
index 00000000..8658d855
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/draw/powerline.zig
@@ -0,0 +1,394 @@
+//! Powerline + Powerline Extra Symbols | U+E0B0...U+E0D4
+//! https://github.com/ryanoasis/powerline-extra-symbols
+//!
+//!                
+//!              
+//!    
+//!
+//! We implement the more geometric glyphs here, but not the stylized ones.
+//!
+
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+
+const common = @import("common.zig");
+const Thickness = common.Thickness;
+const Shade = common.Shade;
+
+const box = @import("box.zig");
+
+const font = @import("../../main.zig");
+const Quad = font.sprite.Canvas.Quad;
+
+/// 
+pub fn drawE0B0(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+ _ = metrics;
+ const float_width: f64 = @floatFromInt(width);
+ const float_height: f64 = @floatFromInt(height);
+ try canvas.triangle(.{
+ .p0 = .{ .x = 0, .y = 0 },
+ .p1 = .{ .x = float_width, .y = float_height / 2 },
+ .p2 = .{ .x = 0, .y = float_height },
+ }, .on);
+}
+
+/// 
+pub fn drawE0B2(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+ _ = metrics;
+ const float_width: f64 = @floatFromInt(width);
+ const float_height: f64 = @floatFromInt(height);
+ try canvas.triangle(.{
+ .p0 = .{ .x = float_width, .y = 0 },
+ .p1 = .{ .x = 0, .y = float_height / 2 },
+ .p2 = .{ .x = float_width, .y = float_height },
+ }, .on);
+}
+
+/// 
+pub fn drawE0B8(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+ _ = metrics;
+ const float_width: f64 = @floatFromInt(width);
+ const float_height: f64 = @floatFromInt(height);
+ try canvas.triangle(.{
+ .p0 = .{ .x = 0, .y = 0 },
+ .p1 = .{ .x = float_width, .y = float_height },
+ .p2 = .{ .x = 0, .y = float_height },
+ }, .on);
+}
+
+/// 
+pub fn drawE0B9(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+ _ = width;
+ _ = height;
+ box.lightDiagonalUpperLeftToLowerRight(metrics, canvas);
+}
+
+/// 
+pub fn drawE0BA(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+ _ = metrics;
+ const float_width: f64 = @floatFromInt(width);
+ const float_height: f64 = @floatFromInt(height);
+ try canvas.triangle(.{
+ .p0 = .{ .x = float_width, .y = 0 },
+ .p1 = .{ .x = float_width, .y = float_height },
+ .p2 = .{ .x = 0, .y = float_height },
+ }, .on);
+}
+
+/// 
+pub fn drawE0BB(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+ _ = width;
+ _ = height;
+ box.lightDiagonalUpperRightToLowerLeft(metrics, canvas);
+}
+
+/// 
+pub fn drawE0BC(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+ _ = metrics;
+ const float_width: f64 = @floatFromInt(width);
+ const float_height: f64 = @floatFromInt(height);
+ try canvas.triangle(.{
+ .p0 = .{ .x = 0, .y = 0 },
+ .p1 = .{ .x = float_width, .y = 0 },
+ .p2 = .{ .x = 0, .y = float_height },
+ }, .on);
+}
+
+/// 
+pub fn drawE0BD(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+ _ = width;
+ _ = height;
+ box.lightDiagonalUpperRightToLowerLeft(metrics, canvas);
+}
+
+/// 
+pub fn drawE0BE(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+ _ = metrics;
+ const float_width: f64 = @floatFromInt(width);
+ const float_height: f64 = @floatFromInt(height);
+ try canvas.triangle(.{
+ .p0 = .{ .x = 0, .y = 0 },
+ .p1 = .{ .x = float_width, .y = 0 },
+ .p2 = .{ .x = float_width, .y = float_height },
+ }, .on);
+}
+
+/// 
+pub fn drawE0BF(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+ _ = width;
+ _ = height;
+ box.lightDiagonalUpperLeftToLowerRight(metrics, canvas);
+}
+
+/// 
+pub fn drawE0B1(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+ const float_width: f64 = @floatFromInt(width);
+ const float_height: f64 = @floatFromInt(height);
+
+ var path = canvas.staticPath(3);
+ path.moveTo(0, 0);
+ path.lineTo(float_width, float_height / 2);
+ path.lineTo(0, float_height);
+
+ try canvas.strokePath(
+ path.wrapped_path,
+ .{
+ .line_cap_mode = .butt,
+ .line_width = @floatFromInt(
+ Thickness.light.height(metrics.box_thickness),
+ ),
+ },
+ .on,
+ );
+}
+
+/// 
+pub fn drawE0B3(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ try drawE0B1(cp, canvas, width, height, metrics);
+ try canvas.flipHorizontal();
+}
+
+/// 
+pub fn drawE0B4(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+ _ = metrics;
+
+ const float_width: f64 = @floatFromInt(width);
+ const float_height: f64 = @floatFromInt(height);
+
+ // Coefficient for approximating a circular arc.
+ const c: f64 = (std.math.sqrt2 - 1.0) * 4.0 / 3.0;
+
+ const radius: f64 = @min(float_width, float_height / 2);
+
+ var path = canvas.staticPath(6);
+ path.moveTo(0, 0);
+ path.curveTo(
+ radius * c,
+ 0,
+ radius,
+ radius - radius * c,
+ radius,
+ radius,
+ );
+ path.lineTo(radius, float_height - radius);
+ path.curveTo(
+ radius,
+ float_height - radius + radius * c,
+ radius * c,
+ float_height,
+ 0,
+ float_height,
+ );
+ path.close();
+
+ try canvas.fillPath(path.wrapped_path, .{}, .on);
+}
+
+/// 
+pub fn drawE0B5(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+
+ const float_width: f64 = @floatFromInt(width);
+ const float_height: f64 = @floatFromInt(height);
+
+ // Coefficient for approximating a circular arc.
+ const c: f64 = (std.math.sqrt2 - 1.0) * 4.0 / 3.0;
+
+ const radius: f64 = @min(float_width, float_height / 2);
+
+ var path = canvas.staticPath(4);
+ path.moveTo(0, 0);
+ path.curveTo(
+ radius * c,
+ 0,
+ radius,
+ radius - radius * c,
+ radius,
+ radius,
+ );
+ path.lineTo(radius, float_height - radius);
+ path.curveTo(
+ radius,
+ float_height - radius + radius * c,
+ radius * c,
+ float_height,
+ 0,
+ float_height,
+ );
+
+ try canvas.innerStrokePath(path.wrapped_path, .{
+ .line_width = @floatFromInt(metrics.box_thickness),
+ .line_cap_mode = .butt,
+ }, .on);
+}
+
+/// 
+pub fn drawE0B6(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ try drawE0B4(cp, canvas, width, height, metrics);
+ try canvas.flipHorizontal();
+}
+
+/// 
+pub fn drawE0B7(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ try drawE0B5(cp, canvas, width, height, metrics);
+ try canvas.flipHorizontal();
+}
+
+/// 
+pub fn drawE0D2(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+
+ const float_width: f64 = @floatFromInt(width);
+ const float_height: f64 = @floatFromInt(height);
+ const float_thick: f64 = @floatFromInt(metrics.box_thickness);
+
+ // Top piece
+ {
+ var path = canvas.staticPath(6);
+ path.moveTo(0, 0);
+ path.lineTo(float_width, 0);
+ path.lineTo(float_width / 2, float_height / 2 - float_thick / 2);
+ path.lineTo(0, float_height / 2 - float_thick / 2);
+ path.close();
+
+ try canvas.fillPath(path.wrapped_path, .{}, .on);
+ }
+
+ // Bottom piece
+ {
+ var path = canvas.staticPath(6);
+ path.moveTo(0, float_height);
+ path.lineTo(float_width, float_height);
+ path.lineTo(float_width / 2, float_height / 2 + float_thick / 2);
+ path.lineTo(0, float_height / 2 + float_thick / 2);
+ path.close();
+
+ try canvas.fillPath(path.wrapped_path, .{}, .on);
+ }
+}
+
+/// 
+pub fn drawE0D4(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ try drawE0D2(cp, canvas, width, height, metrics);
+ try canvas.flipHorizontal();
+}
diff --git a/vendor/libghostty-vt/src/font/sprite/draw/special.zig b/vendor/libghostty-vt/src/font/sprite/draw/special.zig
new file mode 100644
index 00000000..8cad9ceb
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/draw/special.zig
@@ -0,0 +1,372 @@
+//! This file contains glyph drawing functions for all of the
+//! non-Unicode sprite glyphs, such as cursors and underlines.
+//!
+//! The naming convention in this file differs from the usual
+//! because the draw functions for special sprites are found by
+//! having names that exactly match the enum fields in Sprite.
+
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const font = @import("../../main.zig");
+const Sprite = font.sprite.Sprite;
+
+pub fn underline(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+
+ // We can go beyond the height of the cell a bit, but
+ // we want to be sure never to exceed the height of the
+ // canvas, which extends a quarter cell below the cell
+ // height.
+ const y = @min(
+ metrics.underline_position,
+ height +| canvas.padding_y -| metrics.underline_thickness,
+ );
+
+ canvas.rect(.{
+ .x = 0,
+ .y = @intCast(y),
+ .width = @intCast(width),
+ .height = @intCast(metrics.underline_thickness),
+ }, .on);
+}
+
+pub fn underline_double(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+
+ // We can go beyond the height of the cell a bit, but
+ // we want to be sure never to exceed the height of the
+ // canvas, which extends a quarter cell below the cell
+ // height.
+ const y = @min(
+ metrics.underline_position,
+ height +| canvas.padding_y -| 2 * metrics.underline_thickness,
+ );
+
+ // We place one underline above the underline position, and one below
+ // by one thickness, creating a "negative" underline where the single
+ // underline would be placed.
+ canvas.rect(.{
+ .x = 0,
+ .y = @intCast(y -| metrics.underline_thickness),
+ .width = @intCast(width),
+ .height = @intCast(metrics.underline_thickness),
+ }, .on);
+ canvas.rect(.{
+ .x = 0,
+ .y = @intCast(y +| metrics.underline_thickness),
+ .width = @intCast(width),
+ .height = @intCast(metrics.underline_thickness),
+ }, .on);
+}
+
+pub fn underline_dotted(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+
+ var ctx = canvas.getContext();
+ defer ctx.deinit();
+
+ const float_width: f64 = @floatFromInt(width);
+ const float_height: f64 = @floatFromInt(height);
+ const float_pos: f64 = @floatFromInt(metrics.underline_position);
+ const float_thick: f64 = @floatFromInt(metrics.underline_thickness);
+
+ // The diameter will be sqrt2 * the usual underline thickness
+ // since otherwise dotted underlines look somewhat anemic.
+ const radius = std.math.sqrt1_2 * float_thick;
+
+ // We can go beyond the height of the cell a bit, but
+ // we want to be sure never to exceed the height of the
+ // canvas, which extends a quarter cell below the cell
+ // height.
+ const padding: f64 = @floatFromInt(canvas.padding_y);
+ const y = @min(
+ // The center of the underline stem.
+ float_pos + 0.5 * float_thick,
+ // The lowest we can go on the canvas and not get clipped.
+ float_height + padding - @ceil(radius),
+ );
+
+ const dot_count: f64 = @max(
+ @min(
+ // We should try to have enough dots that the
+ // space between them matches their diameter.
+ @ceil(float_width / (4 * radius)),
+ // And not enough that the space between
+ // each dot is less than their radius.
+ @floor(float_width / (3 * radius)),
+ // And definitely not enough that the space
+ // between them is less than a single pixel.
+ @floor(float_width / (2 * radius + 1)),
+ ),
+ // And we must have at least one dot per cell.
+ 1.0,
+ );
+
+ // What we essentially do is divide the cell in to
+ // dot_count areas with a dot centered in each one.
+ var x: f64 = (float_width / dot_count) / 2;
+ for (0..@as(usize, @intFromFloat(dot_count))) |_| {
+ try ctx.arc(x, y, radius, 0.0, std.math.tau);
+ try ctx.closePath();
+ x += float_width / dot_count;
+ }
+
+ try ctx.fill();
+}
+
+pub fn underline_dashed(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+
+ // We can go beyond the height of the cell a bit, but
+ // we want to be sure never to exceed the height of the
+ // canvas, which extends a quarter cell below the cell
+ // height.
+ const y = @min(
+ metrics.underline_position,
+ height +| canvas.padding_y -| metrics.underline_thickness,
+ );
+
+ const dash_width = width / 3 + 1;
+ const dash_count = (width / dash_width) + 1;
+ var i: u32 = 0;
+ while (i < dash_count) : (i += 2) {
+ const x = i * dash_width;
+ canvas.rect(.{
+ .x = @intCast(x),
+ .y = @intCast(y),
+ .width = @intCast(dash_width),
+ .height = @intCast(metrics.underline_thickness),
+ }, .on);
+ }
+}
+
+pub fn underline_curly(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+
+ var ctx = canvas.getContext();
+ defer ctx.deinit();
+
+ const float_width: f64 = @floatFromInt(width);
+ const float_height: f64 = @floatFromInt(height);
+ const float_pos: f64 = @floatFromInt(metrics.underline_position);
+
+ // Because of we way we draw the undercurl, we end up making it around 1px
+ // thicker than it should be, to fix this we just reduce the thickness by 1.
+ //
+ // We use a minimum thickness of 0.414 because this empirically produces
+ // the nicest undercurls at 1px underline thickness; thinner tends to look
+ // too thin compared to straight underlines and has artefacting.
+ ctx.line_width = @floatFromInt(metrics.underline_thickness);
+
+ // Rounded caps, adjacent underlines will have these overlap and so not be
+ // visible, but it makes the ends look cleaner.
+ ctx.line_cap_mode = .round;
+
+ // Empirically this looks good.
+ const amplitude = float_width / std.math.pi;
+
+ // Make sure we don't exceed the drawable area. This can still be outside
+ // of the cell by some amount (one quarter of the height), but we don't
+ // want underlines to disappear for fonts with bad metadata or when users
+ // set their underline position way too low.
+ const padding: f64 = @floatFromInt(canvas.padding_y);
+ const top: f64 = @min(
+ float_pos,
+ // The lowest we can draw this and not get clipped.
+ float_height + padding - amplitude - ctx.line_width,
+ );
+ const bottom = top + amplitude;
+
+ // Curvature multiplier.
+ // To my eye, 0.4 creates a nice smooth wiggle.
+ const r = 0.4;
+
+ const center = 0.5 * float_width;
+
+ // We create a single cycle of a wave that peaks at the center of the cell.
+ try ctx.moveTo(0, bottom);
+ try ctx.curveTo(
+ center * r,
+ bottom,
+ center - center * r,
+ top,
+ center,
+ top,
+ );
+ try ctx.curveTo(
+ center + center * r,
+ top,
+ float_width - center * r,
+ bottom,
+ float_width,
+ bottom,
+ );
+ try ctx.stroke();
+}
+
+pub fn strikethrough(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+ _ = height;
+
+ canvas.rect(.{
+ .x = 0,
+ .y = @intCast(metrics.strikethrough_position),
+ .width = @intCast(width),
+ .height = @intCast(metrics.strikethrough_thickness),
+ }, .on);
+}
+
+pub fn overline(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+ _ = height;
+
+ // We can go beyond the top of the cell a bit, but we
+ // want to be sure never to exceed the height of the
+ // canvas, which extends a quarter cell above the top
+ // of the cell.
+ const y = @max(
+ metrics.overline_position,
+ -@as(i32, @intCast(canvas.padding_y)),
+ );
+
+ canvas.rect(.{
+ .x = 0,
+ .y = y,
+ .width = @intCast(width),
+ .height = @intCast(metrics.overline_thickness),
+ }, .on);
+}
+
+pub fn cursor_rect(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+ _ = metrics;
+
+ canvas.rect(.{
+ .x = 0,
+ .y = 0,
+ .width = @intCast(width),
+ .height = @intCast(height),
+ }, .on);
+}
+
+pub fn cursor_hollow_rect(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+
+ // We fill the entire rect and then hollow out the inside, this isn't very
+ // efficient but it doesn't need to be and it's the easiest way to write it.
+ canvas.rect(.{
+ .x = 0,
+ .y = 0,
+ .width = @intCast(width),
+ .height = @intCast(height),
+ }, .on);
+ canvas.rect(.{
+ .x = @intCast(metrics.cursor_thickness),
+ .y = @intCast(metrics.cursor_thickness),
+ .width = @intCast(width -| metrics.cursor_thickness * 2),
+ .height = @intCast(height -| metrics.cursor_thickness * 2),
+ }, .off);
+}
+
+pub fn cursor_bar(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+ _ = width;
+
+ // We place the bar cursor half of its thickness over the left edge of the
+ // cell, so that it sits centered between characters, not biased to a side.
+ //
+ // We round up (add 1 before dividing by 2) because, empirically, having a
+ // 1px cursor shifted left a pixel looks better than having it not shifted.
+ canvas.rect(.{
+ .x = -@as(i32, @intCast((metrics.cursor_thickness + 1) / 2)),
+ .y = 0,
+ .width = @intCast(metrics.cursor_thickness),
+ .height = @intCast(height),
+ }, .on);
+}
+
+pub fn cursor_underline(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+
+ // We can go beyond the height of the cell a bit, but
+ // we want to be sure never to exceed the height of the
+ // canvas, which extends a quarter cell below the cell
+ // height.
+ const y = @min(
+ metrics.underline_position,
+ height +| canvas.padding_y -| metrics.underline_thickness,
+ );
+
+ canvas.rect(.{
+ .x = 0,
+ .y = @intCast(y),
+ .width = @intCast(width),
+ .height = @intCast(metrics.cursor_thickness),
+ }, .on);
+}
diff --git a/vendor/libghostty-vt/src/font/sprite/draw/symbols_for_legacy_computing.zig b/vendor/libghostty-vt/src/font/sprite/draw/symbols_for_legacy_computing.zig
new file mode 100644
index 00000000..d99fc870
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/draw/symbols_for_legacy_computing.zig
@@ -0,0 +1,1413 @@
+//! Symbols for Legacy Computing | U+1FB00...U+1FBFF
+//! https://en.wikipedia.org/wiki/Symbols_for_Legacy_Computing
+//!
+//! 🬀 🬁 🬂 🬃 🬄 🬅 🬆 🬇 🬈 🬉 🬊 🬋 🬌 🬍 🬎 🬏
+//! 🬐 🬑 🬒 🬓 🬔 🬕 🬖 🬗 🬘 🬙 🬚 🬛 🬜 🬝 🬞 🬟
+//! 🬠 🬡 🬢 🬣 🬤 🬥 🬦 🬧 🬨 🬩 🬪 🬫 🬬 🬭 🬮 🬯
+//! 🬰 🬱 🬲 🬳 🬴 🬵 🬶 🬷 🬸 🬹 🬺 🬻 🬼 🬽 🬾 🬿
+//! 🭀 🭁 🭂 🭃 🭄 🭅 🭆 🭇 🭈 🭉 🭊 🭋 🭌 🭍 🭎 🭏
+//! 🭐 🭑 🭒 🭓 🭔 🭕 🭖 🭗 🭘 🭙 🭚 🭛 🭜 🭝 🭞 🭟
+//! 🭠 🭡 🭢 🭣 🭤 🭥 🭦 🭧 🭨 🭩 🭪 🭫 🭬 🭭 🭮 🭯
+//! 🭰 🭱 🭲 🭳 🭴 🭵 🭶 🭷 🭸 🭹 🭺 🭻 🭼 🭽 🭾 🭿
+//! 🮀 🮁 🮂 🮃 🮄 🮅 🮆 🮇 🮈 🮉 🮊 🮋 🮌 🮍 🮎 🮏
+//! 🮐 🮑 🮒 🮔 🮕 🮖 🮗 🮘 🮙 🮚 🮛 🮜 🮝 🮞 🮟
+//! 🮠 🮡 🮢 🮣 🮤 🮥 🮦 🮧 🮨 🮩 🮪 🮫 🮬 🮭 🮮 🮯
+//! 🮰 🮱 🮲 🮳 🮴 🮵 🮶 🮷 🮸 🮹 🮺 🮻 🮼 🮽 🮾 🮿
+//! 🯀 🯁 🯂 🯃 🯄 🯅 🯆 🯇 🯈 🯉 🯊 🯋 🯌 🯍 🯎 🯏
+//! 🯐 🯑 🯒 🯓 🯔 🯕 🯖 🯗 🯘 🯙 🯚 🯛 🯜 🯝 🯞 🯟
+//! 🯠 🯡 🯢 🯣 🯤 🯥 🯦 🯧 🯨 🯩 🯪 🯫 🯬 🯭 🯮 🯯
+//! 🯰 🯱 🯲 🯳 🯴 🯵 🯶 🯷 🯸 🯹
+//!
+
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const assert = @import("../../../quirks.zig").inlineAssert;
+
+const common = @import("common.zig");
+const Thickness = common.Thickness;
+const Alignment = common.Alignment;
+const Fraction = common.Fraction;
+const Corner = common.Corner;
+const Quads = common.Quads;
+const Edge = common.Edge;
+const Shade = common.Shade;
+const fill = common.fill;
+
+const box = @import("box.zig");
+const block = @import("block.zig");
+const geo = @import("geometric_shapes.zig");
+
+const font = @import("../../main.zig");
+
+// Utility names for common fractions
+const one_eighth: f64 = 0.125;
+const one_quarter: f64 = 0.25;
+const one_third: f64 = (1.0 / 3.0);
+const three_eighths: f64 = 0.375;
+const half: f64 = 0.5;
+const five_eighths: f64 = 0.625;
+const two_thirds: f64 = (2.0 / 3.0);
+const three_quarters: f64 = 0.75;
+const seven_eighths: f64 = 0.875;
+
+const SmoothMosaic = packed struct(u10) {
+ tl: bool,
+ ul: bool,
+ ll: bool,
+ bl: bool,
+ bc: bool,
+ br: bool,
+ lr: bool,
+ ur: bool,
+ tr: bool,
+ tc: bool,
+
+ fn from(comptime pattern: *const [15:0]u8) SmoothMosaic {
+ return .{
+ .tl = pattern[0] == '#',
+
+ .ul = pattern[4] == '#' and
+ (pattern[0] != '#' or pattern[8] != '#'),
+
+ .ll = pattern[8] == '#' and
+ (pattern[4] != '#' or pattern[12] != '#'),
+
+ .bl = pattern[12] == '#',
+
+ .bc = pattern[13] == '#' and
+ (pattern[12] != '#' or pattern[14] != '#'),
+
+ .br = pattern[14] == '#',
+
+ .lr = pattern[10] == '#' and
+ (pattern[14] != '#' or pattern[6] != '#'),
+
+ .ur = pattern[6] == '#' and
+ (pattern[10] != '#' or pattern[2] != '#'),
+
+ .tr = pattern[2] == '#',
+
+ .tc = pattern[1] == '#' and
+ (pattern[2] != '#' or pattern[0] != '#'),
+ };
+ }
+};
+
+/// Sextants
+pub fn draw1FB00_1FB3B(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = width;
+ _ = height;
+
+ const Sextants = packed struct(u6) {
+ tl: bool,
+ tr: bool,
+ ml: bool,
+ mr: bool,
+ bl: bool,
+ br: bool,
+ };
+
+ assert(cp >= 0x1fb00 and cp <= 0x1fb3b);
+ const idx = cp - 0x1fb00;
+ const sex: Sextants = @bitCast(@as(u6, @intCast(
+ idx + (idx / 0x14) + 1,
+ )));
+ if (sex.tl) fill(metrics, canvas, .zero, .half, .zero, .one_third);
+ if (sex.tr) fill(metrics, canvas, .half, .full, .zero, .one_third);
+ if (sex.ml) fill(metrics, canvas, .zero, .half, .one_third, .two_thirds);
+ if (sex.mr) fill(metrics, canvas, .half, .full, .one_third, .two_thirds);
+ if (sex.bl) fill(metrics, canvas, .zero, .half, .two_thirds, .end);
+ if (sex.br) fill(metrics, canvas, .half, .full, .two_thirds, .end);
+}
+
+/// Smooth Mosaics
+pub fn draw1FB3C_1FB67(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = width;
+ _ = height;
+
+ // Hand written lookup table for these shapes since I couldn't
+ // determine any sort of mathematical pattern in the codepoints.
+ const mosaic: SmoothMosaic = switch (cp) {
+ // '🬼'
+ 0x1fb3c => SmoothMosaic.from(
+ \\...
+ \\...
+ \\#..
+ \\##.
+ ),
+ // '🬽'
+ 0x1fb3d => SmoothMosaic.from(
+ \\...
+ \\...
+ \\#\.
+ \\###
+ ),
+ // '🬾'
+ 0x1fb3e => SmoothMosaic.from(
+ \\...
+ \\#..
+ \\#\.
+ \\##.
+ ),
+ // '🬿'
+ 0x1fb3f => SmoothMosaic.from(
+ \\...
+ \\#..
+ \\##.
+ \\###
+ ),
+ // '🭀'
+ 0x1fb40 => SmoothMosaic.from(
+ \\#..
+ \\#..
+ \\##.
+ \\##.
+ ),
+
+ // '🭁'
+ 0x1fb41 => SmoothMosaic.from(
+ \\/##
+ \\###
+ \\###
+ \\###
+ ),
+ // '🭂'
+ 0x1fb42 => SmoothMosaic.from(
+ \\./#
+ \\###
+ \\###
+ \\###
+ ),
+ // '🭃'
+ 0x1fb43 => SmoothMosaic.from(
+ \\.##
+ \\.##
+ \\###
+ \\###
+ ),
+ // '🭄'
+ 0x1fb44 => SmoothMosaic.from(
+ \\..#
+ \\.##
+ \\###
+ \\###
+ ),
+ // '🭅'
+ 0x1fb45 => SmoothMosaic.from(
+ \\.##
+ \\.##
+ \\.##
+ \\###
+ ),
+ // '🭆'
+ 0x1fb46 => SmoothMosaic.from(
+ \\...
+ \\./#
+ \\###
+ \\###
+ ),
+
+ // '🭇'
+ 0x1fb47 => SmoothMosaic.from(
+ \\...
+ \\...
+ \\..#
+ \\.##
+ ),
+ // '🭈'
+ 0x1fb48 => SmoothMosaic.from(
+ \\...
+ \\...
+ \\./#
+ \\###
+ ),
+ // '🭉'
+ 0x1fb49 => SmoothMosaic.from(
+ \\...
+ \\..#
+ \\./#
+ \\.##
+ ),
+ // '🭊'
+ 0x1fb4a => SmoothMosaic.from(
+ \\...
+ \\..#
+ \\.##
+ \\###
+ ),
+ // '🭋'
+ 0x1fb4b => SmoothMosaic.from(
+ \\..#
+ \\..#
+ \\.##
+ \\.##
+ ),
+
+ // '🭌'
+ 0x1fb4c => SmoothMosaic.from(
+ \\##\
+ \\###
+ \\###
+ \\###
+ ),
+ // '🭍'
+ 0x1fb4d => SmoothMosaic.from(
+ \\#\.
+ \\###
+ \\###
+ \\###
+ ),
+ // '🭎'
+ 0x1fb4e => SmoothMosaic.from(
+ \\##.
+ \\##.
+ \\###
+ \\###
+ ),
+ // '🭏'
+ 0x1fb4f => SmoothMosaic.from(
+ \\#..
+ \\##.
+ \\###
+ \\###
+ ),
+ // '🭐'
+ 0x1fb50 => SmoothMosaic.from(
+ \\##.
+ \\##.
+ \\##.
+ \\###
+ ),
+ // '🭑'
+ 0x1fb51 => SmoothMosaic.from(
+ \\...
+ \\#\.
+ \\###
+ \\###
+ ),
+
+ // '🭒'
+ 0x1fb52 => SmoothMosaic.from(
+ \\###
+ \\###
+ \\###
+ \\\##
+ ),
+ // '🭓'
+ 0x1fb53 => SmoothMosaic.from(
+ \\###
+ \\###
+ \\###
+ \\.\#
+ ),
+ // '🭔'
+ 0x1fb54 => SmoothMosaic.from(
+ \\###
+ \\###
+ \\.##
+ \\.##
+ ),
+ // '🭕'
+ 0x1fb55 => SmoothMosaic.from(
+ \\###
+ \\###
+ \\.##
+ \\..#
+ ),
+ // '🭖'
+ 0x1fb56 => SmoothMosaic.from(
+ \\###
+ \\.##
+ \\.##
+ \\.##
+ ),
+
+ // '🭗'
+ 0x1fb57 => SmoothMosaic.from(
+ \\##.
+ \\#..
+ \\...
+ \\...
+ ),
+ // '🭘'
+ 0x1fb58 => SmoothMosaic.from(
+ \\###
+ \\#/.
+ \\...
+ \\...
+ ),
+ // '🭙'
+ 0x1fb59 => SmoothMosaic.from(
+ \\##.
+ \\#/.
+ \\#..
+ \\...
+ ),
+ // '🭚'
+ 0x1fb5a => SmoothMosaic.from(
+ \\###
+ \\##.
+ \\#..
+ \\...
+ ),
+ // '🭛'
+ 0x1fb5b => SmoothMosaic.from(
+ \\##.
+ \\##.
+ \\#..
+ \\#..
+ ),
+
+ // '🭜'
+ 0x1fb5c => SmoothMosaic.from(
+ \\###
+ \\###
+ \\#/.
+ \\...
+ ),
+ // '🭝'
+ 0x1fb5d => SmoothMosaic.from(
+ \\###
+ \\###
+ \\###
+ \\##/
+ ),
+ // '🭞'
+ 0x1fb5e => SmoothMosaic.from(
+ \\###
+ \\###
+ \\###
+ \\#/.
+ ),
+ // '🭟'
+ 0x1fb5f => SmoothMosaic.from(
+ \\###
+ \\###
+ \\##.
+ \\##.
+ ),
+ // '🭠'
+ 0x1fb60 => SmoothMosaic.from(
+ \\###
+ \\###
+ \\##.
+ \\#..
+ ),
+ // '🭡'
+ 0x1fb61 => SmoothMosaic.from(
+ \\###
+ \\##.
+ \\##.
+ \\##.
+ ),
+
+ // '🭢'
+ 0x1fb62 => SmoothMosaic.from(
+ \\.##
+ \\..#
+ \\...
+ \\...
+ ),
+ // '🭣'
+ 0x1fb63 => SmoothMosaic.from(
+ \\###
+ \\.\#
+ \\...
+ \\...
+ ),
+ // '🭤'
+ 0x1fb64 => SmoothMosaic.from(
+ \\.##
+ \\.\#
+ \\..#
+ \\...
+ ),
+ // '🭥'
+ 0x1fb65 => SmoothMosaic.from(
+ \\###
+ \\.##
+ \\..#
+ \\...
+ ),
+ // '🭦'
+ 0x1fb66 => SmoothMosaic.from(
+ \\.##
+ \\.##
+ \\..#
+ \\..#
+ ),
+ // '🭧'
+ 0x1fb67 => SmoothMosaic.from(
+ \\###
+ \\###
+ \\.\#
+ \\...
+ ),
+ else => unreachable,
+ };
+
+ const top: f64 = 0.0;
+ const upper: f64 = Fraction.one_third.float(metrics.cell_height);
+ const lower: f64 = Fraction.two_thirds.float(metrics.cell_height);
+ const bottom: f64 = @floatFromInt(metrics.cell_height);
+ const left: f64 = 0.0;
+ const center: f64 = Fraction.half.float(metrics.cell_width);
+ const right: f64 = @floatFromInt(metrics.cell_width);
+
+ var path = canvas.staticPath(12); // nodes.len = 0
+ if (mosaic.tl) path.lineTo(left, top); // +1, nodes.len = 1
+ if (mosaic.ul) path.lineTo(left, upper); // +1, nodes.len = 2
+ if (mosaic.ll) path.lineTo(left, lower); // +1, nodes.len = 3
+ if (mosaic.bl) path.lineTo(left, bottom); // +1, nodes.len = 4
+ if (mosaic.bc) path.lineTo(center, bottom); // +1, nodes.len = 5
+ if (mosaic.br) path.lineTo(right, bottom); // +1, nodes.len = 6
+ if (mosaic.lr) path.lineTo(right, lower); // +1, nodes.len = 7
+ if (mosaic.ur) path.lineTo(right, upper); // +1, nodes.len = 8
+ if (mosaic.tr) path.lineTo(right, top); // +1, nodes.len = 9
+ if (mosaic.tc) path.lineTo(center, top); // +1, nodes.len = 10
+ path.close(); // +2, nodes.len = 12
+
+ try canvas.fillPath(path.wrapped_path, .{}, .on);
+}
+
+pub fn draw1FB68_1FB6F(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = width;
+ _ = height;
+
+ switch (cp) {
+ // '🭨'
+ 0x1fb68 => {
+ try edgeTriangle(metrics, canvas, .left);
+ canvas.invert();
+ // Set the clip so we don't include anything outside of the cell.
+ canvas.clip_left = canvas.padding_x;
+ canvas.clip_right = canvas.padding_x;
+ canvas.clip_top = canvas.padding_y;
+ canvas.clip_bottom = canvas.padding_y;
+ },
+ // '🭩'
+ 0x1fb69 => {
+ try edgeTriangle(metrics, canvas, .top);
+ canvas.invert();
+ // Set the clip so we don't include anything outside of the cell.
+ canvas.clip_left = canvas.padding_x;
+ canvas.clip_right = canvas.padding_x;
+ canvas.clip_top = canvas.padding_y;
+ canvas.clip_bottom = canvas.padding_y;
+ },
+ // '🭪'
+ 0x1fb6a => {
+ try edgeTriangle(metrics, canvas, .right);
+ canvas.invert();
+ // Set the clip so we don't include anything outside of the cell.
+ canvas.clip_left = canvas.padding_x;
+ canvas.clip_right = canvas.padding_x;
+ canvas.clip_top = canvas.padding_y;
+ canvas.clip_bottom = canvas.padding_y;
+ },
+ // '🭫'
+ 0x1fb6b => {
+ try edgeTriangle(metrics, canvas, .bottom);
+ canvas.invert();
+ // Set the clip so we don't include anything outside of the cell.
+ canvas.clip_left = canvas.padding_x;
+ canvas.clip_right = canvas.padding_x;
+ canvas.clip_top = canvas.padding_y;
+ canvas.clip_bottom = canvas.padding_y;
+ },
+ // '🭬'
+ 0x1fb6c => try edgeTriangle(metrics, canvas, .left),
+ // '🭭'
+ 0x1fb6d => try edgeTriangle(metrics, canvas, .top),
+ // '🭮'
+ 0x1fb6e => try edgeTriangle(metrics, canvas, .right),
+ // '🭯'
+ 0x1fb6f => try edgeTriangle(metrics, canvas, .bottom),
+
+ else => unreachable,
+ }
+}
+
+/// Vertical one eighth blocks
+pub fn draw1FB70_1FB75(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = width;
+ _ = height;
+
+ const n = cp + 1 - 0x1fb70;
+
+ fill(
+ metrics,
+ canvas,
+ Fraction.eighths[n],
+ Fraction.eighths[n + 1],
+ .top,
+ .bottom,
+ );
+}
+
+/// Horizontal one eighth blocks
+pub fn draw1FB76_1FB7B(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = width;
+ _ = height;
+
+ const n = cp + 1 - 0x1fb76;
+
+ fill(
+ metrics,
+ canvas,
+ .left,
+ .right,
+ Fraction.eighths[n],
+ Fraction.eighths[n + 1],
+ );
+}
+
+pub fn draw1FB7C_1FB97(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ switch (cp) {
+
+ // '🭼' LEFT AND LOWER ONE EIGHTH BLOCK
+ 0x1fb7c => {
+ block.block(metrics, canvas, .left, one_eighth, 1);
+ block.block(metrics, canvas, .lower, 1, one_eighth);
+ },
+ // '🭽' LEFT AND UPPER ONE EIGHTH BLOCK
+ 0x1fb7d => {
+ block.block(metrics, canvas, .left, one_eighth, 1);
+ block.block(metrics, canvas, .upper, 1, one_eighth);
+ },
+ // '🭾' RIGHT AND UPPER ONE EIGHTH BLOCK
+ 0x1fb7e => {
+ block.block(metrics, canvas, .right, one_eighth, 1);
+ block.block(metrics, canvas, .upper, 1, one_eighth);
+ },
+ // '🭿' RIGHT AND LOWER ONE EIGHTH BLOCK
+ 0x1fb7f => {
+ block.block(metrics, canvas, .right, one_eighth, 1);
+ block.block(metrics, canvas, .lower, 1, one_eighth);
+ },
+ // '🮀' UPPER AND LOWER ONE EIGHTH BLOCK
+ 0x1fb80 => {
+ block.block(metrics, canvas, .upper, 1, one_eighth);
+ block.block(metrics, canvas, .lower, 1, one_eighth);
+ },
+ // '🮁' Horizontal One Eighth Block 1358
+ 0x1fb81 => {
+ // We just call the draw function for each of the relevant codepoints.
+ // The first codepoint is actually a lie, it's before the range, but
+ // we need it to get the first (0th) block position. This might be a
+ // bit brittle, oh well, if it breaks we can fix it.
+ try draw1FB76_1FB7B(0x1fb74 + 1, canvas, width, height, metrics);
+ try draw1FB76_1FB7B(0x1fb74 + 3, canvas, width, height, metrics);
+ try draw1FB76_1FB7B(0x1fb74 + 5, canvas, width, height, metrics);
+ try draw1FB76_1FB7B(0x1fb74 + 8, canvas, width, height, metrics);
+ },
+
+ // '🮂' UPPER ONE QUARTER BLOCK
+ 0x1fb82 => block.block(metrics, canvas, .upper, 1, one_quarter),
+ // '🮃' UPPER THREE EIGHTHS BLOCK
+ 0x1fb83 => block.block(metrics, canvas, .upper, 1, three_eighths),
+ // '🮄' UPPER FIVE EIGHTHS BLOCK
+ 0x1fb84 => block.block(metrics, canvas, .upper, 1, five_eighths),
+ // '🮅' UPPER THREE QUARTERS BLOCK
+ 0x1fb85 => block.block(metrics, canvas, .upper, 1, three_quarters),
+ // '🮆' UPPER SEVEN EIGHTHS BLOCK
+ 0x1fb86 => block.block(metrics, canvas, .upper, 1, seven_eighths),
+
+ // '🮇' RIGHT ONE QUARTER BLOCK
+ 0x1fb87 => block.block(metrics, canvas, .right, one_quarter, 1),
+ // '🮈' RIGHT THREE EIGHTHS BLOCK
+ 0x1fb88 => block.block(metrics, canvas, .right, three_eighths, 1),
+ // '🮉' RIGHT FIVE EIGHTHS BLOCK
+ 0x1fb89 => block.block(metrics, canvas, .right, five_eighths, 1),
+ // '🮊' RIGHT THREE QUARTERS BLOCK
+ 0x1fb8a => block.block(metrics, canvas, .right, three_quarters, 1),
+ // '🮋' RIGHT SEVEN EIGHTHS BLOCK/
+ 0x1fb8b => block.block(metrics, canvas, .right, seven_eighths, 1),
+
+ // '🮌'
+ 0x1fb8c => block.blockShade(metrics, canvas, .left, half, 1, .medium),
+ // '🮍'
+ 0x1fb8d => block.blockShade(metrics, canvas, .right, half, 1, .medium),
+ // '🮎'
+ 0x1fb8e => block.blockShade(metrics, canvas, .upper, 1, half, .medium),
+ // '🮏'
+ 0x1fb8f => block.blockShade(metrics, canvas, .lower, 1, half, .medium),
+
+ // '🮐'
+ 0x1fb90 => block.fullBlockShade(metrics, canvas, .medium),
+ // '🮑'
+ 0x1fb91 => {
+ block.fullBlockShade(metrics, canvas, .medium);
+ block.block(metrics, canvas, .upper, 1, half);
+ },
+ // '🮒'
+ 0x1fb92 => {
+ block.fullBlockShade(metrics, canvas, .medium);
+ block.block(metrics, canvas, .lower, 1, half);
+ },
+ 0x1fb93 => {
+ // NOTE: This codepoint is currently un-allocated, it's a hole
+ // in the unicode block, so it's safe to just render it
+ // as an empty glyph, probably.
+ },
+ // '🮔'
+ 0x1fb94 => {
+ block.fullBlockShade(metrics, canvas, .medium);
+ block.block(metrics, canvas, .right, half, 1);
+ },
+ // '🮕'
+ 0x1fb95 => checkerboardFill(metrics, canvas, 0),
+ // '🮖'
+ 0x1fb96 => checkerboardFill(metrics, canvas, 1),
+ // '🮗'
+ 0x1fb97 => {
+ canvas.box(
+ 0,
+ @intCast(height / 4),
+ @intCast(width),
+ @intCast(2 * height / 4),
+ .on,
+ );
+ canvas.box(
+ 0,
+ @intCast(3 * height / 4),
+ @intCast(width),
+ @intCast(height),
+ .on,
+ );
+ },
+
+ else => unreachable,
+ }
+}
+
+/// Upper Left to Lower Right Fill
+/// 🮘
+pub fn draw1FB98(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+ _ = width;
+ _ = height;
+
+ // Set the clip so we don't include anything outside of the cell.
+ canvas.clip_left = canvas.padding_x;
+ canvas.clip_right = canvas.padding_x;
+ canvas.clip_top = canvas.padding_y;
+ canvas.clip_bottom = canvas.padding_y;
+
+ // TODO: This doesn't align properly for most cell sizes, fix that.
+
+ const thick_px = Thickness.light.height(metrics.box_thickness);
+ const line_count = metrics.cell_width / (2 * thick_px);
+
+ const float_width: f64 = @floatFromInt(metrics.cell_width);
+ const float_height: f64 = @floatFromInt(metrics.cell_height);
+ const float_thick: f64 = @floatFromInt(thick_px);
+ const stride = @round(float_width / @as(f64, @floatFromInt(line_count)));
+
+ for (0..line_count * 2 + 1) |_i| {
+ const i = @as(i32, @intCast(_i)) - @as(i32, @intCast(line_count));
+ const top_x = @as(f64, @floatFromInt(i)) * stride;
+ const bottom_x = float_width + top_x;
+ canvas.line(.{
+ .p0 = .{ .x = top_x, .y = 0 },
+ .p1 = .{ .x = bottom_x, .y = float_height },
+ }, float_thick, .on) catch {};
+ }
+}
+
+/// Upper Right to Lower Left Fill
+/// 🮙
+pub fn draw1FB99(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+ _ = width;
+ _ = height;
+
+ // Set the clip so we don't include anything outside of the cell.
+ canvas.clip_left = canvas.padding_x;
+ canvas.clip_right = canvas.padding_x;
+ canvas.clip_top = canvas.padding_y;
+ canvas.clip_bottom = canvas.padding_y;
+
+ // TODO: This doesn't align properly for most cell sizes, fix that.
+
+ const thick_px = Thickness.light.height(metrics.box_thickness);
+ const line_count = metrics.cell_width / (2 * thick_px);
+
+ const float_width: f64 = @floatFromInt(metrics.cell_width);
+ const float_height: f64 = @floatFromInt(metrics.cell_height);
+ const float_thick: f64 = @floatFromInt(thick_px);
+ const stride = @round(float_width / @as(f64, @floatFromInt(line_count)));
+
+ for (0..line_count * 2 + 1) |_i| {
+ const i = @as(i32, @intCast(_i)) - @as(i32, @intCast(line_count));
+ const bottom_x = @as(f64, @floatFromInt(i)) * stride;
+ const top_x = float_width + bottom_x;
+ canvas.line(.{
+ .p0 = .{ .x = top_x, .y = 0 },
+ .p1 = .{ .x = bottom_x, .y = float_height },
+ }, float_thick, .on) catch {};
+ }
+}
+
+pub fn draw1FB9A_1FB9F(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = width;
+ _ = height;
+
+ switch (cp) {
+ // '🮚'
+ 0x1fb9a => {
+ try edgeTriangle(metrics, canvas, .top);
+ try edgeTriangle(metrics, canvas, .bottom);
+ },
+ // '🮛'
+ 0x1fb9b => {
+ try edgeTriangle(metrics, canvas, .left);
+ try edgeTriangle(metrics, canvas, .right);
+ },
+ // '🮜'
+ 0x1fb9c => try geo.cornerTriangleShade(metrics, canvas, .tl, .medium),
+ // '🮝'
+ 0x1fb9d => try geo.cornerTriangleShade(metrics, canvas, .tr, .medium),
+ // '🮞'
+ 0x1fb9e => try geo.cornerTriangleShade(metrics, canvas, .br, .medium),
+ // '🮟'
+ 0x1fb9f => try geo.cornerTriangleShade(metrics, canvas, .bl, .medium),
+
+ else => unreachable,
+ }
+}
+
+pub fn draw1FBA0_1FBAE(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = width;
+ _ = height;
+
+ switch (cp) {
+ // '🮠'
+ 0x1fba0 => cornerDiagonalLines(metrics, canvas, .{ .tl = true }),
+ // '🮡'
+ 0x1fba1 => cornerDiagonalLines(metrics, canvas, .{ .tr = true }),
+ // '🮢'
+ 0x1fba2 => cornerDiagonalLines(metrics, canvas, .{ .bl = true }),
+ // '🮣'
+ 0x1fba3 => cornerDiagonalLines(metrics, canvas, .{ .br = true }),
+ // '🮤'
+ 0x1fba4 => cornerDiagonalLines(metrics, canvas, .{ .tl = true, .bl = true }),
+ // '🮥'
+ 0x1fba5 => cornerDiagonalLines(metrics, canvas, .{ .tr = true, .br = true }),
+ // '🮦'
+ 0x1fba6 => cornerDiagonalLines(metrics, canvas, .{ .bl = true, .br = true }),
+ // '🮧'
+ 0x1fba7 => cornerDiagonalLines(metrics, canvas, .{ .tl = true, .tr = true }),
+ // '🮨'
+ 0x1fba8 => cornerDiagonalLines(metrics, canvas, .{ .tl = true, .br = true }),
+ // '🮩'
+ 0x1fba9 => cornerDiagonalLines(metrics, canvas, .{ .tr = true, .bl = true }),
+ // '🮪'
+ 0x1fbaa => cornerDiagonalLines(metrics, canvas, .{ .tr = true, .bl = true, .br = true }),
+ // '🮫'
+ 0x1fbab => cornerDiagonalLines(metrics, canvas, .{ .tl = true, .bl = true, .br = true }),
+ // '🮬'
+ 0x1fbac => cornerDiagonalLines(metrics, canvas, .{ .tl = true, .tr = true, .br = true }),
+ // '🮭'
+ 0x1fbad => cornerDiagonalLines(metrics, canvas, .{ .tl = true, .tr = true, .bl = true }),
+ // '🮮'
+ 0x1fbae => cornerDiagonalLines(metrics, canvas, .{ .tl = true, .tr = true, .bl = true, .br = true }),
+
+ else => unreachable,
+ }
+}
+
+/// 🮯
+pub fn draw1FBAF(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+ _ = width;
+ _ = height;
+
+ box.linesChar(metrics, canvas, .{
+ .up = .heavy,
+ .down = .heavy,
+ .left = .light,
+ .right = .light,
+ });
+}
+
+/// 🮽
+pub fn draw1FBBD(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+ _ = width;
+ _ = height;
+
+ box.lightDiagonalCross(metrics, canvas);
+ canvas.invert();
+ // Set the clip so we don't include anything outside of the cell.
+ canvas.clip_left = canvas.padding_x;
+ canvas.clip_right = canvas.padding_x;
+ canvas.clip_top = canvas.padding_y;
+ canvas.clip_bottom = canvas.padding_y;
+}
+
+/// 🮾
+pub fn draw1FBBE(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+ _ = width;
+ _ = height;
+
+ cornerDiagonalLines(metrics, canvas, .{ .br = true });
+ canvas.invert();
+ // Set the clip so we don't include anything outside of the cell.
+ canvas.clip_left = canvas.padding_x;
+ canvas.clip_right = canvas.padding_x;
+ canvas.clip_top = canvas.padding_y;
+ canvas.clip_bottom = canvas.padding_y;
+}
+
+/// 🮿
+pub fn draw1FBBF(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+ _ = width;
+ _ = height;
+
+ cornerDiagonalLines(metrics, canvas, .{
+ .tl = true,
+ .tr = true,
+ .bl = true,
+ .br = true,
+ });
+ canvas.invert();
+ // Set the clip so we don't include anything outside of the cell.
+ canvas.clip_left = canvas.padding_x;
+ canvas.clip_right = canvas.padding_x;
+ canvas.clip_top = canvas.padding_y;
+ canvas.clip_bottom = canvas.padding_y;
+}
+
+/// 🯎
+pub fn draw1FBCE(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+ _ = width;
+ _ = height;
+
+ block.block(metrics, canvas, .left, two_thirds, 1);
+}
+
+// 🯏
+pub fn draw1FBCF(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+ _ = width;
+ _ = height;
+
+ block.block(metrics, canvas, .left, one_third, 1);
+}
+
+/// Cell diagonals.
+pub fn draw1FBD0_1FBDF(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = width;
+ _ = height;
+
+ switch (cp) {
+ // '🯐'
+ 0x1fbd0 => cellDiagonal(
+ metrics,
+ canvas,
+ .middle_right,
+ .lower_left,
+ ),
+ // '🯑'
+ 0x1fbd1 => cellDiagonal(
+ metrics,
+ canvas,
+ .upper_right,
+ .middle_left,
+ ),
+ // '🯒'
+ 0x1fbd2 => cellDiagonal(
+ metrics,
+ canvas,
+ .upper_left,
+ .middle_right,
+ ),
+ // '🯓'
+ 0x1fbd3 => cellDiagonal(
+ metrics,
+ canvas,
+ .middle_left,
+ .lower_right,
+ ),
+ // '🯔'
+ 0x1fbd4 => cellDiagonal(
+ metrics,
+ canvas,
+ .upper_left,
+ .lower_center,
+ ),
+ // '🯕'
+ 0x1fbd5 => cellDiagonal(
+ metrics,
+ canvas,
+ .upper_center,
+ .lower_right,
+ ),
+ // '🯖'
+ 0x1fbd6 => cellDiagonal(
+ metrics,
+ canvas,
+ .upper_right,
+ .lower_center,
+ ),
+ // '🯗'
+ 0x1fbd7 => cellDiagonal(
+ metrics,
+ canvas,
+ .upper_center,
+ .lower_left,
+ ),
+ // '🯘'
+ 0x1fbd8 => {
+ cellDiagonal(
+ metrics,
+ canvas,
+ .upper_left,
+ .middle_center,
+ );
+ cellDiagonal(
+ metrics,
+ canvas,
+ .middle_center,
+ .upper_right,
+ );
+ },
+ // '🯙'
+ 0x1fbd9 => {
+ cellDiagonal(
+ metrics,
+ canvas,
+ .upper_right,
+ .middle_center,
+ );
+ cellDiagonal(
+ metrics,
+ canvas,
+ .middle_center,
+ .lower_right,
+ );
+ },
+ // '🯚'
+ 0x1fbda => {
+ cellDiagonal(
+ metrics,
+ canvas,
+ .lower_left,
+ .middle_center,
+ );
+ cellDiagonal(
+ metrics,
+ canvas,
+ .middle_center,
+ .lower_right,
+ );
+ },
+ // '🯛'
+ 0x1fbdb => {
+ cellDiagonal(
+ metrics,
+ canvas,
+ .upper_left,
+ .middle_center,
+ );
+ cellDiagonal(
+ metrics,
+ canvas,
+ .middle_center,
+ .lower_left,
+ );
+ },
+ // '🯜'
+ 0x1fbdc => {
+ cellDiagonal(
+ metrics,
+ canvas,
+ .upper_left,
+ .lower_center,
+ );
+ cellDiagonal(
+ metrics,
+ canvas,
+ .lower_center,
+ .upper_right,
+ );
+ },
+ // '🯝'
+ 0x1fbdd => {
+ cellDiagonal(
+ metrics,
+ canvas,
+ .upper_right,
+ .middle_left,
+ );
+ cellDiagonal(
+ metrics,
+ canvas,
+ .middle_left,
+ .lower_right,
+ );
+ },
+ // '🯞'
+ 0x1fbde => {
+ cellDiagonal(
+ metrics,
+ canvas,
+ .lower_left,
+ .upper_center,
+ );
+ cellDiagonal(
+ metrics,
+ canvas,
+ .upper_center,
+ .lower_right,
+ );
+ },
+ // '🯟'
+ 0x1fbdf => {
+ cellDiagonal(
+ metrics,
+ canvas,
+ .upper_left,
+ .middle_right,
+ );
+ cellDiagonal(
+ metrics,
+ canvas,
+ .middle_right,
+ .lower_left,
+ );
+ },
+
+ else => unreachable,
+ }
+}
+
+pub fn draw1FBE0_1FBEF(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = width;
+ _ = height;
+
+ switch (cp) {
+ // '🯠'
+ 0x1fbe0 => circle(metrics, canvas, .top, false),
+ // '🯡'
+ 0x1fbe1 => circle(metrics, canvas, .right, false),
+ // '🯢'
+ 0x1fbe2 => circle(metrics, canvas, .bottom, false),
+ // '🯣'
+ 0x1fbe3 => circle(metrics, canvas, .left, false),
+ // '🯤'
+ 0x1fbe4 => block.block(metrics, canvas, .upper_center, 0.5, 0.5),
+ // '🯥'
+ 0x1fbe5 => block.block(metrics, canvas, .lower_center, 0.5, 0.5),
+ // '🯦'
+ 0x1fbe6 => block.block(metrics, canvas, .middle_left, 0.5, 0.5),
+ // '🯧'
+ 0x1fbe7 => block.block(metrics, canvas, .middle_right, 0.5, 0.5),
+ // '🯨'
+ 0x1fbe8 => circle(metrics, canvas, .top, true),
+ // '🯩'
+ 0x1fbe9 => circle(metrics, canvas, .right, true),
+ // '🯪'
+ 0x1fbea => circle(metrics, canvas, .bottom, true),
+ // '🯫'
+ 0x1fbeb => circle(metrics, canvas, .left, true),
+ // '🯬'
+ 0x1fbec => circle(metrics, canvas, .top_right, true),
+ // '🯭'
+ 0x1fbed => circle(metrics, canvas, .bottom_left, true),
+ // '🯮'
+ 0x1fbee => circle(metrics, canvas, .bottom_right, true),
+ // '🯯'
+ 0x1fbef => circle(metrics, canvas, .top_left, true),
+
+ else => unreachable,
+ }
+}
+
+fn edgeTriangle(
+ metrics: font.Metrics,
+ canvas: *font.sprite.Canvas,
+ comptime edge: Edge,
+) !void {
+ const upper: f64 = 0.0;
+ const middle: f64 = @round(@as(f64, @floatFromInt(metrics.cell_height)) / 2);
+ const lower: f64 = @floatFromInt(metrics.cell_height);
+ const left: f64 = 0.0;
+ const center: f64 = @round(@as(f64, @floatFromInt(metrics.cell_width)) / 2);
+ const right: f64 = @floatFromInt(metrics.cell_width);
+
+ const x0, const y0, const x1, const y1 = switch (edge) {
+ .top => .{ right, upper, left, upper },
+ .left => .{ left, upper, left, lower },
+ .bottom => .{ left, lower, right, lower },
+ .right => .{ right, lower, right, upper },
+ };
+
+ var path = canvas.staticPath(5); // nodes.len = 0
+ path.moveTo(center, middle); // +1, nodes.len = 1
+ path.lineTo(x0, y0); // +1, nodes.len = 2
+ path.lineTo(x1, y1); // +1, nodes.len = 3
+ path.close(); // +2, nodes.len = 5
+
+ try canvas.fillPath(path.wrapped_path, .{}, .on);
+}
+
+fn cornerDiagonalLines(
+ metrics: font.Metrics,
+ canvas: *font.sprite.Canvas,
+ comptime corners: Quads,
+) void {
+ const thick_px = Thickness.light.height(metrics.box_thickness);
+
+ const float_width: f64 = @floatFromInt(metrics.cell_width);
+ const float_height: f64 = @floatFromInt(metrics.cell_height);
+ const float_thick: f64 = @floatFromInt(thick_px);
+ const center_x: f64 = @floatFromInt(metrics.cell_width / 2 + metrics.cell_width % 2);
+ const center_y: f64 = @floatFromInt(metrics.cell_height / 2 + metrics.cell_height % 2);
+
+ if (corners.tl) canvas.line(.{
+ .p0 = .{ .x = center_x, .y = 0 },
+ .p1 = .{ .x = 0, .y = center_y },
+ }, float_thick, .on) catch {};
+
+ if (corners.tr) canvas.line(.{
+ .p0 = .{ .x = center_x, .y = 0 },
+ .p1 = .{ .x = float_width, .y = center_y },
+ }, float_thick, .on) catch {};
+
+ if (corners.bl) canvas.line(.{
+ .p0 = .{ .x = center_x, .y = float_height },
+ .p1 = .{ .x = 0, .y = center_y },
+ }, float_thick, .on) catch {};
+
+ if (corners.br) canvas.line(.{
+ .p0 = .{ .x = center_x, .y = float_height },
+ .p1 = .{ .x = float_width, .y = center_y },
+ }, float_thick, .on) catch {};
+}
+
+fn cellDiagonal(
+ metrics: font.Metrics,
+ canvas: *font.sprite.Canvas,
+ comptime from: Alignment,
+ comptime to: Alignment,
+) void {
+ const float_width: f64 = @floatFromInt(metrics.cell_width);
+ const float_height: f64 = @floatFromInt(metrics.cell_height);
+
+ const x0: f64 = switch (from.horizontal) {
+ .left => 0,
+ .right => float_width,
+ .center => float_width / 2,
+ };
+ const y0: f64 = switch (from.vertical) {
+ .top => 0,
+ .bottom => float_height,
+ .middle => float_height / 2,
+ };
+ const x1: f64 = switch (to.horizontal) {
+ .left => 0,
+ .right => float_width,
+ .center => float_width / 2,
+ };
+ const y1: f64 = switch (to.vertical) {
+ .top => 0,
+ .bottom => float_height,
+ .middle => float_height / 2,
+ };
+
+ canvas.line(
+ .{
+ .p0 = .{ .x = x0, .y = y0 },
+ .p1 = .{ .x = x1, .y = y1 },
+ },
+ @floatFromInt(Thickness.light.height(metrics.box_thickness)),
+ .on,
+ ) catch {};
+}
+
+fn checkerboardFill(
+ metrics: font.Metrics,
+ canvas: *font.sprite.Canvas,
+ parity: u1,
+) void {
+ const float_width: f64 = @floatFromInt(metrics.cell_width);
+ const float_height: f64 = @floatFromInt(metrics.cell_height);
+ const x_size: usize = 4;
+ const y_size: usize = @intFromFloat(@round(4 * (float_height / float_width)));
+ for (0..x_size) |x| {
+ const x0 = (metrics.cell_width * x) / x_size;
+ const x1 = (metrics.cell_width * (x + 1)) / x_size;
+ for (0..y_size) |y| {
+ const y0 = (metrics.cell_height * y) / y_size;
+ const y1 = (metrics.cell_height * (y + 1)) / y_size;
+ if ((x + y) % 2 == parity) {
+ canvas.rect(.{
+ .x = @intCast(x0),
+ .y = @intCast(y0),
+ .width = @intCast(x1 -| x0),
+ .height = @intCast(y1 -| y0),
+ }, .on);
+ }
+ }
+ }
+}
+
+pub fn circle(
+ metrics: font.Metrics,
+ canvas: *font.sprite.Canvas,
+ comptime position: Alignment,
+ comptime filled: bool,
+) void {
+ // Set the clip so we don't include anything outside of the cell.
+ canvas.clip_left = canvas.padding_x;
+ canvas.clip_right = canvas.padding_x;
+ canvas.clip_top = canvas.padding_y;
+ canvas.clip_bottom = canvas.padding_y;
+
+ const float_width: f64 = @floatFromInt(metrics.cell_width);
+ const float_height: f64 = @floatFromInt(metrics.cell_height);
+
+ const x: f64 = switch (position.horizontal) {
+ .left => 0,
+ .right => float_width,
+ .center => float_width / 2,
+ };
+ const y: f64 = switch (position.vertical) {
+ .top => 0,
+ .bottom => float_height,
+ .middle => float_height / 2,
+ };
+ const r: f64 = 0.5 * @min(float_width, float_height);
+
+ var ctx = canvas.getContext();
+ defer ctx.deinit();
+ ctx.setSource(.{ .opaque_pattern = .{
+ .pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } },
+ } });
+ ctx.setLineWidth(
+ @floatFromInt(Thickness.light.height(metrics.box_thickness)),
+ );
+
+ if (filled) {
+ ctx.arc(x, y, r, 0, std.math.pi * 2) catch return;
+ ctx.closePath() catch return;
+ ctx.fill() catch return;
+ } else {
+ ctx.arc(x, y, r - ctx.line_width / 2, 0, std.math.pi * 2) catch return;
+ ctx.closePath() catch return;
+ ctx.stroke() catch return;
+ }
+}
diff --git a/vendor/libghostty-vt/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig b/vendor/libghostty-vt/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig
new file mode 100644
index 00000000..46c7165a
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig
@@ -0,0 +1,633 @@
+//! Symbols for Legacy Computing Supplement | U+1CC00...U+1CEBF
+//! https://en.wikipedia.org/wiki/Symbols_for_Legacy_Computing_Supplement
+//!
+//! 𜰀 𜰁 𜰂 𜰃 𜰄 𜰅 𜰆 𜰇 𜰈 𜰉 𜰊 𜰋 𜰌 𜰍 𜰎 𜰏
+//! 𜰐 𜰑 𜰒 𜰓 𜰔 𜰕 𜰖 𜰗 𜰘 𜰙 𜰚 𜰛 𜰜 𜰝 𜰞 𜰟
+//! 𜰠 𜰡 𜰢 𜰣 𜰤 𜰥 𜰦 𜰧 𜰨 𜰩 𜰪 𜰫 𜰬 𜰭 𜰮 𜰯
+//! 𜰰 𜰱 𜰲 𜰳 𜰴 𜰵 𜰶 𜰷 𜰸 𜰹 𜰺 𜰻 𜰼 𜰽 𜰾 𜰿
+//! 𜱀 𜱁 𜱂 𜱃 𜱄 𜱅 𜱆 𜱇 𜱈 𜱉 𜱊 𜱋 𜱌 𜱍 𜱎 𜱏
+//! 𜱐 𜱑 𜱒 𜱓 𜱔 𜱕 𜱖 𜱗 𜱘 𜱙 𜱚 𜱛 𜱜 𜱝 𜱞 𜱟
+//! 𜱠 𜱡 𜱢 𜱣 𜱤 𜱥 𜱦 𜱧 𜱨 𜱩 𜱪 𜱫 𜱬 𜱭 𜱮 𜱯
+//! 𜱰 𜱱 𜱲 𜱳 𜱴 𜱵 𜱶 𜱷 𜱸 𜱹 𜱺 𜱻 𜱼 𜱽 𜱾 𜱿
+//! 𜲀 𜲁 𜲂 𜲃 𜲄 𜲅 𜲆 𜲇 𜲈 𜲉 𜲊 𜲋 𜲌 𜲍 𜲎 𜲏
+//! 𜲐 𜲑 𜲒 𜲓 𜲔 𜲕 𜲖 𜲗 𜲘 𜲙 𜲚 𜲛 𜲜 𜲝 𜲞 𜲟
+//! 𜲠 𜲡 𜲢 𜲣 𜲤 𜲥 𜲦 𜲧 𜲨 𜲩 𜲪 𜲫 𜲬 𜲭 𜲮 𜲯
+//! 𜲰 𜲱 𜲲 𜲳 𜲴 𜲵 𜲶 𜲷 𜲸 𜲹 𜲺 𜲻 𜲼 𜲽 𜲾 𜲿
+//! 𜳀 𜳁 𜳂 𜳃 𜳄 𜳅 𜳆 𜳇 𜳈 𜳉 𜳊 𜳋 𜳌 𜳍 𜳎 𜳏
+//! 𜳐 𜳑 𜳒 𜳓 𜳔 𜳕 𜳖 𜳗 𜳘 𜳙 𜳚 𜳛 𜳜 𜳝 𜳞 𜳟
+//! 𜳠 𜳡 𜳢 𜳣 𜳤 𜳥 𜳦 𜳧 𜳨 𜳩 𜳪 𜳫 𜳬 𜳭 𜳮 𜳯
+//! 𜳰 𜳱 𜳲 𜳳 𜳴 𜳵 𜳶 𜳷 𜳸 𜳹
+//! 𜴀 𜴁 𜴂 𜴃 𜴄 𜴅 𜴆 𜴇 𜴈 𜴉 𜴊 𜴋 𜴌 𜴍 𜴎 𜴏
+//! 𜴐 𜴑 𜴒 𜴓 𜴔 𜴕 𜴖 𜴗 𜴘 𜴙 𜴚 𜴛 𜴜 𜴝 𜴞 𜴟
+//! 𜴠 𜴡 𜴢 𜴣 𜴤 𜴥 𜴦 𜴧 𜴨 𜴩 𜴪 𜴫 𜴬 𜴭 𜴮 𜴯
+//! 𜴰 𜴱 𜴲 𜴳 𜴴 𜴵 𜴶 𜴷 𜴸 𜴹 𜴺 𜴻 𜴼 𜴽 𜴾 𜴿
+//! 𜵀 𜵁 𜵂 𜵃 𜵄 𜵅 𜵆 𜵇 𜵈 𜵉 𜵊 𜵋 𜵌 𜵍 𜵎 𜵏
+//! 𜵐 𜵑 𜵒 𜵓 𜵔 𜵕 𜵖 𜵗 𜵘 𜵙 𜵚 𜵛 𜵜 𜵝 𜵞 𜵟
+//! 𜵠 𜵡 𜵢 𜵣 𜵤 𜵥 𜵦 𜵧 𜵨 𜵩 𜵪 𜵫 𜵬 𜵭 𜵮 𜵯
+//! 𜵰 𜵱 𜵲 𜵳 𜵴 𜵵 𜵶 𜵷 𜵸 𜵹 𜵺 𜵻 𜵼 𜵽 𜵾 𜵿
+//! 𜶀 𜶁 𜶂 𜶃 𜶄 𜶅 𜶆 𜶇 𜶈 𜶉 𜶊 𜶋 𜶌 𜶍 𜶎 𜶏
+//! 𜶐 𜶑 𜶒 𜶓 𜶔 𜶕 𜶖 𜶗 𜶘 𜶙 𜶚 𜶛 𜶜 𜶝 𜶞 𜶟
+//! 𜶠 𜶡 𜶢 𜶣 𜶤 𜶥 𜶦 𜶧 𜶨 𜶩 𜶪 𜶫 𜶬 𜶭 𜶮 𜶯
+//! 𜶰 𜶱 𜶲 𜶳 𜶴 𜶵 𜶶 𜶷 𜶸 𜶹 𜶺 𜶻 𜶼 𜶽 𜶾 𜶿
+//! 𜷀 𜷁 𜷂 𜷃 𜷄 𜷅 𜷆 𜷇 𜷈 𜷉 𜷊 𜷋 𜷌 𜷍 𜷎 𜷏
+//! 𜷐 𜷑 𜷒 𜷓 𜷔 𜷕 𜷖 𜷗 𜷘 𜷙 𜷚 𜷛 𜷜 𜷝 𜷞 𜷟
+//! 𜷠 𜷡 𜷢 𜷣 𜷤 𜷥 𜷦 𜷧 𜷨 𜷩 𜷪 𜷫 𜷬 𜷭 𜷮 𜷯
+//! 𜷰 𜷱 𜷲 𜷳 𜷴 𜷵 𜷶 𜷷 𜷸 𜷹 𜷺 𜷻 𜷼 𜷽 𜷾 𜷿
+//! 𜸀 𜸁 𜸂 𜸃 𜸄 𜸅 𜸆 𜸇 𜸈 𜸉 𜸊 𜸋 𜸌 𜸍 𜸎 𜸏
+//! 𜸐 𜸑 𜸒 𜸓 𜸔 𜸕 𜸖 𜸗 𜸘 𜸙 𜸚 𜸛 𜸜 𜸝 𜸞 𜸟
+//! 𜸠 𜸡 𜸢 𜸣 𜸤 𜸥 𜸦 𜸧 𜸨 𜸩 𜸪 𜸫 𜸬 𜸭 𜸮 𜸯
+//! 𜸰 𜸱 𜸲 𜸳 𜸴 𜸵 𜸶 𜸷 𜸸 𜸹 𜸺 𜸻 𜸼 𜸽 𜸾 𜸿
+//! 𜹀 𜹁 𜹂 𜹃 𜹄 𜹅 𜹆 𜹇 𜹈 𜹉 𜹊 𜹋 𜹌 𜹍 𜹎 𜹏
+//! 𜹐 𜹑 𜹒 𜹓 𜹔 𜹕 𜹖 𜹗 𜹘 𜹙 𜹚 𜹛 𜹜 𜹝 𜹞 𜹟
+//! 𜹠 𜹡 𜹢 𜹣 𜹤 𜹥 𜹦 𜹧 𜹨 𜹩 𜹪 𜹫 𜹬 𜹭 𜹮 𜹯
+//! 𜹰 𜹱 𜹲 𜹳 𜹴 𜹵 𜹶 𜹷 𜹸 𜹹 𜹺 𜹻 𜹼 𜹽 𜹾 𜹿
+//! 𜺀 𜺁 𜺂 𜺃 𜺄 𜺅 𜺆 𜺇 𜺈 𜺉 𜺊 𜺋 𜺌 𜺍 𜺎 𜺏
+//! 𜺐 𜺑 𜺒 𜺓 𜺔 𜺕 𜺖 𜺗 𜺘 𜺙 𜺚 𜺛 𜺜 𜺝 𜺞 𜺟
+//! 𜺠 𜺡 𜺢 𜺣 𜺤 𜺥 𜺦 𜺧 𜺨 𜺩 𜺪 𜺫 𜺬 𜺭 𜺮 𜺯
+//! 𜺰 𜺱 𜺲 𜺳
+//!
+
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const assert = @import("../../../quirks.zig").inlineAssert;
+
+const common = @import("common.zig");
+const Thickness = common.Thickness;
+const Fraction = common.Fraction;
+const Corner = common.Corner;
+const Shade = common.Shade;
+const fill = common.fill;
+
+const box = @import("box.zig");
+const sflc = @import("symbols_for_legacy_computing.zig");
+
+const font = @import("../../main.zig");
+
+const octant_min = 0x1cd00;
+const octant_max = 0x1cde5;
+
+/// Octants
+pub fn draw1CD00_1CDE5(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = width;
+ _ = height;
+
+ // Octant representation. We use the funny numeric string keys
+ // so its easier to parse the actual name used in the Symbols for
+ // Legacy Computing spec.
+ const Octant = packed struct(u8) {
+ @"1": bool = false,
+ @"2": bool = false,
+ @"3": bool = false,
+ @"4": bool = false,
+ @"5": bool = false,
+ @"6": bool = false,
+ @"7": bool = false,
+ @"8": bool = false,
+ };
+
+ // Parse the octant data. This is all done at comptime so
+ // that this is static data that is embedded in the binary.
+ const octants_len = octant_max - octant_min + 1;
+ const octants: [octants_len]Octant = comptime octants: {
+ @setEvalBranchQuota(10_000);
+
+ var result: [octants_len]Octant = @splat(.{});
+ var i: usize = 0;
+
+ const data = @embedFile("octants.txt");
+ var it = std.mem.splitScalar(u8, data, '\n');
+ while (it.next()) |raw_line| {
+ // Trim \r so this works with both LF and CRLF line endings,
+ // since git may convert octants.txt to CRLF on Windows checkouts.
+ const line = if (raw_line.len > 0 and raw_line[raw_line.len - 1] == '\r')
+ raw_line[0 .. raw_line.len - 1]
+ else
+ raw_line;
+
+ // Skip comments
+ if (line.len == 0 or line[0] == '#') continue;
+
+ const current = &result[i];
+ i += 1;
+
+ // Octants are in the format "BLOCK OCTANT-1235". The numbers
+ // at the end are keys into our packed struct. Since we're
+ // at comptime we can metaprogram it all.
+ const idx = std.mem.indexOfScalar(u8, line, '-').?;
+ for (line[idx + 1 ..]) |c| @field(current, &.{c}) = true;
+ }
+
+ assert(i == octants_len);
+ break :octants result;
+ };
+
+ const oct = octants[cp - octant_min];
+ if (oct.@"1") fill(metrics, canvas, .zero, .half, .zero, .one_quarter);
+ if (oct.@"2") fill(metrics, canvas, .half, .full, .zero, .one_quarter);
+ if (oct.@"3") fill(metrics, canvas, .zero, .half, .one_quarter, .two_quarters);
+ if (oct.@"4") fill(metrics, canvas, .half, .full, .one_quarter, .two_quarters);
+ if (oct.@"5") fill(metrics, canvas, .zero, .half, .two_quarters, .three_quarters);
+ if (oct.@"6") fill(metrics, canvas, .half, .full, .two_quarters, .three_quarters);
+ if (oct.@"7") fill(metrics, canvas, .zero, .half, .three_quarters, .end);
+ if (oct.@"8") fill(metrics, canvas, .half, .full, .three_quarters, .end);
+}
+
+// Separated Block Quadrants
+// 𜰡 𜰢 𜰣 𜰤 𜰥 𜰦 𜰧 𜰨 𜰩 𜰪 𜰫 𜰬 𜰭 𜰮 𜰯
+pub fn draw1CC21_1CC2F(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = metrics;
+
+ // Struct laid out to match the codepoint order so we can cast from it.
+ const Quads = packed struct(u4) {
+ tl: bool,
+ tr: bool,
+ bl: bool,
+ br: bool,
+ };
+
+ const quad: Quads = @bitCast(@as(u4, @truncate(cp - 0x1CC20)));
+
+ const gap: i32 = @intCast(@max(1, width / 12));
+
+ const mid_gap_x: i32 = gap * 2 + @as(i32, @intCast(width % 2));
+ const mid_gap_y: i32 = gap * 2 + @as(i32, @intCast(height % 2));
+
+ const w: i32 = @divExact(@as(i32, @intCast(width)) - gap * 2 - mid_gap_x, 2);
+ const h: i32 = @divExact(@as(i32, @intCast(height)) - gap * 2 - mid_gap_y, 2);
+
+ if (quad.tl) canvas.box(
+ gap,
+ gap,
+ gap + w,
+ gap + h,
+ .on,
+ );
+ if (quad.tr) canvas.box(
+ gap + w + mid_gap_x,
+ gap,
+ gap + w + mid_gap_x + w,
+ gap + h,
+ .on,
+ );
+ if (quad.bl) canvas.box(
+ gap,
+ gap + h + mid_gap_y,
+ gap + w,
+ gap + h + mid_gap_y + h,
+ .on,
+ );
+ if (quad.br) canvas.box(
+ gap + w + mid_gap_x,
+ gap + h + mid_gap_y,
+ gap + w + mid_gap_x + w,
+ gap + h + mid_gap_y + h,
+ .on,
+ );
+}
+
+/// Twelfth and Quarter circle pieces.
+/// 𜰰 𜰱 𜰲 𜰳 𜰴 𜰵 𜰶 𜰷 𜰸 𜰹 𜰺 𜰻 𜰼 𜰽 𜰾 𜰿
+///
+/// 𜰰𜰱𜰲𜰳
+/// 𜰴𜰵𜰶𜰷
+/// 𜰸𜰹𜰺𜰻
+/// 𜰼𜰽𜰾𜰿
+///
+/// These are actually ellipses, sized to touch
+/// the edge of their enclosing set of cells.
+pub fn draw1CC30_1CC3F(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ switch (cp) {
+ // 𜰰 UPPER LEFT TWELFTH CIRCLE
+ 0x1CC30 => try circlePiece(canvas, width, height, metrics, 0, 0, 2, 2, .tl),
+ // 𜰱 UPPER CENTRE LEFT TWELFTH CIRCLE
+ 0x1CC31 => try circlePiece(canvas, width, height, metrics, 1, 0, 2, 2, .tl),
+ // 𜰲 UPPER CENTRE RIGHT TWELFTH CIRCLE
+ 0x1CC32 => try circlePiece(canvas, width, height, metrics, 2, 0, 2, 2, .tr),
+ // 𜰳 UPPER RIGHT TWELFTH CIRCLE
+ 0x1CC33 => try circlePiece(canvas, width, height, metrics, 3, 0, 2, 2, .tr),
+ // 𜰴 UPPER MIDDLE LEFT TWELFTH CIRCLE
+ 0x1CC34 => try circlePiece(canvas, width, height, metrics, 0, 1, 2, 2, .tl),
+ // 𜰵 UPPER LEFT QUARTER CIRCLE
+ 0x1CC35 => try circlePiece(canvas, width, height, metrics, 0, 0, 1, 1, .tl),
+ // 𜰶 UPPER RIGHT QUARTER CIRCLE
+ 0x1CC36 => try circlePiece(canvas, width, height, metrics, 1, 0, 1, 1, .tr),
+ // 𜰷 UPPER MIDDLE RIGHT TWELFTH CIRCLE
+ 0x1CC37 => try circlePiece(canvas, width, height, metrics, 3, 1, 2, 2, .tr),
+ // 𜰸 LOWER MIDDLE LEFT TWELFTH CIRCLE
+ 0x1CC38 => try circlePiece(canvas, width, height, metrics, 0, 2, 2, 2, .bl),
+ // 𜰹 LOWER LEFT QUARTER CIRCLE
+ 0x1CC39 => try circlePiece(canvas, width, height, metrics, 0, 1, 1, 1, .bl),
+ // 𜰺 LOWER RIGHT QUARTER CIRCLE
+ 0x1CC3A => try circlePiece(canvas, width, height, metrics, 1, 1, 1, 1, .br),
+ // 𜰻 LOWER MIDDLE RIGHT TWELFTH CIRCLE
+ 0x1CC3B => try circlePiece(canvas, width, height, metrics, 3, 2, 2, 2, .br),
+ // 𜰼 LOWER LEFT TWELFTH CIRCLE
+ 0x1CC3C => try circlePiece(canvas, width, height, metrics, 0, 3, 2, 2, .bl),
+ // 𜰽 LOWER CENTRE LEFT TWELFTH CIRCLE
+ 0x1CC3D => try circlePiece(canvas, width, height, metrics, 1, 3, 2, 2, .bl),
+ // 𜰾 LOWER CENTRE RIGHT TWELFTH CIRCLE
+ 0x1CC3E => try circlePiece(canvas, width, height, metrics, 2, 3, 2, 2, .br),
+ // 𜰿 LOWER RIGHT TWELFTH CIRCLE
+ 0x1CC3F => try circlePiece(canvas, width, height, metrics, 3, 3, 2, 2, .br),
+ else => unreachable,
+ }
+}
+
+/// TODO: These two characters should be easy, but it's not clear how they're
+/// meant to align with adjacent cells, what characters they're meant to
+/// be used with:
+/// - 1CC1F 𜰟 BOX DRAWINGS DOUBLE DIAGONAL UPPER RIGHT TO LOWER LEFT
+/// - 1CC20 𜰠 BOX DRAWINGS DOUBLE DIAGONAL UPPER LEFT TO LOWER RIGHT
+pub fn draw1CC1B_1CC1E(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ const w: i32 = @intCast(width);
+ const h: i32 = @intCast(height);
+ const t: i32 = @intCast(metrics.box_thickness);
+ switch (cp) {
+ // 𜰛 BOX DRAWINGS LIGHT HORIZONTAL AND UPPER RIGHT
+ 0x1CC1B => {
+ box.linesChar(metrics, canvas, .{ .left = .light, .right = .light });
+ canvas.box(w - t, 0, w, @divFloor(h, 2), .on);
+ },
+ // 𜰜 BOX DRAWINGS LIGHT HORIZONTAL AND LOWER RIGHT
+ 0x1CC1C => {
+ box.linesChar(metrics, canvas, .{ .left = .light, .right = .light });
+ canvas.box(w - t, @divFloor(h, 2), w, h, .on);
+ },
+ // 𜰝 BOX DRAWINGS LIGHT TOP AND UPPER LEFT
+ 0x1CC1D => {
+ canvas.box(0, 0, w, t, .on);
+ canvas.box(0, 0, t, @divFloor(h, 2), .on);
+ },
+ // 𜰞 BOX DRAWINGS LIGHT BOTTOM AND LOWER LEFT
+ 0x1CC1E => {
+ canvas.box(0, h - t, w, h, .on);
+ canvas.box(0, @divFloor(h, 2), t, h, .on);
+ },
+ else => unreachable,
+ }
+}
+
+/// 𜸀 RIGHT HALF AND LEFT HALF WHITE CIRCLE
+pub fn draw1CE00(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+ _ = width;
+ _ = height;
+ sflc.circle(metrics, canvas, .left, false);
+ sflc.circle(metrics, canvas, .right, false);
+}
+
+/// 𜸁 LOWER HALF AND UPPER HALF WHITE CIRCLE
+pub fn draw1CE01(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+ _ = width;
+ _ = height;
+ sflc.circle(metrics, canvas, .top, false);
+ sflc.circle(metrics, canvas, .bottom, false);
+}
+
+/// 𜸋 LEFT HALF WHITE ELLIPSE
+pub fn draw1CE0B(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+ try circlePiece(canvas, width, height, metrics, 0, 0, 1, 0.5, .tl);
+ try circlePiece(canvas, width, height, metrics, 0, 0, 1, 0.5, .bl);
+}
+
+/// 𜸌 RIGHT HALF WHITE ELLIPSE
+pub fn draw1CE0C(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = cp;
+ try circlePiece(canvas, width, height, metrics, 1, 0, 1, 0.5, .tr);
+ try circlePiece(canvas, width, height, metrics, 1, 0, 1, 0.5, .br);
+}
+
+pub fn draw1CE16_1CE19(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ const w: i32 = @intCast(width);
+ const h: i32 = @intCast(height);
+ const t: i32 = @intCast(metrics.box_thickness);
+ switch (cp) {
+ // 𜸖 BOX DRAWINGS LIGHT VERTICAL AND TOP RIGHT
+ 0x1CE16 => {
+ box.linesChar(metrics, canvas, .{ .up = .light, .down = .light });
+ canvas.box(@divFloor(w, 2), 0, w, t, .on);
+ },
+ // 𜸗 BOX DRAWINGS LIGHT VERTICAL AND BOTTOM RIGHT
+ 0x1CE17 => {
+ box.linesChar(metrics, canvas, .{ .up = .light, .down = .light });
+ canvas.box(@divFloor(w, 2), h - t, w, h, .on);
+ },
+ // 𜸘 BOX DRAWINGS LIGHT VERTICAL AND TOP LEFT
+ 0x1CE18 => {
+ box.linesChar(metrics, canvas, .{ .up = .light, .down = .light });
+ canvas.box(0, 0, @divFloor(w, 2), t, .on);
+ },
+ // 𜸙 BOX DRAWINGS LIGHT VERTICAL AND BOTTOM LEFT
+ 0x1CE19 => {
+ box.linesChar(metrics, canvas, .{ .up = .light, .down = .light });
+ canvas.box(0, h - t, @divFloor(w, 2), h, .on);
+ },
+ else => unreachable,
+ }
+}
+
+/// Separated Block Sextants
+pub fn draw1CE51_1CE8F(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = metrics;
+
+ // Struct laid out to match the codepoint order so we can cast from it.
+ const Sextants = packed struct(u6) {
+ tl: bool,
+ tr: bool,
+ ml: bool,
+ mr: bool,
+ bl: bool,
+ br: bool,
+ };
+
+ const sex: Sextants = @bitCast(@as(u6, @truncate(cp - 0x1CE50)));
+
+ const gap: i32 = @intCast(@max(1, width / 12));
+
+ const mid_gap_x: i32 = gap * 2 + @as(i32, @intCast(width % 2));
+ const y_extra: i32 = @as(i32, @intCast(height % 3));
+ const mid_gap_y: i32 = gap * 2 + @divFloor(y_extra, 2);
+
+ const w: i32 = @divExact(@as(i32, @intCast(width)) - gap * 2 - mid_gap_x, 2);
+ const h: i32 = @divFloor(
+ @as(i32, @intCast(height)) - gap * 2 - mid_gap_y * 2,
+ 3,
+ );
+ // Distribute any leftover height in to the middle row of blocks.
+ const h_m: i32 = @as(i32, @intCast(height)) - gap * 2 - mid_gap_y * 2 - h * 2;
+
+ if (sex.tl) canvas.box(
+ gap,
+ gap,
+ gap + w,
+ gap + h,
+ .on,
+ );
+ if (sex.tr) canvas.box(
+ gap + w + mid_gap_x,
+ gap,
+ gap + w + mid_gap_x + w,
+ gap + h,
+ .on,
+ );
+ if (sex.ml) canvas.box(
+ gap,
+ gap + h + mid_gap_y,
+ gap + w,
+ gap + h + mid_gap_y + h_m,
+ .on,
+ );
+ if (sex.mr) canvas.box(
+ gap + w + mid_gap_x,
+ gap + h + mid_gap_y,
+ gap + w + mid_gap_x + w,
+ gap + h + mid_gap_y + h_m,
+ .on,
+ );
+ if (sex.bl) canvas.box(
+ gap,
+ gap + h + mid_gap_y + h_m + mid_gap_y,
+ gap + w,
+ gap + h + mid_gap_y + h_m + mid_gap_y + h,
+ .on,
+ );
+ if (sex.br) canvas.box(
+ gap + w + mid_gap_x,
+ gap + h + mid_gap_y + h_m + mid_gap_y,
+ gap + w + mid_gap_x + w,
+ gap + h + mid_gap_y + h_m + mid_gap_y + h,
+ .on,
+ );
+}
+
+/// Sixteenth Blocks
+pub fn draw1CE90_1CEAF(
+ cp: u32,
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+) !void {
+ _ = width;
+ _ = height;
+ const q = Fraction.quarters;
+ switch (cp) {
+ // 𜺐 UPPER LEFT ONE SIXTEENTH BLOCK
+ 0x1CE90 => fill(metrics, canvas, q[0], q[1], q[0], q[1]),
+ // 𜺑 UPPER CENTRE LEFT ONE SIXTEENTH BLOCK
+ 0x1CE91 => fill(metrics, canvas, q[1], q[2], q[0], q[1]),
+ // 𜺒 UPPER CENTRE RIGHT ONE SIXTEENTH BLOCK
+ 0x1CE92 => fill(metrics, canvas, q[2], q[3], q[0], q[1]),
+ // 𜺓 UPPER RIGHT ONE SIXTEENTH BLOCK
+ 0x1CE93 => fill(metrics, canvas, q[3], q[4], q[0], q[1]),
+ // 𜺔 UPPER MIDDLE LEFT ONE SIXTEENTH BLOCK
+ 0x1CE94 => fill(metrics, canvas, q[0], q[1], q[1], q[2]),
+ // 𜺕 UPPER MIDDLE CENTRE LEFT ONE SIXTEENTH BLOCK
+ 0x1CE95 => fill(metrics, canvas, q[1], q[2], q[1], q[2]),
+ // 𜺖 UPPER MIDDLE CENTRE RIGHT ONE SIXTEENTH BLOCK
+ 0x1CE96 => fill(metrics, canvas, q[2], q[3], q[1], q[2]),
+ // 𜺗 UPPER MIDDLE RIGHT ONE SIXTEENTH BLOCK
+ 0x1CE97 => fill(metrics, canvas, q[3], q[4], q[1], q[2]),
+ // 𜺘 LOWER MIDDLE LEFT ONE SIXTEENTH BLOCK
+ 0x1CE98 => fill(metrics, canvas, q[0], q[1], q[2], q[3]),
+ // 𜺙 LOWER MIDDLE CENTRE LEFT ONE SIXTEENTH BLOCK
+ 0x1CE99 => fill(metrics, canvas, q[1], q[2], q[2], q[3]),
+ // 𜺚 LOWER MIDDLE CENTRE RIGHT ONE SIXTEENTH BLOCK
+ 0x1CE9A => fill(metrics, canvas, q[2], q[3], q[2], q[3]),
+ // 𜺛 LOWER MIDDLE RIGHT ONE SIXTEENTH BLOCK
+ 0x1CE9B => fill(metrics, canvas, q[3], q[4], q[2], q[3]),
+ // 𜺜 LOWER LEFT ONE SIXTEENTH BLOCK
+ 0x1CE9C => fill(metrics, canvas, q[0], q[1], q[3], q[4]),
+ // 𜺝 LOWER CENTRE LEFT ONE SIXTEENTH BLOCK
+ 0x1CE9D => fill(metrics, canvas, q[1], q[2], q[3], q[4]),
+ // 𜺞 LOWER CENTRE RIGHT ONE SIXTEENTH BLOCK
+ 0x1CE9E => fill(metrics, canvas, q[2], q[3], q[3], q[4]),
+ // 𜺟 LOWER RIGHT ONE SIXTEENTH BLOCK
+ 0x1CE9F => fill(metrics, canvas, q[3], q[4], q[3], q[4]),
+
+ // 𜺠 RIGHT HALF LOWER ONE QUARTER BLOCK
+ 0x1CEA0 => fill(metrics, canvas, q[2], q[4], q[3], q[4]),
+ // 𜺡 RIGHT THREE QUARTERS LOWER ONE QUARTER BLOCK
+ 0x1CEA1 => fill(metrics, canvas, q[1], q[4], q[3], q[4]),
+ // 𜺢 LEFT THREE QUARTERS LOWER ONE QUARTER BLOCK
+ 0x1CEA2 => fill(metrics, canvas, q[0], q[3], q[3], q[4]),
+ // 𜺣 LEFT HALF LOWER ONE QUARTER BLOCK
+ 0x1CEA3 => fill(metrics, canvas, q[0], q[2], q[3], q[4]),
+ // 𜺤 LOWER HALF LEFT ONE QUARTER BLOCK
+ 0x1CEA4 => fill(metrics, canvas, q[0], q[1], q[2], q[4]),
+ // 𜺥 LOWER THREE QUARTERS LEFT ONE QUARTER BLOCK
+ 0x1CEA5 => fill(metrics, canvas, q[0], q[1], q[1], q[4]),
+ // 𜺦 UPPER THREE QUARTERS LEFT ONE QUARTER BLOCK
+ 0x1CEA6 => fill(metrics, canvas, q[0], q[1], q[0], q[3]),
+ // 𜺧 UPPER HALF LEFT ONE QUARTER BLOCK
+ 0x1CEA7 => fill(metrics, canvas, q[0], q[1], q[0], q[2]),
+ // 𜺨 LEFT HALF UPPER ONE QUARTER BLOCK
+ 0x1CEA8 => fill(metrics, canvas, q[0], q[2], q[0], q[1]),
+ // 𜺩 LEFT THREE QUARTERS UPPER ONE QUARTER BLOCK
+ 0x1CEA9 => fill(metrics, canvas, q[0], q[3], q[0], q[1]),
+ // 𜺪 RIGHT THREE QUARTERS UPPER ONE QUARTER BLOCK
+ 0x1CEAA => fill(metrics, canvas, q[1], q[4], q[0], q[1]),
+ // 𜺫 RIGHT HALF UPPER ONE QUARTER BLOCK
+ 0x1CEAB => fill(metrics, canvas, q[2], q[4], q[0], q[1]),
+ // 𜺬 UPPER HALF RIGHT ONE QUARTER BLOCK
+ 0x1CEAC => fill(metrics, canvas, q[3], q[4], q[0], q[2]),
+ // 𜺭 UPPER THREE QUARTERS RIGHT ONE QUARTER BLOCK
+ 0x1CEAD => fill(metrics, canvas, q[3], q[4], q[0], q[3]),
+ // 𜺮 LOWER THREE QUARTERS RIGHT ONE QUARTER BLOCK
+ 0x1CEAE => fill(metrics, canvas, q[3], q[4], q[1], q[4]),
+ // 𜺯 LOWER HALF RIGHT ONE QUARTER BLOCK
+ 0x1CEAF => fill(metrics, canvas, q[3], q[4], q[2], q[4]),
+
+ else => unreachable,
+ }
+}
+
+fn circlePiece(
+ canvas: *font.sprite.Canvas,
+ width: u32,
+ height: u32,
+ metrics: font.Metrics,
+ x: f64,
+ y: f64,
+ w: f64,
+ h: f64,
+ corner: Corner,
+) !void {
+ // Radius in pixels of the arc we need to draw.
+ const wdth: f64 = @as(f64, @floatFromInt(width)) * w;
+ const hght: f64 = @as(f64, @floatFromInt(height)) * h;
+
+ // Position in pixels (rather than cells) for x/y
+ const xp: f64 = @as(f64, @floatFromInt(width)) * x;
+ const yp: f64 = @as(f64, @floatFromInt(height)) * y;
+
+ // Set the clip so we don't include anything outside of the cell.
+ canvas.clip_left = canvas.padding_x;
+ canvas.clip_right = canvas.padding_x;
+ canvas.clip_top = canvas.padding_y;
+ canvas.clip_bottom = canvas.padding_y;
+
+ // Coefficient for approximating a circular arc.
+ const c: f64 = (std.math.sqrt2 - 1.0) * 4.0 / 3.0;
+ const cw = c * wdth;
+ const ch = c * hght;
+
+ const thick: f64 = @floatFromInt(metrics.box_thickness);
+ const ht = thick * 0.5;
+
+ var path = canvas.staticPath(2);
+
+ switch (corner) {
+ .tl => {
+ path.moveTo(wdth - xp, ht - yp);
+ path.curveTo(
+ wdth - cw - xp,
+ ht - yp,
+ ht - xp,
+ hght - ch - yp,
+ ht - xp,
+ hght - yp,
+ );
+ },
+ .tr => {
+ path.moveTo(wdth - xp, ht - yp);
+ path.curveTo(
+ wdth + cw - xp,
+ ht - yp,
+ wdth * 2 - ht - xp,
+ hght - ch - yp,
+ wdth * 2 - ht - xp,
+ hght - yp,
+ );
+ },
+ .bl => {
+ path.moveTo(ht - xp, hght - yp);
+ path.curveTo(
+ ht - xp,
+ hght + ch - yp,
+ wdth - cw - xp,
+ hght * 2 - ht - yp,
+ wdth - xp,
+ hght * 2 - ht - yp,
+ );
+ },
+ .br => {
+ path.moveTo(wdth * 2 - ht - xp, hght - yp);
+ path.curveTo(
+ wdth * 2 - ht - xp,
+ hght + ch - yp,
+ wdth + cw - xp,
+ hght * 2 - ht - yp,
+ wdth - xp,
+ hght * 2 - ht - yp,
+ );
+ },
+ }
+
+ try canvas.strokePath(path.wrapped_path, .{
+ .line_cap_mode = .butt,
+ .line_width = @floatFromInt(metrics.box_thickness),
+ }, .on);
+}
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+1CC00...U+1CCFF-11x21+2.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+1CC00...U+1CCFF-11x21+2.png
new file mode 100644
index 00000000..e04e7726
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+1CC00...U+1CCFF-11x21+2.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+1CC00...U+1CCFF-12x24+3.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+1CC00...U+1CCFF-12x24+3.png
new file mode 100644
index 00000000..ce1b1c42
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+1CC00...U+1CCFF-12x24+3.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+1CC00...U+1CCFF-18x36+4.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+1CC00...U+1CCFF-18x36+4.png
new file mode 100644
index 00000000..1c21f688
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+1CC00...U+1CCFF-18x36+4.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+1CC00...U+1CCFF-9x17+1.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+1CC00...U+1CCFF-9x17+1.png
new file mode 100644
index 00000000..459822e6
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+1CC00...U+1CCFF-9x17+1.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+1CD00...U+1CDFF-11x21+2.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+1CD00...U+1CDFF-11x21+2.png
new file mode 100644
index 00000000..8d7de36a
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+1CD00...U+1CDFF-11x21+2.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+1CD00...U+1CDFF-12x24+3.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+1CD00...U+1CDFF-12x24+3.png
new file mode 100644
index 00000000..ab6bec96
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+1CD00...U+1CDFF-12x24+3.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+1CD00...U+1CDFF-18x36+4.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+1CD00...U+1CDFF-18x36+4.png
new file mode 100644
index 00000000..43035aef
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+1CD00...U+1CDFF-18x36+4.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+1CD00...U+1CDFF-9x17+1.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+1CD00...U+1CDFF-9x17+1.png
new file mode 100644
index 00000000..fc111e2d
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+1CD00...U+1CDFF-9x17+1.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+1CE00...U+1CEFF-11x21+2.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+1CE00...U+1CEFF-11x21+2.png
new file mode 100644
index 00000000..b3cda82d
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+1CE00...U+1CEFF-11x21+2.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+1CE00...U+1CEFF-12x24+3.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+1CE00...U+1CEFF-12x24+3.png
new file mode 100644
index 00000000..e076da7c
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+1CE00...U+1CEFF-12x24+3.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+1CE00...U+1CEFF-18x36+4.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+1CE00...U+1CEFF-18x36+4.png
new file mode 100644
index 00000000..366be386
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+1CE00...U+1CEFF-18x36+4.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+1CE00...U+1CEFF-9x17+1.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+1CE00...U+1CEFF-9x17+1.png
new file mode 100644
index 00000000..5cd7a4ef
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+1CE00...U+1CEFF-9x17+1.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+1FB00...U+1FBFF-11x21+2.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+1FB00...U+1FBFF-11x21+2.png
new file mode 100644
index 00000000..023396fe
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+1FB00...U+1FBFF-11x21+2.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+1FB00...U+1FBFF-12x24+3.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+1FB00...U+1FBFF-12x24+3.png
new file mode 100644
index 00000000..1a4dcb01
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+1FB00...U+1FBFF-12x24+3.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+1FB00...U+1FBFF-18x36+4.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+1FB00...U+1FBFF-18x36+4.png
new file mode 100644
index 00000000..73ead485
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+1FB00...U+1FBFF-18x36+4.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+1FB00...U+1FBFF-9x17+1.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+1FB00...U+1FBFF-9x17+1.png
new file mode 100644
index 00000000..b58ded46
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+1FB00...U+1FBFF-9x17+1.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+2500...U+25FF-11x21+2.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+2500...U+25FF-11x21+2.png
new file mode 100644
index 00000000..9ae5b45f
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+2500...U+25FF-11x21+2.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+2500...U+25FF-12x24+3.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+2500...U+25FF-12x24+3.png
new file mode 100644
index 00000000..89011df4
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+2500...U+25FF-12x24+3.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+2500...U+25FF-18x36+4.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+2500...U+25FF-18x36+4.png
new file mode 100644
index 00000000..05ff2a5f
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+2500...U+25FF-18x36+4.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+2500...U+25FF-9x17+1.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+2500...U+25FF-9x17+1.png
new file mode 100644
index 00000000..cf96b7d1
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+2500...U+25FF-9x17+1.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+2800...U+28FF-11x21+2.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+2800...U+28FF-11x21+2.png
new file mode 100644
index 00000000..804e3017
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+2800...U+28FF-11x21+2.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+2800...U+28FF-12x24+3.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+2800...U+28FF-12x24+3.png
new file mode 100644
index 00000000..aa3e2f99
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+2800...U+28FF-12x24+3.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+2800...U+28FF-18x36+4.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+2800...U+28FF-18x36+4.png
new file mode 100644
index 00000000..49f431a4
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+2800...U+28FF-18x36+4.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+2800...U+28FF-9x17+1.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+2800...U+28FF-9x17+1.png
new file mode 100644
index 00000000..4308c536
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+2800...U+28FF-9x17+1.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+E000...U+E0FF-11x21+2.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+E000...U+E0FF-11x21+2.png
new file mode 100644
index 00000000..addd1f49
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+E000...U+E0FF-11x21+2.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+E000...U+E0FF-12x24+3.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+E000...U+E0FF-12x24+3.png
new file mode 100644
index 00000000..70ae8102
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+E000...U+E0FF-12x24+3.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+E000...U+E0FF-18x36+4.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+E000...U+E0FF-18x36+4.png
new file mode 100644
index 00000000..32a42097
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+E000...U+E0FF-18x36+4.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+E000...U+E0FF-9x17+1.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+E000...U+E0FF-9x17+1.png
new file mode 100644
index 00000000..46151697
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+E000...U+E0FF-9x17+1.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+F500...U+F5FF-11x21+2.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+F500...U+F5FF-11x21+2.png
new file mode 100644
index 00000000..10385379
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+F500...U+F5FF-11x21+2.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+F500...U+F5FF-12x24+3.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+F500...U+F5FF-12x24+3.png
new file mode 100644
index 00000000..b1bb803a
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+F500...U+F5FF-12x24+3.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+F500...U+F5FF-18x36+4.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+F500...U+F5FF-18x36+4.png
new file mode 100644
index 00000000..e7415b7a
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+F500...U+F5FF-18x36+4.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+F500...U+F5FF-9x17+1.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+F500...U+F5FF-9x17+1.png
new file mode 100644
index 00000000..730a4c42
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+F500...U+F5FF-9x17+1.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+F600...U+F6FF-11x21+2.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+F600...U+F6FF-11x21+2.png
new file mode 100644
index 00000000..b216174e
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+F600...U+F6FF-11x21+2.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+F600...U+F6FF-12x24+3.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+F600...U+F6FF-12x24+3.png
new file mode 100644
index 00000000..2243248b
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+F600...U+F6FF-12x24+3.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+F600...U+F6FF-18x36+4.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+F600...U+F6FF-18x36+4.png
new file mode 100644
index 00000000..f3a887d2
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+F600...U+F6FF-18x36+4.png
Binary files differ
diff --git a/vendor/libghostty-vt/src/font/sprite/testdata/U+F600...U+F6FF-9x17+1.png b/vendor/libghostty-vt/src/font/sprite/testdata/U+F600...U+F6FF-9x17+1.png
new file mode 100644
index 00000000..82ce8246
--- /dev/null
+++ b/vendor/libghostty-vt/src/font/sprite/testdata/U+F600...U+F6FF-9x17+1.png
Binary files differ