From d29073d999e88f8a35cd3c368f97f6a3bfa8a0fc Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 24 Jul 2024 11:27:02 -0700 Subject: [PATCH 01/36] terminal/kitty: add graphics diacritics file --- src/terminal/kitty/graphics.zig | 5 + src/terminal/kitty/graphics_diacritics.zig | 329 +++++++++++++++++++++ 2 files changed, 334 insertions(+) create mode 100644 src/terminal/kitty/graphics_diacritics.zig diff --git a/src/terminal/kitty/graphics.zig b/src/terminal/kitty/graphics.zig index cfc45adbc..0fa52fab0 100644 --- a/src/terminal/kitty/graphics.zig +++ b/src/terminal/kitty/graphics.zig @@ -20,3 +20,8 @@ pub usingnamespace @import("graphics_command.zig"); pub usingnamespace @import("graphics_exec.zig"); pub usingnamespace @import("graphics_image.zig"); pub usingnamespace @import("graphics_storage.zig"); +pub const diacritics = @import("graphics_diacritics.zig"); + +test { + @import("std").testing.refAllDecls(@This()); +} diff --git a/src/terminal/kitty/graphics_diacritics.zig b/src/terminal/kitty/graphics_diacritics.zig new file mode 100644 index 000000000..db587f407 --- /dev/null +++ b/src/terminal/kitty/graphics_diacritics.zig @@ -0,0 +1,329 @@ +const std = @import("std"); +const assert = std.debug.assert; +const testing = std.testing; + +/// Get the row/col index for a diacritic codepoint. +pub fn get(cp: u21) ?usize { + return std.sort.binarySearch(u21, cp, diacritics, {}, (struct { + fn order(context: void, lhs: u21, rhs: u21) std.math.Order { + _ = context; + return std.math.order(lhs, rhs); + } + }).order); +} + +/// These are the diacritics used with the Kitty graphics protocol +/// Unicode placement feature to specify the row/column for placement. +/// The index into the array determines the value. +/// +/// This is derived from: +/// https://sw.kovidgoyal.net/kitty/_downloads/f0a0de9ec8d9ff4456206db8e0814937/rowcolumn-diacritics.txt +const diacritics: []const u21 = &.{ + 0x0305, + 0x030D, + 0x030E, + 0x0310, + 0x0312, + 0x033D, + 0x033E, + 0x033F, + 0x0346, + 0x034A, + 0x034B, + 0x034C, + 0x0350, + 0x0351, + 0x0352, + 0x0357, + 0x035B, + 0x0363, + 0x0364, + 0x0365, + 0x0366, + 0x0367, + 0x0368, + 0x0369, + 0x036A, + 0x036B, + 0x036C, + 0x036D, + 0x036E, + 0x036F, + 0x0483, + 0x0484, + 0x0485, + 0x0486, + 0x0487, + 0x0592, + 0x0593, + 0x0594, + 0x0595, + 0x0597, + 0x0598, + 0x0599, + 0x059C, + 0x059D, + 0x059E, + 0x059F, + 0x05A0, + 0x05A1, + 0x05A8, + 0x05A9, + 0x05AB, + 0x05AC, + 0x05AF, + 0x05C4, + 0x0610, + 0x0611, + 0x0612, + 0x0613, + 0x0614, + 0x0615, + 0x0616, + 0x0617, + 0x0657, + 0x0658, + 0x0659, + 0x065A, + 0x065B, + 0x065D, + 0x065E, + 0x06D6, + 0x06D7, + 0x06D8, + 0x06D9, + 0x06DA, + 0x06DB, + 0x06DC, + 0x06DF, + 0x06E0, + 0x06E1, + 0x06E2, + 0x06E4, + 0x06E7, + 0x06E8, + 0x06EB, + 0x06EC, + 0x0730, + 0x0732, + 0x0733, + 0x0735, + 0x0736, + 0x073A, + 0x073D, + 0x073F, + 0x0740, + 0x0741, + 0x0743, + 0x0745, + 0x0747, + 0x0749, + 0x074A, + 0x07EB, + 0x07EC, + 0x07ED, + 0x07EE, + 0x07EF, + 0x07F0, + 0x07F1, + 0x07F3, + 0x0816, + 0x0817, + 0x0818, + 0x0819, + 0x081B, + 0x081C, + 0x081D, + 0x081E, + 0x081F, + 0x0820, + 0x0821, + 0x0822, + 0x0823, + 0x0825, + 0x0826, + 0x0827, + 0x0829, + 0x082A, + 0x082B, + 0x082C, + 0x082D, + 0x0951, + 0x0953, + 0x0954, + 0x0F82, + 0x0F83, + 0x0F86, + 0x0F87, + 0x135D, + 0x135E, + 0x135F, + 0x17DD, + 0x193A, + 0x1A17, + 0x1A75, + 0x1A76, + 0x1A77, + 0x1A78, + 0x1A79, + 0x1A7A, + 0x1A7B, + 0x1A7C, + 0x1B6B, + 0x1B6D, + 0x1B6E, + 0x1B6F, + 0x1B70, + 0x1B71, + 0x1B72, + 0x1B73, + 0x1CD0, + 0x1CD1, + 0x1CD2, + 0x1CDA, + 0x1CDB, + 0x1CE0, + 0x1DC0, + 0x1DC1, + 0x1DC3, + 0x1DC4, + 0x1DC5, + 0x1DC6, + 0x1DC7, + 0x1DC8, + 0x1DC9, + 0x1DCB, + 0x1DCC, + 0x1DD1, + 0x1DD2, + 0x1DD3, + 0x1DD4, + 0x1DD5, + 0x1DD6, + 0x1DD7, + 0x1DD8, + 0x1DD9, + 0x1DDA, + 0x1DDB, + 0x1DDC, + 0x1DDD, + 0x1DDE, + 0x1DDF, + 0x1DE0, + 0x1DE1, + 0x1DE2, + 0x1DE3, + 0x1DE4, + 0x1DE5, + 0x1DE6, + 0x1DFE, + 0x20D0, + 0x20D1, + 0x20D4, + 0x20D5, + 0x20D6, + 0x20D7, + 0x20DB, + 0x20DC, + 0x20E1, + 0x20E7, + 0x20E9, + 0x20F0, + 0x2CEF, + 0x2CF0, + 0x2CF1, + 0x2DE0, + 0x2DE1, + 0x2DE2, + 0x2DE3, + 0x2DE4, + 0x2DE5, + 0x2DE6, + 0x2DE7, + 0x2DE8, + 0x2DE9, + 0x2DEA, + 0x2DEB, + 0x2DEC, + 0x2DED, + 0x2DEE, + 0x2DEF, + 0x2DF0, + 0x2DF1, + 0x2DF2, + 0x2DF3, + 0x2DF4, + 0x2DF5, + 0x2DF6, + 0x2DF7, + 0x2DF8, + 0x2DF9, + 0x2DFA, + 0x2DFB, + 0x2DFC, + 0x2DFD, + 0x2DFE, + 0x2DFF, + 0xA66F, + 0xA67C, + 0xA67D, + 0xA6F0, + 0xA6F1, + 0xA8E0, + 0xA8E1, + 0xA8E2, + 0xA8E3, + 0xA8E4, + 0xA8E5, + 0xA8E6, + 0xA8E7, + 0xA8E8, + 0xA8E9, + 0xA8EA, + 0xA8EB, + 0xA8EC, + 0xA8ED, + 0xA8EE, + 0xA8EF, + 0xA8F0, + 0xA8F1, + 0xAAB0, + 0xAAB2, + 0xAAB3, + 0xAAB7, + 0xAAB8, + 0xAABE, + 0xAABF, + 0xAAC1, + 0xFE20, + 0xFE21, + 0xFE22, + 0xFE23, + 0xFE24, + 0xFE25, + 0xFE26, + 0x10A0F, + 0x10A38, + 0x1D185, + 0x1D186, + 0x1D187, + 0x1D188, + 0x1D189, + 0x1D1AA, + 0x1D1AB, + 0x1D1AC, + 0x1D1AD, + 0x1D242, + 0x1D243, + 0x1D244, +}; + +test { + // diacritics must be sorted since we use a binary search. + try testing.expect(std.sort.isSorted(u21, diacritics, {}, (struct { + fn lessThan(context: void, lhs: u21, rhs: u21) bool { + _ = context; + return lhs < rhs; + } + }).lessThan)); +} From a5c382633fe2d84c416996c52bc78e00401ff94c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 24 Jul 2024 11:38:56 -0700 Subject: [PATCH 02/36] terminal/kitty: placements support location enum (only pin for now) --- src/terminal/kitty/graphics_exec.zig | 34 +++++----- src/terminal/kitty/graphics_storage.zig | 84 ++++++++++++++----------- 2 files changed, 65 insertions(+), 53 deletions(-) diff --git a/src/terminal/kitty/graphics_exec.zig b/src/terminal/kitty/graphics_exec.zig index 0ea084795..1d873c0c2 100644 --- a/src/terminal/kitty/graphics_exec.zig +++ b/src/terminal/kitty/graphics_exec.zig @@ -196,7 +196,9 @@ fn display( // Add the placement const p: ImageStorage.Placement = .{ - .pin = placement_pin, + .location = .{ + .pin = placement_pin, + }, .x_offset = d.x_offset, .y_offset = d.y_offset, .source_x = d.x, @@ -218,21 +220,23 @@ fn display( return result; }; - // Cursor needs to move after placement - switch (d.cursor_movement) { - .none => {}, - .after => { - // We use terminal.index to properly handle scroll regions. - const size = p.gridSize(img, terminal); - for (0..size.rows) |_| terminal.index() catch |err| { - log.warn("failed to move cursor: {}", .{err}); - break; - }; + // Apply cursor movement setting. This only applies to pin placements. + switch (p.location) { + .pin => |pin| switch (d.cursor_movement) { + .none => {}, + .after => { + // We use terminal.index to properly handle scroll regions. + const size = p.gridSize(img, terminal); + for (0..size.rows) |_| terminal.index() catch |err| { + log.warn("failed to move cursor: {}", .{err}); + break; + }; - terminal.setCursorPos( - terminal.screen.cursor.y, - p.pin.x + size.cols + 1, - ); + terminal.setCursorPos( + terminal.screen.cursor.y, + pin.x + size.cols + 1, + ); + }, }, } diff --git a/src/terminal/kitty/graphics_storage.zig b/src/terminal/kitty/graphics_storage.zig index cf02ee73e..27be82b7c 100644 --- a/src/terminal/kitty/graphics_storage.zig +++ b/src/terminal/kitty/graphics_storage.zig @@ -576,8 +576,11 @@ pub const ImageStorage = struct { }; pub const Placement = struct { - /// The tracked pin for this placement. - pin: *PageList.Pin, + /// The location where this placement should be drawn. + location: union(enum) { + /// Exactly placed on a screen pin. + pin: *PageList.Pin, + }, /// Offset of the x/y from the top-left of the cell. x_offset: u32 = 0, @@ -600,7 +603,9 @@ pub const ImageStorage = struct { self: *const Placement, s: *terminal.Screen, ) void { - s.pages.untrackPin(self.pin); + switch (self.location) { + .pin => |p| s.pages.untrackPin(p), + } } /// Returns the size in grid cells that this placement takes up. @@ -648,9 +653,12 @@ pub const ImageStorage = struct { image: Image, t: *const terminal.Terminal, ) Rect { - const grid_size = self.gridSize(image, t); + assert(self.location == .pin); - var br = switch (self.pin.downOverflow(grid_size.rows - 1)) { + const grid_size = self.gridSize(image, t); + const pin = self.location.pin; + + var br = switch (pin.downOverflow(grid_size.rows - 1)) { .offset => |v| v, .overflow => |v| v.end, }; @@ -658,12 +666,12 @@ pub const ImageStorage = struct { // We need to sub one here because the x value is // one width already. So if the image is width "1" // then we add zero to X because X itelf is width 1. - self.pin.x + (grid_size.cols - 1), + pin.x + (grid_size.cols - 1), t.cols - 1, ); return .{ - .top_left = self.pin.*, + .top_left = pin.*, .bottom_right = br, }; } @@ -692,8 +700,8 @@ test "storage: add placement with zero placement id" { defer s.deinit(alloc, &t.screen); try s.addImage(alloc, .{ .id = 1, .width = 50, .height = 50 }); try s.addImage(alloc, .{ .id = 2, .width = 25, .height = 25 }); - try s.addPlacement(alloc, 1, 0, .{ .pin = try trackPin(&t, .{ .x = 25, .y = 25 }) }); - try s.addPlacement(alloc, 1, 0, .{ .pin = try trackPin(&t, .{ .x = 25, .y = 25 }) }); + try s.addPlacement(alloc, 1, 0, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 25, .y = 25 }) } }); + try s.addPlacement(alloc, 1, 0, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 25, .y = 25 }) } }); try testing.expectEqual(@as(usize, 2), s.placements.count()); try testing.expectEqual(@as(usize, 2), s.images.count()); @@ -721,8 +729,8 @@ test "storage: delete all placements and images" { try s.addImage(alloc, .{ .id = 1 }); try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 3 }); - try s.addPlacement(alloc, 1, 1, .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) }); - try s.addPlacement(alloc, 2, 1, .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) }); + try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); + try s.addPlacement(alloc, 2, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); s.dirty = false; s.delete(alloc, &t, .{ .all = true }); @@ -745,8 +753,8 @@ test "storage: delete all placements and images preserves limit" { try s.addImage(alloc, .{ .id = 1 }); try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 3 }); - try s.addPlacement(alloc, 1, 1, .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) }); - try s.addPlacement(alloc, 2, 1, .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) }); + try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); + try s.addPlacement(alloc, 2, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); s.dirty = false; s.delete(alloc, &t, .{ .all = true }); @@ -769,8 +777,8 @@ test "storage: delete all placements" { try s.addImage(alloc, .{ .id = 1 }); try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 3 }); - try s.addPlacement(alloc, 1, 1, .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) }); - try s.addPlacement(alloc, 2, 1, .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) }); + try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); + try s.addPlacement(alloc, 2, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); s.dirty = false; s.delete(alloc, &t, .{ .all = false }); @@ -792,8 +800,8 @@ test "storage: delete all placements by image id" { try s.addImage(alloc, .{ .id = 1 }); try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 3 }); - try s.addPlacement(alloc, 1, 1, .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) }); - try s.addPlacement(alloc, 2, 1, .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) }); + try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); + try s.addPlacement(alloc, 2, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); s.dirty = false; s.delete(alloc, &t, .{ .id = .{ .image_id = 2 } }); @@ -815,8 +823,8 @@ test "storage: delete all placements by image id and unused images" { try s.addImage(alloc, .{ .id = 1 }); try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 3 }); - try s.addPlacement(alloc, 1, 1, .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) }); - try s.addPlacement(alloc, 2, 1, .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) }); + try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); + try s.addPlacement(alloc, 2, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); s.dirty = false; s.delete(alloc, &t, .{ .id = .{ .delete = true, .image_id = 2 } }); @@ -838,9 +846,9 @@ test "storage: delete placement by specific id" { try s.addImage(alloc, .{ .id = 1 }); try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 3 }); - try s.addPlacement(alloc, 1, 1, .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) }); - try s.addPlacement(alloc, 1, 2, .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) }); - try s.addPlacement(alloc, 2, 1, .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) }); + try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); + try s.addPlacement(alloc, 1, 2, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); + try s.addPlacement(alloc, 2, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); s.dirty = false; s.delete(alloc, &t, .{ .id = .{ @@ -867,8 +875,8 @@ test "storage: delete intersecting cursor" { defer s.deinit(alloc, &t.screen); try s.addImage(alloc, .{ .id = 1, .width = 50, .height = 50 }); try s.addImage(alloc, .{ .id = 2, .width = 25, .height = 25 }); - try s.addPlacement(alloc, 1, 1, .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) }); - try s.addPlacement(alloc, 1, 2, .{ .pin = try trackPin(&t, .{ .x = 25, .y = 25 }) }); + try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) } }); + try s.addPlacement(alloc, 1, 2, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 25, .y = 25 }) } }); t.screen.cursorAbsolute(12, 12); @@ -899,8 +907,8 @@ test "storage: delete intersecting cursor plus unused" { defer s.deinit(alloc, &t.screen); try s.addImage(alloc, .{ .id = 1, .width = 50, .height = 50 }); try s.addImage(alloc, .{ .id = 2, .width = 25, .height = 25 }); - try s.addPlacement(alloc, 1, 1, .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) }); - try s.addPlacement(alloc, 1, 2, .{ .pin = try trackPin(&t, .{ .x = 25, .y = 25 }) }); + try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) } }); + try s.addPlacement(alloc, 1, 2, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 25, .y = 25 }) } }); t.screen.cursorAbsolute(12, 12); @@ -931,8 +939,8 @@ test "storage: delete intersecting cursor hits multiple" { defer s.deinit(alloc, &t.screen); try s.addImage(alloc, .{ .id = 1, .width = 50, .height = 50 }); try s.addImage(alloc, .{ .id = 2, .width = 25, .height = 25 }); - try s.addPlacement(alloc, 1, 1, .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) }); - try s.addPlacement(alloc, 1, 2, .{ .pin = try trackPin(&t, .{ .x = 25, .y = 25 }) }); + try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) } }); + try s.addPlacement(alloc, 1, 2, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 25, .y = 25 }) } }); t.screen.cursorAbsolute(26, 26); @@ -957,8 +965,8 @@ test "storage: delete by column" { defer s.deinit(alloc, &t.screen); try s.addImage(alloc, .{ .id = 1, .width = 50, .height = 50 }); try s.addImage(alloc, .{ .id = 2, .width = 25, .height = 25 }); - try s.addPlacement(alloc, 1, 1, .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) }); - try s.addPlacement(alloc, 1, 2, .{ .pin = try trackPin(&t, .{ .x = 25, .y = 25 }) }); + try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) } }); + try s.addPlacement(alloc, 1, 2, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 25, .y = 25 }) } }); s.dirty = false; s.delete(alloc, &t, .{ .column = .{ @@ -988,9 +996,9 @@ test "storage: delete by column 1x1" { var s: ImageStorage = .{}; defer s.deinit(alloc, &t.screen); try s.addImage(alloc, .{ .id = 1, .width = 1, .height = 1 }); - try s.addPlacement(alloc, 1, 1, .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) }); - try s.addPlacement(alloc, 1, 2, .{ .pin = try trackPin(&t, .{ .x = 1, .y = 0 }) }); - try s.addPlacement(alloc, 1, 3, .{ .pin = try trackPin(&t, .{ .x = 2, .y = 0 }) }); + try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) } }); + try s.addPlacement(alloc, 1, 2, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 0 }) } }); + try s.addPlacement(alloc, 1, 3, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 2, .y = 0 }) } }); s.delete(alloc, &t, .{ .column = .{ .delete = false, @@ -1023,8 +1031,8 @@ test "storage: delete by row" { defer s.deinit(alloc, &t.screen); try s.addImage(alloc, .{ .id = 1, .width = 50, .height = 50 }); try s.addImage(alloc, .{ .id = 2, .width = 25, .height = 25 }); - try s.addPlacement(alloc, 1, 1, .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) }); - try s.addPlacement(alloc, 1, 2, .{ .pin = try trackPin(&t, .{ .x = 25, .y = 25 }) }); + try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) } }); + try s.addPlacement(alloc, 1, 2, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 25, .y = 25 }) } }); s.dirty = false; s.delete(alloc, &t, .{ .row = .{ @@ -1054,9 +1062,9 @@ test "storage: delete by row 1x1" { var s: ImageStorage = .{}; defer s.deinit(alloc, &t.screen); try s.addImage(alloc, .{ .id = 1, .width = 1, .height = 1 }); - try s.addPlacement(alloc, 1, 1, .{ .pin = try trackPin(&t, .{ .y = 0 }) }); - try s.addPlacement(alloc, 1, 2, .{ .pin = try trackPin(&t, .{ .y = 1 }) }); - try s.addPlacement(alloc, 1, 3, .{ .pin = try trackPin(&t, .{ .y = 2 }) }); + try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .y = 0 }) } }); + try s.addPlacement(alloc, 1, 2, .{ .location = .{ .pin = try trackPin(&t, .{ .y = 1 }) } }); + try s.addPlacement(alloc, 1, 3, .{ .location = .{ .pin = try trackPin(&t, .{ .y = 2 }) } }); s.delete(alloc, &t, .{ .row = .{ .delete = false, From 7d9e50353b6894b961b99282acef62f575720882 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 24 Jul 2024 11:49:42 -0700 Subject: [PATCH 03/36] terminal/kitty: add virtual placeholders placements --- src/terminal/kitty/graphics_exec.zig | 28 ++++++++++------- src/terminal/kitty/graphics_storage.zig | 41 +++++++++++++++++-------- 2 files changed, 46 insertions(+), 23 deletions(-) diff --git a/src/terminal/kitty/graphics_exec.zig b/src/terminal/kitty/graphics_exec.zig index 1d873c0c2..2859d3f0f 100644 --- a/src/terminal/kitty/graphics_exec.zig +++ b/src/terminal/kitty/graphics_exec.zig @@ -184,21 +184,26 @@ fn display( // Make sure our response has the image id in case we looked up by number result.id = img.id; - // Track a new pin for our cursor. The cursor is always tracked but we - // don't want this one to move with the cursor. - const placement_pin = terminal.screen.pages.trackPin( - terminal.screen.cursor.page_pin.*, - ) catch |err| { - log.warn("failed to create pin for Kitty graphics err={}", .{err}); - result.message = "EINVAL: failed to prepare terminal state"; - return result; + // Location where the placement will go. + const location: ImageStorage.Placement.Location = location: { + // Virtual placements are not tracked + if (d.virtual_placement) break :location .{ .virtual = {} }; + + // Track a new pin for our cursor. The cursor is always tracked but we + // don't want this one to move with the cursor. + const pin = terminal.screen.pages.trackPin( + terminal.screen.cursor.page_pin.*, + ) catch |err| { + log.warn("failed to create pin for Kitty graphics err={}", .{err}); + result.message = "EINVAL: failed to prepare terminal state"; + return result; + }; + break :location .{ .pin = pin }; }; // Add the placement const p: ImageStorage.Placement = .{ - .location = .{ - .pin = placement_pin, - }, + .location = location, .x_offset = d.x_offset, .y_offset = d.y_offset, .source_x = d.x, @@ -222,6 +227,7 @@ fn display( // Apply cursor movement setting. This only applies to pin placements. switch (p.location) { + .virtual => {}, .pin => |pin| switch (d.cursor_movement) { .none => {}, .after => { diff --git a/src/terminal/kitty/graphics_storage.zig b/src/terminal/kitty/graphics_storage.zig index 27be82b7c..82441a54e 100644 --- a/src/terminal/kitty/graphics_storage.zig +++ b/src/terminal/kitty/graphics_storage.zig @@ -218,6 +218,7 @@ pub const ImageStorage = struct { cmd: command.Delete, ) void { switch (cmd) { + // TODO: virtual placeholders must not be deleted according to spec .all => |delete_images| if (delete_images) { // We just reset our entire state. self.deinit(alloc, &t.screen); @@ -318,7 +319,7 @@ pub const ImageStorage = struct { var it = self.placements.iterator(); while (it.next()) |entry| { const img = self.imageById(entry.key_ptr.image_id) orelse continue; - const rect = entry.value_ptr.rect(img, t); + const rect = entry.value_ptr.rect(img, t) orelse continue; if (rect.top_left.x <= x and rect.bottom_right.x >= x) { entry.value_ptr.deinit(&t.screen); self.placements.removeByPtr(entry.key_ptr); @@ -345,7 +346,7 @@ pub const ImageStorage = struct { var it = self.placements.iterator(); while (it.next()) |entry| { const img = self.imageById(entry.key_ptr.image_id) orelse continue; - const rect = entry.value_ptr.rect(img, t); + const rect = entry.value_ptr.rect(img, t) orelse continue; // We need to copy our pin to ensure we are at least at // the top-left x. @@ -365,6 +366,14 @@ pub const ImageStorage = struct { .z => |v| { var it = self.placements.iterator(); while (it.next()) |entry| { + switch (entry.value_ptr.location) { + .pin => {}, + + // Virtual placeholders cannot delete by z according + // to the spec. + .virtual => continue, + } + if (entry.value_ptr.z == v.z) { const image_id = entry.key_ptr.image_id; entry.value_ptr.deinit(&t.screen); @@ -451,7 +460,7 @@ pub const ImageStorage = struct { var it = self.placements.iterator(); while (it.next()) |entry| { const img = self.imageById(entry.key_ptr.image_id) orelse continue; - const rect = entry.value_ptr.rect(img, t); + const rect = entry.value_ptr.rect(img, t) orelse continue; if (target_pin.isBetween(rect.top_left, rect.bottom_right)) { if (filter) |f| if (!f(filter_ctx, entry.value_ptr.*)) continue; entry.value_ptr.deinit(&t.screen); @@ -577,10 +586,7 @@ pub const ImageStorage = struct { pub const Placement = struct { /// The location where this placement should be drawn. - location: union(enum) { - /// Exactly placed on a screen pin. - pin: *PageList.Pin, - }, + location: Location, /// Offset of the x/y from the top-left of the cell. x_offset: u32 = 0, @@ -599,12 +605,21 @@ pub const ImageStorage = struct { /// The z-index for this placement. z: i32 = 0, + pub const Location = union(enum) { + /// Exactly placed on a screen pin. + pin: *PageList.Pin, + + /// Virtual placement (U=1) for unicode placeholders. + virtual: void, + }; + pub fn deinit( self: *const Placement, s: *terminal.Screen, ) void { switch (self.location) { .pin => |p| s.pages.untrackPin(p), + .virtual => {}, } } @@ -647,16 +662,18 @@ pub const ImageStorage = struct { } /// Returns a selection of the entire rectangle this placement - /// occupies within the screen. + /// occupies within the screen. This can return null if the placement + /// doesn't have an associated rect (i.e. a virtual placement). pub fn rect( self: Placement, image: Image, t: *const terminal.Terminal, - ) Rect { - assert(self.location == .pin); - + ) ?Rect { const grid_size = self.gridSize(image, t); - const pin = self.location.pin; + const pin = switch (self.location) { + .pin => |p| p, + .virtual => return null, + }; var br = switch (pin.downOverflow(grid_size.rows - 1)) { .offset => |v| v, From 763e7fab8abf4613c49214ddd993582dde99867f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 24 Jul 2024 11:52:49 -0700 Subject: [PATCH 04/36] renderer: skip virtual placements --- src/renderer/Metal.zig | 9 ++++++--- src/renderer/OpenGL.zig | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index d6c2514cc..07c5956ec 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -1598,8 +1598,11 @@ fn prepKittyGraphics( continue; }; + // Get the rect for the placement. If this placement doesn't have + // a rect then its virtual or something so skip it. + const rect = p.rect(image, t) orelse continue; + // If the selection isn't within our viewport then skip it. - const rect = p.rect(image, t); if (bot.before(rect.top_left)) continue; if (rect.bottom_right.before(top)) continue; @@ -1656,7 +1659,7 @@ fn prepKittyGraphics( // Convert our screen point to a viewport point const viewport: terminal.point.Point = t.screen.pages.pointFromPin( .viewport, - p.pin.*, + rect.top_left, ) orelse .{ .viewport = .{} }; // Calculate the source rectangle @@ -1679,7 +1682,7 @@ fn prepKittyGraphics( if (image.width > 0 and image.height > 0) { try self.image_placements.append(self.alloc, .{ .image_id = kv.key_ptr.image_id, - .x = @intCast(p.pin.x), + .x = @intCast(rect.top_left.x), .y = @intCast(viewport.viewport.y), .z = p.z, .width = dest_width, diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index 46acff1a9..fd9261874 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -785,8 +785,11 @@ fn prepKittyGraphics( continue; }; + // Get the rect for the placement. If this placement doesn't have + // a rect then its virtual or something so skip it. + const rect = p.rect(image, t) orelse continue; + // If the selection isn't within our viewport then skip it. - const rect = p.rect(image, t); if (bot.before(rect.top_left)) continue; if (rect.bottom_right.before(top)) continue; @@ -843,7 +846,7 @@ fn prepKittyGraphics( // Convert our screen point to a viewport point const viewport: terminal.point.Point = t.screen.pages.pointFromPin( .viewport, - p.pin.*, + rect.top_left, ) orelse .{ .viewport = .{} }; // Calculate the source rectangle @@ -866,7 +869,7 @@ fn prepKittyGraphics( if (image.width > 0 and image.height > 0) { try self.image_placements.append(self.alloc, .{ .image_id = kv.key_ptr.image_id, - .x = @intCast(p.pin.x), + .x = @intCast(rect.top_left.x), .y = @intCast(viewport.viewport.y), .z = p.z, .width = dest_width, From 2c0f9bfc28cf9e0e7e7a8040eae73d759b304cb4 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 24 Jul 2024 18:44:14 -0700 Subject: [PATCH 05/36] terminal: cell returns empty for Kitty placeholder So we don't render the replacement char --- src/terminal/Terminal.zig | 6 +++++- src/terminal/kitty/graphics.zig | 1 + src/terminal/kitty/graphics_diacritics.zig | 3 +++ src/terminal/page.zig | 22 ++++++++++++++++++++-- 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index c58377374..16b8a07b9 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -379,7 +379,11 @@ pub fn print(self: *Terminal, c: u21) !void { } } - log.debug("c={x} grapheme attach to left={}", .{ c, prev.left }); + log.debug("c={X} grapheme attach to left={} primary_cp={X}", .{ + c, + prev.left, + prev.cell.codepoint(), + }); self.screen.cursorMarkDirty(); try self.screen.appendGrapheme(prev.cell, c); return; diff --git a/src/terminal/kitty/graphics.zig b/src/terminal/kitty/graphics.zig index 0fa52fab0..14ab6babb 100644 --- a/src/terminal/kitty/graphics.zig +++ b/src/terminal/kitty/graphics.zig @@ -21,6 +21,7 @@ pub usingnamespace @import("graphics_exec.zig"); pub usingnamespace @import("graphics_image.zig"); pub usingnamespace @import("graphics_storage.zig"); pub const diacritics = @import("graphics_diacritics.zig"); +pub const placeholder = diacritics.placeholder; test { @import("std").testing.refAllDecls(@This()); diff --git a/src/terminal/kitty/graphics_diacritics.zig b/src/terminal/kitty/graphics_diacritics.zig index db587f407..92fb816eb 100644 --- a/src/terminal/kitty/graphics_diacritics.zig +++ b/src/terminal/kitty/graphics_diacritics.zig @@ -2,6 +2,9 @@ const std = @import("std"); const assert = std.debug.assert; const testing = std.testing; +/// Codepoint for the unicode placeholder character. +pub const placeholder: u21 = 0x10EEEE; + /// Get the row/col index for a diacritic codepoint. pub fn get(cp: u21) ?usize { return std.sort.binarySearch(u21, cp, diacritics, {}, (struct { diff --git a/src/terminal/page.zig b/src/terminal/page.zig index 677e3fb4a..457207782 100644 --- a/src/terminal/page.zig +++ b/src/terminal/page.zig @@ -8,6 +8,7 @@ const posix = std.posix; const fastmem = @import("../fastmem.zig"); const color = @import("color.zig"); const hyperlink = @import("hyperlink.zig"); +const kitty = @import("kitty.zig"); const sgr = @import("sgr.zig"); const style = @import("style.zig"); const size = @import("size.zig"); @@ -1673,11 +1674,18 @@ pub const Cell = packed struct(u64) { return @as(u64, @bitCast(self)) == 0; } + /// Returns true if this cell represents a cell with text to render. + /// + /// Cases this returns false: + /// - Cell text is blank + /// - Cell is styled but only with a background color and no text + /// - Cell has a unicode placeholder for Kitty graphics protocol pub fn hasText(self: Cell) bool { return switch (self.content_tag) { .codepoint, .codepoint_grapheme, - => self.content.codepoint != 0, + => self.content.codepoint != 0 and + self.content.codepoint != kitty.graphics.placeholder, .bg_color_palette, .bg_color_rgb, @@ -1709,7 +1717,8 @@ pub const Cell = packed struct(u64) { return self.style_id != style.default_id; } - /// Returns true if the cell has no text or styling. + /// Returns true if the cell has no text or styling. This also returns + /// true if the cell represents a Kitty graphics unicode placeholder. pub fn isEmpty(self: Cell) bool { return switch (self.content_tag) { // Textual cells are empty if they have no text and are narrow. @@ -2641,3 +2650,12 @@ test "Page verifyIntegrity zero cols" { page.verifyIntegrity(testing.allocator), ); } + +test "Cell isEmpty for kitty placeholder" { + var c: Cell = .{ + .content_tag = .codepoint_grapheme, + .content = .{ .codepoint = kitty.graphics.placeholder }, + }; + try testing.expectEqual(@as(u21, kitty.graphics.placeholder), c.codepoint()); + try testing.expect(c.isEmpty()); +} From 358b4ca896dd75c76b694c595947ae53988780e6 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 24 Jul 2024 18:59:01 -0700 Subject: [PATCH 06/36] terminal/kitty: parse relative placement fields --- src/terminal/kitty/graphics_command.zig | 20 ++++++++++++++++++++ src/terminal/kitty/graphics_exec.zig | 9 ++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/terminal/kitty/graphics_command.zig b/src/terminal/kitty/graphics_command.zig index fe9a4520f..810a8df55 100644 --- a/src/terminal/kitty/graphics_command.zig +++ b/src/terminal/kitty/graphics_command.zig @@ -462,6 +462,10 @@ pub const Display = struct { rows: u32 = 0, // r cursor_movement: CursorMovement = .after, // C virtual_placement: bool = false, // U + parent_id: u32 = 0, // P + parent_placement_id: u32 = 0, // Q + horizontal_offset: u32 = 0, // H + vertical_offset: u32 = 0, // V z: i32 = 0, // z pub const CursorMovement = enum { @@ -537,6 +541,22 @@ pub const Display = struct { result.z = @bitCast(v); } + if (kv.get('P')) |v| { + result.parent_id = v; + } + + if (kv.get('Q')) |v| { + result.parent_placement_id = v; + } + + if (kv.get('H')) |v| { + result.horizontal_offset = v; + } + + if (kv.get('V')) |v| { + result.vertical_offset = v; + } + return result; } }; diff --git a/src/terminal/kitty/graphics_exec.zig b/src/terminal/kitty/graphics_exec.zig index 2859d3f0f..b8924d56f 100644 --- a/src/terminal/kitty/graphics_exec.zig +++ b/src/terminal/kitty/graphics_exec.zig @@ -187,7 +187,14 @@ fn display( // Location where the placement will go. const location: ImageStorage.Placement.Location = location: { // Virtual placements are not tracked - if (d.virtual_placement) break :location .{ .virtual = {} }; + if (d.virtual_placement) { + if (d.parent_id > 0) { + result.message = "EINVAL: virtual placement cannot refer to a parent"; + return result; + } + + break :location .{ .virtual = {} }; + } // Track a new pin for our cursor. The cursor is always tracked but we // don't want this one to move with the cursor. From f71afcab9595de7bc2d6c8011f84c141ac330fa0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 24 Jul 2024 19:14:17 -0700 Subject: [PATCH 07/36] terminal/kitty: diacritic small tests --- src/terminal/kitty/graphics_diacritics.zig | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/terminal/kitty/graphics_diacritics.zig b/src/terminal/kitty/graphics_diacritics.zig index 92fb816eb..ec3e7d0ca 100644 --- a/src/terminal/kitty/graphics_diacritics.zig +++ b/src/terminal/kitty/graphics_diacritics.zig @@ -5,7 +5,7 @@ const testing = std.testing; /// Codepoint for the unicode placeholder character. pub const placeholder: u21 = 0x10EEEE; -/// Get the row/col index for a diacritic codepoint. +/// Get the row/col index for a diacritic codepoint. These are 0-indexed. pub fn get(cp: u21) ?usize { return std.sort.binarySearch(u21, cp, diacritics, {}, (struct { fn order(context: void, lhs: u21, rhs: u21) std.math.Order { @@ -321,7 +321,7 @@ const diacritics: []const u21 = &.{ 0x1D244, }; -test { +test "sorted" { // diacritics must be sorted since we use a binary search. try testing.expect(std.sort.isSorted(u21, diacritics, {}, (struct { fn lessThan(context: void, lhs: u21, rhs: u21) bool { @@ -330,3 +330,9 @@ test { } }).lessThan)); } + +test "diacritic" { + // Some spot checks based on Kitty behavior + try testing.expectEqual(30, get(0x483).?); + try testing.expectEqual(294, get(0x1d242).?); +} From bb1a9bf532cfec2f632b1b0b78129ee996b1224a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 24 Jul 2024 19:20:45 -0700 Subject: [PATCH 08/36] terminal/kitty: rename diacritics to unicode --- src/terminal/kitty/graphics.zig | 3 +-- .../{graphics_diacritics.zig => graphics_unicode.zig} | 9 ++++++--- src/terminal/page.zig | 6 +++--- 3 files changed, 10 insertions(+), 8 deletions(-) rename src/terminal/kitty/{graphics_diacritics.zig => graphics_unicode.zig} (94%) diff --git a/src/terminal/kitty/graphics.zig b/src/terminal/kitty/graphics.zig index 14ab6babb..22d102f53 100644 --- a/src/terminal/kitty/graphics.zig +++ b/src/terminal/kitty/graphics.zig @@ -20,8 +20,7 @@ pub usingnamespace @import("graphics_command.zig"); pub usingnamespace @import("graphics_exec.zig"); pub usingnamespace @import("graphics_image.zig"); pub usingnamespace @import("graphics_storage.zig"); -pub const diacritics = @import("graphics_diacritics.zig"); -pub const placeholder = diacritics.placeholder; +pub const unicode = @import("graphics_unicode.zig"); test { @import("std").testing.refAllDecls(@This()); diff --git a/src/terminal/kitty/graphics_diacritics.zig b/src/terminal/kitty/graphics_unicode.zig similarity index 94% rename from src/terminal/kitty/graphics_diacritics.zig rename to src/terminal/kitty/graphics_unicode.zig index ec3e7d0ca..d85d07067 100644 --- a/src/terminal/kitty/graphics_diacritics.zig +++ b/src/terminal/kitty/graphics_unicode.zig @@ -1,3 +1,6 @@ +//! This file contains various logic and data for working with the +//! Kitty graphics protocol unicode placeholder, virtual placement feature. + const std = @import("std"); const assert = std.debug.assert; const testing = std.testing; @@ -6,7 +9,7 @@ const testing = std.testing; pub const placeholder: u21 = 0x10EEEE; /// Get the row/col index for a diacritic codepoint. These are 0-indexed. -pub fn get(cp: u21) ?usize { +pub fn getIndex(cp: u21) ?usize { return std.sort.binarySearch(u21, cp, diacritics, {}, (struct { fn order(context: void, lhs: u21, rhs: u21) std.math.Order { _ = context; @@ -333,6 +336,6 @@ test "sorted" { test "diacritic" { // Some spot checks based on Kitty behavior - try testing.expectEqual(30, get(0x483).?); - try testing.expectEqual(294, get(0x1d242).?); + try testing.expectEqual(30, getIndex(0x483).?); + try testing.expectEqual(294, getIndex(0x1d242).?); } diff --git a/src/terminal/page.zig b/src/terminal/page.zig index 457207782..0ad599641 100644 --- a/src/terminal/page.zig +++ b/src/terminal/page.zig @@ -1685,7 +1685,7 @@ pub const Cell = packed struct(u64) { .codepoint, .codepoint_grapheme, => self.content.codepoint != 0 and - self.content.codepoint != kitty.graphics.placeholder, + self.content.codepoint != kitty.graphics.unicode.placeholder, .bg_color_palette, .bg_color_rgb, @@ -2654,8 +2654,8 @@ test "Page verifyIntegrity zero cols" { test "Cell isEmpty for kitty placeholder" { var c: Cell = .{ .content_tag = .codepoint_grapheme, - .content = .{ .codepoint = kitty.graphics.placeholder }, + .content = .{ .codepoint = kitty.graphics.unicode.placeholder }, }; - try testing.expectEqual(@as(u21, kitty.graphics.placeholder), c.codepoint()); + try testing.expectEqual(@as(u21, kitty.graphics.unicode.placeholder), c.codepoint()); try testing.expect(c.isEmpty()); } From deacb10fb1a6cdf093c0f4440ebca206c0fb5fc1 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 25 Jul 2024 10:03:44 -0700 Subject: [PATCH 09/36] terminal: print must use codepoint() now to work with placeholders --- src/terminal/Terminal.zig | 4 ++-- src/terminal/page.zig | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 16b8a07b9..013325c3a 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -281,7 +281,7 @@ pub fn print(self: *Terminal, c: u21) !void { // column. Otherwise, we need to check if there is text to // figure out if we're attaching to the prev or current. if (self.screen.cursor.x != right_limit - 1) break :left 1; - break :left @intFromBool(!self.screen.cursor.page_cell.hasText()); + break :left @intFromBool(self.screen.cursor.page_cell.codepoint() == 0); }; // If the previous cell is a wide spacer tail, then we actually @@ -299,7 +299,7 @@ pub fn print(self: *Terminal, c: u21) !void { // If our cell has no content, then this is a new cell and // necessarily a grapheme break. - if (!prev.cell.hasText()) break :grapheme; + if (prev.cell.codepoint() == 0) break :grapheme; const grapheme_break = brk: { var state: unicode.GraphemeBreakState = .{}; diff --git a/src/terminal/page.zig b/src/terminal/page.zig index 0ad599641..396e976be 100644 --- a/src/terminal/page.zig +++ b/src/terminal/page.zig @@ -1161,7 +1161,7 @@ pub const Page = struct { pub fn appendGrapheme(self: *Page, row: *Row, cell: *Cell, cp: u21) Allocator.Error!void { defer self.assertIntegrity(); - if (comptime std.debug.runtime_safety) assert(cell.hasText()); + if (comptime std.debug.runtime_safety) assert(cell.codepoint() != 0); const cell_offset = getOffset(Cell, self.memory, cell); var map = self.grapheme_map.map(self.memory); From e656fe3b792d5f23fbd52f56333d251ed2c5d0b6 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 25 Jul 2024 10:05:05 -0700 Subject: [PATCH 10/36] terminal/kitty: starting virtual placement iterator --- src/terminal/kitty/graphics_unicode.zig | 94 ++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 2 deletions(-) diff --git a/src/terminal/kitty/graphics_unicode.zig b/src/terminal/kitty/graphics_unicode.zig index d85d07067..21f3657e5 100644 --- a/src/terminal/kitty/graphics_unicode.zig +++ b/src/terminal/kitty/graphics_unicode.zig @@ -4,10 +4,64 @@ const std = @import("std"); const assert = std.debug.assert; const testing = std.testing; +const terminal = @import("../main.zig"); /// Codepoint for the unicode placeholder character. pub const placeholder: u21 = 0x10EEEE; +/// Returns an iterator that iterates over all of the virtual placements +/// in the given pin. If `limit` is provided, the iterator will stop +/// when it reaches that pin (inclusive). If `limit` is not provided, +/// the iterator will continue until the end of the page list. +pub fn placementIterator( + pin: terminal.Pin, + limit: ?terminal.Pin, +) PlacementIterator { + var row_it = pin.rowIterator(.right_down, limit); + const row = row_it.next(); + return .{ .row_it = row_it, .row = row }; +} + +/// Iterator over unicode virtual placements. +pub const PlacementIterator = struct { + row_it: terminal.PageList.RowIterator, + row: ?terminal.Pin, + + pub fn next(self: *PlacementIterator) ?Placement { + while (self.row) |*row| { + // A row must have graphemes to possibly have virtual placements + // since virtual placements are done via diacritics. + if (row.rowAndCell().row.grapheme) { + // Iterate over our remaining cells and find one with a placeholder. + const cells = row.cells(.right); + for (cells, row.x..) |cell, x| { + if (cell.codepoint() != placeholder) continue; + + if (x == cells.len - 1) { + // We are at the end of this row so move to the next row + self.row = self.row_it.next(); + } else { + // We can move right to the next cell. + row.x = @intCast(x + 1); + } + + // TODO + return .{}; + } + } + + // We didn't find any placements. Move to the next row. + self.row = self.row_it.next(); + } + + return null; + } +}; + +/// A virtual placement in the terminal. This can represent more than +/// one cell if the cells combine to be a run. +pub const Placement = struct {}; + /// Get the row/col index for a diacritic codepoint. These are 0-indexed. pub fn getIndex(cp: u21) ?usize { return std.sort.binarySearch(u21, cp, diacritics, {}, (struct { @@ -324,7 +378,7 @@ const diacritics: []const u21 = &.{ 0x1D244, }; -test "sorted" { +test "unicode diacritic sorted" { // diacritics must be sorted since we use a binary search. try testing.expect(std.sort.isSorted(u21, diacritics, {}, (struct { fn lessThan(context: void, lhs: u21, rhs: u21) bool { @@ -334,8 +388,44 @@ test "sorted" { }).lessThan)); } -test "diacritic" { +test "unicode diacritic" { // Some spot checks based on Kitty behavior try testing.expectEqual(30, getIndex(0x483).?); try testing.expectEqual(294, getIndex(0x1d242).?); } + +test "unicode placement: none" { + const alloc = testing.allocator; + var t = try terminal.Terminal.init(alloc, .{ .rows = 5, .cols = 5 }); + defer t.deinit(alloc); + t.modes.set(.grapheme_cluster, true); + + // Single cell + try t.printString("hello\nworld\n1\n2"); + + // No placements + const pin = t.screen.pages.getTopLeft(.viewport); + var it = placementIterator(pin, null); + try testing.expect(it.next() == null); +} + +test "unicode placement: single" { + const alloc = testing.allocator; + var t = try terminal.Terminal.init(alloc, .{ .rows = 5, .cols = 5 }); + defer t.deinit(alloc); + t.modes.set(.grapheme_cluster, true); + + // Single cell + try t.printString("\u{10EEEE}\u{0305}\u{0305}"); + + // Get our top left pin + const pin = t.screen.pages.getTopLeft(.viewport); + + // Should have exactly one placement + var it = placementIterator(pin, null); + { + const p = it.next().?; + _ = p; + } + try testing.expect(it.next() == null); +} From 13df93a1d021bc2c7d5b066aa7d68acf8c0ea38d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 25 Jul 2024 10:35:50 -0700 Subject: [PATCH 11/36] temrinal/kitty: really basic row/col diacritic decoding --- src/terminal/PageList.zig | 2 +- src/terminal/kitty/graphics_unicode.zig | 74 +++++++++++++++++++++---- src/terminal/page.zig | 2 +- 3 files changed, 66 insertions(+), 12 deletions(-) diff --git a/src/terminal/PageList.zig b/src/terminal/PageList.zig index 8a5713333..c1bed792f 100644 --- a/src/terminal/PageList.zig +++ b/src/terminal/PageList.zig @@ -3224,7 +3224,7 @@ pub const Pin = struct { /// Returns the grapheme codepoints for the given cell. These are only /// the EXTRA codepoints and not the first codepoint. - pub fn grapheme(self: Pin, cell: *pagepkg.Cell) ?[]u21 { + pub fn grapheme(self: Pin, cell: *const pagepkg.Cell) ?[]u21 { return self.page.data.lookupGrapheme(cell); } diff --git a/src/terminal/kitty/graphics_unicode.zig b/src/terminal/kitty/graphics_unicode.zig index 21f3657e5..ff1e60955 100644 --- a/src/terminal/kitty/graphics_unicode.zig +++ b/src/terminal/kitty/graphics_unicode.zig @@ -32,21 +32,60 @@ pub const PlacementIterator = struct { // A row must have graphemes to possibly have virtual placements // since virtual placements are done via diacritics. if (row.rowAndCell().row.grapheme) { + // TODO: document + const prev: ?Placement = null; + _ = prev; + // Iterate over our remaining cells and find one with a placeholder. const cells = row.cells(.right); - for (cells, row.x..) |cell, x| { + for (cells, row.x..) |*cell, x| { if (cell.codepoint() != placeholder) continue; + // TODO: we need to support non-grapheme cells that just + // do continuations all the way through. + assert(cell.hasGrapheme()); + + // "row" now points to the top-left pin of the placement. + row.x = @intCast(x); + + // Build our placement + var p: Placement = .{ + .pin = row.*, + + // Filled in below. Marked as undefined so we can catch + // bugs with safety checks. + .col = undefined, + .row = undefined, + + // For now we don't build runs and we always produce + // single cell placements. + .width = 1, + .height = 1, + }; + + // Determine our row/col by looking at the diacritics. + const cps: []const u21 = row.grapheme(cell) orelse &.{}; + if (cps.len > 0) { + p.row = getIndex(cps[0]) orelse @panic("TODO: invalid"); + if (cps.len > 1) { + p.col = getIndex(cps[1]) orelse @panic("TODO: invalid"); + if (cps.len > 2) { + @panic("TODO: higher 8 bits of image ID"); + } + } + } else @panic("TODO: continuations"); + if (x == cells.len - 1) { // We are at the end of this row so move to the next row self.row = self.row_it.next(); } else { - // We can move right to the next cell. - row.x = @intCast(x + 1); + // We can move right to the next cell. row is a pointer + // to self.row so we can modify it directly. + assert(@intFromPtr(row) == @intFromPtr(&self.row)); + row.x += 1; } - // TODO - return .{}; + return p; } } @@ -60,16 +99,30 @@ pub const PlacementIterator = struct { /// A virtual placement in the terminal. This can represent more than /// one cell if the cells combine to be a run. -pub const Placement = struct {}; +pub const Placement = struct { + /// The top-left pin of the placement. This can be used to get the + /// screen x/y. + pin: terminal.Pin, + + /// Starting row/col index for the image itself. This is the "fragment" + /// of the image we want to show in this placement. This is 0-indexed. + col: u32, + row: u32, + + /// The width/height in cells of this placement. + width: u32, + height: u32, +}; /// Get the row/col index for a diacritic codepoint. These are 0-indexed. -pub fn getIndex(cp: u21) ?usize { - return std.sort.binarySearch(u21, cp, diacritics, {}, (struct { +pub fn getIndex(cp: u21) ?u32 { + const idx = std.sort.binarySearch(u21, cp, diacritics, {}, (struct { fn order(context: void, lhs: u21, rhs: u21) std.math.Order { _ = context; return std.math.order(lhs, rhs); } - }).order); + }).order) orelse return null; + return @intCast(idx); } /// These are the diacritics used with the Kitty graphics protocol @@ -425,7 +478,8 @@ test "unicode placement: single" { var it = placementIterator(pin, null); { const p = it.next().?; - _ = p; + try testing.expectEqual(0, p.row); + try testing.expectEqual(0, p.col); } try testing.expect(it.next() == null); } diff --git a/src/terminal/page.zig b/src/terminal/page.zig index 396e976be..18b63f126 100644 --- a/src/terminal/page.zig +++ b/src/terminal/page.zig @@ -1220,7 +1220,7 @@ pub const Page = struct { /// Returns the codepoints for the given cell. These are the codepoints /// in addition to the first codepoint. The first codepoint is NOT /// included since it is on the cell itself. - pub fn lookupGrapheme(self: *const Page, cell: *Cell) ?[]u21 { + pub fn lookupGrapheme(self: *const Page, cell: *const Cell) ?[]u21 { const cell_offset = getOffset(Cell, self.memory, cell); const map = self.grapheme_map.map(self.memory); const slice = map.get(cell_offset) orelse return null; From 578bfc8d230665c2ff75234e089648695af8b3de Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 25 Jul 2024 10:54:00 -0700 Subject: [PATCH 12/36] terminal/kitty: parse image/placement id from style --- src/terminal/kitty/graphics_unicode.zig | 96 +++++++++++++++++++++++-- 1 file changed, 92 insertions(+), 4 deletions(-) diff --git a/src/terminal/kitty/graphics_unicode.zig b/src/terminal/kitty/graphics_unicode.zig index ff1e60955..e67790425 100644 --- a/src/terminal/kitty/graphics_unicode.zig +++ b/src/terminal/kitty/graphics_unicode.zig @@ -22,6 +22,23 @@ pub fn placementIterator( return .{ .row_it = row_it, .row = row }; } +/// Convert a style color to a Kitty image protocol ID. This works by +/// taking the 24 most significant bits of the color, which lets it work +/// for both palette and rgb-based colors. +fn colorToId(c: terminal.Style.Color) u32 { + // TODO: test this + return switch (c) { + .none => 0, + .palette => |v| @intCast(v), + .rgb => |rgb| rgb: { + const r: u24 = @intCast(rgb.r); + const g: u24 = @intCast(rgb.g); + const b: u24 = @intCast(rgb.b); + break :rgb (r << 16) | (g << 8) | b; + }, + }; +} + /// Iterator over unicode virtual placements. pub const PlacementIterator = struct { row_it: terminal.PageList.RowIterator, @@ -32,9 +49,11 @@ pub const PlacementIterator = struct { // A row must have graphemes to possibly have virtual placements // since virtual placements are done via diacritics. if (row.rowAndCell().row.grapheme) { - // TODO: document - const prev: ?Placement = null; - _ = prev; + // Our current run. A run is always only a single row. This + // assumption is built-in to our logic so if we want to change + // this later we have to redo the logic; tests should cover; + const run: ?Placement = null; + _ = run; // Iterate over our remaining cells and find one with a placeholder. const cells = row.cells(.right); @@ -48,9 +67,16 @@ pub const PlacementIterator = struct { // "row" now points to the top-left pin of the placement. row.x = @intCast(x); + // Determine our image ID and placement ID from the style. + const style = row.style(cell); + const image_id = colorToId(style.fg_color); + const placement_id = colorToId(style.underline_color); + // Build our placement var p: Placement = .{ .pin = row.*, + .image_id = image_id, + .placement_id = placement_id, // Filled in below. Marked as undefined so we can catch // bugs with safety checks. @@ -64,6 +90,8 @@ pub const PlacementIterator = struct { }; // Determine our row/col by looking at the diacritics. + // If the cell doesn't have graphemes that's okay because + // of continuations. const cps: []const u21 = row.grapheme(cell) orelse &.{}; if (cps.len > 0) { p.row = getIndex(cps[0]) orelse @panic("TODO: invalid"); @@ -104,6 +132,13 @@ pub const Placement = struct { /// screen x/y. pin: terminal.Pin, + /// The image ID and placement ID for this virtual placement. The + /// image ID is encoded in the fg color (plus optional a 8-bit high + /// value in the 3rd diacritic). The placement ID is encoded in the + /// underline color (optionally). + image_id: u32, + placement_id: u32, + /// Starting row/col index for the image itself. This is the "fragment" /// of the image we want to show in this placement. This is 0-indexed. col: u32, @@ -462,7 +497,7 @@ test "unicode placement: none" { try testing.expect(it.next() == null); } -test "unicode placement: single" { +test "unicode placement: single row/col" { const alloc = testing.allocator; var t = try terminal.Terminal.init(alloc, .{ .rows = 5, .cols = 5 }); defer t.deinit(alloc); @@ -478,6 +513,59 @@ test "unicode placement: single" { var it = placementIterator(pin, null); { const p = it.next().?; + try testing.expectEqual(0, p.image_id); + try testing.expectEqual(0, p.placement_id); + try testing.expectEqual(0, p.row); + try testing.expectEqual(0, p.col); + } + try testing.expect(it.next() == null); +} + +test "unicode placement: specifying image id as palette" { + const alloc = testing.allocator; + var t = try terminal.Terminal.init(alloc, .{ .rows = 5, .cols = 5 }); + defer t.deinit(alloc); + t.modes.set(.grapheme_cluster, true); + + // Single cell + try t.setAttribute(.{ .@"256_fg" = 42 }); + try t.printString("\u{10EEEE}\u{0305}\u{0305}"); + + // Get our top left pin + const pin = t.screen.pages.getTopLeft(.viewport); + + // Should have exactly one placement + var it = placementIterator(pin, null); + { + const p = it.next().?; + try testing.expectEqual(42, p.image_id); + try testing.expectEqual(0, p.placement_id); + try testing.expectEqual(0, p.row); + try testing.expectEqual(0, p.col); + } + try testing.expect(it.next() == null); +} + +test "unicode placement: specifying placement id as palette" { + const alloc = testing.allocator; + var t = try terminal.Terminal.init(alloc, .{ .rows = 5, .cols = 5 }); + defer t.deinit(alloc); + t.modes.set(.grapheme_cluster, true); + + // Single cell + try t.setAttribute(.{ .@"256_fg" = 42 }); + try t.setAttribute(.{ .@"256_underline_color" = 21 }); + try t.printString("\u{10EEEE}\u{0305}\u{0305}"); + + // Get our top left pin + const pin = t.screen.pages.getTopLeft(.viewport); + + // Should have exactly one placement + var it = placementIterator(pin, null); + { + const p = it.next().?; + try testing.expectEqual(42, p.image_id); + try testing.expectEqual(21, p.placement_id); try testing.expectEqual(0, p.row); try testing.expectEqual(0, p.col); } From 7c6ae90300527f7e54ad06eb4ab8ba605524ec60 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 25 Jul 2024 14:09:58 -0700 Subject: [PATCH 13/36] terminal/kitty: implement high bit image id parsing --- src/terminal/kitty/graphics_unicode.zig | 28 ++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/terminal/kitty/graphics_unicode.zig b/src/terminal/kitty/graphics_unicode.zig index e67790425..e04b20bd5 100644 --- a/src/terminal/kitty/graphics_unicode.zig +++ b/src/terminal/kitty/graphics_unicode.zig @@ -98,7 +98,8 @@ pub const PlacementIterator = struct { if (cps.len > 1) { p.col = getIndex(cps[1]) orelse @panic("TODO: invalid"); if (cps.len > 2) { - @panic("TODO: higher 8 bits of image ID"); + const high = getIndex(cps[2]) orelse @panic("TODO: invalid"); + p.image_id += high << 24; } } } else @panic("TODO: continuations"); @@ -546,6 +547,31 @@ test "unicode placement: specifying image id as palette" { try testing.expect(it.next() == null); } +test "unicode placement: specifying image id with high bits" { + const alloc = testing.allocator; + var t = try terminal.Terminal.init(alloc, .{ .rows = 5, .cols = 5 }); + defer t.deinit(alloc); + t.modes.set(.grapheme_cluster, true); + + // Single cell + try t.setAttribute(.{ .@"256_fg" = 42 }); + try t.printString("\u{10EEEE}\u{0305}\u{0305}\u{030E}"); + + // Get our top left pin + const pin = t.screen.pages.getTopLeft(.viewport); + + // Should have exactly one placement + var it = placementIterator(pin, null); + { + const p = it.next().?; + try testing.expectEqual(33554474, p.image_id); + try testing.expectEqual(0, p.placement_id); + try testing.expectEqual(0, p.row); + try testing.expectEqual(0, p.col); + } + try testing.expect(it.next() == null); +} + test "unicode placement: specifying placement id as palette" { const alloc = testing.allocator; var t = try terminal.Terminal.init(alloc, .{ .rows = 5, .cols = 5 }); From cf6463fec0bef77c173d969ecb0e9a4058e4f9d3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 25 Jul 2024 14:56:46 -0700 Subject: [PATCH 14/36] terminal/kitty: preparing to build runs of placements --- src/terminal/PageList.zig | 2 +- src/terminal/kitty/graphics_unicode.zig | 255 +++++++++++++++++------- 2 files changed, 183 insertions(+), 74 deletions(-) diff --git a/src/terminal/PageList.zig b/src/terminal/PageList.zig index c1bed792f..9590f2fbb 100644 --- a/src/terminal/PageList.zig +++ b/src/terminal/PageList.zig @@ -3229,7 +3229,7 @@ pub const Pin = struct { } /// Returns the style for the given cell in this pin. - pub fn style(self: Pin, cell: *pagepkg.Cell) stylepkg.Style { + pub fn style(self: Pin, cell: *const pagepkg.Cell) stylepkg.Style { if (cell.style_id == stylepkg.default_id) return .{}; return self.page.data.styles.get( self.page.data.memory, diff --git a/src/terminal/kitty/graphics_unicode.zig b/src/terminal/kitty/graphics_unicode.zig index e04b20bd5..121e3a157 100644 --- a/src/terminal/kitty/graphics_unicode.zig +++ b/src/terminal/kitty/graphics_unicode.zig @@ -6,6 +6,8 @@ const assert = std.debug.assert; const testing = std.testing; const terminal = @import("../main.zig"); +const log = std.log.scoped(.kitty_gfx); + /// Codepoint for the unicode placeholder character. pub const placeholder: u21 = 0x10EEEE; @@ -22,23 +24,6 @@ pub fn placementIterator( return .{ .row_it = row_it, .row = row }; } -/// Convert a style color to a Kitty image protocol ID. This works by -/// taking the 24 most significant bits of the color, which lets it work -/// for both palette and rgb-based colors. -fn colorToId(c: terminal.Style.Color) u32 { - // TODO: test this - return switch (c) { - .none => 0, - .palette => |v| @intCast(v), - .rgb => |rgb| rgb: { - const r: u24 = @intCast(rgb.r); - const g: u24 = @intCast(rgb.g); - const b: u24 = @intCast(rgb.b); - break :rgb (r << 16) | (g << 8) | b; - }, - }; -} - /// Iterator over unicode virtual placements. pub const PlacementIterator = struct { row_it: terminal.PageList.RowIterator, @@ -46,80 +31,62 @@ pub const PlacementIterator = struct { pub fn next(self: *PlacementIterator) ?Placement { while (self.row) |*row| { + // Our current run. A run is always only a single row. This + // assumption is built-in to our logic so if we want to change + // this later we have to redo the logic; tests should cover; + var run: ?IncompletePlacement = null; + // A row must have graphemes to possibly have virtual placements // since virtual placements are done via diacritics. if (row.rowAndCell().row.grapheme) { - // Our current run. A run is always only a single row. This - // assumption is built-in to our logic so if we want to change - // this later we have to redo the logic; tests should cover; - const run: ?Placement = null; - _ = run; - // Iterate over our remaining cells and find one with a placeholder. const cells = row.cells(.right); for (cells, row.x..) |*cell, x| { - if (cell.codepoint() != placeholder) continue; + // "row" now points to the top-left pin of the placement. + // We need this temporary state to build our incomplete + // placement. + assert(@intFromPtr(row) == @intFromPtr(&self.row)); + row.x = @intCast(x); + + // If this cell doesn't have the placeholder, then we + // complete the run if we have it otherwise we just move + // on and keep searching. + if (cell.codepoint() != placeholder) { + if (run) |prev| return prev.complete(); + continue; + } // TODO: we need to support non-grapheme cells that just // do continuations all the way through. assert(cell.hasGrapheme()); - // "row" now points to the top-left pin of the placement. - row.x = @intCast(x); - - // Determine our image ID and placement ID from the style. - const style = row.style(cell); - const image_id = colorToId(style.fg_color); - const placement_id = colorToId(style.underline_color); - - // Build our placement - var p: Placement = .{ - .pin = row.*, - .image_id = image_id, - .placement_id = placement_id, - - // Filled in below. Marked as undefined so we can catch - // bugs with safety checks. - .col = undefined, - .row = undefined, - - // For now we don't build runs and we always produce - // single cell placements. - .width = 1, - .height = 1, - }; - - // Determine our row/col by looking at the diacritics. - // If the cell doesn't have graphemes that's okay because - // of continuations. - const cps: []const u21 = row.grapheme(cell) orelse &.{}; - if (cps.len > 0) { - p.row = getIndex(cps[0]) orelse @panic("TODO: invalid"); - if (cps.len > 1) { - p.col = getIndex(cps[1]) orelse @panic("TODO: invalid"); - if (cps.len > 2) { - const high = getIndex(cps[2]) orelse @panic("TODO: invalid"); - p.image_id += high << 24; - } + // If we don't have a previous run, then we save this + // incomplete one, start a run, and move on. + const curr = IncompletePlacement.init(row, cell); + if (run) |*prev| { + // If we can't append, then we complete the previous + // run and return it. + if (!prev.append(&curr)) { + // Note: self.row is already updated due to the + // row pointer above. It points back at this same + // cell so we can continue the new placements from + // here. + return prev.complete(); } - } else @panic("TODO: continuations"); - if (x == cells.len - 1) { - // We are at the end of this row so move to the next row - self.row = self.row_it.next(); + // append is mutating so if we reached this point + // then prev has been updated. } else { - // We can move right to the next cell. row is a pointer - // to self.row so we can modify it directly. - assert(@intFromPtr(row) == @intFromPtr(&self.row)); - row.x += 1; + run = curr; } - - return p; } } - // We didn't find any placements. Move to the next row. + // We move to the next row no matter what self.row = self.row_it.next(); + + // If we have a run, we complete it here. + if (run) |prev| return prev.complete(); } return null; @@ -150,8 +117,150 @@ pub const Placement = struct { height: u32, }; +/// IncompletePlacement is the placement information present in a single +/// cell. It is "incomplete" because the specification allows for missing +/// diacritics and so on that continue from previous valid placements. +const IncompletePlacement = struct { + /// The pin of the cell that created this incomplete placement. + pin: terminal.Pin, + + /// Lower 24 bits of the image ID. This is specified in the fg color + /// and is always required. + image_id_low: u24, + + /// Higher 8 bits of the image ID specified using the 3rd diacritic. + /// This is optional. + image_id_high: ?u8 = null, + + /// Placement ID is optionally specified in the underline color. + placement_id: ?u24 = null, + + /// The row/col index for the image. These are 0-indexed. These + /// are specified using diacritics. The row is first and the col + /// is second. Both are optional. If not specified, they can continue + /// a previous placement under certain conditions. + row: ?u32 = null, + col: ?u32 = null, + + /// Parse the incomplete placement information from a row and cell. + /// + /// The cell could be derived from the row but in our usage we already + /// have the cell and we don't want to waste cycles recomputing it. + pub fn init( + row: *const terminal.Pin, + cell: *const terminal.Cell, + ) IncompletePlacement { + assert(cell.codepoint() == placeholder); + const style = row.style(cell); + + var result: IncompletePlacement = .{ + .pin = row.*, + .image_id_low = colorToId(style.fg_color), + .placement_id = placement_id: { + const id = colorToId(style.underline_color); + break :placement_id if (id != 0) id else null; + }, + }; + + // Try to decode all our diacritics. Any invalid diacritics are + // treated as if they don't exist. This isn't explicitly specified + // at the time of writing this but it appears to be how Kitty behaves. + const cps: []const u21 = row.grapheme(cell) orelse &.{}; + if (cps.len > 0) { + result.row = getIndex(cps[0]) orelse value: { + log.warn("virtual placement with invalid row diacritic cp={X}", .{cps[0]}); + break :value null; + }; + + if (cps.len > 1) { + result.col = getIndex(cps[1]) orelse value: { + log.warn("virtual placement with invalid col diacritic cp={X}", .{cps[1]}); + break :value null; + }; + + if (cps.len > 2) { + const high_ = getIndex(cps[2]) orelse value: { + log.warn("virtual placement with invalid high diacritic cp={X}", .{cps[2]}); + break :value null; + }; + + if (high_) |high| { + result.image_id_high = std.math.cast( + u8, + high, + ) orelse value: { + log.warn("virtual placement with invalid high diacritic cp={X} value={}", .{ + cps[2], + high, + }); + break :value null; + }; + } + + // Any additional diacritics are ignored. + } + } + } + + return result; + } + + /// Append this incomplete placement to an existing placement to + /// create a run. This returns true if the placements are compatible + /// and were combined. If this returns false, the other placement is + /// unchanged. + pub fn append(self: *IncompletePlacement, other: *const IncompletePlacement) bool { + return self.canAppend(other); + } + + fn canAppend(self: *const IncompletePlacement, other: *const IncompletePlacement) bool { + if (self.image_id_low != other.image_id_low) return false; + if (self.placement_id != other.placement_id) return false; + return false; + } + + /// Complete the incomplete placement to create a full placement. + /// This creates a new placement that isn't continuous with any previous + /// placements. + /// + /// The pin is the pin of the cell that created this incomplete placement. + pub fn complete(self: *const IncompletePlacement) Placement { + return .{ + .pin = self.pin, + .image_id = image_id: { + const low: u32 = @intCast(self.image_id_low); + const high: u32 = @intCast(self.image_id_high orelse 0); + break :image_id low | (high << 24); + }, + + .placement_id = self.placement_id orelse 0, + .col = self.col orelse 0, + .row = self.row orelse 0, + .width = 1, + .height = 1, + }; + } + + /// Convert a style color to a Kitty image protocol ID. This works by + /// taking the 24 most significant bits of the color, which lets it work + /// for both palette and rgb-based colors. + fn colorToId(c: terminal.Style.Color) u24 { + // TODO: test this + return switch (c) { + .none => 0, + .palette => |v| @intCast(v), + .rgb => |rgb| rgb: { + const r: u24 = @intCast(rgb.r); + const g: u24 = @intCast(rgb.g); + const b: u24 = @intCast(rgb.b); + break :rgb (r << 16) | (g << 8) | b; + }, + }; + } +}; + /// Get the row/col index for a diacritic codepoint. These are 0-indexed. -pub fn getIndex(cp: u21) ?u32 { +fn getIndex(cp: u21) ?u32 { const idx = std.sort.binarySearch(u21, cp, diacritics, {}, (struct { fn order(context: void, lhs: u21, rhs: u21) std.math.Order { _ = context; From 1786502f15bcc2d5808189f1a31637b653954d5d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 25 Jul 2024 15:31:40 -0700 Subject: [PATCH 15/36] terminal/kitty: working runs --- src/terminal/kitty/graphics_unicode.zig | 112 ++++++++++++++++++++++-- 1 file changed, 106 insertions(+), 6 deletions(-) diff --git a/src/terminal/kitty/graphics_unicode.zig b/src/terminal/kitty/graphics_unicode.zig index 121e3a157..cb9703dd7 100644 --- a/src/terminal/kitty/graphics_unicode.zig +++ b/src/terminal/kitty/graphics_unicode.zig @@ -77,7 +77,11 @@ pub const PlacementIterator = struct { // append is mutating so if we reached this point // then prev has been updated. } else { - run = curr; + // For appending, we need to set our initial values. + var prev = curr; + if (prev.row == null) prev.row = 0; + if (prev.col == null) prev.col = 0; + run = prev; } } } @@ -142,6 +146,9 @@ const IncompletePlacement = struct { row: ?u32 = null, col: ?u32 = null, + /// The run width so far in cells. + width: u32 = 1, + /// Parse the incomplete placement information from a row and cell. /// /// The cell could be derived from the row but in our usage we already @@ -210,13 +217,18 @@ const IncompletePlacement = struct { /// and were combined. If this returns false, the other placement is /// unchanged. pub fn append(self: *IncompletePlacement, other: *const IncompletePlacement) bool { - return self.canAppend(other); + if (!self.canAppend(other)) return false; + self.width += 1; + return true; } fn canAppend(self: *const IncompletePlacement, other: *const IncompletePlacement) bool { - if (self.image_id_low != other.image_id_low) return false; - if (self.placement_id != other.placement_id) return false; - return false; + // Converted from Kitty's logic, don't @ me. + return self.image_id_low == other.image_id_low and + self.placement_id == other.placement_id and + (other.row == null or other.row == self.row) and + (other.col == null or other.col == self.col.? + self.width) and + (other.image_id_high == null or other.image_id_high == self.image_id_high); } /// Complete the incomplete placement to create a full placement. @@ -236,7 +248,7 @@ const IncompletePlacement = struct { .placement_id = self.placement_id orelse 0, .col = self.col orelse 0, .row = self.row orelse 0, - .width = 1, + .width = self.width, .height = 1, }; } @@ -631,6 +643,94 @@ test "unicode placement: single row/col" { try testing.expect(it.next() == null); } +test "unicode placement: continuation break" { + const alloc = testing.allocator; + var t = try terminal.Terminal.init(alloc, .{ .rows = 5, .cols = 10 }); + defer t.deinit(alloc); + t.modes.set(.grapheme_cluster, true); + + // Two runs because it jumps cols + try t.printString("\u{10EEEE}\u{0305}\u{0305}"); + try t.printString("\u{10EEEE}\u{0305}\u{030E}"); + + // Get our top left pin + const pin = t.screen.pages.getTopLeft(.viewport); + + // Should have exactly one placement + var it = placementIterator(pin, null); + { + const p = it.next().?; + try testing.expectEqual(0, p.image_id); + try testing.expectEqual(0, p.placement_id); + try testing.expectEqual(0, p.row); + try testing.expectEqual(0, p.col); + try testing.expectEqual(1, p.width); + } + { + const p = it.next().?; + try testing.expectEqual(0, p.image_id); + try testing.expectEqual(0, p.placement_id); + try testing.expectEqual(0, p.row); + try testing.expectEqual(2, p.col); + try testing.expectEqual(1, p.width); + } + try testing.expect(it.next() == null); +} + +test "unicode placement: continuation with diacritics set" { + const alloc = testing.allocator; + var t = try terminal.Terminal.init(alloc, .{ .rows = 5, .cols = 10 }); + defer t.deinit(alloc); + t.modes.set(.grapheme_cluster, true); + + // Three cells. They'll continue even though they're explicit + try t.printString("\u{10EEEE}\u{0305}\u{0305}"); + try t.printString("\u{10EEEE}\u{0305}\u{030D}"); + try t.printString("\u{10EEEE}\u{0305}\u{030E}"); + + // Get our top left pin + const pin = t.screen.pages.getTopLeft(.viewport); + + // Should have exactly one placement + var it = placementIterator(pin, null); + { + const p = it.next().?; + try testing.expectEqual(0, p.image_id); + try testing.expectEqual(0, p.placement_id); + try testing.expectEqual(0, p.row); + try testing.expectEqual(0, p.col); + try testing.expectEqual(3, p.width); + } + try testing.expect(it.next() == null); +} + +test "unicode placement: continuation with no col" { + const alloc = testing.allocator; + var t = try terminal.Terminal.init(alloc, .{ .rows = 5, .cols = 10 }); + defer t.deinit(alloc); + t.modes.set(.grapheme_cluster, true); + + // Three cells. They'll continue even though they're explicit + try t.printString("\u{10EEEE}\u{0305}\u{0305}"); + try t.printString("\u{10EEEE}\u{0305}"); + try t.printString("\u{10EEEE}\u{0305}"); + + // Get our top left pin + const pin = t.screen.pages.getTopLeft(.viewport); + + // Should have exactly one placement + var it = placementIterator(pin, null); + { + const p = it.next().?; + try testing.expectEqual(0, p.image_id); + try testing.expectEqual(0, p.placement_id); + try testing.expectEqual(0, p.row); + try testing.expectEqual(0, p.col); + try testing.expectEqual(3, p.width); + } + try testing.expect(it.next() == null); +} + test "unicode placement: specifying image id as palette" { const alloc = testing.allocator; var t = try terminal.Terminal.init(alloc, .{ .rows = 5, .cols = 5 }); From 91a6e70d1b1d9fb307e644bfb430df06b3fcbb98 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 25 Jul 2024 18:56:47 -0700 Subject: [PATCH 16/36] terminal/kitty: extra placement tests --- src/terminal/kitty/graphics_unicode.zig | 55 ++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/src/terminal/kitty/graphics_unicode.zig b/src/terminal/kitty/graphics_unicode.zig index cb9703dd7..c77d037fa 100644 --- a/src/terminal/kitty/graphics_unicode.zig +++ b/src/terminal/kitty/graphics_unicode.zig @@ -257,7 +257,6 @@ const IncompletePlacement = struct { /// taking the 24 most significant bits of the color, which lets it work /// for both palette and rgb-based colors. fn colorToId(c: terminal.Style.Color) u24 { - // TODO: test this return switch (c) { .none => 0, .palette => |v| @intCast(v), @@ -731,6 +730,60 @@ test "unicode placement: continuation with no col" { try testing.expect(it.next() == null); } +test "unicode placement: run ending" { + const alloc = testing.allocator; + var t = try terminal.Terminal.init(alloc, .{ .rows = 5, .cols = 10 }); + defer t.deinit(alloc); + t.modes.set(.grapheme_cluster, true); + + // Three cells. They'll continue even though they're explicit + try t.printString("\u{10EEEE}\u{0305}\u{0305}"); + try t.printString("\u{10EEEE}\u{0305}\u{030D}"); + try t.printString("ABC"); + + // Get our top left pin + const pin = t.screen.pages.getTopLeft(.viewport); + + // Should have exactly one placement + var it = placementIterator(pin, null); + { + const p = it.next().?; + try testing.expectEqual(0, p.image_id); + try testing.expectEqual(0, p.placement_id); + try testing.expectEqual(0, p.row); + try testing.expectEqual(0, p.col); + try testing.expectEqual(2, p.width); + } + try testing.expect(it.next() == null); +} + +test "unicode placement: run starting in the middle" { + const alloc = testing.allocator; + var t = try terminal.Terminal.init(alloc, .{ .rows = 5, .cols = 10 }); + defer t.deinit(alloc); + t.modes.set(.grapheme_cluster, true); + + // Three cells. They'll continue even though they're explicit + try t.printString("ABC"); + try t.printString("\u{10EEEE}\u{0305}\u{0305}"); + try t.printString("\u{10EEEE}\u{0305}\u{030D}"); + + // Get our top left pin + const pin = t.screen.pages.getTopLeft(.viewport); + + // Should have exactly one placement + var it = placementIterator(pin, null); + { + const p = it.next().?; + try testing.expectEqual(0, p.image_id); + try testing.expectEqual(0, p.placement_id); + try testing.expectEqual(0, p.row); + try testing.expectEqual(0, p.col); + try testing.expectEqual(2, p.width); + } + try testing.expect(it.next() == null); +} + test "unicode placement: specifying image id as palette" { const alloc = testing.allocator; var t = try terminal.Terminal.init(alloc, .{ .rows = 5, .cols = 5 }); From 3549619a64aab3d85f729008026663c5990b16a2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 25 Jul 2024 19:35:11 -0700 Subject: [PATCH 17/36] terminal: introduce row bit for kitty virtual placeholders --- src/terminal/PageList.zig | 6 ++++++ src/terminal/Screen.zig | 10 ++++++++++ src/terminal/Terminal.zig | 6 ++++++ src/terminal/page.zig | 23 ++++++++++++++++++++++- 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/terminal/PageList.zig b/src/terminal/PageList.zig index 9590f2fbb..403fe7e5a 100644 --- a/src/terminal/PageList.zig +++ b/src/terminal/PageList.zig @@ -7,6 +7,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; const fastmem = @import("../fastmem.zig"); +const kitty = @import("kitty.zig"); const point = @import("point.zig"); const pagepkg = @import("page.zig"); const stylepkg = @import("style.zig"); @@ -1056,6 +1057,11 @@ const ReflowCursor = struct { self.page_cell.style_id = id; } + // Copy Kitty virtual placeholder status + if (cell.codepoint() == kitty.graphics.unicode.placeholder) { + self.page_row.kitty_virtual_placeholder = true; + } + self.cursorForward(); } diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index 12bdaa73c..30ffc39e3 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -984,6 +984,16 @@ pub fn clearCells( if (cells.len == self.pages.cols) row.styled = false; } + if (row.kitty_virtual_placeholder and + cells.len == self.pages.cols) + { + for (cells) |c| { + if (c.codepoint() == kitty.graphics.unicode.placeholder) { + break; + } + } else row.kitty_virtual_placeholder = false; + } + @memset(cells, self.blankCell()); } diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 013325c3a..d6b8a7376 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -640,6 +640,12 @@ fn printCell( } } + // If this is a Kitty unicode placeholder then we need to mark the + // row so that the renderer can lookup rows with these much faster. + if (c == kitty.graphics.unicode.placeholder) { + self.screen.cursor.page_row.kitty_virtual_placeholder = true; + } + // We check for an active hyperlink first because setHyperlink // handles clearing the old hyperlink and an optimization if we're // overwriting the same hyperlink. diff --git a/src/terminal/page.zig b/src/terminal/page.zig index 18b63f126..9d149270e 100644 --- a/src/terminal/page.zig +++ b/src/terminal/page.zig @@ -826,6 +826,9 @@ pub const Page = struct { src_cell.style_id, ) orelse src_cell.style_id; } + if (src_cell.codepoint() == kitty.graphics.unicode.placeholder) { + dst_row.kitty_virtual_placeholder = true; + } } } @@ -913,6 +916,9 @@ pub const Page = struct { dst.hyperlink = true; dst_row.hyperlink = true; } + if (src.codepoint() == kitty.graphics.unicode.placeholder) { + dst_row.kitty_virtual_placeholder = true; + } } } @@ -932,6 +938,7 @@ pub const Page = struct { src_row.grapheme = false; src_row.hyperlink = false; src_row.styled = false; + src_row.kitty_virtual_placeholder = false; } } @@ -1029,6 +1036,16 @@ pub const Page = struct { if (cells.len == self.size.cols) row.styled = false; } + if (row.kitty_virtual_placeholder and + cells.len == self.size.cols) + { + for (cells) |c| { + if (c.codepoint() == kitty.graphics.unicode.placeholder) { + break; + } + } else row.kitty_virtual_placeholder = false; + } + // Zero the cells as u64s since empirically this seems // to be a bit faster than using @memset(cells, .{}) @memset(@as([]u64, @ptrCast(cells)), 0); @@ -1552,7 +1569,11 @@ pub const Row = packed struct(u64) { /// running program, or "unknown" if it was never set. semantic_prompt: SemanticPrompt = .unknown, - _padding: u24 = 0, + /// True if this row contains a virtual placeholder for the Kitty + /// graphics protocol. (U+10EEEE) + kitty_virtual_placeholder: bool = false, + + _padding: u23 = 0, /// Semantic prompt type. pub const SemanticPrompt = enum(u3) { From 266033670d7f3ca7c4b29189ffe3d4885d62a860 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 25 Jul 2024 19:42:52 -0700 Subject: [PATCH 18/36] terminal/kitty: support cells with no diacritics --- src/terminal/kitty/graphics_unicode.zig | 113 +++++++++++++++--------- 1 file changed, 69 insertions(+), 44 deletions(-) diff --git a/src/terminal/kitty/graphics_unicode.zig b/src/terminal/kitty/graphics_unicode.zig index c77d037fa..a908ac200 100644 --- a/src/terminal/kitty/graphics_unicode.zig +++ b/src/terminal/kitty/graphics_unicode.zig @@ -31,58 +31,56 @@ pub const PlacementIterator = struct { pub fn next(self: *PlacementIterator) ?Placement { while (self.row) |*row| { + // This row flag is set on rows that have the virtual placeholder + if (!row.rowAndCell().row.kitty_virtual_placeholder) { + self.row = self.row_it.next(); + continue; + } + // Our current run. A run is always only a single row. This // assumption is built-in to our logic so if we want to change // this later we have to redo the logic; tests should cover; var run: ?IncompletePlacement = null; - // A row must have graphemes to possibly have virtual placements - // since virtual placements are done via diacritics. - if (row.rowAndCell().row.grapheme) { - // Iterate over our remaining cells and find one with a placeholder. - const cells = row.cells(.right); - for (cells, row.x..) |*cell, x| { - // "row" now points to the top-left pin of the placement. - // We need this temporary state to build our incomplete - // placement. - assert(@intFromPtr(row) == @intFromPtr(&self.row)); - row.x = @intCast(x); + // Iterate over our remaining cells and find one with a placeholder. + const cells = row.cells(.right); + for (cells, row.x..) |*cell, x| { + // "row" now points to the top-left pin of the placement. + // We need this temporary state to build our incomplete + // placement. + assert(@intFromPtr(row) == @intFromPtr(&self.row)); + row.x = @intCast(x); - // If this cell doesn't have the placeholder, then we - // complete the run if we have it otherwise we just move - // on and keep searching. - if (cell.codepoint() != placeholder) { - if (run) |prev| return prev.complete(); - continue; + // If this cell doesn't have the placeholder, then we + // complete the run if we have it otherwise we just move + // on and keep searching. + if (cell.codepoint() != placeholder) { + if (run) |prev| return prev.complete(); + continue; + } + + // If we don't have a previous run, then we save this + // incomplete one, start a run, and move on. + const curr = IncompletePlacement.init(row, cell); + if (run) |*prev| { + // If we can't append, then we complete the previous + // run and return it. + if (!prev.append(&curr)) { + // Note: self.row is already updated due to the + // row pointer above. It points back at this same + // cell so we can continue the new placements from + // here. + return prev.complete(); } - // TODO: we need to support non-grapheme cells that just - // do continuations all the way through. - assert(cell.hasGrapheme()); - - // If we don't have a previous run, then we save this - // incomplete one, start a run, and move on. - const curr = IncompletePlacement.init(row, cell); - if (run) |*prev| { - // If we can't append, then we complete the previous - // run and return it. - if (!prev.append(&curr)) { - // Note: self.row is already updated due to the - // row pointer above. It points back at this same - // cell so we can continue the new placements from - // here. - return prev.complete(); - } - - // append is mutating so if we reached this point - // then prev has been updated. - } else { - // For appending, we need to set our initial values. - var prev = curr; - if (prev.row == null) prev.row = 0; - if (prev.col == null) prev.col = 0; - run = prev; - } + // append is mutating so if we reached this point + // then prev has been updated. + } else { + // For appending, we need to set our initial values. + var prev = curr; + if (prev.row == null) prev.row = 0; + if (prev.col == null) prev.col = 0; + run = prev; } } @@ -730,6 +728,33 @@ test "unicode placement: continuation with no col" { try testing.expect(it.next() == null); } +test "unicode placement: continuation with no diacritics" { + const alloc = testing.allocator; + var t = try terminal.Terminal.init(alloc, .{ .rows = 5, .cols = 10 }); + defer t.deinit(alloc); + t.modes.set(.grapheme_cluster, true); + + // Three cells. They'll continue even though they're explicit + try t.printString("\u{10EEEE}"); + try t.printString("\u{10EEEE}"); + try t.printString("\u{10EEEE}"); + + // Get our top left pin + const pin = t.screen.pages.getTopLeft(.viewport); + + // Should have exactly one placement + var it = placementIterator(pin, null); + { + const p = it.next().?; + try testing.expectEqual(0, p.image_id); + try testing.expectEqual(0, p.placement_id); + try testing.expectEqual(0, p.row); + try testing.expectEqual(0, p.col); + try testing.expectEqual(3, p.width); + } + try testing.expect(it.next() == null); +} + test "unicode placement: run ending" { const alloc = testing.allocator; var t = try terminal.Terminal.init(alloc, .{ .rows = 5, .cols = 10 }); From d6d95209c6d5234d74b9adc760e0e1ef76674cd1 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 25 Jul 2024 21:35:38 -0700 Subject: [PATCH 19/36] renderer/metal: extract out some image placement logic --- src/renderer/Metal.zig | 235 ++++++++++++++++++++++++----------------- 1 file changed, 136 insertions(+), 99 deletions(-) diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 07c5956ec..c4a22beed 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -124,6 +124,7 @@ images: ImageMap = .{}, image_placements: ImagePlacementList = .{}, image_bg_end: u32 = 0, image_text_end: u32 = 0, +image_virtual: bool = false, /// Metal state shaders: Shaders, // Compiled shaders @@ -927,7 +928,14 @@ pub fn updateFrame( // If we have Kitty graphics data, we enter a SLOW SLOW SLOW path. // We only do this if the Kitty image state is dirty meaning only if // it changes. - if (state.terminal.screen.kitty_images.dirty) { + // + // If we have any virtual references, we must also rebuild our + // kitty state on every frame because any cell change can move + // an image. + // TODO(mitchellh): integrate with row dirty flags + if (state.terminal.screen.kitty_images.dirty or + self.image_virtual) + { try self.prepKittyGraphics(state.terminal); } @@ -1565,6 +1573,7 @@ fn prepKittyGraphics( // We always clear our previous placements no matter what because // we rebuild them from scratch. self.image_placements.clearRetainingCapacity(); + self.image_virtual = false; // Go through our known images and if there are any that are no longer // in use then mark them to be freed. @@ -1588,8 +1597,25 @@ fn prepKittyGraphics( // Go through the placements and ensure the image is loaded on the GPU. var it = storage.placements.iterator(); while (it.next()) |kv| { - // Find the image in storage const p = kv.value_ptr; + + // Special logic based on location + switch (p.location) { + .pin => {}, + .virtual => { + // We need to mark virtual placements on our renderer so that + // we know to rebuild in more scenarios since cell changes can + // now trigger placement changes. + self.image_virtual = true; + + // We also continue out because virtual placements are + // only triggered by the unicode placeholder, not by the + // placement itself. + continue; + }, + } + + // Get the image for the placement const image = storage.imageById(kv.key_ptr.image_id) orelse { log.warn( "missing image for placement, ignoring image_id={}", @@ -1598,103 +1624,7 @@ fn prepKittyGraphics( continue; }; - // Get the rect for the placement. If this placement doesn't have - // a rect then its virtual or something so skip it. - const rect = p.rect(image, t) orelse continue; - - // If the selection isn't within our viewport then skip it. - if (bot.before(rect.top_left)) continue; - if (rect.bottom_right.before(top)) continue; - - // If the top left is outside the viewport we need to calc an offset - // so that we render (0, 0) with some offset for the texture. - const offset_y: u32 = if (rect.top_left.before(top)) offset_y: { - const vp_y = t.screen.pages.pointFromPin(.screen, top).?.screen.y; - const img_y = t.screen.pages.pointFromPin(.screen, rect.top_left).?.screen.y; - const offset_cells = vp_y - img_y; - const offset_pixels = offset_cells * self.grid_metrics.cell_height; - break :offset_y @intCast(offset_pixels); - } else 0; - - // We need to prep this image for upload if it isn't in the cache OR - // it is in the cache but the transmit time doesn't match meaning this - // image is different. - const gop = try self.images.getOrPut(self.alloc, kv.key_ptr.image_id); - if (!gop.found_existing or - gop.value_ptr.transmit_time.order(image.transmit_time) != .eq) - { - // Copy the data into the pending state. - const data = try self.alloc.dupe(u8, image.data); - errdefer self.alloc.free(data); - - // Store it in the map - const pending: Image.Pending = .{ - .width = image.width, - .height = image.height, - .data = data.ptr, - }; - - const new_image: Image = switch (image.format) { - .grey_alpha => .{ .pending_grey_alpha = pending }, - .rgb => .{ .pending_rgb = pending }, - .rgba => .{ .pending_rgba = pending }, - .png => unreachable, // should be decoded by now - }; - - if (!gop.found_existing) { - gop.value_ptr.* = .{ - .image = new_image, - .transmit_time = undefined, - }; - } else { - try gop.value_ptr.image.markForReplace( - self.alloc, - new_image, - ); - } - - gop.value_ptr.transmit_time = image.transmit_time; - } - - // Convert our screen point to a viewport point - const viewport: terminal.point.Point = t.screen.pages.pointFromPin( - .viewport, - rect.top_left, - ) orelse .{ .viewport = .{} }; - - // Calculate the source rectangle - const source_x = @min(image.width, p.source_x); - const source_y = @min(image.height, p.source_y + offset_y); - const source_width = if (p.source_width > 0) - @min(image.width - source_x, p.source_width) - else - image.width; - const source_height = if (p.source_height > 0) - @min(image.height, p.source_height) - else - image.height -| source_y; - - // Calculate the width/height of our image. - const dest_width = if (p.columns > 0) p.columns * self.grid_metrics.cell_width else source_width; - const dest_height = if (p.rows > 0) p.rows * self.grid_metrics.cell_height else source_height; - - // Accumulate the placement - if (image.width > 0 and image.height > 0) { - try self.image_placements.append(self.alloc, .{ - .image_id = kv.key_ptr.image_id, - .x = @intCast(rect.top_left.x), - .y = @intCast(viewport.viewport.y), - .z = p.z, - .width = dest_width, - .height = dest_height, - .cell_offset_x = p.x_offset, - .cell_offset_y = p.y_offset, - .source_x = source_x, - .source_y = source_y, - .source_width = source_width, - .source_height = source_height, - }); - } + try self.prepKittyPlacement(t, &top, &bot, &image, p); } // Sort the placements by their Z value. @@ -1731,6 +1661,113 @@ fn prepKittyGraphics( } } +fn prepKittyPlacement( + self: *Metal, + t: *terminal.Terminal, + top: *const terminal.Pin, + bot: *const terminal.Pin, + image: *const terminal.kitty.graphics.Image, + p: *const terminal.kitty.graphics.ImageStorage.Placement, +) !void { + // Get the rect for the placement. If this placement doesn't have + // a rect then its virtual or something so skip it. + const rect = p.rect(image.*, t) orelse return; + + // If the selection isn't within our viewport then skip it. + if (bot.before(rect.top_left)) return; + if (rect.bottom_right.before(top.*)) return; + + // If the top left is outside the viewport we need to calc an offset + // so that we render (0, 0) with some offset for the texture. + const offset_y: u32 = if (rect.top_left.before(top.*)) offset_y: { + const vp_y = t.screen.pages.pointFromPin(.screen, top.*).?.screen.y; + const img_y = t.screen.pages.pointFromPin(.screen, rect.top_left).?.screen.y; + const offset_cells = vp_y - img_y; + const offset_pixels = offset_cells * self.grid_metrics.cell_height; + break :offset_y @intCast(offset_pixels); + } else 0; + + // We need to prep this image for upload if it isn't in the cache OR + // it is in the cache but the transmit time doesn't match meaning this + // image is different. + const gop = try self.images.getOrPut(self.alloc, image.id); + if (!gop.found_existing or + gop.value_ptr.transmit_time.order(image.transmit_time) != .eq) + { + // Copy the data into the pending state. + const data = try self.alloc.dupe(u8, image.data); + errdefer self.alloc.free(data); + + // Store it in the map + const pending: Image.Pending = .{ + .width = image.width, + .height = image.height, + .data = data.ptr, + }; + + const new_image: Image = switch (image.format) { + .grey_alpha => .{ .pending_grey_alpha = pending }, + .rgb => .{ .pending_rgb = pending }, + .rgba => .{ .pending_rgba = pending }, + .png => unreachable, // should be decoded by now + }; + + if (!gop.found_existing) { + gop.value_ptr.* = .{ + .image = new_image, + .transmit_time = undefined, + }; + } else { + try gop.value_ptr.image.markForReplace( + self.alloc, + new_image, + ); + } + + gop.value_ptr.transmit_time = image.transmit_time; + } + + // Convert our screen point to a viewport point + const viewport: terminal.point.Point = t.screen.pages.pointFromPin( + .viewport, + rect.top_left, + ) orelse .{ .viewport = .{} }; + + // Calculate the source rectangle + const source_x = @min(image.width, p.source_x); + const source_y = @min(image.height, p.source_y + offset_y); + const source_width = if (p.source_width > 0) + @min(image.width - source_x, p.source_width) + else + image.width; + const source_height = if (p.source_height > 0) + @min(image.height, p.source_height) + else + image.height -| source_y; + + // Calculate the width/height of our image. + const dest_width = if (p.columns > 0) p.columns * self.grid_metrics.cell_width else source_width; + const dest_height = if (p.rows > 0) p.rows * self.grid_metrics.cell_height else source_height; + + // Accumulate the placement + if (image.width > 0 and image.height > 0) { + try self.image_placements.append(self.alloc, .{ + .image_id = image.id, + .x = @intCast(rect.top_left.x), + .y = @intCast(viewport.viewport.y), + .z = p.z, + .width = dest_width, + .height = dest_height, + .cell_offset_x = p.x_offset, + .cell_offset_y = p.y_offset, + .source_x = source_x, + .source_y = source_y, + .source_width = source_width, + .source_height = source_height, + }); + } +} + /// Update the configuration. pub fn changeConfig(self: *Metal, config: *DerivedConfig) !void { // We always redo the font shaper in case font features changed. We From 3d4dd5277e7164b9d5d3e4d3a8432af7592ace80 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 25 Jul 2024 22:08:02 -0700 Subject: [PATCH 20/36] renderer/metal: virtual placements are kind of rendering --- src/renderer/Metal.zig | 105 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index c4a22beed..248e5f853 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -1627,6 +1627,17 @@ fn prepKittyGraphics( try self.prepKittyPlacement(t, &top, &bot, &image, p); } + // If we have virtual placements then we need to scan for placeholders. + if (self.image_virtual) { + var v_it = terminal.kitty.graphics.unicode.placementIterator(top, bot); + while (v_it.next()) |virtual_p| try self.prepKittyVirtualPlacement( + t, + &top, + &bot, + &virtual_p, + ); + } + // Sort the placements by their Z value. std.mem.sortUnstable( mtl_image.Placement, @@ -1661,6 +1672,100 @@ fn prepKittyGraphics( } } +fn prepKittyVirtualPlacement( + self: *Metal, + t: *terminal.Terminal, + top: *const terminal.Pin, + bot: *const terminal.Pin, + p: *const terminal.kitty.graphics.unicode.Placement, +) !void { + const storage = &t.screen.kitty_images; + const image = storage.imageById(p.image_id) orelse { + log.warn( + "missing image for virtual placement, ignoring image_id={}", + .{p.image_id}, + ); + return; + }; + + // Get the placement. If an ID is specified we look for the exact one. + // If no ID, then we find the first virtual placement for this image. + const placement = if (p.placement_id > 0) storage.placements.get(.{ + .image_id = p.image_id, + .placement_id = .{ .tag = .external, .id = p.placement_id }, + }) orelse { + log.warn( + "missing placement for virtual placement, ignoring image_id={} placement_id={}", + .{ p.image_id, p.placement_id }, + ); + return; + } else placement: { + var it = storage.placements.iterator(); + while (it.next()) |entry| { + if (entry.key_ptr.image_id == p.image_id and + entry.value_ptr.location == .virtual) + { + break :placement entry.value_ptr.*; + } + } + + log.warn( + "missing placement for virtual placement, ignoring image_id={}", + .{p.image_id}, + ); + return; + }; + + // Calculate our grid size for the placement. If it is isn't explicitly + // provided by the placement we try to calculate it to fit in the + // grid as closely as possible. + const img_grid: renderer.GridSize = grid: { + var rows = placement.rows; + var columns = placement.columns; + + if (rows == 0) { + const cell_height = self.grid_metrics.cell_height; + rows = (image.height + cell_height - 1) / cell_height; + } + + if (columns == 0) { + const cell_width = self.grid_metrics.cell_width; + columns = (image.width + cell_width - 1) / cell_width; + } + + break :grid .{ + .rows = std.math.cast(terminal.size.CellCountInt, rows) orelse { + log.warn( + "placement rows too large for virtual placement, ignoring image_id={}", + .{p.image_id}, + ); + return; + }, + .columns = std.math.cast(terminal.size.CellCountInt, columns) orelse { + log.warn( + "placement columns too large for virtual placement, ignoring image_id={}", + .{p.image_id}, + ); + return; + }, + }; + }; + + // Build our real placement + const real_p: terminal.kitty.graphics.ImageStorage.Placement = .{ + // Note: this constCast is safe because we only ever need a mutable + // pin if we deinit the placement. We never deinit this placement + // because it is a temporary value. + .location = .{ .pin = @constCast(&p.pin) }, + .columns = p.width, + .rows = p.height, + .z = -1, // Render behind cursor + }; + + try self.prepKittyPlacement(t, top, bot, &image, &real_p); + _ = img_grid; +} + fn prepKittyPlacement( self: *Metal, t: *terminal.Terminal, From a5d39103c9e2416f1d5e9cdc9459bdfac62b449a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 26 Jul 2024 12:21:37 -0700 Subject: [PATCH 21/36] renderer/metal: calculate proper grid offsets for image --- src/renderer/Metal.zig | 105 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 93 insertions(+), 12 deletions(-) diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 248e5f853..e359183eb 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -1751,19 +1751,100 @@ fn prepKittyVirtualPlacement( }; }; - // Build our real placement - const real_p: terminal.kitty.graphics.ImageStorage.Placement = .{ - // Note: this constCast is safe because we only ever need a mutable - // pin if we deinit the placement. We never deinit this placement - // because it is a temporary value. - .location = .{ .pin = @constCast(&p.pin) }, - .columns = p.width, - .rows = p.height, - .z = -1, // Render behind cursor - }; + // The image is fit into the placement grid size. We need to calculate + // various offsets in order to center the image vertically/horizontally + // into the grid size while preserving the aspect ratio. + var x_offset: f64 = 0; + var y_offset: f64 = 0; + var x_scale: f64 = 0; + var y_scale: f64 = 0; + const rows_px: f64 = @floatFromInt(img_grid.rows * self.grid_metrics.cell_height); + const cols_px: f64 = @floatFromInt(img_grid.columns * self.grid_metrics.cell_width); + const img_width_f64: f64 = @floatFromInt(image.width); + const img_height_f64: f64 = @floatFromInt(image.height); + if (img_width_f64 * rows_px > img_height_f64 * cols_px) { + // Image is wider than the grid, fit width and center height + x_scale = cols_px / @max(img_width_f64, 1); + y_scale = x_scale; + y_offset = (rows_px - img_height_f64 * y_scale) / 2; + } else { + // Image is taller than the grid, fit height and center width + y_scale = rows_px / @max(img_height_f64, 1); + x_scale = y_scale; + x_offset = (cols_px - img_width_f64 * x_scale) / 2; + } - try self.prepKittyPlacement(t, top, bot, &image, &real_p); - _ = img_grid; + // A bunch of math to map the placement and image to virtual placeholder + // grid. This is ported as closely as possible from Kitty so we get this + // as right as possible. I EXPECT there are some rounding bugs in here + // so compared to Kitty we may be off by 1px here or there. If someone + // can show that to be true let's modify it. + // + // This code is purposely not super Zig-like because I want to keep it + // as close to the Kitty implementation as possible os its easy to + // compare and modify. + var pin: terminal.Pin = p.pin; + var cols: u32 = p.width; + var rows: u32 = p.height; + const x_dst: f64 = @floatFromInt(p.col * self.grid_metrics.cell_width); + const y_dst: f64 = @floatFromInt(p.row * self.grid_metrics.cell_height); + const w_dst: f64 = @floatFromInt(p.width * self.grid_metrics.cell_width); + const h_dst: f64 = @floatFromInt(p.height * self.grid_metrics.cell_height); + var cell_x_off: u32 = 0; + var cell_y_off: u32 = 0; + var src_x: f64 = (x_dst - x_offset) / x_scale; + var src_y: f64 = (y_dst - y_offset) / y_scale; + var src_w: f64 = w_dst / x_scale; + var src_h: f64 = h_dst / y_scale; + if (src_x < 0) { + src_w += src_x; + cell_x_off = @intFromFloat(@round(-src_x * x_scale)); + src_x = 0; + const col_off: u32 = cell_x_off / self.grid_metrics.cell_width; + cell_x_off %= self.grid_metrics.cell_width; + pin = pin.right(col_off); + if (cols <= col_off) return; + cols -= col_off; + } + if (src_y < 0) { + src_h += src_y; + cell_y_off = @intFromFloat(@round(-src_y * y_scale)); + src_y = 0; + const row_off: u32 = cell_y_off / self.grid_metrics.cell_height; + cell_y_off %= self.grid_metrics.cell_height; + pin = pin.down(row_off) orelse return; + if (rows <= row_off) return; + rows -= row_off; + } + + if (src_x + src_w > img_width_f64) { + const redundant_px = src_x + src_w - img_width_f64; + const redundant_cells = @as(u32, @intFromFloat(redundant_px * x_scale)) / self.grid_metrics.cell_width; + if (cols <= redundant_cells) return; + cols -= redundant_cells; + src_w -= @as(f64, @floatFromInt(redundant_cells * self.grid_metrics.cell_width)) / x_scale; + } + if (src_y + src_h > img_height_f64) { + const redundant_px = src_y + src_h - img_height_f64; + const redundant_cells = @as(u32, @intFromFloat(redundant_px * y_scale)) / self.grid_metrics.cell_height; + if (rows <= redundant_cells) return; + rows -= redundant_cells; + src_h -= @as(f64, @floatFromInt(redundant_cells * self.grid_metrics.cell_height)) / y_scale; + } + + // Build our real placement + try self.prepKittyPlacement(t, top, bot, &image, &.{ + .location = .{ .pin = &pin }, + .x_offset = cell_x_off, + .y_offset = cell_y_off, + .source_x = @intFromFloat(@round(src_x)), + .source_y = @intFromFloat(@round(src_y)), + .source_width = @intFromFloat(@round(src_w)), + .source_height = @intFromFloat(@round(src_h)), + .columns = cols, + .rows = rows, + .z = -1, // Render behind cursor + }); } fn prepKittyPlacement( From 6668930b96ff0c0476b84fd641a13b4e1270c31e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 26 Jul 2024 12:24:35 -0700 Subject: [PATCH 22/36] terminal: appendGrapheme should text for codepoint, not text --- src/renderer/Metal.zig | 1 - src/terminal/page.zig | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index e359183eb..7c4e60969 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -1816,7 +1816,6 @@ fn prepKittyVirtualPlacement( if (rows <= row_off) return; rows -= row_off; } - if (src_x + src_w > img_width_f64) { const redundant_px = src_x + src_w - img_width_f64; const redundant_cells = @as(u32, @intFromFloat(redundant_px * x_scale)) / self.grid_metrics.cell_width; diff --git a/src/terminal/page.zig b/src/terminal/page.zig index 9d149270e..c270a0e0d 100644 --- a/src/terminal/page.zig +++ b/src/terminal/page.zig @@ -1152,7 +1152,7 @@ pub const Page = struct { pub fn setGraphemes(self: *Page, row: *Row, cell: *Cell, cps: []u21) Allocator.Error!void { defer self.assertIntegrity(); - assert(cell.hasText()); + assert(cell.codepoint() > 0); assert(cell.content_tag == .codepoint); const cell_offset = getOffset(Cell, self.memory, cell); From 4bf8d30b4430ab6e3b4065b0cd698390be9d47a2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 29 Jul 2024 09:35:51 -0700 Subject: [PATCH 23/36] renderer/metal: rewrite kitty placeholder handling --- src/renderer/Metal.zig | 260 ++++++++++++++++++++++++----------------- 1 file changed, 151 insertions(+), 109 deletions(-) diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 7c4e60969..e5d1a5a77 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -1679,6 +1679,9 @@ fn prepKittyVirtualPlacement( bot: *const terminal.Pin, p: *const terminal.kitty.graphics.unicode.Placement, ) !void { + _ = top; + _ = bot; + const storage = &t.screen.kitty_images; const image = storage.imageById(p.image_id) orelse { log.warn( @@ -1716,18 +1719,23 @@ fn prepKittyVirtualPlacement( return; }; - // Calculate our grid size for the placement. If it is isn't explicitly - // provided by the placement we try to calculate it to fit in the - // grid as closely as possible. + // Calculate the grid size for the placement. For virtual placements, + // we use the requested row/cols. If either isn't specified, we choose + // the best size based on the image size to fit the entire image in its + // original size. + // + // This part of the code does NOT do preserve any aspect ratios. Its + // dumbly fitting the image into the grid size -- possibly user specified. const img_grid: renderer.GridSize = grid: { + // Use requested rows/columns if specified var rows = placement.rows; var columns = placement.columns; + // For unspecified rows/columns, calculate based on the image size. if (rows == 0) { const cell_height = self.grid_metrics.cell_height; rows = (image.height + cell_height - 1) / cell_height; } - if (columns == 0) { const cell_width = self.grid_metrics.cell_width; columns = (image.width + cell_width - 1) / cell_width; @@ -1751,11 +1759,17 @@ fn prepKittyVirtualPlacement( }; }; - // The image is fit into the placement grid size. We need to calculate - // various offsets in order to center the image vertically/horizontally - // into the grid size while preserving the aspect ratio. + // Next we have to fit the source image into the grid size while preserving + // aspect ratio. We will center the image horizontally/vertically if + // necessary. + + // The offsets are the pixel offsets from the top-left of the top-left + // grid cell in order to center the image as best as possible. var x_offset: f64 = 0; var y_offset: f64 = 0; + + // The scale factors are the scaling factors applied to the original + // image size in order to fit it into our placement grid size. var x_scale: f64 = 0; var y_scale: f64 = 0; const rows_px: f64 = @floatFromInt(img_grid.rows * self.grid_metrics.cell_height); @@ -1773,76 +1787,93 @@ fn prepKittyVirtualPlacement( x_scale = y_scale; x_offset = (cols_px - img_width_f64 * x_scale) / 2; } + log.warn("x_offset={}, y_offset={}, x_scale={}, y_scale={}", .{ + x_offset, y_offset, x_scale, y_scale, + }); - // A bunch of math to map the placement and image to virtual placeholder - // grid. This is ported as closely as possible from Kitty so we get this - // as right as possible. I EXPECT there are some rounding bugs in here - // so compared to Kitty we may be off by 1px here or there. If someone - // can show that to be true let's modify it. + // At this point, we have the following information: + // - image.width/height - The original image width and height. + // - img_grid.rows/columns - The requested grid size for the placement. + // - offset/scale - The offset and scale to fit the image into the + // placement grid. // - // This code is purposely not super Zig-like because I want to keep it - // as close to the Kitty implementation as possible os its easy to - // compare and modify. - var pin: terminal.Pin = p.pin; - var cols: u32 = p.width; - var rows: u32 = p.height; - const x_dst: f64 = @floatFromInt(p.col * self.grid_metrics.cell_width); - const y_dst: f64 = @floatFromInt(p.row * self.grid_metrics.cell_height); - const w_dst: f64 = @floatFromInt(p.width * self.grid_metrics.cell_width); - const h_dst: f64 = @floatFromInt(p.height * self.grid_metrics.cell_height); - var cell_x_off: u32 = 0; - var cell_y_off: u32 = 0; - var src_x: f64 = (x_dst - x_offset) / x_scale; - var src_y: f64 = (y_dst - y_offset) / y_scale; - var src_w: f64 = w_dst / x_scale; - var src_h: f64 = h_dst / y_scale; - if (src_x < 0) { - src_w += src_x; - cell_x_off = @intFromFloat(@round(-src_x * x_scale)); - src_x = 0; - const col_off: u32 = cell_x_off / self.grid_metrics.cell_width; - cell_x_off %= self.grid_metrics.cell_width; - pin = pin.right(col_off); - if (cols <= col_off) return; - cols -= col_off; - } - if (src_y < 0) { - src_h += src_y; - cell_y_off = @intFromFloat(@round(-src_y * y_scale)); - src_y = 0; - const row_off: u32 = cell_y_off / self.grid_metrics.cell_height; - cell_y_off %= self.grid_metrics.cell_height; - pin = pin.down(row_off) orelse return; - if (rows <= row_off) return; - rows -= row_off; - } - if (src_x + src_w > img_width_f64) { - const redundant_px = src_x + src_w - img_width_f64; - const redundant_cells = @as(u32, @intFromFloat(redundant_px * x_scale)) / self.grid_metrics.cell_width; - if (cols <= redundant_cells) return; - cols -= redundant_cells; - src_w -= @as(f64, @floatFromInt(redundant_cells * self.grid_metrics.cell_width)) / x_scale; - } - if (src_y + src_h > img_height_f64) { - const redundant_px = src_y + src_h - img_height_f64; - const redundant_cells = @as(u32, @intFromFloat(redundant_px * y_scale)) / self.grid_metrics.cell_height; - if (rows <= redundant_cells) return; - rows -= redundant_cells; - src_h -= @as(f64, @floatFromInt(redundant_cells * self.grid_metrics.cell_height)) / y_scale; + // For our run requested coordinates and size we now need to map + // the original image down into our grid cells honoring the offsets + // calculated for the best fit. + + const img_x_offset: f64 = x_offset / x_scale; + const img_y_offset: f64 = y_offset / y_scale; + const img_width_padded: f64 = img_width_f64 + (img_x_offset * 2); + const img_height_padded: f64 = img_height_f64 + (img_y_offset * 2); + log.warn("padded_width={}, padded_height={} original_width={}, original_height={}", .{ + img_width_padded, img_height_padded, img_width_f64, img_height_f64, + }); + + const source_width_f64: f64 = img_width_padded * (@as(f64, @floatFromInt(p.width)) / @as(f64, @floatFromInt(img_grid.columns))); + var source_height_f64: f64 = img_height_padded * (@as(f64, @floatFromInt(p.height)) / @as(f64, @floatFromInt(img_grid.rows))); + const source_x_f64: f64 = img_width_padded * (@as(f64, @floatFromInt(p.col)) / @as(f64, @floatFromInt(img_grid.columns))); + var source_y_f64: f64 = img_height_padded * (@as(f64, @floatFromInt(p.row)) / @as(f64, @floatFromInt(img_grid.rows))); + + const p_x_offset_f64: f64 = 0; + var p_y_offset_f64: f64 = 0; + const dst_width_f64: f64 = @floatFromInt(p.width * self.grid_metrics.cell_width); + var dst_height_f64: f64 = @floatFromInt(p.height * self.grid_metrics.cell_height); + + // If our y is in our top offset area, we need to adjust the source to + // be shorter, and offset it into the cell. + if (source_y_f64 < img_y_offset) { + const offset: f64 = img_y_offset - source_y_f64; + source_height_f64 -= offset; + p_y_offset_f64 = offset; + dst_height_f64 -= offset * y_scale; + source_y_f64 = 0; } - // Build our real placement - try self.prepKittyPlacement(t, top, bot, &image, &.{ - .location = .{ .pin = &pin }, - .x_offset = cell_x_off, - .y_offset = cell_y_off, - .source_x = @intFromFloat(@round(src_x)), - .source_y = @intFromFloat(@round(src_y)), - .source_width = @intFromFloat(@round(src_w)), - .source_height = @intFromFloat(@round(src_h)), - .columns = cols, - .rows = rows, - .z = -1, // Render behind cursor + // if our y is in our bottom offset area, we need to shorten the + // source to fit in the cell. + if (source_y_f64 + source_height_f64 > img_height_padded - img_y_offset) { + source_y_f64 -= img_y_offset; + source_height_f64 = img_height_padded - img_y_offset - source_y_f64; + source_height_f64 -= img_y_offset; + dst_height_f64 = source_height_f64 * y_scale; + } + + const source_width: u32 = @intFromFloat(@round(source_width_f64)); + const source_height: u32 = @intFromFloat(@round(source_height_f64)); + const source_x: u32 = @intFromFloat(@round(source_x_f64)); + const source_y: u32 = @intFromFloat(@round(source_y_f64)); + const p_x_offset: u32 = @intFromFloat(@round(p_x_offset_f64 * x_scale)); + const p_y_offset: u32 = @intFromFloat(@round(p_y_offset_f64 * y_scale)); + const dest_width: u32 = @intFromFloat(@round(dst_width_f64)); + const dest_height: u32 = @intFromFloat(@round(dst_height_f64)); + + log.warn("source_x={}, source_y={}, source_width={}, source_height={}", .{ + source_x, source_y, source_width, source_height, + }); + log.warn("p_x_offset={}, p_y_offset={}", .{ p_x_offset, p_y_offset }); + log.warn("dest_width={}, dest_height={}", .{ dest_width, dest_height }); + + // Send our image to the GPU + try self.prepKittyImage(&image); + + const viewport: terminal.point.Point = t.screen.pages.pointFromPin( + .viewport, + p.pin, + ) orelse @panic("TODO: unreachable?"); + + try self.image_placements.append(self.alloc, .{ + .image_id = image.id, + .x = @intCast(p.pin.x), + .y = @intCast(viewport.viewport.y), + .z = -1, + .width = dest_width, + .height = dest_height, + .cell_offset_x = p_x_offset, + .cell_offset_y = p_y_offset, + .source_x = source_x, + .source_y = source_y, + .source_width = source_width, + .source_height = source_height, }); } @@ -1875,42 +1906,7 @@ fn prepKittyPlacement( // We need to prep this image for upload if it isn't in the cache OR // it is in the cache but the transmit time doesn't match meaning this // image is different. - const gop = try self.images.getOrPut(self.alloc, image.id); - if (!gop.found_existing or - gop.value_ptr.transmit_time.order(image.transmit_time) != .eq) - { - // Copy the data into the pending state. - const data = try self.alloc.dupe(u8, image.data); - errdefer self.alloc.free(data); - - // Store it in the map - const pending: Image.Pending = .{ - .width = image.width, - .height = image.height, - .data = data.ptr, - }; - - const new_image: Image = switch (image.format) { - .grey_alpha => .{ .pending_grey_alpha = pending }, - .rgb => .{ .pending_rgb = pending }, - .rgba => .{ .pending_rgba = pending }, - .png => unreachable, // should be decoded by now - }; - - if (!gop.found_existing) { - gop.value_ptr.* = .{ - .image = new_image, - .transmit_time = undefined, - }; - } else { - try gop.value_ptr.image.markForReplace( - self.alloc, - new_image, - ); - } - - gop.value_ptr.transmit_time = image.transmit_time; - } + try self.prepKittyImage(image); // Convert our screen point to a viewport point const viewport: terminal.point.Point = t.screen.pages.pointFromPin( @@ -1953,6 +1949,52 @@ fn prepKittyPlacement( } } +fn prepKittyImage( + self: *Metal, + image: *const terminal.kitty.graphics.Image, +) !void { + // If this image exists and its transmit time is the same we assume + // it is the identical image so we don't need to send it to the GPU. + const gop = try self.images.getOrPut(self.alloc, image.id); + if (gop.found_existing and + gop.value_ptr.transmit_time.order(image.transmit_time) == .eq) + { + return; + } + + // Copy the data into the pending state. + const data = try self.alloc.dupe(u8, image.data); + errdefer self.alloc.free(data); + + // Store it in the map + const pending: Image.Pending = .{ + .width = image.width, + .height = image.height, + .data = data.ptr, + }; + + const new_image: Image = switch (image.format) { + .grey_alpha => .{ .pending_grey_alpha = pending }, + .rgb => .{ .pending_rgb = pending }, + .rgba => .{ .pending_rgba = pending }, + .png => unreachable, // should be decoded by now + }; + + if (!gop.found_existing) { + gop.value_ptr.* = .{ + .image = new_image, + .transmit_time = undefined, + }; + } else { + try gop.value_ptr.image.markForReplace( + self.alloc, + new_image, + ); + } + + gop.value_ptr.transmit_time = image.transmit_time; +} + /// Update the configuration. pub fn changeConfig(self: *Metal, config: *DerivedConfig) !void { // We always redo the font shaper in case font features changed. We From 0ebf14fd44a829c7edd7791cb92b3c029b0df127 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 29 Jul 2024 10:39:18 -0700 Subject: [PATCH 24/36] terminal/kitty: working on moving placement math here for testing --- src/renderer/Metal.zig | 44 ++++- src/terminal/kitty/graphics.zig | 2 + src/terminal/kitty/graphics_unicode.zig | 227 ++++++++++++++++++++++++ 3 files changed, 266 insertions(+), 7 deletions(-) diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index e5d1a5a77..e22faaf4c 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -1632,8 +1632,6 @@ fn prepKittyGraphics( var v_it = terminal.kitty.graphics.unicode.placementIterator(top, bot); while (v_it.next()) |virtual_p| try self.prepKittyVirtualPlacement( t, - &top, - &bot, &virtual_p, ); } @@ -1675,13 +1673,8 @@ fn prepKittyGraphics( fn prepKittyVirtualPlacement( self: *Metal, t: *terminal.Terminal, - top: *const terminal.Pin, - bot: *const terminal.Pin, p: *const terminal.kitty.graphics.unicode.Placement, ) !void { - _ = top; - _ = bot; - const storage = &t.screen.kitty_images; const image = storage.imageById(p.image_id) orelse { log.warn( @@ -1691,6 +1684,43 @@ fn prepKittyVirtualPlacement( return; }; + if (true) { + const rp = p.renderPlacement( + &t.screen.kitty_images, + &image, + self.grid_metrics.cell_width, + self.grid_metrics.cell_height, + ) catch |err| { + log.warn("error rendering virtual placement err={}", .{err}); + return; + }; + + // Send our image to the GPU + try self.prepKittyImage(&image); + + const viewport: terminal.point.Point = t.screen.pages.pointFromPin( + .viewport, + rp.top_left, + ) orelse @panic("TODO: unreachable?"); + + try self.image_placements.append(self.alloc, .{ + .image_id = image.id, + .x = @intCast(rp.top_left.x), + .y = @intCast(viewport.viewport.y), + .z = -1, + .width = rp.dest_width, + .height = rp.dest_height, + .cell_offset_x = rp.offset_x, + .cell_offset_y = rp.offset_y, + .source_x = rp.source_x, + .source_y = rp.source_y, + .source_width = rp.source_width, + .source_height = rp.source_height, + }); + + return; + } + // Get the placement. If an ID is specified we look for the exact one. // If no ID, then we find the first virtual placement for this image. const placement = if (p.placement_id > 0) storage.placements.get(.{ diff --git a/src/terminal/kitty/graphics.zig b/src/terminal/kitty/graphics.zig index 22d102f53..0beb4901e 100644 --- a/src/terminal/kitty/graphics.zig +++ b/src/terminal/kitty/graphics.zig @@ -16,11 +16,13 @@ //! aim to ship a v1 of this implementation came at some cost. I learned a lot //! though and I think we can go back through and fix this up. +const render = @import("graphics_render.zig"); pub usingnamespace @import("graphics_command.zig"); pub usingnamespace @import("graphics_exec.zig"); pub usingnamespace @import("graphics_image.zig"); pub usingnamespace @import("graphics_storage.zig"); pub const unicode = @import("graphics_unicode.zig"); +pub const RenderPlacement = render.Placement; test { @import("std").testing.refAllDecls(@This()); diff --git a/src/terminal/kitty/graphics_unicode.zig b/src/terminal/kitty/graphics_unicode.zig index a908ac200..23371d180 100644 --- a/src/terminal/kitty/graphics_unicode.zig +++ b/src/terminal/kitty/graphics_unicode.zig @@ -5,6 +5,8 @@ const std = @import("std"); const assert = std.debug.assert; const testing = std.testing; const terminal = @import("../main.zig"); +const kitty_gfx = terminal.kitty.graphics; +const RenderPlacement = kitty_gfx.RenderPlacement; const log = std.log.scoped(.kitty_gfx); @@ -117,6 +119,231 @@ pub const Placement = struct { /// The width/height in cells of this placement. width: u32, height: u32, + + pub const Error = error{ + PlacementGridOutOfBounds, + PlacementMissingPlacement, + }; + + /// Take this virtual placement and convert it to a render placement. + pub fn renderPlacement( + self: *const Placement, + storage: *const kitty_gfx.ImageStorage, + img: *const kitty_gfx.Image, + cell_width: u32, + cell_height: u32, + ) Error!RenderPlacement { + // In this function, there is a variable naming convention to try + // to make it slightly less confusing. The prefix will tell you what + // coordinate/size space a variable lives in: + // - img_* is for the original image + // - p_* is for the final placement + // - vp_* is for the virtual placement + + // Determine the grid size that this virtual placement fits into. + const p_grid = try self.grid(storage, img, cell_width, cell_height); + + // From here on out we switch to floating point math. These are + // constants that we'll reference repeatedly. + const img_width_f64: f64 = @floatFromInt(img.width); + const img_height_f64: f64 = @floatFromInt(img.height); + + // Next we have to fit the source image into the grid size while preserving + // aspect ratio. We will center the image horizontally/vertically if + // necessary. + const p_scale: struct { + /// The offsets are pixels from the top-left of the placement-sized + /// image in order to center the image as necessary. + x_offset: f64 = 0, + y_offset: f64 = 0, + + /// The multipliers to apply to the width/height of the original + /// image size in order to reach the placement size. + x_scale: f64 = 0, + y_scale: f64 = 0, + } = scale: { + const p_rows_px: f64 = @floatFromInt(p_grid.rows * cell_height); + const p_cols_px: f64 = @floatFromInt(p_grid.columns * cell_width); + if (img_width_f64 * p_rows_px > img_height_f64 * p_cols_px) { + // Image is wider than the grid, fit width and center height + const x_scale = p_cols_px / @max(img_width_f64, 1); + const y_scale = x_scale; + const y_offset = (p_rows_px - img_height_f64 * y_scale) / 2; + break :scale .{ + .x_scale = x_scale, + .y_scale = y_scale, + .y_offset = y_offset, + }; + } else { + // Image is taller than the grid, fit height and center width + const y_scale = p_rows_px / @max(img_height_f64, 1); + const x_scale = y_scale; + const x_offset = (p_cols_px - img_width_f64 * x_scale) / 2; + break :scale .{ + .x_scale = x_scale, + .y_scale = y_scale, + .x_offset = x_offset, + }; + } + }; + + // Scale our original image according to the aspect ratio + // and padding calculated for p_scale. + const img_scaled: struct { + x_offset: f64, + y_offset: f64, + width: f64, + height: f64, + } = scale: { + const x_offset: f64 = p_scale.x_offset / p_scale.x_scale; + const y_offset: f64 = p_scale.y_offset / p_scale.y_scale; + const width: f64 = img_width_f64 + (x_offset * 2); + const height: f64 = img_height_f64 + (y_offset * 2); + break :scale .{ + .x_offset = x_offset, + .y_offset = y_offset, + .width = width, + .height = height, + }; + }; + + // Calculate the source rectangle for the scaled image. These + // coordinates are in the scaled image space. + var img_scale_source: struct { + x: f64, + y: f64, + width: f64, + height: f64, + } = source: { + // Float-converted values we already have + const vp_width: f64 = @floatFromInt(self.width); + const vp_height: f64 = @floatFromInt(self.height); + const vp_col: f64 = @floatFromInt(self.col); + const vp_row: f64 = @floatFromInt(self.row); + const p_grid_cols: f64 = @floatFromInt(p_grid.columns); + const p_grid_rows: f64 = @floatFromInt(p_grid.rows); + + // Calculate the scaled source rectangle for the image, undoing + // the aspect ratio scaling as necessary. + const width: f64 = img_scaled.width * (vp_width / p_grid_cols); + const height: f64 = img_scaled.height * (vp_height / p_grid_rows); + const x: f64 = img_scaled.width * (vp_col / p_grid_cols); + const y: f64 = img_scaled.height * (vp_row / p_grid_rows); + + break :source .{ + .width = width, + .height = height, + .x = x, + .y = y, + }; + }; + + // The destination rectangle. The x/y is specified by offsets from + // the top-left since that's how our RenderPlacement works. + const p_dest: struct { + x_offset: f64, + y_offset: f64, + width: f64, + height: f64, + } = dest: { + const x_offset: f64 = 0; + var y_offset: f64 = 0; + const width: f64 = @floatFromInt(self.width * cell_width); + var height: f64 = @floatFromInt(self.height * cell_height); + + if (img_scale_source.y < img_scaled.y_offset) { + // If our source rect y is within the offset area, we need to + // adjust our source rect and destination since the source texture + // doesnt actually have the offset area blank. + const offset: f64 = img_scaled.y_offset - img_scale_source.y; + img_scale_source.height -= offset; + y_offset = offset; + height -= offset * p_scale.y_scale; + img_scale_source.y = 0; + } + + if (img_scale_source.y + img_scale_source.height > + img_scaled.height - img_scaled.y_offset) + { + // if our y is in our bottom offset area, we need to shorten the + // source to fit in the cell. + img_scale_source.y -= img_scaled.y_offset; + img_scale_source.height = img_scaled.height - img_scaled.y_offset - img_scale_source.y; + img_scale_source.height -= img_scaled.y_offset; + height = img_scale_source.height * p_scale.y_scale; + } + + break :dest .{ + .x_offset = x_offset, + .y_offset = y_offset, + .width = width, + .height = height, + }; + }; + + return .{ + .top_left = self.pin, + .offset_x = @intFromFloat(@round(p_dest.x_offset)), + .offset_y = @intFromFloat(@round(p_dest.y_offset)), + .source_x = @intFromFloat(@round(img_scale_source.x)), + .source_y = @intFromFloat(@round(img_scale_source.y)), + .source_width = @intFromFloat(@round(img_scale_source.width)), + .source_height = @intFromFloat(@round(img_scale_source.height)), + .dest_width = @intFromFloat(@round(p_dest.width)), + .dest_height = @intFromFloat(@round(p_dest.height)), + }; + } + + // Calculate the grid size for the placement. For virtual placements, + // we use the requested row/cols. If either isn't specified, we choose + // the best size based on the image size to fit the entire image in its + // original size. + // + // This part of the code does NOT do preserve any aspect ratios. Its + // dumbly fitting the image into the grid size -- possibly user specified. + fn grid( + self: *const Placement, + storage: *const kitty_gfx.ImageStorage, + image: *const kitty_gfx.Image, + cell_width: u32, + cell_height: u32, + ) !struct { + rows: u32, + columns: u32, + } { + // Get the placement. If an ID is specified we look for the exact one. + // If no ID, then we find the first virtual placement for this image. + const placement = if (self.placement_id > 0) storage.placements.get(.{ + .image_id = self.image_id, + .placement_id = .{ .tag = .external, .id = self.placement_id }, + }) orelse { + return Error.PlacementMissingPlacement; + } else placement: { + var it = storage.placements.iterator(); + while (it.next()) |entry| { + if (entry.key_ptr.image_id == self.image_id and + entry.value_ptr.location == .virtual) + { + break :placement entry.value_ptr.*; + } + } + + return Error.PlacementMissingPlacement; + }; + + // Use requested rows/columns if specified + // For unspecified rows/columns, calculate based on the image size. + var rows = placement.rows; + var columns = placement.columns; + if (rows == 0) rows = (image.height + cell_height - 1) / cell_height; + if (columns == 0) columns = (image.width + cell_width - 1) / cell_width; + return .{ + .rows = std.math.cast(terminal.size.CellCountInt, rows) orelse + return Error.PlacementGridOutOfBounds, + .columns = std.math.cast(terminal.size.CellCountInt, columns) orelse + return Error.PlacementGridOutOfBounds, + }; + } }; /// IncompletePlacement is the placement information present in a single From 359458b96a208683b64806710dd34d82e1ab7408 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 29 Jul 2024 10:52:10 -0700 Subject: [PATCH 25/36] terminal/kitty: switch to new placement math --- src/renderer/Metal.zig | 223 ++---------------------- src/terminal/kitty/graphics_render.zig | 28 +++ src/terminal/kitty/graphics_unicode.zig | 11 +- 3 files changed, 54 insertions(+), 208 deletions(-) create mode 100644 src/terminal/kitty/graphics_render.zig diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index e22faaf4c..06d5346b0 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -1684,226 +1684,37 @@ fn prepKittyVirtualPlacement( return; }; - if (true) { - const rp = p.renderPlacement( - &t.screen.kitty_images, - &image, - self.grid_metrics.cell_width, - self.grid_metrics.cell_height, - ) catch |err| { - log.warn("error rendering virtual placement err={}", .{err}); - return; - }; - - // Send our image to the GPU - try self.prepKittyImage(&image); - - const viewport: terminal.point.Point = t.screen.pages.pointFromPin( - .viewport, - rp.top_left, - ) orelse @panic("TODO: unreachable?"); - - try self.image_placements.append(self.alloc, .{ - .image_id = image.id, - .x = @intCast(rp.top_left.x), - .y = @intCast(viewport.viewport.y), - .z = -1, - .width = rp.dest_width, - .height = rp.dest_height, - .cell_offset_x = rp.offset_x, - .cell_offset_y = rp.offset_y, - .source_x = rp.source_x, - .source_y = rp.source_y, - .source_width = rp.source_width, - .source_height = rp.source_height, - }); - - return; - } - - // Get the placement. If an ID is specified we look for the exact one. - // If no ID, then we find the first virtual placement for this image. - const placement = if (p.placement_id > 0) storage.placements.get(.{ - .image_id = p.image_id, - .placement_id = .{ .tag = .external, .id = p.placement_id }, - }) orelse { - log.warn( - "missing placement for virtual placement, ignoring image_id={} placement_id={}", - .{ p.image_id, p.placement_id }, - ); - return; - } else placement: { - var it = storage.placements.iterator(); - while (it.next()) |entry| { - if (entry.key_ptr.image_id == p.image_id and - entry.value_ptr.location == .virtual) - { - break :placement entry.value_ptr.*; - } - } - - log.warn( - "missing placement for virtual placement, ignoring image_id={}", - .{p.image_id}, - ); + const rp = p.renderPlacement( + &t.screen.kitty_images, + &image, + self.grid_metrics.cell_width, + self.grid_metrics.cell_height, + ) catch |err| { + log.warn("error rendering virtual placement err={}", .{err}); return; }; - // Calculate the grid size for the placement. For virtual placements, - // we use the requested row/cols. If either isn't specified, we choose - // the best size based on the image size to fit the entire image in its - // original size. - // - // This part of the code does NOT do preserve any aspect ratios. Its - // dumbly fitting the image into the grid size -- possibly user specified. - const img_grid: renderer.GridSize = grid: { - // Use requested rows/columns if specified - var rows = placement.rows; - var columns = placement.columns; - - // For unspecified rows/columns, calculate based on the image size. - if (rows == 0) { - const cell_height = self.grid_metrics.cell_height; - rows = (image.height + cell_height - 1) / cell_height; - } - if (columns == 0) { - const cell_width = self.grid_metrics.cell_width; - columns = (image.width + cell_width - 1) / cell_width; - } - - break :grid .{ - .rows = std.math.cast(terminal.size.CellCountInt, rows) orelse { - log.warn( - "placement rows too large for virtual placement, ignoring image_id={}", - .{p.image_id}, - ); - return; - }, - .columns = std.math.cast(terminal.size.CellCountInt, columns) orelse { - log.warn( - "placement columns too large for virtual placement, ignoring image_id={}", - .{p.image_id}, - ); - return; - }, - }; - }; - - // Next we have to fit the source image into the grid size while preserving - // aspect ratio. We will center the image horizontally/vertically if - // necessary. - - // The offsets are the pixel offsets from the top-left of the top-left - // grid cell in order to center the image as best as possible. - var x_offset: f64 = 0; - var y_offset: f64 = 0; - - // The scale factors are the scaling factors applied to the original - // image size in order to fit it into our placement grid size. - var x_scale: f64 = 0; - var y_scale: f64 = 0; - const rows_px: f64 = @floatFromInt(img_grid.rows * self.grid_metrics.cell_height); - const cols_px: f64 = @floatFromInt(img_grid.columns * self.grid_metrics.cell_width); - const img_width_f64: f64 = @floatFromInt(image.width); - const img_height_f64: f64 = @floatFromInt(image.height); - if (img_width_f64 * rows_px > img_height_f64 * cols_px) { - // Image is wider than the grid, fit width and center height - x_scale = cols_px / @max(img_width_f64, 1); - y_scale = x_scale; - y_offset = (rows_px - img_height_f64 * y_scale) / 2; - } else { - // Image is taller than the grid, fit height and center width - y_scale = rows_px / @max(img_height_f64, 1); - x_scale = y_scale; - x_offset = (cols_px - img_width_f64 * x_scale) / 2; - } - log.warn("x_offset={}, y_offset={}, x_scale={}, y_scale={}", .{ - x_offset, y_offset, x_scale, y_scale, - }); - - // At this point, we have the following information: - // - image.width/height - The original image width and height. - // - img_grid.rows/columns - The requested grid size for the placement. - // - offset/scale - The offset and scale to fit the image into the - // placement grid. - // - // For our run requested coordinates and size we now need to map - // the original image down into our grid cells honoring the offsets - // calculated for the best fit. - - const img_x_offset: f64 = x_offset / x_scale; - const img_y_offset: f64 = y_offset / y_scale; - const img_width_padded: f64 = img_width_f64 + (img_x_offset * 2); - const img_height_padded: f64 = img_height_f64 + (img_y_offset * 2); - log.warn("padded_width={}, padded_height={} original_width={}, original_height={}", .{ - img_width_padded, img_height_padded, img_width_f64, img_height_f64, - }); - - const source_width_f64: f64 = img_width_padded * (@as(f64, @floatFromInt(p.width)) / @as(f64, @floatFromInt(img_grid.columns))); - var source_height_f64: f64 = img_height_padded * (@as(f64, @floatFromInt(p.height)) / @as(f64, @floatFromInt(img_grid.rows))); - const source_x_f64: f64 = img_width_padded * (@as(f64, @floatFromInt(p.col)) / @as(f64, @floatFromInt(img_grid.columns))); - var source_y_f64: f64 = img_height_padded * (@as(f64, @floatFromInt(p.row)) / @as(f64, @floatFromInt(img_grid.rows))); - - const p_x_offset_f64: f64 = 0; - var p_y_offset_f64: f64 = 0; - const dst_width_f64: f64 = @floatFromInt(p.width * self.grid_metrics.cell_width); - var dst_height_f64: f64 = @floatFromInt(p.height * self.grid_metrics.cell_height); - - // If our y is in our top offset area, we need to adjust the source to - // be shorter, and offset it into the cell. - if (source_y_f64 < img_y_offset) { - const offset: f64 = img_y_offset - source_y_f64; - source_height_f64 -= offset; - p_y_offset_f64 = offset; - dst_height_f64 -= offset * y_scale; - source_y_f64 = 0; - } - - // if our y is in our bottom offset area, we need to shorten the - // source to fit in the cell. - if (source_y_f64 + source_height_f64 > img_height_padded - img_y_offset) { - source_y_f64 -= img_y_offset; - source_height_f64 = img_height_padded - img_y_offset - source_y_f64; - source_height_f64 -= img_y_offset; - dst_height_f64 = source_height_f64 * y_scale; - } - - const source_width: u32 = @intFromFloat(@round(source_width_f64)); - const source_height: u32 = @intFromFloat(@round(source_height_f64)); - const source_x: u32 = @intFromFloat(@round(source_x_f64)); - const source_y: u32 = @intFromFloat(@round(source_y_f64)); - const p_x_offset: u32 = @intFromFloat(@round(p_x_offset_f64 * x_scale)); - const p_y_offset: u32 = @intFromFloat(@round(p_y_offset_f64 * y_scale)); - const dest_width: u32 = @intFromFloat(@round(dst_width_f64)); - const dest_height: u32 = @intFromFloat(@round(dst_height_f64)); - - log.warn("source_x={}, source_y={}, source_width={}, source_height={}", .{ - source_x, source_y, source_width, source_height, - }); - log.warn("p_x_offset={}, p_y_offset={}", .{ p_x_offset, p_y_offset }); - log.warn("dest_width={}, dest_height={}", .{ dest_width, dest_height }); - // Send our image to the GPU try self.prepKittyImage(&image); const viewport: terminal.point.Point = t.screen.pages.pointFromPin( .viewport, - p.pin, + rp.top_left, ) orelse @panic("TODO: unreachable?"); try self.image_placements.append(self.alloc, .{ .image_id = image.id, - .x = @intCast(p.pin.x), + .x = @intCast(rp.top_left.x), .y = @intCast(viewport.viewport.y), .z = -1, - .width = dest_width, - .height = dest_height, - .cell_offset_x = p_x_offset, - .cell_offset_y = p_y_offset, - .source_x = source_x, - .source_y = source_y, - .source_width = source_width, - .source_height = source_height, + .width = rp.dest_width, + .height = rp.dest_height, + .cell_offset_x = rp.offset_x, + .cell_offset_y = rp.offset_y, + .source_x = rp.source_x, + .source_y = rp.source_y, + .source_width = rp.source_width, + .source_height = rp.source_height, }); } diff --git a/src/terminal/kitty/graphics_render.zig b/src/terminal/kitty/graphics_render.zig new file mode 100644 index 000000000..af888582f --- /dev/null +++ b/src/terminal/kitty/graphics_render.zig @@ -0,0 +1,28 @@ +const std = @import("std"); +const assert = std.debug.assert; +const testing = std.testing; +const terminal = @import("../main.zig"); + +/// A render placement is a way to position a Kitty graphics image onto +/// the screen. It is broken down into the fields that make it easier to +/// position the image using a renderer. +pub const Placement = struct { + /// The top-left corner of the image in grid coordinates. + top_left: terminal.Pin, + + /// The offset in pixels from the top-left corner of the grid cell. + offset_x: u32 = 0, + offset_y: u32 = 0, + + /// The source rectangle of the image to render. This doesn't have to + /// match the size the destination size and the renderer is expected + /// to scale the image to fit the destination size. + source_x: u32 = 0, + source_y: u32 = 0, + source_width: u32 = 0, + source_height: u32 = 0, + + /// The final width/height of the image in pixels. + dest_width: u32 = 0, + dest_height: u32 = 0, +}; diff --git a/src/terminal/kitty/graphics_unicode.zig b/src/terminal/kitty/graphics_unicode.zig index 23371d180..b6ed5b55a 100644 --- a/src/terminal/kitty/graphics_unicode.zig +++ b/src/terminal/kitty/graphics_unicode.zig @@ -274,12 +274,19 @@ pub const Placement = struct { } break :dest .{ - .x_offset = x_offset, - .y_offset = y_offset, + .x_offset = x_offset * p_scale.x_scale, + .y_offset = y_offset * p_scale.y_scale, .width = width, .height = height, }; }; + // log.warn("p_grid={} p_scale={} img_scaled={} img_scale_source={} p_dest={}", .{ + // p_grid, + // p_scale, + // img_scaled, + // img_scale_source, + // p_dest, + // }); return .{ .top_left = self.pin, From 079420730a57763f9dd0c32fb1e2ca79fc15dd52 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 29 Jul 2024 10:55:50 -0700 Subject: [PATCH 26/36] renderer/metal: address some todos --- src/renderer/Metal.zig | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 06d5346b0..47112384a 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -1685,7 +1685,7 @@ fn prepKittyVirtualPlacement( }; const rp = p.renderPlacement( - &t.screen.kitty_images, + storage, &image, self.grid_metrics.cell_width, self.grid_metrics.cell_height, @@ -1694,14 +1694,19 @@ fn prepKittyVirtualPlacement( return; }; - // Send our image to the GPU - try self.prepKittyImage(&image); - const viewport: terminal.point.Point = t.screen.pages.pointFromPin( .viewport, rp.top_left, - ) orelse @panic("TODO: unreachable?"); + ) orelse { + // This is unreachable with virtual placements because we should + // only ever be looking at virtual placements that are in our + // viewport in the renderer and virtual placements only ever take + // up one row. + unreachable; + }; + // Send our image to the GPU and store the placement for rendering. + try self.prepKittyImage(&image); try self.image_placements.append(self.alloc, .{ .image_id = image.id, .x = @intCast(rp.top_left.x), From 5e9b87102803a88b57c42f4e443d4254ab95a7e3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 29 Jul 2024 14:15:51 -0700 Subject: [PATCH 27/36] terminal/kitty: unit tests for unicode placements --- src/terminal/kitty/graphics_unicode.zig | 77 ++++++++++++++++++++++-- src/terminal/kitty/testdata/dog.png | Bin 0 -> 237387 bytes 2 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 src/terminal/kitty/testdata/dog.png diff --git a/src/terminal/kitty/graphics_unicode.zig b/src/terminal/kitty/graphics_unicode.zig index b6ed5b55a..9d306e2b3 100644 --- a/src/terminal/kitty/graphics_unicode.zig +++ b/src/terminal/kitty/graphics_unicode.zig @@ -6,6 +6,8 @@ const assert = std.debug.assert; const testing = std.testing; const terminal = @import("../main.zig"); const kitty_gfx = terminal.kitty.graphics; +const Image = kitty_gfx.Image; +const ImageStorage = kitty_gfx.ImageStorage; const RenderPlacement = kitty_gfx.RenderPlacement; const log = std.log.scoped(.kitty_gfx); @@ -128,8 +130,8 @@ pub const Placement = struct { /// Take this virtual placement and convert it to a render placement. pub fn renderPlacement( self: *const Placement, - storage: *const kitty_gfx.ImageStorage, - img: *const kitty_gfx.Image, + storage: *const ImageStorage, + img: *const Image, cell_width: u32, cell_height: u32, ) Error!RenderPlacement { @@ -310,8 +312,8 @@ pub const Placement = struct { // dumbly fitting the image into the grid size -- possibly user specified. fn grid( self: *const Placement, - storage: *const kitty_gfx.ImageStorage, - image: *const kitty_gfx.Image, + storage: *const ImageStorage, + image: *const Image, cell_width: u32, cell_height: u32, ) !struct { @@ -1118,3 +1120,70 @@ test "unicode placement: specifying placement id as palette" { } try testing.expect(it.next() == null); } + +// Fish: +// printf "\033_Gf=100,i=1,t=f,q=2;$(printf dog.png | base64)\033\\" +// printf "\e[38;5;1m\U10EEEE\U0305\U0305\U10EEEE\U0305\U030D\U10EEEE\U0305\U030E\U10EEEE\U0305\U0310\e[39m\n" +// printf "\e[38;5;1m\U10EEEE\U030D\U0305\U10EEEE\U030D\U030D\U10EEEE\U030D\U030E\U10EEEE\U030D\U0310\e[39m\n" +// printf "\033_Ga=p,i=1,U=1,q=2,c=4,r=2\033\\" +test "unicode render placement: dog 4x2" { + const alloc = testing.allocator; + const cell_width = 36; + const cell_height = 80; + + var t = try terminal.Terminal.init(alloc, .{ .cols = 100, .rows = 100 }); + defer t.deinit(alloc); + var s: ImageStorage = .{}; + defer s.deinit(alloc, &t.screen); + + const image: Image = .{ .id = 1, .width = 500, .height = 306 }; + try s.addImage(alloc, image); + try s.addPlacement(alloc, 1, 0, .{ + .location = .{ .virtual = {} }, + .columns = 4, + .rows = 2, + }); + + // Row 1 + { + const p: Placement = .{ + .pin = t.screen.cursor.page_pin.*, + .image_id = 1, + .placement_id = 0, + .col = 0, + .row = 0, + .width = 4, + .height = 1, + }; + const rp = try p.renderPlacement(&s, &image, cell_width, cell_height); + try testing.expectEqual(0, rp.offset_x); + try testing.expectEqual(36, rp.offset_y); + try testing.expectEqual(0, rp.source_x); + try testing.expectEqual(0, rp.source_y); + try testing.expectEqual(500, rp.source_width); + try testing.expectEqual(153, rp.source_height); + try testing.expectEqual(144, rp.dest_width); + try testing.expectEqual(44, rp.dest_height); + } + // Row 2 + { + const p: Placement = .{ + .pin = t.screen.cursor.page_pin.*, + .image_id = 1, + .placement_id = 0, + .col = 0, + .row = 1, + .width = 4, + .height = 1, + }; + const rp = try p.renderPlacement(&s, &image, cell_width, cell_height); + try testing.expectEqual(0, rp.offset_x); + try testing.expectEqual(0, rp.offset_y); + try testing.expectEqual(0, rp.source_x); + try testing.expectEqual(153, rp.source_y); + try testing.expectEqual(500, rp.source_width); + try testing.expectEqual(153, rp.source_height); + try testing.expectEqual(144, rp.dest_width); + try testing.expectEqual(44, rp.dest_height); + } +} diff --git a/src/terminal/kitty/testdata/dog.png b/src/terminal/kitty/testdata/dog.png new file mode 100644 index 0000000000000000000000000000000000000000..a5749c232030b3f73c9708eb3065d5ffcbcfacd4 GIT binary patch literal 237387 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�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%0Dga4O=~yv9tZwj5=%fYlA^0E7Jn4(5(}{ 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 Date: Mon, 29 Jul 2024 14:36:49 -0700 Subject: [PATCH 28/36] terminal: hasText no longer special cases kitty placeholders --- src/font/shaper/run.zig | 20 +++++++++++++++++++- src/terminal/page.zig | 15 ++------------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/font/shaper/run.zig b/src/font/shaper/run.zig index ef55ba981..8d53c601b 100644 --- a/src/font/shaper/run.zig +++ b/src/font/shaper/run.zig @@ -228,6 +228,12 @@ pub const RunIterator = struct { 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, @@ -284,8 +290,20 @@ pub const RunIterator = struct { 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.grid.getIndex( + alloc, + ' ', + style, + presentation, + ); + } + // Get the font index for the primary codepoint. - const primary_cp: u32 = if (cell.isEmpty() or cell.codepoint() == 0) ' ' else cell.codepoint(); + const primary_cp: u32 = cell.codepoint(); const primary = try self.grid.getIndex( alloc, primary_cp, diff --git a/src/terminal/page.zig b/src/terminal/page.zig index c270a0e0d..b396493fe 100644 --- a/src/terminal/page.zig +++ b/src/terminal/page.zig @@ -1705,8 +1705,7 @@ pub const Cell = packed struct(u64) { return switch (self.content_tag) { .codepoint, .codepoint_grapheme, - => self.content.codepoint != 0 and - self.content.codepoint != kitty.graphics.unicode.placeholder, + => self.content.codepoint != 0, .bg_color_palette, .bg_color_rgb, @@ -1738,8 +1737,7 @@ pub const Cell = packed struct(u64) { return self.style_id != style.default_id; } - /// Returns true if the cell has no text or styling. This also returns - /// true if the cell represents a Kitty graphics unicode placeholder. + /// Returns true if the cell has no text or styling. pub fn isEmpty(self: Cell) bool { return switch (self.content_tag) { // Textual cells are empty if they have no text and are narrow. @@ -2671,12 +2669,3 @@ test "Page verifyIntegrity zero cols" { page.verifyIntegrity(testing.allocator), ); } - -test "Cell isEmpty for kitty placeholder" { - var c: Cell = .{ - .content_tag = .codepoint_grapheme, - .content = .{ .codepoint = kitty.graphics.unicode.placeholder }, - }; - try testing.expectEqual(@as(u21, kitty.graphics.unicode.placeholder), c.codepoint()); - try testing.expect(c.isEmpty()); -} From 39b915ac2502f6576b837668f0c3434a3021a162 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 29 Jul 2024 14:42:27 -0700 Subject: [PATCH 29/36] terminal/kitty: handle width-stretched --- src/terminal/kitty/graphics_unicode.zig | 95 +++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 5 deletions(-) diff --git a/src/terminal/kitty/graphics_unicode.zig b/src/terminal/kitty/graphics_unicode.zig index 9d306e2b3..31e8fce55 100644 --- a/src/terminal/kitty/graphics_unicode.zig +++ b/src/terminal/kitty/graphics_unicode.zig @@ -248,9 +248,9 @@ pub const Placement = struct { width: f64, height: f64, } = dest: { - const x_offset: f64 = 0; + var x_offset: f64 = 0; var y_offset: f64 = 0; - const width: f64 = @floatFromInt(self.width * cell_width); + var width: f64 = @floatFromInt(self.width * cell_width); var height: f64 = @floatFromInt(self.height * cell_height); if (img_scale_source.y < img_scaled.y_offset) { @@ -262,9 +262,7 @@ pub const Placement = struct { y_offset = offset; height -= offset * p_scale.y_scale; img_scale_source.y = 0; - } - - if (img_scale_source.y + img_scale_source.height > + } else if (img_scale_source.y + img_scale_source.height > img_scaled.height - img_scaled.y_offset) { // if our y is in our bottom offset area, we need to shorten the @@ -275,6 +273,26 @@ pub const Placement = struct { height = img_scale_source.height * p_scale.y_scale; } + if (img_scale_source.x < img_scaled.x_offset) { + // If our source rect x is within the offset area, we need to + // adjust our source rect and destination since the source texture + // doesnt actually have the offset area blank. + const offset: f64 = img_scaled.x_offset - img_scale_source.x; + img_scale_source.width -= offset; + x_offset = offset; + width -= offset * p_scale.x_scale; + img_scale_source.x = 0; + } else if (img_scale_source.x + img_scale_source.width > + img_scaled.width - img_scaled.x_offset) + { + // if our x is in our right offset area, we need to shorten the + // source to fit in the cell. + img_scale_source.x -= img_scaled.x_offset; + img_scale_source.width = img_scaled.width - img_scaled.x_offset - img_scale_source.x; + img_scale_source.width -= img_scaled.x_offset; + width = img_scale_source.width * p_scale.x_scale; + } + break :dest .{ .x_offset = x_offset * p_scale.x_scale, .y_offset = y_offset * p_scale.y_scale, @@ -1187,3 +1205,70 @@ test "unicode render placement: dog 4x2" { try testing.expectEqual(44, rp.dest_height); } } + +// Fish: +// printf "\033_Gf=100,i=1,t=f,q=2;$(printf dog.png | base64)\033\\" +// printf "\e[38;5;1m\U10EEEE\U0305\U0305\U10EEEE\U0305\U030D\U10EEEE\U0305\U030E\U10EEEE\U0305\U0310\e[39m\n" +// printf "\e[38;5;1m\U10EEEE\U030D\U0305\U10EEEE\U030D\U030D\U10EEEE\U030D\U030E\U10EEEE\U030D\U0310\e[39m\n" +// printf "\033_Ga=p,i=1,U=1,q=2,c=2,r=2\033\\" +test "unicode render placement: dog 2x2 with blank cells" { + const alloc = testing.allocator; + const cell_width = 36; + const cell_height = 80; + + var t = try terminal.Terminal.init(alloc, .{ .cols = 100, .rows = 100 }); + defer t.deinit(alloc); + var s: ImageStorage = .{}; + defer s.deinit(alloc, &t.screen); + + const image: Image = .{ .id = 1, .width = 500, .height = 306 }; + try s.addImage(alloc, image); + try s.addPlacement(alloc, 1, 0, .{ + .location = .{ .virtual = {} }, + .columns = 2, + .rows = 2, + }); + + // Row 1 + { + const p: Placement = .{ + .pin = t.screen.cursor.page_pin.*, + .image_id = 1, + .placement_id = 0, + .col = 0, + .row = 0, + .width = 4, + .height = 1, + }; + const rp = try p.renderPlacement(&s, &image, cell_width, cell_height); + try testing.expectEqual(0, rp.offset_x); + try testing.expectEqual(58, rp.offset_y); + try testing.expectEqual(0, rp.source_x); + try testing.expectEqual(0, rp.source_y); + try testing.expectEqual(500, rp.source_width); + try testing.expectEqual(153, rp.source_height); + try testing.expectEqual(72, rp.dest_width); + try testing.expectEqual(22, rp.dest_height); + } + // Row 2 + { + const p: Placement = .{ + .pin = t.screen.cursor.page_pin.*, + .image_id = 1, + .placement_id = 0, + .col = 0, + .row = 1, + .width = 4, + .height = 1, + }; + const rp = try p.renderPlacement(&s, &image, cell_width, cell_height); + try testing.expectEqual(0, rp.offset_x); + try testing.expectEqual(0, rp.offset_y); + try testing.expectEqual(0, rp.source_x); + try testing.expectEqual(153, rp.source_y); + try testing.expectEqual(500, rp.source_width); + try testing.expectEqual(153, rp.source_height); + try testing.expectEqual(72, rp.dest_width); + try testing.expectEqual(22, rp.dest_height); + } +} From 0c81ca44b8a4f0ca20f7093d94805c16ceabff31 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 29 Jul 2024 14:52:09 -0700 Subject: [PATCH 30/36] terminal/kitty: do not render blank virtual placement cells --- src/renderer/Metal.zig | 3 +++ src/terminal/kitty/graphics_unicode.zig | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 47112384a..77b92040c 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -1694,6 +1694,9 @@ fn prepKittyVirtualPlacement( return; }; + // If our placement is zero sized then we don't do anything. + if (rp.dest_width == 0 or rp.dest_height == 0) return; + const viewport: terminal.point.Point = t.screen.pages.pointFromPin( .viewport, rp.top_left, diff --git a/src/terminal/kitty/graphics_unicode.zig b/src/terminal/kitty/graphics_unicode.zig index 31e8fce55..2fc34b67e 100644 --- a/src/terminal/kitty/graphics_unicode.zig +++ b/src/terminal/kitty/graphics_unicode.zig @@ -293,6 +293,13 @@ pub const Placement = struct { width = img_scale_source.width * p_scale.x_scale; } + // If our modified source width/height is less than zero then + // we render nothing because it means we're rendering outside + // of the visible image. + if (img_scale_source.width <= 0 or img_scale_source.height <= 0) { + return .{ .top_left = self.pin }; + } + break :dest .{ .x_offset = x_offset * p_scale.x_scale, .y_offset = y_offset * p_scale.y_scale, From d510bb449775f6365916e8de9cd6b05e29207311 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 29 Jul 2024 17:26:57 -0700 Subject: [PATCH 31/36] terminal/kitty: adjust middle rows for image offsets --- src/terminal/kitty/graphics_unicode.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/terminal/kitty/graphics_unicode.zig b/src/terminal/kitty/graphics_unicode.zig index 2fc34b67e..b8b76407b 100644 --- a/src/terminal/kitty/graphics_unicode.zig +++ b/src/terminal/kitty/graphics_unicode.zig @@ -271,6 +271,8 @@ pub const Placement = struct { img_scale_source.height = img_scaled.height - img_scaled.y_offset - img_scale_source.y; img_scale_source.height -= img_scaled.y_offset; height = img_scale_source.height * p_scale.y_scale; + } else { + img_scale_source.y -= img_scaled.y_offset; } if (img_scale_source.x < img_scaled.x_offset) { @@ -291,6 +293,8 @@ pub const Placement = struct { img_scale_source.width = img_scaled.width - img_scaled.x_offset - img_scale_source.x; img_scale_source.width -= img_scaled.x_offset; width = img_scale_source.width * p_scale.x_scale; + } else { + img_scale_source.x -= img_scaled.x_offset; } // If our modified source width/height is less than zero then From bf3075365770d387ce5496eb7ceb258271791038 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 29 Jul 2024 18:57:31 -0700 Subject: [PATCH 32/36] terminal/kitty: handle case where both offsets are in one grid cell --- src/terminal/kitty/graphics_unicode.zig | 65 ++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/src/terminal/kitty/graphics_unicode.zig b/src/terminal/kitty/graphics_unicode.zig index b8b76407b..339ad6202 100644 --- a/src/terminal/kitty/graphics_unicode.zig +++ b/src/terminal/kitty/graphics_unicode.zig @@ -262,6 +262,14 @@ pub const Placement = struct { y_offset = offset; height -= offset * p_scale.y_scale; img_scale_source.y = 0; + + // If our height is greater than our original height, + // bring it back down. This addresses the case where the top + // and bottom offsets are both used. + if (img_scale_source.height > img_height_f64) { + img_scale_source.height = img_height_f64; + height = img_height_f64 * p_scale.y_scale; + } } else if (img_scale_source.y + img_scale_source.height > img_scaled.height - img_scaled.y_offset) { @@ -284,6 +292,14 @@ pub const Placement = struct { x_offset = offset; width -= offset * p_scale.x_scale; img_scale_source.x = 0; + + // If our width is greater than our original width, + // bring it back down. This addresses the case where the left + // and right offsets are both used. + if (img_scale_source.width > img_width_f64) { + img_scale_source.width = img_width_f64; + width = img_width_f64 * p_scale.x_scale; + } } else if (img_scale_source.x + img_scale_source.width > img_scaled.width - img_scaled.x_offset) { @@ -311,7 +327,9 @@ pub const Placement = struct { .height = height, }; }; - // log.warn("p_grid={} p_scale={} img_scaled={} img_scale_source={} p_dest={}", .{ + // log.warn("img_width={} img_height={}\np_grid={}\np_scale={}\nimg_scaled={}\nimg_scale_source={}\np_dest={}\n", .{ + // img_width_f64, + // img_height_f64, // p_grid, // p_scale, // img_scaled, @@ -1283,3 +1301,48 @@ test "unicode render placement: dog 2x2 with blank cells" { try testing.expectEqual(22, rp.dest_height); } } + +// Fish: +// printf "\033_Gf=100,i=1,t=f,q=2;$(printf dog.png | base64)\033\\" +// printf "\e[38;5;1m\U10EEEE\U0305\U0305\U10EEEE\U0305\U030D\U10EEEE\U0305\U030E\U10EEEE\U0305\U0310\e[39m\n" +// printf "\033_Ga=p,i=1,U=1,q=2,c=1,r=1\033\\" +test "unicode render placement: dog 1x1" { + const alloc = testing.allocator; + const cell_width = 36; + const cell_height = 80; + + var t = try terminal.Terminal.init(alloc, .{ .cols = 100, .rows = 100 }); + defer t.deinit(alloc); + var s: ImageStorage = .{}; + defer s.deinit(alloc, &t.screen); + + const image: Image = .{ .id = 1, .width = 500, .height = 306 }; + try s.addImage(alloc, image); + try s.addPlacement(alloc, 1, 0, .{ + .location = .{ .virtual = {} }, + .columns = 1, + .rows = 1, + }); + + // Row 1 + { + const p: Placement = .{ + .pin = t.screen.cursor.page_pin.*, + .image_id = 1, + .placement_id = 0, + .col = 0, + .row = 0, + .width = 4, + .height = 1, + }; + const rp = try p.renderPlacement(&s, &image, cell_width, cell_height); + try testing.expectEqual(0, rp.offset_x); + try testing.expectEqual(29, rp.offset_y); + try testing.expectEqual(0, rp.source_x); + try testing.expectEqual(0, rp.source_y); + try testing.expectEqual(500, rp.source_width); + try testing.expectEqual(306, rp.source_height); + try testing.expectEqual(36, rp.dest_width); + try testing.expectEqual(22, rp.dest_height); + } +} From 765254e78467b5207be609116d4876dc31a72d44 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 29 Jul 2024 19:15:42 -0700 Subject: [PATCH 33/36] renderer/opengl: unicode placeholder support --- src/renderer/Metal.zig | 1 - src/renderer/OpenGL.zig | 307 +++++++++++++++++++++++++++------------- 2 files changed, 210 insertions(+), 98 deletions(-) diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 77b92040c..3e4cac699 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -932,7 +932,6 @@ pub fn updateFrame( // If we have any virtual references, we must also rebuild our // kitty state on every frame because any cell change can move // an image. - // TODO(mitchellh): integrate with row dirty flags if (state.terminal.screen.kitty_images.dirty or self.image_virtual) { diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index fd9261874..2645ebc26 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -120,6 +120,7 @@ images: ImageMap = .{}, image_placements: ImagePlacementList = .{}, image_bg_end: u32 = 0, image_text_end: u32 = 0, +image_virtual: bool = false, /// Defererred OpenGL operation to update the screen size. const SetScreenSize = struct { @@ -693,7 +694,13 @@ pub fn updateFrame( // If we have Kitty graphics data, we enter a SLOW SLOW SLOW path. // We only do this if the Kitty image state is dirty meaning only if // it changes. - if (state.terminal.screen.kitty_images.dirty) { + // + // If we have any virtual references, we must also rebuild our + // kitty state on every frame because any cell change can move + // an image. + if (state.terminal.screen.kitty_images.dirty or + self.image_virtual) + { // prepKittyGraphics touches self.images which is also used // in drawFrame so if we're drawing on a separate thread we need // to lock this. @@ -752,6 +759,7 @@ fn prepKittyGraphics( // We always clear our previous placements no matter what because // we rebuild them from scratch. self.image_placements.clearRetainingCapacity(); + self.image_virtual = false; // Go through our known images and if there are any that are no longer // in use then mark them to be freed. @@ -777,6 +785,23 @@ fn prepKittyGraphics( while (it.next()) |kv| { // Find the image in storage const p = kv.value_ptr; + + // Special logic based on location + switch (p.location) { + .pin => {}, + .virtual => { + // We need to mark virtual placements on our renderer so that + // we know to rebuild in more scenarios since cell changes can + // now trigger placement changes. + self.image_virtual = true; + + // We also continue out because virtual placements are + // only triggered by the unicode placeholder, not by the + // placement itself. + continue; + }, + } + const image = storage.imageById(kv.key_ptr.image_id) orelse { log.warn( "missing image for placement, ignoring image_id={}", @@ -785,103 +810,16 @@ fn prepKittyGraphics( continue; }; - // Get the rect for the placement. If this placement doesn't have - // a rect then its virtual or something so skip it. - const rect = p.rect(image, t) orelse continue; + try self.prepKittyPlacement(t, &top, &bot, &image, p); + } - // If the selection isn't within our viewport then skip it. - if (bot.before(rect.top_left)) continue; - if (rect.bottom_right.before(top)) continue; - - // If the top left is outside the viewport we need to calc an offset - // so that we render (0, 0) with some offset for the texture. - const offset_y: u32 = if (rect.top_left.before(top)) offset_y: { - const vp_y = t.screen.pages.pointFromPin(.screen, top).?.screen.y; - const img_y = t.screen.pages.pointFromPin(.screen, rect.top_left).?.screen.y; - const offset_cells = vp_y - img_y; - const offset_pixels = offset_cells * self.grid_metrics.cell_height; - break :offset_y @intCast(offset_pixels); - } else 0; - - // We need to prep this image for upload if it isn't in the cache OR - // it is in the cache but the transmit time doesn't match meaning this - // image is different. - const gop = try self.images.getOrPut(self.alloc, kv.key_ptr.image_id); - if (!gop.found_existing or - gop.value_ptr.transmit_time.order(image.transmit_time) != .eq) - { - // Copy the data into the pending state. - const data = try self.alloc.dupe(u8, image.data); - errdefer self.alloc.free(data); - - // Store it in the map - const pending: Image.Pending = .{ - .width = image.width, - .height = image.height, - .data = data.ptr, - }; - - const new_image: Image = switch (image.format) { - .grey_alpha => .{ .pending_grey_alpha = pending }, - .rgb => .{ .pending_rgb = pending }, - .rgba => .{ .pending_rgba = pending }, - .png => unreachable, // should be decoded by now - }; - - if (!gop.found_existing) { - gop.value_ptr.* = .{ - .image = new_image, - .transmit_time = undefined, - }; - } else { - try gop.value_ptr.image.markForReplace( - self.alloc, - new_image, - ); - } - - gop.value_ptr.transmit_time = image.transmit_time; - } - - // Convert our screen point to a viewport point - const viewport: terminal.point.Point = t.screen.pages.pointFromPin( - .viewport, - rect.top_left, - ) orelse .{ .viewport = .{} }; - - // Calculate the source rectangle - const source_x = @min(image.width, p.source_x); - const source_y = @min(image.height, p.source_y + offset_y); - const source_width = if (p.source_width > 0) - @min(image.width - source_x, p.source_width) - else - image.width; - const source_height = if (p.source_height > 0) - @min(image.height, p.source_height) - else - image.height -| source_y; - - // Calculate the width/height of our image. - const dest_width = if (p.columns > 0) p.columns * self.grid_metrics.cell_width else source_width; - const dest_height = if (p.rows > 0) p.rows * self.grid_metrics.cell_height else source_height; - - // Accumulate the placement - if (image.width > 0 and image.height > 0) { - try self.image_placements.append(self.alloc, .{ - .image_id = kv.key_ptr.image_id, - .x = @intCast(rect.top_left.x), - .y = @intCast(viewport.viewport.y), - .z = p.z, - .width = dest_width, - .height = dest_height, - .cell_offset_x = p.x_offset, - .cell_offset_y = p.y_offset, - .source_x = source_x, - .source_y = source_y, - .source_width = source_width, - .source_height = source_height, - }); - } + // If we have virtual placements then we need to scan for placeholders. + if (self.image_virtual) { + var v_it = terminal.kitty.graphics.unicode.placementIterator(top, bot); + while (v_it.next()) |virtual_p| try self.prepKittyVirtualPlacement( + t, + &virtual_p, + ); } // Sort the placements by their Z value. @@ -918,6 +856,181 @@ fn prepKittyGraphics( } } +fn prepKittyVirtualPlacement( + self: *OpenGL, + t: *terminal.Terminal, + p: *const terminal.kitty.graphics.unicode.Placement, +) !void { + const storage = &t.screen.kitty_images; + const image = storage.imageById(p.image_id) orelse { + log.warn( + "missing image for virtual placement, ignoring image_id={}", + .{p.image_id}, + ); + return; + }; + + const rp = p.renderPlacement( + storage, + &image, + self.grid_metrics.cell_width, + self.grid_metrics.cell_height, + ) catch |err| { + log.warn("error rendering virtual placement err={}", .{err}); + return; + }; + + // If our placement is zero sized then we don't do anything. + if (rp.dest_width == 0 or rp.dest_height == 0) return; + + const viewport: terminal.point.Point = t.screen.pages.pointFromPin( + .viewport, + rp.top_left, + ) orelse { + // This is unreachable with virtual placements because we should + // only ever be looking at virtual placements that are in our + // viewport in the renderer and virtual placements only ever take + // up one row. + unreachable; + }; + + // Send our image to the GPU and store the placement for rendering. + try self.prepKittyImage(&image); + try self.image_placements.append(self.alloc, .{ + .image_id = image.id, + .x = @intCast(rp.top_left.x), + .y = @intCast(viewport.viewport.y), + .z = -1, + .width = rp.dest_width, + .height = rp.dest_height, + .cell_offset_x = rp.offset_x, + .cell_offset_y = rp.offset_y, + .source_x = rp.source_x, + .source_y = rp.source_y, + .source_width = rp.source_width, + .source_height = rp.source_height, + }); +} + +fn prepKittyPlacement( + self: *OpenGL, + t: *terminal.Terminal, + top: *const terminal.Pin, + bot: *const terminal.Pin, + image: *const terminal.kitty.graphics.Image, + p: *const terminal.kitty.graphics.ImageStorage.Placement, +) !void { + // Get the rect for the placement. If this placement doesn't have + // a rect then its virtual or something so skip it. + const rect = p.rect(image.*, t) orelse return; + + // If the selection isn't within our viewport then skip it. + if (bot.before(rect.top_left)) return; + if (rect.bottom_right.before(top.*)) return; + + // If the top left is outside the viewport we need to calc an offset + // so that we render (0, 0) with some offset for the texture. + const offset_y: u32 = if (rect.top_left.before(top.*)) offset_y: { + const vp_y = t.screen.pages.pointFromPin(.screen, top.*).?.screen.y; + const img_y = t.screen.pages.pointFromPin(.screen, rect.top_left).?.screen.y; + const offset_cells = vp_y - img_y; + const offset_pixels = offset_cells * self.grid_metrics.cell_height; + break :offset_y @intCast(offset_pixels); + } else 0; + + // We need to prep this image for upload if it isn't in the cache OR + // it is in the cache but the transmit time doesn't match meaning this + // image is different. + try self.prepKittyImage(image); + + // Convert our screen point to a viewport point + const viewport: terminal.point.Point = t.screen.pages.pointFromPin( + .viewport, + rect.top_left, + ) orelse .{ .viewport = .{} }; + + // Calculate the source rectangle + const source_x = @min(image.width, p.source_x); + const source_y = @min(image.height, p.source_y + offset_y); + const source_width = if (p.source_width > 0) + @min(image.width - source_x, p.source_width) + else + image.width; + const source_height = if (p.source_height > 0) + @min(image.height, p.source_height) + else + image.height -| source_y; + + // Calculate the width/height of our image. + const dest_width = if (p.columns > 0) p.columns * self.grid_metrics.cell_width else source_width; + const dest_height = if (p.rows > 0) p.rows * self.grid_metrics.cell_height else source_height; + + // Accumulate the placement + if (image.width > 0 and image.height > 0) { + try self.image_placements.append(self.alloc, .{ + .image_id = image.id, + .x = @intCast(rect.top_left.x), + .y = @intCast(viewport.viewport.y), + .z = p.z, + .width = dest_width, + .height = dest_height, + .cell_offset_x = p.x_offset, + .cell_offset_y = p.y_offset, + .source_x = source_x, + .source_y = source_y, + .source_width = source_width, + .source_height = source_height, + }); + } +} + +fn prepKittyImage( + self: *OpenGL, + image: *const terminal.kitty.graphics.Image, +) !void { + // We need to prep this image for upload if it isn't in the cache OR + // it is in the cache but the transmit time doesn't match meaning this + // image is different. + const gop = try self.images.getOrPut(self.alloc, image.id); + if (gop.found_existing and + gop.value_ptr.transmit_time.order(image.transmit_time) == .eq) + { + return; + } + + // Copy the data into the pending state. + const data = try self.alloc.dupe(u8, image.data); + errdefer self.alloc.free(data); + + // Store it in the map + const pending: Image.Pending = .{ + .width = image.width, + .height = image.height, + .data = data.ptr, + }; + + const new_image: Image = switch (image.format) { + .grey_alpha => .{ .pending_grey_alpha = pending }, + .rgb => .{ .pending_rgb = pending }, + .rgba => .{ .pending_rgba = pending }, + .png => unreachable, // should be decoded by now + }; + + if (!gop.found_existing) { + gop.value_ptr.* = .{ + .image = new_image, + .transmit_time = undefined, + }; + } else { + try gop.value_ptr.image.markForReplace( + self.alloc, + new_image, + ); + } + + gop.value_ptr.transmit_time = image.transmit_time; +} + /// rebuildCells rebuilds all the GPU cells from our CPU state. This is a /// slow operation but ensures that the GPU state exactly matches the CPU state. /// In steady-state operation, we use some GPU tricks to send down stale data From a1276b3cc37c01f2b0a89a769b71031cc25d0776 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 29 Jul 2024 19:37:47 -0700 Subject: [PATCH 34/36] terminal/kitty: delete all images ignores virtual placements --- src/terminal/kitty/graphics_storage.zig | 34 +++++++++++++++---------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/terminal/kitty/graphics_storage.zig b/src/terminal/kitty/graphics_storage.zig index 82441a54e..bf8633c88 100644 --- a/src/terminal/kitty/graphics_storage.zig +++ b/src/terminal/kitty/graphics_storage.zig @@ -218,19 +218,27 @@ pub const ImageStorage = struct { cmd: command.Delete, ) void { switch (cmd) { - // TODO: virtual placeholders must not be deleted according to spec - .all => |delete_images| if (delete_images) { - // We just reset our entire state. - self.deinit(alloc, &t.screen); - self.* = .{ - .dirty = true, - .total_limit = self.total_limit, - }; - } else { - // Delete all our placements - self.clearPlacements(&t.screen); - self.placements.deinit(alloc); - self.placements = .{}; + .all => |delete_images| { + var it = self.placements.iterator(); + while (it.next()) |entry| { + // Skip virtual placements + switch (entry.value_ptr.location) { + .pin => {}, + .virtual => continue, + } + + // Deinit the placement and remove it + const image_id = entry.key_ptr.image_id; + entry.value_ptr.deinit(&t.screen); + self.placements.removeByPtr(entry.key_ptr); + if (delete_images) self.deleteIfUnused(alloc, image_id); + } + + if (delete_images) { + var image_it = self.images.iterator(); + while (image_it.next()) |kv| self.deleteIfUnused(alloc, kv.key_ptr.*); + } + self.dirty = true; }, From 2c4ddc594ec2bdeda5c08f9deea39589d6c8367b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 31 Jul 2024 09:48:00 -0700 Subject: [PATCH 35/36] terminal: reflow tests for kitty virtual placeholder flag --- src/terminal/PageList.zig | 119 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/src/terminal/PageList.zig b/src/terminal/PageList.zig index 403fe7e5a..64b741f2c 100644 --- a/src/terminal/PageList.zig +++ b/src/terminal/PageList.zig @@ -7962,3 +7962,122 @@ test "PageList resize reflow less cols to wrap a wide char" { } } } + +test "PageList resize reflow less cols copy kitty placeholder" { + const testing = std.testing; + const alloc = testing.allocator; + + var s = try init(alloc, 4, 2, 0); + defer s.deinit(); + { + try testing.expect(s.pages.first == s.pages.last); + const page = &s.pages.first.?.data; + + // Write unicode placeholders + for (0..s.cols - 1) |x| { + const rac = page.getRowAndCell(x, 0); + rac.row.kitty_virtual_placeholder = true; + rac.cell.* = .{ + .content_tag = .codepoint, + .content = .{ .codepoint = kitty.graphics.unicode.placeholder }, + }; + } + } + + // Resize + try s.resize(.{ .cols = 2, .reflow = true }); + try testing.expectEqual(@as(usize, 2), s.cols); + try testing.expectEqual(@as(usize, 2), s.totalRows()); + + var it = s.rowIterator(.right_down, .{ .active = .{} }, null); + while (it.next()) |offset| { + for (0..s.cols - 1) |x| { + var offset_copy = offset; + offset_copy.x = @intCast(x); + const rac = offset_copy.rowAndCell(); + + const row = rac.row; + try testing.expect(row.kitty_virtual_placeholder); + } + } +} + +test "PageList resize reflow more cols clears kitty placeholder" { + const testing = std.testing; + const alloc = testing.allocator; + + var s = try init(alloc, 4, 2, 0); + defer s.deinit(); + { + try testing.expect(s.pages.first == s.pages.last); + const page = &s.pages.first.?.data; + + // Write unicode placeholders + for (0..s.cols - 1) |x| { + const rac = page.getRowAndCell(x, 0); + rac.row.kitty_virtual_placeholder = true; + rac.cell.* = .{ + .content_tag = .codepoint, + .content = .{ .codepoint = kitty.graphics.unicode.placeholder }, + }; + } + } + + // Resize smaller then larger + try s.resize(.{ .cols = 2, .reflow = true }); + try s.resize(.{ .cols = 4, .reflow = true }); + try testing.expectEqual(@as(usize, 4), s.cols); + try testing.expectEqual(@as(usize, 2), s.totalRows()); + + var it = s.rowIterator(.right_down, .{ .active = .{} }, null); + { + const row = it.next().?; + const rac = row.rowAndCell(); + try testing.expect(rac.row.kitty_virtual_placeholder); + } + { + const row = it.next().?; + const rac = row.rowAndCell(); + try testing.expect(!rac.row.kitty_virtual_placeholder); + } + try testing.expect(it.next() == null); +} + +test "PageList resize reflow wrap moves kitty placeholder" { + const testing = std.testing; + const alloc = testing.allocator; + + var s = try init(alloc, 4, 2, 0); + defer s.deinit(); + { + try testing.expect(s.pages.first == s.pages.last); + const page = &s.pages.first.?.data; + + // Write unicode placeholders + for (2..s.cols - 1) |x| { + const rac = page.getRowAndCell(x, 0); + rac.row.kitty_virtual_placeholder = true; + rac.cell.* = .{ + .content_tag = .codepoint, + .content = .{ .codepoint = kitty.graphics.unicode.placeholder }, + }; + } + } + + try s.resize(.{ .cols = 2, .reflow = true }); + try testing.expectEqual(@as(usize, 2), s.cols); + try testing.expectEqual(@as(usize, 2), s.totalRows()); + + var it = s.rowIterator(.right_down, .{ .active = .{} }, null); + { + const row = it.next().?; + const rac = row.rowAndCell(); + try testing.expect(!rac.row.kitty_virtual_placeholder); + } + { + const row = it.next().?; + const rac = row.rowAndCell(); + try testing.expect(rac.row.kitty_virtual_placeholder); + } + try testing.expect(it.next() == null); +} From d40101fea0e276189fb04be57ad47a9985075c08 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 31 Jul 2024 09:51:32 -0700 Subject: [PATCH 36/36] terminal: more tests --- src/terminal/Terminal.zig | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index d6b8a7376..063368960 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -3552,6 +3552,24 @@ test "Terminal: print invoke charset single" { } } +test "Terminal: print kitty unicode placeholder" { + var t = try init(testing.allocator, .{ .cols = 10, .rows = 10 }); + defer t.deinit(testing.allocator); + + try t.print(kitty.graphics.unicode.placeholder); + try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); + try testing.expectEqual(@as(usize, 1), t.screen.cursor.x); + + { + const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; + const cell = list_cell.cell; + try testing.expectEqual(@as(u21, kitty.graphics.unicode.placeholder), cell.content.codepoint); + try testing.expect(list_cell.row.kitty_virtual_placeholder); + } + + try testing.expect(t.isDirty(.{ .screen = .{ .x = 0, .y = 0 } })); +} + test "Terminal: soft wrap" { var t = try init(testing.allocator, .{ .cols = 3, .rows = 80 }); defer t.deinit(testing.allocator);