Package: fonts found by any of their names are copied, and skipped fonts are named
File › Package and Document Info › Font Details looked fonts up with FontDb::face and compared the face's family with the name the document stores. A font a document named by another of its names (a PostScript or localized name, as SVG and PDF files do) was reported as not available and left out of the package, although the canvas drew it and exports embedded it. - Package and Font Details resolve fonts with FontDb::resolve, as text.fonts and the renderer do. A style the family lacks is still copied as its closest style; the report now names the style it copies, and Font Details lists the font as substituted, with that style's file and permission. - FontFace::embeddable reads fsType as exports do (Embedding), so fonts that allow bitmap embedding only (bit 9), or that set the restricted bit together with reserved bit 0 (0x0003), are no longer copied. Document Info's permission labels follow the same rule: when a font sets more than one usage bit, the label names the most permissive one. - The Package dialog and its status line name the fonts it didn't copy.
This commit is contained in:
parent
0a91fd9b44
commit
75f2145b92
|
|
@ -7,6 +7,7 @@ use serde_json::{Value, json};
|
|||
use vectorcraft_color::Paint;
|
||||
use vectorcraft_doc::{ColorMode, Document, Node, NodeId, NodeKind};
|
||||
use vectorcraft_geom::{Affine, Rect};
|
||||
use vectorcraft_text::embed::Embedding;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
|
@ -77,13 +78,17 @@ pub struct Section {
|
|||
pub rows: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
/// What a font's `fsType` lets a copy of it do.
|
||||
/// What a font's `fsType` lets a copy of it do, read as exports read it ([`Embedding`]): the
|
||||
/// most permissive usage bit wins, and bitmap-only fonts may not be embedded.
|
||||
pub fn embedding_label(fs_type: u16) -> &'static str {
|
||||
match fs_type & 0x000f {
|
||||
0x0002 => "embedding not allowed",
|
||||
0x0004 => "embedding for preview and print",
|
||||
0x0008 => "embedding for editing",
|
||||
_ => "embedding allowed",
|
||||
if Embedding::from_fs_type(fs_type) == Embedding::Forbidden {
|
||||
"embedding not allowed"
|
||||
} else if fs_type & 0x0008 != 0 {
|
||||
"embedding for editing"
|
||||
} else if fs_type & 0x0004 != 0 {
|
||||
"embedding for preview and print"
|
||||
} else {
|
||||
"embedding allowed"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -136,13 +141,19 @@ fn sections(d: &Document, info: &Value, fonts: &BTreeSet<(String, String)>, imag
|
|||
fonts
|
||||
.iter()
|
||||
.map(|(family, style)| {
|
||||
let detail = match db.face(family, style) {
|
||||
Some(f) if f.family.eq_ignore_ascii_case(family) => {
|
||||
// Found by any of its names; a style the family lacks shows in another.
|
||||
let detail = match db.resolve(family, style) {
|
||||
Some((f, m)) if m != vectorcraft_text::FontMatch::Missing => {
|
||||
let file =
|
||||
f.path().and_then(|p| p.file_name()).map_or_else(|| "built in".into(), |n| n.to_string_lossy().into_owned());
|
||||
format!("{file}; {}", embedding_label(f.fs_type()))
|
||||
let found = format!("{file}; {}", embedding_label(f.fs_type()));
|
||||
if m == vectorcraft_text::FontMatch::Style {
|
||||
format!("substituted: shown in {} {} ({found})", f.family, f.style)
|
||||
} else {
|
||||
found
|
||||
}
|
||||
}
|
||||
Some(f) => format!("missing: shown in {} {}", f.family, f.style),
|
||||
Some((f, _)) => format!("missing: shown in {} {}", f.family, f.style),
|
||||
None => "missing".into(),
|
||||
};
|
||||
(format!("{family} {style}"), detail)
|
||||
|
|
@ -396,6 +407,12 @@ mod tests {
|
|||
assert_eq!(embedding_label(0x0002), "embedding not allowed");
|
||||
assert_eq!(embedding_label(0x0004 | 0x0100), "embedding for preview and print");
|
||||
assert_eq!(embedding_label(0x0008), "embedding for editing");
|
||||
// As exports read it: bitmap embedding only, the restricted bit with reserved bit 0, the
|
||||
// most permissive of several usage bits.
|
||||
assert_eq!(embedding_label(0x0200), "embedding not allowed");
|
||||
assert_eq!(embedding_label(0x0003), "embedding not allowed");
|
||||
assert_eq!(embedding_label(0x0006), "embedding for preview and print");
|
||||
assert_eq!(embedding_label(0x000c), "embedding for editing");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -109,8 +109,15 @@ fn package(s: &mut Session, p: &Value) -> Result<Value> {
|
|||
lines.push("FONTS".to_string());
|
||||
for (family, style) in super::fonts::used_fonts(&st.doc) {
|
||||
let font = format!("{family} {style}");
|
||||
let reason = match db.face(&family, &style) {
|
||||
Some(f) if f.family.eq_ignore_ascii_case(&family) && f.embeddable() => {
|
||||
// Found by any of its names, as the canvas draws it.
|
||||
let resolved = db.resolve(&family, &style).filter(|(_, m)| *m != vectorcraft_text::FontMatch::Missing);
|
||||
// A style the family lacks is shown in its closest style, whose file is copied.
|
||||
let shown = match &resolved {
|
||||
Some((f, vectorcraft_text::FontMatch::Style)) => format!(" (shown in {} {})", f.family, f.style),
|
||||
_ => String::new(),
|
||||
};
|
||||
let reason = match resolved {
|
||||
Some((f, _)) if f.embeddable() => {
|
||||
let data = f.file_data();
|
||||
let file = f.path().and_then(Path::file_name).map_or_else(
|
||||
|| format!("{}-{}.{}", f.family, f.style, font_ext(data)).replace(|c: char| !(c.is_alphanumeric() || "-_.".contains(c)), ""),
|
||||
|
|
@ -121,13 +128,13 @@ fn package(s: &mut Session, p: &Value) -> Result<Value> {
|
|||
entries.push((format!("Fonts/{file}"), data.to_vec()));
|
||||
fonts += 1;
|
||||
}
|
||||
lines.push(format!("{font} → Fonts/{file}"));
|
||||
lines.push(format!("{font}{shown} → Fonts/{file}"));
|
||||
continue;
|
||||
}
|
||||
Some(f) if f.family.eq_ignore_ascii_case(&family) => "its licence doesn't allow embedding",
|
||||
_ => "not available on this computer",
|
||||
Some(_) => "its licence doesn't allow embedding",
|
||||
None => "not available on this computer",
|
||||
};
|
||||
lines.push(format!("{font}: {reason}, not copied"));
|
||||
lines.push(format!("{font}{shown}: {reason}, not copied"));
|
||||
skipped.push(json!({ "font": font, "reason": reason }));
|
||||
}
|
||||
lines.push(String::new());
|
||||
|
|
|
|||
|
|
@ -157,6 +157,36 @@ fn a_missing_link_is_reported_not_copied() {
|
|||
assert!(report.contains("blue.png: not found"), "{report}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_font_named_by_another_of_its_names_is_copied() {
|
||||
let dir = Folder::new("package-alias");
|
||||
let mut s = poster(&dir);
|
||||
// The bundled Semibold by its PostScript name, as an SVG or a PDF can name it.
|
||||
run(&mut s, "text.create", json!({"x": 10, "y": 80, "text": "Alias", "font": "SourceSans3-Semibold"}));
|
||||
let r = run(&mut s, "file.package", json!({}));
|
||||
assert_eq!((r["fonts"].as_u64(), r["skippedFonts"].clone()), (Some(2), json!([])), "{}", r["skippedFonts"]);
|
||||
let zip = unzip(&vectorcraft_format::base64_decode(r["dataBase64"].as_str().unwrap()).unwrap());
|
||||
let report = String::from_utf8(zip["poster Folder/poster Report.txt"].clone()).unwrap();
|
||||
assert!(report.contains("SourceSans3-Semibold Regular → Fonts/"), "{report}");
|
||||
let details = report.lines().find(|l| l.starts_with("SourceSans3-Semibold Regular: ")).unwrap_or_default();
|
||||
assert!(details.contains("embedding allowed") && !details.contains("missing"), "{report}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_substituted_style_names_the_style_shown() {
|
||||
let dir = Folder::new("package-style");
|
||||
let mut s = poster(&dir);
|
||||
// No Source Sans 3 has a Black Wide, bundled or installed: its closest style stands in, and
|
||||
// that style's file is copied.
|
||||
run(&mut s, "text.create", json!({"x": 10, "y": 80, "text": "Heavy", "font": "Source Sans 3", "style": "Black Wide"}));
|
||||
let r = run(&mut s, "file.package", json!({}));
|
||||
assert_eq!(r["skippedFonts"], json!([]));
|
||||
let zip = unzip(&vectorcraft_format::base64_decode(r["dataBase64"].as_str().unwrap()).unwrap());
|
||||
let report = String::from_utf8(zip["poster Folder/poster Report.txt"].clone()).unwrap();
|
||||
assert!(report.contains("Source Sans 3 Black Wide (shown in Source Sans 3 "), "{report}");
|
||||
assert!(report.contains("Source Sans 3 Black Wide: substituted: shown in Source Sans 3 "), "{report}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_document_info_report_has_every_category() {
|
||||
let dir = Folder::new("docinfo-report");
|
||||
|
|
|
|||
|
|
@ -270,9 +270,10 @@ impl FontFace {
|
|||
use skrifa::raw::TableProvider;
|
||||
self.skrifa().and_then(|f| f.os2().ok()).map_or(0, |t| t.fs_type())
|
||||
}
|
||||
/// May the font file be copied along with a document (its licence doesn't restrict embedding)?
|
||||
/// May the font file be copied along with a document? Its license allows embedding its
|
||||
/// outlines, as exports check it ([`Self::embedding`]).
|
||||
pub fn embeddable(&self) -> bool {
|
||||
self.fs_type() & 0x000f != 0x0002
|
||||
self.embedding() != crate::embed::Embedding::Forbidden
|
||||
}
|
||||
/// The face's index in its font file ([`Self::file_data`]; collections hold several).
|
||||
pub fn face_index(&self) -> u32 {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,42 @@ fn fs_type_flags_decide_the_embedding() {
|
|||
assert_eq!(Embedding::from_fs_type(0x0108), Embedding::Whole, "no subsetting");
|
||||
}
|
||||
|
||||
/// The bundled Source Sans 3 Regular renamed `family` (13 characters, as long as the original
|
||||
/// name) with OS/2 `fsType` set to `fs_type`.
|
||||
fn licensed_font(family: &str, fs_type: u16) -> Vec<u8> {
|
||||
let mut data = include_bytes!("../../../assets/fonts/SourceSans3-Regular.ttf").to_vec();
|
||||
let table = |data: &[u8], tag: &[u8; 4]| {
|
||||
let f = skrifa::FontRef::new(data).unwrap();
|
||||
let r = f.table_directory.table_records().iter().find(|r| r.tag().to_be_bytes() == *tag).unwrap();
|
||||
(r.offset() as usize, r.length() as usize)
|
||||
};
|
||||
let (os2, _) = table(&data, b"OS/2");
|
||||
data[os2 + 8..os2 + 10].copy_from_slice(&fs_type.to_be_bytes());
|
||||
let (name, len) = table(&data, b"name");
|
||||
assert_eq!(family.len(), "Source Sans 3".len());
|
||||
let utf16 = |s: &str| s.encode_utf16().flat_map(u16::to_be_bytes).collect::<Vec<u8>>();
|
||||
for (from, to) in [(utf16("Source Sans 3"), utf16(family)), (b"Source Sans 3".to_vec(), family.as_bytes().to_vec())] {
|
||||
let mut i = name;
|
||||
while let Some(at) = data[i..name + len].windows(from.len()).position(|w| w == from) {
|
||||
data[i + at..i + at + to.len()].copy_from_slice(&to);
|
||||
i += at + to.len();
|
||||
}
|
||||
}
|
||||
data
|
||||
}
|
||||
|
||||
/// A font file goes with a document (File → Package) only when exports may embed it: not when
|
||||
/// its license is restricted or allows bitmaps only.
|
||||
#[test]
|
||||
fn fonts_exports_may_not_embed_are_not_embeddable() {
|
||||
let db = FontDb::with_font_dirs(vec![]);
|
||||
for (family, fs_type, embeddable) in [("Bitmap Sans 3", 0x0200, false), ("Lowbit Sans 3", 0x0003, false), ("Wholed Sans 3", 0x0100, true)] {
|
||||
assert!(db.add_font(licensed_font(family, fs_type)) > 0, "{family}");
|
||||
let face = db.face(family, "Regular").unwrap();
|
||||
assert_eq!((face.family.as_str(), face.embeddable()), (family, embeddable), "fsType {fs_type:#06x}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_subset_holds_only_the_glyphs_used_and_maps_their_characters() {
|
||||
let face = FontDb::global().face("Source Sans 3", "Regular").unwrap();
|
||||
|
|
|
|||
|
|
@ -114,7 +114,12 @@ fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
|
|||
app.ui.dialog = None;
|
||||
let n = r["files"].as_array().map_or(0, Vec::len);
|
||||
let missing = r["missingLinks"].as_array().map_or(0, Vec::len);
|
||||
let note = if missing > 0 { format!(" ({missing} linked file(s) not found)") } else { String::new() };
|
||||
// The fonts left out: their license doesn't allow it, or they aren't on this computer.
|
||||
let fonts = r["skippedFonts"].as_array().into_iter().flatten().filter_map(|f| f["font"].as_str()).collect::<Vec<_>>().join(", ");
|
||||
let mut note = if missing > 0 { format!(" ({missing} linked file(s) not found)") } else { String::new() };
|
||||
if !fonts.is_empty() {
|
||||
note.push_str(&format!(" (fonts not copied: {fonts})"));
|
||||
}
|
||||
if web {
|
||||
let (name, data) = (r["name"].as_str().unwrap_or("package.zip"), r["dataBase64"].as_str().unwrap_or_default());
|
||||
let bytes = vectorcraft_format::base64_decode(data).ok_or("the package came back damaged")?;
|
||||
|
|
@ -124,11 +129,14 @@ fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
|
|||
}
|
||||
let folder = r["folder"].as_str().unwrap_or_default().to_string();
|
||||
app.status(format!("Packaged {n} file(s) in {folder}{note}"));
|
||||
let shown_note = if missing > 0 {
|
||||
let mut shown_note = if missing > 0 {
|
||||
format!(" ({})", crate::i18n::fmt(tl!("{count} linked file(s) not found"), &[("count", &missing.to_string())]))
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
if !fonts.is_empty() {
|
||||
shown_note.push_str(&format!(" ({})", crate::i18n::fmt(tl!("fonts not copied: {fonts}"), &[("fonts", &fonts)])));
|
||||
}
|
||||
let detail = crate::i18n::fmt(
|
||||
tl!("{count} file(s) in {folder}{note}. Show the package folder?"),
|
||||
&[("count", &n.to_string()), ("folder", &folder), ("note", &shown_note)],
|
||||
|
|
|
|||
|
|
@ -106,6 +106,30 @@ fn the_web_downloads_a_zip() {
|
|||
assert!(got[0].1 > 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fonts_left_out_of_the_package_are_named() {
|
||||
let dir = Folder::new("fonts");
|
||||
let mut app = saved(&dir);
|
||||
app.run("text.create", json!({"x": 10, "y": 40, "text": "Kept"})).unwrap();
|
||||
app.run("text.create", json!({"x": 10, "y": 80, "text": "Gone", "font": "No Such Font Family"})).unwrap();
|
||||
app.run("ui.packageDialog", json!({})).unwrap();
|
||||
let out = dir.path("out");
|
||||
std::fs::create_dir_all(&out).unwrap();
|
||||
app.ui.dialog.as_mut().unwrap().fields.insert("folder".into(), json!(out));
|
||||
let r = confirm(&mut app).unwrap();
|
||||
assert_eq!(r["skippedFonts"], json!([{"font": "No Such Font Family Regular", "reason": "not available on this computer"}]));
|
||||
let gone = "fonts not copied: No Such Font Family Regular";
|
||||
assert!(app.ui.status.contains(gone), "{}", app.ui.status);
|
||||
let detail = app.ui.dialog.as_ref().unwrap().str("detail");
|
||||
assert!(detail.contains(gone) && !detail.contains("Source Sans 3"), "{detail}");
|
||||
// The web's download says so too.
|
||||
app.ui.dialog = None;
|
||||
app.services.download = Some(Box::new(|_: &str, _: &[u8]| {}));
|
||||
app.run("ui.packageDialog", json!({})).unwrap();
|
||||
confirm(&mut app).unwrap();
|
||||
assert!(app.ui.status.contains(gone), "{}", app.ui.status);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn package_without_a_location_says_so() {
|
||||
let dir = Folder::new("empty");
|
||||
|
|
|
|||
|
|
@ -2989,6 +2989,7 @@
|
|||
Scrub Numeric Fields by Dragging Ajustar campos numéricos arrastrando
|
||||
{count} file(s) in {folder}{note}. Show the package folder? {count} archivo(s) en {folder}{note}. ¿Mostrar la carpeta del paquete?
|
||||
{count} linked file(s) not found No se encontraron {count} archivo(s) vinculado(s)
|
||||
fonts not copied: {fonts} fuentes no copiadas: {fonts}
|
||||
{count} linked image(s) changed since this document was saved. Show the new versions? {count} imagen(es) vinculada(s) cambiaron desde que se guardó este documento. ¿Mostrar las versiones nuevas?
|
||||
{count} more rules: Export writes every one. {count} reglas más: Exportar las escribe todas.
|
||||
{count} unnamed objects left out: name them, or turn on Unnamed Objects in the panel menu. Se omitieron {count} objetos sin nombre: ponles nombre o activa Objetos sin nombre en el menú del panel.
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -2986,6 +2986,7 @@
|
|||
Scrub Numeric Fields by Dragging Modifier les champs numériques en faisant glisser
|
||||
{count} file(s) in {folder}{note}. Show the package folder? {count} fichier(s) dans {folder}{note}. Afficher le dossier de l’assemblage ?
|
||||
{count} linked file(s) not found {count} fichier(s) lié(s) introuvable(s)
|
||||
fonts not copied: {fonts} polices non copiées : {fonts}
|
||||
{count} linked image(s) changed since this document was saved. Show the new versions? {count} image(s) liée(s) modifiée(s) depuis l’enregistrement de ce document. Afficher les nouvelles versions ?
|
||||
{count} more rules: Export writes every one. {count} règles de plus : Exporter les écrit toutes.
|
||||
{count} unnamed objects left out: name them, or turn on Unnamed Objects in the panel menu. {count} objets sans nom omis : nommez-les ou activez Objets sans nom dans le menu du panneau.
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -2989,6 +2989,7 @@
|
|||
Scrub Numeric Fields by Dragging Regola i campi numerici trascinando
|
||||
{count} file(s) in {folder}{note}. Show the package folder? {count} file in {folder}{note}. Vuoi mostrare la cartella del pacchetto?
|
||||
{count} linked file(s) not found File collegati non trovati: {count}
|
||||
fonts not copied: {fonts} font non copiati: {fonts}
|
||||
{count} linked image(s) changed since this document was saved. Show the new versions? Immagini collegate modificate dopo il salvataggio di questo documento: {count}. Vuoi mostrare le nuove versioni?
|
||||
{count} more rules: Export writes every one. Altre {count} regole: Esporta le scrive tutte.
|
||||
{count} unnamed objects left out: name them, or turn on Unnamed Objects in the panel menu. {count} oggetti senza nome esclusi: assegna loro un nome o attiva Oggetti senza nome nel menu del pannello.
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -2666,6 +2666,7 @@
|
|||
to →
|
||||
{count} file(s) in {folder}{note}. Show the package folder? {folder} に {count} 個のファイル{note}。パッケージフォルダーを表示しますか?
|
||||
{count} linked file(s) not found リンクファイルが {count} 個見つかりません
|
||||
fonts not copied: {fonts} コピーされなかったフォント:{fonts}
|
||||
{count} linked image(s) changed since this document was saved. Show the new versions? 保存後に {count} 個のリンク画像が変更されています。新しいバージョンを表示しますか?
|
||||
{count} more rules: Export writes every one. ほかに {count} 個のルールがあります。書き出しにはすべて含まれます。
|
||||
{count} unnamed objects left out: name them, or turn on Unnamed Objects in the panel menu. 名前のないオブジェクト {count} 個を除外しました。名前を付けるか、パネルメニューで「名前のないオブジェクト」をオンにしてください。
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -2590,6 +2590,7 @@
|
|||
to para
|
||||
{count} file(s) in {folder}{note}. Show the package folder? {count} arquivo(s) em {folder}{note}. Mostrar a pasta do pacote?
|
||||
{count} linked file(s) not found {count} arquivo(s) vinculado(s) não encontrado(s)
|
||||
fonts not copied: {fonts} fontes não copiadas: {fonts}
|
||||
{count} linked image(s) changed since this document was saved. Show the new versions? {count} imagem(ns) vinculada(s) alterada(s) desde que este documento foi salvo. Mostrar as novas versões?
|
||||
{count} more rules: Export writes every one. {count} regras a mais: a Exportação escreve cada uma.
|
||||
{count} unnamed objects left out: name them, or turn on Unnamed Objects in the panel menu. {count} objetos sem nome deixados de fora: nomeie-os, ou ative Objetos Sem Nome no menu do painel.
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
|
|
@ -2985,6 +2985,7 @@
|
|||
Scrub Numeric Fields by Dragging Изменять числовые поля перетаскиванием
|
||||
{count} file(s) in {folder}{note}. Show the package folder? Файлов: {count} в {folder}{note}. Показать папку пакета?
|
||||
{count} linked file(s) not found Не найдено связанных файлов: {count}
|
||||
fonts not copied: {fonts} шрифты не скопированы: {fonts}
|
||||
{count} linked image(s) changed since this document was saved. Show the new versions? Связанных изображений ({count}) изменились с момента сохранения этого документа. Показать новые версии?
|
||||
{count} more rules: Export writes every one. Ещё правил: {count}: экспорт записывает все.
|
||||
{count} unnamed objects left out: name them, or turn on Unnamed Objects in the panel menu. Пропущено объектов без имени: {count}: назовите их или включите «Объекты без имени» в меню панели.
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -2981,6 +2981,7 @@
|
|||
Scrub Numeric Fields by Dragging Змінювати числові поля перетягуванням
|
||||
{count} file(s) in {folder}{note}. Show the package folder? файлів: {count} {folder}{note}. Показати папку пакета?
|
||||
{count} linked file(s) not found не знайдено пов’язаних файлів: {count}
|
||||
fonts not copied: {fonts} шрифти не скопійовано: {fonts}
|
||||
{count} linked image(s) changed since this document was saved. Show the new versions? пов’язані зображення ({count}) змінилися з моменту збереження цього документа. Показати нові версії?
|
||||
{count} more rules: Export writes every one. ще правил: {count}: експорт записує все.
|
||||
{count} unnamed objects left out: name them, or turn on Unnamed Objects in the panel menu. пропущено об’єкти без імені: {count}: назвіть їх або увімкніть «Об’єкти без імені» у меню панелі.
|
||||
|
|
|
|||
|
Can't render this file because it has a wrong number of fields in line 14.
|
|
|
@ -2909,6 +2909,7 @@
|
|||
Scrub Numeric Fields by Dragging 拖动以调整数值字段
|
||||
{count} file(s) in {folder}{note}. Show the package folder? {folder} 中有 {count} 个文件{note}。要显示打包文件夹吗?
|
||||
{count} linked file(s) not found 找不到 {count} 个链接文件
|
||||
fonts not copied: {fonts} 未复制的字体:{fonts}
|
||||
{count} linked image(s) changed since this document was saved. Show the new versions? 自存储此文档后,有 {count} 个链接图像已更改。要显示新版本吗?
|
||||
{count} more rules: Export writes every one. 另有 {count} 条规则:导出时会全部写出。
|
||||
{count} unnamed objects left out: name them, or turn on Unnamed Objects in the panel menu. 已略过 {count} 个未命名对象:请为它们命名,或在面板菜单中开启“未命名对象”。
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -2997,6 +2997,7 @@
|
|||
Scrub Numeric Fields by Dragging 拖曳以調整數值欄位
|
||||
{count} file(s) in {folder}{note}. Show the package folder? {folder} 中有 {count} 個檔案{note}。要顯示封裝資料夾嗎?
|
||||
{count} linked file(s) not found 找不到 {count} 個連結檔案
|
||||
fonts not copied: {fonts} 未複製的字體:{fonts}
|
||||
{count} linked image(s) changed since this document was saved. Show the new versions? 自儲存此文件後,有 {count} 個連結影像已變更。要顯示新版本嗎?
|
||||
{count} more rules: Export writes every one. 另有 {count} 條規則:匯出時會全部寫出。
|
||||
{count} unnamed objects left out: name them, or turn on Unnamed Objects in the panel menu. 已略過 {count} 個未命名物件:請為它們命名,或在面板選單中開啟「未命名物件」。
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
Loading…
Reference in New Issue