Files
opentui/packages/core/scripts/native-abi-pointers.ts
T
Simon Klee 542275a354 editor: show control characters as notation (#1636)
Keep controls in editor text and render them as safe, visible notation.
This lets the cursor, selection, wrapping, and offsets handle controls
consistently without sending them to the terminal. Strip terminal escape
sequences from pasted text so pasted controls cannot change terminal
state.

Alternative to #1632 and aligned with how vim does it.

We accidentally in pre 0.6 and explicit in 0.6.x rejects edit text that
contains a C0 control other than tab, carriage return, and line feed,
DEL, or a C1 control: `setText()`, `insertText()`, `replaceText()`,
Textarea `initialValue`, and `Input.value` throw `NativeError` with
`InvalidArgument`. The reason is that the editor places its cursor by
display cell, and a control took no cells, so the cursor could neither
move past it nor delete it. In 0.5.14, which accepted controls, the
cursor in `"a\fb"` could not reach column 0, Backspace stopped at
`"a\f"`, and rendering `"A\x07B\x1bC\fD"` wrote raw BEL and ESC bytes to
the terminal while `C` and `D` vanished.

This is an alternative to #1632, which drops these characters. Instead,
editors keep them and show them the way Vim and Neovim do. Measured with
Neovim 0.12.5 and Vim 9.2, both show `a^Lb^@c^[d<85>e^?f`: a C0 control
or DEL as caret notation in 2 cells, and a C1 control as hex in 4 cells.
The cursor stops on each one, `x` deletes one, and writing the file back
gives identical bytes.

Editors now do the same. A per-buffer `ControlDisplay` setting is passed
with the tab width to the native width functions, so cursor movement,
deletion, wrapping, selection, history, and drawing share one rule:

- C0 controls other than tab, LF, and CR, and DEL, show as `^@`..`^_`
and `^?` in 2 cells. C1 controls show as `<80>`..`<9f>` in 4 cells. Tab,
LF, and CR keep their current behavior.
- Cells still never hold a raw control: drawing writes the notation's
printable glyphs.
- Each control is its own grapheme, so it no longer joins the next
character, and a soft wrap never splits its notation.
- `getText()`, undo and redo, and selection copy return the original
bytes.
- Read-only text keeps hiding controls with zero width, so
`TextRenderable`, the console, and other displayed text do not change.
Only `EditBuffer` and its placeholder use notation.

Text input changes with it:

- Paste keeps controls, as Vim does, and still removes escape sequences.
The stripper is now one shared implementation, so Bun and Node give the
same result, and core no longer depends on `strip-ansi`. A lone ESC and
a truncated sequence are removed, so a paste never inserts ESC. Key
sequences with a control stay ignored, so Escape does not insert `^[`.
- `Input` puts its cursor at the native end of the value instead of the
string length.
- Extmarks convert display offsets through native `offsetToPosition`
instead of JavaScript string widths, and Right stops at the native end
of the buffer before a virtual extmark. `deleteLine` adjusts extmarks by
native line offsets.
- `EditBuffer.measureText(text)` and `ot_edit_buffer_measure_text`
return the display offset at the end of a text as the buffer would lay
it out, without storing it. JavaScript width functions give a control
and a tab no cells, so an app that computes editor offsets in JavaScript
needs this: in `"\f@fi"` the `@` is at offset 2, not 0.

Along the way, these bugs that already exist in are fixed:

- A standalone zero-width cluster (zero-width space, word joiner, BOM)
blocked Left and Backspace in the Unicode width methods.
- After `"日\u{fffd}"`, Backspace deleted the wide character. In the
no_zwj method, one emoji step had the wrong size.
- U+FFFD inside a joining sequence, such as `"क्\u{FFFD}\u{301}ष"`, made
layout and cursor movement disagree: Right got stuck, and Backspace
deleted the wrong cells.
- An extmark ending at a line end after a tab lost its last cell. Right
could step into a virtual extmark after CJK text. `deleteLine` moved
extmarks by UTF-16 indexes. `Input` placed its cursor in the middle
after CJK text.

Backspace no longer measures the whole chunk for each step. The native
"backspace 500 chars" benchmark takes 0.21 ms instead of 25.3 ms on
v0.6.0; other benchmarks match v0.6.0 within noise.

Most of the diff passes the new setting to the width functions: of the
added lines, most only add `.hidden`, `.notation`, or `control_display`,
and others of them in existing tests. Net source is +86 lines and net
tests +95.

Not included: a distinct style for the notation (like Vim's SpecialKey),
a literal-insert key, `^M` for a lone CR, controls as word boundaries,
and clipping wide units cell by cell at the left edge of a horizontally
scrolled view.


Fixes #1642
2026-10-11 21:15:07 +02:00

316 lines
19 KiB
TypeScript

export interface PointerPolicy {
ffi: "buffer" | "ptr"
nullable: "never" | "empty" | "optional"
retention:
| "call"
| "until-measure-replaced-unset-or-node-context-destroyed"
| "until-observer-replaced-unset-or-context-destroyed"
source: "view" | "native-address" | "view-or-native-address" | "callback"
result?: { nullable: "on-error"; release: "ot_context_destroy" }
}
export type PointerPolicies = Record<string, Partial<Record<number | "returns", PointerPolicy>>>
export type AddressFields = Record<
string,
Record<string, { type: string; nullable: boolean; lifetime: string; release: string; element: string }>
>
const context = { ffi: "ptr", nullable: "never", retention: "call", source: "native-address" } as const
const buffer = { ffi: "buffer", nullable: "never", retention: "call", source: "view" } as const
const optional = { ffi: "ptr", nullable: "optional", retention: "call", source: "view" } as const
const empty = { ffi: "ptr", nullable: "empty", retention: "call", source: "view" } as const
const pixels = { ffi: "ptr", nullable: "empty", retention: "call", source: "view-or-native-address" } as const
const measure = {
ffi: "ptr",
nullable: "optional",
source: "callback",
retention: "until-measure-replaced-unset-or-node-context-destroyed",
} as const
const observer = {
ffi: "ptr",
nullable: "optional",
source: "callback",
retention: "until-observer-replaced-unset-or-context-destroyed",
} as const
// This is host policy, not a second C signature table. Each pointer position is
// explicit; scalar widths, pointer types, symbols and records come from Translate-C.
// `nullable` describes values passed by this binding. Several C spans also accept
// NULL at zero length, but their existing TS callers always pass a view (`buffer`).
// `retention` describes borrowing the pointer itself, not copying/retaining handles.
// Changes to C ownership comments require review here: C pointer types cannot prove lifetimes.
export const nativePointerPolicies = {
ot_output_write_callback: {
0: { ...optional, source: "native-address" },
1: { ...context, source: "native-address" },
},
ot_context_create: { 0: buffer, 1: { ...buffer, result: { nullable: "on-error", release: "ot_context_destroy" } } },
ot_context_destroy: { 0: context },
ot_context_get_last_error: { 0: context, 1: buffer },
ot_context_drain_diagnostics: { 0: context, 1: empty, 3: buffer },
ot_context_get_link_url: { 0: context, 2: buffer, 4: buffer },
ot_unicode_create: { 0: context, 1: empty, 4: buffer },
ot_unicode_destroy: { 0: context, 1: buffer },
ot_unicode_get: { 0: context, 1: buffer, 2: empty, 4: buffer },
ot_buffer_draw_unicode: { 0: context, 1: buffer, 2: optional, 3: buffer, 7: buffer, 8: buffer },
ot_embedded_terminal_create: { 0: context, 1: buffer, 2: buffer },
ot_embedded_terminal_destroy: { 0: context, 1: buffer },
ot_embedded_terminal_write: { 0: context, 1: buffer, 2: empty },
ot_embedded_terminal_resize: { 0: context, 1: buffer },
ot_embedded_terminal_command: { 0: context, 1: buffer },
ot_embedded_terminal_set_selection: { 0: context, 1: buffer },
ot_embedded_terminal_get_selected_text: { 0: context, 1: buffer, 2: empty, 4: buffer },
ot_embedded_terminal_compose: { 0: context, 1: buffer, 2: buffer, 3: optional },
ot_embedded_terminal_cursor_get: { 0: context, 1: buffer, 2: buffer },
ot_embedded_terminal_encode_key: { 0: context, 1: buffer, 2: buffer, 3: empty, 5: empty, 7: empty, 9: buffer },
ot_embedded_terminal_encode_mouse: { 0: context, 1: buffer, 2: buffer, 3: buffer, 5: buffer },
ot_embedded_terminal_encode_paste: { 0: context, 1: buffer, 2: empty, 4: empty, 6: buffer },
ot_embedded_terminal_encode_focus: { 0: context, 1: buffer, 3: empty, 5: buffer },
ot_embedded_terminal_drain_responses: { 0: context, 1: buffer, 2: buffer, 4: buffer },
ot_image_destroy: { 0: context, 1: buffer },
ot_image_inspect: { 0: context, 1: buffer, 3: buffer },
ot_image_decode: { 0: context, 1: buffer, 3: buffer },
ot_image_create_pixels: { 0: context, 1: buffer, 8: buffer },
ot_image_update_pixels: { 0: context, 1: buffer, 2: buffer },
ot_image_clone: { 0: context, 1: context, 2: buffer, 3: buffer },
ot_image_retain: { 0: context, 1: buffer, 2: buffer },
ot_image_get_info: { 0: context, 1: buffer, 2: buffer },
ot_image_resize: { 0: context, 1: buffer, 5: buffer },
ot_image_extract: { 0: context, 1: buffer, 6: buffer },
ot_image_extend: { 0: context, 1: buffer, 6: buffer, 7: buffer },
ot_image_transform: { 0: context, 1: buffer, 3: buffer },
ot_image_composite: { 0: context, 1: buffer, 2: buffer, 7: buffer },
ot_image_copy_pixels: { 0: context, 1: buffer, 2: buffer },
ot_image_copy_png: { 0: context, 1: buffer, 2: buffer, 4: buffer },
ot_image_take_pixels: { 0: context, 1: buffer, 2: buffer },
ot_image_pixels_release: { 0: context, 1: buffer },
ot_scene_set_image: { 0: context, 1: buffer, 2: optional, 5: optional },
ot_buffer_draw_image: { 0: context, 1: buffer, 2: optional, 3: buffer, 4: buffer, 5: buffer },
ot_session_set_image_resolution: { 0: context, 1: buffer },
ot_session_set_kitty_image_transport: { 0: context, 1: buffer },
ot_session_get_kitty_image_transport: { 0: context, 1: buffer, 2: buffer },
ot_session_poll_kitty_image_transport: { 0: context, 1: buffer, 3: buffer },
ot_session_cancel_kitty_image_transport: { 0: context, 1: buffer },
ot_session_process_kitty_image_reply: { 0: context, 1: buffer, 2: empty, 4: buffer },
ot_session_start_kitty_file_probe: { 0: context, 1: buffer },
ot_text_buffer_create: { 0: context, 1: buffer, 2: buffer },
ot_text_buffer_destroy: { 0: context, 1: buffer },
ot_text_buffer_view_create: { 0: context, 1: buffer, 2: buffer },
ot_text_buffer_view_destroy: { 0: context, 1: buffer },
ot_text_buffer_set_text: { 0: context, 1: buffer, 2: empty },
ot_text_buffer_append: { 0: context, 1: buffer, 2: empty },
ot_text_buffer_clear: { 0: context, 1: buffer },
ot_text_buffer_set_styled_text: { 0: context, 1: buffer, 2: empty, 4: empty, 6: empty },
ot_text_buffer_replace_styled_batch: { 0: context, 1: buffer, 3: buffer, 5: buffer, 7: buffer, 9: buffer },
ot_text_buffer_set_syntax_style: { 0: context, 1: buffer, 2: optional },
ot_text_buffer_get_info: { 0: context, 1: buffer, 2: buffer },
ot_text_buffer_get_text: { 0: context, 1: buffer, 2: empty, 4: buffer },
ot_text_buffer_get_range: { 0: context, 1: buffer, 4: empty, 6: buffer },
ot_text_buffer_set_defaults: { 0: context, 1: buffer, 3: buffer },
ot_text_buffer_set_tab_width: { 0: context, 1: buffer },
ot_text_buffer_highlight: { 0: context, 1: buffer, 4: optional },
ot_text_buffer_get_highlights: { 0: context, 1: buffer, 3: empty, 5: buffer },
ot_text_buffer_view_set_viewport: { 0: context, 1: buffer, 2: buffer },
ot_text_buffer_view_command: { 0: context, 1: buffer },
ot_text_buffer_view_set_tab_color: { 0: context, 1: buffer, 2: optional },
ot_text_buffer_view_select: { 0: context, 1: buffer, 2: buffer, 3: buffer },
ot_text_buffer_view_get_info: { 0: context, 1: buffer, 2: buffer },
ot_text_buffer_view_get_selected_text: { 0: context, 1: buffer, 2: empty, 4: buffer },
ot_text_buffer_view_get_lines: { 0: context, 1: buffer, 4: empty, 6: buffer },
ot_text_buffer_view_measure: { 0: context, 1: buffer, 4: buffer },
ot_scene_set_text_view: { 0: context, 1: buffer, 2: optional },
ot_scene_set_text_view_paint: { 0: context, 1: buffer },
ot_buffer_draw_text_view: { 0: context, 1: buffer, 2: optional, 3: buffer },
ot_buffer_draw_editor_view: { 0: context, 1: buffer, 2: optional, 3: buffer },
ot_buffer_draw_scene_text: { 0: context, 1: buffer, 2: optional, 3: buffer },
ot_edit_buffer_create: { 0: context, 1: buffer, 2: buffer },
ot_edit_buffer_destroy: { 0: context, 1: buffer },
ot_editor_view_create: { 0: context, 1: buffer, 4: buffer },
ot_editor_view_destroy: { 0: context, 1: buffer },
ot_syntax_style_create: { 0: context, 1: buffer },
ot_syntax_style_destroy: { 0: context, 1: buffer },
ot_edit_buffer_set_syntax_style: { 0: context, 1: buffer, 2: optional },
ot_edit_buffer_set_text: { 0: context, 1: buffer, 2: empty },
ot_edit_buffer_insert_text: { 0: context, 1: buffer, 2: empty },
ot_edit_buffer_delete_range: { 0: context, 1: buffer },
ot_edit_buffer_set_cursor: { 0: context, 1: buffer },
ot_edit_buffer_get_text: { 0: context, 1: buffer, 2: empty, 4: buffer },
ot_edit_buffer_get_info: { 0: context, 1: buffer, 2: buffer },
ot_edit_buffer_set_tab_width: { 0: context, 1: buffer },
ot_edit_buffer_command: { 0: context, 1: buffer },
ot_edit_buffer_history: { 0: context, 1: buffer, 3: buffer, 5: buffer },
ot_edit_buffer_get_position: { 0: context, 1: buffer, 5: buffer },
ot_edit_buffer_get_range: { 0: context, 1: buffer, 7: empty, 9: buffer },
ot_edit_buffer_measure_text: { 0: context, 1: buffer, 2: empty, 4: buffer },
ot_edit_buffer_set_defaults: { 0: context, 1: buffer, 3: buffer },
ot_syntax_style_register: { 0: context, 1: buffer, 2: empty, 4: buffer, 5: buffer },
ot_syntax_style_resolve: { 0: context, 1: buffer, 2: empty, 4: buffer },
ot_syntax_style_get_count: { 0: context, 1: buffer, 2: buffer },
ot_edit_buffer_highlight: { 0: context, 1: buffer, 4: optional },
ot_edit_buffer_get_highlights: { 0: context, 1: buffer, 3: empty, 5: buffer },
ot_editor_view_set_viewport: { 0: context, 1: buffer, 2: buffer },
ot_editor_view_get_viewport: { 0: context, 1: buffer, 2: buffer },
ot_editor_view_set_scroll_margin: { 0: context, 1: buffer },
ot_editor_view_command: { 0: context, 1: buffer },
ot_editor_view_replace_selection: { 0: context, 1: buffer, 2: empty, 4: buffer },
ot_editor_view_set_tab_color: { 0: context, 1: buffer, 2: optional },
ot_editor_view_select: { 0: context, 1: buffer, 2: buffer, 3: buffer },
ot_editor_view_get_info: { 0: context, 1: buffer, 3: buffer },
ot_editor_view_get_selection: { 0: context, 1: buffer, 2: buffer },
ot_editor_view_get_selected_text: { 0: context, 1: buffer, 2: empty, 4: buffer },
ot_editor_view_get_position: { 0: context, 1: buffer, 3: buffer },
ot_editor_view_get_lines: { 0: context, 1: buffer, 3: empty, 5: buffer },
ot_editor_view_measure: { 0: context, 1: buffer, 4: buffer },
ot_editor_view_set_placeholder: { 0: context, 1: buffer, 2: empty, 4: empty },
ot_context_set_edit_event_callback: { 0: context, 1: observer },
ot_buffer_create: { 0: context, 1: buffer, 2: buffer },
ot_buffer_destroy: { 0: context, 1: buffer },
ot_buffer_resize: { 0: context, 1: buffer },
ot_buffer_draw: { 0: context, 1: buffer, 2: optional, 3: buffer, 4: optional, 5: buffer, 7: buffer },
ot_buffer_stack: { 0: context, 1: buffer, 2: optional, 8: buffer, 9: buffer },
ot_buffer_draw_grid: { 0: context, 1: buffer, 2: optional, 3: buffer, 4: buffer, 6: buffer },
ot_buffer_draw_packed: { 0: context, 1: buffer, 2: optional, 3: pixels },
ot_buffer_draw_supersample: { 0: context, 1: buffer, 2: optional, 3: pixels },
ot_buffer_draw_grayscale: { 0: context, 1: buffer, 2: optional, 3: buffer, 9: optional, 10: optional },
ot_buffer_color_matrix: { 0: context, 1: buffer, 2: optional, 3: buffer, 5: optional, 7: buffer },
ot_buffer_acquire_lease: { 0: context, 1: buffer, 2: buffer },
ot_session_create: { 0: context, 1: buffer, 2: buffer },
ot_session_attach_renderer: { 0: context, 1: buffer, 2: buffer },
ot_session_attach_renderer_with_env: { 0: context, 1: buffer, 2: buffer, 3: buffer },
ot_session_render: { 0: context, 1: buffer, 3: buffer },
ot_scene_frame_copy_buffer: { 0: context, 1: buffer, 2: buffer, 3: buffer },
ot_scene_measure_layout: { 0: context, 1: buffer, 2: buffer },
ot_session_render_split: { 0: context, 1: buffer, 2: optional, 3: empty, 7: buffer },
ot_session_split_control: { 0: context, 1: buffer, 2: buffer, 3: buffer },
ot_session_set_screen: { 0: context, 1: buffer, 5: buffer },
ot_session_sync_detached: { 0: context, 1: buffer, 2: buffer },
ot_session_resize_renderer: { 0: context, 1: buffer },
ot_session_draw_buffer: { 0: context, 1: buffer, 2: buffer },
ot_session_set_debug_overlay: { 0: context, 1: buffer },
ot_session_update_stats: { 0: context, 1: buffer },
ot_session_update_memory_stats: { 0: context, 1: buffer },
ot_session_dump_hit_grid: { 0: context, 1: buffer },
ot_session_get_renderer_state: { 0: context, 1: buffer, 2: buffer },
ot_session_acquire_buffer_lease: { 0: context, 1: buffer, 3: buffer },
ot_session_get_link_id: { 0: context, 1: buffer, 4: buffer },
ot_buffer_lease_validate: { 0: context, 1: buffer },
ot_buffer_lease_release: { 0: context, 1: buffer },
ot_buffer_lease_get_real_char_size: { 0: context, 1: buffer, 3: buffer },
ot_buffer_lease_write_resolved_chars: { 0: context, 1: buffer, 2: empty, 5: empty, 7: buffer },
ot_session_setup_terminal: { 0: context, 1: buffer, 2: buffer },
ot_session_suspend: { 0: context, 1: buffer },
ot_session_resume: { 0: context, 1: buffer },
ot_session_get_terminal_state: { 0: context, 1: buffer, 2: buffer },
ot_session_pump: { 0: context, 1: buffer, 4: buffer },
ot_session_pump_exit: { 0: context, 1: buffer, 2: buffer },
ot_session_control: { 0: context, 1: buffer, 2: buffer, 3: empty },
ot_session_clipboard: { 0: context, 1: buffer, 3: buffer, 5: buffer },
ot_session_notification: { 0: context, 1: buffer, 2: buffer, 4: empty, 6: buffer },
ot_session_set_palette_state: { 0: context, 1: buffer, 2: buffer, 4: buffer, 5: buffer },
ot_session_get_capabilities: { 0: context, 1: buffer, 2: buffer },
ot_session_get_write_limit: { 0: context, 1: buffer, 2: buffer },
ot_session_write: { 0: context, 1: buffer, 2: empty },
ot_session_read_output: { 0: context, 1: buffer, 2: empty, 4: buffer },
ot_session_drain_output: {
0: context,
1: buffer,
3: buffer,
4: { ...optional, source: "view-or-native-address" },
5: { ffi: "ptr", nullable: "never", retention: "call", source: "callback" },
},
ot_session_drain_stdout: { 0: context, 1: buffer, 3: buffer },
ot_session_complete_output: { 0: context, 1: buffer, 2: buffer },
ot_session_close: { 0: context, 1: buffer },
ot_session_cancel: { 0: context, 1: buffer },
ot_session_get_state: { 0: context, 1: buffer, 2: buffer },
ot_session_destroy: { 0: context, 1: buffer },
ot_clipboard_service_create: { 0: context, 3: empty },
ot_clipboard_service_begin_shutdown: { 0: context },
ot_clipboard_service_poll_shutdown: { 0: context },
ot_clipboard_service_destroy: { 0: context },
ot_clipboard_service_drain: { 0: context },
ot_clipboard_read_operation_start: { 0: context, 1: buffer, 8: buffer },
ot_clipboard_write_operation_start: { 0: context, 1: buffer, 5: buffer },
ot_clipboard_clear_operation_start: { 0: context, 3: buffer },
ot_clipboard_operation_poll: { 0: context, 1: buffer },
ot_clipboard_operation_cancel: { 0: context, 1: buffer },
ot_clipboard_operation_result_mime_length: { 0: context, 1: buffer, 2: buffer },
ot_clipboard_operation_result_mime_copy: { 0: context, 1: buffer, 2: buffer },
ot_clipboard_operation_result_data_length: { 0: context, 1: buffer, 2: buffer },
ot_clipboard_operation_result_data_copy: { 0: context, 1: buffer, 2: buffer },
ot_clipboard_operation_result_error_code: { 0: context, 1: buffer, 2: buffer },
ot_clipboard_operation_result_diagnostic_length: { 0: context, 1: buffer, 2: buffer },
ot_clipboard_operation_result_diagnostic_copy: { 0: context, 1: buffer, 2: buffer },
ot_clipboard_operation_destroy: { 0: context, 1: buffer },
ot_scene_create_node: { 0: context, 1: buffer, 4: buffer },
ot_scene_destroy_node: { 0: context, 1: buffer },
ot_scene_move_node: { 0: context, 1: buffer, 2: optional },
ot_scene_set_viewport: { 0: context, 1: buffer, 2: optional },
ot_scene_set_focus: { 0: context, 1: buffer },
ot_scene_set_style: { 0: context, 1: buffer },
ot_scene_flush: { 0: context, 1: empty, 3: buffer },
ot_scene_set_measure: { 0: context, 1: buffer, 2: measure },
ot_scene_has_measure: { 0: context, 1: buffer, 2: buffer },
ot_scene_mark_dirty: { 0: context, 1: buffer },
ot_scene_get_style: { 0: context, 1: buffer, 5: buffer },
ot_scene_set_paint: { 0: context, 1: buffer, 2: buffer },
ot_scene_set_surface: { 0: context, 1: buffer, 2: optional },
ot_scene_set_box_details: { 0: context, 1: buffer, 2: buffer, 3: empty, 5: empty },
ot_scene_set_slider: { 0: context, 1: buffer, 2: buffer },
ot_scene_get_slider_thumb: { 0: context, 1: buffer, 2: buffer },
ot_scene_set_arrow: { 0: context, 1: buffer, 2: buffer, 3: optional },
ot_scene_set_editor_view: { 0: context, 1: buffer, 2: optional },
ot_scene_set_editor_options: { 0: context, 1: buffer, 2: buffer },
ot_scene_set_text: { 0: context, 1: buffer, 2: empty },
ot_scene_set_styled_text: { 0: context, 1: buffer, 2: empty, 4: empty, 6: empty },
ot_scene_set_text_options: { 0: context, 1: buffer, 2: buffer },
ot_scene_set_text_selection: { 0: context, 1: buffer, 2: buffer, 3: buffer },
ot_scene_get_text_selection: { 0: context, 1: buffer, 2: buffer },
ot_scene_get_selected_text: { 0: context, 1: buffer, 2: empty, 4: buffer },
ot_scene_get_text: { 0: context, 1: buffer, 2: empty, 4: buffer },
ot_scene_get_text_info: { 0: context, 1: buffer, 2: buffer },
ot_scene_get_text_lines: { 0: context, 1: buffer, 3: empty, 5: buffer },
ot_scene_get_layout: { 0: context, 1: buffer, 3: buffer },
ot_scene_paint: { 0: context, 1: buffer, 2: buffer, 5: buffer },
ot_scene_set_hooks: { 0: context, 1: buffer, 2: buffer },
ot_scene_frame_step_with_geometry: {
0: context,
1: buffer,
2: optional,
3: buffer,
5: optional,
7: buffer,
8: buffer,
},
ot_scene_frame_get_paint_slots: { 0: context, 1: buffer, 2: buffer, 3: buffer, 5: buffer },
ot_scene_frame_acquire_buffer_lease: { 0: context, 1: buffer, 2: buffer, 4: buffer },
ot_scene_frame_draw_buffer: { 0: context, 1: buffer, 2: buffer, 3: buffer },
ot_scene_frame_commit: { 0: context, 1: buffer, 2: buffer, 4: buffer },
ot_scene_frame_cancel: { 0: context, 1: buffer },
ot_scene_hit_test: { 0: context, 1: buffer, 4: buffer },
ot_scene_get_stats: { 0: context, 1: buffer, 2: buffer },
ot_scene_get_cursor_state: { 0: context, 1: buffer, 2: buffer },
ot_scene_measure_callback: { 7: { ffi: "ptr", nullable: "never", retention: "call", source: "native-address" } },
} satisfies PointerPolicies
// The header intentionally transports these native addresses as fixed-width u64.
// Their storage is borrowed until lease release; record copying never retains it.
export const nativeAddressFields = {
ot_image_pixels: {
pixels: { type: "u64", nullable: false, lifetime: "lease", release: "ot_image_pixels_release", element: "u8" },
},
ot_buffer_lease_snapshot: {
char_ptr: { type: "u64", nullable: false, lifetime: "lease", release: "ot_buffer_lease_release", element: "u32" },
fg_ptr: { type: "u64", nullable: false, lifetime: "lease", release: "ot_buffer_lease_release", element: "u16" },
bg_ptr: { type: "u64", nullable: false, lifetime: "lease", release: "ot_buffer_lease_release", element: "u16" },
attributes_ptr: {
type: "u64",
nullable: false,
lifetime: "lease",
release: "ot_buffer_lease_release",
element: "u32",
},
},
} as const satisfies AddressFields