Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_austenite/src/bin/pearl_demo_gen.rs

5.1 KiB, 1 run

created by r1870400018:38584, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! `pearl_demo_gen` -- THROWAWAY generator for the web reader's link + annotation demo sample.
2//!
3//! No author UI exists yet, so this exercises the create -> store -> read -> render path by hand: it
4//! builds a small two-page document that carries an external `#link`, an internal `@ref` (resolving on
5//! page two through the shipped ledger), then attaches one `highlight` and one `note` annotation to the
6//! page block hashes with the public [`PearlDoc::add_annotation`] API, and writes the result to
7//! `web/pearl-reader/samples/demo.prl`. It is a demo fixture builder, not part of the engine; delete
8//! once a real annotation authoring path lands.
9//!
10//! Usage: `pearl_demo_gen <OUTPUT.prl>`.
11
12use std::sync::Arc;
13
14use oxedyne_fe2o3_austenite::emit::pearl::{
15 Annotation,
16 AnnotationKind,
17 PearlBuilder,
18 PearlDoc,
19};
20use oxedyne_fe2o3_austenite::font::ShapedText;
21use oxedyne_fe2o3_austenite::fonts;
22use oxedyne_fe2o3_austenite::ir::{
23 Dims,
24 DrawOp,
25 Graphic,
26 LinkTarget,
27 Sp,
28};
29use oxedyne_fe2o3_austenite::ledger::{
30 Anchor,
31 AnchorId,
32 AnchorKind,
33 Ledger,
34 Position,
35};
36use oxedyne_fe2o3_austenite::page::{
37 Frame,
38 Page,
39 PageGeometry,
40 Placed,
41 PlacedKind,
42};
43
44use oxedyne_fe2o3_core::prelude::*;
45use oxedyne_fe2o3_font::{
46 face::Role,
47 shape::Dir,
48};
49use oxedyne_fe2o3_graphics::{
50 colour::Rgba,
51 path::{
52 Bounds,
53 Path,
54 },
55};
56
57fn main() -> Outcome<()> {
58 let out = match std::env::args().nth(1) {
59 Some(s) => s,
60 None => return Err(err!(
61 "Usage: pearl_demo_gen <OUTPUT.prl>"; Input, Invalid, Missing)),
62 };
63
64 let fonts = Arc::new(res!(fonts::libertinus()));
65 let geom = PageGeometry::a4();
66 let anchor = AnchorId::new(AnchorKind::Label, "sec:page-two");
67
68 // A helper placing a shaped line of body text at (x, y) points from the page's top-left, returning the
69 // dimensions so the caller can line up an annotation rectangle over it.
70 let text = |frame: &mut Frame, x: f64, y: f64, size: f64, s: &str| -> Outcome<Dims> {
71 let shaped = res!(ShapedText::new(fonts.clone(), Role::Body, Dir::Ltr, Sp::from_pt(size), s));
72 let dims = shaped.dims();
73 frame.push(Placed::new(Sp::from_pt(x), Sp::from_pt(y), dims, PlacedKind::Text(shaped)));
74 Ok(dims)
75 };
76
77 // A small filled box acting as a linked figure: the `link` leaf covers its placement box, so the reader
78 // lays a clickable hotspot there.
79 let link_box = |target: LinkTarget, w: f64, h: f64| -> Outcome<Graphic> {
80 let fill = res!(Path::rect(Bounds::new(0.0, 0.0, w as f32, h as f32)));
81 let mut g = Graphic::new(
82 vec![DrawOp::Fill { path: fill, colour: Rgba::new(219, 234, 254, 255) }],
83 Dims::new(Sp::from_pt(w), Sp::from_pt(h), Sp::ZERO));
84 g.link = Some(target);
85 Ok(g)
86 };
87
88 // Page one: a title, a highlighted body line, and two linked boxes -- one external, one internal.
89 let mut f1 = Frame::new();
90 res!(text(&mut f1, 72.0, 90.0, 20.0, "The Pearl Format"));
91 let hl = res!(text(&mut f1, 72.0, 140.0, 12.0,
92 "This line of body text carries a highlight annotation."));
93 res!(text(&mut f1, 72.0, 190.0, 12.0, "External link (opens in a new tab):"));
94 let ext = res!(link_box(LinkTarget::Uri("https://oxedyne.com".to_string()), 150.0, 16.0));
95 f1.push(Placed::new(Sp::from_pt(300.0), Sp::from_pt(180.0), ext.dims, PlacedKind::Graphic(Arc::new(ext))));
96 res!(text(&mut f1, 72.0, 230.0, 12.0, "Internal link (jumps to page two):"));
97 let int = res!(link_box(LinkTarget::Anchor(anchor.clone()), 150.0, 16.0));
98 f1.push(Placed::new(Sp::from_pt(300.0), Sp::from_pt(220.0), int.dims, PlacedKind::Graphic(Arc::new(int))));
99 let page1 = Page::new(1, geom, f1);
100
101 // Page two: the anchor target, and a line that carries the note annotation.
102 let mut f2 = Frame::new();
103 res!(text(&mut f2, 72.0, 90.0, 20.0, "Page Two"));
104 res!(text(&mut f2, 72.0, 140.0, 12.0, "The internal link scrolled the reader here."));
105 res!(text(&mut f2, 72.0, 170.0, 12.0, "This block carries a margin note annotation."));
106 let page2 = Page::new(2, geom, f2);
107
108 // The ledger fixes the internal anchor on page two, as a composition pass would.
109 let mut ledger = Ledger::new();
110 ledger.record(Anchor::new(anchor.clone(), Position::new(2, Sp::ZERO, Sp::ZERO)));
111
112 let mut builder = res!(PearlBuilder::new(&ledger, geom));
113 res!(builder.add_page(&page1));
114 res!(builder.add_page(&page2));
115 let mut doc = res!(PearlDoc::from_string(res!(builder.to_string())));
116
117 // Attach the two annotations by block hash: a highlight over the body line on page one, a whole-block
118 // note on page two.
119 let hashes = res!(doc.block_hashes());
120 let hl_h = hl.height.to_pt();
121 res!(doc.add_annotation(Annotation::new(
122 hashes[0].as_str(), AnnotationKind::Highlight,
123 "The core claim of the document.", "jason", "2026-09-17T10:00:00Z")
124 .with_rect(
125 Sp::from_pt(70.0),
126 Sp::from_pt(140.0 - 2.0),
127 Sp::from_pt(330.0),
128 Sp::from_pt(hl_h + 5.0))));
129 res!(doc.add_annotation(Annotation::new(
130 hashes[1].as_str(), AnnotationKind::Note,
131 "This is an anchored note. It reveals its text on click, and follows the block across repagination.",
132 "jason", "2026-09-17T11:00:00Z")));
133
134 res!(doc.write_file(&out));
135 println!("wrote {} ({} pages, {} annotations, links on page 1)",
136 out, res!(doc.page_count()), res!(doc.annotations()).len());
137 Ok(())
138}