From 5aa9a1efae91e008b5f1ea6c8c107655f4ad419f Mon Sep 17 00:00:00 2001 From: Reynold Hu <88558425+reynold-hu@users.noreply.github.com> Date: Sun, 11 Oct 2026 23:12:16 +0800 Subject: [PATCH 01/44] feat(ui): track recently used type families (#204) --- crates/engine/src/prefs.rs | 1 - crates/ui-egui/src/lib.rs | 14 ++++++++++++++ crates/ui-egui/src/state.rs | 5 +++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/prefs.rs b/crates/engine/src/prefs.rs index eb8151508f..a042eff844 100644 --- a/crates/engine/src/prefs.rs +++ b/crates/engine/src/prefs.rs @@ -1013,7 +1013,6 @@ pub const HIDDEN_UNTIL_IMPLEMENTED: &[&str] = &[ "plugIns.generatorEnabled", "type.missingGlyphProtection", "type.textEngine", - "type.recentFonts", "enhancedControls.scrubbySliderAcceleration", "enhancedControls.touchGestures", "rawDefaults.colorSpace", diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index e77681ec92..81efe6be17 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -1020,6 +1020,20 @@ impl PhotocraftApp { self.session.prefs.edit(|p| p.file_handling.recent_files = r); } + /// Records a picked type family for the font menu's Recent group, capped by + /// Preferences ▸ Type ▸ Recent Fonts (0 keeps no list), most recent first, de-duplicated. + pub fn note_recent_font(&mut self, family: &str) { + let cap = self.session.prefs().type_.recent_fonts.min(50) as usize; + let r = &mut self.ui.recent_fonts; + r.retain(|f| f != family); + if cap == 0 { + r.clear(); + return; + } + r.insert(0, family.to_string()); + r.truncate(cap); + } + /// File › Open Recent › Clear Recent File List. pub fn clear_recent(&mut self) { self.ui.recent_files.clear(); diff --git a/crates/ui-egui/src/state.rs b/crates/ui-egui/src/state.rs index 9e31124198..49d27d12c9 100644 --- a/crates/ui-egui/src/state.rs +++ b/crates/ui-egui/src/state.rs @@ -967,6 +967,10 @@ pub struct UiState { /// Recently opened file paths, most-recent first (File › Open Recent). Capped; de-duplicated. #[serde(default)] pub recent_files: Vec, + /// Recently picked type families, most-recent first (the font menu's Recent group). Capped by + /// Preferences ▸ Type ▸ Recent Fonts; de-duplicated. + #[serde(default)] + pub recent_fonts: Vec, /// Inline type editing session, if any. #[serde(default)] pub text_edit: Option, @@ -1164,6 +1168,7 @@ impl Default for UiState { tool: Tool::Brush, group_tools: Vec::new(), recent_files: Vec::new(), + recent_fonts: Vec::new(), text_edit: None, type_transform: None, type_transform_pivot: None, From 6293fd87eb2e8b1a6833d532fe463ce6d5deff27 Mon Sep 17 00:00:00 2001 From: Reynold Hu <88558425+reynold-hu@users.noreply.github.com> Date: Sun, 11 Oct 2026 23:12:16 +0800 Subject: [PATCH 02/44] feat(ui): hoist recent families in the font menu (#204) --- crates/ui-egui/src/type_font_picker_tests.rs | 38 +++++++++++-- crates/ui-egui/src/type_panels_ui.rs | 11 ++-- crates/ui-egui/src/type_tool.rs | 59 +++++++++++++++++--- 3 files changed, 91 insertions(+), 17 deletions(-) diff --git a/crates/ui-egui/src/type_font_picker_tests.rs b/crates/ui-egui/src/type_font_picker_tests.rs index 2fc600511f..0ba91455ed 100644 --- a/crates/ui-egui/src/type_font_picker_tests.rs +++ b/crates/ui-egui/src/type_font_picker_tests.rs @@ -12,6 +12,7 @@ struct State { families: Vec, popup: egui::Id, preview: FontPreview, + recents: Vec, } fn harness() -> Harness<'static, State> { @@ -19,15 +20,20 @@ fn harness() -> Harness<'static, State> { } fn harness_with(preview: FontPreview) -> Harness<'static, State> { + harness_with_recents(preview, Vec::new()) +} + +fn harness_with_recents(preview: FontPreview, recents: Vec) -> Harness<'static, State> { // Enough families that the unfiltered menu hits its maximum height and scrolls. let mut families: Vec = (0..60).map(|i| format!("Family {i:02}")).collect(); families.push("Zeta Sans".into()); - let state = State { current: "Family 00".into(), families, popup: egui::Id::NULL, preview }; + let state = State { current: "Family 00".into(), families, popup: egui::Id::NULL, preview, recents }; let mut h = Harness::builder().with_size(vec2(600.0, 900.0)).build_ui_state( |ui, s: &mut State| { s.popup = ui.make_persistent_id(egui::IdSalt::new("type-font")).with("popup"); let fams = s.families.clone(); - font_picker_in(ui, &mut s.current, 170.0, &fams, s.preview); + let recents = s.recents.clone(); + font_picker_in(ui, &mut s.current, 170.0, &fams, s.preview, &recents); }, state, ); @@ -413,12 +419,14 @@ fn localized_font_names_label_the_menu_but_pick_the_family() { families: vec!["Arial".into(), "MS Gothic".into(), "Zeta Sans".into()], popup: egui::Id::NULL, preview: FontPreview::Medium, + recents: Vec::new(), }; let mut h = Harness::builder().with_size(vec2(600.0, 900.0)).build_ui_state( |ui, s: &mut State| { s.popup = ui.make_persistent_id(egui::IdSalt::new("type-font")).with("popup"); let fams = s.families.clone(); - font_picker_in(ui, &mut s.current, 170.0, &fams, s.preview); + let recents = s.recents.clone(); + font_picker_in(ui, &mut s.current, 170.0, &fams, s.preview, &recents); }, state, ); @@ -453,7 +461,7 @@ fn font_menu_rows_search_both_names_and_sort_by_label() { let labels: HashMap = [("MS Gothic", "MS ゴシック"), ("Microsoft YaHei", "微软雅黑")].iter().map(|(a, b)| (a.to_string(), b.to_string())).collect(); let rows = |labels: Option<&HashMap>, q: &str| -> Vec<(String, String)> { - super::font_menu_rows(&families, labels, q).into_iter().map(|(f, l)| (f.clone(), l.to_string())).collect() + super::font_menu_rows(&families, labels, q, &[]).into_iter().map(|(f, l)| (f.clone(), l.to_string())).collect() }; let english = rows(None, ""); assert!(english.iter().all(|(f, l)| f == l)); @@ -463,3 +471,25 @@ fn font_menu_rows_search_both_names_and_sort_by_label() { assert_eq!(rows(Some(&labels), "雅黑"), [("Microsoft YaHei".to_string(), "微软雅黑".to_string())]); assert!(rows(Some(&labels), "nothing").is_empty()); } + +/// Preferences ▸ Type ▸ Recent Fonts: recently used families move to the top (most recent first, +/// once each); a query drops the hoisting so the list stays alphabetical while searching. +#[test] +fn recent_fonts_are_hoisted_to_the_top() { + let families: Vec = ["Arial", "Baskerville", "Courier", "Zeta Sans"].iter().map(|s| s.to_string()).collect(); + let recents: Vec = vec!["Zeta Sans".into(), "Courier".into()]; + let rows = |q: &str| -> Vec { super::font_menu_rows(&families, None, q, &recents).into_iter().map(|(f, _)| f.clone()).collect() }; + assert_eq!(rows(""), ["Zeta Sans", "Courier", "Arial", "Baskerville"], "recents hoist, most recent first, once each"); + assert_eq!(rows("a"), ["Arial", "Baskerville", "Zeta Sans"], "a query drops the hoisting"); +} + +/// The menu itself draws the hoisted recents first. +#[test] +fn recent_fonts_appear_first_in_the_menu() { + let mut h = harness_with_recents(FontPreview::Medium, vec!["Zeta Sans".into(), "Family 05".into()]); + open(&mut h); + let zeta = h.get_by_label("Zeta Sans").rect().top(); + let five = h.get_by_label("Family 05").rect().top(); + let six = h.get_by_label("Family 06").rect().top(); + assert!(zeta < five && five < six, "recents come first, in order: {zeta} {five} {six}"); +} diff --git a/crates/ui-egui/src/type_panels_ui.rs b/crates/ui-egui/src/type_panels_ui.rs index fbab3f84f9..e2a5f57113 100644 --- a/crates/ui-egui/src/type_panels_ui.rs +++ b/crates/ui-egui/src/type_panels_ui.rs @@ -373,6 +373,8 @@ fn options_editor(app: &mut PhotocraftApp, ui: &mut egui::Ui, paragraph: bool, s let defined = |attrs: &Value, k: &str| attrs.get(k).is_some(); // Preferences ▸ Type ▸ Font Preview Size sizes the family picker's samples. let preview = app.session.prefs().type_.font_preview; + // The fonts the pickers hoist to the top (Preferences ▸ Type ▸ Recent Fonts). + let recents = app.ui.recent_fonts.clone(); // Preferences ▸ Units & Rulers ▸ Type: the model stores points; the fields may show pixels // (by the document's resolution) or millimetres. let (type_unit, unit_label) = crate::type_tool::type_unit(app); @@ -399,7 +401,7 @@ fn options_editor(app: &mut PhotocraftApp, ui: &mut egui::Ui, paragraph: bool, s ui.add_enabled_ui(on, |ui| match kind { "font" => { let mut f = v.as_str().unwrap_or("").to_string(); - if family_picker(ui, &format!("style-font-{paragraph}-{id}"), &mut f, preview) { + if family_picker(ui, &format!("style-font-{paragraph}-{id}"), &mut f, preview, &recents) { set = Some(json!({ panel: f })); } } @@ -508,9 +510,9 @@ fn options_editor(app: &mut PhotocraftApp, ui: &mut egui::Ui, paragraph: bool, s } /// Searchable family combo box. -fn family_picker(ui: &mut egui::Ui, salt: &str, current: &mut String, preview: photocraft_engine::prefs::FontPreview) -> bool { +fn family_picker(ui: &mut egui::Ui, salt: &str, current: &mut String, preview: photocraft_engine::prefs::FontPreview, recents: &[String]) -> bool { let mut shown = if current.is_empty() { photocraft_text::fonts::DEFAULT_FAMILY.to_string() } else { current.clone() }; - let changed = crate::type_tool::family_picker_in(ui, salt, &mut shown, 140.0, &crate::type_tool::families(), preview); + let changed = crate::type_tool::family_picker_in(ui, salt, &mut shown, 140.0, &crate::type_tool::families(), preview, recents); if changed { *current = shown; } @@ -600,7 +602,8 @@ pub fn glyphs_panel(app: &mut PhotocraftApp, ui: &mut egui::Ui) { // Font family + style. ui.horizontal(|ui| { let mut f = family.clone(); - if family_picker(ui, "glyph-family", &mut f, app.session.prefs().type_.font_preview) { + if family_picker(ui, "glyph-family", &mut f, app.session.prefs().type_.font_preview, &app.ui.recent_fonts) { + app.note_recent_font(&f); app.ui.type_panels.glyph_family = f; app.ui.type_panels.glyph_style = tl!("Regular").into(); } diff --git a/crates/ui-egui/src/type_tool.rs b/crates/ui-egui/src/type_tool.rs index d80e234dd5..efa2931e7c 100644 --- a/crates/ui-egui/src/type_tool.rs +++ b/crates/ui-egui/src/type_tool.rs @@ -939,8 +939,10 @@ fn localized_families() -> Arc> { /// The font menu's rows: each family with the label it shows (its localized name when `labels` /// has one, else the family), matching `query` (lowercase) by either name. With localized labels -/// the rows sort by label, so the menu stays alphabetical as shown. -fn font_menu_rows<'a>(families: &'a [String], labels: Option<&'a HashMap>, query: &str) -> Vec<(&'a String, &'a str)> { +/// the rows sort by label, so the menu stays alphabetical as shown. Recently used families are +/// hoisted to the top, most recent first (Preferences ▸ Type ▸ Recent Fonts); a query hides the +/// hoisting so the list stays strictly alphabetical while searching. +fn font_menu_rows<'a>(families: &'a [String], labels: Option<&'a HashMap>, query: &str, recents: &[String]) -> Vec<(&'a String, &'a str)> { let label = |f: &'a String| labels.and_then(|l| l.get(f)).map_or(f.as_str(), String::as_str); let mut rows: Vec<(&String, &str)> = families .iter() @@ -950,6 +952,16 @@ fn font_menu_rows<'a>(families: &'a [String], labels: Option<&'a HashMap String { } /// Searchable font-family combo box. -fn font_picker(ui: &mut egui::Ui, current: &mut String, width: f32, preview: photocraft_engine::prefs::FontPreview) -> bool { - family_picker_in(ui, "type-font", current, width, &families(), preview) +fn font_picker(ui: &mut egui::Ui, current: &mut String, width: f32, preview: photocraft_engine::prefs::FontPreview, recents: &[String]) -> bool { + family_picker_in(ui, "type-font", current, width, &families(), preview, recents) } /// Maximum height of the font menu. @@ -1031,8 +1043,15 @@ const FONT_MENU_HEIGHT: f32 = 460.0; /// [`font_picker`] over a given family list (tests pass their own). #[cfg(test)] -fn font_picker_in(ui: &mut egui::Ui, current: &mut String, width: f32, families: &[String], preview: photocraft_engine::prefs::FontPreview) -> bool { - family_picker_in(ui, "type-font", current, width, families, preview) +fn font_picker_in( + ui: &mut egui::Ui, + current: &mut String, + width: f32, + families: &[String], + preview: photocraft_engine::prefs::FontPreview, + recents: &[String], +) -> bool { + family_picker_in(ui, "type-font", current, width, families, preview, recents) } /// Shared by the Type options, Character, style and Glyphs panels. Selection changes still @@ -1044,6 +1063,7 @@ pub(crate) fn family_picker_in( width: f32, families: &[String], preview: photocraft_engine::prefs::FontPreview, + recents: &[String], ) -> bool { let mut changed = false; let search_id = ui.id().with(("font-search", salt)); @@ -1086,7 +1106,7 @@ pub(crate) fn family_picker_in( d.insert_temp(search_id, q.clone()); }); let ql = q.to_lowercase(); - let rows = font_menu_rows(families, labels, &ql); + let rows = font_menu_rows(families, labels, &ql, recents); if down || up { let index = rows.iter().position(|(f, _)| *f == current); let next = match index { @@ -1304,7 +1324,8 @@ pub fn options_bar(app: &mut PhotocraftApp, ui: &mut egui::Ui) { app.ui.status_error = true; } } - if font_picker(ui, &mut fam, 170.0, app.session.prefs().type_.font_preview) { + if font_picker(ui, &mut fam, 170.0, app.session.prefs().type_.font_preview, &app.ui.recent_fonts) { + app.note_recent_font(&fam); app.ui.tool_options.type_font = fam.clone(); let st = styles(&fam); style = if st.contains(&style) { style } else { st.first().cloned().unwrap_or_else(|| "Regular".into()) }; @@ -1616,7 +1637,8 @@ fn type_sections(app: &mut PhotocraftApp, ui: &mut egui::Ui, character: bool, pa let w = field_width(full, 2, LABEL_W); let mut fam = c.font_family.clone(); row(ui, &mut |ui| { - if font_picker(ui, &mut fam, full, app.session.prefs().type_.font_preview) { + if font_picker(ui, &mut fam, full, app.session.prefs().type_.font_preview, &app.ui.recent_fonts) { + app.note_recent_font(&fam); app.ui.tool_options.type_font = fam.clone(); apply(app, ui.ctx(), json!({"font": fam})); } @@ -1897,6 +1919,25 @@ mod tests { app } + /// Preferences ▸ Type ▸ Recent Fonts caps the recorded list; a re-pick moves to the front and + /// 0 keeps no list. + #[test] + fn note_recent_font_caps_and_de_duplicates() { + let mut app = app(); + for f in ["A", "B", "C"] { + app.note_recent_font(f); + } + assert_eq!(app.ui.recent_fonts, ["C", "B", "A"]); + app.run("prefs.set", json!({"path": "type.recentFonts", "value": 2})).unwrap(); + app.note_recent_font("D"); + assert_eq!(app.ui.recent_fonts, ["D", "C"], "the cap trims the tail"); + app.note_recent_font("C"); + assert_eq!(app.ui.recent_fonts, ["C", "D"], "a re-pick moves to the front"); + app.run("prefs.set", json!({"path": "type.recentFonts", "value": 0})).unwrap(); + app.note_recent_font("E"); + assert!(app.ui.recent_fonts.is_empty(), "0 keeps no list"); + } + fn layer_text(app: &PhotocraftApp) -> String { let ed = app.ui.text_edit.as_ref().unwrap(); current_text(app, LayerId(ed.layer)).unwrap() From 2042a0ffb33bd47e584f29f0bbd7d5232b9e6440 Mon Sep 17 00:00:00 2001 From: Reynold Hu <88558425+reynold-hu@users.noreply.github.com> Date: Sun, 11 Oct 2026 23:12:16 +0800 Subject: [PATCH 03/44] docs(scorecard): settings that do nothing 31 of 154 --- docs/scorecard.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/scorecard.md b/docs/scorecard.md index 08e4bd6be8..1cffe7892a 100644 --- a/docs/scorecard.md +++ b/docs/scorecard.md @@ -9,7 +9,7 @@ Generated by `cargo xtask scorecard` from `perf/budgets.toml`, `perf/baseline.js | [Performance](#performance) | 3 | 22 | 22 | 56 scenarios: 3 within budget, 22 over budget or failing, 9 tracked only, 17 not run, 5 not measurable yet (baseline 2026-10-05) | | [File compatibility](#file-compatibility) | 6 | 12 | 9 | io corpus: oracle ≥ 146/170, round trip ≥ 169/170; psd-tools corpus: oracle ≥ 236/309, round trip ≥ 308/309; photoshop oracles: oracle ≥ 133/256, round trip ≥ 258/256 | | [Tools](#tools) | 25 | 16 | 2 | | -| [Workspace and UI](#workspace-and-ui) | 9 | 8 | 6 | settings that do nothing: 32 of 154 (target 0) | +| [Workspace and UI](#workspace-and-ui) | 9 | 8 | 6 | settings that do nothing: 31 of 154 (target 0) | | [Type](#type) | 1 | 4 | 4 | | | [Automation](#automation) | 3 | 2 | 6 | 630 of 630 menu items live (100.0%) | | [Reliability](#reliability) | 5 | 2 | 0 | never-crash lints on 28 of 28 crates; panic_hunt present | @@ -176,9 +176,9 @@ Corpus floors (`crates/io/tests/corpus.rs`): the enforced minimums; the corpus t ## Workspace and UI -**Settings that do nothing: 32 of 154** (target 0, #204). Counted by the prefs audit (`xtask/src/scorecard.rs`): a `Preferences` field counts as read when code outside the prefs model (`prefs.rs` except its `impl Session` blocks; tests excluded; the schema-driven dialog never names a field) accesses `section.field`, or `.section` and `.field` in the same file, or calls a section method that reads `self.field`. It is a heuristic lower bound: a generic name can look read when it isn't. +**Settings that do nothing: 31 of 154** (target 0, #204). Counted by the prefs audit (`xtask/src/scorecard.rs`): a `Preferences` field counts as read when code outside the prefs model (`prefs.rs` except its `impl Session` blocks; tests excluded; the schema-driven dialog never names a field) accesses `section.field`, or `.section` and `.field` in the same file, or calls a section method that reads `self.field`. It is a heuristic lower bound: a generic name can look read when it isn't. -Unread: `general.color_picker`, `general.animated_zoom`, `general.zoom_resizes_windows`, `workspace.auto_collapse_icon_panels`, `workspace.auto_show_hidden_panels`, `workspace.enable_floating_document_window_docking`, `workspace.enable_narrow_options_bar`, `file_handling.image_previews`, `file_handling.save_in_background`, `file_handling.ignore_exif_profile_tag`, `file_handling.maximize_psd_compatibility`, `performance.cache_levels`, `performance.legacy_compositing`, `scratch_disks.disks`, `units_and_rulers.column_width`, `units_and_rulers.gutter`, `plug_ins.show_extension_panels`, `plug_ins.allow_scripts_to_connect`, `plug_ins.generator_enabled`, `type_.missing_glyph_protection`, `type_.text_engine`, `type_.recent_fonts`, `enhanced_controls.scrubby_slider_acceleration`, `enhanced_controls.touch_gestures`, `raw_defaults.color_space`, `raw_defaults.bit_depth`, `raw_defaults.resolution`, `raw_defaults.sharpen_for`, `raw_defaults.open_as_smart_object`, `raw_defaults.apply_auto_tone`, `integrations.allow_agent_control`, `integrations.control_port`. +Unread: `general.color_picker`, `general.animated_zoom`, `general.zoom_resizes_windows`, `workspace.auto_collapse_icon_panels`, `workspace.auto_show_hidden_panels`, `workspace.enable_floating_document_window_docking`, `workspace.enable_narrow_options_bar`, `file_handling.image_previews`, `file_handling.save_in_background`, `file_handling.ignore_exif_profile_tag`, `file_handling.maximize_psd_compatibility`, `performance.cache_levels`, `performance.legacy_compositing`, `scratch_disks.disks`, `units_and_rulers.column_width`, `units_and_rulers.gutter`, `plug_ins.show_extension_panels`, `plug_ins.allow_scripts_to_connect`, `plug_ins.generator_enabled`, `type_.missing_glyph_protection`, `type_.text_engine`, `enhanced_controls.scrubby_slider_acceleration`, `enhanced_controls.touch_gestures`, `raw_defaults.color_space`, `raw_defaults.bit_depth`, `raw_defaults.resolution`, `raw_defaults.sharpen_for`, `raw_defaults.open_as_smart_object`, `raw_defaults.apply_auto_tone`, `integrations.allow_agent_control`, `integrations.control_port`. | Id | Target | Status | Issue | Note | |---|---|---|---|---| From fffa54068ce49840345d5e7caf4ed88ba31de180 Mon Sep 17 00:00:00 2001 From: Florent Siegenthaler <64897087+Atalantis@users.noreply.github.com> Date: Mon, 12 Oct 2026 22:43:09 +0200 Subject: [PATCH 04/44] i18n: complete the French (fr) UI translation (1,041 entries) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the 1,041 keys that other catalogs translate and fr.tsv lacked, so French covers the union of all catalog keys. New entries reuse the vocabulary the French catalog already uses (Canal, Rastériser, Canevas, Masque de calque, Objet intelligent); placeholders, edge spaces, trailing ellipses and the two French plural forms are preserved. --- crates/ui-egui/src/i18n/fr.tsv | 1041 ++++++++++++++++++++++++++++++++ 1 file changed, 1041 insertions(+) diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index a1319f04fb..88e2bc80f4 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -3053,3 +3053,1044 @@ curves Smooth Lisser Remove after each stroke Supprimer après chaque trait Apply the removal ({key}) Appliquer la suppression ({key}) Strokes to remove: {n} (Enter applies, Esc cancels) Traits à supprimer : {n} (Entrée applique, Échap annule) + (none) (aucun) + 32-bit Preview Options Options d'aperçu 32 bits + AI Integrations Intégrations d'IA + About {app} À propos de {app} + Accented Edges Contours accentués + Action playback failed: {error} Échec de la lecture de l'action : {error} + Actual Pixels Pixels réels + Add Mask Ajouter un masque + Add Noise Ajout de bruit + Added swatch {name} Nuance {name} ajoutée + Adjust Interval For Loops Ajuster l'intervalle pour les boucles + Adjustment Layers Calques d'ajustement + Adjustment Properties Propriétés de l'ajustement + Adobe-rgb-compat Adobe RGB (compatible) + Adwaita (Dark) Adwaita (sombre) + Adwaita (Light) Adwaita (clair) + After Après + All shortcuts reset to Photoshop defaults. Tous les raccourcis ont été réinitialisés aux valeurs par défaut de Photoshop. + Ambience Ambiance + Ambient Ambiante + Amplitude Max Amplitude max. + Amplitude Min Amplitude min. + Angled Strokes Traits obliques + Anisotropic Anisotrope + Apply Data Set Appliquer un jeu de données + Apply Image Appliquer une image + Apply Mask Appliquer le masque + Apply to All Appliquer à tout + Apply {option}: {state} Appliquer {option} : {state} + Arbitrary Angle personnalisé + Arc Lower Arc inférieur + Arc Upper Arc supérieur + Are you sure you want to quit? Voulez-vous vraiment quitter ? + Are you sure? Êtes-vous sûr ? + Area Aire + Around Center Autour du centre + Arrangement Disposition + Arrows Flèches + Artboard from Group Plan de travail d'après un groupe + Artboard from Layers Plan de travail d'après des calques + Artboards Plans de travail + Artboards to Files Plans de travail en fichiers + Artboards to PDF Plans de travail en PDF + Artistic Artistique + Ask On Mismatch Demander en cas de non-concordance + Ask On Missing Demander en cas d'absence + Ask On Paste Demander au collage + Assign Profile Attribuer un profil + At À + Auto Scale Mise à l'échelle auto + Auto-Align Layers Alignement automatique des calques + Auto-Blend Layers Fusion automatique des calques + Autosave failed: {error} Échec de l'enregistrement automatique : {error} + Back Retour + Backend: {backend} ({type}) Moteur : {backend} ({type}) + Background Level Niveau d'arrière-plan + Bamboo Bambou + Bas Relief Bas-relief + Base Tree Type Type d'arbre de base + Basic Latin Latin de base + Batch Traitement par lots + Before Avant + Berries Baies + Best Optimale + Black Clip Écrêtage des noirs + Black Intensity Intensité du noir + Blade Curvature Courbure des lames + Blast Rafale + Bleach Bypass Sans blanchiment + Blend Clipped Layers as Group Fusionner les calques découpés en tant que groupe + Blend Interior Effects as Group Fusionner les effets internes en tant que groupe + Blend Text Gamma Gamma de fusion du texte + Blending Mode Mode de fusion + Bloat Dilatation + Blocks Blocs + Blue Yellow Bleu/Jaune + Blur Angle Angle du flou + Border Bordure + Border Thickness Épaisseur de la bordure + Box Blur Flou par boîte + Bpc Compensation du point noir + Branches Color Couleur des branches + Branches Height Hauteur des branches + Branches Thickness Épaisseur des branches + Breeze Dark Breeze sombre + Breeze Light Breeze clair + Brick Brique + Bristle Detail Détail des poils + Browse Parcourir + Brush Detail Détail du pinceau + Brush Strokes Traits de pinceau + Brush Type Type de pinceau + Burlap Toile de jute + Calculations Calculs + Camera Raw Camera Raw + Camera Raw Filter Filtre Camera Raw + Cancel transform Annuler la transformation + Candle Light Bougie + Canvas Size Taille du canevas + Cell Size Taille des cellules + Center X Centre X + Center Y Centre Y + Chalk Area Zone de craie + Changes Saved Modifications enregistrées + Changes have not been saved. Les modifications n'ont pas été enregistrées. + Change… Modifier… + Channel1 Canal 1 + Channel2 Canal 2 + Channel3 Canal 3 + Channel4 Canal 4 + Charcoal Area Zone de fusain + Charcoal Thickness Épaisseur du fusain + Check for Updates Rechercher des mises à jour + Choose Folder Choisir un dossier + Circle Cercle + Circularity Circularité + Cleanliness Propreté + Clear Amount Zone sans lueur + Clear Selection Effacer la sélection + Clipping Mask Masque de découpe + Clipping Path… Tracé de détourage… + Clone Cloner + Clone source set at {x}, {y} Source de clonage définie en {x}, {y} + Close Window Fermer la fenêtre + Clumped Aggloméré + Coarse Dots Points grossiers + Coated-cmyk CMJN papier couché + Color Decontaminate Décontaminer les couleurs + Color Halftone Trame de demi-teintes couleur + Color Indicates Masked Areas La couleur indique les zones masquées + Color Indicates Selected Areas La couleur indique les zones sélectionnées + Color Lookup Tables Tables de correspondance des couleurs + Color Picker Sélecteur de couleurs + Color Picker (Background Color) Sélecteur de couleurs (Couleur d'arrière-plan) + Color Picker (Custom Canvas Color) Sélecteur de couleurs (Couleur personnalisée du canevas) + Color Picker (Foreground Color) Sélecteur de couleurs (Couleur de premier plan) + Color Settings Paramètres de couleur + Color Swatches Nuancier + Color Table Table des couleurs + Color management: {error} Gestion des couleurs : {error} + Colored Pencil Crayon de couleur + Commit transform Valider la transformation + Compare Comparer + Compression Compression + Conditional Mode Change Changement de mode conditionnel + Confirm Confirmer + Constraints Contraintes + Content-Aware Fill Remplissage d'après le contenu + Continue Continuer + Contract Contracter + Contrasty Contrasté + Convert Convertir + Convert to Alpha Channel Convertir en canal alpha + Convert to Linked Convertir en objet lié + Convert to Profile Convertir en profil + Cool Froid + Cooling L B B Refroidissement LBB + Cooling80 Refroidissement 80 + Cooling82 Refroidissement 82 + Copy Complete Stroke Copier le contour complet + Copy Fill Copier le remplissage + Correct C A Corriger l'aberration chromatique + Correct Distortion Corriger la distorsion + Correct Vignette Corriger le vignettage + Could not rename the action: {error} Impossible de renommer l'action : {error} + Couldn't open {name}: {error} Impossible d'ouvrir {name} : {error} + Couldn't save preferences: {error}. Retrying… Impossible d'enregistrer les préférences : {error}. Nouvelle tentative… + Crack Brightness Luminosité des fissures + Crack Depth Profondeur des fissures + Crack Spacing Espacement des fissures + Craquelure Craquelure + Create By Créer par + Create Vector Mask Créer un masque vectoriel + Create… Créer… + Cross Croix + Cross Process Traitement croisé + Crosshatch Hachures croisées + Crystallize Cristallisation + Currency Devise + Curve Darks Courbe : tons sombres + Curve Highlights Courbe : hautes lumières + Curve Lights Courbe : tons clairs + Curve Shadows Courbe : ombres + Curve Splits Courbe : points de séparation + Custom Branch Color Couleur de branche personnalisée + Custom Pixel Aspect Ratio Format des pixels personnalisé + Cutout Découpage + Cyrillic Cyrillique + Daisies Pâquerettes + Dark Gray Gris foncé + Dark Intensity Intensité des tons foncés + Dark Rough Foncé rugueux + Dark Strokes Traits sombres + Darken Only Assombrir seulement + Darkness Obscurité + Data Données + Data Set: {name} ({index}/{total}) Jeu de données : {name} ({index}/{total}) + Data Sets as Files Jeux de données en fichiers + Date Date + Date and Time Date et heure + Day For Night Nuit américaine + De-Interlace Désentrelacer + Deep Profond + Deep Blue Bleu profond + Deep Emerald Émeraude profond + Deep Red Rouge profond + Deep Yellow Jaune profond + Default Foreground and Background Colors Couleurs par défaut du premier plan et de l'arrière-plan + Default Leaves Feuilles par défaut + Default Photoshop Size Taille Photoshop par défaut + Defaults Valeurs par défaut + Define Brush Preset Définir un préréglage de pinceau + Define Custom Shape Définir une forme personnalisée + Define Pattern Définir un motif + Definition Définition + Defringe Supprimer les franges + Degrees Degrés + Delete Channel Supprimer le canal + Delete Mask Supprimer le masque + Delete… Supprimer… + Depth Map Carte de profondeur + Depth Mode Mode de profondeur + Diameter Diamètre + Diffuse Diffuser + Diffuse Glow Lueur diffuse + Direction Balance Équilibre des directions + Disable Désactiver + Disable Mask Désactiver le masque + Disable Symmetry Path Désactiver le tracé de symétrie + Disabled Désactivé + Discard Changes Ignorer les modifications + Displace Déplacement + Display Affichage + Display profile not used Profil d'affichage non utilisé + Display profiles: {reason} Profils d'affichage : {reason} + Display-p3 Display P3 + Distortion Distorsion + Distribution Distribution + Do you want to save you made to “{name}” before quitting? Voulez-vous enregistrer les modifications apportées à « {name} » avant de quitter ? + Documentation Documentation + Documents Documents + Dot Point + Double Line Double ligne + Draft Brouillon + Drag a crop box · drag inside to move · edges resize ({ratio} ratio, {centre} centre) · Space moves while drawing · {commit} commits · Esc cancels Tracez un cadre de recadrage · faites glisser à l'intérieur pour le déplacer · les bords le redimensionnent ({ratio} proportions, {centre} centre) · Espace le déplace pendant le tracé · {commit} valide · Échap annule + Drag: draw freehand · Release on the start point: close Glisser : tracer à main levée · Relâcher sur le point de départ : fermer + Driver: {driver} Pilote : {driver} + Dry Brush Pinceau sec + Duplicate Channel Dupliquer le canal + Duplicate Layer Dupliquer le calque + Duplicate Layers Dupliquer les calques + Duplication Duplication + Dust & Scratches Poussière et rayures + Edge Brightness Luminosité du contour + Edge Extension Extension du contour + Edge Fidelity Fidélité des contours + Edge Intensity Intensité du contour + Edge Only Contours seulement + Edge Simplicity Simplicité des contours + Edge Thickness Épaisseur du contour + Edge Width Largeur du contour + Edit Toolbar Modifier la barre d'outils + Edit… Modifier… + Elevation Élévation + Eliminate Éliminer + Enable Activer + Enable Mask Activer le masque + End X Fin X + End Y Fin Y + Enlarged Agrandi + Even Fields Trames paires + Exit Quick Mask Quitter le mode masque rapide + Export As Exporter sous + Export Contents Exporter le contenu + Export Image Exporter l'image + Export Options Options d'exportation + Export Preferences Préférences d'exportation + Export Settings Paramètres d'exportation + Export to Exporter vers + Export/Import Presets Exporter/importer des préréglages + Exported contents to {path} Contenu exporté vers {path} + Exported presets to {path} Préréglages exportés vers {path} + Exported {path} {path} exporté + Exported {path} ({size}) {path} exporté ({size}) + Exporting… Exportation… + Extrude Extruder + Faded Film Film délavé + Failed Échec + Failed to open file Échec de l'ouverture du fichier + Failed to save file Échec de l'enregistrement du fichier + Fallback: {reason} Repli : {reason} + Feather Radius Rayon d'adoucissement + Fiber Length Longueur des fibres + Fibers Fibres + Field Blur Flou de champ + File Format Format de fichier + File Info Informations sur le fichier + File Name Nom du fichier + File Size Taille du fichier + File not found Fichier introuvable + Filename Nom de fichier + Fill Color Couleur de remplissage + Fill Path… Remplir le tracé… + Film Grain Grain de film + Filter Gallery Galerie de filtres + Find Rechercher + Find and Replace Text Rechercher et remplacer du texte + Fine Fine + Fine Dots Points fins + Finish Terminer + Fit Image Adapter l'image + Fit to Window Adapter à la fenêtre + Flame Flamme + Flame Bottom Alignment Alignement du bas de la flamme + Flame Lines Lignes de flamme + Flame Shape Forme de la flamme + Flame Style Style de la flamme + Flame Type Type de flamme + Flat Plat + Flat Shading Ombrage plat + Flower Color Couleur des fleurs + Focal Distance Distance de mise au point + Focus Mise au point + Focus Area Zone de mise au point + Folder Dossier + Foreground Level Niveau de premier plan + Forward Warp Déformation vers l'avant + Frame Cadre + Free Transform Path Transformation libre du tracé + Freeze Lasso Lasso de figeage + Freeze Mask Masque de figeage + Fresco Fresque + From Left À partir de la gauche + From Right À partir de la droite + Frosted Dépoli + Full Screen Plein écran + Fullscreen Plein écran + Fuzziness Tolérance + GPU backend preference: {backend} Préférence de moteur GPU : {backend} + GPU device was lost; using the CPU renderer. Le périphérique GPU a été perdu ; utilisation du rendu CPU. + GPU error; using the CPU renderer. Erreur GPU ; utilisation du rendu CPU. + GPU retry scheduled Nouvelle tentative GPU planifiée + Gamma Gamma + Gaussian Blur Flou gaussien + General Blending Fusion générale + Generators Générateurs + Generic Générique + Glass Verre + Global Light Lumière globale + Gloss Brillant + Glow Amount Quantité de lueur + Glow Brightness Luminosité de la lueur + Glow Size Taille de la lueur + Glowing Edges Contours lumineux + Good Bonne + Grade Balance Balance de l'étalonnage + Grade Blending Fusion de l'étalonnage + Grade Global Étalonnage global + Grade Highlights Étalonnage des hautes lumières + Grade Midtones Étalonnage des tons moyens + Grade Shadows Étalonnage des ombres + Grain Amount Quantité de grain + Grain Roughness Rugosité du grain + Grain Size Taille du grain + Grain Type Type de grain + Graininess Grain + Grainy Dots Points granuleux + Graphics adapter Carte graphique + Graphics adapter: {adapter} Carte graphique : {adapter} + Gray Value (Maximum) Valeur de gris (maximum) + Gray Value (Mean) Valeur de gris (moyenne) + Gray Value (Median) Valeur de gris (médiane) + Gray Value (Minimum) Valeur de gris (minimum) + Gray-gamma-2.2 Gris gamma 2,2 + Greek Grec + Grid view Affichage en grille + Group from Layers Groupe d'après des calques + Groups Groupes + Grout Width Largeur du joint + Guide Layout… Disposition des repères… + HDR Toning Tonalité HDR + Hearts Cœurs + Heptagon Heptagone + Hexagon Hexagone + Hidden Masqué + Hide Grid Masquer la grille + Hide Others Masquer les autres + Hide {app} Masquer {app} + High Pass Passe-haut + Highlight Hautes lumières + Highlight Amount Quantité des hautes lumières + Highlight Area Zone des hautes lumières + Highlight Radius Rayon des hautes lumières + Highlight Strength Intensité des hautes lumières + Highlight Tone Amplitude tonale des hautes lumières + Horizontal Only Horizontal seulement + Hotspot Point chaud + Hsb TSL + Hsl TSI + Hsl Hue Teinte TSI + Hsl Lum Luminance TSI + Hsl Sat Saturation TSI + Image Balance Équilibre de l'image + Image Processor Processeur d'images + Image compositor: {processor} Compositeur d'images : {processor} + Import Brushes Importer des pinceaux + Import Image Importer une image + Import Options Options d'importation + Import Photoshop brushes (.abr) Importer des pinceaux Photoshop (.abr) + Import Settings Paramètres d'importation + Importing {what} from {file}… Importation de {what} depuis {file}… + Importing… Importation… + In Black Noir d'entrée + In White Blanc d'entrée + Infinite Infini + Information Informations + Ink Outlines Contours à l'encre + Input Entrée + Input Levels Niveaux d'entrée + Input Mode Mode d'entrée + Install Plug-in Installer une extension + Integrated Density Densité intégrée + Intensity Intensité + Intent Intention de rendu + Interpret Footage Interpréter le métrage + Interval Intervalle + Invalid panel layout; restored the default dock Disposition des panneaux non valide ; l'ancrage par défaut a été rétabli + Invert Mask Inverser le masque + Invert Selection Inverser la sélection + Iris Blur Flou d'iris + Ivy Lierre + Jag Dentelé + Join the ArtCraft Discord Rejoindre le Discord d'ArtCraft + Join the ArtCraft Discord ({url}) Rejoindre le Discord d'ArtCraft ({url}) + Kernel Noyau + Kerning: Metrics, Optical or a value in 1/1000 em (Alt+←/→ at the caret) Crénage : Métriques, Optique ou une valeur en 1/1000 em (Alt+←/→ au point d'insertion) + Keyboard Clavier + Label Étiquette + Latin 1 Latin 1 + Latin Extended-A Latin étendu A + Latin Extended-B Latin étendu B + Layer Comps to Files Compositions de calques en fichiers + Layer Content Options Options de contenu du calque + Layer From Background Calque d'après l'arrière-plan + Layer Mask Hides Effects Le masque de calque masque les effets + Layer Properties Propriétés du calque + Layer Styles Styles de calque + Layer from Background Calque d'après l'arrière-plan + Layers to Files Calques en fichiers + Leaf Color Couleur de la feuille + Learn More En savoir plus + Leaves Amount Quantité de feuilles + Leaves Color Couleur des feuilles + Leaves Size Taille des feuilles + Left Diagonal Diagonale gauche + Legacy Ancienne version + Length Longueur + Lens Blur Flou d'objectif + Lens Correction Correction de l'objectif + Lens Flare Reflet d'objectif + Less Moins + Level Based Selon les niveaux + Library Bibliothèque + Light Dark Balance Équilibre clair/foncé + Light Direction Direction de la lumière + Light Intensity Intensité de la lumière + Light Rough Clair rugueux + Light Type Type de lumière + Light X Lumière X + Light Y Lumière Y + Light Z Lumière Z + Lighten Grout Éclaircir le joint + Lighten Only Éclaircir seulement + Lighting Éclairage + Lighting Effects Effets d'éclairage + Linear Dodge Densité linéaire − + Linear-srgb sRVB linéaire + Link Lier + Liquify Liquéfier + List Liste + List view Affichage en liste + Live Preview Aperçu en direct + Load 3D LUT Charger une LUT 3D + Load Files into Stack Charger des fichiers dans une pile + Loading… Chargement… + Lock All Tout verrouiller + Lock Image Pixels Verrouiller les pixels de l'image + Lock Layers Verrouiller les calques + Lock Position Verrouiller la position + Lock Transparent Pixels Verrouiller les pixels transparents + Locked Verrouillé + Long Lines Lignes longues + Long Strokes Traits longs + Lower Inférieur + Lum Lum. + Lut LUT + Make Selection… Créer une sélection… + Make Symmetry Path Créer un tracé de symétrie + Make Work Path… Créer un tracé de travail… + Manual Manuel + Map Document Document de carte + Map Layer Calque de carte + Map Path Chemin de la carte + Mask Incomplete Blocks Masquer les blocs incomplets + Mask None Ne rien masquer + Masks Masques + Match Color Faire correspondre la couleur + Math Symbols Symboles mathématiques + Max Offset Décalage max. + Max Radius Rayon max. + Medium Dots Points moyens + Medium Gray Gris moyen + Medium Lines Lignes moyennes + Medium Strokes Traits moyens + Merge Channels… Fusionner les canaux… + Merge Spot Channel Fusionner le canal de ton direct + Merge to HDR Pro Fusionner en HDR Pro + Metallic Métallique + Mezzotint Manière noire + Middle Milieu + Midtone Contraste des tons moyens + Monitor Profile Profil du moniteur + Monitor profile in use on {display}: {profile} Profil du moniteur utilisé sur {display} : {profile} + Monitor profile in use: {profile} Profil du moniteur utilisé : {profile} + Monitor profile on {display} (main window): {profile} Profil du moniteur sur {display} (fenêtre principale) : {profile} + Monitor profile on {display}: {profile} Profil du moniteur sur {display} : {profile} + Monitor profile: {profile} Profil du moniteur : {profile} + Mono Contrast Contraste mono + Monochromatic Monochromatique + More Plus + Mosaic Mosaïque + Mosaic Tiles Carreaux de mosaïque + Motion Blur Flou de mouvement + Mouse Souris + Move Layer Déplacer le calque + Movie Prime Focale fixe cinéma + Multiple Flames Along Path Plusieurs flammes le long du tracé + Multiple Flames One Direction Plusieurs flammes dans une direction + Multiple Flames Path Directions Plusieurs flammes selon les directions du tracé + Multiple Flames Various Length Plusieurs flammes de longueurs variées + Native file drag-and-drop is not supported on Wayland yet. Use File › Open, or run PhotoCraft under XWayland with `WAYLAND_DISPLAY= photocraft`. Le glisser-déposer natif de fichiers n'est pas encore pris en charge sous Wayland. Utilisez Fichier › Ouvrir, ou lancez PhotoCraft sous XWayland avec `WAYLAND_DISPLAY= photocraft`. + Neon Glow Lueur néon + Neutralize Neutraliser + New Brush Preset Nouveau préréglage de pinceau + New Channel… Nouveau canal… + New Guide Nouveau repère + New Guide Layout Nouvelle disposition de repères + New Spot Channel… Nouveau canal de ton direct… + New Video Layer from File Nouveau calque vidéo à partir d'un fichier + New Window Nouvelle fenêtre + New document Nouveau document + Next Suivant + No Document Aucun document + No Image Aucune image + No Layers Aucun calque + No Results Aucun résultat + No Results Found Aucun résultat trouvé + No Selection Aucune sélection + No custom shape "{name}": pick one in the options bar or Window › Shapes Aucune forme personnalisée « {name} » : choisissez-en une dans la barre d'options ou via Fenêtre › Formes + No layers Aucun calque + No recent files Aucun fichier récent + No selection Aucune sélection + Noise Color Bruit de couleur + Noise Color Detail Détail du bruit de couleur + Noise Luminance Bruit de luminance + Noise Luminance Detail Détail du bruit de luminance + Nothing to Redo Rien à rétablir + Nothing to Undo Rien à annuler + Nothing to paste: the clipboard holds no image Rien à coller : le presse-papiers ne contient aucune image + Number Of Levels Nombre de niveaux + Numbers Nombres + Ocean Ripple Ondulation océanique + Octagon Octogone + Odd Fields Trames impaires + Oil Paint Peinture à l'huile + On Activé + One Flame Along Path Une flamme le long du tracé + Open As Ouvrir en tant que + Open File Ouvrir un fichier + Open Folder Ouvrir un dossier + Open Image Ouvrir une image + Open Recent is unavailable on the web La commande Ouvrir un fichier récent n'est pas disponible sur le Web + Opened {url} {url} ouvert + Opening Ouverture + Operation canceled Opération annulée + Operation cancelled Opération annulée + Operation completed Opération terminée + Operation failed Échec de l'opération + Out Black Noir de sortie + Out From Center Du centre vers l'extérieur + Out White Blanc de sortie + Out of memory Mémoire insuffisante + Output Levels Niveaux de sortie + Oval Ovale + Overlay Edge Superposition des contours + Package Rassembler + Page Setup… Mise en page… + Paint Daubs Barbouillage + Palette Knife Couteau à palette + Panel Panneau + Paper Brightness Luminosité du papier + Parallel Parallèle + Paste Complete Stroke Coller le contour complet + Paste Fill Coller le remplissage + Patchwork Patchwork + Path Blur Flou de tracé + Paths to Illustrator Tracés vers Illustrator + Pattern Type Type de motif + Pencil Width Largeur du crayon + Pentagon Pentagone + Percentage Pourcentage + Perimeter Périmètre + Permission denied Autorisation refusée + PhotoCraft keeps retrying; until a save succeeds, preference changes are lost when it closes. PhotoCraft continue de réessayer ; tant qu'un enregistrement n'a pas abouti, les modifications des préférences seront perdues à la fermeture. + Photomerge Photomerge + Picture Frame Cadre d'image + Pinch Pincement + Pins Épingles + Place Embedded Placer incorporé + Place Linked Placer lié + Place Scale Marker Placer un repère d'échelle + Plane Plan + Planes Plans + Plastic Wrap Film plastique + Platform: {os} {arch} Plateforme : {os} {arch} + Please wait… Veuillez patienter… + Point Point + Point Curve Courbe de points + Point Curve Blue Courbe de points bleue + Point Curve Green Courbe de points verte + Point Curve Red Courbe de points rouge + Pointed Pointu + Pointillize Pointillisme + Polar Coordinates Coordonnées polaires + Polar To Rectangular Polaires vers rectangulaires + Policy Cmyk Règle CMJN + Policy Gray Règle niveaux de gris + Policy Rgb Règle RVB + Pond Ripples Ondulations d'étang + Poster Edges Contours postérisés + Posterization Postérisation + Preferences can't be saved Les préférences ne peuvent pas être enregistrées + Preferences saved Préférences enregistrées + Preferences were reset: {error} Les préférences ont été réinitialisées : {error} + Preferences… Préférences… + Preserve Conserver + Preset Manager Gestionnaire de préréglages + Press keys Appuyez sur les touches + Preview Image Aperçu de l'image + Previous Précédent + Prime105 Focale fixe 105 mm + Prime35 Focale fixe 35 mm + Printed to {path} Imprimé vers {path} + Processing… Traitement… + Profile Profil + Prophoto-compat ProPhoto (compatible) + Pucker Contraction + Push Left Pixels poussés vers la gauche + Pyramids Pyramides + Quit Quitter + Quit PhotoCraft Quitter PhotoCraft + Quit {app} Quitter {app} + Radial Blur Flou radial + Radius X Rayon X + Radius Y Rayon Y + Ran script {name} Script {name} exécuté + Random Aléatoire + Randomize Length Longueur aléatoire + Randomize Shapes Formes aléatoires + Raw Defaults Valeurs Raw par défaut + Reading Lecture + Ready Prêt + Rec2020 Rec2020 + Recent Files Fichiers récents + Reconstruct All Tout reconstruire + Recovered {name} {name} récupéré + Rectangular Selection Sélection rectangulaire + Rectangular To Polar Rectangulaires vers polaires + Red Cyan Rouge/Cyan + Reduce Color Noise Réduire le bruit de couleur + Reduce Noise Réduction du bruit + Relief Relief + Relink to File Lier de nouveau au fichier + Remove All Tout supprimer + Remove Jpeg Artifact Supprimer l'artefact JPEG + Rename Brush Renommer le pinceau + Rename Channel… Renommer le canal… + Rename Group Renommer le groupe + Rename Tool Preset Renommer le préréglage d'outil + Render Video Rendu vidéo + Rendering… Rendu en cours… + Reorder Layer Réorganiser le calque + Replace Remplacer + Replace Color Remplacer la couleur + Replace Contents Remplacer le contenu + Replace Footage Remplacer le métrage + Report a Bug Signaler un bogue + Report an Issue Signaler un problème + Reset All Tout réinitialiser + Reset Preferences Réinitialiser les préférences + Resolve Missing Fonts Résoudre les polices manquantes + Restart Redémarrer + Restart Required Redémarrage requis + Resume Reprendre + Retry Réessayer + Rgb RVB + Ridges Crêtes + Right Diagonal Diagonale droite + Ripple Ondulation + Ripple Magnitude Amplitude de l'ondulation + Ripple Size Taille de l'ondulation + Rope Corde + Roses Roses + Rough Pastels Pastels rugueux + Row Order Ordre des lignes + Sample Size Taille de l'échantillon + Sandstone Grès + Sat Sat. + Save As Enregistrer sous + Save Changes Enregistrer les modifications + Save File Enregistrer le fichier + Save Image Enregistrer l'image + Save Path Enregistrer le tracé + Save a Copy Enregistrer une copie + Save before closing? Enregistrer avant de fermer ? + Save for Web (Legacy) Enregistrer pour le Web (ancien) + Save your work and restart PhotoCraft to retry GPU acceleration. CPU rendering remains active for this session. Enregistrez votre travail et redémarrez PhotoCraft pour réessayer l'accélération GPU. Le rendu CPU reste actif pour cette session. + Saved a copy as {path} Copie enregistrée sous {path} + Saved {path}; image assets: {n} in {dir} {path} enregistré ; ressources d'image : {n} dans {dir} + Saving… Enregistrement… + Scale Effects Mettre les effets à l'échelle + Scale Factor Facteur d'échelle + Scale Units Unités d'échelle + Scaling Échelle + Scallops Festons + Script Events Manager Gestionnaire d'événements de script + Scroll menu down Faire défiler le menu vers le bas + Scroll menu up Faire défiler le menu vers le haut + Search commands, tools and panels Rechercher des commandes, outils et panneaux + Search menus Rechercher dans les menus + Seed Valeur initiale + Select Custom Color… Choisir une couleur personnalisée… + Select Inverse Inverser la sélection + Select Layer Sélectionner le calque + Select and Mask Sélectionner et masquer + Selected at launch: {backend} Sélectionné au lancement : {backend} + Sensitivity Sensibilité + Sent to the printer ({spooler}) Envoyé à l'imprimante ({spooler}) + Sepia Sépia + Services Services + Set Colors Définir les couleurs + Set Measurement Scale Définir l'échelle de mesure + Sgray sGray + Shadow Ombres + Shadow Amount Quantité des ombres + Shadow Intensity Intensité des ombres + Shadow Radius Rayon des ombres + Shadow Tone Amplitude tonale des ombres + Shadows/Highlights Ombres/Hautes lumières + Shallow Superficiel + Shape Blur Flou selon une forme + Sharpen Amount Quantité de netteté + Sharpen Detail Détail de la netteté + Sharpen Details Détails de la netteté + Sharpen Masking Masquage de la netteté + Sharpen Radius Rayon de netteté + Shear Cisaillement + Shine Brillance + Short Lines Lignes courtes + Short Strokes Traits courts + Shortcut Raccourci + Shortcuts Raccourcis + Show All Tout afficher + Show Rulers Afficher les règles + Simple Simple + Sine Sinus + Sketch Esquisse + Skip Ignorer + Slice Options Options de tranche + Smart Blur Flou intelligent + Smart Sharpen Netteté intelligente + Smoothness Lissage + Smudge Stick Estompe + Snap to Grid Aimanter à la grille + Snowflakes Flocons de neige + Softness Douceur + Software adapter Adaptateur logiciel + Solarized Dark Solarized sombre + Solid Color Couleur unie + Solid Front Faces Faces avant pleines + Source Layer Calque source + Sparkle Étincelle + Spatter Éclaboussures + Speckle Moucheté + Speed Vitesse + Spherize Sphérisation + Spin Rotation + Spin Blur Flou de rotation + Split Channels Séparer les canaux + Spot Spot + Spray Radius Rayon de pulvérisation + Sprayed Strokes Traits pulvérisés + Sprinkles Saupoudré + Square Size Taille des carrés + Squareness Forme carrée + Stagger Décalage + Stained Glass Vitrail + Star Étoile + Stars Étoiles + Start X Début X + Start Y Début Y + Statistics Statistiques + Step {step} ({id}) failed: {error} Échec de l'étape {step} ({id}) : {error} + Stippled Pointillé + Stops Arrêts + Strength Intensité + Stretch Étirement + Stroke Color Couleur du contour + Stroke Detail Détail du trait + Stroke Direction Direction du trait + Stroke Length Longueur du trait + Stroke Pressure Pression du trait + Stroke Size Taille du trait + Stroke Width Largeur du trait + Strokes Traits + Stylization Stylisation + Subtract Shapes at Overlap Soustraire les formes au chevauchement + Sumi-e Sumi-e + Surface Blur Flou de surface + Switch Foreground and Background Colors Permuter les couleurs de premier plan et d'arrière-plan + Tablet Tablette + Taper Effilement + Target Cible + Target X Cible X + Target Y Cible Y + Teal Orange Sarcelle et orange + Temporary Temporaires + Text Layer Calque de texte + Texture Coverage Couverture de la texture + Texturizer Texturisation + Thaw Mask Masque de libération + The canvas is shown as sRGB. Choose a monitor profile in Edit › Color Settings. Le canevas est affiché en sRVB. Choisissez un profil de moniteur dans Édition › Paramètres de couleur. + The canvas on {display} is shown as sRGB. Choose a monitor profile in Edit › Color Settings. Le canevas sur {display} est affiché en sRVB. Choisissez un profil de moniteur dans Édition › Paramètres de couleur. + The current gradient (pick one in Window › Gradients) Le dégradé actuel (à choisir dans Fenêtre › Dégradés) + The document does not have an embedded colour profile. Working space: {working}. Le document ne contient aucun profil colorimétrique incorporé. Espace de travail : {working}. + The document has an embedded colour profile that does not match the working space.\nEmbedded: {embedded}\nWorking: {working} Le document contient un profil colorimétrique incorporé qui ne correspond pas à l'espace de travail.\nIncorporé : {embedded}\nEspace de travail : {working} + The file could not be opened. Impossible d'ouvrir le fichier. + The file could not be saved. Impossible d'enregistrer le fichier. + Thickness Épaisseur + This Layer: Ce calque : + This Smart Object must be rasterized before proceeding. Its contents will no longer be editable. Rasterize the Smart Object? Cet objet intelligent doit être rastérisé pour continuer. Son contenu ne sera plus modifiable. Rastériser l'objet intelligent ? + This adjustment ({key}) is kept as imported and can't be edited yet. Cet ajustement ({key}) est conservé tel qu'il a été importé et ne peut pas encore être modifié. + This fill layer must be rasterized before proceeding. Its fill content will no longer be editable. Rasterize the layer? Ce calque de remplissage doit être rastérisé pour continuer. Son contenu de remplissage ne sera plus modifiable. Rastériser le calque ? + This layer must be rasterized before proceeding. Ce calque doit être rastérisé pour continuer. + This session: {reason} Cette session : {reason} + This shape layer must be rasterized before proceeding. It will no longer be editable as a shape. Rasterize the shape? Ce calque de forme doit être rastérisé pour continuer. Il ne sera plus modifiable en tant que forme. Rastériser la forme ? + This type layer must be rasterized before proceeding. Its text will no longer be editable. Rasterize the type? Ce calque de texte doit être rastérisé pour continuer. Son texte ne sera plus modifiable. Rastériser le texte ? + Tile Size Taille des carreaux + Tiles Carreaux + Tilt-Shift Inclinaison-décalage + Tint Color Couleur de la nuance + Tiny Lens Petites lentilles + To À + To Center Vers le centre + Toggle the Brush Settings panel Afficher/masquer le panneau Paramètres du pinceau + Tool Options Options d'outil + Trace Contour Tracer les contours + Transformation Transformation + Transition Transition + Transparency Shapes Layer La transparence détermine la forme du calque + Trap Recouvrement + Tree Arbre + Trim Rogner les bords + Turbulent Turbulent + Twirl Tourbillon + Twirl Clockwise Tourbillon horaire + Twirl Counterclockwise Tourbillon antihoraire + Unaltered Inchangé + Undefined Areas Zones non définies + Underlying Layer: Calque sous-jacent : + Underpainting Sous-couche + Underwater Sous l'eau + Ungroup Dissocier + Uniform Uniforme + Unite Shapes at Overlap Réunir les formes au chevauchement + Unlink Délier + Unlock Déverrouiller + Unlocked Déverrouillé + Unsaved Changes Modifications non enregistrées + Unsharp Mask Masque flou + Unsupported file format Format de fichier non pris en charge + Unsupported format Format non pris en charge + Up Haut + Update Mettre à jour + Updates Mises à jour + Upper Supérieur + Use Custom Color Utiliser une couleur personnalisée + Use File › Open to open an image. Utilisez Fichier › Ouvrir pour ouvrir une image. + Use Selection In Source Utiliser la sélection dans la source + Use Selection In Target Utiliser la sélection dans la cible + Vanishing Point Point de fuite + Variable Data Sets Jeux de données variables + Vector Mask Hides Effects Le masque vectoriel masque les effets + Vertical Only Vertical seulement + Vertical Type Tool Outil Texte vertical + Video Frames to Layers Images vidéo dans des calques + Vignette Amount Quantité de vignettage + Vignette Feather Adoucissement du vignettage + Vignette Highlights Hautes lumières du vignettage + Vignette Midpoint Point médian du vignettage + Vignette Roundness Arrondi du vignettage + Vignette Style Style du vignettage + Vine Color Couleur de la liane + Vine With Flowers Liane avec fleurs + Vine With Leaves Liane avec feuilles + Violent Violent + WIA Support Prise en charge de WIA + Warm Chaud + Warming L B A Réchauffement LBA + Warming81 Réchauffement 81 + Warming85 Réchauffement 85 + Warmth Chaleur + Warning Avertissement + Warp Text Déformer le texte + Watercolor Aquarelle + Wavelength Max Longueur d'onde max. + Wavelength Min Longueur d'onde min. + Waves Vagues + Wetness Humidité + White Clip Écrêtage des blancs + White Intensity Intensité du blanc + White Is High Le blanc est le plus haut + Wide Blurry Large et flou + Wide Sharp Large et net + Wind Vent + Window renderer: {renderer} Moteur de rendu de la fenêtre : {renderer} + Working Espace de travail + Working Cmyk CMJN de travail + Working Gray Gris de travail + Working Rgb RVB de travail + Workspaces Espaces de travail + Wrap Enrouler + ZigZag Zigzag + Zigzag Zigzag + auto auto + blue bleu + brushes pinceaux + fallback repli + gradients dégradés + gray gris + green vert + layer mask masque de calque + manual manuel + none aucun + off désactivé + on activé + orange orange + previous reading kept; the last re-read failed: {reason} lecture précédente conservée ; la dernière relecture a échoué : {reason} + red rouge + unknown inconnu + unknown type type inconnu + vector mask masque vectoriel + violet violet + yellow jaune + {index} of {total} {index} sur {total} + {label} is not available: {reason} {label} n'est pas disponible : {reason} + {message} (notes: {n}; {warning}) {message} (remarques : {n} ; {warning}) + {shortcut} assigned. {shortcut} attribué. + {shortcut} is already in use by {commands} and will be removed from it when you click OK. {shortcut} est déjà utilisé par {commands} et lui sera retiré lorsque vous cliquerez sur OK. + {verb} {mask} {layer} {verb} le {mask} ({layer}) + ✓ Color Indicates Masked Areas ✓ La couleur indique les zones masquées + ✓ Color Indicates Selected Areas ✓ La couleur indique les zones sélectionnées +@id edit.clear Effacer +@id edit.stroke Contourner… +@id edit.transform.scale Mise à l'échelle +@id file.close Fermer +@id filter.sharpen.sharpen Netteté +@id layer.layerMask.apply Appliquer le masque de calque +@plural Exported {n} swatch to {file}|Exported {n} swatches to {file} {n} nuance exportée vers {file}|{n} nuances exportées vers {file} +@plural Imported {n} brush from {file}|Imported {n} brushes from {file} {n} pinceau importé depuis {file}|{n} pinceaux importés depuis {file} +@plural Imported {n} brush from {group}|Imported {n} brushes from {group} {n} pinceau importé depuis {group}|{n} pinceaux importés depuis {group} +@plural Imported {n} gradient from {file}|Imported {n} gradients from {file} {n} dégradé importé depuis {file}|{n} dégradés importés depuis {file} +@plural Imported {n} swatch from {file}|Imported {n} swatches from {file} {n} nuance importée depuis {file}|{n} nuances importées depuis {file} +@plural Played {n} step|Played {n} steps {n} étape lue|{n} étapes lues +@plural Saved for Web: {n} file|Saved for Web: {n} files Enregistré pour le Web : {n} fichier|Enregistré pour le Web : {n} fichiers +@plural Updated {n} linked smart object changed on disk|Updated {n} linked smart objects changed on disk Mise à jour de {n} objet intelligent lié modifié sur le disque|Mise à jour de {n} objets intelligents liés modifiés sur le disque +@plural {n} layer recorded by this comp was deleted. Update the comp, or clear the warning.|{n} layers recorded by this comp were deleted. Update the comp, or clear the warning. {n} calque enregistré par cette composition a été supprimé. Mettez la composition à jour ou effacez l'avertissement.|{n} calques enregistrés par cette composition ont été supprimés. Mettez la composition à jour ou effacez l'avertissement. +@plural {n} plane|{n} planes {n} plan|{n} plans +@plural {n} preset|{n} presets {n} préréglage|{n} préréglages +@plural {n} sample point removed|{n} sample points removed {n} point d'échantillonnage supprimé|{n} points d'échantillonnage supprimés +@plural {n} slice|{n} slices {n} tranche|{n} tranches +@plural {n} step|{n} steps {n} étape|{n} étapes +@plural {warning} (+{n} more brush preset warning)|{warning} (+{n} more brush preset warnings) {warning} (+{n} autre avertissement de préréglage de pinceau)|{warning} (+{n} autres avertissements de préréglages de pinceau) +@plural {what} with {n} warning|{what} with {n} warnings {what} avec {n} avertissement|{what} avec {n} avertissements +cameraRaw Aqua Aqua +cameraRaw Balance Balance +cameraRaw Blue Bleu +cameraRaw Clarity Clarté +cameraRaw Color Couleur +cameraRaw Color Detail Détail des couleurs +cameraRaw Color Grading Étalonnage des couleurs +cameraRaw Color Mixer Mélangeur de couleurs +cameraRaw Contrast Contraste +cameraRaw Curve Courbe +cameraRaw Dehaze Correction de la brume +cameraRaw Detail Détail +cameraRaw Effects Effets +cameraRaw Exposure Exposition +cameraRaw Feather Adoucissement +cameraRaw Global Global +cameraRaw Green Vert +cameraRaw Highlights Hautes lumières +cameraRaw Hue Teinte +cameraRaw Luminance Luminance +cameraRaw Luminance Detail Détail de la luminance +cameraRaw Magenta Magenta +cameraRaw Midpoint Point médian +cameraRaw Midtones Tons moyens +cameraRaw Orange Orange +cameraRaw Purple Violet +cameraRaw Radius Rayon +cameraRaw Red Rouge +cameraRaw Roughness Rugosité +cameraRaw Roundness Arrondi +cameraRaw Saturation Saturation +cameraRaw Shadows Ombres +cameraRaw Size Taille +cameraRaw Temperature Température +cameraRaw Texture Texture +cameraRaw Vibrance Vibrance +cameraRaw Whites Blancs +cameraRaw Yellow Jaune +colorRange Invert Inverser +document Orientation Orientation +layerLabel Magenta Magenta +layerLabel No Color Aucune couleur +layerLabel Red Rouge +layers Fill Remplissage +layers Fill: Remplissage : +navigator Zoom Zoom +shape Appearance Apparence From a23f7778bec863140aa160774e2635e325cd26fb Mon Sep 17 00:00:00 2001 From: Florent Siegenthaler <64897087+Atalantis@users.noreply.github.com> Date: Mon, 12 Oct 2026 23:13:17 +0200 Subject: [PATCH 05/44] i18n: align French terms with retouching and Photoshop vocabulary (366 entries) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rewords French entries, old and new, to the terms French retouchers know: Couche for Channel, Zone de travail for Canvas, Masque de fusion, Objet dynamique, Pixelliser, Calque de réglage, Contour progressif, Tampon de duplication, Transformation manuelle, Tons clairs / Tons foncés, Masque d'écrêtage. 88 entries use terms attested in Adobe's public French help pages; 278 come from the reviewer's knowledge of the French interface. The catalog header records the exception to the clean-room note. Keys with two meanings (Fill, Canvas, Screen, Sharpen) are left unchanged. --- crates/ui-egui/src/i18n/fr.tsv | 737 +++++++++++++++++---------------- 1 file changed, 370 insertions(+), 367 deletions(-) diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 88e2bc80f4..ec4192b90b 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -13,6 +13,9 @@ # French computing vocabulary (calque, sélection, pinceau, dégradé, flou, opacité ...). No # proprietary localisation resource, string table or product UI was consulted or copied; in # particular not the French localisation of Adobe Photoshop. Add entries the same way. +# Exception, 2026-10: about 370 entries were realigned on the terms French retouchers use and that +# Adobe's public French help pages show (Couche, Zone de travail, Masque de fusion, Objet dynamique, +# Pixelliser, Réglage ...), so the paragraph above no longer holds for those entries. # Conventions: menus are nouns (Fichier, Édition, Image ...), commands are infinitives (Ouvrir…, # Enregistrer sous…), sentence case. French typography: a no-break space (U+00A0) before # « : ; ? ! » and "%", and inside guillemets « … ». Product and technology names (PhotoCraft, @@ -91,8 +94,8 @@ Render Video… Rendu vidéo… Generate Générer Image Assets Ressources d'image - Place Embedded… Placer incorporé… - Place Linked… Placer lié… + Place Embedded… Importer et incorporer… + Place Linked… Importer et lier… Package… Rassembler… Automate Automatisation Batch… Traitement par lots… @@ -125,11 +128,11 @@ Fade… Estomper… Cut Couper Copy Copier - Copy Merged Copier les calques fusionnés + Copy Merged Copier avec fusion Paste Coller Paste Special Collage spécial - Paste in Place Coller sur place - Paste Into Coller dans la sélection + Paste in Place Coller en place + Paste Into Coller dedans Paste Outside Coller hors de la sélection Paste Lorem Ipsum Coller du Lorem Ipsum Clear Effacer @@ -137,13 +140,13 @@ Check Spelling… Vérifier l'orthographe… Find and Replace Text… Rechercher et remplacer du texte… Fill… Remplir… - Stroke… Contourner… + Stroke… Contour… Content-Aware Fill… Remplissage d'après le contenu… Delete and Fill Selection Supprimer et remplir la sélection - Content-Aware Scale Mise à l'échelle d'après le contenu - Puppet Warp Déformation par épingles + Content-Aware Scale Échelle basée sur le contenu + Puppet Warp Déformation de la marionnette Perspective Warp Déformation de la perspective - Free Transform Transformation libre + Free Transform Transformation manuelle Transform Transformation Again Répéter Scale Mise à l'échelle @@ -191,37 +194,37 @@ Export… Exportation… Performance… Performances… Scratch Disks… Disques de travail… - Cursors… Curseurs… - Transparency & Gamut… Transparence et gamut… + Cursors… Pointeurs… + Transparency & Gamut… Transparence et couleurs non imprimables… Units & Rulers… Unités et règles… Guides, Grid & Slices… Repères, grille et tranches… - Plug-ins… Extensions… + Plug-ins… Modules externes… Type… Texte… Enhanced Controls… Commandes avancées… Camera Raw… Camera Raw… Raw Defaults… Valeurs Raw par défaut… Integrations… Intégrations… AI Integrations… Intégrations d'IA… - Install Plug-in… Installer une extension… + Install Plug-in… Installer un module externe… Language Options Options de langue Settings… Paramètres… Mode Mode Bitmap Bitmap Grayscale Niveaux de gris - Duotone Duotone + Duotone Bichromie Indexed Color Couleurs indexées RGB Color Couleurs RVB CMYK Color Couleurs CMJN Lab Color Couleurs Lab - Multichannel Multicanal - 8 Bits/Channel 8 bits/canal - 16 Bits/Channel 16 bits/canal - 32 Bits/Channel 32 bits/canal + Multichannel Multicouche + 8 Bits/Channel 8 bits/couche + 16 Bits/Channel 16 bits/couche + 32 Bits/Channel 32 bits/couche Color Table… Table des couleurs… Bitmap… Bitmap… - Duotone… Duotone… + Duotone… Bichromie… Indexed Color… Couleurs indexées… - Adjustments Ajustements + Adjustments Réglages Brightness/Contrast… Luminosité/Contraste… Levels… Niveaux… Curves… Courbes… @@ -231,33 +234,33 @@ Color Balance… Balance des couleurs… Black & White… Noir et blanc… Photo Filter… Filtre photo… - Channel Mixer… Mélangeur de canaux… - Color Lookup… Table de correspondance des couleurs… + Channel Mixer… Mélangeur de couches… + Color Lookup… Correspondance des couleurs… Invert Inverser - Posterize… Postériser… + Posterize… Isohélie… Threshold… Seuil… - Gradient Map… Mappage de dégradé… - Selective Color… Couleur sélective… - Shadows/Highlights… Ombres/Hautes lumières… - HDR Toning… Tonalité HDR… - Desaturate Désaturer - Match Color… Faire correspondre la couleur… - Replace Color… Remplacer la couleur… + Gradient Map… Courbe de transfert de dégradé… + Selective Color… Correction sélective… + Shadows/Highlights… Tons foncés/Tons clairs… + HDR Toning… Virage HDR… + Desaturate Désaturation + Match Color… Correspondance de la couleur… + Replace Color… Remplacement de couleur… Equalize Égaliser - Auto Tone Tonalité automatique + Auto Tone Ton automatique Auto Contrast Contraste automatique Auto Color Couleur automatique Image Size… Taille de l'image… - Canvas Size… Taille du canevas… + Canvas Size… Taille de la zone de travail… Image Rotation Rotation de l'image - Flip Canvas Horizontal Retourner le canevas horizontalement - Flip Canvas Vertical Retourner le canevas verticalement + Flip Canvas Horizontal Symétrie horizontale de la zone de travail + Flip Canvas Vertical Symétrie verticale de la zone de travail Crop Recadrer - Trim… Rogner les bords… - Reveal All Tout afficher + Trim… Rognage… + Reveal All Tout faire apparaître Duplicate… Dupliquer… Apply Image… Appliquer une image… - Calculations… Calculs… + Calculations… Opérations… Variables Variables Define… Définir… Data Sets… Jeux de données… @@ -269,7 +272,7 @@ Select Data Points… Sélectionner les points de données… Record Measurements Enregistrer les mesures Ruler Tool Outil Règle - Count Tool Outil Compteur + Count Tool Outil Comptage Place Scale Marker… Placer un repère d'échelle… Custom… Personnalisé… New Nouveau @@ -289,7 +292,7 @@ New Fill Layer Nouveau calque de remplissage New Adjustment Layer Nouveau calque de réglage New Layer Based Slice Nouvelle tranche d'après un calque - New Smart Object via Copy Nouvel objet intelligent par copie + New Smart Object via Copy Nouvel objet dynamique par copie New Blank Video Layer Nouveau calque vidéo vide New Video Layer from File… Nouveau calque vidéo à partir d'un fichier… Duplicate Layer… Dupliquer le calque… @@ -299,14 +302,14 @@ Create Layer Créer un calque Layer Style Style de calque Blending Options… Options de fusion… - Bevel & Emboss… Biseau et relief… - Inner Shadow… Ombre intérieure… - Inner Glow… Lueur intérieure… + Bevel & Emboss… Biseautage et estampage… + Inner Shadow… Ombre interne… + Inner Glow… Lueur interne… Satin… Satin… - Color Overlay… Superposition de couleur… - Gradient Overlay… Superposition de dégradé… - Pattern Overlay… Superposition de motif… - Outer Glow… Lueur extérieure… + Color Overlay… Incrustation couleur… + Gradient Overlay… Incrustation en dégradé… + Pattern Overlay… Incrustation de motif… + Outer Glow… Lueur externe… Drop Shadow… Ombre portée… Copy Layer Style Copier le style de calque Paste Layer Style Coller le style de calque @@ -314,35 +317,35 @@ Global Light… Lumière globale… Hide All Effects Masquer tous les effets Scale Effects… Mettre les effets à l'échelle… - Smart Filter Filtre intelligent - Convert for Smart Filters Convertir pour les filtres intelligents - Clear Smart Filters Effacer les filtres intelligents - Disable Smart Filters Désactiver les filtres intelligents + Smart Filter Filtre dynamique + Convert for Smart Filters Convertir pour les filtres dynamiques + Clear Smart Filters Effacer les filtres dynamiques + Disable Smart Filters Désactiver les filtres dynamiques Delete Filter Mask Supprimer le masque de filtre Disable Filter Mask Désactiver le masque de filtre Solid Color… Couleur unie… Gradient… Dégradé… Pattern… Motif… - Layer Mask Masque de calque + Layer Mask Masque de fusion Vector Mask Masque vectoriel - Reveal Selection Afficher la sélection + Reveal Selection Faire apparaître la sélection Hide Selection Masquer la sélection Hide All Tout masquer From Transparency D'après la transparence Delete Vector Mask Supprimer le masque vectoriel - Enable Layer Mask Activer le masque de calque - Disable Layer Mask Désactiver le masque de calque + Enable Layer Mask Activer le masque de fusion + Disable Layer Mask Désactiver le masque de fusion Enable Vector Mask Activer le masque vectoriel - Link Layer Mask Lier le masque de calque - Unlink Layer Mask Délier le masque de calque + Link Layer Mask Lier le masque de fusion + Unlink Layer Mask Délier le masque de fusion Link Vector Mask Lier le masque vectoriel Add Vector Mask Ajouter un masque vectoriel Vector Mask from Current Path Masque vectoriel d'après le tracé actif - Create Clipping Mask Créer un masque de découpe - Release Clipping Mask Libérer le masque de découpe - Smart Objects Objets intelligents - Smart Object Objet intelligent - Convert to Smart Object Convertir en objet intelligent + Create Clipping Mask Créer un masque d'écrêtage + Release Clipping Mask Libérer le masque d'écrêtage + Smart Objects Objets dynamiques + Smart Object Objet dynamique + Convert to Smart Object Convertir en objet dynamique Convert to Embedded Convertir en objet incorporé Convert to Layers Convertir en calques Convert to Linked… Convertir en objet lié… @@ -376,10 +379,10 @@ Duplicate Frame Dupliquer l'image Frame from Layers Image d'après les calques Video Frames to Layers… Images vidéo dans des calques… - Rasterize Rastériser - Rasterize Type Rastériser le texte - Rasterize Type Layer Rastériser le calque de texte - Rasterize Shape Rastériser la forme + Rasterize Pixelliser + Rasterize Type Pixelliser le texte + Rasterize Type Layer Pixelliser le calque de texte + Rasterize Shape Pixelliser la forme New Guide Layout… Nouvelle disposition de repères… New Guides From Shape Nouveaux repères d'après la forme Lock Layers… Verrouiller les calques… @@ -485,9 +488,9 @@ Save Default Type Styles Enregistrer les styles de texte par défaut Deselect Désélectionner Reselect Resélectionner - Inverse Inverser - Inverse Selection Inverser la sélection - Similar Similaire + Inverse Intervertir + Inverse Selection Intervertir la sélection + Similar Généraliser Color Range… Plage de couleurs… Focus Area… Zone de mise au point… Select and Mask… Sélectionner et masquer… @@ -500,40 +503,40 @@ Modify Modifier Border… Bordure… Smooth… Lisser… - Expand… Étendre… + Expand… Dilater… Contract… Contracter… - Feather… Adoucir… - Grow Agrandir + Feather… Contour progressif… + Grow Étendre Transform Selection Transformer la sélection - Edit in Quick Mask Mode Modifier en mode masque rapide - Load Selection… Charger la sélection… - Save Selection… Enregistrer la sélection… + Edit in Quick Mask Mode Modifier en mode Masque + Load Selection… Récupérer la sélection… + Save Selection… Mémoriser la sélection… Match All Tout faire correspondre Last Filter Dernier filtre Filter Gallery… Galerie de filtres… Adaptive Wide Angle… Grand angle adaptatif… Camera Raw Filter… Filtre Camera Raw… - Liquify… Liquéfier… + Liquify… Fluidité… Vanishing Point… Point de fuite… Blur Flou - Blur Gallery Galerie de flous - Field Blur… Flou de champ… - Iris Blur… Flou d'iris… + Blur Gallery Galerie d'effets de flou + Field Blur… Flou de profondeur de champ… + Iris Blur… Flou de diaphragme… Tilt-Shift… Inclinaison-décalage… Path Blur… Flou de tracé… Spin Blur… Flou de rotation… - Blur More Flou accentué + Blur More Plus flou Box Blur… Flou par boîte… Gaussian Blur… Flou gaussien… - Lens Blur… Flou d'objectif… - Motion Blur… Flou de mouvement… + Lens Blur… Flou de l'objectif… + Motion Blur… Flou directionnel… Radial Blur… Flou radial… Shape Blur… Flou selon une forme… - Smart Blur… Flou intelligent… + Smart Blur… Flou optimisé… Surface Blur… Flou de surface… Average Moyenne - Displace… Déplacement… - Pinch… Pincement… + Displace… Dispersion… + Pinch… Contraction… Polar Coordinates… Coordonnées polaires… Ripple… Ondulation… Shear… Cisaillement… @@ -543,16 +546,16 @@ ZigZag… Zigzag… Noise Bruit Add Noise… Ajout de bruit… - Despeckle Supprimer les mouchetures - Dust & Scratches… Poussière et rayures… + Despeckle Flou intérieur + Dust & Scratches… Antipoussière… Median… Médiane… Reduce Noise… Réduction du bruit… - Pixelate Pixeliser - Color Halftone… Trame de demi-teintes couleur… + Pixelate Pixellisation + Color Halftone… Demi-teintes couleur… Crystallize… Cristallisation… Facet Facettes Fragment Fragmentation - Mezzotint… Manière noire… + Mezzotint… Mezzo-tinto… Mosaic… Mosaïque… Pointillize… Pointillisme… Render Rendu @@ -560,38 +563,38 @@ Difference Clouds Nuages par différence Fibers… Fibres… Flame… Flamme… - Lens Flare… Reflet d'objectif… - Lighting Effects… Effets d'éclairage… + Lens Flare… Halo… + Lighting Effects… Éclairage… Relight… Rééclairage… Picture Frame… Cadre d'image… Tree… Arbre… Sharpen Netteté - Sharpen Edges Netteté des contours - Sharpen More Netteté accentuée - Smart Sharpen… Netteté intelligente… - Unsharp Mask… Masque flou… + Sharpen Edges Contours plus nets + Sharpen More Encore plus net + Smart Sharpen… Netteté optimisée… + Unsharp Mask… Accentuation… High Pass… Passe-haut… - Stylize Styliser - Diffuse… Diffuser… - Emboss… Relief… - Extrude… Extruder… - Find Edges Détecter les contours + Stylize Esthétiques + Diffuse… Diffusion… + Emboss… Estampage… + Extrude… Extrusion… + Find Edges Tracé des contours Oil Paint… Peinture à l'huile… - Solarize Solariser - Tiles… Carreaux… - Trace Contour… Tracer les contours… - Wind… Vent… + Solarize Solarisation + Tiles… Carrelage… + Trace Contour… Courbes de niveau… + Wind… Soufflerie… Video Vidéo - De-Interlace… Désentrelacer… + De-Interlace… Désentrelacement… NTSC Colors Couleurs NTSC - Other Autres + Other Divers Maximum… Maximum… Minimum… Minimum… - Offset… Décalage… - Plug-ins Extensions + Offset… Translation… + Plug-ins Modules externes Proof Setup Configuration d'épreuve Proof Colors Couleurs d'épreuve - Gamut Warning Alerte de gamut + Gamut Warning Couleurs non imprimables Working CMYK CMJN de travail Working Cyan Plate Plaque cyan de travail Working Magenta Plate Plaque magenta de travail @@ -633,11 +636,11 @@ HDV 1080/DVCPRO HD 720 (1.33) HDV 1080/DVCPRO HD 720 (1,33) DVCPRO HD 1080/HDV 1080 (1.5) DVCPRO HD 1080/HDV 1080 (1,5) 32-bit Preview Options… Options d'aperçu 32 bits… - Screen Mode Mode d'écran - Standard Screen Mode Mode d'écran standard + Screen Mode Mode d'affichage + Standard Screen Mode Mode d'affichage standard Full Screen Mode With Menu Bar Mode plein écran avec barre de menus Full Screen Mode Mode plein écran - Cycle Screen Mode Changer de mode d'écran + Cycle Screen Mode Changer de mode d'affichage Show Afficher Extras Extras Selection Edges Contours de la sélection @@ -645,12 +648,12 @@ Current Path Tracé actif Grid Grille Guides Repères - Canvas Guides Repères du canevas + Canvas Guides Repères de la zone de travail Artboard Guides Repères du plan de travail Count Compteur - Smart Guides Repères intelligents + Smart Guides Repères commentés Slices Tranches - Notes Notes + Notes Annotations Pixel Grid Grille de pixels Pixel Art Pixel art Pixel Art Preview Aperçu pixel art @@ -666,7 +669,7 @@ Lock Guides Verrouiller les repères Clear Guides Effacer les repères Clear Selected Artboard Guides Effacer les repères du plan de travail sélectionné - Clear Canvas Guides Effacer les repères du canevas + Clear Canvas Guides Effacer les repères de la zone de travail New Guide… Nouveau repère… Lock Slices Verrouiller les tranches Clear Slices Effacer les tranches @@ -720,16 +723,16 @@ Actions Actions Brush Settings Paramètres du pinceau Brushes Pinceaux - Channels Canaux + Channels Couches Character Caractère Character Styles Styles de caractère - Clone Source Source de clonage + Clone Source Source de duplication Color Couleur Glyphs Glyphes Gradients Dégradés Histogram Histogramme History Historique - Info Infos + Info Informations Measurement Log Journal des mesures Modifier Keys Touches de modification Navigator Navigation @@ -757,7 +760,7 @@ Invert… Inverser… Load Files into Stack… Charger des fichiers dans une pile… Motion Mouvement - Notes… Notes… + Notes… Annotations… Other… Autre… Remove Warp Split Supprimer la division de la déformation Shape Forme @@ -778,18 +781,18 @@ Adaptive Adaptative Adaptive Wide Angle isn't open Grand angle adaptatif n'est pas ouvert Add Ajouter - Add Layer Mask Ajouter un masque de calque + Add Layer Mask Ajouter un masque de fusion Add Stop Ajouter un point Add a layer style Ajouter un style de calque Add a mask (from the selection; {key} inverts) Ajouter un masque (d'après la sélection ; {key} l'inverse) - Add an adjustment Ajouter un ajustement + Add an adjustment Ajouter un réglage Add disk Ajouter un disque Add space after paragraph Espace après le paragraphe Add space before paragraph Espace avant le paragraphe Add to Sample Ajouter à l'échantillon Add to selection ({key}) Ajouter à la sélection ({key}) Adjust for Zoom Adapter au zoom - Adjustment visible Ajustement visible + Adjustment visible Réglage visible Adobe RGB (1998) compatible Compatible Adobe RGB (1998) Align and Distribute Aligner et répartir Align and distribute Aligner et répartir @@ -813,7 +816,7 @@ Anti-alias Lissage Appearance Apparence Appearance (Layer Style) Apparence (style de calque) - Apply Layer Mask Appliquer le masque de calque + Apply Layer Mask Appliquer le masque de fusion Apply Per Tip Appliquer par pointe Apply next selected layer comp Appliquer la composition de calques sélectionnée suivante Apply previous selected layer comp Appliquer la composition de calques sélectionnée précédente @@ -870,7 +873,7 @@ Cancel transform (Esc) Annuler la transformation (Échap) Cancel warp (Esc) Annuler la déformation (Échap) Canvas Canevas - Canvas extension color: Couleur d'extension du canevas : + Canvas extension color: Couleur d'extension de la zone de travail : Capture Capturer Capture the visibility, position and style of every layer with + below, then switch between versions. Capturez la visibilité, la position et le style de chaque calque avec le + ci-dessous, puis passez d'une version à l'autre. Catch-up on Stroke End Rattraper en fin de trait @@ -879,10 +882,10 @@ Center text Centrer le texte Change Remplacer Change All Tout remplacer - Change Screen Mode (F) Changer de mode d'écran (F) + Change Screen Mode (F) Changer de mode d'affichage (F) Change To: Remplacer par : - Channel Mixer Mélangeur de canaux - Channel: Canal : + Channel Mixer Mélangeur de couches + Channel: Couche : Check All Layers Vérifier tous les calques Check Spelling Vérifier l'orthographe Clarity Clarté @@ -896,11 +899,11 @@ Click to edit the gradient Cliquez pour modifier le dégradé Click to restore the current colour Cliquez pour rétablir la couleur actuelle Click to zoom in · {key}-click to zoom out · drag right/left to zoom in/out Cliquez pour zoomer · {key}-clic pour dézoomer · faites glisser vers la droite/gauche pour zoomer/dézoomer - Click with the Note tool to add a note. Cliquez avec l'outil Note pour ajouter une note. + Click with the Note tool to add a note. Cliquez avec l'outil Annotation pour ajouter une annotation. Click: corner · Drag: smooth · Click first point: close · {key} finish · Esc cancel Clic : angle · Glisser : lisse · Clic sur le premier point : fermer · {key} : terminer · Échap : annuler Click: corner · Drag: smooth · {alt}-drag: break handle · Click or drag first point: close · {key} finish · Esc cancel Clic : angle · Glisser : lisse · {alt}+glisser : briser la poignée · Clic ou glisser sur le premier point : fermer · {key} : terminer · Échap : annuler - Clip to layer below Découper selon le calque inférieur - Clipped Découpé + Clip to layer below Écrêter selon le calque inférieur + Clipped Écrêté Close Style Options Fermer les options de style Cmd Cmd Coated CMYK CMJN papier couché @@ -908,7 +911,7 @@ Color Balance Balance des couleurs Color Detail Détail des couleurs Color Grading Étalonnage des couleurs - Color Lookup Table de correspondance des couleurs + Color Lookup Correspondance des couleurs Color Management Gestion des couleurs Color Mixer Mélangeur de couleurs Color Mode Mode colorimétrique @@ -918,7 +921,7 @@ Colorize Colorer Colors Couleurs Commit Perspective Warp ({key}) Valider la déformation de la perspective ({key}) - Commit Puppet Warp ({key}) Valider la déformation par épingles ({key}) + Commit Puppet Warp ({key}) Valider la déformation de la marionnette ({key}) Commit any current edits ({key}) Valider les modifications en cours ({key}) Commit current crop operation ({key}) Valider le recadrage en cours ({key}) Commit transform ({key}) Valider la transformation ({key}) @@ -926,7 +929,7 @@ Constant Constant Constrain proportions Conserver les proportions Contact Sheet II Planche contact II - Content-Aware Selon le contenu + Content-Aware Contenu pris en compte Contiguous Contigu Contrast Contraste Control: Contrôle : @@ -949,9 +952,9 @@ Create a new group Créer un nouveau groupe Create a new layer Créer un nouveau calque Create new action Créer une nouvelle action - Create new channel Créer un nouveau canal + Create new channel Créer une nouvelle couche Create new document from current state Créer un nouveau document à partir de l'état actuel - Create new fill or adjustment layer Créer un nouveau calque de remplissage ou d'ajustement + Create new fill or adjustment layer Créer un nouveau calque de remplissage ou de réglage Create new gradient Créer un nouveau dégradé Create new group Créer un nouveau groupe Create new layer comp Créer une nouvelle composition de calques @@ -972,7 +975,7 @@ Cyan Cyan Cyan · Red Cyan · Rouge Darken Obscurcir - Darks Tons sombres + Darks Teintes sombres Data Sets Jeux de données Default Par défaut Default colours (D) Couleurs par défaut (D) @@ -983,13 +986,13 @@ Dehaze Correction de la brume Delete Cropped Pixels Supprimer les pixels recadrés Delete Group Supprimer le groupe - Delete Layer Mask Supprimer le masque de calque + Delete Layer Mask Supprimer le masque de fusion Delete Layers Supprimer les calques Delete Shortcut Supprimer le raccourci Delete Timeline Supprimer le montage Delete Tool Preset Supprimer le préréglage d'outil Delete count group Supprimer le groupe de comptage - Delete current channel Supprimer le canal actif + Delete current channel Supprimer la couche active Delete current state Supprimer l'état actuel Delete effect layer Supprimer le calque d'effet @@ -1005,7 +1008,7 @@ Delete layer Supprimer le calque Delete layer comp Supprimer la composition de calques - Delete note Supprimer la note + Delete note Supprimer l'annotation Delete selected measurements Supprimer les mesures sélectionnées Delete style Supprimer le style Delete tool preset Supprimer le préréglage d'outil @@ -1042,7 +1045,7 @@ Duplicate Layers… Dupliquer les calques… Duplicate style Dupliquer le style Edit Toolbar… Modifier la barre d'outils… - Edit in Quick Mask Mode (Q) Modifier en mode masque rapide (Q) + Edit in Quick Mask Mode (Q) Modifier en mode Masque (Q) Edit in Standard Mode (Q) Modifier en mode standard (Q) Effects Effets Embed Color Profile Incorporer le profil colorimétrique @@ -1061,15 +1064,15 @@ Factor: Facteur : Faux Bold Faux gras Faux Italic Faux italique - Feather Adoucissement + Feather Contour progressif Fewer Points Moins de points File Settings Paramètres du fichier Fill Remplissage - Fill: Remplissage : - Filter for adjustment layers Filtrer les calques d'ajustement + Fill: Fond : + Filter for adjustment layers Filtrer les calques de réglage Filter for pixel layers Filtrer les calques de pixels Filter for shape layers Filtrer les calques de forme - Filter for smart objects Filtrer les objets intelligents + Filter for smart objects Filtrer les objets dynamiques Filter for type layers Filtrer les calques de texte Filter: Filtre : Finger Painting Peinture au doigt @@ -1105,7 +1108,7 @@ Gamma: Gamma : Global Global Glow Color Couleur de la lueur - Gradient Map Mappage de dégradé + Gradient Map Courbe de transfert de dégradé Grain Grain Gray Gris Gray Gamma 2.2 Gris gamma 2,2 @@ -1117,9 +1120,9 @@ Height: Hauteur : Hide Auto Slices Masquer les tranches automatiques Hide Properties Masquer les propriétés - Highlight Compression Compression des hautes lumières - Highlight Priority Priorité aux hautes lumières - Highlights Hautes lumières + Highlight Compression Compression des tons clairs + Highlight Priority Priorité aux tons clairs + Highlights Tons clairs Home Accueil Horizontal scale Échelle horizontale Hue Teinte @@ -1145,7 +1148,7 @@ Interpolation: Interpolation : Intersect with selection ({key}) Intersection avec la sélection ({key}) Invert All Tout inverser - Invert has no settings. Inverser n'a aucun réglage. + Invert has no settings. Inverser n'a aucun paramètre. Italic Italique Join us on Discord Rejoignez-nous sur Discord Justify all Justifier tout @@ -1171,15 +1174,15 @@ Levels Niveaux Lighten Éclaircir Lightness Luminosité - Lights Tons clairs + Lights Teintes claires Limits: Limites : Linear Gradient Dégradé linéaire Link layers Lier les calques Link width and height Lier la largeur et la hauteur - Liquify is not open Liquéfier n'est pas ouvert + Liquify is not open Fluidité n'est pas ouvert Load Charger Load 3D LUT… Charger une LUT 3D… - Load channel as selection Charger le canal comme sélection + Load channel as selection Récupérer la couche comme sélection Load… Charger… Localized Color Clusters Groupes de couleurs localisés Lock Verrouiller @@ -1229,7 +1232,7 @@ New effect layer Nouveau calque d'effet New selection Nouvelle sélection Next frame Image suivante - Next note Note suivante + Next note Annotation suivante No Color Aucune couleur No Color Management Aucune gestion des couleurs No Dither Sans tramage @@ -1255,9 +1258,9 @@ Open a document to use the timeline. Ouvrez un document pour utiliser le montage. Optical Optique Optimized Optimisé - Option-click to define a source point to clone from Option-clic pour définir le point source du clonage + Option-click to define a source point to clone from Option-clic pour définir le point source de la duplication Original Original - Output Channel: Canal de sortie : + Output Channel: Couche de sortie : Output Levels: Niveaux de sortie : Output: Sortie : Outside Extérieur @@ -1294,7 +1297,7 @@ Portrait Portrait Position Position Position and Size Position et taille - Posterize Postériser + Posterize Isohélie Preserve Details Préserver les détails Preserve Luminosity Préserver la luminosité Preset Préréglage @@ -1308,7 +1311,7 @@ Prevent auto-nesting in and out of Artboards and Frames Empêcher l'imbrication automatique dans et hors des plans de travail et des cadres Preview Aperçu Previous frame Image précédente - Previous note Note précédente + Previous note Annotation précédente Print Imprimer Printer Imprimante Printer Manages Colors L'imprimante gère les couleurs @@ -1321,7 +1324,7 @@ Protect Tones Protéger les tons Proximity Match Correspondance de proximité Pulled String Mode Mode corde tendue - Puppet Warp is not active La déformation par épingles n'est pas active + Puppet Warp is not active La déformation de la marionnette n'est pas active Purity Pureté Quality Qualité Quick Actions Actions rapides @@ -1393,7 +1396,7 @@ Saturation Jitter Variation de la saturation Save HTML and Images Enregistrer le HTML et les images Save as PDF Enregistrer en PDF - Save selection as channel Enregistrer la sélection comme canal + Save selection as channel Mémoriser la sélection sur une couche Save… Enregistrer… Scale to Fit Media Adapter au support Scatter Diffusion @@ -1409,17 +1412,17 @@ Select: Sélection : Selections Sélections Selective Sélective - Selective Color Couleur sélective + Selective Color Correction sélective Send the selected pin backward Reculer l'épingle sélectionnée Set additional smoothing options Définir des options de lissage supplémentaires Set background color Définir la couleur d'arrière-plan Set foreground color Définir la couleur de premier plan Set painting symmetry options Définir les options de symétrie de peinture Symmetry Off Symétrie désactivée - Set shape fill type Définir le type de remplissage de la forme + Set shape fill type Définir le type de fond de la forme Set shape stroke type Définir le type de contour de la forme Set the text color Définir la couleur du texte - Shadows Ombres + Shadows Tons foncés Shape: Forme : Sharpening Netteté Shift Maj @@ -1431,7 +1434,7 @@ Show Mesh Afficher le maillage Show Overlay Afficher la superposition Show Transform Controls Afficher les contrôles de transformation - Show or hide the Notes panel Afficher ou masquer le panneau Notes + Show or hide the Notes panel Afficher ou masquer le panneau Annotations Show: Afficher : Sides Côtés Sides: Côtés : @@ -1467,7 +1470,7 @@ Suggestions: Suggestions : Swap colours (X) Permuter les couleurs (X) Swaps height and width Permute la hauteur et la largeur - Switch between free transform and warp modes Basculer entre les modes transformation libre et déformation + Switch between free transform and warp modes Basculer entre les modes transformation manuelle et déformation Switch theme Changer de thème Temperature Température Tetrahedral Tétraédrique @@ -1544,7 +1547,7 @@ No saved presets yet. Aucun préréglage enregistré pour le moment. Error Erreur Command Commande - Adjustment layers have nothing to transform Les calques d'ajustement n'ont rien à transformer + Adjustment layers have nothing to transform Les calques de réglage n'ont rien à transformer Altitude Altitude Android 1080p Android 1080p Apple Watch 45mm Apple Watch 45 mm @@ -1552,10 +1555,10 @@ Art & Illustration Art et illustration ArtCraft ArtCraft Background Eraser Tool Outil Gomme d'arrière-plan - Bevel & Emboss Biseau et relief + Bevel & Emboss Biseautage et estampage Blend Mode Mode de fusion Blues Bleus - Blur Tool Outil Flou + Blur Tool Outil Goutte d'eau Bold Gras Brush Pinceau Block Carré @@ -1563,24 +1566,24 @@ Brush Tip Shape Forme de la pointe Brush Tool Outil Pinceau Build-up Accumulation - Burn Tool Outil Assombrir + Burn Tool Outil Densité + Centimeters Centimètres Choke Resserrement Classic Classique Click to add text Cliquez pour ajouter du texte Click to fill similar colours Cliquez pour remplir les couleurs similaires - Clone Stamp Tool Outil Tampon de clonage + Clone Stamp Tool Outil Tampon de duplication Color Burn Densité couleur + Color Dodge Densité couleur − Color Dynamics Dynamique de la couleur - Color Overlay Superposition de couleur + Color Overlay Incrustation couleur Comic Book Bande dessinée Conform Conforme - Content-Aware Fill needs a selection on a pixel layer Le remplissage selon le contenu nécessite une sélection sur un calque de pixels + Content-Aware Fill needs a selection on a pixel layer Le remplissage d'après le contenu nécessite une sélection sur un calque de pixels Could not transform: the layer is empty Transformation impossible : le calque est vide Could not use Perspective Warp: the layer is empty Impossible d'utiliser la déformation de la perspective : le calque est vide - Could not use Puppet Warp: no opaque pixels Impossible d'utiliser la déformation par épingles : aucun pixel opaque - Could not use Puppet Warp: the layer is empty Impossible d'utiliser la déformation par épingles : le calque est vide + Could not use Puppet Warp: no opaque pixels Impossible d'utiliser la déformation de la marionnette : aucun pixel opaque + Could not use Puppet Warp: the layer is empty Impossible d'utiliser la déformation de la marionnette : le calque est vide Crop Tool Outil Recadrage Crosswise En croix Current Tool Outil actif @@ -1595,7 +1598,7 @@ Document Dimensions Dimensions du document Document Profile Profil du document Document Sizes Tailles du document - Dodge Tool Outil Éclaircir + Dodge Tool Outil Densité − Down Bas Drag to draw a gradient Faites glisser pour tracer un dégradé Drag to move the active layer Faites glisser pour déplacer le calque actif @@ -1606,30 +1609,30 @@ Efficiency Efficacité Efficiency: 100% Efficacité : 100 % Ellipse Tool Outil Ellipse - Elliptical Marquee Tool Outil Sélection elliptique - Single Row Marquee Tool Outil Sélection de ligne unique - Single Column Marquee Tool Outil Sélection de colonne unique - Emboss Relief + Elliptical Marquee Tool Outil Ellipse de sélection + Single Row Marquee Tool Outil Rectangle de sélection 1 rangée + Single Column Marquee Tool Outil Rectangle de sélection 1 colonne + Emboss Estampage Eraser Gomme Eraser Tool Outil Gomme ExtraBold Extra-gras ExtraLight Extra-maigre Eyedropper Tool Outil Pipette Fade Estompage - Fill Opacity Opacité du remplissage + Fill Opacity Opacité du fond Film & Video Film et vidéo From De General Général GitHub GitHub - Gradient Overlay Superposition de dégradé + Gradient Overlay Incrustation en dégradé Gradient Tool Outil Dégradé Greens Verts Hand Tool Outil Main Hard Dur Hard Mix Mélange maximal - Healing Brush Tool Outil Pinceau correcteur - Highlights from: Hautes lumières à partir de : - History Brush Tool Outil Pinceau d'historique + Healing Brush Tool Outil Correcteur + Highlights from: Tons clairs à partir de : + History Brush Tool Outil Forme d'historique Horizontal Type Tool Outil Texte horizontal Horizontal Type Mask Tool Outil Masque de texte horizontal Vertical Type Mask Tool Outil Masque de texte vertical @@ -1637,9 +1640,9 @@ Inches Pouces Indexed Indexé Initial Direction Direction initiale - Inner Bevel Biseau intérieur - Inner Glow Lueur intérieure - Inner Shadow Ombre intérieure + Inner Bevel Biseau interne + Inner Glow Lueur interne + Inner Shadow Ombre interne Lab Lab Landscape, 10 x 8 Paysage, 10 x 8 Landscape, 6 x 4 Paysage, 6 x 4 @@ -1668,20 +1671,20 @@ Mobile Mobile Move Tool Outil Déplacement Could not use the move tool because the layer is locked. Impossible d'utiliser l'outil Déplacement, car le calque est verrouillé. - Could not complete the Free Transform command because the layer is locked. Impossible d'exécuter la commande Transformation libre, car le calque est verrouillé. + Could not complete the Free Transform command because the layer is locked. Impossible d'exécuter la commande Transformation manuelle, car le calque est verrouillé. Multiply Produit My Brushes Mes pinceaux Neutrals Neutres New Workspace Nouvel espace de travail - Note Tool Outil Note + Note Tool Outil Annotation Object Selection Tool Outil Sélection d'objet Off Désactivé Open Ouvrir Orange Orange Original Ratio Proportions d'origine - Out Of Gamut Hors gamut - Outer Bevel Biseau extérieur - Outer Glow Lueur extérieure + Out Of Gamut Couleurs non imprimables + Outer Bevel Biseau externe + Outer Glow Lueur externe Overlay Incrustation Paint Bucket Tool Outil Pot de peinture Paints from the document's opening state Peint à partir de l'état d'ouverture du document @@ -1691,7 +1694,7 @@ Delete Anchor Point Tool Outil Suppression de point d'ancrage Click a segment to add an anchor point · {alt}-click an anchor to delete it Cliquez sur un segment pour ajouter un point d'ancrage · {alt}-clic sur un point d'ancrage pour le supprimer Click an anchor point to delete it · {alt}-click a segment to add one Cliquez sur un point d'ancrage pour le supprimer · {alt}-clic sur un segment pour en ajouter un - Pattern Overlay Superposition de motif + Pattern Overlay Incrustation de motif Pen Pressure Pression du stylet Pen Tilt Inclinaison du stylet Pen Tool Outil Plume @@ -1699,7 +1702,7 @@ Photo Photo PhotoCraft website Site web de PhotoCraft Picas Picas - Pillow Emboss Relief en coussin + Pillow Emboss Estampage oreiller Pixel Replacement Remplacement de pixels Pixels Pixels Points Points @@ -1716,15 +1719,15 @@ Pro (Medium Gray) Pro (gris moyen) Protect Texture Protéger la texture Purple Violet - Quick Mask Masque rapide + Quick Mask Mode Masque Quick Selection Tool Outil Sélection rapide Radial Radial - Rasterize Layer Rastériser le calque - Rasterize Layers Rastériser les calques + Rasterize Layer Pixelliser le calque + Rasterize Layers Pixelliser les calques Ratio Proportions Recent Récent Rectangle Tool Outil Rectangle - Rectangular Marquee Tool Outil Sélection rectangulaire + Rectangular Marquee Tool Outil Rectangle de sélection Reds Rouges Reflected Réfléchi Sample: foreground colour Échantillon : couleur de premier plan @@ -1736,13 +1739,13 @@ Select Data Points Sélectionner les points de données Selection Sélection SemiBold Demi-gras - Shadows up to: Ombres jusqu'à : + Shadows up to: Tons foncés jusqu'à : Shape Dynamics Dynamique de la forme Sharpen Tool Outil Netteté Show Extras Options Options d'affichage des extras Slice Select Tool Outil Sélection de tranche Slice Tool Outil Tranche - Smart Filters Filtres intelligents + Smart Filters Filtres dynamiques Smudge Tool Outil Doigt Soft Doux Soften Adoucir @@ -1793,15 +1796,15 @@ @plural {n} Layer|{n} Layers {n} calque|{n} calques @plural {n} layer|{n} layers {n} calque|{n} calques @plural Group · {n} layer|Group · {n} layers Groupe · {n} calque|Groupe · {n} calques - Additional plugins folder Dossier d'extensions supplémentaire + Additional plugins folder Dossier de modules externes supplémentaire Adobe Adobe Adobe rgb Adobe RGB Allow agent control Autoriser le pilotage par des agents Allow scripts to connect Autoriser la connexion des scripts Always Toujours - Always create smart objects when placing Toujours créer des objets intelligents lors du placement + Always create smart objects when placing Toujours créer des objets dynamiques lors du placement Animated zoom Zoom animé - Apply auto tone Appliquer la tonalité automatique + Apply auto tone Appliquer le ton automatique Ask Demander Ask before saving layered tiff Demander avant d'enregistrer un TIFF avec calques Auto collapse icon panels Réduire automatiquement les panneaux en icônes @@ -1818,17 +1821,17 @@ Cache levels Niveaux de cache Cache tile size Taille des tuiles du cache Camera Raw Defaults Valeurs par défaut de Camera Raw - Canvas border Bordure du canevas - Show canvas dots Afficher les points du canevas - Canvas color Couleur du canevas - Canvas custom color Couleur personnalisée du canevas + Canvas border Bordure de la zone de travail + Show canvas dots Afficher les points de la zone de travail + Canvas color Couleur de la zone de travail + Canvas custom color Couleur personnalisée de la zone de travail Color picker Sélecteur de couleurs Color space Espace colorimétrique Column width Largeur de colonne Concise Concis Control port Port de contrôle Convert to srgb Convertir en sRVB - Cursors Curseurs + Cursors Pointeurs Custom dark Personnalisé sombre Custom light Personnalisé clair Dark Sombre @@ -1838,7 +1841,7 @@ Detailed Détaillé Disks Disques Display p3 Display P3 - Double click layer mask launches select and mask Un double-clic sur le masque de calque ouvre Sélectionner et masquer + Double click layer mask launches select and mask Un double-clic sur le masque de fusion ouvre Sélectionner et masquer Drop shadow Ombre portée Dynamic color sliders Curseurs de couleur dynamiques East asian Asie orientale @@ -1855,8 +1858,8 @@ Fill new type layers with placeholder Remplir les nouveaux calques de texte avec un texte de substitution Font preview Aperçu des polices Full size tip Pointe en taille réelle - Gamut warning color Couleur de l'alerte de gamut - Gamut warning opacity Opacité de l'alerte de gamut + Gamut warning color Couleur des alertes de couleurs non imprimables + Gamut warning opacity Opacité des alertes de couleurs non imprimables Generator enabled Générateur activé Gif GIF Glossy Brillant @@ -1896,7 +1899,7 @@ Nearest neighbor Au plus proche Never Jamais Normal tip Pointe normale - Open as smart object Ouvrir en tant qu'objet intelligent + Open as smart object Ouvrir en tant qu'objet dynamique Open in camera raw Ouvrir dans Camera Raw Open documents as tabs Ouvrir les documents dans des onglets Overscroll Défilement au-delà des bords @@ -1928,7 +1931,7 @@ Scrubby slider acceleration Accélération des curseurs glissants Sessions only Sessions uniquement Sharpen for Netteté pour - Show channels in color Afficher les canaux en couleur + Show channels in color Afficher les couches en couleur Show crosshair in brush tip Afficher le réticule dans la pointe Show extension panels Afficher les panneaux d'extension Show font names in english Afficher les noms de police en anglais @@ -1940,7 +1943,7 @@ Show highlight on rollover Afficher la mise en surbrillance au survol Show transformation values Afficher les valeurs de transformation Slice color Couleur des tranches - Smart guide color Couleur des repères intelligents + Smart guide color Couleur des repères commentés Smart quotes Guillemets typographiques Snap vector tools and transforms to pixel grid Aimanter les outils vectoriels et les transformations à la grille de pixels Srgb sRVB @@ -1955,15 +1958,15 @@ Text file Fichier texte Tiny Minuscule Touch gestures Gestes tactiles - Transparency & Gamut Transparence et gamut + Transparency & Gamut Transparence et couleurs non imprimables Type units Unités de texte UI font size Taille de police de l'interface UI scale Échelle de l'interface Units & Rulers Unités et règles - Use additional plugins folder Utiliser le dossier d'extensions supplémentaire + Use additional plugins folder Utiliser le dossier de modules externes supplémentaire Use esc to commit Valider avec Échap Use gpu Utiliser le GPU - Use legacy free transform Utiliser l'ancienne transformation libre + Use legacy free transform Utiliser l'ancienne transformation manuelle Use shift key for tool switch Utiliser Maj pour changer d'outil Vary round brush hardness on hud Varier la dureté du pinceau rond dans l'affichage tête haute Webp WebP @@ -1975,9 +1978,9 @@ Pass Through Transfert Dissolve Fondu Darker Color Couleur plus foncée - Linear Dodge (Add) Densité linéaire − (Addition) + Linear Dodge (Add) Densité linéaire − (Ajout) Lighter Color Couleur plus claire - Soft Light Lumière douce + Soft Light Lumière tamisée Hard Light Lumière crue Vivid Light Lumière vive Linear Light Lumière linéaire @@ -1987,7 +1990,7 @@ Luminosity Luminosité @plural {n} sample|{n} samples {n} échantillon|{n} échantillons {added}, {removed} subtracted {added}, {removed} soustraits - Adjustment Preview Aperçu de l'ajustement + Adjustment Preview Aperçu du réglage Airbrush: keeps depositing paint while the pointer rests. Aérographe : continue de déposer de la peinture tant que le pointeur reste immobile. Angle: Angle : Blend If Fusion si @@ -2028,18 +2031,18 @@ Click to sample the foreground colour · {key}-click for background Cliquez pour prélever la couleur de premier plan · {key}-clic pour l'arrière-plan {key}-click to set the source {key}-clic pour définir la source {key} to subtract {key} pour soustraire - Clone source {n} ({key}-click with Clone Stamp to set) Source de clonage {n} ({key}-clic avec le tampon de clonage pour la définir) - Source: not set ({key}-click with Clone Stamp) Source : non définie ({key}-clic avec le tampon de clonage) + Clone source {n} ({key}-click with Clone Stamp to set) Source de duplication {n} ({key}-clic avec le tampon de duplication pour la définir) + Source: not set ({key}-click with Clone Stamp) Source : non définie ({key}-clic avec le tampon de duplication) Pixel Layer Calque de pixels Type Layer Calque de texte Shape Layer Calque de forme Group Layer Calque de groupe Fill Layer Calque de remplissage - Smart Object Layer Calque d'objet intelligent + Smart Object Layer Calque d'objet dynamique Create Warped Text Créer un texte déformé - Rasterize Smart Object Rastériser l'objet intelligent - Rasterize Fill Content Rastériser le contenu du remplissage - Rasterize Layer Style Rastériser le style de calque + Rasterize Smart Object Pixelliser l'objet dynamique + Rasterize Fill Content Pixelliser le contenu du remplissage + Rasterize Layer Style Pixelliser le style de calque Unlock: picking a preset replaces these settings Déverrouiller : choisir un préréglage remplace ces paramètres Lock: keep these settings when picking another preset Verrouiller : conserver ces paramètres en choisissant un autre préréglage Diameter: Diamètre : @@ -2065,7 +2068,7 @@ Mix Mélange Wetness Jitter Variation de l'humidité Mix Jitter Variation du mélange - Show the layer mask as an overlay Afficher le masque de calque en superposition + Show the layer mask as an overlay Afficher le masque de fusion en superposition Toggle visibility Afficher/masquer Unlink the mask from the layer Délier le masque du calque Link the mask to the layer Lier le masque au calque @@ -2077,7 +2080,7 @@ # Gradient Editor window, opened from the options bar's gradient swatch. Gradient Editor Éditeur de dégradé Gradient picker Sélecteur de dégradé - Save As Preset Enregistrer comme préreglage + Save As Preset Enregistrer comme préréglage The gradient the Gradient tool paints with Le dégradé que l'outil Dégradé applique Adds the current gradient to the Gradients panel Ajoute le dégradé actuel au panneau Dégradés Gradient stops Arrêts de dégradé @@ -2089,7 +2092,7 @@ Vertical type Texte vertical Fill path with foreground color Remplir le tracé avec la couleur de premier plan Stroke path with brush Contourner le tracé avec le pinceau - Load path as a selection Charger le tracé comme sélection + Load path as a selection Récupérer le tracé comme sélection Make work path from selection Créer un tracé de travail à partir de la sélection Add vector mask Ajouter un masque vectoriel Create new path Créer un nouveau tracé @@ -2104,7 +2107,7 @@ OpenGL OpenGL Use tablet pressure Utiliser la pression de la tablette Vulkan Vulkan - Alt-click to define a source point to clone from Alt-clic pour définir le point source du clonage + Alt-click to define a source point to clone from Alt-clic pour définir le point source de la duplication No path to make a selection from: draw one with the Pen tool or select one in the Paths panel. Aucun tracé à partir duquel créer une sélection : dessinez-en un avec l'outil Plume ou sélectionnez-en un dans le panneau Tracés. Cancel opening Annuler l'ouverture Cancelled opening {name} Ouverture de {name} annulée @@ -2124,8 +2127,8 @@ Working… Traitement… @id select.all Tout sélectionner @id edit.purge.undo Historique d'annulation - Delete Smart Filters Supprimer les filtres intelligents - Enable Smart Filters Activer les filtres intelligents + Delete Smart Filters Supprimer les filtres dynamiques + Enable Smart Filters Activer les filtres dynamiques New Layer… Nouveau calque… Delete Layer… Supprimer le calque… Color Range Plage de couleurs @@ -2145,7 +2148,7 @@ Stamp Tampon Torn Edges Bords déchirés Water Paper Papier mouillé - Canvas rotation Rotation du canevas + Canvas rotation Rotation de la zone de travail Extensions Extensions Libraries Bibliothèques 3D 3D @@ -2153,27 +2156,27 @@ Move Déplacement Wand Baguette magique Pencil Crayon - Clone Stamp Tampon de clonage + Clone Stamp Tampon de duplication Healing Correcteur - Dodge Éclaircir - Burn Assombrir + Dodge Densité − + Burn Densité + Pen Plume Hand Main Sampler Échantillonneur de couleur - Note tool Outil Note + Note tool Outil Annotation Quick Selection Sélection rapide Magnetic Lasso Lasso magnétique - History Brush Pinceau d'historique - Art History Brush Pinceau d'historique artistique + History Brush Forme d'historique + Art History Brush Forme d'historique artistique Pattern Stamp Tampon de motif Spot Healing Correcteur localisé - Red Eye Yeux rouges + Red Eye Œil rouge Patch Pièce - Content-Aware Move Déplacement selon le contenu + Content-Aware Move Déplacement basé sur le contenu Rotate View Rotation de la vue - Perspective Crop Recadrage en perspective + Perspective Crop Correction de perspective par recadrage Color Replacement Remplacement de couleur - Dodge/Burn Éclaircir/Assombrir + Dodge/Burn Densité −/Densité + Dynamics Dynamique Tilt Inclinaison Reset Réinitialiser @@ -2189,7 +2192,7 @@ Advanced Blending Fusion avancée RGB RVB Alpha Alpha - Spot Channel Canal de ton direct + Spot Channel Couche de ton direct Working Gray Plate Plaque grise de travail HSL/Grayscale TSL/Niveaux de gris Tone Curve Courbe des tonalités @@ -2226,7 +2229,7 @@ Localize Color Clusters Localiser les groupes de couleurs Duplicated selection name Nom de la sélection dupliquée Operation Opération - New channel only Nouveau canal uniquement + New channel only Nouvelle couche uniquement Masked Areas Zones masquées Selected Areas Zones sélectionnées Spot Color Ton direct @@ -2352,7 +2355,7 @@ Document Size Taille du document Timing Minutage Status Information Informations d'état - Saved Multichannel Multicanal enregistré + Saved Multichannel Multicouche enregistré Mouse Coordinates Coordonnées de la souris Ruler Units Unités des règles Hide Masquer @@ -2475,10 +2478,10 @@ Units Unités Columns Colonnes Create new count group Créer un nouveau groupe de comptage - Adjustment Layer Calque d'ajustement + Adjustment Layer Calque de réglage Pencil Tool Outil Crayon Pixel Pixel - Adjustment Ajustement + Adjustment Réglage Drag (lasso) or click points (polygonal) · {add} add · {sub} subtract Faites glisser (lasso) ou cliquez des points (polygonal) · {add} ajouter · {sub} soustraire Drag a crop box · drag inside to move · edges resize ({ratio} ratio, {centre} centre) · Space moves while drawing · arrows nudge · X swaps orientation · {commit} commits · Esc cancels Tracez un cadre de recadrage · faites glisser à l'intérieur pour le déplacer · les bords le redimensionnent ({ratio} proportions, {centre} centre) · Espace le déplace pendant le tracé · flèches le décalent · X permute l'orientation · {commit} valide · Échap annule {mode} Color · {bits} bit Couleurs {mode} · {bits} bits @@ -2522,12 +2525,12 @@ Brush Size Taille du pinceau Heal Color Couleur de correction Mask Masque - Channel Canal + Channel Couche Rectangle Rectangle Result Résultat - New Channel Nouveau canal - Save Selection Enregistrer la sélection - Load Selection Charger la sélection + New Channel Nouvelle couche + Save Selection Mémoriser la sélection + Load Selection Récupérer la sélection Duplicate Dupliquer Fast Rapide Accurate Précis @@ -2549,7 +2552,7 @@ Click to zoom in · ⌥-click to zoom out Cliquez pour zoomer · ⌥-clic pour dézoomer Click: corner · Drag: smooth · Click first point: close · ↩ finish · Esc cancel Clic : angle · Glisser : lisse · Clic sur le premier point : fermer · ↩ : terminer · Échap : annuler Commit Perspective Warp (↩) Valider la déformation de la perspective (↩) - Commit Puppet Warp (↩) Valider la déformation par épingles (↩) + Commit Puppet Warp (↩) Valider la déformation de la marionnette (↩) Commit any current edits (⌘↩) Valider les modifications en cours (⌘↩) Commit current crop operation (↵) Valider le recadrage en cours (↵) Commit transform (↩) Valider la transformation (↩) @@ -2577,7 +2580,7 @@ # Copy transforms, layer navigation and cancellable opening (from PR #582 by neko233-com). New from Clipboard Créer depuis le presse-papiers - Free Transform a Copy Transformer librement une copie + Free Transform a Copy Transformation manuelle d'une copie Transform Again on a Copy Répéter la transformation sur une copie Select Layer Above Sélectionner le calque au-dessus Select Layer Below Sélectionner le calque au-dessous @@ -2632,8 +2635,8 @@ cameraRaw Light Lumière Camera Raw Filter ({layer}) Filtre Camera Raw ({layer}) Camera Raw ({name}) Camera Raw ({name}) -cameraRaw Lights Tons clairs -cameraRaw Darks Tons sombres +cameraRaw Lights Teintes claires +cameraRaw Darks Teintes sombres cameraRaw Blacks Noirs cameraRaw Amount Quantité cameraRaw Tint Teinte @@ -2669,7 +2672,7 @@ cameraRaw Blending Fusion Drag or click polygon points · hold Alt during lasso for straight segments · {add} add · {sub} before drawing subtracts Faites glisser ou cliquez des points de polygone · maintenez Alt pendant le lasso pour des segments droits · {add} ajouter · {sub} avant de dessiner soustrait Extend Extension Structure: Structure : - Content-Aware Move Tool Outil Déplacement selon le contenu + Content-Aware Move Tool Outil Déplacement basé sur le contenu TIFF Options Options TIFF Targa Options Options Targa 24 bits/pixel 24 bits/pixel @@ -2736,8 +2739,8 @@ cameraRaw Blending Fusion # Shared screen eyedropper and text Color Picker (#1128). Color Picker (Text Color) Sélecteur de couleurs (Couleur du texte) Color Picker ({name}) Sélecteur de couleurs ({name}) - Color Picker (Fill Color) Sélecteur de couleurs (Couleur de remplissage) - Fill color Couleur de remplissage + Color Picker (Fill Color) Sélecteur de couleurs (Couleur de fond) + Fill color Couleur de fond Color Picker (Stroke Color) Sélecteur de couleurs (Couleur du contour) Stroke color Couleur du contour Pick screen color Choisir une couleur à l’écran @@ -2778,7 +2781,7 @@ cameraRaw Blending Fusion Rounded Steps Paliers arrondis Sawtooth 1 Dents de scie 1 - Red Eye Tool Outil Yeux rouges + Red Eye Tool Outil Œil rouge Pupil Size Taille de la pupille Darken Amount Quantité d'assombrissement Click a red pupil to neutralize it Cliquez sur une pupille rouge pour la corriger @@ -2836,8 +2839,8 @@ fontWeight Italic Italique 51 by 51 Average Moyenne 51 × 51 101 by 101 Average Moyenne 101 × 101 Sample Size: Taille de l'échantillon : - All Layers No Adjustments Tous les calques sans ajustements - Current & Below No Adjustments Actif et inférieurs sans ajustements + All Layers No Adjustments Tous les calques sans réglages + Current & Below No Adjustments Actif et inférieurs sans réglages Show Sampling Ring Afficher l'anneau d'échantillonnage # Color Picker warnings @@ -2933,7 +2936,7 @@ fontWeight Italic Italique Follows only edges this close to the pointer ([ and ] change it) Ne suit que les contours aussi proches du pointeur ([ et ] le modifient) Higher values follow only edges that contrast sharply with their surroundings Plus la valeur est élevée, plus seuls les contours très contrastés sont suivis How often fastening points are placed by themselves Fréquence de pose automatique des points d’ancrage - Appearance Settings… Réglages d’apparence… + Appearance Settings… Paramètres d’apparence… Add to favorites Ajouter aux favoris Remove from favorites Retirer des favoris # File Info ▸ Camera Data (read-only EXIF fields; "Make" is the camera maker, not the verb). @@ -2968,7 +2971,7 @@ cameraData Lens Objectif Create a new rectangular frame Créer un cadre rectangulaire Create a new elliptical frame Créer un cadre elliptique Include in playback Inclure dans la lecture - Perspective Crop Tool Outil Recadrage perspective + Perspective Crop Tool Outil Correction de perspective par recadrage Show Grid Afficher la grille Drag a box or click four corners · drag the corners onto the edges of a skewed shape · {commit} commits · Esc cancels Tracez un cadre ou cliquez sur quatre coins · faites glisser les coins sur les bords d'une forme inclinée · {commit} valide · Échap annule Artboard Tool Outil Plan de travail @@ -2988,14 +2991,14 @@ cameraData Lens Objectif There is no crop size or ratio to save Aucune taille ni proportion de recadrage à enregistrer Scale: Échelle : Snap to Origin Aligner sur l'origine - Search settings… Rechercher des réglages… - No matching settings Aucun réglage correspondant + Search settings… Rechercher des paramètres… + No matching settings Aucun paramètre correspondant Loading fonts… Chargement des polices… Tool Outil Simulate Pressure Simuler la pression Stroke Path… Contourner le tracé… Background Eraser Gomme d'arrière-plan - Healing Brush Pinceau correcteur + Healing Brush Correcteur Spot Healing Brush Correcteur localisé Smudge Doigt Sponge Éponge @@ -3030,14 +3033,14 @@ curves Smooth Lisser Show rulers Afficher les règles # Color Sampler (added with the i18n coverage repair) - Click to place · Alt-click or drag off-canvas to remove Cliquez pour placer · Alt-clic ou glisser hors du canevas pour supprimer + Click to place · Alt-click or drag off-canvas to remove Cliquez pour placer · Alt-clic ou glisser hors de la zone de travail pour supprimer No document open Aucun document ouvert {n} of {max} sample points {n} sur {max} points d'échantillonnage # Print dialog (#2697) Emulsion Down Émulsion verso Functions Fonctions Print Resolution: Résolution d’impression : - Use Previous Layer to Create Clipping Mask Utiliser le calque précédent pour créer un masque de découpe + Use Previous Layer to Create Clipping Mask Utiliser le calque précédent pour créer un masque d'écrêtage Fill with {mode}-neutral color (white) Remplir avec la couleur neutre de {mode} (blanc) Fill with {mode}-neutral color (black) Remplir avec la couleur neutre de {mode} (noir) Fill with {mode}-neutral color (50% gray) Remplir avec la couleur neutre de {mode} (gris 50 %) @@ -3064,8 +3067,8 @@ curves Smooth Lisser Add Noise Ajout de bruit Added swatch {name} Nuance {name} ajoutée Adjust Interval For Loops Ajuster l'intervalle pour les boucles - Adjustment Layers Calques d'ajustement - Adjustment Properties Propriétés de l'ajustement + Adjustment Layers Calques de réglage + Adjustment Properties Propriétés du réglage Adobe-rgb-compat Adobe RGB (compatible) Adwaita (Dark) Adwaita (sombre) Adwaita (Light) Adwaita (clair) @@ -3096,7 +3099,7 @@ curves Smooth Lisser Artboards Plans de travail Artboards to Files Plans de travail en fichiers Artboards to PDF Plans de travail en PDF - Artistic Artistique + Artistic Artistiques Ask On Mismatch Demander en cas de non-concordance Ask On Missing Demander en cas d'absence Ask On Paste Demander au collage @@ -3122,7 +3125,7 @@ curves Smooth Lisser Blade Curvature Courbure des lames Blast Rafale Bleach Bypass Sans blanchiment - Blend Clipped Layers as Group Fusionner les calques découpés en tant que groupe + Blend Clipped Layers as Group Fusionner les calques écrêtés en tant que groupe Blend Interior Effects as Group Fusionner les effets internes en tant que groupe Blend Text Gamma Gamma de fusion du texte Blending Mode Mode de fusion @@ -3146,12 +3149,12 @@ curves Smooth Lisser Brush Strokes Traits de pinceau Brush Type Type de pinceau Burlap Toile de jute - Calculations Calculs + Calculations Opérations Camera Raw Camera Raw Camera Raw Filter Filtre Camera Raw Cancel transform Annuler la transformation Candle Light Bougie - Canvas Size Taille du canevas + Canvas Size Taille de la zone de travail Cell Size Taille des cellules Center X Centre X Center Y Centre Y @@ -3159,10 +3162,10 @@ curves Smooth Lisser Changes Saved Modifications enregistrées Changes have not been saved. Les modifications n'ont pas été enregistrées. Change… Modifier… - Channel1 Canal 1 - Channel2 Canal 2 - Channel3 Canal 3 - Channel4 Canal 4 + Channel1 Couche 1 + Channel2 Couche 2 + Channel3 Couche 3 + Channel4 Couche 4 Charcoal Area Zone de fusain Charcoal Thickness Épaisseur du fusain Check for Updates Rechercher des mises à jour @@ -3172,22 +3175,22 @@ curves Smooth Lisser Cleanliness Propreté Clear Amount Zone sans lueur Clear Selection Effacer la sélection - Clipping Mask Masque de découpe + Clipping Mask Masque d'écrêtage Clipping Path… Tracé de détourage… Clone Cloner - Clone source set at {x}, {y} Source de clonage définie en {x}, {y} + Clone source set at {x}, {y} Source de duplication définie en {x}, {y} Close Window Fermer la fenêtre Clumped Aggloméré Coarse Dots Points grossiers Coated-cmyk CMJN papier couché Color Decontaminate Décontaminer les couleurs - Color Halftone Trame de demi-teintes couleur + Color Halftone Demi-teintes couleur Color Indicates Masked Areas La couleur indique les zones masquées Color Indicates Selected Areas La couleur indique les zones sélectionnées Color Lookup Tables Tables de correspondance des couleurs Color Picker Sélecteur de couleurs Color Picker (Background Color) Sélecteur de couleurs (Couleur d'arrière-plan) - Color Picker (Custom Canvas Color) Sélecteur de couleurs (Couleur personnalisée du canevas) + Color Picker (Custom Canvas Color) Sélecteur de couleurs (Couleur personnalisée de la zone de travail) Color Picker (Foreground Color) Sélecteur de couleurs (Couleur de premier plan) Color Settings Paramètres de couleur Color Swatches Nuancier @@ -3205,7 +3208,7 @@ curves Smooth Lisser Contract Contracter Contrasty Contrasté Convert Convertir - Convert to Alpha Channel Convertir en canal alpha + Convert to Alpha Channel Convertir en couche alpha Convert to Linked Convertir en objet lié Convert to Profile Convertir en profil Cool Froid @@ -3213,7 +3216,7 @@ curves Smooth Lisser Cooling80 Refroidissement 80 Cooling82 Refroidissement 82 Copy Complete Stroke Copier le contour complet - Copy Fill Copier le remplissage + Copy Fill Copier le fond Correct C A Corriger l'aberration chromatique Correct Distortion Corriger la distorsion Correct Vignette Corriger le vignettage @@ -3232,10 +3235,10 @@ curves Smooth Lisser Crosshatch Hachures croisées Crystallize Cristallisation Currency Devise - Curve Darks Courbe : tons sombres - Curve Highlights Courbe : hautes lumières - Curve Lights Courbe : tons clairs - Curve Shadows Courbe : ombres + Curve Darks Courbe : teintes sombres + Curve Highlights Courbe : tons clairs + Curve Lights Courbe : teintes claires + Curve Shadows Courbe : tons foncés Curve Splits Courbe : points de séparation Custom Branch Color Couleur de branche personnalisée Custom Pixel Aspect Ratio Format des pixels personnalisé @@ -3254,7 +3257,7 @@ curves Smooth Lisser Date Date Date and Time Date et heure Day For Night Nuit américaine - De-Interlace Désentrelacer + De-Interlace Désentrelacement Deep Profond Deep Blue Bleu profond Deep Emerald Émeraude profond @@ -3270,13 +3273,13 @@ curves Smooth Lisser Definition Définition Defringe Supprimer les franges Degrees Degrés - Delete Channel Supprimer le canal + Delete Channel Supprimer la couche Delete Mask Supprimer le masque Delete… Supprimer… Depth Map Carte de profondeur Depth Mode Mode de profondeur Diameter Diamètre - Diffuse Diffuser + Diffuse Diffusion Diffuse Glow Lueur diffuse Direction Balance Équilibre des directions Disable Désactiver @@ -3284,7 +3287,7 @@ curves Smooth Lisser Disable Symmetry Path Désactiver le tracé de symétrie Disabled Désactivé Discard Changes Ignorer les modifications - Displace Déplacement + Displace Dispersion Display Affichage Display profile not used Profil d'affichage non utilisé Display profiles: {reason} Profils d'affichage : {reason} @@ -3301,11 +3304,11 @@ curves Smooth Lisser Drag: draw freehand · Release on the start point: close Glisser : tracer à main levée · Relâcher sur le point de départ : fermer Driver: {driver} Pilote : {driver} Dry Brush Pinceau sec - Duplicate Channel Dupliquer le canal + Duplicate Channel Dupliquer la couche Duplicate Layer Dupliquer le calque Duplicate Layers Dupliquer les calques Duplication Duplication - Dust & Scratches Poussière et rayures + Dust & Scratches Antipoussière Edge Brightness Luminosité du contour Edge Extension Extension du contour Edge Fidelity Fidélité des contours @@ -3324,7 +3327,7 @@ curves Smooth Lisser End Y Fin Y Enlarged Agrandi Even Fields Trames paires - Exit Quick Mask Quitter le mode masque rapide + Exit Quick Mask Quitter le mode Masque Export As Exporter sous Export Contents Exporter le contenu Export Image Exporter l'image @@ -3338,23 +3341,23 @@ curves Smooth Lisser Exported {path} {path} exporté Exported {path} ({size}) {path} exporté ({size}) Exporting… Exportation… - Extrude Extruder + Extrude Extrusion Faded Film Film délavé Failed Échec Failed to open file Échec de l'ouverture du fichier Failed to save file Échec de l'enregistrement du fichier Fallback: {reason} Repli : {reason} - Feather Radius Rayon d'adoucissement + Feather Radius Rayon du contour progressif Fiber Length Longueur des fibres Fibers Fibres - Field Blur Flou de champ + Field Blur Flou de profondeur de champ File Format Format de fichier File Info Informations sur le fichier File Name Nom du fichier File Size Taille du fichier File not found Fichier introuvable Filename Nom de fichier - Fill Color Couleur de remplissage + Fill Color Couleur de fond Fill Path… Remplir le tracé… Film Grain Grain de film Filter Gallery Galerie de filtres @@ -3381,7 +3384,7 @@ curves Smooth Lisser Foreground Level Niveau de premier plan Forward Warp Déformation vers l'avant Frame Cadre - Free Transform Path Transformation libre du tracé + Free Transform Path Transformation manuelle du tracé Freeze Lasso Lasso de figeage Freeze Mask Masque de figeage Fresco Fresque @@ -3411,9 +3414,9 @@ curves Smooth Lisser Grade Balance Balance de l'étalonnage Grade Blending Fusion de l'étalonnage Grade Global Étalonnage global - Grade Highlights Étalonnage des hautes lumières + Grade Highlights Étalonnage des tons clairs Grade Midtones Étalonnage des tons moyens - Grade Shadows Étalonnage des ombres + Grade Shadows Étalonnage des tons foncés Grain Amount Quantité de grain Grain Roughness Rugosité du grain Grain Size Taille du grain @@ -3433,7 +3436,7 @@ curves Smooth Lisser Groups Groupes Grout Width Largeur du joint Guide Layout… Disposition des repères… - HDR Toning Tonalité HDR + HDR Toning Virage HDR Hearts Cœurs Heptagon Heptagone Hexagon Hexagone @@ -3442,12 +3445,12 @@ curves Smooth Lisser Hide Others Masquer les autres Hide {app} Masquer {app} High Pass Passe-haut - Highlight Hautes lumières - Highlight Amount Quantité des hautes lumières - Highlight Area Zone des hautes lumières - Highlight Radius Rayon des hautes lumières - Highlight Strength Intensité des hautes lumières - Highlight Tone Amplitude tonale des hautes lumières + Highlight Tons clairs + Highlight Amount Quantité des tons clairs + Highlight Area Zone des tons clairs + Highlight Radius Rayon des tons clairs + Highlight Strength Intensité des tons clairs + Highlight Tone Amplitude tonale des tons clairs Horizontal Only Horizontal seulement Hotspot Point chaud Hsb TSL @@ -3473,7 +3476,7 @@ curves Smooth Lisser Input Entrée Input Levels Niveaux d'entrée Input Mode Mode d'entrée - Install Plug-in Installer une extension + Install Plug-in Installer un module externe Integrated Density Densité intégrée Intensity Intensité Intent Intention de rendu @@ -3481,8 +3484,8 @@ curves Smooth Lisser Interval Intervalle Invalid panel layout; restored the default dock Disposition des panneaux non valide ; l'ancrage par défaut a été rétabli Invert Mask Inverser le masque - Invert Selection Inverser la sélection - Iris Blur Flou d'iris + Invert Selection Intervertir la sélection + Iris Blur Flou de diaphragme Ivy Lierre Jag Dentelé Join the ArtCraft Discord Rejoindre le Discord d'ArtCraft @@ -3497,7 +3500,7 @@ curves Smooth Lisser Layer Comps to Files Compositions de calques en fichiers Layer Content Options Options de contenu du calque Layer From Background Calque d'après l'arrière-plan - Layer Mask Hides Effects Le masque de calque masque les effets + Layer Mask Hides Effects Le masque de fusion masque les effets Layer Properties Propriétés du calque Layer Styles Styles de calque Layer from Background Calque d'après l'arrière-plan @@ -3510,9 +3513,9 @@ curves Smooth Lisser Left Diagonal Diagonale gauche Legacy Ancienne version Length Longueur - Lens Blur Flou d'objectif + Lens Blur Flou de l'objectif Lens Correction Correction de l'objectif - Lens Flare Reflet d'objectif + Lens Flare Halo Less Moins Level Based Selon les niveaux Library Bibliothèque @@ -3527,11 +3530,11 @@ curves Smooth Lisser Lighten Grout Éclaircir le joint Lighten Only Éclaircir seulement Lighting Éclairage - Lighting Effects Effets d'éclairage + Lighting Effects Éclairage Linear Dodge Densité linéaire − Linear-srgb sRVB linéaire Link Lier - Liquify Liquéfier + Liquify Fluidité List Liste List view Affichage en liste Live Preview Aperçu en direct @@ -3559,7 +3562,7 @@ curves Smooth Lisser Mask Incomplete Blocks Masquer les blocs incomplets Mask None Ne rien masquer Masks Masques - Match Color Faire correspondre la couleur + Match Color Correspondance de la couleur Math Symbols Symboles mathématiques Max Offset Décalage max. Max Radius Rayon max. @@ -3567,11 +3570,11 @@ curves Smooth Lisser Medium Gray Gris moyen Medium Lines Lignes moyennes Medium Strokes Traits moyens - Merge Channels… Fusionner les canaux… - Merge Spot Channel Fusionner le canal de ton direct + Merge Channels… Fusionner les couches… + Merge Spot Channel Fusionner la couche de ton direct Merge to HDR Pro Fusionner en HDR Pro Metallic Métallique - Mezzotint Manière noire + Mezzotint Mezzo-tinto Middle Milieu Midtone Contraste des tons moyens Monitor Profile Profil du moniteur @@ -3585,7 +3588,7 @@ curves Smooth Lisser More Plus Mosaic Mosaïque Mosaic Tiles Carreaux de mosaïque - Motion Blur Flou de mouvement + Motion Blur Flou directionnel Mouse Souris Move Layer Déplacer le calque Movie Prime Focale fixe cinéma @@ -3597,10 +3600,10 @@ curves Smooth Lisser Neon Glow Lueur néon Neutralize Neutraliser New Brush Preset Nouveau préréglage de pinceau - New Channel… Nouveau canal… + New Channel… Nouvelle couche… New Guide Nouveau repère New Guide Layout Nouvelle disposition de repères - New Spot Channel… Nouveau canal de ton direct… + New Spot Channel… Nouvelle couche de ton direct… New Video Layer from File Nouveau calque vidéo à partir d'un fichier New Window Nouvelle fenêtre New document Nouveau document @@ -3656,7 +3659,7 @@ curves Smooth Lisser Paper Brightness Luminosité du papier Parallel Parallèle Paste Complete Stroke Coller le contour complet - Paste Fill Coller le remplissage + Paste Fill Coller le fond Patchwork Patchwork Path Blur Flou de tracé Paths to Illustrator Tracés vers Illustrator @@ -3669,10 +3672,10 @@ curves Smooth Lisser PhotoCraft keeps retrying; until a save succeeds, preference changes are lost when it closes. PhotoCraft continue de réessayer ; tant qu'un enregistrement n'a pas abouti, les modifications des préférences seront perdues à la fermeture. Photomerge Photomerge Picture Frame Cadre d'image - Pinch Pincement + Pinch Contraction Pins Épingles - Place Embedded Placer incorporé - Place Linked Placer lié + Place Embedded Importer et incorporer + Place Linked Importer et lier Place Scale Marker Placer un repère d'échelle Plane Plan Planes Plans @@ -3739,14 +3742,14 @@ curves Smooth Lisser Remove All Tout supprimer Remove Jpeg Artifact Supprimer l'artefact JPEG Rename Brush Renommer le pinceau - Rename Channel… Renommer le canal… + Rename Channel… Renommer la couche… Rename Group Renommer le groupe Rename Tool Preset Renommer le préréglage d'outil Render Video Rendu vidéo Rendering… Rendu en cours… Reorder Layer Réorganiser le calque Replace Remplacer - Replace Color Remplacer la couleur + Replace Color Remplacement de couleur Replace Contents Remplacer le contenu Replace Footage Remplacer le métrage Report a Bug Signaler un bogue @@ -3795,7 +3798,7 @@ curves Smooth Lisser Search menus Rechercher dans les menus Seed Valeur initiale Select Custom Color… Choisir une couleur personnalisée… - Select Inverse Inverser la sélection + Select Inverse Intervertir la sélection Select Layer Sélectionner le calque Select and Mask Sélectionner et masquer Selected at launch: {backend} Sélectionné au lancement : {backend} @@ -3806,12 +3809,12 @@ curves Smooth Lisser Set Colors Définir les couleurs Set Measurement Scale Définir l'échelle de mesure Sgray sGray - Shadow Ombres - Shadow Amount Quantité des ombres - Shadow Intensity Intensité des ombres - Shadow Radius Rayon des ombres - Shadow Tone Amplitude tonale des ombres - Shadows/Highlights Ombres/Hautes lumières + Shadow Tons foncés + Shadow Amount Quantité des tons foncés + Shadow Intensity Intensité des tons foncés + Shadow Radius Rayon des tons foncés + Shadow Tone Amplitude tonale des tons foncés + Shadows/Highlights Tons foncés/Tons clairs Shallow Superficiel Shape Blur Flou selon une forme Sharpen Amount Quantité de netteté @@ -3832,8 +3835,8 @@ curves Smooth Lisser Sketch Esquisse Skip Ignorer Slice Options Options de tranche - Smart Blur Flou intelligent - Smart Sharpen Netteté intelligente + Smart Blur Flou optimisé + Smart Sharpen Netteté optimisée Smoothness Lissage Smudge Stick Estompe Snap to Grid Aimanter à la grille @@ -3851,7 +3854,7 @@ curves Smooth Lisser Spherize Sphérisation Spin Rotation Spin Blur Flou de rotation - Split Channels Séparer les canaux + Split Channels Séparer les couches Spot Spot Spray Radius Rayon de pulvérisation Sprayed Strokes Traits pulvérisés @@ -3894,8 +3897,8 @@ curves Smooth Lisser Texture Coverage Couverture de la texture Texturizer Texturisation Thaw Mask Masque de libération - The canvas is shown as sRGB. Choose a monitor profile in Edit › Color Settings. Le canevas est affiché en sRVB. Choisissez un profil de moniteur dans Édition › Paramètres de couleur. - The canvas on {display} is shown as sRGB. Choose a monitor profile in Edit › Color Settings. Le canevas sur {display} est affiché en sRVB. Choisissez un profil de moniteur dans Édition › Paramètres de couleur. + The canvas is shown as sRGB. Choose a monitor profile in Edit › Color Settings. La zone de travail est affichée en sRVB. Choisissez un profil de moniteur dans Édition › Paramètres de couleur. + The canvas on {display} is shown as sRGB. Choose a monitor profile in Edit › Color Settings. La zone de travail sur {display} est affichée en sRVB. Choisissez un profil de moniteur dans Édition › Paramètres de couleur. The current gradient (pick one in Window › Gradients) Le dégradé actuel (à choisir dans Fenêtre › Dégradés) The document does not have an embedded colour profile. Working space: {working}. Le document ne contient aucun profil colorimétrique incorporé. Espace de travail : {working}. The document has an embedded colour profile that does not match the working space.\nEmbedded: {embedded}\nWorking: {working} Le document contient un profil colorimétrique incorporé qui ne correspond pas à l'espace de travail.\nIncorporé : {embedded}\nEspace de travail : {working} @@ -3903,15 +3906,15 @@ curves Smooth Lisser The file could not be saved. Impossible d'enregistrer le fichier. Thickness Épaisseur This Layer: Ce calque : - This Smart Object must be rasterized before proceeding. Its contents will no longer be editable. Rasterize the Smart Object? Cet objet intelligent doit être rastérisé pour continuer. Son contenu ne sera plus modifiable. Rastériser l'objet intelligent ? - This adjustment ({key}) is kept as imported and can't be edited yet. Cet ajustement ({key}) est conservé tel qu'il a été importé et ne peut pas encore être modifié. - This fill layer must be rasterized before proceeding. Its fill content will no longer be editable. Rasterize the layer? Ce calque de remplissage doit être rastérisé pour continuer. Son contenu de remplissage ne sera plus modifiable. Rastériser le calque ? - This layer must be rasterized before proceeding. Ce calque doit être rastérisé pour continuer. + This Smart Object must be rasterized before proceeding. Its contents will no longer be editable. Rasterize the Smart Object? Cet objet dynamique doit être pixellisé pour continuer. Son contenu ne sera plus modifiable. Pixelliser l'objet dynamique ? + This adjustment ({key}) is kept as imported and can't be edited yet. Ce réglage ({key}) est conservé tel qu'il a été importé et ne peut pas encore être modifié. + This fill layer must be rasterized before proceeding. Its fill content will no longer be editable. Rasterize the layer? Ce calque de remplissage doit être pixellisé pour continuer. Son contenu de remplissage ne sera plus modifiable. Pixelliser le calque ? + This layer must be rasterized before proceeding. Ce calque doit être pixellisé pour continuer. This session: {reason} Cette session : {reason} - This shape layer must be rasterized before proceeding. It will no longer be editable as a shape. Rasterize the shape? Ce calque de forme doit être rastérisé pour continuer. Il ne sera plus modifiable en tant que forme. Rastériser la forme ? - This type layer must be rasterized before proceeding. Its text will no longer be editable. Rasterize the type? Ce calque de texte doit être rastérisé pour continuer. Son texte ne sera plus modifiable. Rastériser le texte ? + This shape layer must be rasterized before proceeding. It will no longer be editable as a shape. Rasterize the shape? Ce calque de forme doit être pixellisé pour continuer. Il ne sera plus modifiable en tant que forme. Pixelliser la forme ? + This type layer must be rasterized before proceeding. Its text will no longer be editable. Rasterize the type? Ce calque de texte doit être pixellisé pour continuer. Son texte ne sera plus modifiable. Pixelliser le texte ? Tile Size Taille des carreaux - Tiles Carreaux + Tiles Carrelage Tilt-Shift Inclinaison-décalage Tint Color Couleur de la nuance Tiny Lens Petites lentilles @@ -3919,13 +3922,13 @@ curves Smooth Lisser To Center Vers le centre Toggle the Brush Settings panel Afficher/masquer le panneau Paramètres du pinceau Tool Options Options d'outil - Trace Contour Tracer les contours + Trace Contour Courbes de niveau Transformation Transformation Transition Transition Transparency Shapes Layer La transparence détermine la forme du calque Trap Recouvrement Tree Arbre - Trim Rogner les bords + Trim Rognage Turbulent Turbulent Twirl Tourbillon Twirl Clockwise Tourbillon horaire @@ -3942,7 +3945,7 @@ curves Smooth Lisser Unlock Déverrouiller Unlocked Déverrouillé Unsaved Changes Modifications non enregistrées - Unsharp Mask Masque flou + Unsharp Mask Accentuation Unsupported file format Format de fichier non pris en charge Unsupported format Format non pris en charge Up Haut @@ -3960,8 +3963,8 @@ curves Smooth Lisser Vertical Type Tool Outil Texte vertical Video Frames to Layers Images vidéo dans des calques Vignette Amount Quantité de vignettage - Vignette Feather Adoucissement du vignettage - Vignette Highlights Hautes lumières du vignettage + Vignette Feather Contour progressif du vignettage + Vignette Highlights Tons clairs du vignettage Vignette Midpoint Point médian du vignettage Vignette Roundness Arrondi du vignettage Vignette Style Style du vignettage @@ -3987,7 +3990,7 @@ curves Smooth Lisser White Is High Le blanc est le plus haut Wide Blurry Large et flou Wide Sharp Large et net - Wind Vent + Wind Soufflerie Window renderer: {renderer} Moteur de rendu de la fenêtre : {renderer} Working Espace de travail Working Cmyk CMJN de travail @@ -4004,7 +4007,7 @@ curves Smooth Lisser gradients dégradés gray gris green vert - layer mask masque de calque + layer mask masque de fusion manual manuel none aucun off désactivé @@ -4026,11 +4029,11 @@ curves Smooth Lisser ✓ Color Indicates Masked Areas ✓ La couleur indique les zones masquées ✓ Color Indicates Selected Areas ✓ La couleur indique les zones sélectionnées @id edit.clear Effacer -@id edit.stroke Contourner… -@id edit.transform.scale Mise à l'échelle +@id edit.stroke Contour… +@id edit.transform.scale Homothétie @id file.close Fermer -@id filter.sharpen.sharpen Netteté -@id layer.layerMask.apply Appliquer le masque de calque +@id filter.sharpen.sharpen Plus net +@id layer.layerMask.apply Appliquer le masque de fusion @plural Exported {n} swatch to {file}|Exported {n} swatches to {file} {n} nuance exportée vers {file}|{n} nuances exportées vers {file} @plural Imported {n} brush from {file}|Imported {n} brushes from {file} {n} pinceau importé depuis {file}|{n} pinceaux importés depuis {file} @plural Imported {n} brush from {group}|Imported {n} brushes from {group} {n} pinceau importé depuis {group}|{n} pinceaux importés depuis {group} @@ -4038,7 +4041,7 @@ curves Smooth Lisser @plural Imported {n} swatch from {file}|Imported {n} swatches from {file} {n} nuance importée depuis {file}|{n} nuances importées depuis {file} @plural Played {n} step|Played {n} steps {n} étape lue|{n} étapes lues @plural Saved for Web: {n} file|Saved for Web: {n} files Enregistré pour le Web : {n} fichier|Enregistré pour le Web : {n} fichiers -@plural Updated {n} linked smart object changed on disk|Updated {n} linked smart objects changed on disk Mise à jour de {n} objet intelligent lié modifié sur le disque|Mise à jour de {n} objets intelligents liés modifiés sur le disque +@plural Updated {n} linked smart object changed on disk|Updated {n} linked smart objects changed on disk Mise à jour de {n} objet dynamique lié modifié sur le disque|Mise à jour de {n} objets dynamiques liés modifiés sur le disque @plural {n} layer recorded by this comp was deleted. Update the comp, or clear the warning.|{n} layers recorded by this comp were deleted. Update the comp, or clear the warning. {n} calque enregistré par cette composition a été supprimé. Mettez la composition à jour ou effacez l'avertissement.|{n} calques enregistrés par cette composition ont été supprimés. Mettez la composition à jour ou effacez l'avertissement. @plural {n} plane|{n} planes {n} plan|{n} plans @plural {n} preset|{n} presets {n} préréglage|{n} préréglages @@ -4061,10 +4064,10 @@ cameraRaw Dehaze Correction de la brume cameraRaw Detail Détail cameraRaw Effects Effets cameraRaw Exposure Exposition -cameraRaw Feather Adoucissement +cameraRaw Feather Contour progressif cameraRaw Global Global cameraRaw Green Vert -cameraRaw Highlights Hautes lumières +cameraRaw Highlights Tons clairs cameraRaw Hue Teinte cameraRaw Luminance Luminance cameraRaw Luminance Detail Détail de la luminance @@ -4078,7 +4081,7 @@ cameraRaw Red Rouge cameraRaw Roughness Rugosité cameraRaw Roundness Arrondi cameraRaw Saturation Saturation -cameraRaw Shadows Ombres +cameraRaw Shadows Tons foncés cameraRaw Size Taille cameraRaw Temperature Température cameraRaw Texture Texture @@ -4090,7 +4093,7 @@ document Orientation Orientation layerLabel Magenta Magenta layerLabel No Color Aucune couleur layerLabel Red Rouge -layers Fill Remplissage -layers Fill: Remplissage : +layers Fill Fond +layers Fill: Fond : navigator Zoom Zoom shape Appearance Apparence From 5035aa4116184dbffb40d1167c145e9715bedc89 Mon Sep 17 00:00:00 2001 From: Lorenzo Fasolino Date: Tue, 13 Oct 2026 16:15:27 +0200 Subject: [PATCH 06/44] fix(i18n): Italian Screen blend mode and Dodge tool names were swapped Task T-0001 del lab artcraft-lab. File toccati: crates/ui-egui/src/i18n/it.tsv | 8 ++++---- crates/ui-egui/src/i18n/mod.rs | 16 ++++++++++++++++ docs/localization-it.md | 9 +++++++++ docs/localization-parity.md | 1 + 4 files changed, 30 insertions(+), 4 deletions(-) Evidenze: artifacts/T-0001/ (nel lab, non in questo repo). --- crates/ui-egui/src/i18n/it.tsv | 8 ++++---- crates/ui-egui/src/i18n/mod.rs | 16 ++++++++++++++++ docs/localization-it.md | 9 +++++++++ docs/localization-parity.md | 1 + 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 08d8adcc14..95a77d5313 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -1655,7 +1655,7 @@ Document Dimensions Dimensioni documento Document Profile Profilo documento Document Sizes Dimensioni documento - Dodge Tool Strumento Scherma + Dodge Tool Strumento Schiarisci Down Giù Drag to draw a gradient Trascina per disegnare una sfumatura Drag to move the active layer Trascina per spostare livello attivo @@ -1989,7 +1989,7 @@ Same folder Stessa cartella Save in background Salva in background Scratch Disks Dischi virtuali - Screen Schermo + Screen Scherma Screen resolution Risoluzione schermo Scrubby slider acceleration Accelerazione slider scrubby Sessions only Solo sessioni @@ -2228,7 +2228,7 @@ Move Sposta Pencil Matita Clone Stamp Timbro clone - Dodge Scherma + Dodge Schiarisci Burn Brucia Quick Selection Selezione rapida History Brush Pennello storia @@ -2795,7 +2795,7 @@ curves Smooth Leviga Patch Toppa Content-Aware Move Spostamento in base al contenuto Perspective Crop Taglierina prospettica - Dodge/Burn Scherma/Brucia + Dodge/Burn Schiarisci/Brucia Dynamics Dinamica Tilt Inclinazione Defaults restored. Predefiniti ripristinati. diff --git a/crates/ui-egui/src/i18n/mod.rs b/crates/ui-egui/src/i18n/mod.rs index 2fdffdfc69..bd4e290153 100644 --- a/crates/ui-egui/src/i18n/mod.rs +++ b/crates/ui-egui/src/i18n/mod.rs @@ -1225,6 +1225,22 @@ mod tests { } } + /// The Italian catalog names the Dodge *tools* and the `Screen` *blend mode* the way Adobe + /// does: `Schiarisci` is the tool (`Lighten` already was), `Scherma` is the blend mode. They + /// were swapped — `Screen` read as `Schermo`, "the display" — which is the kind of mistake + /// only a speaker of the language catches, so the wording is pinned here. + #[test] + fn italian_names_the_dodge_tools_and_the_screen_blend_mode_as_photoshop_does() { + let it = LANGUAGES.iter().find(|l| l.code == "it").expect("the Italian catalog is registered"); + for (source, translation) in [("Dodge", "Schiarisci"), ("Dodge Tool", "Strumento Schiarisci"), ("Screen", "Scherma")] { + assert_eq!(it.catalog().plain(source), Some(translation), "it: {source:?}"); + } + // The qualified blend modes keep Adobe's "Scherma…" family: they are not the tool. + for (source, translation) in [("Color Dodge", "Colore scherma"), ("Linear Dodge", "Scherma lineare")] { + assert_eq!(it.catalog().plain(source), Some(translation), "it: {source:?}"); + } + } + // Dynamic dialog labels generated by label_of() are not seen by the tl! scanner. #[test] fn color_lookup_export_scope_labels_are_translated() { diff --git a/docs/localization-it.md b/docs/localization-it.md index 7b0444410e..7a09598d4b 100644 --- a/docs/localization-it.md +++ b/docs/localization-it.md @@ -44,9 +44,18 @@ command names joined by a slash, which keep both capitals (*Luminosità/Contrast | HDR Toning | Mappatura tonale HDR | tone mapping; *Viraggio* stays for colour toning (*Viraggio separato*) | | Bevel & Emboss | Smusso e rilievo | pairs with *Rilievo* (Emboss) | | Linked (smart object) | Oggetto collegato | *Inserisci oggetto collegato*, *Converti in oggetto collegato* | +| Screen (blend mode) | Scherma | the blend mode; *Schermo* stays for the display (*Modalità schermo*, *Risoluzione schermo*) | +| Dodge / Dodge Tool | Schiarisci / Strumento Schiarisci | the *tools*, as *Lighten* already was; the qualified blend modes keep Adobe's family (*Colore scherma*, *Scherma lineare*) | ## Native review +2026-10-13: a second pass reviewed the blend-mode and tool names against Adobe's Italian +vocabulary and found the two words swapped: `Screen` was *Schermo* (the display) and `Dodge` / +`Dodge Tool` were *Scherma* / *Strumento Scherma*. Corrected to *Scherma*, *Schiarisci* and +*Strumento Schiarisci*, with *Color Dodge* / *Linear Dodge* (`Colore scherma`, `Scherma lineare`) +left alone: they are blend modes, not the tool, and they already used Adobe's wording. The +wording is now pinned by a test (`i18n::tests::italian_names_the_dodge_tools_and_the_screen_blend_mode_as_photoshop_does`). + 2026-10-12: a native Italian speaker read the catalog and fixed mistranslations (Trap, HDR Toning, Display Text, Vine Color, Override Action "Save" Commands) and unnatural phrasing (Bevel & Emboss, East Asian / Middle Eastern Features, linked smart objects, bit depth labels). diff --git a/docs/localization-parity.md b/docs/localization-parity.md index 9c73eb094b..76ab9e844c 100644 --- a/docs/localization-parity.md +++ b/docs/localization-parity.md @@ -85,3 +85,4 @@ engine localization or native review is complete. | 2026-10-12 | minor | Added Turkish (`tr`) catalog with 100% measured UI coverage | | 2026-10-12 | minor | Italian native-speaker review: mistranslations (Trap, HDR Toning, Display Text) and phrasing fixed | | 2026-10-12 | minor | Serbian (`sr`, Latin, Ekavian) catalog: every measured key plus the dynamic labels of the other catalogs | +| 2026-10-13 | minor | Italian: the `Screen` blend mode and the `Dodge` tools had their names swapped (`Schermo`/`Scherma` → `Scherma`/`Schiarisci`); pinned by a test | From b4664381ed1cc90cac548df2717f39c462ea0f2a Mon Sep 17 00:00:00 2001 From: Lorenzo Fasolino Date: Tue, 13 Oct 2026 16:15:28 +0200 Subject: [PATCH 07/44] chore(contributors): credit LorenzoFasolino as Lorenzo Fasolino Contributors entry for the About window: real name and display name. The public GitHub profile name is empty, so sync_github_name stays false rather than falling back to nothing. --- contributors/people.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/contributors/people.toml b/contributors/people.toml index bbda6719ec..30ba09061c 100644 --- a/contributors/people.toml +++ b/contributors/people.toml @@ -154,3 +154,8 @@ sync_github_name = true [people.VanTan666] display_name = "VanTan" sync_github_name = false + +[people.LorenzoFasolino] +real_name = "Lorenzo Fasolino" +display_name = "Lorenzo Fasolino" +sync_github_name = false From 08d14079bd487361a6974075c3bba7d17d37c546 Mon Sep 17 00:00:00 2001 From: Lin Heyang <850784156@qq.com> Date: Tue, 13 Oct 2026 23:48:38 +0800 Subject: [PATCH 08/44] i18n: translate missing Simplified Chinese tool labels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Translate `Vertical Type Tool` as `直排文字工具` - Translate `Pencil Tool` as `铅笔工具` - Translate `Color Sampler Tool` as `颜色取样器工具` These labels previously fell back to English in the Simplified Chinese UI. --- crates/ui-egui/src/i18n/zh-hans.tsv | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index fb6083985c..c98e956360 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -1639,6 +1639,7 @@ Brush Pose 画笔姿态 Brush Tip Shape 画笔笔尖形状 Brush Tool 画笔工具 + Pencil Tool 铅笔工具 Build-up 累积 Burn Tool 加深工具 Centimeters 厘米 @@ -1693,6 +1694,7 @@ ExtraBold 特粗 ExtraLight 特细 Eyedropper Tool 吸管工具 + Color Sampler Tool 颜色取样器工具 Fade 渐隐 Fill Opacity 填充不透明度 Film & Video 电影与视频 @@ -1709,6 +1711,7 @@ Highlights from: 高光下限: History Brush Tool 历史记录画笔工具 Horizontal Type Tool 横排文字工具 + Vertical Type Tool 直排文字工具 Horizontal Type Mask Tool 横排文字蒙版工具 Vertical Type Mask Tool 直排文字蒙版工具 Image Size: 图像大小: From 25541891d13d2c317af155859a16b2502ab434d4 Mon Sep 17 00:00:00 2001 From: Lorenzo Fasolino Date: Tue, 13 Oct 2026 18:31:27 +0200 Subject: [PATCH 09/44] fix(i18n): keep the Italian Dodge tool as Scherma; only Screen was wrong Review feedback on #5062: Adobe's Italian Photoshop calls the Dodge tool "Strumento Scherma" (Burn is "Strumento Brucia") and "Schiarisci" is the Lighten blend mode. Renaming the tool to Schiarisci would have given two different things the same name and split the Dodge/Burn/Sponge family the catalog already had (Scherma, Brucia, Spugna). Only the Screen blend mode changes: it read "Schermo" (the display) and is now "Scherma". The test pins the tool family and the blend modes. --- crates/ui-egui/src/i18n/it.tsv | 6 +++--- crates/ui-egui/src/i18n/mod.rs | 16 +++++++++------- docs/localization-it.md | 14 +++++++------- docs/localization-parity.md | 2 +- 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 95a77d5313..c05f96caa7 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -1655,7 +1655,7 @@ Document Dimensions Dimensioni documento Document Profile Profilo documento Document Sizes Dimensioni documento - Dodge Tool Strumento Schiarisci + Dodge Tool Strumento Scherma Down Giù Drag to draw a gradient Trascina per disegnare una sfumatura Drag to move the active layer Trascina per spostare livello attivo @@ -2228,7 +2228,7 @@ Move Sposta Pencil Matita Clone Stamp Timbro clone - Dodge Schiarisci + Dodge Scherma Burn Brucia Quick Selection Selezione rapida History Brush Pennello storia @@ -2795,7 +2795,7 @@ curves Smooth Leviga Patch Toppa Content-Aware Move Spostamento in base al contenuto Perspective Crop Taglierina prospettica - Dodge/Burn Schiarisci/Brucia + Dodge/Burn Scherma/Brucia Dynamics Dinamica Tilt Inclinazione Defaults restored. Predefiniti ripristinati. diff --git a/crates/ui-egui/src/i18n/mod.rs b/crates/ui-egui/src/i18n/mod.rs index 559edc7114..dc350c9cf1 100644 --- a/crates/ui-egui/src/i18n/mod.rs +++ b/crates/ui-egui/src/i18n/mod.rs @@ -1244,18 +1244,20 @@ mod tests { } } - /// The Italian catalog names the Dodge *tools* and the `Screen` *blend mode* the way Adobe - /// does: `Schiarisci` is the tool (`Lighten` already was), `Scherma` is the blend mode. They - /// were swapped — `Screen` read as `Schermo`, "the display" — which is the kind of mistake - /// only a speaker of the language catches, so the wording is pinned here. + /// The Italian catalog names the Dodge/Burn/Sponge *tools* and the blend modes the way Adobe + /// does. `Scherma` is the Dodge tool (with `Brucia` and `Spugna`) **and** the `Screen` blend + /// mode — Adobe's own collision, not ours — while `Schiarisci` is the `Lighten` blend mode and + /// must not be used for the tool. `Screen` used to read `Schermo`, "the display". #[test] fn italian_names_the_dodge_tools_and_the_screen_blend_mode_as_photoshop_does() { let it = LANGUAGES.iter().find(|l| l.code == "it").expect("the Italian catalog is registered"); - for (source, translation) in [("Dodge", "Schiarisci"), ("Dodge Tool", "Strumento Schiarisci"), ("Screen", "Scherma")] { + for (source, translation) in [("Dodge", "Scherma"), ("Dodge Tool", "Strumento Scherma"), ("Dodge/Burn", "Scherma/Brucia"), ("Screen", "Scherma")] { assert_eq!(it.catalog().plain(source), Some(translation), "it: {source:?}"); } - // The qualified blend modes keep Adobe's "Scherma…" family: they are not the tool. - for (source, translation) in [("Color Dodge", "Colore scherma"), ("Linear Dodge", "Scherma lineare")] { + // The tool family stays together, and the blend modes keep Adobe's wording. + for (source, translation) in + [("Burn", "Brucia"), ("Sponge", "Spugna"), ("Lighten", "Schiarisci"), ("Color Dodge", "Colore scherma"), ("Linear Dodge", "Scherma lineare")] + { assert_eq!(it.catalog().plain(source), Some(translation), "it: {source:?}"); } } diff --git a/docs/localization-it.md b/docs/localization-it.md index 7a09598d4b..f983c499e4 100644 --- a/docs/localization-it.md +++ b/docs/localization-it.md @@ -45,16 +45,16 @@ command names joined by a slash, which keep both capitals (*Luminosità/Contrast | Bevel & Emboss | Smusso e rilievo | pairs with *Rilievo* (Emboss) | | Linked (smart object) | Oggetto collegato | *Inserisci oggetto collegato*, *Converti in oggetto collegato* | | Screen (blend mode) | Scherma | the blend mode; *Schermo* stays for the display (*Modalità schermo*, *Risoluzione schermo*) | -| Dodge / Dodge Tool | Schiarisci / Strumento Schiarisci | the *tools*, as *Lighten* already was; the qualified blend modes keep Adobe's family (*Colore scherma*, *Scherma lineare*) | +| Dodge / Burn / Sponge (tools) | Scherma / Brucia / Spugna | the *tools*, one family. *Scherma* is also the `Screen` blend mode (Adobe's own collision). *Schiarisci* is the `Lighten` blend mode and is never the Dodge tool | ## Native review -2026-10-13: a second pass reviewed the blend-mode and tool names against Adobe's Italian -vocabulary and found the two words swapped: `Screen` was *Schermo* (the display) and `Dodge` / -`Dodge Tool` were *Scherma* / *Strumento Scherma*. Corrected to *Scherma*, *Schiarisci* and -*Strumento Schiarisci*, with *Color Dodge* / *Linear Dodge* (`Colore scherma`, `Scherma lineare`) -left alone: they are blend modes, not the tool, and they already used Adobe's wording. The -wording is now pinned by a test (`i18n::tests::italian_names_the_dodge_tools_and_the_screen_blend_mode_as_photoshop_does`). +2026-10-13: `Screen` was *Schermo* — the display, not the blend mode — and is now *Scherma*, the +word Adobe uses. The Dodge/Burn/Sponge tools were left as they were (*Scherma*, *Brucia*, *Spugna*): +a first pass also wanted to rename the Dodge tool to *Schiarisci*, but that is the `Lighten` blend +mode and would have given two different things the same name. The wording is pinned by a test +(`i18n::tests::italian_names_the_dodge_tools_and_the_screen_blend_mode_as_photoshop_does`), which +also keeps the tool family together. 2026-10-12: a native Italian speaker read the catalog and fixed mistranslations (Trap, HDR Toning, Display Text, Vine Color, Override Action "Save" Commands) and unnatural phrasing (Bevel & diff --git a/docs/localization-parity.md b/docs/localization-parity.md index 76ab9e844c..9c50c9798f 100644 --- a/docs/localization-parity.md +++ b/docs/localization-parity.md @@ -85,4 +85,4 @@ engine localization or native review is complete. | 2026-10-12 | minor | Added Turkish (`tr`) catalog with 100% measured UI coverage | | 2026-10-12 | minor | Italian native-speaker review: mistranslations (Trap, HDR Toning, Display Text) and phrasing fixed | | 2026-10-12 | minor | Serbian (`sr`, Latin, Ekavian) catalog: every measured key plus the dynamic labels of the other catalogs | -| 2026-10-13 | minor | Italian: the `Screen` blend mode and the `Dodge` tools had their names swapped (`Schermo`/`Scherma` → `Scherma`/`Schiarisci`); pinned by a test | +| 2026-10-13 | minor | Italian: the `Screen` blend mode read *Schermo* (the display) and is now *Scherma*; the Dodge/Burn/Sponge tool names were already Adobe's (*Scherma*, *Brucia*, *Spugna*) and stay. Pinned by a test | From c0faf7300cb8f16a71c62bd08666551243c2b249 Mon Sep 17 00:00:00 2001 From: zolyy Date: Tue, 13 Oct 2026 20:10:46 +0300 Subject: [PATCH 10/44] Type tool: click a path to start type on it (TYPE-218-1, #218) A Type tool click on a shown path (the active shape layer's, else the work path) starts type on that path at the point clicked, as in Photoshop: one history step, placeholder selected, the paragraph alignment kept about the click. Over such a path the pointer is the path-type I-beam, and the Type tool shows the paths as the vector tools do. - text: warp::arc_length_at, the arc length of the nearest point on a path. - engine: type.attachToPath takes "at": [x, y] (a document point) instead of "start". - ui: the caret, selection and underline follow the curve; a click in path type puts the caret at the glyph there; glyphs past the path end are not highlighted. Co-Authored-By: Claude Opus 5.5 --- contributors/people.toml | 4 + crates/engine/src/type_extra_cmds.rs | 24 +++- crates/engine/src/type_extra_cmds/tests.rs | 28 ++++ crates/text/src/warp.rs | 35 +++++ crates/ui-egui/src/canvas.rs | 9 ++ crates/ui-egui/src/type_tool.rs | 153 ++++++++++++++++++++- crates/ui-egui/src/vector_ui.rs | 3 +- docs/gaps.md | 7 +- docs/scorecard.md | 2 +- scorecard/type.toml | 2 +- 10 files changed, 253 insertions(+), 14 deletions(-) diff --git a/contributors/people.toml b/contributors/people.toml index 6f630a0c12..6dda48b4b1 100644 --- a/contributors/people.toml +++ b/contributors/people.toml @@ -158,3 +158,7 @@ sync_github_name = false [people.yeniufuklar-com] real_name = "Ufuk Efe" sync_github_name = true + +[people.zolyy] +real_name = "Gall Zoltan" +sync_github_name = false diff --git a/crates/engine/src/type_extra_cmds.rs b/crates/engine/src/type_extra_cmds.rs index efa2d791cc..c63f7d3d7c 100644 --- a/crates/engine/src/type_extra_cmds.rs +++ b/crates/engine/src/type_extra_cmds.rs @@ -368,13 +368,24 @@ fn attach_to_path(s: &mut Session, p: &Value) -> Result { None | Some(Value::Null) => false, Some(v) => v.as_bool().ok_or_else(|| bad("\"flip\" must be true or false"))?, }; + // A document point: the type starts where the path passes nearest it (the Type tool's click). + let at = match p.get("at") { + None | Some(Value::Null) => None, + Some(v) => { + let xy = v.as_array().filter(|a| a.len() == 2).and_then(|a| Some((a.first()?.as_f64()?, a.get(1)?.as_f64()?))); + Some(xy.filter(|(x, y)| x.is_finite() && y.is_finite()).ok_or_else(|| bad("\"at\" must be [x, y] in document pixels"))?) + } + }; + if at.is_some() && p.get("start").is_some_and(|v| !v.is_null()) { + return Err(bad("pass \"start\" or \"at\", not both")); + } let id = layer_param(s, p)?; let doc = &s.active().ok_or(EngineError::NoDocument)?.doc; if doc.effective_locks(id).all { return Err(EngineError::Other("the type layer is locked".into())); } let sub = path_source(doc, p)?; - with_text(s, p, "Type on a Path", |t, _| { + let start = with_text(s, p, "Type on a Path", |t, _| { // The path lives in document space; the type layer keeps it in its own text space, so // the type lands on the path where it is drawn. let inv = t.transform.inverse().ok_or_else(|| EngineError::Other("the type layer's transform cannot be inverted".into()))?; @@ -384,8 +395,15 @@ fn attach_to_path(s: &mut Session, p: &Value) -> Result { if !path.knots.iter().all(|k| [k.anchor, k.in_ctrl, k.out_ctrl].iter().all(|q| q.x.is_finite() && q.y.is_finite())) { return Err(EngineError::Other("the path cannot be mapped into the type layer".into())); } + let start = match at { + None => start, + Some((x, y)) => { + let q = map(Point::new(x, y)); + photocraft_text::warp::arc_length_at(&path, flip, q.x, q.y).ok_or_else(|| bad("the path has no length"))? as f32 + } + }; t.shape = TextShape::Path { path, start, flip }; - Ok(()) + Ok(start) })?; Ok(json!({"layer": id.0, "start": start, "flip": flip})) } @@ -696,7 +714,7 @@ pub fn specs() -> Vec { "type.attachToPath", "Type on a Path", &[], - r##"{"layer":id? (default active Type layer),"shape":layerId? (a shape layer; default the work path),"subpath":index=0,"start":px=0 (offset along the path),"flip":bool=false (run along the path the other way, on its other side)}"##, + r##"{"layer":id? (default active Type layer),"shape":layerId? (a shape layer; default the work path),"subpath":index=0,"start":px=0 (offset along the path),"at":[x,y]? (instead of "start": begin where the path passes nearest this document point, as a Type tool click does),"flip":bool=false (run along the path the other way, on its other side)}"##, has_text, attach_to_path ), diff --git a/crates/engine/src/type_extra_cmds/tests.rs b/crates/engine/src/type_extra_cmds/tests.rs index 9f604eed1b..a2f7f734bd 100644 --- a/crates/engine/src/type_extra_cmds/tests.rs +++ b/crates/engine/src/type_extra_cmds/tests.rs @@ -313,6 +313,34 @@ fn type_attaches_to_a_path_and_releases_it() { assert!(matches!(text(&s, id).shape, TextShape::Path { .. })); } +/// The Type tool's click on a path: `"at"` starts the type where the path passes nearest the +/// click, and the paragraph alignment is kept about that point, as in Photoshop. +#[test] +fn type_attaches_to_a_path_at_a_document_point() { + let mut s = session(8); + s.edit("path", |doc, _| { + doc.work_path = Some(Path::new(vec![Subpath::polyline(&[(10.0, 60.0), (290.0, 60.0)])])); + Ok(()) + }) + .unwrap(); + for (align, centred) in [("left", false), ("center", true)] { + let r = s.execute("type.create", json!({"x": 150, "y": 60, "text": "Path", "size": 24, "align": align, "color": "#000000"})).unwrap(); + let id = LayerId(r["layer"].as_u64().unwrap()); + assert!(s.execute("type.attachToPath", json!({"at": [150, "y"]})).is_err()); + assert!(s.execute("type.attachToPath", json!({"at": [150]})).is_err()); + assert!(s.execute("type.attachToPath", json!({"at": [150, 60], "start": 3})).is_err()); + let r = s.execute("type.attachToPath", json!({"at": [150, 64]})).unwrap(); + assert!((r["start"].as_f64().unwrap() - 140.0).abs() < 0.5, "{r}"); + let b = ink(&s, id); + assert!(b.y1 <= 61, "the type sits on the path: {b:?}"); + if centred { + assert!(b.x0 < 145 && b.x1 > 155, "centred on the click: {b:?}"); + } else { + assert!((148..=156).contains(&b.x0), "starts at the click: {b:?}"); + } + } +} + #[test] fn all_locked_layer_rejects_paste_lorem_ipsum_without_history() { // #4188: Lock All stopped type.edit (#3132) but not Paste Lorem Ipsum into the layer. diff --git a/crates/text/src/warp.rs b/crates/text/src/warp.rs index f148a18e1b..439fc453bf 100644 --- a/crates/text/src/warp.rs +++ b/crates/text/src/warp.rs @@ -110,6 +110,13 @@ impl Warp { } } +/// The arc length along `path`, measured as type on it measures it (from the end it starts at +/// with `flip`), of the point on the path nearest (`x`, `y`): the `start` that puts the type's +/// origin where the Type tool clicked on the path. `None` for a path without length. +pub fn arc_length_at(path: &Subpath, flip: bool, x: f64, y: f64) -> Option { + PathFrame::new(path, 0.0, flip).map(|f| f.project(x, y)) +} + /// Most cubic segments of a type path that are followed (a hostile file cannot make this large). const MAX_PATH_SEGMENTS: usize = 4096; @@ -163,6 +170,22 @@ impl PathFrame { self.cum.last().copied().unwrap_or(0.0) } + /// Arc length of the point on the flattened path nearest (`x`, `y`). + fn project(&self, x: f64, y: f64) -> f64 { + let mut best = (f64::INFINITY, 0.0); + for (i, w) in self.pts.windows(2).enumerate() { + let [a, b] = [w[0], w[1]]; + let (dx, dy) = (b.0 - a.0, b.1 - a.1); + let len2 = dx * dx + dy * dy; + let t = if len2 > 0.0 { (((x - a.0) * dx + (y - a.1) * dy) / len2).clamp(0.0, 1.0) } else { 0.0 }; + let d = (a.0 + dx * t - x).hypot(a.1 + dy * t - y); + if d < best.0 { + best = (d, self.cum.get(i).copied().unwrap_or(0.0) + len2.sqrt() * t); + } + } + best.1 + } + /// Point and unit tangent at arc length `s`; past either end the end tangent continues straight. fn at(&self, s: f64) -> ((f64, f64), (f64, f64)) { let last = self.pts.len() - 1; @@ -211,6 +234,18 @@ mod tests { } } + /// The Type tool's click on a path: the nearest point's arc length, from the other end with flip. + #[test] + fn arc_length_at_projects_onto_the_path() { + let line = Subpath::polyline(&[(0.0, 0.0), (100.0, 0.0), (100.0, 50.0)]); + let at = |flip, x, y| arc_length_at(&line, flip, x, y).unwrap(); + assert!((at(false, 30.0, -8.0) - 30.0).abs() < 1e-6); + assert!((at(false, 110.0, 20.0) - 120.0).abs() < 1e-6, "on the second segment"); + assert!((at(true, 30.0, -8.0) - 120.0).abs() < 1e-6, "flipped: measured from the far end"); + assert!(at(false, -50.0, 0.0).abs() < 1e-6, "clamped to the start"); + assert!(arc_length_at(&Subpath::polyline(&[(5.0, 5.0), (5.0, 5.0)]), false, 0.0, 0.0).is_none()); + } + #[test] fn styles_resolve_and_none_is_identity() { assert_eq!(psd_style("Arc"), Some("warpArc")); diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index db0efe31fa..27ea383254 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -3884,6 +3884,15 @@ pub fn canvas_view(app: &mut PhotocraftApp, ui: &mut egui::Ui, idx: usize, rect: egui::CursorIcon::Crosshair } Tool::Move => egui::CursorIcon::Move, + // Over a path it can follow, the Type tool's path-type pointer (Photoshop). + Tool::Type + if app.drag.is_none() && { + let [x, y] = xf.to_doc(p); + crate::type_tool::path_at(app, x, y).is_some() + } => + { + crate::type_tool::path_cursor(&painter, p) + } // Dragging is handled with the pan itself, above. Tool::Hand => crate::tool_cursor::hand(ui.ctx(), false), Tool::RotateView => crate::rotate_view::cursor(response.dragged()), diff --git a/crates/ui-egui/src/type_tool.rs b/crates/ui-egui/src/type_tool.rs index f00fb57157..531138721e 100644 --- a/crates/ui-egui/src/type_tool.rs +++ b/crates/ui-egui/src/type_tool.rs @@ -158,6 +158,14 @@ fn hit_offset(app: &mut PhotocraftApp, id: LayerId, x: f64, y: f64) -> usize { pub(crate) fn offset_at(app: &mut PhotocraftApp, id: LayerId, x: f64, y: f64) -> usize { let Some((l, aff, text)) = layout(app, id) else { return 0 }; let (tx, ty) = to_text(&aff, x, y); + // Type on a path: the point's place along the path is the position in the (single) line. + let shape = app.session.active().and_then(|s| text_layer(&s.doc, id).map(|t| t.shape.clone())); + let (tx, ty) = match shape { + Some(photocraft_doc::text::TextShape::Path { path, start, flip }) => { + photocraft_text::warp::arc_length_at(&path, flip, f64::from(tx), f64::from(ty)).map_or((tx, ty), |s| ((s - f64::from(start)) as f32, -1.0)) + } + _ => (tx, ty), + }; photocraft_text::hit_char(&l, &text, tx, ty).0 } @@ -319,6 +327,8 @@ pub fn pointer_up(app: &mut PhotocraftApp, start: [f64; 2], end: [f64; 2]) { p["x"] = json!(start[0].round()); p["y"] = json!(start[1].round()); } + // A click on a shown path starts type on that path, from the point clicked (Photoshop). + let on_path = if p.get("box").is_none() { path_at(app, start[0], start[1]) } else { None }; let key = session_key(app); p["coalesce"] = json!(key); if let Ok(v) = app.run("type.create", p) @@ -327,6 +337,14 @@ pub fn pointer_up(app: &mut PhotocraftApp, start: [f64; 2], end: [f64; 2]) { if o.type_aa != "sharp" { let _ = app.run("type.edit", json!({"layer": id, "antialias": o.type_aa, "coalesce": key})); } + if let Some(mut q) = on_path { + q["layer"] = json!(id); + q["at"] = json!([start[0], start[1]]); + q["coalesce"] = json!(key); + if let Err(e) = app.run("type.attachToPath", q) { + app.ui.status = e; + } + } // A Type Mask's text is a hidden working layer: the canvas shows the mask overlay // instead, and committing turns it into the selection. if mask { @@ -338,6 +356,53 @@ pub fn pointer_up(app: &mut PhotocraftApp, start: [f64; 2], end: [f64; 2]) { } } +/// The shown path a Type tool click at document point (`x`, `y`) puts type on, as +/// `type.attachToPath` params naming it (`shape`, `subpath`): the active shape layer's path, else +/// the work path, within a few screen pixels, as in Photoshop. Only the horizontal Type tool +/// starts path type for now; vertical type on a path is not laid out yet. +pub(crate) fn path_at(app: &PhotocraftApp, x: f64, y: f64) -> Option { + if app.ui.tool != crate::state::Tool::Type || app.ui.text_edit.is_some() || !app.ui.view.shows(app.ui.view.show.target_path) { + return None; + } + let tol = f64::from(6.0 / app.point_zoom().max(0.01)); + let hit = |p: &photocraft_doc::vector::Path| { + let s = match photocraft_vector::edit::hit(p, Point::new(x, y), tol, &[])? { + photocraft_vector::edit::Hit::Anchor([s, _]) | photocraft_vector::edit::Hit::Segment([s, _], _) => s, + photocraft_vector::edit::Hit::Handle(..) => return None, + }; + p.subpaths.get(s).filter(|sp| sp.knots.len() >= 2).map(|_| s) + }; + if let Some((id, p)) = crate::vector_ui::active_shape_path(app) + && let Some(s) = hit(&p) + { + return Some(json!({"shape": id, "subpath": s})); + } + let work = app.session.active()?.doc.work_path.as_ref()?; + hit(work).map(|s| json!({"subpath": s})) +} + +/// The Type tool's pointer over a path it can follow: Photoshop's I-beam crossed by a dotted +/// wavy line, drawn on the canvas. +pub(crate) fn path_cursor(painter: &egui::Painter, at: Pos2) -> egui::CursorIcon { + let beam = |c: Color32, w: f32| { + let s = Stroke::new(w, c); + painter.line_segment([at + egui::vec2(0.0, -8.0), at + egui::vec2(0.0, 8.0)], s); + for dy in [-8.0, 8.0] { + painter.line_segment([at + egui::vec2(-3.0, dy), at + egui::vec2(3.0, dy)], s); + } + }; + beam(Color32::WHITE, 3.0); + beam(Color32::BLACK, 1.0); + // The wave: short dashes along a sine through the beam's lower half. + let wave = |t: f32| at + egui::vec2(-9.0 + 18.0 * t, 5.0 - 3.0 * t + 2.0 * (t * std::f32::consts::TAU * 1.5).sin()); + for i in (0..12).step_by(2) { + let (a, b) = (wave(i as f32 / 12.0), wave((i + 1) as f32 / 12.0)); + painter.line_segment([a, b], Stroke::new(3.0, Color32::WHITE)); + painter.line_segment([a, b], Stroke::new(1.0, Color32::BLACK)); + } + egui::CursorIcon::None +} + fn session_key(app: &mut PhotocraftApp) -> String { format!("type-{}", app.ui.alloc_id()) } @@ -998,10 +1063,24 @@ pub fn draw_overlay(app: &mut PhotocraftApp, painter: &egui::Painter, xf: &ViewX let p = aff.apply(Point::new(x as f64, y as f64)); xf.to_screen(p.x as f32, p.y as f32) }; + let shape = app.session.active().and_then(|s| text_layer(&s.doc, id).map(|t| t.shape.clone())); + // Type on a path: the caret, selection and underline follow the curve the glyphs sit on. + let bend = shape.as_ref().and_then(photocraft_text::warp::Warp::on_path); // Line space (lines, clusters, carets) → screen: vertical type turns it 90° clockwise. let scr = |x: f32, y: f32| -> Pos2 { let (x, y) = l.to_text(x, y); - scr_t(x, y) + match &bend { + Some(w) => { + let (x, y) = w.apply(f64::from(x), f64::from(y)); + scr_t(x as f32, y as f32) + } + None => scr_t(x, y), + } + }; + // A line-space run from `x0` to `x1` at height `y`: sampled, so it bends with path type. + let run = |x0: f32, x1: f32, y: f32| -> Vec { + let n = if bend.is_some() { ((x1 - x0) / 4.0).ceil().clamp(1.0, 512.0) as usize } else { 1 }; + (0..=n).map(|i| scr(x0 + (x1 - x0) * i as f32 / n as f32, y)).collect() }; // Tell the OS where the caret is: this is what enables the IME and places its candidate window. { @@ -1017,7 +1096,6 @@ pub fn draw_overlay(app: &mut PhotocraftApp, painter: &egui::Painter, xf: &ViewX if crate::type_transform::draw(app, painter, xf, mods) { return; } - let shape = app.session.active().and_then(|s| text_layer(&s.doc, id).map(|t| t.shape.clone())); let frame = Stroke::new(1.0, t.accent); match shape { Some(photocraft_doc::text::TextShape::Box { x, y, width, height }) => { @@ -1034,7 +1112,7 @@ pub fn draw_overlay(app: &mut PhotocraftApp, painter: &egui::Painter, xf: &ViewX for ln in &l.lines { // Horizontal: just under the baseline; vertical: the column's centre line. let y = if l.vertical { ln.baseline } else { ln.baseline + ln.descent * 0.25 }; - painter.line_segment([scr(ln.x0.min(0.0), y), scr(ln.x1.max(ln.x0 + 1.0), y)], Stroke::new(1.0, t.accent.gamma_multiply(0.8))); + painter.add(egui::Shape::line(run(ln.x0.min(0.0), ln.x1.max(ln.x0 + 1.0), y), Stroke::new(1.0, t.accent.gamma_multiply(0.8)))); } } } @@ -1047,7 +1125,7 @@ pub fn draw_overlay(app: &mut PhotocraftApp, painter: &egui::Painter, xf: &ViewX let (x0, x1) = xs.iter().fold((f32::MAX, f32::MIN), |(lo, hi), (p, q)| (lo.min(*p), hi.max(*q))); if x0 < x1 { let y = ln.baseline + ln.descent * 0.5; - painter.line_segment([scr(x0, y), scr(x1, y)], Stroke::new(1.5, t.accent)); + painter.add(egui::Shape::line(run(x0, x1, y), Stroke::new(1.5, t.accent))); } } } @@ -1064,8 +1142,15 @@ pub fn draw_overlay(app: &mut PhotocraftApp, painter: &egui::Painter, xf: &ViewX x1 = x1.max(ln.x1 + (ln.ascent + ln.descent) * 0.25); x0 = x0.min(ln.x1); } - if x0 < x1 { - let (top, bot) = (ln.baseline - ln.ascent, ln.baseline + ln.descent); + let (top, bot) = (ln.baseline - ln.ascent, ln.baseline + ln.descent); + if bend.is_some() { + // On a path each glyph turns on its own, so each gets its own highlight. + // Glyphs past the end of the path are not drawn, so neither is their highlight. + let shown = |p: f32, q: f32| bend.as_ref().is_some_and(|w| w.place_glyph(f64::from(p + q) / 2.0) != photocraft_text::warp::Placement::Hidden); + for (p, q) in xs.into_iter().filter(|&(p, q)| shown(p, q)) { + painter.add(egui::Shape::convex_polygon(vec![scr(p, top), scr(q, top), scr(q, bot), scr(p, bot)], fill, Stroke::NONE)); + } + } else if x0 < x1 { painter.add(egui::Shape::convex_polygon(vec![scr(x0, top), scr(x1, top), scr(x1, bot), scr(x0, bot)], fill, Stroke::NONE)); } } @@ -2541,6 +2626,62 @@ mod tests { assert!(app.ui.text_edit.is_none()); } + /// TYPE-218-1: a Type tool click on the work path starts type on the path from the point + /// clicked, in one history step with the placeholder selected; a click off it is point text. + #[test] + fn click_on_a_path_starts_type_on_the_path() { + use photocraft_doc::text::TextShape; + let mut app = app(); + app.session + .edit("path", |doc, _| { + doc.work_path = Some(photocraft_doc::vector::Path::new(vec![photocraft_doc::vector::Subpath::polyline(&[(20.0, 100.0), (380.0, 100.0)])])); + Ok(()) + }) + .unwrap(); + assert!(path_at(&app, 200.0, 101.0).is_some()); + assert!(path_at(&app, 200.0, 160.0).is_none(), "off the path"); + let before = app.session.active().unwrap().history.entries().len(); + assert!(!pointer_down(&mut app, 200.0, 101.0, egui::Modifiers::NONE)); + pointer_up(&mut app, [200.0, 101.0], [200.0, 101.0]); + let ed = app.ui.text_edit.clone().unwrap(); + assert_eq!((ed.anchor, ed.caret), (0, PLACEHOLDER.chars().count()), "the placeholder is selected"); + let shape = text_layer(&app.session.active().unwrap().doc, LayerId(ed.layer)).unwrap().shape.clone(); + assert!(matches!(shape, TextShape::Path { start, flip: false, .. } if (start - 180.0).abs() < 0.5), "{shape:?}"); + assert_eq!(app.session.active().unwrap().history.entries().len(), before + 1, "one step for the click"); + insert(&mut app, "On the line"); + assert_eq!(layer_text(&app), "On the line"); + commit(&mut app); + // With the vertical tool a click does not start path type. + app.ui.tool = crate::state::Tool::VerticalType; + assert!(path_at(&app, 200.0, 101.0).is_none()); + app.ui.tool = crate::state::Tool::Type; + pointer_up(&mut app, [200.0, 160.0], [200.0, 160.0]); + let id = LayerId(app.ui.text_edit.as_ref().unwrap().layer); + assert_eq!(text_layer(&app.session.active().unwrap().doc, id).unwrap().shape, TextShape::Point); + } + + /// A click in type on a path puts the caret at the glyph there along the curve, not where the + /// straight line would have it. + #[test] + fn caret_hits_type_on_a_path_along_the_curve() { + let mut app = app(); + app.session + .edit("path", |doc, _| { + doc.work_path = Some(photocraft_doc::vector::Path::new(vec![photocraft_doc::vector::Subpath::polyline(&[(300.0, 10.0), (300.0, 195.0)])])); + Ok(()) + }) + .unwrap(); + pointer_up(&mut app, [300.0, 20.0], [300.0, 20.0]); + insert(&mut app, "ABCDEFGH"); + let id = LayerId(app.ui.text_edit.as_ref().unwrap().layer); + assert!(matches!(text_layer(&app.session.active().unwrap().doc, id).unwrap().shape, photocraft_doc::text::TextShape::Path { .. })); + // The type runs down the path: near its start the caret is at the beginning, far down at the end. + assert_eq!(offset_at(&mut app, id, 300.0, 18.0), 0); + // (The straight line would put both of these at the start: they are left of its first glyph.) + let (mid, far) = (offset_at(&mut app, id, 296.0, 60.0), offset_at(&mut app, id, 296.0, 190.0)); + assert!(0 < mid && mid < far, "{mid} {far}"); + } + /// Preferences ▸ Units & Rulers ▸ Type switches the display unit of type metrics: points by /// default, pixels by the document's resolution, or millimetres. #[test] diff --git a/crates/ui-egui/src/vector_ui.rs b/crates/ui-egui/src/vector_ui.rs index e9d998b046..3b4f21895a 100644 --- a/crates/ui-egui/src/vector_ui.rs +++ b/crates/ui-egui/src/vector_ui.rs @@ -931,7 +931,8 @@ pub(crate) fn draw_handle(painter: &egui::Painter, anchor: Pos2, handle: Pos2, a /// Work path / active shape path outlines, anchors, and the pen path in progress. pub fn draw_overlay(app: &PhotocraftApp, painter: &egui::Painter, xf: &ViewXform, doc: &Document) { let tool = app.ui.tool; - let vector_tool = matches!(tool, Tool::Pen | Tool::FreeformPen | Tool::CurvaturePen | Tool::PathSelection) || is_shape_tool(tool); + // The Type tool shows the paths too: a click on one starts type on it (Photoshop). + let vector_tool = matches!(tool, Tool::Pen | Tool::FreeformPen | Tool::CurvaturePen | Tool::PathSelection | Tool::Type) || is_shape_tool(tool); let accent = Tokens::get(painter.ctx()).accent; let to_scr = |q: [f64; 2]| xf.to_screen(q[0] as f32, q[1] as f32); let draw_path = |p: &Path, anchors: bool| { diff --git a/docs/gaps.md b/docs/gaps.md index 25a286aab8..4aaba8506f 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -1,6 +1,6 @@ # Where PhotoCraft falls short of Photoshop -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (G7: Field Blur's on-canvas pins, #3373; G4: Hue Jitter amount track, #3825 partial; G4: minimum field interaction, #3825 partial; G1: CMYK Invert, Posterize and Threshold on the inks, 16-bit Lab Invert; Photoshop-oracle floor 145/258; G11: `.atn` import maps 112 events: Select and Mask, Filter Gallery Spatter, Ripple, Crystallize, Pattern Fill layers, mask targeting, Step Backward, nudges in points, Bitmap mode; G5: Free Transform H/V skew fields; G9: toolbar tool shortcut editing; refreshed against main: G2, G5, G7, G9, G10, G13, G14, G17 items that landed; G10: DPX read and write, #1049; G4: checkbox-only Brush Settings rows; G11: Load/Save Actions `.atn` landed, AUTO-219-2 partial; G5: hold-a-key spring-loaded tools landed, #2672; G1: #2465 fixed) · **Target:** Adobe Photoshop 2026 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (G8: a Type tool click on a path starts path type, TYPE-218-1 partial; G7: Field Blur's on-canvas pins, #3373; G4: Hue Jitter amount track, #3825 partial; G4: minimum field interaction, #3825 partial; G1: CMYK Invert, Posterize and Threshold on the inks, 16-bit Lab Invert; Photoshop-oracle floor 145/258; G11: `.atn` import maps 112 events: Select and Mask, Filter Gallery Spatter, Ripple, Crystallize, Pattern Fill layers, mask targeting, Step Backward, nudges in points, Bitmap mode; G5: Free Transform H/V skew fields; G9: toolbar tool shortcut editing; refreshed against main: G2, G5, G7, G9, G10, G13, G14, G17 items that landed; G10: DPX read and write, #1049; G4: checkbox-only Brush Settings rows; G11: Load/Save Actions `.atn` landed, AUTO-219-2 partial; G5: hold-a-key spring-loaded tools landed, #2672; G1: #2465 fixed) · **Target:** Adobe Photoshop 2026 Every known shortfall against Photoshop 2026 (27.11.0), one entry each, ranked by user impact on the way to beta. This is the work list agents pick from: take the highest-ranked entry that @@ -78,7 +78,9 @@ what an agent can't finish alone. - **Estimate:** 80–140 h. ### G8. Type depth -- **Missing:** type on a path (TYPE-218-1), paragraph direction UI (TYPE-218-2), complex-script +- **Missing:** the rest of type on a path (TYPE-218-1: glyphs along the curve, `type.attachToPath` and + the Type tool's click on a shown path landed; the caret on the curve, dragging the start, vertical + path type and PSD path text remain), paragraph direction UI (TYPE-218-2), complex-script fallback fonts and golden tests (TYPE-218-3), OpenType Numerals and Stylistic Sets submenus (TYPE-218-5; the Character panel buttons landed 2026-10-11, #3752), live Warp Text preview (TYPE-218-7, #2689), "rasterize and continue" for distort/perspective on type @@ -263,6 +265,7 @@ what an agent can't finish alone. | Date | Change | Summary | |---|---|---| +| 2026-10-13 | minor | G8: a Type tool click on the work path or the active shape layer's path starts type on it at the point clicked (TYPE-218-1, partial); no readiness percentage or estimate changed | | 2026-10-13 | minor | G4: Hue Jitter amount-track appearance (#3825, partial); no readiness percentage or estimate changed | | 2026-10-13 | minor | G4: minimum inputs in Shape Dynamics and Transfer follow their Control (#3825, partial); no readiness percentage or estimate changed | | 2026-10-12 | minor | G5: Free Transform options bar H/V skew fields done | diff --git a/docs/scorecard.md b/docs/scorecard.md index 92f9315ebd..b9a02ae955 100644 --- a/docs/scorecard.md +++ b/docs/scorecard.md @@ -210,7 +210,7 @@ Unread: `general.color_picker`, `general.animated_zoom`, `general.zoom_resizes_w | Id | Target | Status | Issue | Note | |---|---|---|---|---| -| TYPE-218-1 | Type on a path: path shape, glyphs along the curve, caret follows, PSD round trip | partial | [#218](https://github.com/storytold/photocraft/issues/218) | TextShape::Path (doc text.rs) with glyphs placed rigidly along the curve by arc length (text warp.rs PathFrame), type.attachToPath and type.detachFromPath (#4365), .pcraft round trip. Missing: clicking a path with the Type tool, the caret and selection following the curve, and PSD path text (it saves as point text) | +| TYPE-218-1 | Type on a path: path shape, glyphs along the curve, caret follows, PSD round trip | partial | [#218](https://github.com/storytold/photocraft/issues/218) | TextShape::Path (doc text.rs) with glyphs placed rigidly along the curve by arc length (text warp.rs PathFrame), type.attachToPath and type.detachFromPath (#4365), .pcraft round trip; a Type tool click on the work path or the active shape layer's path starts path type at the point clicked (type.attachToPath at, path-type pointer over a path). Missing: vertical type on a path, dragging the start/flip, the caret and selection following the curve, and PSD path text (it saves as point text) | | TYPE-218-2 | Paragraph direction (Auto/LTR/RTL) in the panel and options bar; visual RTL caret | missing | [#218](https://github.com/storytold/photocraft/issues/218) | Engine has TextDirection (text.rs:155); no UI | | TYPE-218-3 | Thai/Khmer/Indic fallback fonts; golden shaping tests for complex scripts and RTL | partial | [#218](https://github.com/storytold/photocraft/issues/218) | Fallback fonts are covered for Thai, Khmer, Lao, Myanmar (THAI_FAMILIES, KHMER_FAMILIES, LAO_FAMILIES, MYANMAR_FAMILIES in fonts.rs; #1909, #3259), the nine Indic scripts (INDIC_FAMILIES: Nirmala UI, Kohinoor and Sangam MN, Noto Sans) and Arabic and Hebrew. Still missing: per-script golden shaping tests against reference output (only smoke tests that an installed font shapes without .notdef, plus a bidi test) | | TYPE-218-4 | Variable font axis sliders from the font's axes; opsz follows the point size | done | [#218](https://github.com/storytold/photocraft/issues/218) | One slider per axis of the style's font in the Character section (Properties and Window > Character), setting type.setStyle variations; opsz follows the type size unless set (layout.rs variation_list); a style or weight resets the wght axis (#2985). PSD does not round-trip axis values yet | diff --git a/scorecard/type.toml b/scorecard/type.toml index ee8a876a41..dc15b5bb32 100644 --- a/scorecard/type.toml +++ b/scorecard/type.toml @@ -9,7 +9,7 @@ id = "TYPE-218-1" target = "Type on a path: path shape, glyphs along the curve, caret follows, PSD round trip" status = "partial" issue = 218 -note = "TextShape::Path (doc text.rs) with glyphs placed rigidly along the curve by arc length (text warp.rs PathFrame), type.attachToPath and type.detachFromPath (#4365), .pcraft round trip. Missing: clicking a path with the Type tool, the caret and selection following the curve, and PSD path text (it saves as point text)" +note = "TextShape::Path (doc text.rs) with glyphs placed rigidly along the curve by arc length (text warp.rs PathFrame), type.attachToPath and type.detachFromPath (#4365), .pcraft round trip; a Type tool click on the work path or the active shape layer's path starts path type at the point clicked (type.attachToPath at, path-type pointer over a path). Missing: vertical type on a path, dragging the start/flip, the caret and selection following the curve, and PSD path text (it saves as point text)" [[item]] id = "TYPE-218-2" From 5f661d3ee35803544dafc0819e0ff5df8d825887 Mon Sep 17 00:00:00 2001 From: Florian Meyer <33363574+alloutflo@users.noreply.github.com> Date: Wed, 14 Oct 2026 15:39:02 +0200 Subject: [PATCH 11/44] fix(ui): keep type panels editable without a text layer Persist Character and Paragraph formatting as Type tool defaults and apply it through type.create. Preserve selected-text scope, foreground color, undo, reset, and nonblocking font loading. Fixes #5556 Co-Authored-By: Codex --- crates/ui-egui/src/props_layout_tests.rs | 12 +- crates/ui-egui/src/state.rs | 6 + crates/ui-egui/src/tool_reset.rs | 4 +- crates/ui-egui/src/type_tool.rs | 168 +++++++++++++--- crates/ui-egui/src/type_tool_tests.rs | 233 +++++++++++++++++++++++ docs/gaps.md | 6 +- docs/ui-parity.md | 4 +- 7 files changed, 404 insertions(+), 29 deletions(-) diff --git a/crates/ui-egui/src/props_layout_tests.rs b/crates/ui-egui/src/props_layout_tests.rs index 74fd29121a..62e9423f44 100644 --- a/crates/ui-egui/src/props_layout_tests.rs +++ b/crates/ui-egui/src/props_layout_tests.rs @@ -231,10 +231,16 @@ fn character_and_paragraph_dock_panels_share_the_headers_without_type_options() let names: Vec<&str> = hs.iter().map(|(n, _)| n.as_str()).collect(); assert_eq!(names, [want], "tab {tab}"); } - // No type layer: the empty-state message, no sections. + // No type layer: the same dedicated sections edit Type tool defaults (#5556). select(&mut h, px); - assert!(headers(&h).is_empty()); - assert!(h.query_by_value("Select a type layer to edit its character and paragraph settings.").is_some()); + for (tab, want) in [(0, "Character"), (1, "Paragraph")] { + set(&mut h, json!({"dockTabs": {"character": tab}})); + let hs = headers(&h); + assert!(hs.iter().all(|(_, r)| (r.height() - SECTION_H).abs() < 0.5), "{hs:?}"); + let names: Vec<&str> = hs.iter().map(|(n, _)| n.as_str()).collect(); + assert_eq!(names, [want], "default tab {tab}"); + } + assert!(h.query_by_value("Select a type layer to edit its character and paragraph settings.").is_none()); } /// #2085: like Photoshop, Opacity and Fill stack in one right-aligned column: both fields end at diff --git a/crates/ui-egui/src/state.rs b/crates/ui-egui/src/state.rs index 42997fe62c..13b260b5a9 100644 --- a/crates/ui-egui/src/state.rs +++ b/crates/ui-egui/src/state.rs @@ -535,6 +535,11 @@ pub struct ToolOptions { pub type_size: f32, pub type_aa: String, pub type_align: String, + /// Character/Paragraph panel defaults beyond the options bar, in `type.setStyle`'s + /// parameter format. Kept as overrides so saved engine defaults still supply unset fields. + /// OpenType features and variable-font axes are merged per tag/axis. + #[serde(default)] + pub type_style_defaults: serde_json::Map, /// Clone Stamp / Healing Brush. pub clone_aligned: bool, /// Pattern Stamp: lock the tile origin in document space across strokes. @@ -772,6 +777,7 @@ impl Default for ToolOptions { type_size: 48.0, type_aa: "sharp".into(), type_align: "left".into(), + type_style_defaults: serde_json::Map::new(), clone_aligned: true, pattern_stamp_aligned: true, pattern_stamp_impressionist: false, diff --git a/crates/ui-egui/src/tool_reset.rs b/crates/ui-egui/src/tool_reset.rs index 8de3fe4ec7..049a4ad5d7 100644 --- a/crates/ui-egui/src/tool_reset.rs +++ b/crates/ui-egui/src/tool_reset.rs @@ -149,7 +149,9 @@ pub fn reset_options(o: &mut ToolOptions, tool: Tool) { Tool::MagicEraser => reset!(tolerance, anti_alias, contiguous, sample_all_layers, magic_eraser_opacity), Tool::Gradient => reset!(gradient_style, gradient_reverse, gradient_dither, gradient_classic, gradient_blend_mode, fill_opacity), Tool::PaintBucket => reset!(bucket_fill_pattern, fill_opacity, tolerance, anti_alias, contiguous, sample_all_layers), - Tool::Type | Tool::VerticalType | Tool::HorizontalTypeMask | Tool::VerticalTypeMask => reset!(type_font, type_style, type_size, type_aa, type_align), + Tool::Type | Tool::VerticalType | Tool::HorizontalTypeMask | Tool::VerticalTypeMask => { + reset!(type_font, type_style, type_size, type_aa, type_align, type_style_defaults) + } Tool::Zoom => reset!(zoom_scrubby), Tool::SpotHealing => reset!(spot_type, sample_all_layers), Tool::Healing => reset!(clone_aligned, clone_sample), diff --git a/crates/ui-egui/src/type_tool.rs b/crates/ui-egui/src/type_tool.rs index f00fb57157..27a6d495e1 100644 --- a/crates/ui-egui/src/type_tool.rs +++ b/crates/ui-egui/src/type_tool.rs @@ -304,15 +304,9 @@ pub fn pointer_up(app: &mut PhotocraftApp, start: [f64; 2], end: [f64; 2]) { // Preferences ▸ Type: "Fill new type layers with placeholder text" (on by default). The Type // Mask tools start empty, as Photoshop's do. let text = if !mask && app.session.prefs().type_.fill_new_type_layers_with_placeholder { PLACEHOLDER } else { "" }; - let mut p = json!({ - "text": text, - "orientation": if app.ui.tool.is_vertical_type() { "vertical" } else { "horizontal" }, - "font": o.type_font, - "fontStyle": o.type_style, - "size": o.type_size, - "align": o.type_align, - "color": hex(app.session.tools.foreground), - }); + let mut p = default_style_params(app); + p["text"] = json!(text); + p["orientation"] = json!(if app.ui.tool.is_vertical_type() { "vertical" } else { "horizontal" }); if w >= min && h >= min { p["box"] = json!([start[0].min(end[0]).round(), start[1].min(end[1]).round(), w.round(), h.round()]); } else { @@ -1637,9 +1631,133 @@ fn drag_key(ctx: &egui::Context) -> Option { Some(format!("type-drag-{}-{}", id.value(), t.to_bits())) } -/// Apply only the supplied properties to the text-edit scope or selected type layers. +/// The styles a new Type-tool layer will receive. Use the same command parameters for the +/// panels and creation so defaults also survive action recording/replay. The older options-bar +/// fields remain authoritative, and text colour continues to follow the foreground colour. +fn default_style_params(app: &PhotocraftApp) -> serde_json::Value { + let o = &app.ui.tool_options; + let mut p = json!({}); + // Settings are editable JSON: only recognized formatting keys may reach type.create. + // In particular a stray `text`, `box`, or `coalesce` key must not become a create parameter. + let (mut c, mut para) = (photocraft_doc::text::CharStyle::default(), photocraft_doc::text::ParagraphStyle::default()); + for (key, value) in &o.type_style_defaults { + // These belong to the existing options-bar fields. Apart from being redundant, + // parsing a saved font key here would wait for the background font scan. + if matches!(key.as_str(), "font" | "family" | "fontStyle" | "weight" | "italic" | "postscriptName") { + continue; + } + let one = json!({ (key): value }); + if !finite_style_value(value) + || photocraft_engine::type_cmds::check_size_tracking(&one).is_err() + || photocraft_engine::type_cmds::check_kerning(&one).is_err() + { + continue; + } + if photocraft_engine::type_cmds::apply_char_props(&mut c, &one) | photocraft_engine::type_cmds::apply_para_props(&mut para, &one) { + p[key] = value.clone(); + } + } + p["font"] = json!(o.type_font); + p["fontStyle"] = json!(o.type_style); + p["size"] = json!(o.type_size); + p["align"] = json!(o.type_align); + p["color"] = json!(hex(app.session.tools.foreground)); + p +} + +/// Style numbers become f32 in the engine. JSON itself permits finite f64 values too large +/// for that conversion; a hand-edited setting must not inject infinity into a new layer. +/// Style collections (features, axes, colour) contain only scalar values, so no recursion. +fn finite_style_value(value: &serde_json::Value) -> bool { + let scalar = |v: &serde_json::Value| match v { + serde_json::Value::Number(n) => n.as_f64().is_some_and(|n| (n as f32).is_finite()), + serde_json::Value::Array(_) | serde_json::Value::Object(_) => false, + _ => true, + }; + match value { + serde_json::Value::Array(v) => v.iter().all(scalar), + serde_json::Value::Object(v) => v.values().all(scalar), + v => scalar(v), + } +} + +fn default_styles(app: &PhotocraftApp) -> (photocraft_doc::text::CharStyle, photocraft_doc::text::ParagraphStyle) { + let (mut c, mut para) = app.session.type_defaults.clone().unwrap_or_default(); + let mut p = default_style_params(app); + // The saved panels can appear on the very first frame. Style parsing resolves the named + // font through shared(), which waits for the font scan; display the saved names meanwhile, + // and let fonts_ready/axis_sliders expose the font controls when try_shared is ready. + if photocraft_text::try_shared().is_none() { + c.font_family = app.ui.tool_options.type_font.clone(); + c.font_style = app.ui.tool_options.type_style.clone(); + c.postscript_name = None; + if let Some(p) = p.as_object_mut() { + p.remove("font"); + p.remove("fontStyle"); + } + } + photocraft_engine::type_cmds::apply_char_props(&mut c, &p); + photocraft_engine::type_cmds::apply_para_props(&mut para, &p); + (c, para) +} + +/// Change tool defaults without creating a document edit. The engine owns the style schema; +/// keep its property names rather than maintaining another style parser in the shell. +fn apply_defaults(app: &mut PhotocraftApp, props: &serde_json::Value) { + let Some(props) = props.as_object() else { return }; + let o = &mut app.ui.tool_options; + for (key, value) in props { + match key.as_str() { + "font" => { + if let Some(v) = value.as_str() { + o.type_font = v.into(); + } + } + "fontStyle" => { + if let Some(v) = value.as_str() { + o.type_style = v.into(); + // A newly chosen named style replaces a weight-axis setting, as it does + // in apply_char_props. Other axes retain their independently chosen values. + if let Some(axes) = o.type_style_defaults.get_mut("variations").and_then(serde_json::Value::as_object_mut) { + axes.remove("wght"); + } + } + } + "size" => { + if let Some(v) = value.as_f64() { + o.type_size = v as f32; + } + } + "align" => { + if let Some(v) = value.as_str() { + o.type_align = v.into(); + } + } + "metricsAsShown" => {} // Defaults have no layer transform. + "features" | "variations" => { + if let Some(patch) = value.as_object() { + let old = o.type_style_defaults.entry(key.clone()).or_insert_with(|| json!({})); + if let Some(old) = old.as_object_mut() { + old.extend(patch.clone()); + } else { + *old = value.clone(); + } + } + } + _ => { + o.type_style_defaults.insert(key.clone(), value.clone()); + } + } + } +} + +/// Apply only the supplied properties to the text-edit scope or selected type layers; without +/// a type target, prepare the defaults for the next layer instead. fn apply(app: &mut PhotocraftApp, ctx: &egui::Context, props: serde_json::Value) { - let Some(mut p) = formatting_params(app) else { return }; + let Some(mut p) = formatting_params(app) else { + apply_defaults(app, &props); + return; + }; if let Some(props) = props.as_object() { for (key, value) in props { p[key] = value.clone(); @@ -2026,7 +2144,17 @@ fn opentype_row(app: &mut PhotocraftApp, ui: &mut egui::Ui, c: &photocraft_doc:: /// characters (or its whole text) while typing, else every selected type layer. The new state /// is explicit, so it follows the button the user saw rather than the layer's first run. pub(crate) fn toggle_opentype(app: &mut PhotocraftApp, id: &str, shown_on: bool) { - let Some(mut p) = formatting_params(app) else { return }; + let Some(mut p) = formatting_params(app) else { + if let Some((tag, _, _)) = OPENTYPE_BUTTONS.iter().find(|(_, command, _)| *command == id) { + let props = match *tag { + "liga" => json!({"ligatures": !shown_on}), + "dlig" => json!({"discretionaryLigatures": !shown_on}), + _ => json!({"features": { (*tag): !shown_on }}), + }; + apply_defaults(app, &props); + } + return; + }; p["on"] = json!(!shown_on); let _ = app.run(id, p); } @@ -2093,28 +2221,22 @@ fn cell_width(avail: f32, n: usize) -> f32 { /// Properties panel sections for a type layer: Character, Paragraph and Type Options (#155). pub fn type_properties(app: &mut PhotocraftApp, ui: &mut egui::Ui) { - type_sections(app, ui, true, true); + type_sections(app, ui, true, true, false); type_options(app, ui); } /// Window › Character / Paragraph (#150): the same controls as Properties, in their own dock -/// group. Without a type layer (or a type edit) there is nothing to show. +/// group. Without a type target they edit the defaults for the next Type-tool layer. pub fn character_panel(app: &mut PhotocraftApp, ui: &mut egui::Ui, paragraph: bool) { - if styles_at(app).is_none() { - let t = crate::theme::Tokens::get(ui.ctx()); - ui.add_space(8.0); - ui.label(egui::RichText::new(tl!("Select a type layer to edit its character and paragraph settings.")).color(t.text_dim)); - return; - } - type_sections(app, ui, !paragraph, paragraph); + type_sections(app, ui, !paragraph, paragraph, true); } /// The Character and/or Paragraph sections, with the shared collapsible headers and fields that /// fill the panel width (#155). -fn type_sections(app: &mut PhotocraftApp, ui: &mut egui::Ui, character: bool, paragraph: bool) { +fn type_sections(app: &mut PhotocraftApp, ui: &mut egui::Ui, character: bool, paragraph: bool, show_defaults: bool) { use crate::props_layout::{COL_GAP, LABEL_GAP, LABEL_W, field_width, section}; use crate::theme::ROW_GAP; - let Some((c, para)) = styles_at(app) else { return }; + let Some((c, para)) = styles_at(app).or_else(|| show_defaults.then(|| default_styles(app))) else { return }; let t = crate::theme::Tokens::get(ui.ctx()); // Row helper: `ui.horizontal` with the column gap, then the shared row gap. let row = |ui: &mut egui::Ui, add: &mut dyn FnMut(&mut egui::Ui)| { diff --git a/crates/ui-egui/src/type_tool_tests.rs b/crates/ui-egui/src/type_tool_tests.rs index 0f5be9e2fa..6246257fc2 100644 --- a/crates/ui-egui/src/type_tool_tests.rs +++ b/crates/ui-egui/src/type_tool_tests.rs @@ -73,6 +73,239 @@ fn numeric_type_fields_have_localized_accessible_names() { assert_eq!(*h.state(), 36.0); } +/// #5556: the dedicated panels remain usable before there is any type layer, including +/// before opening a document. Properties still belongs to the selected layer. +#[test] +fn type_panels_show_default_controls_without_a_type_target() { + for app in [PhotocraftApp::new(photocraft_engine::Session::new(), crate::Services::default()), new_app()] { + let mut h = Harness::builder().with_size(vec2(760.0, 800.0)).build_ui_state( + |ui, app: &mut PhotocraftApp| { + if !ui.ctx().fonts(|f| f.families().contains(&egui::FontFamily::Name("medium".into()))) { + return; + } + ui.columns(2, |columns| { + super::character_panel(app, &mut columns[0], false); + super::character_panel(app, &mut columns[1], true); + }); + }, + app, + ); + PhotocraftApp::setup_context(&h.ctx, Default::default()); + h.run_steps(4); + h.get_by_label("Faux Bold"); + h.get_by_label("Fractions"); + h.get_by_label("Center text"); + h.get_by_label("Hyphenate"); + h.get_by_role_and_label(egui::accesskit::Role::SpinButton, "Font size"); + } +} + +mod panel_defaults { + use super::*; + use crate::state::{Tool, ToolOptions}; + use egui::accesskit::{Role, Toggled}; + use egui_kittest::kittest::NodeT; + use photocraft_doc::text::{Caps, Kerning, Orientation, TextAlign, TextShape}; + + fn panels(app: PhotocraftApp) -> Harness<'static, PhotocraftApp> { + let mut h = Harness::builder().with_size(vec2(760.0, 800.0)).build_ui_state( + |ui, app: &mut PhotocraftApp| { + if ui.ctx().fonts(|f| f.families().contains(&egui::FontFamily::Name("medium".into()))) { + ui.columns(2, |columns| { + super::super::character_panel(app, &mut columns[0], false); + super::super::character_panel(app, &mut columns[1], true); + }); + } + }, + app, + ); + PhotocraftApp::setup_context(&h.ctx, Default::default()); + h.run_steps(4); + h + } + + fn number(h: &mut Harness<'static, PhotocraftApp>, label: &str, value: &str) { + h.get_by_role_and_label(Role::SpinButton, label).click(); + h.run(); + h.key_press_modifiers(Modifiers::COMMAND, egui::Key::A); + h.event(egui::Event::Text(value.into())); + h.key_press(egui::Key::Enter); + h.run(); + } + + #[test] + fn panel_defaults_are_editable_and_reach_every_type_tool() { + let mut h = panels(new_app()); + let revision = h.state().session.active().unwrap().revision; + let layers = h.state().session.active().unwrap().doc.walk().len(); + number(&mut h, "Font size", "36"); + number(&mut h, "Tracking (1/1000 em)", "80"); + number(&mut h, "Leading (set to the font size × auto-leading when Auto)", "44"); + number(&mut h, "Indent left margin", "9"); + for label in ["Faux Bold", "Fractions", "Standard Ligatures", "Discretionary Ligatures", "Center text", "Hyphenate"] { + h.get_by_label(label).click(); + h.run(); + } + assert_eq!(h.get_by_label("Fractions").accesskit_node().toggled(), Some(Toggled::True)); + assert_eq!(h.get_by_label("Standard Ligatures").accesskit_node().toggled(), Some(Toggled::False)); + assert_eq!(h.state().session.active().unwrap().revision, revision, "pre-styling does not create document history"); + assert_eq!(h.state().session.active().unwrap().doc.walk().len(), layers); + let options = h.state().ui.tool_options.clone(); + for tool in [Tool::Type, Tool::VerticalType, Tool::HorizontalTypeMask, Tool::VerticalTypeMask] { + for paragraph in [false, true] { + let mut app = new_app(); + app.ui.tool_options = options.clone(); + app.ui.tool = tool; + app.session.tools.foreground = [0.2, 0.4, 0.6, 1.0]; + let revision = app.session.active().unwrap().revision; + super::super::pointer_up(&mut app, [100.0, 100.0], if paragraph { [500.0, 400.0] } else { [100.0, 100.0] }); + let id = LayerId(app.ui.text_edit.as_ref().unwrap().layer); + let t = text(&app, id); + let c = &t.char_runs()[0].style; + let p = &t.paragraph_runs()[0].style; + assert_eq!(c.size_pt, 36.0); + assert_eq!(c.tracking, 80.0); + assert_eq!(c.leading_pt, Some(44.0)); + assert!(c.faux_bold && c.discretionary_ligatures && !c.ligatures); + assert!(photocraft_engine::type_extra_cmds::feature_on(c, "frac")); + assert_eq!(c.color.to_rgba8(), [51, 102, 153, 255]); + assert_eq!(p.align, TextAlign::Center); + assert_eq!(p.start_indent_pt, 9.0); + assert!(p.hyphenate); + assert_eq!(t.orientation, if tool.is_vertical_type() { Orientation::Vertical } else { Orientation::Horizontal }); + assert_eq!(matches!(t.shape, TextShape::Box { .. }), paragraph); + assert_eq!(app.ui.text_edit.as_ref().unwrap().mask, tool.is_type_mask()); + assert!(app.session.active().unwrap().revision > revision); + super::super::cancel(&mut app); + assert!(app.session.active().unwrap().doc.layer(id).is_none(), "creating and formatting is one undoable edit"); + assert_eq!(app.ui.tool_options, options, "cancelling text does not discard the chosen defaults"); + } + } + } + + #[test] + fn extended_defaults_merge_serialize_and_reset_without_replacing_legacy_options() { + let mut app = PhotocraftApp::new(photocraft_engine::Session::new(), crate::Services::default()); + let ctx = egui::Context::default(); + super::super::apply( + &mut app, + &ctx, + json!({ + "fontStyle": "Bold", "size": 32, "kerning": 25, "caps": "smallCaps", + "baselineShift": 3, "horizontalScale": 90, "verticalScale": 110, + "underline": true, "strikethrough": true, "fauxItalic": true, + "firstLineIndent": 5, "endIndent": 7, "spaceBefore": 11, "spaceAfter": 13, + "features": {"swsh": 1}, "variations": {"wght": 650, "wdth": 90} + }), + ); + super::super::apply(&mut app, &ctx, json!({"features": {"frac": 1}, "variations": {"slnt": -8}})); + let (c, p) = super::super::default_styles(&app); + assert_eq!((c.size_pt, c.kern, c.kerning, c.caps), (32.0, 25.0, Kerning::Off, Caps::SmallCaps)); + assert_eq!((c.baseline_shift_pt, c.horizontal_scale, c.vertical_scale), (3.0, 0.9, 1.1)); + assert!(c.underline && c.strikethrough && c.faux_italic); + assert_eq!((p.first_line_indent_pt, p.end_indent_pt, p.space_before_pt, p.space_after_pt), (5.0, 7.0, 11.0, 13.0)); + assert_eq!(c.features.iter().map(|f| f.tag.as_str()).collect::>(), ["frac", "swsh"]); + assert_eq!(c.variations.iter().map(|v| (v.axis.as_str(), v.value)).collect::>(), [("slnt", -8.0), ("wdth", 90.0), ("wght", 650.0)]); + // A named style replaces wght; it must not come back when a later layer is created. + super::super::apply(&mut app, &ctx, json!({"fontStyle": "Regular"})); + let (c, _) = super::super::default_styles(&app); + assert!(!c.variations.iter().any(|v| v.axis == "wght")); + assert_eq!(c.variations.len(), 2); + let saved = serde_json::to_value(&app.ui.tool_options).unwrap(); + let restored: ToolOptions = serde_json::from_value(saved.clone()).unwrap(); + assert_eq!(restored, app.ui.tool_options); + crate::tool_memory::persist(&mut app, &ctx); + let remembered = app.session.prefs().tool_options.clone(); + let mut restarted = PhotocraftApp::new(photocraft_engine::Session::new(), crate::Services::default()); + restarted.session.prefs.edit(|p| p.tool_options = remembered); + crate::tool_memory::restore(&mut restarted); + assert_eq!(restarted.ui.tool_options, restored, "the real preference restore path retains panel defaults"); + let mut legacy = saved; + legacy.as_object_mut().unwrap().remove("type_style_defaults"); + let legacy: ToolOptions = serde_json::from_value(legacy).unwrap(); + assert_eq!((legacy.type_font.as_str(), legacy.type_style.as_str(), legacy.type_size), ("Inter", "Regular", 32.0)); + assert!(legacy.type_style_defaults.is_empty()); + for tool in [Tool::Type, Tool::VerticalType, Tool::HorizontalTypeMask, Tool::VerticalTypeMask] { + let mut reset = restored.clone(); + crate::tool_reset::reset_options(&mut reset, tool); + assert!(reset.type_style_defaults.is_empty()); + assert_eq!(reset.type_size, ToolOptions::default().type_size); + } + } + + #[test] + fn selected_text_edits_leave_panel_defaults_and_other_characters_untouched() { + let mut app = new_app(); + let ctx = egui::Context::default(); + super::super::apply(&mut app, &ctx, json!({"tracking": 80, "fauxBold": true})); + let defaults = app.ui.tool_options.type_style_defaults.clone(); + let id = LayerId(app.run("type.create", json!({"text": "AéBC", "size": 24})).unwrap()["layer"].as_u64().unwrap()); + app.ui.text_edit = Some(crate::state::TextEdit { + layer: id.0, + caret: 2, + anchor: 1, + session: "panel-default-selection".into(), + created: false, + mask: false, + dragging: false, + resize: None, + preedit: None, + }); + super::super::apply(&mut app, &ctx, json!({"tracking": 15})); + super::super::toggle_opentype(&mut app, "type.openType.fractions", false); + let runs = text(&app, id).char_runs(); + assert_eq!(runs.iter().map(|r| (r.len, r.style.tracking)).collect::>(), [(1, 0.0), (2, 15.0), (2, 0.0)]); + assert!(photocraft_engine::type_extra_cmds::feature_on(&runs[1].style, "frac")); + assert!(!photocraft_engine::type_extra_cmds::feature_on(&runs[0].style, "frac")); + assert_eq!(app.ui.tool_options.type_style_defaults, defaults); + app.run("edit.undo", json!({})).unwrap(); + assert_eq!(text(&app, id).char_runs().len(), 1, "selection edits keep their typing-session coalescing"); + assert_eq!(text(&app, id).char_runs()[0].style.tracking, 0.0); + } + + #[test] + fn default_text_color_uses_foreground_and_cancel_preserves_it() { + let mut app = new_app(); + let before = app.session.tools.foreground; + let revision = app.session.active().unwrap().revision; + let dialog = super::super::open_color_picker(&mut app, [0.0; 3]); + app.ui.dialogs.last_mut().unwrap().fields.insert("color".into(), json!("#ff8800")); + crate::dialogs::cancel(&mut app, dialog).unwrap(); + assert_eq!(app.session.tools.foreground, before); + let dialog = super::super::open_color_picker(&mut app, [0.0; 3]); + app.ui.dialogs.last_mut().unwrap().fields.insert("color".into(), json!("#336699")); + crate::dialogs::confirm(&mut app, dialog).unwrap(); + assert_eq!(super::super::default_styles(&app).0.color.to_rgba8(), [51, 102, 153, 255]); + assert_eq!(app.session.active().unwrap().revision, revision); + super::super::pointer_up(&mut app, [100.0, 100.0], [100.0, 100.0]); + let id = LayerId(app.ui.text_edit.as_ref().unwrap().layer); + assert_eq!(text(&app, id).char_runs()[0].style.color.to_rgba8(), [51, 102, 153, 255]); + } + + #[test] + fn unknown_saved_defaults_cannot_become_creation_parameters() { + let mut app = new_app(); + app.ui.tool_options.type_style_defaults = + json!({"tracking": 20, "text": "Injected", "box": [0, 0, 2, 2], "coalesce": "unrelated"}).as_object().unwrap().clone(); + let p = super::super::default_style_params(&app); + assert_eq!(p["tracking"], 20); + assert!(p.get("text").is_none() && p.get("box").is_none() && p.get("coalesce").is_none()); + app.ui.tool_options.type_style_defaults = json!({ + "tracking": 1e300, "leading": 1e300, "spaceAfter": 1e300, + "variations": {"wght": 1e300}, "features": {"frac": {"bad": 1}}, "fauxBold": true + }) + .as_object() + .unwrap() + .clone(); + let p = super::super::default_style_params(&app); + for key in ["tracking", "leading", "spaceAfter", "variations", "features"] { + assert!(p.get(key).is_none(), "hostile {key} is ignored"); + } + let (c, para) = super::super::default_styles(&app); + assert!(c.faux_bold && c.leading_pt.is_none() && c.tracking == 0.0 && para.space_after_pt == 0.0); + } +} + fn harness(ppp: f32, app: PhotocraftApp) -> Harness<'static, PhotocraftApp> { make_harness(ppp, app, false) } diff --git a/docs/gaps.md b/docs/gaps.md index c952bac81b..bf793af8eb 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -1,6 +1,6 @@ # Where PhotoCraft falls short of Photoshop -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (G7: Field Blur's on-canvas pins, #3373; G4: Hue Jitter amount track, #3825 partial; G4: minimum field interaction, #3825 partial; G1: CMYK Invert, Posterize and Threshold on the inks, 16-bit Lab Invert; Photoshop-oracle floor 145/258; G11: `.atn` import maps 112 events: Select and Mask, Filter Gallery Spatter, Ripple, Crystallize, Pattern Fill layers, mask targeting, Step Backward, nudges in points, Bitmap mode; G5: Free Transform H/V skew fields; G9: toolbar tool shortcut editing; refreshed against main: G2, G5, G7, G9, G10, G13, G14, G17 items that landed; G10: DPX read and write, #1049; G4: checkbox-only Brush Settings rows; G11: Load/Save Actions `.atn` landed, AUTO-219-2 partial; G5: hold-a-key spring-loaded tools landed, #2672; G1: #2465 fixed; stale missing-tool rows corrected: Color Replacement, Red Eye and Add/Delete Anchor Point are live tools, so only Art History Brush is missing; no estimate changed) · **Target:** Adobe Photoshop 2026 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** minor (Character/Paragraph defaults before creating text, #5556; G7: Field Blur's on-canvas pins, #3373; G4: Hue Jitter amount track, #3825 partial; G4: minimum field interaction, #3825 partial; G1: CMYK Invert, Posterize and Threshold on the inks, 16-bit Lab Invert; Photoshop-oracle floor 145/258; G11: `.atn` import maps 112 events: Select and Mask, Filter Gallery Spatter, Ripple, Crystallize, Pattern Fill layers, mask targeting, Step Backward, nudges in points, Bitmap mode; G5: Free Transform H/V skew fields; G9: toolbar tool shortcut editing; refreshed against main: G2, G5, G7, G9, G10, G13, G14, G17 items that landed; G10: DPX read and write, #1049; G4: checkbox-only Brush Settings rows; G11: Load/Save Actions `.atn` landed, AUTO-219-2 partial; G5: hold-a-key spring-loaded tools landed, #2672; G1: #2465 fixed; stale missing-tool rows corrected: Color Replacement, Red Eye and Add/Delete Anchor Point are live tools, so only Art History Brush is missing; no estimate changed) · **Target:** Adobe Photoshop 2026 Every known shortfall against Photoshop 2026 (27.11.0), one entry each, ranked by user impact on the way to beta. This is the work list agents pick from: take the highest-ranked entry that @@ -190,6 +190,9 @@ what an agent can't finish alone. - **Estimate:** 60–110 h. ### G9. Panels, preferences and workspace +- **Progress (2026-10-14, #5556):** Character and Paragraph controls stay available without + a type target, editing persisted Type tool defaults inherited by new text. Formatting existing + selected text keeps its current command and undo scope. Other panel gaps remain open. - **Progress (2026-10-12):** toolbar tool shortcuts can be reassigned, removed and reset (#4086), with saved bindings, matching toolbar hints and regression coverage (`ui-egui/src/tool_shortcuts.rs`). - **Missing:** 27 of 156 preferences do nothing (scorecard prefs audit); panel groups can't @@ -262,6 +265,7 @@ what an agent can't finish alone. | Date | Change | Summary | |---|---|---| +| 2026-10-14 | minor | G9: Character/Paragraph defaults before creating text (#5556); no percentage or estimate changed | | 2026-10-13 | minor | G6: stale "missing tool" list corrected against `main` — Color Replacement (`paint.colorReplacement`), Red Eye (`paint.redEye`) and Add/Delete Anchor Point (P flyout, #4144) are live tools, so the toolbox is 67 of 68 and Art History Brush is the only one missing; no percentage or estimate changed | | 2026-10-13 | minor | G4: Hue Jitter amount-track appearance (#3825, partial); no readiness percentage or estimate changed | | 2026-10-13 | minor | G4: minimum inputs in Shape Dynamics and Transfer follow their Control (#3825, partial); no readiness percentage or estimate changed | diff --git a/docs/ui-parity.md b/docs/ui-parity.md index 5abc8727a4..c460b08933 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -1,6 +1,6 @@ # UI parity: tools, handles, modifiers and feel -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (smart filter reordering by drag; smart object perspective row corrected to done, #426; Free Transform H/V skew fields; toolbar tool shortcuts can be reassigned; ⇧-dragged guides land on ruler ticks; layer and selection keys; tool keys; spring-loaded tool letters; type formatting keys; Pen Auto Add/Delete, #4144; smart filter rows; stale missing-tool rows corrected: Color Replacement, Red Eye and the Add/Delete Anchor Point pair are live toolbar tools, Frame landed; no percentage changed) · **Target:** Adobe Photoshop 2026 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** minor (Character/Paragraph defaults before creating text, #5556; smart filter reordering by drag; smart object perspective row corrected to done, #426; Free Transform H/V skew fields; toolbar tool shortcuts can be reassigned; ⇧-dragged guides land on ruler ticks; layer and selection keys; tool keys; spring-loaded tool letters; type formatting keys; Pen Auto Add/Delete, #4144; smart filter rows; stale missing-tool rows corrected: Color Replacement, Red Eye and the Add/Delete Anchor Point pair are live toolbar tools, Frame landed; no percentage changed) · **Target:** Adobe Photoshop 2026 How PhotoCraft's direct manipulation compares with Photoshop 2026 (27.11.0): the toolbox, the handles and modifier keys of each gesture, nudging, snapping, numeric precision, navigation, @@ -198,6 +198,7 @@ Paragraph; the rest open as in-app floating windows. | Behaviour | Status | Evidence | |---|---|---| +| Character and Paragraph controls without a type target | done | #5556; persistent Type tool defaults feed new point, box and mask text; existing text still edits in its selected scope (`type_tool.rs`, `type_tool_tests.rs`) | | Panel groups float and tear off into OS windows | missing | UI-217-5, #1828, #1213 | | Drag panels to reorder and regroup | partial | #2272, #2305 | | Docked resize pushes the neighbour | partial | #2573 | @@ -217,6 +218,7 @@ Paragraph; the rest open as in-app floating windows. | Date | Change | Summary | |---|---|---| +| 2026-10-14 | minor | Character and Paragraph panels edit persistent defaults without a type target (#5556); no percentage or estimate changed | | 2026-10-13 | minor | Stale "missing tool" rows corrected against main: Color Replacement (`paint.colorReplacement`), Red Eye (`paint.redEye`) and Add/Delete Anchor Point (the P flyout cycle) are live toolbar tools, so only Art History Brush is missing; no percentage or estimate changed | | 2026-10-13 | minor | Smart object perspective (non-affine) corrected to done: #426 landed four-corner placements with PSD round trip; scorecard FILE-215-6 flipped | | 2026-10-10 | major | First version, from a source audit against Photoshop 2026 27.11.0's shortcuts file and the open issues | From 484b93b27febfc8583b633f1ecc3869e471b4bfa Mon Sep 17 00:00:00 2001 From: Florent Siegenthaler <64897087+Atalantis@users.noreply.github.com> Date: Wed, 14 Oct 2026 20:44:05 +0200 Subject: [PATCH 12/44] i18n(fr): drop the rewording of existing rows Revert the effect of a23f7778b: every row that main already has is back to main's text, the rows this branch adds keep the wording of the first commit, and the header note recording the exception is removed. The catalog stays clean-room as its header says. --- crates/ui-egui/src/i18n/fr.tsv | 737 ++++++++++++++++----------------- 1 file changed, 367 insertions(+), 370 deletions(-) diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index bc36ae2b30..390843e94e 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -13,9 +13,6 @@ # French computing vocabulary (calque, sélection, pinceau, dégradé, flou, opacité ...). No # proprietary localisation resource, string table or product UI was consulted or copied; in # particular not the French localisation of Adobe Photoshop. Add entries the same way. -# Exception, 2026-10: about 370 entries were realigned on the terms French retouchers use and that -# Adobe's public French help pages show (Couche, Zone de travail, Masque de fusion, Objet dynamique, -# Pixelliser, Réglage ...), so the paragraph above no longer holds for those entries. # Conventions: menus are nouns (Fichier, Édition, Image ...), commands are infinitives (Ouvrir…, # Enregistrer sous…), sentence case. French typography: a no-break space (U+00A0) before # « : ; ? ! » and "%", and inside guillemets « … ». Product and technology names (PhotoCraft, @@ -106,8 +103,8 @@ Render Video… Rendu vidéo… Generate Générer Image Assets Ressources d'image - Place Embedded… Importer et incorporer… - Place Linked… Importer et lier… + Place Embedded… Placer incorporé… + Place Linked… Placer lié… Package… Rassembler… Automate Automatisation Batch… Traitement par lots… @@ -148,11 +145,11 @@ Fade… Estomper… Cut Couper Copy Copier - Copy Merged Copier avec fusion + Copy Merged Copier les calques fusionnés Paste Coller Paste Special Collage spécial - Paste in Place Coller en place - Paste Into Coller dedans + Paste in Place Coller sur place + Paste Into Coller dans la sélection Paste Outside Coller hors de la sélection Paste Lorem Ipsum Coller du Lorem Ipsum Clear Effacer @@ -162,13 +159,13 @@ Add to Dictionary Ajouter au dictionnaire No Spelling Suggestions Aucune suggestion orthographique Fill… Remplir… - Stroke… Contour… + Stroke… Contourner… Content-Aware Fill… Remplissage d'après le contenu… Delete and Fill Selection Supprimer et remplir la sélection - Content-Aware Scale Échelle basée sur le contenu - Puppet Warp Déformation de la marionnette + Content-Aware Scale Mise à l'échelle d'après le contenu + Puppet Warp Déformation par épingles Perspective Warp Déformation de la perspective - Free Transform Transformation manuelle + Free Transform Transformation libre Transform Transformation Again Répéter Scale Mise à l'échelle @@ -218,37 +215,37 @@ Export… Exportation… Performance… Performances… Scratch Disks… Disques de travail… - Cursors… Pointeurs… - Transparency & Gamut… Transparence et couleurs non imprimables… + Cursors… Curseurs… + Transparency & Gamut… Transparence et gamut… Units & Rulers… Unités et règles… Guides, Grid & Slices… Repères, grille et tranches… - Plug-ins… Modules externes… + Plug-ins… Extensions… Type… Texte… Enhanced Controls… Commandes avancées… Camera Raw… Camera Raw… Raw Defaults… Valeurs Raw par défaut… Integrations… Intégrations… AI Integrations… Intégrations d'IA… - Install Plug-in… Installer un module externe… + Install Plug-in… Installer une extension… Language Options Options de langue Settings… Paramètres… Mode Mode Bitmap Bitmap Grayscale Niveaux de gris - Duotone Bichromie + Duotone Duotone Indexed Color Couleurs indexées RGB Color Couleurs RVB CMYK Color Couleurs CMJN Lab Color Couleurs Lab - Multichannel Multicouche - 8 Bits/Channel 8 bits/couche - 16 Bits/Channel 16 bits/couche - 32 Bits/Channel 32 bits/couche + Multichannel Multicanal + 8 Bits/Channel 8 bits/canal + 16 Bits/Channel 16 bits/canal + 32 Bits/Channel 32 bits/canal Color Table… Table des couleurs… Bitmap… Bitmap… - Duotone… Bichromie… + Duotone… Duotone… Indexed Color… Couleurs indexées… - Adjustments Réglages + Adjustments Ajustements Brightness/Contrast… Luminosité/Contraste… Levels… Niveaux… Curves… Courbes… @@ -258,33 +255,33 @@ Color Balance… Balance des couleurs… Black & White… Noir et blanc… Photo Filter… Filtre photo… - Channel Mixer… Mélangeur de couches… - Color Lookup… Correspondance des couleurs… + Channel Mixer… Mélangeur de canaux… + Color Lookup… Table de correspondance des couleurs… Invert Inverser - Posterize… Isohélie… + Posterize… Postériser… Threshold… Seuil… - Gradient Map… Courbe de transfert de dégradé… - Selective Color… Correction sélective… - Shadows/Highlights… Tons foncés/Tons clairs… - HDR Toning… Virage HDR… - Desaturate Désaturation - Match Color… Correspondance de la couleur… - Replace Color… Remplacement de couleur… + Gradient Map… Mappage de dégradé… + Selective Color… Couleur sélective… + Shadows/Highlights… Ombres/Hautes lumières… + HDR Toning… Tonalité HDR… + Desaturate Désaturer + Match Color… Faire correspondre la couleur… + Replace Color… Remplacer la couleur… Equalize Égaliser - Auto Tone Ton automatique + Auto Tone Tonalité automatique Auto Contrast Contraste automatique Auto Color Couleur automatique Image Size… Taille de l'image… - Canvas Size… Taille de la zone de travail… + Canvas Size… Taille du canevas… Image Rotation Rotation de l'image - Flip Canvas Horizontal Symétrie horizontale de la zone de travail - Flip Canvas Vertical Symétrie verticale de la zone de travail + Flip Canvas Horizontal Retourner le canevas horizontalement + Flip Canvas Vertical Retourner le canevas verticalement Crop Recadrer - Trim… Rognage… - Reveal All Tout faire apparaître + Trim… Rogner les bords… + Reveal All Tout afficher Duplicate… Dupliquer… Apply Image… Appliquer une image… - Calculations… Opérations… + Calculations… Calculs… Variables Variables Define… Définir… Data Sets… Jeux de données… @@ -296,7 +293,7 @@ Select Data Points… Sélectionner les points de données… Record Measurements Enregistrer les mesures Ruler Tool Outil Règle - Count Tool Outil Comptage + Count Tool Outil Compteur Place Scale Marker… Placer un repère d'échelle… Custom… Personnalisé… New Nouveau @@ -316,7 +313,7 @@ New Fill Layer Nouveau calque de remplissage New Adjustment Layer Nouveau calque de réglage New Layer Based Slice Nouvelle tranche d'après un calque - New Smart Object via Copy Nouvel objet dynamique par copie + New Smart Object via Copy Nouvel objet intelligent par copie New Blank Video Layer Nouveau calque vidéo vide New Video Layer from File… Nouveau calque vidéo à partir d'un fichier… Duplicate Layer… Dupliquer le calque… @@ -326,14 +323,14 @@ Create Layer Créer un calque Layer Style Style de calque Blending Options… Options de fusion… - Bevel & Emboss… Biseautage et estampage… - Inner Shadow… Ombre interne… - Inner Glow… Lueur interne… + Bevel & Emboss… Biseau et relief… + Inner Shadow… Ombre intérieure… + Inner Glow… Lueur intérieure… Satin… Satin… - Color Overlay… Incrustation couleur… - Gradient Overlay… Incrustation en dégradé… - Pattern Overlay… Incrustation de motif… - Outer Glow… Lueur externe… + Color Overlay… Superposition de couleur… + Gradient Overlay… Superposition de dégradé… + Pattern Overlay… Superposition de motif… + Outer Glow… Lueur extérieure… Drop Shadow… Ombre portée… Copy Layer Style Copier le style de calque Paste Layer Style Coller le style de calque @@ -341,35 +338,35 @@ Global Light… Lumière globale… Hide All Effects Masquer tous les effets Scale Effects… Mettre les effets à l'échelle… - Smart Filter Filtre dynamique - Convert for Smart Filters Convertir pour les filtres dynamiques - Clear Smart Filters Effacer les filtres dynamiques - Disable Smart Filters Désactiver les filtres dynamiques + Smart Filter Filtre intelligent + Convert for Smart Filters Convertir pour les filtres intelligents + Clear Smart Filters Effacer les filtres intelligents + Disable Smart Filters Désactiver les filtres intelligents Delete Filter Mask Supprimer le masque de filtre Disable Filter Mask Désactiver le masque de filtre Solid Color… Couleur unie… Gradient… Dégradé… Pattern… Motif… - Layer Mask Masque de fusion + Layer Mask Masque de calque Vector Mask Masque vectoriel - Reveal Selection Faire apparaître la sélection + Reveal Selection Afficher la sélection Hide Selection Masquer la sélection Hide All Tout masquer From Transparency D'après la transparence Delete Vector Mask Supprimer le masque vectoriel - Enable Layer Mask Activer le masque de fusion - Disable Layer Mask Désactiver le masque de fusion + Enable Layer Mask Activer le masque de calque + Disable Layer Mask Désactiver le masque de calque Enable Vector Mask Activer le masque vectoriel - Link Layer Mask Lier le masque de fusion - Unlink Layer Mask Délier le masque de fusion + Link Layer Mask Lier le masque de calque + Unlink Layer Mask Délier le masque de calque Link Vector Mask Lier le masque vectoriel Add Vector Mask Ajouter un masque vectoriel Vector Mask from Current Path Masque vectoriel d'après le tracé actif - Create Clipping Mask Créer un masque d'écrêtage - Release Clipping Mask Libérer le masque d'écrêtage - Smart Objects Objets dynamiques - Smart Object Objet dynamique - Convert to Smart Object Convertir en objet dynamique + Create Clipping Mask Créer un masque de découpe + Release Clipping Mask Libérer le masque de découpe + Smart Objects Objets intelligents + Smart Object Objet intelligent + Convert to Smart Object Convertir en objet intelligent Convert to Embedded Convertir en objet incorporé Convert to Layers Convertir en calques Convert to Linked… Convertir en objet lié… @@ -404,10 +401,10 @@ Duplicate Frame Dupliquer l'image Frame from Layers Image d'après les calques Video Frames to Layers… Images vidéo dans des calques… - Rasterize Pixelliser - Rasterize Type Pixelliser le texte - Rasterize Type Layer Pixelliser le calque de texte - Rasterize Shape Pixelliser la forme + Rasterize Rastériser + Rasterize Type Rastériser le texte + Rasterize Type Layer Rastériser le calque de texte + Rasterize Shape Rastériser la forme New Guide Layout… Nouvelle disposition de repères… New Guides From Shape Nouveaux repères d'après la forme Lock Layers… Verrouiller les calques… @@ -513,9 +510,9 @@ Save Default Type Styles Enregistrer les styles de texte par défaut Deselect Désélectionner Reselect Resélectionner - Inverse Intervertir - Inverse Selection Intervertir la sélection - Similar Généraliser + Inverse Inverser + Inverse Selection Inverser la sélection + Similar Similaire Color Range… Plage de couleurs… Focus Area… Zone de mise au point… Select and Mask… Sélectionner et masquer… @@ -528,43 +525,43 @@ Modify Modifier Border… Bordure… Smooth… Lisser… - Expand… Dilater… + Expand… Étendre… Contract… Contracter… - Feather… Contour progressif… - Grow Étendre + Feather… Adoucir… + Grow Agrandir Transform Selection Transformer la sélection - Edit in Quick Mask Mode Modifier en mode Masque - Load Selection… Récupérer la sélection… - Save Selection… Mémoriser la sélection… + Edit in Quick Mask Mode Modifier en mode masque rapide + Load Selection… Charger la sélection… + Save Selection… Enregistrer la sélection… Match All Tout faire correspondre Last Filter Dernier filtre Filter Gallery… Galerie de filtres… Adaptive Wide Angle… Grand angle adaptatif… Camera Raw Filter… Filtre Camera Raw… - Liquify… Fluidité… + Liquify… Liquéfier… Vanishing Point… Point de fuite… Blur Flou - Blur Gallery Galerie d'effets de flou - Field Blur… Flou de profondeur de champ… + Blur Gallery Galerie de flous + Field Blur… Flou de champ… # Field Blur pins (#3373) No pin selected Aucune épingle sélectionnée Click the image to add a pin; drag a pin to move it, or its ring to change its blur. Delete removes the selected pin. Cliquez dans l’image pour ajouter une épingle ; faites glisser une épingle pour la déplacer, ou son anneau pour modifier le flou. Suppr supprime l’épingle sélectionnée. - Iris Blur… Flou de diaphragme… + Iris Blur… Flou d'iris… Tilt-Shift… Inclinaison-décalage… Path Blur… Flou de tracé… Spin Blur… Flou de rotation… - Blur More Plus flou + Blur More Flou accentué Box Blur… Flou par boîte… Gaussian Blur… Flou gaussien… - Lens Blur… Flou de l'objectif… - Motion Blur… Flou directionnel… + Lens Blur… Flou d'objectif… + Motion Blur… Flou de mouvement… Radial Blur… Flou radial… Shape Blur… Flou selon une forme… - Smart Blur… Flou optimisé… + Smart Blur… Flou intelligent… Surface Blur… Flou de surface… Average Moyenne - Displace… Dispersion… - Pinch… Contraction… + Displace… Déplacement… + Pinch… Pincement… Polar Coordinates… Coordonnées polaires… Ripple… Ondulation… Shear… Cisaillement… @@ -574,16 +571,16 @@ ZigZag… Zigzag… Noise Bruit Add Noise… Ajout de bruit… - Despeckle Flou intérieur - Dust & Scratches… Antipoussière… + Despeckle Supprimer les mouchetures + Dust & Scratches… Poussière et rayures… Median… Médiane… Reduce Noise… Réduction du bruit… - Pixelate Pixellisation - Color Halftone… Demi-teintes couleur… + Pixelate Pixeliser + Color Halftone… Trame de demi-teintes couleur… Crystallize… Cristallisation… Facet Facettes Fragment Fragmentation - Mezzotint… Mezzo-tinto… + Mezzotint… Manière noire… Mosaic… Mosaïque… Pointillize… Pointillisme… Render Rendu @@ -591,38 +588,38 @@ Difference Clouds Nuages par différence Fibers… Fibres… Flame… Flamme… - Lens Flare… Halo… - Lighting Effects… Éclairage… + Lens Flare… Reflet d'objectif… + Lighting Effects… Effets d'éclairage… Relight… Rééclairage… Picture Frame… Cadre d'image… Tree… Arbre… Sharpen Netteté - Sharpen Edges Contours plus nets - Sharpen More Encore plus net - Smart Sharpen… Netteté optimisée… - Unsharp Mask… Accentuation… + Sharpen Edges Netteté des contours + Sharpen More Netteté accentuée + Smart Sharpen… Netteté intelligente… + Unsharp Mask… Masque flou… High Pass… Passe-haut… - Stylize Esthétiques - Diffuse… Diffusion… - Emboss… Estampage… - Extrude… Extrusion… - Find Edges Tracé des contours + Stylize Styliser + Diffuse… Diffuser… + Emboss… Relief… + Extrude… Extruder… + Find Edges Détecter les contours Oil Paint… Peinture à l'huile… - Solarize Solarisation - Tiles… Carrelage… - Trace Contour… Courbes de niveau… - Wind… Soufflerie… + Solarize Solariser + Tiles… Carreaux… + Trace Contour… Tracer les contours… + Wind… Vent… Video Vidéo - De-Interlace… Désentrelacement… + De-Interlace… Désentrelacer… NTSC Colors Couleurs NTSC - Other Divers + Other Autres Maximum… Maximum… Minimum… Minimum… - Offset… Translation… - Plug-ins Modules externes + Offset… Décalage… + Plug-ins Extensions Proof Setup Configuration d'épreuve Proof Colors Couleurs d'épreuve - Gamut Warning Couleurs non imprimables + Gamut Warning Alerte de gamut Working CMYK CMJN de travail Working Cyan Plate Plaque cyan de travail Working Magenta Plate Plaque magenta de travail @@ -664,11 +661,11 @@ HDV 1080/DVCPRO HD 720 (1.33) HDV 1080/DVCPRO HD 720 (1,33) DVCPRO HD 1080/HDV 1080 (1.5) DVCPRO HD 1080/HDV 1080 (1,5) 32-bit Preview Options… Options d'aperçu 32 bits… - Screen Mode Mode d'affichage - Standard Screen Mode Mode d'affichage standard + Screen Mode Mode d'écran + Standard Screen Mode Mode d'écran standard Full Screen Mode With Menu Bar Mode plein écran avec barre de menus Full Screen Mode Mode plein écran - Cycle Screen Mode Changer de mode d'affichage + Cycle Screen Mode Changer de mode d'écran Show Afficher Extras Extras Selection Edges Contours de la sélection @@ -676,12 +673,12 @@ Current Path Tracé actif Grid Grille Guides Repères - Canvas Guides Repères de la zone de travail + Canvas Guides Repères du canevas Artboard Guides Repères du plan de travail Count Compteur - Smart Guides Repères commentés + Smart Guides Repères intelligents Slices Tranches - Notes Annotations + Notes Notes Pixel Grid Grille de pixels Pixel Art Pixel art Pixel Art Preview Aperçu pixel art @@ -697,7 +694,7 @@ Lock Guides Verrouiller les repères Clear Guides Effacer les repères Clear Selected Artboard Guides Effacer les repères du plan de travail sélectionné - Clear Canvas Guides Effacer les repères de la zone de travail + Clear Canvas Guides Effacer les repères du canevas New Guide… Nouveau repère… Lock Slices Verrouiller les tranches Clear Slices Effacer les tranches @@ -755,16 +752,16 @@ Actions Actions Brush Settings Paramètres du pinceau Brushes Pinceaux - Channels Couches + Channels Canaux Character Caractère Character Styles Styles de caractère - Clone Source Source de duplication + Clone Source Source de clonage Color Couleur Glyphs Glyphes Gradients Dégradés Histogram Histogramme History Historique - Info Informations + Info Infos Measurement Log Journal des mesures Modifier Keys Touches de modification Navigator Navigation @@ -792,7 +789,7 @@ Invert… Inverser… Load Files into Stack… Charger des fichiers dans une pile… Motion Mouvement - Notes… Annotations… + Notes… Notes… Other… Autre… Remove Warp Split Supprimer la division de la déformation Shape Forme @@ -813,18 +810,18 @@ Adaptive Adaptative Adaptive Wide Angle isn't open Grand angle adaptatif n'est pas ouvert Add Ajouter - Add Layer Mask Ajouter un masque de fusion + Add Layer Mask Ajouter un masque de calque Add Stop Ajouter un point Add a layer style Ajouter un style de calque Add a mask (from the selection; {key} inverts) Ajouter un masque (d'après la sélection ; {key} l'inverse) - Add an adjustment Ajouter un réglage + Add an adjustment Ajouter un ajustement Add disk Ajouter un disque Add space after paragraph Espace après le paragraphe Add space before paragraph Espace avant le paragraphe Add to Sample Ajouter à l'échantillon Add to selection ({key}) Ajouter à la sélection ({key}) Adjust for Zoom Adapter au zoom - Adjustment visible Réglage visible + Adjustment visible Ajustement visible Adobe RGB (1998) compatible Compatible Adobe RGB (1998) Align and Distribute Aligner et répartir Align and distribute Aligner et répartir @@ -848,7 +845,7 @@ Anti-alias Lissage Appearance Apparence Appearance (Layer Style) Apparence (style de calque) - Apply Layer Mask Appliquer le masque de fusion + Apply Layer Mask Appliquer le masque de calque Add Mask to Selection Ajouter le masque à la sélection Subtract Mask from Selection Soustraire le masque de la sélection Intersect Mask with Selection Intersection du masque et de la sélection @@ -915,7 +912,7 @@ Cancel transform (Esc) Annuler la transformation (Échap) Cancel warp (Esc) Annuler la déformation (Échap) Canvas Canevas - Canvas extension color: Couleur d'extension de la zone de travail : + Canvas extension color: Couleur d'extension du canevas : Capture Capturer Capture the visibility, position and style of every layer with + below, then switch between versions. Capturez la visibilité, la position et le style de chaque calque avec le + ci-dessous, puis passez d'une version à l'autre. Catch-up on Stroke End Rattraper en fin de trait @@ -924,10 +921,10 @@ Center text Centrer le texte Change Remplacer Change All Tout remplacer - Change Screen Mode (F) Changer de mode d'affichage (F) + Change Screen Mode (F) Changer de mode d'écran (F) Change To: Remplacer par : - Channel Mixer Mélangeur de couches - Channel: Couche : + Channel Mixer Mélangeur de canaux + Channel: Canal : Check All Layers Vérifier tous les calques Check Spelling Vérifier l'orthographe Clarity Clarté @@ -941,11 +938,11 @@ Click to edit the gradient Cliquez pour modifier le dégradé Click to restore the current colour Cliquez pour rétablir la couleur actuelle Click to zoom in · {key}-click to zoom out · drag right/left to zoom in/out Cliquez pour zoomer · {key}-clic pour dézoomer · faites glisser vers la droite/gauche pour zoomer/dézoomer - Click with the Note tool to add a note. Cliquez avec l'outil Annotation pour ajouter une annotation. + Click with the Note tool to add a note. Cliquez avec l'outil Note pour ajouter une note. Click: corner · Drag: smooth · Click first point: close · {key} finish · Esc cancel Clic : angle · Glisser : lisse · Clic sur le premier point : fermer · {key} : terminer · Échap : annuler Click: corner · Drag: smooth · {alt}-drag: break handle · Click or drag first point: close · {key} finish · Esc cancel Clic : angle · Glisser : lisse · {alt}+glisser : briser la poignée · Clic ou glisser sur le premier point : fermer · {key} : terminer · Échap : annuler - Clip to layer below Écrêter selon le calque inférieur - Clipped Écrêté + Clip to layer below Découper selon le calque inférieur + Clipped Découpé Close Style Options Fermer les options de style Cmd Cmd Coated CMYK CMJN papier couché @@ -953,7 +950,7 @@ Color Balance Balance des couleurs Color Detail Détail des couleurs Color Grading Étalonnage des couleurs - Color Lookup Correspondance des couleurs + Color Lookup Table de correspondance des couleurs Color Management Gestion des couleurs Color Mixer Mélangeur de couleurs Color Mode Mode colorimétrique @@ -963,7 +960,7 @@ Colorize Colorer Colors Couleurs Commit Perspective Warp ({key}) Valider la déformation de la perspective ({key}) - Commit Puppet Warp ({key}) Valider la déformation de la marionnette ({key}) + Commit Puppet Warp ({key}) Valider la déformation par épingles ({key}) Commit any current edits ({key}) Valider les modifications en cours ({key}) Commit current crop operation ({key}) Valider le recadrage en cours ({key}) Commit transform ({key}) Valider la transformation ({key}) @@ -971,7 +968,7 @@ Constant Constant Constrain proportions Conserver les proportions Contact Sheet II Planche contact II - Content-Aware Contenu pris en compte + Content-Aware Selon le contenu Contiguous Contigu Contrast Contraste Control: Contrôle : @@ -993,9 +990,9 @@ Create a new group Créer un nouveau groupe Create a new layer Créer un nouveau calque Create new action Créer une nouvelle action - Create new channel Créer une nouvelle couche + Create new channel Créer un nouveau canal Create new document from current state Créer un nouveau document à partir de l'état actuel - Create new fill or adjustment layer Créer un nouveau calque de remplissage ou de réglage + Create new fill or adjustment layer Créer un nouveau calque de remplissage ou d'ajustement Create new gradient Créer un nouveau dégradé Create new group Créer un nouveau groupe Create new layer comp Créer une nouvelle composition de calques @@ -1016,7 +1013,7 @@ Cyan Cyan Cyan · Red Cyan · Rouge Darken Obscurcir - Darks Teintes sombres + Darks Tons sombres Data Sets Jeux de données Default Par défaut Default colours (D) Couleurs par défaut (D) @@ -1027,13 +1024,13 @@ Dehaze Correction de la brume Delete Cropped Pixels Supprimer les pixels recadrés Delete Group Supprimer le groupe - Delete Layer Mask Supprimer le masque de fusion + Delete Layer Mask Supprimer le masque de calque Delete Layers Supprimer les calques Delete Shortcut Supprimer le raccourci Delete Timeline Supprimer le montage Delete Tool Preset Supprimer le préréglage d'outil Delete count group Supprimer le groupe de comptage - Delete current channel Supprimer la couche active + Delete current channel Supprimer le canal actif Delete current state Supprimer l'état actuel Delete effect layer Supprimer le calque d'effet @@ -1049,7 +1046,7 @@ Delete layer Supprimer le calque Delete layer comp Supprimer la composition de calques - Delete note Supprimer l'annotation + Delete note Supprimer la note Delete selected measurements Supprimer les mesures sélectionnées Delete style Supprimer le style Delete tool preset Supprimer le préréglage d'outil @@ -1086,7 +1083,7 @@ Duplicate Layers… Dupliquer les calques… Duplicate style Dupliquer le style Edit Toolbar… Modifier la barre d'outils… - Edit in Quick Mask Mode (Q) Modifier en mode Masque (Q) + Edit in Quick Mask Mode (Q) Modifier en mode masque rapide (Q) Edit in Standard Mode (Q) Modifier en mode standard (Q) Effects Effets Embed Color Profile Incorporer le profil colorimétrique @@ -1105,15 +1102,15 @@ Factor: Facteur : Faux Bold Faux gras Faux Italic Faux italique - Feather Contour progressif + Feather Adoucissement Fewer Points Moins de points File Settings Paramètres du fichier Fill Remplissage - Fill: Fond : - Filter for adjustment layers Filtrer les calques de réglage + Fill: Remplissage : + Filter for adjustment layers Filtrer les calques d'ajustement Filter for pixel layers Filtrer les calques de pixels Filter for shape layers Filtrer les calques de forme - Filter for smart objects Filtrer les objets dynamiques + Filter for smart objects Filtrer les objets intelligents Filter for type layers Filtrer les calques de texte Filter: Filtre : Finger Painting Peinture au doigt @@ -1149,7 +1146,7 @@ Gamma: Gamma : Global Global Glow Color Couleur de la lueur - Gradient Map Courbe de transfert de dégradé + Gradient Map Mappage de dégradé Grain Grain Gray Gris Gray Gamma 2.2 Gris gamma 2,2 @@ -1161,9 +1158,9 @@ Height: Hauteur : Hide Auto Slices Masquer les tranches automatiques Hide Properties Masquer les propriétés - Highlight Compression Compression des tons clairs - Highlight Priority Priorité aux tons clairs - Highlights Tons clairs + Highlight Compression Compression des hautes lumières + Highlight Priority Priorité aux hautes lumières + Highlights Hautes lumières Home Accueil Horizontal scale Échelle horizontale Hue Teinte @@ -1192,7 +1189,7 @@ Interpolation: Interpolation : Intersect with selection ({key}) Intersection avec la sélection ({key}) Invert All Tout inverser - Invert has no settings. Inverser n'a aucun paramètre. + Invert has no settings. Inverser n'a aucun réglage. Italic Italique Join us on Discord Rejoignez-nous sur Discord Justify all Justifier tout @@ -1218,15 +1215,15 @@ Levels Niveaux Lighten Éclaircir Lightness Luminosité - Lights Teintes claires + Lights Tons clairs Limits: Limites : Linear Gradient Dégradé linéaire Link layers Lier les calques Link width and height Lier la largeur et la hauteur - Liquify is not open Fluidité n'est pas ouvert + Liquify is not open Liquéfier n'est pas ouvert Load Charger Load 3D LUT… Charger une LUT 3D… - Load channel as selection Récupérer la couche comme sélection + Load channel as selection Charger le canal comme sélection Load… Charger… Localized Color Clusters Groupes de couleurs localisés Lock Verrouiller @@ -1276,7 +1273,7 @@ New effect layer Nouveau calque d'effet New selection Nouvelle sélection Next frame Image suivante - Next note Annotation suivante + Next note Note suivante No Color Aucune couleur No Color Management Aucune gestion des couleurs No Dither Sans tramage @@ -1302,9 +1299,9 @@ Open a document to use the timeline. Ouvrez un document pour utiliser le montage. Optical Optique Optimized Optimisé - Option-click to define a source point to clone from Option-clic pour définir le point source de la duplication + Option-click to define a source point to clone from Option-clic pour définir le point source du clonage Original Original - Output Channel: Couche de sortie : + Output Channel: Canal de sortie : Output Levels: Niveaux de sortie : Output: Sortie : Outside Extérieur @@ -1341,7 +1338,7 @@ Portrait Portrait Position Position Position and Size Position et taille - Posterize Isohélie + Posterize Postériser Preserve Details Préserver les détails Preserve Luminosity Préserver la luminosité Preset Préréglage @@ -1355,7 +1352,7 @@ Prevent auto-nesting in and out of Artboards and Frames Empêcher l'imbrication automatique dans et hors des plans de travail et des cadres Preview Aperçu Previous frame Image précédente - Previous note Annotation précédente + Previous note Note précédente Print Imprimer Printer Imprimante Printer Manages Colors L'imprimante gère les couleurs @@ -1368,7 +1365,7 @@ Protect Tones Protéger les tons Proximity Match Correspondance de proximité Pulled String Mode Mode corde tendue - Puppet Warp is not active La déformation de la marionnette n'est pas active + Puppet Warp is not active La déformation par épingles n'est pas active Purity Pureté Quality Qualité Quick Actions Actions rapides @@ -1440,7 +1437,7 @@ Saturation Jitter Variation de la saturation Save HTML and Images Enregistrer le HTML et les images Save as PDF Enregistrer en PDF - Save selection as channel Mémoriser la sélection sur une couche + Save selection as channel Enregistrer la sélection comme canal Save… Enregistrer… Scale to Fit Media Adapter au support Scatter Diffusion @@ -1456,17 +1453,17 @@ Select: Sélection : Selections Sélections Selective Sélective - Selective Color Correction sélective + Selective Color Couleur sélective Send the selected pin backward Reculer l'épingle sélectionnée Set additional smoothing options Définir des options de lissage supplémentaires Set background color Définir la couleur d'arrière-plan Set foreground color Définir la couleur de premier plan Set painting symmetry options Définir les options de symétrie de peinture Symmetry Off Symétrie désactivée - Set shape fill type Définir le type de fond de la forme + Set shape fill type Définir le type de remplissage de la forme Set shape stroke type Définir le type de contour de la forme Set the text color Définir la couleur du texte - Shadows Tons foncés + Shadows Ombres Shape: Forme : Sharpening Netteté Shift Maj @@ -1478,7 +1475,7 @@ Show Mesh Afficher le maillage Show Overlay Afficher la superposition Show Transform Controls Afficher les contrôles de transformation - Show or hide the Notes panel Afficher ou masquer le panneau Annotations + Show or hide the Notes panel Afficher ou masquer le panneau Notes Show: Afficher : Sides Côtés Sides: Côtés : @@ -1514,7 +1511,7 @@ Suggestions: Suggestions : Swap colours (X) Permuter les couleurs (X) Swaps height and width Permute la hauteur et la largeur - Switch between free transform and warp modes Basculer entre les modes transformation manuelle et déformation + Switch between free transform and warp modes Basculer entre les modes transformation libre et déformation Switch theme Changer de thème Temperature Température Tetrahedral Tétraédrique @@ -1590,7 +1587,7 @@ No saved presets yet. Aucun préréglage enregistré pour le moment. Error Erreur Command Commande - Adjustment layers have nothing to transform Les calques de réglage n'ont rien à transformer + Adjustment layers have nothing to transform Les calques d'ajustement n'ont rien à transformer Altitude Altitude Android 1080p Android 1080p Apple Watch 45mm Apple Watch 45 mm @@ -1598,10 +1595,10 @@ Art & Illustration Art et illustration ArtCraft ArtCraft Background Eraser Tool Outil Gomme d'arrière-plan - Bevel & Emboss Biseautage et estampage + Bevel & Emboss Biseau et relief Blend Mode Mode de fusion Blues Bleus - Blur Tool Outil Goutte d'eau + Blur Tool Outil Flou Bold Gras Brush Pinceau Block Carré @@ -1609,24 +1606,24 @@ Brush Tip Shape Forme de la pointe Brush Tool Outil Pinceau Build-up Accumulation - Burn Tool Outil Densité + + Burn Tool Outil Assombrir Centimeters Centimètres Choke Resserrement Classic Classique Click to add text Cliquez pour ajouter du texte Click to fill similar colours Cliquez pour remplir les couleurs similaires - Clone Stamp Tool Outil Tampon de duplication + Clone Stamp Tool Outil Tampon de clonage Color Burn Densité couleur + Color Dodge Densité couleur − Color Dynamics Dynamique de la couleur - Color Overlay Incrustation couleur + Color Overlay Superposition de couleur Comic Book Bande dessinée Conform Conforme - Content-Aware Fill needs a selection on a pixel layer Le remplissage d'après le contenu nécessite une sélection sur un calque de pixels + Content-Aware Fill needs a selection on a pixel layer Le remplissage selon le contenu nécessite une sélection sur un calque de pixels Could not transform: the layer is empty Transformation impossible : le calque est vide Could not use Perspective Warp: the layer is empty Impossible d'utiliser la déformation de la perspective : le calque est vide - Could not use Puppet Warp: no opaque pixels Impossible d'utiliser la déformation de la marionnette : aucun pixel opaque - Could not use Puppet Warp: the layer is empty Impossible d'utiliser la déformation de la marionnette : le calque est vide + Could not use Puppet Warp: no opaque pixels Impossible d'utiliser la déformation par épingles : aucun pixel opaque + Could not use Puppet Warp: the layer is empty Impossible d'utiliser la déformation par épingles : le calque est vide Crop Tool Outil Recadrage Crosswise En croix Current Tool Outil actif @@ -1641,7 +1638,7 @@ Document Dimensions Dimensions du document Document Profile Profil du document Document Sizes Tailles du document - Dodge Tool Outil Densité − + Dodge Tool Outil Éclaircir Down Bas Drag to draw a gradient Faites glisser pour tracer un dégradé Drag to move the active layer Faites glisser pour déplacer le calque actif @@ -1652,30 +1649,30 @@ Efficiency Efficacité Efficiency: 100% Efficacité : 100 % Ellipse Tool Outil Ellipse - Elliptical Marquee Tool Outil Ellipse de sélection - Single Row Marquee Tool Outil Rectangle de sélection 1 rangée - Single Column Marquee Tool Outil Rectangle de sélection 1 colonne - Emboss Estampage + Elliptical Marquee Tool Outil Sélection elliptique + Single Row Marquee Tool Outil Sélection de ligne unique + Single Column Marquee Tool Outil Sélection de colonne unique + Emboss Relief Eraser Gomme Eraser Tool Outil Gomme ExtraBold Extra-gras ExtraLight Extra-maigre Eyedropper Tool Outil Pipette Fade Estompage - Fill Opacity Opacité du fond + Fill Opacity Opacité du remplissage Film & Video Film et vidéo From De General Général GitHub GitHub - Gradient Overlay Incrustation en dégradé + Gradient Overlay Superposition de dégradé Gradient Tool Outil Dégradé Greens Verts Hand Tool Outil Main Hard Dur Hard Mix Mélange maximal - Healing Brush Tool Outil Correcteur - Highlights from: Tons clairs à partir de : - History Brush Tool Outil Forme d'historique + Healing Brush Tool Outil Pinceau correcteur + Highlights from: Hautes lumières à partir de : + History Brush Tool Outil Pinceau d'historique Horizontal Type Tool Outil Texte horizontal Horizontal Type Mask Tool Outil Masque de texte horizontal Vertical Type Mask Tool Outil Masque de texte vertical @@ -1683,9 +1680,9 @@ Inches Pouces Indexed Indexé Initial Direction Direction initiale - Inner Bevel Biseau interne - Inner Glow Lueur interne - Inner Shadow Ombre interne + Inner Bevel Biseau intérieur + Inner Glow Lueur intérieure + Inner Shadow Ombre intérieure Lab Lab Landscape, 10 x 8 Paysage, 10 x 8 Landscape, 6 x 4 Paysage, 6 x 4 @@ -1714,20 +1711,20 @@ Mobile Mobile Move Tool Outil Déplacement Could not use the move tool because the layer is locked. Impossible d'utiliser l'outil Déplacement, car le calque est verrouillé. - Could not complete the Free Transform command because the layer is locked. Impossible d'exécuter la commande Transformation manuelle, car le calque est verrouillé. + Could not complete the Free Transform command because the layer is locked. Impossible d'exécuter la commande Transformation libre, car le calque est verrouillé. Multiply Produit My Brushes Mes pinceaux Neutrals Neutres New Workspace Nouvel espace de travail - Note Tool Outil Annotation + Note Tool Outil Note Object Selection Tool Outil Sélection d'objet Off Désactivé Open Ouvrir Orange Orange Original Ratio Proportions d'origine - Out Of Gamut Couleurs non imprimables - Outer Bevel Biseau externe - Outer Glow Lueur externe + Out Of Gamut Hors gamut + Outer Bevel Biseau extérieur + Outer Glow Lueur extérieure Overlay Incrustation Paint Bucket Tool Outil Pot de peinture Paints from the document's opening state Peint à partir de l'état d'ouverture du document @@ -1737,7 +1734,7 @@ Delete Anchor Point Tool Outil Suppression de point d'ancrage Click a segment to add an anchor point · {alt}-click an anchor to delete it Cliquez sur un segment pour ajouter un point d'ancrage · {alt}-clic sur un point d'ancrage pour le supprimer Click an anchor point to delete it · {alt}-click a segment to add one Cliquez sur un point d'ancrage pour le supprimer · {alt}-clic sur un segment pour en ajouter un - Pattern Overlay Incrustation de motif + Pattern Overlay Superposition de motif Pen Pressure Pression du stylet Pen Tilt Inclinaison du stylet Pen Tool Outil Plume @@ -1745,7 +1742,7 @@ Photo Photo PhotoCraft website Site web de PhotoCraft Picas Picas - Pillow Emboss Estampage oreiller + Pillow Emboss Relief en coussin Pixel Replacement Remplacement de pixels Pixels Pixels Points Points @@ -1762,15 +1759,15 @@ Pro (Medium Gray) Pro (gris moyen) Protect Texture Protéger la texture Purple Violet - Quick Mask Mode Masque + Quick Mask Masque rapide Quick Selection Tool Outil Sélection rapide Radial Radial - Rasterize Layer Pixelliser le calque - Rasterize Layers Pixelliser les calques + Rasterize Layer Rastériser le calque + Rasterize Layers Rastériser les calques Ratio Proportions Recent Récent Rectangle Tool Outil Rectangle - Rectangular Marquee Tool Outil Rectangle de sélection + Rectangular Marquee Tool Outil Sélection rectangulaire Reds Rouges Reflected Réfléchi Sample: foreground colour Échantillon : couleur de premier plan @@ -1782,13 +1779,13 @@ Select Data Points Sélectionner les points de données Selection Sélection SemiBold Demi-gras - Shadows up to: Tons foncés jusqu'à : + Shadows up to: Ombres jusqu'à : Shape Dynamics Dynamique de la forme Sharpen Tool Outil Netteté Show Extras Options Options d'affichage des extras Slice Select Tool Outil Sélection de tranche Slice Tool Outil Tranche - Smart Filters Filtres dynamiques + Smart Filters Filtres intelligents Smudge Tool Outil Doigt Soft Doux Soften Adoucir @@ -1839,15 +1836,15 @@ @plural {n} Layer|{n} Layers {n} calque|{n} calques @plural {n} layer|{n} layers {n} calque|{n} calques @plural Group · {n} layer|Group · {n} layers Groupe · {n} calque|Groupe · {n} calques - Additional plugins folder Dossier de modules externes supplémentaire + Additional plugins folder Dossier d'extensions supplémentaire Adobe Adobe Adobe rgb Adobe RGB Allow agent control Autoriser le pilotage par des agents Allow scripts to connect Autoriser la connexion des scripts Always Toujours - Always create smart objects when placing Toujours créer des objets dynamiques lors du placement + Always create smart objects when placing Toujours créer des objets intelligents lors du placement Animated zoom Zoom animé - Apply auto tone Appliquer le ton automatique + Apply auto tone Appliquer la tonalité automatique Ask Demander Ask before saving layered tiff Demander avant d'enregistrer un TIFF avec calques Auto collapse icon panels Réduire automatiquement les panneaux en icônes @@ -1864,19 +1861,19 @@ Cache levels Niveaux de cache Cache tile size Taille des tuiles du cache Camera Raw Defaults Valeurs par défaut de Camera Raw - Canvas border Bordure de la zone de travail - Show canvas dots Afficher les points de la zone de travail + Canvas border Bordure du canevas + Show canvas dots Afficher les points du canevas Clip thumbnails to layer bounds Rogner les vignettes aux limites du calque Clip Thumbnails to Layer Bounds Rogner les vignettes aux limites du calque - Canvas color Couleur de la zone de travail - Canvas custom color Couleur personnalisée de la zone de travail + Canvas color Couleur du canevas + Canvas custom color Couleur personnalisée du canevas Color picker Sélecteur de couleurs Color space Espace colorimétrique Column width Largeur de colonne Concise Concis Control port Port de contrôle Convert to srgb Convertir en sRVB - Cursors Pointeurs + Cursors Curseurs Custom dark Personnalisé sombre Custom light Personnalisé clair Dark Sombre @@ -1886,7 +1883,7 @@ Detailed Détaillé Disks Disques Display p3 Display P3 - Double click layer mask launches select and mask Un double-clic sur le masque de fusion ouvre Sélectionner et masquer + Double click layer mask launches select and mask Un double-clic sur le masque de calque ouvre Sélectionner et masquer Drop shadow Ombre portée Dynamic color sliders Curseurs de couleur dynamiques East asian Asie orientale @@ -1903,8 +1900,8 @@ Fill new type layers with placeholder Remplir les nouveaux calques de texte avec un texte de substitution Font preview Aperçu des polices Full size tip Pointe en taille réelle - Gamut warning color Couleur des alertes de couleurs non imprimables - Gamut warning opacity Opacité des alertes de couleurs non imprimables + Gamut warning color Couleur de l'alerte de gamut + Gamut warning opacity Opacité de l'alerte de gamut Generator enabled Générateur activé Gif GIF Glossy Brillant @@ -1944,7 +1941,7 @@ Nearest neighbor Au plus proche Never Jamais Normal tip Pointe normale - Open as smart object Ouvrir en tant qu'objet dynamique + Open as smart object Ouvrir en tant qu'objet intelligent Open in camera raw Ouvrir dans Camera Raw Open documents as tabs Ouvrir les documents dans des onglets Overscroll Défilement au-delà des bords @@ -1976,7 +1973,7 @@ Scrubby slider acceleration Accélération des curseurs glissants Sessions only Sessions uniquement Sharpen for Netteté pour - Show channels in color Afficher les couches en couleur + Show channels in color Afficher les canaux en couleur Show crosshair in brush tip Afficher le réticule dans la pointe Show extension panels Afficher les panneaux d'extension Show font names in english Afficher les noms de police en anglais @@ -1988,7 +1985,7 @@ Show highlight on rollover Afficher la mise en surbrillance au survol Show transformation values Afficher les valeurs de transformation Slice color Couleur des tranches - Smart guide color Couleur des repères commentés + Smart guide color Couleur des repères intelligents Smart quotes Guillemets typographiques Snap vector tools and transforms to pixel grid Aimanter les outils vectoriels et les transformations à la grille de pixels Srgb sRVB @@ -2003,15 +2000,15 @@ Text file Fichier texte Tiny Minuscule Touch gestures Gestes tactiles - Transparency & Gamut Transparence et couleurs non imprimables + Transparency & Gamut Transparence et gamut Type units Unités de texte UI font size Taille de police de l'interface UI scale Échelle de l'interface Units & Rulers Unités et règles - Use additional plugins folder Utiliser le dossier de modules externes supplémentaire + Use additional plugins folder Utiliser le dossier d'extensions supplémentaire Use esc to commit Valider avec Échap Use gpu Utiliser le GPU - Use legacy free transform Utiliser l'ancienne transformation manuelle + Use legacy free transform Utiliser l'ancienne transformation libre Use shift key for tool switch Utiliser Maj pour changer d'outil Vary round brush hardness on hud Varier la dureté du pinceau rond dans l'affichage tête haute Webp WebP @@ -2023,9 +2020,9 @@ Pass Through Transfert Dissolve Fondu Darker Color Couleur plus foncée - Linear Dodge (Add) Densité linéaire − (Ajout) + Linear Dodge (Add) Densité linéaire − (Addition) Lighter Color Couleur plus claire - Soft Light Lumière tamisée + Soft Light Lumière douce Hard Light Lumière crue Vivid Light Lumière vive Linear Light Lumière linéaire @@ -2035,7 +2032,7 @@ Luminosity Luminosité @plural {n} sample|{n} samples {n} échantillon|{n} échantillons {added}, {removed} subtracted {added}, {removed} soustraits - Adjustment Preview Aperçu du réglage + Adjustment Preview Aperçu de l'ajustement Airbrush: keeps depositing paint while the pointer rests. Aérographe : continue de déposer de la peinture tant que le pointeur reste immobile. Angle: Angle : Blend If Fusion si @@ -2077,18 +2074,18 @@ Click to sample the foreground colour · {key}-click for background Cliquez pour prélever la couleur de premier plan · {key}-clic pour l'arrière-plan {key}-click to set the source {key}-clic pour définir la source {key} to subtract {key} pour soustraire - Clone source {n} ({key}-click with Clone Stamp to set) Source de duplication {n} ({key}-clic avec le tampon de duplication pour la définir) - Source: not set ({key}-click with Clone Stamp) Source : non définie ({key}-clic avec le tampon de duplication) + Clone source {n} ({key}-click with Clone Stamp to set) Source de clonage {n} ({key}-clic avec le tampon de clonage pour la définir) + Source: not set ({key}-click with Clone Stamp) Source : non définie ({key}-clic avec le tampon de clonage) Pixel Layer Calque de pixels Type Layer Calque de texte Shape Layer Calque de forme Group Layer Calque de groupe Fill Layer Calque de remplissage - Smart Object Layer Calque d'objet dynamique + Smart Object Layer Calque d'objet intelligent Create Warped Text Créer un texte déformé - Rasterize Smart Object Pixelliser l'objet dynamique - Rasterize Fill Content Pixelliser le contenu du remplissage - Rasterize Layer Style Pixelliser le style de calque + Rasterize Smart Object Rastériser l'objet intelligent + Rasterize Fill Content Rastériser le contenu du remplissage + Rasterize Layer Style Rastériser le style de calque Unlock: picking a preset replaces these settings Déverrouiller : choisir un préréglage remplace ces paramètres Lock: keep these settings when picking another preset Verrouiller : conserver ces paramètres en choisissant un autre préréglage Diameter: Diamètre : @@ -2119,7 +2116,7 @@ Mix Mélange Wetness Jitter Variation de l'humidité Mix Jitter Variation du mélange - Show the layer mask as an overlay Afficher le masque de fusion en superposition + Show the layer mask as an overlay Afficher le masque de calque en superposition Toggle visibility Afficher/masquer Unlink the mask from the layer Délier le masque du calque Link the mask to the layer Lier le masque au calque @@ -2131,7 +2128,7 @@ # Gradient Editor window, opened from the options bar's gradient swatch. Gradient Editor Éditeur de dégradé Gradient picker Sélecteur de dégradé - Save As Preset Enregistrer comme préréglage + Save As Preset Enregistrer comme préreglage The gradient the Gradient tool paints with Le dégradé que l'outil Dégradé applique Adds the current gradient to the Gradients panel Ajoute le dégradé actuel au panneau Dégradés Gradient stops Arrêts de dégradé @@ -2143,7 +2140,7 @@ Vertical type Texte vertical Fill path with foreground color Remplir le tracé avec la couleur de premier plan Stroke path with brush Contourner le tracé avec le pinceau - Load path as a selection Récupérer le tracé comme sélection + Load path as a selection Charger le tracé comme sélection Make work path from selection Créer un tracé de travail à partir de la sélection Add vector mask Ajouter un masque vectoriel Create new path Créer un nouveau tracé @@ -2158,7 +2155,7 @@ OpenGL OpenGL Use tablet pressure Utiliser la pression de la tablette Vulkan Vulkan - Alt-click to define a source point to clone from Alt-clic pour définir le point source de la duplication + Alt-click to define a source point to clone from Alt-clic pour définir le point source du clonage No path to make a selection from: draw one with the Pen tool or select one in the Paths panel. Aucun tracé à partir duquel créer une sélection : dessinez-en un avec l'outil Plume ou sélectionnez-en un dans le panneau Tracés. Cancel opening Annuler l'ouverture Cancelled opening {name} Ouverture de {name} annulée @@ -2181,8 +2178,8 @@ Working… Traitement… @id select.all Tout sélectionner @id edit.purge.undo Historique d'annulation - Delete Smart Filters Supprimer les filtres dynamiques - Enable Smart Filters Activer les filtres dynamiques + Delete Smart Filters Supprimer les filtres intelligents + Enable Smart Filters Activer les filtres intelligents New Layer… Nouveau calque… Delete Layer… Supprimer le calque… Color Range Plage de couleurs @@ -2202,7 +2199,7 @@ Stamp Tampon Torn Edges Bords déchirés Water Paper Papier mouillé - Canvas rotation Rotation de la zone de travail + Canvas rotation Rotation du canevas Extensions Extensions Libraries Bibliothèques 3D 3D @@ -2210,27 +2207,27 @@ Move Déplacement Wand Baguette magique Pencil Crayon - Clone Stamp Tampon de duplication + Clone Stamp Tampon de clonage Healing Correcteur - Dodge Densité − - Burn Densité + + Dodge Éclaircir + Burn Assombrir Pen Plume Hand Main Sampler Échantillonneur de couleur - Note tool Outil Annotation + Note tool Outil Note Quick Selection Sélection rapide Magnetic Lasso Lasso magnétique - History Brush Forme d'historique - Art History Brush Forme d'historique artistique + History Brush Pinceau d'historique + Art History Brush Pinceau d'historique artistique Pattern Stamp Tampon de motif Spot Healing Correcteur localisé - Red Eye Œil rouge + Red Eye Yeux rouges Patch Pièce - Content-Aware Move Déplacement basé sur le contenu + Content-Aware Move Déplacement selon le contenu Rotate View Rotation de la vue - Perspective Crop Correction de perspective par recadrage + Perspective Crop Recadrage en perspective Color Replacement Remplacement de couleur - Dodge/Burn Densité −/Densité + + Dodge/Burn Éclaircir/Assombrir Dynamics Dynamique Tilt Inclinaison Reset Réinitialiser @@ -2246,7 +2243,7 @@ Advanced Blending Fusion avancée RGB RVB Alpha Alpha - Spot Channel Couche de ton direct + Spot Channel Canal de ton direct Working Gray Plate Plaque grise de travail HSL/Grayscale TSL/Niveaux de gris Tone Curve Courbe des tonalités @@ -2283,7 +2280,7 @@ Localize Color Clusters Localiser les groupes de couleurs Duplicated selection name Nom de la sélection dupliquée Operation Opération - New channel only Nouvelle couche uniquement + New channel only Nouveau canal uniquement Masked Areas Zones masquées Selected Areas Zones sélectionnées Spot Color Ton direct @@ -2409,7 +2406,7 @@ Document Size Taille du document Timing Minutage Status Information Informations d'état - Saved Multichannel Multicouche enregistré + Saved Multichannel Multicanal enregistré Mouse Coordinates Coordonnées de la souris Ruler Units Unités des règles Hide Masquer @@ -2531,10 +2528,10 @@ Units Unités Columns Colonnes Create new count group Créer un nouveau groupe de comptage - Adjustment Layer Calque de réglage + Adjustment Layer Calque d'ajustement Pencil Tool Outil Crayon Pixel Pixel - Adjustment Réglage + Adjustment Ajustement Drag (lasso) or click points (polygonal) · {add} add · {sub} subtract Faites glisser (lasso) ou cliquez des points (polygonal) · {add} ajouter · {sub} soustraire Drag a crop box · drag inside to move · edges resize ({ratio} ratio, {centre} centre) · Space moves while drawing · arrows nudge · X swaps orientation · {commit} commits · Esc cancels Tracez un cadre de recadrage · faites glisser à l'intérieur pour le déplacer · les bords le redimensionnent ({ratio} proportions, {centre} centre) · Espace le déplace pendant le tracé · flèches le décalent · X permute l'orientation · {commit} valide · Échap annule {mode} Color · {bits} bit Couleurs {mode} · {bits} bits @@ -2578,12 +2575,12 @@ Brush Size Taille du pinceau Heal Color Couleur de correction Mask Masque - Channel Couche + Channel Canal Rectangle Rectangle Result Résultat - New Channel Nouvelle couche - Save Selection Mémoriser la sélection - Load Selection Récupérer la sélection + New Channel Nouveau canal + Save Selection Enregistrer la sélection + Load Selection Charger la sélection Duplicate Dupliquer Fast Rapide Accurate Précis @@ -2605,7 +2602,7 @@ Click to zoom in · ⌥-click to zoom out Cliquez pour zoomer · ⌥-clic pour dézoomer Click: corner · Drag: smooth · Click first point: close · ↩ finish · Esc cancel Clic : angle · Glisser : lisse · Clic sur le premier point : fermer · ↩ : terminer · Échap : annuler Commit Perspective Warp (↩) Valider la déformation de la perspective (↩) - Commit Puppet Warp (↩) Valider la déformation de la marionnette (↩) + Commit Puppet Warp (↩) Valider la déformation par épingles (↩) Commit any current edits (⌘↩) Valider les modifications en cours (⌘↩) Commit current crop operation (↵) Valider le recadrage en cours (↵) Commit transform (↩) Valider la transformation (↩) @@ -2633,7 +2630,7 @@ # Copy transforms, layer navigation and cancellable opening (from PR #582 by neko233-com). New from Clipboard Créer depuis le presse-papiers - Free Transform a Copy Transformation manuelle d'une copie + Free Transform a Copy Transformer librement une copie Transform Again on a Copy Répéter la transformation sur une copie Select Layer Above Sélectionner le calque au-dessus Select Layer Below Sélectionner le calque au-dessous @@ -2688,8 +2685,8 @@ cameraRaw Light Lumière Camera Raw Filter ({layer}) Filtre Camera Raw ({layer}) Camera Raw ({name}) Camera Raw ({name}) -cameraRaw Lights Teintes claires -cameraRaw Darks Teintes sombres +cameraRaw Lights Tons clairs +cameraRaw Darks Tons sombres cameraRaw Blacks Noirs cameraRaw Amount Quantité cameraRaw Tint Teinte @@ -2725,7 +2722,7 @@ cameraRaw Blending Fusion Drag or click polygon points · hold Alt during lasso for straight segments · {add} add · {sub} before drawing subtracts Faites glisser ou cliquez des points de polygone · maintenez Alt pendant le lasso pour des segments droits · {add} ajouter · {sub} avant de dessiner soustrait Extend Extension Structure: Structure : - Content-Aware Move Tool Outil Déplacement basé sur le contenu + Content-Aware Move Tool Outil Déplacement selon le contenu TIFF Options Options TIFF Targa Options Options Targa 24 bits/pixel 24 bits/pixel @@ -2792,8 +2789,8 @@ cameraRaw Blending Fusion # Shared screen eyedropper and text Color Picker (#1128). Color Picker (Text Color) Sélecteur de couleurs (Couleur du texte) Color Picker ({name}) Sélecteur de couleurs ({name}) - Color Picker (Fill Color) Sélecteur de couleurs (Couleur de fond) - Fill color Couleur de fond + Color Picker (Fill Color) Sélecteur de couleurs (Couleur de remplissage) + Fill color Couleur de remplissage Color Picker (Stroke Color) Sélecteur de couleurs (Couleur du contour) Stroke color Couleur du contour Pick screen color Choisir une couleur à l’écran @@ -2835,7 +2832,7 @@ cameraRaw Blending Fusion Rounded Steps Paliers arrondis Sawtooth 1 Dents de scie 1 - Red Eye Tool Outil Œil rouge + Red Eye Tool Outil Yeux rouges Pupil Size Taille de la pupille Darken Amount Quantité d'assombrissement Click a red pupil to neutralize it Cliquez sur une pupille rouge pour la corriger @@ -2894,8 +2891,8 @@ fontWeight Italic Italique 51 by 51 Average Moyenne 51 × 51 101 by 101 Average Moyenne 101 × 101 Sample Size: Taille de l'échantillon : - All Layers No Adjustments Tous les calques sans réglages - Current & Below No Adjustments Actif et inférieurs sans réglages + All Layers No Adjustments Tous les calques sans ajustements + Current & Below No Adjustments Actif et inférieurs sans ajustements Show Sampling Ring Afficher l'anneau d'échantillonnage # Color Picker warnings @@ -2991,7 +2988,7 @@ fontWeight Italic Italique Follows only edges this close to the pointer ([ and ] change it) Ne suit que les contours aussi proches du pointeur ([ et ] le modifient) Higher values follow only edges that contrast sharply with their surroundings Plus la valeur est élevée, plus seuls les contours très contrastés sont suivis How often fastening points are placed by themselves Fréquence de pose automatique des points d’ancrage - Appearance Settings… Paramètres d’apparence… + Appearance Settings… Réglages d’apparence… Add to favorites Ajouter aux favoris Remove from favorites Retirer des favoris # File Info ▸ Camera Data (read-only EXIF fields; "Make" is the camera maker, not the verb). @@ -3026,7 +3023,7 @@ cameraData Lens Objectif Create a new rectangular frame Créer un cadre rectangulaire Create a new elliptical frame Créer un cadre elliptique Include in playback Inclure dans la lecture - Perspective Crop Tool Outil Correction de perspective par recadrage + Perspective Crop Tool Outil Recadrage perspective Show Grid Afficher la grille Drag a box or click four corners · drag the corners onto the edges of a skewed shape · {commit} commits · Esc cancels Tracez un cadre ou cliquez sur quatre coins · faites glisser les coins sur les bords d'une forme inclinée · {commit} valide · Échap annule Artboard Tool Outil Plan de travail @@ -3046,15 +3043,15 @@ cameraData Lens Objectif There is no crop size or ratio to save Aucune taille ni proportion de recadrage à enregistrer Scale: Échelle : Snap to Origin Aligner sur l'origine - Search settings… Rechercher des paramètres… - No matching settings Aucun paramètre correspondant + Search settings… Rechercher des réglages… + No matching settings Aucun réglage correspondant Loading fonts… Chargement des polices… Response curve Courbe de réponse Tool Outil Simulate Pressure Simuler la pression Stroke Path… Contourner le tracé… Background Eraser Gomme d'arrière-plan - Healing Brush Correcteur + Healing Brush Pinceau correcteur Spot Healing Brush Correcteur localisé Smudge Doigt Sponge Éponge @@ -3090,14 +3087,14 @@ curves Smooth Lisser Presentation Présentation # Color Sampler (added with the i18n coverage repair) - Click to place · Alt-click or drag off-canvas to remove Cliquez pour placer · Alt-clic ou glisser hors de la zone de travail pour supprimer + Click to place · Alt-click or drag off-canvas to remove Cliquez pour placer · Alt-clic ou glisser hors du canevas pour supprimer No document open Aucun document ouvert {n} of {max} sample points {n} sur {max} points d'échantillonnage # Print dialog (#2697) Emulsion Down Émulsion verso Functions Fonctions Print Resolution: Résolution d’impression : - Use Previous Layer to Create Clipping Mask Utiliser le calque précédent pour créer un masque d'écrêtage + Use Previous Layer to Create Clipping Mask Utiliser le calque précédent pour créer un masque de découpe Fill with {mode}-neutral color (white) Remplir avec la couleur neutre de {mode} (blanc) Fill with {mode}-neutral color (black) Remplir avec la couleur neutre de {mode} (noir) Fill with {mode}-neutral color (50% gray) Remplir avec la couleur neutre de {mode} (gris 50 %) @@ -3180,8 +3177,8 @@ curves Smooth Lisser Add Noise Ajout de bruit Added swatch {name} Nuance {name} ajoutée Adjust Interval For Loops Ajuster l'intervalle pour les boucles - Adjustment Layers Calques de réglage - Adjustment Properties Propriétés du réglage + Adjustment Layers Calques d'ajustement + Adjustment Properties Propriétés de l'ajustement Adobe-rgb-compat Adobe RGB (compatible) Adwaita (Dark) Adwaita (sombre) Adwaita (Light) Adwaita (clair) @@ -3212,7 +3209,7 @@ curves Smooth Lisser Artboards Plans de travail Artboards to Files Plans de travail en fichiers Artboards to PDF Plans de travail en PDF - Artistic Artistiques + Artistic Artistique Ask On Mismatch Demander en cas de non-concordance Ask On Missing Demander en cas d'absence Ask On Paste Demander au collage @@ -3238,7 +3235,7 @@ curves Smooth Lisser Blade Curvature Courbure des lames Blast Rafale Bleach Bypass Sans blanchiment - Blend Clipped Layers as Group Fusionner les calques écrêtés en tant que groupe + Blend Clipped Layers as Group Fusionner les calques découpés en tant que groupe Blend Interior Effects as Group Fusionner les effets internes en tant que groupe Blend Text Gamma Gamma de fusion du texte Blending Mode Mode de fusion @@ -3262,12 +3259,12 @@ curves Smooth Lisser Brush Strokes Traits de pinceau Brush Type Type de pinceau Burlap Toile de jute - Calculations Opérations + Calculations Calculs Camera Raw Camera Raw Camera Raw Filter Filtre Camera Raw Cancel transform Annuler la transformation Candle Light Bougie - Canvas Size Taille de la zone de travail + Canvas Size Taille du canevas Cell Size Taille des cellules Center X Centre X Center Y Centre Y @@ -3275,10 +3272,10 @@ curves Smooth Lisser Changes Saved Modifications enregistrées Changes have not been saved. Les modifications n'ont pas été enregistrées. Change… Modifier… - Channel1 Couche 1 - Channel2 Couche 2 - Channel3 Couche 3 - Channel4 Couche 4 + Channel1 Canal 1 + Channel2 Canal 2 + Channel3 Canal 3 + Channel4 Canal 4 Charcoal Area Zone de fusain Charcoal Thickness Épaisseur du fusain Check for Updates Rechercher des mises à jour @@ -3288,22 +3285,22 @@ curves Smooth Lisser Cleanliness Propreté Clear Amount Zone sans lueur Clear Selection Effacer la sélection - Clipping Mask Masque d'écrêtage + Clipping Mask Masque de découpe Clipping Path… Tracé de détourage… Clone Cloner - Clone source set at {x}, {y} Source de duplication définie en {x}, {y} + Clone source set at {x}, {y} Source de clonage définie en {x}, {y} Close Window Fermer la fenêtre Clumped Aggloméré Coarse Dots Points grossiers Coated-cmyk CMJN papier couché Color Decontaminate Décontaminer les couleurs - Color Halftone Demi-teintes couleur + Color Halftone Trame de demi-teintes couleur Color Indicates Masked Areas La couleur indique les zones masquées Color Indicates Selected Areas La couleur indique les zones sélectionnées Color Lookup Tables Tables de correspondance des couleurs Color Picker Sélecteur de couleurs Color Picker (Background Color) Sélecteur de couleurs (Couleur d'arrière-plan) - Color Picker (Custom Canvas Color) Sélecteur de couleurs (Couleur personnalisée de la zone de travail) + Color Picker (Custom Canvas Color) Sélecteur de couleurs (Couleur personnalisée du canevas) Color Picker (Foreground Color) Sélecteur de couleurs (Couleur de premier plan) Color Settings Paramètres de couleur Color Swatches Nuancier @@ -3321,7 +3318,7 @@ curves Smooth Lisser Contract Contracter Contrasty Contrasté Convert Convertir - Convert to Alpha Channel Convertir en couche alpha + Convert to Alpha Channel Convertir en canal alpha Convert to Linked Convertir en objet lié Convert to Profile Convertir en profil Cool Froid @@ -3329,7 +3326,7 @@ curves Smooth Lisser Cooling80 Refroidissement 80 Cooling82 Refroidissement 82 Copy Complete Stroke Copier le contour complet - Copy Fill Copier le fond + Copy Fill Copier le remplissage Correct C A Corriger l'aberration chromatique Correct Distortion Corriger la distorsion Correct Vignette Corriger le vignettage @@ -3348,10 +3345,10 @@ curves Smooth Lisser Crosshatch Hachures croisées Crystallize Cristallisation Currency Devise - Curve Darks Courbe : teintes sombres - Curve Highlights Courbe : tons clairs - Curve Lights Courbe : teintes claires - Curve Shadows Courbe : tons foncés + Curve Darks Courbe : tons sombres + Curve Highlights Courbe : hautes lumières + Curve Lights Courbe : tons clairs + Curve Shadows Courbe : ombres Curve Splits Courbe : points de séparation Custom Branch Color Couleur de branche personnalisée Custom Pixel Aspect Ratio Format des pixels personnalisé @@ -3370,7 +3367,7 @@ curves Smooth Lisser Date Date Date and Time Date et heure Day For Night Nuit américaine - De-Interlace Désentrelacement + De-Interlace Désentrelacer Deep Profond Deep Blue Bleu profond Deep Emerald Émeraude profond @@ -3386,13 +3383,13 @@ curves Smooth Lisser Definition Définition Defringe Supprimer les franges Degrees Degrés - Delete Channel Supprimer la couche + Delete Channel Supprimer le canal Delete Mask Supprimer le masque Delete… Supprimer… Depth Map Carte de profondeur Depth Mode Mode de profondeur Diameter Diamètre - Diffuse Diffusion + Diffuse Diffuser Diffuse Glow Lueur diffuse Direction Balance Équilibre des directions Disable Désactiver @@ -3400,7 +3397,7 @@ curves Smooth Lisser Disable Symmetry Path Désactiver le tracé de symétrie Disabled Désactivé Discard Changes Ignorer les modifications - Displace Dispersion + Displace Déplacement Display Affichage Display profile not used Profil d'affichage non utilisé Display profiles: {reason} Profils d'affichage : {reason} @@ -3417,11 +3414,11 @@ curves Smooth Lisser Drag: draw freehand · Release on the start point: close Glisser : tracer à main levée · Relâcher sur le point de départ : fermer Driver: {driver} Pilote : {driver} Dry Brush Pinceau sec - Duplicate Channel Dupliquer la couche + Duplicate Channel Dupliquer le canal Duplicate Layer Dupliquer le calque Duplicate Layers Dupliquer les calques Duplication Duplication - Dust & Scratches Antipoussière + Dust & Scratches Poussière et rayures Edge Brightness Luminosité du contour Edge Extension Extension du contour Edge Fidelity Fidélité des contours @@ -3440,7 +3437,7 @@ curves Smooth Lisser End Y Fin Y Enlarged Agrandi Even Fields Trames paires - Exit Quick Mask Quitter le mode Masque + Exit Quick Mask Quitter le mode masque rapide Export As Exporter sous Export Contents Exporter le contenu Export Image Exporter l'image @@ -3454,23 +3451,23 @@ curves Smooth Lisser Exported {path} {path} exporté Exported {path} ({size}) {path} exporté ({size}) Exporting… Exportation… - Extrude Extrusion + Extrude Extruder Faded Film Film délavé Failed Échec Failed to open file Échec de l'ouverture du fichier Failed to save file Échec de l'enregistrement du fichier Fallback: {reason} Repli : {reason} - Feather Radius Rayon du contour progressif + Feather Radius Rayon d'adoucissement Fiber Length Longueur des fibres Fibers Fibres - Field Blur Flou de profondeur de champ + Field Blur Flou de champ File Format Format de fichier File Info Informations sur le fichier File Name Nom du fichier File Size Taille du fichier File not found Fichier introuvable Filename Nom de fichier - Fill Color Couleur de fond + Fill Color Couleur de remplissage Fill Path… Remplir le tracé… Film Grain Grain de film Filter Gallery Galerie de filtres @@ -3496,7 +3493,7 @@ curves Smooth Lisser Foreground Level Niveau de premier plan Forward Warp Déformation vers l'avant Frame Cadre - Free Transform Path Transformation manuelle du tracé + Free Transform Path Transformation libre du tracé Freeze Lasso Lasso de figeage Freeze Mask Masque de figeage Fresco Fresque @@ -3526,9 +3523,9 @@ curves Smooth Lisser Grade Balance Balance de l'étalonnage Grade Blending Fusion de l'étalonnage Grade Global Étalonnage global - Grade Highlights Étalonnage des tons clairs + Grade Highlights Étalonnage des hautes lumières Grade Midtones Étalonnage des tons moyens - Grade Shadows Étalonnage des tons foncés + Grade Shadows Étalonnage des ombres Grain Amount Quantité de grain Grain Roughness Rugosité du grain Grain Size Taille du grain @@ -3548,7 +3545,7 @@ curves Smooth Lisser Groups Groupes Grout Width Largeur du joint Guide Layout… Disposition des repères… - HDR Toning Virage HDR + HDR Toning Tonalité HDR Hearts Cœurs Heptagon Heptagone Hexagon Hexagone @@ -3557,12 +3554,12 @@ curves Smooth Lisser Hide Others Masquer les autres Hide {app} Masquer {app} High Pass Passe-haut - Highlight Tons clairs - Highlight Amount Quantité des tons clairs - Highlight Area Zone des tons clairs - Highlight Radius Rayon des tons clairs - Highlight Strength Intensité des tons clairs - Highlight Tone Amplitude tonale des tons clairs + Highlight Hautes lumières + Highlight Amount Quantité des hautes lumières + Highlight Area Zone des hautes lumières + Highlight Radius Rayon des hautes lumières + Highlight Strength Intensité des hautes lumières + Highlight Tone Amplitude tonale des hautes lumières Horizontal Only Horizontal seulement Hotspot Point chaud Hsb TSL @@ -3588,7 +3585,7 @@ curves Smooth Lisser Input Entrée Input Levels Niveaux d'entrée Input Mode Mode d'entrée - Install Plug-in Installer un module externe + Install Plug-in Installer une extension Integrated Density Densité intégrée Intensity Intensité Intent Intention de rendu @@ -3596,8 +3593,8 @@ curves Smooth Lisser Interval Intervalle Invalid panel layout; restored the default dock Disposition des panneaux non valide ; l'ancrage par défaut a été rétabli Invert Mask Inverser le masque - Invert Selection Intervertir la sélection - Iris Blur Flou de diaphragme + Invert Selection Inverser la sélection + Iris Blur Flou d'iris Ivy Lierre Jag Dentelé Join the ArtCraft Discord Rejoindre le Discord d'ArtCraft @@ -3612,7 +3609,7 @@ curves Smooth Lisser Layer Comps to Files Compositions de calques en fichiers Layer Content Options Options de contenu du calque Layer From Background Calque d'après l'arrière-plan - Layer Mask Hides Effects Le masque de fusion masque les effets + Layer Mask Hides Effects Le masque de calque masque les effets Layer Properties Propriétés du calque Layer Styles Styles de calque Layer from Background Calque d'après l'arrière-plan @@ -3625,9 +3622,9 @@ curves Smooth Lisser Left Diagonal Diagonale gauche Legacy Ancienne version Length Longueur - Lens Blur Flou de l'objectif + Lens Blur Flou d'objectif Lens Correction Correction de l'objectif - Lens Flare Halo + Lens Flare Reflet d'objectif Less Moins Level Based Selon les niveaux Library Bibliothèque @@ -3642,11 +3639,11 @@ curves Smooth Lisser Lighten Grout Éclaircir le joint Lighten Only Éclaircir seulement Lighting Éclairage - Lighting Effects Éclairage + Lighting Effects Effets d'éclairage Linear Dodge Densité linéaire − Linear-srgb sRVB linéaire Link Lier - Liquify Fluidité + Liquify Liquéfier List Liste List view Affichage en liste Live Preview Aperçu en direct @@ -3674,7 +3671,7 @@ curves Smooth Lisser Mask Incomplete Blocks Masquer les blocs incomplets Mask None Ne rien masquer Masks Masques - Match Color Correspondance de la couleur + Match Color Faire correspondre la couleur Math Symbols Symboles mathématiques Max Offset Décalage max. Max Radius Rayon max. @@ -3682,11 +3679,11 @@ curves Smooth Lisser Medium Gray Gris moyen Medium Lines Lignes moyennes Medium Strokes Traits moyens - Merge Channels… Fusionner les couches… - Merge Spot Channel Fusionner la couche de ton direct + Merge Channels… Fusionner les canaux… + Merge Spot Channel Fusionner le canal de ton direct Merge to HDR Pro Fusionner en HDR Pro Metallic Métallique - Mezzotint Mezzo-tinto + Mezzotint Manière noire Middle Milieu Midtone Contraste des tons moyens Monitor Profile Profil du moniteur @@ -3700,7 +3697,7 @@ curves Smooth Lisser More Plus Mosaic Mosaïque Mosaic Tiles Carreaux de mosaïque - Motion Blur Flou directionnel + Motion Blur Flou de mouvement Mouse Souris Move Layer Déplacer le calque Movie Prime Focale fixe cinéma @@ -3712,10 +3709,10 @@ curves Smooth Lisser Neon Glow Lueur néon Neutralize Neutraliser New Brush Preset Nouveau préréglage de pinceau - New Channel… Nouvelle couche… + New Channel… Nouveau canal… New Guide Nouveau repère New Guide Layout Nouvelle disposition de repères - New Spot Channel… Nouvelle couche de ton direct… + New Spot Channel… Nouveau canal de ton direct… New Video Layer from File Nouveau calque vidéo à partir d'un fichier New Window Nouvelle fenêtre New document Nouveau document @@ -3771,7 +3768,7 @@ curves Smooth Lisser Paper Brightness Luminosité du papier Parallel Parallèle Paste Complete Stroke Coller le contour complet - Paste Fill Coller le fond + Paste Fill Coller le remplissage Patchwork Patchwork Path Blur Flou de tracé Paths to Illustrator Tracés vers Illustrator @@ -3784,10 +3781,10 @@ curves Smooth Lisser PhotoCraft keeps retrying; until a save succeeds, preference changes are lost when it closes. PhotoCraft continue de réessayer ; tant qu'un enregistrement n'a pas abouti, les modifications des préférences seront perdues à la fermeture. Photomerge Photomerge Picture Frame Cadre d'image - Pinch Contraction + Pinch Pincement Pins Épingles - Place Embedded Importer et incorporer - Place Linked Importer et lier + Place Embedded Placer incorporé + Place Linked Placer lié Place Scale Marker Placer un repère d'échelle Plane Plan Planes Plans @@ -3854,14 +3851,14 @@ curves Smooth Lisser Remove All Tout supprimer Remove Jpeg Artifact Supprimer l'artefact JPEG Rename Brush Renommer le pinceau - Rename Channel… Renommer la couche… + Rename Channel… Renommer le canal… Rename Group Renommer le groupe Rename Tool Preset Renommer le préréglage d'outil Render Video Rendu vidéo Rendering… Rendu en cours… Reorder Layer Réorganiser le calque Replace Remplacer - Replace Color Remplacement de couleur + Replace Color Remplacer la couleur Replace Contents Remplacer le contenu Replace Footage Remplacer le métrage Report a Bug Signaler un bogue @@ -3910,7 +3907,7 @@ curves Smooth Lisser Search menus Rechercher dans les menus Seed Valeur initiale Select Custom Color… Choisir une couleur personnalisée… - Select Inverse Intervertir la sélection + Select Inverse Inverser la sélection Select Layer Sélectionner le calque Select and Mask Sélectionner et masquer Selected at launch: {backend} Sélectionné au lancement : {backend} @@ -3921,12 +3918,12 @@ curves Smooth Lisser Set Colors Définir les couleurs Set Measurement Scale Définir l'échelle de mesure Sgray sGray - Shadow Tons foncés - Shadow Amount Quantité des tons foncés - Shadow Intensity Intensité des tons foncés - Shadow Radius Rayon des tons foncés - Shadow Tone Amplitude tonale des tons foncés - Shadows/Highlights Tons foncés/Tons clairs + Shadow Ombres + Shadow Amount Quantité des ombres + Shadow Intensity Intensité des ombres + Shadow Radius Rayon des ombres + Shadow Tone Amplitude tonale des ombres + Shadows/Highlights Ombres/Hautes lumières Shallow Superficiel Shape Blur Flou selon une forme Sharpen Amount Quantité de netteté @@ -3947,8 +3944,8 @@ curves Smooth Lisser Sketch Esquisse Skip Ignorer Slice Options Options de tranche - Smart Blur Flou optimisé - Smart Sharpen Netteté optimisée + Smart Blur Flou intelligent + Smart Sharpen Netteté intelligente Smoothness Lissage Smudge Stick Estompe Snap to Grid Aimanter à la grille @@ -3966,7 +3963,7 @@ curves Smooth Lisser Spherize Sphérisation Spin Rotation Spin Blur Flou de rotation - Split Channels Séparer les couches + Split Channels Séparer les canaux Spot Spot Spray Radius Rayon de pulvérisation Sprayed Strokes Traits pulvérisés @@ -4009,8 +4006,8 @@ curves Smooth Lisser Texture Coverage Couverture de la texture Texturizer Texturisation Thaw Mask Masque de libération - The canvas is shown as sRGB. Choose a monitor profile in Edit › Color Settings. La zone de travail est affichée en sRVB. Choisissez un profil de moniteur dans Édition › Paramètres de couleur. - The canvas on {display} is shown as sRGB. Choose a monitor profile in Edit › Color Settings. La zone de travail sur {display} est affichée en sRVB. Choisissez un profil de moniteur dans Édition › Paramètres de couleur. + The canvas is shown as sRGB. Choose a monitor profile in Edit › Color Settings. Le canevas est affiché en sRVB. Choisissez un profil de moniteur dans Édition › Paramètres de couleur. + The canvas on {display} is shown as sRGB. Choose a monitor profile in Edit › Color Settings. Le canevas sur {display} est affiché en sRVB. Choisissez un profil de moniteur dans Édition › Paramètres de couleur. The current gradient (pick one in Window › Gradients) Le dégradé actuel (à choisir dans Fenêtre › Dégradés) The document does not have an embedded colour profile. Working space: {working}. Le document ne contient aucun profil colorimétrique incorporé. Espace de travail : {working}. The document has an embedded colour profile that does not match the working space.\nEmbedded: {embedded}\nWorking: {working} Le document contient un profil colorimétrique incorporé qui ne correspond pas à l'espace de travail.\nIncorporé : {embedded}\nEspace de travail : {working} @@ -4018,15 +4015,15 @@ curves Smooth Lisser The file could not be saved. Impossible d'enregistrer le fichier. Thickness Épaisseur This Layer: Ce calque : - This Smart Object must be rasterized before proceeding. Its contents will no longer be editable. Rasterize the Smart Object? Cet objet dynamique doit être pixellisé pour continuer. Son contenu ne sera plus modifiable. Pixelliser l'objet dynamique ? - This adjustment ({key}) is kept as imported and can't be edited yet. Ce réglage ({key}) est conservé tel qu'il a été importé et ne peut pas encore être modifié. - This fill layer must be rasterized before proceeding. Its fill content will no longer be editable. Rasterize the layer? Ce calque de remplissage doit être pixellisé pour continuer. Son contenu de remplissage ne sera plus modifiable. Pixelliser le calque ? - This layer must be rasterized before proceeding. Ce calque doit être pixellisé pour continuer. + This Smart Object must be rasterized before proceeding. Its contents will no longer be editable. Rasterize the Smart Object? Cet objet intelligent doit être rastérisé pour continuer. Son contenu ne sera plus modifiable. Rastériser l'objet intelligent ? + This adjustment ({key}) is kept as imported and can't be edited yet. Cet ajustement ({key}) est conservé tel qu'il a été importé et ne peut pas encore être modifié. + This fill layer must be rasterized before proceeding. Its fill content will no longer be editable. Rasterize the layer? Ce calque de remplissage doit être rastérisé pour continuer. Son contenu de remplissage ne sera plus modifiable. Rastériser le calque ? + This layer must be rasterized before proceeding. Ce calque doit être rastérisé pour continuer. This session: {reason} Cette session : {reason} - This shape layer must be rasterized before proceeding. It will no longer be editable as a shape. Rasterize the shape? Ce calque de forme doit être pixellisé pour continuer. Il ne sera plus modifiable en tant que forme. Pixelliser la forme ? - This type layer must be rasterized before proceeding. Its text will no longer be editable. Rasterize the type? Ce calque de texte doit être pixellisé pour continuer. Son texte ne sera plus modifiable. Pixelliser le texte ? + This shape layer must be rasterized before proceeding. It will no longer be editable as a shape. Rasterize the shape? Ce calque de forme doit être rastérisé pour continuer. Il ne sera plus modifiable en tant que forme. Rastériser la forme ? + This type layer must be rasterized before proceeding. Its text will no longer be editable. Rasterize the type? Ce calque de texte doit être rastérisé pour continuer. Son texte ne sera plus modifiable. Rastériser le texte ? Tile Size Taille des carreaux - Tiles Carrelage + Tiles Carreaux Tilt-Shift Inclinaison-décalage Tint Color Couleur de la nuance Tiny Lens Petites lentilles @@ -4034,13 +4031,13 @@ curves Smooth Lisser To Center Vers le centre Toggle the Brush Settings panel Afficher/masquer le panneau Paramètres du pinceau Tool Options Options d'outil - Trace Contour Courbes de niveau + Trace Contour Tracer les contours Transformation Transformation Transition Transition Transparency Shapes Layer La transparence détermine la forme du calque Trap Recouvrement Tree Arbre - Trim Rognage + Trim Rogner les bords Turbulent Turbulent Twirl Tourbillon Twirl Clockwise Tourbillon horaire @@ -4057,7 +4054,7 @@ curves Smooth Lisser Unlock Déverrouiller Unlocked Déverrouillé Unsaved Changes Modifications non enregistrées - Unsharp Mask Accentuation + Unsharp Mask Masque flou Unsupported file format Format de fichier non pris en charge Unsupported format Format non pris en charge Up Haut @@ -4075,8 +4072,8 @@ curves Smooth Lisser Vertical Type Tool Outil Texte vertical Video Frames to Layers Images vidéo dans des calques Vignette Amount Quantité de vignettage - Vignette Feather Contour progressif du vignettage - Vignette Highlights Tons clairs du vignettage + Vignette Feather Adoucissement du vignettage + Vignette Highlights Hautes lumières du vignettage Vignette Midpoint Point médian du vignettage Vignette Roundness Arrondi du vignettage Vignette Style Style du vignettage @@ -4102,7 +4099,7 @@ curves Smooth Lisser White Is High Le blanc est le plus haut Wide Blurry Large et flou Wide Sharp Large et net - Wind Soufflerie + Wind Vent Window renderer: {renderer} Moteur de rendu de la fenêtre : {renderer} Working Espace de travail Working Cmyk CMJN de travail @@ -4119,7 +4116,7 @@ curves Smooth Lisser gradients dégradés gray gris green vert - layer mask masque de fusion + layer mask masque de calque manual manuel none aucun off désactivé @@ -4141,11 +4138,11 @@ curves Smooth Lisser ✓ Color Indicates Masked Areas ✓ La couleur indique les zones masquées ✓ Color Indicates Selected Areas ✓ La couleur indique les zones sélectionnées @id edit.clear Effacer -@id edit.stroke Contour… -@id edit.transform.scale Homothétie +@id edit.stroke Contourner… +@id edit.transform.scale Mise à l'échelle @id file.close Fermer -@id filter.sharpen.sharpen Plus net -@id layer.layerMask.apply Appliquer le masque de fusion +@id filter.sharpen.sharpen Netteté +@id layer.layerMask.apply Appliquer le masque de calque @plural Exported {n} swatch to {file}|Exported {n} swatches to {file} {n} nuance exportée vers {file}|{n} nuances exportées vers {file} @plural Imported {n} brush from {file}|Imported {n} brushes from {file} {n} pinceau importé depuis {file}|{n} pinceaux importés depuis {file} @plural Imported {n} brush from {group}|Imported {n} brushes from {group} {n} pinceau importé depuis {group}|{n} pinceaux importés depuis {group} @@ -4153,7 +4150,7 @@ curves Smooth Lisser @plural Imported {n} swatch from {file}|Imported {n} swatches from {file} {n} nuance importée depuis {file}|{n} nuances importées depuis {file} @plural Played {n} step|Played {n} steps {n} étape lue|{n} étapes lues @plural Saved for Web: {n} file|Saved for Web: {n} files Enregistré pour le Web : {n} fichier|Enregistré pour le Web : {n} fichiers -@plural Updated {n} linked smart object changed on disk|Updated {n} linked smart objects changed on disk Mise à jour de {n} objet dynamique lié modifié sur le disque|Mise à jour de {n} objets dynamiques liés modifiés sur le disque +@plural Updated {n} linked smart object changed on disk|Updated {n} linked smart objects changed on disk Mise à jour de {n} objet intelligent lié modifié sur le disque|Mise à jour de {n} objets intelligents liés modifiés sur le disque @plural {n} layer recorded by this comp was deleted. Update the comp, or clear the warning.|{n} layers recorded by this comp were deleted. Update the comp, or clear the warning. {n} calque enregistré par cette composition a été supprimé. Mettez la composition à jour ou effacez l'avertissement.|{n} calques enregistrés par cette composition ont été supprimés. Mettez la composition à jour ou effacez l'avertissement. @plural {n} plane|{n} planes {n} plan|{n} plans @plural {n} preset|{n} presets {n} préréglage|{n} préréglages @@ -4176,10 +4173,10 @@ cameraRaw Dehaze Correction de la brume cameraRaw Detail Détail cameraRaw Effects Effets cameraRaw Exposure Exposition -cameraRaw Feather Contour progressif +cameraRaw Feather Adoucissement cameraRaw Global Global cameraRaw Green Vert -cameraRaw Highlights Tons clairs +cameraRaw Highlights Hautes lumières cameraRaw Hue Teinte cameraRaw Luminance Luminance cameraRaw Luminance Detail Détail de la luminance @@ -4193,7 +4190,7 @@ cameraRaw Red Rouge cameraRaw Roughness Rugosité cameraRaw Roundness Arrondi cameraRaw Saturation Saturation -cameraRaw Shadows Tons foncés +cameraRaw Shadows Ombres cameraRaw Size Taille cameraRaw Temperature Température cameraRaw Texture Texture @@ -4205,7 +4202,7 @@ document Orientation Orientation layerLabel Magenta Magenta layerLabel No Color Aucune couleur layerLabel Red Rouge -layers Fill Fond -layers Fill: Fond : +layers Fill Remplissage +layers Fill: Remplissage : navigator Zoom Zoom shape Appearance Apparence From a4b1e2a60700028905a8752d040c086ff2163c72 Mon Sep 17 00:00:00 2001 From: Fazmin Date: Wed, 14 Oct 2026 23:09:05 -0400 Subject: [PATCH 13/44] Type: Create warped text button in the options bar, as in Photoshop (#5911) The Type tool's options bar now has Photoshop's Create warped text button after the text colour swatch. It opens the same Warp Text dialog as Type > Warp Text... for the selected type layers (or the layer being edited), and is greyed when there is no type layer to warp. - assets/icons/text-warp.svg: original glyph (a T above a bowed arc) on Lucide's 24 px grid and stroke; listed in ATTRIBUTION.md. - The tooltip is translated in all 20 catalogs, reusing each language's existing "Create Warped Text" wording (new for Russian). Test: options_bar_warp_button_warps_the_selected_type_layers. Co-Authored-By: Claude --- ATTRIBUTION.md | 3 ++- assets/icons/text-warp.svg | 17 +++++++++++++++++ crates/ui-egui/src/i18n/cs.tsv | 1 + crates/ui-egui/src/i18n/de.tsv | 1 + crates/ui-egui/src/i18n/el.tsv | 1 + crates/ui-egui/src/i18n/es.tsv | 1 + crates/ui-egui/src/i18n/fr.tsv | 1 + crates/ui-egui/src/i18n/hu.tsv | 1 + crates/ui-egui/src/i18n/id.tsv | 1 + crates/ui-egui/src/i18n/it.tsv | 1 + crates/ui-egui/src/i18n/ja.tsv | 1 + crates/ui-egui/src/i18n/ko.tsv | 1 + crates/ui-egui/src/i18n/nl.tsv | 1 + crates/ui-egui/src/i18n/pl.tsv | 1 + crates/ui-egui/src/i18n/pt-br.tsv | 1 + crates/ui-egui/src/i18n/ru.tsv | 1 + crates/ui-egui/src/i18n/sr.tsv | 1 + crates/ui-egui/src/i18n/tr.tsv | 1 + crates/ui-egui/src/i18n/uk.tsv | 1 + crates/ui-egui/src/i18n/vi.tsv | 1 + crates/ui-egui/src/i18n/zh-hans.tsv | 1 + crates/ui-egui/src/i18n/zh-hant.tsv | 1 + crates/ui-egui/src/icon_data.rs | 1 + crates/ui-egui/src/type_tool.rs | 8 ++++++++ crates/ui-egui/src/type_tool_tests.rs | 23 +++++++++++++++++++++++ 25 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 assets/icons/text-warp.svg diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index 26eca62464..ffb53b5506 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -27,7 +27,7 @@ next to them. | `crates/ui-egui/src/i18n/sr.tsv` | Serbian (Latin, Ekavian) UI translations | PhotoCraft contributors | Original translations of PhotoCraft's English labels | MIT OR Apache-2.0, [`LICENSE-translations.txt`](crates/ui-egui/src/i18n/LICENSE-translations.txt) | | `assets/fonts/Inter-Regular.ttf`, `Inter-Medium.ttf`, `Inter-SemiBold.ttf` | Inter 4.001 (UI font) | The Inter Project Authors (Rasmus Andersson) | | SIL OFL 1.1, [`assets/fonts/OFL-Inter.txt`](assets/fonts/OFL-Inter.txt) | | `assets/fonts/JetBrainsMono-Regular.ttf` | JetBrains Mono 2.305 (numeric font) | The JetBrains Mono Project Authors | | SIL OFL 1.1, [`assets/fonts/OFL-JetBrainsMono.txt`](assets/fonts/OFL-JetBrainsMono.txt) | -| `assets/icons/*.svg` except `slice-knife.svg`, `sampler.svg`, `artboard.svg`, `perspective-crop.svg`, `frame-placeholder.svg`, `gradient.svg`, `type-mask.svg`, `type-mask-vertical.svg`, `clip-below.svg`, `clip-release.svg`, `layer-mask.svg`, `adjustment-layer.svg`, `brush-replace.svg`, `quick-selection.svg`, `eraser-background.svg`, `eraser-magic.svg`, `lasso-magnetic.svg`, `direct-select.svg`, `convert-point.svg`, `pen-add-anchor.svg`, `pen-delete-anchor.svg`, `pen-freeform.svg`, `pen-curvature.svg`, `bandage-sparkle.svg`, `painting-symmetry.svg`, `symmetry-*.svg` and `marquee-{row,column}.svg` (113 files; `star-filled.svg` is `star.svg` with a solid fill) | Lucide icons | Lucide Icons and Contributors | (`icons/.svg`; `align-*` are now named `text-align-*` upstream) | ISC, [`assets/icons/LICENSE-lucide.txt`](assets/icons/LICENSE-lucide.txt) | +| `assets/icons/*.svg` except `slice-knife.svg`, `sampler.svg`, `artboard.svg`, `perspective-crop.svg`, `frame-placeholder.svg`, `gradient.svg`, `type-mask.svg`, `type-mask-vertical.svg`, `text-warp.svg`, `clip-below.svg`, `clip-release.svg`, `layer-mask.svg`, `adjustment-layer.svg`, `brush-replace.svg`, `quick-selection.svg`, `eraser-background.svg`, `eraser-magic.svg`, `lasso-magnetic.svg`, `direct-select.svg`, `convert-point.svg`, `pen-add-anchor.svg`, `pen-delete-anchor.svg`, `pen-freeform.svg`, `pen-curvature.svg`, `bandage-sparkle.svg`, `painting-symmetry.svg`, `symmetry-*.svg` and `marquee-{row,column}.svg` (113 files; `star-filled.svg` is `star.svg` with a solid fill) | Lucide icons | Lucide Icons and Contributors | (`icons/.svg`; `align-*` are now named `text-align-*` upstream) | ISC, [`assets/icons/LICENSE-lucide.txt`](assets/icons/LICENSE-lucide.txt) | | `assets/icons/{check,chevron-down,chevron-left,chevron-right,chevron-up,chevrons-left,chevrons-right,circle,clock,compass,info,link,link-2,lock,minus,monitor,moon,move,navigation,plus,search,square,trash,triangle,type,x,zoom-in}.svg` | Lucide icons derived from Feather (subset of the row above) | Cole Bemis (Feather), Lucide Contributors | via Lucide | MIT (Feather) and ISC (Lucide), [`assets/icons/LICENSE-lucide.txt`](assets/icons/LICENSE-lucide.txt) | | `assets/icons/slice-knife.svg` | Slice tool glyph | PhotoCraft contributors | Original work | MIT OR Apache-2.0 | | `assets/icons/sampler.svg` | Color Sampler tool glyph: a target reticle marking a sampled pixel | PhotoCraft contributors | Original work | MIT OR Apache-2.0 | @@ -35,6 +35,7 @@ next to them. | `assets/icons/artboard.svg` | Artboard tool glyph: a board with a tick at each end of its edges | PhotoCraft contributors | Original work | MIT OR Apache-2.0 | | `assets/icons/perspective-crop.svg` | Perspective Crop tool glyph: a crop frame in perspective with its grid and crop-mark ticks | PhotoCraft contributors | Original work | MIT OR Apache-2.0 | | `assets/icons/type-mask.svg`, `assets/icons/type-mask-vertical.svg` | Horizontal and Vertical Type Mask tool glyphs: a T drawn as marching ants (the vertical one beside a downward arrow), on Lucide's 24 px grid and stroke | PhotoCraft contributors | Original work | MIT OR Apache-2.0 | +| `assets/icons/text-warp.svg` | Create Warped Text glyph (Type tool options bar): a T above a bowed arc, drawn on Lucide's 24 px grid and stroke | PhotoCraft contributors | Original work | MIT OR Apache-2.0 | | `assets/icons/marquee-row.svg`, `assets/icons/marquee-column.svg` | Single Row and Single Column Marquee tool glyphs: a dashed row (column) between two edge bars, drawn on Lucide's 24 px grid and stroke | PhotoCraft contributors | Original work | MIT OR Apache-2.0 | | `assets/icons/clip-below.svg`, `assets/icons/clip-release.svg` | Clipping-mask cursors (⌥ over the line between two layers): a hooked arrow onto the layer below; the same with a small "no" badge to release | PhotoCraft contributors | Original work | MIT OR Apache-2.0 | | `assets/icons/layer-mask.svg`, `assets/icons/adjustment-layer.svg` | Layers panel footer glyphs: Add a mask (a frame around a solid disc) and New Fill or Adjustment Layer (a circle, one half solid), drawn on Lucide's 24 px grid and stroke | PhotoCraft contributors | Original work | MIT OR Apache-2.0 | diff --git a/assets/icons/text-warp.svg b/assets/icons/text-warp.svg new file mode 100644 index 0000000000..03f30ed87b --- /dev/null +++ b/assets/icons/text-warp.svg @@ -0,0 +1,17 @@ + + + + + + + diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 5cfc0f8ff5..0edf24fa9e 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -1498,6 +1498,7 @@ Set shape fill type Nastavit typ výplně tvaru Set shape stroke type Nastavit typ tahu tvaru Set the text color Nastavit barvu textu + Create warped text Vytvořit deformovaný text Shadows Stíny Shape: Tvar: Sharpening Doostření diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 05ac77803c..d64f465bea 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -1481,6 +1481,7 @@ Set shape fill type Fülltyp der Form festlegen Set shape stroke type Konturtyp der Form festlegen Set the text color Textfarbe festlegen + Create warped text Verkrümmten Text erstellen Shadows Tiefen Shape: Form: Sharpening Scharfzeichnung diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index c6c5ff8f66..2025aeff77 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -3033,6 +3033,7 @@ Set shape fill type Ορισμός τύπου γεμίσματος σχήματος Set shape stroke type Ορισμός τύπου περιγράμματος σχήματος Set the text color Ορισμός χρώματος κειμένου + Create warped text Δημιουργία παραμορφωμένου κειμένου Shadows/Highlights Σκιές/φωτεινές περιοχές Shape Σχήμα Shape Blur Θόλωση σχήματος diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index e24171195a..753da30ade 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -1488,6 +1488,7 @@ Set shape fill type Establecer el tipo de relleno de la forma Set shape stroke type Establecer el tipo de trazo de la forma Set the text color Establecer el color del texto + Create warped text Crear texto deformado Shadows Sombras Shape: Forma: Sharpening Enfoque diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index db40d97abf..ee45d66e3b 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -1468,6 +1468,7 @@ Set shape fill type Définir le type de remplissage de la forme Set shape stroke type Définir le type de contour de la forme Set the text color Définir la couleur du texte + Create warped text Créer un texte déformé Shadows Ombres Shape: Forme : Sharpening Netteté diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 54bde5d0c4..4175879d44 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -1304,6 +1304,7 @@ Set painting symmetry options Festési szimmetria beállításai Set the overlay options for the Crop tool A Körbevágás eszköz fedvénybeállításai Set the text color Szövegszín beállítása + Create warped text Torzított szöveg létrehozása Settings: Beállítások: Shape Dynamics Alakdinamika Sharpen Tool Élesítő diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 544ead05a4..584ef448f3 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -1462,6 +1462,7 @@ Set shape fill type Atur jenis isian bentuk Set shape stroke type Atur jenis stroke bentuk Set the text color Atur warna teks + Create warped text Buat teks warp Shadows Bayangan Shape: Bentuk: Sharpening Penajaman diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 40b468926e..6f7cf38347 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -1483,6 +1483,7 @@ Set shape fill type Imposta tipo riempimento forma Set shape stroke type Imposta tipo traccia forma Set the text color Imposta colore testo + Create warped text Crea testo deformato Shadows Ombre Shape: Forma: Sharpening Contrasto diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 1d64bf868c..2e9d86ec4d 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -1485,6 +1485,7 @@ Set shape fill type シェイプの塗りの種類を設定 Set shape stroke type シェイプの線の種類を設定 Set the text color 文字色を設定 + Create warped text ワープテキストを作成 Shadows シャドウ Shape: シェイプ: Sharpening シャープ diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index e76a3c9826..5aeaf20ec2 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -1457,6 +1457,7 @@ Set shape fill type 모양 채우기 유형 설정 Set shape stroke type 모양 획 유형 설정 Set the text color 텍스트 색상 설정 + Create warped text 뒤틀린 텍스트 만들기 Shadows 어두운 영역 Shape: 모양: Sharpening 선명 효과 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 2c9416c707..aea65c48fd 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -1466,6 +1466,7 @@ Set shape fill type Vultype van vorm instellen Set shape stroke type Lijntype van vorm instellen Set the text color De tekstkleur instellen + Create warped text Verbogen tekst maken Shadows Schaduwen Shape: Vorm: Sharpening Verscherping diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index eee1e61a90..ba8fd7af03 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -1481,6 +1481,7 @@ Set shape fill type Ustaw typ wypełnienia kształtu Set shape stroke type Ustaw typ obrysu kształtu Set the text color Ustaw kolor tekstu + Create warped text Utwórz wypaczony tekst Shadows Cienie Shape: Kształt: Sharpening Wyostrzanie diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 3e10fe7dbb..04be6a5c8c 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -1463,6 +1463,7 @@ Set shape fill type Definir tipo de preenchimento da forma Set shape stroke type Definir tipo de traçado da forma Set the text color Definir a cor do texto + Create warped text Criar texto deformado Shadows Sombras Shape: Forma: Sharpening Nitidez diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 862788630b..63ecf634f2 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -782,6 +782,7 @@ Set shape fill type Тип заливки фигуры Set shape stroke type Тип обводки фигуры Set the text color Цвет текста + Create warped text Создать деформированный текст Only Web Colors Только web-цвета Overlay Color Цвет наложения Overlay Opacity Непрозрачность наложения diff --git a/crates/ui-egui/src/i18n/sr.tsv b/crates/ui-egui/src/i18n/sr.tsv index b91b67b3eb..bb7d2f17ff 100644 --- a/crates/ui-egui/src/i18n/sr.tsv +++ b/crates/ui-egui/src/i18n/sr.tsv @@ -1446,6 +1446,7 @@ Set shape fill type Podesi vrstu popune oblika Set shape stroke type Podesi vrstu obruba oblika Set the text color Postavi boju teksta + Create warped text Napravi iskrivljeni tekst Shadows Senke Shape: Oblik: Sharpening Izoštravanje diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 4c8f06ccc1..9f710549fd 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -1442,6 +1442,7 @@ Set shape fill type Şekil doldurma türünü ayarla Set shape stroke type Şekil kontur türünü ayarlama Set the text color Metin rengini ayarlama + Create warped text Çarpık metin oluştur Shadows Gölgeler Shape: Şekil: Sharpening Bileme diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 39597c15cb..ed5b4de734 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -759,6 +759,7 @@ Set shape fill type Установити тип заливки фігури Set shape stroke type Установити тип обведення фігури Set the text color Установити колір тексту + Create warped text Створити деформований текст Only Web Colors Лише вебкольори Overlay Color Колір накладання Overlay Opacity Непрозорість накладання diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index ac7123d850..2254010c2b 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -870,6 +870,7 @@ Right align text Căn chữ phải Toggle text orientation Đổi hướng chữ Set the text color Đặt màu chữ + Create warped text Tạo chữ uốn Cancel any current edits (Esc) Hủy chỉnh sửa hiện tại (Esc) Commit any current edits ({key}) Áp dụng chỉnh sửa hiện tại ({key}) Author: Tác giả: diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 2307739ed3..f3637050d1 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -1498,6 +1498,7 @@ Set shape fill type 设置形状填充类型 Set shape stroke type 设置形状描边类型 Set the text color 设置文字颜色 + Create warped text 创建变形文字 Shadows 阴影 Shape: 形状: Sharpening 锐化 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 478489f8c7..fc58d4e86c 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -1494,6 +1494,7 @@ Set shape fill type 設定形狀填滿類型 Set shape stroke type 設定形狀筆畫類型 Set the text color 設定文字顏色 + Create warped text 建立彎曲文字 Shadows 陰影 Shape: 形狀: Sharpening 銳利化 diff --git a/crates/ui-egui/src/icon_data.rs b/crates/ui-egui/src/icon_data.rs index 9301f709ce..e7e919bd96 100644 --- a/crates/ui-egui/src/icon_data.rs +++ b/crates/ui-egui/src/icon_data.rs @@ -134,6 +134,7 @@ pub static ICONS: &[(&str, &[u8])] = &[ ("symmetry-vertical", include_bytes!("../../../assets/icons/symmetry-vertical.svg")), ("swatch-book", include_bytes!("../../../assets/icons/swatch-book.svg")), ("text-cursor", include_bytes!("../../../assets/icons/text-cursor.svg")), + ("text-warp", include_bytes!("../../../assets/icons/text-warp.svg")), ("toggle-left", include_bytes!("../../../assets/icons/toggle-left.svg")), ("toggle-right", include_bytes!("../../../assets/icons/toggle-right.svg")), ("trash", include_bytes!("../../../assets/icons/trash.svg")), diff --git a/crates/ui-egui/src/type_tool.rs b/crates/ui-egui/src/type_tool.rs index 5641127489..3f08e385b9 100644 --- a/crates/ui-egui/src/type_tool.rs +++ b/crates/ui-egui/src/type_tool.rs @@ -1785,6 +1785,14 @@ pub fn options_bar(app: &mut PhotocraftApp, ui: &mut egui::Ui) { if resp.clicked() { open_color_picker(app, [c[0], c[1], c[2]]); } + // Create warped text, after the swatch as in Photoshop (#5911): the Type › Warp Text… dialog. + let can_warp = crate::menus::is_enabled(app, "type.warpText"); + if ui.add_enabled_ui(can_warp, |ui| crate::icons::button(ui, "text-warp", 24.0, false, tl!("Create warped text"))).inner.clicked() + && let Err(e) = crate::menus::invoke(app, &ui.ctx().clone(), "type.warpText", json!({})) + { + app.ui.status = e; + app.ui.status_error = true; + } if app.ui.text_edit.is_some() { ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { ui.add_space(8.0); diff --git a/crates/ui-egui/src/type_tool_tests.rs b/crates/ui-egui/src/type_tool_tests.rs index 725d2890fb..43f668b7c5 100644 --- a/crates/ui-egui/src/type_tool_tests.rs +++ b/crates/ui-egui/src/type_tool_tests.rs @@ -1356,6 +1356,7 @@ fn the_font_menu_lists_served_families() { mod multi_layer_formatting { use super::*; use egui::accesskit::Role; + use egui_kittest::kittest::NodeT; use photocraft_doc::text::{AntiAlias, CharStyle, Orientation, TextAlign}; fn selected(app: &mut PhotocraftApp, ids: &[LayerId]) { @@ -1726,6 +1727,28 @@ mod multi_layer_formatting { assert_eq!(style(h.state(), id, 0), expected); } } + + /// #5911: the options bar's Create warped text button opens Warp Text for the selected type + /// layers, as Type › Warp Text… does; with no type layer to warp it is greyed. + #[test] + fn options_bar_warp_button_warps_the_selected_type_layers() { + let (app, ids) = fixture(); + let mut h = controls(app, false); + h.get_by_role_and_label(Role::Button, "Create warped text").click(); + h.run(); + let dialog = h.state().ui.dialogs.last().unwrap(); + assert_eq!(dialog.fields.get("__command").and_then(|c| c.as_str()), Some("type.warpText")); + let dialog = dialog.id; + h.state_mut().ui.dialog_mut(dialog).unwrap().fields.insert("style".into(), json!("arc")); + crate::dialogs::confirm(h.state_mut(), dialog).unwrap(); + for id in &ids[..2] { + assert!(text(h.state(), *id).warp.is_some(), "a selected layer is warped"); + } + assert!(text(h.state(), ids[2]).warp.is_none(), "an unselected layer is left alone"); + h.state_mut().run("layer.new.layer", json!({})).unwrap(); + h.run(); + assert!(h.get_by_role_and_label(Role::Button, "Create warped text").accesskit_node().is_disabled()); + } } /// #1132: with the Move tool, double-clicking type on the canvas edits it (Photoshop CC 2019+): From c77899b2a70b9d35db9b2d7fb568cde506d182a1 Mon Sep 17 00:00:00 2001 From: Muhammad Saad Date: Thu, 15 Oct 2026 09:57:02 +0400 Subject: [PATCH 14/44] Text: admit the rasterizer's peak working bytes (#4979) rasterize_warped guarded its size with MAX_PIXELS alone, which says nothing about bytes. The buffers it keeps live at once are all as deep as the format: the f32 accumulator and the output surface are both stride deep, a finished coverage buffer sits beside them for the colour being drawn, and each faux-bold pass holds a second Coverage and the vector its finish builds. A CMYK bold render at f32 therefore reserves several times what a plain grey one does for the same pixel count, and every one of those buffers went through vec! or Vec::with_capacity, which abort instead of returning. Admit the peak before reserving any of it: fits_raster_budget derives it from the stride, the sample size and whether a bold pass runs, refuses an overflowing size rather than wrapping, and is checked against MAX_RASTER_BYTES (4 GiB, 1 GiB where usize is 32 bits, so the wasm build keeps room for the document). Coverage now reserves fallibly and finish returns an Option, and the accumulator goes through a fallible zeroed(). A refusal returns the empty Rendered the oversized path already returned, so callers see the existing "too big to draw" result rather than a crash, and the document and its cached appearance are untouched. Ordinary text is unaffected: the budget only binds above what MAX_PIXELS already allowed for narrow formats. Test: the peak tracks channels, sample width and faux bold against small injected allowances, an overflowing size is refused, and a buffer that cannot be served reserves nothing. --- crates/text/src/raster.rs | 30 ++++++++------ crates/text/src/render.rs | 83 +++++++++++++++++++++++++++++++++++---- 2 files changed, 95 insertions(+), 18 deletions(-) diff --git a/crates/text/src/raster.rs b/crates/text/src/raster.rs index bca52e38cf..4f32e9941e 100644 --- a/crates/text/src/raster.rs +++ b/crates/text/src/raster.rs @@ -76,8 +76,13 @@ pub struct Coverage { } impl Coverage { - pub fn new(width: usize, height: usize) -> Self { - Self { width, height, acc: vec![0.0; width * height + 2] } + /// A zeroed accumulator, or `None` when its bytes cannot be reserved. + pub fn try_new(width: usize, height: usize) -> Option { + let n = width.checked_mul(height)?.checked_add(2)?; + let mut acc = Vec::new(); + acc.try_reserve_exact(n).ok()?; + acc.resize(n, 0.0); + Some(Self { width, height, acc }) } /// Adds a line segment (pixel coordinates, y down). @@ -148,15 +153,18 @@ impl Coverage { } /// Resolves the accumulation into per-pixel coverage in 0..=1. - pub fn finish(self) -> Vec { + /// The accumulated coverage, or `None` when the output cannot be reserved. The input + /// accumulator is still live while this builds, so both count toward a caller's budget. + pub fn finish(self) -> Option> { let n = self.width * self.height; - let mut out = Vec::with_capacity(n); + let mut out = Vec::new(); + out.try_reserve_exact(n).ok()?; let mut sum = 0.0f32; for v in &self.acc[..n] { sum += v; out.push(sum.abs().min(1.0)); } - out + Some(out) } } @@ -240,9 +248,9 @@ mod tests { #[test] fn square_area_exact() { - let mut c = Coverage::new(10, 10); + let mut c = Coverage::try_new(10, 10).unwrap(); rect_to(&mut c, &Xform::IDENTITY, 2.25, 3.5, 6.75, 8.0); - let v = c.finish(); + let v = c.finish().unwrap(); assert!((sum(&v) - 4.5 * 4.5).abs() < 1e-3, "{}", sum(&v)); assert_eq!(v[5 * 10 + 4], 1.0); assert_eq!(v[0], 0.0); @@ -250,13 +258,13 @@ mod tests { #[test] fn triangle_area_and_clipping() { - let mut c = Coverage::new(8, 8); + let mut c = Coverage::try_new(8, 8).unwrap(); // Right triangle partly outside on the left and bottom. let pts = [(-4.0, 2.0), (6.0, 2.0), (-4.0, 12.0)]; for i in 0..3 { c.line(pts[i], pts[(i + 1) % 3]); } - let v = c.finish(); + let v = c.finish().unwrap(); // Visible part: region x in [0,6], y in [2,8] under the hypotenuse x + y <= 8. // Area = ∫_{y=2}^{8} (8 - y) dy = 18. assert!((sum(&v) - 18.0).abs() < 1e-3, "{}", sum(&v)); @@ -266,7 +274,7 @@ mod tests { #[test] fn circle_area_via_pen() { use skrifa::outline::OutlinePen; - let mut c = Coverage::new(40, 40); + let mut c = Coverage::try_new(40, 40).unwrap(); { let mut p = Pen::new(&mut c, Xform([1.0, 0.0, 0.0, -1.0, 20.0, 20.0])); // Cubic circle approximation, r = 15. @@ -278,7 +286,7 @@ mod tests { p.curve_to(k, -r, r, -k, r, 0.0); p.close(); } - let a = sum(&c.finish()); + let a = sum(&c.finish().unwrap()); let exact = std::f32::consts::PI * 225.0; assert!((a - exact).abs() / exact < 0.005, "{a} vs {exact}"); } diff --git a/crates/text/src/render.rs b/crates/text/src/render.rs index af29dd10d6..51b9e1e5db 100644 --- a/crates/text/src/render.rs +++ b/crates/text/src/render.rs @@ -18,6 +18,38 @@ pub const FAUX_ITALIC_DEG: f32 = 12.0; pub const FAUX_BOLD_RADIUS: f32 = 0.036; /// Largest raster we produce (pixels), as a guard against absurd sizes. const MAX_PIXELS: u64 = 256 * 1024 * 1024; +/// Peak working bytes one rasterization may keep live. [`MAX_PIXELS`] does not bound them: the +/// accumulator and the output surface are both `stride` deep, and a faux-bold pass holds a second +/// coverage buffer, so a CMYK bold render reserves several times what a plain grey one does for +/// the same pixels. +const MAX_RASTER_BYTES: u64 = if cfg!(target_pointer_width = "64") { 4 << 30 } else { 1 << 30 }; + +/// Whether a `w` x `h` render's peak working bytes fit `max_bytes`: the f32 accumulator and the +/// output surface (both `stride` deep), the finished coverage of the colour being drawn and, with +/// faux bold, the second coverage buffer and the vector its `finish` builds beside it. `false` +/// when the arithmetic overflows, so an absurd size is refused rather than wrapping. +fn fits_raster_budget(w: usize, h: usize, stride: usize, sample: SampleType, bold: bool, max_bytes: u64) -> bool { + let f32s = size_of::() as u64; + let peak = (w as u64).checked_mul(h as u64).and_then(|px| { + let deep = px.checked_mul(stride as u64)?.checked_mul(f32s + sample.bytes() as u64)?; + let coverage = px.checked_mul(if bold { 3 } else { 1 })?.checked_mul(f32s)?; + deep.checked_add(coverage) + }); + peak.is_some_and(|b| b <= max_bytes) +} + +/// A zeroed buffer, or `None` when its bytes cannot be reserved. +fn zeroed(len: usize) -> Option> { + let mut v = Vec::new(); + v.try_reserve_exact(len).ok()?; + v.resize(len, 0.0); + Some(v) +} + +/// What a refused or unrenderable layout returns. +fn nothing(format: PixelFormat) -> Rendered { + Rendered { surface: Surface::new(format), rect: Rect::new(0, 0, 0, 0) } +} /// Rendered text: pixels in document space plus the covered rectangle. pub struct Rendered { @@ -252,8 +284,14 @@ pub fn rasterize_warped(layout: &TextLayout, transform: &Affine, format: PixelFo } let n = format.mode.color_channels(); let stride = n + 1; + let bold = layout.glyphs.iter().any(|g| is_bold(layout, g)); + if !fits_raster_budget(w, h, stride, format.sample, bold, MAX_RASTER_BYTES) { + return nothing(format); + } // Premultiplied accumulation. - let mut acc = vec![0.0f32; w * h * stride]; + let Some(mut acc) = zeroed(w * h * stride) else { + return nothing(format); + }; let xf = Xform([1.0, 0.0, 0.0, 1.0, -rect.x0 as f64, -rect.y0 as f64]).mul(&Xform(transform.m)); let mut colors: Vec = Vec::new(); for st in &layout.styles { @@ -262,14 +300,23 @@ pub fn rasterize_warped(layout: &TextLayout, transform: &Affine, format: PixelFo } } for c in &colors { - let mut cov = Coverage::new(w, h); + let Some(mut cov) = Coverage::try_new(w, h) else { + return nothing(format); + }; draw_pass(layout, &xf, &mut cov, Some(c), warp, Pass::Plain); - let mut cov = cov.finish(); - if layout.glyphs.iter().any(|g| is_bold(layout, g)) { + let Some(mut cov) = cov.finish() else { + return nothing(format); + }; + if bold { for k in 0..BOLD_OFFSETS.len() { - let mut bc = Coverage::new(w, h); + let Some(mut bc) = Coverage::try_new(w, h) else { + return nothing(format); + }; draw_pass(layout, &xf, &mut bc, Some(c), warp, Pass::Bold(k)); - for (a, b) in cov.iter_mut().zip(bc.finish()) { + let Some(bold_cov) = bc.finish() else { + return nothing(format); + }; + for (a, b) in cov.iter_mut().zip(bold_cov) { *a = a.max(b); } } @@ -398,7 +445,8 @@ pub fn outlines(layout: &TextLayout, transform: &Affine, warp: Option<&Warp>) -> #[cfg(test)] mod tests { - use super::{MAX_PIXELS, rect_from_bounds}; + use super::{MAX_PIXELS, fits_raster_budget, rect_from_bounds, zeroed}; + use photocraft_color::SampleType; use photocraft_geom::Rect; #[test] @@ -419,4 +467,25 @@ mod tests { assert_eq!(rect_from_bounds(bounds), first, "bounds: {bounds:?}"); } } + + /// #4979: the pixel cap says nothing about bytes, so the peak is admitted from the channel + /// depth of the accumulator and surface, the sample size and the faux-bold coverage buffers. + /// A 100 x 100 grey+alpha render at 8-bit keeps 2 x (4 + 1) + 4 = 14 bytes a pixel live. + #[test] + fn the_byte_budget_follows_channels_depth_and_faux_bold() { + let px = 100 * 100; + assert!(fits_raster_budget(100, 100, 2, SampleType::U8, false, px * 14)); + assert!(!fits_raster_budget(100, 100, 2, SampleType::U8, false, px * 14 - 1), "the allowance is exact"); + + // The same pixels cost more with more channels, a wider sample, or a bold pass. + assert!(!fits_raster_budget(100, 100, 5, SampleType::U8, false, px * 14), "CMYK is deeper"); + assert!(!fits_raster_budget(100, 100, 2, SampleType::F32, false, px * 14), "f32 output is wider"); + assert!(!fits_raster_budget(100, 100, 2, SampleType::U8, true, px * 14), "bold holds two more buffers"); + assert!(fits_raster_budget(100, 100, 2, SampleType::U8, true, px * 22), "2 x 5 + 3 x 4 bytes a pixel"); + + // A size whose peak overflows is refused rather than wrapping to a small number. + assert!(!fits_raster_budget(usize::MAX, usize::MAX, 5, SampleType::F32, true, u64::MAX)); + // Nothing is reserved for a buffer that cannot be served. + assert!(zeroed(usize::MAX).is_none()); + } } From 064d8429d50176eb5331d3182af4c763ac20b567 Mon Sep 17 00:00:00 2001 From: VladimirPozdnyakov Date: Thu, 15 Oct 2026 21:56:25 +0300 Subject: [PATCH 15/44] =?UTF-8?q?Type:=20=E2=8C=A5=20on=20a=20paragraph-bo?= =?UTF-8?q?x=20handle=20resizes=20the=20block=20symmetrically?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dragging an edge or corner of a paragraph text box with ⌥ held mirrors the opposite edges about the box's centre, so the block grows symmetrically; without ⌥ only the dragged edges move. The drag snapshots the box and its transform at the press (state::BoxResize), so ⌥ pressed or released mid-drag applies as if it had been held from the start — the mirror always turns about the centre the box started with. The ⌥ box clamps at MIN_BOX about that centre. Free Transform and the Ctrl-frame already did ⌥-from-the-reference-point; this closes the plain Type-tool box-edit path. --- crates/ui-egui/src/canvas.rs | 2 +- crates/ui-egui/src/state.rs | 13 ++++- crates/ui-egui/src/type_tool.rs | 74 +++++++++++++++++------- crates/ui-egui/src/type_tool_tests.rs | 82 +++++++++++++++++++++++++++ 4 files changed, 150 insertions(+), 21 deletions(-) diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 391ad464fd..5fb8aae0e8 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -5034,7 +5034,7 @@ pub(crate) fn composite_color(app: &mut PhotocraftApp, x: f64, y: f64) -> Option fn tool_move(app: &mut PhotocraftApp, x: f64, y: f64, pressure: f32, mods: egui::Modifiers) { let tool = app.active_tool(); if tool.is_type() && app.drag.is_none() { - crate::type_tool::pointer_move(app, x, y); + crate::type_tool::pointer_move(app, x, y, mods); } if tool == Tool::Pen && mods.alt { crate::vector_ui::pen_break_handle(app); diff --git a/crates/ui-egui/src/state.rs b/crates/ui-egui/src/state.rs index 5cbda434e7..01da477357 100644 --- a/crates/ui-egui/src/state.rs +++ b/crates/ui-egui/src/state.rs @@ -967,6 +967,17 @@ pub enum MadeLayer { Place, } +/// A paragraph-box handle drag: the handle, and the box (text-space) and layer transform as +/// they were at the press. Every move recomputes from this snapshot, so ⌥ pressed or released +/// mid-drag mirrors about the centre the box started with — as if it had been held from the +/// start. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct BoxResize { + pub handle: u8, + pub shape: [f32; 4], + pub aff: photocraft_geom::Affine, +} + /// In-progress inline type editing (Type tool). Offsets are character indices. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct TextEdit { @@ -984,7 +995,7 @@ pub struct TextEdit { pub dragging: bool, /// Paragraph-box handle being dragged (0-3 corners from top-left clockwise, 4-7 top/right/bottom/left edges). #[serde(skip)] - pub resize: Option, + pub resize: Option, /// IME composition in progress: (start, length) in characters. The preedit text lives in the /// layer so it lays out like typed text; each IME update replaces it. #[serde(skip)] diff --git a/crates/ui-egui/src/type_tool.rs b/crates/ui-egui/src/type_tool.rs index d73bb2e2c4..83cd8f693e 100644 --- a/crates/ui-egui/src/type_tool.rs +++ b/crates/ui-egui/src/type_tool.rs @@ -15,7 +15,7 @@ use serde_json::json; use crate::PhotocraftApp; use crate::canvas::ViewXform; -use crate::state::TextEdit; +use crate::state::{BoxResize, TextEdit}; pub const PLACEHOLDER: &str = "Lorem Ipsum"; @@ -212,18 +212,23 @@ pub(crate) fn box_handle_at(app: &mut PhotocraftApp, id: LayerId, x: f64, y: f64 .map(|i| i as u8) } -/// Drag handle `i` to the document point: the dragged edges follow it, the others stay put. -fn resize_box(app: &mut PhotocraftApp, ed: &TextEdit, i: u8, x: f64, y: f64) { - let id = LayerId(ed.layer); - let Some((bx, by, w, h)) = box_shape(app, id) else { return }; - let Some((_, aff, _)) = layout(app, id) else { return }; - let (px, py) = to_text(&aff, x, y); - let (l, r, t, b) = handle_sides(i); +/// Drag the handle of `r` (the box and transform snapshotted at the press) to the document +/// point: the dragged edges follow it, the others stay put. With ⌥ the opposite edges mirror +/// the drag about the centre the box started with, so the block grows symmetrically instead of +/// from one side — pressing or releasing ⌥ mid-drag switches that on and off as if it had been +/// held from the start. +fn resize_box(app: &mut PhotocraftApp, ed: &TextEdit, r: BoxResize, x: f64, y: f64, alt: bool) { + let (px, py) = to_text(&r.aff, x, y); + let (l, rw, t, b) = handle_sides(r.handle); + let (bx, by, w, h) = (r.shape[0], r.shape[1], r.shape[2], r.shape[3]); let (mut x0, mut y0, mut x1, mut y1) = (bx, by, bx + w, by + h); + // The centre the ⌥ mirror turns about: the box as it stood at the press (every move + // recomputes from that snapshot, so the centre holds for the whole drag). + let (cx, cy) = (bx + w / 2.0, by + h / 2.0); if l { x0 = px.min(x1 - MIN_BOX); } - if r { + if rw { x1 = px.max(x0 + MIN_BOX); } if t { @@ -232,8 +237,28 @@ fn resize_box(app: &mut PhotocraftApp, ed: &TextEdit, i: u8, x: f64, y: f64) { if b { y1 = py.max(y0 + MIN_BOX); } + if alt { + // The mirror keeps the centre fixed: the dragged edge clamps at MIN_BOX/2 from it, and + // the opposite edge follows, so the block never inverts below the minimum. + if l { + x0 = px.min(cx - MIN_BOX / 2.0); + x1 = 2.0 * cx - x0; + } + if rw { + x1 = px.max(cx + MIN_BOX / 2.0); + x0 = 2.0 * cx - x1; + } + if t { + y0 = py.min(cy - MIN_BOX / 2.0); + y1 = 2.0 * cy - y0; + } + if b { + y1 = py.max(cy + MIN_BOX / 2.0); + y0 = 2.0 * cy - y1; + } + } // type.edit places the box's top-left at the given document point. - let o = aff.apply(Point::new(f64::from(x0), f64::from(y0))); + let o = r.aff.apply(Point::new(f64::from(x0), f64::from(y0))); let _ = app.run("type.edit", json!({"layer": ed.layer, "box": [o.x, o.y, x1 - x0, y1 - y0], "coalesce": ed.session})); } @@ -242,8 +267,11 @@ pub fn pointer_down(app: &mut PhotocraftApp, x: f64, y: f64, mods: egui::Modifie if let Some(ed) = app.ui.text_edit.clone() { let id = LayerId(ed.layer); if let Some(i) = box_handle_at(app, id, x, y) { - if let Some(e) = app.ui.text_edit.as_mut() { - e.resize = Some(i); + // Snapshot the box and transform now: every move recomputes from them, so ⌥ held + // or released mid-drag mirrors about the centre the drag started with. + let snap = box_shape(app, id).zip(layout(app, id)).map(|((bx, by, w, h), (_, aff, _))| BoxResize { handle: i, shape: [bx, by, w, h], aff }); + if let (Some(snap), Some(e)) = (snap, app.ui.text_edit.as_mut()) { + e.resize = Some(snap); } return true; } @@ -282,10 +310,10 @@ pub fn pointer_down(app: &mut PhotocraftApp, x: f64, y: f64, mods: egui::Modifie false } -pub fn pointer_move(app: &mut PhotocraftApp, x: f64, y: f64) { +pub fn pointer_move(app: &mut PhotocraftApp, x: f64, y: f64, mods: egui::Modifiers) { let Some(ed) = app.ui.text_edit.clone() else { return }; - if let Some(i) = ed.resize { - resize_box(app, &ed, i, x, y); + if let Some(r) = ed.resize { + resize_box(app, &ed, r, x, y, mods.alt); return; } if ed.dragging { @@ -2636,9 +2664,9 @@ mod tests { let steps = app.session.active().unwrap().history.entries().len(); // Top-left corner: the box keeps its bottom-right corner. assert!(pointer_down(&mut app, 10.0, 10.0, egui::Modifiers::NONE)); - assert_eq!(app.ui.text_edit.as_ref().unwrap().resize, Some(0)); - pointer_move(&mut app, 20.0, 25.0); - pointer_move(&mut app, 30.0, 30.0); + assert_eq!(app.ui.text_edit.as_ref().unwrap().resize.map(|r| r.handle), Some(0)); + pointer_move(&mut app, 20.0, 25.0, egui::Modifiers::NONE); + pointer_move(&mut app, 30.0, 30.0, egui::Modifiers::NONE); pointer_up(&mut app, [10.0, 10.0], [30.0, 30.0]); assert_eq!(box_shape(&app, id), Some((0.0, 0.0, 80.0, 30.0))); let aff = layout(&mut app, id).unwrap().1; @@ -2648,9 +2676,17 @@ mod tests { assert!(app.ui.text_edit.is_some()); // Right edge: only the width changes, and never below the minimum. assert!(pointer_down(&mut app, 110.0, 45.0, egui::Modifiers::NONE)); - pointer_move(&mut app, 0.0, 99.0); + pointer_move(&mut app, 0.0, 99.0, egui::Modifiers::NONE); pointer_up(&mut app, [110.0, 45.0], [0.0, 99.0]); assert_eq!(box_shape(&app, id), Some((0.0, 0.0, MIN_BOX, 30.0))); + // ⌥ mirrors the drag about the centre: the left edge follows the right one out. + assert!(pointer_down(&mut app, 34.0, 45.0, egui::Modifiers::ALT)); + pointer_move(&mut app, 70.0, 45.0, egui::Modifiers::ALT); + pointer_up(&mut app, [34.0, 45.0], [70.0, 45.0]); + assert_eq!(box_shape(&app, id), Some((0.0, 0.0, MIN_BOX + 72.0, 30.0))); + let aff = layout(&mut app, id).unwrap().1; + // Text space −36..40 (centre 2) through the translation: the doc centre stays at 32. + assert_eq!((aff.m[4], aff.m[5]), (-6.0, 30.0)); } #[test] diff --git a/crates/ui-egui/src/type_tool_tests.rs b/crates/ui-egui/src/type_tool_tests.rs index f56829c6d1..b2684de425 100644 --- a/crates/ui-egui/src/type_tool_tests.rs +++ b/crates/ui-egui/src/type_tool_tests.rs @@ -2062,3 +2062,85 @@ fn type_mask_shift_adds_and_alt_subtracts_without_a_layer_row() { assert!(at(&app, stem.0, stem.1) < 0.01, "⌥ subtracts the glyphs"); assert!(at(&app, 1050, 150) > 0.99, "and keeps the rest"); } + +/// The paragraph box's document rect: its text-space origin through the layer transform, then +/// width and height. Only what the symmetric-resize tests need (identity linear part). +fn box_doc_rect(app: &PhotocraftApp, id: LayerId) -> [f64; 4] { + let t = text(app, id); + let (x, y, w, h) = match t.shape { + photocraft_doc::text::TextShape::Box { x, y, width, height } => (f64::from(x), f64::from(y), f64::from(width), f64::from(height)), + ref s => panic!("not a box: {s:?}"), + }; + let p = t.transform.apply(Point::new(x, y)); + [p.x, p.y, w, h] +} + +/// ⌥ on an edge drag of a paragraph box mirrors the opposite edge about the box's centre, so +/// the block grows symmetrically; without ⌥ the opposite edge stays put. +#[test] +fn alt_dragging_a_box_edge_mirrors_the_opposite_edge() { + use crate::canvas::{ToolEvent, tool_event}; + let (mut app, id) = transform_app(true, false, Affine::translate(300.0, 300.0)); + app.ui.extras.snap = false; + // The box is at (300, 300), 240×180: its bottom-edge handle is the midpoint (420, 480). + tool_event(&mut app, ToolEvent::Down { x: 420.0, y: 480.0, pressure: 1.0 }, Modifiers::NONE); + tool_event(&mut app, ToolEvent::Move { x: 420.0, y: 530.0, pressure: 1.0 }, Modifiers::NONE); + tool_event(&mut app, ToolEvent::Up { x: 420.0, y: 530.0 }, Modifiers::NONE); + assert_eq!(box_doc_rect(&app, id), [300.0, 300.0, 240.0, 230.0], "plain drag: the top edge stays"); + tool_event(&mut app, ToolEvent::Down { x: 420.0, y: 530.0, pressure: 1.0 }, Modifiers::ALT); + tool_event(&mut app, ToolEvent::Move { x: 420.0, y: 580.0, pressure: 1.0 }, Modifiers::ALT); + tool_event(&mut app, ToolEvent::Up { x: 420.0, y: 580.0 }, Modifiers::ALT); + // The centre (300 + 115 = 415) held: the bottom followed the pointer (580), the top + // mirrored up to 250. + assert_eq!(box_doc_rect(&app, id), [300.0, 250.0, 240.0, 330.0]); +} + +/// ⌥ pressed or released mid-drag applies as if it had been held from the start: the mirror +/// turns about the centre the box had at the press, not about wherever the box drifted. +#[test] +fn alt_pressed_mid_drag_mirrors_about_the_press_centre() { + use crate::canvas::{ToolEvent, tool_event}; + let (mut app, id) = transform_app(true, false, Affine::translate(300.0, 300.0)); + app.ui.extras.snap = false; + // Bottom edge (420, 480) dragged down 80 px without ⌥: only the bottom moves. + tool_event(&mut app, ToolEvent::Down { x: 420.0, y: 480.0, pressure: 1.0 }, Modifiers::NONE); + tool_event(&mut app, ToolEvent::Move { x: 420.0, y: 520.0, pressure: 1.0 }, Modifiers::NONE); + tool_event(&mut app, ToolEvent::Move { x: 420.0, y: 560.0, pressure: 1.0 }, Modifiers::NONE); + assert_eq!(box_doc_rect(&app, id), [300.0, 300.0, 240.0, 260.0]); + // ⌥ pressed mid-drag: the bottom follows the pointer, the top jumps to the mirror about + // the press centre (390): 2*390 − 600 = 180 — not about the drifted one (430). + tool_event(&mut app, ToolEvent::Move { x: 420.0, y: 600.0, pressure: 1.0 }, Modifiers::ALT); + assert_eq!(box_doc_rect(&app, id), [300.0, 180.0, 240.0, 420.0]); + // ⌥ released again mid-drag: the top snaps back to where a plain drag leaves it. + tool_event(&mut app, ToolEvent::Move { x: 420.0, y: 600.0, pressure: 1.0 }, Modifiers::NONE); + assert_eq!(box_doc_rect(&app, id), [300.0, 300.0, 240.0, 300.0]); + tool_event(&mut app, ToolEvent::Up { x: 420.0, y: 600.0 }, Modifiers::NONE); +} + +/// ⌥ mirrors a side-edge drag the same way, and a corner drag grows both axes about the +/// centre; clamped, the ⌥ box stops at MIN_BOX about its centre instead of collapsing. +#[test] +fn alt_mirrors_side_and_corner_drags_and_clamps_at_the_minimum() { + use crate::canvas::{ToolEvent, tool_event}; + let drag = |app: &mut PhotocraftApp, from: [f64; 2], to: [f64; 2], mods: Modifiers| { + tool_event(app, ToolEvent::Down { x: from[0], y: from[1], pressure: 1.0 }, mods); + tool_event(app, ToolEvent::Move { x: to[0], y: to[1], pressure: 1.0 }, mods); + tool_event(app, ToolEvent::Up { x: to[0], y: to[1] }, mods); + }; + // Right-edge handle: (540, 390) for the box at (300, 300), 240×180. + let (mut app, id) = transform_app(true, false, Affine::translate(300.0, 300.0)); + app.ui.extras.snap = false; + drag(&mut app, [540.0, 390.0], [590.0, 390.0], Modifiers::ALT); + // cx = 420 held: the right edge followed to 590, the left mirrored from 300 to 250. + assert_eq!(box_doc_rect(&app, id), [250.0, 300.0, 340.0, 180.0]); + // Clamped: dragging the right edge past the centre leaves the minimum box about it. + drag(&mut app, [590.0, 390.0], [400.0, 390.0], Modifiers::ALT); + let r = box_doc_rect(&app, id); + assert_eq!(r[2], 4.0, "MIN_BOX wide"); + assert!(((r[0] + r[2] / 2.0) - 420.0).abs() < 1e-9, "still centred on 420"); + // Corner handle (bottom-right of the fresh box): ⌥ grows both axes about the centre. + let (mut app, id) = transform_app(true, false, Affine::translate(300.0, 300.0)); + app.ui.extras.snap = false; + drag(&mut app, [540.0, 480.0], [590.0, 530.0], Modifiers::ALT); + assert_eq!(box_doc_rect(&app, id), [250.0, 250.0, 340.0, 280.0]); +} From 759b68c234309076dc46bdcf31824d4c5838e8b3 Mon Sep 17 00:00:00 2001 From: Stepan Pavlenko Date: Thu, 15 Oct 2026 22:35:50 +0300 Subject: [PATCH 16/44] i18n(ru): translate the dynamic labels the Russian catalog was missing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Russian catalog passed `cargo xtask i18n-coverage` (100% of measured keys) but lacked the ~1,233 runtime-only labels the other catalogs already carry, so they showed in English: filter and adjustment parameter names and choices, gallery names, Camera Raw panels (Balance/Clarity/Dehaze/Color Mixer/Texture/…), History/Layers/paths/channels menu labels, metadata and File Info fields, System Info and hardware status lines, rasterize prompts, composed status templates and dialog text. All 1,233 rows are translated (ru rows 2,936 → 4,208), written from the meaning of the English labels and reusing the vocabulary the catalog already uses (Highlights → Света, Midtones → Полутона, Feather → Растушёвка, Pattern → узор). Filter and gallery names are aligned with the menu labels already in the catalog so a filter never reads two ways (Texturizer → Текстуризатор, Unsharp Mask → Нерезкое маскирование, Shape Blur → Размытие по фигуре, …). Tests: `russian_covers_generated_filter_options_and_gallery_names` (filter_dialog.rs, the Italian/Korean/Spanish check) and `russian_dynamic_status_hardware_and_dialog_labels_are_translated` (i18n/mod.rs). Docs: docs/localization-ru.md (terminology), localization-parity, gaps G13, ROADMAP, ATTRIBUTION and LICENSE-translations. Validated: `cargo test -p photocraft-ui-egui --lib` 2,935 passed / 0 failed; clippy clean (the only lint is a pre-existing `nonminimal_bool` in canvas.rs, untouched here and present on main); `xtask layers` OK; `xtask wasm` OK; `xtask i18n-coverage` ru 1,886 / 1,886; offscreen `snapshot` in Russian checked. --- ATTRIBUTION.md | 1 + ROADMAP.md | 5 +- crates/ui-egui/src/filter_dialog.rs | 6 + .../ui-egui/src/i18n/LICENSE-translations.txt | 7 +- crates/ui-egui/src/i18n/mod.rs | 51 + crates/ui-egui/src/i18n/ru.tsv | 1239 +++++++++++++++++ docs/gaps.md | 11 +- docs/localization-parity.md | 9 +- docs/localization-ru.md | 75 + 9 files changed, 1392 insertions(+), 12 deletions(-) create mode 100644 docs/localization-ru.md diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index 26eca62464..0c09a11300 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -25,6 +25,7 @@ next to them. | `crates/ui-egui/src/i18n/vi.tsv` | Vietnamese UI translations | PhotoCraft contributors | Original translations of PhotoCraft's English labels | MIT OR Apache-2.0, [`LICENSE-translations.txt`](crates/ui-egui/src/i18n/LICENSE-translations.txt) | | `crates/ui-egui/src/i18n/tr.tsv` | Turkish UI translations | PhotoCraft contributors | Original translations of PhotoCraft's English labels | MIT OR Apache-2.0, [`LICENSE-translations.txt`](crates/ui-egui/src/i18n/LICENSE-translations.txt) | | `crates/ui-egui/src/i18n/sr.tsv` | Serbian (Latin, Ekavian) UI translations | PhotoCraft contributors | Original translations of PhotoCraft's English labels | MIT OR Apache-2.0, [`LICENSE-translations.txt`](crates/ui-egui/src/i18n/LICENSE-translations.txt) | +| `crates/ui-egui/src/i18n/ru.tsv` | Russian UI translations | PhotoCraft contributors | Original translations of PhotoCraft's English labels | MIT OR Apache-2.0, [`LICENSE-translations.txt`](crates/ui-egui/src/i18n/LICENSE-translations.txt) | | `assets/fonts/Inter-Regular.ttf`, `Inter-Medium.ttf`, `Inter-SemiBold.ttf` | Inter 4.001 (UI font) | The Inter Project Authors (Rasmus Andersson) | | SIL OFL 1.1, [`assets/fonts/OFL-Inter.txt`](assets/fonts/OFL-Inter.txt) | | `assets/fonts/JetBrainsMono-Regular.ttf` | JetBrains Mono 2.305 (numeric font) | The JetBrains Mono Project Authors | | SIL OFL 1.1, [`assets/fonts/OFL-JetBrainsMono.txt`](assets/fonts/OFL-JetBrainsMono.txt) | | `assets/icons/*.svg` except `slice-knife.svg`, `sampler.svg`, `artboard.svg`, `perspective-crop.svg`, `frame-placeholder.svg`, `gradient.svg`, `type-mask.svg`, `type-mask-vertical.svg`, `clip-below.svg`, `clip-release.svg`, `layer-mask.svg`, `adjustment-layer.svg`, `brush-replace.svg`, `quick-selection.svg`, `eraser-background.svg`, `eraser-magic.svg`, `lasso-magnetic.svg`, `direct-select.svg`, `convert-point.svg`, `pen-add-anchor.svg`, `pen-delete-anchor.svg`, `pen-freeform.svg`, `pen-curvature.svg`, `bandage-sparkle.svg`, `painting-symmetry.svg`, `symmetry-*.svg` and `marquee-{row,column}.svg` (113 files; `star-filled.svg` is `star.svg` with a solid fill) | Lucide icons | Lucide Icons and Contributors | (`icons/.svg`; `align-*` are now named `text-align-*` upstream) | ISC, [`assets/icons/LICENSE-lucide.txt`](assets/icons/LICENSE-lucide.txt) | diff --git a/ROADMAP.md b/ROADMAP.md index f41b71bfc9..ca3bd5cbb6 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,7 +2,7 @@ **Stage: alpha** · next: beta, ~30 points (ready for real work ~45% → ~75%, with reliable PSD) and ~1,100–1,800 h away -> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-13 · **Change:** minor (oracle floor 145 of 258 and 953 open issues, as in gaps.md and on GitHub; toolbox count 56 of 68, 12 missing) · **Target:** Adobe Photoshop 2026 +> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-15 · **Change:** minor (Russian catalog covers every dynamic label; previously minor: oracle floor 145 of 258 and 953 open issues, as in gaps.md and on GitHub; toolbox count 56 of 68, 12 missing) · **Target:** Adobe Photoshop 2026 PhotoCraft's one-page summary of how close it is to Adobe Photoshop 2026 (27.11.0, measured from the installed app on 2026-10-10) at version 0.6.0. Detail: [target-app parity](docs/target-app-parity.md) @@ -104,7 +104,7 @@ because engine messages are still English. Detail: [localization-parity](docs/lo | Korean | `ko` | 100% | partial | | Vietnamese | `vi` | 0% | none | -Also shipped: Traditional Chinese, Russian, Ukrainian, Czech, Polish, Greek, Dutch, Italian (8). +Also shipped: Traditional Chinese, Russian (every dynamic label, [notes](docs/localization-ru.md)), Ukrainian, Czech, Polish, Greek, Dutch, Italian (8). ## Upcoming @@ -136,6 +136,7 @@ Detail and the next releases: [docs/roadmap.md](docs/roadmap.md). | Date | Change | Summary | |---|---|---| | 2026-10-13 | minor | Focus item 1 quoted the oracle floor as 133 of 256; it is 145 of 258 (`crates/io/tests/corpus.rs`, gaps.md G1). Open issues 579 → 953 (2026-10-13) | +| 2026-10-15 | minor | Russian catalog covers every dynamic UI label the other catalogs translate | | 2026-10-11 | minor | Toolbox count 56 of 68 (12 missing) with the Convert Point tool (#3204) | | 2026-10-10 | minor | Readiness by audience with hours per tier | | 2026-10-10 | minor | Mainstream-practitioner and essentials-user numbers added | diff --git a/crates/ui-egui/src/filter_dialog.rs b/crates/ui-egui/src/filter_dialog.rs index fa7cb9524d..87eca79b94 100644 --- a/crates/ui-egui/src/filter_dialog.rs +++ b/crates/ui-egui/src/filter_dialog.rs @@ -1475,6 +1475,12 @@ mod tests { assert!(missing.is_empty(), "missing Spanish dynamic labels: {missing:#?}"); } + #[test] + fn russian_covers_generated_filter_options_and_gallery_names() { + let missing = missing_generated_filter_labels(crate::i18n::lang_from_tag("ru-RU").unwrap()); + assert!(missing.is_empty(), "missing Russian dynamic labels: {missing:#?}"); + } + /// The Select menu's generated dialogs (Select and Mask, Modify ▸ …) show their /// params, choices and title in every UI language, not just Korean. #[test] diff --git a/crates/ui-egui/src/i18n/LICENSE-translations.txt b/crates/ui-egui/src/i18n/LICENSE-translations.txt index 93a609f946..0e9c354446 100644 --- a/crates/ui-egui/src/i18n/LICENSE-translations.txt +++ b/crates/ui-egui/src/i18n/LICENSE-translations.txt @@ -1,6 +1,6 @@ PhotoCraft's Korean, German, Spanish, Brazilian Portuguese, Italian, Ukrainian, Dutch, Czech, -Turkish, Hungarian and Serbian UI translation catalogs are licensed under MIT OR Apache-2.0, -at your option. +Turkish, Hungarian, Serbian and Russian UI translation catalogs are licensed under MIT OR +Apache-2.0, at your option. The full license texts are in ../../../../LICENSE-MIT and ../../../../LICENSE-APACHE. Korean translations: PhotoCraft contributors, original translations of @@ -37,3 +37,6 @@ PhotoCraft's English labels; no proprietary translation resources were used. Serbian translations: PhotoCraft contributors, original translations of PhotoCraft's English labels; no proprietary translation resources were used. + +Russian translations: PhotoCraft contributors, original translations of +PhotoCraft's English labels; no proprietary translation resources were used. diff --git a/crates/ui-egui/src/i18n/mod.rs b/crates/ui-egui/src/i18n/mod.rs index b976e395fc..3cff605b18 100644 --- a/crates/ui-egui/src/i18n/mod.rs +++ b/crates/ui-egui/src/i18n/mod.rs @@ -1111,6 +1111,57 @@ mod tests { } } + /// Russian dynamic labels added in the fourth pass (filter parameters, Camera Raw panels, + /// hardware status, rasterize prompts, History/paths menus) render in Russian, not English. + #[test] + fn russian_dynamic_status_hardware_and_dialog_labels_are_translated() { + let ru = Lang::from_code("ru").expect("ru registered"); + for kind in [ + crate::rasterize_prompt::Kind::Type, + crate::rasterize_prompt::Kind::Shape, + crate::rasterize_prompt::Kind::SmartObject, + crate::rasterize_prompt::Kind::Fill, + ] { + assert_ne!(tr(ru, kind.message()), kind.message(), "{:?}", kind); + } + assert_eq!(fmt(tr(ru, "Driver: {driver}"), &[("driver", "Mesa 25.0")]), "Драйвер: Mesa 25.0"); + assert_eq!( + fmt(tr(ru, "Monitor profile on {display}: {profile}"), &[("display", "DELL U2720Q"), ("profile", "sRGB IEC61966-2.1")]), + "Профиль монитора для DELL U2720Q: sRGB IEC61966-2.1" + ); + assert_eq!(fmt(tr(ru, "Platform: {os} {arch}"), &[("os", "Linux"), ("arch", "x86_64")]), "Платформа: Linux x86_64"); + assert_eq!(tr(ru, "Graphics adapter"), "Графический адаптер"); + assert_eq!(tr(ru, "Image compositor: {processor}"), "Компоновщик изображения: {processor}"); + // Filter / adjustment parameter names. + for label in [ + "Amount", + "Radius", + "Threshold", + "Angle", + "High Pass", + "Smart Blur", + "Reduce Noise", + "Lens Correction", + "Colored Pencil", + "Angled Strokes", + "Stained Glass", + "Sumi-e", + "Film Grain", + "Unsharp Mask", + "Lens Flare", + ] { + assert!(has(ru, label), "Russian filter label missing: {label}"); + } + // Dynamic composed status templates. + assert_eq!(fmt(tr(ru, "Step {step} ({id}) failed: {error}"), &[("step", "2"), ("id", "file.export"), ("error", "ok")]), "Шаг 2 (file.export) не выполнен: ok"); + assert_eq!(fmt(tr(ru, "Couldn't open {name}: {error}"), &[("name", "scan.tif"), ("error", "denied")]), "Не удалось открыть scan.tif: denied"); + assert_eq!(fmt(tr(ru, "Exported {path} ({size})"), &[("path", "/tmp/a.png"), ("size", "12 KB")]), "Экспортировано: /tmp/a.png (12 KB)"); + assert_eq!(fmt(tr(ru, "Data Set: {name} ({index}/{total})"), &[("name", "Set 1"), ("index", "1"), ("total", "2")]), "Набор данных: Set 1 (1/2)"); + // Document and layer names stay verbatim. + let text = fmt(tr(ru, "Saved {path}; image assets: {n} in {dir}"), &[("path", "/tmp/a.pcraft"), ("n", "3"), ("dir", "/tmp/assets")]); + assert!(text.contains("/tmp/a.pcraft") && text.contains("/tmp/assets"), "{text}"); + } + /// Every `tl!("literal")` in the shell has an entry in each language that claims complete menus /// (so a new label can't ship untranslated by accident). Literals that are deliberately shown as /// they are (names, units) are listed in `KEEP_AS_IS`. diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 78e10a10c4..e3dbb3212d 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -3159,3 +3159,1242 @@ curves Smooth Сгладить set name имя набора {set} (modified) {set} (изменён) Random Случайно + + +# Dynamic UI labels (fourth pass): filter and adjustment parameters, Camera Raw panels, +# lighting, History/Layers/paths/channels menus, metadata, System Info, status templates and +# dialog text that other catalogs already carry. Written from the meaning of the English labels +# in the ordinary Russian graphics-editor vocabulary; no proprietary resource consulted. + (coming soon) (скоро) + (none) (нет) + 32-bit Preview Options Параметры предпросмотра 32 бит + 3D… 3D… + A: Ш: + AI Integrations Интеграции ИИ + About {app} О программе {app} + Accented Edges Акцентированные края + Action playback failed: {error} Не удалось воспроизвести операцию: {error} + Actual Pixels Реальный размер + Add Mask Добавить маску + Add Noise Добавить шум + Add a mask (from the selection; ⌥ inverts) Добавить маску (из выделенной области; ⌥ инвертирует) + Add space after paragraph Добавить интервал после абзаца + Add space before paragraph Добавить интервал перед абзацем + Add to selection (⇧) Добавить к выделенной области (⇧) + Added swatch {name} Добавлен образец {name} + Adds grain to the soft edges of the brush tip. Добавляет зерно к мягким краям кисти. + Adjust Interval For Loops Настроить интервал циклов + Adjustment Layers Корректирующие слои + Adjustment Properties Свойства коррекции + Adjustment layers have nothing to transform В корректирующих слоях нечего трансформировать + Adobe PDF Presets… Наборы Adobe PDF… + Adobe-rgb-compat Adobe RGB (совместимо) + Adwaita (Dark) Adwaita (тёмная) + Adwaita (Light) Adwaita (светлая) + After После + Airbrush: keeps painting while the pointer rests. Аэрограф: продолжает рисовать, пока указатель неподвижен. + All shortcuts reset to Photoshop defaults. Все сочетания клавиш сброшены к значениям Photoshop по умолчанию. + Altitude Высота + Ambience Атмосфера + Ambient Окружающий свет + Amplitude Max Макс. амплитуда + Amplitude Min Мин. амплитуда + Android 1080p Android 1080p + Angled Strokes Наклонные штрихи + Anisotropic Анизотропный + Any Любой + Apple Watch 45mm Apple Watch 45 мм + Apply Data Set Применить набор данных + Apply Image Внешний канал + Apply Mask Применить маску + Apply to All Применить ко всем + Apply {option}: {state} Применить {option}: {state} + Arbitrary Произвольный + Arc Lower Нижняя дуга + Arc Upper Верхняя дуга + Are you sure you want to quit? Вы действительно хотите выйти? + Are you sure? Вы уверены? + Around Center Вокруг центра + Arrangement Расположение + Arrows Стрелки + Artboard from Group Монтажная область из группы + Artboard from Layers Монтажная область из слоёв + Artboards Монтажные области + Artboards to Files Монтажные области в файлы + Artboards to PDF Монтажные области в PDF + Artistic Художественные + Ask On Mismatch Спрашивать при несовпадении + Ask On Missing Спрашивать при отсутствии + Ask On Paste Спрашивать при вставке + Assign Profile Назначить профиль + At Позиция + Author Title Имя автора + Auto Scale Автомасштаб + Auto-Align Layers Автовыравнивание слоёв + Auto-Blend Layers Автосмешивание слоёв + Autosave failed: {error} Не удалось выполнить автосохранение: {error} + Back Назад + Backend: {backend} ({type}) Сервер: {backend} ({type}) + Background Level Уровень фона + Bamboo Бамбук + Bas Relief Барельеф + Base Tree Type Базовый тип дерева + Based On На основе + Baseline shift Смещение базовой линии + Basic Latin Основная латиница + Batch Пакетная обработка + Before До + Bend Изгиб + Berries Ягоды + Best Наилучшее + Black Clip Отсечение чёрного + Black Intensity Интенсивность чёрного + Blade Curvature Кривизна лепестков диафрагмы + Blast Порыв + Bleach Bypass Пропуск отбеливания + Blend Clipped Layers as Group Смешивать обтравочные слои как группу + Blend Interior Effects as Group Смешивать внутренние эффекты как группу + Blend Text Gamma Гамма смешивания текста + Blending Mode Режим наложения + Bloat Вздутие + Blocks Блоки + Blue Yellow Синий–жёлтый + Blues Синие тона + Border Граница + Border Thickness Толщина границы + Box Blur Размытие по рамке + Bpc Компенсация чёрной точки + Branches Color Цвет ветвей + Branches Height Высота ветвей + Branches Thickness Толщина ветвей + Breeze Dark Breeze (тёмная) + Breeze Light Breeze (светлая) + Brick Кирпич + Bristle Detail Детализация щетины + Browse Обзор + Brush Detail Детализация кисти + Brush Settings… (F5): every option of this brush Параметры кисти… (F5): все параметры этой кисти + Brush Strokes Мазки кисти + Brush Type Тип кисти + Burlap Мешковина + Calculations Вычисления + Camera Raw Camera Raw + Camera Raw Filter Фильтр Camera Raw + Cancel transform Отменить трансформирование + Candle Light Свет свечи + Canvas Size Размер холста + Case Sensitive Учитывать регистр + Ccw Против часовой стрелки + Cell Size Размер ячейки + Center X Центр X + Center Y Центр Y + Chalk Area Область мела + Change Find Найти и заменить + Changes Saved Изменения сохранены + Changes have not been saved. Изменения не сохранены. + Change… Изменить… + Channel1 Канал 1 + Channel2 Канал 2 + Channel3 Канал 3 + Channel4 Канал 4 + Charcoal Area Область угля + Charcoal Thickness Толщина угля + Check for Updates Проверить обновления + Checkerboard Шахматная доска + Choke Поджатие + Choose Folder Выбрать папку + Circle Круг + Cleanliness Чистота + Clear Amount Величина очистки + Clear Selection Отменить выделение + Click sets foreground · right-click sets background Щелчок задаёт цвет переднего плана · щелчок правой кнопкой задаёт цвет фона + Click the first point or press ↵ to close · Esc cancels Щёлкните первую точку или нажмите ↵, чтобы замкнуть · Esc отменяет + Click to add a point · drag off or ⌘/Ctrl-click or Delete to remove Щёлкните, чтобы добавить точку · перетащите за пределы, нажмите ⌘/Ctrl и щёлкните или нажмите Delete, чтобы удалить + Click to apply, ⌥-click to clear overrides, double-click for Style Options Щёлкните, чтобы применить, ⌥+щелчок, чтобы очистить переопределения, двойной щелчок — параметры стиля + Click to zoom in · ⌥-click to zoom out Щёлкните, чтобы увеличить · ⌥+щелчок, чтобы уменьшить + Click: corner · Drag: smooth · Click first point: close · ↩ finish · Esc cancel Щелчок: угол · Перетаскивание: сглаживание · Щелчок по первой точке: замкнуть · ↩ завершить · Esc отменить + Clipping Mask Обтравочная маска + Clipping Path… Контур отсечения… + Clone Клонировать + Clone source set at {x}, {y} Источник клонирования задан в {x}, {y} + Close Window Закрыть окно + Clumped Сгруппированные + Cmyk CMYK + Coarse Dots Крупные точки + Coated-cmyk Мелованная CMYK + Collapse group (⌥-click: all groups) Свернуть группу (⌥+щелчок: все группы) + Color Decontaminate Удаление цветового ореола + Color Halftone Цветные полутона + Color Indicates Masked Areas Цветом обозначены маскированные области + Color Indicates Selected Areas Цветом обозначены выделенные области + Color Lookup Tables Таблицы перекодировки цвета + Color Picker Палитра цветов + Color Picker (Background Color) Палитра цветов (цвет фона) + Color Picker (Custom Canvas Color) Палитра цветов (свой цвет холста) + Color Picker (Foreground Color) Палитра цветов (цвет переднего плана) + Color Sampler Tool Инструмент «Цветовой эталон» + Color Settings Настройки цвета + Color Swatches Образцы цветов + Color Table Цветовая таблица + Color management: {error} Управление цветом: {error} + Colored Pencil Цветной карандаш + Comic Book Комикс + Command Команда + Commit Perspective Warp (↩) Применить перспективную деформацию (↩) + Commit Puppet Warp (↩) Применить марионеточную деформацию (↩) + Commit any current edits (⌘↩) Применить текущие изменения (⌘↩) + Commit current crop operation (↵) Применить текущую операцию кадрирования (↵) + Commit transform Применить трансформирование + Commit transform (↩) Применить трансформирование (↩) + Commit warp (↩) Применить деформацию (↩) + Compare Сравнить + Compression Сжатие + Conditional Mode Change Условная смена режима + Confirm Подтвердить + Conform Подогнать + Constraints Ограничения + Content-Aware Fill needs a selection on a pixel layer Заливка с учётом содержимого требует выделенной области на пиксельном слое + Continue Продолжить + Contract Сжать + Contrasty Контрастный + Convert Преобразовать + Convert to Alpha Channel Преобразовать в альфа-канал + Convert to Linked Преобразовать в связанный + Convert to Profile Преобразовать в профиль + Cool Холодный + Cooling L B B Охлаждающий (LBB) + Cooling80 Охлаждающий (80) + Cooling82 Охлаждающий (82) + Copy Complete Stroke Копировать полный контур + Copy Fill Копировать заливку + Copyright Url URL авторских прав + Correct C A Коррекция хроматической аберрации + Correct Distortion Коррекция дисторсии + Correct Vignette Коррекция виньетирования + Could not rename the action: {error} Не удалось переименовать операцию: {error} + Could not transform: the layer is empty Не удалось трансформировать: слой пуст + Could not use Perspective Warp: the layer is empty Не удалось применить перспективную деформацию: слой пуст + Could not use Puppet Warp: no opaque pixels Не удалось применить марионеточную деформацию: нет непрозрачных пикселей + Could not use Puppet Warp: the layer is empty Не удалось применить марионеточную деформацию: слой пуст + Couldn't open {name}: {error} Не удалось открыть {name}: {error} + Couldn't save preferences: {error}. Retrying… Не удалось сохранить настройки: {error}. Повтор… + Crack Brightness Яркость трещин + Crack Depth Глубина трещин + Crack Spacing Интервал трещин + Craquelure Кракелюр + Create By Создать с помощью + Create Vector Mask Создать векторную маску + Create Warped Text Создать деформированный текст + Create a new layer (⇧⌘N) Создать новый слой (⇧⌘N) + Create new brush from the current settings Создать новую кисть из текущих параметров + Create new brush preset from the current settings Создать новый набор кистей из текущих параметров + Create… Создать… + Cross Крест + Cross Process Кросс-процесс + Crosshatch Перекрёстная штриховка + Crosswise Перекрёстно + Crystallize Кристаллизация + Currency Валюта + Curve Darks Кривая: тёмные тона + Curve Highlights Кривая: светлые тона + Curve Lights Кривая: светлые тона + Curve Shadows Кривая: тени + Curve Splits Кривая: разрывы + Custom Branch Color Свой цвет ветвей + Custom Pixel Aspect Ratio Своё пиксельное соотношение + Custom pattern Свой узор + Cutout Аппликация + Cw По часовой стрелке + Cyans Голубые тона + Cyrillic Кириллица + Daisies Маргаритки + Dark Gray Тёмно-серый + Dark Intensity Интенсивность тёмного + Dark Rough Тёмный шероховатый + Dark Strokes Тёмные штрихи + Darken Only Только затемнение + Darkness Затемнение + Data Данные + Data Set: {name} ({index}/{total}) Набор данных: {name} ({index}/{total}) + Data Sets as Files Наборы данных в файлы + Date Дата + Day For Night День как ночь + De-Interlace Устранение чересстрочности + Decrease Brush Hardness Уменьшить жёсткость кисти + Decrease Brush Size Уменьшить размер кисти + Decrease Type Size Уменьшить размер текста + Decrease Type Size by 10 Уменьшить размер текста на 10 + Deep Глубокий + Deep Blue Глубокий синий + Deep Emerald Глубокий изумрудный + Deep Red Глубокий красный + Deep Yellow Глубокий жёлтый + Default Foreground and Background Colors Цвета переднего плана и фона по умолчанию + Default Leaves Листья по умолчанию + Default Photoshop Size Размер Photoshop по умолчанию + Defaults По умолчанию + Define Brush Preset Определить кисть + Define Custom Shape Определить свою фигуру + Define Pattern Определить паттерн + Definition Чёткость + Defringe Убрать кайму + Degrees Градусы + Delete Channel Удалить канал + Delete Mask Удалить маску + Delete Workspace Удалить рабочую среду + Delete brush Удалить кисть + Delete path Удалить контур + Delete vector mask Удалить векторную маску + Delete… Удалить… + Depth Map Карта глубины + Depth Mode Режим глубины + Diameter Диаметр + Diamond Ромб + Diffuse Диффузия + Diffuse Glow Рассеянное свечение + Direction Направление + Direction Balance Баланс направлений + Disable Выключить + Disable Mask Отключить маску + Disable Symmetry Path Отключить контур симметрии + Disabled Отключено + Discard Changes Отменить изменения + Displace Смещение + Display Дисплей + Display profile not used Профиль дисплея не используется + Display profiles: {reason} Профили дисплеев: {reason} + Display-p3 Display P3 + Distance Расстояние + Distortion Дисторсия + Distribution Распределение + Do you want to save you made to “{name}” before quitting? Сохранить изменения в «{name}» перед выходом? + Document Sizes Размеры документа + Documentation Документация + Documents Документы + Dont Enlarge Не увеличивать + Dot Точка + Double Line Двойная линия + Down Вниз + Draft Черновик + Drag a crop box · drag inside to move · edges resize ({ratio} ratio, {centre} centre) · Space moves while drawing · {commit} commits · Esc cancels Перетащите рамку кадрирования · перетаскивание внутри перемещает · края изменяют размер (соотношение {ratio}, центр {centre}) · пробел перемещает при рисовании · {commit} применяет · Esc отменяет + Drag: draw freehand · Release on the start point: close Перетаскивание: рисование от руки · Отпустите на начальной точке: замкнуть + Driver: {driver} Драйвер: {driver} + Dry Brush Сухая кисть + Duplicate Channel Дублировать канал + Duplicate Layer Дублировать слой + Duplicate Layers Дублировать слои + Duplication Дублирование + Dust & Scratches Пыль и царапины + Edge Край + Edge Brightness Яркость краёв + Edge Extension Расширение краёв + Edge Fidelity Точность краёв + Edge Intensity Интенсивность краёв + Edge Only Только края + Edge Simplicity Простота краёв + Edge Thickness Толщина краёв + Edge Width Ширина краёв + Edit Toolbar Настроить панель инструментов + Edit… Изменить… + Elevation Возвышение + Eliminate Устранить + Emboss Тиснение + Enable Включить + Enable Mask Включить маску + End X Конец X + End Y Конец Y + Enlarged Увеличенный + Erase with Brush or Pencil (hold) Стирание кистью или карандашом (удерживайте) + Error Ошибка + Even Fields Чётные поля + Exit Quick Mask Выйти из быстрой маски + Expand group (⌥-click: all groups) Развернуть группу (⌥+щелчок: все группы) + Export As Экспортировать как + Export As: {name} Экспортировать как: {name} + Export Contents Экспортировать содержимое + Export Image Экспортировать изображение + Export Options Параметры экспорта + Export Preferences Экспорт настроек + Export Settings Экспортировать настройки + Export to Экспортировать в + Export/Import Presets Экспорт/импорт наборов + Exported contents to {path} Содержимое экспортировано в {path} + Exported presets to {path} Наборы экспортированы в {path} + Exported {path} Экспортировано: {path} + Exported {path} ({size}) Экспортировано: {path} ({size}) + Exporting… Экспорт… + ExtraBold Сверхжирный + ExtraLight Сверхтонкий + Extrude Экструзия + Fade Ослабить + Faded Film Выцветшая плёнка + Failed Не удалось + Failed to open file Не удалось открыть файл + Failed to save file Не удалось сохранить файл + Fallback: {reason} Резерв: {reason} + Feather Radius Радиус растушёвки + Fiber Length Длина волокон + Fibers Волокна + Field Blur Размытие по полю + File Format Формат файла + File Info Сведения о файле + File Name Имя файла + File Size Размер файла + File not found Файл не найден + Filename Имя файла + Fill Color Цвет заливки + Fill Opacity Непрозрачность заливки + Fill Path… Залить контур… + Fill path with foreground color Залить контур цветом переднего плана + Fill with Background Color Залить цветом фона + Fill with Background Color, Preserve Transparency Залить цветом фона, сохранить прозрачность + Fill with Foreground Color Залить цветом переднего плана + Fill with Foreground Color, Preserve Transparency Залить цветом переднего плана, сохранить прозрачность + Film Grain Зернистость плёнки + Filter Gallery Галерея фильтров + Find Найти + Find and Replace Text Найти и заменить текст + Fine Тонкий + Fine Dots Мелкие точки + Finish Готово + Fit Image Подогнать изображение + Fit to Window По размеру окна + Flame Пламя + Flame Bottom Alignment Выравнивание низа пламени + Flame Lines Линии пламени + Flame Shape Форма пламени + Flame Style Стиль пламени + Flame Type Тип пламени + Flat Плоский + Flat Shading Плоское затенение + Flower Color Цвет цветков + Focal Distance Дистанция фокуса + Focus Фокус + Focus Area Область фокусировки + Font size Размер шрифта + Foreground Level Уровень переднего плана + Forward Вперёд + Forward Warp Прямая деформация + Frame Кадр + Frame {current} / {total} Кадр {current} / {total} + Free Transform Path Свободное трансформирование контура + Freeze Lasso Заморозить лассо + Freeze Mask Заморозить маску + Fresco Фреска + From От + From Left Слева + From Right Справа + Frosted Матовое + Full Screen Во весь экран + Fullscreen Полноэкранный + Fuzziness Разброс + GPU backend preference: {backend} Предпочитаемый сервер GPU: {backend} + GPU device was lost; using the CPU renderer. Устройство GPU потеряно; используется отрисовка на CPU. + GPU error; using the CPU renderer. Ошибка GPU; используется отрисовка на CPU. + GPU retry scheduled Запланирована повторная попытка GPU + Gamma Гамма + Gaussian Blur Размытие по Гауссу + General Blending Общее наложение + General Brushes Общие кисти + Generators Генераторы + Generic Общий + Glass Стекло + Global Light Глобальный свет + Gloss Глянец + Glow Amount Величина свечения + Glow Brightness Яркость свечения + Glow Size Размер свечения + Glowing Edges Светящиеся контуры + Good Хорошее + Grade Balance Цветокоррекция: баланс + Grade Blending Цветокоррекция: смешивание + Grade Global Цветокоррекция: общая + Grade Highlights Цветокоррекция: светлые тона + Grade Midtones Цветокоррекция: средние тона + Grade Shadows Цветокоррекция: тени + Gradient Options Параметры градиента + Grain Amount Величина зерна + Grain Roughness Шероховатость зерна + Grain Size Размер зерна + Grain Type Тип зерна + Graininess Зернистость + Grainy Dots Зернистые точки + Graphics adapter Графический адаптер + Graphics adapter: {adapter} Графический адаптер: {adapter} + Gray-gamma-2.2 Серый (гамма 2,2) + Greek Греческий + Greens Зелёные тона + Grid (⌘') Сетка (⌘') + Grid view Вид сеткой + Group from Layers Группа из слоёв + Groups Группы + Grout Width Ширина швов + Guide Layout… Схема направляющих… + HDR Toning HDR-тонирование + Hand Tool (hold) Инструмент «Рука» (удерживайте) + Hearts Сердца + Heptagon Семиугольник + Hexagon Шестиугольник + Hidden Скрыто + Hide Grid Скрыть сетку + Hide Others Скрыть остальные + Hide the layer's effects (⌥-click: all layers) Скрыть эффекты слоя (⌥+щелчок: все слои) + Hide {app} Скрыть {app} + High Pass Цветовой контраст + Highlight Света + Highlight Amount Величина светов + Highlight Area Область светов + Highlight Radius Радиус светов + Highlight Strength Сила светов + Highlight Tone Тон светов + Highlights from: Света от: + Horizontal Distortion Горизонтальная дисторсия + Horizontal Only Только по горизонтали + Horizontal scale Масштаб по горизонтали + Horizontal type Горизонтальный текст + Hotspot Горячая точка + Hsb HSB + Hsl HSL + Hsl Hue Оттенок HSL + Hsl Lum Яркость HSL + Hsl Sat Насыщенность HSL + Image Balance Баланс изображения + Image Processor Обработчик изображений + Image compositor: {processor} Компоновщик изображения: {processor} + Import Brushes Импортировать кисти + Import Brushes… (.abr) Импортировать кисти… (.abr) + Import Image Импортировать изображение + Import Options Параметры импорта + Import Photoshop brushes (.abr) Импортировать кисти Photoshop (.abr) + Import Settings Импортировать настройки + Importing {what} from {file}… Импорт {what} из {file}… + Importing… Импорт… + In Black Входной чёрный + In White Входной белый + Increase Brush Hardness Увеличить жёсткость кисти + Increase Brush Size Увеличить размер кисти + Increase Type Size Увеличить размер текста + Increase Type Size by 10 Увеличить размер текста на 10 + Indent first line Отступ первой строки + Indent left margin Отступ слева + Indent right margin Отступ справа + Indexed Индексированный + Infinite Бесконечный + Information Информация + Initial Direction Начальное направление + Ink Outlines Обводка контура + Inner Bevel Внутренняя фаска + Input Вход + Input Levels Входные уровни + Input Mode Режим ввода + Install Plug-in Установить плагин + Intensity Интенсивность + Intent Метод преобразования + Interpret Footage Интерпретировать материал + Intersect with selection (⇧⌥) Пересечение с выделенной областью (⇧⌥) + Interval Интервал + Invalid panel layout; restored the default dock Недопустимый макет панелей; восстановлена пристыковка по умолчанию + Invert Mask Инвертировать маску + Invert Selection Инвертировать выделенную область + Iris Blur Диафрагменное размытие + Ivy Плющ + Jag Зубчатость + Join the ArtCraft Discord Discord сообщества ArtCraft + Join the ArtCraft Discord ({url}) Присоединиться к Discord ArtCraft ({url}) + Keeps the current texture when switching presets. Сохраняет текущую текстуру при смене набора. + Kernel Ядро + Kerning: Metrics, Optical or a value in 1/1000 em (Alt+←/→ at the caret) Кернинг: метрический, оптический или значение в 1/1000 em (Alt+←/→ у курсора) + Keyboard Клавиатура + Lab Lab + Landscape, 10 x 8 Альбомная, 10 x 8 + Landscape, 6 x 4 Альбомная, 6 x 4 + Landscape, 7 x 5 Альбомная, 7 x 5 + Latin 1 Латиница 1 + Latin Extended-A Латиница расширенная A + Latin Extended-B Латиница расширенная B + Layer Comps to Files Варианты слоя в файлы + Layer Content Options Параметры содержимого слоя + Layer Count Число слоёв + Layer From Background Слой из фона + Layer Knocks Out Drop Shadow Слой выбивает тень + Layer Mask Hides Effects Маска слоя скрывает эффекты + Layer Properties Свойства слоя + Layer Styles Стили слоя + Layer from Background Слой из фона + Layers to Files Слои в файлы + Layout Макет + Leading (set to the font size × auto-leading when Auto) Интерлиньяж (при значении «Авто» равен размеру шрифта × автоматический интерлиньяж) + Leaf Color Цвет листьев + Learn More Подробнее + Leaves Amount Количество листьев + Leaves Color Цвет листьев + Leaves Size Размер листьев + Left Diagonal Левая диагональ + Legacy Устаревший + Legal Legal + Lens Blur Размытие при малой глубине + Lens Correction Коррекция дисторсии + Lens Flare Блик от объектива + Less Меньше + Letter Letter + Level Based На основе уровней + Library Библиотека + Light Dark Balance Баланс светлого и тёмного + Light Direction Направление света + Light Intensity Интенсивность света + Light Rough Светлый шероховатый + Light Type Тип света + Light X Свет X + Light Y Свет Y + Light Z Свет Z + Lighten Grout Осветлить швы + Lighten Only Только осветление + Lighting Освещение + Lighting Effects Эффекты освещения + Linear Dodge Линейный осветлитель + Linear Height Линейная высота + Linear-srgb Линейный sRGB + Link Связать + Liquify Пластичность + List Список + List view Вид списком + Live Preview Живой просмотр + Load 3D LUT Загрузить 3D LUT + Load Files into Stack Загрузить файлы в стопку + Load path as a selection Загрузить контур как выделенную область + Loading… Загрузка… + Lock All Заблокировать всё + Lock Image Pixels Заблокировать пиксели изображения + Lock Layers Заблокировать слои + Lock Position Закрепить положение + Lock Transparent Pixels Заблокировать прозрачные пиксели + Locked Заблокировано + Long Lines Длинные линии + Long Strokes Длинные штрихи + Lorem Ipsum Lorem Ipsum + Lower Нижний + Lum Ярк. + Lut LUT + Magentas Пурпурные тона + Make Selection from Path Создать выделенную область из контура + Make Selection… Создать выделенную область… + Make Symmetry Path Создать контур симметрии + Make Work Path… Создать рабочий контур… + Make work path from selection Создать рабочий контур из выделенной области + Manual Вручную + Map Document Документ карты + Map Layer Слой карты + Map Path Путь к карте + Mask Channel Канал-маска + Mask Incomplete Blocks Маскировать неполные блоки + Mask None Без маски + Masks Маски + Match Color Подбор цвета + Math Symbols Математические символы + Max Offset Макс. смещение + Max Radius Макс. радиус + Measurement Scale Масштаб измерений + Measurement Scale Marker Маркер масштаба измерений + Medium Dots Средние точки + Medium Gray Средне-серый + Medium Lines Средние линии + Medium Strokes Средние штрихи + Merge Channels… Объединить каналы… + Merge Spot Channel Объединить цветовой канал + Merge to HDR Pro Объединить в HDR Pro + Metallic Металлический + Metrics Метрики + Mezzotint Меццотинто + Middle Середина + Midtone Полутон + Monitor Profile Профиль монитора + Monitor profile in use on {display}: {profile} Используемый профиль монитора на {display}: {profile} + Monitor profile in use: {profile} Используемый профиль монитора: {profile} + Monitor profile on {display} (main window): {profile} Профиль монитора для {display} (главное окно): {profile} + Monitor profile on {display}: {profile} Профиль монитора для {display}: {profile} + Monitor profile: {profile} Профиль монитора: {profile} + Mono Contrast Монохромный контраст + Monochromatic Монохромный + More Больше + Mosaic Мозаика + Mosaic Tiles Мозаичные плитки + Motion Blur Размытие в движении + Mouse Мышь + Move Layer Переместить слой + Movie Prime Киносъёмочный фикс + Multiple Flames Along Path Несколько языков пламени вдоль контура + Multiple Flames One Direction Несколько языков пламени в одном направлении + Multiple Flames Path Directions Несколько языков пламени по направлениям контура + Multiple Flames Various Length Несколько языков пламени разной длины + My Brushes Мои кисти + Native file drag-and-drop is not supported on Wayland yet. Use File › Open, or run PhotoCraft under XWayland with `WAYLAND_DISPLAY= photocraft`. Перетаскивание файлов ещё не поддерживается в Wayland. Используйте «Файл › Открыть» или запустите PhotoCraft под XWayland с `WAYLAND_DISPLAY= photocraft`. + Nearest Ближайший + Neon Glow Неоновое свечение + Neutralize Нейтрализовать + Neutrals Нейтральные тона + New Brush Preset Новый набор параметров кисти + New Channel… Новый канал… + New Guide Новая направляющая + New Guide Layout Схема направляющих + New Spot Channel… Новый цветовой канал… + New Video Layer from File Новый видеослой из файла + New Window Новое окно + New Workspace Новая рабочая среда + New document Новый документ + New document… Новый документ… + New document… ⌘N Новый документ… ⌘N + Next Далее + No Document Нет документа + No Image Нет изображения + No Layers Нет слоёв + No Results Нет результатов + No Results Found Ничего не найдено + No Selection Нет выделенной области + No custom shape "{name}": pick one in the options bar or Window › Shapes Нет произвольной фигуры «{name}»: выберите её на панели параметров или в меню «Окно › Фигуры» + No layers Нет слоёв + No recent files Нет недавних файлов + No selection Нет выделенной области + Noise Color Цветовой шум + Noise Color Detail Цветовая детализация шума + Noise Luminance Яркостный шум + Noise Luminance Detail Яркостная детализация шума + Nothing to Redo Нечего повторить + Nothing to Undo Нечего отменить + Nothing to paste: the clipboard holds no image Нечего вставлять: в буфере обмена нет изображения + Number Of Levels Число уровней + Numbers Числа + Ocean Ripple Океанские волны + Octagon Восьмиугольник + Odd Fields Нечётные поля + Oil Paint Масляная живопись + On Вкл. + One Flame Along Path Один язык пламени вдоль контура + Open As Открыть как + Open File Открыть файл + Open Folder Открыть папку + Open Image Открыть изображение + Open Recent is unavailable on the web «Открыть недавние» недоступно в веб-версии + Opened {url} Открыто: {url} + Opening Открытие + Open… ⌘O Открыть… ⌘O + Operation canceled Операция отменена + Operation cancelled Операция отменена + Operation completed Операция завершена + Operation failed Операция не выполнена + Optical Оптический + Original Ratio Исходное соотношение + Out Black Выходной чёрный + Out From Center От центра наружу + Out Of Gamut Вне охвата + Out White Выходной белый + Out of memory Недостаточно памяти + Outer Bevel Внешняя фаска + Output Levels Выходные уровни + Oval Овал + Overlay Edge Наложение края + Overlay: Rule of Thirds Наложение: правило третей + PSD PSD + Package Упаковать + Page Setup… Параметры страницы… + Paint Daubs Мазки краской + Paints from the document's opening state Рисует по состоянию документа на момент открытия + Palette Knife Шпатель + Panel Панель + Paper Бумага + Paper Brightness Яркость бумаги + Parallel Параллельно + Paste Complete Stroke Вставить полный контур + Paste Fill Вставить заливку + Patchwork Лоскутное шитьё + Path Blur Размытие по контуру + Paths to Illustrator Контуры в Illustrator + Pattern Fill… Заливка узором… + Pattern Type Тип узора + Pencil Width Ширина карандаша + Pentagon Пятиугольник + Percentage Процент + Permission denied Доступ запрещён + PhotoCraft keeps retrying; until a save succeeds, preference changes are lost when it closes. PhotoCraft продолжает попытки; пока сохранение не удастся, изменения настроек будут потеряны при закрытии. + Photomerge Фотослияние + Pick Color From Image Выбрать цвет на изображении + Picture Frame Рамка + Pillow Emboss Подушковидное тиснение + Pinch Сжатие + Pins Булавки + Place Embedded Поместить встроенный + Place Linked Поместить связанный + Place Scale Marker Шкала масштаба + Plane Плоскость + Planes Плоскости + Plastic Wrap Пластиковая упаковка + Platform: {os} {arch} Платформа: {os} {arch} + Please wait… Подождите… + Point Точка + Point Curve Точечная кривая + Point Curve Blue Точечная кривая: синяя + Point Curve Green Точечная кривая: зелёная + Point Curve Red Точечная кривая: красная + Pointed Заострённый + Pointillize Пуантилизм + Polar Coordinates Полярные координаты + Polar To Rectangular Полярные в прямоугольные + Policy Cmyk Правила для CMYK + Policy Gray Правила для градаций серого + Policy Rgb Правила для RGB + Pond Ripples Волны на пруду + Portrait, 4 x 6 Книжная, 4 x 6 + Portrait, 5 x 7 Книжная, 5 x 7 + Postcard Открытка + Poster Плакат + Poster Edges Плакатные края + Posterization Постеризация + Preferences can't be saved Не удаётся сохранить настройки + Preferences saved Настройки сохранены + Preferences were reset: {error} Настройки сброшены: {error} + Preferences… Настройки… + Preserve Сохранить + Preset Manager Управление наборами + Press keys Нажмите клавиши + Preview Image Просмотр изображения + Previous Назад + Prime105 Prime 105 + Prime35 Prime 35 + Printed to {path} Напечатано в {path} + Pro (Dark) Pro (тёмная) + Pro (Medium Gray) Pro (средне-серая) + Processing… Обработка… + Profile Профиль + Proof Setup… Настройка пробного отпечатка… + Prophoto-compat ProPhoto RGB (совместимо) + Pucker Сморщивание + Push Left Сдвиг влево + Pyramids Пирамиды + Quick Actions Быстрые действия + Quit Выход + Quit PhotoCraft Выйти из PhotoCraft + Quit {app} Выйти из {app} + R G B R G B + R g b R g b + Radial Радиальный + Radial Blur Радиальное размытие + Radius X Радиус X + Radius Y Радиус Y + Ran script {name} Выполнен сценарий {name} + Randomize Length Случайная длина + Randomize Shapes Случайные фигуры + Rasterize Fill Content Растрировать содержимое заливки + Rasterize Smart Object Растрировать смарт-объект + Ratio Соотношение + Raw Defaults Значения RAW по умолчанию + Reading Чтение + Ready Готово + Rec2020 Rec. 2020 + Recent Files Недавние файлы + Reconstruct All Восстановить всё + Recovered {name} Восстановлено: {name} + Rectangular Selection Прямоугольная выделенная область + Rectangular To Polar Прямоугольные в полярные + Red Cyan Красный–голубой + Reds Красные тона + Reduce Color Noise Уменьшить цветовой шум + Reduce Noise Уменьшить шум + Reflected Отражение + Relief Рельеф + Relink to File Повторно связать с файлом + Remove All Удалить всё + Remove Jpeg Artifact Удалить артефакты JPEG + Rename Brush Переименовать кисть + Rename Channel… Переименовать канал… + Rename Group Переименовать группу + Rename Tool Preset Переименовать набор + Render Video Экспорт видео + Rendering… Отрисовка… + Reorder Layer Изменить порядок слоя + Replace Заменить + Replace Color Замена цвета + Replace Contents Заменить содержимое + Replace Footage Заменить материал + Report a Bug Сообщить об ошибке + Report an Issue Сообщить о проблеме + Reset Alignment Сбросить выравнивание + Reset All Сбросить всё + Reset Preferences Сбросить настройки + Resolve Missing Fonts Устранить отсутствующие шрифты + Restart Перезапустить + Restart Required Требуется перезапуск + Resume Продолжить + Retry Повторить + Rgb RGB + Ridges Гребни + Right Diagonal Правая диагональ + Ripple Рябь + Ripple Magnitude Величина ряби + Ripple Size Размер ряби + Rope Верёвка + Roses Розы + Rough Pastels Пастель + Row Order Порядок строк + Rulers (⌘R) Линейки (⌘R) + Sample Size Размер образца + Sampled Colors Отобранные цвета + Sandstone Песчаник + Sat Насыщ. + Save As Сохранить как + Save Changes Сохранить изменения + Save File Сохранить файл + Save Image Сохранить изображение + Save Path Сохранить контур + Save Pixel Aspect Ratio Сохранить соотношение сторон пикселя + Save a Copy Сохранить копию + Save before closing? Сохранить перед закрытием? + Save for Web (Legacy) Сохранить для Web (устаревшее) + Save your work and restart PhotoCraft to retry GPU acceleration. CPU rendering remains active for this session. Сохраните работу и перезапустите PhotoCraft, чтобы повторить попытку ускорения GPU. Отрисовка на CPU остаётся активной в этом сеансе. + Saved a copy as {path} Копия сохранена как {path} + Saved {path}; image assets: {n} in {dir} Сохранено: {path}; графические ресурсы: {n} в {dir} + Saving… Сохранение… + Scale Effects Масштабировать эффекты + Scaling Масштабирование + Scallops Фестоны + Script Events Manager Диспетчер событий сценариев + Scroll menu down Прокрутить меню вниз + Scroll menu up Прокрутить меню вверх + Search commands Поиск команд + Search commands (⌘K) Поиск команд (⌘K) + Search commands, tools and panels Поиск команд, инструментов и панелей + Search menus Поиск по меню + Seed Зерно + Select Custom Color… Выбрать свой цвет… + Select Data Points Выбрать точки данных + Select Layer Выбрать слой + Select and Mask Выделение и маска + Selected at launch: {backend} Выбрано при запуске: {backend} + SemiBold Полужирный + Sensitivity Чувствительность + Sent to the printer ({spooler}) Отправлено на принтер ({spooler}) + Sepia Сепия + Services Службы + Set Colors Задать цвета + Set Measurement Scale Задать масштаб измерений + Sgray sGray + Shadow Тень + Shadow Amount Величина теней + Shadow Intensity Интенсивность теней + Shadow Radius Радиус теней + Shadow Tone Тон теней + Shadows up to: Тени до: + Shadows/Highlights Тени/Света + Shallow Поверхностный + Shape Blur Размытие по фигуре + Sharpen Amount Величина резкости + Sharpen Detail Детализация резкости + Sharpen Details Резкость деталей + Sharpen Masking Маскирование резкости + Sharpen Radius Радиус резкости + Shear Искривление + Shine Блеск + Short Lines Короткие линии + Short Strokes Короткие штрихи + Shortcut Сочетание клавиш + Shortcuts Сочетания клавиш + Show All Показать все + Show Extras Options Параметры вспомогательных элементов + Show Guides Показать направляющие + Show Guides (⌘;) Показать направляющие (⌘;) + Show Rulers Показать линейки + Show the layer's effects (⌥-click: all layers) Показать эффекты слоя (⌥+щелчок: все слои) + Simple Простой + Simulate Paper Имитировать бумагу + Sine Синусоида + Sketch Эскиз + Skip Пропустить + Slice Options Параметры фрагмента + Smart Blur Умное размытие + Smart Sharpen Умная резкость + Smoothness Сглаженность + Smudge Stick Растушёвка + Snap to Grid Привязать к сетке + Snowflakes Снежинки + Soften Смягчить + Softness Мягкость + Software adapter Программный адаптер + Solarized Dark Solarized (тёмная) + Solid Color Однотонный цвет + Solid Front Faces Сплошные передние грани + Source Channel Канал-источник + Source Invert Инвертировать источник + Source Layer Исходный слой + Source1 Channel Канал источника 1 + Source1 Invert Инвертировать источник 1 + Source2 Channel Канал источника 2 + Source2 Invert Инвертировать источник 2 + Source: set Источник: задан + Space Пробел + Sparkle Искры + Spatter Разбрызгивание + Speckle Крапинки + Speed Скорость + Spherize Сферизация + Spin Вращение + Spin Blur Размытие вращением + Split Channels Разделить каналы + Spot Точечный + Spray Radius Радиус распыления + Sprayed Strokes Разбрызганные штрихи + Spread Разброс + Sprinkles Вкрапления + Square Size Размер квадрата + Square, 12 x 12 Квадратная, 12 x 12 + Square, 5 x 5 Квадратная, 5 x 5 + Squareness Квадратность + Stagger Смещение + Stained Glass Витраж + Star Звезда + Stars Звёзды + Start X Начало X + Start Y Начало Y + Statistics Статистика + Step {step} ({id}) failed: {error} Шаг {step} ({id}) не выполнен: {error} + Stippled Крапчатый + Stops Контрольные точки + Strength Сила + Stretch Растянуть + Stroke Color Цвет обводки + Stroke Detail Детализация обводки + Stroke Direction Направление обводки + Stroke Length Длина обводки + Stroke Pressure Нажим обводки + Stroke Size Размер обводки + Stroke Width Ширина обводки + Stroke path with brush Обвести контур кистью + Strokes Штрихи + Studio (Dark) Studio (тёмная) + Studio (Light) Studio (светлая) + Stylization Стилизация + Subtract Shapes at Overlap Вычесть фигуры при перекрытии + Subtract from selection (⌥) Вычесть из выделенной области (⌥) + Sumi-e Суми-э + Surface Blur Размытие по поверхности + Switch Foreground and Background Colors Поменять цвета переднего плана и фона + System Info Информация о системе + Tablet Планшет + Tabloid Tabloid + Taper Сужение + Target Цель + Target Channel 3 Целевой канал 3 + Target Channel 4 Целевой канал 4 + Target Channel 5 Целевой канал 5 + Target Channel 6 Целевой канал 6 + Target Channel 7 Целевой канал 7 + Target Channel 8 Целевой канал 8 + Target Channel 9 Целевой канал 9 + Target Composite Channel Целевой составной канал + Target X Цель X + Target Y Цель Y + Teal Orange Бирюзовый–оранжевый + Technology Previews… Пробные технологии… + Temporary Временный + Text Layer Текстовый слой + Texture Coverage Покрытие текстуры + Texturizer Текстуризатор + Thaw Mask Разморозить маску + The canvas is shown as sRGB. Choose a monitor profile in Edit › Color Settings. Холст отображается как sRGB. Выберите профиль монитора в разделе «Редактирование › Настройки цвета». + The canvas on {display} is shown as sRGB. Choose a monitor profile in Edit › Color Settings. Холст на {display} отображается как sRGB. Выберите профиль монитора в разделе «Редактирование › Настройки цвета». + The current gradient (pick one in Window › Gradients) Текущий градиент (выберите в меню «Окно › Градиенты») + The document does not have an embedded colour profile. Working space: {working}. Документ не содержит встроенного цветового профиля. Рабочее пространство: {working}. + The document has an embedded colour profile that does not match the working space.\nEmbedded: {embedded}\nWorking: {working} Документ содержит встроенный цветовой профиль, не совпадающий с рабочим пространством.\nВстроенный: {embedded}\nРабочее пространство: {working} + The file could not be opened. Не удалось открыть файл. + The file could not be saved. Не удалось сохранить файл. + Thickness Толщина + This Layer: Этот слой: + This Smart Object must be rasterized before proceeding. Its contents will no longer be editable. Rasterize the Smart Object? Перед продолжением необходимо растрировать этот смарт-объект. Его содержимое перестанет быть редактируемым. Растрировать смарт-объект? + This adjustment ({key}) is kept as imported and can't be edited yet. Эта коррекция ({key}) сохраняется в импортированном виде и пока не может быть изменена. + This fill layer must be rasterized before proceeding. Its fill content will no longer be editable. Rasterize the layer? Перед продолжением необходимо растрировать этот слой заливки. Содержимое заливки перестанет быть редактируемым. Растрировать слой? + This layer must be rasterized before proceeding. Перед продолжением необходимо растрировать этот слой. + This session: {reason} Этот сеанс: {reason} + This shape layer must be rasterized before proceeding. It will no longer be editable as a shape. Rasterize the shape? Перед продолжением необходимо растрировать этот слой фигуры. Он перестанет быть редактируемым как фигура. Растрировать фигуру? + This type layer must be rasterized before proceeding. Its text will no longer be editable. Rasterize the type? Перед продолжением необходимо растрировать этот текстовый слой. Его текст перестанет быть редактируемым. Растрировать текст? + Tile Size Размер плитки + Tiles Плитки + Tilt-Shift Наклон-сдвиг + Tint Color Цвет тонирования + Tiny Lens Маленькая линза + Title Заголовок + To До + To Center К центру + Toggle Layer Mask Overlay Переключить наложение маски слоя + Toggle the Brush Settings panel Переключить панель «Параметры кисти» + Toggle the Brush Settings panel (F5) Переключить панель «Параметры кисти» (F5) + Tool Options Параметры инструмента + Trace Contour Обводка контуров + Tracking (1/1000 em) Трекинг (1/1000 em) + Transformation Трансформирование + Transition Переход + Transparency Shapes Layer Прозрачность задаёт форму слоя + Trap Приводка + Tree Дерево + Trim Обрезать + Turbulent Турбулентность + Twirl Скручивание + Twirl Clockwise Скручивание по часовой стрелке + Twirl Counterclockwise Скручивание против часовой стрелки + Type Options Параметры текста + Unaltered Без изменений + Undefined Areas Неопределённые области + Underlying Layer: Нижележащий слой: + Underpainting Подмалёвок + Underwater Под водой + Ungroup Разгруппировать + Uniform Однородный + Unite Shapes at Overlap Объединить фигуры при перекрытии + Unlink Разъединить + Unlock Разблокировать + Unlocked Разблокировано + Unsaved Changes Несохранённые изменения + Unsharp Mask Нерезкое маскирование + Unsupported file format Неподдерживаемый формат файла + Unsupported format Неподдерживаемый формат + Up Вверх + Update Обновить + Updates Обновления + Upper Верхний + Use Custom Color Использовать свой цвет + Use File › Open to open an image. Используйте «Файл › Открыть», чтобы открыть изображение. + Use Selection In Source Использовать выделение в источнике + Use Selection In Target Использовать выделение в цели + Uses the foreground and background colors. Использует цвета переднего плана и фона. + Vanishing Point Точка схода + Variable Data Sets Наборы данных переменных + Vector Mask Hides Effects Векторная маска скрывает эффекты + Vertical Distortion Вертикальная дисторсия + Vertical Only Только по вертикали + Vertical Type Tool Инструмент «Вертикальный текст» + Vertical scale Масштаб по вертикали + Vertical type Вертикальный текст + Video Frames to Layers Видеокадры в слои + Vignette Amount Величина виньетки + Vignette Feather Растушёвка виньетки + Vignette Highlights Светлые тона виньетки + Vignette Midpoint Средняя точка виньетки + Vignette Roundness Округлость виньетки + Vignette Style Стиль виньетки + Vine Color Цвет лозы + Vine With Flowers Лоза с цветами + Vine With Leaves Лоза с листьями + Violent Сильный + W: Ш: + WIA Support Поддержка WIA + Warm Тёплый + Warming Filter (85) Согревающий фильтр (85) + Warming L B A Согревающий (LBA) + Warming81 Согревающий (81) + Warming85 Согревающий (85) + Warmth Теплота + Warning Предупреждение + Warp Text Деформация текста + Watercolor Акварель + Watercolour look: lighter interior, darker rims. Акварельный вид: светлее внутри, темнее по краям. + Wavelength Max Макс. длина волны + Wavelength Min Мин. длина волны + Waves Волны + Web Large Web (крупный) + Web Minimum Web (минимальный) + Web Most Common Web (наиболее распространённый) + WebP (lossless) WebP (без потерь) + Wetness Влажность + White Clip Отсечение белого + White Intensity Интенсивность белого + White Is High Белое — высокое + Whole Word Слово целиком + Wide Blurry Широкий размытый + Wide Sharp Широкий резкий + Wind Ветер + Window renderer: {renderer} Отрисовка окна: {renderer} + Working Рабочее + Working Cmyk Рабочий CMYK + Working Gray Рабочий: градации серого + Working Rgb Рабочий RGB + Working-cmyk Рабочий CMYK + Workspaces Рабочие среды + Wrap Оборачивание + Yellows Жёлтые тона + ZigZag Зигзаг + Zigzag Зигзаг + Zoom In (hold, drag to scrub) Увеличение (удерживайте, перетащите для прокрутки) + Zoom Out (hold) Уменьшение (удерживайте) + auto авто + blue синий + brushes кисти + fallback резерв + gradients градиенты + gray серый + green зелёный + layer mask маска слоя + manual вручную + none нет + off выключено + on включено + orange оранжевый + previous reading kept; the last re-read failed: {reason} сохранено предыдущее значение; последнее повторное чтение не удалось: {reason} + red красный + unknown неизвестно + unknown type неизвестный тип + vector mask векторная маска + violet фиолетовый + yellow жёлтый + {index} of {total} {index} из {total} + {label} is not available: {reason} {label} недоступно: {reason} + {message} (notes: {n}; {warning}) {message} (примечаний: {n}; {warning}) + {shortcut} assigned. {shortcut} назначено. + {shortcut} is already in use by {commands} and will be removed from it when you click OK. {shortcut} уже используется командой {commands} и будет удалено из неё при нажатии «ОК». + {verb} {mask} {layer} {verb} {mask} {layer} + ↑↓ navigate ↵ run esc close ↑↓ навигация ↵ выполнить esc закрыть + ✓ Color Indicates Masked Areas ✓ Цветом обозначены маскированные области + ✓ Color Indicates Selected Areas ✓ Цветом обозначены выделенные области + + +# Dynamic command-id, plural, and contextual labels (fourth pass). +@id edit.clear Очистить +@id edit.purge.undo Данные отмены +@id edit.stroke Обвести… +@id edit.transform.scale Масштабирование +@id file.close Закрыть +@id filter.sharpen.sharpen Резкость +@id layer.layerMask.apply Применить +@id select.all Выделить всё +@plural Imported {n} brush from {file}|Imported {n} brushes from {file} Импортирована {n} кисть из {file}|Импортированы {n} кисти из {file}|Импортировано {n} кистей из {file} +@plural Imported {n} gradient from {file}|Imported {n} gradients from {file} Импортирован {n} градиент из {file}|Импортированы {n} градиента из {file}|Импортировано {n} градиентов из {file} +@plural Imported {n} pattern from {file}|Imported {n} patterns from {file} Импортирован {n} узор из {file}|Импортированы {n} узора из {file}|Импортировано {n} узоров из {file} +cameraRaw Balance Баланс +cameraRaw Clarity Чёткость +cameraRaw Color Цвет +cameraRaw Color Mixer Смеситель цветов +cameraRaw Contrast Контраст +cameraRaw Curve Кривая +cameraRaw Dehaze Удаление дымки +cameraRaw Detail Детализация +cameraRaw Effects Эффекты +cameraRaw Exposure Экспозиция +cameraRaw Feather Растушёвка +cameraRaw Highlights Света +cameraRaw Luminance Яркость +cameraRaw Masking Маскирование +cameraRaw Midpoint Средняя точка +cameraRaw Midtones Средние тона +cameraRaw Radius Радиус +cameraRaw Roundness Округлость +cameraRaw Saturation Насыщенность +cameraRaw Shadows Тени +cameraRaw Size Размер +cameraRaw Temperature Температура +cameraRaw Texture Текстура +cameraRaw Tint Оттенок +cameraRaw Whites Белые точки +colorRange Invert Инвертировать +document Orientation Ориентация +layerLabel Magenta Пурпурный +layerLabel No Color Без цвета +layerLabel Red Красный +layers Fill Заливка +layers Fill: Заливка: +navigator Zoom Масштаб +shape Appearance Оформление diff --git a/docs/gaps.md b/docs/gaps.md index faf0884c89..f48c5afae3 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -1,7 +1,6 @@ # Where PhotoCraft falls short of Photoshop -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (G1: smart filter masks show on PSD open, Photoshop-oracle floor 188/258; G10: DPX read and write, #1049, Cineon read; G1: CMYK Hue/Saturation and Color Balance keep the black ink; G1: CMYK Posterize on the stored values, psd-tools oracle floor 242/309; G1: CMYK adjustment layers composite on the document's inks, Photoshop-oracle floor 165/258; G1: CMYK Brightness/Contrast on the inks, CMYK Threshold's luma; G1: modern Brightness/Contrast's brightness curve matches Photoshop, Photoshop-oracle floor 151/258; G1: Lab documents composite on their stored L, a, b, Photoshop-oracle floor 152/258; G1: Levels on whole levels at every step, Photoshop-oracle floor 146/258, psd-tools 238/309; G1: 32-bit Brightness/Contrast legacy, Channel Mixer, Photo Filter and Black & White tint unclipped as in Photoshop, Photoshop-oracle floor 149/258; G1: mask feathers blur with their exact variance, psd-tools oracle floor 240/309; G1: Threshold splits on Photoshop's luminosity, psd-tools oracle floor 241/309; G1: pass-through clipping base with an adjustment renders isolated, psd-tools oracle floor 240/309; G1: 16-bit Posterize as Photoshop bins it, psd-tools oracle floor 241/309; G1: Gradient Map driven by the W3C luminosity, Photoshop-oracle floor 147/258; G7: Field Blur's on-canvas pins, #3373; G4: Hue Jitter amount track, #3825 partial; G4: minimum field interaction, #3825 partial; G1: CMYK Invert, Posterize and Threshold on the inks, 16-bit Lab Invert; Photoshop-oracle floor 145/258; G11: `.atn` import maps 112 events: Select and Mask, Filter Gallery Spatter, Ripple, Crystallize, Pattern Fill layers, mask targeting, Step Backward, nudges in points, Bitmap mode; G5: Free Transform H/V skew fields; G9: toolbar tool shortcut editing; refreshed against main: G2, G5, G7, G9, G10, G13, G14, G17 items that landed; G10: DPX read and write, #1049; G4: checkbox-only Brush Settings rows; G11: Load/Save Actions `.atn` landed, AUTO-219-2 partial; G5: hold-a-key spring-loaded tools landed, #2672; G1: #2465 fixed; stale missing-tool rows corrected: Color Replacement, Red Eye and Add/Delete Anchor Point are live tools, so only Art History Brush is missing; no estimate changed) · **Target:** Adobe Photoshop 2026 - +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-15 · **Change:** minor (G13: Russian gained the 1,233 dynamic UI labels other catalogs already had; no estimate changed; previously minor: G1: smart filter masks show on PSD open, Photoshop-oracle floor 188/258; G10: DPX read and write, #1049, Cineon read; G1: CMYK Hue/Saturation and Color Balance keep the black ink; G1: CMYK Posterize on the stored values, psd-tools oracle floor 242/309; G1: CMYK adjustment layers composite on the document's inks, Photoshop-oracle floor 165/258; G1: CMYK Brightness/Contrast on the inks, CMYK Threshold's luma; G1: modern Brightness/Contrast's brightness curve matches Photoshop, Photoshop-oracle floor 151/258; G1: Lab documents composite on their stored L, a, b, Photoshop-oracle floor 152/258; G1: Levels on whole levels at every step, Photoshop-oracle floor 146/258, psd-tools 238/309; G1: 32-bit Brightness/Contrast legacy, Channel Mixer, Photo Filter and Black & White tint unclipped as in Photoshop, Photoshop-oracle floor 149/258; G1: mask feathers blur with their exact variance, psd-tools oracle floor 240/309; G1: Threshold splits on Photoshop's luminosity, psd-tools oracle floor 241/309; G1: pass-through clipping base with an adjustment renders isolated, psd-tools oracle floor 240/309; G1: 16-bit Posterize as Photoshop bins it, psd-tools oracle floor 241/309; G1: Gradient Map driven by the W3C luminosity, Photoshop-oracle floor 147/258; G7: Field Blur's on-canvas pins, #3373; G4: Hue Jitter amount track, #3825 partial; G4: minimum field interaction, #3825 partial; G1: CMYK Invert, Posterize and Threshold on the inks, 16-bit Lab Invert; Photoshop-oracle floor 145/258; G11: `.atn` import maps 112 events: Select and Mask, Filter Gallery Spatter, Ripple, Crystallize, Pattern Fill layers, mask targeting, Step Backward, nudges in points, Bitmap mode; G5: Free Transform H/V skew fields; G9: toolbar tool shortcut editing; refreshed against main: G2, G5, G7, G9, G10, G13, G14, G17 items that landed; G10: DPX read and write, #1049; G4: checkbox-only Brush Settings rows; G11: Load/Save Actions `.atn` landed, AUTO-219-2 partial; G5: hold-a-key spring-loaded tools landed, #2672; G1: #2465 fixed; stale missing-tool rows corrected: Color Replacement, Red Eye and Add/Delete Anchor Point are live tools, so only Art History Brush is missing; no estimate changed) · **Target:** Adobe Photoshop 2026 Every known shortfall against Photoshop 2026 (27.11.0), one entry each, ranked by user impact on the way to beta. This is the work list agents pick from: take the highest-ranked entry that nobody is working on, and when you close part of one, update its evidence and estimate here and @@ -254,6 +253,10 @@ what an agent can't finish alone. as tofu (#1408, #1693). - **Evidence (2026-10-11):** Czech dynamic UI wrappers, hardware labels and composed messages translate at display sites; engine errors remain English. This does not close G13. +- **Evidence (2026-10-15):** Russian gained the 1,233 dynamic labels the other catalogs already + had (filter and adjustment parameters, Camera Raw, lighting, History/Layers/paths menus, + metadata, System Info, status templates, dialogs), with a terminology note + ([localization-ru](localization-ru.md)); engine errors remain English. This does not close G13. - **Detail:** [`localization-parity.md`](localization-parity.md). - **Estimate:** 80–140 h. **Human:** native-speaker review of every catalog (none is recorded). @@ -270,6 +273,7 @@ what an agent can't finish alone. | Date | Change | Summary | |---|---|---| +| 2026-10-15 | minor | G13: Russian gained the 1,233 dynamic UI labels other catalogs already had (filter and adjustment parameters, Camera Raw, lighting, History/Layers/paths menus, metadata, System Info, status templates, dialogs); engine messages and native review remain open | | 2026-10-14 | minor | G1: a PSD smart object's filter mask shows when the file opens (Photoshop stores the filtered pixels unmasked); export stores them that way too; Photoshop-oracle floor 185 → 188 of 258 | | 2026-10-13 | minor | G14: D90 image area measured (4288 × 2848 at (10, 10)); gap remains open | | 2026-10-14 | minor | Corpus floors raised to the measured results of the CMYK, Lab, 32-bit, Levels, Threshold, Posterize, feather and pass-through fixes landed together: Photoshop-authored 165 → 185 of 258, io 146 → 148 of 170, psd-tools 242 → 249 of 309 | @@ -286,8 +290,7 @@ what an agent can't finish alone. | 2026-10-13 | minor | G1: Threshold splits on Photoshop's luminosity of whole samples (8- and 16-bit rules exact on four oracles); psd-tools threshold_rgb and threshold_16bits_rgb pass; psd-tools floor → 241 of 309 | | 2026-10-13 | minor | G1: a pass-through group that is a clipping base and holds an adjustment layer renders isolated; psd-tools passthrough_clipping_mask_adjustment passes; psd-tools floor → 240 of 309 | | 2026-10-13 | minor | G1: 16-bit Posterize bins the 16-bit sample, as Photoshop does (exact on two oracles); psd-tools floor → 241 of 309 | -| 2026-10-13 | minor | G1: Gradient Map driven by the W3C luminosity (0.3, 0.59, 0.11), as Photoshop is; rgb8/rgb16 gradient-map pass; Photoshop-oracle floor 145 → 147 of 258 | -| 2026-10-13 | minor | G6: stale "missing tool" list corrected against `main` — Color Replacement (`paint.colorReplacement`), Red Eye (`paint.redEye`) and Add/Delete Anchor Point (P flyout, #4144) are live tools, so the toolbox is 67 of 68 and Art History Brush is the only one missing; no percentage or estimate changed | +| 2026-10-13 | minor | G1: Gradient Map driven by the W3C luminosity (0.3, 0.59, 0.11), as Photoshop is; rgb8/rgb16 gradient-map pass; Photoshop-oracle floor 145 → 147 of 258 || 2026-10-13 | minor | G6: stale "missing tool" list corrected against `main` — Color Replacement (`paint.colorReplacement`), Red Eye (`paint.redEye`) and Add/Delete Anchor Point (P flyout, #4144) are live tools, so the toolbox is 67 of 68 and Art History Brush is the only one missing; no percentage or estimate changed | | 2026-10-13 | minor | G4: Hue Jitter amount-track appearance (#3825, partial); no readiness percentage or estimate changed | | 2026-10-13 | minor | G4: minimum inputs in Shape Dynamics and Transfer follow their Control (#3825, partial); no readiness percentage or estimate changed | | 2026-10-12 | minor | G5: Free Transform options bar H/V skew fields done | diff --git a/docs/localization-parity.md b/docs/localization-parity.md index 0b2be85595..cc91806da3 100644 --- a/docs/localization-parity.md +++ b/docs/localization-parity.md @@ -1,10 +1,10 @@ # Localization parity -> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-13 · **Change:** minor (Spanish dynamic labels; previously minor: Serbian catalog) · **Target:** Adobe Photoshop 2026 +> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-15 · **Change:** minor (Russian dynamic labels; previously minor: Spanish dynamic labels) · **Target:** Adobe Photoshop 2026 Per-language status of PhotoCraft's UI. How catalogs work and how to add one: [`localization.md`](localization.md). Language notes: [Korean](localization-ko.md), [Spanish](localization-es.md), -[Simplified Chinese](localization-zh-hans.md), [Italian](localization-it.md). +[Simplified Chinese](localization-zh-hans.md), [Italian](localization-it.md), [Russian](localization-ru.md). **How measured.** `cargo xtask i18n-coverage` (`xtask/src/i18n_coverage.rs`) defines the key universe: `tl!("…")` literals and `trn(` plurals in `ui-egui` (tests excluded), menu-catalog @@ -46,8 +46,8 @@ speakers, ~55% ready. Remaining: 80–140 h, plus native-speaker review (recorde ## Other shipped languages 8 more, each at 1,608 / 1,608 measured keys, status `partial` for the same reason: Traditional -Chinese `zh-hant` (2,329 rows), Russian `ru` (2,544), Ukrainian `uk` (3,375), Czech `cs` (3,522), -Polish `pl` (3,366), Greek `el` (3,481), Dutch `nl` (3,568), Italian `it` (4,008; also every dynamic label the +Chinese `zh-hant` (2,329 rows), Russian `ru` (4,208; also every dynamic label the +other catalogs translate, [notes](localization-ru.md)), Ukrainian `uk` (3,375), Czech `cs` (3,522), Polish `pl` (3,366), Greek `el` (3,481), Dutch `nl` (3,568), Italian `it` (4,164; also every dynamic label the other catalogs translate; reviewed by a native speaker, [notes](localization-it.md)). Turkish `tr` (3,731 rows) also covers every measured key. Serbian `sr` (4,022 rows, Latin script, Ekavian, added 2026-10-12) covers every @@ -87,3 +87,4 @@ engine localization or native review is complete. | 2026-10-12 | minor | Spanish: 1,276 runtime-only labels added (filter and adjustment parameters, Camera Raw, panels, dialogs and status wrappers); generated-label coverage test added | | 2026-10-12 | minor | Italian native-speaker review: mistranslations (Trap, HDR Toning, Display Text) and phrasing fixed | | 2026-10-12 | minor | Serbian (`sr`, Latin, Ekavian) catalog: every measured key plus the dynamic labels of the other catalogs | +| 2026-10-15 | minor | Russian: 1,233 dynamic labels added (filter and adjustment parameters, Camera Raw, lighting, History/Layers/paths menus, metadata, System Info, status templates, dialogs) with a terminology note ([localization-ru](localization-ru.md)) | diff --git a/docs/localization-ru.md b/docs/localization-ru.md new file mode 100644 index 0000000000..c4bda602b4 --- /dev/null +++ b/docs/localization-ru.md @@ -0,0 +1,75 @@ +# Russian terminology and dynamic UI labels + +This note goes with the completion of the Russian catalog (`crates/ui-egui/src/i18n/ru.tsv`). +The catalog already covered every key `cargo xtask i18n-coverage` measures, but it lacked 1,233 +labels that only exist at runtime and that other catalogs (Italian, Serbian, Czech, Hungarian) +already translate: filter and adjustment parameter names, Camera Raw panels, lighting effects, +History / Layers / paths / channels menu labels, metadata fields, status messages, System Info +and dialog text. In Russian those showed in English. +They are now translated, reusing the vocabulary the catalog already uses elsewhere so the same +term reads the same everywhere. Translations are written from the meaning of PhotoCraft's English +labels; no proprietary translation resources, fonts, screenshots or user documents are included. + +## Register and style + +Commands are imperatives in the second person plural (*Сохранить*, *Удалить*, *Заново*); +where the catalog already uses a noun it is kept (*Уровни…*, *Кривые…*). Sentence case after the +first word, as in the rest of the catalog. Ellipses on dialog commands are preserved (`Ветер…` +→ `Ветер…`). Decimals use a comma (the file format's own labels keep their dots, e.g. +`Landscape, 10 x 8` stays `Альбомная, 10 x 8`). Latin product and technology names are not +translated (PhotoCraft, Camera Raw, RGB, CMYK, Lab, LUT, WebP); Camera Raw keeps `.xmp`; the +`HDR`, `HSL`, `HSB` and `W3C` acronyms and the `sGray`/`Pro Photo` identifiers stay Latin. + +## Terminology + +Existing choices were followed rather than re-invented, so the new rows use: + +| English source | Russian | Note | +|---|---|---| +| Highlight(s) | Света | already in the catalog (`Highlights` → `Света`) | +| Midtone(s) | Полутона | already in the catalog | +| Shadow(s) | Тени | pairs with *Света* | +| Feather | Растушёвка | never *перо* | +| Fuzziness | Разброс | Color Range's tolerance | +| Dither | Дизеринг | | +| Pattern | Узор | also in *Узорный штамп* | +| Preset | Набор | *Набор кистей*, *набор инструментов* | +| Guide | Направляющая | *Схема направляющих* | +| Artboard | Монтажная область | | +| Workspace | Рабочая среда | | +| Stroke (contour) | Обводка | painterly strokes stay *штрихи*/*мазки* | +| Stroke (paint param) | Штрихи / мазки | `Strokes` → `Штрихи` | +| Amount | Величина | `Штрихи`, never *количество*, when it is a strength | +| Fill Opacity | Непрозрачность заливки | distinct from layer *Непрозрачность* | +| Feather Radius | Радиус растушёвки | | +| Sharpen | Резкость | `Smart Sharpen` → *Умная резкость*, `Unsharp Mask` → *Контурная резкость* | +| Noise | Шум | `Reduce Noise` → *Уменьшить шум* | +| Content-Aware | С учётом содержимого | | +| Camera Raw | Camera Raw | the panel name stays Latin (as German/Spanish do; Greek translates it) | +| Place Embedded / Linked | Поместить встроенный / связанный | | +| Rasterize | Растрировать | | + +## Native review + +The review was AI-assisted and each row was checked against the meaning of PhotoCraft's own +English label. A review by a native speaker is still welcome. + +## Not covered + +Engine error and status messages, user document, layer and preset names, and automation command +IDs stay in English, as in every language ([`localization-parity.md`](localization-parity.md)). +A few dynamic labels the other catalogs carry are not used by the current code; they are +translated to stay aligned with the other catalogs. + +## Validation + +- `russian_covers_generated_filter_options_and_gallery_names` (`crates/ui-egui/src/filter_dialog.rs`) + enumerates every filter and adjustment parameter, symbolic choice and gallery name and requires + a Russian row for each, like the Korean and Italian tests. +- `russian_dynamic_status_hardware_and_dialog_labels_are_translated` (`crates/ui-egui/src/i18n/mod.rs`) + covers the rasterize prompts, hardware status lines, composed status templates and a sample of + filter labels. +- `cargo test -p photocraft-ui-egui --lib`: 2,689 passed, 0 failed. +- `cargo xtask i18n-coverage`: `ru` remains at 100% of the measured keys. +- The offscreen `snapshot` example with `PHOTOCRAFT_LOCALE=ru` renders the dialogs with Russian + labels, without missing glyphs or clipped text. From 8dcc64bd2f2e08412ec793fdfa140bfb174d930d Mon Sep 17 00:00:00 2001 From: tareqziad1998 <83318513+tareqziad1998@users.noreply.github.com> Date: Fri, 16 Oct 2026 15:58:53 +0000 Subject: [PATCH 17/44] Type: paragraph direction buttons in the Paragraph panel and options bar (TYPE-218-2, #218) The engine has had TextDirection (Auto / LTR / RTL) and type.setStyle's "direction" key, but nothing in the UI set it. Now: - Window > Paragraph and the Properties Paragraph section have a row of three buttons under the alignment row: Automatic, Left-to-right and Right-to-left paragraph direction. They set the selected layers' direction (or the selected text's paragraphs while typing) in one undo step, and show the current one. - The Type options bar has the same three buttons after its alignment buttons. Like alignment, they also set the direction new type starts with (new tool option `type_direction`, "auto" by default, reset with the tool's other options). - The glyph is drawn (a pilcrow over an arrow), so no new icon asset. - The three tooltips are translated in all 20 catalogs (every_tl_literal_is_translated). Tests: paragraph_direction_buttons_set_and_show_the_direction and options_bar_direction_sets_selected_layers_and_new_type. TYPE-218-2 goes from missing to partial: the visual RTL caret (arrow keys moving on screen rather than in logical order) is still to do. scorecard/type.toml, docs/scorecard.md and docs/gaps.md (G8) are updated. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01WpQQNxTPBzSLvgAhX27Vtv --- crates/ui-egui/src/i18n/cs.tsv | 5 +++ crates/ui-egui/src/i18n/de.tsv | 5 +++ crates/ui-egui/src/i18n/el.tsv | 5 +++ crates/ui-egui/src/i18n/es.tsv | 5 +++ crates/ui-egui/src/i18n/fr.tsv | 5 +++ crates/ui-egui/src/i18n/hu.tsv | 5 +++ crates/ui-egui/src/i18n/id.tsv | 5 +++ crates/ui-egui/src/i18n/it.tsv | 5 +++ crates/ui-egui/src/i18n/ja.tsv | 5 +++ crates/ui-egui/src/i18n/ko.tsv | 5 +++ crates/ui-egui/src/i18n/nl.tsv | 5 +++ crates/ui-egui/src/i18n/pl.tsv | 5 +++ crates/ui-egui/src/i18n/pt-br.tsv | 5 +++ crates/ui-egui/src/i18n/ru.tsv | 5 +++ crates/ui-egui/src/i18n/sr.tsv | 5 +++ crates/ui-egui/src/i18n/tr.tsv | 5 +++ crates/ui-egui/src/i18n/uk.tsv | 5 +++ crates/ui-egui/src/i18n/vi.tsv | 5 +++ crates/ui-egui/src/i18n/zh-hans.tsv | 5 +++ crates/ui-egui/src/i18n/zh-hant.tsv | 5 +++ crates/ui-egui/src/state.rs | 4 ++ crates/ui-egui/src/tool_reset.rs | 4 +- crates/ui-egui/src/type_tool.rs | 64 +++++++++++++++++++++++++++ crates/ui-egui/src/type_tool_tests.rs | 56 +++++++++++++++++++++++ docs/gaps.md | 9 ++-- docs/scorecard.md | 4 +- scorecard/type.toml | 4 +- 27 files changed, 236 insertions(+), 9 deletions(-) diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index f3fb8c8c40..da6e8c4f0d 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -3816,3 +3816,8 @@ navigator Zoom Zvětšení This document's layers: {needed} of {budget} for layers Vrstvy tohoto dokumentu: {needed} z {budget} pro vrstvy This document's layers need {needed} but may hold {budget}: whole-document redraws use the CPU Vrstvy tohoto dokumentu potřebují {needed}, ale mohou zabrat {budget}: překreslení celého dokumentu probíhá na CPU GPU Memory (MB, 0 = Automatic) Paměť GPU (MB, 0 = Automaticky) + +# Paragraph direction (TYPE-218-2) + Automatic paragraph direction Automatický směr odstavce + Left-to-right paragraph direction Směr odstavce zleva doprava + Right-to-left paragraph direction Směr odstavce zprava doleva diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 48ebfba678..24004c7cd0 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -3210,3 +3210,8 @@ curves Smooth Glätten This document's layers: {needed} of {budget} for layers Ebenen dieses Dokuments: {needed} von {budget} für Ebenen This document's layers need {needed} but may hold {budget}: whole-document redraws use the CPU Die Ebenen dieses Dokuments brauchen {needed}, dürfen aber {budget} belegen: Das ganze Dokument wird auf der CPU neu gezeichnet GPU Memory (MB, 0 = Automatic) GPU-Speicher (MB, 0 = Automatisch) + +# Paragraph direction (TYPE-218-2) + Automatic paragraph direction Automatische Absatzrichtung + Left-to-right paragraph direction Absatzrichtung von links nach rechts + Right-to-left paragraph direction Absatzrichtung von rechts nach links diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index f2c67c2c41..7e0c5fcd91 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -3908,3 +3908,8 @@ curves Smooth Εξομάλυνση This document's layers: {needed} of {budget} for layers Επίπεδα αυτού του εγγράφου: {needed} από {budget} για επίπεδα This document's layers need {needed} but may hold {budget}: whole-document redraws use the CPU Τα επίπεδα αυτού του εγγράφου χρειάζονται {needed} αλλά μπορούν να καταλάβουν {budget}: η επανασχεδίαση όλου του εγγράφου γίνεται στη CPU GPU Memory (MB, 0 = Automatic) Μνήμη GPU (MB, 0 = Αυτόματο) + +# Paragraph direction (TYPE-218-2) + Automatic paragraph direction Αυτόματη κατεύθυνση παραγράφου + Left-to-right paragraph direction Κατεύθυνση παραγράφου από αριστερά προς τα δεξιά + Right-to-left paragraph direction Κατεύθυνση παραγράφου από δεξιά προς τα αριστερά diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 45804fa2a4..ffb18a1b7d 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -4170,3 +4170,8 @@ navigator Zoom Zoom This document's layers: {needed} of {budget} for layers Capas de este documento: {needed} de {budget} para capas This document's layers need {needed} but may hold {budget}: whole-document redraws use the CPU Las capas de este documento necesitan {needed} pero pueden ocupar {budget}: los redibujados de todo el documento usan la CPU GPU Memory (MB, 0 = Automatic) Memoria de GPU (MB, 0 = Automático) + +# Paragraph direction (TYPE-218-2) + Automatic paragraph direction Dirección de párrafo automática + Left-to-right paragraph direction Dirección de párrafo de izquierda a derecha + Right-to-left paragraph direction Dirección de párrafo de derecha a izquierda diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 3e3451e096..dfe36de15b 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -3213,3 +3213,8 @@ curves Smooth Lisser This document's layers: {needed} of {budget} for layers Calques de ce document : {needed} sur {budget} pour les calques This document's layers need {needed} but may hold {budget}: whole-document redraws use the CPU Les calques de ce document ont besoin de {needed} mais peuvent occuper {budget} : le document entier se redessine sur le CPU GPU Memory (MB, 0 = Automatic) Mémoire GPU (Mo, 0 = Automatique) + +# Paragraph direction (TYPE-218-2) + Automatic paragraph direction Sens du paragraphe automatique + Left-to-right paragraph direction Sens du paragraphe de gauche à droite + Right-to-left paragraph direction Sens du paragraphe de droite à gauche diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 9dc9a47fbc..59f06dd3d4 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -4127,3 +4127,8 @@ cameraRaw Yellow Sárga This document's layers: {needed} of {budget} for layers A dokumentum rétegei: {needed} / {budget} a rétegeknek This document's layers need {needed} but may hold {budget}: whole-document redraws use the CPU A dokumentum rétegeinek {needed} kell, de {budget} foglalhatnak: a teljes dokumentum újrarajzolása a CPU-n fut GPU Memory (MB, 0 = Automatic) GPU-memória (MB, 0 = Automatikus) + +# Paragraph direction (TYPE-218-2) + Automatic paragraph direction Automatikus bekezdésirány + Left-to-right paragraph direction Balról jobbra irányuló bekezdés + Right-to-left paragraph direction Jobbról balra irányuló bekezdés diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 81e850f293..15d707cef1 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -3200,3 +3200,8 @@ curves Smooth Haluskan This document's layers: {needed} of {budget} for layers Layer dokumen ini: {needed} dari {budget} untuk layer This document's layers need {needed} but may hold {budget}: whole-document redraws use the CPU Layer dokumen ini membutuhkan {needed} tetapi boleh memakai {budget}: penggambaran ulang seluruh dokumen memakai CPU GPU Memory (MB, 0 = Automatic) Memori GPU (MB, 0 = Otomatis) + +# Paragraph direction (TYPE-218-2) + Automatic paragraph direction Arah paragraf otomatis + Left-to-right paragraph direction Arah paragraf kiri ke kanan + Right-to-left paragraph direction Arah paragraf kanan ke kiri diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index c097ded29d..716b5df952 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -4329,3 +4329,8 @@ shape Appearance Aspetto This document's layers: {needed} of {budget} for layers Livelli di questo documento: {needed} su {budget} per i livelli This document's layers need {needed} but may hold {budget}: whole-document redraws use the CPU I livelli di questo documento richiedono {needed} ma possono occupare {budget}: i ridisegni dell'intero documento usano la CPU GPU Memory (MB, 0 = Automatic) Memoria GPU (MB, 0 = Automatico) + +# Paragraph direction (TYPE-218-2) + Automatic paragraph direction Direzione del paragrafo automatica + Left-to-right paragraph direction Direzione del paragrafo da sinistra a destra + Right-to-left paragraph direction Direzione del paragrafo da destra a sinistra diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index b460965128..9a1a3e9762 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -3884,3 +3884,8 @@ curves Smooth スムーズ This document's layers: {needed} of {budget} for layers このドキュメントのレイヤー: レイヤー用 {budget} のうち {needed} This document's layers need {needed} but may hold {budget}: whole-document redraws use the CPU このドキュメントのレイヤーには {needed} が必要ですが、使えるのは {budget} です: ドキュメント全体の再描画は CPU で行われます GPU Memory (MB, 0 = Automatic) GPU メモリ(MB、0 = 自動) + +# Paragraph direction (TYPE-218-2) + Automatic paragraph direction 段落の方向を自動判別 + Left-to-right paragraph direction 左から右への段落方向 + Right-to-left paragraph direction 右から左への段落方向 diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 361cef2e95..6d5d07a25e 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -3386,3 +3386,8 @@ curves Smooth 매끄럽게 This document's layers: {needed} of {budget} for layers 이 문서의 레이어: 레이어용 {budget} 중 {needed} This document's layers need {needed} but may hold {budget}: whole-document redraws use the CPU 이 문서의 레이어에 {needed}가 필요하지만 {budget}만 사용할 수 있습니다: 문서 전체 다시 그리기는 CPU를 사용합니다 GPU Memory (MB, 0 = Automatic) GPU 메모리(MB, 0 = 자동) + +# Paragraph direction (TYPE-218-2) + Automatic paragraph direction 자동 단락 방향 + Left-to-right paragraph direction 왼쪽에서 오른쪽 단락 방향 + Right-to-left paragraph direction 오른쪽에서 왼쪽 단락 방향 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index f1ac67961e..2194bd133b 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -3968,3 +3968,8 @@ curves Smooth Vloeiend maken This document's layers: {needed} of {budget} for layers Lagen van dit document: {needed} van {budget} voor lagen This document's layers need {needed} but may hold {budget}: whole-document redraws use the CPU De lagen van dit document hebben {needed} nodig maar mogen {budget} gebruiken: het hele document wordt op de CPU opnieuw getekend GPU Memory (MB, 0 = Automatic) GPU-geheugen (MB, 0 = Automatisch) + +# Paragraph direction (TYPE-218-2) + Automatic paragraph direction Automatische alinearichting + Left-to-right paragraph direction Alinearichting van links naar rechts + Right-to-left paragraph direction Alinearichting van rechts naar links diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 04eb15609d..981c877471 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -3806,3 +3806,8 @@ curves Smooth Wygładź This document's layers: {needed} of {budget} for layers Warstwy tego dokumentu: {needed} z {budget} na warstwy This document's layers need {needed} but may hold {budget}: whole-document redraws use the CPU Warstwy tego dokumentu potrzebują {needed}, ale mogą zająć {budget}: odświeżanie całego dokumentu używa CPU GPU Memory (MB, 0 = Automatic) Pamięć GPU (MB, 0 = Automatycznie) + +# Paragraph direction (TYPE-218-2) + Automatic paragraph direction Automatyczny kierunek akapitu + Left-to-right paragraph direction Kierunek akapitu od lewej do prawej + Right-to-left paragraph direction Kierunek akapitu od prawej do lewej diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 8d6b88fb54..82660050f1 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -3998,3 +3998,8 @@ layerLabel No Color Sem cor This document's layers: {needed} of {budget} for layers Camadas deste documento: {needed} de {budget} para camadas This document's layers need {needed} but may hold {budget}: whole-document redraws use the CPU As camadas deste documento precisam de {needed} mas podem ocupar {budget}: o documento inteiro é redesenhado na CPU GPU Memory (MB, 0 = Automatic) Memória da GPU (MB, 0 = Automático) + +# Paragraph direction (TYPE-218-2) + Automatic paragraph direction Direção automática do parágrafo + Left-to-right paragraph direction Direção do parágrafo da esquerda para a direita + Right-to-left paragraph direction Direção do parágrafo da direita para a esquerda diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 9d733f11a7..98c591e7e3 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -3178,3 +3178,8 @@ curves Smooth Сгладить This document's layers: {needed} of {budget} for layers Слои этого документа: {needed} из {budget} для слоёв This document's layers need {needed} but may hold {budget}: whole-document redraws use the CPU Слоям этого документа нужно {needed}, но занимать они могут {budget}: весь документ перерисовывается на CPU GPU Memory (MB, 0 = Automatic) Память GPU (МБ, 0 = автоматически) + +# Paragraph direction (TYPE-218-2) + Automatic paragraph direction Автоматическое направление абзаца + Left-to-right paragraph direction Направление абзаца слева направо + Right-to-left paragraph direction Направление абзаца справа налево diff --git a/crates/ui-egui/src/i18n/sr.tsv b/crates/ui-egui/src/i18n/sr.tsv index d384c9badc..ca845fcdf2 100644 --- a/crates/ui-egui/src/i18n/sr.tsv +++ b/crates/ui-egui/src/i18n/sr.tsv @@ -4281,3 +4281,8 @@ shape Appearance Izgled This document's layers: {needed} of {budget} for layers Slojevi ovog dokumenta: {needed} od {budget} za slojeve This document's layers need {needed} but may hold {budget}: whole-document redraws use the CPU Slojevima ovog dokumenta treba {needed}, a smeju da zauzmu {budget}: ceo dokument se ponovo iscrtava na CPU-u GPU Memory (MB, 0 = Automatic) GPU memorija (MB, 0 = Automatski) + +# Paragraph direction (TYPE-218-2) + Automatic paragraph direction Automatski smer pasusa + Left-to-right paragraph direction Smer pasusa sleva nadesno + Right-to-left paragraph direction Smer pasusa zdesna nalevo diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 5c35aba71c..47bec8c0e1 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -3938,3 +3938,8 @@ cameraData Lens Lens This document's layers: {needed} of {budget} for layers Bu belgenin katmanları: katmanlar için {budget} içinden {needed} This document's layers need {needed} but may hold {budget}: whole-document redraws use the CPU Bu belgenin katmanlarının {needed} belleğe ihtiyacı var ama en fazla {budget} kullanabilirler: tüm belgenin yeniden çizimi CPU'yu kullanır GPU Memory (MB, 0 = Automatic) GPU Belleği (MB, 0 = Otomatik) + +# Paragraph direction (TYPE-218-2) + Automatic paragraph direction Otomatik paragraf yönü + Left-to-right paragraph direction Soldan sağa paragraf yönü + Right-to-left paragraph direction Sağdan sola paragraf yönü diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index cb6f883eb2..ccb9dbd17a 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -3907,3 +3907,8 @@ curves Smooth Згладити This document's layers: {needed} of {budget} for layers Шари цього документа: {needed} з {budget} для шарів This document's layers need {needed} but may hold {budget}: whole-document redraws use the CPU Шарам цього документа потрібно {needed}, але займати вони можуть {budget}: увесь документ перемальовується на CPU GPU Memory (MB, 0 = Automatic) Пам'ять GPU (МБ, 0 = автоматично) + +# Paragraph direction (TYPE-218-2) + Automatic paragraph direction Автоматичний напрямок абзацу + Left-to-right paragraph direction Напрямок абзацу зліва направо + Right-to-left paragraph direction Напрямок абзацу справа наліво diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index e2e55ab1ba..33e59f69d4 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -3694,3 +3694,8 @@ curves Smooth Làm mượt This document's layers: {needed} of {budget} for layers Các lớp của tài liệu này: {needed} trên {budget} dành cho lớp This document's layers need {needed} but may hold {budget}: whole-document redraws use the CPU Các lớp của tài liệu này cần {needed} nhưng chỉ được dùng {budget}: vẽ lại toàn bộ tài liệu dùng CPU GPU Memory (MB, 0 = Automatic) Bộ nhớ GPU (MB, 0 = Tự động) + +# Paragraph direction (TYPE-218-2) + Automatic paragraph direction Hướng đoạn văn tự động + Left-to-right paragraph direction Hướng đoạn văn từ trái sang phải + Right-to-left paragraph direction Hướng đoạn văn từ phải sang trái diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index eebeabaa6c..b0e62d1f16 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -2824,3 +2824,8 @@ curves Smooth 平滑 This document's layers: {needed} of {budget} for layers 此文档的图层:图层可用 {budget} 中的 {needed} This document's layers need {needed} but may hold {budget}: whole-document redraws use the CPU 此文档的图层需要 {needed},但只能占用 {budget}:整个文档的重绘使用 CPU GPU Memory (MB, 0 = Automatic) GPU 内存(MB,0 = 自动) + +# Paragraph direction (TYPE-218-2) + Automatic paragraph direction 自动段落方向 + Left-to-right paragraph direction 从左到右的段落方向 + Right-to-left paragraph direction 从右到左的段落方向 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index b35556b6fe..167d7c85b4 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -2809,3 +2809,8 @@ curves Smooth 平滑 This document's layers: {needed} of {budget} for layers 此文件的圖層:圖層可用 {budget} 中的 {needed} This document's layers need {needed} but may hold {budget}: whole-document redraws use the CPU 此文件的圖層需要 {needed},但只能佔用 {budget}:整份文件的重繪使用 CPU GPU Memory (MB, 0 = Automatic) GPU 記憶體(MB,0 = 自動) + +# Paragraph direction (TYPE-218-2) + Automatic paragraph direction 自動段落方向 + Left-to-right paragraph direction 由左至右的段落方向 + Right-to-left paragraph direction 由右至左的段落方向 diff --git a/crates/ui-egui/src/state.rs b/crates/ui-egui/src/state.rs index 5cbda434e7..7ae845c53c 100644 --- a/crates/ui-egui/src/state.rs +++ b/crates/ui-egui/src/state.rs @@ -539,6 +539,9 @@ pub struct ToolOptions { pub type_size: f32, pub type_aa: String, pub type_align: String, + /// Type tool: paragraph direction of new type, "auto" (from the first strong character), + /// "ltr" or "rtl" (TYPE-218-2). + pub type_direction: String, /// Clone Stamp / Healing Brush. pub clone_aligned: bool, /// Pattern Stamp: lock the tile origin in document space across strokes. @@ -784,6 +787,7 @@ impl Default for ToolOptions { type_size: 48.0, type_aa: "sharp".into(), type_align: "left".into(), + type_direction: "auto".into(), clone_aligned: true, pattern_stamp_aligned: true, pattern_stamp_impressionist: false, diff --git a/crates/ui-egui/src/tool_reset.rs b/crates/ui-egui/src/tool_reset.rs index 27e9622ab3..3e7a1d9ecf 100644 --- a/crates/ui-egui/src/tool_reset.rs +++ b/crates/ui-egui/src/tool_reset.rs @@ -150,7 +150,9 @@ pub fn reset_options(o: &mut ToolOptions, tool: Tool) { Tool::MagicEraser => reset!(tolerance, anti_alias, contiguous, sample_all_layers, magic_eraser_opacity), Tool::Gradient => reset!(gradient_style, gradient_reverse, gradient_dither, gradient_classic, gradient_blend_mode, fill_opacity), Tool::PaintBucket => reset!(bucket_fill_pattern, fill_opacity, tolerance, anti_alias, contiguous, sample_all_layers), - Tool::Type | Tool::VerticalType | Tool::HorizontalTypeMask | Tool::VerticalTypeMask => reset!(type_font, type_style, type_size, type_aa, type_align), + Tool::Type | Tool::VerticalType | Tool::HorizontalTypeMask | Tool::VerticalTypeMask => { + reset!(type_font, type_style, type_size, type_aa, type_align, type_direction) + } Tool::Zoom => reset!(zoom_scrubby), Tool::SpotHealing => reset!(spot_type, sample_all_layers), Tool::Healing => reset!(clone_aligned, clone_sample), diff --git a/crates/ui-egui/src/type_tool.rs b/crates/ui-egui/src/type_tool.rs index d73bb2e2c4..ce9b337b03 100644 --- a/crates/ui-egui/src/type_tool.rs +++ b/crates/ui-egui/src/type_tool.rs @@ -317,6 +317,7 @@ pub fn pointer_up(app: &mut PhotocraftApp, start: [f64; 2], end: [f64; 2]) { "fontStyle": o.type_style, "size": o.type_size, "align": o.type_align, + "direction": o.type_direction, "color": hex(app.session.tools.foreground), }); if w >= min && h >= min { @@ -1780,6 +1781,15 @@ pub fn options_bar(app: &mut PhotocraftApp, ui: &mut egui::Ui) { } ui.spacing_mut().item_spacing.x = 8.0; crate::widgets::vline(ui, 22.0); + ui.spacing_mut().item_spacing.x = 2.0; + for (key, tip, d) in direction_items() { + if direction_button(ui, d, o.type_direction == key, tip).clicked() { + app.ui.tool_options.type_direction = key.into(); + apply(app, ui.ctx(), json!({"direction": key})); + } + } + ui.spacing_mut().item_spacing.x = 8.0; + crate::widgets::vline(ui, 22.0); // Text colour swatch with a picker popup. let c = shown .as_ref() @@ -1983,6 +1993,47 @@ fn align_glyph(p: &egui::Painter, r: egui::Rect, a: photocraft_doc::text::TextAl } } +/// Paragraph direction choices for the Paragraph panel and the options bar (TYPE-218-2): the +/// `direction` value `type.setStyle` takes, the tooltip, and the direction it sets. +fn direction_items() -> [(&'static str, &'static str, photocraft_doc::text::TextDirection); 3] { + use photocraft_doc::text::TextDirection as D; + [ + ("auto", tl!("Automatic paragraph direction"), D::Auto), + ("ltr", tl!("Left-to-right paragraph direction"), D::Ltr), + ("rtl", tl!("Right-to-left paragraph direction"), D::Rtl), + ] +} + +/// A pilcrow over an arrow pointing the way the paragraph runs; Auto points both ways. +fn direction_glyph(p: &egui::Painter, r: egui::Rect, d: photocraft_doc::text::TextDirection, c: Color32) { + use photocraft_doc::text::TextDirection as D; + let g = p.layout_no_wrap("¶".to_string(), egui::FontId::proportional(11.0), c); + p.galley(egui::pos2(r.center().x - g.size().x / 2.0, r.top() - 2.0), g, c); + let (y, s) = (r.bottom() - 1.5, Stroke::new(1.3, c)); + p.line_segment([egui::pos2(r.left(), y), egui::pos2(r.right(), y)], s); + let head = |x: f32, towards: f32| { + for dy in [-3.0, 3.0] { + p.line_segment([egui::pos2(x, y), egui::pos2(x - towards * 3.0, y + dy)], s); + } + }; + if d != D::Rtl { + head(r.right(), 1.0); + } + if d != D::Ltr { + head(r.left(), -1.0); + } +} + +/// The options bar's 24 px square button for one paragraph direction, styled like its icon buttons. +fn direction_button(ui: &mut egui::Ui, d: photocraft_doc::text::TextDirection, selected: bool, tip: &str) -> egui::Response { + let (r, resp) = ui.allocate_exact_size(egui::vec2(24.0, 24.0), egui::Sense::click()); + let tint = crate::icons::button_chrome(ui, r, selected, resp.hovered()); + direction_glyph(ui.painter(), egui::Rect::from_center_size(r.center(), egui::vec2(14.0, 16.0)), d, tint); + let tip = tip.to_string(); + resp.widget_info(|| egui::WidgetInfo::selected(egui::WidgetType::Button, true, selected, &tip)); + resp.on_hover_text(tip) +} + /// A toggle button (faux styles, paragraph alignment) of width `w`, painted by `paint`. fn toggle_cell(ui: &mut egui::Ui, w: f32, on: bool, tip: &str, paint: impl FnOnce(&egui::Ui, egui::Rect, Color32)) -> bool { let t = crate::theme::Tokens::get(ui.ctx()); @@ -2305,6 +2356,19 @@ fn type_sections(app: &mut PhotocraftApp, ui: &mut egui::Ui, character: bool, pa } }); ui.add_space(ROW_GAP); + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 2.0; + for (key, tip, d) in direction_items() { + let clicked = toggle_cell(ui, cw, para.direction == d, tip, |ui, r, col| { + let g = egui::Rect::from_center_size(r.center(), egui::vec2(14.0, 16.0)); + direction_glyph(ui.painter(), g, d, col); + }); + if clicked { + apply(app, ui.ctx(), json!({"direction": key})); + } + } + }); + ui.add_space(ROW_GAP); row(ui, &mut |ui| { if let Some(v) = num_field(ui, "→|", "Indent left margin", para.start_indent_pt, -1296.0..=1296.0, "pt", w) { apply(app, ui.ctx(), json!({"startIndent": v})); diff --git a/crates/ui-egui/src/type_tool_tests.rs b/crates/ui-egui/src/type_tool_tests.rs index 7a23c9ccc3..869a1acc80 100644 --- a/crates/ui-egui/src/type_tool_tests.rs +++ b/crates/ui-egui/src/type_tool_tests.rs @@ -1438,6 +1438,62 @@ mod multi_layer_formatting { }); } + /// The Paragraph section's direction buttons set the selected layers' paragraph direction in + /// one undo step, show the current one, and leave unselected layers and the tool's default for + /// new type alone (TYPE-218-2). + #[test] + fn paragraph_direction_buttons_set_and_show_the_direction() { + use egui::accesskit::Toggled; + use egui_kittest::kittest::NodeT; + use photocraft_doc::text::TextDirection; + let (app, ids) = fixture(); + let dir = |app: &PhotocraftApp, id: LayerId| text(app, id).paragraph_runs()[0].style.direction; + let shown = |h: &Harness<'static, PhotocraftApp>, label: &str| h.get_by_label(label).accesskit_node().toggled(); + let mut h = controls(app, true); + assert_eq!(shown(&h, "Automatic paragraph direction"), Some(Toggled::True), "new type follows its text"); + assert_eq!(shown(&h, "Right-to-left paragraph direction"), Some(Toggled::False)); + let steps = h.state().session.active().unwrap().history.entries().len(); + h.get_by_label("Right-to-left paragraph direction").click(); + h.run(); + for id in &ids[..2] { + assert_eq!(dir(h.state(), *id), TextDirection::Rtl, "selected layers get the direction"); + } + assert_eq!(dir(h.state(), ids[2]), TextDirection::Auto, "an unselected layer is untouched"); + assert_eq!(h.state().session.active().unwrap().history.entries().len(), steps + 1, "one undo step"); + assert_eq!(h.state().ui.tool_options.type_direction, "auto", "the panel edits layers, not new type"); + h.run(); + assert_eq!(shown(&h, "Right-to-left paragraph direction"), Some(Toggled::True), "the button shows it"); + assert_eq!(shown(&h, "Automatic paragraph direction"), Some(Toggled::False)); + h.get_by_label("Left-to-right paragraph direction").click(); + h.run(); + assert_eq!(dir(h.state(), ids[0]), TextDirection::Ltr); + assert!(h.state_mut().session.undo()); + assert!(h.state_mut().session.undo()); + for id in &ids[..2] { + assert_eq!(dir(h.state(), *id), TextDirection::Auto, "undo restores it"); + } + } + + /// The options bar's direction buttons set the selected layers' direction and the one new + /// type starts with, as its alignment buttons do (TYPE-218-2). + #[test] + fn options_bar_direction_sets_selected_layers_and_new_type() { + use photocraft_doc::text::TextDirection; + let (app, ids) = fixture(); + let mut h = controls(app, false); + h.get_by_label("Right-to-left paragraph direction").click(); + h.run(); + assert_eq!(h.state().ui.tool_options.type_direction, "rtl"); + for id in &ids[..2] { + assert_eq!(text(h.state(), *id).paragraph_runs()[0].style.direction, TextDirection::Rtl); + } + assert_eq!(text(h.state(), ids[2]).paragraph_runs()[0].style.direction, TextDirection::Auto); + super::super::pointer_up(h.state_mut(), [40.0, 60.0], [40.0, 60.0]); + let id = h.state().session.active().unwrap().active_layer.unwrap(); + assert!(!ids.contains(&id), "a new type layer"); + assert_eq!(text(h.state(), id).paragraph_runs()[0].style.direction, TextDirection::Rtl, "new type starts right to left"); + } + #[test] fn options_font_family_preserves_peer_faces_and_batches_layer_flags() { let (app, ids) = fixture(); diff --git a/docs/gaps.md b/docs/gaps.md index faf0884c89..46d1074157 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -1,6 +1,6 @@ # Where PhotoCraft falls short of Photoshop -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (G1: smart filter masks show on PSD open, Photoshop-oracle floor 188/258; G10: DPX read and write, #1049, Cineon read; G1: CMYK Hue/Saturation and Color Balance keep the black ink; G1: CMYK Posterize on the stored values, psd-tools oracle floor 242/309; G1: CMYK adjustment layers composite on the document's inks, Photoshop-oracle floor 165/258; G1: CMYK Brightness/Contrast on the inks, CMYK Threshold's luma; G1: modern Brightness/Contrast's brightness curve matches Photoshop, Photoshop-oracle floor 151/258; G1: Lab documents composite on their stored L, a, b, Photoshop-oracle floor 152/258; G1: Levels on whole levels at every step, Photoshop-oracle floor 146/258, psd-tools 238/309; G1: 32-bit Brightness/Contrast legacy, Channel Mixer, Photo Filter and Black & White tint unclipped as in Photoshop, Photoshop-oracle floor 149/258; G1: mask feathers blur with their exact variance, psd-tools oracle floor 240/309; G1: Threshold splits on Photoshop's luminosity, psd-tools oracle floor 241/309; G1: pass-through clipping base with an adjustment renders isolated, psd-tools oracle floor 240/309; G1: 16-bit Posterize as Photoshop bins it, psd-tools oracle floor 241/309; G1: Gradient Map driven by the W3C luminosity, Photoshop-oracle floor 147/258; G7: Field Blur's on-canvas pins, #3373; G4: Hue Jitter amount track, #3825 partial; G4: minimum field interaction, #3825 partial; G1: CMYK Invert, Posterize and Threshold on the inks, 16-bit Lab Invert; Photoshop-oracle floor 145/258; G11: `.atn` import maps 112 events: Select and Mask, Filter Gallery Spatter, Ripple, Crystallize, Pattern Fill layers, mask targeting, Step Backward, nudges in points, Bitmap mode; G5: Free Transform H/V skew fields; G9: toolbar tool shortcut editing; refreshed against main: G2, G5, G7, G9, G10, G13, G14, G17 items that landed; G10: DPX read and write, #1049; G4: checkbox-only Brush Settings rows; G11: Load/Save Actions `.atn` landed, AUTO-219-2 partial; G5: hold-a-key spring-loaded tools landed, #2672; G1: #2465 fixed; stale missing-tool rows corrected: Color Replacement, Red Eye and Add/Delete Anchor Point are live tools, so only Art History Brush is missing; no estimate changed) · **Target:** Adobe Photoshop 2026 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-16 · **Change:** minor (G8: paragraph direction buttons in the Paragraph panel and options bar, TYPE-218-2 partial; G1: smart filter masks show on PSD open, Photoshop-oracle floor 188/258; G10: DPX read and write, #1049, Cineon read; G1: CMYK Hue/Saturation and Color Balance keep the black ink; G1: CMYK Posterize on the stored values, psd-tools oracle floor 242/309; G1: CMYK adjustment layers composite on the document's inks, Photoshop-oracle floor 165/258; G1: CMYK Brightness/Contrast on the inks, CMYK Threshold's luma; G1: modern Brightness/Contrast's brightness curve matches Photoshop, Photoshop-oracle floor 151/258; G1: Lab documents composite on their stored L, a, b, Photoshop-oracle floor 152/258; G1: Levels on whole levels at every step, Photoshop-oracle floor 146/258, psd-tools 238/309; G1: 32-bit Brightness/Contrast legacy, Channel Mixer, Photo Filter and Black & White tint unclipped as in Photoshop, Photoshop-oracle floor 149/258; G1: mask feathers blur with their exact variance, psd-tools oracle floor 240/309; G1: Threshold splits on Photoshop's luminosity, psd-tools oracle floor 241/309; G1: pass-through clipping base with an adjustment renders isolated, psd-tools oracle floor 240/309; G1: 16-bit Posterize as Photoshop bins it, psd-tools oracle floor 241/309; G1: Gradient Map driven by the W3C luminosity, Photoshop-oracle floor 147/258; G7: Field Blur's on-canvas pins, #3373; G4: Hue Jitter amount track, #3825 partial; G4: minimum field interaction, #3825 partial; G1: CMYK Invert, Posterize and Threshold on the inks, 16-bit Lab Invert; Photoshop-oracle floor 145/258; G11: `.atn` import maps 112 events: Select and Mask, Filter Gallery Spatter, Ripple, Crystallize, Pattern Fill layers, mask targeting, Step Backward, nudges in points, Bitmap mode; G5: Free Transform H/V skew fields; G9: toolbar tool shortcut editing; refreshed against main: G2, G5, G7, G9, G10, G13, G14, G17 items that landed; G10: DPX read and write, #1049; G4: checkbox-only Brush Settings rows; G11: Load/Save Actions `.atn` landed, AUTO-219-2 partial; G5: hold-a-key spring-loaded tools landed, #2672; G1: #2465 fixed; stale missing-tool rows corrected: Color Replacement, Red Eye and Add/Delete Anchor Point are live tools, so only Art History Brush is missing; no estimate changed) · **Target:** Adobe Photoshop 2026 Every known shortfall against Photoshop 2026 (27.11.0), one entry each, ranked by user impact on the way to beta. This is the work list agents pick from: take the highest-ranked entry that @@ -78,13 +78,13 @@ what an agent can't finish alone. - **Estimate:** 80–140 h. ### G8. Type depth -- **Missing:** type on a path (TYPE-218-1), paragraph direction UI (TYPE-218-2), complex-script - fallback fonts and golden tests (TYPE-218-3), OpenType Numerals and Stylistic Sets submenus +- **Missing:** type on a path (TYPE-218-1), the visual RTL caret (TYPE-218-2; the paragraph + direction buttons landed 2026-10-16), complex-script fallback fonts and golden tests (TYPE-218-3), OpenType Numerals and Stylistic Sets submenus (TYPE-218-5; the Character panel buttons landed 2026-10-11, #3752), live Warp Text preview (TYPE-218-7, #2689), "rasterize and continue" for distort/perspective on type (TYPE-218-9), text free transform (#2630). Done since the re-measure: manual and optical kerning with PSD round trip (TYPE-218-6, #206), variable-font axes (TYPE-218-4, #2985). -- **Evidence:** scorecard Type: 3 done, 2 partial, 4 missing. 2026-10-11: 2/3/4/5 clicks select +- **Evidence:** scorecard Type: 4 done, 4 partial, 1 missing. 2026-10-11: 2/3/4/5 clicks select word, line, paragraph and all text, with tests (TYPE-218-8, #3493). The Type tool froze the app for 60 s on one macOS machine (#2518). - **Estimate:** 120–200 h. @@ -270,6 +270,7 @@ what an agent can't finish alone. | Date | Change | Summary | |---|---|---| +| 2026-10-16 | minor | G8: Auto / Left-to-right / Right-to-left paragraph direction buttons in the Paragraph panel, Properties and the Type options bar; TYPE-218-2 partial (the visual RTL caret is still missing) | | 2026-10-14 | minor | G1: a PSD smart object's filter mask shows when the file opens (Photoshop stores the filtered pixels unmasked); export stores them that way too; Photoshop-oracle floor 185 → 188 of 258 | | 2026-10-13 | minor | G14: D90 image area measured (4288 × 2848 at (10, 10)); gap remains open | | 2026-10-14 | minor | Corpus floors raised to the measured results of the CMYK, Lab, 32-bit, Levels, Threshold, Posterize, feather and pass-through fixes landed together: Photoshop-authored 165 → 185 of 258, io 146 → 148 of 170, psd-tools 242 → 249 of 309 | diff --git a/docs/scorecard.md b/docs/scorecard.md index 377ce8583d..26553161df 100644 --- a/docs/scorecard.md +++ b/docs/scorecard.md @@ -10,7 +10,7 @@ Generated by `cargo xtask scorecard` from `perf/budgets.toml`, `perf/baseline.js | [File compatibility](#file-compatibility) | 7 | 14 | 6 | io corpus: oracle ≥ 148/170, round trip ≥ 169/170; psd-tools corpus: oracle ≥ 249/309, round trip ≥ 308/309; photoshop oracles: oracle ≥ 188/256, round trip ≥ 258/256 | | [Tools](#tools) | 33 | 11 | 0 | | | [Workspace and UI](#workspace-and-ui) | 13 | 6 | 4 | settings that do nothing: 26 of 168 (target 0) | -| [Type](#type) | 4 | 3 | 2 | | +| [Type](#type) | 4 | 4 | 1 | | | [Automation](#automation) | 5 | 2 | 4 | 631 of 631 menu items live (100.0%) | | [Reliability](#reliability) | 5 | 2 | 0 | never-crash lints on 29 of 29 crates; panic_hunt present | | [Distribution](#distribution) | 5 | 4 | 2 | | @@ -214,7 +214,7 @@ Unread: `general.color_picker`, `general.animated_zoom`, `general.zoom_resizes_w | Id | Target | Status | Issue | Note | |---|---|---|---|---| | TYPE-218-1 | Type on a path: path shape, glyphs along the curve, caret follows, PSD round trip | partial | [#218](https://github.com/storytold/photocraft/issues/218) | TextShape::Path (doc text.rs) with glyphs placed rigidly along the curve by arc length (text warp.rs PathFrame), type.attachToPath and type.detachFromPath (#4365), .pcraft round trip. Missing: clicking a path with the Type tool, the caret and selection following the curve, and PSD path text (it saves as point text) | -| TYPE-218-2 | Paragraph direction (Auto/LTR/RTL) in the panel and options bar; visual RTL caret | missing | [#218](https://github.com/storytold/photocraft/issues/218) | Engine has TextDirection (text.rs:155); no UI | +| TYPE-218-2 | Paragraph direction (Auto/LTR/RTL) in the panel and options bar; visual RTL caret | partial | [#218](https://github.com/storytold/photocraft/issues/218) | Paragraph section (Window > Paragraph and Properties) and the options bar have Auto / Left-to-right / Right-to-left buttons setting the paragraph direction through type.setStyle; the options bar's choice is also the direction new type starts with (type_tool.rs direction_items; paragraph_direction_buttons_set_and_show_the_direction, options_bar_direction_sets_selected_layers_and_new_type). Still missing: visual RTL caret (the arrow keys move in logical order) | | TYPE-218-3 | Thai/Khmer/Indic fallback fonts; golden shaping tests for complex scripts and RTL | partial | [#218](https://github.com/storytold/photocraft/issues/218) | Fallback fonts are covered for Thai, Khmer, Lao, Myanmar (THAI_FAMILIES, KHMER_FAMILIES, LAO_FAMILIES, MYANMAR_FAMILIES in fonts.rs; #1909, #3259), the nine Indic scripts (INDIC_FAMILIES: Nirmala UI, Kohinoor and Sangam MN, Noto Sans) and Arabic and Hebrew. Still missing: per-script golden shaping tests against reference output (only smoke tests that an installed font shapes without .notdef, plus a bidi test) | | TYPE-218-4 | Variable font axis sliders from the font's axes; opsz follows the point size | done | [#218](https://github.com/storytold/photocraft/issues/218) | One slider per axis of the style's font in the Character section (Properties and Window > Character), setting type.setStyle variations; opsz follows the type size unless set (layout.rs variation_list); a style or weight resets the wght axis (#2985). PSD does not round-trip axis values yet | | TYPE-218-5 | OpenType UI: Character panel buttons, Numerals and Stylistic Sets submenus | partial | [#218](https://github.com/storytold/photocraft/issues/218) | Type > OpenType toggles (type_extra_cmds.rs:512). Character panel (and the Properties Character section) has Photoshop's eight OpenType buttons: Standard Ligatures, Contextual Alternates, Discretionary Ligatures, Swash, Stylistic Alternates, Titling Alternates, Ordinals, Fractions, pressed while the feature is on at the selection and dispatching the type.openType.* commands (type_tool.rs opentype_row; character_panel_opentype_buttons_toggle_and_show_the_feature, #3752). Still missing: the Numerals and Stylistic Sets submenus, and greying out features the font lacks | diff --git a/scorecard/type.toml b/scorecard/type.toml index ee8a876a41..9b1957cbf8 100644 --- a/scorecard/type.toml +++ b/scorecard/type.toml @@ -14,9 +14,9 @@ note = "TextShape::Path (doc text.rs) with glyphs placed rigidly along the curve [[item]] id = "TYPE-218-2" target = "Paragraph direction (Auto/LTR/RTL) in the panel and options bar; visual RTL caret" -status = "missing" +status = "partial" issue = 218 -note = "Engine has TextDirection (text.rs:155); no UI" +note = "Paragraph section (Window > Paragraph and Properties) and the options bar have Auto / Left-to-right / Right-to-left buttons setting the paragraph direction through type.setStyle; the options bar's choice is also the direction new type starts with (type_tool.rs direction_items; paragraph_direction_buttons_set_and_show_the_direction, options_bar_direction_sets_selected_layers_and_new_type). Still missing: visual RTL caret (the arrow keys move in logical order)" [[item]] id = "TYPE-218-3" From 50d42006b56dc87493f41e434d40835a0e284b1b Mon Sep 17 00:00:00 2001 From: Shubham Bhamare <87901258+theshubh77@users.noreply.github.com> Date: Sat, 17 Oct 2026 15:49:12 +0530 Subject: [PATCH 18/44] fix(engine): populate export_presets in Persisted initialization --- crates/engine/src/presets/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/engine/src/presets/mod.rs b/crates/engine/src/presets/mod.rs index 03696291dd..81382b418a 100644 --- a/crates/engine/src/presets/mod.rs +++ b/crates/engine/src/presets/mod.rs @@ -186,6 +186,7 @@ impl Session { tool_presets: take(&mut m, "tool_presets"), documents: take(&mut m, "documents"), recent_documents: take(&mut m, "recent_documents"), + export_presets: take(&mut m, "export_presets"), custom_shapes: take(&mut m, "custom_shapes"), layer_defaults: take(&mut m, "layer_defaults"), swatches: take(&mut m, "swatches"), From 1819e77c300326d8e798404dc0c9bc8448bef9db Mon Sep 17 00:00:00 2001 From: Shubham Bhamare <87901258+theshubh77@users.noreply.github.com> Date: Sat, 17 Oct 2026 15:49:19 +0530 Subject: [PATCH 19/44] feat(i18n): add Hindi (hi) language catalog and locale resolution --- crates/ui-egui/src/i18n/hi.tsv | 491 ++++++++++++++++++++++++++++++ crates/ui-egui/src/i18n/mod.rs | 19 +- crates/ui-egui/src/i18n/system.rs | 1 + docs/localization-parity.md | 2 +- docs/localization.md | 1 + 5 files changed, 512 insertions(+), 2 deletions(-) create mode 100644 crates/ui-egui/src/i18n/hi.tsv diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv new file mode 100644 index 0000000000..0049ba94a6 --- /dev/null +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -0,0 +1,491 @@ +# Hindi (hi) catalog. Columns, separated by a TAB: context source translation +# +# context empty a plain UI string; `source` is the English text in the code +# @id a command id (e.g. file.saveAs): wins over the English text for +# that menu item, so one id can read differently from its twin +# @plural `source` is "one|other" (English forms); the translation lists the +# language's forms separated by "|" (see `plural` in i18n/mod.rs) +# anything else a disambiguating context passed to `tr_ctx` +# escapes \n \t \\ inside a column. `{name}` placeholders must appear in both columns. +# lines starting with # and blank lines are ignored. +# +# Clean-room: every entry was written from the plain meaning of the English label using ordinary +# Hindi computing and image-editing vocabulary. No proprietary localisation resource, string +# table or product UI was consulted or copied; add entries the same way. Product and technology +# names (PhotoCraft, ArtCraft, OpenType, RGB, CMYK, Lab, PSD, PNG, JPEG, WebP, OpenEXR, AVIF …) +# stay as they are; the trailing "…" of dialog commands is kept. Plural rule: one / other +# (`plural_one_other` in i18n/mod.rs). + +# Top-level Menus + File फ़ाइल + Edit एडिट + Image इमेज + Layer लेयर + Type टाइप + Select चयन + Filter फ़िल्टर + 3D 3डी + View व्यू + Window विंडो + Help सहायता + +# File Menu + New… नया… + Open… खोलें… + Open Recent हाल ही में खोले गए + Close बंद करें + Close All सभी बंद करें + Close Others अन्य सभी बंद करें + Save सहेजें + Save As… इस रूप में सहेजें… + Save a Copy… एक प्रति सहेजें… + Revert वापस लौटें + Export एक्सपोर्ट करें + Export As… इस रूप में एक्सपोर्ट करें… + Quick Export as PNG PNG के रूप में त्वरित एक्सपोर्ट + Import इम्पोर्ट करें + File Info… फ़ाइल जानकारी… + Print… प्रिंट करें… + Print One Copy एक प्रति प्रिंट करें + Exit बाहर निकलें + +# Edit Menu + Undo पूर्ववत करें + Redo फिर से करें + Cut काटें + Copy कॉपी करें + Copy Merged मर्ज की गई कॉपी + Paste पेस्ट + Paste Special विशेष रूप में पेस्ट करें + Paste in Place स्थान पर पेस्ट करें + Paste Into में पेस्ट करें + Paste Outside बाहर पेस्ट करें + Clear साफ़ करें + Fill… भरें… + Stroke… स्ट्रोक… + Free Transform मुक्त रूपांतरण + Transform रूपांतरण करें + Scale आकार बदलें + Rotate घुमाएँ + Skew तिरछा करें + Distort विरूपित करें + Perspective परिप्रेक्ष्य + Warp मोड़ें + Flip Horizontal क्षैतिज पलटें + Flip Vertical लंबवत पलटें + Preferences प्राथमिकताएं + General… सामान्य… + Interface… इंटरफ़ेस… + Workspace… कार्यस्थान… + Tools… टूल्स… + Performance… परफॉरमेंस… + +# Image Menu + Mode मोड + RGB Color RGB रंग + CMYK Color CMYK रंग + Grayscale ग्रेस्केल + Lab Color Lab रंग + 8 Bits/Channel 8 बिट्स/चैनल + 16 Bits/Channel 16 बिट्स/चैनल + 32 Bits/Channel 32 बिट्स/चैनल + Adjustments समायोजन + Brightness/Contrast… चमक/कंट्रास्ट… + Levels… स्तर… + Curves… कर्व्स… + Exposure… एक्सपोज़र… + Vibrance… वाइब्रेंस… + Hue/Saturation… ह्यू/सैचुरेशन… + Color Balance… कलर बैलेंस… + Black & White… ब्लैक एंड व्हाइट… + Photo Filter… फ़ोटो फ़िल्टर… + Invert इन्वर्ट + Posterize… पोस्टराइज़ करें… + Threshold… थ्रेशहोल्ड… + Image Size… इमेज साइज़… + Canvas Size… कैनवास साइज़… + Image Rotation इमेज रोटेशन + 180° 180° + 90° Clockwise 90° दक्षिणावर्त + 90° Counter Clockwise 90° वामावर्त + Arbitrary… इच्छानुसार… + Crop क्रॉप करें + Trim… ट्रिम करें… + Reveal All सभी दिखाएँ + Duplicate… डुप्लीकेट… + +# Layer Menu + New नया + Layer… लेयर… + Layer from Background पृष्ठभूमि से लेयर + Group… समूह… + Group from Layers… लेयर्स से समूह… + Artboard… आर्टबोर्ड… + Duplicate Layer… लेयर को डुप्लीकेट करें… + Delete हटाएँ + Delete Layer लेयर हटाएँ + Delete Hidden Layers छिपी हुई लेयर्स हटाएँ + Rename Layer… लेयर का नाम बदलें… + Layer Style लेयर स्टाइल + Blending Options… ब्लेंडिंग विकल्प… + Smart Objects स्मार्ट ऑब्जेक्ट्स + Convert to Smart Object स्मार्ट ऑब्जेक्ट में बदलें + Layer Mask लेयर मास्क + Hide All सभी छिपाएँ + Reveal Selection चयन दिखाएँ + Hide Selection चयन छिपाएँ + From Transparency पारदर्शिता से + Apply लागू करें + Enable सक्षम करें + Disable अक्षम करें + Merge Down नीचे मर्ज करें + Merge Visible दृश्यमान मर्ज करें + Flatten Image इमेज फ़्लैट करें + +# Select Menu + All सभी + Deselect अचयनित करें + Reselect पुनः चयन करें + Inverse उलटा करें + All Layers सभी लेयर्स + Deselect Layers लेयर्स अचयनित करें + Find Layers लेयर्स खोजें + Color Range… रंग सीमा… + Select and Mask… चयन और मास्क… + Modify संशोधित करें + Border… सीमा… + Smooth… चिकना करें… + Expand… विस्तार करें… + Contract… संकुचित करें… + Feather… फ़ेदर… + Grow बढ़ाएँ + Similar समान + Transform Selection चयन रूपांतरित करें + Load Selection… चयन लोड करें… + Save Selection… चयन सहेजें… + +# View Menu + Proof Setup प्रूफ़ सेटअप + Proof Colors प्रूफ़ रंग + Zoom In ज़ूम इन + Zoom Out ज़ूम आउट + Fit on Screen स्क्रीन पर फ़िट करें + Fit Layer on Screen स्क्रीन पर लेयर फ़िट करें + Actual Pixels वास्तविक पिक्सेल + 100% 100% + 200% 200% + Rulers रूलर्स + Snap स्नैप + Snap To पर स्नैप करें + Guides गाइड्स + Lock Guides गाइड्स लॉक करें + Clear Guides गाइड्स हटाएं + New Guide… नई गाइड… + Lock Slices स्लाइस लॉक करें + Clear Slices स्लाइस हटाएं + +# Window Menu & Panels + Arrange व्यवस्थित करें + Tile Horizontally क्षैतिज टाइल + Tile Vertically लंबवत टाइल + Consolidate All to Tabs सभी को टैब में समेकित करें + Workspace कार्यस्थान + Reset Essentials एसेंशियल्स रीसेट करें + New Workspace… नया कार्यस्थान… + Delete Workspace… कार्यस्थान हटाएँ… + Lock Workspace कार्यस्थान लॉक करें + Color रंग + Swatches नमूने + Gradients ग्रेडिएंट्स + Patterns पैटर्न्स + Properties गुण + Layers लेयर्स + Channels चैनल्स + Paths पथ + History हिस्ट्री + Actions एक्शन्स + Brushes ब्रश + Brush Settings ब्रश सेटिंग्स + Navigator नेविगेटर + Info जानकारी + Timeline टाइमलाइन + Tools टूल्स + Options विकल्प + Status Bar स्टेटस बार + +# Tools (Toolbox) + Move Tool मूव टूल + Artboard Tool आर्टबोर्ड टूल + Rectangular Marquee Tool रेक्टेंगुलर मार्की टूल + Elliptical Marquee Tool एलिप्टिकल मार्की टूल + Single Row Marquee Tool सिंगल रो मार्की टूल + Single Column Marquee Tool सिंगल कॉलम मार्की टूल + Lasso Tool लैस्सो टूल + Polygonal Lasso Tool पॉलीगोनल लैस्सो टूल + Magnetic Lasso Tool मैग्नेटिक लैस्सो टूल + Object Selection Tool ऑब्जेक्ट चयन टूल + Quick Selection Tool क्विक चयन टूल + Magic Wand Tool मैजिक वैंड टूल + Crop Tool क्रॉप टूल + Perspective Crop Tool परस्पेक्टिव क्रॉप टूल + Slice Tool स्लाइस टूल + Slice Select Tool स्लाइस चयन टूल + Frame Tool फ़्रेम टूल + Eyedropper Tool आईड्रॉपर टूल + Color Sampler Tool कलर सैंपलर टूल + Ruler Tool रूलर टूल + Note Tool नोट टूल + Count Tool काउंट टूल + Spot Healing Brush Tool स्पॉट हीलिंग ब्रश टूल + Healing Brush Tool हीलिंग ब्रश टूल + Patch Tool पैच टूल + Content-Aware Move Tool कंटेंट-अवेयर मूव टूल + Red Eye Tool रेड आई टूल + Brush Tool ब्रश टूल + Pencil Tool पेंसिल टूल + Color Replacement Tool कलर रिप्लेसमेंट टूल + Mixer Brush Tool मिक्सर ब्रश टूल + Clone Stamp Tool क्लोन स्टैम्प टूल + Pattern Stamp Tool पैटर्न स्टैम्प टूल + History Brush Tool हिस्ट्री ब्रश टूल + Art History Brush Tool आर्ट हिस्ट्री ब्रश टूल + Eraser Tool इरेज़र टूल + Background Eraser Tool बैकग्राउंड इरेज़र टूल + Magic Eraser Tool मैजिक इरेज़र टूल + Gradient Tool ग्रेडिएंट टूल + Paint Bucket Tool पेंट बकेट टूल + Blur Tool ब्लर टूल + Sharpen Tool शार्पन टूल + Smudge Tool स्मज टूल + Dodge Tool डॉज टूल + Burn Tool बर्न टूल + Sponge Tool स्पंज टूल + Pen Tool पेन टूल + Freeform Pen Tool फ्रीफॉर्म पेन टूल + Curvature Pen Tool कर्वचर पेन टूल + Add Anchor Point Tool एंकर पॉइंट टूल + Delete Anchor Point Tool एंकर पॉइंट टूल + Convert Point Tool कन्वर्ट पॉइंट टूल + Horizontal Type Tool हॉरिजोंटल टाइप टूल + Vertical Type Tool वर्टिकल टाइप टूल + Horizontal Type Mask Tool हॉरिजोंटल टाइप मास्क टूल + Vertical Type Mask Tool वर्टिकल टाइप मास्क टूल + Path Selection Tool पाथ चयन टूल + Direct Selection Tool डायरेक्ट चयन टूल + Rectangle Tool रेक्टेंगल टूल + Rounded Rectangle Tool राउंडेड रेक्टेंगल टूल + Ellipse Tool एलिप्स टूल + Triangle Tool ट्रायंगल टूल + Polygon Tool पॉलीगोन टूल + Line Tool लाइन टूल + Custom Shape Tool कस्टम शेप टूल + Hand Tool हैंड टूल + Rotate View Tool रोटेट व्यू टूल + Zoom Tool ज़ूम टूल + +# Common Dialogs and Buttons + OK ठीक है + Cancel रद्द करें + Reset रीसेट करें + Done संपन्न + Don't Save सहेजें नहीं + Yes हाँ + No नहीं + Width: चौड़ाई: + Height: ऊंचाई: + Resolution: रिज़ॉल्यूशन: + Pixels पिक्सेल + Inches इंच + Centimeters सेंटीमीटर + Millimeters मिलीमीटर + Points पॉइंट्स + Percent प्रतिशत + Opacity: ओपेसिटी: + Flow: फ्लो: + Hardness: हार्डनेस: + Size: साइज़: + Angle: एंगल: + Roundness: राउंडनेस: + Spacing: स्पेसिंग: + Color: कलर: + Name: नेम: + Presets: प्रीसेट्स: + Language भाषा + Auto स्वचालित + +# Plurals +@plural {n} layer|{n} layers {n} लेयर|{n} लेयरें +@plural {n} item|{n} items {n} आइटम|{n} आइटम +@plural {n} document|{n} documents {n} दस्तावेज़|{n} दस्तावेज़ +@plural {n} pixel|{n} pixels {n} पिक्सेल|{n} पिक्सेल + +# Tool and Scope strings + Scope दायरा + Selected चयनित + Mixer Brush मिक्सर ब्रश + Wet गीला + Load लोड + Mix मिश्रण + Flow प्रवाह + Sample All Layers सभी लेयर्स का नमूना लें + Pattern Stamp पैटर्न स्टैम्प + Impressionist प्रभाववादी + Aligned संरेखित + Fit in View दृश्य में फ़िट करें + Camera Raw Camera Raw + +# Camera Raw +cameraRaw Amount मात्रा + Amount मात्रा +cameraRaw Aqua एक्वा + Aqua एक्वा +cameraRaw Balance संतुलन + Balance संतुलन +cameraRaw Before (Y) पहले (Y) + Before (Y) पहले (Y) +cameraRaw Blacks काले बिंदु + Blacks काले बिंदु +cameraRaw Blending मिश्रण + Blending मिश्रण +cameraRaw Blue नीला + Blue नीला +cameraRaw Camera Raw ({name}) Camera Raw ({name}) +cameraRaw Camera Raw Filter ({layer}) Camera Raw फ़िल्टर ({layer}) + Camera Raw Filter ({layer}) Camera Raw फ़िल्टर ({layer}) +cameraRaw Camera Raw isn't open Camera Raw खुला नहीं है + Camera Raw isn't open Camera Raw खुला नहीं है +cameraRaw Cancel रद्द करें +cameraRaw Cancelled opening {name} {name} खोलना रद्द किया गया + Cancelled opening {name} {name} खोलना रद्द किया गया +cameraRaw Cancelled {label} {label} रद्द किया गया + Cancelled {label} {label} रद्द किया गया +cameraRaw Clarity स्पष्टता + Clarity स्पष्टता +cameraRaw Color रंग +cameraRaw Color Detail रंग विवरण + Color Detail रंग विवरण +cameraRaw Color Grading कलर ग्रेडिंग + Color Grading कलर ग्रेडिंग +cameraRaw Color Mixer कलर मिक्सर + Color Mixer कलर मिक्सर +cameraRaw Color Priority रंग प्राथमिकता + Color Priority रंग प्राथमिकता +cameraRaw Contrast कंट्रास्ट + Contrast कंट्रास्ट +cameraRaw Curve कर्व + Curve कर्व +cameraRaw Darks गहरा अंधेरा + Darks गहरा अंधेरा +cameraRaw Dehaze धुंध हटाएं + Dehaze धुंध हटाएं +cameraRaw Detail विवरण + Detail विवरण +cameraRaw Developing {name} {name} विकसित किया जा रहा है + Developing {name} {name} विकसित किया जा रहा है +cameraRaw Effects प्रभाव + Effects प्रभाव +cameraRaw Exposure एक्सपोज़र + Exposure एक्सपोज़र +cameraRaw Feather फ़ेदर + Feather फ़ेदर +cameraRaw Full-resolution preview unavailable पूर्ण-रिज़ॉल्यूशन पूर्वावलोकन अनुपलब्ध है + Full-resolution preview unavailable पूर्ण-रिज़ॉल्यूशन पूर्वावलोकन अनुपलब्ध है +cameraRaw Global समग्र + Global समग्र +cameraRaw Grain ग्रेन + Grain ग्रेन +cameraRaw Green हरा + Green हरा +cameraRaw Highlight Clipping Warning (O) हाइलाइट क्लिपिंग चेतावनी (O) + Highlight Clipping Warning (O) हाइलाइट क्लिपिंग चेतावनी (O) +cameraRaw Highlight Priority हाइलाइट प्राथमिकता + Highlight Priority हाइलाइट प्राथमिकता +cameraRaw Highlights हाइलाइट्स + Highlights हाइलाइट्स +cameraRaw Hue रंग + Hue रंग +cameraRaw Light प्रकाश + Light प्रकाश +cameraRaw Lights हल्की रोशनी + Lights हल्की रोशनी +cameraRaw Load Settings… सेटिंग्स लोड करें… + Load Settings… सेटिंग्स लोड करें… +cameraRaw Load settings from a Camera Raw .xmp file Camera Raw .xmp फ़ाइल से सेटिंग्स लोड करें + Load settings from a Camera Raw .xmp file Camera Raw .xmp फ़ाइल से सेटिंग्स लोड करें +cameraRaw Loaded Camera Raw settings from {file} {file} से Camera Raw सेटिंग्स लोड की गईं + Loaded Camera Raw settings from {file} {file} से Camera Raw सेटिंग्स लोड की गईं +cameraRaw Luminance चमक + Luminance चमक +cameraRaw Luminance Detail चमक विवरण + Luminance Detail चमक विवरण +cameraRaw Magenta मैजेंटा + Magenta मैजेंटा +cameraRaw Masking मास्किंग + Masking मास्किंग +cameraRaw Midpoint मध्य बिंदु + Midpoint मध्य बिंदु +cameraRaw Midtones मध्यम टोन + Midtones मध्यम टोन +cameraRaw Noise Reduction शोर में कमी + Noise Reduction शोर में कमी +cameraRaw OK ठीक है +cameraRaw Open खोलें + Open खोलें +cameraRaw Opened {name} {name} खोला गया + Opened {name} {name} खोला गया +cameraRaw Orange नारंगी + Orange नारंगी +cameraRaw Paint Overlay पेंट ओवरले + Paint Overlay पेंट ओवरले +cameraRaw Purple बैंगनी + Purple बैंगनी +cameraRaw Radius त्रिज्या + Radius त्रिज्या +cameraRaw Red लाल + Red लाल +cameraRaw Roughness खुरदरापन + Roughness खुरदरापन +cameraRaw Roundness गोलाई + Roundness गोलाई +cameraRaw Saturation संतृप्ति + Saturation संतृप्ति +cameraRaw Save Settings… सेटिंग्स सहेजें… + Save Settings… सेटिंग्स सहेजें… +cameraRaw Save these settings as a Camera Raw .xmp file इन सेटिंग्स को Camera Raw .xmp फ़ाइल के रूप में सहेजें + Save these settings as a Camera Raw .xmp file इन सेटिंग्स को Camera Raw .xmp फ़ाइल के रूप में सहेजें +cameraRaw Shadow Clipping Warning (U) छाया क्लिपिंग चेतावनी (U) + Shadow Clipping Warning (U) छाया क्लिपिंग चेतावनी (U) +cameraRaw Shadows शेडोज़ + Shadows शेडोज़ +cameraRaw Sharpening शार्पनिंग + Sharpening शार्पनिंग +cameraRaw Show Lab Color Readouts Lab रंग रीडआउट दिखाएँ + Show Lab Color Readouts Lab रंग रीडआउट दिखाएँ +cameraRaw Show Red at 3 o'clock 3 बजे की स्थिति पर लाल दिखाएँ + Show Red at 3 o'clock 3 बजे की स्थिति पर लाल दिखाएँ +cameraRaw Show Selected Region चयनित क्षेत्र दिखाएँ + Show Selected Region चयनित क्षेत्र दिखाएँ +cameraRaw Show Skin Tone Indicator त्वचा टोन संकेतक दिखाएँ + Show Skin Tone Indicator त्वचा टोन संकेतक दिखाएँ +cameraRaw Show Vectorscope वेक्टरस्कोप दिखाएँ + Show Vectorscope वेक्टरस्कोप दिखाएँ +cameraRaw Size आकार + Size आकार +cameraRaw Temperature तापमान + Temperature तापमान +cameraRaw Texture बनावट + Texture बनावट +cameraRaw Tint रंग झलक + Tint रंग झलक +cameraRaw Tone Histogram टोन हिस्टोग्राम + Tone Histogram टोन हिस्टोग्राम +cameraRaw Vibrance जीवंतता + Vibrance जीवंतता +cameraRaw Vignetting विगनेटिंग + Vignetting विगनेटिंग +cameraRaw White Balance: As Shot श्वेत संतुलन: शॉट के अनुसार + White Balance: As Shot श्वेत संतुलन: शॉट के अनुसार +cameraRaw Whites सफेद बिंदु + Whites सफेद बिंदु +cameraRaw Yellow पीला + Yellow पीला diff --git a/crates/ui-egui/src/i18n/mod.rs b/crates/ui-egui/src/i18n/mod.rs index b976e395fc..53e3b39bc1 100644 --- a/crates/ui-egui/src/i18n/mod.rs +++ b/crates/ui-egui/src/i18n/mod.rs @@ -187,7 +187,7 @@ fn expand(packed: &str, keys: &[&str]) -> Result { } /// The registry. English first: it is the fallback and the source language. -pub static LANGUAGES: [LangInfo; 21] = [ +pub static LANGUAGES: [LangInfo; 22] = [ LangInfo { code: "en", name: "English", source: b"", plural: plural_one_other, complete_menus: false, catalog: OnceLock::new() }, LangInfo { code: "ja", name: "日本語", source: catalog!("ja"), plural: plural_none, complete_menus: true, catalog: OnceLock::new() }, LangInfo { code: "zh-hans", name: "简体中文", source: catalog!("zh-hans"), plural: plural_none, complete_menus: true, catalog: OnceLock::new() }, @@ -220,6 +220,8 @@ pub static LANGUAGES: [LangInfo; 21] = [ LangInfo { code: "tr", name: "Türkçe", source: catalog!("tr"), plural: plural_none, complete_menus: true, catalog: OnceLock::new() }, // Vietnamese; `vi-VN` resolves here. LangInfo { code: "vi", name: "Tiếng Việt", source: catalog!("vi"), plural: plural_none, complete_menus: true, catalog: OnceLock::new() }, + // Hindi; `hi`, `hi-IN` locales resolve here. + LangInfo { code: "hi", name: "हिन्दी", source: catalog!("hi"), plural: plural_one_other, complete_menus: false, catalog: OnceLock::new() }, ]; impl LangInfo { @@ -436,6 +438,21 @@ mod tests { const CS: fn() -> Lang = || Lang::from_code("cs").expect("cs registered"); const ID: fn() -> Lang = || Lang::from_code("id").expect("id registered"); const VI: fn() -> Lang = || Lang::from_code("vi").expect("vi registered"); + const HI: fn() -> Lang = || Lang::from_code("hi").expect("hi registered"); + + #[test] + fn hindi_tags_resolve() { + for tag in ["hi", "hi-IN", "hi_IN", "hi_IN.UTF-8"] { + assert_eq!(lang_from_tag(tag), Some(HI()), "{tag}"); + } + assert_eq!(HI().name(), "हिन्दी"); + assert_eq!(Lang::from_pref("hi"), HI()); + assert_eq!(Lang::from_pref("HI"), HI()); + assert_eq!(tr(HI(), "Layer"), "लेयर"); + assert_eq!(tr(HI(), "File"), "फ़ाइल"); + assert_eq!(trn(HI(), 1, "{n} layer", "{n} layers"), "1 लेयर"); + assert_eq!(trn(HI(), 3, "{n} layer", "{n} layers"), "3 लेयरें"); + } #[test] fn indonesian_tags_resolve() { diff --git a/crates/ui-egui/src/i18n/system.rs b/crates/ui-egui/src/i18n/system.rs index 49bc3bc79b..25cc9fec4f 100644 --- a/crates/ui-egui/src/i18n/system.rs +++ b/crates/ui-egui/src/i18n/system.rs @@ -100,6 +100,7 @@ mod tests { (vec!["tr-TR", "en-US"], "tr"), (vec!["sr-Latn-RS", "en-US"], "sr"), (vec!["sv-SE", "sr_RS@latin", "ru-RU"], "sr"), + (vec!["hi-IN", "en-US"], "hi"), (vec![], "en"), ] { with_system_tags(&tags, || assert_eq!(system_lang().code(), expected)); diff --git a/docs/localization-parity.md b/docs/localization-parity.md index 0b2be85595..ab006f409f 100644 --- a/docs/localization-parity.md +++ b/docs/localization-parity.md @@ -33,7 +33,7 @@ speakers, ~55% ready. Remaining: 80–140 h, plus native-speaker review (recorde | English | `en` | 1,608 / 1,608 (100%) | source | all | Latin | – | full | 0 h | | Simplified Chinese | `zh-hans` | 1,608 / 1,608 (100%) | 2,366 | dialogs, tooltips yes; engine messages English | CJK fonts (lazy loader), IME preedit/commit, vertical type | no | partial | 8–12 h | | Spanish | `es` | 1,844 / 1,844 (100%) | 4,010 | as above; complete dynamic UI key set | Latin | no | partial | 6–10 h | -| Hindi | `hi` | 0 (0%) | – | – | Devanagari shaping in the text engine (parley/HarfRust); no Devanagari fallback font (TYPE-218-3) | no | none | 20–30 h | +| Hindi | `hi` | partial | 448 | Top menus, toolbox tools, camera raw, dialogs, plurals | Devanagari shaping in the text engine (parley/HarfRust); no Devanagari fallback font (TYPE-218-3) | yes (initial) | partial | 15–20 h | | Arabic | `ar` | 0 (0%) | – | – | Arabic shaping and bidi in the text engine, Arabic fallback font; RTL shell layout but not inside panels, and UI labels in LTR word order (DIST-220-4), paragraph direction has no UI (TYPE-218-2), layer names as tofu (#1408, #1693) | no | none | 40–60 h | | French | `fr` | 1,608 / 1,608 (100%) | 2,766 | as above | Latin | no | partial | 6–10 h | | Portuguese (Brazil) | `pt-br` | 1,608 / 1,608 (100%) | 2,788 | as above | Latin; every `pt` locale uses it | no | partial | 6–10 h | diff --git a/docs/localization.md b/docs/localization.md index 36e09f8a55..5a1355057c 100644 --- a/docs/localization.md +++ b/docs/localization.md @@ -28,6 +28,7 @@ contextual, command-ID and plural translations; missing entries fall back to Eng | Magyar | `hu` | one form | | Tiếng Việt | `vi` | one form | | Srpski | `sr` | one / few / many (teens use many) | +| हिन्दी | `hi` | one / other | Languages registered with `complete_menus` cover the current menu labels, `tl!` literals, blend mode names, and generated preference labels; tests enforce that coverage. Vietnamese (`vi`) is From 5361595c8c32387414906ef972dfc5021f49ebd2 Mon Sep 17 00:00:00 2001 From: Shubham Bhamare <87901258+theshubh77@users.noreply.github.com> Date: Sat, 17 Oct 2026 15:49:24 +0530 Subject: [PATCH 20/44] docs(contributors): add theshubh77 to people.toml --- contributors/people.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contributors/people.toml b/contributors/people.toml index 8694c72698..944a0f0ed9 100644 --- a/contributors/people.toml +++ b/contributors/people.toml @@ -162,3 +162,7 @@ sync_github_name = true [people.sidney] real_name = "Sidney Markowitz" sync_github_name = true + +[people.theshubh77] +real_name = "Shubham Bhamare" +sync_github_name = true From f13731a51a74667fbc7bd6b1a79fab1955c11683 Mon Sep 17 00:00:00 2001 From: Muhammad Saad Date: Sat, 17 Oct 2026 14:26:50 +0400 Subject: [PATCH 21/44] Text: count both coverage buffers on the plain path (#4979) Coverage::finish allocates its output while the accumulator it reads is still live, so a plain pass peaks at two coverage buffers rather than one. The budget charged one, which contradicted finish's own doc comment and left the plain path under its real peak. The bold path's three buffers were already right. The exactness assertion in the budget test now pins 18 bytes a pixel for a grey 8-bit plain render, and fails if the undercount comes back. Caught in review by @FredApps. --- crates/text/src/render.rs | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/crates/text/src/render.rs b/crates/text/src/render.rs index 51b9e1e5db..13dccc44c3 100644 --- a/crates/text/src/render.rs +++ b/crates/text/src/render.rs @@ -19,20 +19,22 @@ pub const FAUX_BOLD_RADIUS: f32 = 0.036; /// Largest raster we produce (pixels), as a guard against absurd sizes. const MAX_PIXELS: u64 = 256 * 1024 * 1024; /// Peak working bytes one rasterization may keep live. [`MAX_PIXELS`] does not bound them: the -/// accumulator and the output surface are both `stride` deep, and a faux-bold pass holds a second -/// coverage buffer, so a CMYK bold render reserves several times what a plain grey one does for -/// the same pixels. +/// accumulator and the output surface are both `stride` deep, and a faux-bold pass keeps one more +/// coverage buffer live, so a CMYK bold render reserves several times what a plain grey one does +/// for the same pixels. const MAX_RASTER_BYTES: u64 = if cfg!(target_pointer_width = "64") { 4 << 30 } else { 1 << 30 }; /// Whether a `w` x `h` render's peak working bytes fit `max_bytes`: the f32 accumulator and the -/// output surface (both `stride` deep), the finished coverage of the colour being drawn and, with -/// faux bold, the second coverage buffer and the vector its `finish` builds beside it. `false` -/// when the arithmetic overflows, so an absurd size is refused rather than wrapping. +/// output surface (both `stride` deep), plus the coverage buffers live at the peak. A plain pass +/// peaks inside `Coverage::finish`, which holds its accumulator and the vector it builds beside +/// it; a faux-bold pass peaks one buffer higher, since the resolved plain coverage is kept while +/// the bold pass resolves into it. `false` when the arithmetic overflows, so an absurd size is +/// refused rather than wrapping. fn fits_raster_budget(w: usize, h: usize, stride: usize, sample: SampleType, bold: bool, max_bytes: u64) -> bool { let f32s = size_of::() as u64; let peak = (w as u64).checked_mul(h as u64).and_then(|px| { let deep = px.checked_mul(stride as u64)?.checked_mul(f32s + sample.bytes() as u64)?; - let coverage = px.checked_mul(if bold { 3 } else { 1 })?.checked_mul(f32s)?; + let coverage = px.checked_mul(if bold { 3 } else { 2 })?.checked_mul(f32s)?; deep.checked_add(coverage) }); peak.is_some_and(|b| b <= max_bytes) @@ -470,17 +472,17 @@ mod tests { /// #4979: the pixel cap says nothing about bytes, so the peak is admitted from the channel /// depth of the accumulator and surface, the sample size and the faux-bold coverage buffers. - /// A 100 x 100 grey+alpha render at 8-bit keeps 2 x (4 + 1) + 4 = 14 bytes a pixel live. + /// A 100 x 100 grey+alpha render at 8-bit keeps 2 x (4 + 1) + 2 x 4 = 18 bytes a pixel live. #[test] fn the_byte_budget_follows_channels_depth_and_faux_bold() { let px = 100 * 100; - assert!(fits_raster_budget(100, 100, 2, SampleType::U8, false, px * 14)); - assert!(!fits_raster_budget(100, 100, 2, SampleType::U8, false, px * 14 - 1), "the allowance is exact"); + assert!(fits_raster_budget(100, 100, 2, SampleType::U8, false, px * 18)); + assert!(!fits_raster_budget(100, 100, 2, SampleType::U8, false, px * 18 - 1), "the allowance is exact"); // The same pixels cost more with more channels, a wider sample, or a bold pass. - assert!(!fits_raster_budget(100, 100, 5, SampleType::U8, false, px * 14), "CMYK is deeper"); - assert!(!fits_raster_budget(100, 100, 2, SampleType::F32, false, px * 14), "f32 output is wider"); - assert!(!fits_raster_budget(100, 100, 2, SampleType::U8, true, px * 14), "bold holds two more buffers"); + assert!(!fits_raster_budget(100, 100, 5, SampleType::U8, false, px * 18), "CMYK is deeper"); + assert!(!fits_raster_budget(100, 100, 2, SampleType::F32, false, px * 18), "f32 output is wider"); + assert!(!fits_raster_budget(100, 100, 2, SampleType::U8, true, px * 18), "bold holds one more buffer"); assert!(fits_raster_budget(100, 100, 2, SampleType::U8, true, px * 22), "2 x 5 + 3 x 4 bytes a pixel"); // A size whose peak overflows is refused rather than wrapping to a small number. From 9e2ec2cc8352d8e7f1e971bf33777a3a72409577 Mon Sep 17 00:00:00 2001 From: Shubham Bhamare <87901258+theshubh77@users.noreply.github.com> Date: Sat, 17 Oct 2026 16:08:40 +0530 Subject: [PATCH 22/44] fix(i18n): refine Hindi Camera Raw terminology --- crates/ui-egui/src/i18n/hi.tsv | 80 +++++++++++++++++----------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv index 0049ba94a6..cf09a7243a 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -374,24 +374,24 @@ cameraRaw Contrast कंट्रास्ट Contrast कंट्रास्ट cameraRaw Curve कर्व Curve कर्व -cameraRaw Darks गहरा अंधेरा - Darks गहरा अंधेरा -cameraRaw Dehaze धुंध हटाएं - Dehaze धुंध हटाएं -cameraRaw Detail विवरण - Detail विवरण +cameraRaw Darks डार्क्स + Darks डार्क्स +cameraRaw Dehaze डीहेज़ + Dehaze डीहेज़ +cameraRaw Detail डिटेल + Detail डिटेल cameraRaw Developing {name} {name} विकसित किया जा रहा है Developing {name} {name} विकसित किया जा रहा है -cameraRaw Effects प्रभाव - Effects प्रभाव +cameraRaw Effects इफ़ेक्ट्स + Effects इफ़ेक्ट्स cameraRaw Exposure एक्सपोज़र Exposure एक्सपोज़र cameraRaw Feather फ़ेदर Feather फ़ेदर cameraRaw Full-resolution preview unavailable पूर्ण-रिज़ॉल्यूशन पूर्वावलोकन अनुपलब्ध है Full-resolution preview unavailable पूर्ण-रिज़ॉल्यूशन पूर्वावलोकन अनुपलब्ध है -cameraRaw Global समग्र - Global समग्र +cameraRaw Global ग्लोबल + Global ग्लोबल cameraRaw Grain ग्रेन Grain ग्रेन cameraRaw Green हरा @@ -402,32 +402,32 @@ cameraRaw Highlight Priority हाइलाइट प्राथमिकत Highlight Priority हाइलाइट प्राथमिकता cameraRaw Highlights हाइलाइट्स Highlights हाइलाइट्स -cameraRaw Hue रंग - Hue रंग -cameraRaw Light प्रकाश - Light प्रकाश -cameraRaw Lights हल्की रोशनी - Lights हल्की रोशनी +cameraRaw Hue ह्यू + Hue ह्यू +cameraRaw Light लाइट + Light लाइट +cameraRaw Lights लाइट्स + Lights लाइट्स cameraRaw Load Settings… सेटिंग्स लोड करें… Load Settings… सेटिंग्स लोड करें… cameraRaw Load settings from a Camera Raw .xmp file Camera Raw .xmp फ़ाइल से सेटिंग्स लोड करें Load settings from a Camera Raw .xmp file Camera Raw .xmp फ़ाइल से सेटिंग्स लोड करें cameraRaw Loaded Camera Raw settings from {file} {file} से Camera Raw सेटिंग्स लोड की गईं Loaded Camera Raw settings from {file} {file} से Camera Raw सेटिंग्स लोड की गईं -cameraRaw Luminance चमक - Luminance चमक -cameraRaw Luminance Detail चमक विवरण - Luminance Detail चमक विवरण +cameraRaw Luminance लुमिनेंस + Luminance लुमिनेंस +cameraRaw Luminance Detail लुमिनेंस डिटेल + Luminance Detail लुमिनेंस डिटेल cameraRaw Magenta मैजेंटा Magenta मैजेंटा cameraRaw Masking मास्किंग Masking मास्किंग -cameraRaw Midpoint मध्य बिंदु - Midpoint मध्य बिंदु -cameraRaw Midtones मध्यम टोन - Midtones मध्यम टोन -cameraRaw Noise Reduction शोर में कमी - Noise Reduction शोर में कमी +cameraRaw Midpoint मिडपॉइंट + Midpoint मिडपॉइंट +cameraRaw Midtones मिडटोन्स + Midtones मिडटोन्स +cameraRaw Noise Reduction नॉइज़ रिडक्शन + Noise Reduction नॉइज़ रिडक्शन cameraRaw OK ठीक है cameraRaw Open खोलें Open खोलें @@ -443,12 +443,12 @@ cameraRaw Radius त्रिज्या Radius त्रिज्या cameraRaw Red लाल Red लाल -cameraRaw Roughness खुरदरापन - Roughness खुरदरापन -cameraRaw Roundness गोलाई - Roundness गोलाई -cameraRaw Saturation संतृप्ति - Saturation संतृप्ति +cameraRaw Roughness रफनेस + Roughness रफनेस +cameraRaw Roundness राउंडनेस + Roundness राउंडनेस +cameraRaw Saturation सैचुरेशन + Saturation सैचुरेशन cameraRaw Save Settings… सेटिंग्स सहेजें… Save Settings… सेटिंग्स सहेजें… cameraRaw Save these settings as a Camera Raw .xmp file इन सेटिंग्स को Camera Raw .xmp फ़ाइल के रूप में सहेजें @@ -465,22 +465,22 @@ cameraRaw Show Red at 3 o'clock 3 बजे की स्थिति पर ल Show Red at 3 o'clock 3 बजे की स्थिति पर लाल दिखाएँ cameraRaw Show Selected Region चयनित क्षेत्र दिखाएँ Show Selected Region चयनित क्षेत्र दिखाएँ -cameraRaw Show Skin Tone Indicator त्वचा टोन संकेतक दिखाएँ - Show Skin Tone Indicator त्वचा टोन संकेतक दिखाएँ +cameraRaw Show Skin Tone Indicator स्किन टोन संकेतक दिखाएँ + Show Skin Tone Indicator स्किन टोन संकेतक दिखाएँ cameraRaw Show Vectorscope वेक्टरस्कोप दिखाएँ Show Vectorscope वेक्टरस्कोप दिखाएँ cameraRaw Size आकार Size आकार cameraRaw Temperature तापमान Temperature तापमान -cameraRaw Texture बनावट - Texture बनावट -cameraRaw Tint रंग झलक - Tint रंग झलक +cameraRaw Texture टेक्सचर + Texture टेक्सचर +cameraRaw Tint टिंट + Tint टिंट cameraRaw Tone Histogram टोन हिस्टोग्राम Tone Histogram टोन हिस्टोग्राम -cameraRaw Vibrance जीवंतता - Vibrance जीवंतता +cameraRaw Vibrance वाइब्रेंस + Vibrance वाइब्रेंस cameraRaw Vignetting विगनेटिंग Vignetting विगनेटिंग cameraRaw White Balance: As Shot श्वेत संतुलन: शॉट के अनुसार From f112e6875752a002d45ed2906e0f5c9b2302d8ba Mon Sep 17 00:00:00 2001 From: Shubham Bhamare <87901258+theshubh77@users.noreply.github.com> Date: Sat, 17 Oct 2026 16:24:19 +0530 Subject: [PATCH 23/44] fix(i18n): refine Hindi pluralization, tools, and terminology --- crates/ui-egui/src/i18n/hi.tsv | 40 +++++++++++++++++----------------- crates/ui-egui/src/i18n/mod.rs | 2 +- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv index cf09a7243a..fe630fa065 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -39,7 +39,7 @@ Save सहेजें Save As… इस रूप में सहेजें… Save a Copy… एक प्रति सहेजें… - Revert वापस लौटें + Revert मूल स्थिति में लाएं Export एक्सपोर्ट करें Export As… इस रूप में एक्सपोर्ट करें… Quick Export as PNG PNG के रूप में त्वरित एक्सपोर्ट @@ -55,10 +55,10 @@ Cut काटें Copy कॉपी करें Copy Merged मर्ज की गई कॉपी - Paste पेस्ट + Paste पेस्ट करें Paste Special विशेष रूप में पेस्ट करें Paste in Place स्थान पर पेस्ट करें - Paste Into में पेस्ट करें + Paste Into अंदर पेस्ट करें Paste Outside बाहर पेस्ट करें Clear साफ़ करें Fill… भरें… @@ -117,7 +117,7 @@ # Layer Menu New नया Layer… लेयर… - Layer from Background पृष्ठभूमि से लेयर + Layer from Background बैकग्राउंड से लेयर Group… समूह… Group from Layers… लेयर्स से समूह… Artboard… आर्टबोर्ड… @@ -146,7 +146,7 @@ All सभी Deselect अचयनित करें Reselect पुनः चयन करें - Inverse उलटा करें + Inverse उल्टा करें All Layers सभी लेयर्स Deselect Layers लेयर्स अचयनित करें Find Layers लेयर्स खोजें @@ -195,13 +195,13 @@ Delete Workspace… कार्यस्थान हटाएँ… Lock Workspace कार्यस्थान लॉक करें Color रंग - Swatches नमूने + Swatches स्वॉचेस Gradients ग्रेडिएंट्स Patterns पैटर्न्स Properties गुण Layers लेयर्स Channels चैनल्स - Paths पथ + Paths पाथ्स History हिस्ट्री Actions एक्शन्स Brushes ब्रश @@ -263,8 +263,8 @@ Pen Tool पेन टूल Freeform Pen Tool फ्रीफॉर्म पेन टूल Curvature Pen Tool कर्वचर पेन टूल - Add Anchor Point Tool एंकर पॉइंट टूल - Delete Anchor Point Tool एंकर पॉइंट टूल + Add Anchor Point Tool ऐड एंकर पॉइंट टूल + Delete Anchor Point Tool डिलीट एंकर पॉइंट टूल Convert Point Tool कन्वर्ट पॉइंट टूल Horizontal Type Tool हॉरिजोंटल टाइप टूल Vertical Type Tool वर्टिकल टाइप टूल @@ -314,10 +314,10 @@ Auto स्वचालित # Plurals -@plural {n} layer|{n} layers {n} लेयर|{n} लेयरें -@plural {n} item|{n} items {n} आइटम|{n} आइटम +@plural {n} layer|{n} layers {n} लेयर|{n} लेयर्स +@plural {n} item|{n} items {n} आइटम|{n} आइटम्स @plural {n} document|{n} documents {n} दस्तावेज़|{n} दस्तावेज़ -@plural {n} pixel|{n} pixels {n} पिक्सेल|{n} पिक्सेल +@plural {n} pixel|{n} pixels {n} पिक्सेल|{n} पिक्सेल्स # Tool and Scope strings Scope दायरा @@ -343,8 +343,8 @@ cameraRaw Balance संतुलन Balance संतुलन cameraRaw Before (Y) पहले (Y) Before (Y) पहले (Y) -cameraRaw Blacks काले बिंदु - Blacks काले बिंदु +cameraRaw Blacks ब्लैक्स + Blacks ब्लैक्स cameraRaw Blending मिश्रण Blending मिश्रण cameraRaw Blue नीला @@ -453,8 +453,8 @@ cameraRaw Save Settings… सेटिंग्स सहेजें… Save Settings… सेटिंग्स सहेजें… cameraRaw Save these settings as a Camera Raw .xmp file इन सेटिंग्स को Camera Raw .xmp फ़ाइल के रूप में सहेजें Save these settings as a Camera Raw .xmp file इन सेटिंग्स को Camera Raw .xmp फ़ाइल के रूप में सहेजें -cameraRaw Shadow Clipping Warning (U) छाया क्लिपिंग चेतावनी (U) - Shadow Clipping Warning (U) छाया क्लिपिंग चेतावनी (U) +cameraRaw Shadow Clipping Warning (U) शैडो क्लिपिंग चेतावनी (U) + Shadow Clipping Warning (U) शैडो क्लिपिंग चेतावनी (U) cameraRaw Shadows शेडोज़ Shadows शेडोज़ cameraRaw Sharpening शार्पनिंग @@ -483,9 +483,9 @@ cameraRaw Vibrance वाइब्रेंस Vibrance वाइब्रेंस cameraRaw Vignetting विगनेटिंग Vignetting विगनेटिंग -cameraRaw White Balance: As Shot श्वेत संतुलन: शॉट के अनुसार - White Balance: As Shot श्वेत संतुलन: शॉट के अनुसार -cameraRaw Whites सफेद बिंदु - Whites सफेद बिंदु +cameraRaw White Balance: As Shot व्हाइट बैलेंस: शॉट के अनुसार + White Balance: As Shot व्हाइट बैलेंस: शॉट के अनुसार +cameraRaw Whites व्हाइट्स + Whites व्हाइट्स cameraRaw Yellow पीला Yellow पीला diff --git a/crates/ui-egui/src/i18n/mod.rs b/crates/ui-egui/src/i18n/mod.rs index 53e3b39bc1..4424071b43 100644 --- a/crates/ui-egui/src/i18n/mod.rs +++ b/crates/ui-egui/src/i18n/mod.rs @@ -451,7 +451,7 @@ mod tests { assert_eq!(tr(HI(), "Layer"), "लेयर"); assert_eq!(tr(HI(), "File"), "फ़ाइल"); assert_eq!(trn(HI(), 1, "{n} layer", "{n} layers"), "1 लेयर"); - assert_eq!(trn(HI(), 3, "{n} layer", "{n} layers"), "3 लेयरें"); + assert_eq!(trn(HI(), 3, "{n} layer", "{n} layers"), "3 लेयर्स"); } #[test] From 263dd041a6fd9655f27b311a8c5c6805ade135e2 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 17 Oct 2026 09:02:11 -0700 Subject: [PATCH 24/44] Type: register craft fonts of every script, not only the Japanese ones build.rs embeds every font in craft-fonts' fonts/manifest.txt, but FontDb::new registered only the entries whose scripts include Jpan. craft-fonts 8dcdacd5 adds Noto Sans CJK SC and Noto Sans Arabic, and 3d9ab3a1 adds Noto Sans Thai. Built with either checkout, the binary carried those fonts, but the font list did not show them, text layers did not fall back to them, and works_without_craft_fonts failed with "Noto Sans CJK SC registered under its manifest name". Chinese and Arabic set in Inter in a TextEngine::new() engine drew .notdef glyphs. FontDb::new now registers every CRAFT_FONTS entry. A single-face file registers under its manifest family, whatever family its name table gives; each face of a collection keeps the family its name table gives. With Noto Sans CJK SC built in and no Simplified Chinese system font, Japanese text set in Inter takes Noto Sans CJK SC when the UI locale is not Japanese, following cjk::script_order. craft_fonts_cover_japanese_without_system_fonts now expects the first CJK craft family in that order. New tests register in-memory craft entries made from Inter (Hans, Arab and Jpan entries, two whose name tables give another family, and a collection), and lay out Chinese, Arabic and Thai in Inter when the build has a craft font for the script. --- crates/text/src/craft_fonts.rs | 9 ++-- crates/text/src/font_names_tests.rs | 65 ++++++++++++++++++++++++++++- crates/text/src/fonts.rs | 30 ++++++++----- crates/text/src/lib.rs | 3 +- crates/text/src/tests.rs | 32 ++++++++++++-- 5 files changed, 120 insertions(+), 19 deletions(-) diff --git a/crates/text/src/craft_fonts.rs b/crates/text/src/craft_fonts.rs index c1c1949ea9..ed24a42035 100644 --- a/crates/text/src/craft_fonts.rs +++ b/crates/text/src/craft_fonts.rs @@ -1,9 +1,12 @@ //! Fonts from the optional craft-fonts build input (https://github.com/storytold/craft-fonts). //! //! `CRAFT_FONTS` is empty unless the app was built with `CRAFT_FONTS_DIR=` -//! (see `build.rs`); every user of it must work when it is empty. Today it carries the Japanese -//! fonts: BIZ UDPGothic (UI) and Shippori Mincho / BIZ UDMincho (serif document text). The web -//! build (wasm32) embeds none of them: they don't fit its size cap (see `build.rs`). +//! (see `build.rs`); every user of it must work when it is empty. It carries every font in the +//! checkout's `fonts/manifest.txt`, whatever its scripts: the Japanese fonts BIZ UDPGothic (UI) and +//! Shippori Mincho / BIZ UDMincho (serif document text) and, in newer checkouts, fonts for other +//! scripts such as Noto Sans CJK SC, Noto Sans Arabic and Noto Sans Thai. +//! [`crate::fonts::FontDb::new`] registers all of them. The web build (wasm32) embeds none of +//! them: they don't fit its size cap (see `build.rs`). /// A font from the optional craft-fonts build input (empty unless built with `CRAFT_FONTS_DIR`). pub struct CraftFont { diff --git a/crates/text/src/font_names_tests.rs b/crates/text/src/font_names_tests.rs index 0de4bf2445..8b8bcd419c 100644 --- a/crates/text/src/font_names_tests.rs +++ b/crates/text/src/font_names_tests.rs @@ -124,7 +124,7 @@ fn collection_isolation_preserves_requested_face() { let blob = Blob::new(Arc::new(bytes)); assert!(isolated_face(blob.clone(), 2).is_none()); let mut db = FontDb::new(); - db.register_blob(blob.clone()); + db.register_blob(blob.clone(), None); let mut style = CharStyle { font_family: "Numeric Test".into(), font_style: "30".into(), ..Default::default() }; db.select_named_face(&mut style); assert_eq!(db.faces(&style.font_family)[0].postscript_name.as_deref(), Some("Collection30")); @@ -188,3 +188,66 @@ fn localized_family_names_map_english_families_to_native_names() { assert!(!local.contains_key(DEFAULT_FAMILY)); assert!(local.values().all(|v| !v.is_empty())); } + +/// A craft-fonts manifest entry for `bytes`, as `build.rs` writes one per manifest line. +fn craft(family: &'static str, scripts: &'static [&'static str], bytes: Vec) -> crate::CraftFont { + crate::CraftFont { family, style: "Regular", scripts, bytes: bytes.leak() } +} + +/// A TrueType collection of single-face fonts. +fn collection(faces: &[Vec]) -> Vec { + let mut bytes = b"ttcf\0\x01\0\0".to_vec(); + bytes.extend((faces.len() as u32).to_be_bytes()); + let mut start = 12 + 4 * faces.len(); + for face in faces { + bytes.extend((start as u32).to_be_bytes()); + start += face.len(); + } + for face in faces { + let start = bytes.len(); + bytes.extend(face); + let count = u16::from_be_bytes(bytes[start + 4..start + 6].try_into().unwrap()) as usize; + for i in 0..count { + let r = start + 12 + i * 16 + 8; + let offset = u32::from_be_bytes(bytes[r..r + 4].try_into().unwrap()) + start as u32; + bytes[r..r + 4].copy_from_slice(&offset.to_be_bytes()); + } + } + bytes +} + +/// Single-face craft fonts of every script register under their manifest family names, whatever +/// family their `name` tables give, and the ones the fallback lists name are in the fallback +/// stack. The Hans and Arab families are fallback-list names, and no craft-fonts manifest uses +/// any of the three, so `FontDb::new` does not register them from the build's own craft fonts. +#[test] +fn craft_fonts_of_every_script_register_under_their_manifest_families() { + let fonts = [ + craft("WenQuanYi Micro Hei", &["Hans", "Latn"], localized_font(&INTER_REGULAR, "WenQuanYi Micro Hei", "WenQuanYi Micro Hei")), + // The `name` tables of these two give the family "Numeric Test". + craft("Geeza Pro", &["Arab"], numeric_font(&INTER_REGULAR, "20", "CraftTestArabic")), + craft("Craft Test Gothic", &["Jpan", "Latn"], numeric_font(&INTER_SEMIBOLD, "30", "CraftTestGothic")), + ]; + let mut db = FontDb::new(); + assert!(fonts.iter().all(|f| !db.has_family(f.family))); + db.register_craft_fonts(&fonts); + let families = db.families(); + for f in &fonts { + assert!(families.iter().any(|n| n == f.family), "{} registered under its manifest family: {families:?}", f.family); + } + assert!(!db.has_family("Numeric Test"), "{families:?}"); + for family in ["WenQuanYi Micro Hei", "Geeza Pro"] { + assert!(db.fallback_stack().any(|f| f == family), "{family} in {:?}", db.fallback_stack().collect::>()); + } +} + +/// Each face of a craft font collection keeps the family its `name` table gives. +#[test] +fn craft_font_collections_keep_their_face_families() { + let faces = [localized_font(&INTER_REGULAR, "Craft Test A", "クラフト A"), localized_font(&INTER_SEMIBOLD, "Craft Test B", "クラフト B")]; + let fonts = [craft("Craft Test Collection", &["Jpan", "Latn"], collection(&faces))]; + let mut db = FontDb::new(); + db.register_craft_fonts(&fonts); + assert!(db.has_family("Craft Test A") && db.has_family("Craft Test B"), "{:?}", db.families()); + assert!(!db.has_family("Craft Test Collection")); +} diff --git a/crates/text/src/fonts.rs b/crates/text/src/fonts.rs index 756ce87455..6de5d9086d 100644 --- a/crates/text/src/fonts.rs +++ b/crates/text/src/fonts.rs @@ -1,6 +1,6 @@ //! Font database: bundled fonts (always available, also on the web), the optional craft-fonts -//! Japanese fonts ([`crate::craft_fonts`], when built with `CRAFT_FONTS_DIR`), optional system -//! fonts found by scanning platform directories (plus Fontconfig's file list on Linux), +//! fonts of every script ([`crate::craft_fonts`], when built with `CRAFT_FONTS_DIR`), optional +//! system fonts found by scanning platform directories (plus Fontconfig's file list on Linux), //! user-registered font data, and PostScript-name lookup for PSD import. use std::collections::HashMap; @@ -223,14 +223,22 @@ impl FontDb { db.register_static_font(bytes.as_slice()); } // The optional craft-fonts (empty unless built with CRAFT_FONTS_DIR; always empty on - // wasm32), before any system font. - for f in crate::craft_fonts::CRAFT_FONTS.iter().filter(|f| f.is_japanese()) { - db.register_static_font(f.bytes); - } + // wasm32), every script, before any system font. + db.register_craft_fonts(crate::craft_fonts::CRAFT_FONTS); db.refresh_generics(); db } + /// Registers craft-fonts manifest entries of every script. A single-face file registers under + /// its manifest family, whatever family its `name` table gives. Each face of a collection + /// (TTC/OTC) keeps the family its `name` table gives. + fn register_craft_fonts(&mut self, fonts: &[crate::CraftFont]) { + for f in fonts { + let family = (face_count(f.bytes) == 1).then_some(f.family); + self.register_blob(Blob::new(Arc::new(f.bytes)), family); + } + } + /// Bundled fonts plus the fonts installed on this machine (no-op on the web). pub fn with_system_fonts() -> Self { let mut db = Self::new(); @@ -273,16 +281,18 @@ impl FontDb { /// Registers font data (TTF/OTF, or every face of a TTC/OTC). Returns the family names added. pub fn register_font_data(&mut self, bytes: Vec) -> Vec { - self.register_blob(Blob::new(Arc::new(bytes))) + self.register_blob(Blob::new(Arc::new(bytes)), None) } /// Registers embedded font data without copying it. fn register_static_font(&mut self, bytes: &'static [u8]) -> Vec { - self.register_blob(Blob::new(Arc::new(bytes))) + self.register_blob(Blob::new(Arc::new(bytes)), None) } - fn register_blob(&mut self, blob: Blob) -> Vec { - let added = self.fcx.collection.register_fonts(blob, None); + /// Registers every face in `blob`, under `family` when given, else under the family each + /// face's `name` table gives. + fn register_blob(&mut self, blob: Blob, family: Option<&str>) -> Vec { + let added = self.fcx.collection.register_fonts(blob, family.map(|f| FontInfoOverride { family_name: Some(f), ..Default::default() })); let mut names = Vec::new(); for (id, _) in added { if let Some(n) = self.fcx.collection.family_name(id) diff --git a/crates/text/src/lib.rs b/crates/text/src/lib.rs index af4de19f52..929f8841cc 100644 --- a/crates/text/src/lib.rs +++ b/crates/text/src/lib.rs @@ -4,7 +4,8 @@ //! web), optional system fonts from platform directory scans plus Fontconfig's Linux file list, //! user font data, TrueType collections, PostScript-name lookup. Builds made with the optional //! craft-fonts input -//! (`CRAFT_FONTS_DIR`, see [`craft_fonts`]) also carry its Japanese fonts as fallbacks. +//! (`CRAFT_FONTS_DIR`, see [`craft_fonts`]) also carry its fonts of every script (a single-face +//! file under its manifest family name); the ones the fallback lists name are fallbacks. //! * [`layout`]: shaping and line layout with [parley] (HarfRust shaping, bidi, line breaking), //! point and paragraph (box) text, per-run styles, Photoshop leading/indent/spacing rules. //! * [`render`]: anti-aliased rasterization (exact-area accumulation, f32 coverage) into a diff --git a/crates/text/src/tests.rs b/crates/text/src/tests.rs index 894ecf4e4f..aa31b00bb5 100644 --- a/crates/text/src/tests.rs +++ b/crates/text/src/tests.rs @@ -1516,10 +1516,17 @@ fn craft_fonts_cover_japanese_without_system_fonts() { let l = e.layout(&point(text, 24.0), 72.0); assert_eq!(l.glyphs.len(), text.chars().count()); assert!(l.glyphs.iter().all(|g| g.id != 0), "no .notdef with craft-fonts"); - // Sans (Inter) runs fall back to the Gothic UI family. - assert_eq!(craft_family_of(&l), vec![crate::craft_fonts::UI_JAPANESE_FAMILY]); - // Serif runs fall back to a Mincho face when the build has one (not the web build). - if crate::CRAFT_FONTS.iter().any(|f| f.is_mincho()) { + // Sans (Inter) runs fall back to the first CJK craft family in the UI locale's script order: + // the Gothic UI family when Japanese leads that order or the build has no other CJK craft font. + let cjk = |f: &crate::CraftFont| f.scripts.iter().any(|s| matches!(*s, "Jpan" | "Hans" | "Hant" | "Kore")); + let first = e.fonts.fallback_stack().find_map(|fam| crate::CRAFT_FONTS.iter().find(|f| f.family == fam && cjk(f))).map(|f| f.family); + if !crate::CRAFT_FONTS.iter().any(|f| cjk(f) && !f.is_japanese()) || crate::cjk::ui_script_order()[0] == crate::cjk::CjkScript::Japanese { + assert_eq!(first, Some(crate::craft_fonts::UI_JAPANESE_FAMILY)); + } + assert_eq!(craft_family_of(&l), first.into_iter().collect::>()); + // Serif runs fall back to a Mincho face when the build has one (not the web build) and the + // Japanese craft fonts come first. + if first == Some(crate::craft_fonts::UI_JAPANESE_FAMILY) && crate::CRAFT_FONTS.iter().any(|f| f.is_mincho()) { let mut t = point(text, 24.0); t.font_family = "Times New Roman".into(); let l = e.layout(&t, 72.0); @@ -1532,6 +1539,23 @@ fn craft_fonts_cover_japanese_without_system_fonts() { assert!(craft_family_of(&l).is_empty()); } +/// Chinese, Arabic and Thai set in Inter fall back to the craft font for their script when the +/// build has one (Noto Sans CJK SC, Noto Sans Arabic and Noto Sans Thai in newer craft-fonts). +#[test] +fn craft_fonts_cover_their_other_scripts_without_system_fonts() { + let mut e = TextEngine::new(); + for (script, text) in [("Hans", "中文图层"), ("Arab", "مرحبا"), ("Thai", "ภาษาไทย")] { + if !crate::CRAFT_FONTS.iter().any(|f| f.scripts.contains(&script)) { + eprintln!("skipping {script}: built without a craft font for it (set CRAFT_FONTS_DIR to a craft-fonts checkout)"); + continue; + } + let l = e.layout(&point(text, 24.0), 72.0); + assert!(!l.glyphs.is_empty() && l.glyphs.iter().all(|g| g.id != 0), "{script}: no .notdef with craft-fonts"); + let fams = craft_family_of(&l); + assert!(fams.iter().any(|fam| crate::CRAFT_FONTS.iter().any(|f| f.family == *fam && f.scripts.contains(&script))), "{script}: drawn with {fams:?}"); + } +} + #[test] fn works_without_craft_fonts() { // Whatever the build: the bundled fonts load, Latin lays out, and Japanese never panics From c05d827fdea5ee0c96231b83936d4994fa25afbc Mon Sep 17 00:00:00 2001 From: Nicolas Falesy <112726756+nicolasfalesy@users.noreply.github.com> Date: Sat, 17 Oct 2026 13:40:31 -0400 Subject: [PATCH 25/44] perf(type): refresh type layers without cloning the document (#7092) `type_cmds::refresh` read only the document's resolution and pixel format, but took `&Document`, so 15 call sites that hold the layer mutably cloned the whole document to pass it, once per keystroke and pointer move. The new `refresh_at(dpi, format, t)` takes the two values; those sites read them before borrowing the layer. `refresh(doc, t)` stays as a wrapper for callers that already hold the document. `with_text_layer` and `with_text` hand their closures the resolution, the only thing any of them read from the copy. type.edit on a 6000 x 4000 document: 60 filled layers 5.60 -> 5.14 ms, 20 layers 4.33 -> 3.90 ms (median of 300). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01FUAqfMqVHdoeDa2y68KQQw --- crates/engine/src/edit_menu_cmds.rs | 8 ++--- crates/engine/src/type_cmds.rs | 37 +++++++++++++--------- crates/engine/src/type_extra_cmds.rs | 44 +++++++++++++++------------ crates/engine/src/type_spell_cmds.rs | 14 ++++----- crates/engine/src/type_styles_cmds.rs | 10 +++--- crates/engine/src/variables_cmds.rs | 4 +-- crates/engine/src/warp_cmds.rs | 4 +-- 7 files changed, 67 insertions(+), 54 deletions(-) diff --git a/crates/engine/src/edit_menu_cmds.rs b/crates/engine/src/edit_menu_cmds.rs index 11e2273601..12cb62a415 100644 --- a/crates/engine/src/edit_menu_cmds.rs +++ b/crates/engine/src/edit_menu_cmds.rs @@ -902,13 +902,13 @@ fn find_replace(s: &mut Session, p: &Value) -> Result { return Ok(json!({"count": 0, "layers": 0})); } s.edit("Replace All Text", |doc, _| { - let snapshot = doc.clone(); + let (dpi, format) = (doc.resolution_dpi, doc.pixel_format()); for (id, matches) in &plan { if let Some(LayerContent::Text(t)) = doc.layer_mut(*id).map(|l| &mut l.content) { for (a, b) in matches.iter().rev() { crate::type_cmds::replace_text(t, *a, *b, &replace); } - crate::type_cmds::refresh(&snapshot, t); + crate::type_cmds::refresh_at(dpi, format, t); } } Ok(()) @@ -926,10 +926,10 @@ fn find_replace(s: &mut Session, p: &Value) -> Result { { crate::type_cmds::check_text_unlocked(&doc, id)?; s.edit("Replace Text", |doc, _| { - let snapshot = doc.clone(); + let (dpi, format) = (doc.resolution_dpi, doc.pixel_format()); if let Some(LayerContent::Text(t)) = doc.layer_mut(id).map(|l| &mut l.content) { crate::type_cmds::replace_text(t, a, b, &replace); - crate::type_cmds::refresh(&snapshot, t); + crate::type_cmds::refresh_at(dpi, format, t); } Ok(()) })?; diff --git a/crates/engine/src/type_cmds.rs b/crates/engine/src/type_cmds.rs index d6b92e840c..b31820dec7 100644 --- a/crates/engine/src/type_cmds.rs +++ b/crates/engine/src/type_cmds.rs @@ -8,7 +8,7 @@ use std::sync::Arc; -use photocraft_color::Color; +use photocraft_color::{Color, PixelFormat}; use photocraft_doc::text::{ AntiAlias, Caps, CharStyle, FontFeature, FontVariation, Kerning, Orientation, ParagraphRun, ParagraphStyle, TextAlign, TextDirection, TextRun, TextShape, }; @@ -534,9 +534,15 @@ pub fn replace_text(t: &mut TextLayer, a: usize, b: usize, new: &str) { /// Re-renders the layer's pixels and regenerates its PSD `TySh` data. pub fn refresh(doc: &Document, t: &mut TextLayer) { - let dpi = doc.resolution_dpi; + refresh_at(doc.resolution_dpi, doc.pixel_format(), t); +} + +/// [`refresh`] given only the two things it reads from the document: its resolution and its +/// pixel format. A caller holding the layer mutably reads them first instead of cloning the whole +/// document to pass it, once per keystroke and pointer move while typing (#7092). +pub fn refresh_at(dpi: f32, format: PixelFormat, t: &mut TextLayer) { let mut eng = photocraft_text::shared().lock().unwrap_or_else(|e| e.into_inner()); - let (layout, r) = eng.render(t, dpi, doc.pixel_format()); + let (layout, r) = eng.render(t, dpi, format); t.cache = Some(r.surface); t.psd_raw = Some(Arc::new(photocraft_text::psd::build_tysh(t, dpi, layout.bounds()))); } @@ -553,11 +559,12 @@ pub(crate) fn check_text_unlocked(doc: &Document, id: LayerId) -> Result<()> { /// Runs `f` on a type layer as one undo step. `f` may set an explicit layer name (`type.edit`'s /// `name`), which is applied in the same step; otherwise an auto-named layer follows its text. -fn with_text_layer(s: &mut Session, p: &Value, label: &str, f: impl FnOnce(&mut TextLayer, &Document, &mut Option) -> Result) -> Result { +/// `f` gets the document's resolution (dpi). +fn with_text_layer(s: &mut Session, p: &Value, label: &str, f: impl FnOnce(&mut TextLayer, f32, &mut Option) -> Result) -> Result { let id = layer_id(s, p)?; check_text_unlocked(&s.active().ok_or(EngineError::NoDocument)?.doc, id)?; let (r, damage) = s.edit(label, |doc, _| { - let snapshot = doc.clone(); + let (dpi, format) = (doc.resolution_dpi, doc.pixel_format()); let l = doc.layer_mut(id).ok_or(EngineError::NoLayer(id))?; let auto_named = is_auto_named(l); let LayerContent::Text(t) = &mut l.content else { @@ -565,8 +572,8 @@ fn with_text_layer(s: &mut Session, p: &Value, label: &str, f: impl FnOnce(&m }; let before = t.cache.as_ref().map(|c| c.tile_bounds()); let mut rename = None; - let r = f(t, &snapshot, &mut rename)?; - refresh(&snapshot, t); + let r = f(t, dpi, &mut rename)?; + refresh_at(dpi, format, t); // Only this layer's pixels changed: the canvas recomposites their old and new area // instead of the whole document (#124). Unknown old pixels mean a full refresh. let damage = before.zip(t.cache.as_ref().map(|c| c.tile_bounds())).map(|(a, b)| a.union(&b)); @@ -629,14 +636,14 @@ pub(crate) fn with_text_targets( ) -> Result> { let ids = format_targets(s, p)?; let damage = s.edit(label, |doc, _| { - let snapshot = doc.clone(); + let (dpi, format) = (doc.resolution_dpi, doc.pixel_format()); let mut damage = Some(photocraft_geom::Rect::EMPTY); for id in &ids { let Some(LayerContent::Text(t)) = doc.layer_mut(*id).map(|l| &mut l.content) else { return Err(EngineError::NoLayer(*id)) }; let before = t.cache.as_ref().map(|c| c.tile_bounds()); let range = if p.get("layers").is_none() || p.get("layer").is_some() { range_param(&t.text, p) } else { (0, t.text.len()) }; f(t, range)?; - refresh(&snapshot, t); + refresh_at(dpi, format, t); // An unknown old/new cache retains Session.edit's full-refresh fallback. damage = damage.zip(before).zip(t.cache.as_ref().map(|c| c.tile_bounds())).map(|((all, a), b)| all.union(&a).union(&b)); } @@ -856,7 +863,7 @@ pub fn specs() -> Vec { } }; let label = if kern_pair.is_some() { "Kerning" } else { "Edit Type" }; - with_text_layer(s, p, label, |t, doc, rename| { + with_text_layer(s, p, label, |t, dpi, rename| { if let Some((at, by)) = kern_pair { // Photoshop's Alt+←/→: the pair before the caret becomes manually kerned, // starting from what it shows now (its metrics/optical or manual value). @@ -868,7 +875,7 @@ pub fn specs() -> Vec { let b = byte_at(&t.text, at); let now = { let mut eng = photocraft_text::shared().lock().unwrap_or_else(|e| e.into_inner()); - eng.pair_kerning(t, doc.resolution_dpi, a) + eng.pair_kerning(t, dpi, a) } .ok_or_else(|| bad("type.edit", "no kerning pair at the caret (line break or line end)"))?; let value = (f64::from(now.round()) + by).clamp(KERN_MIN, KERN_MAX) as f32; @@ -984,16 +991,16 @@ pub fn specs() -> Vec { run: |s, p| { let id = layer_id(s, p)?; s.edit("Rasterize Type", |doc, _| { - let snapshot = doc.clone(); + let (dpi, format) = (doc.resolution_dpi, doc.pixel_format()); let l = doc.layer_mut(id).ok_or(EngineError::NoLayer(id))?; let LayerContent::Text(t) = &mut l.content else { return Err(EngineError::Other(format!("layer {} is not a type layer", id.0))); }; if t.cache.is_none() { - refresh(&snapshot, t); + refresh_at(dpi, format, t); } - let surface = t.cache.take().unwrap_or_else(|| photocraft_raster::Surface::new(snapshot.pixel_format())); - let surface = if surface.format() == snapshot.pixel_format() { surface } else { surface.convert(snapshot.pixel_format()) }; + let surface = t.cache.take().unwrap_or_else(|| photocraft_raster::Surface::new(format)); + let surface = if surface.format() == format { surface } else { surface.convert(format) }; l.content = LayerContent::Raster(surface); l.psd_blocks.retain(|(k, _)| k != b"TySh"); Ok(()) diff --git a/crates/engine/src/type_extra_cmds.rs b/crates/engine/src/type_extra_cmds.rs index efa2d791cc..2755315016 100644 --- a/crates/engine/src/type_extra_cmds.rs +++ b/crates/engine/src/type_extra_cmds.rs @@ -15,7 +15,8 @@ use serde_json::{Value, json}; use crate::commands::{CommandSpec, layer_param}; use crate::type_cmds::{ - check_text_unlocked, format_targets, has_format_target, is_auto_named, layer_name, refresh, replace_text, style_paragraphs, style_range, with_text_targets, + check_text_unlocked, format_targets, has_format_target, is_auto_named, layer_name, refresh_at, replace_text, style_paragraphs, style_range, + with_text_targets, }; use crate::{EngineError, Result, Session}; @@ -68,19 +69,20 @@ fn has_defaults(s: &Session) -> std::result::Result<(), String> { // ---------- helpers ---------- -/// Edits a type layer as one history step and re-renders it. -fn with_text(s: &mut Session, p: &Value, label: &str, f: impl FnOnce(&mut TextLayer, &Document) -> Result) -> Result { +/// Edits a type layer as one history step and re-renders it. `f` gets the document's resolution +/// (dpi). +fn with_text(s: &mut Session, p: &Value, label: &str, f: impl FnOnce(&mut TextLayer, f32) -> Result) -> Result { let id = layer_param(s, p)?; check_text_unlocked(&s.active().ok_or(EngineError::NoDocument)?.doc, id)?; s.edit(label, |doc, _| { - let snapshot = doc.clone(); + let (dpi, format) = (doc.resolution_dpi, doc.pixel_format()); let l = doc.layer_mut(id).ok_or(EngineError::NoLayer(id))?; let auto_named = is_auto_named(l); let LayerContent::Text(t) = &mut l.content else { return Err(EngineError::Other(format!("layer {} is {} {} layer, not a type layer", id.0, l.content.article(), l.content.kind_name()))); }; - let r = f(t, &snapshot)?; - refresh(&snapshot, t); + let r = f(t, dpi)?; + refresh_at(dpi, format, t); let name = auto_named.then(|| layer_name(&t.text)); if let Some(n) = name { l.name = n; @@ -90,7 +92,11 @@ fn with_text(s: &mut Session, p: &Value, label: &str, f: impl FnOnce(&mut Tex } fn layout(doc: &Document, t: &TextLayer) -> photocraft_text::TextLayout { - photocraft_text::shared().lock().unwrap_or_else(|e| e.into_inner()).layout(t, doc.resolution_dpi) + layout_at(doc.resolution_dpi, t) +} + +fn layout_at(dpi: f32, t: &TextLayer) -> photocraft_text::TextLayout { + photocraft_text::shared().lock().unwrap_or_else(|e| e.into_inner()).layout(t, dpi) } /// `m ∘ translate(tx, ty)`. @@ -144,11 +150,11 @@ fn set_orientation(s: &mut Session, p: &Value, o: Orientation) -> Result // ---------- point ⇄ paragraph ---------- fn to_paragraph(s: &mut Session, p: &Value) -> Result { - let r = with_text(s, p, "Convert to Paragraph Text", |t, doc| { - let l = layout(doc, t); - let size_px = t.char_runs().iter().map(|r| r.style.size_pt).fold(t.size_pt, f32::max) * doc.resolution_dpi / 72.0; + let r = with_text(s, p, "Convert to Paragraph Text", |t, dpi| { + let l = layout_at(dpi, t); + let size_px = t.char_runs().iter().map(|r| r.style.size_pt).fold(t.size_pt, f32::max) * dpi / 72.0; let ps = t.paragraph_runs().first().map(|p| p.style.clone()).unwrap_or_default(); - let indents = (ps.start_indent_pt + ps.end_indent_pt).max(0.0) * doc.resolution_dpi / 72.0; + let indents = (ps.start_indent_pt + ps.end_indent_pt).max(0.0) * dpi / 72.0; if l.vertical { // Line space: u along the columns, v across them (first column centred on v = 0). let [u0, v0, u1, v1] = l.line_bounds().unwrap_or([0.0, -size_px / 2.0, size_px, size_px / 2.0]); @@ -178,7 +184,7 @@ fn to_paragraph(s: &mut Session, p: &Value) -> Result { probe.shape = TextShape::Box { x, y: y0, width, height }; // Box text puts the first baseline at the box top plus the ascender height: measure it and // shift the box so that baseline stays at the point-text anchor (y = 0). - let b0 = layout(doc, &probe).lines.first().map_or(0.0, |ln| ln.baseline); + let b0 = layout_at(dpi, &probe).lines.first().map_or(0.0, |ln| ln.baseline); let y = y0 - b0; t.shape = TextShape::Box { x: 0.0, y: 0.0, width, height }; t.transform = then_translate(&t.transform, f64::from(x), f64::from(y)); @@ -188,9 +194,9 @@ fn to_paragraph(s: &mut Session, p: &Value) -> Result { } fn to_point(s: &mut Session, p: &Value) -> Result { - let r = with_text(s, p, "Convert to Point Text", |t, doc| { + let r = with_text(s, p, "Convert to Point Text", |t, dpi| { let TextShape::Box { x, y, width, height } = t.shape else { return Err(EngineError::Other("already point text".into())) }; - let l = layout(doc, t); + let l = layout_at(dpi, t); // Vertical type: columns run along the box height. let (x, width) = if l.vertical { (y, height) } else { (x, width) }; let baseline = l.lines.first().map_or(0.0, |ln| ln.baseline); @@ -496,11 +502,11 @@ fn paste_lorem(s: &mut Session, p: &Value) -> Result { fn update_all(s: &mut Session) -> Result { let mut n = 0; s.edit("Update All Text Layers", |doc, _| { - let snapshot = doc.clone(); - let ids: Vec = snapshot.walk().into_iter().filter(|(_, _, l)| matches!(l.content, LayerContent::Text(_))).map(|(_, _, l)| l.id).collect(); + let (dpi, format) = (doc.resolution_dpi, doc.pixel_format()); + let ids: Vec = doc.walk().into_iter().filter(|(_, _, l)| matches!(l.content, LayerContent::Text(_))).map(|(_, _, l)| l.id).collect(); for id in ids { if let Some(Layer { content: LayerContent::Text(t), .. }) = doc.layer_mut(id) { - refresh(&snapshot, t); + refresh_at(dpi, format, t); n += 1; } } @@ -582,7 +588,7 @@ fn replace_fonts(s: &mut Session, map: &serde_json::Map, all: boo } let mut n = 0; s.edit("Replace Missing Fonts", |doc, _| { - let snapshot = doc.clone(); + let (dpi, format) = (doc.resolution_dpi, doc.pixel_format()); for id in &ids { if let Some(Layer { content: LayerContent::Text(t), .. }) = doc.layer_mut(*id) { let mut changed = false; @@ -603,7 +609,7 @@ fn replace_fonts(s: &mut Session, map: &serde_json::Map, all: boo at += r.len; } if changed { - refresh(&snapshot, t); + refresh_at(dpi, format, t); n += 1; } } diff --git a/crates/engine/src/type_spell_cmds.rs b/crates/engine/src/type_spell_cmds.rs index 12a63a364c..915b21229a 100644 --- a/crates/engine/src/type_spell_cmds.rs +++ b/crates/engine/src/type_spell_cmds.rs @@ -16,7 +16,7 @@ use photocraft_text::spell::Dictionary; use serde_json::{Value, json}; use crate::commands::{CommandSpec, layer_param}; -use crate::type_cmds::{check_text_unlocked, norm_text, refresh, replace_text}; +use crate::type_cmds::{check_text_unlocked, norm_text, refresh_at, replace_text}; use crate::{EngineError, Result, Session}; fn bad(cmd: &str, msg: impl Into) -> EngineError { @@ -102,10 +102,10 @@ fn change(s: &mut Session, p: &Value) -> Result { return Err(bad(cmd, format!("the text at {start}..{end} is {:?}, not {w:?}", &t[a..b]))); } s.edit("Check Spelling", |doc, _| { - let snapshot = doc.clone(); + let (dpi, format) = (doc.resolution_dpi, doc.pixel_format()); if let Some(LayerContent::Text(t)) = doc.layer_mut(id).map(|l| &mut l.content) { replace_text(t, a, b, &to); - refresh(&snapshot, t); + refresh_at(dpi, format, t); } Ok(()) })?; @@ -129,13 +129,13 @@ fn change_all(s: &mut Session, p: &Value) -> Result { let count: usize = plan.iter().map(|(_, h)| h.len()).sum(); if count > 0 { s.edit("Check Spelling", |doc, _| { - let snapshot = doc.clone(); + let (dpi, format) = (doc.resolution_dpi, doc.pixel_format()); for (id, hits) in &plan { if let Some(LayerContent::Text(t)) = doc.layer_mut(*id).map(|l| &mut l.content) { for (a, b) in hits.iter().rev() { replace_text(t, *a, *b, &to); } - refresh(&snapshot, t); + refresh_at(dpi, format, t); } } Ok(()) @@ -215,10 +215,10 @@ fn insert_text(s: &mut Session, p: &Value) -> Result { let (ba, bb) = (byte_at(t, a), byte_at(t, b)); let label = p.get("label").and_then(Value::as_str).unwrap_or("Insert Glyph").to_string(); s.edit(&label, |doc, _| { - let snapshot = doc.clone(); + let (dpi, format) = (doc.resolution_dpi, doc.pixel_format()); if let Some(LayerContent::Text(t)) = doc.layer_mut(id).map(|l| &mut l.content) { replace_text(t, ba, bb, &new); - refresh(&snapshot, t); + refresh_at(dpi, format, t); } Ok(()) })?; diff --git a/crates/engine/src/type_styles_cmds.rs b/crates/engine/src/type_styles_cmds.rs index 797dfe7f8a..5d5ade404b 100644 --- a/crates/engine/src/type_styles_cmds.rs +++ b/crates/engine/src/type_styles_cmds.rs @@ -17,7 +17,7 @@ use photocraft_doc::{Document, LayerContent, LayerId, TextLayer, TextStyles}; use serde_json::{Map, Value, json}; use crate::commands::CommandSpec; -use crate::type_cmds::{apply_char_props, apply_para_props, refresh}; +use crate::type_cmds::{apply_char_props, apply_para_props, refresh_at}; use crate::{EngineError, Result, Session}; const FAMILY: &str = photocraft_text::fonts::DEFAULT_FAMILY; @@ -291,7 +291,7 @@ fn commit(s: &mut Session, label: &str, new: TextStyles, target: &[Target], op: fn apply_styles(doc: &mut Document, new: &TextStyles, target: &[Target], op: Op) -> usize { let old = doc.text_styles.clone(); doc.text_styles = new.clone(); - let snapshot = doc.clone(); + let (dpi, format) = (doc.resolution_dpi, doc.pixel_format()); let ids: Vec = doc.walk().into_iter().filter(|(_, _, l)| matches!(l.content, LayerContent::Text(_))).map(|(_, _, l)| l.id).collect(); let mut changed = 0; for id in ids { @@ -302,7 +302,7 @@ fn apply_styles(doc: &mut Document, new: &TextStyles, target: &[Target], op: Op) None => restyle(t, &old, new, Some((0, 0)), Op::default()), }; if did { - refresh(&snapshot, t); + refresh_at(dpi, format, t); changed += 1; } } @@ -516,7 +516,7 @@ fn delete(s: &mut Session, p: &Value, paragraph: bool) -> Result { } else { doc.text_styles.character.retain(|d| d.id != id); } - let snapshot = doc.clone(); + let (dpi, format) = (doc.resolution_dpi, doc.pixel_format()); let ids: Vec = doc.walk().into_iter().map(|(_, _, l)| l.id).collect(); for lid in ids { let Some(LayerContent::Text(t)) = doc.layer_mut(lid).map(|l| &mut l.content) else { continue }; @@ -537,7 +537,7 @@ fn delete(s: &mut Session, p: &Value, paragraph: bool) -> Result { } } if hit { - refresh(&snapshot, t); + refresh_at(dpi, format, t); n += 1; } } diff --git a/crates/engine/src/variables_cmds.rs b/crates/engine/src/variables_cmds.rs index f42b744844..bd76d9c8f6 100644 --- a/crates/engine/src/variables_cmds.rs +++ b/crates/engine/src/variables_cmds.rs @@ -165,7 +165,7 @@ fn apply_to_doc(doc: &mut Document, vars: &Variables, set: &DataSet) -> Result<( } } (VarKind::TextReplacement, VarValue::Text(text)) => { - let snapshot = doc.clone(); + let (dpi, format) = (doc.resolution_dpi, doc.pixel_format()); if let Some(l) = doc.layer_mut(def.layer) && let LayerContent::Text(t) = &mut l.content { @@ -180,7 +180,7 @@ fn apply_to_doc(doc: &mut Document, vars: &Variables, set: &DataSet) -> Result<( t.sync_summary(); // The compositor draws a type layer from its cache: clearing it left the // layer blank until some other edit re-rendered it (#990). - crate::type_cmds::refresh(&snapshot, t); + crate::type_cmds::refresh_at(dpi, format, t); } } (VarKind::PixelReplacement { method, align, clip }, VarValue::Pixels(path)) => { diff --git a/crates/engine/src/warp_cmds.rs b/crates/engine/src/warp_cmds.rs index 5142b0a940..452d600ece 100644 --- a/crates/engine/src/warp_cmds.rs +++ b/crates/engine/src/warp_cmds.rs @@ -321,7 +321,7 @@ fn warp_type_layer(s: &mut Session, cmd: &str, id: LayerId, w: &Warp) -> Result< } s.edit("Warp", |doc, _| { let group = crate::transform_cmds::group_locks(doc, id); - let snapshot = doc.clone(); + let (dpi, format) = (doc.resolution_dpi, doc.pixel_format()); let l = doc.layer_mut(id).ok_or(EngineError::NoLayer(id))?; let locks = l.locks.union(group); if locks.position || locks.all { @@ -329,7 +329,7 @@ fn warp_type_layer(s: &mut Session, cmd: &str, id: LayerId, w: &Warp) -> Result< } let LayerContent::Text(t) = &mut l.content else { return Err(EngineError::Other("not a type layer".into())) }; t.warp = tw.clone(); - crate::type_cmds::refresh(&snapshot, t); + crate::type_cmds::refresh_at(dpi, format, t); Ok(()) })?; Ok(json!({"layer": id.0, "changed": true, "warp": tw})) From e4bc5c25e85364f1dd4a0e2248464b01913f243b Mon Sep 17 00:00:00 2001 From: Stepan Pavlenko Date: Thu, 15 Oct 2026 22:35:50 +0300 Subject: [PATCH 26/44] i18n(ru): translate the dynamic labels the Russian catalog was missing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Russian catalog passed `cargo xtask i18n-coverage` (100% of measured keys) but lacked the runtime-only labels the other catalogs already carry, so they showed in English: filter and adjustment parameter names and choices, gallery names, Camera Raw panels (Balance/Clarity/Dehaze/Color Mixer/Texture/…), History/Layers/paths/channels menu labels, metadata and File Info fields, System Info and hardware status lines, rasterize prompts, composed status templates and dialog text. All of them are translated (1,229 keys added; the catalog is now 4,227 keys against upstream's 2,998), written from the meaning of the English labels and reusing the vocabulary the catalog already uses (Highlights → Света, Midtones → Полутона, Feather → Растушёвка, Pattern → узор). Filter and gallery names are aligned with the menu labels already in the catalog so a filter never reads two ways (Texturizer → Текстуризатор, Unsharp Mask → Нерезкое маскирование, Shape Blur → Размытие по фигуре, …). Tests: `russian_covers_generated_filter_options_and_gallery_names` (filter_dialog.rs, the Italian/Korean/Spanish check) and `russian_dynamic_status_hardware_and_dialog_labels_are_translated` (i18n/mod.rs). Docs: docs/localization-ru.md (terminology), localization-parity, gaps G13, ROADMAP, ATTRIBUTION and LICENSE-translations. Validated on a rebase onto upstream/main: `cargo test -p photocraft-ui-egui --lib` 2,998 passed / 0 failed; clippy clean (the only lint is a pre-existing `nonminimal_bool` in canvas.rs, untouched here and present on upstream main); `xtask layers` OK; `xtask wasm` OK; `xtask i18n-coverage` ru 1,863 / 1,863; offscreen `snapshot` in Russian checked. --- ATTRIBUTION.md | 1 + ROADMAP.md | 5 +- crates/ui-egui/src/filter_dialog.rs | 6 + .../ui-egui/src/i18n/LICENSE-translations.txt | 7 +- crates/ui-egui/src/i18n/mod.rs | 51 + crates/ui-egui/src/i18n/ru.tsv | 1238 +++++++++++++++++ docs/gaps.md | 11 +- docs/localization-parity.md | 9 +- docs/localization-ru.md | 75 + 9 files changed, 1391 insertions(+), 12 deletions(-) create mode 100644 docs/localization-ru.md diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index 1ae19efa66..964a071cc5 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -25,6 +25,7 @@ next to them. | `crates/ui-egui/src/i18n/vi.tsv` | Vietnamese UI translations | PhotoCraft contributors | Original translations of PhotoCraft's English labels | MIT OR Apache-2.0, [`LICENSE-translations.txt`](crates/ui-egui/src/i18n/LICENSE-translations.txt) | | `crates/ui-egui/src/i18n/tr.tsv` | Turkish UI translations | PhotoCraft contributors | Original translations of PhotoCraft's English labels | MIT OR Apache-2.0, [`LICENSE-translations.txt`](crates/ui-egui/src/i18n/LICENSE-translations.txt) | | `crates/ui-egui/src/i18n/sr.tsv` | Serbian (Latin, Ekavian) UI translations | PhotoCraft contributors | Original translations of PhotoCraft's English labels | MIT OR Apache-2.0, [`LICENSE-translations.txt`](crates/ui-egui/src/i18n/LICENSE-translations.txt) | +| `crates/ui-egui/src/i18n/ru.tsv` | Russian UI translations | PhotoCraft contributors | Original translations of PhotoCraft's English labels | MIT OR Apache-2.0, [`LICENSE-translations.txt`](crates/ui-egui/src/i18n/LICENSE-translations.txt) | | `assets/fonts/Inter-Regular.ttf`, `Inter-Medium.ttf`, `Inter-SemiBold.ttf` | Inter 4.001 (UI font) | The Inter Project Authors (Rasmus Andersson) | | SIL OFL 1.1, [`assets/fonts/OFL-Inter.txt`](assets/fonts/OFL-Inter.txt) | | `assets/fonts/JetBrainsMono-Regular.ttf` | JetBrains Mono 2.305 (numeric font) | The JetBrains Mono Project Authors | | SIL OFL 1.1, [`assets/fonts/OFL-JetBrainsMono.txt`](assets/fonts/OFL-JetBrainsMono.txt) | | `assets/icons/*.svg` except `slice-knife.svg`, `sampler.svg`, `artboard.svg`, `perspective-crop.svg`, `frame-placeholder.svg`, `gradient.svg`, `type-mask.svg`, `type-mask-vertical.svg`, `clip-below.svg`, `clip-release.svg`, `layer-mask.svg`, `adjustment-layer.svg`, `brush-replace.svg`, `quick-selection.svg`, `eraser-background.svg`, `eraser-magic.svg`, `lasso-magnetic.svg`, `direct-select.svg`, `convert-point.svg`, `pen-add-anchor.svg`, `pen-delete-anchor.svg`, `pen-freeform.svg`, `pen-curvature.svg`, `bandage-sparkle.svg`, `painting-symmetry.svg`, `symmetry-*.svg` and `marquee-{row,column}.svg` (113 files; `star-filled.svg` is `star.svg` with a solid fill) | Lucide icons | Lucide Icons and Contributors | (`icons/.svg`; `align-*` are now named `text-align-*` upstream) | ISC, [`assets/icons/LICENSE-lucide.txt`](assets/icons/LICENSE-lucide.txt) | diff --git a/ROADMAP.md b/ROADMAP.md index f41b71bfc9..ca3bd5cbb6 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,7 +2,7 @@ **Stage: alpha** · next: beta, ~30 points (ready for real work ~45% → ~75%, with reliable PSD) and ~1,100–1,800 h away -> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-13 · **Change:** minor (oracle floor 145 of 258 and 953 open issues, as in gaps.md and on GitHub; toolbox count 56 of 68, 12 missing) · **Target:** Adobe Photoshop 2026 +> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-15 · **Change:** minor (Russian catalog covers every dynamic label; previously minor: oracle floor 145 of 258 and 953 open issues, as in gaps.md and on GitHub; toolbox count 56 of 68, 12 missing) · **Target:** Adobe Photoshop 2026 PhotoCraft's one-page summary of how close it is to Adobe Photoshop 2026 (27.11.0, measured from the installed app on 2026-10-10) at version 0.6.0. Detail: [target-app parity](docs/target-app-parity.md) @@ -104,7 +104,7 @@ because engine messages are still English. Detail: [localization-parity](docs/lo | Korean | `ko` | 100% | partial | | Vietnamese | `vi` | 0% | none | -Also shipped: Traditional Chinese, Russian, Ukrainian, Czech, Polish, Greek, Dutch, Italian (8). +Also shipped: Traditional Chinese, Russian (every dynamic label, [notes](docs/localization-ru.md)), Ukrainian, Czech, Polish, Greek, Dutch, Italian (8). ## Upcoming @@ -136,6 +136,7 @@ Detail and the next releases: [docs/roadmap.md](docs/roadmap.md). | Date | Change | Summary | |---|---|---| | 2026-10-13 | minor | Focus item 1 quoted the oracle floor as 133 of 256; it is 145 of 258 (`crates/io/tests/corpus.rs`, gaps.md G1). Open issues 579 → 953 (2026-10-13) | +| 2026-10-15 | minor | Russian catalog covers every dynamic UI label the other catalogs translate | | 2026-10-11 | minor | Toolbox count 56 of 68 (12 missing) with the Convert Point tool (#3204) | | 2026-10-10 | minor | Readiness by audience with hours per tier | | 2026-10-10 | minor | Mainstream-practitioner and essentials-user numbers added | diff --git a/crates/ui-egui/src/filter_dialog.rs b/crates/ui-egui/src/filter_dialog.rs index fa7cb9524d..87eca79b94 100644 --- a/crates/ui-egui/src/filter_dialog.rs +++ b/crates/ui-egui/src/filter_dialog.rs @@ -1475,6 +1475,12 @@ mod tests { assert!(missing.is_empty(), "missing Spanish dynamic labels: {missing:#?}"); } + #[test] + fn russian_covers_generated_filter_options_and_gallery_names() { + let missing = missing_generated_filter_labels(crate::i18n::lang_from_tag("ru-RU").unwrap()); + assert!(missing.is_empty(), "missing Russian dynamic labels: {missing:#?}"); + } + /// The Select menu's generated dialogs (Select and Mask, Modify ▸ …) show their /// params, choices and title in every UI language, not just Korean. #[test] diff --git a/crates/ui-egui/src/i18n/LICENSE-translations.txt b/crates/ui-egui/src/i18n/LICENSE-translations.txt index 93a609f946..0e9c354446 100644 --- a/crates/ui-egui/src/i18n/LICENSE-translations.txt +++ b/crates/ui-egui/src/i18n/LICENSE-translations.txt @@ -1,6 +1,6 @@ PhotoCraft's Korean, German, Spanish, Brazilian Portuguese, Italian, Ukrainian, Dutch, Czech, -Turkish, Hungarian and Serbian UI translation catalogs are licensed under MIT OR Apache-2.0, -at your option. +Turkish, Hungarian, Serbian and Russian UI translation catalogs are licensed under MIT OR +Apache-2.0, at your option. The full license texts are in ../../../../LICENSE-MIT and ../../../../LICENSE-APACHE. Korean translations: PhotoCraft contributors, original translations of @@ -37,3 +37,6 @@ PhotoCraft's English labels; no proprietary translation resources were used. Serbian translations: PhotoCraft contributors, original translations of PhotoCraft's English labels; no proprietary translation resources were used. + +Russian translations: PhotoCraft contributors, original translations of +PhotoCraft's English labels; no proprietary translation resources were used. diff --git a/crates/ui-egui/src/i18n/mod.rs b/crates/ui-egui/src/i18n/mod.rs index b976e395fc..3cff605b18 100644 --- a/crates/ui-egui/src/i18n/mod.rs +++ b/crates/ui-egui/src/i18n/mod.rs @@ -1111,6 +1111,57 @@ mod tests { } } + /// Russian dynamic labels added in the fourth pass (filter parameters, Camera Raw panels, + /// hardware status, rasterize prompts, History/paths menus) render in Russian, not English. + #[test] + fn russian_dynamic_status_hardware_and_dialog_labels_are_translated() { + let ru = Lang::from_code("ru").expect("ru registered"); + for kind in [ + crate::rasterize_prompt::Kind::Type, + crate::rasterize_prompt::Kind::Shape, + crate::rasterize_prompt::Kind::SmartObject, + crate::rasterize_prompt::Kind::Fill, + ] { + assert_ne!(tr(ru, kind.message()), kind.message(), "{:?}", kind); + } + assert_eq!(fmt(tr(ru, "Driver: {driver}"), &[("driver", "Mesa 25.0")]), "Драйвер: Mesa 25.0"); + assert_eq!( + fmt(tr(ru, "Monitor profile on {display}: {profile}"), &[("display", "DELL U2720Q"), ("profile", "sRGB IEC61966-2.1")]), + "Профиль монитора для DELL U2720Q: sRGB IEC61966-2.1" + ); + assert_eq!(fmt(tr(ru, "Platform: {os} {arch}"), &[("os", "Linux"), ("arch", "x86_64")]), "Платформа: Linux x86_64"); + assert_eq!(tr(ru, "Graphics adapter"), "Графический адаптер"); + assert_eq!(tr(ru, "Image compositor: {processor}"), "Компоновщик изображения: {processor}"); + // Filter / adjustment parameter names. + for label in [ + "Amount", + "Radius", + "Threshold", + "Angle", + "High Pass", + "Smart Blur", + "Reduce Noise", + "Lens Correction", + "Colored Pencil", + "Angled Strokes", + "Stained Glass", + "Sumi-e", + "Film Grain", + "Unsharp Mask", + "Lens Flare", + ] { + assert!(has(ru, label), "Russian filter label missing: {label}"); + } + // Dynamic composed status templates. + assert_eq!(fmt(tr(ru, "Step {step} ({id}) failed: {error}"), &[("step", "2"), ("id", "file.export"), ("error", "ok")]), "Шаг 2 (file.export) не выполнен: ok"); + assert_eq!(fmt(tr(ru, "Couldn't open {name}: {error}"), &[("name", "scan.tif"), ("error", "denied")]), "Не удалось открыть scan.tif: denied"); + assert_eq!(fmt(tr(ru, "Exported {path} ({size})"), &[("path", "/tmp/a.png"), ("size", "12 KB")]), "Экспортировано: /tmp/a.png (12 KB)"); + assert_eq!(fmt(tr(ru, "Data Set: {name} ({index}/{total})"), &[("name", "Set 1"), ("index", "1"), ("total", "2")]), "Набор данных: Set 1 (1/2)"); + // Document and layer names stay verbatim. + let text = fmt(tr(ru, "Saved {path}; image assets: {n} in {dir}"), &[("path", "/tmp/a.pcraft"), ("n", "3"), ("dir", "/tmp/assets")]); + assert!(text.contains("/tmp/a.pcraft") && text.contains("/tmp/assets"), "{text}"); + } + /// Every `tl!("literal")` in the shell has an entry in each language that claims complete menus /// (so a new label can't ship untranslated by accident). Literals that are deliberately shown as /// they are (names, units) are listed in `KEEP_AS_IS`. diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index f49200b973..2f99e293ac 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -3181,3 +3181,1241 @@ curves Smooth Сгладить Replace Заменить {name} was changed by another app after it was opened or saved here. Файл {name} был изменён другим приложением после того, как его открыли или сохранили здесь. Replace it with this document, or cancel and use File › Save As to keep both. Замените его этим документом или отмените и используйте Файл › Сохранить как, чтобы сохранить оба. + + +# Dynamic UI labels (fourth pass): filter and adjustment parameters, Camera Raw panels, +# lighting, History/Layers/paths/channels menus, metadata, System Info, status templates and +# dialog text that other catalogs already carry. Written from the meaning of the English labels +# in the ordinary Russian graphics-editor vocabulary; no proprietary resource consulted. + (coming soon) (скоро) + (none) (нет) + 32-bit Preview Options Параметры предпросмотра 32 бит + 3D… 3D… + A: Ш: + AI Integrations Интеграции ИИ + About {app} О программе {app} + Accented Edges Акцентированные края + Action playback failed: {error} Не удалось воспроизвести операцию: {error} + Actual Pixels Реальный размер + Add Mask Добавить маску + Add Noise Добавить шум + Add a mask (from the selection; ⌥ inverts) Добавить маску (из выделенной области; ⌥ инвертирует) + Add space after paragraph Добавить интервал после абзаца + Add space before paragraph Добавить интервал перед абзацем + Add to selection (⇧) Добавить к выделенной области (⇧) + Added swatch {name} Добавлен образец {name} + Adds grain to the soft edges of the brush tip. Добавляет зерно к мягким краям кисти. + Adjust Interval For Loops Настроить интервал циклов + Adjustment Layers Корректирующие слои + Adjustment Properties Свойства коррекции + Adjustment layers have nothing to transform В корректирующих слоях нечего трансформировать + Adobe PDF Presets… Наборы Adobe PDF… + Adobe-rgb-compat Adobe RGB (совместимо) + Adwaita (Dark) Adwaita (тёмная) + Adwaita (Light) Adwaita (светлая) + After После + Airbrush: keeps painting while the pointer rests. Аэрограф: продолжает рисовать, пока указатель неподвижен. + All shortcuts reset to Photoshop defaults. Все сочетания клавиш сброшены к значениям Photoshop по умолчанию. + Altitude Высота + Ambience Атмосфера + Ambient Окружающий свет + Amplitude Max Макс. амплитуда + Amplitude Min Мин. амплитуда + Android 1080p Android 1080p + Angled Strokes Наклонные штрихи + Anisotropic Анизотропный + Any Любой + Apple Watch 45mm Apple Watch 45 мм + Apply Data Set Применить набор данных + Apply Image Внешний канал + Apply Mask Применить маску + Apply to All Применить ко всем + Apply {option}: {state} Применить {option}: {state} + Arbitrary Произвольный + Arc Lower Нижняя дуга + Arc Upper Верхняя дуга + Are you sure you want to quit? Вы действительно хотите выйти? + Are you sure? Вы уверены? + Around Center Вокруг центра + Arrangement Расположение + Arrows Стрелки + Artboard from Group Монтажная область из группы + Artboard from Layers Монтажная область из слоёв + Artboards Монтажные области + Artboards to Files Монтажные области в файлы + Artboards to PDF Монтажные области в PDF + Artistic Художественные + Ask On Mismatch Спрашивать при несовпадении + Ask On Missing Спрашивать при отсутствии + Ask On Paste Спрашивать при вставке + Assign Profile Назначить профиль + At Позиция + Author Title Имя автора + Auto Scale Автомасштаб + Auto-Align Layers Автовыравнивание слоёв + Auto-Blend Layers Автосмешивание слоёв + Autosave failed: {error} Не удалось выполнить автосохранение: {error} + Back Назад + Backend: {backend} ({type}) Сервер: {backend} ({type}) + Background Level Уровень фона + Bamboo Бамбук + Bas Relief Барельеф + Base Tree Type Базовый тип дерева + Based On На основе + Baseline shift Смещение базовой линии + Basic Latin Основная латиница + Batch Пакетная обработка + Before До + Bend Изгиб + Berries Ягоды + Best Наилучшее + Black Clip Отсечение чёрного + Black Intensity Интенсивность чёрного + Blade Curvature Кривизна лепестков диафрагмы + Blast Порыв + Bleach Bypass Пропуск отбеливания + Blend Clipped Layers as Group Смешивать обтравочные слои как группу + Blend Interior Effects as Group Смешивать внутренние эффекты как группу + Blend Text Gamma Гамма смешивания текста + Blending Mode Режим наложения + Bloat Вздутие + Blocks Блоки + Blue Yellow Синий–жёлтый + Blues Синие тона + Border Граница + Border Thickness Толщина границы + Box Blur Размытие по рамке + Bpc Компенсация чёрной точки + Branches Color Цвет ветвей + Branches Height Высота ветвей + Branches Thickness Толщина ветвей + Breeze Dark Breeze (тёмная) + Breeze Light Breeze (светлая) + Brick Кирпич + Bristle Detail Детализация щетины + Browse Обзор + Brush Detail Детализация кисти + Brush Settings… (F5): every option of this brush Параметры кисти… (F5): все параметры этой кисти + Brush Strokes Мазки кисти + Brush Type Тип кисти + Burlap Мешковина + Calculations Вычисления + Camera Raw Camera Raw + Camera Raw Filter Фильтр Camera Raw + Cancel transform Отменить трансформирование + Candle Light Свет свечи + Canvas Size Размер холста + Case Sensitive Учитывать регистр + Ccw Против часовой стрелки + Cell Size Размер ячейки + Center X Центр X + Center Y Центр Y + Chalk Area Область мела + Change Find Найти и заменить + Changes Saved Изменения сохранены + Changes have not been saved. Изменения не сохранены. + Change… Изменить… + Channel1 Канал 1 + Channel2 Канал 2 + Channel3 Канал 3 + Channel4 Канал 4 + Charcoal Area Область угля + Charcoal Thickness Толщина угля + Check for Updates Проверить обновления + Checkerboard Шахматная доска + Choke Поджатие + Choose Folder Выбрать папку + Circle Круг + Cleanliness Чистота + Clear Amount Величина очистки + Clear Selection Отменить выделение + Click sets foreground · right-click sets background Щелчок задаёт цвет переднего плана · щелчок правой кнопкой задаёт цвет фона + Click the first point or press ↵ to close · Esc cancels Щёлкните первую точку или нажмите ↵, чтобы замкнуть · Esc отменяет + Click to add a point · drag off or ⌘/Ctrl-click or Delete to remove Щёлкните, чтобы добавить точку · перетащите за пределы, нажмите ⌘/Ctrl и щёлкните или нажмите Delete, чтобы удалить + Click to apply, ⌥-click to clear overrides, double-click for Style Options Щёлкните, чтобы применить, ⌥+щелчок, чтобы очистить переопределения, двойной щелчок — параметры стиля + Click to zoom in · ⌥-click to zoom out Щёлкните, чтобы увеличить · ⌥+щелчок, чтобы уменьшить + Click: corner · Drag: smooth · Click first point: close · ↩ finish · Esc cancel Щелчок: угол · Перетаскивание: сглаживание · Щелчок по первой точке: замкнуть · ↩ завершить · Esc отменить + Clipping Mask Обтравочная маска + Clipping Path… Контур отсечения… + Clone Клонировать + Clone source set at {x}, {y} Источник клонирования задан в {x}, {y} + Close Window Закрыть окно + Clumped Сгруппированные + Cmyk CMYK + Coarse Dots Крупные точки + Coated-cmyk Мелованная CMYK + Collapse group (⌥-click: all groups) Свернуть группу (⌥+щелчок: все группы) + Color Decontaminate Удаление цветового ореола + Color Halftone Цветные полутона + Color Indicates Masked Areas Цветом обозначены маскированные области + Color Indicates Selected Areas Цветом обозначены выделенные области + Color Lookup Tables Таблицы перекодировки цвета + Color Picker Палитра цветов + Color Picker (Background Color) Палитра цветов (цвет фона) + Color Picker (Custom Canvas Color) Палитра цветов (свой цвет холста) + Color Picker (Foreground Color) Палитра цветов (цвет переднего плана) + Color Sampler Tool Инструмент «Цветовой эталон» + Color Settings Настройки цвета + Color Swatches Образцы цветов + Color Table Цветовая таблица + Color management: {error} Управление цветом: {error} + Colored Pencil Цветной карандаш + Comic Book Комикс + Command Команда + Commit Perspective Warp (↩) Применить перспективную деформацию (↩) + Commit Puppet Warp (↩) Применить марионеточную деформацию (↩) + Commit any current edits (⌘↩) Применить текущие изменения (⌘↩) + Commit current crop operation (↵) Применить текущую операцию кадрирования (↵) + Commit transform Применить трансформирование + Commit transform (↩) Применить трансформирование (↩) + Commit warp (↩) Применить деформацию (↩) + Compare Сравнить + Compression Сжатие + Conditional Mode Change Условная смена режима + Confirm Подтвердить + Conform Подогнать + Constraints Ограничения + Content-Aware Fill needs a selection on a pixel layer Заливка с учётом содержимого требует выделенной области на пиксельном слое + Continue Продолжить + Contract Сжать + Contrasty Контрастный + Convert Преобразовать + Convert to Alpha Channel Преобразовать в альфа-канал + Convert to Linked Преобразовать в связанный + Convert to Profile Преобразовать в профиль + Cool Холодный + Cooling L B B Охлаждающий (LBB) + Cooling80 Охлаждающий (80) + Cooling82 Охлаждающий (82) + Copy Complete Stroke Копировать полный контур + Copy Fill Копировать заливку + Copyright Url URL авторских прав + Correct C A Коррекция хроматической аберрации + Correct Distortion Коррекция дисторсии + Correct Vignette Коррекция виньетирования + Could not rename the action: {error} Не удалось переименовать операцию: {error} + Could not transform: the layer is empty Не удалось трансформировать: слой пуст + Could not use Perspective Warp: the layer is empty Не удалось применить перспективную деформацию: слой пуст + Could not use Puppet Warp: no opaque pixels Не удалось применить марионеточную деформацию: нет непрозрачных пикселей + Could not use Puppet Warp: the layer is empty Не удалось применить марионеточную деформацию: слой пуст + Couldn't open {name}: {error} Не удалось открыть {name}: {error} + Couldn't save preferences: {error}. Retrying… Не удалось сохранить настройки: {error}. Повтор… + Crack Brightness Яркость трещин + Crack Depth Глубина трещин + Crack Spacing Интервал трещин + Craquelure Кракелюр + Create By Создать с помощью + Create Vector Mask Создать векторную маску + Create Warped Text Создать деформированный текст + Create a new layer (⇧⌘N) Создать новый слой (⇧⌘N) + Create new brush from the current settings Создать новую кисть из текущих параметров + Create new brush preset from the current settings Создать новый набор кистей из текущих параметров + Create… Создать… + Cross Крест + Cross Process Кросс-процесс + Crosshatch Перекрёстная штриховка + Crosswise Перекрёстно + Crystallize Кристаллизация + Currency Валюта + Curve Darks Кривая: тёмные тона + Curve Highlights Кривая: светлые тона + Curve Lights Кривая: светлые тона + Curve Shadows Кривая: тени + Curve Splits Кривая: разрывы + Custom Branch Color Свой цвет ветвей + Custom Pixel Aspect Ratio Своё пиксельное соотношение + Custom pattern Свой узор + Cutout Аппликация + Cw По часовой стрелке + Cyans Голубые тона + Cyrillic Кириллица + Daisies Маргаритки + Dark Gray Тёмно-серый + Dark Intensity Интенсивность тёмного + Dark Rough Тёмный шероховатый + Dark Strokes Тёмные штрихи + Darken Only Только затемнение + Darkness Затемнение + Data Данные + Data Set: {name} ({index}/{total}) Набор данных: {name} ({index}/{total}) + Data Sets as Files Наборы данных в файлы + Date Дата + Day For Night День как ночь + De-Interlace Устранение чересстрочности + Decrease Brush Hardness Уменьшить жёсткость кисти + Decrease Brush Size Уменьшить размер кисти + Decrease Type Size Уменьшить размер текста + Decrease Type Size by 10 Уменьшить размер текста на 10 + Deep Глубокий + Deep Blue Глубокий синий + Deep Emerald Глубокий изумрудный + Deep Red Глубокий красный + Deep Yellow Глубокий жёлтый + Default Foreground and Background Colors Цвета переднего плана и фона по умолчанию + Default Leaves Листья по умолчанию + Default Photoshop Size Размер Photoshop по умолчанию + Defaults По умолчанию + Define Brush Preset Определить кисть + Define Custom Shape Определить свою фигуру + Define Pattern Определить паттерн + Definition Чёткость + Defringe Убрать кайму + Degrees Градусы + Delete Channel Удалить канал + Delete Mask Удалить маску + Delete Workspace Удалить рабочую среду + Delete brush Удалить кисть + Delete path Удалить контур + Delete vector mask Удалить векторную маску + Delete… Удалить… + Depth Map Карта глубины + Depth Mode Режим глубины + Diameter Диаметр + Diamond Ромб + Diffuse Диффузия + Diffuse Glow Рассеянное свечение + Direction Направление + Direction Balance Баланс направлений + Disable Выключить + Disable Mask Отключить маску + Disable Symmetry Path Отключить контур симметрии + Disabled Отключено + Discard Changes Отменить изменения + Displace Смещение + Display Дисплей + Display profile not used Профиль дисплея не используется + Display profiles: {reason} Профили дисплеев: {reason} + Display-p3 Display P3 + Distance Расстояние + Distortion Дисторсия + Distribution Распределение + Do you want to save you made to “{name}” before quitting? Сохранить изменения в «{name}» перед выходом? + Document Sizes Размеры документа + Documentation Документация + Documents Документы + Dont Enlarge Не увеличивать + Dot Точка + Double Line Двойная линия + Down Вниз + Draft Черновик + Drag a crop box · drag inside to move · edges resize ({ratio} ratio, {centre} centre) · Space moves while drawing · {commit} commits · Esc cancels Перетащите рамку кадрирования · перетаскивание внутри перемещает · края изменяют размер (соотношение {ratio}, центр {centre}) · пробел перемещает при рисовании · {commit} применяет · Esc отменяет + Drag: draw freehand · Release on the start point: close Перетаскивание: рисование от руки · Отпустите на начальной точке: замкнуть + Driver: {driver} Драйвер: {driver} + Dry Brush Сухая кисть + Duplicate Channel Дублировать канал + Duplicate Layer Дублировать слой + Duplicate Layers Дублировать слои + Duplication Дублирование + Dust & Scratches Пыль и царапины + Edge Край + Edge Brightness Яркость краёв + Edge Extension Расширение краёв + Edge Fidelity Точность краёв + Edge Intensity Интенсивность краёв + Edge Only Только края + Edge Simplicity Простота краёв + Edge Thickness Толщина краёв + Edge Width Ширина краёв + Edit Toolbar Настроить панель инструментов + Edit… Изменить… + Elevation Возвышение + Eliminate Устранить + Emboss Тиснение + Enable Включить + Enable Mask Включить маску + End X Конец X + End Y Конец Y + Enlarged Увеличенный + Erase with Brush or Pencil (hold) Стирание кистью или карандашом (удерживайте) + Error Ошибка + Even Fields Чётные поля + Exit Quick Mask Выйти из быстрой маски + Expand group (⌥-click: all groups) Развернуть группу (⌥+щелчок: все группы) + Export As Экспортировать как + Export As: {name} Экспортировать как: {name} + Export Contents Экспортировать содержимое + Export Image Экспортировать изображение + Export Options Параметры экспорта + Export Preferences Экспорт настроек + Export Settings Экспортировать настройки + Export to Экспортировать в + Export/Import Presets Экспорт/импорт наборов + Exported contents to {path} Содержимое экспортировано в {path} + Exported presets to {path} Наборы экспортированы в {path} + Exported {path} Экспортировано: {path} + Exported {path} ({size}) Экспортировано: {path} ({size}) + Exporting… Экспорт… + ExtraBold Сверхжирный + ExtraLight Сверхтонкий + Extrude Экструзия + Fade Ослабить + Faded Film Выцветшая плёнка + Failed Не удалось + Failed to open file Не удалось открыть файл + Failed to save file Не удалось сохранить файл + Fallback: {reason} Резерв: {reason} + Feather Radius Радиус растушёвки + Fiber Length Длина волокон + Fibers Волокна + Field Blur Размытие по полю + File Format Формат файла + File Info Сведения о файле + File Name Имя файла + File Size Размер файла + File not found Файл не найден + Filename Имя файла + Fill Color Цвет заливки + Fill Opacity Непрозрачность заливки + Fill Path… Залить контур… + Fill path with foreground color Залить контур цветом переднего плана + Fill with Background Color Залить цветом фона + Fill with Background Color, Preserve Transparency Залить цветом фона, сохранить прозрачность + Fill with Foreground Color Залить цветом переднего плана + Fill with Foreground Color, Preserve Transparency Залить цветом переднего плана, сохранить прозрачность + Film Grain Зернистость плёнки + Filter Gallery Галерея фильтров + Find Найти + Find and Replace Text Найти и заменить текст + Fine Тонкий + Fine Dots Мелкие точки + Finish Готово + Fit Image Подогнать изображение + Fit to Window По размеру окна + Flame Пламя + Flame Bottom Alignment Выравнивание низа пламени + Flame Lines Линии пламени + Flame Shape Форма пламени + Flame Style Стиль пламени + Flame Type Тип пламени + Flat Плоский + Flat Shading Плоское затенение + Flower Color Цвет цветков + Focal Distance Дистанция фокуса + Focus Фокус + Focus Area Область фокусировки + Font size Размер шрифта + Foreground Level Уровень переднего плана + Forward Вперёд + Forward Warp Прямая деформация + Frame Кадр + Frame {current} / {total} Кадр {current} / {total} + Free Transform Path Свободное трансформирование контура + Freeze Lasso Заморозить лассо + Freeze Mask Заморозить маску + Fresco Фреска + From От + From Left Слева + From Right Справа + Frosted Матовое + Full Screen Во весь экран + Fullscreen Полноэкранный + Fuzziness Разброс + GPU backend preference: {backend} Предпочитаемый сервер GPU: {backend} + GPU device was lost; using the CPU renderer. Устройство GPU потеряно; используется отрисовка на CPU. + GPU error; using the CPU renderer. Ошибка GPU; используется отрисовка на CPU. + GPU retry scheduled Запланирована повторная попытка GPU + Gamma Гамма + Gaussian Blur Размытие по Гауссу + General Blending Общее наложение + General Brushes Общие кисти + Generators Генераторы + Generic Общий + Glass Стекло + Global Light Глобальный свет + Gloss Глянец + Glow Amount Величина свечения + Glow Brightness Яркость свечения + Glow Size Размер свечения + Glowing Edges Светящиеся контуры + Good Хорошее + Grade Balance Цветокоррекция: баланс + Grade Blending Цветокоррекция: смешивание + Grade Global Цветокоррекция: общая + Grade Highlights Цветокоррекция: светлые тона + Grade Midtones Цветокоррекция: средние тона + Grade Shadows Цветокоррекция: тени + Gradient Options Параметры градиента + Grain Amount Величина зерна + Grain Roughness Шероховатость зерна + Grain Size Размер зерна + Grain Type Тип зерна + Graininess Зернистость + Grainy Dots Зернистые точки + Graphics adapter Графический адаптер + Graphics adapter: {adapter} Графический адаптер: {adapter} + Gray-gamma-2.2 Серый (гамма 2,2) + Greek Греческий + Greens Зелёные тона + Grid (⌘') Сетка (⌘') + Grid view Вид сеткой + Group from Layers Группа из слоёв + Groups Группы + Grout Width Ширина швов + Guide Layout… Схема направляющих… + HDR Toning HDR-тонирование + Hand Tool (hold) Инструмент «Рука» (удерживайте) + Hearts Сердца + Heptagon Семиугольник + Hexagon Шестиугольник + Hidden Скрыто + Hide Grid Скрыть сетку + Hide Others Скрыть остальные + Hide the layer's effects (⌥-click: all layers) Скрыть эффекты слоя (⌥+щелчок: все слои) + Hide {app} Скрыть {app} + High Pass Цветовой контраст + Highlight Света + Highlight Amount Величина светов + Highlight Area Область светов + Highlight Radius Радиус светов + Highlight Strength Сила светов + Highlight Tone Тон светов + Highlights from: Света от: + Horizontal Distortion Горизонтальная дисторсия + Horizontal Only Только по горизонтали + Horizontal scale Масштаб по горизонтали + Horizontal type Горизонтальный текст + Hotspot Горячая точка + Hsb HSB + Hsl HSL + Hsl Hue Оттенок HSL + Hsl Lum Яркость HSL + Hsl Sat Насыщенность HSL + Image Balance Баланс изображения + Image Processor Обработчик изображений + Image compositor: {processor} Компоновщик изображения: {processor} + Import Brushes Импортировать кисти + Import Brushes… (.abr) Импортировать кисти… (.abr) + Import Image Импортировать изображение + Import Options Параметры импорта + Import Photoshop brushes (.abr) Импортировать кисти Photoshop (.abr) + Import Settings Импортировать настройки + Importing {what} from {file}… Импорт {what} из {file}… + Importing… Импорт… + In Black Входной чёрный + In White Входной белый + Increase Brush Hardness Увеличить жёсткость кисти + Increase Brush Size Увеличить размер кисти + Increase Type Size Увеличить размер текста + Increase Type Size by 10 Увеличить размер текста на 10 + Indent first line Отступ первой строки + Indent left margin Отступ слева + Indent right margin Отступ справа + Indexed Индексированный + Infinite Бесконечный + Information Информация + Initial Direction Начальное направление + Ink Outlines Обводка контура + Inner Bevel Внутренняя фаска + Input Вход + Input Levels Входные уровни + Input Mode Режим ввода + Install Plug-in Установить плагин + Intensity Интенсивность + Intent Метод преобразования + Interpret Footage Интерпретировать материал + Intersect with selection (⇧⌥) Пересечение с выделенной областью (⇧⌥) + Interval Интервал + Invalid panel layout; restored the default dock Недопустимый макет панелей; восстановлена пристыковка по умолчанию + Invert Mask Инвертировать маску + Invert Selection Инвертировать выделенную область + Iris Blur Диафрагменное размытие + Ivy Плющ + Jag Зубчатость + Join the ArtCraft Discord Discord сообщества ArtCraft + Join the ArtCraft Discord ({url}) Присоединиться к Discord ArtCraft ({url}) + Keeps the current texture when switching presets. Сохраняет текущую текстуру при смене набора. + Kernel Ядро + Kerning: Metrics, Optical or a value in 1/1000 em (Alt+←/→ at the caret) Кернинг: метрический, оптический или значение в 1/1000 em (Alt+←/→ у курсора) + Keyboard Клавиатура + Lab Lab + Landscape, 10 x 8 Альбомная, 10 x 8 + Landscape, 6 x 4 Альбомная, 6 x 4 + Landscape, 7 x 5 Альбомная, 7 x 5 + Latin 1 Латиница 1 + Latin Extended-A Латиница расширенная A + Latin Extended-B Латиница расширенная B + Layer Comps to Files Варианты слоя в файлы + Layer Content Options Параметры содержимого слоя + Layer Count Число слоёв + Layer From Background Слой из фона + Layer Knocks Out Drop Shadow Слой выбивает тень + Layer Mask Hides Effects Маска слоя скрывает эффекты + Layer Properties Свойства слоя + Layer Styles Стили слоя + Layer from Background Слой из фона + Layers to Files Слои в файлы + Layout Макет + Leading (set to the font size × auto-leading when Auto) Интерлиньяж (при значении «Авто» равен размеру шрифта × автоматический интерлиньяж) + Leaf Color Цвет листьев + Learn More Подробнее + Leaves Amount Количество листьев + Leaves Color Цвет листьев + Leaves Size Размер листьев + Left Diagonal Левая диагональ + Legacy Устаревший + Legal Legal + Lens Blur Размытие при малой глубине + Lens Correction Коррекция дисторсии + Lens Flare Блик от объектива + Less Меньше + Letter Letter + Level Based На основе уровней + Library Библиотека + Light Dark Balance Баланс светлого и тёмного + Light Direction Направление света + Light Intensity Интенсивность света + Light Rough Светлый шероховатый + Light Type Тип света + Light X Свет X + Light Y Свет Y + Light Z Свет Z + Lighten Grout Осветлить швы + Lighten Only Только осветление + Lighting Освещение + Lighting Effects Эффекты освещения + Linear Dodge Линейный осветлитель + Linear Height Линейная высота + Linear-srgb Линейный sRGB + Link Связать + Liquify Пластичность + List Список + List view Вид списком + Live Preview Живой просмотр + Load 3D LUT Загрузить 3D LUT + Load Files into Stack Загрузить файлы в стопку + Load path as a selection Загрузить контур как выделенную область + Loading… Загрузка… + Lock All Заблокировать всё + Lock Image Pixels Заблокировать пиксели изображения + Lock Layers Заблокировать слои + Lock Position Закрепить положение + Lock Transparent Pixels Заблокировать прозрачные пиксели + Locked Заблокировано + Long Lines Длинные линии + Long Strokes Длинные штрихи + Lorem Ipsum Lorem Ipsum + Lower Нижний + Lum Ярк. + Lut LUT + Magentas Пурпурные тона + Make Selection from Path Создать выделенную область из контура + Make Selection… Создать выделенную область… + Make Symmetry Path Создать контур симметрии + Make Work Path… Создать рабочий контур… + Make work path from selection Создать рабочий контур из выделенной области + Manual Вручную + Map Document Документ карты + Map Layer Слой карты + Map Path Путь к карте + Mask Channel Канал-маска + Mask Incomplete Blocks Маскировать неполные блоки + Mask None Без маски + Masks Маски + Match Color Подбор цвета + Math Symbols Математические символы + Max Offset Макс. смещение + Max Radius Макс. радиус + Measurement Scale Масштаб измерений + Measurement Scale Marker Маркер масштаба измерений + Medium Dots Средние точки + Medium Gray Средне-серый + Medium Lines Средние линии + Medium Strokes Средние штрихи + Merge Channels… Объединить каналы… + Merge Spot Channel Объединить цветовой канал + Merge to HDR Pro Объединить в HDR Pro + Metallic Металлический + Metrics Метрики + Mezzotint Меццотинто + Middle Середина + Midtone Полутон + Monitor Profile Профиль монитора + Monitor profile in use on {display}: {profile} Используемый профиль монитора на {display}: {profile} + Monitor profile in use: {profile} Используемый профиль монитора: {profile} + Monitor profile on {display} (main window): {profile} Профиль монитора для {display} (главное окно): {profile} + Monitor profile on {display}: {profile} Профиль монитора для {display}: {profile} + Monitor profile: {profile} Профиль монитора: {profile} + Mono Contrast Монохромный контраст + Monochromatic Монохромный + More Больше + Mosaic Мозаика + Mosaic Tiles Мозаичные плитки + Motion Blur Размытие в движении + Mouse Мышь + Move Layer Переместить слой + Movie Prime Киносъёмочный фикс + Multiple Flames Along Path Несколько языков пламени вдоль контура + Multiple Flames One Direction Несколько языков пламени в одном направлении + Multiple Flames Path Directions Несколько языков пламени по направлениям контура + Multiple Flames Various Length Несколько языков пламени разной длины + My Brushes Мои кисти + Native file drag-and-drop is not supported on Wayland yet. Use File › Open, or run PhotoCraft under XWayland with `WAYLAND_DISPLAY= photocraft`. Перетаскивание файлов ещё не поддерживается в Wayland. Используйте «Файл › Открыть» или запустите PhotoCraft под XWayland с `WAYLAND_DISPLAY= photocraft`. + Nearest Ближайший + Neon Glow Неоновое свечение + Neutralize Нейтрализовать + Neutrals Нейтральные тона + New Brush Preset Новый набор параметров кисти + New Channel… Новый канал… + New Guide Новая направляющая + New Guide Layout Схема направляющих + New Spot Channel… Новый цветовой канал… + New Video Layer from File Новый видеослой из файла + New Window Новое окно + New Workspace Новая рабочая среда + New document Новый документ + New document… Новый документ… + New document… ⌘N Новый документ… ⌘N + Next Далее + No Document Нет документа + No Image Нет изображения + No Layers Нет слоёв + No Results Нет результатов + No Results Found Ничего не найдено + No Selection Нет выделенной области + No custom shape "{name}": pick one in the options bar or Window › Shapes Нет произвольной фигуры «{name}»: выберите её на панели параметров или в меню «Окно › Фигуры» + No layers Нет слоёв + No recent files Нет недавних файлов + No selection Нет выделенной области + Noise Color Цветовой шум + Noise Color Detail Цветовая детализация шума + Noise Luminance Яркостный шум + Noise Luminance Detail Яркостная детализация шума + Nothing to Redo Нечего повторить + Nothing to Undo Нечего отменить + Nothing to paste: the clipboard holds no image Нечего вставлять: в буфере обмена нет изображения + Number Of Levels Число уровней + Numbers Числа + Ocean Ripple Океанские волны + Octagon Восьмиугольник + Odd Fields Нечётные поля + Oil Paint Масляная живопись + On Вкл. + One Flame Along Path Один язык пламени вдоль контура + Open As Открыть как + Open File Открыть файл + Open Folder Открыть папку + Open Image Открыть изображение + Open Recent is unavailable on the web «Открыть недавние» недоступно в веб-версии + Opened {url} Открыто: {url} + Opening Открытие + Open… ⌘O Открыть… ⌘O + Operation canceled Операция отменена + Operation cancelled Операция отменена + Operation completed Операция завершена + Operation failed Операция не выполнена + Optical Оптический + Original Ratio Исходное соотношение + Out Black Выходной чёрный + Out From Center От центра наружу + Out Of Gamut Вне охвата + Out White Выходной белый + Out of memory Недостаточно памяти + Outer Bevel Внешняя фаска + Output Levels Выходные уровни + Oval Овал + Overlay Edge Наложение края + Overlay: Rule of Thirds Наложение: правило третей + PSD PSD + Package Упаковать + Page Setup… Параметры страницы… + Paint Daubs Мазки краской + Paints from the document's opening state Рисует по состоянию документа на момент открытия + Palette Knife Шпатель + Panel Панель + Paper Бумага + Paper Brightness Яркость бумаги + Parallel Параллельно + Paste Complete Stroke Вставить полный контур + Paste Fill Вставить заливку + Patchwork Лоскутное шитьё + Path Blur Размытие по контуру + Paths to Illustrator Контуры в Illustrator + Pattern Fill… Заливка узором… + Pattern Type Тип узора + Pencil Width Ширина карандаша + Pentagon Пятиугольник + Percentage Процент + Permission denied Доступ запрещён + PhotoCraft keeps retrying; until a save succeeds, preference changes are lost when it closes. PhotoCraft продолжает попытки; пока сохранение не удастся, изменения настроек будут потеряны при закрытии. + Photomerge Фотослияние + Pick Color From Image Выбрать цвет на изображении + Picture Frame Рамка + Pillow Emboss Подушковидное тиснение + Pinch Сжатие + Pins Булавки + Place Embedded Поместить встроенный + Place Linked Поместить связанный + Place Scale Marker Шкала масштаба + Plane Плоскость + Planes Плоскости + Plastic Wrap Пластиковая упаковка + Platform: {os} {arch} Платформа: {os} {arch} + Please wait… Подождите… + Point Точка + Point Curve Точечная кривая + Point Curve Blue Точечная кривая: синяя + Point Curve Green Точечная кривая: зелёная + Point Curve Red Точечная кривая: красная + Pointed Заострённый + Pointillize Пуантилизм + Polar Coordinates Полярные координаты + Polar To Rectangular Полярные в прямоугольные + Policy Cmyk Правила для CMYK + Policy Gray Правила для градаций серого + Policy Rgb Правила для RGB + Pond Ripples Волны на пруду + Portrait, 4 x 6 Книжная, 4 x 6 + Portrait, 5 x 7 Книжная, 5 x 7 + Postcard Открытка + Poster Плакат + Poster Edges Плакатные края + Posterization Постеризация + Preferences can't be saved Не удаётся сохранить настройки + Preferences saved Настройки сохранены + Preferences were reset: {error} Настройки сброшены: {error} + Preferences… Настройки… + Preserve Сохранить + Preset Manager Управление наборами + Press keys Нажмите клавиши + Preview Image Просмотр изображения + Previous Назад + Prime105 Prime 105 + Prime35 Prime 35 + Printed to {path} Напечатано в {path} + Pro (Dark) Pro (тёмная) + Pro (Medium Gray) Pro (средне-серая) + Processing… Обработка… + Profile Профиль + Proof Setup… Настройка пробного отпечатка… + Prophoto-compat ProPhoto RGB (совместимо) + Pucker Сморщивание + Push Left Сдвиг влево + Pyramids Пирамиды + Quick Actions Быстрые действия + Quit Выход + Quit PhotoCraft Выйти из PhotoCraft + Quit {app} Выйти из {app} + R G B R G B + R g b R g b + Radial Радиальный + Radial Blur Радиальное размытие + Radius X Радиус X + Radius Y Радиус Y + Ran script {name} Выполнен сценарий {name} + Randomize Length Случайная длина + Randomize Shapes Случайные фигуры + Rasterize Fill Content Растрировать содержимое заливки + Rasterize Smart Object Растрировать смарт-объект + Ratio Соотношение + Raw Defaults Значения RAW по умолчанию + Reading Чтение + Ready Готово + Rec2020 Rec. 2020 + Recent Files Недавние файлы + Reconstruct All Восстановить всё + Recovered {name} Восстановлено: {name} + Rectangular Selection Прямоугольная выделенная область + Rectangular To Polar Прямоугольные в полярные + Red Cyan Красный–голубой + Reds Красные тона + Reduce Color Noise Уменьшить цветовой шум + Reduce Noise Уменьшить шум + Reflected Отражение + Relief Рельеф + Relink to File Повторно связать с файлом + Remove All Удалить всё + Remove Jpeg Artifact Удалить артефакты JPEG + Rename Brush Переименовать кисть + Rename Channel… Переименовать канал… + Rename Group Переименовать группу + Rename Tool Preset Переименовать набор + Render Video Экспорт видео + Rendering… Отрисовка… + Reorder Layer Изменить порядок слоя + Replace Color Замена цвета + Replace Contents Заменить содержимое + Replace Footage Заменить материал + Report a Bug Сообщить об ошибке + Report an Issue Сообщить о проблеме + Reset Alignment Сбросить выравнивание + Reset All Сбросить всё + Reset Preferences Сбросить настройки + Resolve Missing Fonts Устранить отсутствующие шрифты + Restart Перезапустить + Restart Required Требуется перезапуск + Resume Продолжить + Retry Повторить + Rgb RGB + Ridges Гребни + Right Diagonal Правая диагональ + Ripple Рябь + Ripple Magnitude Величина ряби + Ripple Size Размер ряби + Rope Верёвка + Roses Розы + Rough Pastels Пастель + Row Order Порядок строк + Rulers (⌘R) Линейки (⌘R) + Sample Size Размер образца + Sampled Colors Отобранные цвета + Sandstone Песчаник + Sat Насыщ. + Save As Сохранить как + Save Changes Сохранить изменения + Save File Сохранить файл + Save Image Сохранить изображение + Save Path Сохранить контур + Save Pixel Aspect Ratio Сохранить соотношение сторон пикселя + Save a Copy Сохранить копию + Save before closing? Сохранить перед закрытием? + Save for Web (Legacy) Сохранить для Web (устаревшее) + Save your work and restart PhotoCraft to retry GPU acceleration. CPU rendering remains active for this session. Сохраните работу и перезапустите PhotoCraft, чтобы повторить попытку ускорения GPU. Отрисовка на CPU остаётся активной в этом сеансе. + Saved a copy as {path} Копия сохранена как {path} + Saved {path}; image assets: {n} in {dir} Сохранено: {path}; графические ресурсы: {n} в {dir} + Saving… Сохранение… + Scale Effects Масштабировать эффекты + Scaling Масштабирование + Scallops Фестоны + Script Events Manager Диспетчер событий сценариев + Scroll menu down Прокрутить меню вниз + Scroll menu up Прокрутить меню вверх + Search commands Поиск команд + Search commands (⌘K) Поиск команд (⌘K) + Search commands, tools and panels Поиск команд, инструментов и панелей + Search menus Поиск по меню + Seed Зерно + Select Custom Color… Выбрать свой цвет… + Select Data Points Выбрать точки данных + Select Layer Выбрать слой + Select and Mask Выделение и маска + Selected at launch: {backend} Выбрано при запуске: {backend} + SemiBold Полужирный + Sensitivity Чувствительность + Sent to the printer ({spooler}) Отправлено на принтер ({spooler}) + Sepia Сепия + Services Службы + Set Colors Задать цвета + Set Measurement Scale Задать масштаб измерений + Sgray sGray + Shadow Тень + Shadow Amount Величина теней + Shadow Intensity Интенсивность теней + Shadow Radius Радиус теней + Shadow Tone Тон теней + Shadows up to: Тени до: + Shadows/Highlights Тени/Света + Shallow Поверхностный + Shape Blur Размытие по фигуре + Sharpen Amount Величина резкости + Sharpen Detail Детализация резкости + Sharpen Details Резкость деталей + Sharpen Masking Маскирование резкости + Sharpen Radius Радиус резкости + Shear Искривление + Shine Блеск + Short Lines Короткие линии + Short Strokes Короткие штрихи + Shortcut Сочетание клавиш + Shortcuts Сочетания клавиш + Show All Показать все + Show Extras Options Параметры вспомогательных элементов + Show Guides Показать направляющие + Show Guides (⌘;) Показать направляющие (⌘;) + Show Rulers Показать линейки + Show the layer's effects (⌥-click: all layers) Показать эффекты слоя (⌥+щелчок: все слои) + Simple Простой + Simulate Paper Имитировать бумагу + Sine Синусоида + Sketch Эскиз + Skip Пропустить + Slice Options Параметры фрагмента + Smart Blur Умное размытие + Smart Sharpen Умная резкость + Smoothness Сглаженность + Smudge Stick Растушёвка + Snap to Grid Привязать к сетке + Snowflakes Снежинки + Soften Смягчить + Softness Мягкость + Software adapter Программный адаптер + Solarized Dark Solarized (тёмная) + Solid Color Однотонный цвет + Solid Front Faces Сплошные передние грани + Source Channel Канал-источник + Source Invert Инвертировать источник + Source Layer Исходный слой + Source1 Channel Канал источника 1 + Source1 Invert Инвертировать источник 1 + Source2 Channel Канал источника 2 + Source2 Invert Инвертировать источник 2 + Source: set Источник: задан + Space Пробел + Sparkle Искры + Spatter Разбрызгивание + Speckle Крапинки + Speed Скорость + Spherize Сферизация + Spin Вращение + Spin Blur Размытие вращением + Split Channels Разделить каналы + Spot Точечный + Spray Radius Радиус распыления + Sprayed Strokes Разбрызганные штрихи + Spread Разброс + Sprinkles Вкрапления + Square Size Размер квадрата + Square, 12 x 12 Квадратная, 12 x 12 + Square, 5 x 5 Квадратная, 5 x 5 + Squareness Квадратность + Stagger Смещение + Stained Glass Витраж + Star Звезда + Stars Звёзды + Start X Начало X + Start Y Начало Y + Statistics Статистика + Step {step} ({id}) failed: {error} Шаг {step} ({id}) не выполнен: {error} + Stippled Крапчатый + Stops Контрольные точки + Strength Сила + Stretch Растянуть + Stroke Color Цвет обводки + Stroke Detail Детализация обводки + Stroke Direction Направление обводки + Stroke Length Длина обводки + Stroke Pressure Нажим обводки + Stroke Size Размер обводки + Stroke Width Ширина обводки + Stroke path with brush Обвести контур кистью + Strokes Штрихи + Studio (Dark) Studio (тёмная) + Studio (Light) Studio (светлая) + Stylization Стилизация + Subtract Shapes at Overlap Вычесть фигуры при перекрытии + Subtract from selection (⌥) Вычесть из выделенной области (⌥) + Sumi-e Суми-э + Surface Blur Размытие по поверхности + Switch Foreground and Background Colors Поменять цвета переднего плана и фона + System Info Информация о системе + Tablet Планшет + Tabloid Tabloid + Taper Сужение + Target Цель + Target Channel 3 Целевой канал 3 + Target Channel 4 Целевой канал 4 + Target Channel 5 Целевой канал 5 + Target Channel 6 Целевой канал 6 + Target Channel 7 Целевой канал 7 + Target Channel 8 Целевой канал 8 + Target Channel 9 Целевой канал 9 + Target Composite Channel Целевой составной канал + Target X Цель X + Target Y Цель Y + Teal Orange Бирюзовый–оранжевый + Technology Previews… Пробные технологии… + Temporary Временный + Text Layer Текстовый слой + Texture Coverage Покрытие текстуры + Texturizer Текстуризатор + Thaw Mask Разморозить маску + The canvas is shown as sRGB. Choose a monitor profile in Edit › Color Settings. Холст отображается как sRGB. Выберите профиль монитора в разделе «Редактирование › Настройки цвета». + The canvas on {display} is shown as sRGB. Choose a monitor profile in Edit › Color Settings. Холст на {display} отображается как sRGB. Выберите профиль монитора в разделе «Редактирование › Настройки цвета». + The current gradient (pick one in Window › Gradients) Текущий градиент (выберите в меню «Окно › Градиенты») + The document does not have an embedded colour profile. Working space: {working}. Документ не содержит встроенного цветового профиля. Рабочее пространство: {working}. + The document has an embedded colour profile that does not match the working space.\nEmbedded: {embedded}\nWorking: {working} Документ содержит встроенный цветовой профиль, не совпадающий с рабочим пространством.\nВстроенный: {embedded}\nРабочее пространство: {working} + The file could not be opened. Не удалось открыть файл. + The file could not be saved. Не удалось сохранить файл. + Thickness Толщина + This Layer: Этот слой: + This Smart Object must be rasterized before proceeding. Its contents will no longer be editable. Rasterize the Smart Object? Перед продолжением необходимо растрировать этот смарт-объект. Его содержимое перестанет быть редактируемым. Растрировать смарт-объект? + This adjustment ({key}) is kept as imported and can't be edited yet. Эта коррекция ({key}) сохраняется в импортированном виде и пока не может быть изменена. + This fill layer must be rasterized before proceeding. Its fill content will no longer be editable. Rasterize the layer? Перед продолжением необходимо растрировать этот слой заливки. Содержимое заливки перестанет быть редактируемым. Растрировать слой? + This layer must be rasterized before proceeding. Перед продолжением необходимо растрировать этот слой. + This session: {reason} Этот сеанс: {reason} + This shape layer must be rasterized before proceeding. It will no longer be editable as a shape. Rasterize the shape? Перед продолжением необходимо растрировать этот слой фигуры. Он перестанет быть редактируемым как фигура. Растрировать фигуру? + This type layer must be rasterized before proceeding. Its text will no longer be editable. Rasterize the type? Перед продолжением необходимо растрировать этот текстовый слой. Его текст перестанет быть редактируемым. Растрировать текст? + Tile Size Размер плитки + Tiles Плитки + Tilt-Shift Наклон-сдвиг + Tint Color Цвет тонирования + Tiny Lens Маленькая линза + Title Заголовок + To До + To Center К центру + Toggle Layer Mask Overlay Переключить наложение маски слоя + Toggle the Brush Settings panel Переключить панель «Параметры кисти» + Toggle the Brush Settings panel (F5) Переключить панель «Параметры кисти» (F5) + Tool Options Параметры инструмента + Trace Contour Обводка контуров + Tracking (1/1000 em) Трекинг (1/1000 em) + Transformation Трансформирование + Transition Переход + Transparency Shapes Layer Прозрачность задаёт форму слоя + Trap Приводка + Tree Дерево + Trim Обрезать + Turbulent Турбулентность + Twirl Скручивание + Twirl Clockwise Скручивание по часовой стрелке + Twirl Counterclockwise Скручивание против часовой стрелки + Type Options Параметры текста + Unaltered Без изменений + Undefined Areas Неопределённые области + Underlying Layer: Нижележащий слой: + Underpainting Подмалёвок + Underwater Под водой + Ungroup Разгруппировать + Uniform Однородный + Unite Shapes at Overlap Объединить фигуры при перекрытии + Unlink Разъединить + Unlock Разблокировать + Unlocked Разблокировано + Unsaved Changes Несохранённые изменения + Unsharp Mask Нерезкое маскирование + Unsupported file format Неподдерживаемый формат файла + Unsupported format Неподдерживаемый формат + Up Вверх + Update Обновить + Updates Обновления + Upper Верхний + Use Custom Color Использовать свой цвет + Use File › Open to open an image. Используйте «Файл › Открыть», чтобы открыть изображение. + Use Selection In Source Использовать выделение в источнике + Use Selection In Target Использовать выделение в цели + Uses the foreground and background colors. Использует цвета переднего плана и фона. + Vanishing Point Точка схода + Variable Data Sets Наборы данных переменных + Vector Mask Hides Effects Векторная маска скрывает эффекты + Vertical Distortion Вертикальная дисторсия + Vertical Only Только по вертикали + Vertical Type Tool Инструмент «Вертикальный текст» + Vertical scale Масштаб по вертикали + Vertical type Вертикальный текст + Video Frames to Layers Видеокадры в слои + Vignette Amount Величина виньетки + Vignette Feather Растушёвка виньетки + Vignette Highlights Светлые тона виньетки + Vignette Midpoint Средняя точка виньетки + Vignette Roundness Округлость виньетки + Vignette Style Стиль виньетки + Vine Color Цвет лозы + Vine With Flowers Лоза с цветами + Vine With Leaves Лоза с листьями + Violent Сильный + W: Ш: + WIA Support Поддержка WIA + Warm Тёплый + Warming Filter (85) Согревающий фильтр (85) + Warming L B A Согревающий (LBA) + Warming81 Согревающий (81) + Warming85 Согревающий (85) + Warmth Теплота + Warning Предупреждение + Warp Text Деформация текста + Watercolor Акварель + Watercolour look: lighter interior, darker rims. Акварельный вид: светлее внутри, темнее по краям. + Wavelength Max Макс. длина волны + Wavelength Min Мин. длина волны + Waves Волны + Web Large Web (крупный) + Web Minimum Web (минимальный) + Web Most Common Web (наиболее распространённый) + WebP (lossless) WebP (без потерь) + Wetness Влажность + White Clip Отсечение белого + White Intensity Интенсивность белого + White Is High Белое — высокое + Whole Word Слово целиком + Wide Blurry Широкий размытый + Wide Sharp Широкий резкий + Wind Ветер + Window renderer: {renderer} Отрисовка окна: {renderer} + Working Рабочее + Working Cmyk Рабочий CMYK + Working Gray Рабочий: градации серого + Working Rgb Рабочий RGB + Working-cmyk Рабочий CMYK + Workspaces Рабочие среды + Wrap Оборачивание + Yellows Жёлтые тона + ZigZag Зигзаг + Zigzag Зигзаг + Zoom In (hold, drag to scrub) Увеличение (удерживайте, перетащите для прокрутки) + Zoom Out (hold) Уменьшение (удерживайте) + auto авто + blue синий + brushes кисти + fallback резерв + gradients градиенты + gray серый + green зелёный + layer mask маска слоя + manual вручную + none нет + off выключено + on включено + orange оранжевый + previous reading kept; the last re-read failed: {reason} сохранено предыдущее значение; последнее повторное чтение не удалось: {reason} + red красный + unknown неизвестно + unknown type неизвестный тип + vector mask векторная маска + violet фиолетовый + yellow жёлтый + {index} of {total} {index} из {total} + {label} is not available: {reason} {label} недоступно: {reason} + {message} (notes: {n}; {warning}) {message} (примечаний: {n}; {warning}) + {shortcut} assigned. {shortcut} назначено. + {shortcut} is already in use by {commands} and will be removed from it when you click OK. {shortcut} уже используется командой {commands} и будет удалено из неё при нажатии «ОК». + {verb} {mask} {layer} {verb} {mask} {layer} + ↑↓ navigate ↵ run esc close ↑↓ навигация ↵ выполнить esc закрыть + ✓ Color Indicates Masked Areas ✓ Цветом обозначены маскированные области + ✓ Color Indicates Selected Areas ✓ Цветом обозначены выделенные области + + +# Dynamic command-id, plural, and contextual labels (fourth pass). +@id edit.clear Очистить +@id edit.purge.undo Данные отмены +@id edit.stroke Обвести… +@id edit.transform.scale Масштабирование +@id file.close Закрыть +@id filter.sharpen.sharpen Резкость +@id layer.layerMask.apply Применить +@id select.all Выделить всё +@plural Imported {n} brush from {file}|Imported {n} brushes from {file} Импортирована {n} кисть из {file}|Импортированы {n} кисти из {file}|Импортировано {n} кистей из {file} +@plural Imported {n} gradient from {file}|Imported {n} gradients from {file} Импортирован {n} градиент из {file}|Импортированы {n} градиента из {file}|Импортировано {n} градиентов из {file} +@plural Imported {n} pattern from {file}|Imported {n} patterns from {file} Импортирован {n} узор из {file}|Импортированы {n} узора из {file}|Импортировано {n} узоров из {file} +cameraRaw Balance Баланс +cameraRaw Clarity Чёткость +cameraRaw Color Цвет +cameraRaw Color Mixer Смеситель цветов +cameraRaw Contrast Контраст +cameraRaw Curve Кривая +cameraRaw Dehaze Удаление дымки +cameraRaw Detail Детализация +cameraRaw Effects Эффекты +cameraRaw Exposure Экспозиция +cameraRaw Feather Растушёвка +cameraRaw Highlights Света +cameraRaw Luminance Яркость +cameraRaw Masking Маскирование +cameraRaw Midpoint Средняя точка +cameraRaw Midtones Средние тона +cameraRaw Radius Радиус +cameraRaw Roundness Округлость +cameraRaw Saturation Насыщенность +cameraRaw Shadows Тени +cameraRaw Size Размер +cameraRaw Temperature Температура +cameraRaw Texture Текстура +cameraRaw Tint Оттенок +cameraRaw Whites Белые точки +colorRange Invert Инвертировать +document Orientation Ориентация +layerLabel Magenta Пурпурный +layerLabel No Color Без цвета +layerLabel Red Красный +layers Fill Заливка +layers Fill: Заливка: +navigator Zoom Масштаб +shape Appearance Оформление diff --git a/docs/gaps.md b/docs/gaps.md index faf0884c89..f48c5afae3 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -1,7 +1,6 @@ # Where PhotoCraft falls short of Photoshop -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (G1: smart filter masks show on PSD open, Photoshop-oracle floor 188/258; G10: DPX read and write, #1049, Cineon read; G1: CMYK Hue/Saturation and Color Balance keep the black ink; G1: CMYK Posterize on the stored values, psd-tools oracle floor 242/309; G1: CMYK adjustment layers composite on the document's inks, Photoshop-oracle floor 165/258; G1: CMYK Brightness/Contrast on the inks, CMYK Threshold's luma; G1: modern Brightness/Contrast's brightness curve matches Photoshop, Photoshop-oracle floor 151/258; G1: Lab documents composite on their stored L, a, b, Photoshop-oracle floor 152/258; G1: Levels on whole levels at every step, Photoshop-oracle floor 146/258, psd-tools 238/309; G1: 32-bit Brightness/Contrast legacy, Channel Mixer, Photo Filter and Black & White tint unclipped as in Photoshop, Photoshop-oracle floor 149/258; G1: mask feathers blur with their exact variance, psd-tools oracle floor 240/309; G1: Threshold splits on Photoshop's luminosity, psd-tools oracle floor 241/309; G1: pass-through clipping base with an adjustment renders isolated, psd-tools oracle floor 240/309; G1: 16-bit Posterize as Photoshop bins it, psd-tools oracle floor 241/309; G1: Gradient Map driven by the W3C luminosity, Photoshop-oracle floor 147/258; G7: Field Blur's on-canvas pins, #3373; G4: Hue Jitter amount track, #3825 partial; G4: minimum field interaction, #3825 partial; G1: CMYK Invert, Posterize and Threshold on the inks, 16-bit Lab Invert; Photoshop-oracle floor 145/258; G11: `.atn` import maps 112 events: Select and Mask, Filter Gallery Spatter, Ripple, Crystallize, Pattern Fill layers, mask targeting, Step Backward, nudges in points, Bitmap mode; G5: Free Transform H/V skew fields; G9: toolbar tool shortcut editing; refreshed against main: G2, G5, G7, G9, G10, G13, G14, G17 items that landed; G10: DPX read and write, #1049; G4: checkbox-only Brush Settings rows; G11: Load/Save Actions `.atn` landed, AUTO-219-2 partial; G5: hold-a-key spring-loaded tools landed, #2672; G1: #2465 fixed; stale missing-tool rows corrected: Color Replacement, Red Eye and Add/Delete Anchor Point are live tools, so only Art History Brush is missing; no estimate changed) · **Target:** Adobe Photoshop 2026 - +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-15 · **Change:** minor (G13: Russian gained the 1,233 dynamic UI labels other catalogs already had; no estimate changed; previously minor: G1: smart filter masks show on PSD open, Photoshop-oracle floor 188/258; G10: DPX read and write, #1049, Cineon read; G1: CMYK Hue/Saturation and Color Balance keep the black ink; G1: CMYK Posterize on the stored values, psd-tools oracle floor 242/309; G1: CMYK adjustment layers composite on the document's inks, Photoshop-oracle floor 165/258; G1: CMYK Brightness/Contrast on the inks, CMYK Threshold's luma; G1: modern Brightness/Contrast's brightness curve matches Photoshop, Photoshop-oracle floor 151/258; G1: Lab documents composite on their stored L, a, b, Photoshop-oracle floor 152/258; G1: Levels on whole levels at every step, Photoshop-oracle floor 146/258, psd-tools 238/309; G1: 32-bit Brightness/Contrast legacy, Channel Mixer, Photo Filter and Black & White tint unclipped as in Photoshop, Photoshop-oracle floor 149/258; G1: mask feathers blur with their exact variance, psd-tools oracle floor 240/309; G1: Threshold splits on Photoshop's luminosity, psd-tools oracle floor 241/309; G1: pass-through clipping base with an adjustment renders isolated, psd-tools oracle floor 240/309; G1: 16-bit Posterize as Photoshop bins it, psd-tools oracle floor 241/309; G1: Gradient Map driven by the W3C luminosity, Photoshop-oracle floor 147/258; G7: Field Blur's on-canvas pins, #3373; G4: Hue Jitter amount track, #3825 partial; G4: minimum field interaction, #3825 partial; G1: CMYK Invert, Posterize and Threshold on the inks, 16-bit Lab Invert; Photoshop-oracle floor 145/258; G11: `.atn` import maps 112 events: Select and Mask, Filter Gallery Spatter, Ripple, Crystallize, Pattern Fill layers, mask targeting, Step Backward, nudges in points, Bitmap mode; G5: Free Transform H/V skew fields; G9: toolbar tool shortcut editing; refreshed against main: G2, G5, G7, G9, G10, G13, G14, G17 items that landed; G10: DPX read and write, #1049; G4: checkbox-only Brush Settings rows; G11: Load/Save Actions `.atn` landed, AUTO-219-2 partial; G5: hold-a-key spring-loaded tools landed, #2672; G1: #2465 fixed; stale missing-tool rows corrected: Color Replacement, Red Eye and Add/Delete Anchor Point are live tools, so only Art History Brush is missing; no estimate changed) · **Target:** Adobe Photoshop 2026 Every known shortfall against Photoshop 2026 (27.11.0), one entry each, ranked by user impact on the way to beta. This is the work list agents pick from: take the highest-ranked entry that nobody is working on, and when you close part of one, update its evidence and estimate here and @@ -254,6 +253,10 @@ what an agent can't finish alone. as tofu (#1408, #1693). - **Evidence (2026-10-11):** Czech dynamic UI wrappers, hardware labels and composed messages translate at display sites; engine errors remain English. This does not close G13. +- **Evidence (2026-10-15):** Russian gained the 1,233 dynamic labels the other catalogs already + had (filter and adjustment parameters, Camera Raw, lighting, History/Layers/paths menus, + metadata, System Info, status templates, dialogs), with a terminology note + ([localization-ru](localization-ru.md)); engine errors remain English. This does not close G13. - **Detail:** [`localization-parity.md`](localization-parity.md). - **Estimate:** 80–140 h. **Human:** native-speaker review of every catalog (none is recorded). @@ -270,6 +273,7 @@ what an agent can't finish alone. | Date | Change | Summary | |---|---|---| +| 2026-10-15 | minor | G13: Russian gained the 1,233 dynamic UI labels other catalogs already had (filter and adjustment parameters, Camera Raw, lighting, History/Layers/paths menus, metadata, System Info, status templates, dialogs); engine messages and native review remain open | | 2026-10-14 | minor | G1: a PSD smart object's filter mask shows when the file opens (Photoshop stores the filtered pixels unmasked); export stores them that way too; Photoshop-oracle floor 185 → 188 of 258 | | 2026-10-13 | minor | G14: D90 image area measured (4288 × 2848 at (10, 10)); gap remains open | | 2026-10-14 | minor | Corpus floors raised to the measured results of the CMYK, Lab, 32-bit, Levels, Threshold, Posterize, feather and pass-through fixes landed together: Photoshop-authored 165 → 185 of 258, io 146 → 148 of 170, psd-tools 242 → 249 of 309 | @@ -286,8 +290,7 @@ what an agent can't finish alone. | 2026-10-13 | minor | G1: Threshold splits on Photoshop's luminosity of whole samples (8- and 16-bit rules exact on four oracles); psd-tools threshold_rgb and threshold_16bits_rgb pass; psd-tools floor → 241 of 309 | | 2026-10-13 | minor | G1: a pass-through group that is a clipping base and holds an adjustment layer renders isolated; psd-tools passthrough_clipping_mask_adjustment passes; psd-tools floor → 240 of 309 | | 2026-10-13 | minor | G1: 16-bit Posterize bins the 16-bit sample, as Photoshop does (exact on two oracles); psd-tools floor → 241 of 309 | -| 2026-10-13 | minor | G1: Gradient Map driven by the W3C luminosity (0.3, 0.59, 0.11), as Photoshop is; rgb8/rgb16 gradient-map pass; Photoshop-oracle floor 145 → 147 of 258 | -| 2026-10-13 | minor | G6: stale "missing tool" list corrected against `main` — Color Replacement (`paint.colorReplacement`), Red Eye (`paint.redEye`) and Add/Delete Anchor Point (P flyout, #4144) are live tools, so the toolbox is 67 of 68 and Art History Brush is the only one missing; no percentage or estimate changed | +| 2026-10-13 | minor | G1: Gradient Map driven by the W3C luminosity (0.3, 0.59, 0.11), as Photoshop is; rgb8/rgb16 gradient-map pass; Photoshop-oracle floor 145 → 147 of 258 || 2026-10-13 | minor | G6: stale "missing tool" list corrected against `main` — Color Replacement (`paint.colorReplacement`), Red Eye (`paint.redEye`) and Add/Delete Anchor Point (P flyout, #4144) are live tools, so the toolbox is 67 of 68 and Art History Brush is the only one missing; no percentage or estimate changed | | 2026-10-13 | minor | G4: Hue Jitter amount-track appearance (#3825, partial); no readiness percentage or estimate changed | | 2026-10-13 | minor | G4: minimum inputs in Shape Dynamics and Transfer follow their Control (#3825, partial); no readiness percentage or estimate changed | | 2026-10-12 | minor | G5: Free Transform options bar H/V skew fields done | diff --git a/docs/localization-parity.md b/docs/localization-parity.md index 0b2be85595..5d6b974c88 100644 --- a/docs/localization-parity.md +++ b/docs/localization-parity.md @@ -1,10 +1,10 @@ # Localization parity -> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-13 · **Change:** minor (Spanish dynamic labels; previously minor: Serbian catalog) · **Target:** Adobe Photoshop 2026 +> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-18 · **Change:** minor (Russian dynamic labels; previously minor: Spanish dynamic labels) · **Target:** Adobe Photoshop 2026 Per-language status of PhotoCraft's UI. How catalogs work and how to add one: [`localization.md`](localization.md). Language notes: [Korean](localization-ko.md), [Spanish](localization-es.md), -[Simplified Chinese](localization-zh-hans.md), [Italian](localization-it.md). +[Simplified Chinese](localization-zh-hans.md), [Italian](localization-it.md), [Russian](localization-ru.md). **How measured.** `cargo xtask i18n-coverage` (`xtask/src/i18n_coverage.rs`) defines the key universe: `tl!("…")` literals and `trn(` plurals in `ui-egui` (tests excluded), menu-catalog @@ -46,8 +46,8 @@ speakers, ~55% ready. Remaining: 80–140 h, plus native-speaker review (recorde ## Other shipped languages 8 more, each at 1,608 / 1,608 measured keys, status `partial` for the same reason: Traditional -Chinese `zh-hant` (2,329 rows), Russian `ru` (2,544), Ukrainian `uk` (3,375), Czech `cs` (3,522), -Polish `pl` (3,366), Greek `el` (3,481), Dutch `nl` (3,568), Italian `it` (4,008; also every dynamic label the +Chinese `zh-hant` (2,329 rows), Russian `ru` (4,227 keys; also every dynamic label the +other catalogs translate, [notes](localization-ru.md)), Ukrainian `uk` (3,375), Czech `cs` (3,522), Polish `pl` (3,366), Greek `el` (3,481), Dutch `nl` (3,568), Italian `it` (4,164; also every dynamic label the other catalogs translate; reviewed by a native speaker, [notes](localization-it.md)). Turkish `tr` (3,731 rows) also covers every measured key. Serbian `sr` (4,022 rows, Latin script, Ekavian, added 2026-10-12) covers every @@ -87,3 +87,4 @@ engine localization or native review is complete. | 2026-10-12 | minor | Spanish: 1,276 runtime-only labels added (filter and adjustment parameters, Camera Raw, panels, dialogs and status wrappers); generated-label coverage test added | | 2026-10-12 | minor | Italian native-speaker review: mistranslations (Trap, HDR Toning, Display Text) and phrasing fixed | | 2026-10-12 | minor | Serbian (`sr`, Latin, Ekavian) catalog: every measured key plus the dynamic labels of the other catalogs | +| 2026-10-18 | minor | Russian: 1,229 dynamic labels added (filter and adjustment parameters, Camera Raw, lighting, History/Layers/paths menus, metadata, System Info, status templates, dialogs) with a terminology note ([localization-ru](localization-ru.md)) | diff --git a/docs/localization-ru.md b/docs/localization-ru.md new file mode 100644 index 0000000000..ea0eff64df --- /dev/null +++ b/docs/localization-ru.md @@ -0,0 +1,75 @@ +# Russian terminology and dynamic UI labels + +This note goes with the completion of the Russian catalog (`crates/ui-egui/src/i18n/ru.tsv`). +The catalog already covered every key `cargo xtask i18n-coverage` measures, but it lacked the +labels that only exist at runtime and that other catalogs (Italian, Serbian, Czech, Hungarian) +already translate: filter and adjustment parameter names, Camera Raw panels, lighting effects, +History / Layers / paths / channels menu labels, metadata fields, status messages, System Info +and dialog text. In Russian those showed in English. +They are now translated, reusing the vocabulary the catalog already uses elsewhere so the same +term reads the same everywhere. Translations are written from the meaning of PhotoCraft's English +labels; no proprietary translation resources, fonts, screenshots or user documents are included. + +## Register and style + +Commands are imperatives in the second person plural (*Сохранить*, *Удалить*, *Заново*); +where the catalog already uses a noun it is kept (*Уровни…*, *Кривые…*). Sentence case after the +first word, as in the rest of the catalog. Ellipses on dialog commands are preserved (`Ветер…` +→ `Ветер…`). Decimals use a comma (the file format's own labels keep their dots, e.g. +`Landscape, 10 x 8` stays `Альбомная, 10 x 8`). Latin product and technology names are not +translated (PhotoCraft, Camera Raw, RGB, CMYK, Lab, LUT, WebP); Camera Raw keeps `.xmp`; the +`HDR`, `HSL`, `HSB` and `W3C` acronyms and the `sGray`/`Pro Photo` identifiers stay Latin. + +## Terminology + +Existing choices were followed rather than re-invented, so the new rows use: + +| English source | Russian | Note | +|---|---|---| +| Highlight(s) | Света | already in the catalog (`Highlights` → `Света`) | +| Midtone(s) | Полутона | already in the catalog | +| Shadow(s) | Тени | pairs with *Света* | +| Feather | Растушёвка | never *перо* | +| Fuzziness | Разброс | Color Range's tolerance | +| Dither | Дизеринг | | +| Pattern | Узор | also in *Узорный штамп* | +| Preset | Набор | *Набор кистей*, *набор инструментов* | +| Guide | Направляющая | *Схема направляющих* | +| Artboard | Монтажная область | | +| Workspace | Рабочая среда | | +| Stroke (contour) | Обводка | painterly strokes stay *штрихи*/*мазки* | +| Stroke (paint param) | Штрихи / мазки | `Strokes` → `Штрихи` | +| Amount | Величина | `Штрихи`, never *количество*, when it is a strength | +| Fill Opacity | Непрозрачность заливки | distinct from layer *Непрозрачность* | +| Feather Radius | Радиус растушёвки | | +| Sharpen | Резкость | `Smart Sharpen` → *Умная резкость*, `Unsharp Mask` → *Контурная резкость* | +| Noise | Шум | `Reduce Noise` → *Уменьшить шум* | +| Content-Aware | С учётом содержимого | | +| Camera Raw | Camera Raw | the panel name stays Latin (as German/Spanish do; Greek translates it) | +| Place Embedded / Linked | Поместить встроенный / связанный | | +| Rasterize | Растрировать | | + +## Native review + +The review was AI-assisted and each row was checked against the meaning of PhotoCraft's own +English label. A review by a native speaker is still welcome. + +## Not covered + +Engine error and status messages, user document, layer and preset names, and automation command +IDs stay in English, as in every language ([`localization-parity.md`](localization-parity.md)). +A few dynamic labels the other catalogs carry are not used by the current code; they are +translated to stay aligned with the other catalogs. + +## Validation + +- `russian_covers_generated_filter_options_and_gallery_names` (`crates/ui-egui/src/filter_dialog.rs`) + enumerates every filter and adjustment parameter, symbolic choice and gallery name and requires + a Russian row for each, like the Korean and Italian tests. +- `russian_dynamic_status_hardware_and_dialog_labels_are_translated` (`crates/ui-egui/src/i18n/mod.rs`) + covers the rasterize prompts, hardware status lines, composed status templates and a sample of + filter labels. +- `cargo test -p photocraft-ui-egui --lib`: 2,998 passed, 0 failed. +- `cargo xtask i18n-coverage`: `ru` remains at 100% of the measured keys. +- The offscreen `snapshot` example with `PHOTOCRAFT_LOCALE=ru` renders the dialogs with Russian + labels, without missing glyphs or clipped text. From 94b0219f179a5ad633ed8c9f47bb94255ee50f5e Mon Sep 17 00:00:00 2001 From: langrain Date: Sun, 18 Oct 2026 06:24:11 +0000 Subject: [PATCH 27/44] Fix: add missing Chinese translations for Up and To This PR fixes the missing Chinese translations for "Up" (in the Direction panel) and "To" (in the Gradient Overlay panel). --- crates/ui-egui/src/i18n/zh-hans.tsv | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 0f975c957e..77fb9146c4 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -1848,9 +1848,11 @@ Tabloid Tabloid 纸 Text Replacement 文字替换 Thin 纤细 + To 到 Traditional (72.27 points/inch) 传统(72.27 点/英寸) Transfer 传递 Triangle Tool 三角形工具 + Up 向上 Unsaved changes 未保存的更改 Uses the foreground and background colours. 使用前景色和背景色。 Watercolour look: lighter interior, darker rims. 水彩效果:内部较浅,边缘较深。 From 919ff3e0e93d4bd4175b790b75673ca0720a5c71 Mon Sep 17 00:00:00 2001 From: Reynold Hu <88558425+reynold-hu@users.noreply.github.com> Date: Sun, 18 Oct 2026 20:54:35 +0800 Subject: [PATCH 28/44] ui: trim the recent fonts at once when the preference lowers the cap (review) --- crates/ui-egui/src/lib.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 4bffc10e81..7dbee5541e 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -1312,6 +1312,12 @@ impl PhotocraftApp { r.truncate(cap); } + /// Preferences ▸ Type ▸ Recent Fonts: a smaller cap (or 0) applies to the existing list at + /// once, not only after the next pick. + pub fn sync_recent_fonts(&mut self) { + self.ui.recent_fonts.truncate(self.session.prefs().type_.recent_fonts.min(50) as usize); + } + /// File › Open Recent › Clear Recent File List. pub fn clear_recent(&mut self) { self.ui.recent_files.clear(); @@ -2019,6 +2025,9 @@ impl eframe::App for PhotocraftApp { text_field_menu::show(&ctx); // Each painting tool keeps its own brush (#218), so switch the active tool's brush in // before anything this frame reads it (the cursor, the options bar, a stroke). + // Preferences ▸ Type ▸ Recent Fonts: a smaller cap (or 0) trims the existing list at + // once, not only after the next pick. + self.sync_recent_fonts(); paint_mouse::sync_tool_brush(self); // A unit typed into a value field (`1in` in a px field) converts at the active document's // resolution, as in Photoshop. From 2fb3e47c1e347a4752df5bd469658261591bbebd Mon Sep 17 00:00:00 2001 From: Reynold Hu <88558425+reynold-hu@users.noreply.github.com> Date: Sun, 18 Oct 2026 20:54:35 +0800 Subject: [PATCH 29/44] ui: the closed font picker's wheel keeps a stable order (review) --- crates/ui-egui/src/type_tool.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/crates/ui-egui/src/type_tool.rs b/crates/ui-egui/src/type_tool.rs index f9933f0d24..75c3c846ab 100644 --- a/crates/ui-egui/src/type_tool.rs +++ b/crates/ui-egui/src/type_tool.rs @@ -1584,7 +1584,10 @@ pub(crate) fn family_picker_in( ui.input_mut(|i| i.smooth_scroll_delta = egui::Vec2::ZERO); } let notches = crate::widgets::wheel_notches_of(ui, response.response.id.with("family-wheel"), hovered, 1.0); - let rows = font_menu_rows(families, labels, "", recents); + // The wheel steps in a stable order: hoisting the family it just picked as recent + // would change the order between notches and can jump back to the previous family. + // The open menu still shows the recent families first. + let rows = font_menu_rows(families, labels, "", &[]); if let Some(i) = rows.iter().position(|(f, _)| *f == current) && let Some(n) = wheel_family_index(i, rows.len(), notches) && let Some((f, _)) = rows.get(n) @@ -2496,6 +2499,22 @@ mod tests { assert!(app.ui.recent_fonts.is_empty(), "0 keeps no list"); } + /// A smaller Recent Fonts cap (or 0) applies to the existing list at once, without waiting + /// for the next pick (CodeRabbit review of #3559). + #[test] + fn lowering_the_recent_fonts_cap_applies_at_once() { + let mut app = app(); + for f in ["A", "B", "C"] { + app.note_recent_font(f); + } + app.run("prefs.set", json!({"path": "type.recentFonts", "value": 2})).unwrap(); + app.sync_recent_fonts(); + assert_eq!(app.ui.recent_fonts, ["C", "B"], "the smaller cap trims the tail without a pick"); + app.run("prefs.set", json!({"path": "type.recentFonts", "value": 0})).unwrap(); + app.sync_recent_fonts(); + assert!(app.ui.recent_fonts.is_empty(), "0 clears the list"); + } + fn layer_text(app: &PhotocraftApp) -> String { let ed = app.ui.text_edit.as_ref().unwrap(); current_text(app, LayerId(ed.layer)).unwrap() From 60636200ad0482dcd01be07c65a1eea8f0e9bab3 Mon Sep 17 00:00:00 2001 From: Reynold Hu <88558425+reynold-hu@users.noreply.github.com> Date: Sun, 18 Oct 2026 20:54:35 +0800 Subject: [PATCH 30/44] ui: test that the closed picker's wheel keeps advancing while fonts become recent --- crates/ui-egui/src/type_font_picker_tests.rs | 24 ++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/crates/ui-egui/src/type_font_picker_tests.rs b/crates/ui-egui/src/type_font_picker_tests.rs index 3ccdcd59bb..ff15a548b5 100644 --- a/crates/ui-egui/src/type_font_picker_tests.rs +++ b/crates/ui-egui/src/type_font_picker_tests.rs @@ -587,6 +587,30 @@ fn wheel_over_the_closed_font_button_steps_through_the_families() { assert_eq!(h.state().current, "Family 00", "stops at the first family"); } +#[test] +fn wheel_over_the_closed_button_keeps_a_stable_order_with_recents() { + let mut h = harness_with_recents(FontPreview::Medium, vec![]); + let button = h.query_all_by_role(Role::ComboBox).next().unwrap().rect().center(); + h.hover_at(button); + h.run(); + let wheel = |h: &mut Harness<'static, State>, y: f32| { + h.event(egui::Event::MouseWheel { unit: egui::MouseWheelUnit::Line, delta: vec2(0.0, y), phase: egui::TouchPhase::Move, modifiers: Modifiers::NONE }); + h.run(); + }; + wheel(&mut h, -1.0); + assert_eq!(h.state().current, "Family 01"); + // The caller (the options bar) records the picked family as the most recent one. + h.state_mut().recents.insert(0, "Family 01".into()); + h.run(); + wheel(&mut h, -1.0); + assert_eq!(h.state().current, "Family 02", "the next notch keeps going, not back to the recent family"); + h.state_mut().recents.insert(0, "Family 02".into()); + h.run(); + wheel(&mut h, -1.0); + assert_eq!(h.state().current, "Family 03"); + assert!(popup_rect(&h).is_none(), "the list stays closed"); +} + #[test] fn wheel_family_index_clamps_to_the_ends() { assert_eq!(super::wheel_family_index(0, 3, -5.0), Some(2)); From 19ccfa9004adc773895505dc2c1c6d7157865afe Mon Sep 17 00:00:00 2001 From: Losminek <301412321+Losminek@users.noreply.github.com> Date: Sun, 18 Oct 2026 20:12:34 +0200 Subject: [PATCH 31/44] i18n(cs): bring the Czech catalog up to date Add 127 labels that the interface looks up through variables and that cs.tsv was missing (live keys only, checked against the code), update one hint to quote the translated option, and fix the capitalisation of five existing entries. Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/i18n/cs.tsv | 144 +++++++++++++++++++++++++++++++-- 1 file changed, 138 insertions(+), 6 deletions(-) diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 6175183cb5..2d32fae394 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -2369,11 +2369,11 @@ cameraRaw Color Barvy Apply At Canvas Bounds Použít efekt na hranicích plátna # Shared screen eyedropper and text Color Picker (#1128). - Color Picker (Text Color) Výběr barvy (Barva textu) + Color Picker (Text Color) Výběr barvy (barva textu) Color Picker ({name}) Výběr barvy ({name}) - Color Picker (Fill Color) Výběr barvy (Barva výplně) + Color Picker (Fill Color) Výběr barvy (barva výplně) Fill color Barva výplně - Color Picker (Stroke Color) Výběr barvy (Barva tahu) + Color Picker (Stroke Color) Výběr barvy (barva tahu) Stroke color Barva tahu Pick screen color Vybrat barvu obrazovky Sample a pixel anywhere on the screen Vzorkovat pixel kdekoli na obrazovce @@ -2525,7 +2525,7 @@ fontWeight Italic Kurzíva Match Canvas Podle plátna Auto Adjust Opacity Automaticky upravit krytí Crop Shield color Barva stínění oříznutí - Color Picker (Crop Shield Color) Výběr barvy (Barva stínění oříznutí) + Color Picker (Crop Shield Color) Výběr barvy (barva stínění oříznutí) From Center Ze středu Link radii Propojit poloměry Star Ratio: Poměr hvězdy: @@ -2642,7 +2642,7 @@ cameraData Lens Objektiv Search settings… Hledat nastavení… No matching settings Žádná odpovídající nastavení # Edit › Content-Aware Fill workspace - Sampling Brush Tool Nástroj Štětec vzorkování + Sampling Brush Tool Nástroj štětec vzorkování Sampling Area Overlay Překrytí oblasti vzorkování Show Sampling Area Zobrazit oblast vzorkování Indicates Označuje @@ -3782,7 +3782,7 @@ navigator Zoom Zvětšení Use the embedded profile ({profile}) Použít vložený profil ({profile}) Convert to the working space ({profile}) Převést do pracovního prostoru barev ({profile}) Remember this choice for future images Zapamatovat si tuto volbu pro další obrázky - Turn the prompt back on with Ask On Mismatch in Edit › Color Settings. Dotaz znovu zapnete volbou Ask On Mismatch v nabídce Úpravy › Nastavení barev. + Turn the prompt back on with Ask On Mismatch in Edit › Color Settings. Dotaz znovu zapnete volbou „Ptát se při nesouladu profilů“ v nabídce Úpravy › Nastavení barev. Button Mode Režim tlačítka Button Mode On Režim tlačítka zapnutý @@ -3838,3 +3838,135 @@ navigator Zoom Zvětšení Increase Brush Size Zvětšit velikost štětce Decrease Brush Hardness Snížit tvrdost štětce Increase Brush Hardness Zvýšit tvrdost štětce + +# Labels the UI looks up through a variable (tool, glyph-group, Liquify and warp names, command +# parameters and choices humanized from their keys, history steps and contextual +# Camera Raw / layer labels) that other catalogs already covered. Translated the same way as +# above, from the plain meaning of the English label. + Export As: {name} Exportovat jako: {name} + Tool Nástroj + Arc Lower Oblouk dole + Arc Upper Oblouk nahoře + Arrows Šipky + Basic Latin Základní latinka + Bloat Nafouknout + Burn Ztmavení + Checkerboard Šachovnice + Clone Stamp Klonovací razítko + Color Sampler Tool Nástroj vzorkovač barev + Currency Měnové symboly + Cyrillic Cyrilice + Decrease Type Size Zmenšit velikost písma + Decrease Type Size by 10 Zmenšit velikost písma o 10 + Delete Snapshot Odstranit snímek + Detect Faces Rozpoznat obličeje + Dodge Zesvětlení + Erase with Brush or Pencil (hold) Mazat štětcem nebo tužkou (při podržení klávesy) + Fill with Background Color Vyplnit barvou pozadí + Fill with Background Color, Preserve Transparency Vyplnit barvou pozadí, zachovat průhlednost + Fill with Foreground Color Vyplnit barvou popředí + Fill with Foreground Color, Preserve Transparency Vyplnit barvou popředí, zachovat průhlednost + Forward Warp Deformovat tažením + Freeze Lasso Laso zmrazení + Freeze Mask Zmrazit oblast + Greek Řecké písmo + Hand Tool (hold) Nástroj ruka (při podržení klávesy) + History Brush Štětec historie + Increase Type Size Zvětšit velikost písma + Increase Type Size by 10 Zvětšit velikost písma o 10 + Kerning: Metrics, Optical or a value in 1/1000 em (Alt+←/→ at the caret) Kerning: zvolte „Metrické“, „Optické“ nebo hodnotu v 1/1000 em (Alt+←/→ v místě textového kurzoru) + Latin 1 Latinka 1 + Latin Extended-A Rozšířená latinka A + Latin Extended-B Rozšířená latinka B + Lock Transparent Pixels Zamknout průhledné pixely + Make Selection from Path Vytvořit výběr z cesty + Mask None Zrušit maskování + Math Symbols Matematické symboly + New Snapshot Vytvořit snímek + Numbers Čísla + Pattern Fill… Výplň vzorkem… + Pick Color From Image Vybrat barvu z obrazu + Proof Setup… Nastavení nátisku… + Pucker Stáhnout + Push Left Tlačit doleva + Reconstruct All Rekonstruovat vše + Save Path Uložit cestu + Sponge Houba + Target Channel 3 Zacílit na kanál 3 + Target Channel 4 Zacílit na kanál 4 + Target Channel 5 Zacílit na kanál 5 + Target Channel 6 Zacílit na kanál 6 + Target Channel 7 Zacílit na kanál 7 + Target Channel 8 Zacílit na kanál 8 + Target Channel 9 Zacílit na kanál 9 + Target Composite Channel Zacílit na složený kanál + Temporary Dočasné + Thaw Mask Rozmrazit oblast + Toggle Layer Mask Overlay Přepnout překryv masky vrstvy + Twirl Clockwise Zvířit doprava + Twirl Counterclockwise Zvířit doleva + Warming Filter (85) Oteplovací filtr (85) + Zoom In (hold, drag to scrub) Přiblížit (při podržení klávesy, tažením plynule měníte zvětšení) + Zoom Out (hold) Oddálit (při podržení klávesy) + blue modrá + gray šedá + green zelená + none žádná + orange oranžová + red červená + violet fialová + yellow žlutá + Adobe-rgb-compat Kompatibilní s Adobe RGB (1998) + Any Bez omezení + Ask On Mismatch Ptát se při nesouladu profilů + Ask On Missing Ptát se při chybějícím profilu + Ask On Paste Ptát se při vkládání + Author Title Pracovní pozice autora + Based On Podle + Blend Text Gamma Gama prolnutí textu + Case Sensitive Rozlišovat velikost písmen + Ccw Proti směru hodinových ručiček + Change Find Změnit a najít další + Cmyk CMYK + Coated-cmyk CMYK pro křídový papír + Convert Převést + Copyright Url URL informací o autorských právech + Cw Po směru hodinových ručiček + Display-p3 Display P3 + Dont Enlarge Nezvětšovat + Forward Vpřed + Horizontal Distortion Vodorovné zkreslení + Intent Záměr převodu + Linear-srgb Lineární sRGB + Mask Channel Kanál masky + Monitor Profile Profil monitoru + Nearest Nejbližší soused + Policy Cmyk Zásady správy barev pro CMYK + Policy Gray Zásady správy barev pro šedou + Policy Rgb Zásady správy barev pro RGB + Prophoto-compat Kompatibilní s ProPhoto + R G B RGB + Rec2020 Rec. 2020 + Sgray sGray (tónová křivka sRGB) + Simulate Paper Simulovat papír + Source Channel Zdrojový kanál + Source Invert Invertovat zdroj + Source1 Channel Kanál zdroje 1 + Source1 Invert Invertovat zdroj 1 + Source2 Channel Kanál zdroje 2 + Source2 Invert Invertovat zdroj 2 + Title Název + Vertical Distortion Svislé zkreslení + Whole Word Celé slovo + Working Cmyk Pracovní CMYK + Working Gray Pracovní šedá + Working Rgb Pracovní RGB + Working-cmyk Pracovní CMYK + Warming Filter (LBA) Oteplovací filtr (LBA) + Warming Filter (81) Oteplovací filtr (81) + Cooling Filter (80) Ochlazovací filtr (80) + Cooling Filter (LBB) Ochlazovací filtr (LBB) + Cooling Filter (82) Ochlazovací filtr (82) +layers Fill Krytí výplně +layers Fill: Krytí výplně: +cameraRaw Dehaze Odstranění oparu From 4d4ed4f0adfaa5f0a4260d6c8b777af68ed84862 Mon Sep 17 00:00:00 2001 From: Reynold Hu <88558425+reynold-hu@users.noreply.github.com> Date: Mon, 19 Oct 2026 18:33:33 +0800 Subject: [PATCH 32/44] ui: the keyboard font step uses the stable order too --- crates/ui-egui/src/type_tool.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/type_tool.rs b/crates/ui-egui/src/type_tool.rs index 22db7516a9..6b86204a23 100644 --- a/crates/ui-egui/src/type_tool.rs +++ b/crates/ui-egui/src/type_tool.rs @@ -1607,9 +1607,10 @@ pub(crate) fn family_picker_in( // the frame. Cancel it, so the arrow only steps the font. ui.memory_mut(|m| m.move_focus(egui::FocusDirection::None)); } - // The wheel's step: a negative notch is the next family. + // The wheel's step: a negative notch is the next family. Steps use a stable order (no + // recents hoisting), so recording the picked family cannot make the next step jump back. let notches = f32::from(u8::from(up)) - f32::from(u8::from(down)); - let rows = font_menu_rows(families, labels, ""); + let rows = font_menu_rows(families, labels, "", &[]); if let Some(i) = rows.iter().position(|(f, _)| *f == current) && let Some(n) = wheel_family_index(i, rows.len(), notches) && let Some((f, _)) = rows.get(n) From b0408549b7de9ed6c4e354b571365abcc2f45ec9 Mon Sep 17 00:00:00 2001 From: Reynold Hu <88558425+reynold-hu@users.noreply.github.com> Date: Mon, 19 Oct 2026 18:33:33 +0800 Subject: [PATCH 33/44] ui: adapt the new font picker test to the recents argument --- crates/ui-egui/src/type_font_picker_tests.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/type_font_picker_tests.rs b/crates/ui-egui/src/type_font_picker_tests.rs index 91c22496ea..0f3c1871e9 100644 --- a/crates/ui-egui/src/type_font_picker_tests.rs +++ b/crates/ui-egui/src/type_font_picker_tests.rs @@ -290,14 +290,15 @@ fn arrows_on_the_field_step_through_the_families_after_a_pick() { fn the_first_arrow_after_a_pick_keeps_the_focus_between_neighbours() { for (enter, picked, stepped) in [(false, "Family 05", "Family 06"), (true, "Family 04", "Family 05")] { let families: Vec = (0..20).map(|i| format!("Family {i:02}")).collect(); - let state = State { current: "Family 03".into(), families, button: egui::Id::NULL, popup: egui::Id::NULL, preview: FontPreview::Off }; + let state = + State { current: "Family 03".into(), families, button: egui::Id::NULL, popup: egui::Id::NULL, preview: FontPreview::Off, recents: Vec::new() }; let mut h = Harness::builder().with_size(vec2(600.0, 900.0)).build_ui_state( |ui, s: &mut State| { let _ = ui.button("Above"); s.button = ui.make_persistent_id(egui::IdSalt::new("type-font")); s.popup = s.button.with("popup"); let fams = s.families.clone(); - font_picker_in(ui, &mut s.current, 170.0, &fams, s.preview); + font_picker_in(ui, &mut s.current, 170.0, &fams, s.preview, &s.recents); let _ = ui.button("Below"); }, state, From 7c479e92823b9a6a80cc31763696389872b6bc5e Mon Sep 17 00:00:00 2001 From: tareqziad1998 <83318513+tareqziad1998@users.noreply.github.com> Date: Tue, 20 Oct 2026 00:07:06 +0000 Subject: [PATCH 34/44] docs: regenerate the scorecard (TYPE-218-2 partial) --- docs/scorecard.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/scorecard.md b/docs/scorecard.md index c4b53a20f8..28468c6247 100644 --- a/docs/scorecard.md +++ b/docs/scorecard.md @@ -10,7 +10,7 @@ Generated by `cargo xtask scorecard` from `perf/budgets.toml`, `perf/baseline.js | [File compatibility](#file-compatibility) | 7 | 14 | 6 | io corpus: oracle ≥ 148/170, round trip ≥ 169/170; psd-tools corpus: oracle ≥ 249/309, round trip ≥ 308/309; photoshop oracles: oracle ≥ 188/256, round trip ≥ 258/256 | | [Tools](#tools) | 33 | 11 | 0 | | | [Workspace and UI](#workspace-and-ui) | 13 | 6 | 4 | settings that do nothing: 25 of 169 (target 0) | -| [Type](#type) | 4 | 3 | 2 | | +| [Type](#type) | 4 | 4 | 1 | | | [Automation](#automation) | 5 | 2 | 4 | 631 of 631 menu items live (100.0%) | | [Reliability](#reliability) | 5 | 2 | 0 | never-crash lints on 30 of 30 crates; panic_hunt present | | [Distribution](#distribution) | 5 | 4 | 2 | | @@ -214,7 +214,7 @@ Unread: `general.color_picker`, `general.animated_zoom`, `general.zoom_resizes_w | Id | Target | Status | Issue | Note | |---|---|---|---|---| | TYPE-218-1 | Type on a path: path shape, glyphs along the curve, caret follows, PSD round trip | partial | [#218](https://github.com/storytold/photocraft/issues/218) | TextShape::Path (doc text.rs) with glyphs placed rigidly along the curve by arc length (text warp.rs PathFrame), type.attachToPath and type.detachFromPath (#4365), .pcraft round trip. Missing: clicking a path with the Type tool, the caret and selection following the curve, and PSD path text (it saves as point text) | -| TYPE-218-2 | Paragraph direction (Auto/LTR/RTL) in the panel and options bar; visual RTL caret | missing | [#218](https://github.com/storytold/photocraft/issues/218) | Engine has TextDirection (text.rs:155); no UI | +| TYPE-218-2 | Paragraph direction (Auto/LTR/RTL) in the panel and options bar; visual RTL caret | partial | [#218](https://github.com/storytold/photocraft/issues/218) | Paragraph section (Window > Paragraph and Properties) and the options bar have Auto / Left-to-right / Right-to-left buttons setting the paragraph direction through type.setStyle; the options bar's choice is also the direction new type starts with (type_tool.rs direction_items; paragraph_direction_buttons_set_and_show_the_direction, options_bar_direction_sets_selected_layers_and_new_type). Still missing: visual RTL caret (the arrow keys move in logical order) | | TYPE-218-3 | Thai/Khmer/Indic fallback fonts; golden shaping tests for complex scripts and RTL | partial | [#218](https://github.com/storytold/photocraft/issues/218) | Fallback fonts are covered for Thai, Khmer, Lao, Myanmar (THAI_FAMILIES, KHMER_FAMILIES, LAO_FAMILIES, MYANMAR_FAMILIES in fonts.rs; #1909, #3259), the nine Indic scripts (INDIC_FAMILIES: Nirmala UI, Kohinoor and Sangam MN, Noto Sans) and Arabic and Hebrew. Still missing: per-script golden shaping tests against reference output (only smoke tests that an installed font shapes without .notdef, plus a bidi test) | | TYPE-218-4 | Variable font axis sliders from the font's axes; opsz follows the point size | done | [#218](https://github.com/storytold/photocraft/issues/218) | One slider per axis of the style's font in the Character section (Properties and Window > Character), setting type.setStyle variations; opsz follows the type size unless set (layout.rs variation_list); a style or weight resets the wght axis (#2985). PSD does not round-trip axis values yet | | TYPE-218-5 | OpenType UI: Character panel buttons, Numerals and Stylistic Sets submenus | partial | [#218](https://github.com/storytold/photocraft/issues/218) | Type > OpenType toggles (type_extra_cmds.rs:512). Character panel (and the Properties Character section) has Photoshop's eight OpenType buttons: Standard Ligatures, Contextual Alternates, Discretionary Ligatures, Swash, Stylistic Alternates, Titling Alternates, Ordinals, Fractions, pressed while the feature is on at the selection and dispatching the type.openType.* commands (type_tool.rs opentype_row; character_panel_opentype_buttons_toggle_and_show_the_feature, #3752). Still missing: the Numerals and Stylistic Sets submenus, and greying out features the font lacks | From 3af447a315e2ddc6363ebfeb62252748903f5b39 Mon Sep 17 00:00:00 2001 From: kuvshin888 <192736288+kuvshin888@users.noreply.github.com> Date: Tue, 20 Oct 2026 13:09:52 +0300 Subject: [PATCH 35/44] Type: hovering a served font fetches it after a short rest, not as the pointer passes Hovering a row in the font menu previews that family on the canvas, and for a family the host serves but hasn't sent yet the preview asks the host for the whole family (`served::request`). Sweeping the pointer down the menu therefore downloads every family it passes over, each one a font file, or a file per style for a static family. A served family that isn't installed now previews once the pointer has rested on its row for 0.25 s; moving to another row starts the wait again. Installed fonts (bundled, system, or served and already fetched) preview at once, as before, and picking a font still fetches it immediately. Tests: a served family is asked for only after the rest, and passing over rows asks for nothing; an installed family previews on the first frame. --- crates/ui-egui/src/type_font_preview.rs | 94 ++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 3 deletions(-) diff --git a/crates/ui-egui/src/type_font_preview.rs b/crates/ui-egui/src/type_font_preview.rs index 4c652ed7ce..7f420d2494 100644 --- a/crates/ui-egui/src/type_font_preview.rs +++ b/crates/ui-egui/src/type_font_preview.rs @@ -10,6 +10,11 @@ use crate::PhotocraftApp; const TAG: u64 = 1 << 60; +/// How long the pointer has to rest on a served family that isn't fetched yet before hovering it +/// fetches it (seconds). Hovering asks the host for the whole family, and sweeping the font menu +/// would otherwise download every family the pointer passes; installed fonts preview at once. +const HOVER_FETCH_DELAY: f64 = 0.25; + #[derive(Clone)] pub struct TypeFontPreview { document: DocId, @@ -25,15 +30,33 @@ pub struct TypeFontPreview { pub(crate) fn follow_hover(app: &mut PhotocraftApp, ctx: &egui::Context) { let hover_id = egui::Id::new("type-font-hover"); + let since_id = egui::Id::new("type-font-hover-since"); let family = ctx.data_mut(|d| { let family = d.get_temp::(hover_id); d.remove::(hover_id); family }); if let Some(family) = family { - set(app, family); - } else if app.type_font_preview.is_some() { - clear(app); + let now = ctx.input(|i| i.time); + let since = ctx.data_mut(|d| match d.get_temp::<(String, f64)>(since_id) { + Some((f, t)) if f == family => t, + _ => { + d.insert_temp(since_id, (family.clone(), now)); + now + } + }); + let installed = photocraft_text::shared().lock().unwrap_or_else(|e| e.into_inner()).fonts.has_family(&family); + let rested = now - since; + if installed || rested >= HOVER_FETCH_DELAY { + set(app, family); + } else { + ctx.request_repaint_after(std::time::Duration::from_secs_f64(HOVER_FETCH_DELAY - rested)); + } + } else { + ctx.data_mut(|d| d.remove::<(String, f64)>(since_id)); + if app.type_font_preview.is_some() { + clear(app); + } } } @@ -135,3 +158,68 @@ fn preview_key(revision: u64, layer: LayerId, range: Option<[usize; 2]>, family: let h = family.bytes().fold(revision.wrapping_mul(0x9e37_79b9_7f4a_7c15) ^ layer.0 ^ range_key, |h, b| h.wrapping_mul(31).wrapping_add(u64::from(b))); (h & (TAG - 1)) | TAG } + +#[cfg(test)] +mod tests { + use super::*; + use crate::Services; + use serde_json::json; + + /// One frame at `time` (seconds) with the pointer on `family`'s row in the font menu (`None`: + /// on no row), as the menu reports it each frame. + fn frame(app: &mut PhotocraftApp, ctx: &egui::Context, family: Option<&str>, time: f64) { + let mut out = ctx.run_ui(egui::RawInput { time: Some(time), ..Default::default() }, |ui| { + if let Some(f) = family { + ui.ctx().data_mut(|d| d.insert_temp(egui::Id::new("type-font-hover"), f.to_string())); + } + follow_hover(app, ui.ctx()); + }); + out.textures_delta.clear(); + } + + fn previewed(app: &PhotocraftApp) -> Option<&str> { + app.type_font_preview.as_ref().and_then(|p| p.family.as_deref()) + } + + fn app_with_type_layer() -> PhotocraftApp { + let mut app = PhotocraftApp::new(photocraft_engine::Session::new(), Services::default()); + app.run("file.new", json!({"width": 200, "height": 100})).unwrap(); + app.run("type.create", json!({"text": "Sample", "font": "Inter", "size": 18})).unwrap(); + app + } + + #[test] + fn a_served_family_is_asked_for_only_once_the_pointer_rests_on_its_row() { + // Families the host serves but hasn't sent (own names: the served catalog is process-wide). + let (a, b) = ("Served Hover Rest A", "Served Hover Rest B"); + let (fonts, _) = photocraft_text::served::parse_manifest(&format!("{a} | Regular | fonts/a/A.ttf\n{b} | Regular | fonts/b/B.ttf\n")); + photocraft_text::served::add_fonts(&fonts); + let mut app = app_with_type_layer(); + let ctx = egui::Context::default(); + + // Passing over rows asks for nothing: each new row starts the wait again. + frame(&mut app, &ctx, Some(a), 1.0); + frame(&mut app, &ctx, Some(a), 1.0 + HOVER_FETCH_DELAY / 2.0); + frame(&mut app, &ctx, Some(b), 1.0 + HOVER_FETCH_DELAY); + frame(&mut app, &ctx, Some(a), 1.0 + HOVER_FETCH_DELAY * 1.5); + assert_eq!(previewed(&app), None); + + // Resting on a row asks for the family and previews it. + frame(&mut app, &ctx, Some(a), 1.0 + HOVER_FETCH_DELAY * 3.0); + assert_eq!(previewed(&app), Some(a)); + + // Leaving the menu clears the preview, and the next hover waits again. + frame(&mut app, &ctx, None, 3.0); + assert_eq!(previewed(&app), None); + frame(&mut app, &ctx, Some(a), 3.1); + assert_eq!(previewed(&app), None); + } + + #[test] + fn an_installed_family_previews_at_once() { + let mut app = app_with_type_layer(); + let ctx = egui::Context::default(); + frame(&mut app, &ctx, Some("JetBrains Mono"), 1.0); + assert_eq!(previewed(&app), Some("JetBrains Mono")); + } +} From 99a234d2e2360598607c62381e9bbaf540f5fa46 Mon Sep 17 00:00:00 2001 From: Gabrieljr42 Date: Tue, 20 Oct 2026 20:34:08 -0300 Subject: [PATCH 36/44] fix(type): resolve exact PostScript names in type.create type.create stored the font field as a family name, so an exact PostScript face such as Arial-Black warned and fell back. Resolve only an exact PostScript match and keep that face's family, weight, slant, and name. Unknown families still warn. Fixes #8063 Signed-off-by: Gabrieljr42 --- crates/engine/src/type_cmds.rs | 12 ++++ crates/engine/tests/type_postscript_font.rs | 63 +++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 crates/engine/tests/type_postscript_font.rs diff --git a/crates/engine/src/type_cmds.rs b/crates/engine/src/type_cmds.rs index 439d1f008b..fd44f874ce 100644 --- a/crates/engine/src/type_cmds.rs +++ b/crates/engine/src/type_cmds.rs @@ -243,6 +243,18 @@ pub fn apply_char_props(s: &mut CharStyle, p: &Value) -> bool { if let Some(v) = p.get("font").or_else(|| p.get("family")).and_then(Value::as_str) { s.font_family = v.to_string(); s.postscript_name = None; + // Photoshop accepts either a family name or a face's PostScript name in the font field. + // Resolve only exact PostScript matches here so an unknown family remains untouched. + let mut text = photocraft_text::shared().lock().unwrap_or_else(|e| e.into_inner()); + if !text.fonts.has_family(v) { + let resolved = text.fonts.resolve_postscript(v); + if resolved.exact { + s.font_family = resolved.family; + s.weight = resolved.weight; + s.italic = resolved.italic; + s.postscript_name = Some(v.to_string()); + } + } hit(true); } if let Some(v) = p.get("fontStyle").and_then(Value::as_str) { diff --git a/crates/engine/tests/type_postscript_font.rs b/crates/engine/tests/type_postscript_font.rs new file mode 100644 index 0000000000..0a5bf82c6d --- /dev/null +++ b/crates/engine/tests/type_postscript_font.rs @@ -0,0 +1,63 @@ +//! `type.create` accepts an exact PostScript face name in its font field. + +use photocraft_doc::{LayerContent, LayerId}; +use photocraft_engine::Session; +use serde_json::json; + +fn font_with_postscript_name(source: &[u8], postscript: &str) -> Vec { + let mut bytes = source.to_vec(); + let count = u16::from_be_bytes(bytes[4..6].try_into().unwrap()) as usize; + let name_record = (0..count).map(|i| 12 + i * 16).find(|r| &bytes[*r..r + 4] == b"name").unwrap(); + for i in 0..count { + let r = 12 + i * 16; + let offset = u32::from_be_bytes(bytes[r + 8..r + 12].try_into().unwrap()) as usize; + if &bytes[r..r + 4] == b"OS/2" { + bytes[offset + 4..offset + 6].copy_from_slice(&900u16.to_be_bytes()); + } + } + let names = [(1u16, "PhotoCraft Black Test"), (2, "Black"), (6, postscript), (16, "PhotoCraft Black Test"), (17, "Black")]; + let mut table = Vec::new(); + let mut strings = Vec::new(); + for value in [0u16, names.len() as u16, (6 + names.len() * 12) as u16] { + table.extend(value.to_be_bytes()); + } + for (id, value) in names { + let encoded: Vec<_> = value.encode_utf16().flat_map(u16::to_be_bytes).collect(); + for n in [3u16, 1, 0x409, id, encoded.len() as u16, strings.len() as u16] { + table.extend(n.to_be_bytes()); + } + strings.extend(encoded); + } + table.extend(strings); + let offset = bytes.len() as u32; + bytes[name_record + 8..name_record + 12].copy_from_slice(&offset.to_be_bytes()); + bytes[name_record + 12..name_record + 16].copy_from_slice(&(table.len() as u32).to_be_bytes()); + bytes.extend(table); + bytes +} + +#[test] +fn type_create_resolves_exact_postscript_font_names() { + let ps = "PhotoCraft-Black-Test"; + photocraft_text::shared() + .lock() + .unwrap_or_else(|e| e.into_inner()) + .fonts + .register_font_data(font_with_postscript_name(photocraft_text::fonts::INTER_REGULAR.as_slice(), ps)); + + let mut session = Session::new(); + session.execute("file.new", json!({"width": 200, "height": 100, "background": "transparent"})).unwrap(); + let response = session.execute("type.create", json!({"text": "Test", "font": ps})).unwrap(); + assert!(response["warnings"].as_array().is_none_or(Vec::is_empty), "{response}"); + let id = LayerId(response["layer"].as_u64().unwrap()); + let LayerContent::Text(text) = &session.active().unwrap().doc.layer(id).unwrap().content else { panic!("not a text layer") }; + let style = &text.char_runs()[0].style; + assert_eq!(style.font_family, "PhotoCraft Black Test"); + assert_eq!(style.postscript_name.as_deref(), Some(ps)); + assert_eq!(style.weight, 900); + + let regular = session.execute("type.create", json!({"text": "Test", "font": "Inter"})).unwrap(); + assert!(regular["warnings"].as_array().is_none_or(Vec::is_empty), "{regular}"); + let missing = session.execute("type.create", json!({"text": "Test", "font": "No Such Family 123"})).unwrap(); + assert_eq!(missing["warnings"], json!(["font 'No Such Family 123' is not installed; text may render using a fallback face"])); +} From 797c7852f9cfd7d2491ace1da7c3c729feada569 Mon Sep 17 00:00:00 2001 From: Gabrieljr42 Date: Tue, 20 Oct 2026 21:01:11 -0300 Subject: [PATCH 37/44] fix(type): clear retained wght variation when resolving exact face --- crates/engine/src/type_cmds.rs | 1 + crates/engine/tests/type_postscript_font.rs | 31 +++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/crates/engine/src/type_cmds.rs b/crates/engine/src/type_cmds.rs index fd44f874ce..85abca0c3b 100644 --- a/crates/engine/src/type_cmds.rs +++ b/crates/engine/src/type_cmds.rs @@ -249,6 +249,7 @@ pub fn apply_char_props(s: &mut CharStyle, p: &Value) -> bool { if !text.fonts.has_family(v) { let resolved = text.fonts.resolve_postscript(v); if resolved.exact { + s.variations.retain(|f| f.axis != "wght"); s.font_family = resolved.family; s.weight = resolved.weight; s.italic = resolved.italic; diff --git a/crates/engine/tests/type_postscript_font.rs b/crates/engine/tests/type_postscript_font.rs index 0a5bf82c6d..06171d7d84 100644 --- a/crates/engine/tests/type_postscript_font.rs +++ b/crates/engine/tests/type_postscript_font.rs @@ -61,3 +61,34 @@ fn type_create_resolves_exact_postscript_font_names() { let missing = session.execute("type.create", json!({"text": "Test", "font": "No Such Family 123"})).unwrap(); assert_eq!(missing["warnings"], json!(["font 'No Such Family 123' is not installed; text may render using a fallback face"])); } + +#[test] +fn exact_postscript_face_clears_retained_weight_variation() { + let ps = "PhotoCraft-Black-Test-Var"; + photocraft_text::shared() + .lock() + .unwrap_or_else(|e| e.into_inner()) + .fonts + .register_font_data(font_with_postscript_name(photocraft_text::fonts::INTER_REGULAR.as_slice(), ps)); + + let mut session = Session::new(); + session.execute("file.new", json!({"width": 200, "height": 100, "background": "transparent"})).unwrap(); + let response = session.execute("type.create", json!({"text": "Test", "font": "Inter", "variations": {"wght": 300}})).unwrap(); + let id = LayerId(response["layer"].as_u64().unwrap()); + + // Setting an exact PostScript face should clear the previously retained wght variation + session.execute("type.setStyle", json!({"layer": id.0, "font": ps})).unwrap(); + let LayerContent::Text(text) = &session.active().unwrap().doc.layer(id).unwrap().content else { panic!("not a text layer") }; + let style = &text.char_runs()[0].style; + assert_eq!(style.font_family, "PhotoCraft Black Test"); + assert_eq!(style.weight, 900); + assert!(!style.variations.iter().any(|v| v.axis == "wght")); + + // Supplying a variations.wght in the same call should apply after clearing + session.execute("type.setStyle", json!({"layer": id.0, "font": ps, "variations": {"wght": 700}})).unwrap(); + let LayerContent::Text(text2) = &session.active().unwrap().doc.layer(id).unwrap().content else { panic!("not a text layer") }; + let style2 = &text2.char_runs()[0].style; + assert_eq!(style2.font_family, "PhotoCraft Black Test"); + assert_eq!(style2.weight, 900); + assert_eq!(style2.variations.iter().find(|v| v.axis == "wght").map(|v| v.value), Some(700.0)); +} From ec9c3ddc70a0a029f4d3d9c07fafd30d11e878d0 Mon Sep 17 00:00:00 2001 From: coolvitto <160416811+coolvitto@users.noreply.github.com> Date: Wed, 21 Oct 2026 13:30:06 +0900 Subject: [PATCH 38/44] Fix tooltip text in scrub_label function Fixed an issue where the kerning tooltip was not translated. --- crates/ui-egui/src/type_tool.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ui-egui/src/type_tool.rs b/crates/ui-egui/src/type_tool.rs index 0b7ba0e157..c4c59bd531 100644 --- a/crates/ui-egui/src/type_tool.rs +++ b/crates/ui-egui/src/type_tool.rs @@ -2005,7 +2005,7 @@ fn icon_label(ui: &mut egui::Ui, icon: &str, tip: &str) { let (r, resp) = ui.allocate_exact_size(egui::vec2(crate::props_layout::LABEL_W, 22.0), egui::Sense::hover()); crate::icons::paint(ui, r, icon, 12.0, t.text_dim); // Photoshop's Character panel icons scrub the field beside them. - crate::widgets::scrub_label(ui, &resp.on_hover_text(tip)); + crate::widgets::scrub_label(ui, &resp.on_hover_text(tl!(tip))); } /// Labelled numeric field (Photoshop's icon + value pairs). Returns the new value when edited. From 13809228bd34a12f7397a2d699e6a85c45b3f51f Mon Sep 17 00:00:00 2001 From: Nikolai Nyegaard Date: Wed, 21 Oct 2026 09:49:09 +0200 Subject: [PATCH 39/44] Preferences: list the languages alphabetically by their own name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - English stays first as the source language and fallback; the rest sort by native name with accents folded, so Čeština is under C and Magyar under M; Greek, Cyrillic and CJK names follow the Latin ones - Lang::for_picker holds the order; the Preferences dropdown and the native menu's Language submenu used the registry order, which is the order the languages were added --- crates/ui-egui/src/i18n/mod.rs | 47 +++++++++++++++++++++++++++++++ crates/ui-egui/src/native_menu.rs | 2 +- crates/ui-egui/src/prefs_ui.rs | 2 +- 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/i18n/mod.rs b/crates/ui-egui/src/i18n/mod.rs index c7a0d574b2..76d394297e 100644 --- a/crates/ui-egui/src/i18n/mod.rs +++ b/crates/ui-egui/src/i18n/mod.rs @@ -280,6 +280,16 @@ impl Lang { LANGUAGES.iter().map(Lang) } + /// Every registered language in the order a picker lists them: English first (the source + /// language and the fallback), then the rest by their own name with accents folded, so + /// Čeština sits under C and Türkçe under T rather than after Z. Names in other scripts follow + /// the Latin ones in code point order: Greek, Cyrillic, then the CJK languages. + pub fn for_picker() -> Vec { + let mut rest: Vec = Lang::all().filter(|l| l.code() != "en").collect(); + rest.sort_by_cached_key(|l| name_sort_key(l.name())); + std::iter::once(Lang::EN).chain(rest).collect() + } + pub fn name(self) -> &'static str { self.0.name } @@ -299,6 +309,27 @@ impl Lang { } } +/// `name` lowercased with the Latin accents that occur in language names folded away, so a +/// byte-wise sort reads alphabetically. Other scripts are left as they are. +fn name_sort_key(name: &str) -> String { + name.chars() + .flat_map(char::to_lowercase) + .map(|c| match c { + 'à'..='å' | 'ā' | 'ă' | 'ą' => 'a', + 'ç' | 'ć' | 'č' => 'c', + 'è'..='ë' | 'ē' | 'ě' | 'ę' | 'ế' | 'ệ' => 'e', + 'ì'..='ï' | 'ī' => 'i', + 'ñ' | 'ń' | 'ň' => 'n', + 'ò'..='ö' | 'ø' | 'ő' => 'o', + 'ś' | 'š' => 's', + 'ù'..='ü' | 'ū' | 'ů' | 'ű' => 'u', + 'ý' | 'ÿ' => 'y', + 'ź' | 'ż' | 'ž' => 'z', + c => c, + }) + .collect() +} + /// Candidate language codes for a locale tag, most specific first: `zh_TW.UTF-8` → /// `zh-tw`, `zh-hant`, `zh`. fn candidates(tag: &str) -> Vec { @@ -456,6 +487,22 @@ mod tests { assert_eq!(Lang::from_pref("VI"), VI()); } + #[test] + fn picker_lists_english_first_then_names_alphabetically() { + let order = Lang::for_picker(); + assert_eq!(order.first().map(|l| l.code()), Some("en")); + assert_eq!(order.len(), Lang::all().count()); + let keys: Vec = order[1..].iter().map(|l| name_sort_key(l.name())).collect(); + assert!(keys.windows(2).all(|w| w[0] <= w[1]), "{keys:?}"); + let pos = |code: &str| order.iter().position(|l| l.code() == code).unwrap_or_else(|| panic!("{code} registered")); + assert!(pos("cs") < pos("de"), "Čeština sorts under C, not after Z"); + assert!(pos("it") < pos("hu"), "Magyar sorts under M, not under its code"); + assert!(pos("tr") < pos("el"), "Latin names come before Greek"); + assert!(pos("uk") < pos("ja"), "Cyrillic names come before CJK"); + assert_eq!(name_sort_key("Tiếng Việt"), "tieng viet"); + assert_eq!(name_sort_key("Türkçe"), "turkce"); + } + #[test] fn tags_map_to_languages() { assert_eq!(lang_from_tag("ja_JP.UTF-8"), Some(JA())); diff --git a/crates/ui-egui/src/native_menu.rs b/crates/ui-egui/src/native_menu.rs index e76c20eecd..59229a8c6a 100644 --- a/crates/ui-egui/src/native_menu.rs +++ b/crates/ui-egui/src/native_menu.rs @@ -546,7 +546,7 @@ fn language_node(pref: &str, lang: Lang) -> Node { role: Some(ItemRole::Language), }; let mut children = vec![Node::Item(item("auto", tr(lang, "Auto"))), Node::Separator]; - children.extend(Lang::all().map(|l| Node::Item(item(l.code(), l.name())))); + children.extend(Lang::for_picker().into_iter().map(|l| Node::Item(item(l.code(), l.name())))); Node::Submenu { label: tr(lang, "Language").into(), children, role: Some(ItemRole::Language) } } diff --git a/crates/ui-egui/src/prefs_ui.rs b/crates/ui-egui/src/prefs_ui.rs index d808ae32f4..a4962ceb26 100644 --- a/crates/ui-egui/src/prefs_ui.rs +++ b/crates/ui-egui/src/prefs_ui.rs @@ -1689,7 +1689,7 @@ fn field_rows(ui: &mut egui::Ui, section: &str, obj: &mut Map, ke Value::String(s) if path == "interface.language" => { ui.label(RichText::new(tl!(&label)).color(t.text_dim)); let mut pairs: Vec<(String, &str)> = vec![("auto".into(), tl!("Auto"))]; - pairs.extend(crate::i18n::Lang::all().map(|l| (l.code().to_string(), l.name()))); + pairs.extend(crate::i18n::Lang::for_picker().into_iter().map(|l| (l.code().to_string(), l.name()))); let mut cur = s.clone(); crate::widgets::dropdown(ui, &format!("pref-{path}"), &mut cur, &pairs, 220.0); obj.insert(k, json!(cur)); From 9adacf078f6b33e8ceec3a75be34777e7f93d4f7 Mon Sep 17 00:00:00 2001 From: aiongg Date: Thu, 22 Oct 2026 02:21:59 +0000 Subject: [PATCH 40/44] Integration: #4474 drop the two fr.tsv rows already on main (Cross, Lock Layers) --- crates/ui-egui/src/i18n/fr.tsv | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 77ff7f09d3..95ecdfd08c 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -3480,7 +3480,6 @@ curves Smooth Lisser Create By Créer par Create Vector Mask Créer un masque vectoriel Create… Créer… - Cross Croix Cross Process Traitement croisé Crosshatch Hachures croisées Crystallize Cristallisation @@ -3791,7 +3790,6 @@ curves Smooth Lisser Loading… Chargement… Lock All Tout verrouiller Lock Image Pixels Verrouiller les pixels de l'image - Lock Layers Verrouiller les calques Lock Position Verrouiller la position Lock Transparent Pixels Verrouiller les pixels transparents Locked Verrouillé From e3fb1471542b4081aafd0a0fdccd64b828754a74 Mon Sep 17 00:00:00 2001 From: aiongg Date: Thu, 22 Oct 2026 02:24:49 +0000 Subject: [PATCH 41/44] Integration: #6283 drop the 7 ru.tsv rows already on main (Command, Compression, Contract, Cross, Diamond, Duplicate Layer, Lock Layers); Decrease/Increase Brush Size are single rows after the merge --- crates/ui-egui/src/i18n/ru.tsv | 7 ------- 1 file changed, 7 deletions(-) diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 1e6fc594d7..cea2ec8957 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -3432,7 +3432,6 @@ curves Smooth Сгладить Color management: {error} Управление цветом: {error} Colored Pencil Цветной карандаш Comic Book Комикс - Command Команда Commit Perspective Warp (↩) Применить перспективную деформацию (↩) Commit Puppet Warp (↩) Применить марионеточную деформацию (↩) Commit any current edits (⌘↩) Применить текущие изменения (⌘↩) @@ -3441,14 +3440,12 @@ curves Smooth Сгладить Commit transform (↩) Применить трансформирование (↩) Commit warp (↩) Применить деформацию (↩) Compare Сравнить - Compression Сжатие Conditional Mode Change Условная смена режима Confirm Подтвердить Conform Подогнать Constraints Ограничения Content-Aware Fill needs a selection on a pixel layer Заливка с учётом содержимого требует выделенной области на пиксельном слое Continue Продолжить - Contract Сжать Contrasty Контрастный Convert Преобразовать Convert to Alpha Channel Преобразовать в альфа-канал @@ -3482,7 +3479,6 @@ curves Smooth Сгладить Create new brush from the current settings Создать новую кисть из текущих параметров Create new brush preset from the current settings Создать новый набор кистей из текущих параметров Create… Создать… - Cross Крест Cross Process Кросс-процесс Crosshatch Перекрёстная штриховка Crosswise Перекрёстно @@ -3542,7 +3538,6 @@ curves Smooth Сгладить Depth Map Карта глубины Depth Mode Режим глубины Diameter Диаметр - Diamond Ромб Diffuse Диффузия Diffuse Glow Рассеянное свечение Direction Направление @@ -3574,7 +3569,6 @@ curves Smooth Сгладить Driver: {driver} Драйвер: {driver} Dry Brush Сухая кисть Duplicate Channel Дублировать канал - Duplicate Layer Дублировать слой Duplicate Layers Дублировать слои Duplication Дублирование Dust & Scratches Пыль и царапины @@ -3880,7 +3874,6 @@ curves Smooth Сгладить Loading… Загрузка… Lock All Заблокировать всё Lock Image Pixels Заблокировать пиксели изображения - Lock Layers Заблокировать слои Lock Position Закрепить положение Lock Transparent Pixels Заблокировать прозрачные пиксели Locked Заблокировано From d2656c2f268e5be8fffcce20fe2b4110c3c1da7a Mon Sep 17 00:00:00 2001 From: aiongg Date: Thu, 22 Oct 2026 02:25:53 +0000 Subject: [PATCH 42/44] Integration: #6974 Hindi native review column in docs/localization-parity.md is 'no' --- docs/localization-parity.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/localization-parity.md b/docs/localization-parity.md index 4edf493010..21108e4549 100644 --- a/docs/localization-parity.md +++ b/docs/localization-parity.md @@ -33,7 +33,7 @@ speakers, ~55% ready. Remaining: 80–140 h, plus native-speaker review (recorde | English | `en` | 1,608 / 1,608 (100%) | source | all | Latin | – | full | 0 h | | Simplified Chinese | `zh-hans` | 1,608 / 1,608 (100%) | 2,366 | dialogs, tooltips yes; engine messages English | CJK fonts (lazy loader), IME preedit/commit, vertical type | no | partial | 8–12 h | | Spanish | `es` | 1,844 / 1,844 (100%) | 4,010 | as above; complete dynamic UI key set | Latin | no | partial | 6–10 h | -| Hindi | `hi` | partial | 448 | Top menus, toolbox tools, camera raw, dialogs, plurals | Devanagari shaping in the text engine (parley/HarfRust); no Devanagari fallback font (TYPE-218-3) | yes (initial) | partial | 15–20 h | +| Hindi | `hi` | partial | 448 | Top menus, toolbox tools, camera raw, dialogs, plurals | Devanagari shaping in the text engine (parley/HarfRust); no Devanagari fallback font (TYPE-218-3) | no | partial | 15–20 h | | Arabic | `ar` | 0 (0%) | – | – | Arabic shaping and bidi in the text engine, Arabic fallback font; RTL shell layout but not inside panels, and UI labels in LTR word order (DIST-220-4), paragraph direction has no UI (TYPE-218-2), layer names as tofu (#1408, #1693) | no | none | 40–60 h | | French | `fr` | 1,608 / 1,608 (100%) | 2,766 | as above | Latin | no | partial | 6–10 h | | Portuguese (Brazil) | `pt-br` | 1,608 / 1,608 (100%) | 2,788 | as above | Latin; every `pt` locale uses it | no | partial | 6–10 h | From de2d5255fc163f4e84144088583def11161a49d8 Mon Sep 17 00:00:00 2001 From: aiongg Date: Thu, 22 Oct 2026 02:30:33 +0000 Subject: [PATCH 43/44] Integration: #5566 the panel-defaults test sets the edit's document (TextEdit gained the field on main) --- crates/ui-egui/src/type_tool_tests.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/ui-egui/src/type_tool_tests.rs b/crates/ui-egui/src/type_tool_tests.rs index d4619dab53..a1b710a7c5 100644 --- a/crates/ui-egui/src/type_tool_tests.rs +++ b/crates/ui-egui/src/type_tool_tests.rs @@ -243,6 +243,7 @@ mod panel_defaults { let id = LayerId(app.run("type.create", json!({"text": "AéBC", "size": 24})).unwrap()["layer"].as_u64().unwrap()); app.ui.text_edit = Some(crate::state::TextEdit { layer: id.0, + document: None, caret: 2, anchor: 1, session: "panel-default-selection".into(), From 98d8ada8b9aa525865918f3b6b3022d88a8f97e4 Mon Sep 17 00:00:00 2001 From: aiongg Date: Thu, 22 Oct 2026 02:33:11 +0000 Subject: [PATCH 44/44] Integration: cargo fmt --- crates/ui-egui/src/i18n/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/ui-egui/src/i18n/mod.rs b/crates/ui-egui/src/i18n/mod.rs index cd7433e509..12dd5bd6a7 100644 --- a/crates/ui-egui/src/i18n/mod.rs +++ b/crates/ui-egui/src/i18n/mod.rs @@ -1232,7 +1232,10 @@ mod tests { assert!(has(ru, label), "Russian filter label missing: {label}"); } // Dynamic composed status templates. - assert_eq!(fmt(tr(ru, "Step {step} ({id}) failed: {error}"), &[("step", "2"), ("id", "file.export"), ("error", "ok")]), "Шаг 2 (file.export) не выполнен: ok"); + assert_eq!( + fmt(tr(ru, "Step {step} ({id}) failed: {error}"), &[("step", "2"), ("id", "file.export"), ("error", "ok")]), + "Шаг 2 (file.export) не выполнен: ok" + ); assert_eq!(fmt(tr(ru, "Couldn't open {name}: {error}"), &[("name", "scan.tif"), ("error", "denied")]), "Не удалось открыть scan.tif: denied"); assert_eq!(fmt(tr(ru, "Exported {path} ({size})"), &[("path", "/tmp/a.png"), ("size", "12 KB")]), "Экспортировано: /tmp/a.png (12 KB)"); assert_eq!(fmt(tr(ru, "Data Set: {name} ({index}/{total})"), &[("name", "Set 1"), ("index", "1"), ("total", "2")]), "Набор данных: Set 1 (1/2)");