mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-14 19:27:04 -04:00
demo-pdf: two fixes so xtask demo-pdf and xtask screenshots work on Windows (#954)
* demo-pdf: build a working file:// URL on Windows Windows Path::canonicalize returns a verbatim \?\C:\... path. After the backslash swap that is //?/C:/..., which the Unix/Windows branch then misreads as an absolute Unix path, producing file:////?/C:/... Chrome fails to load that URL and prints one blank page, so on Windows cargo xtask demo-pdf and cargo xtask screenshots always fail with "Chrome produced 1 pages but the showcase layout expects 12". Strip the //?/ prefix before classifying, and add a regression test. * demo-pdf: don't accept Skia's incremental %%EOF writes as a finished PDF Chrome writes the print-to-pdf output incrementally and every increment ends with its own %%EOF, so the completion poll accepted the first ~29 KB increment (one page of twelve) as final and killed Chrome mid-print. Require the file size to stay quiet for 10 consecutive 500 ms polls before treating the file as finished; waiting for process exit alone is not enough because some Chrome builds linger after writing. --------- Co-authored-by: pechnikov2026-star <[email protected]>
This commit is contained in:
1 parent
43d16ef315
commit
66edead348
2 files changed
+35
-5
No files matched your search
@@ -94,6 +94,10 @@ pub fn print_to_pdf(chrome: &Path, html: &Path, pdf: &Path, log: &Path) -> Resul
|
||||
|
||||
let started = Instant::now();
|
||||
let mut last_len = None;
|
||||
// Chrome's Skia back-end writes the PDF incrementally, and every increment ends with its own
|
||||
// `%%EOF`: a stable file that looks complete can still grow while later pages rasterise. Only
|
||||
// treat the file as finished after the size has been quiet for several consecutive polls.
|
||||
let mut quiet_polls = 0;
|
||||
loop {
|
||||
if let Some(status) = child.try_wait()? {
|
||||
if is_complete(pdf) {
|
||||
@@ -102,10 +106,15 @@ pub fn print_to_pdf(chrome: &Path, html: &Path, pdf: &Path, log: &Path) -> Resul
|
||||
bail!("Chrome exited with {status} without writing {}; see {}", pdf.display(), log.display());
|
||||
}
|
||||
let len = fs::metadata(pdf).map(|m| m.len()).ok();
|
||||
if len.is_some() && len == last_len && is_complete(pdf) {
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
return Ok(());
|
||||
if len == last_len && len.is_some() && is_complete(pdf) {
|
||||
quiet_polls += 1;
|
||||
if quiet_polls >= 10 {
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
quiet_polls = 0;
|
||||
}
|
||||
last_len = len;
|
||||
if started.elapsed() > Duration::from_secs(120) {
|
||||
|
||||
@@ -97,7 +97,13 @@ fn repo_root() -> PathBuf {
|
||||
/// A `file://` URL for an absolute path, percent-encoding unsafe bytes.
|
||||
pub(crate) fn file_url(path: &Path) -> String {
|
||||
let abs = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
|
||||
let s = abs.to_string_lossy().replace('\\', "/");
|
||||
let mut s = abs.to_string_lossy().replace('\\', "/");
|
||||
// Windows `canonicalize` returns a verbatim `\\?\C:\...` path; after the slash swap that is
|
||||
// `//?/C:/...`, which must not be mistaken for an absolute Unix path, or the URL Chrome is
|
||||
// handed becomes `file:////?/C:/...` and it prints one blank page instead of the document.
|
||||
if let Some(rest) = s.strip_prefix("//?/") {
|
||||
s = rest.to_string();
|
||||
}
|
||||
let mut url = String::from(if s.starts_with('/') { "file://" } else { "file:///" });
|
||||
for byte in s.bytes() {
|
||||
match byte {
|
||||
@@ -185,3 +191,18 @@ fn foreign_fonts(doc: &lopdf::Document) -> Vec<String> {
|
||||
}
|
||||
bad.into_iter().collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// On Windows `canonicalize` returns a verbatim `\?\C:\...` path; the URL must not carry the
|
||||
/// `//?/` prefix over (regression test for `file:////?/C:/...`, which Chrome prints as one
|
||||
/// blank page).
|
||||
#[test]
|
||||
fn file_url_has_no_verbatim_prefix() {
|
||||
let url = file_url(&std::env::temp_dir());
|
||||
assert!(!url.contains("//?/"), "{url}");
|
||||
assert!(url.starts_with("file:///"), "{url}");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user