diff --git a/ROADMAP.md b/ROADMAP.md index f5de8044e1..2c86653a4d 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -205,6 +205,7 @@ Ranked; detail and the full milestone table in [docs/roadmap.md](docs/roadmap.md - **2026-10-24 (M3, window move on Wayland, #3970 follow-up):** After a move or resize by PdfCraft's own title bar or edges, the button release given back to egui goes before the pointer leaving the window, so hover no longer sticks at the old place until the pointer moves. Found by CodeRabbit in review of #3970; a unit test covers it. Estimates unchanged. - **2026-10-24 (M14, #4253):** The annotation quick bar remembers Keep tool selected across restarts, including unpinning. Old and malformed settings keep safe defaults; the opt-in UI control channel reads and changes the same preference. Estimates unchanged. +- **2026-10-23 (M6, calculation checks):** The calculation order now flags a formula that reads a field the form doesn't have (a field deleted from under a sum, a typo in notation, a script's `getField`) and a script that builds the names it reads at run time. `form_calculation_order` gives each field's formula, and `form_fields` shows a custom Calculate script. - **2026-10-23 (M7, #4280):** Edit text names each standard stand-in font after its face (`PCEdTimesBold`, ...) instead of one shared `PCEdHelv`. Rewriting a bold paragraph whose subset lacks an accent no longer repoints the paragraphs set earlier in a regular stand-in at Times-Bold, and the editor's frame stays on the paragraph being edited. Pages saved by older builds keep their shared name and are not repaired. Estimates unchanged. - **2026-10-23 (M8, #3205 follow-up):** Phone-pattern redaction matches complete labelled Italian geographic numbers such as `Tel. +39 0426/123456`, with synthetic matcher and mark/apply/save/reopen coverage; unlabelled international slash numbers remain unsupported. - **2026-10-23 (M7, #2636 partial):** A new `PasteContent` edit pastes an added text box or image copied in the same document or another, keeping its style, picture, turns, flips and crop, as one undo step; `content_copy` drives it headlessly. A paste lands beside earlier copies (18 pt steps) and stays on the page. A pasted image reuses its picture object in its own document and after the first paste into another; there a colour profile is dropped and CMYK colours stored losslessly are turned to RGB. The desktop keys and menus follow in separate PRs. Estimates unchanged. diff --git a/crates/automation/src/forms.rs b/crates/automation/src/forms.rs index 1c1e3c3371..1dec77bf03 100644 --- a/crates/automation/src/forms.rs +++ b/crates/automation/src/forms.rs @@ -116,6 +116,9 @@ fn calculate_arg(v: &Value) -> Result { if let Some(n) = o.get("notation").and_then(Value::as_str) { return Ok(Calculate::Notation(n.to_string())); } + if o.contains_key("script") { + return Err(bad("a custom calculate script is set with form_set_script (event: calculate)")); + } let op = match o.get("op").and_then(Value::as_str).unwrap_or("") { "sum" => CalcOp::Sum, "product" => CalcOp::Product, @@ -132,6 +135,17 @@ fn calculate_arg(v: &Value) -> Result { Ok(Calculate::Simple { op, fields }) } +/// A field's calculation as `form_fields` and `form_calculation_order` show it, in +/// `form_set_props`'s shape, or `{"script": …}` for a custom Calculate script; `None` without one. +fn calculate_json(f: &FormField) -> Option { + match (&f.actions.calculate, &f.actions.scripts.calculate) { + (Calculate::Simple { op, fields }, _) => Some(json!({ "op": op.code().to_lowercase(), "fields": fields })), + (Calculate::Notation(n), _) => Some(json!({ "notation": n })), + (Calculate::None, Some(js)) => Some(json!({ "script": js })), + (Calculate::None, None) => None, + } +} + fn format_json(f: &Format) -> Value { match f { Format::None => Value::Null, @@ -220,14 +234,8 @@ impl Automation { if let Validate::Range { min, max } = &f.actions.validate { o.insert("validate".into(), json!({ "min": min, "max": max })); } - match &f.actions.calculate { - Calculate::Simple { op, fields } => { - o.insert("calculate".into(), json!({ "op": op.code().to_lowercase(), "fields": fields })); - } - Calculate::Notation(n) => { - o.insert("calculate".into(), json!({ "notation": n })); - } - Calculate::None => {} + if let Some(c) = calculate_json(f) { + o.insert("calculate".into(), c); } if !f.actions.unsupported.is_empty() { o.insert("unsupported_scripts".into(), json!(f.actions.unsupported)); @@ -653,15 +661,30 @@ impl Automation { }; let order = self.doc(a)?.calculation_order(); let issue = |i: &CalculationIssue| match i { + CalculationIssue::MissingField { field } => json!({ "issue": "missing_field", "field": field }), CalculationIssue::RunsTooEarly { field } => json!({ "issue": "runs_too_early", "field": field }), CalculationIssue::Cycle { fields } => json!({ "issue": "cycle", "fields": fields }), CalculationIssue::NotInOrder => json!({ "issue": "not_in_order" }), + CalculationIssue::BuiltNames => json!({ "issue": "built_names" }), }; + let doc = self.doc(a)?; + let mut by_name: std::collections::HashMap<&str, &FormField> = std::collections::HashMap::with_capacity(doc.form.len()); + for f in doc.form.iter() { + by_name.entry(f.name.as_str()).or_insert(f); + } out["calculation_order"] = json!(order.fields.iter().map(|f| f.name.as_str()).collect::>()); out["fields"] = order .fields .iter() - .map(|f| json!({ "name": f.name, "reads": f.reads, "listed": f.listed, "issues": f.issues.iter().map(issue).collect::>() })) + .map(|f| { + json!({ + "name": f.name, + "formula": by_name.get(f.name.as_str()).and_then(|x| calculate_json(x)), + "reads": f.reads, + "listed": f.listed, + "issues": f.issues.iter().map(issue).collect::>(), + }) + }) .collect(); out["extra"] = json!(order.extra); out["fixable"] = json!(order.fixable()); diff --git a/crates/automation/src/tools.rs b/crates/automation/src/tools.rs index 53c4582af9..b41e114442 100644 --- a/crates/automation/src/tools.rs +++ b/crates/automation/src/tools.rs @@ -391,7 +391,7 @@ pub fn tools() -> Vec { }), &["doc", "from", "to"], )), - t("form_fields", "List form fields", "Every interactive form field: name, type (text, checkbox, radio, combo, list, button, signature), value, options, page, rect (top-left-origin points) and rotation (0, 90, 180 or 270 degrees counterclockwise), read-only and required flags. Push buttons include caption (first widget's normal label) and captions [{widget, caption}] for all widgets (1-based).") + t("form_fields", "List form fields", "Every interactive form field: name, type (text, checkbox, radio, combo, list, button, signature), value, options, page, rect (top-left-origin points) and rotation (0, 90, 180 or 270 degrees counterclockwise), read-only and required flags. A calculated field includes calculate: {op, fields} or {notation} as form_set_props takes them, or {script} for a custom Calculate script (set with form_set_script). Push buttons include caption (first widget's normal label) and captions [{widget, caption}] for all widgets (1-based).") .ro() .with(schema(json!({ "doc": doc() }), &["doc"])), t( @@ -502,7 +502,7 @@ pub fn tools() -> Vec { json!({ "doc": doc(), "order": { "type": "string", "enum": ["row", "column", "structure", "annotations"] }, "pages": pages("to set (default: all)"), "field": { "type": "string" }, "move": { "type": "string", "enum": ["earlier", "later"] } }), &["doc"], )), - t("form_calculation_order", "Read or set the calculation order", "The calculated fields in the order they calculate (Acrobat: Set Field Calculation Order), each with the fields its formula reads (`reads`; a script's names built at run time aren't known) and its `issues`: runs_too_early (it reads `field`, which calculates after it, so it shows a stale value), cycle (it reads `fields`, which read it back: no order fixes that, a formula has to change) and not_in_order (the PDF's order leaves it out, so other viewers may not calculate it). `extra` lists entries of the PDF's order that calculate nothing. Without a change it only reads. Changes, one at a time: `order` (these fields first, in this order, then the other calculated fields in their order), `field` with `move` earlier|later (one place), or `fix` true (only what must move moves, so every field calculates after the fields it reads, and the extra entries go). A change recalculates the form. Undoable.") + t("form_calculation_order", "Read or set the calculation order", "The calculated fields in the order they calculate (Acrobat: Set Field Calculation Order), each with its `formula` (as form_fields shows calculate), the fields it reads (`reads`; a script's names built at run time aren't known) and its `issues`: missing_field (it reads `field`, which the form doesn't have, so it gets no value there), runs_too_early (it reads `field`, which calculates after it, so it shows a stale value), cycle (it reads `fields`, which read it back: no order fixes that, a formula has to change) and not_in_order (the PDF's order leaves it out, so other viewers may not calculate it) and built_names (its script also reads fields by names it builds at run time, which aren't checked). `extra` lists entries of the PDF's order that calculate nothing. Without a change it only reads. Changes, one at a time: `order` (these fields first, in this order, then the other calculated fields in their order), `field` with `move` earlier|later (one place), or `fix` true (only what must move moves, so every field calculates after the fields it reads, and the extra entries go). A change recalculates the form. Undoable.") .cmd("form.calculation_order") .with(schema( json!({ "doc": doc(), "order": { "type": "array", "items": { "type": "string" } }, "field": { "type": "string" }, "move": { "type": "string", "enum": ["earlier", "later"] }, "fix": { "type": "boolean" } }), diff --git a/crates/automation/tests/it/automation.rs b/crates/automation/tests/it/automation.rs index 01d0d1eb24..22b5cd17e2 100644 --- a/crates/automation/tests/it/automation.rs +++ b/crates/automation/tests/it/automation.rs @@ -5058,6 +5058,39 @@ fn the_calculation_order_is_read_fixed_and_set() { assert_eq!((value(&mut a, "total"), value(&mut a, "tax")), (json!("0"), json!("0")), "undone with the values it recalculated"); } +/// The calculation order through the tool shows each formula, in form_set_props's shape or as a +/// script, and flags a read of a field the form doesn't have and a script that builds the names +/// it reads; form_fields shows a custom script too. +#[test] +fn the_calculation_order_shows_formulas_and_what_they_cannot_read() { + let dir = workdir("calcformulas"); + let mut a = auto(&dir); + let doc = ok(&mut a, "doc_open", json!({ "path": "a.pdf" }))["doc"].as_u64().unwrap(); + for (i, name) in ["qty", "price", "sub", "tax", "total"].into_iter().enumerate() { + let y = 20 + 30 * i; + ok(&mut a, "form_add_field", json!({ "doc": doc, "page": 1, "type": "text", "rect": [20, y, 180, y + 22], "name": name })); + } + let script = "event.value = getField(\"sub\").value / 10 + getField(\"row\" + 1).value;"; + ok(&mut a, "form_set_props", json!({ "doc": doc, "field": "sub", "calculate": { "notation": "qty * pric" } })); + ok(&mut a, "form_set_script", json!({ "doc": doc, "field": "tax", "event": "calculate", "script": script })); + ok(&mut a, "form_set_props", json!({ "doc": doc, "field": "total", "calculate": { "op": "sum", "fields": ["sub", "tax"] } })); + let r = ok(&mut a, "form_calculation_order", json!({ "doc": doc })); + assert_eq!(r["calculation_order"], json!(["sub", "tax", "total"])); + let formulas: Vec = r["fields"].as_array().unwrap().iter().map(|f| f["formula"].clone()).collect(); + assert_eq!(formulas, [json!({ "notation": "qty * pric" }), json!({ "script": script }), json!({ "op": "sum", "fields": ["sub", "tax"] })]); + assert_eq!(r["fields"][0]["issues"], json!([{ "issue": "missing_field", "field": "pric" }])); + assert_eq!(r["fields"][1]["reads"], json!(["sub"])); + assert_eq!(r["fields"][1]["issues"], json!([{ "issue": "built_names" }])); + assert_eq!(r["fields"][2]["issues"], json!([])); + assert_eq!(r["fixable"], false, "neither is an order to fix"); + let fields = ok(&mut a, "form_fields", json!({ "doc": doc })); + let tax = fields["fields"].as_array().unwrap().iter().find(|f| f["name"] == "tax").unwrap(); + assert_eq!(tax["calculate"], json!({ "script": script })); + // A script is set with form_set_script, and form_set_props says so. + let e = err(&mut a, "form_set_props", json!({ "doc": doc, "field": "tax", "calculate": { "script": script } })); + assert!(e.to_string().contains("form_set_script"), "{e}"); +} + #[test] fn page_tab_orders_are_read_and_set_per_page_through_tools() { let dir = workdir("pagetabs"); diff --git a/crates/forms/README.md b/crates/forms/README.md index 93b65bd14e..3b5e46eee9 100644 --- a/crates/forms/README.md +++ b/crates/forms/README.md @@ -90,7 +90,10 @@ let cells: Vec = grid.cells_for(names)?; // "Table1_1", "Table order `recalculate` runs them, with the fields each formula reads (`Calculate::reads`, `af::calculation_reads`; names built at run time aren't known) and what is wrong with the order: a field reading one that calculates later, a loop, a field `/CO` leaves out, and `/CO` - entries that calculate nothing. `CalculationOrder::fixed` is Fix order: a stable topological + entries that calculate nothing. Two issues are about the formula itself: it reads a field the + form doesn't have (`MissingField`; the field of that name, a field under it and a widget of one, + `price.0`, all count as there), or its script builds names at run time (`BuiltNames`, + `af::reads_built_names`), so its reads are incomplete. `CalculationOrder::fixed` is Fix order: a stable topological sort that keeps the current order wherever it can and leaves loops as they are. `set_calculation_order` writes `/CO` afresh. A field that gets a calculation joins the end of the order; one whose calculation changes keeps its place. diff --git a/crates/forms/src/af.rs b/crates/forms/src/af.rs index ee4aa18e3c..00cfc99d12 100644 --- a/crates/forms/src/af.rs +++ b/crates/forms/src/af.rs @@ -19,7 +19,7 @@ mod script; #[cfg(test)] use calc::{Notation, eval_notation, notation_name, notation_tokens, unique}; -pub use calc::{calculate, calculation_reads, notation_js, rename_in_calculation}; +pub use calc::{calculate, calculation_reads, notation_js, reads_built_names, rename_in_calculation}; pub use format::{DATE_PRESETS, DateTime, TIME_PRESETS, format_date, format_value, js_number, keystroke, make_number, parse_date, validate}; use script::{Arg, call}; pub use script::{calculate_js, format_js, parse_calculate, parse_format, parse_validate, validate_js}; diff --git a/crates/forms/src/af/calc.rs b/crates/forms/src/af/calc.rs index b3a1524a4f..958b5d4d04 100644 --- a/crates/forms/src/af/calc.rs +++ b/crates/forms/src/af/calc.rs @@ -206,6 +206,10 @@ enum Site { Commas(String), /// A name in the simplified field notation of a `BVCALC` comment. Notation(String), + /// A field read by a name built at run time: a `getField` whose argument is more than one + /// string literal (`getField("row" + i)`), or an `AFSimple_Calculate` list that isn't + /// written out. Which field it is isn't known. + Built, } impl Site { @@ -213,6 +217,7 @@ impl Site { match self { Site::Literal(name) | Site::Notation(name) => vec![name.as_str()], Site::Commas(list) => list.split(',').map(str::trim).filter(|n| !n.is_empty()).collect(), + Site::Built => Vec::new(), } } @@ -229,14 +234,16 @@ impl Site { js_str(&parts.join(",")) }) } + Site::Built => None, } } } /// Where a Calculate script names fields, in order: the simplified field notation in its comment /// (see [`notation_range`]; [`parse_calculate`] reads it there too), the field list of -/// `AFSimple_Calculate`, and every `getField` whose whole argument is one string literal. -/// A name built at run time (`getField("row" + i)`) is not a site. +/// `AFSimple_Calculate`, and every `getField` whose whole argument is one string literal. A name +/// built at run time (`getField("row" + i)`) is a [`Site::Built`], at its `getField` or +/// `AFSimple_Calculate`. fn calculation_sites(js: &str) -> Vec<(Range, Site)> { let mut sites = Vec::new(); let (tokens, comments) = js_tokens(js); @@ -255,35 +262,45 @@ fn calculation_sites(js: &str) -> Vec<(Range, Site)> { Some((range, Js::Str(text))) => Some((range.clone(), text.clone())), _ => None, }; + let built = |k: usize| tokens.get(k).map(|(range, _)| (range.clone(), Site::Built)); for k in 0..tokens.len() { - if ident(k, "getField") - && punct(k + 1, '(') - && punct(k + 3, ')') - && let Some((range, name)) = string(k + 2) - { - sites.push((range, Site::Literal(name))); + if ident(k, "getField") && punct(k + 1, '(') { + match string(k + 2) { + Some((range, name)) if punct(k + 3, ')') => sites.push((range, Site::Literal(name))), + _ => sites.extend(built(k)), + } } - if !(ident(k, "AFSimple_Calculate") && punct(k + 1, '(') && string(k + 2).is_some() && punct(k + 3, ',')) { + if !(ident(k, "AFSimple_Calculate") && punct(k + 1, '(')) { + continue; + } + if !(string(k + 2).is_some() && punct(k + 3, ',')) { + sites.extend(built(k)); continue; } if let Some((range, list)) = string(k + 4) { sites.push((range, Site::Commas(list))); continue; } - // `new Array("a", "b")` or `["a", "b"]`: its items, up to the first that isn't a string. - // Nothing else is read, so no token is read for two calls. - let first = if punct(k + 4, '[') { - k + 5 + // `new Array("a", "b")` or `["a", "b"]`: its items, up to the first that isn't a string, + // which has to close the list for the list to be written out. Nothing else is read, so no + // token is read for two calls. + let (first, close) = if punct(k + 4, '[') { + (k + 5, ']') } else if ident(k + 4, "new") && ident(k + 5, "Array") && punct(k + 6, '(') { - k + 7 + (k + 7, ')') } else { + sites.extend(built(k)); continue; }; for (range, token) in tokens.iter().skip(first) { match token { Js::Str(name) => sites.push((range.clone(), Site::Literal(name.clone()))), Js::Punct(',') => {} - _ => break, + Js::Punct(c) if *c == close => break, + _ => { + sites.extend(built(k)); + break; + } } } } @@ -315,6 +332,12 @@ pub fn calculation_reads(js: &str) -> Vec { unique(sites.iter().flat_map(|(_, site)| site.names()).map(str::to_owned)) } +/// Whether a Calculate script also reads fields by names it builds at run time +/// (`getField("row" + i)`), which [`calculation_reads`] can't list. +pub fn reads_built_names(js: &str) -> bool { + calculation_sites(js).iter().any(|(_, site)| *site == Site::Built) +} + impl Calculate { /// The fields it reads, each once, in the order it first names them. A name also stands for /// the fields under it (`row` for `row.0` and `row.1`), as [`calculate`] reads them. diff --git a/crates/forms/src/af/tests.rs b/crates/forms/src/af/tests.rs index ff25e06968..5198fcc5ad 100644 --- a/crates/forms/src/af/tests.rs +++ b/crates/forms/src/af/tests.rs @@ -156,6 +156,37 @@ fn a_regex_after_a_condition_is_not_read_as_code() { assert_eq!(renamed(divides), Some(divides.replace("\"a\"", "\"qty\""))); } +/// A `getField` whose argument is more than one string, or an `AFSimple_Calculate` list that +/// isn't written out, reads a field whose name isn't known until the script runs. +#[test] +fn names_built_at_run_time_are_noticed() { + for js in [ + "event.value = getField(\"row\" + i).value;", + "event.value = this.getField(name).value;", + "AFSimple_Calculate(\"SUM\", list);", + "AFSimple_Calculate(\"SUM\", new Array(\"a\", b));", + "AFSimple_Calculate(\"SUM\", [\"a\", prefix + \"b\"]);", + "AFSimple_Calculate(\"SUM\", new Array(\"a\", \"b\" + c));", + "AFSimple_Calculate(op, new Array(\"a\"));", + ] { + assert!(reads_built_names(js), "{js}"); + } + for js in [ + "event.value = getField(\"a\").value + getField('b').value;", + "AFSimple_Calculate(\"SUM\", new Array(\"a\", \"b\"));", + "AFSimple_Calculate(\"SUM\", [\"a\", \"b\",]);", + "AFSimple_Calculate(\"SUM\", \"a, b\");", + "// getField(\"row\" + i)\nvar s = 'getField(name)'; event.value = 1;", + "/** BVCALC a + b EVCALC **/ event.value = getField(\"a\").value + getField(\"b\").value;", + ] { + assert!(!reads_built_names(js), "{js}"); + } + // The names it does know are still read and renamed. + let js = "event.value = getField(\"a\").value + getField(\"row\" + i).value + AFSimple_Calculate(\"SUM\", new Array(\"a\", b));"; + assert_eq!(calculation_reads(js), ["a"]); + assert_eq!(rename_in_calculation(js, "a", "qty"), Some(js.replace("\"a\"", "\"qty\""))); +} + /// Scripts are read in one pass: a line of unfinished regular expressions, or calls nested in /// one another's field lists, don't make a rename read the rest of the script again for each. #[test] diff --git a/crates/forms/src/calc_order.rs b/crates/forms/src/calc_order.rs index 4e7f0309e9..c4a1b5bd37 100644 --- a/crates/forms/src/calc_order.rs +++ b/crates/forms/src/calc_order.rs @@ -26,9 +26,12 @@ pub struct CalculatedField { pub issues: Vec, } -/// Something wrong with a calculated field's place in the calculation order. +/// Something wrong with a calculated field's formula or its place in the calculation order. #[derive(Clone, Debug, PartialEq)] pub enum CalculationIssue { + /// It reads `field`, which the form doesn't have (a field deleted, renamed outside PdfCraft, + /// or mistyped), so the formula gets no value there. + MissingField { field: String }, /// It reads `field`, which calculates after it, so it uses that field's previous value. RunsTooEarly { field: String }, /// Its formula reads its own result: it reads `fields`, which read it back (itself, when it @@ -37,6 +40,9 @@ pub enum CalculationIssue { /// `/CO` leaves it out. PdfCraft calculates it after the listed fields, but other PDF viewers /// may not calculate it at all. NotInOrder, + /// Its script also reads fields by names it builds at run time (`getField("row" + i)`). Those + /// aren't in `reads`, so the order isn't checked against them. + BuiltNames, } /// A form's calculation order and what is wrong with it. @@ -152,11 +158,14 @@ pub fn calculation_order(doc: &Document, fields: &[Field]) -> CalculationOrder { .collect(); let links = links(calculated.iter().map(|(f, _)| f.name.as_str()), &reads); let component = components(&links); + let mut all: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect(); + all.sort_unstable(); let name = |k: usize| calculated.get(k).map(|(f, _)| f.name.clone()).unwrap_or_default(); let mut out = Vec::with_capacity(calculated.len()); for (k, ((f, listed), reads)) in calculated.iter().zip(reads).enumerate() { let own = component.get(k).copied(); - let mut issues = Vec::new(); + let mut issues: Vec = + reads.iter().filter(|name| !found(&all, name)).map(|name| CalculationIssue::MissingField { field: name.clone() }).collect(); let read = links.get(k).map(Vec::as_slice).unwrap_or_default(); for &j in read { if j > k && component.get(j).copied() != own { @@ -172,12 +181,25 @@ pub fn calculation_order(doc: &Document, fields: &[Field]) -> CalculationOrder { if !listed { issues.push(CalculationIssue::NotInOrder); } + if f.actions.scripts.calculate.as_deref().is_some_and(crate::af::reads_built_names) { + issues.push(CalculationIssue::BuiltNames); + } out.push(CalculatedField { name: f.name.clone(), reads, listed: *listed, issues }); } let fixed = fixed_order(&links, &component); CalculationOrder { fields: out, extra, fixed } } +/// Whether a formula's read `name` finds a field in `names` (sorted): the field of that name, a +/// field under it (`row` for `row.1`), or a widget of one (`price.0`, as `getField` numbers them). +fn found(names: &[&str], name: &str) -> bool { + let has = |n: &str| names.binary_search(&n).is_ok(); + let prefix = format!("{name}."); + let under = names.partition_point(|n| *n < prefix.as_str()); + let widget = name.rsplit_once('.').is_some_and(|(base, i)| !i.is_empty() && i.bytes().all(|b| b.is_ascii_digit()) && has(base)); + has(name) || names.get(under).is_some_and(|n| n.starts_with(&prefix)) || widget +} + /// For each calculated field (by its place in `names`), the places of the calculated fields it /// reads, sorted. A read name finds the field of that name and the fields under it, as /// [`crate::recalculate`] looks values up. @@ -383,6 +405,18 @@ mod tests { assert_eq!(links(names.into_iter(), &reads)[5], [0, 1, 2], "row, row.1 and row.10; not rows or row-1"); } + #[test] + fn a_read_name_finds_a_field_a_group_or_a_widget() { + let mut names = vec!["price", "row.1", "row.2", "rows", "total"]; + names.sort_unstable(); + for name in ["price", "row", "row.1", "rows", "price.0", "price.12"] { + assert!(found(&names, name), "{name}"); + } + for name in ["pric", "ro", "row.3", "price.", "price.x", "price.-1", "total.0.1", ""] { + assert!(!found(&names, name), "{name}"); + } + } + /// Two thousand formulas each reading a group of two thousand fields would be four million /// reads; the checks stop at their cap. #[test] diff --git a/crates/forms/src/tests.rs b/crates/forms/src/tests.rs index ac414af2ae..6f848f9628 100644 --- a/crates/forms/src/tests.rs +++ b/crates/forms/src/tests.rs @@ -1711,6 +1711,45 @@ fn the_calculation_order_is_checked_fixed_and_kept() { assert_eq!(order.fixed(), ["sub", "total", "tax", "grand"], "only tax moves, to just after total"); } +/// A formula that reads a field the form doesn't have is flagged, whichever kind of formula it +/// is: a field deleted from under a sum, a typo in notation, a script's `getField`. So is a +/// script that builds the names it reads. Neither is an order Fix order can fix. +#[test] +fn missing_fields_and_built_names_are_flagged() { + use crate::af::{CalcOp, Calculate}; + use crate::calc_order::CalculationIssue::{BuiltNames, MissingField}; + let mut doc = one_page(); + let text = NewField::Text { multiline: false }; + for (i, name) in ["qty", "gone", "sum", "notation", "script", "built"].into_iter().enumerate() { + let y = 700.0 - 40.0 * i as f64; + add_field(&mut doc, 0, [50.0, y, 250.0, y + 20.0], &text, Some(name)).unwrap(); + } + let calculate = |c: Calculate| FieldProps { calculate: Some(c), ..FieldProps::default() }; + set_props(&mut doc, "sum", &calculate(Calculate::Simple { op: CalcOp::Sum, fields: vec!["qty".into(), "gone".into()] })).unwrap(); + set_props(&mut doc, "notation", &calculate(Calculate::Notation("qty * nope".into()))).unwrap(); + set_field_script(&mut doc, "script", "calculate", Some("event.value = getField(\"qty\").value + getField(\"ghost\").value;")).unwrap(); + set_field_script(&mut doc, "built", "calculate", Some("event.value = getField(\"qty\").value + getField(\"row\" + 1).value;")).unwrap(); + let issues = |doc: &Document| { + let order = calculation_order(doc, &fields(doc)); + assert!(!order.fixable(), "nothing Fix order can fix"); + order.fields.into_iter().map(|f| (f.name, f.issues)).collect::>() + }; + let expected = |sum: Vec<_>| { + vec![ + ("sum".to_owned(), sum), + ("notation".to_owned(), vec![MissingField { field: "nope".into() }]), + ("script".to_owned(), vec![MissingField { field: "ghost".into() }]), + ("built".to_owned(), vec![BuiltNames]), + ] + }; + assert_eq!(issues(&doc), expected(vec![])); + delete_field(&mut doc, "gone").unwrap(); + assert_eq!(issues(&doc), expected(vec![MissingField { field: "gone".into() }]), "a deleted field leaves the sum reading nothing"); + assert_eq!(issues(&reopen(&doc)), expected(vec![MissingField { field: "gone".into() }])); + let order = calculation_order(&doc, &fields(&doc)); + assert_eq!(order.fields[3].reads, ["qty"], "the names it does know are still read"); +} + /// `/CO` as other tools leave it: a calculated field missing, a field without a calculation, an /// entry repeated, a reference to nothing and a number. The order reads around them, and setting /// the order writes `/CO` afresh. diff --git a/crates/ui-egui/src/i18n/ar.tsv b/crates/ui-egui/src/i18n/ar.tsv index 3e6d735523..0d429f657c 100644 --- a/crates/ui-egui/src/i18n/ar.tsv +++ b/crates/ui-egui/src/i18n/ar.tsv @@ -541,6 +541,8 @@ Reads {names}, which read it back. No order can fix a loop: change one of the formulas. يقرأ {names} التي تقرؤه بدورها. لا يمكن لأي ترتيب إصلاح الحلقة: غيّر إحدى الصيغ. Not in order خارج الترتيب Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. غير موجود في ترتيب الحساب المحفوظ في ملف PDF: قد لا تحسبه برامج عرض PDF الأخرى. + Reads {name}, which the form doesn't have, so it gets no value there. يقرأ {name}، وهو غير موجود في النموذج، لذا لا يحصل منه على أي قيمة. + Its script also reads fields whose names it builds as it runs, so those reads can't be checked. يقرأ سكربته أيضًا حقولًا يبني أسماءها أثناء التشغيل، لذا لا يمكن التحقق من هذه القراءات. Back to Prepare a form العودة إلى تجهيز نموذج No field in this form calculates its value yet. Give a field a calculation on the Calculate tab of its properties, and it shows here. لا يحسب أي حقل في هذا النموذج قيمته بعد. امنح حقلًا عملية حساب في علامة التبويب «الحساب» ضمن خصائصه، وسيظهر هنا. {names} and {last} {names} و{last} diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index ec0339f18a..2ef342e51b 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -202,6 +202,8 @@ Reads {names}, which read it back. No order can fix a loop: change one of the formulas. Чете {names}, които на свой ред го четат. Никой ред не може да поправи цикъл: променете една от формулите. Not in order Извън реда Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. Липсва в реда на изчисляване, записан в PDF файла: други програми за PDF може да не го изчисляват. + Reads {name}, which the form doesn't have, so it gets no value there. Чете {name}, което го няма във формуляра, затова не получава стойност оттам. + Its script also reads fields whose names it builds as it runs, so those reads can't be checked. Скриптът му чете и полета, чиито имена съставя по време на изпълнение, затова тези четения не могат да бъдат проверени. Back to Prepare a form Назад към „Подготви формуляр“ No field in this form calculates its value yet. Give a field a calculation on the Calculate tab of its properties, and it shows here. Все още нито едно поле в този формуляр не изчислява стойността си. Задайте изчисление на поле в раздела „Изчисляване“ на свойствата му и то ще се покаже тук. {names} and {last} {names} и {last} diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 0cacae3c2c..dda1208dad 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -2720,6 +2720,8 @@ theme Appearance Aspecte Reads {names}, which read it back. No order can fix a loop: change one of the formulas. Reads {names}, which read it back. No order can fix a loop: change one of the formulas. Not in order Not in order Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. + Reads {name}, which the form doesn't have, so it gets no value there. Reads {name}, which the form doesn't have, so it gets no value there. + Its script also reads fields whose names it builds as it runs, so those reads can't be checked. Its script also reads fields whose names it builds as it runs, so those reads can't be checked. Back to Prepare a form Back to Prepare a form No field in this form calculates its value yet. Give a field a calculation on the Calculate tab of its properties, and it shows here. No field in this form calculates its value yet. Give a field a calculation on the Calculate tab of its properties, and it shows here. {names} and {last} {names} and {last} diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index ed6d051b57..3c38493305 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -625,6 +625,8 @@ key Space Mezerník Reads {names}, which read it back. No order can fix a loop: change one of the formulas. Čte {names}, která ho čtou zpět. Žádné pořadí smyčku neopraví: změňte jeden ze vzorců. Not in order Mimo pořadí Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. Chybí v pořadí výpočtů uloženém v PDF: jiné prohlížeče PDF ho možná nepočítají. + Reads {name}, which the form doesn't have, so it gets no value there. Čte {name}, které formulář nemá, takže odtud nedostane žádnou hodnotu. + Its script also reads fields whose names it builds as it runs, so those reads can't be checked. Jeho skript čte i pole, jejichž názvy sestavuje za běhu, takže tato čtení nelze zkontrolovat. Back to Prepare a form Zpět na Připravit formulář No field in this form calculates its value yet. Give a field a calculation on the Calculate tab of its properties, and it shows here. Žádné pole tohoto formuláře zatím nepočítá svou hodnotu. Zadejte poli výpočet na kartě Výpočet v jeho vlastnostech a zobrazí se zde. {names} and {last} {names} a {last} diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 8586a21d43..8d605a024b 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -514,6 +514,8 @@ Reads {names}, which read it back. No order can fix a loop: change one of the formulas. Liest {names}, die es wieder lesen. Keine Reihenfolge kann eine Schleife beheben: Ändere eine der Formeln. Not in order Nicht in der Reihenfolge Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. Fehlt in der im PDF gespeicherten Berechnungsreihenfolge: Andere PDF-Viewer berechnen es womöglich nicht. + Reads {name}, which the form doesn't have, so it gets no value there. Liest {name}, das es im Formular nicht gibt, und erhält dort deshalb keinen Wert. + Its script also reads fields whose names it builds as it runs, so those reads can't be checked. Sein Skript liest auch Felder, deren Namen es erst beim Ausführen bildet. Diese Lesezugriffe lassen sich nicht prüfen. Back to Prepare a form Zurück zu „Formular vorbereiten“ No field in this form calculates its value yet. Give a field a calculation on the Calculate tab of its properties, and it shows here. Noch berechnet kein Feld dieses Formulars seinen Wert. Gib einem Feld auf dem Tab „Berechnen“ seiner Eigenschaften eine Berechnung, dann erscheint es hier. {names} and {last} {names} und {last} diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index e3eac77caa..3efb631ae1 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -1852,6 +1852,8 @@ Reads {names}, which read it back. No order can fix a loop: change one of the formulas. Διαβάζει τα {names}, που με τη σειρά τους το διαβάζουν. Καμία σειρά δεν διορθώνει έναν βρόχο: αλλάξτε έναν από τους τύπους. Not in order Εκτός σειράς Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. Λείπει από τη σειρά υπολογισμού που είναι αποθηκευμένη στο PDF: άλλα προγράμματα προβολής PDF ίσως δεν το υπολογίζουν. + Reads {name}, which the form doesn't have, so it gets no value there. Διαβάζει το {name}, που δεν υπάρχει στη φόρμα, οπότε δεν παίρνει τιμή από εκεί. + Its script also reads fields whose names it builds as it runs, so those reads can't be checked. Η δέσμη ενεργειών του διαβάζει επίσης πεδία των οποίων τα ονόματα σχηματίζει κατά την εκτέλεση, οπότε αυτές οι αναγνώσεις δεν μπορούν να ελεγχθούν. Back to Prepare a form Επιστροφή στην Προετοιμασία φόρμας No field in this form calculates its value yet. Give a field a calculation on the Calculate tab of its properties, and it shows here. Κανένα πεδίο αυτής της φόρμας δεν υπολογίζει ακόμη την τιμή του. Δώστε σε ένα πεδίο υπολογισμό στην καρτέλα Υπολογισμός των ιδιοτήτων του και θα εμφανιστεί εδώ. {names} and {last} {names} και {last} diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 0d4e505a70..fb17254189 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -537,6 +537,8 @@ Reads {names}, which read it back. No order can fix a loop: change one of the formulas. Lee {names}, que a su vez lo leen. Ningún orden puede corregir un bucle: cambia una de las fórmulas. Not in order Fuera del orden Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. Falta en el orden de cálculo guardado en el PDF: otros visores de PDF podrían no calcularlo. + Reads {name}, which the form doesn't have, so it gets no value there. Lee {name}, que no existe en el formulario, así que no obtiene ningún valor de ahí. + Its script also reads fields whose names it builds as it runs, so those reads can't be checked. Su script también lee campos cuyos nombres construye mientras se ejecuta, así que esas lecturas no se pueden comprobar. Back to Prepare a form Volver a Preparar un formulario No field in this form calculates its value yet. Give a field a calculation on the Calculate tab of its properties, and it shows here. Ningún campo de este formulario calcula todavía su valor. Dale a un campo un cálculo en la pestaña Calcular de sus propiedades y aparecerá aquí. {names} and {last} {names} y {last} diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index a542ee9b91..f35cb08d5a 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -2783,6 +2783,9 @@ theme Appearance Teema The signature's signed attributes have no message digest, so the signature does not cover the document. Allekirjoituksen allekirjoitetuista määritteistä puuttuu viestitiiviste, joten allekirjoitus ei kata asiakirjaa. The signature's signed attributes name a content type other than data, so it is not a signature of the document. Allekirjoituksen allekirjoitetut määritteet nimeävät muun sisältötyypin kuin data, joten se ei ole asiakirjan allekirjoitus. Remove {standard} identification Poista {standard}-tunniste +# Untranslated: English text until a translator fills these in. + Reads {name}, which the form doesn't have, so it gets no value there. Reads {name}, which the form doesn't have, so it gets no value there. + Its script also reads fields whose names it builds as it runs, so those reads can't be checked. Its script also reads fields whose names it builds as it runs, so those reads can't be checked. # Prepare a form ▸ Text field table (AI-assisted, not separately native-reviewed) Add text field table Lisää tekstikenttätaulukko diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 61c8b5171d..e83249e394 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -514,6 +514,8 @@ Reads {names}, which read it back. No order can fix a loop: change one of the formulas. Lit {names}, qui le lisent en retour. Aucun ordre ne peut corriger une boucle : modifiez l'une des formules. Not in order Hors de l'ordre Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. Absent de l'ordre de calcul enregistré dans le PDF : d'autres lecteurs PDF risquent de ne pas le calculer. + Reads {name}, which the form doesn't have, so it gets no value there. Lit {name}, qui n'existe pas dans le formulaire : aucune valeur n'en provient. + Its script also reads fields whose names it builds as it runs, so those reads can't be checked. Son script lit aussi des champs dont il construit les noms à l'exécution : ces lectures ne peuvent pas être vérifiées. Back to Prepare a form Retour à Préparer un formulaire No field in this form calculates its value yet. Give a field a calculation on the Calculate tab of its properties, and it shows here. Aucun champ de ce formulaire ne calcule encore sa valeur. Donnez un calcul à un champ dans l'onglet Calculer de ses propriétés, et il apparaîtra ici. {names} and {last} {names} et {last} diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv index e9d627aca9..354b58ee89 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -2721,6 +2721,8 @@ theme Appearance स्वरूप Links on page Links on page Loop Loop Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. + Reads {name}, which the form doesn't have, so it gets no value there. Reads {name}, which the form doesn't have, so it gets no value there. + Its script also reads fields whose names it builds as it runs, so those reads can't be checked. Its script also reads fields whose names it builds as it runs, so those reads can't be checked. Move only what must move, so every field calculates after the fields it reads. Move only what must move, so every field calculates after the fields it reads. Moves only what must move Moves only what must move No field reads it No field reads it diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 6b37b9169b..f64c76d51a 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -512,6 +512,8 @@ Reads {names}, which read it back. No order can fix a loop: change one of the formulas. Ezeket olvassa: {names}, amelyek visszaolvassák. Hurkot semmilyen sorrend nem javít meg: módosítsd az egyik képletet. Not in order Nincs a sorrendben Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. Hiányzik a PDF-ben mentett számítási sorrendből: más PDF-megjelenítők talán nem számítják ki. + Reads {name}, which the form doesn't have, so it gets no value there. Ezt olvassa: {name}, amely nincs az űrlapon, így onnan nem kap értéket. + Its script also reads fields whose names it builds as it runs, so those reads can't be checked. A szkriptje olyan mezőket is olvas, amelyek nevét futás közben állítja össze, így ezek az olvasások nem ellenőrizhetők. Back to Prepare a form Vissza az Űrlap előkészítéséhez No field in this form calculates its value yet. Give a field a calculation on the Calculate tab of its properties, and it shows here. Ennek az űrlapnak még egyik mezője sem számítja ki az értékét. Adj egy mezőnek számítást a tulajdonságai Számítás lapján, és itt megjelenik. {names} and {last} {names} és {last} diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 292b336e10..0f62baf71f 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -2722,6 +2722,8 @@ theme Appearance Tampilan Links on page Links on page Loop Loop Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. + Reads {name}, which the form doesn't have, so it gets no value there. Reads {name}, which the form doesn't have, so it gets no value there. + Its script also reads fields whose names it builds as it runs, so those reads can't be checked. Its script also reads fields whose names it builds as it runs, so those reads can't be checked. Move only what must move, so every field calculates after the fields it reads. Move only what must move, so every field calculates after the fields it reads. Moves only what must move Moves only what must move No field reads it No field reads it diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 20729bae6a..8c8208e706 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -538,6 +538,8 @@ Reads {names}, which read it back. No order can fix a loop: change one of the formulas. Legge {names}, che a loro volta lo leggono. Nessun ordine può correggere un ciclo: modifica una delle formule. Not in order Fuori ordine Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. Manca nell'ordine di calcolo salvato nel PDF: altri visualizzatori PDF potrebbero non calcolarlo. + Reads {name}, which the form doesn't have, so it gets no value there. Legge {name}, che non esiste nel modulo, quindi da lì non riceve alcun valore. + Its script also reads fields whose names it builds as it runs, so those reads can't be checked. Il suo script legge anche campi i cui nomi costruisce durante l'esecuzione, quindi queste letture non possono essere verificate. Back to Prepare a form Torna a Prepara un modulo No field in this form calculates its value yet. Give a field a calculation on the Calculate tab of its properties, and it shows here. Nessun campo di questo modulo calcola ancora il proprio valore. Assegna un calcolo a un campo nella scheda Calcolo delle sue proprietà e comparirà qui. {names} and {last} {names} e {last} diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 1ed4d43b6c..8a19ef110b 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -541,6 +541,8 @@ Reads {names}, which read it back. No order can fix a loop: change one of the formulas. {names} を読み取り、それらがこのフィールドを読み返します。循環はどの順序でも直せません。いずれかの数式を変更してください。 Not in order 順序にない Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. PDF に保存された計算順序にありません。ほかの PDF ビューアーでは計算されない可能性があります。 + Reads {name}, which the form doesn't have, so it gets no value there. フォームにない {name} を読み取るため、そこからは値を得られません。 + Its script also reads fields whose names it builds as it runs, so those reads can't be checked. スクリプトが実行中に名前を組み立てるフィールドも読み取るため、それらの読み取りは確認できません。 Back to Prepare a form 「フォームを作成」に戻る No field in this form calculates its value yet. Give a field a calculation on the Calculate tab of its properties, and it shows here. このフォームには値を計算するフィールドがまだありません。フィールドのプロパティの[計算]タブで計算を設定すると、ここに表示されます。 {names} and {last} {names}、{last} diff --git a/crates/ui-egui/src/i18n/kk.tsv b/crates/ui-egui/src/i18n/kk.tsv index 9022c3591c..ff5bcd317b 100644 --- a/crates/ui-egui/src/i18n/kk.tsv +++ b/crates/ui-egui/src/i18n/kk.tsv @@ -501,6 +501,8 @@ Reads {names}, which read it back. No order can fix a loop: change one of the formulas. {names} өрістерін оқиды, олар оны қайта оқиды. Ешбір рет циклды түзете алмайды: формулалардың бірін өзгертіңіз. Not in order Ретте жоқ Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. PDF ішінде сақталған есептеу ретінде жоқ: басқа PDF қарау құралдары оны есептемеуі мүмкін. + Reads {name}, which the form doesn't have, so it gets no value there. {name} өрісін оқиды, бірақ ол пішінде жоқ, сондықтан одан мән алмайды. + Its script also reads fields whose names it builds as it runs, so those reads can't be checked. Оның скрипті атауларын орындалу кезінде құрастыратын өрістерді де оқиды, сондықтан бұл оқуларды тексеру мүмкін емес. Back to Prepare a form Форманы дайындауға оралу No field in this form calculates its value yet. Give a field a calculation on the Calculate tab of its properties, and it shows here. Бұл формада мәнін есептейтін өріс әлі жоқ. Өріс сипаттарының «Есептеу» қойындысында оған есептеу беріңіз, сонда ол осында көрсетіледі. {names} and {last} {names} және {last} diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 09b9cad9fa..139c016b5f 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -2721,6 +2721,8 @@ theme Appearance 모양 Links on page Links on page Loop Loop Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. + Reads {name}, which the form doesn't have, so it gets no value there. Reads {name}, which the form doesn't have, so it gets no value there. + Its script also reads fields whose names it builds as it runs, so those reads can't be checked. Its script also reads fields whose names it builds as it runs, so those reads can't be checked. Move only what must move, so every field calculates after the fields it reads. Move only what must move, so every field calculates after the fields it reads. Moves only what must move Moves only what must move No field reads it No field reads it diff --git a/crates/ui-egui/src/i18n/lt.tsv b/crates/ui-egui/src/i18n/lt.tsv index 43749f4ec8..e19abaf203 100644 --- a/crates/ui-egui/src/i18n/lt.tsv +++ b/crates/ui-egui/src/i18n/lt.tsv @@ -701,6 +701,8 @@ Reads {names}, which read it back. No order can fix a loop: change one of the formulas. Skaito {names}, kurie savo ruožtu skaito jį. Jokia tvarka neištaisys ciklo: pakeiskite vieną iš formulių. Not in order Ne tvarkoje Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. Trūksta PDF faile įrašytoje skaičiavimo tvarkoje: kitos PDF peržiūros programos gali jo neskaičiuoti. + Reads {name}, which the form doesn't have, so it gets no value there. Skaito {name}, kurio formoje nėra, todėl iš ten negauna jokios reikšmės. + Its script also reads fields whose names it builds as it runs, so those reads can't be checked. Jo scenarijus taip pat skaito laukus, kurių pavadinimus sudaro vykdymo metu, todėl šių skaitymų patikrinti negalima. Back to Prepare a form Grįžti į „Rengti formą“ No field in this form calculates its value yet. Give a field a calculation on the Calculate tab of its properties, and it shows here. Nė vienas šios formos laukas dar neskaičiuoja savo reikšmės. Nustatykite laukui skaičiavimą jo savybių kortelėje „Skaičiavimas“, ir jis bus rodomas čia. {names} and {last} {names} ir {last} diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 963d717584..aa3444289f 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -536,6 +536,8 @@ Reads {names}, which read it back. No order can fix a loop: change one of the formulas. Odczytuje {names}, które z kolei odczytują je. Żadna kolejność nie naprawi pętli: zmień jedną z formuł. Not in order Poza kolejnością Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. Brakuje go w kolejności obliczeń zapisanej w pliku PDF: inne przeglądarki PDF mogą go nie obliczać. + Reads {name}, which the form doesn't have, so it gets no value there. Odczytuje {name}, którego nie ma w formularzu, więc nie dostaje stamtąd żadnej wartości. + Its script also reads fields whose names it builds as it runs, so those reads can't be checked. Jego skrypt odczytuje też pola, których nazwy tworzy w trakcie działania, więc tych odczytów nie da się sprawdzić. Back to Prepare a form Wróć do „Przygotuj formularz” No field in this form calculates its value yet. Give a field a calculation on the Calculate tab of its properties, and it shows here. Żadne pole tego formularza nie oblicza jeszcze swojej wartości. Nadaj polu obliczenie na karcie Obliczenia w jego właściwościach, a pojawi się tutaj. {names} and {last} {names} i {last} diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index c34ae83bc5..5ca0e2fb60 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -159,6 +159,8 @@ Reads {names}, which read it back. No order can fix a loop: change one of the formulas. Lê {names}, que o leem de volta. Nenhuma ordem resolve um ciclo: altere uma das fórmulas. Not in order Fora da ordem Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. Ausente da ordem de cálculo salva no PDF: outros leitores de PDF podem não calculá-lo. + Reads {name}, which the form doesn't have, so it gets no value there. Lê {name}, que não existe no formulário, então não recebe nenhum valor dali. + Its script also reads fields whose names it builds as it runs, so those reads can't be checked. Seu script também lê campos cujos nomes monta durante a execução, então essas leituras não podem ser verificadas. Back to Prepare a form Voltar para Preparar um formulário No field in this form calculates its value yet. Give a field a calculation on the Calculate tab of its properties, and it shows here. Nenhum campo deste formulário calcula seu valor ainda. Dê um cálculo a um campo na aba Calcular das propriedades dele, e ele aparecerá aqui. {names} and {last} {names} e {last} diff --git a/crates/ui-egui/src/i18n/ro.tsv b/crates/ui-egui/src/i18n/ro.tsv index 78d2298b93..cf1c74ad4d 100644 --- a/crates/ui-egui/src/i18n/ro.tsv +++ b/crates/ui-egui/src/i18n/ro.tsv @@ -2723,6 +2723,8 @@ theme Appearance Aspect Reads {names}, which read it back. No order can fix a loop: change one of the formulas. Citește {names}, care îl citesc la rândul lor. Nicio ordine nu poate rezolva o buclă: modificați una dintre formule. Not in order În afara ordinii Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. Lipsește din ordinea de calcul salvată în PDF: alte aplicații de vizualizare PDF s-ar putea să nu-l calculeze. + Reads {name}, which the form doesn't have, so it gets no value there. Citește {name}, care nu există în formular, deci nu primește nicio valoare de acolo. + Its script also reads fields whose names it builds as it runs, so those reads can't be checked. Scriptul său citește și câmpuri ale căror nume le construiește în timpul rulării, deci aceste citiri nu pot fi verificate. Back to Prepare a form Înapoi la Pregătire formular No field in this form calculates its value yet. Give a field a calculation on the Calculate tab of its properties, and it shows here. Niciun câmp din acest formular nu își calculează încă valoarea. Adăugați un calcul unui câmp în fila Calcul din proprietățile lui și va apărea aici. {names} and {last} {names} și {last} diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 274232d586..acfc72a2a0 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -534,6 +534,8 @@ Reads {names}, which read it back. No order can fix a loop: change one of the formulas. Читает {names}, которые в свою очередь читают его. Никакой порядок не исправит цикл: измените одну из формул. Not in order Вне порядка Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. Отсутствует в порядке вычислений, сохранённом в PDF: другие программы просмотра PDF могут его не вычислять. + Reads {name}, which the form doesn't have, so it gets no value there. Читает {name}, которого нет в форме, поэтому не получает оттуда значения. + Its script also reads fields whose names it builds as it runs, so those reads can't be checked. Его скрипт также читает поля, имена которых составляет во время выполнения, поэтому эти чтения нельзя проверить. Back to Prepare a form Назад к «Подготовить форму» No field in this form calculates its value yet. Give a field a calculation on the Calculate tab of its properties, and it shows here. Ни одно поле этой формы пока не вычисляет своё значение. Задайте полю вычисление на вкладке «Вычислить» его свойств, и оно появится здесь. {names} and {last} {names} и {last} diff --git a/crates/ui-egui/src/i18n/sl.tsv b/crates/ui-egui/src/i18n/sl.tsv index 420e6a5996..19ff9c059b 100644 --- a/crates/ui-egui/src/i18n/sl.tsv +++ b/crates/ui-egui/src/i18n/sl.tsv @@ -1027,6 +1027,8 @@ Reads {names}, which read it back. No order can fix a loop: change one of the formulas. Bere {names}, ki ga berejo nazaj. Noben vrstni red ne popravi zanke: spremenite eno od formul. Not in order Ni v vrstnem redu Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. Manjka v vrstnem redu izračunov, shranjenem v PDF-ju: drugi pregledovalniki PDF ga morda ne izračunajo. + Reads {name}, which the form doesn't have, so it gets no value there. Bere {name}, ki ga v obrazcu ni, zato od tam ne dobi vrednosti. + Its script also reads fields whose names it builds as it runs, so those reads can't be checked. Njegov skript bere tudi polja, katerih imena sestavi med izvajanjem, zato teh branj ni mogoče preveriti. Back to Prepare a form Nazaj na Pripravi obrazec No field in this form calculates its value yet. Give a field a calculation on the Calculate tab of its properties, and it shows here. Nobeno polje tega obrazca še ne izračuna svoje vrednosti. Polju dodelite izračun na zavihku Izračunaj v njegovih lastnostih in prikazalo se bo tukaj. {names} and {last} {names} in {last} diff --git a/crates/ui-egui/src/i18n/sq.tsv b/crates/ui-egui/src/i18n/sq.tsv index ba7c9dc5b3..ed552c8005 100644 --- a/crates/ui-egui/src/i18n/sq.tsv +++ b/crates/ui-egui/src/i18n/sq.tsv @@ -2409,6 +2409,8 @@ animation effects Off Joaktiv Mark as artifact Mark as artifact Merge image fragments Merge image fragments Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. + Reads {name}, which the form doesn't have, so it gets no value there. Reads {name}, which the form doesn't have, so it gets no value there. + Its script also reads fields whose names it builds as it runs, so those reads can't be checked. Its script also reads fields whose names it builds as it runs, so those reads can't be checked. Mobile Mobile Move only what must move, so every field calculates after the fields it reads. Move only what must move, so every field calculates after the fields it reads. Moves only what must move Moves only what must move diff --git a/crates/ui-egui/src/i18n/te.tsv b/crates/ui-egui/src/i18n/te.tsv index 7f339989ee..e058226539 100644 --- a/crates/ui-egui/src/i18n/te.tsv +++ b/crates/ui-egui/src/i18n/te.tsv @@ -518,6 +518,8 @@ comment menu Edit సవరించు Reads {names}, which read it back. No order can fix a loop: change one of the formulas. {names}ను చదువుతుంది, అవి దీన్ని తిరిగి చదువుతాయి. ఏ క్రమమూ లూప్‌ను సరిచేయదు: సూత్రాలలో ఒకదాన్ని మార్చండి. Not in order క్రమంలో లేదు Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. PDFలో సేవ్ చేసిన గణన క్రమంలో లేదు: ఇతర PDF వీక్షకులు దీన్ని లెక్కించకపోవచ్చు. + Reads {name}, which the form doesn't have, so it gets no value there. {name}ను చదువుతుంది, కానీ అది ఫారమ్‌లో లేదు, కాబట్టి దాని నుండి విలువ రాదు. + Its script also reads fields whose names it builds as it runs, so those reads can't be checked. దీని స్క్రిప్ట్ నడుస్తున్నప్పుడు పేర్లను కూర్చే ఫీల్డ్‌లను కూడా చదువుతుంది, కాబట్టి ఆ చదువులను తనిఖీ చేయలేము. Back to Prepare a form ‘ఫారాన్ని సిద్ధం చేయి’కి తిరిగి వెళ్లు No field in this form calculates its value yet. Give a field a calculation on the Calculate tab of its properties, and it shows here. ఈ ఫారంలో ఏ ఫీల్డ్ ఇంకా తన విలువను గణించదు. ఫీల్డ్ లక్షణాలలోని గణన ట్యాబ్‌లో దానికి గణన ఇవ్వండి, అది ఇక్కడ కనిపిస్తుంది. {names} and {last} {names} మరియు {last} diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index a2ecaea179..0be6a69520 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -499,6 +499,8 @@ Reads {names}, which read it back. No order can fix a loop: change one of the formulas. {names} alanlarını okur, onlar da bu alanı okur. Hiçbir sıra bir döngüyü düzeltemez: formüllerden birini değiştirin. Not in order Sırada değil Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. PDF'te kayıtlı hesaplama sırasında yok: diğer PDF görüntüleyicileri bunu hesaplamayabilir. + Reads {name}, which the form doesn't have, so it gets no value there. Formda olmayan {name} alanını okur, bu yüzden oradan değer alamaz. + Its script also reads fields whose names it builds as it runs, so those reads can't be checked. Komut dosyası, adlarını çalışırken oluşturduğu alanları da okur; bu okumalar denetlenemez. Back to Prepare a form Form hazırla'ya geri dön No field in this form calculates its value yet. Give a field a calculation on the Calculate tab of its properties, and it shows here. Bu formdaki hiçbir alan henüz değerini hesaplamıyor. Bir alana özelliklerinin Hesapla sekmesinde bir hesaplama verin, burada görünür. {names} and {last} {names} ve {last} diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 9c76868839..5d45a3f4b6 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -539,6 +539,8 @@ Reads {names}, which read it back. No order can fix a loop: change one of the formulas. Читає {names}, які своєю чергою читають його. Жоден порядок не виправить цикл: змініть одну з формул. Not in order Поза порядком Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. Відсутнє в порядку обчислень, збереженому в PDF: інші переглядачі PDF можуть його не обчислювати. + Reads {name}, which the form doesn't have, so it gets no value there. Читає {name}, якого немає у формі, тому не отримує звідти значення. + Its script also reads fields whose names it builds as it runs, so those reads can't be checked. Його сценарій також читає поля, імена яких складає під час виконання, тому ці читання неможливо перевірити. Back to Prepare a form Назад до «Підготувати форму» No field in this form calculates its value yet. Give a field a calculation on the Calculate tab of its properties, and it shows here. Жодне поле цієї форми поки не обчислює своє значення. Задайте полю обчислення на вкладці «Обчислення» його властивостей, і воно з’явиться тут. {names} and {last} {names} і {last} diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 973ba870ea..2b51f445a3 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -697,6 +697,8 @@ theme Appearance Giao diện Reads {names}, which read it back. No order can fix a loop: change one of the formulas. Đọc {names}, các trường này lại đọc ngược nó. Không thứ tự nào sửa được vòng lặp: hãy đổi một trong các công thức. Not in order Không có trong thứ tự Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. Không có trong thứ tự tính toán lưu trong PDF: các trình xem PDF khác có thể không tính trường này. + Reads {name}, which the form doesn't have, so it gets no value there. Đọc {name}, nhưng biểu mẫu không có trường này, nên không nhận được giá trị nào từ đó. + Its script also reads fields whose names it builds as it runs, so those reads can't be checked. Tập lệnh của nó còn đọc các trường có tên được tạo ra khi chạy, nên không thể kiểm tra các lần đọc đó. Back to Prepare a form Quay lại Chuẩn bị biểu mẫu No field in this form calculates its value yet. Give a field a calculation on the Calculate tab of its properties, and it shows here. Chưa có trường nào trong biểu mẫu này tự tính giá trị. Hãy đặt phép tính cho một trường ở thẻ Tính toán trong thuộc tính của nó, trường đó sẽ hiện ở đây. {names} and {last} {names} và {last} diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index d77c8f6425..c4aae05c8a 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -208,6 +208,8 @@ Reads {names}, which read it back. No order can fix a loop: change one of the formulas. 读取 {names},而它们又读取此字段。任何顺序都无法修正循环:请修改其中一个公式。 Not in order 不在顺序中 Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. PDF 中保存的计算顺序缺少此字段:其他 PDF 查看器可能不会计算它。 + Reads {name}, which the form doesn't have, so it gets no value there. 读取表单中不存在的 {name},因此从中得不到值。 + Its script also reads fields whose names it builds as it runs, so those reads can't be checked. 其脚本还会读取运行时才拼出名称的字段,因此无法检查这些读取。 Back to Prepare a form 返回准备表单 No field in this form calculates its value yet. Give a field a calculation on the Calculate tab of its properties, and it shows here. 此表单中还没有计算自身值的字段。在字段属性的“计算”选项卡中为字段设置计算后,它会显示在这里。 {names} and {last} {names}和{last} diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index ce3b939f1d..82d6eb6a5d 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -164,6 +164,8 @@ Reads {names}, which read it back. No order can fix a loop: change one of the formulas. 讀取 {names},而它們又讀取此欄位。任何順序都無法修正迴圈:請修改其中一個公式。 Not in order 不在順序中 Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it. PDF 中儲存的計算順序缺少此欄位:其他 PDF 檢視器可能不會計算它。 + Reads {name}, which the form doesn't have, so it gets no value there. 讀取表單中不存在的 {name},因此從中得不到值。 + Its script also reads fields whose names it builds as it runs, so those reads can't be checked. 其指令碼還會讀取執行時才組出名稱的欄位,因此無法檢查這些讀取。 Back to Prepare a form 返回準備表單 No field in this form calculates its value yet. Give a field a calculation on the Calculate tab of its properties, and it shows here. 此表單中還沒有計算自身值的欄位。在欄位內容的「計算」索引標籤中為欄位設定計算後,它會顯示在這裡。 {names} and {last} {names}和{last} diff --git a/crates/ui-egui/src/tools/calc_order_ui.rs b/crates/ui-egui/src/tools/calc_order_ui.rs index 2b7a0c5f94..e02983ac5d 100644 --- a/crates/ui-egui/src/tools/calc_order_ui.rs +++ b/crates/ui-egui/src/tools/calc_order_ui.rs @@ -475,6 +475,9 @@ fn explained(issues: &[CalculationIssue]) -> String { issues .iter() .map(|i| match i { + CalculationIssue::MissingField { field } => { + crate::i18n::fmt(tl!("Reads {name}, which the form doesn't have, so it gets no value there."), &[("name", field)]) + } CalculationIssue::RunsTooEarly { field } => { crate::i18n::fmt(tl!("Reads {name}, which calculates later, so it shows an old value."), &[("name", field)]) } @@ -485,6 +488,9 @@ fn explained(issues: &[CalculationIssue]) -> String { CalculationIssue::NotInOrder => { tl!("Missing from the calculation order saved in the PDF: other PDF viewers may not calculate it.").to_owned() } + CalculationIssue::BuiltNames => { + tl!("Its script also reads fields whose names it builds as it runs, so those reads can't be checked.").to_owned() + } }) .collect::>() .join("\n") diff --git a/docs/roadmap.md b/docs/roadmap.md index 25205e92c1..6e929825ba 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -2,6 +2,7 @@ > **Last reviewed:** 2026-10-27 | **Last updated:** 2026-10-27 | **Change:** minor (M10: OCR model downloads use verified ONNX pairs with legacy discovery fallback; estimates unchanged) > **Last reviewed:** 2026-10-25 | **Last updated:** 2026-10-25 | **Change:** minor (M14 localization current state: Arabic logical catalog, explicit RTL label direction and mirrored stock chrome; native/browser RTL workflows unqualified. Native release Hans face via LXGW WenKai Lite; estimates unchanged) +> **Last reviewed:** 2026-10-23 · **Last updated:** 2026-10-23 · **Change:** minor (M6: the calculation order flags a formula that reads a field the form doesn't have and a script that builds the names it reads; the tools show each formula; estimates unchanged) > **Last reviewed:** 2026-10-23 · **Last updated:** 2026-10-23 · **Change:** minor (M7, #2636 partial: pasting added text boxes and images in the engine and through `content_copy`; estimates unchanged) > **Last reviewed:** 2026-10-23 · **Last updated:** 2026-10-23 · **Change:** minor (M11: image optimization accounts for page-local UserUnit; estimates unchanged) @@ -255,6 +256,7 @@ Estimates unchanged. | Date | Change | Summary | |---|---|---| +| 2026-10-23 | minor | M6: calculation checks: a formula reading a field the form doesn't have, and a script building the names it reads, are flagged by `form_calculation_order` and on the Calculation order page; `form_calculation_order` gives each formula and `form_fields` a custom Calculate script. Estimates unchanged. | | 2026-10-27 | minor | M10: OCR stages SHA-256-verified ONNX models after the upstream RTEN URL brownout; discovery prefers a complete ONNX pair per directory, falls back to a complete RTEN pair, and never mixes formats or directories; language/web limits and estimates unchanged. | | 2026-10-24 | minor | M3, #3970 follow-up: the button release given back after a window move or resize goes before the pointer leaving, so hover doesn't stick. Estimates unchanged. | | 2026-10-23 | minor | M8, #3205 follow-up: phone-pattern redaction includes the +39 country code on labelled Italian geographic numbers with a slash; dates, protocol identifiers and whole-run guards remain. Estimates unchanged. | diff --git a/parity/acrobat-features.toml b/parity/acrobat-features.toml index 3b8dba920c..766c439703 100644 --- a/parity/acrobat-features.toml +++ b/parity/acrobat-features.toml @@ -4679,8 +4679,8 @@ milestone = "M6" status = "shipped" commands = ["form.field.properties", "form.calculation_order"] tools = ["form_set_props", "form_fill", "form_calculation_order"] -evidence = ["crates/forms/src/tests.rs::formats_validation_and_calculations_run_like_acrobat", "crates/forms/src/tests.rs::the_calculation_order_is_checked_fixed_and_kept", "crates/forms/src/tests.rs::a_calculation_order_with_gaps_and_extra_entries", "crates/forms/src/tests.rs::two_fields_with_one_name_keep_their_places_in_the_order", "crates/engine/src/tests.rs::setting_the_calculation_order_recalculates_and_undoes", "crates/automation/tests/it/automation.rs::the_calculation_order_is_read_fixed_and_set", "crates/ui-egui/tests/it/prepare.rs::the_calculation_order_is_moved_dragged_and_fixed", "crates/engine/src/tests.rs::checkbox_and_rank_changes_recalculate_ordered_dependents", "crates/engine/src/tests.rs::dynamic_calculation_reads_and_side_effects_reach_later_fields"] -notes = "/CO order, then other calculated fields; recalculated after every fill and Clear form. Order editing: Prepare a form ▸ Calculation order lists the calculated fields in order with their formulas and values, Up/Down arrows (as Acrobat's Set Field Calculation Order dialog), Alt+Up/Down and drag, says which fields read a field calculated later, which read each other in a loop and which /CO leaves out, and Fix order moves only what must move; on the page it numbers each calculated field's step and draws arrows from the fields the focused field reads and to the fields that read it; form_calculation_order reads, sets, moves and fixes it. A new calculation joins the end of /CO and an edited one keeps its place. Acrobat's dialog is known from Adobe's help summary only, not yet observed." +evidence = ["crates/forms/src/tests.rs::formats_validation_and_calculations_run_like_acrobat", "crates/forms/src/tests.rs::the_calculation_order_is_checked_fixed_and_kept", "crates/forms/src/tests.rs::a_calculation_order_with_gaps_and_extra_entries", "crates/forms/src/tests.rs::two_fields_with_one_name_keep_their_places_in_the_order", "crates/engine/src/tests.rs::setting_the_calculation_order_recalculates_and_undoes", "crates/automation/tests/it/automation.rs::the_calculation_order_is_read_fixed_and_set", "crates/ui-egui/tests/it/prepare.rs::the_calculation_order_is_moved_dragged_and_fixed", "crates/engine/src/tests.rs::checkbox_and_rank_changes_recalculate_ordered_dependents", "crates/engine/src/tests.rs::dynamic_calculation_reads_and_side_effects_reach_later_fields", "crates/forms/src/tests.rs::missing_fields_and_built_names_are_flagged", "crates/forms/src/calc_order.rs::a_read_name_finds_a_field_a_group_or_a_widget", "crates/forms/src/af/tests.rs::names_built_at_run_time_are_noticed", "crates/automation/tests/it/automation.rs::the_calculation_order_shows_formulas_and_what_they_cannot_read"] +notes = "/CO order, then other calculated fields; recalculated after every fill and Clear form. Order editing: Prepare a form ▸ Calculation order lists the calculated fields in order with their formulas and values, Up/Down arrows (as Acrobat's Set Field Calculation Order dialog), Alt+Up/Down and drag, says which fields read a field calculated later, which read each other in a loop and which /CO leaves out, and Fix order moves only what must move; on the page it numbers each calculated field's step and draws arrows from the fields the focused field reads and to the fields that read it; form_calculation_order reads, sets, moves and fixes it. Each formula is also checked against the form: a read of a field the form doesn't have (a field deleted from under a sum, a typo in notation, a script's getField) and a script that builds the names it reads at run time are flagged, in form_calculation_order (which also gives each formula) and in the page's row tooltips. A new calculation joins the end of /CO and an edited one keeps its place. Acrobat's dialog is known from Adobe's help summary only, not yet observed." [[feature]] id = "form.event-order" @@ -5051,8 +5051,8 @@ milestone = "M6" status = "shipped" commands = ["form.field.properties"] tools = ["form_set_props", "form_fill"] -evidence = ["crates/ui-egui/tests/it/prepare.rs::format_validate_and_calculate_tabs", "crates/automation/tests/it/automation.rs::form_formats_and_calculations_through_tools", "crates/forms/src/tests.rs::renaming_a_field_renames_it_in_the_calculations_that_read_it", "crates/forms/src/tests.rs::a_rename_reaches_every_stored_form_of_a_calculation", "crates/automation/tests/it/automation.rs::renaming_a_field_keeps_the_formulas_that_read_it", "crates/ui-egui/tests/it/prepare.rs::renaming_a_field_says_how_many_calculations_now_use_the_new_name", "crates/forms/src/af/tests.rs::a_regex_after_a_condition_is_not_read_as_code", "crates/forms/src/af/tests.rs::a_line_comment_ends_at_any_line_terminator", "crates/forms/src/af/tests.rs::a_token_ends_at_a_line_separator"] -notes = "sum/product/average/min/max of picked fields, simplified field notation; custom script pending. Renaming a field renames it in the calculations that read it (field lists, notation, and getField calls naming it in a string literal), in the rename's undo step; Field Properties says how many changed and form_set_props lists them. Not rewritten: names built at run time, other events' scripts, document-level scripts. A regular expression that starts a statement right after a `}` is still read as code, so a quote inside one can hide the getField calls after it from a rename and from the calculation order. Whether Acrobat does this is not observed." +evidence = ["crates/ui-egui/tests/it/prepare.rs::format_validate_and_calculate_tabs", "crates/automation/tests/it/automation.rs::form_formats_and_calculations_through_tools", "crates/forms/src/tests.rs::renaming_a_field_renames_it_in_the_calculations_that_read_it", "crates/forms/src/tests.rs::a_rename_reaches_every_stored_form_of_a_calculation", "crates/automation/tests/it/automation.rs::renaming_a_field_keeps_the_formulas_that_read_it", "crates/ui-egui/tests/it/prepare.rs::renaming_a_field_says_how_many_calculations_now_use_the_new_name", "crates/forms/src/af/tests.rs::a_regex_after_a_condition_is_not_read_as_code", "crates/forms/src/af/tests.rs::a_line_comment_ends_at_any_line_terminator", "crates/forms/src/af/tests.rs::a_token_ends_at_a_line_separator", "crates/automation/tests/it/automation.rs::the_calculation_order_shows_formulas_and_what_they_cannot_read"] +notes = "sum/product/average/min/max of picked fields, simplified field notation; custom script pending. form_fields shows a custom Calculate script as {script}. Renaming a field renames it in the calculations that read it (field lists, notation, and getField calls naming it in a string literal), in the rename's undo step; Field Properties says how many changed and form_set_props lists them. Not rewritten: names built at run time, other events' scripts, document-level scripts. A regular expression that starts a statement right after a `}` is still read as code, so a quote inside one can hide the getField calls after it from a rename and from the calculation order. Whether Acrobat does this is not observed." [[feature]] id = "form.props-selection-change"