libghostty: add convenience accessors for kitty graphics (#12229)

Add three sized structs that let callers fetch all image, placement, or
rendering metadata in a single call instead of many individual queries.
This is an optimization for environments with high per-call overhead
such as FFI or Cgo.

GhosttyKittyGraphicsImageInfo is returned via image_get() with the new
GHOSTTY_KITTY_IMAGE_DATA_INFO data kind. It bundles id, number, width,
height, format, compression, data pointer, and data length.

GhosttyKittyGraphicsPlacementInfo is returned via placement_get() with
the new GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_INFO data kind. It bundles
image id, placement id, virtual flag, offsets, source rect, columns,
rows, and z-index.

GhosttyKittyGraphicsPlacementRenderInfo is returned by the new
ghostty_kitty_graphics_placement_render_info() function, which combines
pixel size, grid size, viewport position, and resolved source rectangle.
This one requires image and terminal handles so it does not fit the
existing _get() pattern and is a dedicated function.

All three use the sized-struct ABI pattern with GHOSTTY_INIT_SIZED for
forward compatibility.
This commit is contained in:
Mitchell Hashimoto
2026-04-10 14:26:28 -07:00
committed by GitHub
4 changed files with 524 additions and 39 deletions

View File

@@ -214,6 +214,18 @@ typedef enum GHOSTTY_ENUM_TYPED {
* Output type: int32_t *
*/
GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_Z = 12,
/**
* All placement metadata as a sized struct.
*
* This is an optimization over querying each field individually,
* particularly useful in environments with high per-call overhead
* such as FFI or Cgo.
*
* Output type: GhosttyKittyGraphicsPlacementInfo *
* (initialize with GHOSTTY_INIT_SIZED)
*/
GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_INFO = 13,
GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
} GhosttyKittyGraphicsPlacementData;
@@ -341,9 +353,141 @@ typedef enum GHOSTTY_ENUM_TYPED {
* Output type: size_t *
*/
GHOSTTY_KITTY_IMAGE_DATA_DATA_LEN = 8,
/**
* All image metadata as a sized struct.
*
* This is an optimization over querying each field individually,
* particularly useful in environments with high per-call overhead
* such as FFI or Cgo.
*
* Output type: GhosttyKittyGraphicsImageInfo *
* (initialize with GHOSTTY_INIT_SIZED)
*/
GHOSTTY_KITTY_IMAGE_DATA_INFO = 9,
GHOSTTY_KITTY_IMAGE_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
} GhosttyKittyGraphicsImageData;
/**
* All image metadata in a single sized struct.
*
* Returned by ghostty_kitty_graphics_image_get() with
* GHOSTTY_KITTY_IMAGE_DATA_INFO. This is an optimization over
* querying each field individually, particularly useful in
* environments with high per-call overhead such as FFI or Cgo.
*
* This struct uses the sized-struct ABI pattern. Initialize with
* GHOSTTY_INIT_SIZED(GhosttyKittyGraphicsImageInfo).
*
* @ingroup kitty_graphics
*/
typedef struct {
/** Size of this struct in bytes. Must be set to sizeof(GhosttyKittyGraphicsImageInfo). */
size_t size;
/** The image ID. */
uint32_t id;
/** The image number. */
uint32_t number;
/** Image width in pixels. */
uint32_t width;
/** Image height in pixels. */
uint32_t height;
/** Pixel format of the image. */
GhosttyKittyImageFormat format;
/** Compression of the image. */
GhosttyKittyImageCompression compression;
/** Borrowed pointer to the raw pixel data. */
const uint8_t* data_ptr;
/** Length of the raw pixel data in bytes. */
size_t data_len;
} GhosttyKittyGraphicsImageInfo;
/**
* All placement metadata in a single sized struct.
*
* Returned by ghostty_kitty_graphics_placement_get() with
* GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_INFO. This is an optimization
* over querying each field individually, particularly useful in
* environments with high per-call overhead such as FFI or Cgo.
*
* This struct uses the sized-struct ABI pattern. Initialize with
* GHOSTTY_INIT_SIZED(GhosttyKittyGraphicsPlacementInfo).
*
* @ingroup kitty_graphics
*/
typedef struct {
/** Size of this struct in bytes. Must be set to sizeof(GhosttyKittyGraphicsPlacementInfo). */
size_t size;
/** The image ID this placement belongs to. */
uint32_t image_id;
/** The placement ID. */
uint32_t placement_id;
/** Whether this is a virtual placement (unicode placeholder). */
bool is_virtual;
/** Pixel offset from the left edge of the cell. */
uint32_t x_offset;
/** Pixel offset from the top edge of the cell. */
uint32_t y_offset;
/** Source rectangle x origin in pixels. */
uint32_t source_x;
/** Source rectangle y origin in pixels. */
uint32_t source_y;
/** Source rectangle width in pixels (0 = full image width). */
uint32_t source_width;
/** Source rectangle height in pixels (0 = full image height). */
uint32_t source_height;
/** Number of columns this placement occupies. */
uint32_t columns;
/** Number of rows this placement occupies. */
uint32_t rows;
/** Z-index for this placement. */
int32_t z;
} GhosttyKittyGraphicsPlacementInfo;
/**
* Combined rendering geometry for a placement in a single sized struct.
*
* Combines the results of ghostty_kitty_graphics_placement_pixel_size(),
* ghostty_kitty_graphics_placement_grid_size(),
* ghostty_kitty_graphics_placement_viewport_pos(), and
* ghostty_kitty_graphics_placement_source_rect() into one call. This is
* an optimization over calling those four functions individually,
* particularly useful in environments with high per-call overhead such
* as FFI or Cgo.
*
* This struct uses the sized-struct ABI pattern. Initialize with
* GHOSTTY_INIT_SIZED(GhosttyKittyGraphicsPlacementRenderInfo) before calling
* ghostty_kitty_graphics_placement_render_info().
*
* @ingroup kitty_graphics
*/
typedef struct {
/** Size of this struct in bytes. Must be set to sizeof(GhosttyKittyGraphicsPlacementRenderInfo). */
size_t size;
/** Rendered width in pixels. */
uint32_t pixel_width;
/** Rendered height in pixels. */
uint32_t pixel_height;
/** Number of grid columns the placement occupies. */
uint32_t grid_cols;
/** Number of grid rows the placement occupies. */
uint32_t grid_rows;
/** Viewport-relative column (may be negative for partially visible placements). */
int32_t viewport_col;
/** Viewport-relative row (may be negative for partially visible placements). */
int32_t viewport_row;
/** False when the placement is fully off-screen or virtual. */
bool viewport_visible;
/** Resolved source rectangle x origin in pixels. */
uint32_t source_x;
/** Resolved source rectangle y origin in pixels. */
uint32_t source_y;
/** Resolved source rectangle width in pixels. */
uint32_t source_width;
/** Resolved source rectangle height in pixels. */
uint32_t source_height;
} GhosttyKittyGraphicsPlacementRenderInfo;
/**
* Get data from a kitty graphics storage instance.
*
@@ -627,6 +771,31 @@ GHOSTTY_API GhosttyResult ghostty_kitty_graphics_placement_source_rect(
uint32_t* out_width,
uint32_t* out_height);
/**
* Get all rendering geometry for a placement in a single call.
*
* Combines pixel size, grid size, viewport position, and source
* rectangle into one struct. Initialize with
* GHOSTTY_INIT_SIZED(GhosttyKittyGraphicsPlacementRenderInfo).
*
* When viewport_visible is false, the placement is fully off-screen
* or is a virtual placement; viewport_col and viewport_row may
* contain meaningless values in that case.
*
* @param iterator The iterator positioned on a placement
* @param image The image handle for this placement's image
* @param terminal The terminal handle
* @param[out] out_info Pointer to receive the rendering geometry
* @return GHOSTTY_SUCCESS on success
*
* @ingroup kitty_graphics
*/
GHOSTTY_API GhosttyResult ghostty_kitty_graphics_placement_render_info(
GhosttyKittyGraphicsPlacementIterator iterator,
GhosttyKittyGraphicsImage image,
GhosttyTerminal terminal,
GhosttyKittyGraphicsPlacementRenderInfo* out_info);
/** @} */
#ifdef __cplusplus

View File

@@ -248,6 +248,7 @@ comptime {
@export(&c.kitty_graphics_placement_grid_size, .{ .name = "ghostty_kitty_graphics_placement_grid_size" });
@export(&c.kitty_graphics_placement_viewport_pos, .{ .name = "ghostty_kitty_graphics_placement_viewport_pos" });
@export(&c.kitty_graphics_placement_source_rect, .{ .name = "ghostty_kitty_graphics_placement_source_rect" });
@export(&c.kitty_graphics_placement_render_info, .{ .name = "ghostty_kitty_graphics_placement_render_info" });
@export(&c.grid_ref_cell, .{ .name = "ghostty_grid_ref_cell" });
@export(&c.grid_ref_row, .{ .name = "ghostty_grid_ref_row" });
@export(&c.grid_ref_graphemes, .{ .name = "ghostty_grid_ref_graphemes" });

View File

@@ -9,6 +9,7 @@ const Image = @import("../kitty/graphics_image.zig").Image;
const grid_ref = @import("grid_ref.zig");
const selection_c = @import("selection.zig");
const terminal_c = @import("terminal.zig");
const Terminal = @import("../Terminal.zig");
const Result = @import("result.zig").Result;
/// C: GhosttyKittyGraphics
@@ -75,6 +76,7 @@ pub const PlacementData = enum(c_int) {
columns = 10,
rows = 11,
z = 12,
info = 13,
pub fn OutType(comptime self: PlacementData) type {
return switch (self) {
@@ -91,6 +93,7 @@ pub const PlacementData = enum(c_int) {
.rows,
=> u32,
.z => i32,
.info => PlacementInfo,
};
}
};
@@ -177,6 +180,7 @@ pub const ImageData = enum(c_int) {
compression = 6,
data_ptr = 7,
data_len = 8,
info = 9,
pub fn OutType(comptime self: ImageData) type {
return switch (self) {
@@ -186,6 +190,7 @@ pub const ImageData = enum(c_int) {
.compression => ImageCompression,
.data_ptr => [*]const u8,
.data_len => usize,
.info => ImageInfo,
};
}
};
@@ -234,6 +239,17 @@ fn imageGetTyped(
.compression => out.* = image.compression,
.data_ptr => out.* = image.data.ptr,
.data_len => out.* = image.data.len,
.info => {
if (out.size < @sizeOf(ImageInfo)) return .invalid_value;
out.id = image.id;
out.number = image.number;
out.width = image.width;
out.height = image.height;
out.format = image.format;
out.compression = image.compression;
out.data_ptr = image.data.ptr;
out.data_len = image.data.len;
},
}
return .success;
@@ -335,20 +351,38 @@ fn placementGetTyped(
const iter = iter_ orelse return .invalid_value;
const entry = iter.entry orelse return .invalid_value;
const key = entry.key_ptr;
const val = entry.value_ptr;
switch (data) {
.invalid => return .invalid_value,
.image_id => out.* = entry.key_ptr.image_id,
.placement_id => out.* = entry.key_ptr.placement_id.id,
.is_virtual => out.* = entry.value_ptr.location == .virtual,
.x_offset => out.* = entry.value_ptr.x_offset,
.y_offset => out.* = entry.value_ptr.y_offset,
.source_x => out.* = entry.value_ptr.source_x,
.source_y => out.* = entry.value_ptr.source_y,
.source_width => out.* = entry.value_ptr.source_width,
.source_height => out.* = entry.value_ptr.source_height,
.columns => out.* = entry.value_ptr.columns,
.rows => out.* = entry.value_ptr.rows,
.z => out.* = entry.value_ptr.z,
.image_id => out.* = key.image_id,
.placement_id => out.* = key.placement_id.id,
.is_virtual => out.* = val.location == .virtual,
.x_offset => out.* = val.x_offset,
.y_offset => out.* = val.y_offset,
.source_x => out.* = val.source_x,
.source_y => out.* = val.source_y,
.source_width => out.* = val.source_width,
.source_height => out.* = val.source_height,
.columns => out.* = val.columns,
.rows => out.* = val.rows,
.z => out.* = val.z,
.info => {
if (out.size < @sizeOf(PlacementInfo)) return .invalid_value;
out.image_id = key.image_id;
out.placement_id = key.placement_id.id;
out.is_virtual = val.location == .virtual;
out.x_offset = val.x_offset;
out.y_offset = val.y_offset;
out.source_x = val.source_x;
out.source_y = val.source_y;
out.source_width = val.source_width;
out.source_height = val.source_height;
out.columns = val.columns;
out.rows = val.rows;
out.z = val.z;
},
}
return .success;
@@ -435,35 +469,12 @@ pub fn placement_viewport_pos(
const image = image_ orelse return .invalid_value;
const iter = iter_ orelse return .invalid_value;
const entry = iter.entry orelse return .invalid_value;
const pin = switch (entry.value_ptr.location) {
.pin => |p| p,
.virtual => return .no_value,
};
const pages = &wrapper.terminal.screens.active.pages;
const vp = computeViewportPos(entry.value_ptr, image, wrapper.terminal);
if (!vp.visible) return .no_value;
// Get screen-absolute coordinates for both the pin and the
// viewport origin, then subtract to get viewport-relative
// coordinates that can be negative for partially visible
// placements above the viewport.
const pin_screen = pages.pointFromPin(.screen, pin.*) orelse return .no_value;
const vp_tl = pages.getTopLeft(.viewport);
const vp_screen = pages.pointFromPin(.screen, vp_tl) orelse return .no_value;
const vp_row: i32 = @as(i32, @intCast(pin_screen.screen.y)) -
@as(i32, @intCast(vp_screen.screen.y));
const vp_col: i32 = @intCast(pin_screen.screen.x);
// Check if the placement is fully off-screen. A placement is
// invisible if its bottom edge is above the viewport or its
// top edge is at or below the viewport's last row.
const grid_size = entry.value_ptr.gridSize(image.*, wrapper.terminal);
const rows_i32: i32 = @intCast(grid_size.rows);
const term_rows: i32 = @intCast(wrapper.terminal.rows);
if (vp_row + rows_i32 <= 0 or vp_row >= term_rows) return .no_value;
out_col.* = vp_col;
out_row.* = vp_row;
out_col.* = vp.col;
out_row.* = vp.row;
return .success;
}
@@ -497,6 +508,144 @@ pub fn placement_source_rect(
return .success;
}
/// C: GhosttyKittyGraphicsImageInfo
pub const ImageInfo = extern struct {
size: usize = @sizeOf(ImageInfo),
id: u32 = 0,
number: u32 = 0,
width: u32 = 0,
height: u32 = 0,
format: ImageFormat = .rgb,
compression: ImageCompression = .none,
data_ptr: [*]const u8 = @as([*]const u8, @ptrFromInt(1)),
data_len: usize = 0,
};
/// C: GhosttyKittyGraphicsPlacementInfo
pub const PlacementInfo = extern struct {
size: usize = @sizeOf(PlacementInfo),
image_id: u32 = 0,
placement_id: u32 = 0,
is_virtual: bool = false,
x_offset: u32 = 0,
y_offset: u32 = 0,
source_x: u32 = 0,
source_y: u32 = 0,
source_width: u32 = 0,
source_height: u32 = 0,
columns: u32 = 0,
rows: u32 = 0,
z: i32 = 0,
};
/// C: GhosttyKittyGraphicsPlacementRenderInfo
pub const PlacementRenderInfo = extern struct {
size: usize = @sizeOf(PlacementRenderInfo),
pixel_width: u32 = 0,
pixel_height: u32 = 0,
grid_cols: u32 = 0,
grid_rows: u32 = 0,
viewport_col: i32 = 0,
viewport_row: i32 = 0,
viewport_visible: bool = false,
source_x: u32 = 0,
source_y: u32 = 0,
source_width: u32 = 0,
source_height: u32 = 0,
};
pub fn placement_render_info(
iter_: PlacementIterator,
image_: ImageHandle,
terminal_: terminal_c.Terminal,
out_: ?*PlacementRenderInfo,
) callconv(lib.calling_conv) Result {
if (comptime !build_options.kitty_graphics) return .no_value;
const wrapper = terminal_ orelse return .invalid_value;
const image = image_ orelse return .invalid_value;
const iter = iter_ orelse return .invalid_value;
const entry = iter.entry orelse return .invalid_value;
const out = out_ orelse return .invalid_value;
if (out.size < @sizeOf(PlacementRenderInfo)) return .invalid_value;
const p = entry.value_ptr;
const ps = p.pixelSize(image.*, wrapper.terminal);
out.pixel_width = ps.width;
out.pixel_height = ps.height;
const gs = p.gridSize(image.*, wrapper.terminal);
out.grid_cols = gs.cols;
out.grid_rows = gs.rows;
const vp = computeViewportPos(p, image, wrapper.terminal);
out.viewport_col = vp.col;
out.viewport_row = vp.row;
out.viewport_visible = vp.visible;
const x = @min(p.source_x, image.width);
const y = @min(p.source_y, image.height);
out.source_x = x;
out.source_y = y;
out.source_width = @min(if (p.source_width > 0) p.source_width else image.width, image.width - x);
out.source_height = @min(if (p.source_height > 0) p.source_height else image.height, image.height - y);
return .success;
}
/// Compute viewport-relative position of a placement.
///
/// Converts the placement's internal pin to viewport-relative column
/// and row coordinates by getting screen-absolute coordinates for
/// both the pin and the viewport origin, then subtracting to get
/// viewport-relative coordinates. The row value can be negative when
/// the placement's origin has scrolled above the top of the viewport.
///
/// A placement is considered not visible if it is a virtual (unicode
/// placeholder) placement, or if it is fully off-screen (its bottom
/// edge is above the viewport or its top edge is at or below the
/// viewport's last row).
fn computeViewportPos(
p: *const kitty_storage.ImageStorage.Placement,
image: *const Image,
t: *Terminal,
) struct { col: i32, row: i32, visible: bool } {
// Virtual placements use unicode placeholders and don't have a
// screen position — they are rendered inline by the text layout.
const pin = switch (p.location) {
.pin => |pin| pin,
.virtual => return .{ .col = 0, .row = 0, .visible = false },
};
// Convert both the placement's pin and the viewport's top-left
// corner to screen-absolute coordinates so we can subtract them
// to get viewport-relative coordinates.
const pages = &t.screens.active.pages;
const pin_screen = pages.pointFromPin(.screen, pin.*) orelse
return .{ .col = 0, .row = 0, .visible = false };
const vp_tl = pages.getTopLeft(.viewport);
const vp_screen = pages.pointFromPin(.screen, vp_tl) orelse
return .{ .col = 0, .row = 0, .visible = false };
// Subtracting viewport origin from the pin gives us viewport-
// relative coordinates. The row can be negative when the
// placement has partially scrolled above the viewport.
const vp_row: i32 = @as(i32, @intCast(pin_screen.screen.y)) -
@as(i32, @intCast(vp_screen.screen.y));
const vp_col: i32 = @intCast(pin_screen.screen.x);
// A placement is invisible if its bottom edge (row + height)
// is above the viewport, or its top edge is at or below the
// viewport's last row.
const grid_size = p.gridSize(image.*, t);
const rows_i32: i32 = @intCast(grid_size.rows);
const term_rows: i32 = @intCast(t.rows);
const visible = vp_row + rows_i32 > 0 and vp_row < term_rows;
return .{ .col = vp_col, .row = vp_row, .visible = visible };
}
test "placement_iterator new/free" {
var iter: PlacementIterator = null;
try testing.expectEqual(Result.success, placement_iterator_new(
@@ -1373,3 +1522,168 @@ test "image_get on null returns invalid_value" {
var id: u32 = undefined;
try testing.expectEqual(Result.invalid_value, image_get(null, .id, @ptrCast(&id)));
}
test "image_get info returns all fields" {
if (comptime !build_options.kitty_graphics) return error.SkipZigTest;
var t: terminal_c.Terminal = null;
try testing.expectEqual(Result.success, terminal_c.new(
&lib.alloc.test_allocator,
&t,
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
));
defer terminal_c.free(t);
try testing.expectEqual(Result.success, terminal_c.resize(t, 80, 24, 10, 20));
const cmd = "\x1b_Ga=T,t=d,f=24,i=1,p=1,s=1,v=2;////////\x1b\\";
terminal_c.vt_write(t, cmd.ptr, cmd.len);
var graphics: KittyGraphics = undefined;
try testing.expectEqual(Result.success, terminal_c.get(t, .kitty_graphics, @ptrCast(&graphics)));
const img = image_get_handle(graphics, 1);
try testing.expect(img != null);
var info: ImageInfo = .{};
try testing.expectEqual(Result.success, image_get(img, .info, @ptrCast(&info)));
try testing.expectEqual(1, info.id);
try testing.expectEqual(1, info.width);
try testing.expectEqual(2, info.height);
try testing.expectEqual(ImageFormat.rgb, info.format);
try testing.expectEqual(ImageCompression.none, info.compression);
try testing.expectEqual(6, info.data_len);
}
test "image_get info null returns invalid_value" {
if (comptime !build_options.kitty_graphics) return error.SkipZigTest;
var info: ImageInfo = .{};
try testing.expectEqual(Result.invalid_value, image_get(null, .info, @ptrCast(&info)));
}
test "placement_get info returns all fields" {
if (comptime !build_options.kitty_graphics) return error.SkipZigTest;
var t: terminal_c.Terminal = null;
try testing.expectEqual(Result.success, terminal_c.new(
&lib.alloc.test_allocator,
&t,
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
));
defer terminal_c.free(t);
try testing.expectEqual(Result.success, terminal_c.resize(t, 80, 24, 10, 20));
const cmd = "\x1b_Ga=T,t=d,f=24,i=1,p=1,s=1,v=2,c=10,r=1;////////\x1b\\";
terminal_c.vt_write(t, cmd.ptr, cmd.len);
var graphics: KittyGraphics = undefined;
try testing.expectEqual(Result.success, terminal_c.get(t, .kitty_graphics, @ptrCast(&graphics)));
var iter: PlacementIterator = null;
try testing.expectEqual(Result.success, placement_iterator_new(&lib.alloc.test_allocator, &iter));
defer placement_iterator_free(iter);
try testing.expectEqual(Result.success, get(graphics, .placement_iterator, @ptrCast(&iter)));
try testing.expect(placement_iterator_next(iter));
var info: PlacementInfo = .{};
try testing.expectEqual(Result.success, placement_get(iter, .info, @ptrCast(&info)));
try testing.expectEqual(1, info.image_id);
try testing.expectEqual(1, info.placement_id);
try testing.expect(!info.is_virtual);
try testing.expectEqual(10, info.columns);
try testing.expectEqual(1, info.rows);
}
test "placement_get info null returns invalid_value" {
if (comptime !build_options.kitty_graphics) return error.SkipZigTest;
var info: PlacementInfo = .{};
try testing.expectEqual(Result.invalid_value, placement_get(null, .info, @ptrCast(&info)));
}
test "placement_render_info returns all fields" {
if (comptime !build_options.kitty_graphics) return error.SkipZigTest;
var t: terminal_c.Terminal = null;
try testing.expectEqual(Result.success, terminal_c.new(
&lib.alloc.test_allocator,
&t,
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
));
defer terminal_c.free(t);
try testing.expectEqual(Result.success, terminal_c.resize(t, 80, 24, 10, 20));
const cmd = "\x1b_Ga=T,t=d,f=24,i=1,p=1,s=1,v=2,c=10,r=1;////////\x1b\\";
terminal_c.vt_write(t, cmd.ptr, cmd.len);
var graphics: KittyGraphics = undefined;
try testing.expectEqual(Result.success, terminal_c.get(t, .kitty_graphics, @ptrCast(&graphics)));
const img = image_get_handle(graphics, 1);
try testing.expect(img != null);
var iter: PlacementIterator = null;
try testing.expectEqual(Result.success, placement_iterator_new(&lib.alloc.test_allocator, &iter));
defer placement_iterator_free(iter);
try testing.expectEqual(Result.success, get(graphics, .placement_iterator, @ptrCast(&iter)));
try testing.expect(placement_iterator_next(iter));
var ri: PlacementRenderInfo = .{};
try testing.expectEqual(Result.success, placement_render_info(iter, img, t, &ri));
try testing.expect(ri.viewport_visible);
try testing.expectEqual(0, ri.viewport_col);
try testing.expectEqual(0, ri.viewport_row);
try testing.expectEqual(10, ri.grid_cols);
try testing.expectEqual(1, ri.grid_rows);
try testing.expectEqual(0, ri.source_x);
try testing.expectEqual(0, ri.source_y);
try testing.expectEqual(1, ri.source_width);
try testing.expectEqual(2, ri.source_height);
}
test "placement_render_info off-screen sets viewport_visible false" {
if (comptime !build_options.kitty_graphics) return error.SkipZigTest;
var t: terminal_c.Terminal = null;
try testing.expectEqual(Result.success, terminal_c.new(
&lib.alloc.test_allocator,
&t,
.{ .cols = 80, .rows = 5, .max_scrollback = 100 },
));
defer terminal_c.free(t);
try testing.expectEqual(Result.success, terminal_c.resize(t, 80, 5, 10, 20));
const transmit = "\x1b_Ga=t,t=d,f=24,i=1,s=1,v=2;////////\x1b\\";
const display = "\x1b_Ga=p,i=1,p=1,c=1,r=1;\x1b\\";
terminal_c.vt_write(t, transmit.ptr, transmit.len);
terminal_c.vt_write(t, display.ptr, display.len);
// Scroll the image completely off-screen.
const scroll = "\n\n\n\n\n\n\n\n\n\n";
terminal_c.vt_write(t, scroll.ptr, scroll.len);
var graphics: KittyGraphics = undefined;
try testing.expectEqual(Result.success, terminal_c.get(t, .kitty_graphics, @ptrCast(&graphics)));
const img = image_get_handle(graphics, 1);
try testing.expect(img != null);
var iter: PlacementIterator = null;
try testing.expectEqual(Result.success, placement_iterator_new(&lib.alloc.test_allocator, &iter));
defer placement_iterator_free(iter);
try testing.expectEqual(Result.success, get(graphics, .placement_iterator, @ptrCast(&iter)));
try testing.expect(placement_iterator_next(iter));
var ri: PlacementRenderInfo = .{};
try testing.expectEqual(Result.success, placement_render_info(iter, img, t, &ri));
try testing.expect(!ri.viewport_visible);
// Other fields should still be populated.
try testing.expectEqual(1, ri.grid_cols);
try testing.expectEqual(1, ri.grid_rows);
try testing.expectEqual(1, ri.source_width);
try testing.expectEqual(2, ri.source_height);
}
test "placement_render_info null returns invalid_value" {
if (comptime !build_options.kitty_graphics) return error.SkipZigTest;
var ri: PlacementRenderInfo = .{};
try testing.expectEqual(Result.invalid_value, placement_render_info(null, null, null, &ri));
}

View File

@@ -22,6 +22,7 @@ pub const kitty_graphics_placement_pixel_size = kitty_graphics.placement_pixel_s
pub const kitty_graphics_placement_grid_size = kitty_graphics.placement_grid_size;
pub const kitty_graphics_placement_viewport_pos = kitty_graphics.placement_viewport_pos;
pub const kitty_graphics_placement_source_rect = kitty_graphics.placement_source_rect;
pub const kitty_graphics_placement_render_info = kitty_graphics.placement_render_info;
pub const types = @import("types.zig");
pub const modes = @import("modes.zig");
pub const osc = @import("osc.zig");