1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
//! Shared plumbing for the `un*` document extraction family — [unpdf], [undoc], [unhwp].
//!
//! [unpdf]: https://crates.io/crates/unpdf
//! [undoc]: https://crates.io/crates/undoc
//! [unhwp]: https://crates.io/crates/unhwp
//!
//! # What this crate is for
//!
//! Those three libraries have the same shape — parse a container, build an intermediate
//! representation, render Markdown — and expose it the same way: a Rust API, a C ABI, and
//! C#, Python and WebAssembly bindings over that ABI. The *document* parts differ
//! entirely and belong in each library. The parts that do not differ live here: thread-local
//! last-error storage, catching panics before they cross `extern "C"`, and the integer space
//! the error classifications live in. VLM/AI-assisted extraction is also here now, as an
//! opt-in feature — see [Modules](#modules) below.
//!
//! # Scope, and what is kept out
//!
//! - **No domain content.** No document model, no rendering, no format knowledge. If a
//! type would mention a page, a paragraph or a spreadsheet cell, it belongs upstream.
//! Facts about the *output* syntax are not domain content: where CommonMark lets an
//! emphasis delimiter stand is the same answer for every writer ([`markdown`]), and so is
//! how RFC 4180 quotes a field and where a merged cell's value goes on a CSV grid ([`csv`] —
//! its `Cell` is the writer's input shape, not a document model).
//! - **The crate root has no dependencies.** Three published cdylibs link it statically, so
//! a dependency here is a dependency in all of them. Everything in [`csv`], [`ffi`], [`kind`],
//! [`markdown`] and [`scaffold`] is `std`-only. Optional capabilities each bring their own dependency
//! behind their own feature flag instead of widening this baseline.
//!
//! It is `std`-only at the root — [`ffi::LastErrorSlot`] holds a `CString` and lives in a
//! `thread_local!` — which suits every consumer including `wasm32-unknown-unknown`
//! (the `refine` feature does too; `ai` is native-only — see its module docs).
//!
//! # Modules
//!
//! - [`csv`] — a table as CSV (RFC 4180) or tab-separated text: [`csv::to_csv`] and
//! [`csv::to_delimited`] lay the rows on their grid, a merged cell's value at its top-left
//! position and the positions it covers empty, and quote the fields.
//! - [`grid`] — where a table's cells sit: [`grid::place`] gives each cell's grid column and
//! the table's width from the cells' spans, a merged cell recorded once on the cell that owns
//! it.
//! - [`kind`] — the error-kind values the family already shares, and the bands that keep
//! future ones from colliding. Read its docs before adding a kind anywhere.
//! - [`ffi`] — [`ffi::LastErrorSlot`], the [`ffi::catch`] panic guard, and the macros that
//! export the ABI over them.
//! - [`markdown`] — CommonMark syntax facts the Markdown writers share:
//! [`markdown::emphasis_span`], the part of a styled run emphasis delimiters can wrap;
//! [`markdown::image`], [`markdown::link`] and [`markdown::link_destination`], which write a
//! picture's description, a link's text and title, and a link's target so that they stay
//! inside the construct and read back as given.
//! - [`refine`] *(feature `refine`)* — a lossless, idempotent markdown shape-refinement pass:
//! table shape normalization, ordered-list renumbering, link/image path normalization,
//! frontmatter normalization, and section anchors. Composes with a renderer's own
//! `cleanup` stage in either order.
//! - [`ai`] *(feature `ai`, native only)* — VLM-based image understanding and
//! AI-assisted markdown refine, active only when a caller supplies an API key.
//! - [`scaffold`] — macros that assemble an entry point out of those primitives. Reach for
//! these before writing the five steps by hand; the sentinel differs by return type and
//! that is where hand-written entry points drift.
//!
//! # A minimal library
//!
//! ```
//! use std::ffi::c_int;
//! use unparser_shared::ffi::{self, FfiError, LastErrorSlot};
//! use unparser_shared::kind;
//!
//! // The slot lives here, not in unparser-shared — that is what keeps it per-library. See
//! // `ffi`'s module docs.
//! thread_local! {
//! static LAST_ERROR: LastErrorSlot = const { LastErrorSlot::new() };
//! }
//!
//! unparser_shared::export_last_error_abi!(LAST_ERROR, mylib_last_error, mylib_last_error_kind);
//!
//! /// Returns the page count, or -1 on failure.
//! #[no_mangle]
//! pub extern "C" fn mylib_page_count(handle: *const u8) -> c_int {
//! let result: Result<c_int, FfiError> = ffi::catch(|| {
//! if handle.is_null() {
//! return Err(ffi::invalid_argument("handle was null"));
//! }
//! Ok(7)
//! });
//!
//! match result {
//! Ok(count) => {
//! LAST_ERROR.with(|slot| slot.clear());
//! count
//! }
//! Err(error) => {
//! LAST_ERROR.with(|slot| slot.set_error(&error));
//! -1
//! }
//! }
//! }
//!
//! assert_eq!(mylib_page_count(std::ptr::null()), -1);
//! assert_eq!(mylib_last_error_kind(), kind::INVALID_ARGUMENT);
//!
//! let data = 0u8;
//! assert_eq!(mylib_page_count(&data), 7);
//! assert_eq!(mylib_last_error_kind(), kind::NONE);
//! ```