1use std::future::Future;
2use std::path::Path;
3use tokio::fs;
4
5use chromiumoxide::cdp::browser_protocol::page::PrintToPdfParams;
6use chromiumoxide::cdp::browser_protocol::target::CreateTargetParams;
7use chromiumoxide::handler::viewport::Viewport;
8use chromiumoxide::page::MediaTypeParams;
9use chromiumoxide::Page;
10use futures::StreamExt;
11
12pub use chromiumoxide::browser::Browser;
13pub use chromiumoxide::browser::BrowserConfig;
14pub mod util;
15
16pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
17
18pub trait BrowserWeb2Pdf {
19 fn web2pdf_launch_from_config(
20 browser_config: BrowserConfig,
21 ) -> impl Future<Output = Result<Browser>> + Send;
22 fn web2pdf_launch() -> impl Future<Output = Result<Browser>> + Send;
23 fn web2pdf_launch_from_executable_path(
24 path: impl AsRef<Path> + Send,
25 ) -> impl Future<Output = Result<Browser>> + Send;
26 fn close_and_wait(self) -> impl Future<Output = Result<Browser>> + Send;
27 fn web2pdf_new_page(
28 &self,
29 params: impl Into<CreateTargetParams> + Send,
30 ) -> impl Future<Output = Result<Page>> + Send;
31 fn web2pdf_load_cookie_file(
32 &self,
33 file: impl AsRef<Path> + Send,
34 ) -> impl Future<Output = Result<()>> + Send;
35}
36
37pub trait PageWeb2Pdf {
38 fn web2pdf_save_pdf_standard(
39 &self,
40 output: impl AsRef<Path> + Send,
41 ) -> impl Future<Output = chromiumoxide::Result<Vec<u8>>> + Send;
42 fn web2pdf_save_pdf_mono(
43 &self,
44 opts: PrintToPdfParams,
45 output: impl AsRef<Path> + Send,
46 ) -> impl Future<Output = chromiumoxide::Result<Vec<u8>>> + Send;
47 fn web2pdf_save_pdf_mono_standard(
48 &self,
49 output: impl AsRef<Path> + Send,
50 ) -> impl Future<Output = chromiumoxide::Result<Vec<u8>>> + Send;
51}
52
53pub trait ViewportWeb2Pdf {
54 fn web2pdf_viewport() -> Viewport;
55}
56
57impl BrowserWeb2Pdf for Browser {
58 fn web2pdf_launch_from_config(
67 browser_config: BrowserConfig,
68 ) -> impl Future<Output = Result<Browser>> + Send {
69 async {
70 let (browser, mut handler) = Browser::launch(browser_config).await?;
71
72 let _ = tokio::spawn(async move { while let Some(_) = handler.next().await {} });
74
75 tracing::debug!("Web2Pdf browser launched");
76
77 Ok(browser)
78 }
79 }
80
81 fn web2pdf_launch() -> impl Future<Output = Result<Browser>> + Send {
86 async {
87 let browser_config = BrowserConfig::builder()
89 .viewport(Some(Viewport::web2pdf_viewport()))
90 .build()?;
91
92 tracing::debug!("Web2Pdf browser launching using standard config");
93
94 Self::web2pdf_launch_from_config(browser_config).await
95 }
96 }
97
98 fn web2pdf_launch_from_executable_path(
106 path: impl AsRef<Path> + Send,
107 ) -> impl Future<Output = Result<Browser>> + Send {
108 async move {
109 let browser_config = BrowserConfig::builder()
111 .viewport(Some(Viewport::web2pdf_viewport()))
112 .chrome_executable(&path)
113 .build()?;
114
115 tracing::debug!("Web2Pdf browser launching using executable path");
116
117 Self::web2pdf_launch_from_config(browser_config).await
118 }
119 }
120
121 fn close_and_wait(mut self) -> impl Future<Output = Result<Browser>> + Send {
126 async move {
127 self.close().await?;
128 self.wait().await?;
129 Ok(self)
130 }
131 }
132
133 fn web2pdf_new_page(
141 &self,
142 params: impl Into<CreateTargetParams> + Send,
143 ) -> impl Future<Output = Result<Page>> + Send {
144 async move {
145 let page = self.new_page(params).await?;
146 page.emulate_media_type(MediaTypeParams::Print).await?;
147 tracing::debug!("Web2Pdf new page created");
148 Ok(page)
149 }
150 }
151
152 fn web2pdf_load_cookie_file(
154 &self,
155 file: impl AsRef<Path> + Send,
156 ) -> impl Future<Output = Result<()>> + Send {
157 async move {
158 let file_contents = fs::read_to_string(file).await?;
159
160 let cookies = util::parse_cookie_file(&file_contents)?;
161
162 self.set_cookies(cookies).await?;
163 Ok(())
164 }
165 }
166}
167
168impl PageWeb2Pdf for Page {
169 fn web2pdf_save_pdf_standard(
178 &self,
179 output: impl AsRef<Path> + Send,
180 ) -> impl Future<Output = chromiumoxide::Result<Vec<u8>>> + Send {
181 async move {
182 let pdf_params = PrintToPdfParams::builder()
183 .print_background(true)
184 .prefer_css_page_size(true)
185 .build();
186 let pdf = self.save_pdf(pdf_params, output).await?;
187
188 Ok(pdf)
189 }
190 }
191
192 fn web2pdf_save_pdf_mono(
203 &self,
204 mut opts: PrintToPdfParams,
205 output: impl AsRef<Path> + Send,
206 ) -> impl Future<Output = chromiumoxide::Result<Vec<u8>>> + Send {
207 async move {
208 let layout = self.layout_metrics().await?;
209
210 opts.scale = None;
211 opts.landscape = Some(false);
212
213 opts.paper_height = Some(
215 (layout.css_content_size.height / 96.0)
216 + opts.margin_top.unwrap_or(0.4)
217 + opts.margin_bottom.unwrap_or(0.4),
218 );
219 opts.paper_width = Some(
220 (layout.css_content_size.width / 96.0)
221 + opts.margin_left.unwrap_or(0.4)
222 + opts.margin_right.unwrap_or(0.4),
223 );
224
225 opts.page_ranges = Some("1".to_string());
227
228 tracing::trace!("Web2Pdf mono page layout: {:?}", layout);
229
230 let pdf = self.save_pdf(opts, output).await?;
231
232 Ok(pdf)
233 }
234 }
235
236 fn web2pdf_save_pdf_mono_standard(
246 &self,
247 output: impl AsRef<Path> + Send,
248 ) -> impl Future<Output = chromiumoxide::Result<Vec<u8>>> + Send {
249 async move {
250 let opts = PrintToPdfParams::builder()
251 .print_background(true)
252 .prefer_css_page_size(true)
253 .build();
254
255 self.web2pdf_save_pdf_mono(opts, output).await
256 }
257 }
258}
259
260impl ViewportWeb2Pdf for Viewport {
261 fn web2pdf_viewport() -> Viewport {
264 Viewport {
265 width: 717,
266 height: 1046,
267 device_scale_factor: Some(1.0),
268 emulating_mobile: false,
269 is_landscape: false,
270 has_touch: false,
271 }
272 }
273}