From cdfbde663de12f10598be5dc074b29b7e00159d5 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Fri, 13 Nov 2026 09:20:11 -0800 Subject: [PATCH 01/29] i18n: translate the composite font errors in the complete-message languages The errors of type.compositeFonts.set, export and import, the composite font checks behind them and the Composite Fonts dialog showed in English in every language. Each complete-message catalog gets a row for each of them. The translations keep the font name, the parameter keys and the class keys (kanji, kana, punctuation, symbols, roman, numerals) as written, and NAMED_KEYS checks that they do. --- crates/ui-egui/src/i18n/ca.tsv | 36 +++++++++++ crates/ui-egui/src/i18n/cs.tsv | 36 +++++++++++ crates/ui-egui/src/i18n/de.tsv | 36 +++++++++++ crates/ui-egui/src/i18n/el.tsv | 36 +++++++++++ crates/ui-egui/src/i18n/es.tsv | 36 +++++++++++ crates/ui-egui/src/i18n/fi.tsv | 36 +++++++++++ crates/ui-egui/src/i18n/fr.tsv | 36 +++++++++++ crates/ui-egui/src/i18n/hi.tsv | 36 +++++++++++ crates/ui-egui/src/i18n/hu.tsv | 36 +++++++++++ crates/ui-egui/src/i18n/it.tsv | 36 +++++++++++ crates/ui-egui/src/i18n/ja.tsv | 36 +++++++++++ crates/ui-egui/src/i18n/ka.tsv | 36 +++++++++++ crates/ui-egui/src/i18n/ko.tsv | 36 +++++++++++ crates/ui-egui/src/i18n/nl.tsv | 36 +++++++++++ crates/ui-egui/src/i18n/pl.tsv | 36 +++++++++++ crates/ui-egui/src/i18n/pt-br.tsv | 36 +++++++++++ crates/ui-egui/src/i18n/ru.tsv | 36 +++++++++++ crates/ui-egui/src/i18n/sv.tsv | 36 +++++++++++ crates/ui-egui/src/i18n/tests.rs | 95 +++++++++++++++++++++++++++++ crates/ui-egui/src/i18n/tr.tsv | 36 +++++++++++ crates/ui-egui/src/i18n/uk.tsv | 36 +++++++++++ crates/ui-egui/src/i18n/vi.tsv | 36 +++++++++++ crates/ui-egui/src/i18n/zh-hans.tsv | 36 +++++++++++ crates/ui-egui/src/i18n/zh-hant.tsv | 36 +++++++++++ 24 files changed, 923 insertions(+) diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 5516c9662f..f293bb4202 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -4589,6 +4589,42 @@ digits Malayalam Malaiàlam Composite Fonts Tipus de lletra compostos Export Composite Fonts Exporta tipus de lletra compostos Import Composite Fonts Importa tipus de lletra compostos +@msg each font must be {name, parts} cada tipus de lletra ha de ser {name, parts} +@msg unknown key `{k}`; a font takes name and parts clau desconeguda `{k}`; un tipus de lletra admet name i parts +@msg each font needs a name cada tipus de lletra necessita un nom +@msg `{raw}` can't name a composite font (1 to {COMPOSITE_NAME_MAX} characters, no control characters) `{raw}` no pot anomenar un tipus de lletra compost (d’1 a {COMPOSITE_NAME_MAX} caràcters, sense caràcters de control) +@msg `{name}`: parts must be an object `{name}`: parts ha de ser un objecte +@msg `{name}`: unknown class `{k}`; the classes are kanji, kana, punctuation, symbols, roman and numerals `{name}`: classe desconeguda `{k}`; les classes són kanji, kana, punctuation, symbols, roman i numerals +@msg `{name}`: {_1} must be an object `{name}`: {_1} ha de ser un objecte +@msg `{name}`: unknown key `{k}` in {_1} `{name}`: clau desconeguda `{k}` a {_1} +@msg `{name}`: give the kanji part `{name}`: indiqueu la part kanji +@msg a document keeps at most {COMPOSITE_FONTS_MAX} composite fonts un document conté com a màxim {COMPOSITE_FONTS_MAX} tipus de lletra compostos +@msg two composite fonts are named `{_1}` dos tipus de lletra compostos es diuen `{_1}` +@msg `{_1}` is the name of an installed font `{_1}` és el nom d’un tipus de lletra instal·lat +@msg `{_1}`: {_2} is set in `{font}`, a composite font `{_1}`: {_2} usa `{font}`, un tipus de lletra compost +@msg give fonts: [{name, parts}…] indiqueu fonts: [{name, parts}…] +@msg path is empty path és buit +@msg fonts must be a list of composite fonts fonts ha de ser una llista de tipus de lletra compostos +@msg names must be strings names ha de contenir cadenes +@msg no composite font named `{n}` cap tipus de lletra compost anomenat `{n}` +@msg names must be a list of names names ha de ser una llista de noms +@msg no composite fonts to export cap tipus de lletra compost per exportar +@msg not a composite fonts file: {e} no és un fitxer de tipus de lletra compostos: {e} +@msg not a composite fonts file: no compositeFonts list no és un fitxer de tipus de lletra compostos: no hi ha cap llista compositeFonts +@msg the file holds more than {COMPOSITE_FONTS_MAX} composite fonts el fitxer conté més de {COMPOSITE_FONTS_MAX} tipus de lletra compostos +@msg the file is larger than 4 MB el fitxer ocupa més de 4 MB +@msg dataBase64 is not base64 dataBase64 no és base64 +@msg give one of path, data or dataBase64 indiqueu path, data o dataBase64 +@msg the file holds no composite fonts el fitxer no conté cap tipus de lletra compost +@msg the file holds two composite fonts named `{_1}` el fitxer conté dos tipus de lletra compostos anomenats `{_1}` +@msg `{_1}` can't name a composite font `{_1}` no pot anomenar un tipus de lletra compost +@msg {k}: give a font {k}: indiqueu un tipus de lletra +@msg {k}: size must be {_1} to {_2}% {k}: size ha d’estar entre {_1} i {_2} % +@msg {k}: baseline must be {_1} to {_2}% {k}: baseline ha d’estar entre {_1} i {_2} % +@msg {k}: scale must be {_1} to {_2}% {k}: l’escala ha d’estar entre {_1} i {_2} % +@msg {k}: only kana, roman and numerals can be scaled {k}: només kana, roman i numerals es poden escalar +@msg {k}: only kana can be scaled at the center {k}: només kana es pot escalar des del centre +@msg no composite font cap tipus de lletra compost Delete Font Suprimeix el tipus de lletra Kanji Kanji Kana Kana diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index d03d192a3a..5e850005d1 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -1638,6 +1638,42 @@ digits Malayalam Malajálamské Composite Fonts Složená písma Export Composite Fonts Exportovat složená písma Import Composite Fonts Importovat složená písma +@msg each font must be {name, parts} každé písmo musí být {name, parts} +@msg unknown key `{k}`; a font takes name and parts neznámý klíč `{k}`; písmo přijímá name a parts +@msg each font needs a name každé písmo potřebuje název +@msg `{raw}` can't name a composite font (1 to {COMPOSITE_NAME_MAX} characters, no control characters) `{raw}` nemůže být názvem složeného písma (1 až {COMPOSITE_NAME_MAX} znaků, bez řídicích znaků) +@msg `{name}`: parts must be an object `{name}`: parts musí být objekt +@msg `{name}`: unknown class `{k}`; the classes are kanji, kana, punctuation, symbols, roman and numerals `{name}`: neznámá třída `{k}`; třídy jsou kanji, kana, punctuation, symbols, roman a numerals +@msg `{name}`: {_1} must be an object `{name}`: {_1} musí být objekt +@msg `{name}`: unknown key `{k}` in {_1} `{name}`: neznámý klíč `{k}` v {_1} +@msg `{name}`: give the kanji part `{name}`: zadejte část kanji +@msg a document keeps at most {COMPOSITE_FONTS_MAX} composite fonts dokument obsahuje nejvýše {COMPOSITE_FONTS_MAX} složených písem +@msg two composite fonts are named `{_1}` dvě složená písma se jmenují `{_1}` +@msg `{_1}` is the name of an installed font `{_1}` je název nainstalovaného písma +@msg `{_1}`: {_2} is set in `{font}`, a composite font `{_1}`: {_2} používá `{font}`, což je složené písmo +@msg give fonts: [{name, parts}…] zadejte fonts: [{name, parts}…] +@msg path is empty path je prázdné +@msg fonts must be a list of composite fonts fonts musí být seznam složených písem +@msg names must be strings names musí být řetězce +@msg no composite font named `{n}` žádné složené písmo s názvem `{n}` +@msg names must be a list of names names musí být seznam názvů +@msg no composite fonts to export žádná složená písma k exportu +@msg not a composite fonts file: {e} nejde o soubor složených písem: {e} +@msg not a composite fonts file: no compositeFonts list nejde o soubor složených písem: chybí seznam compositeFonts +@msg the file holds more than {COMPOSITE_FONTS_MAX} composite fonts soubor obsahuje více než {COMPOSITE_FONTS_MAX} složených písem +@msg the file is larger than 4 MB soubor je větší než 4 MB +@msg dataBase64 is not base64 dataBase64 není base64 +@msg give one of path, data or dataBase64 zadejte path, data nebo dataBase64 +@msg the file holds no composite fonts soubor neobsahuje žádná složená písma +@msg the file holds two composite fonts named `{_1}` soubor obsahuje dvě složená písma s názvem `{_1}` +@msg `{_1}` can't name a composite font `{_1}` nemůže být názvem složeného písma +@msg {k}: give a font {k}: zadejte písmo +@msg {k}: size must be {_1} to {_2}% {k}: size musí být {_1} až {_2} % +@msg {k}: baseline must be {_1} to {_2}% {k}: baseline musí být {_1} až {_2} % +@msg {k}: scale must be {_1} to {_2}% {k}: měřítko musí být {_1} až {_2} % +@msg {k}: only kana, roman and numerals can be scaled {k}: měnit měřítko lze jen u kana, roman a numerals +@msg {k}: only kana can be scaled at the center {k}: měřítko od středu lze měnit jen u kana +@msg no composite font žádné složené písmo Delete Font Odstranit písmo Kanji Kandži Kana Kana diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 47337855aa..0fd46c7d0b 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -3590,6 +3590,42 @@ effect Points Punkte Composite Fonts Zusammengesetzte Schriften Export Composite Fonts Zusammengesetzte Schriften exportieren Import Composite Fonts Zusammengesetzte Schriften importieren +@msg each font must be {name, parts} jede Schrift muss {name, parts} sein +@msg unknown key `{k}`; a font takes name and parts unbekannter Schlüssel `{k}`; eine Schrift nimmt name und parts +@msg each font needs a name jede Schrift braucht einen Namen +@msg `{raw}` can't name a composite font (1 to {COMPOSITE_NAME_MAX} characters, no control characters) `{raw}` kann keine zusammengesetzte Schrift benennen (1 bis {COMPOSITE_NAME_MAX} Zeichen, keine Steuerzeichen) +@msg `{name}`: parts must be an object `{name}`: parts muss ein Objekt sein +@msg `{name}`: unknown class `{k}`; the classes are kanji, kana, punctuation, symbols, roman and numerals `{name}`: unbekannte Klasse `{k}`; die Klassen sind kanji, kana, punctuation, symbols, roman und numerals +@msg `{name}`: {_1} must be an object `{name}`: {_1} muss ein Objekt sein +@msg `{name}`: unknown key `{k}` in {_1} `{name}`: unbekannter Schlüssel `{k}` in {_1} +@msg `{name}`: give the kanji part `{name}`: geben Sie den Teil kanji an +@msg a document keeps at most {COMPOSITE_FONTS_MAX} composite fonts ein Dokument enthält höchstens {COMPOSITE_FONTS_MAX} zusammengesetzte Schriften +@msg two composite fonts are named `{_1}` zwei zusammengesetzte Schriften heißen `{_1}` +@msg `{_1}` is the name of an installed font `{_1}` ist der Name einer installierten Schrift +@msg `{_1}`: {_2} is set in `{font}`, a composite font `{_1}`: {_2} ist in `{font}` gesetzt, einer zusammengesetzten Schrift +@msg give fonts: [{name, parts}…] geben Sie fonts an: [{name, parts}…] +@msg path is empty path ist leer +@msg fonts must be a list of composite fonts fonts muss eine Liste zusammengesetzter Schriften sein +@msg names must be strings names müssen Zeichenketten sein +@msg no composite font named `{n}` keine zusammengesetzte Schrift namens `{n}` +@msg names must be a list of names names muss eine Liste von Namen sein +@msg no composite fonts to export keine zusammengesetzten Schriften zum Exportieren +@msg not a composite fonts file: {e} keine Datei mit zusammengesetzten Schriften: {e} +@msg not a composite fonts file: no compositeFonts list keine Datei mit zusammengesetzten Schriften: keine Liste compositeFonts +@msg the file holds more than {COMPOSITE_FONTS_MAX} composite fonts die Datei enthält mehr als {COMPOSITE_FONTS_MAX} zusammengesetzte Schriften +@msg the file is larger than 4 MB die Datei ist größer als 4 MB +@msg dataBase64 is not base64 dataBase64 ist kein Base64 +@msg give one of path, data or dataBase64 geben Sie path, data oder dataBase64 an +@msg the file holds no composite fonts die Datei enthält keine zusammengesetzten Schriften +@msg the file holds two composite fonts named `{_1}` die Datei enthält zwei zusammengesetzte Schriften namens `{_1}` +@msg `{_1}` can't name a composite font `{_1}` kann keine zusammengesetzte Schrift benennen +@msg {k}: give a font {k}: geben Sie eine Schrift an +@msg {k}: size must be {_1} to {_2}% {k}: size muss {_1} bis {_2} % sein +@msg {k}: baseline must be {_1} to {_2}% {k}: baseline muss {_1} bis {_2} % sein +@msg {k}: scale must be {_1} to {_2}% {k}: die Skalierung muss {_1} bis {_2} % sein +@msg {k}: only kana, roman and numerals can be scaled {k}: nur kana, roman und numerals lassen sich skalieren +@msg {k}: only kana can be scaled at the center {k}: nur kana lässt sich am Mittelpunkt skalieren +@msg no composite font keine zusammengesetzte Schrift Delete Font Schrift löschen Kanji Kanji Kana Kana diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index c319729a71..98f743c7fc 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -4109,6 +4109,42 @@ digits Malayalam Μαλαγιάλαμ Composite Fonts Σύνθετες γραμματοσειρές Export Composite Fonts Εξαγωγή σύνθετων γραμματοσειρών Import Composite Fonts Εισαγωγή σύνθετων γραμματοσειρών +@msg each font must be {name, parts} κάθε γραμματοσειρά πρέπει να είναι {name, parts} +@msg unknown key `{k}`; a font takes name and parts άγνωστο κλειδί `{k}`· μια γραμματοσειρά δέχεται name και parts +@msg each font needs a name κάθε γραμματοσειρά χρειάζεται όνομα +@msg `{raw}` can't name a composite font (1 to {COMPOSITE_NAME_MAX} characters, no control characters) το `{raw}` δεν μπορεί να είναι όνομα σύνθετης γραμματοσειράς (1 έως {COMPOSITE_NAME_MAX} χαρακτήρες, χωρίς χαρακτήρες ελέγχου) +@msg `{name}`: parts must be an object `{name}`: το parts πρέπει να είναι αντικείμενο +@msg `{name}`: unknown class `{k}`; the classes are kanji, kana, punctuation, symbols, roman and numerals `{name}`: άγνωστη κλάση `{k}`· οι κλάσεις είναι kanji, kana, punctuation, symbols, roman και numerals +@msg `{name}`: {_1} must be an object `{name}`: το {_1} πρέπει να είναι αντικείμενο +@msg `{name}`: unknown key `{k}` in {_1} `{name}`: άγνωστο κλειδί `{k}` στο {_1} +@msg `{name}`: give the kanji part `{name}`: δώστε το τμήμα kanji +@msg a document keeps at most {COMPOSITE_FONTS_MAX} composite fonts ένα έγγραφο περιέχει το πολύ {COMPOSITE_FONTS_MAX} σύνθετες γραμματοσειρές +@msg two composite fonts are named `{_1}` δύο σύνθετες γραμματοσειρές ονομάζονται `{_1}` +@msg `{_1}` is the name of an installed font το `{_1}` είναι όνομα εγκατεστημένης γραμματοσειράς +@msg `{_1}`: {_2} is set in `{font}`, a composite font `{_1}`: το {_2} χρησιμοποιεί την `{font}`, που είναι σύνθετη γραμματοσειρά +@msg give fonts: [{name, parts}…] δώστε fonts: [{name, parts}…] +@msg path is empty το path είναι κενό +@msg fonts must be a list of composite fonts το fonts πρέπει να είναι λίστα σύνθετων γραμματοσειρών +@msg names must be strings τα names πρέπει να είναι συμβολοσειρές +@msg no composite font named `{n}` δεν υπάρχει σύνθετη γραμματοσειρά με όνομα `{n}` +@msg names must be a list of names το names πρέπει να είναι λίστα ονομάτων +@msg no composite fonts to export δεν υπάρχουν σύνθετες γραμματοσειρές για εξαγωγή +@msg not a composite fonts file: {e} δεν είναι αρχείο σύνθετων γραμματοσειρών: {e} +@msg not a composite fonts file: no compositeFonts list δεν είναι αρχείο σύνθετων γραμματοσειρών: δεν υπάρχει λίστα compositeFonts +@msg the file holds more than {COMPOSITE_FONTS_MAX} composite fonts το αρχείο περιέχει περισσότερες από {COMPOSITE_FONTS_MAX} σύνθετες γραμματοσειρές +@msg the file is larger than 4 MB το αρχείο είναι μεγαλύτερο από 4 MB +@msg dataBase64 is not base64 το dataBase64 δεν είναι base64 +@msg give one of path, data or dataBase64 δώστε path, data ή dataBase64 +@msg the file holds no composite fonts το αρχείο δεν περιέχει σύνθετες γραμματοσειρές +@msg the file holds two composite fonts named `{_1}` το αρχείο περιέχει δύο σύνθετες γραμματοσειρές με όνομα `{_1}` +@msg `{_1}` can't name a composite font το `{_1}` δεν μπορεί να είναι όνομα σύνθετης γραμματοσειράς +@msg {k}: give a font {k}: δώστε γραμματοσειρά +@msg {k}: size must be {_1} to {_2}% {k}: το size πρέπει να είναι από {_1} έως {_2}% +@msg {k}: baseline must be {_1} to {_2}% {k}: το baseline πρέπει να είναι από {_1} έως {_2}% +@msg {k}: scale must be {_1} to {_2}% {k}: η κλίμακα πρέπει να είναι από {_1} έως {_2}% +@msg {k}: only kana, roman and numerals can be scaled {k}: μόνο τα kana, roman και numerals αλλάζουν κλίμακα +@msg {k}: only kana can be scaled at the center {k}: μόνο το kana αλλάζει κλίμακα από το κέντρο +@msg no composite font καμία σύνθετη γραμματοσειρά Delete Font Διαγραφή γραμματοσειράς Kanji Κάντζι Kana Κάνα diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 5b203292a4..af64c7e2de 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -3638,6 +3638,42 @@ digits Malayalam Malayálam Composite Fonts Fuentes compuestas Export Composite Fonts Exportar fuentes compuestas Import Composite Fonts Importar fuentes compuestas +@msg each font must be {name, parts} cada fuente debe ser {name, parts} +@msg unknown key `{k}`; a font takes name and parts clave desconocida `{k}`; una fuente admite name y parts +@msg each font needs a name cada fuente necesita un nombre +@msg `{raw}` can't name a composite font (1 to {COMPOSITE_NAME_MAX} characters, no control characters) `{raw}` no puede nombrar una fuente compuesta (de 1 a {COMPOSITE_NAME_MAX} caracteres, sin caracteres de control) +@msg `{name}`: parts must be an object `{name}`: parts debe ser un objeto +@msg `{name}`: unknown class `{k}`; the classes are kanji, kana, punctuation, symbols, roman and numerals `{name}`: clase desconocida `{k}`; las clases son kanji, kana, punctuation, symbols, roman y numerals +@msg `{name}`: {_1} must be an object `{name}`: {_1} debe ser un objeto +@msg `{name}`: unknown key `{k}` in {_1} `{name}`: clave desconocida `{k}` en {_1} +@msg `{name}`: give the kanji part `{name}`: indique la parte kanji +@msg a document keeps at most {COMPOSITE_FONTS_MAX} composite fonts un documento admite como máximo {COMPOSITE_FONTS_MAX} fuentes compuestas +@msg two composite fonts are named `{_1}` dos fuentes compuestas se llaman `{_1}` +@msg `{_1}` is the name of an installed font `{_1}` es el nombre de una fuente instalada +@msg `{_1}`: {_2} is set in `{font}`, a composite font `{_1}`: {_2} usa `{font}`, una fuente compuesta +@msg give fonts: [{name, parts}…] indique fonts: [{name, parts}…] +@msg path is empty path está vacío +@msg fonts must be a list of composite fonts fonts debe ser una lista de fuentes compuestas +@msg names must be strings names debe contener cadenas +@msg no composite font named `{n}` no hay ninguna fuente compuesta llamada `{n}` +@msg names must be a list of names names debe ser una lista de nombres +@msg no composite fonts to export no hay fuentes compuestas para exportar +@msg not a composite fonts file: {e} no es un archivo de fuentes compuestas: {e} +@msg not a composite fonts file: no compositeFonts list no es un archivo de fuentes compuestas: no hay lista compositeFonts +@msg the file holds more than {COMPOSITE_FONTS_MAX} composite fonts el archivo contiene más de {COMPOSITE_FONTS_MAX} fuentes compuestas +@msg the file is larger than 4 MB el archivo ocupa más de 4 MB +@msg dataBase64 is not base64 dataBase64 no es base64 +@msg give one of path, data or dataBase64 indique path, data o dataBase64 +@msg the file holds no composite fonts el archivo no contiene fuentes compuestas +@msg the file holds two composite fonts named `{_1}` el archivo contiene dos fuentes compuestas llamadas `{_1}` +@msg `{_1}` can't name a composite font `{_1}` no puede nombrar una fuente compuesta +@msg {k}: give a font {k}: indique una fuente +@msg {k}: size must be {_1} to {_2}% {k}: size debe estar entre {_1} y {_2} % +@msg {k}: baseline must be {_1} to {_2}% {k}: baseline debe estar entre {_1} y {_2} % +@msg {k}: scale must be {_1} to {_2}% {k}: la escala debe estar entre {_1} y {_2} % +@msg {k}: only kana, roman and numerals can be scaled {k}: solo kana, roman y numerals se pueden escalar +@msg {k}: only kana can be scaled at the center {k}: solo kana se puede escalar desde el centro +@msg no composite font ninguna fuente compuesta Delete Font Eliminar fuente Kanji Kanji Kana Kana diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index b2c6f48d54..ccff1a1a06 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -1611,6 +1611,42 @@ digits Malayalam Malajalam Composite Fonts Yhdistelmäfontit Export Composite Fonts Vie yhdistelmäfontit Import Composite Fonts Tuo yhdistelmäfontit +@msg each font must be {name, parts} jokaisen fontin on oltava {name, parts} +@msg unknown key `{k}`; a font takes name and parts tuntematon avain `{k}`; fontti ottaa name ja parts +@msg each font needs a name jokainen fontti tarvitsee nimen +@msg `{raw}` can't name a composite font (1 to {COMPOSITE_NAME_MAX} characters, no control characters) `{raw}` ei kelpaa yhdistelmäfontin nimeksi (1–{COMPOSITE_NAME_MAX} merkkiä, ei ohjausmerkkejä) +@msg `{name}`: parts must be an object `{name}`: parts on oltava objekti +@msg `{name}`: unknown class `{k}`; the classes are kanji, kana, punctuation, symbols, roman and numerals `{name}`: tuntematon luokka `{k}`; luokat ovat kanji, kana, punctuation, symbols, roman ja numerals +@msg `{name}`: {_1} must be an object `{name}`: {_1} on oltava objekti +@msg `{name}`: unknown key `{k}` in {_1} `{name}`: tuntematon avain `{k}` osassa {_1} +@msg `{name}`: give the kanji part `{name}`: anna osa kanji +@msg a document keeps at most {COMPOSITE_FONTS_MAX} composite fonts asiakirjassa voi olla enintään {COMPOSITE_FONTS_MAX} yhdistelmäfonttia +@msg two composite fonts are named `{_1}` kahden yhdistelmäfontin nimi on `{_1}` +@msg `{_1}` is the name of an installed font `{_1}` on asennetun fontin nimi +@msg `{_1}`: {_2} is set in `{font}`, a composite font `{_1}`: {_2} käyttää fonttia `{font}`, joka on yhdistelmäfontti +@msg give fonts: [{name, parts}…] anna fonts: [{name, parts}…] +@msg path is empty path on tyhjä +@msg fonts must be a list of composite fonts fonts on oltava luettelo yhdistelmäfonteista +@msg names must be strings names on oltava merkkijonoja +@msg no composite font named `{n}` yhdistelmäfonttia `{n}` ei ole +@msg names must be a list of names names on oltava luettelo nimistä +@msg no composite fonts to export ei vietäviä yhdistelmäfontteja +@msg not a composite fonts file: {e} ei yhdistelmäfonttitiedosto: {e} +@msg not a composite fonts file: no compositeFonts list ei yhdistelmäfonttitiedosto: luettelo compositeFonts puuttuu +@msg the file holds more than {COMPOSITE_FONTS_MAX} composite fonts tiedostossa on yli {COMPOSITE_FONTS_MAX} yhdistelmäfonttia +@msg the file is larger than 4 MB tiedosto on suurempi kuin 4 Mt +@msg dataBase64 is not base64 dataBase64 ei ole base64-muotoa +@msg give one of path, data or dataBase64 anna path, data tai dataBase64 +@msg the file holds no composite fonts tiedostossa ei ole yhdistelmäfontteja +@msg the file holds two composite fonts named `{_1}` tiedostossa on kaksi yhdistelmäfonttia nimeltä `{_1}` +@msg `{_1}` can't name a composite font `{_1}` ei kelpaa yhdistelmäfontin nimeksi +@msg {k}: give a font {k}: anna fontti +@msg {k}: size must be {_1} to {_2}% {k}: size on oltava {_1}–{_2} % +@msg {k}: baseline must be {_1} to {_2}% {k}: baseline on oltava {_1}–{_2} % +@msg {k}: scale must be {_1} to {_2}% {k}: skaalauksen on oltava {_1}–{_2} % +@msg {k}: only kana, roman and numerals can be scaled {k}: vain kana, roman ja numerals voidaan skaalata +@msg {k}: only kana can be scaled at the center {k}: vain kana voidaan skaalata keskeltä +@msg no composite font ei yhdistelmäfonttia Delete Font Poista fontti Kanji Kanji Kana Kana diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index e84f344060..2468d74626 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -3630,6 +3630,42 @@ digits Malayalam Malayalam Composite Fonts Polices composites Export Composite Fonts Exporter des polices composites Import Composite Fonts Importer des polices composites +@msg each font must be {name, parts} chaque police doit être {name, parts} +@msg unknown key `{k}`; a font takes name and parts clé inconnue `{k}` ; une police prend name et parts +@msg each font needs a name chaque police doit avoir un nom +@msg `{raw}` can't name a composite font (1 to {COMPOSITE_NAME_MAX} characters, no control characters) `{raw}` ne peut pas nommer une police composite (1 à {COMPOSITE_NAME_MAX} caractères, sans caractères de contrôle) +@msg `{name}`: parts must be an object `{name}` : parts doit être un objet +@msg `{name}`: unknown class `{k}`; the classes are kanji, kana, punctuation, symbols, roman and numerals `{name}` : classe inconnue `{k}` ; les classes sont kanji, kana, punctuation, symbols, roman et numerals +@msg `{name}`: {_1} must be an object `{name}` : {_1} doit être un objet +@msg `{name}`: unknown key `{k}` in {_1} `{name}` : clé inconnue `{k}` dans {_1} +@msg `{name}`: give the kanji part `{name}` : indiquez la partie kanji +@msg a document keeps at most {COMPOSITE_FONTS_MAX} composite fonts un document contient au plus {COMPOSITE_FONTS_MAX} polices composites +@msg two composite fonts are named `{_1}` deux polices composites s’appellent `{_1}` +@msg `{_1}` is the name of an installed font `{_1}` est le nom d’une police installée +@msg `{_1}`: {_2} is set in `{font}`, a composite font `{_1}` : {_2} utilise `{font}`, une police composite +@msg give fonts: [{name, parts}…] indiquez fonts : [{name, parts}…] +@msg path is empty path est vide +@msg fonts must be a list of composite fonts fonts doit être une liste de polices composites +@msg names must be strings names doit contenir des chaînes +@msg no composite font named `{n}` aucune police composite nommée `{n}` +@msg names must be a list of names names doit être une liste de noms +@msg no composite fonts to export aucune police composite à exporter +@msg not a composite fonts file: {e} ce n’est pas un fichier de polices composites : {e} +@msg not a composite fonts file: no compositeFonts list ce n’est pas un fichier de polices composites : pas de liste compositeFonts +@msg the file holds more than {COMPOSITE_FONTS_MAX} composite fonts le fichier contient plus de {COMPOSITE_FONTS_MAX} polices composites +@msg the file is larger than 4 MB le fichier dépasse 4 Mo +@msg dataBase64 is not base64 dataBase64 n’est pas du base64 +@msg give one of path, data or dataBase64 indiquez path, data ou dataBase64 +@msg the file holds no composite fonts le fichier ne contient aucune police composite +@msg the file holds two composite fonts named `{_1}` le fichier contient deux polices composites nommées `{_1}` +@msg `{_1}` can't name a composite font `{_1}` ne peut pas nommer une police composite +@msg {k}: give a font {k} : indiquez une police +@msg {k}: size must be {_1} to {_2}% {k} : size doit être compris entre {_1} et {_2} % +@msg {k}: baseline must be {_1} to {_2}% {k} : baseline doit être compris entre {_1} et {_2} % +@msg {k}: scale must be {_1} to {_2}% {k} : l’échelle doit être comprise entre {_1} et {_2} % +@msg {k}: only kana, roman and numerals can be scaled {k} : seuls kana, roman et numerals peuvent être mis à l’échelle +@msg {k}: only kana can be scaled at the center {k} : seul kana peut être mis à l’échelle depuis le centre +@msg no composite font aucune police composite Delete Font Supprimer la police Kanji Kanji Kana Kana diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv index 017bc03750..4246a55ac3 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -4972,6 +4972,42 @@ antiAlias Sharp शार्प Composite Fonts कंपोज़िट फ़ॉन्ट Export Composite Fonts कंपोज़िट फ़ॉन्ट एक्सपोर्ट करें Import Composite Fonts कंपोज़िट फ़ॉन्ट इम्पोर्ट करें +@msg each font must be {name, parts} हर फ़ॉन्ट {name, parts} होना चाहिए +@msg unknown key `{k}`; a font takes name and parts अज्ञात कुंजी `{k}`; फ़ॉन्ट name और parts लेता है +@msg each font needs a name हर फ़ॉन्ट का एक नाम होना चाहिए +@msg `{raw}` can't name a composite font (1 to {COMPOSITE_NAME_MAX} characters, no control characters) `{raw}` कंपोज़िट फ़ॉन्ट का नाम नहीं हो सकता (1 से {COMPOSITE_NAME_MAX} अक्षर, कोई नियंत्रण अक्षर नहीं) +@msg `{name}`: parts must be an object `{name}`: parts एक ऑब्जेक्ट होना चाहिए +@msg `{name}`: unknown class `{k}`; the classes are kanji, kana, punctuation, symbols, roman and numerals `{name}`: अज्ञात वर्ग `{k}`; वर्ग kanji, kana, punctuation, symbols, roman और numerals हैं +@msg `{name}`: {_1} must be an object `{name}`: {_1} एक ऑब्जेक्ट होना चाहिए +@msg `{name}`: unknown key `{k}` in {_1} `{name}`: {_1} में अज्ञात कुंजी `{k}` +@msg `{name}`: give the kanji part `{name}`: kanji भाग दें +@msg a document keeps at most {COMPOSITE_FONTS_MAX} composite fonts एक दस्तावेज़ में अधिकतम {COMPOSITE_FONTS_MAX} कंपोज़िट फ़ॉन्ट रहते हैं +@msg two composite fonts are named `{_1}` दो कंपोज़िट फ़ॉन्ट का नाम `{_1}` है +@msg `{_1}` is the name of an installed font `{_1}` एक इंस्टॉल किए गए फ़ॉन्ट का नाम है +@msg `{_1}`: {_2} is set in `{font}`, a composite font `{_1}`: {_2} `{font}` में सेट है, जो एक कंपोज़िट फ़ॉन्ट है +@msg give fonts: [{name, parts}…] fonts दें: [{name, parts}…] +@msg path is empty path खाली है +@msg fonts must be a list of composite fonts fonts कंपोज़िट फ़ॉन्ट की सूची होनी चाहिए +@msg names must be strings names स्ट्रिंग होने चाहिए +@msg no composite font named `{n}` `{n}` नाम का कोई कंपोज़िट फ़ॉन्ट नहीं है +@msg names must be a list of names names नामों की सूची होनी चाहिए +@msg no composite fonts to export एक्सपोर्ट करने के लिए कोई कंपोज़िट फ़ॉन्ट नहीं है +@msg not a composite fonts file: {e} यह कंपोज़िट फ़ॉन्ट फ़ाइल नहीं है: {e} +@msg not a composite fonts file: no compositeFonts list यह कंपोज़िट फ़ॉन्ट फ़ाइल नहीं है: compositeFonts सूची नहीं है +@msg the file holds more than {COMPOSITE_FONTS_MAX} composite fonts फ़ाइल में {COMPOSITE_FONTS_MAX} से अधिक कंपोज़िट फ़ॉन्ट हैं +@msg the file is larger than 4 MB फ़ाइल 4 MB से बड़ी है +@msg dataBase64 is not base64 dataBase64 base64 नहीं है +@msg give one of path, data or dataBase64 path, data या dataBase64 में से एक दें +@msg the file holds no composite fonts फ़ाइल में कोई कंपोज़िट फ़ॉन्ट नहीं है +@msg the file holds two composite fonts named `{_1}` फ़ाइल में `{_1}` नाम के दो कंपोज़िट फ़ॉन्ट हैं +@msg `{_1}` can't name a composite font `{_1}` कंपोज़िट फ़ॉन्ट का नाम नहीं हो सकता +@msg {k}: give a font {k}: एक फ़ॉन्ट दें +@msg {k}: size must be {_1} to {_2}% {k}: size {_1} से {_2}% तक होना चाहिए +@msg {k}: baseline must be {_1} to {_2}% {k}: baseline {_1} से {_2}% तक होना चाहिए +@msg {k}: scale must be {_1} to {_2}% {k}: स्केल {_1} से {_2}% तक होना चाहिए +@msg {k}: only kana, roman and numerals can be scaled {k}: केवल kana, roman और numerals को स्केल किया जा सकता है +@msg {k}: only kana can be scaled at the center {k}: केंद्र से केवल kana को स्केल किया जा सकता है +@msg no composite font कोई कंपोज़िट फ़ॉन्ट नहीं Delete Font फ़ॉन्ट हटाएँ Kanji कांजी Kana काना diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index effb31e5d8..5c8f222c62 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -4303,6 +4303,42 @@ mark-style Japanese Japán Composite Fonts Összetett betűtípusok Export Composite Fonts Összetett betűtípusok exportálása Import Composite Fonts Összetett betűtípusok importálása +@msg each font must be {name, parts} minden betűtípusnak {name, parts} alakúnak kell lennie +@msg unknown key `{k}`; a font takes name and parts ismeretlen kulcs: `{k}`; egy betűtípus name és parts kulcsot fogad +@msg each font needs a name minden betűtípusnak kell egy név +@msg `{raw}` can't name a composite font (1 to {COMPOSITE_NAME_MAX} characters, no control characters) `{raw}` nem lehet összetett betűtípus neve (1–{COMPOSITE_NAME_MAX} karakter, vezérlőkarakterek nélkül) +@msg `{name}`: parts must be an object `{name}`: a parts értékének objektumnak kell lennie +@msg `{name}`: unknown class `{k}`; the classes are kanji, kana, punctuation, symbols, roman and numerals `{name}`: ismeretlen osztály: `{k}`; az osztályok: kanji, kana, punctuation, symbols, roman és numerals +@msg `{name}`: {_1} must be an object `{name}`: a(z) {_1} értékének objektumnak kell lennie +@msg `{name}`: unknown key `{k}` in {_1} `{name}`: ismeretlen kulcs: `{k}` itt: {_1} +@msg `{name}`: give the kanji part `{name}`: adja meg a kanji részt +@msg a document keeps at most {COMPOSITE_FONTS_MAX} composite fonts egy dokumentum legfeljebb {COMPOSITE_FONTS_MAX} összetett betűtípust tartalmazhat +@msg two composite fonts are named `{_1}` két összetett betűtípus neve is `{_1}` +@msg `{_1}` is the name of an installed font `{_1}` egy telepített betűtípus neve +@msg `{_1}`: {_2} is set in `{font}`, a composite font `{_1}`: a(z) {_2} a(z) `{font}` összetett betűtípust használja +@msg give fonts: [{name, parts}…] adja meg: fonts: [{name, parts}…] +@msg path is empty a path üres +@msg fonts must be a list of composite fonts a fonts értékének összetett betűtípusok listájának kell lennie +@msg names must be strings a names elemeinek karakterláncoknak kell lenniük +@msg no composite font named `{n}` nincs `{n}` nevű összetett betűtípus +@msg names must be a list of names a names értékének nevek listájának kell lennie +@msg no composite fonts to export nincs exportálható összetett betűtípus +@msg not a composite fonts file: {e} nem összetettbetűtípus-fájl: {e} +@msg not a composite fonts file: no compositeFonts list nem összetettbetűtípus-fájl: nincs compositeFonts lista +@msg the file holds more than {COMPOSITE_FONTS_MAX} composite fonts a fájl több mint {COMPOSITE_FONTS_MAX} összetett betűtípust tartalmaz +@msg the file is larger than 4 MB a fájl nagyobb 4 MB-nál +@msg dataBase64 is not base64 a dataBase64 nem base64 +@msg give one of path, data or dataBase64 adja meg a path, data vagy dataBase64 egyikét +@msg the file holds no composite fonts a fájl nem tartalmaz összetett betűtípust +@msg the file holds two composite fonts named `{_1}` a fájl két `{_1}` nevű összetett betűtípust tartalmaz +@msg `{_1}` can't name a composite font `{_1}` nem lehet összetett betűtípus neve +@msg {k}: give a font {k}: adjon meg egy betűtípust +@msg {k}: size must be {_1} to {_2}% {k}: a size értéke {_1}–{_2}% lehet +@msg {k}: baseline must be {_1} to {_2}% {k}: a baseline értéke {_1}–{_2}% lehet +@msg {k}: scale must be {_1} to {_2}% {k}: a méretezés {_1}–{_2}% lehet +@msg {k}: only kana, roman and numerals can be scaled {k}: csak a kana, roman és numerals méretezhető +@msg {k}: only kana can be scaled at the center {k}: csak a kana méretezhető a középpontból +@msg no composite font nincs összetett betűtípus Delete Font Betűtípus törlése Kanji Kandzsi Kana Kana diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 2d6c7e2186..98e597c75e 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -3684,6 +3684,42 @@ digits Malayalam Malayalam Composite Fonts Font compositi Export Composite Fonts Esporta font compositi Import Composite Fonts Importa font compositi +@msg each font must be {name, parts} ogni font deve essere {name, parts} +@msg unknown key `{k}`; a font takes name and parts chiave sconosciuta `{k}`; un font accetta name e parts +@msg each font needs a name ogni font deve avere un nome +@msg `{raw}` can't name a composite font (1 to {COMPOSITE_NAME_MAX} characters, no control characters) `{raw}` non può essere il nome di un font composito (da 1 a {COMPOSITE_NAME_MAX} caratteri, senza caratteri di controllo) +@msg `{name}`: parts must be an object `{name}`: parts deve essere un oggetto +@msg `{name}`: unknown class `{k}`; the classes are kanji, kana, punctuation, symbols, roman and numerals `{name}`: classe sconosciuta `{k}`; le classi sono kanji, kana, punctuation, symbols, roman e numerals +@msg `{name}`: {_1} must be an object `{name}`: {_1} deve essere un oggetto +@msg `{name}`: unknown key `{k}` in {_1} `{name}`: chiave sconosciuta `{k}` in {_1} +@msg `{name}`: give the kanji part `{name}`: indica la parte kanji +@msg a document keeps at most {COMPOSITE_FONTS_MAX} composite fonts un documento contiene al massimo {COMPOSITE_FONTS_MAX} font compositi +@msg two composite fonts are named `{_1}` due font compositi si chiamano `{_1}` +@msg `{_1}` is the name of an installed font `{_1}` è il nome di un font installato +@msg `{_1}`: {_2} is set in `{font}`, a composite font `{_1}`: {_2} usa `{font}`, un font composito +@msg give fonts: [{name, parts}…] indica fonts: [{name, parts}…] +@msg path is empty path è vuoto +@msg fonts must be a list of composite fonts fonts deve essere un elenco di font compositi +@msg names must be strings names deve contenere stringhe +@msg no composite font named `{n}` nessun font composito di nome `{n}` +@msg names must be a list of names names deve essere un elenco di nomi +@msg no composite fonts to export nessun font composito da esportare +@msg not a composite fonts file: {e} non è un file di font compositi: {e} +@msg not a composite fonts file: no compositeFonts list non è un file di font compositi: manca l’elenco compositeFonts +@msg the file holds more than {COMPOSITE_FONTS_MAX} composite fonts il file contiene più di {COMPOSITE_FONTS_MAX} font compositi +@msg the file is larger than 4 MB il file supera 4 MB +@msg dataBase64 is not base64 dataBase64 non è base64 +@msg give one of path, data or dataBase64 indica path, data o dataBase64 +@msg the file holds no composite fonts il file non contiene font compositi +@msg the file holds two composite fonts named `{_1}` il file contiene due font compositi di nome `{_1}` +@msg `{_1}` can't name a composite font `{_1}` non può essere il nome di un font composito +@msg {k}: give a font {k}: indica un font +@msg {k}: size must be {_1} to {_2}% {k}: size deve essere tra {_1} e {_2}% +@msg {k}: baseline must be {_1} to {_2}% {k}: baseline deve essere tra {_1} e {_2}% +@msg {k}: scale must be {_1} to {_2}% {k}: la scala deve essere tra {_1} e {_2}% +@msg {k}: only kana, roman and numerals can be scaled {k}: solo kana, roman e numerals si possono ridimensionare +@msg {k}: only kana can be scaled at the center {k}: solo kana si può ridimensionare dal centro +@msg no composite font nessun font composito Delete Font Elimina font Kanji Kanji Kana Kana diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 17444d894e..89f481878d 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -4013,6 +4013,42 @@ file-filter Templates テンプレート Composite Fonts 合成フォント Export Composite Fonts 合成フォントを書き出し Import Composite Fonts 合成フォントを読み込み +@msg each font must be {name, parts} 各フォントは {name, parts} の形にしてください +@msg unknown key `{k}`; a font takes name and parts `{k}` は不明なキーです。フォントに使えるのは name と parts です +@msg each font needs a name 各フォントには名前が必要です +@msg `{raw}` can't name a composite font (1 to {COMPOSITE_NAME_MAX} characters, no control characters) `{raw}` は合成フォントの名前にできません (1~{COMPOSITE_NAME_MAX} 文字、制御文字なし) +@msg `{name}`: parts must be an object `{name}`: parts はオブジェクトにしてください +@msg `{name}`: unknown class `{k}`; the classes are kanji, kana, punctuation, symbols, roman and numerals `{name}`: `{k}` は不明なクラスです。クラスは kanji、kana、punctuation、symbols、roman、numerals です +@msg `{name}`: {_1} must be an object `{name}`: {_1} はオブジェクトにしてください +@msg `{name}`: unknown key `{k}` in {_1} `{name}`: {_1} の `{k}` は不明なキーです +@msg `{name}`: give the kanji part `{name}`: kanji の部分を指定してください +@msg a document keeps at most {COMPOSITE_FONTS_MAX} composite fonts 1 つのドキュメントに置ける合成フォントは {COMPOSITE_FONTS_MAX} 個までです +@msg two composite fonts are named `{_1}` `{_1}` という名前の合成フォントが 2 つあります +@msg `{_1}` is the name of an installed font `{_1}` はインストール済みのフォントの名前です +@msg `{_1}`: {_2} is set in `{font}`, a composite font `{_1}`: {_2} に合成フォントの `{font}` が設定されています +@msg give fonts: [{name, parts}…] fonts を指定してください: [{name, parts}…] +@msg path is empty path が空です +@msg fonts must be a list of composite fonts fonts は合成フォントのリストにしてください +@msg names must be strings names は文字列にしてください +@msg no composite font named `{n}` `{n}` という合成フォントはありません +@msg names must be a list of names names は名前のリストにしてください +@msg no composite fonts to export 書き出す合成フォントがありません +@msg not a composite fonts file: {e} 合成フォントのファイルではありません: {e} +@msg not a composite fonts file: no compositeFonts list 合成フォントのファイルではありません: compositeFonts のリストがありません +@msg the file holds more than {COMPOSITE_FONTS_MAX} composite fonts ファイルに {COMPOSITE_FONTS_MAX} 個を超える合成フォントがあります +@msg the file is larger than 4 MB ファイルが 4 MB を超えています +@msg dataBase64 is not base64 dataBase64 が base64 ではありません +@msg give one of path, data or dataBase64 path、data、dataBase64 のいずれかを指定してください +@msg the file holds no composite fonts ファイルに合成フォントがありません +@msg the file holds two composite fonts named `{_1}` ファイルに `{_1}` という名前の合成フォントが 2 つあります +@msg `{_1}` can't name a composite font `{_1}` は合成フォントの名前にできません +@msg {k}: give a font {k}: フォントを指定してください +@msg {k}: size must be {_1} to {_2}% {k}: size は {_1}~{_2}% にしてください +@msg {k}: baseline must be {_1} to {_2}% {k}: baseline は {_1}~{_2}% にしてください +@msg {k}: scale must be {_1} to {_2}% {k}: 比率は {_1}~{_2}% にしてください +@msg {k}: only kana, roman and numerals can be scaled {k}: 比率を変更できるのは kana、roman、numerals だけです +@msg {k}: only kana can be scaled at the center {k}: 中央を基準に比率を変更できるのは kana だけです +@msg no composite font 合成フォントがありません Delete Font フォントを削除 Kanji 漢字 Kana かな diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index be8a847cb2..07ae7d42b2 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -1482,6 +1482,42 @@ digits Malayalam მალაიალამური Composite Fonts კომპოზიტური შრიფტები Export Composite Fonts კომპოზიტური შრიფტების ექსპორტი Import Composite Fonts კომპოზიტური შრიფტების იმპორტი +@msg each font must be {name, parts} თითოეული შრიფტი უნდა იყოს {name, parts} +@msg unknown key `{k}`; a font takes name and parts უცნობი გასაღები `{k}`; შრიფტი იღებს name-სა და parts-ს +@msg each font needs a name თითოეულ შრიფტს სახელი სჭირდება +@msg `{raw}` can't name a composite font (1 to {COMPOSITE_NAME_MAX} characters, no control characters) `{raw}` ვერ იქნება კომპოზიტური შრიფტის სახელი (1-დან {COMPOSITE_NAME_MAX} სიმბოლომდე, მმართველი სიმბოლოების გარეშე) +@msg `{name}`: parts must be an object `{name}`: parts უნდა იყოს ობიექტი +@msg `{name}`: unknown class `{k}`; the classes are kanji, kana, punctuation, symbols, roman and numerals `{name}`: უცნობი კლასი `{k}`; კლასებია: kanji, kana, punctuation, symbols, roman და numerals +@msg `{name}`: {_1} must be an object `{name}`: {_1} უნდა იყოს ობიექტი +@msg `{name}`: unknown key `{k}` in {_1} `{name}`: უცნობი გასაღები `{k}` ნაწილში {_1} +@msg `{name}`: give the kanji part `{name}`: მიუთითეთ ნაწილი kanji +@msg a document keeps at most {COMPOSITE_FONTS_MAX} composite fonts დოკუმენტი შეიცავს არაუმეტეს {COMPOSITE_FONTS_MAX} კომპოზიტურ შრიფტს +@msg two composite fonts are named `{_1}` ორ კომპოზიტურ შრიფტს ჰქვია `{_1}` +@msg `{_1}` is the name of an installed font `{_1}` დაინსტალირებული შრიფტის სახელია +@msg `{_1}`: {_2} is set in `{font}`, a composite font `{_1}`: {_2} იყენებს `{font}`-ს, რომელიც კომპოზიტური შრიფტია +@msg give fonts: [{name, parts}…] მიუთითეთ fonts: [{name, parts}…] +@msg path is empty path ცარიელია +@msg fonts must be a list of composite fonts fonts უნდა იყოს კომპოზიტური შრიფტების სია +@msg names must be strings names უნდა იყოს სტრიქონები +@msg no composite font named `{n}` კომპოზიტური შრიფტი სახელით `{n}` არ არსებობს +@msg names must be a list of names names უნდა იყოს სახელების სია +@msg no composite fonts to export საექსპორტო კომპოზიტური შრიფტები არ არის +@msg not a composite fonts file: {e} ეს არ არის კომპოზიტური შრიფტების ფაილი: {e} +@msg not a composite fonts file: no compositeFonts list ეს არ არის კომპოზიტური შრიფტების ფაილი: სია compositeFonts არ არის +@msg the file holds more than {COMPOSITE_FONTS_MAX} composite fonts ფაილი შეიცავს {COMPOSITE_FONTS_MAX}-ზე მეტ კომპოზიტურ შრიფტს +@msg the file is larger than 4 MB ფაილი 4 MB-ზე დიდია +@msg dataBase64 is not base64 dataBase64 არ არის base64 +@msg give one of path, data or dataBase64 მიუთითეთ path, data ან dataBase64 +@msg the file holds no composite fonts ფაილი კომპოზიტურ შრიფტებს არ შეიცავს +@msg the file holds two composite fonts named `{_1}` ფაილი შეიცავს ორ კომპოზიტურ შრიფტს სახელით `{_1}` +@msg `{_1}` can't name a composite font `{_1}` ვერ იქნება კომპოზიტური შრიფტის სახელი +@msg {k}: give a font {k}: მიუთითეთ შრიფტი +@msg {k}: size must be {_1} to {_2}% {k}: size უნდა იყოს {_1}-დან {_2}%-მდე +@msg {k}: baseline must be {_1} to {_2}% {k}: baseline უნდა იყოს {_1}-დან {_2}%-მდე +@msg {k}: scale must be {_1} to {_2}% {k}: მასშტაბი უნდა იყოს {_1}-დან {_2}%-მდე +@msg {k}: only kana, roman and numerals can be scaled {k}: მასშტაბირება შეიძლება მხოლოდ kana, roman და numerals ნაწილებში +@msg {k}: only kana can be scaled at the center {k}: ცენტრიდან მასშტაბირება შეიძლება მხოლოდ kana ნაწილში +@msg no composite font კომპოზიტური შრიფტი არ არის Delete Font შრიფტის წაშლა Kanji კანძი Kana კანა diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 991daba6d0..fbb22fd589 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -4001,6 +4001,42 @@ file-filter Templates 템플릿 Composite Fonts 합성 글꼴 Export Composite Fonts 합성 글꼴 내보내기 Import Composite Fonts 합성 글꼴 가져오기 +@msg each font must be {name, parts} 각 글꼴은 {name, parts} 형식이어야 합니다 +@msg unknown key `{k}`; a font takes name and parts 알 수 없는 키 `{k}`: 글꼴에는 name과 parts만 쓸 수 있습니다 +@msg each font needs a name 각 글꼴에는 이름이 필요합니다 +@msg `{raw}` can't name a composite font (1 to {COMPOSITE_NAME_MAX} characters, no control characters) `{raw}`은(는) 합성 글꼴 이름으로 쓸 수 없습니다(1~{COMPOSITE_NAME_MAX}자, 제어 문자 없음) +@msg `{name}`: parts must be an object `{name}`: parts는 객체여야 합니다 +@msg `{name}`: unknown class `{k}`; the classes are kanji, kana, punctuation, symbols, roman and numerals `{name}`: 알 수 없는 클래스 `{k}`: 클래스는 kanji, kana, punctuation, symbols, roman, numerals입니다 +@msg `{name}`: {_1} must be an object `{name}`: {_1}은(는) 객체여야 합니다 +@msg `{name}`: unknown key `{k}` in {_1} `{name}`: {_1}에 알 수 없는 키 `{k}`이(가) 있습니다 +@msg `{name}`: give the kanji part `{name}`: kanji 부분을 지정하세요 +@msg a document keeps at most {COMPOSITE_FONTS_MAX} composite fonts 문서에는 합성 글꼴을 최대 {COMPOSITE_FONTS_MAX}개까지 둘 수 있습니다 +@msg two composite fonts are named `{_1}` 이름이 `{_1}`인 합성 글꼴이 두 개 있습니다 +@msg `{_1}` is the name of an installed font `{_1}`은(는) 설치된 글꼴의 이름입니다 +@msg `{_1}`: {_2} is set in `{font}`, a composite font `{_1}`: {_2}이(가) 합성 글꼴인 `{font}`(으)로 설정되어 있습니다 +@msg give fonts: [{name, parts}…] fonts를 지정하세요: [{name, parts}…] +@msg path is empty path가 비어 있습니다 +@msg fonts must be a list of composite fonts fonts는 합성 글꼴 목록이어야 합니다 +@msg names must be strings names는 문자열이어야 합니다 +@msg no composite font named `{n}` 이름이 `{n}`인 합성 글꼴이 없습니다 +@msg names must be a list of names names는 이름 목록이어야 합니다 +@msg no composite fonts to export 내보낼 합성 글꼴이 없습니다 +@msg not a composite fonts file: {e} 합성 글꼴 파일이 아닙니다: {e} +@msg not a composite fonts file: no compositeFonts list 합성 글꼴 파일이 아닙니다: compositeFonts 목록이 없습니다 +@msg the file holds more than {COMPOSITE_FONTS_MAX} composite fonts 파일에 합성 글꼴이 {COMPOSITE_FONTS_MAX}개보다 많습니다 +@msg the file is larger than 4 MB 파일이 4MB보다 큽니다 +@msg dataBase64 is not base64 dataBase64가 base64가 아닙니다 +@msg give one of path, data or dataBase64 path, data, dataBase64 중 하나를 지정하세요 +@msg the file holds no composite fonts 파일에 합성 글꼴이 없습니다 +@msg the file holds two composite fonts named `{_1}` 파일에 이름이 `{_1}`인 합성 글꼴이 두 개 있습니다 +@msg `{_1}` can't name a composite font `{_1}`은(는) 합성 글꼴 이름으로 쓸 수 없습니다 +@msg {k}: give a font {k}: 글꼴을 지정하세요 +@msg {k}: size must be {_1} to {_2}% {k}: size는 {_1}~{_2}%여야 합니다 +@msg {k}: baseline must be {_1} to {_2}% {k}: baseline은 {_1}~{_2}%여야 합니다 +@msg {k}: scale must be {_1} to {_2}% {k}: 비율은 {_1}~{_2}%여야 합니다 +@msg {k}: only kana, roman and numerals can be scaled {k}: 비율을 바꿀 수 있는 것은 kana, roman, numerals뿐입니다 +@msg {k}: only kana can be scaled at the center {k}: 가운데를 기준으로 비율을 바꿀 수 있는 것은 kana뿐입니다 +@msg no composite font 합성 글꼴이 없습니다 Delete Font 글꼴 삭제 Kanji 한자 Kana 가나 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index b8ca33858f..3335e94b74 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -2059,6 +2059,42 @@ digits Malayalam Malayalam Composite Fonts Samengestelde lettertypen Export Composite Fonts Samengestelde lettertypen exporteren Import Composite Fonts Samengestelde lettertypen importeren +@msg each font must be {name, parts} elk lettertype moet {name, parts} zijn +@msg unknown key `{k}`; a font takes name and parts onbekende sleutel `{k}`; een lettertype neemt name en parts +@msg each font needs a name elk lettertype heeft een naam nodig +@msg `{raw}` can't name a composite font (1 to {COMPOSITE_NAME_MAX} characters, no control characters) `{raw}` kan geen naam van een samengesteld lettertype zijn (1 tot {COMPOSITE_NAME_MAX} tekens, geen besturingstekens) +@msg `{name}`: parts must be an object `{name}`: parts moet een object zijn +@msg `{name}`: unknown class `{k}`; the classes are kanji, kana, punctuation, symbols, roman and numerals `{name}`: onbekende klasse `{k}`; de klassen zijn kanji, kana, punctuation, symbols, roman en numerals +@msg `{name}`: {_1} must be an object `{name}`: {_1} moet een object zijn +@msg `{name}`: unknown key `{k}` in {_1} `{name}`: onbekende sleutel `{k}` in {_1} +@msg `{name}`: give the kanji part `{name}`: geef het deel kanji op +@msg a document keeps at most {COMPOSITE_FONTS_MAX} composite fonts een document bevat hoogstens {COMPOSITE_FONTS_MAX} samengestelde lettertypen +@msg two composite fonts are named `{_1}` twee samengestelde lettertypen heten `{_1}` +@msg `{_1}` is the name of an installed font `{_1}` is de naam van een geïnstalleerd lettertype +@msg `{_1}`: {_2} is set in `{font}`, a composite font `{_1}`: {_2} gebruikt `{font}`, een samengesteld lettertype +@msg give fonts: [{name, parts}…] geef fonts op: [{name, parts}…] +@msg path is empty path is leeg +@msg fonts must be a list of composite fonts fonts moet een lijst van samengestelde lettertypen zijn +@msg names must be strings names moeten tekenreeksen zijn +@msg no composite font named `{n}` geen samengesteld lettertype met de naam `{n}` +@msg names must be a list of names names moet een lijst van namen zijn +@msg no composite fonts to export geen samengestelde lettertypen om te exporteren +@msg not a composite fonts file: {e} geen bestand met samengestelde lettertypen: {e} +@msg not a composite fonts file: no compositeFonts list geen bestand met samengestelde lettertypen: geen lijst compositeFonts +@msg the file holds more than {COMPOSITE_FONTS_MAX} composite fonts het bestand bevat meer dan {COMPOSITE_FONTS_MAX} samengestelde lettertypen +@msg the file is larger than 4 MB het bestand is groter dan 4 MB +@msg dataBase64 is not base64 dataBase64 is geen base64 +@msg give one of path, data or dataBase64 geef path, data of dataBase64 op +@msg the file holds no composite fonts het bestand bevat geen samengestelde lettertypen +@msg the file holds two composite fonts named `{_1}` het bestand bevat twee samengestelde lettertypen met de naam `{_1}` +@msg `{_1}` can't name a composite font `{_1}` kan geen naam van een samengesteld lettertype zijn +@msg {k}: give a font {k}: geef een lettertype op +@msg {k}: size must be {_1} to {_2}% {k}: size moet tussen {_1} en {_2}% liggen +@msg {k}: baseline must be {_1} to {_2}% {k}: baseline moet tussen {_1} en {_2}% liggen +@msg {k}: scale must be {_1} to {_2}% {k}: de schaal moet tussen {_1} en {_2}% liggen +@msg {k}: only kana, roman and numerals can be scaled {k}: alleen kana, roman en numerals kunnen worden geschaald +@msg {k}: only kana can be scaled at the center {k}: alleen kana kan vanuit het midden worden geschaald +@msg no composite font geen samengesteld lettertype Delete Font Lettertype verwijderen Kanji Kanji Kana Kana diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index eb5afe87cc..cb236dfcaf 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -1550,6 +1550,42 @@ digits Malayalam Malajalam Composite Fonts Fonty złożone Export Composite Fonts Eksportuj fonty złożone Import Composite Fonts Importuj fonty złożone +@msg each font must be {name, parts} każdy font musi mieć postać {name, parts} +@msg unknown key `{k}`; a font takes name and parts nieznany klucz `{k}`; font przyjmuje name i parts +@msg each font needs a name każdy font musi mieć nazwę +@msg `{raw}` can't name a composite font (1 to {COMPOSITE_NAME_MAX} characters, no control characters) `{raw}` nie może być nazwą fontu złożonego (od 1 do {COMPOSITE_NAME_MAX} znaków, bez znaków sterujących) +@msg `{name}`: parts must be an object `{name}`: parts musi być obiektem +@msg `{name}`: unknown class `{k}`; the classes are kanji, kana, punctuation, symbols, roman and numerals `{name}`: nieznana klasa `{k}`; klasy to kanji, kana, punctuation, symbols, roman i numerals +@msg `{name}`: {_1} must be an object `{name}`: {_1} musi być obiektem +@msg `{name}`: unknown key `{k}` in {_1} `{name}`: nieznany klucz `{k}` w {_1} +@msg `{name}`: give the kanji part `{name}`: podaj część kanji +@msg a document keeps at most {COMPOSITE_FONTS_MAX} composite fonts dokument może zawierać najwyżej {COMPOSITE_FONTS_MAX} fontów złożonych +@msg two composite fonts are named `{_1}` dwa fonty złożone mają nazwę `{_1}` +@msg `{_1}` is the name of an installed font `{_1}` to nazwa zainstalowanego fontu +@msg `{_1}`: {_2} is set in `{font}`, a composite font `{_1}`: {_2} używa `{font}`, który jest fontem złożonym +@msg give fonts: [{name, parts}…] podaj fonts: [{name, parts}…] +@msg path is empty path jest puste +@msg fonts must be a list of composite fonts fonts musi być listą fontów złożonych +@msg names must be strings names muszą być ciągami znaków +@msg no composite font named `{n}` brak fontu złożonego o nazwie `{n}` +@msg names must be a list of names names musi być listą nazw +@msg no composite fonts to export brak fontów złożonych do eksportu +@msg not a composite fonts file: {e} to nie jest plik fontów złożonych: {e} +@msg not a composite fonts file: no compositeFonts list to nie jest plik fontów złożonych: brak listy compositeFonts +@msg the file holds more than {COMPOSITE_FONTS_MAX} composite fonts plik zawiera więcej niż {COMPOSITE_FONTS_MAX} fontów złożonych +@msg the file is larger than 4 MB plik jest większy niż 4 MB +@msg dataBase64 is not base64 dataBase64 nie jest w formacie base64 +@msg give one of path, data or dataBase64 podaj path, data lub dataBase64 +@msg the file holds no composite fonts plik nie zawiera fontów złożonych +@msg the file holds two composite fonts named `{_1}` plik zawiera dwa fonty złożone o nazwie `{_1}` +@msg `{_1}` can't name a composite font `{_1}` nie może być nazwą fontu złożonego +@msg {k}: give a font {k}: podaj font +@msg {k}: size must be {_1} to {_2}% {k}: size musi wynosić od {_1} do {_2}% +@msg {k}: baseline must be {_1} to {_2}% {k}: baseline musi wynosić od {_1} do {_2}% +@msg {k}: scale must be {_1} to {_2}% {k}: skala musi wynosić od {_1} do {_2}% +@msg {k}: only kana, roman and numerals can be scaled {k}: skalować można tylko kana, roman i numerals +@msg {k}: only kana can be scaled at the center {k}: od środka można skalować tylko kana +@msg no composite font brak fontu złożonego Delete Font Usuń font Kanji Kanji Kana Kana diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 80615d6370..8f9a2ef218 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -5181,6 +5181,42 @@ axis Both Ambos Composite Fonts Fontes compostas Export Composite Fonts Exportar fontes compostas Import Composite Fonts Importar fontes compostas +@msg each font must be {name, parts} cada fonte deve ser {name, parts} +@msg unknown key `{k}`; a font takes name and parts chave desconhecida `{k}`; uma fonte aceita name e parts +@msg each font needs a name cada fonte precisa de um nome +@msg `{raw}` can't name a composite font (1 to {COMPOSITE_NAME_MAX} characters, no control characters) `{raw}` não pode nomear uma fonte composta (de 1 a {COMPOSITE_NAME_MAX} caracteres, sem caracteres de controle) +@msg `{name}`: parts must be an object `{name}`: parts deve ser um objeto +@msg `{name}`: unknown class `{k}`; the classes are kanji, kana, punctuation, symbols, roman and numerals `{name}`: classe desconhecida `{k}`; as classes são kanji, kana, punctuation, symbols, roman e numerals +@msg `{name}`: {_1} must be an object `{name}`: {_1} deve ser um objeto +@msg `{name}`: unknown key `{k}` in {_1} `{name}`: chave desconhecida `{k}` em {_1} +@msg `{name}`: give the kanji part `{name}`: informe a parte kanji +@msg a document keeps at most {COMPOSITE_FONTS_MAX} composite fonts um documento contém no máximo {COMPOSITE_FONTS_MAX} fontes compostas +@msg two composite fonts are named `{_1}` duas fontes compostas se chamam `{_1}` +@msg `{_1}` is the name of an installed font `{_1}` é o nome de uma fonte instalada +@msg `{_1}`: {_2} is set in `{font}`, a composite font `{_1}`: {_2} usa `{font}`, uma fonte composta +@msg give fonts: [{name, parts}…] informe fonts: [{name, parts}…] +@msg path is empty path está vazio +@msg fonts must be a list of composite fonts fonts deve ser uma lista de fontes compostas +@msg names must be strings names deve conter strings +@msg no composite font named `{n}` nenhuma fonte composta chamada `{n}` +@msg names must be a list of names names deve ser uma lista de nomes +@msg no composite fonts to export nenhuma fonte composta para exportar +@msg not a composite fonts file: {e} não é um arquivo de fontes compostas: {e} +@msg not a composite fonts file: no compositeFonts list não é um arquivo de fontes compostas: não há lista compositeFonts +@msg the file holds more than {COMPOSITE_FONTS_MAX} composite fonts o arquivo contém mais de {COMPOSITE_FONTS_MAX} fontes compostas +@msg the file is larger than 4 MB o arquivo tem mais de 4 MB +@msg dataBase64 is not base64 dataBase64 não é base64 +@msg give one of path, data or dataBase64 informe path, data ou dataBase64 +@msg the file holds no composite fonts o arquivo não contém fontes compostas +@msg the file holds two composite fonts named `{_1}` o arquivo contém duas fontes compostas chamadas `{_1}` +@msg `{_1}` can't name a composite font `{_1}` não pode nomear uma fonte composta +@msg {k}: give a font {k}: informe uma fonte +@msg {k}: size must be {_1} to {_2}% {k}: size deve estar entre {_1} e {_2}% +@msg {k}: baseline must be {_1} to {_2}% {k}: baseline deve estar entre {_1} e {_2}% +@msg {k}: scale must be {_1} to {_2}% {k}: a escala deve estar entre {_1} e {_2}% +@msg {k}: only kana, roman and numerals can be scaled {k}: só kana, roman e numerals podem ser escalados +@msg {k}: only kana can be scaled at the center {k}: só kana pode ser escalado a partir do centro +@msg no composite font nenhuma fonte composta Delete Font Excluir fonte Kanji Kanji Kana Kana diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index c830e69b37..84da770f7a 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -3670,6 +3670,42 @@ digits Malayalam Малаялам Composite Fonts Составные шрифты Export Composite Fonts Экспорт составных шрифтов Import Composite Fonts Импорт составных шрифтов +@msg each font must be {name, parts} каждый шрифт должен иметь вид {name, parts} +@msg unknown key `{k}`; a font takes name and parts неизвестный ключ `{k}`; шрифт принимает name и parts +@msg each font needs a name у каждого шрифта должно быть имя +@msg `{raw}` can't name a composite font (1 to {COMPOSITE_NAME_MAX} characters, no control characters) `{raw}` не может быть именем составного шрифта (от 1 до {COMPOSITE_NAME_MAX} символов, без управляющих символов) +@msg `{name}`: parts must be an object `{name}`: parts должен быть объектом +@msg `{name}`: unknown class `{k}`; the classes are kanji, kana, punctuation, symbols, roman and numerals `{name}`: неизвестный класс `{k}`; классы: kanji, kana, punctuation, symbols, roman и numerals +@msg `{name}`: {_1} must be an object `{name}`: {_1} должен быть объектом +@msg `{name}`: unknown key `{k}` in {_1} `{name}`: неизвестный ключ `{k}` в {_1} +@msg `{name}`: give the kanji part `{name}`: укажите часть kanji +@msg a document keeps at most {COMPOSITE_FONTS_MAX} composite fonts документ содержит не более {COMPOSITE_FONTS_MAX} составных шрифтов +@msg two composite fonts are named `{_1}` два составных шрифта называются `{_1}` +@msg `{_1}` is the name of an installed font `{_1}` является именем установленного шрифта +@msg `{_1}`: {_2} is set in `{font}`, a composite font `{_1}`: {_2} использует `{font}`, а это составной шрифт +@msg give fonts: [{name, parts}…] укажите fonts: [{name, parts}…] +@msg path is empty path пуст +@msg fonts must be a list of composite fonts fonts должен быть списком составных шрифтов +@msg names must be strings names должны быть строками +@msg no composite font named `{n}` нет составного шрифта с именем `{n}` +@msg names must be a list of names names должен быть списком имён +@msg no composite fonts to export нет составных шрифтов для экспорта +@msg not a composite fonts file: {e} это не файл составных шрифтов: {e} +@msg not a composite fonts file: no compositeFonts list это не файл составных шрифтов: нет списка compositeFonts +@msg the file holds more than {COMPOSITE_FONTS_MAX} composite fonts в файле больше {COMPOSITE_FONTS_MAX} составных шрифтов +@msg the file is larger than 4 MB файл больше 4 МБ +@msg dataBase64 is not base64 dataBase64 не в формате base64 +@msg give one of path, data or dataBase64 укажите path, data или dataBase64 +@msg the file holds no composite fonts в файле нет составных шрифтов +@msg the file holds two composite fonts named `{_1}` в файле два составных шрифта с именем `{_1}` +@msg `{_1}` can't name a composite font `{_1}` не может быть именем составного шрифта +@msg {k}: give a font {k}: укажите шрифт +@msg {k}: size must be {_1} to {_2}% {k}: size должен быть от {_1} до {_2}% +@msg {k}: baseline must be {_1} to {_2}% {k}: baseline должен быть от {_1} до {_2}% +@msg {k}: scale must be {_1} to {_2}% {k}: масштаб должен быть от {_1} до {_2}% +@msg {k}: only kana, roman and numerals can be scaled {k}: масштабировать можно только kana, roman и numerals +@msg {k}: only kana can be scaled at the center {k}: масштабировать от центра можно только kana +@msg no composite font нет составного шрифта Delete Font Удалить шрифт Kanji Кандзи Kana Кана diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index d4304b97bd..3994415e3a 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -3560,6 +3560,42 @@ digits Malayalam Malayalam Composite Fonts Sammansatta typsnitt Export Composite Fonts Exportera sammansatta typsnitt Import Composite Fonts Importera sammansatta typsnitt +@msg each font must be {name, parts} varje typsnitt måste vara {name, parts} +@msg unknown key `{k}`; a font takes name and parts okänd nyckel `{k}`; ett typsnitt tar name och parts +@msg each font needs a name varje typsnitt behöver ett namn +@msg `{raw}` can't name a composite font (1 to {COMPOSITE_NAME_MAX} characters, no control characters) `{raw}` kan inte vara namnet på ett sammansatt typsnitt (1 till {COMPOSITE_NAME_MAX} tecken, inga styrtecken) +@msg `{name}`: parts must be an object `{name}`: parts måste vara ett objekt +@msg `{name}`: unknown class `{k}`; the classes are kanji, kana, punctuation, symbols, roman and numerals `{name}`: okänd klass `{k}`; klasserna är kanji, kana, punctuation, symbols, roman och numerals +@msg `{name}`: {_1} must be an object `{name}`: {_1} måste vara ett objekt +@msg `{name}`: unknown key `{k}` in {_1} `{name}`: okänd nyckel `{k}` i {_1} +@msg `{name}`: give the kanji part `{name}`: ange delen kanji +@msg a document keeps at most {COMPOSITE_FONTS_MAX} composite fonts ett dokument har högst {COMPOSITE_FONTS_MAX} sammansatta typsnitt +@msg two composite fonts are named `{_1}` två sammansatta typsnitt heter `{_1}` +@msg `{_1}` is the name of an installed font `{_1}` är namnet på ett installerat typsnitt +@msg `{_1}`: {_2} is set in `{font}`, a composite font `{_1}`: {_2} använder `{font}`, ett sammansatt typsnitt +@msg give fonts: [{name, parts}…] ange fonts: [{name, parts}…] +@msg path is empty path är tom +@msg fonts must be a list of composite fonts fonts måste vara en lista med sammansatta typsnitt +@msg names must be strings names måste vara strängar +@msg no composite font named `{n}` inget sammansatt typsnitt heter `{n}` +@msg names must be a list of names names måste vara en lista med namn +@msg no composite fonts to export inga sammansatta typsnitt att exportera +@msg not a composite fonts file: {e} inte en fil med sammansatta typsnitt: {e} +@msg not a composite fonts file: no compositeFonts list inte en fil med sammansatta typsnitt: ingen lista compositeFonts +@msg the file holds more than {COMPOSITE_FONTS_MAX} composite fonts filen innehåller fler än {COMPOSITE_FONTS_MAX} sammansatta typsnitt +@msg the file is larger than 4 MB filen är större än 4 MB +@msg dataBase64 is not base64 dataBase64 är inte base64 +@msg give one of path, data or dataBase64 ange path, data eller dataBase64 +@msg the file holds no composite fonts filen innehåller inga sammansatta typsnitt +@msg the file holds two composite fonts named `{_1}` filen innehåller två sammansatta typsnitt som heter `{_1}` +@msg `{_1}` can't name a composite font `{_1}` kan inte vara namnet på ett sammansatt typsnitt +@msg {k}: give a font {k}: ange ett typsnitt +@msg {k}: size must be {_1} to {_2}% {k}: size måste vara {_1} till {_2} % +@msg {k}: baseline must be {_1} to {_2}% {k}: baseline måste vara {_1} till {_2} % +@msg {k}: scale must be {_1} to {_2}% {k}: skalan måste vara {_1} till {_2} % +@msg {k}: only kana, roman and numerals can be scaled {k}: bara kana, roman och numerals kan skalas +@msg {k}: only kana can be scaled at the center {k}: bara kana kan skalas från mitten +@msg no composite font inget sammansatt typsnitt Delete Font Ta bort typsnitt Kanji Kanji Kana Kana diff --git a/crates/ui-egui/src/i18n/tests.rs b/crates/ui-egui/src/i18n/tests.rs index d7f2c8a2ef..72f9a4db10 100644 --- a/crates/ui-egui/src/i18n/tests.rs +++ b/crates/ui-egui/src/i18n/tests.rs @@ -1801,6 +1801,81 @@ fn recolor_notes_on_objects_left_unchanged_translate_with_their_counts() { } } +/// Every complete-message catalog has a row for each error that `type.compositeFonts.set`, +/// `.export` and `.import`, the composite font checks behind them and the Composite Fonts dialog +/// give, and translates them with the font name, the key and the class key kept, and the reason +/// after a font's name translated in turn. +#[test] +fn composite_font_errors_translate_in_complete_languages() { + const SOURCES: &[&str] = &[ + "each font must be {name, parts}", + "unknown key `{k}`; a font takes name and parts", + "each font needs a name", + "`{raw}` can't name a composite font (1 to {COMPOSITE_NAME_MAX} characters, no control characters)", + "`{name}`: parts must be an object", + "`{name}`: unknown class `{k}`; the classes are kanji, kana, punctuation, symbols, roman and numerals", + "`{name}`: {_1} must be an object", + "`{name}`: unknown key `{k}` in {_1}", + "`{name}`: give the kanji part", + "a document keeps at most {COMPOSITE_FONTS_MAX} composite fonts", + "two composite fonts are named `{_1}`", + "`{_1}` is the name of an installed font", + "`{_1}`: {_2} is set in `{font}`, a composite font", + "give fonts: [{name, parts}…]", + "path is empty", + "fonts must be a list of composite fonts", + "names must be strings", + "no composite font named `{n}`", + "names must be a list of names", + "no composite fonts to export", + "not a composite fonts file: {e}", + "not a composite fonts file: no compositeFonts list", + "the file holds more than {COMPOSITE_FONTS_MAX} composite fonts", + "the file is larger than 4 MB", + "dataBase64 is not base64", + "give one of path, data or dataBase64", + "the file holds no composite fonts", + "the file holds two composite fonts named `{_1}`", + "`{_1}` can't name a composite font", + "{k}: give a font", + "{k}: size must be {_1} to {_2}%", + "{k}: baseline must be {_1} to {_2}%", + "{k}: scale must be {_1} to {_2}%", + "{k}: only kana, roman and numerals can be scaled", + "{k}: only kana can be scaled at the center", + "no composite font", + ]; + // An error, English words its translation replaces, and the text it keeps. + let cases: &[(&str, &str, &[&str])] = &[ + ( + "`Mincho Mix`: unknown class `kanjj`; the classes are kanji, kana, punctuation, symbols, roman and numerals", + "the classes are", + &["`Mincho Mix`", "`kanjj`", "kanji", "kana", "punctuation", "symbols", "roman", "numerals"], + ), + ("`Mincho Mix`: give the kanji part", "give the", &["`Mincho Mix`", "kanji"]), + ("`Mincho Mix`: parts must be an object", "must be an object", &["`Mincho Mix`", "parts"]), + ("`Mincho Mix`: kana must be an object", "must be an object", &["`Mincho Mix`", "kana"]), + ("`Mincho Mix`: unknown key `weight` in roman", "unknown key", &["`Mincho Mix`", "`weight`", "roman"]), + ("two composite fonts are named `Mincho Mix`", "are named", &["`Mincho Mix`"]), + ("`Mincho Mix`: kana: give a font", "give a font", &["`Mincho Mix`", "kana"]), + ("not a composite fonts file: the file is larger than 4 MB", "larger than", &["4"]), + ]; + for code in COMPLETE_MESSAGES { + let lang = Lang::from_code(code).expect("registered"); + let (entries, _) = parse_entries(lang.0.source); + let rows: Vec<&str> = entries.iter().filter(|(ctx, _, _)| ctx == "@msg").map(|(_, src, _)| src.as_str()).collect(); + let missing: Vec<&&str> = SOURCES.iter().filter(|s| !rows.contains(s)).collect(); + assert!(missing.is_empty(), "{code} has no row for {missing:?}"); + for (error, english, kept) in cases { + let shown = message(lang, error); + assert!(!shown.contains(english), "{code}: {shown}"); + for k in *kept { + assert!(shown.contains(k), "{code}: {k} in {shown}"); + } + } + } +} + #[test] fn package_status_message_templates_parse() { let c = Catalog::parse(concat!( @@ -3542,8 +3617,13 @@ const NAMED_KEYS: &[(&str, &[&str])] = &[ ("column must be true or a number from 1", &["column"]), ("column: true floats the whole column and takes no onto, above or below", &["column", "onto", "above", "below"]), ("control must be fillStroke, coloring, drawMode or screenMode", &["control", "fillStroke", "coloring", "drawMode", "screenMode"]), + ("dataBase64 is not base64", &["dataBase64"]), ("decimals must be {_1}–{_2} (got {_3})", &["decimals"]), ("detect must be true or false", &["detect"]), + ("each font must be {name, parts}", &["name", "parts"]), + ("fonts must be a list of composite fonts", &["fonts"]), + ("give fonts: [{name, parts}…]", &["fonts", "name", "parts"]), + ("give one of path, data or dataBase64", &["path", "data", "dataBase64"]), ("give origin: [x, y], or reset: true", &["origin", "reset"]), ("global must be true or false", &["global"]), ("group must be true or false", &["group"]), @@ -3562,12 +3642,16 @@ const NAMED_KEYS: &[(&str, &[&str])] = &[ ("member is for order front, forward, backward or back", &["member", "order", "front", "forward", "backward", "back"]), ("mode is set, add or toggle, not `{other}`", &["mode", "set", "add", "toggle"]), ("names is empty", &["names"]), + ("names must be a list of names", &["names"]), + ("names must be strings", &["names"]), + ("not a composite fonts file: no compositeFonts list", &["compositeFonts"]), ("on must be true or false", &["on"]), ("onto must name a floating panel", &["onto"]), ("others must be true or false", &["others"]), ("page must be a whole number from 1", &["page"]), ("panel is not in a dock group", &["panel"]), ("panel must name a registered panel", &["panel"]), + ("path is empty", &["path"]), ("paths must be strings", &["paths"]), ("percent must be above 0 and at most 6400, not {p}", &["percent"]), ("plane must be left, right or ground", &["plane", "left", "right", "ground"]), @@ -3583,6 +3667,7 @@ const NAMED_KEYS: &[(&str, &[&str])] = &[ ("span: \"selection\" places a linear, radial, conic or diamond gradient, not a freeform one", &["span"]), ("thickness must be from 0.01 to 5000 pt", &["thickness"]), ("title can't be empty", &["title"]), + ("unknown key `{k}`; a font takes name and parts", &["name", "parts"]), ( "unknown light setting: use color, intensity, rotation, height, softness or behind", &["color", "intensity", "rotation", "height", "softness", "behind"], @@ -3595,6 +3680,16 @@ const NAMED_KEYS: &[(&str, &[&str])] = &[ ("`strokeMode` applies to strokes (stroke: true)", &["stroke"]), ("`{GRAPHICS}` entries take symbol, face, x, y, scale and rotation, not `{k}`", &["symbol", "face", "x", "y", "scale", "rotation"]), ("`{GRAPHICS}` face must be front or back", &["face", "front", "back"]), + ("`{name}`: give the kanji part", &["kanji"]), + ("`{name}`: parts must be an object", &["parts"]), + ( + "`{name}`: unknown class `{k}`; the classes are kanji, kana, punctuation, symbols, roman and numerals", + &["kanji", "kana", "punctuation", "symbols", "roman", "numerals"], + ), + ("{k}: baseline must be {_1} to {_2}%", &["baseline"]), + ("{k}: only kana can be scaled at the center", &["kana"]), + ("{k}: only kana, roman and numerals can be scaled", &["kana", "roman", "numerals"]), + ("{k}: size must be {_1} to {_2}%", &["size"]), ]; /// Every catalog's translation of a [`NAMED_KEYS`] message keeps each name the table lists, as diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index f5fc694ec2..b61389b633 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -3449,6 +3449,42 @@ digits Malayalam Malayalam Composite Fonts Bileşik Fontlar Export Composite Fonts Bileşik Fontları Dışa Aktar Import Composite Fonts Bileşik Fontları İçe Aktar +@msg each font must be {name, parts} her font {name, parts} biçiminde olmalı +@msg unknown key `{k}`; a font takes name and parts bilinmeyen anahtar `{k}`; bir font name ve parts alır +@msg each font needs a name her fontun bir adı olmalı +@msg `{raw}` can't name a composite font (1 to {COMPOSITE_NAME_MAX} characters, no control characters) `{raw}` bir bileşik fontun adı olamaz (1 ile {COMPOSITE_NAME_MAX} karakter arası, denetim karakteri olmadan) +@msg `{name}`: parts must be an object `{name}`: parts bir nesne olmalı +@msg `{name}`: unknown class `{k}`; the classes are kanji, kana, punctuation, symbols, roman and numerals `{name}`: bilinmeyen sınıf `{k}`; sınıflar: kanji, kana, punctuation, symbols, roman ve numerals +@msg `{name}`: {_1} must be an object `{name}`: {_1} bir nesne olmalı +@msg `{name}`: unknown key `{k}` in {_1} `{name}`: {_1} içinde bilinmeyen anahtar `{k}` +@msg `{name}`: give the kanji part `{name}`: kanji bölümünü verin +@msg a document keeps at most {COMPOSITE_FONTS_MAX} composite fonts bir belge en fazla {COMPOSITE_FONTS_MAX} bileşik font tutar +@msg two composite fonts are named `{_1}` iki bileşik fontun adı `{_1}` +@msg `{_1}` is the name of an installed font `{_1}` yüklü bir fontun adı +@msg `{_1}`: {_2} is set in `{font}`, a composite font `{_1}`: {_2}, bir bileşik font olan `{font}` ile ayarlı +@msg give fonts: [{name, parts}…] fonts verin: [{name, parts}…] +@msg path is empty path boş +@msg fonts must be a list of composite fonts fonts bir bileşik font listesi olmalı +@msg names must be strings names dizeler olmalı +@msg no composite font named `{n}` `{n}` adlı bileşik font yok +@msg names must be a list of names names bir ad listesi olmalı +@msg no composite fonts to export dışa aktarılacak bileşik font yok +@msg not a composite fonts file: {e} bileşik font dosyası değil: {e} +@msg not a composite fonts file: no compositeFonts list bileşik font dosyası değil: compositeFonts listesi yok +@msg the file holds more than {COMPOSITE_FONTS_MAX} composite fonts dosyada {COMPOSITE_FONTS_MAX} taneden fazla bileşik font var +@msg the file is larger than 4 MB dosya 4 MB'tan büyük +@msg dataBase64 is not base64 dataBase64 base64 değil +@msg give one of path, data or dataBase64 path, data veya dataBase64 değerlerinden birini verin +@msg the file holds no composite fonts dosyada bileşik font yok +@msg the file holds two composite fonts named `{_1}` dosyada `{_1}` adlı iki bileşik font var +@msg `{_1}` can't name a composite font `{_1}` bir bileşik fontun adı olamaz +@msg {k}: give a font {k}: bir font verin +@msg {k}: size must be {_1} to {_2}% {k}: size %{_1} ile %{_2} arasında olmalı +@msg {k}: baseline must be {_1} to {_2}% {k}: baseline %{_1} ile %{_2} arasında olmalı +@msg {k}: scale must be {_1} to {_2}% {k}: ölçek %{_1} ile %{_2} arasında olmalı +@msg {k}: only kana, roman and numerals can be scaled {k}: yalnızca kana, roman ve numerals ölçeklenebilir +@msg {k}: only kana can be scaled at the center {k}: merkezden yalnızca kana ölçeklenebilir +@msg no composite font bileşik font yok Delete Font Fontu Sil Kanji Kanji Kana Kana diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 5a36512db0..361401931f 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -3677,6 +3677,42 @@ effect Points Точки Composite Fonts Складені шрифти Export Composite Fonts Експорт складених шрифтів Import Composite Fonts Імпорт складених шрифтів +@msg each font must be {name, parts} кожен шрифт має бути у вигляді {name, parts} +@msg unknown key `{k}`; a font takes name and parts невідомий ключ `{k}`; шрифт приймає name і parts +@msg each font needs a name кожен шрифт повинен мати назву +@msg `{raw}` can't name a composite font (1 to {COMPOSITE_NAME_MAX} characters, no control characters) `{raw}` не може бути назвою складеного шрифту (від 1 до {COMPOSITE_NAME_MAX} символів, без керівних символів) +@msg `{name}`: parts must be an object `{name}`: parts має бути об’єктом +@msg `{name}`: unknown class `{k}`; the classes are kanji, kana, punctuation, symbols, roman and numerals `{name}`: невідомий клас `{k}`; класи: kanji, kana, punctuation, symbols, roman і numerals +@msg `{name}`: {_1} must be an object `{name}`: {_1} має бути об’єктом +@msg `{name}`: unknown key `{k}` in {_1} `{name}`: невідомий ключ `{k}` у {_1} +@msg `{name}`: give the kanji part `{name}`: вкажіть частину kanji +@msg a document keeps at most {COMPOSITE_FONTS_MAX} composite fonts документ містить щонайбільше {COMPOSITE_FONTS_MAX} складених шрифтів +@msg two composite fonts are named `{_1}` два складені шрифти мають назву `{_1}` +@msg `{_1}` is the name of an installed font `{_1}` є назвою встановленого шрифту +@msg `{_1}`: {_2} is set in `{font}`, a composite font `{_1}`: {_2} використовує `{font}`, а це складений шрифт +@msg give fonts: [{name, parts}…] вкажіть fonts: [{name, parts}…] +@msg path is empty path порожній +@msg fonts must be a list of composite fonts fonts має бути списком складених шрифтів +@msg names must be strings names мають бути рядками +@msg no composite font named `{n}` немає складеного шрифту з назвою `{n}` +@msg names must be a list of names names має бути списком назв +@msg no composite fonts to export немає складених шрифтів для експорту +@msg not a composite fonts file: {e} це не файл складених шрифтів: {e} +@msg not a composite fonts file: no compositeFonts list це не файл складених шрифтів: немає списку compositeFonts +@msg the file holds more than {COMPOSITE_FONTS_MAX} composite fonts у файлі понад {COMPOSITE_FONTS_MAX} складених шрифтів +@msg the file is larger than 4 MB файл більший за 4 МБ +@msg dataBase64 is not base64 dataBase64 не у форматі base64 +@msg give one of path, data or dataBase64 вкажіть path, data або dataBase64 +@msg the file holds no composite fonts у файлі немає складених шрифтів +@msg the file holds two composite fonts named `{_1}` у файлі два складені шрифти з назвою `{_1}` +@msg `{_1}` can't name a composite font `{_1}` не може бути назвою складеного шрифту +@msg {k}: give a font {k}: вкажіть шрифт +@msg {k}: size must be {_1} to {_2}% {k}: size має бути від {_1} до {_2}% +@msg {k}: baseline must be {_1} to {_2}% {k}: baseline має бути від {_1} до {_2}% +@msg {k}: scale must be {_1} to {_2}% {k}: масштаб має бути від {_1} до {_2}% +@msg {k}: only kana, roman and numerals can be scaled {k}: масштабувати можна лише kana, roman і numerals +@msg {k}: only kana can be scaled at the center {k}: масштабувати від центру можна лише kana +@msg no composite font немає складеного шрифту Delete Font Видалити шрифт Kanji Кандзі Kana Кана diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 839f5c7405..0a7d8e8776 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -4022,6 +4022,42 @@ mark-style Japanese Kiểu Nhật Composite Fonts Phông chữ tổ hợp Export Composite Fonts Xuất phông chữ tổ hợp Import Composite Fonts Nhập phông chữ tổ hợp +@msg each font must be {name, parts} mỗi phông chữ phải là {name, parts} +@msg unknown key `{k}`; a font takes name and parts khóa không xác định `{k}`; một phông chữ nhận name và parts +@msg each font needs a name mỗi phông chữ cần có tên +@msg `{raw}` can't name a composite font (1 to {COMPOSITE_NAME_MAX} characters, no control characters) `{raw}` không thể là tên phông chữ tổ hợp (từ 1 đến {COMPOSITE_NAME_MAX} ký tự, không có ký tự điều khiển) +@msg `{name}`: parts must be an object `{name}`: parts phải là một đối tượng +@msg `{name}`: unknown class `{k}`; the classes are kanji, kana, punctuation, symbols, roman and numerals `{name}`: lớp không xác định `{k}`; các lớp là kanji, kana, punctuation, symbols, roman và numerals +@msg `{name}`: {_1} must be an object `{name}`: {_1} phải là một đối tượng +@msg `{name}`: unknown key `{k}` in {_1} `{name}`: khóa không xác định `{k}` trong {_1} +@msg `{name}`: give the kanji part `{name}`: hãy cung cấp phần kanji +@msg a document keeps at most {COMPOSITE_FONTS_MAX} composite fonts một tài liệu chứa tối đa {COMPOSITE_FONTS_MAX} phông chữ tổ hợp +@msg two composite fonts are named `{_1}` có hai phông chữ tổ hợp cùng tên `{_1}` +@msg `{_1}` is the name of an installed font `{_1}` là tên của một phông chữ đã cài đặt +@msg `{_1}`: {_2} is set in `{font}`, a composite font `{_1}`: {_2} dùng `{font}`, một phông chữ tổ hợp +@msg give fonts: [{name, parts}…] hãy cung cấp fonts: [{name, parts}…] +@msg path is empty path trống +@msg fonts must be a list of composite fonts fonts phải là danh sách phông chữ tổ hợp +@msg names must be strings names phải là chuỗi +@msg no composite font named `{n}` không có phông chữ tổ hợp nào tên `{n}` +@msg names must be a list of names names phải là danh sách tên +@msg no composite fonts to export không có phông chữ tổ hợp nào để xuất +@msg not a composite fonts file: {e} không phải tệp phông chữ tổ hợp: {e} +@msg not a composite fonts file: no compositeFonts list không phải tệp phông chữ tổ hợp: không có danh sách compositeFonts +@msg the file holds more than {COMPOSITE_FONTS_MAX} composite fonts tệp chứa hơn {COMPOSITE_FONTS_MAX} phông chữ tổ hợp +@msg the file is larger than 4 MB tệp lớn hơn 4 MB +@msg dataBase64 is not base64 dataBase64 không phải base64 +@msg give one of path, data or dataBase64 hãy cung cấp path, data hoặc dataBase64 +@msg the file holds no composite fonts tệp không chứa phông chữ tổ hợp nào +@msg the file holds two composite fonts named `{_1}` tệp chứa hai phông chữ tổ hợp cùng tên `{_1}` +@msg `{_1}` can't name a composite font `{_1}` không thể là tên phông chữ tổ hợp +@msg {k}: give a font {k}: hãy cung cấp một phông chữ +@msg {k}: size must be {_1} to {_2}% {k}: size phải từ {_1} đến {_2}% +@msg {k}: baseline must be {_1} to {_2}% {k}: baseline phải từ {_1} đến {_2}% +@msg {k}: scale must be {_1} to {_2}% {k}: tỷ lệ phải từ {_1} đến {_2}% +@msg {k}: only kana, roman and numerals can be scaled {k}: chỉ kana, roman và numerals mới co giãn được +@msg {k}: only kana can be scaled at the center {k}: chỉ kana mới co giãn từ tâm được +@msg no composite font không có phông chữ tổ hợp Delete Font Xóa phông chữ Kanji Kanji Kana Kana diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index b0d6001e32..2a2c0356e8 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -3499,6 +3499,42 @@ digits Malayalam 马拉雅拉姆数字 Composite Fonts 复合字体 Export Composite Fonts 导出复合字体 Import Composite Fonts 导入复合字体 +@msg each font must be {name, parts} 每个字体都必须是 {name, parts} +@msg unknown key `{k}`; a font takes name and parts 未知键 `{k}`;字体可用的键为 name 和 parts +@msg each font needs a name 每个字体都需要名称 +@msg `{raw}` can't name a composite font (1 to {COMPOSITE_NAME_MAX} characters, no control characters) `{raw}` 不能用作复合字体的名称(1 到 {COMPOSITE_NAME_MAX} 个字符,不含控制字符) +@msg `{name}`: parts must be an object `{name}`:parts 必须是对象 +@msg `{name}`: unknown class `{k}`; the classes are kanji, kana, punctuation, symbols, roman and numerals `{name}`:未知类别 `{k}`;类别为 kanji、kana、punctuation、symbols、roman 和 numerals +@msg `{name}`: {_1} must be an object `{name}`:{_1} 必须是对象 +@msg `{name}`: unknown key `{k}` in {_1} `{name}`:{_1} 中有未知键 `{k}` +@msg `{name}`: give the kanji part `{name}`:请指定 kanji 部分 +@msg a document keeps at most {COMPOSITE_FONTS_MAX} composite fonts 一个文档最多保存 {COMPOSITE_FONTS_MAX} 个复合字体 +@msg two composite fonts are named `{_1}` 有两个复合字体都名为 `{_1}` +@msg `{_1}` is the name of an installed font `{_1}` 是已安装字体的名称 +@msg `{_1}`: {_2} is set in `{font}`, a composite font `{_1}`:{_2} 使用了复合字体 `{font}` +@msg give fonts: [{name, parts}…] 请指定 fonts:[{name, parts}…] +@msg path is empty path 为空 +@msg fonts must be a list of composite fonts fonts 必须是复合字体列表 +@msg names must be strings names 必须是字符串 +@msg no composite font named `{n}` 没有名为 `{n}` 的复合字体 +@msg names must be a list of names names 必须是名称列表 +@msg no composite fonts to export 没有可导出的复合字体 +@msg not a composite fonts file: {e} 不是复合字体文件:{e} +@msg not a composite fonts file: no compositeFonts list 不是复合字体文件:没有 compositeFonts 列表 +@msg the file holds more than {COMPOSITE_FONTS_MAX} composite fonts 文件包含超过 {COMPOSITE_FONTS_MAX} 个复合字体 +@msg the file is larger than 4 MB 文件大于 4 MB +@msg dataBase64 is not base64 dataBase64 不是 base64 +@msg give one of path, data or dataBase64 请指定 path、data 或 dataBase64 之一 +@msg the file holds no composite fonts 文件不含复合字体 +@msg the file holds two composite fonts named `{_1}` 文件中有两个复合字体都名为 `{_1}` +@msg `{_1}` can't name a composite font `{_1}` 不能用作复合字体的名称 +@msg {k}: give a font {k}:请指定字体 +@msg {k}: size must be {_1} to {_2}% {k}:size 必须为 {_1}% 到 {_2}% +@msg {k}: baseline must be {_1} to {_2}% {k}:baseline 必须为 {_1}% 到 {_2}% +@msg {k}: scale must be {_1} to {_2}% {k}:缩放必须为 {_1}% 到 {_2}% +@msg {k}: only kana, roman and numerals can be scaled {k}:只有 kana、roman 和 numerals 可以缩放 +@msg {k}: only kana can be scaled at the center {k}:只有 kana 可以从中心缩放 +@msg no composite font 没有复合字体 Delete Font 删除字体 Kanji 汉字 Kana 假名 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index c24ae37609..5d7fd456ed 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -4738,6 +4738,42 @@ digits Malayalam 馬來亞拉姆數字 Composite Fonts 複合字體 Export Composite Fonts 匯出複合字體 Import Composite Fonts 匯入複合字體 +@msg each font must be {name, parts} 每個字體都必須是 {name, parts} +@msg unknown key `{k}`; a font takes name and parts 未知的鍵 `{k}`;字體可用的鍵為 name 和 parts +@msg each font needs a name 每個字體都需要名稱 +@msg `{raw}` can't name a composite font (1 to {COMPOSITE_NAME_MAX} characters, no control characters) `{raw}` 不能作為複合字體的名稱(1 到 {COMPOSITE_NAME_MAX} 個字元,不含控制字元) +@msg `{name}`: parts must be an object `{name}`:parts 必須是物件 +@msg `{name}`: unknown class `{k}`; the classes are kanji, kana, punctuation, symbols, roman and numerals `{name}`:未知的類別 `{k}`;類別為 kanji、kana、punctuation、symbols、roman 和 numerals +@msg `{name}`: {_1} must be an object `{name}`:{_1} 必須是物件 +@msg `{name}`: unknown key `{k}` in {_1} `{name}`:{_1} 中有未知的鍵 `{k}` +@msg `{name}`: give the kanji part `{name}`:請指定 kanji 部分 +@msg a document keeps at most {COMPOSITE_FONTS_MAX} composite fonts 一份文件最多保存 {COMPOSITE_FONTS_MAX} 個複合字體 +@msg two composite fonts are named `{_1}` 有兩個複合字體都名為 `{_1}` +@msg `{_1}` is the name of an installed font `{_1}` 是已安裝字體的名稱 +@msg `{_1}`: {_2} is set in `{font}`, a composite font `{_1}`:{_2} 使用了複合字體 `{font}` +@msg give fonts: [{name, parts}…] 請指定 fonts:[{name, parts}…] +@msg path is empty path 為空 +@msg fonts must be a list of composite fonts fonts 必須是複合字體清單 +@msg names must be strings names 必須是字串 +@msg no composite font named `{n}` 沒有名為 `{n}` 的複合字體 +@msg names must be a list of names names 必須是名稱清單 +@msg no composite fonts to export 沒有可匯出的複合字體 +@msg not a composite fonts file: {e} 不是複合字體檔案:{e} +@msg not a composite fonts file: no compositeFonts list 不是複合字體檔案:沒有 compositeFonts 清單 +@msg the file holds more than {COMPOSITE_FONTS_MAX} composite fonts 檔案包含超過 {COMPOSITE_FONTS_MAX} 個複合字體 +@msg the file is larger than 4 MB 檔案大於 4 MB +@msg dataBase64 is not base64 dataBase64 不是 base64 +@msg give one of path, data or dataBase64 請指定 path、data 或 dataBase64 其中之一 +@msg the file holds no composite fonts 檔案不含複合字體 +@msg the file holds two composite fonts named `{_1}` 檔案中有兩個複合字體都名為 `{_1}` +@msg `{_1}` can't name a composite font `{_1}` 不能作為複合字體的名稱 +@msg {k}: give a font {k}:請指定字體 +@msg {k}: size must be {_1} to {_2}% {k}:size 必須為 {_1}% 到 {_2}% +@msg {k}: baseline must be {_1} to {_2}% {k}:baseline 必須為 {_1}% 到 {_2}% +@msg {k}: scale must be {_1} to {_2}% {k}:縮放必須為 {_1}% 到 {_2}% +@msg {k}: only kana, roman and numerals can be scaled {k}:只有 kana、roman 和 numerals 可以縮放 +@msg {k}: only kana can be scaled at the center {k}:只有 kana 可以從中心縮放 +@msg no composite font 沒有複合字體 Delete Font 刪除字體 Kanji 漢字 Kana 假名 From 92d3a8158ba7896d1219b74bab052c2b1b7d9562 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Fri, 13 Nov 2026 20:14:36 -0800 Subject: [PATCH 02/29] i18n: Turkish Draw labels use the drawing verb instead of a tied game MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Turkish catalog translated the toolbar's Draw category and the freeform gradient's Draw: row as "Beraberlik", which means a tied game. Both now read "Çiz", the verb the catalog already uses for Draw Behind, Draw Inside and Draw Normal. The Turkish "unknown control" message also listed the control names as "colouring,drawMode": it now lists "coloring, drawMode", the names the command accepts. The two dimension scale rows read the English word order wrongly. The hint under the Dimension dialog's scale field now says that 1:10 labels 1 unit on the page as 10, and the scale error keeps "çizim:gerçek" as one form instead of splitting it around the verb. --- crates/ui-egui/src/i18n/tests.rs | 22 ++++++++++++++++++++++ crates/ui-egui/src/i18n/tr.tsv | 10 +++++----- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/crates/ui-egui/src/i18n/tests.rs b/crates/ui-egui/src/i18n/tests.rs index 705fc68db2..e49c33baf0 100644 --- a/crates/ui-egui/src/i18n/tests.rs +++ b/crates/ui-egui/src/i18n/tests.rs @@ -280,6 +280,28 @@ fn languages_resolve_from_their_tags_and_name_themselves() { } } +/// Turkish `Draw` labels use the drawing verb of the catalog's other drawing modes, and the +/// accepted control names in its messages keep the spelling the command reads. The dimension +/// scale rows keep the `drawing:real` form and say that the ratio labels a page unit. +#[test] +fn turkish_draw_labels_use_the_drawing_verb() { + let tr_lang = Lang::from_code("tr").expect("tr registered"); + assert_eq!(tr(tr_lang, "Draw"), "Çiz"); + assert_eq!(tr(tr_lang, "Draw:"), "Çiz:"); + assert_eq!(tr(tr_lang, "Draw Behind"), "Arkaya Çiz"); + let (entries, _) = parse_entries(tr_lang.0.source); + let tie: Vec<_> = + entries.iter().filter(|(_, _, t)| t.to_lowercase().contains("beraber")).map(|(_, src, t)| format!("{src:?} -> {t:?}")).collect(); + assert!(tie.is_empty(), "Turkish rows read as a tied game:\n{}", tie.join("\n")); + let unknown = message(tr_lang, "unknown control `x`: fillStroke, coloring, drawMode or screenMode"); + assert!(unknown.contains("fillStroke, coloring, drawMode veya screenMode"), "{unknown}"); + assert_eq!( + tr(tr_lang, "Drawing : real size — 1:10 labels 1 unit on the page as 10."), + "Çizim : gerçek boyut — 1:10, sayfadaki 1 birimi 10 olarak etiketler." + ); + assert_eq!(message(tr_lang, "Scale must be drawing:real, for example 1:10"), "Ölçek çizim:gerçek biçiminde olmalıdır, örneğin 1:10"); +} + /// The Catalan catalog registers under `ca`, claims complete menus and messages, and passes /// locale resolution, vocabulary and count formatting checks. #[test] diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 2b3e843ba3..09b9d27210 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -903,12 +903,12 @@ font-technology Variable Değişken Drag art here, or select it and click +. Hold Alt for one asset of several objects. Resmi buraya sürükleyin veya seçip + işaretine tıklayın. Birkaç nesneden oluşan bir varlık için Alt tuşunu basılı tutun. Drag onto art to apply this appearance Bu görünümü uygulamak için resmin üzerine sürükleyin Drag the art to move it on the paper Resmi kağıt üzerinde taşımak için sürükleyin - Draw Beraberlik + Draw Çiz Draw Behind Arkaya Çiz Draw in Perspective Perspektifte Çiz Draw Inside İçini Çiz Draw Normal Normal Çiz - Draw: Beraberlik: + Draw: Çiz: Drawing Mode Çizim Modu Drop Shadow Alt Gölge Drop Shadow… Alt Gölge… @@ -5714,7 +5714,7 @@ theme Appearance Görünüm Drawer menu Çekmece menüsü Toolbar: Show Controls Araç Çubuğu: Kontrolleri Göster @msg control must be fillStroke, coloring, drawMode or screenMode control fillStroke, coloring, drawMode veya screenMode olmalıdır -@msg unknown control `{name}`: fillStroke, coloring, drawMode or screenMode bilinmeyen kontrol `{name}`: fillStroke, colouring,drawMode veya screenMode +@msg unknown control `{name}`: fillStroke, coloring, drawMode or screenMode bilinmeyen kontrol `{name}`: fillStroke, coloring, drawMode veya screenMode @msg give a fraction `t` from 0 to 1 0'dan 1'e kadar bir `t` kesri verin @msg give the path `id` `id` yolunu ver @msg invalid stroke widths geçersiz kontur genişlikleri @@ -5734,7 +5734,7 @@ theme Appearance Görünüm Below Object Nesnenin altında Registration: Kayıt: Dimension Boyut - Drawing : real size — 1:10 labels 1 unit on the page as 10. Çizim : Gerçek boyut — 1:10 etiketler sayfada 10 adet olarak 1 adettir. + Drawing : real size — 1:10 labels 1 unit on the page as 10. Çizim : gerçek boyut — 1:10, sayfadaki 1 birimi 10 olarak etiketler. Preview (1 inch on the page) Önizleme (sayfada 1 inç) Set as Default Varsayılan Olarak Ayarla out from a highlighted edge to place its dimension | boyutunu yerleştirmek için vurgulanan kenardan dışarı | @@ -5772,7 +5772,7 @@ theme Appearance Görünüm @msg Measured segment no longer exists Ölçülen segment artık mevcut değil @msg Non-finite dimension Sonlu olmayan boyut @msg Only linear dimensions are horizontal or vertical Yalnızca doğrusal boyutlar yatay veya dikey olabilir -@msg Scale must be drawing:real, for example 1:10 Ölçek çizim olmalıdır:gerçek, örneğin 1:10 +@msg Scale must be drawing:real, for example 1:10 Ölçek çizim:gerçek biçiminde olmalıdır, örneğin 1:10 @msg Scale ratio must be between 1:1000000 and 1000000:1 Ölçek oranı 1:1000000 ile 1000000:1 arasında olmalıdır @msg Second measured segment no longer exists Ölçülen ikinci segment artık mevcut değil @msg Unknown dimension mode Bilinmeyen boyut modu From ba3c5b0a3c7a159d099db6d217029ae068c2f039 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Fri, 13 Nov 2026 21:04:08 -0800 Subject: [PATCH 03/29] .ai save: a top object with uneven sides stays a live envelope A top object whose top and bottom sides, or left and right sides, have different numbers of segments was written to the editing data as its distorted art. The writer now splits the side with fewer segments into as many as the side across from it has, each segment into equal spans of its curve, up to the 50 columns or rows the grid holds. The reader opens the grid's edge as the top object's outline with the same corners and the same mapping of the contents; the outline holds the added anchors. A split that would share out the contents another way, or a side of more than 50 segments, still writes the envelope as its art with the warning. --- crates/eps/src/ai_editing/tests.rs | 121 ++++++++++++++++-- crates/eps/src/ai_editing/writer/envelopes.rs | 90 ++++++++++--- docs/target-app-parity.md | 1 + 3 files changed, 184 insertions(+), 28 deletions(-) diff --git a/crates/eps/src/ai_editing/tests.rs b/crates/eps/src/ai_editing/tests.rs index 967c982ad4..bc9d3a5332 100644 --- a/crates/eps/src/ai_editing/tests.rs +++ b/crates/eps/src/ai_editing/tests.rs @@ -9689,7 +9689,7 @@ fn an_envelope_is_written_in_the_order_the_reader_reads_it() { /// Envelopes the reader would open in another shape, or not at all, are written as their distorted /// art with a warning, and open as that art: an envelope of type, one in a turned frame, a warp drawn /// without its style's net, a mesh the reader would lay out the other way round, a top object -/// whose sides don't pair up, a Fidelity that isn't a whole number and bounds margins kept with +/// with a side of more segments than the grid has columns, a Fidelity that isn't a whole number and bounds margins kept with /// Distort Appearance off. Other live objects keep their own warning. #[test] fn envelopes_the_reader_cannot_open_live_are_written_as_their_art() { @@ -9726,15 +9726,10 @@ fn envelopes_the_reader_cannot_open_live_are_written_as_their_art() { **kind = EnvelopeKind::Mesh { rows: 2, cols: 3, points: transpose(points, 3, 2), handles: vec![] }; } }); - let mut uneven = kurbo::BezPath::new(); - uneven.move_to((30.0, 10.0)); - for p in [(100.0, 0.0), (170.0, 10.0), (170.0, 90.0), (30.0, 90.0)] { - uneven.line_to(p); - } - uneven.close_path(); - let lopsided = change(&|k| { + // A top side of 51 segments: more grid columns than the reader reads. + let crowded = change(&|k| { if let NodeKind::Envelope { kind, .. } = k { - **kind = EnvelopeKind::TopObject { path: PathData::from_bezpath(&uneven), corners: None }; + **kind = EnvelopeKind::TopObject { path: PathData::from_bezpath(&uneven_top_object(51)), corners: None }; } }); let fractional = change(&|k| { @@ -9753,7 +9748,7 @@ fn envelopes_the_reader_cannot_open_live_are_written_as_their_art() { ("turned", turned), ("old warp", old_warp), ("sideways mesh", sideways), - ("lopsided top object", lopsided), + ("top object past the grid's limit", crowded), ("fractional fidelity", fractional), ("margin kept", margin_kept), ]; @@ -9774,6 +9769,112 @@ fn transpose(points: Vec, rows: usize, cols: usize) -> Vec kurbo::BezPath { + let mut path = kurbo::BezPath::new(); + path.move_to((30.0, 10.0)); + for k in 1..top { + path.line_to((30.0 + 140.0 * k as f64 / top as f64, if k % 2 == 1 { 20.0 } else { 10.0 })); + } + for p in [(170.0, 10.0), (170.0, 90.0), (30.0, 90.0)] { + path.line_to(p); + } + path.close_path(); + path +} + +/// Do the patches of top objects `a` and `b` lie within 0.001 pt of each other at every place of a +/// 32 × 32 grid? +fn same_top_object_patch(a: &vectorcraft_doc::live::EnvelopeKind, b: &vectorcraft_doc::live::EnvelopeKind) -> bool { + use vectorcraft_doc::live::{Coons, EnvelopeKind}; + let (EnvelopeKind::TopObject { path, corners }, EnvelopeKind::TopObject { path: p, corners: c }) = (a, b) else { return false }; + let (Some(a), Some(b)) = (Coons::from_top_object(path, *corners), Coons::from_top_object(p, *c)) else { return false }; + let at = |i: u32| f64::from(i) / 32.0; + (0..=32).all(|j| (0..=32).all(|i| a.eval(at(i), at(j)).distance(b.eval(at(i), at(j))) < 1e-3)) +} + +/// A top object whose top and bottom sides have different numbers of segments is written live, its +/// side with fewer segments split into as many as the side across from it has, with Edit Contents off +/// and on: two straight segments over one straight one, three over one, and two over one curve. +/// VectorCraft's reader opens it as a top object with its corners on the same four points, the same +/// patch, contents, Fidelity, Edit Contents and Envelope Options, and saved again it opens the same. +#[test] +fn a_top_object_with_uneven_sides_is_read_back_live() { + use vectorcraft_doc::live::{EnvelopeKind, EnvelopeOptions}; + let mut curved = kurbo::BezPath::new(); + curved.move_to((30.0, 10.0)); + curved.line_to((100.0, 0.0)); + curved.line_to((170.0, 10.0)); + curved.line_to((170.0, 90.0)); + curved.curve_to((130.0, 105.0), (70.0, 105.0), (30.0, 90.0)); + curved.close_path(); + let shapes = [("two over one", uneven_top_object(2), 6), ("three over one", uneven_top_object(3), 8), ("two over a curve", curved, 6)]; + for (name, path, anchors) in shapes { + let kind = EnvelopeKind::TopObject { path: PathData::from_bezpath(&path), corners: None }; + for editing in [false, true] { + let what = format!("{name}, editing {editing}"); + let doc = envelope_doc(kind.clone(), 50.0, editing, EnvelopeOptions::NEW); + let text = program(&doc); + assert!(text.contains("AIDeformOptionsKey") && text.contains("%_3 /Int (AIDeformStyle) ,\n"), "{what}: {text}"); + let (back, warnings, notes) = envelope_back(&doc); + assert!(!warnings.iter().any(|w| w == writer::ENVELOPES), "{what}: {warnings:?}"); + assert!(!notes.iter().any(|w| w == crate::import::ENVELOPE_DRAWN), "{what}: {notes:?}"); + let NodeKind::Envelope { content, kind: got, fidelity, editing: e, options, frame } = &back.kind else { panic!("{what}: {back:#?}") }; + assert_eq!((*fidelity, *e, *options, **frame), (50.0, editing, EnvelopeOptions::NEW, Affine::IDENTITY), "{what}"); + let EnvelopeKind::TopObject { path: outline, corners: Some(corners) } = &**got else { panic!("{what}: {got:?}") }; + let sp = &outline.subpaths[0]; + assert_eq!(sp.anchors.len(), anchors, "{what}: {outline:?}"); + let points = corners.map(|i| sp.anchors[i as usize].p); + let want = [(30.0, 10.0), (170.0, 10.0), (170.0, 90.0), (30.0, 90.0)].map(kurbo::Point::from); + assert!(points.iter().zip(want).all(|(a, b)| a.distance(b) < 1e-6), "{what}: {points:?}"); + assert!(same_top_object_patch(&kind, got), "{what}: {got:?}"); + let colors: Vec> = content.iter().map(|c| c.appearance.fill_paint().color()).collect(); + assert_eq!(colors, [Some(Color::rgb(1.0, 0.0, 0.0)), Some(Color::rgb(0.0, 0.0, 1.0))], "{what}"); + let mut again = Document::new(200.0, 100.0); + let layer = again.layers[0].id; + again.insert(Some(layer), usize::MAX, back.clone()).unwrap(); + let (twice, warnings, notes) = envelope_back(&again); + assert!(!warnings.iter().any(|w| w == writer::ENVELOPES), "{what}, saved again: {warnings:?}"); + let NodeKind::Envelope { kind: twice, .. } = &twice.kind else { panic!("{what}, saved again: {twice:#?} {notes:?}") }; + assert!(same_top_object_patch(&kind, twice), "{what}, saved again: {twice:?}"); + } + } +} + +/// A top object with a top side of 50 segments over a bottom side of one is written live with a grid +/// of 50 columns, the most the reader reads. +#[test] +fn a_top_object_split_to_the_grids_limit_is_read_back_live() { + use vectorcraft_doc::live::{EnvelopeKind, EnvelopeOptions}; + let kind = EnvelopeKind::TopObject { path: PathData::from_bezpath(&uneven_top_object(50)), corners: None }; + let doc = envelope_doc(kind.clone(), 50.0, false, EnvelopeOptions::NEW); + let text = program(&doc); + assert!(text.contains("[50 1] /Size X#"), "{text}"); + let (back, warnings, notes) = envelope_back(&doc); + assert!(!warnings.iter().any(|w| w == writer::ENVELOPES), "{warnings:?}"); + let NodeKind::Envelope { kind: got, .. } = &back.kind else { panic!("{back:#?} {notes:?}") }; + assert!(same_top_object_patch(&kind, got), "{got:?}"); +} + +/// A top object whose side with fewer segments would share out the content another way once split +/// (two curves that aren't one curve, under three segments) is written as its art, with a warning. +#[test] +fn a_top_object_whose_split_side_would_map_another_way_is_written_as_its_art() { + use vectorcraft_doc::live::{EnvelopeKind, EnvelopeOptions}; + let mut path = uneven_top_object(3); + path.truncate(5); + path.curve_to((150.0, 110.0), (120.0, 110.0), (100.0, 90.0)); + path.curve_to((80.0, 95.0), (40.0, 80.0), (30.0, 90.0)); + path.close_path(); + let kind = EnvelopeKind::TopObject { path: PathData::from_bezpath(&path), corners: None }; + let doc = envelope_doc(kind, 50.0, false, EnvelopeOptions::NEW); + assert!(!program(&doc).contains("AIDeformOptionsKey")); + let (back, warnings, _) = envelope_back(&doc); + assert!(warnings.iter().any(|w| w == writer::ENVELOPES), "{warnings:?}"); + assert!(matches!(back.kind, NodeKind::Group { .. }), "{back:#?}"); +} + /// An envelope with its contents in an opacity mask's art is written as its art there, and an /// envelope in a group stays live. #[test] diff --git a/crates/eps/src/ai_editing/writer/envelopes.rs b/crates/eps/src/ai_editing/writer/envelopes.rs index dcd7274291..bf01c276de 100644 --- a/crates/eps/src/ai_editing/writer/envelopes.rs +++ b/crates/eps/src/ai_editing/writer/envelopes.rs @@ -8,13 +8,14 @@ //! contents in an `X=` … `X+` block (while they are edited, the grid) as `AIDeformArtKey`, and the //! kind (`AIDeformStyle`: 1 a warp, 2 a mesh, 3 a top object); then the distorted art on plain //! lines, and the plugin's `XP` after it. A top object's outline is the edge of the grid written -//! for it, one grid edge to each segment of its sides. An envelope the reader would open in another -//! shape is written as its distorted art. +//! for it, one grid edge to each segment of its sides, a side with fewer segments than the side +//! across from it split into more. An envelope the reader would open in another shape is written as +//! its distorted art. use std::fmt::Write; use std::sync::Arc; -use kurbo::{Point, Vec2}; +use kurbo::{CubicBez, ParamCurve, Point, Vec2}; use vectorcraft_doc::live::{ Coons, EnvelopeKind, EnvelopeOptions, GradientMesh, H_DOWN, H_LEFT, H_RIGHT, H_UP, MeshPoint, PreserveShape, WARP_NET, envelope_content_boxes, envelope_grid, envelope_stroke_margin, warp_grid, @@ -173,11 +174,44 @@ fn reads_as_written(rows: usize, cols: usize, pts: &[GridPoint]) -> bool { envelope_upright(&mesh).is_some_and(|(r, c, read)| (r as usize, c as usize) == (rows, cols) && read == pts) } +/// `pieces`, one side of a top object, split into `n` pieces: each piece is split into equal spans +/// of its curve parameter, the extra pieces going one at a time to the piece with the longest +/// control polygon for each of its spans. `None` where `n` is fewer pieces than it has, more than +/// [`MAX_GRID`], or a split leaves a piece of no length. +fn split_to(pieces: &[CubicBez], n: usize) -> Option> { + if n < pieces.len() || n > MAX_GRID { + return None; + } + if n == pieces.len() { + return Some(pieces.to_vec()); + } + let polygon = |c: &CubicBez| c.p0.distance(c.p1) + c.p1.distance(c.p2) + c.p2.distance(c.p3); + let lengths: Vec = pieces.iter().map(polygon).collect(); + let mut parts = vec![1usize; pieces.len()]; + for _ in pieces.len()..n { + let most = (0..pieces.len()).max_by(|a, b| (lengths[*a] / parts[*a] as f64).total_cmp(&(lengths[*b] / parts[*b] as f64)))?; + parts[most] += 1; + } + let mut out = Vec::with_capacity(n); + for (c, p) in pieces.iter().zip(parts) { + for j in 0..p { + let piece = c.subsegment(j as f64 / p as f64..(j + 1) as f64 / p as f64); + if [piece.p1, piece.p2, piece.p3].iter().all(|q| q.distance(piece.p0) <= 1e-9) { + return None; + } + out.push(piece); + } + } + Some(out) +} + /// The grid a top object with outline `path` and corners `corners` is written with: its edge runs -/// through the outline's anchors clockwise from the top left corner, one grid edge to a segment, so -/// the top and bottom sides need as many segments as each other, as do the left and right sides; -/// the points inside blend the sides. `None` where that grid's edge, read back as the top object's -/// outline, doesn't give the same patch ([`Coons::from_top_object`]) at every place checked. +/// through the outline's anchors clockwise from the top left corner, one grid edge to a segment, +/// the points inside blending the sides. The top and bottom sides need as many grid edges as each +/// other, as do the left and right sides, so where one side of a pair has fewer segments than the +/// other, its segments are split ([`split_to`]) without changing its shape. `None` where that +/// grid's edge, read back as the top object's outline, doesn't give the same patch +/// ([`Coons::from_top_object`]) at every place checked. fn top_object_grid(path: &PathData, corners: Option<[u32; 4]>) -> Option<(usize, usize, Vec)> { let coons = Coons::from_top_object(path, corners)?; // The outline, as the patch takes it: the largest subpath. @@ -214,10 +248,35 @@ fn top_object_grid(path: &PathData, corners: Option<[u32; 4]>) -> Option<(usize, } }; let gap = |k: usize| (at[(k + 1) % 4] + count - at[k]) % count; - let (cols, rows) = (gap(0), gap(1)); - if gap(2) != cols || gap(3) != rows || cols > MAX_GRID || rows > MAX_GRID { + if (0..4).any(|k| gap(k) > MAX_GRID) { return None; } + // Each side's segments from its corner on, a straight one as a cubic with its handles a third of + // the way along, which the patch spreads evenly along, as it does a straight segment. + let side = |k: usize| -> Vec { + (0..gap(k)) + .map(|j| { + let i = (at[k] + j) % count; + let seg = sp.segment(i); + if sp.segment_is_line(i) { + CubicBez::new(seg.p0, seg.p0.lerp(seg.p3, 1.0 / 3.0), seg.p0.lerp(seg.p3, 2.0 / 3.0), seg.p3) + } else { + seg + } + }) + .collect() + }; + let mut sides = [side(0), side(1), side(2), side(3)]; + // Opposite sides take as many segments as each other: the one with fewer has some split. + for k in 0..2 { + let n = sides[k].len().max(sides[k + 2].len()); + for s in [k, k + 2] { + sides[s] = split_to(&sides[s], n)?; + } + } + let (cols, rows) = (sides[0].len(), sides[1].len()); + let edge: Vec = sides.concat(); + let total = edge.len(); let width = cols + 1; let mut pts: Vec = vec![(Point::ZERO, [Vec2::ZERO; 4]); (rows + 1) * width]; // Where the `k`th anchor from the top left corner goes, and the slots of the handles of the @@ -233,20 +292,15 @@ fn top_object_grid(path: &PathData, corners: Option<[u32; 4]>) -> Option<(usize, (rows - (k - 2 * cols - rows), 0, H_UP, H_DOWN) } }; - for k in 0..count { - let i = (at[0] + k) % count; - let seg = sp.segment(i); - // A straight segment's patch spreads evenly along it, as a cubic's with its handles a third - // of the way does. - let (p1, p2) = if sp.segment_is_line(i) { (seg.p0.lerp(seg.p3, 1.0 / 3.0), seg.p0.lerp(seg.p3, 2.0 / 3.0)) } else { (seg.p1, seg.p2) }; + for (k, seg) in edge.iter().enumerate() { let (r, c, out, into) = place(k); - let (nr, nc, _, _) = place((k + 1) % count); + let (nr, nc, _, _) = place((k + 1) % total); if let Some(q) = pts.get_mut(r * width + c) { q.0 = seg.p0; - q.1[out] = p1 - seg.p0; + q.1[out] = seg.p1 - seg.p0; } if let Some(q) = pts.get_mut(nr * width + nc) { - q.1[into] = p2 - seg.p3; + q.1[into] = seg.p2 - seg.p3; } } // The points inside, blended from the sides. diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 296bd84b2b..342eb492b2 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1639,6 +1639,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur SVG import keeps attributes in other applications' XML namespaces (`plotter:pen="2"`) on the objects and layers they belong to, as it keeps their `data-*` attributes: copies, duplicates and `.vectorcraft` files keep them, and SVG export writes them back with each namespace declared once on the root (the rest of #522; `crates/svg/tests/it/foreign_attributes.rs`, `tests/attributes.rs`). A PDF-compatible `.ai` saved with Include Editing Data for Other Apps defines every symbol in its editing copy with its art, registration point and place in the Symbols panel's order, and writes its instances as symbol instances, which VectorCraft's `.ai` reader opens as symbols and instances again (`symbols_and_their_instances_are_read_back` in `crates/eps/src/ai_editing/tests.rs`; [file-format-parity.md](file-format-parity.md) lists the instances written as their art). A PDF-compatible `.ai` saved with Include Editing Data for Other Apps keeps envelopes made with a warp, a mesh or a top object editable: its editing copy holds each envelope's contents, shape, Fidelity, Envelope Options and Edit Contents state in the form VectorCraft's `.ai` envelope reader reads, beside its distorted art, so the file opens with the same live envelope, Edit Contents still switching between the contents and the envelope ([Edit the contents of envelopes](https://helpx.adobe.com/illustrator/desktop/manage-objects/reshape-transform-objects/edit-the-contents-of-envelopes.html)). Envelopes of type, envelopes in opacity masks, pattern tiles or another envelope's contents, and envelopes the reader would open with another shape or other settings (a Fidelity that isn't a whole number, bounds margins kept with Distort Appearance off), and envelopes after the first 256 in a file are written as their distorted art, with a warning (`envelopes_are_read_back_live` and `envelopes_the_reader_cannot_open_live_are_written_as_their_art` in `crates/eps/src/ai_editing/tests.rs`). + A top object whose top and bottom sides, or left and right sides, have different numbers of segments is also saved live: the side with fewer segments is split into as many as the side across from it has (each segment into equal spans of its curve, a straight one at even steps), up to the 50 a side the grid holds, so the file opens with the same corners and the same mapping of the contents, its outline holding the added anchors. Where the split side would share out the contents another way, as a side of two curves that aren't one curve can, or a side has more than 50 segments, the envelope is written as its distorted art, with the warning (`a_top_object_with_uneven_sides_is_read_back_live`, `a_top_object_split_to_the_grids_limit_is_read_back_live` and `a_top_object_whose_split_side_would_map_another_way_is_written_as_its_art` in `crates/eps/src/ai_editing/tests.rs`). A PDF-compatible `.ai` saved with Include Editing Data for Other Apps writes eligible paths, compound paths and groups with live effects with an art style when the writer supports their appearance and effects (not a filled path that uses the even-odd rule, and none on a large canvas), which VectorCraft's `.ai` reader opens as the same appearance with its effects live (`objects_with_live_effects_are_read_back` and `every_effect_setting_written_on_an_object_is_read_back` in `crates/eps/src/ai_editing/tests.rs`; [file-format-parity.md](file-format-parity.md) lists the objects written without one and the effects they lose). The same holds for the objects of an opacity mask's art, a mask inside another mask's art and a hidden art root included, so a mask whose art has Offset Path opens drawing the image it masks as it did, with the effect live; geometry effects that no art style holds on a mask's art, such as Roughen, are written applied to the art, which opens drawing what it drew (`a_masked_image_with_live_offset_path_round_trips`, `hidden_and_nested_mask_art_keep_live_offset_path` and `a_masks_art_with_an_effect_no_style_holds_is_written_as_it_draws` in `crates/eps/src/ai_editing/tests.rs`). A PDF-compatible `.ai` saved with Include Editing Data for Other Apps writes point type, area type and type on a path as type in its editing copy, and objects next to it keep their own transparency (`crates/eps/src/ai_editing/tests.rs`, `cmd::fileio::tests_pdfedit::type_comes_back_from_the_editing_copy`; [file-format-parity.md](file-format-parity.md) lists what is written as outlines). From 8b1802349e3c44bc425fce95dbbae7831277b1e3 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 14 Nov 2026 05:37:32 -0800 Subject: [PATCH 04/29] i18n: composite font errors with a part name translate behind the font name The engine reports a bad part as `name`: part: reason, which the generic `{name}`: {_1}: {e} row matched first and left untranslated. Each complete catalog gets name-prefixed rows for the six part errors, and the test lists both forms. --- crates/ui-egui/src/i18n/ca.tsv | 6 ++++++ crates/ui-egui/src/i18n/cs.tsv | 6 ++++++ crates/ui-egui/src/i18n/de.tsv | 6 ++++++ crates/ui-egui/src/i18n/el.tsv | 6 ++++++ crates/ui-egui/src/i18n/es.tsv | 6 ++++++ crates/ui-egui/src/i18n/fi.tsv | 6 ++++++ crates/ui-egui/src/i18n/fr.tsv | 6 ++++++ crates/ui-egui/src/i18n/hi.tsv | 6 ++++++ crates/ui-egui/src/i18n/hu.tsv | 6 ++++++ crates/ui-egui/src/i18n/it.tsv | 6 ++++++ crates/ui-egui/src/i18n/ja.tsv | 6 ++++++ crates/ui-egui/src/i18n/ka.tsv | 6 ++++++ crates/ui-egui/src/i18n/ko.tsv | 6 ++++++ crates/ui-egui/src/i18n/nl.tsv | 6 ++++++ crates/ui-egui/src/i18n/pl.tsv | 6 ++++++ crates/ui-egui/src/i18n/pt-br.tsv | 6 ++++++ crates/ui-egui/src/i18n/ru.tsv | 6 ++++++ crates/ui-egui/src/i18n/sv.tsv | 6 ++++++ crates/ui-egui/src/i18n/tests.rs | 10 ++++++++++ crates/ui-egui/src/i18n/tr.tsv | 6 ++++++ crates/ui-egui/src/i18n/uk.tsv | 6 ++++++ crates/ui-egui/src/i18n/vi.tsv | 6 ++++++ crates/ui-egui/src/i18n/zh-hans.tsv | 6 ++++++ crates/ui-egui/src/i18n/zh-hant.tsv | 6 ++++++ 24 files changed, 148 insertions(+) diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 5ba4d1b5c2..f14714f6bf 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -4680,11 +4680,17 @@ digits Malayalam Malaiàlam @msg the file holds two composite fonts named `{_1}` el fitxer conté dos tipus de lletra compostos anomenats `{_1}` @msg `{_1}` can't name a composite font `{_1}` no pot anomenar un tipus de lletra compost @msg {k}: give a font {k}: indiqueu un tipus de lletra +@msg `{name}`: {k}: give a font `{name}`: {k}: indiqueu un tipus de lletra @msg {k}: size must be {_1} to {_2}% {k}: size ha d’estar entre {_1} i {_2} % +@msg `{name}`: {k}: size must be {_1} to {_2}% `{name}`: {k}: size ha d’estar entre {_1} i {_2} % @msg {k}: baseline must be {_1} to {_2}% {k}: baseline ha d’estar entre {_1} i {_2} % +@msg `{name}`: {k}: baseline must be {_1} to {_2}% `{name}`: {k}: baseline ha d’estar entre {_1} i {_2} % @msg {k}: scale must be {_1} to {_2}% {k}: l’escala ha d’estar entre {_1} i {_2} % +@msg `{name}`: {k}: scale must be {_1} to {_2}% `{name}`: {k}: l’escala ha d’estar entre {_1} i {_2} % @msg {k}: only kana, roman and numerals can be scaled {k}: només kana, roman i numerals es poden escalar +@msg `{name}`: {k}: only kana, roman and numerals can be scaled `{name}`: {k}: només kana, roman i numerals es poden escalar @msg {k}: only kana can be scaled at the center {k}: només kana es pot escalar des del centre +@msg `{name}`: {k}: only kana can be scaled at the center `{name}`: {k}: només kana es pot escalar des del centre @msg no composite font cap tipus de lletra compost Delete Font Suprimeix el tipus de lletra Kanji Kanji diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 339306a7e8..a9fff03d03 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -1678,11 +1678,17 @@ digits Malayalam Malajálamské @msg the file holds two composite fonts named `{_1}` soubor obsahuje dvě složená písma s názvem `{_1}` @msg `{_1}` can't name a composite font `{_1}` nemůže být názvem složeného písma @msg {k}: give a font {k}: zadejte písmo +@msg `{name}`: {k}: give a font `{name}`: {k}: zadejte písmo @msg {k}: size must be {_1} to {_2}% {k}: size musí být {_1} až {_2} % +@msg `{name}`: {k}: size must be {_1} to {_2}% `{name}`: {k}: size musí být {_1} až {_2} % @msg {k}: baseline must be {_1} to {_2}% {k}: baseline musí být {_1} až {_2} % +@msg `{name}`: {k}: baseline must be {_1} to {_2}% `{name}`: {k}: baseline musí být {_1} až {_2} % @msg {k}: scale must be {_1} to {_2}% {k}: měřítko musí být {_1} až {_2} % +@msg `{name}`: {k}: scale must be {_1} to {_2}% `{name}`: {k}: měřítko musí být {_1} až {_2} % @msg {k}: only kana, roman and numerals can be scaled {k}: měnit měřítko lze jen u kana, roman a numerals +@msg `{name}`: {k}: only kana, roman and numerals can be scaled `{name}`: {k}: měnit měřítko lze jen u kana, roman a numerals @msg {k}: only kana can be scaled at the center {k}: měřítko od středu lze měnit jen u kana +@msg `{name}`: {k}: only kana can be scaled at the center `{name}`: {k}: měřítko od středu lze měnit jen u kana @msg no composite font žádné složené písmo Delete Font Odstranit písmo Kanji Kandži diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index c79364b8f6..158034d575 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -3659,11 +3659,17 @@ effect Points Punkte @msg the file holds two composite fonts named `{_1}` die Datei enthält zwei zusammengesetzte Schriften namens `{_1}` @msg `{_1}` can't name a composite font `{_1}` kann keine zusammengesetzte Schrift benennen @msg {k}: give a font {k}: geben Sie eine Schrift an +@msg `{name}`: {k}: give a font `{name}`: {k}: geben Sie eine Schrift an @msg {k}: size must be {_1} to {_2}% {k}: size muss {_1} bis {_2} % sein +@msg `{name}`: {k}: size must be {_1} to {_2}% `{name}`: {k}: size muss {_1} bis {_2} % sein @msg {k}: baseline must be {_1} to {_2}% {k}: baseline muss {_1} bis {_2} % sein +@msg `{name}`: {k}: baseline must be {_1} to {_2}% `{name}`: {k}: baseline muss {_1} bis {_2} % sein @msg {k}: scale must be {_1} to {_2}% {k}: die Skalierung muss {_1} bis {_2} % sein +@msg `{name}`: {k}: scale must be {_1} to {_2}% `{name}`: {k}: die Skalierung muss {_1} bis {_2} % sein @msg {k}: only kana, roman and numerals can be scaled {k}: nur kana, roman und numerals lassen sich skalieren +@msg `{name}`: {k}: only kana, roman and numerals can be scaled `{name}`: {k}: nur kana, roman und numerals lassen sich skalieren @msg {k}: only kana can be scaled at the center {k}: nur kana lässt sich am Mittelpunkt skalieren +@msg `{name}`: {k}: only kana can be scaled at the center `{name}`: {k}: nur kana lässt sich am Mittelpunkt skalieren @msg no composite font keine zusammengesetzte Schrift Delete Font Schrift löschen Kanji Kanji diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 385746ed1e..97ec8ca3d9 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -4192,11 +4192,17 @@ digits Malayalam Μαλαγιάλαμ @msg the file holds two composite fonts named `{_1}` το αρχείο περιέχει δύο σύνθετες γραμματοσειρές με όνομα `{_1}` @msg `{_1}` can't name a composite font το `{_1}` δεν μπορεί να είναι όνομα σύνθετης γραμματοσειράς @msg {k}: give a font {k}: δώστε γραμματοσειρά +@msg `{name}`: {k}: give a font `{name}`: {k}: δώστε γραμματοσειρά @msg {k}: size must be {_1} to {_2}% {k}: το size πρέπει να είναι από {_1} έως {_2}% +@msg `{name}`: {k}: size must be {_1} to {_2}% `{name}`: {k}: το size πρέπει να είναι από {_1} έως {_2}% @msg {k}: baseline must be {_1} to {_2}% {k}: το baseline πρέπει να είναι από {_1} έως {_2}% +@msg `{name}`: {k}: baseline must be {_1} to {_2}% `{name}`: {k}: το baseline πρέπει να είναι από {_1} έως {_2}% @msg {k}: scale must be {_1} to {_2}% {k}: η κλίμακα πρέπει να είναι από {_1} έως {_2}% +@msg `{name}`: {k}: scale must be {_1} to {_2}% `{name}`: {k}: η κλίμακα πρέπει να είναι από {_1} έως {_2}% @msg {k}: only kana, roman and numerals can be scaled {k}: μόνο τα kana, roman και numerals αλλάζουν κλίμακα +@msg `{name}`: {k}: only kana, roman and numerals can be scaled `{name}`: {k}: μόνο τα kana, roman και numerals αλλάζουν κλίμακα @msg {k}: only kana can be scaled at the center {k}: μόνο το kana αλλάζει κλίμακα από το κέντρο +@msg `{name}`: {k}: only kana can be scaled at the center `{name}`: {k}: μόνο το kana αλλάζει κλίμακα από το κέντρο @msg no composite font καμία σύνθετη γραμματοσειρά Delete Font Διαγραφή γραμματοσειράς Kanji Κάντζι diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index c8d2a3a976..dff2eb955a 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -3707,11 +3707,17 @@ digits Malayalam Malayálam @msg the file holds two composite fonts named `{_1}` el archivo contiene dos fuentes compuestas llamadas `{_1}` @msg `{_1}` can't name a composite font `{_1}` no puede nombrar una fuente compuesta @msg {k}: give a font {k}: indique una fuente +@msg `{name}`: {k}: give a font `{name}`: {k}: indique una fuente @msg {k}: size must be {_1} to {_2}% {k}: size debe estar entre {_1} y {_2} % +@msg `{name}`: {k}: size must be {_1} to {_2}% `{name}`: {k}: size debe estar entre {_1} y {_2} % @msg {k}: baseline must be {_1} to {_2}% {k}: baseline debe estar entre {_1} y {_2} % +@msg `{name}`: {k}: baseline must be {_1} to {_2}% `{name}`: {k}: baseline debe estar entre {_1} y {_2} % @msg {k}: scale must be {_1} to {_2}% {k}: la escala debe estar entre {_1} y {_2} % +@msg `{name}`: {k}: scale must be {_1} to {_2}% `{name}`: {k}: la escala debe estar entre {_1} y {_2} % @msg {k}: only kana, roman and numerals can be scaled {k}: solo kana, roman y numerals se pueden escalar +@msg `{name}`: {k}: only kana, roman and numerals can be scaled `{name}`: {k}: solo kana, roman y numerals se pueden escalar @msg {k}: only kana can be scaled at the center {k}: solo kana se puede escalar desde el centro +@msg `{name}`: {k}: only kana can be scaled at the center `{name}`: {k}: solo kana se puede escalar desde el centro @msg no composite font ninguna fuente compuesta Delete Font Eliminar fuente Kanji Kanji diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 469a79077e..86659781e9 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -1651,11 +1651,17 @@ digits Malayalam Malajalam @msg the file holds two composite fonts named `{_1}` tiedostossa on kaksi yhdistelmäfonttia nimeltä `{_1}` @msg `{_1}` can't name a composite font `{_1}` ei kelpaa yhdistelmäfontin nimeksi @msg {k}: give a font {k}: anna fontti +@msg `{name}`: {k}: give a font `{name}`: {k}: anna fontti @msg {k}: size must be {_1} to {_2}% {k}: size on oltava {_1}–{_2} % +@msg `{name}`: {k}: size must be {_1} to {_2}% `{name}`: {k}: size on oltava {_1}–{_2} % @msg {k}: baseline must be {_1} to {_2}% {k}: baseline on oltava {_1}–{_2} % +@msg `{name}`: {k}: baseline must be {_1} to {_2}% `{name}`: {k}: baseline on oltava {_1}–{_2} % @msg {k}: scale must be {_1} to {_2}% {k}: skaalauksen on oltava {_1}–{_2} % +@msg `{name}`: {k}: scale must be {_1} to {_2}% `{name}`: {k}: skaalauksen on oltava {_1}–{_2} % @msg {k}: only kana, roman and numerals can be scaled {k}: vain kana, roman ja numerals voidaan skaalata +@msg `{name}`: {k}: only kana, roman and numerals can be scaled `{name}`: {k}: vain kana, roman ja numerals voidaan skaalata @msg {k}: only kana can be scaled at the center {k}: vain kana voidaan skaalata keskeltä +@msg `{name}`: {k}: only kana can be scaled at the center `{name}`: {k}: vain kana voidaan skaalata keskeltä @msg no composite font ei yhdistelmäfonttia Delete Font Poista fontti Kanji Kanji diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 14c2a7edd0..a1b7a53ef6 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -3699,11 +3699,17 @@ digits Malayalam Malayalam @msg the file holds two composite fonts named `{_1}` le fichier contient deux polices composites nommées `{_1}` @msg `{_1}` can't name a composite font `{_1}` ne peut pas nommer une police composite @msg {k}: give a font {k} : indiquez une police +@msg `{name}`: {k}: give a font `{name}`: {k} : indiquez une police @msg {k}: size must be {_1} to {_2}% {k} : size doit être compris entre {_1} et {_2} % +@msg `{name}`: {k}: size must be {_1} to {_2}% `{name}`: {k} : size doit être compris entre {_1} et {_2} % @msg {k}: baseline must be {_1} to {_2}% {k} : baseline doit être compris entre {_1} et {_2} % +@msg `{name}`: {k}: baseline must be {_1} to {_2}% `{name}`: {k} : baseline doit être compris entre {_1} et {_2} % @msg {k}: scale must be {_1} to {_2}% {k} : l’échelle doit être comprise entre {_1} et {_2} % +@msg `{name}`: {k}: scale must be {_1} to {_2}% `{name}`: {k} : l’échelle doit être comprise entre {_1} et {_2} % @msg {k}: only kana, roman and numerals can be scaled {k} : seuls kana, roman et numerals peuvent être mis à l’échelle +@msg `{name}`: {k}: only kana, roman and numerals can be scaled `{name}`: {k} : seuls kana, roman et numerals peuvent être mis à l’échelle @msg {k}: only kana can be scaled at the center {k} : seul kana peut être mis à l’échelle depuis le centre +@msg `{name}`: {k}: only kana can be scaled at the center `{name}`: {k} : seul kana peut être mis à l’échelle depuis le centre @msg no composite font aucune police composite Delete Font Supprimer la police Kanji Kanji diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv index 6b0144ae13..ec57c34ad3 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -5065,11 +5065,17 @@ antiAlias Sharp शार्प @msg the file holds two composite fonts named `{_1}` फ़ाइल में `{_1}` नाम के दो कंपोज़िट फ़ॉन्ट हैं @msg `{_1}` can't name a composite font `{_1}` कंपोज़िट फ़ॉन्ट का नाम नहीं हो सकता @msg {k}: give a font {k}: एक फ़ॉन्ट दें +@msg `{name}`: {k}: give a font `{name}`: {k}: एक फ़ॉन्ट दें @msg {k}: size must be {_1} to {_2}% {k}: size {_1} से {_2}% तक होना चाहिए +@msg `{name}`: {k}: size must be {_1} to {_2}% `{name}`: {k}: size {_1} से {_2}% तक होना चाहिए @msg {k}: baseline must be {_1} to {_2}% {k}: baseline {_1} से {_2}% तक होना चाहिए +@msg `{name}`: {k}: baseline must be {_1} to {_2}% `{name}`: {k}: baseline {_1} से {_2}% तक होना चाहिए @msg {k}: scale must be {_1} to {_2}% {k}: स्केल {_1} से {_2}% तक होना चाहिए +@msg `{name}`: {k}: scale must be {_1} to {_2}% `{name}`: {k}: स्केल {_1} से {_2}% तक होना चाहिए @msg {k}: only kana, roman and numerals can be scaled {k}: केवल kana, roman और numerals को स्केल किया जा सकता है +@msg `{name}`: {k}: only kana, roman and numerals can be scaled `{name}`: {k}: केवल kana, roman और numerals को स्केल किया जा सकता है @msg {k}: only kana can be scaled at the center {k}: केंद्र से केवल kana को स्केल किया जा सकता है +@msg `{name}`: {k}: only kana can be scaled at the center `{name}`: {k}: केंद्र से केवल kana को स्केल किया जा सकता है @msg no composite font कोई कंपोज़िट फ़ॉन्ट नहीं Delete Font फ़ॉन्ट हटाएँ Kanji कांजी diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 9dc6095770..c8130d54a1 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -4375,11 +4375,17 @@ mark-style Japanese Japán @msg the file holds two composite fonts named `{_1}` a fájl két `{_1}` nevű összetett betűtípust tartalmaz @msg `{_1}` can't name a composite font `{_1}` nem lehet összetett betűtípus neve @msg {k}: give a font {k}: adjon meg egy betűtípust +@msg `{name}`: {k}: give a font `{name}`: {k}: adjon meg egy betűtípust @msg {k}: size must be {_1} to {_2}% {k}: a size értéke {_1}–{_2}% lehet +@msg `{name}`: {k}: size must be {_1} to {_2}% `{name}`: {k}: a size értéke {_1}–{_2}% lehet @msg {k}: baseline must be {_1} to {_2}% {k}: a baseline értéke {_1}–{_2}% lehet +@msg `{name}`: {k}: baseline must be {_1} to {_2}% `{name}`: {k}: a baseline értéke {_1}–{_2}% lehet @msg {k}: scale must be {_1} to {_2}% {k}: a méretezés {_1}–{_2}% lehet +@msg `{name}`: {k}: scale must be {_1} to {_2}% `{name}`: {k}: a méretezés {_1}–{_2}% lehet @msg {k}: only kana, roman and numerals can be scaled {k}: csak a kana, roman és numerals méretezhető +@msg `{name}`: {k}: only kana, roman and numerals can be scaled `{name}`: {k}: csak a kana, roman és numerals méretezhető @msg {k}: only kana can be scaled at the center {k}: csak a kana méretezhető a középpontból +@msg `{name}`: {k}: only kana can be scaled at the center `{name}`: {k}: csak a kana méretezhető a középpontból @msg no composite font nincs összetett betűtípus Delete Font Betűtípus törlése Kanji Kandzsi diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 8c92a434fe..19cc1215a1 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -3753,11 +3753,17 @@ digits Malayalam Malayalam @msg the file holds two composite fonts named `{_1}` il file contiene due font compositi di nome `{_1}` @msg `{_1}` can't name a composite font `{_1}` non può essere il nome di un font composito @msg {k}: give a font {k}: indica un font +@msg `{name}`: {k}: give a font `{name}`: {k}: indica un font @msg {k}: size must be {_1} to {_2}% {k}: size deve essere tra {_1} e {_2}% +@msg `{name}`: {k}: size must be {_1} to {_2}% `{name}`: {k}: size deve essere tra {_1} e {_2}% @msg {k}: baseline must be {_1} to {_2}% {k}: baseline deve essere tra {_1} e {_2}% +@msg `{name}`: {k}: baseline must be {_1} to {_2}% `{name}`: {k}: baseline deve essere tra {_1} e {_2}% @msg {k}: scale must be {_1} to {_2}% {k}: la scala deve essere tra {_1} e {_2}% +@msg `{name}`: {k}: scale must be {_1} to {_2}% `{name}`: {k}: la scala deve essere tra {_1} e {_2}% @msg {k}: only kana, roman and numerals can be scaled {k}: solo kana, roman e numerals si possono ridimensionare +@msg `{name}`: {k}: only kana, roman and numerals can be scaled `{name}`: {k}: solo kana, roman e numerals si possono ridimensionare @msg {k}: only kana can be scaled at the center {k}: solo kana si può ridimensionare dal centro +@msg `{name}`: {k}: only kana can be scaled at the center `{name}`: {k}: solo kana si può ridimensionare dal centro @msg no composite font nessun font composito Delete Font Elimina font Kanji Kanji diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 8906c0e6c0..fe2e9f90bb 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -4082,11 +4082,17 @@ file-filter Templates テンプレート @msg the file holds two composite fonts named `{_1}` ファイルに `{_1}` という名前の合成フォントが 2 つあります @msg `{_1}` can't name a composite font `{_1}` は合成フォントの名前にできません @msg {k}: give a font {k}: フォントを指定してください +@msg `{name}`: {k}: give a font `{name}`: {k}: フォントを指定してください @msg {k}: size must be {_1} to {_2}% {k}: size は {_1}~{_2}% にしてください +@msg `{name}`: {k}: size must be {_1} to {_2}% `{name}`: {k}: size は {_1}~{_2}% にしてください @msg {k}: baseline must be {_1} to {_2}% {k}: baseline は {_1}~{_2}% にしてください +@msg `{name}`: {k}: baseline must be {_1} to {_2}% `{name}`: {k}: baseline は {_1}~{_2}% にしてください @msg {k}: scale must be {_1} to {_2}% {k}: 比率は {_1}~{_2}% にしてください +@msg `{name}`: {k}: scale must be {_1} to {_2}% `{name}`: {k}: 比率は {_1}~{_2}% にしてください @msg {k}: only kana, roman and numerals can be scaled {k}: 比率を変更できるのは kana、roman、numerals だけです +@msg `{name}`: {k}: only kana, roman and numerals can be scaled `{name}`: {k}: 比率を変更できるのは kana、roman、numerals だけです @msg {k}: only kana can be scaled at the center {k}: 中央を基準に比率を変更できるのは kana だけです +@msg `{name}`: {k}: only kana can be scaled at the center `{name}`: {k}: 中央を基準に比率を変更できるのは kana だけです @msg no composite font 合成フォントがありません Delete Font フォントを削除 Kanji 漢字 diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index de2c7a7dc7..6b4826f8c3 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -1522,11 +1522,17 @@ digits Malayalam მალაიალამური @msg the file holds two composite fonts named `{_1}` ფაილი შეიცავს ორ კომპოზიტურ შრიფტს სახელით `{_1}` @msg `{_1}` can't name a composite font `{_1}` ვერ იქნება კომპოზიტური შრიფტის სახელი @msg {k}: give a font {k}: მიუთითეთ შრიფტი +@msg `{name}`: {k}: give a font `{name}`: {k}: მიუთითეთ შრიფტი @msg {k}: size must be {_1} to {_2}% {k}: size უნდა იყოს {_1}-დან {_2}%-მდე +@msg `{name}`: {k}: size must be {_1} to {_2}% `{name}`: {k}: size უნდა იყოს {_1}-დან {_2}%-მდე @msg {k}: baseline must be {_1} to {_2}% {k}: baseline უნდა იყოს {_1}-დან {_2}%-მდე +@msg `{name}`: {k}: baseline must be {_1} to {_2}% `{name}`: {k}: baseline უნდა იყოს {_1}-დან {_2}%-მდე @msg {k}: scale must be {_1} to {_2}% {k}: მასშტაბი უნდა იყოს {_1}-დან {_2}%-მდე +@msg `{name}`: {k}: scale must be {_1} to {_2}% `{name}`: {k}: მასშტაბი უნდა იყოს {_1}-დან {_2}%-მდე @msg {k}: only kana, roman and numerals can be scaled {k}: მასშტაბირება შეიძლება მხოლოდ kana, roman და numerals ნაწილებში +@msg `{name}`: {k}: only kana, roman and numerals can be scaled `{name}`: {k}: მასშტაბირება შეიძლება მხოლოდ kana, roman და numerals ნაწილებში @msg {k}: only kana can be scaled at the center {k}: ცენტრიდან მასშტაბირება შეიძლება მხოლოდ kana ნაწილში +@msg `{name}`: {k}: only kana can be scaled at the center `{name}`: {k}: ცენტრიდან მასშტაბირება შეიძლება მხოლოდ kana ნაწილში @msg no composite font კომპოზიტური შრიფტი არ არის Delete Font შრიფტის წაშლა Kanji კანძი diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 2aa70c2d5d..ec34bd50f0 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -4070,11 +4070,17 @@ file-filter Templates 템플릿 @msg the file holds two composite fonts named `{_1}` 파일에 이름이 `{_1}`인 합성 글꼴이 두 개 있습니다 @msg `{_1}` can't name a composite font `{_1}`은(는) 합성 글꼴 이름으로 쓸 수 없습니다 @msg {k}: give a font {k}: 글꼴을 지정하세요 +@msg `{name}`: {k}: give a font `{name}`: {k}: 글꼴을 지정하세요 @msg {k}: size must be {_1} to {_2}% {k}: size는 {_1}~{_2}%여야 합니다 +@msg `{name}`: {k}: size must be {_1} to {_2}% `{name}`: {k}: size는 {_1}~{_2}%여야 합니다 @msg {k}: baseline must be {_1} to {_2}% {k}: baseline은 {_1}~{_2}%여야 합니다 +@msg `{name}`: {k}: baseline must be {_1} to {_2}% `{name}`: {k}: baseline은 {_1}~{_2}%여야 합니다 @msg {k}: scale must be {_1} to {_2}% {k}: 비율은 {_1}~{_2}%여야 합니다 +@msg `{name}`: {k}: scale must be {_1} to {_2}% `{name}`: {k}: 비율은 {_1}~{_2}%여야 합니다 @msg {k}: only kana, roman and numerals can be scaled {k}: 비율을 바꿀 수 있는 것은 kana, roman, numerals뿐입니다 +@msg `{name}`: {k}: only kana, roman and numerals can be scaled `{name}`: {k}: 비율을 바꿀 수 있는 것은 kana, roman, numerals뿐입니다 @msg {k}: only kana can be scaled at the center {k}: 가운데를 기준으로 비율을 바꿀 수 있는 것은 kana뿐입니다 +@msg `{name}`: {k}: only kana can be scaled at the center `{name}`: {k}: 가운데를 기준으로 비율을 바꿀 수 있는 것은 kana뿐입니다 @msg no composite font 합성 글꼴이 없습니다 Delete Font 글꼴 삭제 Kanji 한자 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 8b49530620..e2eacab182 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -2102,11 +2102,17 @@ digits Malayalam Malayalam @msg the file holds two composite fonts named `{_1}` het bestand bevat twee samengestelde lettertypen met de naam `{_1}` @msg `{_1}` can't name a composite font `{_1}` kan geen naam van een samengesteld lettertype zijn @msg {k}: give a font {k}: geef een lettertype op +@msg `{name}`: {k}: give a font `{name}`: {k}: geef een lettertype op @msg {k}: size must be {_1} to {_2}% {k}: size moet tussen {_1} en {_2}% liggen +@msg `{name}`: {k}: size must be {_1} to {_2}% `{name}`: {k}: size moet tussen {_1} en {_2}% liggen @msg {k}: baseline must be {_1} to {_2}% {k}: baseline moet tussen {_1} en {_2}% liggen +@msg `{name}`: {k}: baseline must be {_1} to {_2}% `{name}`: {k}: baseline moet tussen {_1} en {_2}% liggen @msg {k}: scale must be {_1} to {_2}% {k}: de schaal moet tussen {_1} en {_2}% liggen +@msg `{name}`: {k}: scale must be {_1} to {_2}% `{name}`: {k}: de schaal moet tussen {_1} en {_2}% liggen @msg {k}: only kana, roman and numerals can be scaled {k}: alleen kana, roman en numerals kunnen worden geschaald +@msg `{name}`: {k}: only kana, roman and numerals can be scaled `{name}`: {k}: alleen kana, roman en numerals kunnen worden geschaald @msg {k}: only kana can be scaled at the center {k}: alleen kana kan vanuit het midden worden geschaald +@msg `{name}`: {k}: only kana can be scaled at the center `{name}`: {k}: alleen kana kan vanuit het midden worden geschaald @msg no composite font geen samengesteld lettertype Delete Font Lettertype verwijderen Kanji Kanji diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index b80eee81d7..0becf9d142 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -1590,11 +1590,17 @@ digits Malayalam Malajalam @msg the file holds two composite fonts named `{_1}` plik zawiera dwa fonty złożone o nazwie `{_1}` @msg `{_1}` can't name a composite font `{_1}` nie może być nazwą fontu złożonego @msg {k}: give a font {k}: podaj font +@msg `{name}`: {k}: give a font `{name}`: {k}: podaj font @msg {k}: size must be {_1} to {_2}% {k}: size musi wynosić od {_1} do {_2}% +@msg `{name}`: {k}: size must be {_1} to {_2}% `{name}`: {k}: size musi wynosić od {_1} do {_2}% @msg {k}: baseline must be {_1} to {_2}% {k}: baseline musi wynosić od {_1} do {_2}% +@msg `{name}`: {k}: baseline must be {_1} to {_2}% `{name}`: {k}: baseline musi wynosić od {_1} do {_2}% @msg {k}: scale must be {_1} to {_2}% {k}: skala musi wynosić od {_1} do {_2}% +@msg `{name}`: {k}: scale must be {_1} to {_2}% `{name}`: {k}: skala musi wynosić od {_1} do {_2}% @msg {k}: only kana, roman and numerals can be scaled {k}: skalować można tylko kana, roman i numerals +@msg `{name}`: {k}: only kana, roman and numerals can be scaled `{name}`: {k}: skalować można tylko kana, roman i numerals @msg {k}: only kana can be scaled at the center {k}: od środka można skalować tylko kana +@msg `{name}`: {k}: only kana can be scaled at the center `{name}`: {k}: od środka można skalować tylko kana @msg no composite font brak fontu złożonego Delete Font Usuń font Kanji Kanji diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 479212841b..9a6a2d6082 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -5267,11 +5267,17 @@ axis Both Ambos @msg the file holds two composite fonts named `{_1}` o arquivo contém duas fontes compostas chamadas `{_1}` @msg `{_1}` can't name a composite font `{_1}` não pode nomear uma fonte composta @msg {k}: give a font {k}: informe uma fonte +@msg `{name}`: {k}: give a font `{name}`: {k}: informe uma fonte @msg {k}: size must be {_1} to {_2}% {k}: size deve estar entre {_1} e {_2}% +@msg `{name}`: {k}: size must be {_1} to {_2}% `{name}`: {k}: size deve estar entre {_1} e {_2}% @msg {k}: baseline must be {_1} to {_2}% {k}: baseline deve estar entre {_1} e {_2}% +@msg `{name}`: {k}: baseline must be {_1} to {_2}% `{name}`: {k}: baseline deve estar entre {_1} e {_2}% @msg {k}: scale must be {_1} to {_2}% {k}: a escala deve estar entre {_1} e {_2}% +@msg `{name}`: {k}: scale must be {_1} to {_2}% `{name}`: {k}: a escala deve estar entre {_1} e {_2}% @msg {k}: only kana, roman and numerals can be scaled {k}: só kana, roman e numerals podem ser escalados +@msg `{name}`: {k}: only kana, roman and numerals can be scaled `{name}`: {k}: só kana, roman e numerals podem ser escalados @msg {k}: only kana can be scaled at the center {k}: só kana pode ser escalado a partir do centro +@msg `{name}`: {k}: only kana can be scaled at the center `{name}`: {k}: só kana pode ser escalado a partir do centro @msg no composite font nenhuma fonte composta Delete Font Excluir fonte Kanji Kanji diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 07b1369c88..9f3c78c1d1 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -3739,11 +3739,17 @@ digits Malayalam Малаялам @msg the file holds two composite fonts named `{_1}` в файле два составных шрифта с именем `{_1}` @msg `{_1}` can't name a composite font `{_1}` не может быть именем составного шрифта @msg {k}: give a font {k}: укажите шрифт +@msg `{name}`: {k}: give a font `{name}`: {k}: укажите шрифт @msg {k}: size must be {_1} to {_2}% {k}: size должен быть от {_1} до {_2}% +@msg `{name}`: {k}: size must be {_1} to {_2}% `{name}`: {k}: size должен быть от {_1} до {_2}% @msg {k}: baseline must be {_1} to {_2}% {k}: baseline должен быть от {_1} до {_2}% +@msg `{name}`: {k}: baseline must be {_1} to {_2}% `{name}`: {k}: baseline должен быть от {_1} до {_2}% @msg {k}: scale must be {_1} to {_2}% {k}: масштаб должен быть от {_1} до {_2}% +@msg `{name}`: {k}: scale must be {_1} to {_2}% `{name}`: {k}: масштаб должен быть от {_1} до {_2}% @msg {k}: only kana, roman and numerals can be scaled {k}: масштабировать можно только kana, roman и numerals +@msg `{name}`: {k}: only kana, roman and numerals can be scaled `{name}`: {k}: масштабировать можно только kana, roman и numerals @msg {k}: only kana can be scaled at the center {k}: масштабировать от центра можно только kana +@msg `{name}`: {k}: only kana can be scaled at the center `{name}`: {k}: масштабировать от центра можно только kana @msg no composite font нет составного шрифта Delete Font Удалить шрифт Kanji Кандзи diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index dc0b4fc494..42e71340e5 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -3629,11 +3629,17 @@ digits Malayalam Malayalam @msg the file holds two composite fonts named `{_1}` filen innehåller två sammansatta typsnitt som heter `{_1}` @msg `{_1}` can't name a composite font `{_1}` kan inte vara namnet på ett sammansatt typsnitt @msg {k}: give a font {k}: ange ett typsnitt +@msg `{name}`: {k}: give a font `{name}`: {k}: ange ett typsnitt @msg {k}: size must be {_1} to {_2}% {k}: size måste vara {_1} till {_2} % +@msg `{name}`: {k}: size must be {_1} to {_2}% `{name}`: {k}: size måste vara {_1} till {_2} % @msg {k}: baseline must be {_1} to {_2}% {k}: baseline måste vara {_1} till {_2} % +@msg `{name}`: {k}: baseline must be {_1} to {_2}% `{name}`: {k}: baseline måste vara {_1} till {_2} % @msg {k}: scale must be {_1} to {_2}% {k}: skalan måste vara {_1} till {_2} % +@msg `{name}`: {k}: scale must be {_1} to {_2}% `{name}`: {k}: skalan måste vara {_1} till {_2} % @msg {k}: only kana, roman and numerals can be scaled {k}: bara kana, roman och numerals kan skalas +@msg `{name}`: {k}: only kana, roman and numerals can be scaled `{name}`: {k}: bara kana, roman och numerals kan skalas @msg {k}: only kana can be scaled at the center {k}: bara kana kan skalas från mitten +@msg `{name}`: {k}: only kana can be scaled at the center `{name}`: {k}: bara kana kan skalas från mitten @msg no composite font inget sammansatt typsnitt Delete Font Ta bort typsnitt Kanji Kanji diff --git a/crates/ui-egui/src/i18n/tests.rs b/crates/ui-egui/src/i18n/tests.rs index 671ce6ce92..3bc53ebb8c 100644 --- a/crates/ui-egui/src/i18n/tests.rs +++ b/crates/ui-egui/src/i18n/tests.rs @@ -2103,6 +2103,12 @@ fn composite_font_errors_translate_in_complete_languages() { "{k}: scale must be {_1} to {_2}%", "{k}: only kana, roman and numerals can be scaled", "{k}: only kana can be scaled at the center", + "`{name}`: {k}: give a font", + "`{name}`: {k}: size must be {_1} to {_2}%", + "`{name}`: {k}: baseline must be {_1} to {_2}%", + "`{name}`: {k}: scale must be {_1} to {_2}%", + "`{name}`: {k}: only kana, roman and numerals can be scaled", + "`{name}`: {k}: only kana can be scaled at the center", "no composite font", ]; // An error, English words its translation replaces, and the text it keeps. @@ -4077,6 +4083,10 @@ const NAMED_KEYS: &[(&str, &[&str])] = &[ ("{k}: only kana can be scaled at the center", &["kana"]), ("{k}: only kana, roman and numerals can be scaled", &["kana", "roman", "numerals"]), ("{k}: size must be {_1} to {_2}%", &["size"]), + ("`{name}`: {k}: baseline must be {_1} to {_2}%", &["baseline"]), + ("`{name}`: {k}: only kana can be scaled at the center", &["kana"]), + ("`{name}`: {k}: only kana, roman and numerals can be scaled", &["kana", "roman", "numerals"]), + ("`{name}`: {k}: size must be {_1} to {_2}%", &["size"]), ]; /// Every catalog's translation of a [`NAMED_KEYS`] message keeps each name the table lists, as diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index da3b7bd26a..36572fa017 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -3493,11 +3493,17 @@ digits Malayalam Malayalam @msg the file holds two composite fonts named `{_1}` dosyada `{_1}` adlı iki bileşik font var @msg `{_1}` can't name a composite font `{_1}` bir bileşik fontun adı olamaz @msg {k}: give a font {k}: bir font verin +@msg `{name}`: {k}: give a font `{name}`: {k}: bir font verin @msg {k}: size must be {_1} to {_2}% {k}: size %{_1} ile %{_2} arasında olmalı +@msg `{name}`: {k}: size must be {_1} to {_2}% `{name}`: {k}: size %{_1} ile %{_2} arasında olmalı @msg {k}: baseline must be {_1} to {_2}% {k}: baseline %{_1} ile %{_2} arasında olmalı +@msg `{name}`: {k}: baseline must be {_1} to {_2}% `{name}`: {k}: baseline %{_1} ile %{_2} arasında olmalı @msg {k}: scale must be {_1} to {_2}% {k}: ölçek %{_1} ile %{_2} arasında olmalı +@msg `{name}`: {k}: scale must be {_1} to {_2}% `{name}`: {k}: ölçek %{_1} ile %{_2} arasında olmalı @msg {k}: only kana, roman and numerals can be scaled {k}: yalnızca kana, roman ve numerals ölçeklenebilir +@msg `{name}`: {k}: only kana, roman and numerals can be scaled `{name}`: {k}: yalnızca kana, roman ve numerals ölçeklenebilir @msg {k}: only kana can be scaled at the center {k}: merkezden yalnızca kana ölçeklenebilir +@msg `{name}`: {k}: only kana can be scaled at the center `{name}`: {k}: merkezden yalnızca kana ölçeklenebilir @msg no composite font bileşik font yok Delete Font Fontu Sil Kanji Kanji diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 243479fcb3..1a5b662ecd 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -3746,11 +3746,17 @@ effect Points Точки @msg the file holds two composite fonts named `{_1}` у файлі два складені шрифти з назвою `{_1}` @msg `{_1}` can't name a composite font `{_1}` не може бути назвою складеного шрифту @msg {k}: give a font {k}: вкажіть шрифт +@msg `{name}`: {k}: give a font `{name}`: {k}: вкажіть шрифт @msg {k}: size must be {_1} to {_2}% {k}: size має бути від {_1} до {_2}% +@msg `{name}`: {k}: size must be {_1} to {_2}% `{name}`: {k}: size має бути від {_1} до {_2}% @msg {k}: baseline must be {_1} to {_2}% {k}: baseline має бути від {_1} до {_2}% +@msg `{name}`: {k}: baseline must be {_1} to {_2}% `{name}`: {k}: baseline має бути від {_1} до {_2}% @msg {k}: scale must be {_1} to {_2}% {k}: масштаб має бути від {_1} до {_2}% +@msg `{name}`: {k}: scale must be {_1} to {_2}% `{name}`: {k}: масштаб має бути від {_1} до {_2}% @msg {k}: only kana, roman and numerals can be scaled {k}: масштабувати можна лише kana, roman і numerals +@msg `{name}`: {k}: only kana, roman and numerals can be scaled `{name}`: {k}: масштабувати можна лише kana, roman і numerals @msg {k}: only kana can be scaled at the center {k}: масштабувати від центру можна лише kana +@msg `{name}`: {k}: only kana can be scaled at the center `{name}`: {k}: масштабувати від центру можна лише kana @msg no composite font немає складеного шрифту Delete Font Видалити шрифт Kanji Кандзі diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 7698b3d145..2fe710c328 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -4092,11 +4092,17 @@ mark-style Japanese Kiểu Nhật @msg the file holds two composite fonts named `{_1}` tệp chứa hai phông chữ tổ hợp cùng tên `{_1}` @msg `{_1}` can't name a composite font `{_1}` không thể là tên phông chữ tổ hợp @msg {k}: give a font {k}: hãy cung cấp một phông chữ +@msg `{name}`: {k}: give a font `{name}`: {k}: hãy cung cấp một phông chữ @msg {k}: size must be {_1} to {_2}% {k}: size phải từ {_1} đến {_2}% +@msg `{name}`: {k}: size must be {_1} to {_2}% `{name}`: {k}: size phải từ {_1} đến {_2}% @msg {k}: baseline must be {_1} to {_2}% {k}: baseline phải từ {_1} đến {_2}% +@msg `{name}`: {k}: baseline must be {_1} to {_2}% `{name}`: {k}: baseline phải từ {_1} đến {_2}% @msg {k}: scale must be {_1} to {_2}% {k}: tỷ lệ phải từ {_1} đến {_2}% +@msg `{name}`: {k}: scale must be {_1} to {_2}% `{name}`: {k}: tỷ lệ phải từ {_1} đến {_2}% @msg {k}: only kana, roman and numerals can be scaled {k}: chỉ kana, roman và numerals mới co giãn được +@msg `{name}`: {k}: only kana, roman and numerals can be scaled `{name}`: {k}: chỉ kana, roman và numerals mới co giãn được @msg {k}: only kana can be scaled at the center {k}: chỉ kana mới co giãn từ tâm được +@msg `{name}`: {k}: only kana can be scaled at the center `{name}`: {k}: chỉ kana mới co giãn từ tâm được @msg no composite font không có phông chữ tổ hợp Delete Font Xóa phông chữ Kanji Kanji diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 4f796f9090..c7cbd36aa6 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -3568,11 +3568,17 @@ digits Malayalam 马拉雅拉姆数字 @msg the file holds two composite fonts named `{_1}` 文件中有两个复合字体都名为 `{_1}` @msg `{_1}` can't name a composite font `{_1}` 不能用作复合字体的名称 @msg {k}: give a font {k}:请指定字体 +@msg `{name}`: {k}: give a font `{name}`: {k}:请指定字体 @msg {k}: size must be {_1} to {_2}% {k}:size 必须为 {_1}% 到 {_2}% +@msg `{name}`: {k}: size must be {_1} to {_2}% `{name}`: {k}:size 必须为 {_1}% 到 {_2}% @msg {k}: baseline must be {_1} to {_2}% {k}:baseline 必须为 {_1}% 到 {_2}% +@msg `{name}`: {k}: baseline must be {_1} to {_2}% `{name}`: {k}:baseline 必须为 {_1}% 到 {_2}% @msg {k}: scale must be {_1} to {_2}% {k}:缩放必须为 {_1}% 到 {_2}% +@msg `{name}`: {k}: scale must be {_1} to {_2}% `{name}`: {k}:缩放必须为 {_1}% 到 {_2}% @msg {k}: only kana, roman and numerals can be scaled {k}:只有 kana、roman 和 numerals 可以缩放 +@msg `{name}`: {k}: only kana, roman and numerals can be scaled `{name}`: {k}:只有 kana、roman 和 numerals 可以缩放 @msg {k}: only kana can be scaled at the center {k}:只有 kana 可以从中心缩放 +@msg `{name}`: {k}: only kana can be scaled at the center `{name}`: {k}:只有 kana 可以从中心缩放 @msg no composite font 没有复合字体 Delete Font 删除字体 Kanji 汉字 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 4af3d3a822..5e74d83d12 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -4823,11 +4823,17 @@ digits Malayalam 馬來亞拉姆數字 @msg the file holds two composite fonts named `{_1}` 檔案中有兩個複合字體都名為 `{_1}` @msg `{_1}` can't name a composite font `{_1}` 不能作為複合字體的名稱 @msg {k}: give a font {k}:請指定字體 +@msg `{name}`: {k}: give a font `{name}`: {k}:請指定字體 @msg {k}: size must be {_1} to {_2}% {k}:size 必須為 {_1}% 到 {_2}% +@msg `{name}`: {k}: size must be {_1} to {_2}% `{name}`: {k}:size 必須為 {_1}% 到 {_2}% @msg {k}: baseline must be {_1} to {_2}% {k}:baseline 必須為 {_1}% 到 {_2}% +@msg `{name}`: {k}: baseline must be {_1} to {_2}% `{name}`: {k}:baseline 必須為 {_1}% 到 {_2}% @msg {k}: scale must be {_1} to {_2}% {k}:縮放必須為 {_1}% 到 {_2}% +@msg `{name}`: {k}: scale must be {_1} to {_2}% `{name}`: {k}:縮放必須為 {_1}% 到 {_2}% @msg {k}: only kana, roman and numerals can be scaled {k}:只有 kana、roman 和 numerals 可以縮放 +@msg `{name}`: {k}: only kana, roman and numerals can be scaled `{name}`: {k}:只有 kana、roman 和 numerals 可以縮放 @msg {k}: only kana can be scaled at the center {k}:只有 kana 可以從中心縮放 +@msg `{name}`: {k}: only kana can be scaled at the center `{name}`: {k}:只有 kana 可以從中心縮放 @msg no composite font 沒有複合字體 Delete Font 刪除字體 Kanji 漢字 From 90a2bd0494ed1997e4f169baad01747e7b10161e Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 14 Nov 2026 08:54:28 -0800 Subject: [PATCH 05/29] Graphs: aligned paint and stroke rows given with a design's art are rows of its stack paint.setFill, paint.setStroke, paint.invert and paint.complement with an item, stroke.set, stroke.setAdvanced, paint.editGradient, paint.setGradientGeom and the paint.freeform edits read an item given with aligned: true and the art of a graph design in a series first as a row of the art's stack, as appearance.setItem and transparency.set do. Each other object edits the row equal to it and an object without one is left as it is. A row the art does not have, a row of the other kind, or a row no other object has is an error, also when the art is the only object given, and the document and its undo history stay as they are. Without stroke, the gradient commands edit the kind of the art's row. The objects and ids the commands report, a gradient spanning the selection, and the last color and last gradient count only the objects whose row was edited. --- crates/engine/src/cmd/appearance.rs | 63 ++++++++-- crates/engine/src/cmd/effectcmd.rs | 12 +- crates/engine/src/cmd/freeform.rs | 8 +- crates/engine/src/cmd/gradient.rs | 20 +++- crates/engine/src/cmd/graph/tests.rs | 167 +++++++++++++++++++++++++++ crates/engine/src/cmd/paint.rs | 35 ++++-- crates/engine/src/cmd/stroke.rs | 11 +- docs/mcp.md | 10 +- 8 files changed, 280 insertions(+), 46 deletions(-) diff --git a/crates/engine/src/cmd/appearance.rs b/crates/engine/src/cmd/appearance.rs index 09b03f2d63..439661b209 100644 --- a/crates/engine/src/cmd/appearance.rs +++ b/crates/engine/src/cmd/appearance.rs @@ -422,6 +422,15 @@ pub(crate) fn design_reference(s: &Session, p: &Value, cmd: &str) -> Result Result> { + if matches!(item, ItemTarget::Item { explicit: false, .. }) { + return Ok(None); + } + design_reference(s, p, cmd) +} + /// `ids` after `of` (see [`design_reference`]), when there is one. pub(crate) fn with_reference(of: Option, ids: &[NodeId]) -> Vec { of.into_iter().chain(ids.iter().copied()).collect() @@ -606,16 +615,22 @@ fn require_targets(s: &Session, p: &Value, cmd: &str) -> Result> { /// What the edit changes on the topmost visible fill and stroke of graph series marks is stored on /// their graphs in the same step ([`capture_series_changes`]). Nothing targeted is not an edit, and /// when `f` leaves every target as it was, the document and its undo history stay as they are. +/// With `of` (see [`reference_of`]), an explicit aligned `item` is a row of the stack of `of`, which +/// is not among `ids`: it must be there and of the edited kind, each target edits the row equal to +/// it, a target without one is left as it is, and no target with one is an error. Returns the +/// targets `f` was aimed at, in the order of `ids`. +#[allow(clippy::too_many_arguments)] pub(crate) fn edit_items( s: &mut Session, ids: &[NodeId], item: ItemTarget, + of: Option, cmd: &str, label: &str, fill: bool, f: impl FnMut(&mut Node, Option) -> Result<()>, -) -> Result<()> { - edit_items_then(s, ids, item, cmd, label, fill, f, |before, d, edited| Ok(capture_series_changes(before, d, edited))) +) -> Result> { + edit_items_then(s, ids, item, of, cmd, label, fill, f, |before, d, edited| Ok(capture_series_changes(before, d, edited))) } /// [`edit_items`], then `after` on the document as it was before the edit, the edited document and @@ -627,17 +642,29 @@ pub(crate) fn edit_items_then( s: &mut Session, ids: &[NodeId], item: ItemTarget, + of: Option, cmd: &str, label: &str, fill: bool, mut f: impl FnMut(&mut Node, Option) -> Result<()>, mut after: impl FnMut(&vectorcraft_doc::Document, &mut vectorcraft_doc::Document, &[NodeId]) -> Result, -) -> Result<()> { - if ids.is_empty() { - return Ok(()); - } +) -> Result> { + let art = of.and_then(|of| s.doc().ok()?.doc.node(of).map(|n| n.appearance.clone())); + let per = match (item, art) { + // Checked even when the art is the only object given, which leaves `ids` empty. + (ItemTarget::Item { index, explicit: true, aligned: true }, Some(art)) => { + item.resolve(&art, fill, cmd)?; + let per = item.per_target_of(s, of, ids); + if per.iter().all(Option::is_none) { + return Err(bad(cmd, format!("no appearance item {index}"))); + } + per + } + _ if ids.is_empty() => return Ok(Vec::new()), + _ => item.per_target(s, ids), + }; + let aimed: Vec = ids.iter().zip(&per).filter(|(_, item)| item.is_some()).map(|(id, _)| *id).collect(); let before = s.doc()?.doc.clone(); - let per = item.per_target(s, ids); super::overprint::edit_counted(s, label, |d| { let mut edited = Vec::new(); // The objects whose rows `f` edited: on type, no row means its characters. @@ -656,7 +683,13 @@ pub(crate) fn edit_items_then( let labels = super::graph::capture_label_rows(d, &rows); Ok(count_changed(d, &before, &edited) + usize::from(more || labels)) })?; - Ok(()) + Ok(aimed) +} + +/// The first of `ids` that [`edit_items`] aimed at `item` with `of` changes, and the item it changes there. Read it +/// before the edit: once edited, a row that was equal to a row of the art of `of` no longer is. +pub(crate) fn first_edited(s: &Session, item: ItemTarget, of: Option, ids: &[NodeId]) -> Option<(NodeId, ItemTarget)> { + ids.iter().zip(item.per_target_of(s, of, ids)).find_map(|(id, it)| Some((*id, it?))) } /// How many of `ids` differ in `d` from `before`. @@ -665,8 +698,8 @@ pub(crate) fn count_changed(d: &vectorcraft_doc::Document, before: &vectorcraft_ } /// Whether an edit aimed at `item` changes a stroke: the `stroke` param, else the kind of the -/// targeted item (on the object whose row it is, see [`ItemTarget::anchor`]), else `default`. A `stroke` other than true, false or -/// null is an error of command `cmd`. +/// targeted item (on the object whose row it is, see [`ItemTarget::anchor`] and [`reference_of`]), else `default`. A `stroke` other than +/// true, false or null is an error of command `cmd`. pub(crate) fn edits_stroke(s: &Session, p: &Value, item: ItemTarget, default: bool, cmd: &str) -> Result { check_switches(p, &["stroke"], cmd)?; if let Some(b) = p.get("stroke").and_then(Value::as_bool) { @@ -674,8 +707,14 @@ pub(crate) fn edits_stroke(s: &Session, p: &Value, item: ItemTarget, default: bo } let ItemTarget::Item { index, .. } = item else { return Ok(default) }; let d = &s.doc()?.doc; - let ids = drop_design_art(d, appearance_targets(s, p)?); - Ok(ids.get(item.anchor(s, &ids)).and_then(|id| d.node(*id)?.appearance.items.get(index)).map_or(default, |it| !it.is_fill())) + let holder = match reference_of(s, p, item, cmd)? { + Some(of) => Some(of), + None => { + let ids = drop_design_art(d, appearance_targets(s, p)?); + ids.get(item.anchor(s, &ids)).copied() + } + }; + Ok(holder.and_then(|id| d.node(id)?.appearance.items.get(index)).map_or(default, |it| !it.is_fill())) } /// The whole number of 0 or more that `key` holds, read with [`u64_req`] and [`index`]. diff --git a/crates/engine/src/cmd/effectcmd.rs b/crates/engine/src/cmd/effectcmd.rs index 7619444c74..798b635e65 100644 --- a/crates/engine/src/cmd/effectcmd.rs +++ b/crates/engine/src/cmd/effectcmd.rs @@ -14,7 +14,8 @@ use vectorcraft_geom::{FillRule, PathData}; use vectorcraft_render::effects; use super::appearance::{ - ItemTarget, aligned, appearance_targets, design_reference, edit_targets, index_param, item_indices, item_target, item_target_at, with_reference, + ItemTarget, aligned, appearance_targets, design_reference, edit_targets, index_param, item_indices, item_target, item_target_at, reference_of, + with_reference, }; use super::expand::has_seeded; use super::rasterfx; @@ -336,15 +337,6 @@ pub(crate) fn effect_targets(s: &Session, p: &Value, item: ItemTarget, index: us /// have. const APPENDED: usize = usize::MAX; -/// The [`design_reference`] of an effect edit aimed at `item`: none for the Appearance panel's active row, which is a -/// row of the first selected object's stack. -fn reference_of(s: &Session, p: &Value, item: ItemTarget, cmd: &str) -> Result> { - if matches!(item, ItemTarget::Item { explicit: false, .. }) { - return Ok(None); - } - design_reference(s, p, cmd) -} - #[allow(clippy::too_many_arguments)] fn edit_effects( s: &mut Session, diff --git a/crates/engine/src/cmd/freeform.rs b/crates/engine/src/cmd/freeform.rs index a37cce38d0..c0cb1b5292 100644 --- a/crates/engine/src/cmd/freeform.rs +++ b/crates/engine/src/cmd/freeform.rs @@ -201,7 +201,9 @@ fn edit(s: &mut Session, p: &Value, cmd: &str, mut f: impl FnMut(&mut Freefor first.get_or_insert(r); Ok(()) }; - edit_items(s, &ids, item, cmd, "Gradient", !stroke, |n, index| { + let of = super::appearance::reference_of(s, p, item, cmd)?; + let shown_at = super::appearance::first_edited(s, item, of, &ids); + edit_items(s, &ids, item, of, cmd, "Gradient", !stroke, |n, index| { if index.is_none() && let NodeKind::Text(t) = &mut n.kind { @@ -222,7 +224,9 @@ fn edit(s: &mut Session, p: &Value, cmd: &str, mut f: impl FnMut(&mut Freefor })?; let r = first.ok_or_else(|| bad(cmd, NOT_FREEFORM))?; // The edited gradient is the last one used (`paint.lastGradient`). - let shown = s.doc()?.doc.node(ids[0]).map(|n| super::paint::proxy_paint(n, stroke, item.resolve(&n.appearance, !stroke, cmd).ok().flatten())); + let shown = shown_at.and_then(|(id, item)| { + s.doc().ok()?.doc.node(id).map(|n| super::paint::proxy_paint(n, stroke, item.resolve(&n.appearance, !stroke, cmd).ok().flatten())) + }); if let Some(p) = shown { s.remember_paint(&p); } diff --git a/crates/engine/src/cmd/gradient.rs b/crates/engine/src/cmd/gradient.rs index 7c7724f0f9..a3ebacf42f 100644 --- a/crates/engine/src/cmd/gradient.rs +++ b/crates/engine/src/cmd/gradient.rs @@ -580,7 +580,10 @@ fn edit_gradient(s: &mut Session, p: &Value) -> Result { let new_default = apply_gradient_edit_in_model(&default_paint, &with_stored_stops(&for_default, &default_paint, &stored), None, &|_| true, model) .map_err(|e| bad(C, e))?; let freeform = str_param(p, "kind").and_then(GradientKind::parse) == Some(GradientKind::Freeform); - edit_items(s, &ids, item, C, "Gradient", !stroke, |n, index| { + let of = super::appearance::reference_of(s, p, item, C)?; + let shown_at = super::appearance::first_edited(s, item, of, &ids); + // The objects returned: with a design's art, those with a row equal to the art's. + let edited = edit_items(s, &ids, item, of, C, "Gradient", !stroke, |n, index| { if index.is_none() && let NodeKind::Text(t) = &mut n.kind { @@ -616,9 +619,12 @@ fn edit_gradient(s: &mut Session, p: &Value) -> Result { st.gradient_mode = m; } // The last gradient is the one the first object now shows (the defaults' without objects). - let shown = match ids.first() { - Some(id) => s.doc()?.doc.node(*id).map(|n| super::paint::proxy_paint(n, stroke, item.resolve(&n.appearance, !stroke, C).ok().flatten())), - None => Some(new_default.clone()), + let shown = match shown_at { + Some((id, item)) => { + s.doc()?.doc.node(id).map(|n| super::paint::proxy_paint(n, stroke, item.resolve(&n.appearance, !stroke, C).ok().flatten())) + } + None if ids.is_empty() => Some(new_default.clone()), + None => None, }; if let Some(shown) = shown { s.remember_paint(&shown); @@ -628,7 +634,7 @@ fn edit_gradient(s: &mut Session, p: &Value) -> Result { } else { s.paint.fill = new_default; } - Ok(json!({"ids": ids.iter().map(|i| i.0).collect::>()})) + Ok(json!({"ids": edited.iter().map(|i| i.0).collect::>()})) } /// A painted stroke on a type run without a weight gets 1 pt. @@ -766,7 +772,9 @@ fn set_gradient_geom(s: &mut Session, p: &Value) -> Result { } let targets = super::appearance::drop_design_art(&s.doc()?.doc, targets); let model = super::paint::new_color_model(s, &Value::Null); - edit_items(s, &targets, item, C, "Gradient", !stroke, |n, index| { + let of = super::appearance::reference_of(s, p, item, C)?; + // The objects returned: with a design's art, those with a row equal to the art's. + let targets = edit_items(s, &targets, item, of, C, "Gradient", !stroke, |n, index| { if index.is_none() && let NodeKind::Text(t) = &mut n.kind { diff --git a/crates/engine/src/cmd/graph/tests.rs b/crates/engine/src/cmd/graph/tests.rs index df002ae526..ba059aac4a 100644 --- a/crates/engine/src/cmd/graph/tests.rs +++ b/crates/engine/src/cmd/graph/tests.rs @@ -859,6 +859,173 @@ fn aligned_rows_of_a_graph_designs_art_and_a_different_stack_are_not_moved() { assert_eq!(paints, [art_was.items[1].paint().clone(), art_was.items[0].paint().clone(), art_was.items[1].paint().clone()]); } +/// An `item` given with `aligned: true` and the art of a graph design in a series first is a row of the art's stack in the +/// paint and stroke commands: each other object edits the row equal to it, and a row the art does not have as a fill or +/// stroke as edited, or that no other object has, is an error that leaves the document and its undo history as they are. +#[test] +fn aligned_paint_rows_of_a_graph_designs_art_edit_the_equal_rows_of_the_other_objects() { + let rows = |s: &Session, id: NodeId| (*s.doc().unwrap().doc.node(id).unwrap().appearance).clone(); + let green = Paint::solid(vectorcraft_color::Color::rgb(0.0, 1.0, 0.0)); + // The art's row 1 is the blue fill the mark has as row 0, and its row 2 the black stroke the mark has as row 1. + let (mut s, _, art, mark) = aligned_design_art(); + let black = rows(&s, mark).items[1].clone(); + s.edit("Stroke", |d, _| { + d.node_mut(art).unwrap().appearance.items.push(black.clone()); + Ok(()) + }) + .unwrap(); + let (art_was, mark_was) = (rows(&s, art), rows(&s, mark)); + let fill = |s: &mut Session, cmd: &str, p: serde_json::Value| { + s.execute(cmd, &p).unwrap(); + let now = rows(s, mark); + assert_eq!(rows(s, art), art_was, "{cmd}"); + assert_ne!(now.items[0], mark_was.items[0], "{cmd} edits the mark's blue fill"); + assert_eq!(now.items[1], mark_was.items[1], "{cmd} leaves the mark's stroke"); + s.execute("edit.undo", &json!({})).unwrap(); + assert_eq!(rows(s, mark), mark_was, "{cmd} is one undo step"); + now + }; + let ids = [art.0, mark.0]; + let now = fill(&mut s, "paint.setFill", json!({"ids": ids, "item": 1, "aligned": true, "color": "#00ff00"})); + assert_eq!(now.items[0].paint(), &green); + let now = fill(&mut s, "paint.invert", json!({"ids": ids, "item": 1, "aligned": true})); + assert_eq!(Some(s.last_solid), now.items[0].paint().color(), "the inverted color is the last color, not the art's"); + let now = fill(&mut s, "paint.editGradient", json!({"ids": ids, "item": 1, "aligned": true, "kind": "linear"})); + let Paint::Gradient(g) = now.items[0].paint() else { panic!("{:?}", now.items[0].paint()) }; + assert_eq!(&s.last_gradient, &**g, "the mark's new gradient is the last gradient"); + + let stroke = |s: &mut Session, cmd: &str, p: serde_json::Value| { + s.execute(cmd, &p).unwrap(); + let now = rows(s, mark); + assert_eq!(rows(s, art), art_was, "{cmd}"); + assert_eq!(now.items[0], mark_was.items[0], "{cmd} leaves the mark's fill"); + assert_ne!(now.items[1], mark_was.items[1], "{cmd} edits the mark's stroke"); + s.execute("edit.undo", &json!({})).unwrap(); + assert_eq!(rows(s, mark), mark_was, "{cmd} is one undo step"); + now + }; + let now = stroke(&mut s, "paint.setStroke", json!({"ids": ids, "item": 2, "aligned": true, "color": "#00ff00"})); + assert_eq!(now.items[1].paint(), &green); + let now = stroke(&mut s, "stroke.set", json!({"ids": ids, "item": 2, "aligned": true, "weight": 5})); + assert_eq!(now.stroke_at(Some(1)).unwrap().width, 5.0); + let now = stroke(&mut s, "stroke.setAdvanced", json!({"ids": ids, "item": 2, "aligned": true, "arrowScale": [200, 200]})); + assert_eq!(now.stroke_at(Some(1)).unwrap().arrow_scale, (200.0, 200.0)); + // Without `stroke`, the kind of the art's row is edited. + let now = stroke(&mut s, "paint.editGradient", json!({"ids": ids, "item": 2, "aligned": true, "kind": "linear"})); + assert!(matches!(now.items[1].paint(), Paint::Gradient(_))); + fill(&mut s, "paint.complement", json!({"ids": ids, "item": 1, "aligned": true, "stroke": false})); + + let steps = s.doc().unwrap().history.undo.len(); + for (cmd, p, said) in [ + ("stroke.set", json!({"ids": ids, "item": 1, "aligned": true, "weight": 5}), "appearance item 1 is not a stroke"), + ("paint.setFill", json!({"ids": ids, "item": 2, "aligned": true, "color": "#00ff00"}), "appearance item 2 is not a fill"), + ("paint.setFill", json!({"ids": ids, "item": 0, "aligned": true, "color": "#00ff00"}), "no appearance item 0"), + ("paint.setFill", json!({"ids": ids, "item": 9, "aligned": true, "color": "#00ff00"}), "no appearance item 9"), + // The art alone: no other object has the row. + ("paint.setFill", json!({"ids": [art.0], "item": 9, "aligned": true, "color": "#00ff00"}), "no appearance item 9"), + ("paint.setFill", json!({"ids": [art.0], "item": 1, "aligned": true, "color": "#00ff00"}), "no appearance item 1"), + ("stroke.set", json!({"ids": [art.0], "item": 1, "aligned": true, "weight": 5}), "appearance item 1 is not a stroke"), + ("paint.editGradient", json!({"ids": [art.0], "item": 1, "aligned": true, "kind": "linear"}), "no appearance item 1"), + ] { + let defaults = format!("{:?}", (&s.paint.fill, &s.paint.stroke, &s.last_gradient)); + let e = s.execute(cmd, &p).unwrap_err().to_string(); + assert!(e.contains(said), "{cmd}: {e}"); + assert_eq!((rows(&s, art), rows(&s, mark)), (art_was.clone(), mark_was.clone()), "{cmd}"); + assert_eq!(s.doc().unwrap().history.undo.len(), steps, "{cmd}"); + assert_eq!(format!("{:?}", (&s.paint.fill, &s.paint.stroke, &s.last_gradient)), defaults, "{cmd}"); + } + + // An object with no row equal to the art's is left out of what the commands report as edited. + s.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 10, "height": 10})).unwrap(); + let rect = *s.doc().unwrap().selection.subjects().first().unwrap(); + s.execute("paint.setFill", &json!({"ids": [rect.0], "color": "#ff00ff"})).unwrap(); + s.execute("paint.setStroke", &json!({"ids": [rect.0], "color": "#ff00ff"})).unwrap(); + let rect_was = rows(&s, rect); + let ids = [art.0, mark.0, rect.0]; + for (cmd, p, key, said) in [ + ("paint.setFill", json!({"ids": ids, "item": 1, "aligned": true, "color": "#00ff00"}), "objects", json!(1)), + ("stroke.set", json!({"ids": ids, "item": 2, "aligned": true, "weight": 5}), "objects", json!(1)), + ("paint.editGradient", json!({"ids": ids, "item": 1, "aligned": true, "kind": "linear"}), "ids", json!([mark.0])), + ("paint.setGradientGeom", json!({"ids": ids, "item": 1, "aligned": true, "start": [0, 0], "end": [10, 40]}), "ids", json!([mark.0])), + ] { + if cmd == "paint.setGradientGeom" { + s.execute("paint.editGradient", &json!({"ids": [mark.0], "item": 0, "kind": "linear"})).unwrap(); + let gradient = rows(&s, mark).items[0].clone(); + s.edit("Gradient", |d, _| { + d.node_mut(art).unwrap().appearance.items[1] = gradient.clone(); + Ok(()) + }) + .unwrap(); + } + let r = s.execute(cmd, &p).unwrap(); + assert_eq!(r[key], said, "{cmd}: {r}"); + assert_eq!(rows(&s, rect), rect_was, "{cmd}"); + s.execute("edit.undo", &json!({})).unwrap(); + } + + // A gradient spanning the selection spans only the objects that take it: a distant object without an equal row does + // not move it. + s.execute("shape.rectangle", &json!({"x": 5000, "y": 5000, "width": 10, "height": 10})).unwrap(); + let far = *s.doc().unwrap().selection.subjects().first().unwrap(); + s.execute("paint.setFill", &json!({"ids": [far.0], "color": "#ff00ff"})).unwrap(); + let far_was = rows(&s, far); + let gradient = json!({"stops": [{"offset": 0, "color": "#000000"}, {"offset": 1, "color": "#ffffff"}]}); + let mut placed = Vec::new(); + for ids in [vec![art.0, mark.0], vec![art.0, mark.0, far.0]] { + s.execute("paint.setFill", &json!({"ids": ids, "item": 1, "aligned": true, "gradient": gradient, "span": "selection"})).unwrap(); + placed.push(rows(&s, mark).items[0].clone()); + assert_eq!(rows(&s, far), far_was); + s.execute("edit.undo", &json!({})).unwrap(); + } + assert!(matches!(placed[0].paint(), Paint::Gradient(_))); + assert_eq!(placed[1], placed[0]); +} + +/// The gradient and freeform gradient commands read an `item` given with `aligned: true` and the art of a graph design in +/// a series first as a row of the art's stack, as the paint commands do. +#[test] +fn aligned_gradient_rows_of_a_graph_designs_art_edit_the_equal_rows_of_the_other_objects() { + let rows = |s: &Session, id: NodeId| (*s.doc().unwrap().doc.node(id).unwrap().appearance).clone(); + for kind in ["linear", "freeform"] { + let (mut s, _, art, mark) = aligned_design_art(); + // The mark's fill and the art's equal row become the same gradient. + s.execute("paint.editGradient", &json!({"ids": [mark.0], "item": 0, "kind": kind})).unwrap(); + let gradient = rows(&s, mark).items[0].clone(); + // A yellow fill above the mark's gradient is the mark's row 1, as the gradient is the art's row 1. + let mut yellow = gradient.clone(); + *yellow.paint_mut() = Paint::solid(vectorcraft_color::Color::rgb(1.0, 1.0, 0.0)); + s.edit("Gradient", |d, _| { + d.node_mut(art).unwrap().appearance.items[1] = gradient.clone(); + d.node_mut(mark).unwrap().appearance.items.insert(1, yellow.clone()); + Ok(()) + }) + .unwrap(); + let ids = [art.0, mark.0]; + let edits = if kind == "linear" { + vec![ + ("paint.setGradientGeom", json!({"ids": ids, "item": 1, "aligned": true, "start": [0, 0], "end": [10, 40]})), + ("paint.editGradient", json!({"ids": ids, "item": 1, "aligned": true, "kind": "radial"})), + ] + } else { + vec![("paint.freeform.addPoint", json!({"ids": ids, "item": 1, "aligned": true, "at": [5, 5]}))] + }; + for (cmd, p) in edits { + let (art_was, mark_was) = (rows(&s, art), rows(&s, mark)); + s.execute(cmd, &p).unwrap(); + let now = rows(&s, mark); + assert_eq!(rows(&s, art), art_was, "{cmd}"); + assert_ne!(now.items[0], mark_was.items[0], "{cmd} edits the mark's gradient fill"); + assert_eq!(now.items[1..], mark_was.items[1..], "{cmd}"); + s.execute("edit.undo", &json!({})).unwrap(); + // The edited gradient is the last gradient (paint.setGradientGeom does not set it). + if cmd != "paint.setGradientGeom" { + let Paint::Gradient(g) = now.items[0].paint() else { panic!("{cmd}") }; + assert_eq!(&s.last_gradient, &**g, "{cmd}: the mark's edited gradient is the last gradient"); + } + } + } +} + /// The paint commands leave the art of a graph design in a series out, a copy of the design given by id with an item /// included. #[test] diff --git a/crates/engine/src/cmd/paint.rs b/crates/engine/src/cmd/paint.rs index 02875517f7..b4090cad29 100644 --- a/crates/engine/src/cmd/paint.rs +++ b/crates/engine/src/cmd/paint.rs @@ -538,7 +538,14 @@ pub(crate) fn apply_paint(s: &mut Session, p: &Value, paint: Paint, fill: bool) let item = item_target(s, p, cmd)?.of_kind(s, fill); let selected = item.targets(s, p)?; let ids = super::graph::paint_targets(&s.doc()?.doc, &selected); - let paint = if span { placed_across(&s.doc()?.doc, &ids, paint, p, fill) } else { paint }; + let of = super::appearance::reference_of(s, p, item, cmd)?; + let paint = if span { + // With a design's art, the gradient spans only the objects with a row equal to the art's, which take it. + let spanned: Vec = ids.iter().zip(item.per_target_of(s, of, &ids)).filter_map(|(id, it)| it.map(|_| *id)).collect(); + placed_across(&s.doc()?.doc, &spanned, paint, p, fill) + } else { + paint + }; // The gradient mesh points picked with the Mesh tool or Direct Selection: a solid fill // recolours just those points of their meshes. let mesh_points = if fill && matches!(paint, Paint::Solid { .. }) { picked_mesh_points(s, &ids) } else { vec![] }; @@ -555,10 +562,12 @@ pub(crate) fn apply_paint(s: &mut Session, p: &Value, paint: Paint, fill: bool) // The type objects whose characters took the paint (no row targeted): only those change what a graph's labels keep // for their characters; a row edit is kept with the label's rows. let characters = std::cell::RefCell::new(Vec::new()); - edit_items_then( + // The objects counted: with a design's art, those with a row equal to the art's. + let ids = edit_items_then( s, &ids, item, + of, cmd, label, fill, @@ -1004,26 +1013,34 @@ fn proxy_recolor(s: &mut Session, p: &Value, cmd: &str, label: &str, f: &dyn Fn( return Ok(json!({ "changed": changed as usize })); } let item = item_target(s, p, cmd)?.of_kind(s, !stroke); - let (r, shown_item) = match item { + let (r, shown_at) = match item { ItemTarget::Item { index, .. } => { - let ids = super::appearance::drop_design_art(&s.doc()?.doc, item.targets(s, p)?); + let targets = super::appearance::drop_design_art(&s.doc()?.doc, item.targets(s, p)?); + let of = super::appearance::reference_of(s, p, item, cmd)?; + // With design art first, the row shown is the one edited in the first object that has it. + let shown_at = match of { + Some(_) => super::appearance::first_edited(s, item, of, &targets).and_then(|(id, it)| match it { + ItemTarget::Item { index, .. } => Some((id, Some(index))), + ItemTarget::Top => None, + }), + None => ids.first().map(|id| (*id, Some(index))), + }; let mut changed = 0; - edit_items(s, &ids, item, cmd, label, !stroke, |n, i| { + edit_items(s, &targets, item, of, cmd, label, !stroke, |n, i| { let paint = if stroke { n.appearance.stroke_at_mut(i).map(|l| &mut l.paint) } else { n.appearance.fill_at_mut(i).map(|l| &mut l.paint) }; changed += paint.is_some_and(|paint| super::colorcmds::map_paint(paint, f)) as usize; Ok(()) })?; - (json!({ "changed": changed }), Some(index)) + (json!({ "changed": changed }), shown_at) } // The proxy recolors its paints, including a colorized image's fill, without changing image pixels or pattern tiles. ItemTarget::Top => { let q = json!({"fill": !stroke, "stroke": stroke, "includeImages": false, "includePatterns": false, "ids": ids.iter().map(|i| i.0).collect::>()}); - (super::colorcmds::recolor(s, &q, cmd, label, f)?, None) + (super::colorcmds::recolor(s, &q, cmd, label, f)?, leaf_targets(s, &ids)?.first().map(|id| (*id, None))) } }; - let shown_ids = if shown_item.is_some() { ids } else { leaf_targets(s, &ids)? }; - if let Some(shown) = shown_ids.first().and_then(|id| s.doc().ok()?.doc.node(*id).map(|n| proxy_paint(n, stroke, shown_item))) { + if let Some(shown) = shown_at.and_then(|(id, at)| s.doc().ok()?.doc.node(id).map(|n| proxy_paint(n, stroke, at))) { s.remember_paint(&shown); } Ok(r) diff --git a/crates/engine/src/cmd/stroke.rs b/crates/engine/src/cmd/stroke.rs index 2a0159930d..5ee137b399 100644 --- a/crates/engine/src/cmd/stroke.rs +++ b/crates/engine/src/cmd/stroke.rs @@ -22,7 +22,7 @@ pub fn specs() -> Vec { "Stroke Options", ["Window", "Stroke"], None, - "{weight?: pt, cap?: butt|round|square, join?: miter|round|bevel, miterLimit?, align?: center|inside|outside, dash?: [d,g,…]|null (a 0 dash with a round or projecting cap draws dots or squares; a new pattern keeps the current offset and alignment), dashOffset? (exact dashes only), alignDashes?: bool (true: dashes fitted to corners and path ends, every run between them holding whole periods with a dash centred on each corner and end; false, the default for a new pattern: exact lengths), startArrow?, endArrow?: Arrow|ArrowOpen|LineArrow|Barbed|Concave|DoubleArrow|HalfArrowLeft|HalfArrowRight|Chevron|Feather|Swallowtail|Triangle|TriangleOpen|TriangleReverse|TriangleBar|Kite|Leaf|Drop|Circle|CircleOpen|HalfCircle|Oval|Target|Square|SquareOpen|Tag|TagOpen|Diamond|DiamondOpen|Hexagon|HexagonOpen|Star|Cross|Plus|Bar|DoubleBar|DotOnBar|Slash|DoubleSlash|Bracket|Fork|null|{custom: name} (a custom arrowhead of the arrowheads file, stroke.arrowheads.list)|{Custom: {name, path}} (a custom head with its art, as document.inspect reports one; refused when the path fills no area, parts thinner than a millionth of a head unit counting as none, or has more than 4096 anchors, more than 4096 subpaths or a point more than 64 head units from the tip) (HalfArrowLeft/Right: one barb, left or right of the direction the head points), arrowAlign?: \"extend\" (tip past the end point, default)|\"tip\" (tip on the end point; the stroke is shortened), profile?: \"uniform\"|\"lens\"|\"taperStart\"|\"taperEnd\"|\"pinch\"|\"teardrop\"|\"wave\"|a saved profile's name (stroke.widthProfile.list), widthPoints?, gradientMode? (as document.inspect reports them in strokeOptions, so those read back unchanged; ignored here: a custom profile is set with the Width tool, gradientMode with paint.editGradient), item?: stroke item index|null (omitted: the Appearance panel's active item if it is a stroke, else the top stroke, created when missing), ids?|id?, target?: \"object\"|\"contents\", aligned?: bool (index and item are rows of the first target's stack: each other target edits its equal one, as the Appearance panel does for a mixed selection)} Without a stroke item, type takes weight, cap, join, miterLimit, align (around its glyph outlines; the type stays live) and the dash options as its characters' stroke (every run; text.setRangeStyle `strokeOptions` styles a range), and images and symbol instances (also in groups) are left alone. With nothing selected (and no ids) they set up the next object drawn (appearance.newArt; weight always does). On a path directly in a graph series, the graph also stores a weight it changes on the topmost visible stroke (docs/mcp.md); on a graph label's stroke row, the graph stores the label's topmost visible fill and stroke rows for every label of its kind → {objects: how many objects took the stroke options}", + "{weight?: pt, cap?: butt|round|square, join?: miter|round|bevel, miterLimit?, align?: center|inside|outside, dash?: [d,g,…]|null (a 0 dash with a round or projecting cap draws dots or squares; a new pattern keeps the current offset and alignment), dashOffset? (exact dashes only), alignDashes?: bool (true: dashes fitted to corners and path ends, every run between them holding whole periods with a dash centred on each corner and end; false, the default for a new pattern: exact lengths), startArrow?, endArrow?: Arrow|ArrowOpen|LineArrow|Barbed|Concave|DoubleArrow|HalfArrowLeft|HalfArrowRight|Chevron|Feather|Swallowtail|Triangle|TriangleOpen|TriangleReverse|TriangleBar|Kite|Leaf|Drop|Circle|CircleOpen|HalfCircle|Oval|Target|Square|SquareOpen|Tag|TagOpen|Diamond|DiamondOpen|Hexagon|HexagonOpen|Star|Cross|Plus|Bar|DoubleBar|DotOnBar|Slash|DoubleSlash|Bracket|Fork|null|{custom: name} (a custom arrowhead of the arrowheads file, stroke.arrowheads.list)|{Custom: {name, path}} (a custom head with its art, as document.inspect reports one; refused when the path fills no area, parts thinner than a millionth of a head unit counting as none, or has more than 4096 anchors, more than 4096 subpaths or a point more than 64 head units from the tip) (HalfArrowLeft/Right: one barb, left or right of the direction the head points), arrowAlign?: \"extend\" (tip past the end point, default)|\"tip\" (tip on the end point; the stroke is shortened), profile?: \"uniform\"|\"lens\"|\"taperStart\"|\"taperEnd\"|\"pinch\"|\"teardrop\"|\"wave\"|a saved profile's name (stroke.widthProfile.list), widthPoints?, gradientMode? (as document.inspect reports them in strokeOptions, so those read back unchanged; ignored here: a custom profile is set with the Width tool, gradientMode with paint.editGradient), item?: stroke item index|null (omitted: the Appearance panel's active item if it is a stroke, else the top stroke, created when missing), ids?|id?, target?: \"object\"|\"contents\", aligned?: bool (index and item are rows of the first target's stack: each other target edits its equal one, as the Appearance panel does for a mixed selection; with the art of a graph design in a series given first, rows of the art's stack: docs/mcp.md)} Without a stroke item, type takes weight, cap, join, miterLimit, align (around its glyph outlines; the type stays live) and the dash options as its characters' stroke (every run; text.setRangeStyle `strokeOptions` styles a range), and images and symbol instances (also in groups) are left alone. With nothing selected (and no ids) they set up the next object drawn (appearance.newArt; weight always does). On a path directly in a graph series, the graph also stores a weight it changes on the topmost visible stroke (docs/mcp.md); on a graph label's stroke row, the graph stores the label's topmost visible fill and stroke rows for every label of its kind → {objects: how many objects took the stroke options}", has_doc, stroke_set ), @@ -303,7 +303,9 @@ fn stroke_set(s: &mut Session, p: &Value) -> Result { st.profile = pr.clone(); } }; - edit_items(s, &ids, item, C, "Stroke", false, |n, index| { + let of = super::appearance::reference_of(s, p, item, C)?; + // The objects counted: with a design's art, those with a row equal to the art's. + let ids = edit_items(s, &ids, item, of, C, "Stroke", false, |n, index| { if index.is_none() && let NodeKind::Text(t) = &mut n.kind { @@ -372,7 +374,7 @@ fn stroke_step(s: &mut Session, p: &Value, up: bool) -> Result { } let ids = super::appearance::drop_design_art(&s.doc()?.doc, ids); let label = if up { "Stroke Weight Up" } else { "Stroke Weight Down" }; - edit_items(s, &ids, item, c, label, false, |n, index| { + edit_items(s, &ids, item, None, c, label, false, |n, index| { if index.is_none() && let NodeKind::Text(t) = &mut n.kind { @@ -535,7 +537,8 @@ fn stroke_advanced(s: &mut Session, p: &Value) -> Result { { arrows_and_profile(st); } - edit_items(s, &ids, item, C, "Stroke", false, |n, index| { + let of = super::appearance::reference_of(s, p, item, C)?; + edit_items(s, &ids, item, of, C, "Stroke", false, |n, index| { let Some(st) = n.appearance.stroke_at_mut(index) else { return Ok(()) }; arrows_and_profile(st); if let Some(b) = &brush { diff --git a/docs/mcp.md b/docs/mcp.md index 7b13e9eb68..435e1d9629 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -829,9 +829,13 @@ given), `threeD.mapGraphic`, do not use the Appearance panel's active row. When the first object given is design art, rows and effects given with `aligned: true` are rows of its stack in `appearance.setItem`, `appearance.removeItem`, `appearance.duplicateItem`, `transparency.set` with an `item`, `effect.apply`, `appearance.addEffect` and the other `effect.*` commands that edit, -move or remove an effect: each other object edits the row or effect equal to it, and an object without one is left as -it is. When no object has one, `appearance.setItem` and `appearance.removeItem` leave the document as it is, as they do -for a row no object has, and the other commands are an error. `appearance.moveItem`, `effect.move` and +move or remove an effect, `paint.setFill`, `paint.setStroke`, `paint.invert` and `paint.complement` with an `item`, +`stroke.set`, `stroke.setAdvanced`, `paint.editGradient`, `paint.setGradientGeom` and the `paint.freeform.*` commands +that edit points and lines: each other object edits the row or effect equal to it, and an object without one is left +as it is. When no object has one, `appearance.setItem` and `appearance.removeItem` leave the document as it is, as they +do for a row no object has, and the other commands are an error. The paint, stroke and gradient commands are also an +error when the art's row is not the kind of row they edit, and `paint.editGradient`, `paint.setGradientGeom` and the +`paint.freeform.*` commands without `stroke` edit the kind of the art's row. `appearance.moveItem`, `effect.move` and `appearance.duplicateItem` with `to` are an error when the art's stack differs from the other objects', and `appearance.duplicateItem` with `to` is also an error when two or more other objects are given, as it is without the art. To change the art, edit the design with `graph.design`. From 9578b61d286e34ce7c8cb73da5dfaba9aa69c970 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 14 Nov 2026 09:27:38 -0800 Subject: [PATCH 06/29] Graphs: invert and complement remember the color of the first object they recolor With a graph design's art given first and an item without aligned, the art is left out, but paint.invert and paint.complement remembered the art's unchanged color as the last color. They now read the first object they recolor. --- crates/engine/src/cmd/graph/tests.rs | 6 ++++++ crates/engine/src/cmd/paint.rs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/cmd/graph/tests.rs b/crates/engine/src/cmd/graph/tests.rs index cefadcda76..a48f012e40 100644 --- a/crates/engine/src/cmd/graph/tests.rs +++ b/crates/engine/src/cmd/graph/tests.rs @@ -890,6 +890,9 @@ fn aligned_paint_rows_of_a_graph_designs_art_edit_the_equal_rows_of_the_other_ob assert_eq!(now.items[0].paint(), &green); let now = fill(&mut s, "paint.invert", json!({"ids": ids, "item": 1, "aligned": true})); assert_eq!(Some(s.last_solid), now.items[0].paint().color(), "the inverted color is the last color, not the art's"); + // Without `aligned`, the art is left out and the item is a row of the mark. + let now = fill(&mut s, "paint.invert", json!({"ids": ids, "item": 0})); + assert_eq!(Some(s.last_solid), now.items[0].paint().color(), "the mark's inverted color is the last color, not the art's"); let now = fill(&mut s, "paint.editGradient", json!({"ids": ids, "item": 1, "aligned": true, "kind": "linear"})); let Paint::Gradient(g) = now.items[0].paint() else { panic!("{:?}", now.items[0].paint()) }; assert_eq!(&s.last_gradient, &**g, "the mark's new gradient is the last gradient"); @@ -926,6 +929,9 @@ fn aligned_paint_rows_of_a_graph_designs_art_edit_the_equal_rows_of_the_other_ob ("paint.setFill", json!({"ids": [art.0], "item": 1, "aligned": true, "color": "#00ff00"}), "no appearance item 1"), ("stroke.set", json!({"ids": [art.0], "item": 1, "aligned": true, "weight": 5}), "appearance item 1 is not a stroke"), ("paint.editGradient", json!({"ids": [art.0], "item": 1, "aligned": true, "kind": "linear"}), "no appearance item 1"), + // The weight steps take no row: they step each object's active or top stroke. + ("stroke.weightUp", json!({"ids": ids, "item": 2}), "unknown parameter `item`"), + ("stroke.weightDown", json!({"ids": ids, "aligned": true}), "unknown parameter `aligned`"), ] { let defaults = format!("{:?}", (&s.paint.fill, &s.paint.stroke, &s.last_gradient)); let e = s.execute(cmd, &p).unwrap_err().to_string(); diff --git a/crates/engine/src/cmd/paint.rs b/crates/engine/src/cmd/paint.rs index 91e1efdbe3..6e2071d7f8 100644 --- a/crates/engine/src/cmd/paint.rs +++ b/crates/engine/src/cmd/paint.rs @@ -1025,7 +1025,7 @@ fn proxy_recolor(s: &mut Session, p: &Value, cmd: &str, label: &str, f: &dyn Fn( ItemTarget::Item { index, .. } => Some((id, Some(index))), ItemTarget::Top => None, }), - None => ids.first().map(|id| (*id, Some(index))), + None => targets.first().map(|id| (*id, Some(index))), }; let mut changed = 0; edit_items(s, &targets, item, of, cmd, label, !stroke, |n, i| { From 56cbdbb09c8372d08bd33b978100d97b64304006 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 14 Nov 2026 11:11:20 -0800 Subject: [PATCH 07/29] MCP: SVG output under read roots refuses to name a linked image file outside them With automation read roots in force, an SVG or SVGZ that would name a linked image's file outside them is refused before any file, folder or reply is written, with "a linked image file is outside the read roots", which names no file or folder. An names the file with images: link, and with embed when the document holds no pixels for the image. With preserveEditing, the native document the SVG carries names every linked image and placed file, the relative paths kept of them and each data set's image, so those are checked too, and a stored brush entry that does not read as a brush is refused because its links cannot be checked. - The check runs where the linked JavaScript check runs: document.serialize, document.export, document.exportSelection, document.exportForScreens and assets.export, every SVG and SVGZ save, file.exportAs and the SVG Options dialog's Show Code. It also covers clipboard.exportSvg, and a Copy that an automation request asks for puts no SVG on the system clipboard when it would name such a file. - Only the art each file writes is checked: the selected objects with selectedOnly and Export Selection, the art over each chosen artboard, hidden layers and objects only when hiddenLayers is on (a save keeps them by default), never template layers or the symbol or opacity-mask editing layer, and the symbols, patterns and brushes that art uses. An image colorized with a solid fill whose pixels are gray or missing names no file, so it is not checked outside Preserve Editing. - Embedded pixels, a file inside the roots, a relative path that leads inside them and a URL are written as before, and the person at the keyboard exports every link as stored. Docs: docs/mcp.md, docs/target-app-parity.md and the params text; de and nl rows for the new message, next to the JavaScript message's rows. --- crates/engine/src/cmd/clipboard.rs | 29 +- crates/engine/src/cmd/fileio.rs | 3 +- crates/engine/src/cmd/fileio/access.rs | 43 +++ crates/engine/src/cmd/fileio/commands.rs | 8 +- crates/engine/src/cmd/fileio/export.rs | 8 +- crates/engine/src/cmd/fileio/save.rs | 16 +- crates/engine/src/cmd/fileio/screens.rs | 7 + crates/engine/src/cmd/fileio/svg.rs | 14 + crates/engine/src/cmd/links.rs | 107 ++++++- crates/engine/src/tests/links.rs | 347 ++++++++++++++++++++++ crates/mcp/src/ui_catalog.rs | 2 +- crates/ui-egui/src/dialogs/svg_options.rs | 50 +++- crates/ui-egui/src/i18n/de.tsv | 1 + crates/ui-egui/src/i18n/nl.tsv | 1 + crates/ui-egui/src/io/export.rs | 1 + crates/ui-egui/src/menus/ui_commands.rs | 2 +- crates/ui-egui/src/sysclip.rs | 5 +- crates/ui-egui/src/tests/automation.rs | 40 +++ docs/mcp.md | 15 + docs/target-app-parity.md | 1 + 20 files changed, 678 insertions(+), 22 deletions(-) diff --git a/crates/engine/src/cmd/clipboard.rs b/crates/engine/src/cmd/clipboard.rs index 446634d21c..4e086569ae 100644 --- a/crates/engine/src/cmd/clipboard.rs +++ b/crates/engine/src/cmd/clipboard.rs @@ -32,7 +32,7 @@ use crate::DocState; pub fn specs() -> Vec { let mut v = vec![ - cmd!(query "clipboard.exportSvg", "Clipboard as SVG", [], None, "{} → {svg} the copied objects as standalone SVG (null when the clipboard is empty)", always, export_svg), + cmd!(query "clipboard.exportSvg", "Clipboard as SVG", [], None, "{} → {svg} the copied objects as standalone SVG (null when the clipboard is empty). Under automation read roots it is refused while the SVG would name a linked image's file outside them (an image whose pixels the clipboard doesn't hold; a relative path is followed from the document the objects came from while it is open)", always, export_svg), cmd!( query "clipboard.importSvg", "Load SVG into Clipboard", @@ -435,14 +435,37 @@ impl Clipboard { } impl Session { - /// The internal clipboard as standalone SVG (`None` when it is empty). + /// The internal clipboard as standalone SVG (`None` when it is empty, and when the SVG would name + /// a linked image's file the caller asking now may not be shown: [`Self::clipboard_svg_refused`]). pub fn clipboard_svg(&self) -> Option { + if self.clipboard_svg_refused() { + return None; + } self.clipboard.svg() } + + /// Would the internal clipboard's SVG name a linked image's file outside the read roots in + /// force? It names the file of an image whose pixels the clipboard doesn't hold. A relative + /// path is followed from the document the objects came from while it is open; otherwise it + /// counts as outside. + pub fn clipboard_svg_refused(&self) -> bool { + self.check_clipboard_svg("clipboard.exportSvg").is_err() + } + + /// [`Self::clipboard_svg_refused`] as `cmd`'s error, which names no file or folder. + fn check_clipboard_svg(&self, cmd: &str) -> Result<()> { + if crate::file_access::current().is_none() || !self.clipboard.has_art() { + return Ok(()); + } + let from = self.docs.iter().find(|d| Some(d.uid) == self.clipboard.source_doc); + let base = from.map_or_else(super::links::LinkBase::default, super::links::LinkBase::of); + super::fileio::check_images_in(cmd, "svg", &self.clipboard.to_document(), base, &json!({})) + } } fn export_svg(s: &mut Session, _: &Value) -> Result { - Ok(json!({ "svg": s.clipboard_svg() })) + s.check_clipboard_svg("clipboard.exportSvg")?; + Ok(json!({ "svg": s.clipboard.svg() })) } fn import_svg(s: &mut Session, p: &Value) -> Result { diff --git a/crates/engine/src/cmd/fileio.rs b/crates/engine/src/cmd/fileio.rs index 84fb121d0c..aa811b06b0 100644 --- a/crates/engine/src/cmd/fileio.rs +++ b/crates/engine/src/cmd/fileio.rs @@ -121,9 +121,10 @@ mod commands; mod export_names; mod formats; +pub(crate) use access::check_images_in; #[cfg(not(target_arch = "wasm32"))] pub(crate) use access::list_dir; -pub use access::{check_linked_scripts, write_file_with, write_save_file, write_save_file_with}; +pub use access::{check_linked_images, check_linked_scripts, write_file_with, write_save_file, write_save_file_with}; #[cfg(all(test, not(target_arch = "wasm32")))] mod tests_readonly; diff --git a/crates/engine/src/cmd/fileio/access.rs b/crates/engine/src/cmd/fileio/access.rs index 203a2262b0..cb5b4e85cc 100644 --- a/crates/engine/src/cmd/fileio/access.rs +++ b/crates/engine/src/cmd/fileio/access.rs @@ -139,6 +139,49 @@ pub fn check_linked_scripts(cmd: &str, format: &str, st: &crate::DocState) -> Re Ok(()) } +/// The error for an SVG that would name a linked image's file outside the read roots. +pub(crate) const IMAGE_OUTSIDE: &str = "a linked image file is outside the read roots"; + +/// `cmd` may write `d`, the document an output of `st` writes (all of it, or the objects it +/// exports), in format `format` (a format id) with options `p`: any format but SVG and SVGZ, and +/// those too unless read roots are in force and the file would name a linked image's file outside +/// them (a relative path taken from `st`'s folder; a URL is not checked). An `` names the +/// file with SVG Options › Images set to Link, and with Embed when the document holds no pixels for +/// the image, for the images of the art each file draws (with chosen artboards, the art over each +/// one; hidden layers and objects only with `hiddenLayers`, template layers and the opacity-mask +/// and symbol editing layers never) and of the symbols, patterns and brushes that art uses; with +/// Preserve Editing Capabilities, the native document the file carries names every linked file, the relative paths kept of them +/// and each data set's image. The error names no file or folder. +pub fn check_linked_images(cmd: &str, format: &str, st: &crate::DocState, d: &vectorcraft_doc::Document, p: &Value) -> Result<()> { + check_images_in(cmd, format, d, crate::cmd::links::LinkBase::of(st), p) +} + +/// [`check_linked_images`] for `d`, a document whose links are looked for from `base` (the +/// objects on the clipboard, looked for from the document they came from). +pub(crate) fn check_images_in(cmd: &str, format: &str, d: &vectorcraft_doc::Document, base: crate::cmd::links::LinkBase, p: &Value) -> Result<()> { + if !matches!(format, "svg" | "svgz") || crate::file_access::current().is_none() { + return Ok(()); + } + // Options that don't read as the encoder's choices count as Link, Preserve Editing and hidden + // layers kept: the encoder refuses them anyway. + let (link, preserve, hidden) = match super::svg::options_map(p) { + Ok(m) => { + let on = |k: &str| m.get(k).is_some_and(|v| v.as_bool() != Some(false)); + (m.get("images").is_some_and(|v| v.as_str() != Some("embed")), on("preserveEditing"), on("hiddenLayers")) + } + Err(_) => (true, true, true), + }; + // The opacity-mask and symbol editing layers are never written. + let d = &*d.without_edit_modes(); + let names = |d: &vectorcraft_doc::Document| crate::cmd::links::svg_names_hidden_image(d, base, link, hidden); + if preserve && crate::cmd::links::names_hidden_file(d, base) + || names(d) && super::svg::drawn(d, p).is_none_or(|files| files.iter().any(|f| names(f))) + { + return Err(bad(cmd, IMAGE_OUTSIDE)); + } + Ok(()) +} + /// `path` checked for writing against the automation roots in force. #[cfg(not(target_arch = "wasm32"))] pub(crate) fn may_write(path: &str) -> Result<()> { diff --git a/crates/engine/src/cmd/fileio/commands.rs b/crates/engine/src/cmd/fileio/commands.rs index ca74243699..b1c9cf5e2d 100644 --- a/crates/engine/src/cmd/fileio/commands.rs +++ b/crates/engine/src/cmd/fileio/commands.rs @@ -27,7 +27,7 @@ pub fn specs() -> Vec { "Serialize Document", [], None, - "{format?: vectorcraft (default)|template|svg|svgz|pdf|ai|png|jpg|webp|gif|png8|txt|dxf|eps|emf|wmf|hpgl|tiff|bmp|tga|psd (ait, a .ait template, is written by file.saveAsTemplate), …the format's options (see document.formats; SVG ones also as svg: {…}), selectedOnly?: false (the selected objects alone, in the layers and groups that hold them; an object of a blend, envelope, repeat or intertwine on its own)} → {text, warnings} for svg (plus dataBase64, the file, when its encoding isn't UTF-8), else {dataBase64, warnings}; an SVG of several artboards also gives files: [{name, text}], linked images linked: [{name, dataBase64}]. A key no format takes, and an unknown key inside a PDF or EPS option's object, are refused; a key only another format takes is not used, and warnings name it. Under automation read roots, svg and svgz are refused while the document links a JavaScript file outside them (svgInteractivity; a URL is not checked)", + "{format?: vectorcraft (default)|template|svg|svgz|pdf|ai|png|jpg|webp|gif|png8|txt|dxf|eps|emf|wmf|hpgl|tiff|bmp|tga|psd (ait, a .ait template, is written by file.saveAsTemplate), …the format's options (see document.formats; SVG ones also as svg: {…}), selectedOnly?: false (the selected objects alone, in the layers and groups that hold them; an object of a blend, envelope, repeat or intertwine on its own)} → {text, warnings} for svg (plus dataBase64, the file, when its encoding isn't UTF-8), else {dataBase64, warnings}; an SVG of several artboards also gives files: [{name, text}], linked images linked: [{name, dataBase64}]. A key no format takes, and an unknown key inside a PDF or EPS option's object, are refused; a key only another format takes is not used, and warnings name it. Under automation read roots, svg and svgz are refused while the document links a JavaScript file outside them (svgInteractivity; a URL is not checked), or while the file would name a linked image's file outside them (images: link, preserveEditing, or an image whose pixels the document doesn't hold; a URL is not checked)", has_doc, export::serialize ), @@ -36,7 +36,7 @@ pub fn specs() -> Vec { "Export Document", [], None, - "{path?, format?: svg|svgz|pdf|ai|png|jpg|webp|gif|png8|txt|dxf|eps|emf|wmf|hpgl|tiff|bmp|tga|psd|vectorcraft|template (default: from the path's extension, else png; png8 writes an indexed .png; ait, a .ait template, is written by file.saveAsTemplate; an exporter plug-in's format is plugin., which an extension only it lists picks too), params?: {…} (an exporter plug-in's parameters; missing ones take their defaults, see plugin.info), selectedOnly?: false (the selected objects alone, in the layers and groups that hold them; an object of a blend, envelope, repeat or intertwine on its own), includeArtboardFill?: true (raster, PDF, SVG and EPS draw each exported artboard's background color behind its art, a transparent background too; false leaves it out), artboard?: 0, artboards?: [i…], rect?: [x, y, width, height] (raster formats: that area of the page in points, y down, instead of an artboard; not with artboard, artboards, range or useArtboards), range?: \"1-3, 5\" | \"all\" (1-based; PDF and .ai write one page per artboard, default all; SVG writes one file per artboard, {stem}-{artboard}.svg; raster formats write one artboard), useArtboards?: true (raster: one file per chosen artboard, default all, {stem}-{artboard}.{ext}; pdf/ai: every page) | false (pdf/ai/raster: the bounds of the visible art; pdf/ai: without editing data, with a warning, and ai refuses pdfCompatible: false; SVG has it as an SVG option), ppi?: number (raster formats: pixels per inch, stored in the file; wins over scale; without either, the document's raster effects resolution), scale?: number (pixels per point), background?: transparent|white|black|\"#rrggbb\" (jpg: white when transparent; #rrggbbaa and #rgba are refused here and in matte), antiAlias?: none|art (default)|type (text snapped to pixels), interlaced?: false (png, Adam7), embedIcc?: true (png: an sRGB chunk marks the colours as sRGB), quality?: 90 (jpg: 0–100), colorModel?: rgb|cmyk|gray (jpg), method?: baseline|optimized|progressive, scans?: 3 (3–5, progressive), embedIcc?: true, imageMap?: none|client|server (an HTML or NCSA map of the objects with a URL, written as .html / .map), colors?: 256 (gif/png8: 2–256), reduction?: perceptual|selective (default)|adaptive|web|blackWhite|gray, dither?: none|diffusion (default)|pattern|noise, ditherAmount?: 100, transparency?: true, matte?: white|\"#rrggbb\"|none, interlaced?, lossless?: true (webp: lossy WebP isn't available yet: written lossless, with a warning), encoding?: utf8|utf16 (txt: the stories in stacking order, back to front, a thread once; utf16 with a byte order mark), lineEndings?: lf|crlf, selectionOnly?: false, svg?: {styling, outlineText, images, objectIds, decimals, minify, responsive, useArtboards, preserveEditing, metadata, fewerTspans, hiddenLayers, encoding, profile, embedFonts, variableData} (see document.formats), …the SVG options given flat instead, …the PDF options of document.exportPdf, …the DXF options of document.exportDxf (useArtboards: one drawing per artboard), …the EPS options of document.exportEps (useArtboards: one file per artboard, {stem}_{artboard}.eps, after a central {stem}.eps of the whole document when there are two or more and art shows; else the visible art), …emf and wmf write one picture of the artboard (useArtboards: true one file per chosen artboard, false the bounds of the visible art; EMF keeps curves, clipping, transparent images and gradients as images clipped to their shape; WMF flattens curves into polygons behind a placeable header; what a format leaves out comes back in warnings), …hpgl writes a plot of the artboard as pen moves for plotters and cutters (each visible path once along its line in plotter units; useArtboards: true one file per chosen artboard, false the bounds of the visible art), hpglVersion?: hpgl (default: IN, SP, PU, PD) | hpgl2 (also NP and PC pen colors, each pen's paths as one encoded polyline PE), curveTolerance?: 0.1 (points, 0.01 to 10), pens?: [\"#rrggbb\"…] (pen 1 first, up to 255; each path takes the nearest; default a pen per color in the order the art first draws in it, numbered alike in every file), origin?: bottomLeft (default) | center, sortPaths?: false (each pen's next path the nearest), insideFirst?: false (a path after the pen's paths that lie inside it), colorModel?: rgb|cmyk|gray (tiff: rgb keeps transparency as an alpha channel), lzw?: true, byteOrder?: little|big, embedIcc?: true, colorModel?: rgb|gray (bmp), depth?: 24 (bmp: 1 black and white, 4|8 a palette by reduction and dither, 16, 24, 32 with alpha), fileFormat?: windows|os2 (1, 4, 8 or 24 bits), rle?: false (4 and 8 bits, windows), flipRows?: false (top-down rows), depth?: 24 (tga: 16 one-bit alpha, 24, 32 with alpha), colorModel?: rgb|cmyk|gray (psd), layers?: true (each top-level layer a pixel layer with its opacity and blend mode; a background colour is a Background layer; false: one flat image, on white), maxEditability?: false (layers and sublayers become groups, each object a layer named as in the Layers panel, text by its text; clipping, masked or knockout layers stay one layer), hiddenLayers?: false (hidden layers and objects written hidden instead of left out), embedIcc?: true (at most 30000 pixels a side; formats without alpha are flattened on white)} → {path, format, bytes, warnings, files?: [path…] (several), linked?: [path…] (linked images, image maps)}; no path → {dataBase64, format, bytes, warnings, files?: [{name, dataBase64}], linked?: [{name, dataBase64}]}. A key no format takes, and an unknown key inside a PDF or EPS option's object, are refused; a key only another format takes is not used, and warnings name it. Under automation read roots, svg and svgz are refused while the document links a JavaScript file outside them (svgInteractivity; a URL is not checked). Never changes the document's path", + "{path?, format?: svg|svgz|pdf|ai|png|jpg|webp|gif|png8|txt|dxf|eps|emf|wmf|hpgl|tiff|bmp|tga|psd|vectorcraft|template (default: from the path's extension, else png; png8 writes an indexed .png; ait, a .ait template, is written by file.saveAsTemplate; an exporter plug-in's format is plugin., which an extension only it lists picks too), params?: {…} (an exporter plug-in's parameters; missing ones take their defaults, see plugin.info), selectedOnly?: false (the selected objects alone, in the layers and groups that hold them; an object of a blend, envelope, repeat or intertwine on its own), includeArtboardFill?: true (raster, PDF, SVG and EPS draw each exported artboard's background color behind its art, a transparent background too; false leaves it out), artboard?: 0, artboards?: [i…], rect?: [x, y, width, height] (raster formats: that area of the page in points, y down, instead of an artboard; not with artboard, artboards, range or useArtboards), range?: \"1-3, 5\" | \"all\" (1-based; PDF and .ai write one page per artboard, default all; SVG writes one file per artboard, {stem}-{artboard}.svg; raster formats write one artboard), useArtboards?: true (raster: one file per chosen artboard, default all, {stem}-{artboard}.{ext}; pdf/ai: every page) | false (pdf/ai/raster: the bounds of the visible art; pdf/ai: without editing data, with a warning, and ai refuses pdfCompatible: false; SVG has it as an SVG option), ppi?: number (raster formats: pixels per inch, stored in the file; wins over scale; without either, the document's raster effects resolution), scale?: number (pixels per point), background?: transparent|white|black|\"#rrggbb\" (jpg: white when transparent; #rrggbbaa and #rgba are refused here and in matte), antiAlias?: none|art (default)|type (text snapped to pixels), interlaced?: false (png, Adam7), embedIcc?: true (png: an sRGB chunk marks the colours as sRGB), quality?: 90 (jpg: 0–100), colorModel?: rgb|cmyk|gray (jpg), method?: baseline|optimized|progressive, scans?: 3 (3–5, progressive), embedIcc?: true, imageMap?: none|client|server (an HTML or NCSA map of the objects with a URL, written as .html / .map), colors?: 256 (gif/png8: 2–256), reduction?: perceptual|selective (default)|adaptive|web|blackWhite|gray, dither?: none|diffusion (default)|pattern|noise, ditherAmount?: 100, transparency?: true, matte?: white|\"#rrggbb\"|none, interlaced?, lossless?: true (webp: lossy WebP isn't available yet: written lossless, with a warning), encoding?: utf8|utf16 (txt: the stories in stacking order, back to front, a thread once; utf16 with a byte order mark), lineEndings?: lf|crlf, selectionOnly?: false, svg?: {styling, outlineText, images, objectIds, decimals, minify, responsive, useArtboards, preserveEditing, metadata, fewerTspans, hiddenLayers, encoding, profile, embedFonts, variableData} (see document.formats), …the SVG options given flat instead, …the PDF options of document.exportPdf, …the DXF options of document.exportDxf (useArtboards: one drawing per artboard), …the EPS options of document.exportEps (useArtboards: one file per artboard, {stem}_{artboard}.eps, after a central {stem}.eps of the whole document when there are two or more and art shows; else the visible art), …emf and wmf write one picture of the artboard (useArtboards: true one file per chosen artboard, false the bounds of the visible art; EMF keeps curves, clipping, transparent images and gradients as images clipped to their shape; WMF flattens curves into polygons behind a placeable header; what a format leaves out comes back in warnings), …hpgl writes a plot of the artboard as pen moves for plotters and cutters (each visible path once along its line in plotter units; useArtboards: true one file per chosen artboard, false the bounds of the visible art), hpglVersion?: hpgl (default: IN, SP, PU, PD) | hpgl2 (also NP and PC pen colors, each pen's paths as one encoded polyline PE), curveTolerance?: 0.1 (points, 0.01 to 10), pens?: [\"#rrggbb\"…] (pen 1 first, up to 255; each path takes the nearest; default a pen per color in the order the art first draws in it, numbered alike in every file), origin?: bottomLeft (default) | center, sortPaths?: false (each pen's next path the nearest), insideFirst?: false (a path after the pen's paths that lie inside it), colorModel?: rgb|cmyk|gray (tiff: rgb keeps transparency as an alpha channel), lzw?: true, byteOrder?: little|big, embedIcc?: true, colorModel?: rgb|gray (bmp), depth?: 24 (bmp: 1 black and white, 4|8 a palette by reduction and dither, 16, 24, 32 with alpha), fileFormat?: windows|os2 (1, 4, 8 or 24 bits), rle?: false (4 and 8 bits, windows), flipRows?: false (top-down rows), depth?: 24 (tga: 16 one-bit alpha, 24, 32 with alpha), colorModel?: rgb|cmyk|gray (psd), layers?: true (each top-level layer a pixel layer with its opacity and blend mode; a background colour is a Background layer; false: one flat image, on white), maxEditability?: false (layers and sublayers become groups, each object a layer named as in the Layers panel, text by its text; clipping, masked or knockout layers stay one layer), hiddenLayers?: false (hidden layers and objects written hidden instead of left out), embedIcc?: true (at most 30000 pixels a side; formats without alpha are flattened on white)} → {path, format, bytes, warnings, files?: [path…] (several), linked?: [path…] (linked images, image maps)}; no path → {dataBase64, format, bytes, warnings, files?: [{name, dataBase64}], linked?: [{name, dataBase64}]}. A key no format takes, and an unknown key inside a PDF or EPS option's object, are refused; a key only another format takes is not used, and warnings name it. Under automation read roots, svg and svgz are refused while the document links a JavaScript file outside them (svgInteractivity; a URL is not checked), or while the file would name a linked image's file outside them (images: link, preserveEditing, or an image whose pixels the document doesn't hold; a URL is not checked). Never changes the document's path", has_doc, export::export ), @@ -45,7 +45,7 @@ pub fn specs() -> Vec { "Export Selection…", ["File"], None, - "{path?, format?: png|jpg|webp|svg|svgz|pdf|ai|gif|png8|tiff|bmp|tga|psd|txt|dxf|eps|emf|wmf|hpgl|vectorcraft|template (default: from the extension, else png), ppi?: number (raster formats: pixels per inch, stored in the file; wins over scale; without either, the document's raster effects resolution), scale?: number (pixels per point), …the format's options} the selected objects (template layers left out), each in the layers and groups that hold it, whose clipping paths, opacity masks, effects, opacity and blend modes apply as on the canvas (an object of a blend, envelope, repeat or intertwine on its own; txt: the selected type alone), cropped to the bounds the renderer measures for that art (a clip group by its clip) → {path, format, bytes, warnings, bounds} (no path → {dataBase64, format, bytes, warnings, bounds}); warnings say what the format approximated or left out, as for document.export. A key no format takes, and an unknown key inside a PDF or EPS option's object, are refused; a key only another format takes is not used, and warnings name it. Under automation read roots, svg and svgz are refused while the document links a JavaScript file outside them, as document.export refuses them. In the app, with no params or {}, the save panel picks the file and its format's options dialog follows (Save PDF, SVG Options, the raster options, EPS Options, DXF Options, Text Export Options or an exporter plug-in's parameters; other formats are written at once), whose OK writes the selection: the call returns null while the dialog is open, and OK returns what file.exportAs returns, without format or bounds (Text Export Options: {path})", + "{path?, format?: png|jpg|webp|svg|svgz|pdf|ai|gif|png8|tiff|bmp|tga|psd|txt|dxf|eps|emf|wmf|hpgl|vectorcraft|template (default: from the extension, else png), ppi?: number (raster formats: pixels per inch, stored in the file; wins over scale; without either, the document's raster effects resolution), scale?: number (pixels per point), …the format's options} the selected objects (template layers left out), each in the layers and groups that hold it, whose clipping paths, opacity masks, effects, opacity and blend modes apply as on the canvas (an object of a blend, envelope, repeat or intertwine on its own; txt: the selected type alone), cropped to the bounds the renderer measures for that art (a clip group by its clip) → {path, format, bytes, warnings, bounds} (no path → {dataBase64, format, bytes, warnings, bounds}); warnings say what the format approximated or left out, as for document.export. A key no format takes, and an unknown key inside a PDF or EPS option's object, are refused; a key only another format takes is not used, and warnings name it. Under automation read roots, svg and svgz are refused while the document links a JavaScript file outside them, or while the file would name a linked image's file outside them (an image among the selected objects), as document.export refuses them. In the app, with no params or {}, the save panel picks the file and its format's options dialog follows (Save PDF, SVG Options, the raster options, EPS Options, DXF Options, Text Export Options or an exporter plug-in's parameters; other formats are written at once), whose OK writes the selection: the call returns null while the dialog is open, and OK returns what file.exportAs returns, without format or bounds (Text Export Options: {path})", has_selection, export::export_selection ), @@ -54,7 +54,7 @@ pub fn specs() -> Vec { "Export for Screens", ["File", "Export"], None, - "{folder?, zip?: false (one .zip of every file, each deflated or stored, whichever is smaller: no folder → {name, dataBase64, bytes, files: [name…]}; with a folder it is written there → {path, bytes, files}), artboard?: index | artboards?: [index…] | range?: \"1-3\" (default all), assets?: [asset id…] (instead of artboards: each asset's art alone, cropped to its bounds and named after the asset; see assets.list), layers?: [layer id…] (instead of artboards: each layer's art alone, sublayers included, named after the layer), selection?: one|each (instead of artboards: the selected art as one file, or a file per selected object, named after it), fullDocument?: false (one file per format instead: a PDF of every artboard, other formats the bounds of all visible art, named after the document), includeBleed?: false (artboards grown by the document's bleed), includeArtboardFill?: true (Include Artboard Fill: each artboard's background color behind its art; false leaves it out), pdfs?: multiple|single (Export PDFs As: with several artboards, single writes them as the pages of one PDF per PDF row, named after the document), exportPreset?: (a preset of document.exportPresets.list: its settings, under these params), subfolders?: false (each row's files in a sub-folder: its folder, else its size for raster (1x, 2x, 100w…) or its format (SVG, PDF)), folders?: none|rows (as subfolders)|format (a folder per extension: png, svg, pdf…)|item (a folder per artboard, asset, layer or object)|itemFormat (item/extension) (default: rows with subfolders or a preset, else none), name?: (a file name pattern instead of the item's name, with for the document, for the artboard, asset, layer or object, from 1, #### for the counter zero-padded to the run of #, , , (else the row's suffix ends the name), YYYYMMDD, YYYY-MM-DD, YYYY, ,