Redaction Tool Properties: the mark's appearance, and Make properties default (#2008)

* Redaction Tool Properties: the mark's appearance, and Make properties default

Acrobat's third group in the Redaction Tool Properties dialog: the mark's
outline colour (/C), a fill colour shown while the pointer is over a mark (a
viewing preference, never written), and the mark's opacity (/CA). Make
properties default: ticked, OK keeps the values for the next run (saved with
the settings as redact_prefs); unticked, they hold for this run only and the
saved defaults stay; Cancel puts back what the dialog opened with. The dialog
is laid out in two columns so it fits a laptop screen. Rows in every
language, their checksums in ATTRIBUTION.toml, parity
protect.redaction-properties shipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Refresh catalog hashes after merging main

---------

Co-authored-by: Osvaldo Duran <338149330+regendorf-ux@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
regendorf-ux
2026-10-15 02:38:22 -04:00
committed by GitHub
co-authored by Claude Opus 5.5 Osvaldo Duran Brandon Thomas
parent 6c22c78a72
commit 8da1d3efce
30 changed files with 536 additions and 119 deletions
+21 -21
View File
@@ -7,7 +7,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Greek UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "312db664c98a25a4ed6b098b4849a95da2a29efe8d1a9e039ff47a92d27ccd0e"
sha256 = "8f2dc597b6f0b05c430260cccfa98e59905ae6badd920ea6982e6a48736e7602"
# ATTRIBUTION.toml: one entry per asset (AGENTS.md §1). The source of truth for provenance and licences.
# ATTRIBUTION.md is generated from this file: `cargo xtask assets --write`. Checked by `cargo xtask assets` (part of `xtask ci`).
@@ -30,7 +30,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Simplified Chinese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "1928f3ee702ac4c5d24bd68d8ea3985920e3405347d46a5c8c636dad1a88f761"
sha256 = "747fb15896208d759e3c4d4ef243d39ea55f39e7d096a4f0c17f7b8276dd01f7"
[[asset]]
path = "crates/ui-egui/src/i18n/zh-hant.tsv"
@@ -41,7 +41,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Traditional Chinese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "8b10d4e4bd7ce694afa0653d53148af767f5619dd50637b578e55a3c7e955361"
sha256 = "4430f20b3dd5e86471fb61d63529695f1b81d7af14cba028aa9e4a7709f2eb51"
[[asset]]
path = "crates/ui-egui/src/i18n/cs.tsv"
@@ -52,7 +52,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Czech UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "f953308d54011abee040dd192f48a923216dd2a572f1d14dd550dd6ac8ba86df"
sha256 = "4aef3cd513d168abd15d1760d6e754278e7539e4c02f36770f9eed44dd6e88fd"
[[asset]]
path = "crates/ui-egui/src/i18n/pt-br.tsv"
@@ -63,7 +63,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Brazilian Portuguese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "e01a0ec8da6dffc4dbafe4cc411a7a1ed9cba76bc75b10435818aae04dda0e4f"
sha256 = "707973b0c7f0e546c84c0ba07b4d828071d24a8318f1287ccbdf065b985ac326"
[[asset]]
path = "crates/ui-egui/src/i18n/es.tsv"
@@ -74,7 +74,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Spanish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "d1414ea5a02fd634b635231e4439b5f279dcc3473869bbd534bc9a70dad6dbd8"
sha256 = "df205fdfc750a3f9de5a667710506f09fbf5604d632576b0085f8f0560cf1bfd"
[[asset]]
path = "crates/ui-egui/src/i18n/fr.tsv"
@@ -85,7 +85,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "French UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "234b7850d3067361eb13ec85170f45e5c9708e702b091b529a4c336e9f7b6560"
sha256 = "50fc9a57a9d301f46d38a812764b4e6e9791fff154297cdaa5240519d281c48b"
[[asset]]
path = "crates/ui-egui/src/i18n/de.tsv"
@@ -96,7 +96,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "German UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "0699859ac9c163d408bb6c8273f4d9d0be8e68f6338d2dd156d96fc5a2e411c8"
sha256 = "cbdd825e664c37a1dc2cf559a63a1ca6fb093a8c1036ccdbbf0738d684e61c8b"
[[asset]]
path = "crates/ui-egui/src/i18n/te.tsv"
@@ -107,7 +107,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Telugu UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "512ecfeb69f0ebc1caaa58ce30350c63ffa9a80e9222629b69c9d9df413ba499"
sha256 = "c119f82118264b45e46de6c31f4db3c062c86f35a661b8d2f12170ee11042ddf"
[[asset]]
path = "crates/ui-egui/src/i18n/it.tsv"
@@ -118,7 +118,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Italian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "766362711c5980ba2fae81781f815621afb390db4c47fcfd8a3e77d5f1deb934"
sha256 = "24132323fb0ab81a96a18c1ed7a7dee719469b3733f0425346fce8f9a8ea8081"
[[asset]]
path = "crates/ui-egui/src/i18n/pl.tsv"
@@ -129,7 +129,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Polish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "ea9f02fc204f19e464b802bf32ba090dfac890283883a04eeb066b529cb4d3d1"
sha256 = "8146db6443af5cb41655aabc415d6bd738259c886dc47eddb4fd88b82f166347"
[[asset]]
path = "crates/ui-egui/src/i18n/ru.tsv"
@@ -140,7 +140,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Russian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "e42b97aad738f199698d38ed475aa3eada7ab1765526ef1e8250af3a863768dd"
sha256 = "6e18893f0cad5d23b72bfcd5e8bbd697143811a4288a71efc87292e7bf1ef39b"
[[asset]]
path = "crates/ui-egui/src/i18n/hu.tsv"
@@ -151,7 +151,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Hungarian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "848cc35acaf475c6a532e20cfcefc3f9d4be7570764882bc1becc6ad1d144abb"
sha256 = "8f4ddb7fec77362d652f4a08f65610ebbf83f1fce1c061dbc347b22bd92c9b23"
[[asset]]
path = "crates/ui-egui/src/i18n/bg.tsv"
@@ -162,7 +162,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Bulgarian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "9e65e6297aea35211aff2198700b5596a55d23c67ce8a6d83b7098f49e29b22a"
sha256 = "f1e021147716496129d887db04f576380f3ed75348dece15cf6446e5e57ef0e9"
[[asset]]
path = "crates/ui-egui/src/i18n/kk.tsv"
@@ -173,7 +173,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Kazakh UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "5c2e5a2b373e83506025ae2a065a74e5fc34639145e932d9abaf38be650a1b56"
sha256 = "842fe4d5047359db29f014b23eee4b7c1eb2cb76b0bf13077dcace2c763a17f4"
[[asset]]
path = "crates/ui-egui/src/i18n/ja.tsv"
@@ -184,7 +184,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Japanese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "44ac275334de6ccd1ac14c45bd51378c9d86d7fe6060430c16c581cdcf09715c"
sha256 = "ad4bb29a8c740bc5d9148f93d13e8b548509b107d9612a1a45e2a80decf1b0a8"
[[asset]]
path = "crates/ui-egui/src/i18n/uk.tsv"
@@ -195,7 +195,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Ukrainian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "03638838a52bb75bf9bd17d5e050cffdef42c2f4f2eafd61e04953feaf663ff6"
sha256 = "0fb190f6ee02103b7f06a94ee2ed7764c56ab8b905567d68485b8023b66e3e59"
[[asset]]
path = "crates/ui-egui/src/i18n/ar.tsv"
@@ -206,7 +206,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Arabic UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "df1501a33b15029dac0740994a286809c5ec710195efb3f3c983bd363695cd3f"
sha256 = "15aa44d01688879b0321f820b0087cbcb9e3924a957cf1457ee9256e42626957"
[[asset]]
path = "crates/ui-egui/src/i18n/tr.tsv"
@@ -217,7 +217,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Turkish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "ae494a56449a5e574621219c18d1c45a817a803ccff8a3e8decdbdfe4f5cca21"
sha256 = "09cf93e500a6a0c461942ceb985731dbcee3ca00bb3160c1af60448a402b1862"
[[asset]]
path = "crates/ui-egui/src/i18n/lt.tsv"
@@ -228,7 +228,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Lithuanian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "e2f03a2c1832fabdc037a5b7d4d314c15d4d7b2b337aeec9b0e92b7584b4a49c"
sha256 = "10222eaf5a9dd16f5677554f586ed715f10f2c56ccfc8ffcf68f3f90d2e8c61a"
[[asset]]
@@ -3363,4 +3363,4 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Slovene UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "291a76520d622c7b5ef141e6f0bcaeb3e319974a1390b23179cb14dbcf03a28c"
sha256 = "dddc56fe8c320dd807c56a5d6450eeb5cdaecc81d9e517b6c1adfe986d688a41"
+2 -1
View File
@@ -2452,6 +2452,7 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {
| QuickTool::Snapshot => false,
};
let prefs = &app.comment_prefs;
let redact_mark = (app.redact_prefs.hover_fill, app.redact_prefs.opacity);
let units = app.units;
let allowed = doc.allows_annotation();
let comments_hidden = doc.comments_hidden();
@@ -2727,7 +2728,7 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {
painter.rect_stroke(r, CornerRadius::ZERO, Stroke::new(0.5, t.border.gamma_multiply(0.8)), egui::StrokeKind::Outside);
// Comments: tools, selection, moving and resizing come before text selection.
let pcx = comments::PageCx { page: i, xf: &xf, info, tool, prefs, allowed, hidden: comments_hidden };
let pcx = comments::PageCx { page: i, xf: &xf, info, tool, prefs, allowed, hidden: comments_hidden, redact_mark };
if let QuickTool::Measure(measure_tool) = tool {
crate::measure_ui::page_input(ui, &resp, doc, view, &pcx, measure_tool, units);
}
+4 -1
View File
@@ -454,7 +454,10 @@ impl PdfCraftApp {
self.redact_search.found = None;
self.dialog = Some(Dialog::RedactSearch);
}
"redact.properties" => self.dialog = Some(Dialog::RedactProps),
"redact.properties" => {
self.redact_before = Some(self.redact_prefs.clone());
self.dialog = Some(Dialog::RedactProps);
}
"redact.apply" => {
let marks = active.and_then(|i| self.session.get(self.views[i].id)).map_or(0, |d| d.redaction_marks());
if marks == 0 {
+33 -1
View File
@@ -511,6 +511,9 @@ pub(crate) struct PageCx<'a> {
pub info: &'a DocInfo,
pub tool: QuickTool,
pub prefs: &'a CommentPrefs,
/// A redaction mark's fill while the pointer is over it, and its opacity (Redaction Tool
/// Properties).
pub redact_mark: ([f64; 3], f64),
/// The document allows commenting.
pub allowed: bool,
/// Comments are hidden (Hide all comments): nothing to select.
@@ -556,6 +559,23 @@ impl PageCx<'_> {
}
}
/// A redaction mark's areas: one per marked quad, or its rectangle.
fn redact_rects(&self, a: &Annotation) -> Vec<Rect> {
if a.quads.is_empty() {
return vec![self.screen_rect(a)];
}
a.quads
.iter()
.map(|q| {
let xs = [q[0], q[2], q[4], q[6]];
let ys = [q[1], q[3], q[5], q[7]];
let (x0, x1) = (xs.iter().copied().fold(f32::MAX, f32::min), xs.iter().copied().fold(f32::MIN, f32::max));
let (y0, y1) = (ys.iter().copied().fold(f32::MAX, f32::min), ys.iter().copied().fold(f32::MIN, f32::max));
self.xf.user_rect(self.info, self.page, [x0, y0, x1, y1])
})
.collect()
}
/// The topmost comment under `p`.
fn hit(&self, p: Pos2) -> Option<&Annotation> {
self.comments().filter(|a| self.screen_rects(a).iter().any(|r| r.expand(3.0).contains(p))).last()
@@ -1071,6 +1091,18 @@ pub(crate) fn page_after_text(resp: &egui::Response, cx: &PageCx<'_>, view: &mut
pub(crate) fn paint_page(ui: &egui::Ui, painter: &egui::Painter, cx: &PageCx<'_>, view: &DocView) {
let cv = &view.comments;
let pointer = ui.input(|i| i.pointer.hover_pos());
// A redaction mark shows its fill while the pointer is over it, as Acrobat's does, so the
// marked area reads before anything is applied.
if cv.gesture.is_none()
&& let Some(a) = pointer.and_then(|p| cx.hit(p))
&& a.subtype == "Redact"
{
let ([r, g, b], opacity) = cx.redact_mark;
let fill = Color32::from_rgba_unmultiplied((r * 255.0) as u8, (g * 255.0) as u8, (b * 255.0) as u8, (opacity.clamp(0.0, 1.0) * 255.0) as u8);
for rect in cx.redact_rects(a) {
painter.rect_filled(rect, CornerRadius::ZERO, fill);
}
}
if cv.gesture.is_none()
&& let Some(a) = pointer.and_then(|p| cx.hit(p))
&& (cx.tool == QuickTool::Select || matches!(cx.tool, QuickTool::Fill(_)) && a.fill_sign)
@@ -1231,7 +1263,7 @@ pub(crate) fn composer(ctx: &egui::Context, view: &mut DocView, info: &DocInfo,
// Scrolled away: keep the draft until the page is back.
return None;
};
let cx = PageCx { page, xf: &xf, info, tool: QuickTool::Select, prefs, allowed: true, hidden: false };
let cx = PageCx { page, xf: &xf, info, tool: QuickTool::Select, prefs, allowed: true, hidden: false, redact_mark: ([0.89, 0.13, 0.13], 1.0) };
let anchor = cx.to_screen(c.at);
let t = Tokens::get(ctx);
let mut post = false;
+9
View File
@@ -537,6 +537,15 @@ fn dialogs(app: &mut PdfCraftApp, ctx: &egui::Context) {
let mut prefs = app.redact_prefs.clone();
let (ok, cancel) = crate::redact_ui::props_body(ui, &mut prefs, &t);
app.redact_prefs = prefs;
if ok && app.redact_prefs.remember {
app.redact_defaults = app.redact_prefs.clone();
}
if cancel && let Some(before) = app.redact_before.take() {
app.redact_prefs = before;
}
if ok {
app.redact_before = None;
}
close = ok || cancel;
return;
}
+6
View File
@@ -2415,3 +2415,9 @@ animation effects Off معطّلة
Crop Image قص الصورة
Remove Crop إزالة القص
Trim the picture with its handles; Enter or Done applies قص الصورة بمقابضها؛ يطبّق Enter أو تم
Redaction mark appearance مظهر علامة الحجب
Outline colour: لون الحد الخارجي:
Fill colour: لون التعبئة:
Shown while the pointer is over a mark. يظهر أثناء وجود المؤشر فوق علامة.
Opacity: العتامة:
Make properties default جعل الخصائص افتراضية
Can't render this file because it contains an unexpected character in line 6 and column 47.
+6
View File
@@ -2357,3 +2357,9 @@ animation effects Off Изключени
Crop Image Изрязване на изображението
Remove Crop Премахване на изрязването
Trim the picture with its handles; Enter or Done applies Изрежете картината чрез дръжките; Enter или Готово прилага
Redaction mark appearance Вид на маркировката за заличаване
Outline colour: Цвят на контура:
Fill colour: Цвят на запълването:
Shown while the pointer is over a mark. Показва се, докато показалецът е над маркировка.
Opacity: Непрозрачност:
Make properties default Задаване като свойства по подразбиране
Can't render this file because it contains an unexpected character in line 8 and column 16.
+6
View File
@@ -2468,3 +2468,9 @@ animation effects Off Vypnuto
Crop Image Oříznout obrázek
Remove Crop Odebrat oříznutí
Trim the picture with its handles; Enter or Done applies Ořízněte obrázek úchyty; Enter nebo Hotovo použije
Redaction mark appearance Vzhled značky začernění
Outline colour: Barva obrysu:
Fill colour: Barva výplně:
Shown while the pointer is over a mark. Zobrazí se, když je ukazatel nad značkou.
Opacity: Neprůhlednost:
Make properties default Nastavit vlastnosti jako výchozí
Can't render this file because it contains an unexpected character in line 6 and column 47.
+6
View File
@@ -2395,3 +2395,9 @@ animation effects Off Aus
Crop Image Bild zuschneiden
Remove Crop Zuschnitt entfernen
Trim the picture with its handles; Enter or Done applies Bild mit den Griffen zuschneiden; Eingabe oder Fertig übernimmt
Redaction mark appearance Aussehen der Schwärzungsmarkierung
Outline colour: Konturfarbe:
Fill colour: Füllfarbe:
Shown while the pointer is over a mark. Wird angezeigt, solange der Zeiger über einer Markierung ist.
Opacity: Deckkraft:
Make properties default Eigenschaften als Standard festlegen
Can't render this file because it contains an unexpected character in line 6 and column 51.
+6
View File
@@ -2404,3 +2404,9 @@ animation effects Off Ανενεργά
Crop Image Περικοπή εικόνας
Remove Crop Κατάργηση περικοπής
Trim the picture with its handles; Enter or Done applies Περικόψτε την εικόνα με τις λαβές της· Enter ή Τέλος για εφαρμογή
Redaction mark appearance Εμφάνιση σήμανσης απαλοιφής
Outline colour: Χρώμα περιγράμματος:
Fill colour: Χρώμα γεμίσματος:
Shown while the pointer is over a mark. Εμφανίζεται όσο ο δείκτης είναι πάνω από μια σήμανση.
Opacity: Αδιαφάνεια:
Make properties default Ορισμός ιδιοτήτων ως προεπιλογή
Can't render this file because it contains an unexpected character in line 475 and column 9.
+6
View File
@@ -2524,3 +2524,9 @@ animation effects Off Desactivados
Crop Image Recortar imagen
Remove Crop Quitar recorte
Trim the picture with its handles; Enter or Done applies Recorta la imagen con sus tiradores; Intro o Hecho lo aplica
Redaction mark appearance Aspecto de la marca de censura
Outline colour: Color del contorno:
Fill colour: Color de relleno:
Shown while the pointer is over a mark. Se muestra mientras el puntero está sobre una marca.
Opacity: Opacidad:
Make properties default Usar como propiedades predeterminadas
Can't render this file because it contains an unexpected character in line 6 and column 60.
+6
View File
@@ -2394,3 +2394,9 @@ animation effects Off Désactivés
Crop Image Recadrer l'image
Remove Crop Supprimer le recadrage
Trim the picture with its handles; Enter or Done applies Recadrez l'image avec ses poignées ; Entrée ou Terminé applique
Redaction mark appearance Apparence de la marque de biffure
Outline colour: Couleur du contour :
Fill colour: Couleur de remplissage :
Shown while the pointer is over a mark. Affichée tant que le pointeur survole une marque.
Opacity: Opacité :
Make properties default Définir comme propriétés par défaut
Can't render this file because it contains an unexpected character in line 6 and column 39.
+6
View File
@@ -2393,3 +2393,9 @@ animation effects Off Ki
Crop Image Kép körülvágása
Remove Crop Körülvágás eltávolítása
Trim the picture with its handles; Enter or Done applies Vágja körül a képet a fogantyúival; Enter vagy Kész alkalmazza
Redaction mark appearance Kitakarási jelölés megjelenése
Outline colour: Körvonal színe:
Fill colour: Kitöltés színe:
Shown while the pointer is over a mark. Akkor látszik, amikor a mutató egy jelölés fölött van.
Opacity: Átlátszatlanság:
Make properties default Tulajdonságok beállítása alapértelmezettként
Can't render this file because it contains an unexpected character in line 7 and column 44.
+6
View File
@@ -2403,3 +2403,9 @@ animation effects Off Disattivati
Crop Image Ritaglia immagine
Remove Crop Rimuovi ritaglio
Trim the picture with its handles; Enter or Done applies Ritaglia l'immagine con le maniglie; Invio o Fatto applica
Redaction mark appearance Aspetto del segno di redazione
Outline colour: Colore contorno:
Fill colour: Colore riempimento:
Shown while the pointer is over a mark. Visibile mentre il puntatore è su un segno.
Opacity: Opacità:
Make properties default Imposta proprietà come predefinite
Can't render this file because it contains an unexpected character in line 6 and column 47.
+6
View File
@@ -2394,3 +2394,9 @@ animation effects Off オフ
Crop Image 画像を切り抜き
Remove Crop 切り抜きを解除
Trim the picture with its handles; Enter or Done applies ハンドルで画像を切り抜きます。Enter または完了で適用
Redaction mark appearance 墨消しマークの外観
Outline colour: 枠線の色:
Fill colour: 塗りの色:
Shown while the pointer is over a mark. ポインターがマーク上にあるときに表示されます。
Opacity: 不透明度:
Make properties default プロパティをデフォルトに設定
Can't render this file because it contains an unexpected character in line 6 and column 47.
+6
View File
@@ -2392,3 +2392,9 @@ animation effects Off Өшірулі
Crop Image Суретті қию
Remove Crop Қиюды алып тастау
Trim the picture with its handles; Enter or Done applies Суретті тұтқаларымен қиыңыз; Enter немесе Дайын қолданады
Redaction mark appearance Жасыру белгісінің көрінісі
Outline colour: Контур түсі:
Fill colour: Толтыру түсі:
Shown while the pointer is over a mark. Меңзер белгінің үстінде тұрғанда көрсетіледі.
Opacity: Мөлдірсіздік:
Make properties default Қасиеттерді әдепкі ету
Can't render this file because it contains an unexpected character in line 822 and column 9.
+6
View File
@@ -2638,3 +2638,9 @@ key Space Tarpas
Crop Image Apkarpyti vaizdą
Remove Crop Pašalinti apkarpymą
Trim the picture with its handles; Enter or Done applies Apkarpykite paveikslėlį rankenėlėmis; Enter arba Atlikta pritaiko
Redaction mark appearance Užtamsinimo žymės išvaizda
Outline colour: Kontūro spalva:
Fill colour: Užpildo spalva:
Shown while the pointer is over a mark. Rodoma, kol žymeklis yra virš žymės.
Opacity: Nepermatomumas:
Make properties default Nustatyti ypatybes kaip numatytąsias
Can't render this file because it contains an unexpected character in line 401 and column 9.
+6
View File
@@ -2374,3 +2374,9 @@ animation effects Off Wyłączone
Crop Image Przytnij obraz
Remove Crop Usuń przycięcie
Trim the picture with its handles; Enter or Done applies Przytnij obraz uchwytami; Enter lub Gotowe stosuje
Redaction mark appearance Wygląd znacznika redakcji
Outline colour: Kolor obrysu:
Fill colour: Kolor wypełnienia:
Shown while the pointer is over a mark. Widoczny, gdy wskaźnik jest nad znacznikiem.
Opacity: Krycie:
Make properties default Ustaw właściwości jako domyślne
Can't render this file because it contains an unexpected character in line 6 and column 47.
+6
View File
@@ -2408,3 +2408,9 @@ animation effects Off Desativados
Crop Image Cortar imagem
Remove Crop Remover corte
Trim the picture with its handles; Enter or Done applies Corte a imagem com as alças; Enter ou Concluído aplica
Redaction mark appearance Aparência da marca de redação
Outline colour: Cor do contorno:
Fill colour: Cor de preenchimento:
Shown while the pointer is over a mark. Mostrada enquanto o ponteiro está sobre uma marca.
Opacity: Opacidade:
Make properties default Tornar propriedades padrão
Can't render this file because it contains an unexpected character in line 9 and column 16.
+6
View File
@@ -2307,3 +2307,9 @@ animation effects Off Выкл
Crop Image Обрезать изображение
Remove Crop Убрать обрезку
Trim the picture with its handles; Enter or Done applies Обрежьте изображение за маркеры; Enter или «Готово» применяет
Redaction mark appearance Вид метки скрытия
Outline colour: Цвет контура:
Fill colour: Цвет заливки:
Shown while the pointer is over a mark. Показывается, пока указатель над меткой.
Opacity: Непрозрачность:
Make properties default Сделать свойства стандартными
Can't render this file because it contains an unexpected character in line 9 and column 16.
+6
View File
@@ -2399,3 +2399,9 @@ animation effects Off Izklopljeni
Crop Image Obreži sliko
Remove Crop Odstrani obrezovanje
Trim the picture with its handles; Enter or Done applies Obrežite sliko z ročicami; Enter ali Končano uporabi
Redaction mark appearance Videz oznake za zakritje
Outline colour: Barva obrobe:
Fill colour: Barva polnila:
Shown while the pointer is over a mark. Prikazano, ko je kazalec nad oznako.
Opacity: Prekrivnost:
Make properties default Nastavi lastnosti kot privzete
Can't render this file because it contains an unexpected character in line 9 and column 16.
+6
View File
@@ -2307,3 +2307,9 @@ animation effects Off ఆఫ్
Crop Image చిత్రాన్ని కత్తిరించు
Remove Crop కత్తిరింపును తీసివేయి
Trim the picture with its handles; Enter or Done applies హ్యాండిల్‌లతో చిత్రాన్ని కత్తిరించండి; Enter లేదా పూర్తయింది వర్తింపజేస్తుంది
Redaction mark appearance రెడాక్షన్ గుర్తు రూపం
Outline colour: అంచు రంగు:
Fill colour: నింపు రంగు:
Shown while the pointer is over a mark. పాయింటర్ గుర్తుపై ఉన్నప్పుడు కనిపిస్తుంది.
Opacity: అపారదర్శకత:
Make properties default లక్షణాలను డిఫాల్ట్‌గా చేయి
Can't render this file because it contains an unexpected character in line 13 and column 16.
+6
View File
@@ -2390,3 +2390,9 @@ animation effects Off Kapalı
Crop Image Resmi kırp
Remove Crop Kırpmayı kaldır
Trim the picture with its handles; Enter or Done applies Resmi tutamaçlarıyla kırpın; Enter veya Bitti uygular
Redaction mark appearance Karartma işareti görünümü
Outline colour: Kenar rengi:
Fill colour: Dolgu rengi:
Shown while the pointer is over a mark. İşaretçi bir işaretin üzerindeyken gösterilir.
Opacity: Opaklık:
Make properties default Özellikleri varsayılan yap
Can't render this file because it contains an unexpected character in line 821 and column 9.
+6
View File
@@ -2416,3 +2416,9 @@ animation effects Off Вимкнено
Crop Image Обрізати зображення
Remove Crop Прибрати обрізання
Trim the picture with its handles; Enter or Done applies Обріжте зображення за маркери; Enter або «Готово» застосовує
Redaction mark appearance Вигляд позначки приховування
Outline colour: Колір контуру:
Fill colour: Колір заливки:
Shown while the pointer is over a mark. Показується, поки вказівник над позначкою.
Opacity: Непрозорість:
Make properties default Зробити властивості типовими
Can't render this file because it contains an unexpected character in line 6 and column 47.
+6
View File
@@ -2358,3 +2358,9 @@ animation effects Off 关
Crop Image 裁剪图像
Remove Crop 移除裁剪
Trim the picture with its handles; Enter or Done applies 用控制柄裁剪图片;按 Enter 或“完成”应用
Redaction mark appearance 涂黑标记外观
Outline colour: 轮廓颜色:
Fill colour: 填充颜色:
Shown while the pointer is over a mark. 指针位于标记上方时显示。
Opacity: 不透明度:
Make properties default 将属性设为默认
Can't render this file because it contains an unexpected character in line 9 and column 16.
+6
View File
@@ -2349,3 +2349,9 @@ animation effects Off 關
Crop Image 裁切影像
Remove Crop 移除裁切
Trim the picture with its handles; Enter or Done applies 用控點裁切圖片;按 Enter 或「完成」套用
Redaction mark appearance 塗黑標記外觀
Outline colour: 輪廓色彩:
Fill colour: 填滿色彩:
Shown while the pointer is over a mark. 指標位於標記上方時顯示。
Opacity: 不透明度:
Make properties default 將內容設為預設
Can't render this file because it contains an unexpected character in line 9 and column 16.
+15 -3
View File
@@ -774,6 +774,10 @@ pub struct PdfCraftApp {
pub field_props: Option<prepare::FieldDraft>,
pub bulk_field_props: Option<bulk_fields::Draft>,
pub redact_prefs: RedactPrefs,
/// The Redaction Tool Properties kept for the next run (what the last OK with "Make
/// properties default" ticked left), and the values before the dialog opened, for Cancel.
pub redact_defaults: RedactPrefs,
pub redact_before: Option<RedactPrefs>,
pub redact_pages_draft: RedactPagesDraft,
pub redact_search: RedactSearchDraft,
pub hidden_draft: HiddenDraft,
@@ -1039,6 +1043,8 @@ impl PdfCraftApp {
field_props: None,
bulk_field_props: None,
redact_prefs: RedactPrefs::default(),
redact_defaults: RedactPrefs::default(),
redact_before: None,
redact_pages_draft: RedactPagesDraft::default(),
redact_search: RedactSearchDraft::default(),
hidden_draft: HiddenDraft::default(),
@@ -2140,7 +2146,7 @@ impl PdfCraftApp {
/// Serialize the user's persistent state (recent files, theme). Local only.
pub fn persist(&self) -> String {
let trusted: Vec<String> = self.session.trusted_certificates().iter().map(pdfcraft_engine::sign::x509::to_pem).collect();
serde_json::json!({
let mut v = serde_json::json!({
"docking": self.docking,
"docking_left_open": self.left_open,
"docking_right": self.right,
@@ -2199,8 +2205,10 @@ impl PdfCraftApp {
"combine_zoom": self.combine_zoom,
"comments_panel_closed": self.comments_panel_closed,
"right_panel_closed": self.right_panel_closed,
})
.to_string()
});
// Set apart: one more key inside the macro above passes serde_json's recursion limit.
v["redact_prefs"] = self.redact_defaults.to_json();
v.to_string()
}
/// Restore state written by `persist`. Unknown or malformed data is ignored.
@@ -2278,6 +2286,10 @@ impl PdfCraftApp {
if let Some(size) = v["fill_text_size"].as_f64().and_then(fill_sign::valid_text_size) {
self.fill_text_size = size;
}
if let Some(prefs) = RedactPrefs::from_json(&v["redact_prefs"]) {
self.redact_prefs = prefs.clone();
self.redact_defaults = prefs;
}
// Settings are untrusted: an unreadable value (a colour that isn't three bytes, say)
// fails to deserialize and keeps Off.
if let Ok(colors) = serde_json::from_value::<pdfcraft_render::ReadingColors>(v["reading_colors"].clone()) {
+234 -89
View File
@@ -26,6 +26,14 @@ pub struct RedactPrefs {
pub codes: Vec<&'static str>,
/// Overlay text font, size (0 = auto), colour, alignment and repetition.
pub look: pdfcraft_engine::OverlayLook,
/// How an unapplied mark looks on the page: its outline colour (`/C`), the fill shown while
/// the pointer is over it (a preference, not written to the file) and its opacity (`/CA`).
pub outline: Rgb,
pub hover_fill: Rgb,
pub opacity: f64,
/// "Make properties default": OK also keeps these values for the next run. Off, they last
/// for this run only and the saved defaults stay what they were.
pub remember: bool,
}
impl Default for RedactPrefs {
@@ -39,6 +47,10 @@ impl Default for RedactPrefs {
code_set: REDACTION_CODE_SETS[0],
codes: Vec::new(),
look: Default::default(),
outline: [0.89, 0.13, 0.13],
hover_fill: [0.89, 0.13, 0.13],
opacity: 1.0,
remember: true,
}
}
}
@@ -57,8 +69,102 @@ impl RedactPrefs {
let shape = Shape::Redact { quads, overlay: self.overlay_text(), look: self.look };
let mut style = Style::default_for(&shape);
style.fill = self.fill;
style.color = self.outline;
style.opacity = self.opacity.clamp(0.0, 1.0);
Edit::AddAnnotation(NewAnnotation { page, shape, style, contents: String::new(), author: author.to_string() })
}
/// The settings as saved between runs (`"redact_prefs"` in the settings file): the values
/// the dialog's last OK with "Make properties default" ticked left.
pub fn to_json(&self) -> serde_json::Value {
serde_json::json!({
"fill": self.fill,
"use_overlay": self.use_overlay,
"overlay": self.overlay,
"use_code": self.use_code,
"code_set": self.code_set.id,
"codes": self.codes,
"font": self.look.font.resource(),
"size": self.look.size,
"color": self.look.color,
"align": self.look.align,
"repeat": self.look.repeat,
"outline": self.outline,
"hover_fill": self.hover_fill,
"opacity": self.opacity,
})
}
/// Settings are untrusted: each value is taken only when it reads as what it should be;
/// anything else keeps the default. `None` when nothing was saved.
pub fn from_json(v: &serde_json::Value) -> Option<Self> {
if !v.is_object() {
return None;
}
let rgb = |x: &serde_json::Value| -> Option<Rgb> {
let a = x.as_array()?;
if a.len() != 3 {
return None;
}
let mut out = [0.0f64; 3];
for (slot, n) in out.iter_mut().zip(a) {
*slot = n.as_f64().filter(|n| (0.0..=1.0).contains(n))?;
}
Some(out)
};
let mut d = Self::default();
// `"fill": null` is a saved "no colour"; a missing or malformed fill keeps the default.
match v.get("fill") {
Some(serde_json::Value::Null) => d.fill = None,
Some(x) => {
if let Some(c) = rgb(x) {
d.fill = Some(c);
}
}
None => {}
}
if let Some(b) = v["use_overlay"].as_bool() {
d.use_overlay = b;
}
if let Some(t) = v["overlay"].as_str() {
d.overlay = t.chars().take(1000).collect();
}
if let Some(b) = v["use_code"].as_bool() {
d.use_code = b;
}
if let Some(set) = v["code_set"].as_str().and_then(RedactionCodeSet::from_id) {
d.code_set = set;
}
if let Some(codes) = v["codes"].as_array() {
// Only codes of the saved set, each once, in the set's order.
d.codes = d.code_set.codes.iter().copied().filter(|c| codes.iter().any(|x| x.as_str() == Some(c))).collect();
}
if let Some(f) = v["font"].as_str() {
d.look.font = pdfcraft_engine::OverlayFont::from_resource(f);
}
if let Some(n) = v["size"].as_f64().filter(|n| (0.0..=400.0).contains(n)) {
d.look.size = n;
}
if let Some(c) = rgb(&v["color"]) {
d.look.color = c;
}
if let Some(a) = v["align"].as_u64().filter(|a| *a <= 2) {
d.look.align = a as u8;
}
if let Some(b) = v["repeat"].as_bool() {
d.look.repeat = b;
}
if let Some(c) = rgb(&v["outline"]) {
d.outline = c;
}
if let Some(c) = rgb(&v["hover_fill"]) {
d.hover_fill = c;
}
if let Some(o) = v["opacity"].as_f64().filter(|o| (0.0..=1.0).contains(o)) {
d.opacity = o;
}
Some(d)
}
}
/// Redact pages: which pages to mark.
@@ -327,107 +433,146 @@ pub(crate) fn search_body(ui: &mut egui::Ui, d: &mut SearchDraft, t: &Tokens) ->
/// Redaction Tool Properties. Returns (apply, cancel).
pub(crate) fn props_body(ui: &mut egui::Ui, d: &mut RedactPrefs, _t: &Tokens) -> (bool, bool) {
ui.set_width(380.0);
ui.set_width(780.0);
ui.label(egui::RichText::new(tl!("Redaction Tool Properties")).font(crate::theme::semibold(18.0)));
ui.add_space(8.0);
egui::Grid::new("redact-props").num_columns(2).spacing([12.0, 10.0]).show(ui, |ui| {
ui.label(tl!("Redacted area fill colour:"));
ui.horizontal(|ui| {
let mut none = d.fill.is_none();
if ui.add(widgets::checkbox(&mut none, tl!("No colour"))).changed() {
d.fill = if none { None } else { Some([0.0, 0.0, 0.0]) };
}
});
ui.end_row();
ui.label("");
if let Some(c) = crate::comments::swatch_grid(ui, d.fill) {
d.fill = Some(c);
}
ui.end_row();
ui.label("");
ui.add(widgets::checkbox(&mut d.use_overlay, tl!("Use overlay text")));
ui.end_row();
let on = d.use_overlay;
let l = ui.add_enabled(on, widgets::radio(!d.use_code, tl!("Custom text:")));
if l.clicked() {
d.use_code = false;
}
ui.add_enabled(on && !d.use_code, crate::widgets::line(&mut d.overlay).desired_width(220.0)).labelled_by(l.id);
ui.end_row();
if ui.add_enabled(on, widgets::radio(d.use_code, tl!("Redaction code:"))).clicked() {
d.use_code = true;
}
let codes_on = on && d.use_code;
ui.add_enabled_ui(codes_on, |ui| {
egui::ComboBox::from_id_salt("redaction-code-set").selected_text(d.code_set.name).show_ui(ui, |ui| {
for set in REDACTION_CODE_SETS {
if ui.selectable_label(d.code_set == set, set.name).clicked() && d.code_set != set {
d.code_set = set;
d.codes.clear();
}
}
});
});
ui.end_row();
for row in d.code_set.codes.chunks(4) {
ui.label("");
ui.add_enabled_ui(codes_on, |ui| {
// Two columns, so three colour grids fit a short window: the redacted result and its
// overlay text on the left, the mark's own look (Acrobat's third group) on
// the right.
ui.horizontal_top(|ui| {
ui.vertical(|ui| {
ui.set_width(380.0);
egui::Grid::new("redact-props").num_columns(2).spacing([12.0, 10.0]).show(ui, |ui| {
ui.label(tl!("Redacted area fill colour:"));
ui.horizontal(|ui| {
for &code in row {
let mut picked = d.codes.contains(&code);
if ui.add_sized([62.0, 18.0], widgets::checkbox(&mut picked, code)).changed() {
d.codes.retain(|c| *c != code);
if picked {
d.codes.push(code);
}
}
let mut none = d.fill.is_none();
if ui.add(widgets::checkbox(&mut none, tl!("No colour"))).changed() {
d.fill = if none { None } else { Some([0.0, 0.0, 0.0]) };
}
});
});
ui.end_row();
}
let look = &mut d.look;
ui.label(tl!("Font:"));
ui.add_enabled_ui(on, |ui| {
egui::ComboBox::from_id_salt("overlay-font").selected_text(look.font.name()).show_ui(ui, |ui| {
for f in pdfcraft_engine::OverlayFont::ALL {
ui.selectable_value(&mut look.font, f, f.name());
ui.end_row();
ui.label("");
if let Some(c) = crate::comments::swatch_grid(ui, d.fill) {
d.fill = Some(c);
}
ui.end_row();
ui.label("");
ui.add(widgets::checkbox(&mut d.use_overlay, tl!("Use overlay text")));
ui.end_row();
let on = d.use_overlay;
let l = ui.add_enabled(on, widgets::radio(!d.use_code, tl!("Custom text:")));
if l.clicked() {
d.use_code = false;
}
ui.add_enabled(on && !d.use_code, crate::widgets::line(&mut d.overlay).desired_width(220.0)).labelled_by(l.id);
ui.end_row();
if ui.add_enabled(on, widgets::radio(d.use_code, tl!("Redaction code:"))).clicked() {
d.use_code = true;
}
let codes_on = on && d.use_code;
ui.add_enabled_ui(codes_on, |ui| {
egui::ComboBox::from_id_salt("redaction-code-set").selected_text(d.code_set.name).show_ui(ui, |ui| {
for set in REDACTION_CODE_SETS {
if ui.selectable_label(d.code_set == set, set.name).clicked() && d.code_set != set {
d.code_set = set;
d.codes.clear();
}
}
});
});
ui.end_row();
for row in d.code_set.codes.chunks(4) {
ui.label("");
ui.add_enabled_ui(codes_on, |ui| {
ui.horizontal(|ui| {
for &code in row {
let mut picked = d.codes.contains(&code);
if ui.add_sized([62.0, 18.0], widgets::checkbox(&mut picked, code)).changed() {
d.codes.retain(|c| *c != code);
if picked {
d.codes.push(code);
}
}
}
});
});
ui.end_row();
}
let look = &mut d.look;
ui.label(tl!("Font:"));
ui.add_enabled_ui(on, |ui| {
egui::ComboBox::from_id_salt("overlay-font").selected_text(look.font.name()).show_ui(ui, |ui| {
for f in pdfcraft_engine::OverlayFont::ALL {
ui.selectable_value(&mut look.font, f, f.name());
}
});
});
ui.end_row();
ui.label(tl!("Font size:"));
ui.add_enabled_ui(on, |ui| {
ui.horizontal(|ui| {
let mut auto = look.size <= 0.0;
if ui.add(widgets::checkbox(&mut auto, tl!("Auto-size text to fit redaction region"))).changed() {
look.size = if auto { 0.0 } else { 10.0 };
}
if !auto {
ui.add(egui::DragValue::new(&mut look.size).range(2.0..=144.0).suffix(" pt"));
}
});
});
ui.end_row();
ui.label(tl!("Font colour:"));
ui.add_enabled_ui(on, |ui| {
if let Some(c) = crate::comments::swatch_grid(ui, Some(look.color)) {
look.color = c;
}
});
ui.end_row();
ui.label("");
ui.add_enabled(on, widgets::checkbox(&mut look.repeat, tl!("Repeat overlay text")));
ui.end_row();
ui.label(tl!("Text alignment:"));
ui.add_enabled_ui(on, |ui| {
ui.horizontal(|ui| {
for (a, label) in [(0u8, tl!("Left")), (1, tl!("Center")), (2, tl!("Right"))] {
widgets::radio_value(ui, &mut look.align, a, label);
}
});
});
ui.end_row();
});
});
ui.end_row();
ui.label(tl!("Font size:"));
ui.add_enabled_ui(on, |ui| {
ui.horizontal(|ui| {
let mut auto = look.size <= 0.0;
if ui.add(widgets::checkbox(&mut auto, tl!("Auto-size text to fit redaction region"))).changed() {
look.size = if auto { 0.0 } else { 10.0 };
// No separator here: in a horizontal layout it would stretch to the window's bottom.
ui.add_space(28.0);
ui.vertical(|ui| {
ui.set_width(340.0);
ui.label(egui::RichText::new(tl!("Redaction mark appearance")).strong());
ui.add_space(6.0);
egui::Grid::new("redact-mark").num_columns(2).spacing([12.0, 10.0]).show(ui, |ui| {
ui.label(tl!("Outline colour:"));
if let Some(c) = crate::comments::swatch_grid(ui, Some(d.outline)) {
d.outline = c;
}
if !auto {
ui.add(egui::DragValue::new(&mut look.size).range(2.0..=144.0).suffix(" pt"));
ui.end_row();
ui.label(tl!("Fill colour:"));
ui.vertical(|ui| {
if let Some(c) = crate::comments::swatch_grid(ui, Some(d.hover_fill)) {
d.hover_fill = c;
}
ui.label(egui::RichText::new(tl!("Shown while the pointer is over a mark.")).weak().size(11.5));
});
ui.end_row();
ui.label(tl!("Opacity:"));
let mut pct = d.opacity * 100.0;
if ui.add(egui::Slider::new(&mut pct, 10.0..=100.0).suffix("%")).changed() {
d.opacity = (pct / 100.0).clamp(0.1, 1.0);
}
ui.end_row();
ui.label("");
ui.add(widgets::checkbox(&mut d.remember, tl!("Make properties default")));
ui.end_row();
});
});
ui.end_row();
ui.label(tl!("Font colour:"));
ui.add_enabled_ui(on, |ui| {
if let Some(c) = crate::comments::swatch_grid(ui, Some(look.color)) {
look.color = c;
}
});
ui.end_row();
ui.label("");
ui.add_enabled(on, widgets::checkbox(&mut look.repeat, tl!("Repeat overlay text")));
ui.end_row();
ui.label(tl!("Text alignment:"));
ui.add_enabled_ui(on, |ui| {
ui.horizontal(|ui| {
for (a, label) in [(0u8, tl!("Left")), (1, tl!("Center")), (2, tl!("Right"))] {
widgets::radio_value(ui, &mut look.align, a, label);
}
});
});
ui.end_row();
});
ui.add_space(12.0);
buttons(ui, "OK", true)
+89
View File
@@ -107,6 +107,95 @@ fn redaction_codes_become_the_overlay_text() {
assert_eq!(h.state().redact_prefs.overlay_text(), "CONFIDENTIAL", "custom text is kept while codes are used");
}
/// The Redaction Tool Properties are the next run's too, like every other tool's defaults
/// (Acrobat's "Make Properties Default" without the box): fill, overlay text or codes, the
/// text's look, and the mark's outline colour and opacity all come back from the settings.
#[test]
fn redaction_properties_survive_a_restart() {
let mut h = harness();
{
let p = &mut h.state_mut().redact_prefs;
p.fill = None;
p.use_overlay = true;
p.use_code = true;
p.code_set = pdfcraft_engine::REDACTION_CODE_SETS[0];
p.codes = vec!["(b)(6)", "(b)(7)(C)"];
p.overlay = "CONFIDENTIAL".into();
p.look.font = pdfcraft_engine::OverlayFont::Courier;
p.look.size = 12.0;
p.look.color = [0.0, 0.0, 1.0];
p.look.align = 2;
p.look.repeat = true;
p.outline = [0.0, 0.5, 0.0];
p.hover_fill = [1.0, 1.0, 0.0];
p.opacity = 0.4;
}
// The defaults are what OK with "Make properties default" ticked leaves: go through the
// dialog.
assert!(h.state_mut().execute("redact.properties"));
h.run_steps(6);
h.get_by_label("Make properties default");
h.get_by_label("OK").click();
h.run_steps(2);
let json = h.state().persist();
let mut fresh = PdfCraftApp::new();
fresh.restore(&json);
let p = &fresh.redact_prefs;
assert_eq!(p.fill, None, "no colour is a saved choice, not a missing one");
assert_eq!(p.overlay_text(), "(b)(6), (b)(7)(C)");
assert_eq!(p.overlay, "CONFIDENTIAL");
assert_eq!(p.look.font, pdfcraft_engine::OverlayFont::Courier);
assert_eq!((p.look.size, p.look.color, p.look.align, p.look.repeat), (12.0, [0.0, 0.0, 1.0], 2, true));
assert_eq!((p.outline, p.hover_fill, p.opacity), ([0.0, 0.5, 0.0], [1.0, 1.0, 0.0], 0.4));
// The next mark takes the outline and opacity.
let pdfcraft_engine::Edit::AddAnnotation(a) = p.mark(0, vec![pdfcraft_engine::rect_quad([10.0, 10.0, 50.0, 30.0])], "T") else {
panic!("a mark adds an annotation")
};
assert_eq!((a.style.color, a.style.opacity, a.style.fill), ([0.0, 0.5, 0.0], 0.4, None));
// Settings are untrusted: a bad colour, an unknown code set and a code outside the set are
// dropped, the rest kept.
let mut odd = PdfCraftApp::new();
odd.restore(
r#"{"redact_prefs":{"outline":[2,0,0],"opacity":0.7,"code_set":"nope","codes":["(b)(6)","bogus"],"use_overlay":true,"use_code":true}}"#,
);
let p = &odd.redact_prefs;
assert_eq!((p.outline, p.opacity), ([0.89, 0.13, 0.13], 0.7));
assert_eq!(p.code_set.id, pdfcraft_engine::REDACTION_CODE_SETS[0].id);
assert_eq!(p.overlay_text(), "(b)(6)");
}
/// "Make properties default" off: OK keeps the values for this run only, the saved defaults
/// stay what they were; Cancel puts back what the dialog opened with.
#[test]
fn redaction_properties_for_this_run_only_and_cancel() {
let mut h = harness();
assert!(h.state_mut().execute("redact.properties"));
h.run_steps(6);
h.get_by_label("Redaction mark appearance");
h.get_by_label("Outline colour:");
h.get_by_label("Fill colour:");
h.get_by_label("Opacity:");
h.get_by_label("Make properties default").click();
h.run_steps(3);
assert!(!h.state().redact_prefs.remember);
h.state_mut().redact_prefs.outline = [0.0, 0.0, 1.0];
h.get_by_label("OK").click();
h.run_steps(2);
assert_eq!(h.state().redact_prefs.outline, [0.0, 0.0, 1.0], "used for this run");
assert_eq!(h.state().redact_defaults.outline, [0.89, 0.13, 0.13], "not made the default");
let mut fresh = PdfCraftApp::new();
fresh.restore(&h.state().persist());
assert_eq!(fresh.redact_prefs.outline, [0.89, 0.13, 0.13], "the next run starts from the saved defaults");
// Cancel discards the edits made while the dialog was open.
assert!(h.state_mut().execute("redact.properties"));
h.run_steps(2);
h.state_mut().redact_prefs.opacity = 0.2;
h.get_by_label("Cancel").click();
h.run_steps(2);
assert_eq!(h.state().redact_prefs.opacity, 1.0, "cancelled");
assert_eq!(h.state().redact_prefs.outline, [0.0, 0.0, 1.0], "the run's values stay");
}
#[test]
fn redacting_an_imported_word_list() {
let memo = pdfcraft_engine::Session::new().create_from_text("memo", "Call Ada today\nAda is private\nPublic line").unwrap().to_vec();
+3 -3
View File
@@ -5653,11 +5653,11 @@ title = "Redaction fill colour and overlay text"
area = "G"
tier = "P0"
milestone = "M8"
status = "partial"
status = "shipped"
commands = ["redact.properties"]
tools = ["redact_mark"]
evidence = ["crates/ui-egui/tests/ui_group/redact.rs::marking_applying_and_clearing", "crates/redact/src/tests.rs::overlay_text_takes_its_font_size_colour_alignment_and_repeats"]
notes = "fill colour or none; overlay text with font, size or auto-size, colour, alignment and repeat, or redaction codes (protect.redaction-codes)"
evidence = ["crates/ui-egui/tests/ui_group/redact.rs::marking_applying_and_clearing", "crates/ui-egui/tests/ui_group/redact.rs::redaction_properties_survive_a_restart", "crates/ui-egui/tests/ui_group/redact.rs::redaction_properties_for_this_run_only_and_cancel", "crates/redact/src/tests.rs::overlay_text_takes_its_font_size_colour_alignment_and_repeats"]
notes = "Acrobat's three groups: redacted area fill colour or none; overlay text with font, size or auto-size, colour, alignment and repeat, or redaction codes (protect.redaction-codes); Redaction mark appearance: outline colour (/C), fill colour shown while the pointer is over a mark (a preference, not written), opacity (/CA). Make properties default: ticked, OK keeps the values for the next run (settings \"redact_prefs\"); unticked, this run only and the saved defaults stay; Cancel puts back what the dialog opened with."
[[feature]]
id = "protect.redaction-codes"