oficina / slides Public

Clone
git clone https://amadan.net/oficina/slides

Plain git — no account needed to clone.

Download

Download this file

1//! The C ABI the Go host calls through wazero, following `core/src/abi.rs`:
2//! integers and linear-memory offsets only, every host pointer bounds-
3//! checked before a slice is built over it, and the two results of the
4//! last `slides_pdf_render` call held in module memory behind `_len`/`_read`
5//! pairs.
6//!
7//! There are no handles: a render is one call in, one PDF out, and the
8//! host instantiates a fresh module per export job (a trap or a timeout
9//! discards the instance, as in the CRDT host). `OUT` and `ERR` are
10//! therefore module-global rather than per-handle.
11//!
12//! # Inputs
13//!
14//! `svgs_ptr/len` and `fonts_ptr/len` each describe a sequence of
15//! length-prefixed frames (4-byte big-endian length, then the bytes):
16//! one frame per SVG page, one frame per TTF. The host writes them into
17//! buffers from `slides_pdf_alloc`.
18//!
19//! # Results
20//!
21//! `slides_pdf_render` returns 0 on success (PDF bytes in `out`, `err`
22//! cleared) or a negative code (message in `err`, `out` cleared).
23
24use crate::{render, split_frames, RenderError};
25use std::cell::RefCell;
26
27thread_local! {
28 static OUT: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
29 static ERR: RefCell<String> = const { RefCell::new(String::new()) };
30}
31
32/// The render was refused; the message is in the `err` read pair.
33pub const ERR_RENDER: i32 = -1;
34pub const ERR_BUFFER_TOO_SMALL: i32 = -4;
35/// A `ptr`/`len` pair this module's linear memory cannot satisfy. See
36/// `in_bounds`.
37pub const ERR_INVALID_LENGTH: i32 = -5;
38
39/// Files the outcome of a render. This is the ONLY place `OUT` and `ERR`
40/// are borrowed mutably, and it runs after `render` has returned: no
41/// `RefCell` guard is ever held across a call into usvg or svg2pdf, so a
42/// trap inside them (which does not unwind under `panic = "abort"`)
43/// cannot leave a guard behind. The host discards the instance after a
44/// trap anyway, but the rule is the same one `core/src/abi.rs` keeps.
45fn set_result(r: Result<Vec<u8>, RenderError>) -> i32 {
46 match r {
47 Ok(pdf) => {
48 OUT.with(|o| *o.borrow_mut() = pdf);
49 ERR.with(|e| e.borrow_mut().clear());
50 0
51 }
52 Err(e) => {
53 ERR.with(|m| *m.borrow_mut() = e.0);
54 OUT.with(|o| o.borrow_mut().clear());
55 ERR_RENDER
56 }
57 }
58}
59
60/// Renders the frames and files the result. Native callers (tests) use
61/// this; the wasm export below unpacks the pointers and calls it.
62pub fn render_frames(svgs: &[u8], fonts: &[u8]) -> i32 {
63 let r = (|| {
64 let svgs = split_frames(svgs)?;
65 let fonts = split_frames(fonts)?;
66 render(&svgs, &fonts)
67 })();
68 set_result(r)
69}
70
71fn layout_for(len: u32) -> Option<std::alloc::Layout> {
72 std::alloc::Layout::from_size_align(len.max(1) as usize, 1).ok()
73}
74
75#[cfg(target_arch = "wasm32")]
76fn memory_len_bytes() -> u64 {
77 const WASM_PAGE_BYTES: u64 = 65536;
78 core::arch::wasm32::memory_size(0) as u64 * WASM_PAGE_BYTES
79}
80
81#[cfg(not(target_arch = "wasm32"))]
82fn memory_len_bytes() -> u64 {
83 u64::MAX
84}
85
86/// True if `ptr..ptr+len` is representable as a Rust slice and (on wasm32)
87/// fully inside this module's currently instantiated linear memory.
88fn in_bounds(ptr: u32, len: u32) -> bool {
89 if len as u64 > isize::MAX as u64 {
90 return false;
91 }
92 match (ptr as u64).checked_add(len as u64) {
93 Some(end) => end <= memory_len_bytes(),
94 None => false,
95 }
96}
97
98/// Builds the slice for a host `ptr`/`len` pair that `in_bounds` has
99/// already accepted. A zero length never touches the pointer: the host
100/// sends `(0, 0)` for "no frames" and `slice::from_raw_parts` over a null
101/// pointer is undefined even for zero bytes.
102///
103/// # Safety
104///
105/// `in_bounds(ptr, len)` must be true, and the range must not be written
106/// by anyone for the lifetime of the returned slice.
107unsafe fn host_slice<'a>(ptr: u32, len: u32) -> &'a [u8] {
108 if len == 0 {
109 return &[];
110 }
111 std::slice::from_raw_parts(ptr as *const u8, len as usize)
112}
113
114/// Allocates `len` bytes for the host to write into; 0 on failure, which
115/// the host must check.
116#[no_mangle]
117pub extern "C" fn slides_pdf_alloc(len: u32) -> u32 {
118 match layout_for(len) {
119 // SAFETY: `layout_for` never returns a zero-size Layout.
120 Some(layout) => unsafe { std::alloc::alloc(layout) as u32 },
121 None => 0,
122 }
123}
124
125/// # Safety
126///
127/// `ptr` must be 0 or a value returned by `slides_pdf_alloc` with this
128/// exact `len`, not yet freed.
129#[no_mangle]
130pub unsafe extern "C" fn slides_pdf_free(ptr: u32, len: u32) {
131 if ptr == 0 {
132 return;
133 }
134 if let Some(layout) = layout_for(len) {
135 std::alloc::dealloc(ptr as *mut u8, layout);
136 }
137}
138
139/// # Safety
140///
141/// Both pointer/length pairs must describe byte ranges inside this
142/// module's linear memory that are not mutated during the call (checked
143/// by `in_bounds` before any slice is constructed).
144#[no_mangle]
145pub unsafe extern "C" fn slides_pdf_render(
146 svgs_ptr: u32,
147 svgs_len: u32,
148 fonts_ptr: u32,
149 fonts_len: u32,
150) -> i32 {
151 if !in_bounds(svgs_ptr, svgs_len) || !in_bounds(fonts_ptr, fonts_len) {
152 return ERR_INVALID_LENGTH;
153 }
154 let svgs = host_slice(svgs_ptr, svgs_len);
155 let fonts = host_slice(fonts_ptr, fonts_len);
156 render_frames(svgs, fonts)
157}
158
159fn len_code(bytes: &[u8]) -> i32 {
160 bytes.len().try_into().unwrap_or(i32::MAX)
161}
162
163/// # Safety
164///
165/// `out_ptr`/`out_len` must be a writable range inside linear memory
166/// (checked by `in_bounds` before any write).
167unsafe fn read_into(bytes: &[u8], out_ptr: u32, out_len: u32) -> i32 {
168 if bytes.len() > out_len as usize {
169 return ERR_BUFFER_TOO_SMALL;
170 }
171 if !in_bounds(out_ptr, out_len) {
172 return ERR_INVALID_LENGTH;
173 }
174 if !bytes.is_empty() {
175 std::ptr::copy_nonoverlapping(bytes.as_ptr(), out_ptr as *mut u8, bytes.len());
176 }
177 len_code(bytes)
178}
179
180/// Byte length of the last render's PDF (0 if none or it failed).
181#[no_mangle]
182pub extern "C" fn slides_pdf_out_len() -> i32 {
183 OUT.with(|o| len_code(&o.borrow()))
184}
185
186/// # Safety
187///
188/// See `read_into`.
189#[no_mangle]
190pub unsafe extern "C" fn slides_pdf_out_read(out_ptr: u32, out_len: u32) -> i32 {
191 OUT.with(|o| read_into(&o.borrow(), out_ptr, out_len))
192}
193
194/// Byte length of the last render's refusal message (0 if none or it
195/// succeeded).
196#[no_mangle]
197pub extern "C" fn slides_pdf_err_len() -> i32 {
198 ERR.with(|e| len_code(e.borrow().as_bytes()))
199}
200
201/// # Safety
202///
203/// See `read_into`.
204#[no_mangle]
205pub unsafe extern "C" fn slides_pdf_err_read(out_ptr: u32, out_len: u32) -> i32 {
206 ERR.with(|e| read_into(e.borrow().as_bytes(), out_ptr, out_len))
207}
208