mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
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:
co-authored by
Claude Opus 5.5
Osvaldo Duran
Brandon Thomas
parent
6c22c78a72
commit
8da1d3efce
+21
-21
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user