diff --git a/crates/paperjam-core/src/bookmarks/mod.rs b/crates/paperjam-core/src/bookmarks/mod.rs index 14d7282..20cefeb 100644 --- a/crates/paperjam-core/src/bookmarks/mod.rs +++ b/crates/paperjam-core/src/bookmarks/mod.rs @@ -170,9 +170,9 @@ fn build_outline_children( // Recursively build children let child_ids = build_outline_children(doc, &spec.children, item_id, page_map)?; - if !child_ids.is_empty() { - item_dict.set("First", Object::Reference(child_ids[0])); - item_dict.set("Last", Object::Reference(*child_ids.last().unwrap())); + if let (Some(first), Some(last)) = (child_ids.first(), child_ids.last()) { + item_dict.set("First", Object::Reference(*first)); + item_dict.set("Last", Object::Reference(*last)); let child_count = count_all_items(&spec.children); item_dict.set("Count", Object::Integer(child_count as i64)); } diff --git a/crates/paperjam-core/src/stamp/mod.rs b/crates/paperjam-core/src/stamp/mod.rs index 6fd3fa0..b12fc82 100644 --- a/crates/paperjam-core/src/stamp/mod.rs +++ b/crates/paperjam-core/src/stamp/mod.rs @@ -339,8 +339,12 @@ fn ensure_page_resources(doc: &mut lopdf::Document, page_id: ObjectId) -> Result let res_id = *res_id; if let Ok(res_obj) = doc.get_object(res_id) { let cloned = res_obj.clone(); - let page_obj = doc.get_object_mut(page_id).unwrap(); - let page_dict = page_obj.as_dict_mut().unwrap(); + let page_obj = doc + .get_object_mut(page_id) + .map_err(|e| PdfError::Annotation(format!("Cannot get page: {}", e)))?; + let page_dict = page_obj + .as_dict_mut() + .map_err(|e| PdfError::Annotation(format!("Page not a dict: {}", e)))?; page_dict.set("Resources", cloned); } } @@ -352,7 +356,9 @@ fn ensure_page_resources(doc: &mut lopdf::Document, page_id: ObjectId) -> Result let page_obj = doc .get_object(page_id) .map_err(|e| PdfError::Annotation(format!("Cannot get page: {}", e)))?; - let page_dict = page_obj.as_dict().unwrap(); + let page_dict = page_obj + .as_dict() + .map_err(|e| PdfError::Annotation(format!("Page not a dict: {}", e)))?; if let Ok(parent_ref) = page_dict.get(b"Parent") { if let Ok(parent_id) = parent_ref.as_reference() { diff --git a/crates/paperjam-core/src/table/grid.rs b/crates/paperjam-core/src/table/grid.rs index a0f8abf..ad4fc7d 100644 --- a/crates/paperjam-core/src/table/grid.rs +++ b/crates/paperjam-core/src/table/grid.rs @@ -16,10 +16,12 @@ pub fn build_from_intersections( let mut xs: Vec = intersections.iter().map(|p| p.0).collect(); let mut ys: Vec = intersections.iter().map(|p| p.1).collect(); - xs.sort_by(|a, b| a.partial_cmp(b).unwrap()); + // `total_cmp` gives a total ordering even in the presence of NaN coords + // extracted from malformed PDFs, where `partial_cmp` would panic. + xs.sort_by(|a, b| a.total_cmp(b)); xs.dedup_by(|a, b| (*a - *b).abs() < options.snap_tolerance); - ys.sort_by(|a, b| a.partial_cmp(b).unwrap()); + ys.sort_by(|a, b| a.total_cmp(b)); ys.dedup_by(|a, b| (*a - *b).abs() < options.snap_tolerance); if xs.len() < 2 || ys.len() < 2 { @@ -71,12 +73,9 @@ pub fn build_from_intersections( // Reverse so first row is top of page rows.reverse(); - let bbox = ( - *xs.first().unwrap(), - *ys.first().unwrap(), - *xs.last().unwrap(), - *ys.last().unwrap(), - ); + // Both `xs` and `ys` have len >= 2 here (guarded above), so direct + // indexing is safe and avoids the `unwrap` pattern. + let bbox = (xs[0], ys[0], xs[xs.len() - 1], ys[ys.len() - 1]); Ok(Some(Table { bbox, diff --git a/crates/paperjam-core/src/table/lattice.rs b/crates/paperjam-core/src/table/lattice.rs index 204d59e..86f19ff 100644 --- a/crates/paperjam-core/src/table/lattice.rs +++ b/crates/paperjam-core/src/table/lattice.rs @@ -165,12 +165,9 @@ fn find_intersections( } } - // Deduplicate - points.sort_by(|a, b| { - a.0.partial_cmp(&b.0) - .unwrap() - .then(a.1.partial_cmp(&b.1).unwrap()) - }); + // Deduplicate. `total_cmp` is NaN-safe; malformed PDFs can produce NaN + // coordinates that would otherwise panic `partial_cmp`. + points.sort_by(|a, b| a.0.total_cmp(&b.0).then(a.1.total_cmp(&b.1))); points.dedup_by(|a, b| (a.0 - b.0).abs() < snap && (a.1 - b.1).abs() < snap); points diff --git a/crates/paperjam-core/src/table/stream.rs b/crates/paperjam-core/src/table/stream.rs index 381c101..863348b 100644 --- a/crates/paperjam-core/src/table/stream.rs +++ b/crates/paperjam-core/src/table/stream.rs @@ -104,7 +104,7 @@ fn detect_column_boundaries(lines: &[AnalyzedLine], expected_cols: usize) -> Vec return Vec::new(); } - left_edges.sort_by(|a, b| a.partial_cmp(b).unwrap()); + left_edges.sort_by(|a, b| a.total_cmp(b)); // Simple gap-based clustering let mut boundaries = Vec::new(); diff --git a/crates/paperjam-core/src/validation/pdf_ua.rs b/crates/paperjam-core/src/validation/pdf_ua.rs index 894cb56..675643b 100644 --- a/crates/paperjam-core/src/validation/pdf_ua.rs +++ b/crates/paperjam-core/src/validation/pdf_ua.rs @@ -261,7 +261,7 @@ fn walk_struct_tree( let kids = match elem_dict.get(b"K") { Ok(Object::Array(arr)) => arr.clone(), Ok(Object::Reference(id)) => vec![Object::Reference(*id)], - Ok(Object::Dictionary(_)) => vec![elem_dict.get(b"K").unwrap().clone()], + Ok(obj @ Object::Dictionary(_)) => vec![obj.clone()], _ => return, }; diff --git a/crates/paperjam-core/src/watermark/mod.rs b/crates/paperjam-core/src/watermark/mod.rs index 59648c9..931d2a7 100644 --- a/crates/paperjam-core/src/watermark/mod.rs +++ b/crates/paperjam-core/src/watermark/mod.rs @@ -52,8 +52,10 @@ fn apply_watermark_to_page( height: f64, options: &WatermarkOptions, ) -> Result<()> { - let font_resource_name = b"WMFont1"; - let gs_resource_name = b"WMgs1"; + const FONT_RESOURCE_NAME: &str = "WMFont1"; + const GS_RESOURCE_NAME: &str = "WMgs1"; + let font_resource_name = FONT_RESOURCE_NAME.as_bytes(); + let gs_resource_name = GS_RESOURCE_NAME.as_bytes(); // 1. Create ExtGState for opacity let gs_dict = dictionary! { @@ -180,17 +182,11 @@ fn apply_watermark_to_page( // Add font match resources_dict.get_mut(b"Font") { Ok(Object::Dictionary(font_resources)) => { - font_resources.set( - std::str::from_utf8(font_resource_name).unwrap(), - Object::Reference(font_id), - ); + font_resources.set(FONT_RESOURCE_NAME, Object::Reference(font_id)); } _ => { let mut font_resources = lopdf::Dictionary::new(); - font_resources.set( - std::str::from_utf8(font_resource_name).unwrap(), - Object::Reference(font_id), - ); + font_resources.set(FONT_RESOURCE_NAME, Object::Reference(font_id)); resources_dict.set("Font", Object::Dictionary(font_resources)); } } @@ -198,17 +194,11 @@ fn apply_watermark_to_page( // Add ExtGState match resources_dict.get_mut(b"ExtGState") { Ok(Object::Dictionary(gs_resources)) => { - gs_resources.set( - std::str::from_utf8(gs_resource_name).unwrap(), - Object::Reference(gs_id), - ); + gs_resources.set(GS_RESOURCE_NAME, Object::Reference(gs_id)); } _ => { let mut gs_resources = lopdf::Dictionary::new(); - gs_resources.set( - std::str::from_utf8(gs_resource_name).unwrap(), - Object::Reference(gs_id), - ); + gs_resources.set(GS_RESOURCE_NAME, Object::Reference(gs_id)); resources_dict.set("ExtGState", Object::Dictionary(gs_resources)); } } @@ -280,8 +270,12 @@ fn ensure_page_resources(doc: &mut lopdf::Document, page_id: ObjectId) -> Result let res_id = *res_id; if let Ok(res_obj) = doc.get_object(res_id) { let cloned = res_obj.clone(); - let page_obj = doc.get_object_mut(page_id).unwrap(); - let page_dict = page_obj.as_dict_mut().unwrap(); + let page_obj = doc + .get_object_mut(page_id) + .map_err(|e| PdfError::Watermark(format!("Cannot get page: {}", e)))?; + let page_dict = page_obj + .as_dict_mut() + .map_err(|e| PdfError::Watermark(format!("Page not a dict: {}", e)))?; page_dict.set("Resources", cloned); } } @@ -293,7 +287,9 @@ fn ensure_page_resources(doc: &mut lopdf::Document, page_id: ObjectId) -> Result let page_obj = doc .get_object(page_id) .map_err(|e| PdfError::Watermark(format!("Cannot get page: {}", e)))?; - let page_dict = page_obj.as_dict().unwrap(); + let page_dict = page_obj + .as_dict() + .map_err(|e| PdfError::Watermark(format!("Page not a dict: {}", e)))?; if let Ok(parent_ref) = page_dict.get(b"Parent") { if let Ok(parent_id) = parent_ref.as_reference() {