2929//! - Every call returns `1` for success and `0` for failure, and a failure
3030//! leaves a message at [`imprenta_error_ptr`]. Nothing panics across the boundary:
3131//! a malformed document is an error the host can print, not a dead instance.
32- //! - The result is read with [`imprenta_out_ptr`]/[`imprenta_out_len`] and given back with
32+ //! - The result is read block by block with [`imprenta_out_blocks`]/[`imprenta_out_block_ptr`]/
33+ //! [`imprenta_out_block_len`] and given back with
3334//! [`imprenta_out_release`], so a long-lived instance does not sit on the last PDF it
3435//! produced. The pointer is only good until the next allocation grows the
3536//! memory, which is why the JavaScript binding copies rather than handing a
@@ -56,7 +57,7 @@ thread_local! {
5657 } ;
5758 /// The finished document, kept until the host has read it or released it.
5859 static OUT : RefCell <Outcome > = const {
59- RefCell :: new( Outcome { pdf: Vec :: new ( ) , pages: 0 , diagnostics: Vec :: new( ) } )
60+ RefCell :: new( Outcome { pdf: imprenta_pdf :: Pdf :: empty ( ) , pages: 0 , diagnostics: Vec :: new( ) } )
6061 } ;
6162 /// The diagnostics, as the JSON array the host reads.
6263 static DIAGNOSTICS : RefCell <Vec <u8 >> = const { RefCell :: new( Vec :: new( ) ) } ;
@@ -218,7 +219,7 @@ pub unsafe extern "C" fn imprenta_assets_image(
218219
219220// ── One document, declared whole ────────────────────────────────────────────
220221
221- /// Renders a declared document. Read the result with [`imprenta_out_ptr `].
222+ /// Renders a declared document. Read the result with [`imprenta_out_block_ptr `].
222223///
223224/// # Safety
224225///
@@ -251,7 +252,7 @@ pub unsafe extern "C" fn imprenta_render(ir_ptr: *const u8, ir_len: usize) -> i3
251252// exists and why it packs rather than estimating.
252253
253254/// Measures a run of table rows, keeps them, and hands back one height each
254- /// as little-endian `f32` readable with [`imprenta_out_ptr `].
255+ /// as little-endian `f32` readable with [`imprenta_out_block_ptr `].
255256///
256257/// Only the heights cross: four bytes a row, against the kilobytes the row
257258/// itself weighs. The rows stay here for [`imprenta_fragment_measured`], which
@@ -302,7 +303,8 @@ pub unsafe extern "C" fn imprenta_measure_rows(
302303 MEASURED . with ( |slot| * slot. borrow_mut ( ) = rows) ;
303304 OUT . with ( |slot| {
304305 let mut held = slot. borrow_mut ( ) ;
305- held. pdf = out;
306+ // Already contiguous, so it crosses as one block.
307+ held. pdf = out. into ( ) ;
306308 held. pages = count;
307309 held. diagnostics = Vec :: new ( ) ;
308310 } ) ;
@@ -445,7 +447,8 @@ pub unsafe extern "C" fn imprenta_plan(
445447 OUT . with ( |slot| {
446448 let mut held = slot. borrow_mut ( ) ;
447449 held. pages = json. len ( ) ;
448- held. pdf = bytes;
450+ // Already contiguous, so it crosses as one block.
451+ held. pdf = bytes. into ( ) ;
449452 held. diagnostics = Vec :: new ( ) ;
450453 } ) ;
451454 succeed ( )
@@ -473,7 +476,7 @@ pub unsafe extern "C" fn imprenta_merge_push(ptr: *const u8, len: usize) -> i32
473476 succeed ( )
474477}
475478
476- /// Merges what was pushed and publishes the file at [`imprenta_out_ptr `].
479+ /// Merges what was pushed and publishes the file at [`imprenta_out_block_ptr `].
477480#[ unsafe( no_mangle) ]
478481pub extern "C" fn imprenta_merge_finish ( ) -> i32 {
479482 let fragments = FRAGMENTS . with ( |slot| std:: mem:: take ( & mut * slot. borrow_mut ( ) ) ) ;
@@ -484,7 +487,7 @@ pub extern "C" fn imprenta_merge_finish() -> i32 {
484487 // caught here rather than by whoever opens the document.
485488 let pages = merge:: pages_in ( & pdf) . unwrap_or ( 0 ) ;
486489 publish ( Outcome {
487- pdf,
490+ pdf : pdf . into ( ) ,
488491 pages,
489492 diagnostics : Vec :: new ( ) ,
490493 } )
@@ -563,7 +566,7 @@ pub extern "C" fn imprenta_stream_pending() -> usize {
563566 PRINTER . with ( |slot| slot. borrow ( ) . as_ref ( ) . map_or ( 0 , Printer :: pending) )
564567}
565568
566- /// Paints what is left and closes the document. Read it with [`imprenta_out_ptr `].
569+ /// Paints what is left and closes the document. Read it with [`imprenta_out_block_ptr `].
567570#[ unsafe( no_mangle) ]
568571pub extern "C" fn imprenta_stream_finish ( ) -> i32 {
569572 let printer = PRINTER . with ( |slot| slot. borrow_mut ( ) . take ( ) ) ;
@@ -578,14 +581,35 @@ pub extern "C" fn imprenta_stream_finish() -> i32 {
578581
579582// ── Reading the result ──────────────────────────────────────────────────────
580583
584+ /// How many pieces the result is in. Read them with
585+ /// [`imprenta_out_block_ptr`] and [`imprenta_out_block_len`]; concatenated in
586+ /// order they are the file.
587+ ///
588+ /// Blocks rather than one buffer, deliberately: handing back a contiguous
589+ /// result meant joining the writer's blocks inside linear memory, which held
590+ /// two copies of the finished document at the peak — and linear memory never
591+ /// shrinks, so the peak was the footprint (issue #7). The host assembles the
592+ /// copy on its own heap, where the memory goes back.
593+ #[ unsafe( no_mangle) ]
594+ pub extern "C" fn imprenta_out_blocks ( ) -> usize {
595+ OUT . with ( |slot| slot. borrow ( ) . pdf . block_count ( ) )
596+ }
597+
598+ /// Where the `index`-th piece starts. Null past the end, never a trap.
581599#[ unsafe( no_mangle) ]
582- pub extern "C" fn imprenta_out_ptr ( ) -> * const u8 {
583- OUT . with ( |slot| slot. borrow ( ) . pdf . as_ptr ( ) )
600+ pub extern "C" fn imprenta_out_block_ptr ( index : usize ) -> * const u8 {
601+ OUT . with ( |slot| {
602+ slot. borrow ( )
603+ . pdf
604+ . block ( index)
605+ . map_or ( std:: ptr:: null ( ) , <[ u8 ] >:: as_ptr)
606+ } )
584607}
585608
609+ /// How long the `index`-th piece is. Zero past the end.
586610#[ unsafe( no_mangle) ]
587- pub extern "C" fn imprenta_out_len ( ) -> usize {
588- OUT . with ( |slot| slot. borrow ( ) . pdf . len ( ) )
611+ pub extern "C" fn imprenta_out_block_len ( index : usize ) -> usize {
612+ OUT . with ( |slot| slot. borrow ( ) . pdf . block ( index ) . map_or ( 0 , < [ u8 ] > :: len ) )
589613}
590614
591615#[ unsafe( no_mangle) ]
@@ -625,7 +649,7 @@ pub extern "C" fn imprenta_error_len() -> usize {
625649pub extern "C" fn imprenta_out_release ( ) -> i32 {
626650 OUT . with ( |slot| {
627651 let mut out = slot. borrow_mut ( ) ;
628- out. pdf = Vec :: new ( ) ;
652+ out. pdf = imprenta_pdf :: Pdf :: empty ( ) ;
629653 out. pages = 0 ;
630654 out. diagnostics = Vec :: new ( ) ;
631655 } ) ;
@@ -669,8 +693,25 @@ mod tests {
669693 give_back ( data) ;
670694 }
671695
696+ /// The result as the host assembles it: the blocks, in order.
672697 fn read_out ( ) -> Vec < u8 > {
673- unsafe { std:: slice:: from_raw_parts ( imprenta_out_ptr ( ) , imprenta_out_len ( ) ) } . to_vec ( )
698+ let mut out = Vec :: with_capacity ( out_len ( ) ) ;
699+ for index in 0 ..imprenta_out_blocks ( ) {
700+ out. extend_from_slice ( unsafe {
701+ std:: slice:: from_raw_parts (
702+ imprenta_out_block_ptr ( index) ,
703+ imprenta_out_block_len ( index) ,
704+ )
705+ } ) ;
706+ }
707+ out
708+ }
709+
710+ /// The total the blocks add up to, which is what `out_len` used to say.
711+ fn out_len ( ) -> usize {
712+ ( 0 ..imprenta_out_blocks ( ) )
713+ . map ( |index| imprenta_out_block_len ( index) )
714+ . sum ( )
674715 }
675716
676717 fn read_error ( ) -> String {
@@ -815,17 +856,36 @@ mod tests {
815856 give_back ( ir) ;
816857 }
817858
859+ #[ test]
860+ fn a_block_index_past_the_end_is_null_and_zero_rather_than_a_trap ( ) {
861+ // The index arrives over the boundary, where a panic kills the
862+ // instance. A host that miscounts must get an answer it can check.
863+ let _lock = guard ( ) ;
864+ load_roman ( ) ;
865+ let ir = put ( HELLO ) ;
866+ unsafe { imprenta_render ( ir. 0 , ir. 1 ) } ;
867+ let count = imprenta_out_blocks ( ) ;
868+
869+ assert ! ( count >= 1 ) ;
870+ assert ! ( imprenta_out_block_ptr( count) . is_null( ) ) ;
871+ assert_eq ! ( imprenta_out_block_len( count) , 0 ) ;
872+
873+ imprenta_out_release ( ) ;
874+ give_back ( ir) ;
875+ }
876+
818877 #[ test]
819878 fn releasing_the_result_gives_the_bytes_back ( ) {
820879 let _lock = guard ( ) ;
821880 load_roman ( ) ;
822881 let ir = put ( HELLO ) ;
823882 unsafe { imprenta_render ( ir. 0 , ir. 1 ) } ;
824- assert ! ( imprenta_out_len ( ) > 0 ) ;
883+ assert ! ( out_len ( ) > 0 ) ;
825884
826885 imprenta_out_release ( ) ;
827886
828- assert_eq ! ( imprenta_out_len( ) , 0 ) ;
887+ assert_eq ! ( out_len( ) , 0 ) ;
888+ assert_eq ! ( imprenta_out_blocks( ) , 0 ) ;
829889 assert_eq ! ( imprenta_out_pages( ) , 0 ) ;
830890 assert_eq ! ( imprenta_diagnostics_len( ) , 0 ) ;
831891 give_back ( ir) ;
0 commit comments