From 7e1e5c0dbb74071f2e288975faccd6fc54574c0e Mon Sep 17 00:00:00 2001 From: mtlcl410 <2368091+mtlcl410@users.noreply.github.com> Date: Mon, 2 Nov 2026 09:04:14 +0800 Subject: [PATCH] M2: remove two rebase backups committed by mistake crates/ui-egui/src/panels/right.rs.orig and crates/automation/src/json.rs.orig were left by a merge tool in 211718668. Neither is compiled (the modules load right.rs and json.rs), and right.rs.orig still drew the old flat Layers list, so a reader searching the tree could take it for the code. Review on #4396 (CodeRabbit, crates/ui-egui/src/panels/right.rs.orig). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv --- crates/automation/src/json.rs.orig | 391 ------------- crates/ui-egui/src/panels/right.rs.orig | 731 ------------------------ 2 files changed, 1122 deletions(-) delete mode 100644 crates/automation/src/json.rs.orig delete mode 100644 crates/ui-egui/src/panels/right.rs.orig diff --git a/crates/automation/src/json.rs.orig b/crates/automation/src/json.rs.orig deleted file mode 100644 index 9e93d4c347..0000000000 --- a/crates/automation/src/json.rs.orig +++ /dev/null @@ -1,391 +0,0 @@ -use super::error::{Result, ToolError}; -use super::{Document, Value, comments, json}; - -/// How far from the page's corner a bookmark's position may be given, in points (a PDF page is at -/// most 14 400 across). -const MAX_BOOKMARK_POSITION: f64 = 1_000_000.0; - -// ---- JSON helpers ---------------------------------------------------------------------------- - -/// The most bookmark levels `bookmark_from_text_styles` takes. -const MAX_STYLE_LEVELS: usize = 9; - -/// bookmark_from_text_styles' `levels`: one style per bookmark level, the first level 1, each -/// `{ font, size, bold, italic }` as text_styles lists them. -pub(crate) fn text_style_levels(a: &Args) -> Result> { - let levels = - a.get("levels").and_then(Value::as_array).ok_or_else(|| ToolError::InvalidArgs("levels must list a style for each bookmark level".into()))?; - if levels.is_empty() || levels.len() > MAX_STYLE_LEVELS { - return Err(ToolError::InvalidArgs(format!("levels must list 1 to {MAX_STYLE_LEVELS} styles"))); - } - levels - .iter() - .enumerate() - .map(|(i, v)| { - let bad = |what: &str| ToolError::InvalidArgs(format!("levels[{i}]: {what}")); - let font = - v.get("font").and_then(Value::as_str).filter(|f| !f.is_empty()).ok_or_else(|| bad("font must be a font name from text_styles"))?; - let size = - v.get("size").and_then(Value::as_f64).filter(|s| s.is_finite() && *s > 0.0).ok_or_else(|| bad("size must be a positive number"))?; - let flag = |key: &str| match v.get(key) { - None | Some(Value::Null) => Ok(false), - Some(Value::Bool(b)) => Ok(*b), - Some(_) => Err(bad(&format!("{key} must be true or false"))), - }; - Ok(pdfcraft_engine::text_style_bookmarks::TextStyle { font: font.to_string(), size, bold: flag("bold")?, italic: flag("italic")? }) - }) - .collect() -} - -pub(crate) fn parse_style_ranges(value: Option<&Value>) -> Result> { - let Some(value) = value else { return Ok(Vec::new()) }; - let values = value.as_array().filter(|v| v.len() <= 1024).ok_or_else(|| Args::wrong("ranges", "an array of at most 1024 style objects"))?; - values - .iter() - .map(|v| { - let a = Args(v); - let bounds = a - .get("range") - .and_then(Value::as_array) - .filter(|v| v.len() == 2) - .ok_or_else(|| Args::wrong("range", "[start,end] UTF-8 byte offsets"))?; - let start = bounds - .first() - .and_then(Value::as_u64) - .and_then(|n| usize::try_from(n).ok()) - .ok_or_else(|| Args::wrong("range", "nonnegative byte offsets"))?; - let end = bounds - .get(1) - .and_then(Value::as_u64) - .and_then(|n| usize::try_from(n).ok()) - .ok_or_else(|| Args::wrong("range", "nonnegative byte offsets"))?; - let family = a - .opt_str("font")? - .map(|f| match f { - "helvetica" => Ok(pdfcraft_engine::FontFamily::Helvetica), - "times" => Ok(pdfcraft_engine::FontFamily::Times), - "courier" => Ok(pdfcraft_engine::FontFamily::Courier), - _ => Err(Args::wrong("font", "helvetica, times or courier")), - }) - .transpose()? - .map(|f| -> Result<_> { Ok((f, a.opt_bool("bold")?.unwrap_or(false), a.opt_bool("italic")?.unwrap_or(false))) }) - .transpose()?; - Ok(pdfcraft_engine::RangeStyle { - range: start..end, - family, - resource: a.opt_str("font_resource")?.map(str::to_owned), - size: a.opt_num("size")?, - color: a.opt_str("color")?.map(comments::parse_color).transpose()?, - underline: a.opt_bool("underline")?, - char_spacing: a.opt_num("char_spacing")?, - scale: a.opt_num("scale")?, - }) - }) - .collect() -} - -/// Typed access to validated arguments. -pub(crate) struct Args<'a>(pub(crate) &'a Value); - -impl Args<'_> { - pub(crate) fn get(&self, key: &str) -> Option<&Value> { - self.0.get(key).filter(|v| !v.is_null()) - } - pub(crate) fn missing(key: &str) -> ToolError { - ToolError::InvalidArgs(format!("missing argument {key}")) - } - pub(crate) fn wrong(key: &str, what: &str) -> ToolError { - ToolError::InvalidArgs(format!("{key} must be {what}")) - } - pub(crate) fn str(&self, key: &str) -> Result<&str> { - self.opt_str(key)?.ok_or_else(|| Self::missing(key)) - } - pub(crate) fn opt_str(&self, key: &str) -> Result> { - self.get(key).map(|v| v.as_str().ok_or_else(|| Self::wrong(key, "a string"))).transpose() - } - pub(crate) fn int(&self, key: &str) -> Result { - self.opt_int(key)?.ok_or_else(|| Self::missing(key)) - } - pub(crate) fn opt_int(&self, key: &str) -> Result> { - self.get(key).map(|v| v.as_i64().ok_or_else(|| Self::wrong(key, "an integer"))).transpose() - } - pub(crate) fn opt_num(&self, key: &str) -> Result> { - self.get(key).map(|v| v.as_f64().filter(|f| f.is_finite()).ok_or_else(|| Self::wrong(key, "a number"))).transpose() - } - pub(crate) fn opt_bool(&self, key: &str) -> Result> { - self.get(key).map(|v| v.as_bool().ok_or_else(|| Self::wrong(key, "true or false"))).transpose() - } - pub(crate) fn ints(&self, key: &str) -> Result> { - self.opt_ints(key)?.ok_or_else(|| Self::missing(key)) - } - pub(crate) fn opt_ints(&self, key: &str) -> Result>> { - let Some(v) = self.get(key) else { return Ok(None) }; - let arr = v.as_array().ok_or_else(|| Self::wrong(key, "an array of integers"))?; - arr.iter().map(|x| x.as_i64().ok_or_else(|| Self::wrong(key, "an array of integers"))).collect::>>().map(Some) - } - /// A 1-based bookmark path → 0-based indices. - pub(crate) fn path(&self, key: &str) -> Result> { - let p = self.opt_path(key)?.ok_or_else(|| Self::missing(key))?; - if p.is_empty() { - return Err(ToolError::InvalidArgs(format!("{key} must not be empty"))); - } - Ok(p) - } - pub(crate) fn opt_path(&self, key: &str) -> Result>> { - let Some(v) = self.opt_ints(key)? else { return Ok(None) }; - v.iter().map(|i| if *i >= 1 { Ok(*i as usize - 1) } else { Err(Self::wrong(key, "1-based positions")) }).collect::>>().map(Some) - } - /// A bookmark's `left` or `top`: points on the displayed page, `None` when left out, an error - /// when it is not a number or lies absurdly far off the page. - pub(crate) fn bookmark_coordinate(&self, key: &str) -> Result> { - match self.opt_num(key)? { - None => Ok(None), - Some(v) if v.is_finite() && v.abs() <= MAX_BOOKMARK_POSITION => Ok(Some(v as f32)), - Some(_) => Err(ToolError::InvalidArgs(format!("{key} must be between -{MAX_BOOKMARK_POSITION} and {MAX_BOOKMARK_POSITION} points"))), - } - } - pub(crate) fn strs(&self, key: &str) -> Result> { - let v = self.get(key).ok_or_else(|| Self::missing(key))?; - let arr = v.as_array().ok_or_else(|| Self::wrong(key, "an array of strings"))?; - arr.iter().map(|x| x.as_str().ok_or_else(|| Self::wrong(key, "an array of strings"))).collect() - } -} - -/// The name-tree keys of a document's own attachments, leaving out the files attached to pages as comments. -pub(crate) fn document_attachment_keys(doc: &Document) -> Vec { - doc.info - .attachments - .iter() - .filter_map(|x| match &x.source { - pdfcraft_render::AttachmentSource::Document { key } => Some(key.clone()), - pdfcraft_render::AttachmentSource::Annotation { .. } => None, - }) - .collect() -} - -/// Bookmark rows in listing order as JSON: a row holds the rows that follow it on the page at the -/// next level down in `children`, and paths and pages are 1-based. -pub(crate) fn nest_rows(rows: &[pdfcraft_engine::BookmarkRow], pages: &[pdfcraft_render::PageInfo]) -> Vec { - // The open rows, outermost first, with their depth and the children gathered so far. - let mut open: Vec<(usize, Value, Vec)> = Vec::new(); - let mut roots = Vec::new(); - for row in rows { - let depth = row.path.len().saturating_sub(1); - while open.last().is_some_and(|(d, _, _)| *d >= depth) { - close_row(&mut open, &mut roots); - } - let path: Vec = row.path.iter().map(|p| p.saturating_add(1)).collect(); - let mut fields = json!({ "path": path, "title": row.title, "page": row.page.map(|p| p.saturating_add(1)), "open": row.open }); - // Only what differs from the defaults (black, regular), as the PDF itself stores it. - if let Some(color) = row.color { - fields["color"] = json!(color); - } - if row.bold { - fields["bold"] = json!(true); - } - if row.italic { - fields["italic"] = json!(true); - } - if let Some(destination) = row.page.and_then(|p| pages.get(p)).and_then(|p| destination(&row.view, p)) { - fields["destination"] = destination; - } - open.push((depth, fields, Vec::new())); - } - while !open.is_empty() { - close_row(&mut open, &mut roots); - } - roots -} - -/// Where on its page a bookmark or link goes, in points from the top-left of the displayed page -/// like every `rect`: the destination type (`XYZ`, `Fit`, `FitH`, `FitV`, `FitR`) and its -/// position, `null` where the destination leaves it unspecified (`/XYZ null null null` keeps the -/// reader's position and zoom). `None` for a destination without a type. -pub(crate) fn destination(view: &pdfcraft_render::DestView, page: &pdfcraft_render::PageInfo) -> Option { - use pdfcraft_render::DestView; - let points = |left: Option, top: Option| { - let [x, y] = page.dest_fraction(left, top, 0); - let round = |v: f32| f64::from((v * 100.0).round() / 100.0); - (x.map(|x| round(x * page.width)), y.map(|y| round(y * page.height))) - }; - Some(match view { - DestView::Top => return None, - DestView::Xyz { left, top, zoom } => { - let (x, y) = points(*left, *top); - json!({ "fit": "XYZ", "left": x, "top": y, "zoom": zoom.map(f64::from) }) - } - DestView::Fit => json!({ "fit": "Fit" }), - DestView::FitH { top } => { - let (x, y) = points(None, *top); - json!({ "fit": "FitH", "left": x, "top": y }) - } - DestView::FitV { left } => { - let (x, y) = points(*left, None); - json!({ "fit": "FitV", "left": x, "top": y }) - } - DestView::FitR { rect } => json!({ "fit": "FitR", "rect": comments::rect_to_view(page, *rect) }), - }) -} - -/// Closes the innermost open row: it joins the row above it as a child, or the top level. -fn close_row(open: &mut Vec<(usize, Value, Vec)>, roots: &mut Vec) { - if let Some((_, mut fields, children)) = open.pop() { - fields["children"] = Value::Array(children); - match open.last_mut() { - Some((_, _, siblings)) => siblings.push(fields), - None => roots.push(fields), - } - } -} - -pub(crate) fn one_based(pages: &[i64]) -> Result> { - pages - .iter() - .map(|p| if *p >= 1 { Ok(*p as usize - 1) } else { Err(ToolError::InvalidArgs(format!("page numbers start at 1 (got {p})"))) }) - .collect() -} - -/// The document's security as the next save writes it (never includes passwords). -pub(crate) fn security(d: &Document) -> Value { - match d.security_summary() { - None => json!({ "protected": false }), - Some(s) => { - let p = s.permissions; - json!({ - "protected": true, - "method": s.method, - "pending": s.pending, - "printing": if !p.print() { "none" } else if p.print_high_quality() { "high" } else { "low" }, - "modify": p.modify(), "assemble": p.assemble(), "copy": p.copy(), "annotate": p.annotate(), - "fill_forms": p.fill_forms(), "accessibility": p.extract_for_accessibility(), - }) - } - } -} - -/// What every tool that changes a document returns. -pub(crate) fn summary(d: &Document) -> Value { - json!({ - "doc": d.id.0, - "name": d.name, - "path": d.path, - "pages": d.info.pages.len(), - "dirty": d.dirty, - "editable": d.read_only_reason.is_none(), - "read_only_reason": d.read_only_reason, - "encrypted": d.info.encrypted, - "undo": d.can_undo(), - "redo": d.can_redo(), - }) -} - -pub(crate) fn info(d: &Document) -> Value { - let i = &d.info; - let page1 = |p: usize| p + 1; - let mut security = security(d); - if let Some(s) = d.security_summary() { - security["opened_as_owner"] = json!(s.owner); - } - json!({ - "document": summary(d), - "pdf_version": i.pdf_version, - "file_size": i.file_size, - "title": i.title, "author": i.author, "subject": i.subject, "keywords": i.keywords, - // Custom document properties (Document Properties ▸ Custom): /Info entries beyond the - // ones the spec names, set and cleared with doc_set_info. - "custom": i.custom.iter().map(|(name, value)| json!({ "name": name, "value": value })).collect::>(), - "creator": i.creator, "producer": i.producer, - "tagged": i.tagged, - "has_javascript": i.has_javascript, - // Where the document opens (/OpenAction, named destinations resolved): 1-based page and - // destination, null without one. - "open_destination": i.open_dest.as_ref().map(|(p, view)| json!({ "page": page1(*p), "destination": i.pages.get(*p).and_then(|pg| destination(view, pg)) })), - // XFA forms: "static" (the PDF's own fields work; the XFA data is ignored) or "dynamic" - // (laid out from the template by PdfCraft, see xfa_layout; placeholder pages when that failed). - "xfa": i.xfa.map(|x| match x { pdfcraft_render::Xfa::Static => "static", pdfcraft_render::Xfa::Dynamic => "dynamic" }), - "xfa_layout": d.xfa.as_ref().map(|x| json!({ "pages": x.pages, "fields": x.fields, "warnings": x.warnings })), - // A script of the form ran too long and was abandoned: its scripts no longer run in this - // document (calculations, buttons) until it is opened again. - "xfa_scripts_off": d.xfa_scripts_off(), - // What was rewritten from, or could not be written to, the XFA data. - "xfa_warnings": d.xfa_warnings, - "security": security, - "pages": i.pages.iter().enumerate().map(|(n, p)| json!({ - "page": n + 1, "label": p.label, "width": p.width, "height": p.height, "rotation": p.rotation, - })).collect::>(), - "outline": outline(&i.outline), - "outline_more": i.outline_more, - // Rectangles use the tools' convention (top-left of the displayed page, like - // comment_list and link_list), not raw PDF user space, so they can be fed back to - // geometry-taking tools (#129). - "annotations": i.annotations.iter().map(|a| json!({ - "page": page1(a.page), "type": a.subtype, "author": a.author, "contents": a.contents, - "modified": a.modified, "name": a.name, "in_reply_to": a.in_reply_to, - "rect": view_rect(i, a.page, a.shown_rect(i.pages.get(a.page).map_or(0, |p| p.rotation))), - })).collect::>(), - "fields": i.fields.iter().map(|f| json!({ - "name": f.name, "kind": format!("{:?}", f.kind), "value": f.value, "page": f.page.map(page1), - "tooltip": f.tooltip, "has_actions": f.has_actions, - })).collect::>(), - "links": i.links.iter().map(|l| json!({ - "page": page1(l.page), "rect": view_rect(i, l.page, l.rect), - "target": match &l.target { - pdfcraft_render::LinkTarget::Page(p, _) => json!({ "page": page1(*p) }), - pdfcraft_render::LinkTarget::Uri(u) => json!({ "uri": u }), - pdfcraft_render::LinkTarget::SetLayers { changes, preserve_rb } => json!({ - "layers": changes.iter().map(|(op, ocg)| json!({ - "layer": i.layers.iter().find(|l| l.id == *ocg).map(|l| l.name.as_str()), - "state": match op { - pdfcraft_render::LayerOp::On => "on", - pdfcraft_render::LayerOp::Off => "off", - pdfcraft_render::LayerOp::Toggle => "toggle", - }, - })).collect::>(), - "preserve_rb": preserve_rb, - }), - pdfcraft_render::LinkTarget::RemoteGoTo { launch, file, destination, new_window } => json!({ - "action": if *launch { "Launch" } else { "GoToR" }, - "remote_document": file, - "destination": match destination { - pdfcraft_render::RemoteDestination::Default => json!({ "kind": "default" }), - pdfcraft_render::RemoteDestination::Page { page, view } => json!({ "kind": "page", "page": page1(*page), "view": format!("{view:?}") }), - pdfcraft_render::RemoteDestination::Named(name) => json!({ "kind": "named", "name": name }), - }, - "new_window": new_window, - }), - pdfcraft_render::LinkTarget::Other(o) => json!({ "other": o }), - }, - })).collect::>(), - "layers": i.layers.iter().map(|l| json!({ "name": l.name, "visible": l.visible, "initial": l.initial })).collect::>(), - // Full-screen presentation settings a page carries (ISO 32000-2 §12.4.4): only pages with - // a `/Trans` or `/Dur` appear; `style` is null for a display time alone. - "transitions": i.transitions.iter().map(|t| json!({ - "page": page1(t.page), "style": t.style.map(pdfcraft_render::TransitionStyle::name), "duration": t.duration, - "horizontal": t.horizontal, "inward": t.inward, "direction": t.direction, "scale": t.scale, "opaque": t.opaque, - "display": t.display, - })).collect::>(), - "attachments": i.attachments.iter().map(|a| json!({ "name": a.name, "description": a.description, "size": a.size })).collect::>(), - // `substitute` names the face standing in for a program the document does not embed, and - // `substitution` says why. Both are null for an embedded font: nothing stands in. A null - // `substitute` with a non-null `substitution` means no face was available at all, so - // nothing is drawn for that font -- which is the answer to "why is my text missing?". - "fonts": i.fonts.iter().map(|f| json!({ - "name": f.name, "kind": f.kind, "embedded": f.embedded, "subset": f.subset, "encoding": f.encoding, - "substitute": f.substitute, "substitution": f.substitution, - })).collect::>(), - "warnings": i.warnings, - "repairs": d.repair_log(), - }) -} - -/// A user-space rectangle on 0-based `page` in displayed-page coordinates (points from the -/// top-left of the page as displayed, y down, after `/Rotate`), the way every tool reports -/// rectangles; unchanged when the page is unknown (a malformed annotation that points at no page). -/// Public so `pdfcraft-cli` reports the same rectangles as the tools. -pub fn view_rect(i: &pdfcraft_render::DocInfo, page: usize, rect: [f32; 4]) -> [f32; 4] { - i.pages.get(page).map_or(rect, |p| comments::rect_to_view(p, rect)) -} - -fn outline(items: &[pdfcraft_render::OutlineItem]) -> Value { - Value::Array(items.iter().map(|o| json!({ "title": o.title, "page": o.page.map(|p| p + 1), "children": outline(&o.children) })).collect()) -} diff --git a/crates/ui-egui/src/panels/right.rs.orig b/crates/ui-egui/src/panels/right.rs.orig deleted file mode 100644 index 79769867d3..0000000000 --- a/crates/ui-egui/src/panels/right.rs.orig +++ /dev/null @@ -1,731 +0,0 @@ -use super::bookmarks::{BmAction, OutlineCtx, outline_item, outline_matches}; -use super::pages::{page_tools, pages, thumbnail_size}; -use super::{ - Align, Align2, Color32, DocInfo, FieldKind, Layout, Nav, PdfCraftApp, Rect, RightPanel, Sense, Stroke, Tokens, human_size, icons, theme, vec2, -}; - -/// Draw the selected right-hand panel and apply its navigation, layer, bookmark or edit actions. -pub fn right_panel(app: &mut PdfCraftApp, ui: &mut egui::Ui) { - let t = Tokens::get(ui.ctx()); - let Some((index, id)) = app.active_ids() else { return }; - let Some(panel) = app.right else { return }; - let mut nav: Option