forked from 1b-agent/opencadstudio-upstream
fix(pdfimport): reference geometry precision, file and page messages, picker automation
- The page transform is read as 16.16 fixed point held in single precision and each user-space coordinate as a single-precision value kept to six decimals, so imported coordinates match the reference to the last digit. - Paths wholly outside the page (crop box) or the current clip are left out. - The page's /UserUnit scales the import. - Unreadable files (no trailer, empty, not a PDF) print the "This file cannot be opened." block; encrypted files are asked for again silently. - Page prompt: digits only, "There is no N in <file>." past the last page; page list prompt keeps asking with "Please enter page number or *.". - Automation: pdf_import_pick (the path the next PDF picker returns) and pdf_import_dialog (page / on_screen / scale / rotation / ok / cancel). Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
218150c76a
commit
dae01bdb14
6 files changed
+199
-35
No files matched your search
@@ -201,7 +201,8 @@ pub struct PdfFileImport {
|
||||
impl PdfFileImport {
|
||||
/// Where the page's content goes: one drawing unit per page inch times
|
||||
/// the scale, or, on a page with a viewport measure (a plot), its
|
||||
/// measured units (a 1:1 millimetre plot comes in at its millimetres).
|
||||
/// measured units (a 1:1 millimetre plot comes in at its millimetres);
|
||||
/// a page's user unit multiplies either.
|
||||
pub fn placement(&self) -> Underlay {
|
||||
let mut u = Underlay::pdf();
|
||||
u.insertion_point = Vector3::new(self.insertion.x, self.insertion.y, self.insertion.z);
|
||||
@@ -256,6 +257,25 @@ enum Step {
|
||||
Rotation,
|
||||
}
|
||||
|
||||
/// What a file name that is no readable PDF (damaged, empty, not a PDF)
|
||||
/// prints before the name is asked for again.
|
||||
const UNREADABLE_FILE: &str = "This file cannot be opened.\n\
|
||||
The problem can be caused by several things:\n\
|
||||
- The data in the file is corrupted\n\
|
||||
- The file is an earlier format that is no longer used\n\
|
||||
- The file format is not supported\n\
|
||||
Try to obtain a better copy of the file, recreate the file using different settings, \
|
||||
or convert the file to a supported file format.";
|
||||
|
||||
/// A typed page number: digits only (no sign, decimals or ranges); one too
|
||||
/// large for a 32-bit count is the largest one.
|
||||
fn page_number(token: &str) -> Option<usize> {
|
||||
if token.is_empty() || !token.bytes().all(|b| b.is_ascii_digit()) {
|
||||
return None;
|
||||
}
|
||||
Some(token.parse::<u64>().map_or(i32::MAX as u64, |n| n.min(i32::MAX as u64)) as usize)
|
||||
}
|
||||
|
||||
pub struct PdfImportCommand {
|
||||
step: Step,
|
||||
handle: Handle,
|
||||
@@ -331,10 +351,24 @@ impl PdfImportCommand {
|
||||
let Ok(bytes) = std::fs::read(&path) else {
|
||||
return CmdResult::ReportError(format!("File {shown} does not exist."));
|
||||
};
|
||||
// A damaged file is repaired only when it still has its trailer (the
|
||||
// `trailer` dictionary or a cross-reference stream); a cut-off one
|
||||
// cannot be opened.
|
||||
// ponytail: byte search; a compressed stream holding these bytes
|
||||
// would pass the test.
|
||||
let has = |key: &[u8]| bytes.windows(key.len()).any(|w| w == key);
|
||||
if !has(b"trailer") && !has(b"/XRef") {
|
||||
return CmdResult::ReportError(UNREADABLE_FILE.to_string());
|
||||
}
|
||||
crate::scene::model::pdf_raster::register_source(&path, std::sync::Arc::new(bytes));
|
||||
let Some(pages) = crate::scene::model::pdf_raster::page_count(&path) else {
|
||||
// ponytail: wording for an unreadable PDF not measured.
|
||||
return CmdResult::ReportError(format!("{shown} is not a valid PDF file."));
|
||||
// An encrypted file is asked for again without a message.
|
||||
if crate::scene::model::pdf_raster::is_encrypted(&path) {
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
let Some(pages) =
|
||||
crate::scene::model::pdf_raster::page_count(&path).filter(|&pages| pages > 0)
|
||||
else {
|
||||
return CmdResult::ReportError(UNREADABLE_FILE.to_string());
|
||||
};
|
||||
self.pages = pages;
|
||||
self.file.path = path;
|
||||
@@ -351,18 +385,18 @@ impl PdfImportCommand {
|
||||
let page = if token.is_empty() {
|
||||
1
|
||||
} else {
|
||||
match token.parse::<i64>() {
|
||||
Ok(n) if n > 0 => n as usize,
|
||||
match page_number(token) {
|
||||
// Zero is asked again without a message.
|
||||
Ok(0) => return CmdResult::NeedPoint,
|
||||
_ => {
|
||||
Some(0) => return CmdResult::NeedPoint,
|
||||
Some(n) => n,
|
||||
None => {
|
||||
return CmdResult::ReportError("Please enter page number or '?'.".to_string())
|
||||
}
|
||||
}
|
||||
};
|
||||
// A page past the last one is asked again without a message.
|
||||
if page > self.pages {
|
||||
return CmdResult::NeedPoint;
|
||||
let shown = crate::entities::underlay::display_path(&self.file.path);
|
||||
return CmdResult::ReportError(format!("There is no {page} in {shown}."));
|
||||
}
|
||||
self.file.page = page.to_string();
|
||||
self.step = Step::Insertion;
|
||||
@@ -371,16 +405,23 @@ impl PdfImportCommand {
|
||||
|
||||
fn page_list(&mut self, text: &str) -> CmdResult {
|
||||
let token = text.trim();
|
||||
self.step = Step::Page;
|
||||
if token.is_empty() || token == "*" {
|
||||
self.step = Step::Page;
|
||||
let all: Vec<String> = (1..=self.pages).map(|n| n.to_string()).collect();
|
||||
return CmdResult::ReportMeasurement(all.join("\n"));
|
||||
}
|
||||
match token.parse::<usize>() {
|
||||
Ok(n) if (1..=self.pages).contains(&n) => {
|
||||
CmdResult::ReportMeasurement(format!("Found page {n}."))
|
||||
match page_number(token) {
|
||||
// Zero, or anything but a page number, is asked again.
|
||||
Some(0) => CmdResult::NeedPoint,
|
||||
None => CmdResult::ReportError("Please enter page number or *.".to_string()),
|
||||
Some(n) => {
|
||||
self.step = Step::Page;
|
||||
CmdResult::ReportMeasurement(if n <= self.pages {
|
||||
format!("Found page {n}.")
|
||||
} else {
|
||||
"No matching page found.".to_string()
|
||||
})
|
||||
}
|
||||
_ => CmdResult::ReportMeasurement("No matching page found.".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -576,7 +617,6 @@ impl CadCommand for PdfImportCommand {
|
||||
Step::Scale => match token.parse::<f64>() {
|
||||
Ok(v) => self.scale(v),
|
||||
Err(_) if token.is_empty() => self.scale(1.0),
|
||||
// ponytail: wording for a non-number scale not measured.
|
||||
Err(_) => CmdResult::ReportError(
|
||||
"Requires numeric distance or second point.".to_string(),
|
||||
),
|
||||
|
||||
Reference in new issue
Block a user