1use canvas::gpu::{Paper, Viewport, page::PageScene};
2use canvas::layout::TextEngine;
3use draw::Renderer;
4use onestore::page::Page;
5use onestore::{RevisionIndex, Store, document::Document};
6use std::{env, fs, sync::Arc, time::Duration};
7
8fn main() -> Result<(), Box<dyn std::error::Error>> {
9 let mut args = env::args().skip(1).peekable();
10 let path = args
11 .next()
12 .ok_or("Expected a section snapshot, page title and new PNG path")?;
13 let title = args.next().ok_or("Expected a page title")?;
14 let destination = args.next().ok_or("Expected a new PNG path")?;
15 let origin_y: f32 = if args
16 .peek()
17 .is_some_and(|value| value != "--substitute-font")
18 {
19 args.next().unwrap().parse()?
20 } else {
21 0.0
22 };
23 let mut engine = TextEngine::default();
24 while let Some(option) = args.next() {
25 if option != "--substitute-font" {
26 return Err(format!("Unknown option: {option}").into());
27 }
28 let path = args
29 .next()
30 .ok_or("Provide a font file after --substitute-font.")?;
31 engine.register_substitute(parley::fontique::Blob::new(Arc::new(fs::read(path)?)))?;
32 }
33 let page = {
34 let bytes = fs::read(path)?;
35 let store = Store::parse(&bytes)?;
36 let index = RevisionIndex::parse(&store)?;
37 Page::from_document(&Document::parse(&index)?, &title)?
38 };
39 let margin_origin = page.margin_origin;
40 let object_count = page.objects.len();
41 let mut scene = PageScene::new(page, &mut engine)?;
42 scene.settle(None, 96.0 / 72.0, Paper::WHITE);
43 let mut primitives = Vec::new();
44 scene.append_primitives(&mut primitives, [0.0; 2], Paper::WHITE)?;
45 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());
46 let adapter = pollster::block_on(instance.request_adapter(&Default::default()))?;
47 let (device, queue) = pollster::block_on(adapter.request_device(&Default::default()))?;
48 let size = [1386_u32, 759];
49 let format = wgpu::TextureFormat::Rgba8UnormSrgb;
50 let target = device.create_texture(&wgpu::TextureDescriptor {
51 label: Some("Native page comparison"),
52 size: wgpu::Extent3d {
53 width: size[0],
54 height: size[1],
55 depth_or_array_layers: 1,
56 },
57 mip_level_count: 1,
58 sample_count: 1,
59 dimension: wgpu::TextureDimension::D2,
60 format,
61 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
62 view_formats: &[],
63 });
64 let row_bytes = (size[0] * 4).next_multiple_of(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT);
65 let buffer = device.create_buffer(&wgpu::BufferDescriptor {
66 label: Some("Native page comparison readback"),
67 size: u64::from(row_bytes) * u64::from(size[1]),
68 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
69 mapped_at_creation: false,
70 });
71 let mut renderer = Renderer::new(device, queue, format);
72 let viewport = Viewport {
73 size,
74 scale: 96.0 / 72.0,
75 origin: [
76 (36.0 - margin_origin[0]) * (96.0 / 72.0),
77 (14.4 - margin_origin[1]) * (96.0 / 72.0) + origin_y,
78 ],
79 };
80 renderer
81 .draw(
82 &target.create_view(&Default::default()).into(),
83 size,
84 [1.0; 4],
85 &[viewport.layer(&primitives)],
86 )
87 .map_err(|e| format!("Page comparison rendering failed: {e:?}"))?;
88 let mut encoder = renderer
89 .device()
90 .create_command_encoder(&Default::default());
91 encoder.copy_texture_to_buffer(
92 target.as_image_copy(),
93 wgpu::TexelCopyBufferInfo {
94 buffer: &buffer,
95 layout: wgpu::TexelCopyBufferLayout {
96 offset: 0,
97 bytes_per_row: Some(row_bytes),
98 rows_per_image: Some(size[1]),
99 },
100 },
101 wgpu::Extent3d {
102 width: size[0],
103 height: size[1],
104 depth_or_array_layers: 1,
105 },
106 );
107 renderer.queue().submit([encoder.finish()]);
108 let (sender, receiver) = std::sync::mpsc::channel();
109 buffer.map_async(wgpu::MapMode::Read, .., move |result| {
110 let _ = sender.send(result);
111 });
112 renderer.device().poll(wgpu::PollType::Wait {
113 submission_index: None,
114 timeout: Some(Duration::from_secs(10)),
115 })?;
116 receiver.recv_timeout(Duration::from_secs(10))??;
117 let mapped = buffer.get_mapped_range(..)?;
118 let pixels: Vec<_> = mapped
119 .chunks_exact(row_bytes as usize)
120 .flat_map(|row| row[..size[0] as usize * 4].iter().copied())
121 .collect();
122 let mut encoder = png::Encoder::new(fs::File::create_new(destination)?, size[0], size[1]);
123 encoder.set_color(png::ColorType::Rgba);
124 encoder.set_depth(png::BitDepth::Eight);
125 encoder.write_header()?.write_image_data(&pixels)?;
126 eprintln!(
127 "Rendered {} page objects at 100% / 96 DPI, origin Y {origin_y} px using {:?}",
128 object_count,
129 adapter.get_info()
130 );
131 Ok(())
132}