Merge pull request #1440 from EricKuck/feat/ai-open-from-editing-data
.ai import: editingData "always" and convert --editing-data open a file from its editing data even where its page draws differently
This commit is contained in:
commit
baf9693f0f
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
**Stage: alpha** · next: beta, ~13 points (ready for real work ~62% → ~75%, and reliable `.ai` exchange) and ~170–270 h away
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (community PRs: `.ai` editing-data follow-ups (gradient stops, type of objects with an appearance, short area-type frames), PDF bleed with editing data, dialog keyboard focus, art flush with an artboard, font rescan, symbol instance redraw, `--control` port check; headline numbers unchanged; Rectangular Grid and Polar Grid tool options, #1377 in part; `.ai` files with placed (linked) pictures open from their editing data, #1264; community PRs: `.ai` and PDF bleed and editing-data import, gradient shapes imported as paths, ruler origin, Align To, Live Corners corner selection; headline numbers unchanged; G2: Film Grain added; Photoshop effects 29/57; 28 filters remain; community PRs: graph designs, Graph Data worksheet and Graph Type options; Align both centres; Pen endpoint fixes; earlier: Effect › Brush Strokes landed: Photoshop-style filters 28 of 57, raster effects 40% → 48%; `.ai` gradients, meshes and artboard names from the editing data; headline numbers unchanged; `.ai` art styles and clipping fills from the editing data; headline numbers unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (community PRs: `.ai` editing-data follow-ups (gradient stops, type of objects with an appearance, short area-type frames), PDF bleed with editing data, dialog keyboard focus, art flush with an artboard, font rescan, symbol instance redraw, `--control` port check; headline numbers unchanged; Rectangular Grid and Polar Grid tool options, #1377 in part; `.ai` files with placed (linked) pictures open from their editing data, #1264; community PRs: `.ai` and PDF bleed and editing-data import, gradient shapes imported as paths, ruler origin, Align To, Live Corners corner selection; headline numbers unchanged; G2: Film Grain added; Photoshop effects 29/57; 28 filters remain; community PRs: graph designs, Graph Data worksheet and Graph Type options; Align both centres; Pen endpoint fixes; earlier: Effect › Brush Strokes landed: Photoshop-style filters 28 of 57, raster effects 40% → 48%; `.ai` gradients, meshes and artboard names from the editing data; headline numbers unchanged; `.ai` art styles and clipping fills from the editing data; headline numbers unchanged; `editingData: "always"`; headline numbers unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
|
||||
VectorCraft is a clean-room, open-source, pure-Rust reimplementation of the Adobe Illustrator workflow. It runs
|
||||
on macOS, Windows, Linux, FreeBSD and the web (WASM), and agents can drive all of it over MCP, the CLI and a JSON
|
||||
|
|
@ -127,6 +127,7 @@ Ranked; detail in [docs/roadmap.md](docs/roadmap.md) and [docs/gaps.md](docs/gap
|
|||
|
||||
| Date | What landed |
|
||||
|---|---|
|
||||
| 2026-10-12 | `convert --editing-data` (`editingData: "always"`) opens a `.ai` from its editing data even where its page, which can be older, draws differently, with a note. |
|
||||
| 2026-10-12 | `.ai` import reads an object's art style (a fill and a stroke, variable widths too, with an Outer Glow or Drop Shadow) as its own appearance, paints a clipping path's own fill under what it clips and keeps a group's transparency as its PDF part draws them. |
|
||||
| 2026-10-12 | `.ai` import reads gradients with their matrix (radials and skew too), focal point and stop order, gradient meshes and the artboards' names from the editing data. |
|
||||
| 2026-10-12 | The Rectangular Grid and Polar Grid tools take their options: a click opens the size, the divider counts with a skew slider each, Use Outside Rectangle As Frame (rectangular) or Create Compound Path From Ellipses (polar) and Fill Grid; the choices stay with the tool, so dragging draws with them, and scripts and agents pass them to `shape.rectangularGrid` and `shape.polarGrid` (#1377, in part). |
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
//! vectorcraft-cli mcp [--connect 127.0.0.1:7979 | --headless] [--automation-read-root DIR] [--automation-write-root DIR]
|
||||
//! vectorcraft-cli run [--in FILE] [--cmd id [--params '{json}']]... [--export out.svg]... [--scale 2]
|
||||
//! vectorcraft-cli commands
|
||||
//! vectorcraft-cli convert IN OUT [--scale 2] [--artboard 0 | --range 1-3,5] [--outline-text] [--decimals N] [--replace-lossy]
|
||||
//! vectorcraft-cli convert IN OUT [--scale 2] [--artboard 0 | --range 1-3,5] [--outline-text] [--decimals N] [--replace-lossy] [--editing-data]
|
||||
//! vectorcraft-cli info FILE
|
||||
//! vectorcraft-cli bench FILE [--size 2880x1800] [--iters 5]
|
||||
//! vectorcraft-cli perf [--paths 50000]
|
||||
|
|
@ -84,7 +84,7 @@ USAGE:
|
|||
vectorcraft-cli commands
|
||||
Print the command catalogue as JSON.
|
||||
|
||||
vectorcraft-cli convert IN OUT [--scale N] [--artboard I | --range R] [--outline-text] [--decimals N] [--replace-lossy]
|
||||
vectorcraft-cli convert IN OUT [--scale N] [--artboard I | --range R] [--outline-text] [--decimals N] [--replace-lossy] [--editing-data]
|
||||
Open IN (any readable format) and export OUT in the format its extension picks (see Writable
|
||||
formats). --artboard is 0-based, --range 1-based (\"1-3,5\"); a PDF gets every artboard
|
||||
unless one of them is given, EPS the bounds of the art, the other formats the first artboard.
|
||||
|
|
@ -92,7 +92,8 @@ USAGE:
|
|||
SVG's data-* attributes; --outline-text writes SVG text as paths, --decimals sets SVG's coordinate
|
||||
precision (1–7, default 3). OUT may not be IN when reading
|
||||
IN left things out (hidden text, art or layers it could not read; the notes say which) unless
|
||||
--replace-lossy is given.
|
||||
--replace-lossy is given. --editing-data opens a .ai from its editing copy even where that
|
||||
draws differently from its PDF part (which can be older), as long as it can be read.
|
||||
|
||||
vectorcraft-cli info FILE
|
||||
Print a JSON summary: the import warnings (what didn't come in as it was, such as an EPS
|
||||
|
|
@ -216,7 +217,8 @@ fn commands() -> Result<(), String> {
|
|||
|
||||
fn convert(args: &[String]) -> Result<(), String> {
|
||||
let mut files = vec![];
|
||||
let (mut scale, mut artboard, mut range, mut outline_text, mut replace_lossy) = (1.0f64, None::<u64>, None::<String>, false, false);
|
||||
let (mut scale, mut artboard, mut range, mut outline_text, mut replace_lossy, mut editing_data) =
|
||||
(1.0f64, None::<u64>, None::<String>, false, false, false);
|
||||
let mut decimals = None::<u64>;
|
||||
let mut it = args.iter();
|
||||
while let Some(a) = it.next() {
|
||||
|
|
@ -227,12 +229,18 @@ fn convert(args: &[String]) -> Result<(), String> {
|
|||
"--outline-text" => outline_text = true,
|
||||
"--decimals" => decimals = Some(it.next().and_then(|v| v.parse::<u64>().ok()).ok_or("--decimals needs a number")?),
|
||||
"--replace-lossy" => replace_lossy = true,
|
||||
"--editing-data" => editing_data = true,
|
||||
f => files.push(f.to_string()),
|
||||
}
|
||||
}
|
||||
let [input, output] = <[String; 2]>::try_from(files).map_err(|_| "convert needs IN and OUT")?;
|
||||
let mut h = Headless::new();
|
||||
h.call("app.open", json!({"path": input})).map_err(|e| format!("open {input}: {e}"))?;
|
||||
if editing_data {
|
||||
let params = json!({"path": input, "editingData": "always"});
|
||||
h.call("engine.execute", json!({"command": "document.open", "params": params})).map_err(|e| format!("open {input}: {e}"))?;
|
||||
} else {
|
||||
h.call("app.open", json!({"path": input})).map_err(|e| format!("open {input}: {e}"))?;
|
||||
}
|
||||
// A conversion keeps hidden layers and objects, written hidden, where the format can (SVG,
|
||||
// PSD).
|
||||
let mut params = json!({"path": output, "scale": scale, "artboard": artboard, "range": range, "outlineText": outline_text, "hiddenLayers": true, "acknowledgeLoss": replace_lossy});
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ fn through_editing_data(bytes: &[u8], opts: &LoadOptions, doc: Document, warning
|
|||
}
|
||||
let Some(private) = vectorcraft_pdf::illustrator_data(bytes, opts.password.as_deref()) else { return (doc, warnings) };
|
||||
let outlined = opts.text_as == vectorcraft_pdf::TextAs::Outlines;
|
||||
let (doc, mut warnings, read) = vectorcraft_eps::layered_ai(&private, doc, warnings, outlined);
|
||||
let (doc, mut warnings, read) = vectorcraft_eps::layered_ai(&private, doc, warnings, outlined, opts.trust_editing_data);
|
||||
// The layers weren't read (or couldn't be): the notes of reading the PDF part still hold.
|
||||
if !read {
|
||||
return (doc, warnings);
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
|||
"Open Document",
|
||||
[],
|
||||
None,
|
||||
"{path} or {name, dataBase64}, PDF/.ai: pages?: \"2-3, 5\" (1-based, default all; one artboard each) | page?: n, cropTo?: bounding|art|crop (default)|trim|bleed|media (the box each artboard gets; bounding: the art's bounds; trim: the bleed box round it becomes the document's bleed; a PDF-compatible .ai or a PDF saved with its editing data opens on its trim boxes for crop), password? (encrypted PDFs; see document.pdfInfo), textAs?: text (default: point type per line, lines wrapped in a frame as area type where they lay out the same, in the file's fonts by name; missing ones are listed in warnings) | outlines (glyph paths), layers?: true (default: optional content groups become layers with their visibility, print state and lock — art that is off comes in as a hidden layer; other art goes to a layer per page) | false (one layer per page, without the art that is off; Save then asks for a name), editingData?: true (default: an EPS, .ai or PDF that carries its editor's own copy of the art opens from it, with its layers, hidden objects, guides, artboards and the art outside them; a .ai or PDF whose type shows opens from its PDF part with textAs: outlines) | false (only what its page or PDF part prints, for print pipelines; a .ai saved without PDF compatibility then can't be opened), colorMode?: rgb|cmyk (the mode the document opens in, its colours converted as file.documentColorMode does, grays? as there; default: the file's — a PDF keeps CMYK, Gray and spot inks (spot swatches at a tint) and opens in CMYK when painted mostly in CMYK), DXF: dxf?: {layout?: \"Model\" (default) | a paper layout's name (document.dxfInfo lists them), unit?, scale? (the ratio: 1 unit of the art = scale drawing units; default: the drawing at 1:1 in its own unit), fit?: false (scale to fit an artboard of fitTo?: [612, 792], turned to the art's orientation), scaleLineweights?: false (lineweights scale with the art), center?: true (false: the drawing's origin on the artboard's bottom-left corner; fitted art's bottom-left corner), mergeLayers?: false (all art on one layer)} → {index, title, format, warnings, restored, missingLinks, modifiedLinks, updatedLinks: [{name, path, ids}]}; any readable format (see document.formats): .vectorcraft/.drawcraft, .svg/.svgz, .pdf/.ai, .ait, ASCII .dxf (each DXF layer with art a layer; blocks symbols; what can't come in is listed in warnings), PNG/JPEG/GIF/WebP/TIFF/BMP (an image opens as a document of its size at the resolution it declares, as file.place sizes it; 72 ppi, 1 px = 1 pt, when it declares none), .emf/.wmf (one artboard, the picture's frame; records VectorCraft doesn't read are skipped with one warning), .eps and PostScript .ai (one artboard, the bounding box: an EPS file VectorCraft wrote restores the document it carries, restored: true; other files are read by a PostScript interpreter — paths, colours and spot inks, clips, gradients, images, type in the fonts named — and come in as their TIFF preview, with a warning, when their PostScript can't be read). A PDF/.ai/.ait or SVG saved with Preserve Editing restores the native document it carries (restored: true; a PDF only when no pages are picked), unless the file was changed elsewhere since or the data can't be read: then its artwork is imported and the first warning says why. Templates (native templates, .ait) open as a new untitled document; a restored .ai keeps its path (Save writes .ai again). Linked images are read from their files (looked for at their path, then relative to the document): missing ones show their saved preview (links.relink), modified ones are read again only with Preferences › Update Links: Automatically (else links.update). An SVG's <image> files (relative links from the SVG's folder) stay linked, SVG files become art, missing ones a placeholder in their box (a warning and missingLinks)",
|
||||
"{path} or {name, dataBase64}, PDF/.ai: pages?: \"2-3, 5\" (1-based, default all; one artboard each) | page?: n, cropTo?: bounding|art|crop (default)|trim|bleed|media (the box each artboard gets; bounding: the art's bounds; trim: the bleed box round it becomes the document's bleed; a PDF-compatible .ai or a PDF saved with its editing data opens on its trim boxes for crop), password? (encrypted PDFs; see document.pdfInfo), textAs?: text (default: point type per line, lines wrapped in a frame as area type where they lay out the same, in the file's fonts by name; missing ones are listed in warnings) | outlines (glyph paths), layers?: true (default: optional content groups become layers with their visibility, print state and lock — art that is off comes in as a hidden layer; other art goes to a layer per page) | false (one layer per page, without the art that is off; Save then asks for a name), editingData?: true (default: an EPS, .ai or PDF that carries its editor's own copy of the art opens from it, with its layers, hidden objects, guides, artboards and the art outside them; a .ai or PDF whose type shows opens from its PDF part with textAs: outlines) | \"always\" (as true, and a .ai's editing copy is used even where it draws differently from its PDF part, which can be older; the warnings say so) | false (only what its page or PDF part prints, for print pipelines; a .ai saved without PDF compatibility then can't be opened), colorMode?: rgb|cmyk (the mode the document opens in, its colours converted as file.documentColorMode does, grays? as there; default: the file's — a PDF keeps CMYK, Gray and spot inks (spot swatches at a tint) and opens in CMYK when painted mostly in CMYK), DXF: dxf?: {layout?: \"Model\" (default) | a paper layout's name (document.dxfInfo lists them), unit?, scale? (the ratio: 1 unit of the art = scale drawing units; default: the drawing at 1:1 in its own unit), fit?: false (scale to fit an artboard of fitTo?: [612, 792], turned to the art's orientation), scaleLineweights?: false (lineweights scale with the art), center?: true (false: the drawing's origin on the artboard's bottom-left corner; fitted art's bottom-left corner), mergeLayers?: false (all art on one layer)} → {index, title, format, warnings, restored, missingLinks, modifiedLinks, updatedLinks: [{name, path, ids}]}; any readable format (see document.formats): .vectorcraft/.drawcraft, .svg/.svgz, .pdf/.ai, .ait, ASCII .dxf (each DXF layer with art a layer; blocks symbols; what can't come in is listed in warnings), PNG/JPEG/GIF/WebP/TIFF/BMP (an image opens as a document of its size at the resolution it declares, as file.place sizes it; 72 ppi, 1 px = 1 pt, when it declares none), .emf/.wmf (one artboard, the picture's frame; records VectorCraft doesn't read are skipped with one warning), .eps and PostScript .ai (one artboard, the bounding box: an EPS file VectorCraft wrote restores the document it carries, restored: true; other files are read by a PostScript interpreter — paths, colours and spot inks, clips, gradients, images, type in the fonts named — and come in as their TIFF preview, with a warning, when their PostScript can't be read). A PDF/.ai/.ait or SVG saved with Preserve Editing restores the native document it carries (restored: true; a PDF only when no pages are picked), unless the file was changed elsewhere since or the data can't be read: then its artwork is imported and the first warning says why. Templates (native templates, .ait) open as a new untitled document; a restored .ai keeps its path (Save writes .ai again). Linked images are read from their files (looked for at their path, then relative to the document): missing ones show their saved preview (links.relink), modified ones are read again only with Preferences › Update Links: Automatically (else links.update). An SVG's <image> files (relative links from the SVG's folder) stay linked, SVG files become art, missing ones a placeholder in their box (a warning and missingLinks)",
|
||||
always,
|
||||
load::open
|
||||
),
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ pub struct LoadOptions {
|
|||
/// layers, hidden objects and the art outside its artboards); else only what its page or PDF
|
||||
/// part prints comes in.
|
||||
pub editing_data: bool,
|
||||
/// A `.ai` opens from its editing copy even where that draws differently from its PDF part
|
||||
/// (which can be older than it), with a warning, instead of from the PDF part.
|
||||
pub trust_editing_data: bool,
|
||||
}
|
||||
|
||||
impl Default for LoadOptions {
|
||||
|
|
@ -50,6 +53,7 @@ impl Default for LoadOptions {
|
|||
layers: true,
|
||||
dxf: Default::default(),
|
||||
editing_data: true,
|
||||
trust_editing_data: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -57,7 +61,7 @@ impl Default for LoadOptions {
|
|||
impl LoadOptions {
|
||||
/// From command params: `pages` (`"2-3, 5"`, a page number or `"all"`), `page` (one page, when
|
||||
/// `pages` isn't given), `cropTo` (or `crop`), `password`, `colorMode` (`rgb` | `cmyk`) with `grays`,
|
||||
/// `textAs` (`text` | `outlines`), `layers` (true | false), `editingData` (true | false) and `dxf`
|
||||
/// `textAs` (`text` | `outlines`), `layers` (true | false), `editingData` (true | false | `always`) and `dxf`
|
||||
/// (the DXF import options).
|
||||
pub fn from_params(cmd: &str, p: &Value) -> Result<Self> {
|
||||
let pages = match p.get("pages").filter(|v| !v.is_null()).or_else(|| p.get("page").filter(|v| !v.is_null())) {
|
||||
|
|
@ -81,12 +85,13 @@ impl LoadOptions {
|
|||
None => true,
|
||||
Some(v) => v.as_bool().ok_or_else(|| bad(cmd, "layers must be true or false"))?,
|
||||
};
|
||||
let editing_data = match p.get("editingData").filter(|v| !v.is_null()) {
|
||||
None => true,
|
||||
Some(v) => v.as_bool().ok_or_else(|| bad(cmd, "editingData must be true or false"))?,
|
||||
let (editing_data, trust_editing_data) = match p.get("editingData").filter(|v| !v.is_null()) {
|
||||
None => (true, false),
|
||||
Some(Value::String(s)) if s.eq_ignore_ascii_case("always") => (true, true),
|
||||
Some(v) => (v.as_bool().ok_or_else(|| bad(cmd, "editingData must be true, false or \"always\""))?, false),
|
||||
};
|
||||
let dxf = super::dxfimport::options(cmd, p.get("dxf"))?;
|
||||
Ok(Self { pages, crop, password, color_mode, grays, text_as, layers, dxf, editing_data })
|
||||
Ok(Self { pages, crop, password, color_mode, grays, text_as, layers, dxf, editing_data, trust_editing_data })
|
||||
}
|
||||
|
||||
/// Does the document read only part of the file, or read it differently (a page range, another
|
||||
|
|
|
|||
|
|
@ -211,7 +211,10 @@ fn editing_data_false_opens_only_what_the_page_prints() {
|
|||
assert_ne!(names(layers), ["Art"], "{name}: the page's layer");
|
||||
}
|
||||
let e = open(&mut s, "art.eps", &ai::eps(&ai::sample_data(), ai::page_ps()), json!({"editingData": "no"})).unwrap_err().to_string();
|
||||
assert!(e.contains("editingData must be true or false"), "{e}");
|
||||
assert!(e.contains("editingData must be true, false or \"always\""), "{e}");
|
||||
// `always`: the editing data, as by default (whether it draws as the page does is the reader's).
|
||||
open(&mut s, "art.ai", &ai::ai(&compressed(&ai::sample_data()), ai::page_pdf()), json!({"editingData": "always"})).unwrap();
|
||||
assert_eq!(names(&s.doc().unwrap().doc.layers), ["Art", "Hidden"]);
|
||||
// A file that is only its editing data can't open without it.
|
||||
let e = open(&mut s, "art.ai", &ai::ai(&compressed(&ai::sample_data()), PLACEHOLDER), json!({"editingData": false})).unwrap_err().to_string();
|
||||
assert!(e.contains("placeholder"), "{e}");
|
||||
|
|
|
|||
|
|
@ -1894,7 +1894,7 @@ proptest! {
|
|||
*x = b;
|
||||
}
|
||||
}
|
||||
survive("mutated .ai editing data", || Some(vectorcraft_eps::layered_ai(&private, Document::new(100.0, 100.0), vec![], false).0))?;
|
||||
survive("mutated .ai editing data", || Some(vectorcraft_eps::layered_ai(&private, Document::new(100.0, 100.0), vec![], false, false).0))?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2146,8 +2146,9 @@ proptest! {
|
|||
fn ai_hostile_editing_data_never_panics(tokens in prop::collection::vec(arb_ai_token(), 0..80)) {
|
||||
use vectorcraft_testkit::ai;
|
||||
let data = ai::editing_data(200.0, 100.0, &format!("%AI5_BeginLayer\n1 1 1 1 0 0 1 0 79 128 255 0 50 0 Lb\n(L) Ln\n{}\nLB\n", tokens.join(" ")));
|
||||
survive("hostile editing data", || Some(vectorcraft_eps::layered_ai(data.as_bytes(), Document::new(200.0, 100.0), vec![], false).0))?;
|
||||
survive("hostile editing data, type as outlines", || Some(vectorcraft_eps::layered_ai(data.as_bytes(), Document::new(200.0, 100.0), vec![], true).0))?;
|
||||
survive("hostile editing data", || Some(vectorcraft_eps::layered_ai(data.as_bytes(), Document::new(200.0, 100.0), vec![], false, false).0))?;
|
||||
survive("hostile editing data, type as outlines", || Some(vectorcraft_eps::layered_ai(data.as_bytes(), Document::new(200.0, 100.0), vec![], true, false).0))?;
|
||||
survive("hostile editing data, trusted", || Some(vectorcraft_eps::layered_ai(data.as_bytes(), Document::new(200.0, 100.0), vec![], false, true).0))?;
|
||||
let eps = ai::eps(data.as_bytes(), ai::page_ps());
|
||||
survive("hostile editing data in an EPS", || vectorcraft_engine::cmd::fileio::load("x.eps", &eps).ok().map(|l| l.doc))?;
|
||||
// An EPS whose page is its art's box, not its artboard: what prints outside it is compared too.
|
||||
|
|
|
|||
|
|
@ -871,7 +871,7 @@ struct Frame<'a> {
|
|||
/// (a `.ai` saved without its PDF part), its type is made from the file's text document alone and
|
||||
/// nothing is compared. `outlined`: the page's type was read as outlines, so it has none to put in
|
||||
/// the text objects.
|
||||
fn build(data: &[u8], visible: Option<&Document>, page: Page, outlined: bool) -> Result<(Document, Vec<String>), String> {
|
||||
fn build(data: &[u8], visible: Option<&Document>, page: Page, outlined: bool, trust: bool) -> Result<(Document, Vec<String>), String> {
|
||||
let Structure { mut doc, to_doc, bbox, artboard, template, hidden_unread, slot_names, warnings } = ai::read(data)?;
|
||||
let (mut shown, mut hidden) = (vec![], vec![]);
|
||||
let mut frame = None;
|
||||
|
|
@ -985,7 +985,11 @@ fn build(data: &[u8], visible: Option<&Document>, page: Page, outlined: bool) ->
|
|||
if unseen > 0 {
|
||||
notes.push(format!("{unseen} {UNSEEN_TEXT}"));
|
||||
}
|
||||
notes.extend(compare(&frame, &mut view, &doc)?);
|
||||
match compare(&frame, &mut view, &doc) {
|
||||
Ok(note) => notes.extend(note),
|
||||
Err(why) if trust => notes.push(format!("{why}: they come from its editing data all the same")),
|
||||
Err(why) => return Err(why),
|
||||
}
|
||||
}
|
||||
None => unmark(&mut doc.layers, true, &mut vec![]),
|
||||
}
|
||||
|
|
@ -1112,7 +1116,7 @@ fn compare(frame: &Frame<'_>, view: &mut PageView, layered: &Document) -> Result
|
|||
/// has one that can't be used, `visible` with a warning that says why.
|
||||
pub(super) fn layered(ps: &[u8], visible: Imported) -> Imported {
|
||||
let Some(data) = editing_copy(ps) else { return visible };
|
||||
let result = data.and_then(|data| build(&data, Some(&visible.document), Page::Art, false));
|
||||
let result = data.and_then(|data| build(&data, Some(&visible.document), Page::Art, false, false));
|
||||
match result {
|
||||
Ok((document, mut warnings)) => {
|
||||
for w in &visible.warnings {
|
||||
|
|
@ -1138,12 +1142,13 @@ pub(super) fn layered(ps: &[u8], visible: Imported) -> Imported {
|
|||
/// The flag returned is `true` when the document is the one read from the layers; else the document
|
||||
/// is `visible`, its pages moved by `place_pages` when the layers can't be read. `outlined`: the
|
||||
/// PDF part's type was read as outlines (a file whose type shows then comes in as its PDF part).
|
||||
pub fn layered_ai(private: &[u8], visible: Document, warnings: Vec<String>, outlined: bool) -> (Document, Vec<String>, bool) {
|
||||
/// `trust`: layers that draw differently from the PDF part are used all the same, with a warning.
|
||||
pub fn layered_ai(private: &[u8], visible: Document, warnings: Vec<String>, outlined: bool, trust: bool) -> (Document, Vec<String>, bool) {
|
||||
let Ok(data) = ai::decode_private(private) else { return (visible, warnings, false) };
|
||||
if !has_layers(&data) {
|
||||
return (visible, warnings, false);
|
||||
}
|
||||
match build(&data, Some(&visible), Page::Artboard, outlined) {
|
||||
match build(&data, Some(&visible), Page::Artboard, outlined, trust) {
|
||||
Ok((mut done, notes)) => {
|
||||
done.setup.bleed = visible.setup.bleed;
|
||||
let mut all = warnings;
|
||||
|
|
@ -1210,7 +1215,7 @@ fn place_art(layer: &mut Node, pages: &[Rect], moves: &[Vec2]) {
|
|||
/// alone (`private`, the joined `AIPrivateData` streams) → the document and the import's notes.
|
||||
pub fn ai_alone(private: &[u8]) -> Result<(Document, Vec<String>), String> {
|
||||
let data = ai::decode_private(private)?;
|
||||
build(&data, None, Page::Artboard, false)
|
||||
build(&data, None, Page::Artboard, false, false)
|
||||
}
|
||||
|
||||
/// Does the editing data (or a program in the legacy format) have layers?
|
||||
|
|
@ -1231,7 +1236,7 @@ fn editing_copy(ps: &[u8]) -> Option<Result<Cow<'_, [u8]>, String>> {
|
|||
/// prolog without including it) from its layers alone (`ps`: its program, which has them) → the
|
||||
/// document and the import's notes. `None` when the layers aren't in the program.
|
||||
pub(super) fn program_alone(ps: &[u8]) -> Option<Result<(Document, Vec<String>), String>> {
|
||||
(!ai::has_eps_data(ps) && has_layers(ps)).then(|| build(ps, None, Page::Art, false))
|
||||
(!ai::has_eps_data(ps) && has_layers(ps)).then(|| build(ps, None, Page::Art, false, false))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@ fn pdf_part(x: f64, y: f64) -> Document {
|
|||
fn an_ai_file_gets_its_layers_and_the_art_outside_its_artboard() {
|
||||
// A square on the artboard, and one above it (the artboard is y 0 to 100 here).
|
||||
let art = native(&(layer("Jig", true, &square(10, 10)) + &layer("Parts", true, &square(30, 110)) + &layer("Old", false, &square(60, 60))));
|
||||
let (d, warnings, read) = crate::layered_ai(&private(&art), pdf_part(10.0, 86.0), vec!["a note".into()], false);
|
||||
let (d, warnings, read) = crate::layered_ai(&private(&art), pdf_part(10.0, 86.0), vec!["a note".into()], false, false);
|
||||
assert_eq!(names(&d), ["Jig", "Parts", "Old"], "{warnings:?}");
|
||||
assert!(read);
|
||||
assert_eq!(warnings, ["a note"], "nothing else to say");
|
||||
|
|
@ -229,12 +229,12 @@ fn an_ai_file_gets_its_layers_and_the_art_outside_its_artboard() {
|
|||
fn an_ai_file_that_cant_be_read_through_its_editing_data_keeps_its_pdf_part() {
|
||||
let pdf = pdf_part(10.0, 86.0);
|
||||
// No editing data this reads.
|
||||
let (d, w, read) = crate::layered_ai(b"nothing", pdf.clone(), vec![], false);
|
||||
let (d, w, read) = crate::layered_ai(b"nothing", pdf.clone(), vec![], false, false);
|
||||
assert_eq!(d.layers.len(), pdf.layers.len());
|
||||
assert!(w.is_empty() && !read);
|
||||
// Editing data that isn't for this page.
|
||||
let other = native(&layer("Jig", true, &square(10, 10))).replace("%AI3_Cropmarks: 0 0 100 100", "%AI3_Cropmarks: 0 0 50 50");
|
||||
let (d, w, _) = crate::layered_ai(&private(&other), pdf.clone(), vec![], false);
|
||||
let (d, w, _) = crate::layered_ai(&private(&other), pdf.clone(), vec![], false, false);
|
||||
assert_eq!(names(&d), names(&pdf));
|
||||
assert!(w.iter().any(|w| w.contains("different page")), "{w:?}");
|
||||
}
|
||||
|
|
@ -268,7 +268,7 @@ fn an_ai_file_read_as_its_pdf_part_keeps_its_artboard_layout() {
|
|||
(page.id, page.rect) = (2, vectorcraft_geom::Rect::new(120.0, 0.0, 220.0, 100.0));
|
||||
pdf.artboards.push(page);
|
||||
let squares = |d: &Document| d.layers[0].children().unwrap().iter().map(|n| n.visual_bounds().unwrap().origin()).collect::<Vec<_>>();
|
||||
let (d, w, read) = crate::layered_ai(&private(&unread(&[(0, 0), (0, -150)])), pdf.clone(), vec![], false);
|
||||
let (d, w, read) = crate::layered_ai(&private(&unread(&[(0, 0), (0, -150)])), pdf.clone(), vec![], false, false);
|
||||
assert!(!read && w.iter().any(|w| w.contains("layers weren't read")), "{w:?}");
|
||||
let rects: Vec<_> = d.artboards.iter().map(|a| a.rect).collect();
|
||||
assert_eq!(rects, [vectorcraft_geom::Rect::new(0.0, 0.0, 100.0, 100.0), vectorcraft_geom::Rect::new(0.0, 150.0, 100.0, 250.0)]);
|
||||
|
|
@ -279,7 +279,7 @@ fn an_ai_file_read_as_its_pdf_part_keeps_its_artboard_layout() {
|
|||
assert!((moved[1] - vectorcraft_geom::Point::new(10.0, 160.0)).hypot() < 1e-6, "the second's goes with its page: {moved:?}");
|
||||
// Artboards that don't match the pages leave them in a row.
|
||||
for list in [&[(0, 0)][..], &[(0, 0), (0, -150), (200, 0)]] {
|
||||
let (d, _, _) = crate::layered_ai(&private(&unread(list)), pdf.clone(), vec![], false);
|
||||
let (d, _, _) = crate::layered_ai(&private(&unread(list)), pdf.clone(), vec![], false, false);
|
||||
assert_eq!(d.artboards[1].rect, pdf.artboards[1].rect, "{list:?}");
|
||||
assert_eq!(d.artboards[1].name, pdf.artboards[1].name, "{list:?}");
|
||||
}
|
||||
|
|
@ -626,7 +626,7 @@ fn type_on_a_later_artboard_of_an_ai_file_goes_to_its_text_object() {
|
|||
let text = vectorcraft_doc::TextObject::point(vectorcraft_geom::Point::new(178.5, 50.0), "Hi there", style);
|
||||
let (id, layer_id) = (pdf.alloc_id(), pdf.layers[0].id);
|
||||
pdf.insert(Some(layer_id), 1, Node::new(id, NodeKind::Text(Box::new(text)))).unwrap();
|
||||
let (d, w, read) = crate::layered_ai(&private(&art), pdf, vec![], false);
|
||||
let (d, w, read) = crate::layered_ai(&private(&art), pdf, vec![], false, false);
|
||||
assert!(read, "{w:?}");
|
||||
assert_eq!(d.artboards[1].rect, vectorcraft_geom::Rect::new(0.0, 150.0, 100.0, 250.0));
|
||||
let texts = text_of(&d.layers[0]);
|
||||
|
|
@ -647,7 +647,7 @@ fn a_global_colour_or_a_pattern_fill_keeps_the_sublayers_of_an_ai_file() {
|
|||
let patterned = "(Dots) 0 0 1 1 0 0 0 0 0 [1 0 0 1 0 0] p\n50 50 m\n70 50 L\n70 70 L\n50 70 L\nf";
|
||||
let ground = layer("Ground", true, &(layer("Lawn", true, yellow) + &layer("Paving", false, patterned)));
|
||||
let art = native(&format!("{dots}{ground}"));
|
||||
let (d, warnings, _) = crate::layered_ai(&private(&art), pdf_part(10.0, 86.0), vec![], false);
|
||||
let (d, warnings, _) = crate::layered_ai(&private(&art), pdf_part(10.0, 86.0), vec![], false, false);
|
||||
assert_eq!(names(&d), ["Ground"], "{warnings:?}");
|
||||
let subs: Vec<_> = d.layers[0].children().unwrap().iter().map(|l| (l.name.clone().unwrap_or_default(), kinds(l))).collect();
|
||||
assert_eq!(subs, [("Lawn".to_string(), vec!["path"]), ("Paving".to_string(), vec!["path"])]);
|
||||
|
|
@ -663,6 +663,20 @@ fn a_global_colour_or_a_pattern_fill_keeps_the_sublayers_of_an_ai_file() {
|
|||
assert!(matches!(&fill, Some(vectorcraft_color::Paint::Pattern { pattern, .. }) if pattern == "Dots"), "{fill:?}");
|
||||
}
|
||||
|
||||
/// A `.ai` whose PDF part is older than its editing copy draws less than its layers: trusted, the
|
||||
/// layers are used anyway, and the difference is noted.
|
||||
#[test]
|
||||
fn a_trusted_editing_copy_is_used_where_it_draws_differently_from_the_pdf_part() {
|
||||
let extra = "0 0 1 0 k\n50 50 m\n70 50 L\n70 70 L\n50 70 L\nf";
|
||||
let art = native(&layer("Back", true, &(square(10, 10) + "\n" + extra)));
|
||||
let (d, w, _) = crate::layered_ai(&private(&art), pdf_part(10.0, 86.0), vec![], false, false);
|
||||
assert_ne!(names(&d), ["Back"], "untrusted, the PDF part is used: {w:?}");
|
||||
let (d, w, _) = crate::layered_ai(&private(&art), pdf_part(10.0, 86.0), vec![], false, true);
|
||||
assert_eq!(names(&d), ["Back"], "{w:?}");
|
||||
assert_eq!(d.layers[0].children().unwrap().len(), 2);
|
||||
assert!(w.iter().any(|w| w.contains("differs from the page's") && w.contains("all the same")), "{w:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_object_the_page_doesnt_draw_makes_the_file_come_in_as_its_page() {
|
||||
// A 20-point square that the page doesn't draw: 4% of the page, and an object all the same.
|
||||
|
|
@ -732,12 +746,12 @@ fn type_asked_for_as_outlines_leaves_a_file_whose_type_shows_as_its_pdf_part() {
|
|||
let doc = text_document(&[("Hi\r", 0, (8200.0, 8180.0), (0.0, 0.0), 0.0)]);
|
||||
// Type that shows: the PDF part's outlines are its type, which the text objects can't hold.
|
||||
let shown = native_with_text(&layer("Art", true, &(square(10, 10) + "\n" + &text_object(0))), &doc);
|
||||
let (d, w, _) = crate::layered_ai(&private(&shown), pdf_part(10.0, 86.0), vec![], true);
|
||||
let (d, w, _) = crate::layered_ai(&private(&shown), pdf_part(10.0, 86.0), vec![], true, false);
|
||||
assert_eq!(names(&d), ["Layer 1"], "{w:?}");
|
||||
assert!(w.iter().any(|w| w.contains("layers weren't read") && w.contains("outlines")), "{w:?}");
|
||||
// Type only on a hidden layer: the layers come in.
|
||||
let hidden = native_with_text(&(layer("Art", true, &square(10, 10)) + &layer("Notes", false, &text_object(0))), &doc);
|
||||
let (d, w, _) = crate::layered_ai(&private(&hidden), pdf_part(10.0, 86.0), vec![], true);
|
||||
let (d, w, _) = crate::layered_ai(&private(&hidden), pdf_part(10.0, 86.0), vec![], true, false);
|
||||
assert_eq!(names(&d), ["Art", "Notes"], "{w:?}");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3576,6 +3576,7 @@ effect Regular Pravidelný
|
|||
@msg its page has text, and its layers have no text objects to put it in jeho stránka obsahuje text, ale vrstvy nemají žádné textové objekty, kam by ho bylo možné umístit
|
||||
@msg its type is to come in as outlines, which only its page has jeho text se má načíst jako křivky, které má jen jeho stránka
|
||||
@msg the art on them differs from the page's in {_1}% of it kresba na nich se od stránky liší v {_1} % plochy
|
||||
@msg the art on them differs from the page's in {_1}% of it: they come from its editing data all the same kresba na nich se od stránky liší v {_1} % plochy: přesto pocházejí z jeho dat pro úpravy
|
||||
@msg `to` must be "corner" or "smooth" `to` musí být "corner" nebo "smooth"
|
||||
@msg BMP files are RGB or grayscale: colorModel rgb or gray soubory BMP jsou RGB nebo ve stupních šedi: colorModel rgb nebo gray
|
||||
@msg Blend colors: select at least three objects with filled colours Prolnout barvy: vyberte alespoň tři objekty s barevnou výplní
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -3290,6 +3290,7 @@ theme Appearance Erscheinungsbild
|
|||
@msg its page has text, and its layers have no text objects to put it in seine Seite enthält Text, und seine Ebenen haben keine Textobjekte, die ihn aufnehmen könnten
|
||||
@msg its type is to come in as outlines, which only its page has sein Text soll als Pfade übernommen werden, die nur seine Seite enthält
|
||||
@msg the art on them differs from the page's in {_1}% of it die Grafik darauf weicht in {_1} % von der der Seite ab
|
||||
@msg the art on them differs from the page's in {_1}% of it: they come from its editing data all the same die Grafik darauf weicht in {_1} % von der der Seite ab: sie stammen trotzdem aus seinen Bearbeitungsdaten
|
||||
@msg `to` must be "corner" or "smooth" `to` muss "corner" oder "smooth" sein
|
||||
@msg BMP files are RGB or grayscale: colorModel rgb or gray BMP-Dateien sind RGB oder Graustufen: colorModel rgb oder gray
|
||||
@msg Blend colors: select at least three objects with filled colours Farben angleichen: Wählen Sie mindestens drei Objekte mit Flächenfarbe aus
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -3390,6 +3390,7 @@ axis Both Ambos
|
|||
@msg its page has text, and its layers have no text objects to put it in su página tiene texto y sus capas no tienen objetos de texto donde ponerlo
|
||||
@msg its type is to come in as outlines, which only its page has su texto debe llegar como contornos, y solo su página los tiene
|
||||
@msg the art on them differs from the page's in {_1}% of it el arte de las capas difiere del de la página en el {_1} % de su superficie
|
||||
@msg the art on them differs from the page's in {_1}% of it: they come from its editing data all the same el arte de las capas difiere del de la página en el {_1} % de su superficie: aun así proceden de sus datos de edición
|
||||
@msg `to` must be "corner" or "smooth" `to` debe ser "corner" o "smooth"
|
||||
@msg BMP files are RGB or grayscale: colorModel rgb or gray los archivos BMP son RGB o en escala de grises: colorModel rgb o gray
|
||||
@msg Blend colors: select at least three objects with filled colours Fusionar colores: selecciona al menos tres objetos con relleno de color
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -3387,6 +3387,7 @@ axis Both Les deux
|
|||
@msg its page has text, and its layers have no text objects to put it in sa page contient du texte, et ses calques n’ont aucun objet texte où le mettre
|
||||
@msg its type is to come in as outlines, which only its page has son texte doit être importé sous forme de contours vectorisés, que seule sa page contient
|
||||
@msg the art on them differs from the page's in {_1}% of it le dessin des calques diffère de celui de la page sur {_1} % de sa surface
|
||||
@msg the art on them differs from the page's in {_1}% of it: they come from its editing data all the same le dessin des calques diffère de celui de la page sur {_1} % de sa surface : ils proviennent malgré tout de ses données de modification
|
||||
@msg `to` must be "corner" or "smooth" `to` doit être "corner" ou "smooth"
|
||||
@msg BMP files are RGB or grayscale: colorModel rgb or gray Les fichiers BMP sont en RVB ou en niveaux de gris : colorModel rgb ou gray
|
||||
@msg Blend colors: select at least three objects with filled colours Fusion des couleurs : sélectionnez au moins trois objets dotés d’un fond de couleur
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -4441,6 +4441,7 @@ theme Appearance Kinézet
|
|||
@msg the art has no bounds a grafikának nincsenek határai
|
||||
@msg the art is too light for any dots a grafika túl világos, egyetlen raszterpont sem keletkezik
|
||||
@msg the art on them differs from the page's in {_1}% of it a rajtuk lévő grafika az oldalétól ennyiben tér el: {_1}%
|
||||
@msg the art on them differs from the page's in {_1}% of it: they come from its editing data all the same a rajtuk lévő grafika az oldalétól ennyiben tér el: {_1}%; ennek ellenére a szerkesztési adataiból származnak
|
||||
@msg the artboard to fit must be a positive size up to {_1} pt az illesztendő rajztábla mérete pozitív, legfeljebb {_1} pt lehet
|
||||
@msg the background worker stopped a háttérfolyamat leállt
|
||||
@msg the compressed file unpacks to more than {_1} MB a tömörített fájl kicsomagolva nagyobb lenne, mint {_1} MB
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -3390,6 +3390,7 @@ axis Both Entrambi
|
|||
@msg its page has text, and its layers have no text objects to put it in la sua pagina ha del testo e i suoi livelli non hanno oggetti di testo in cui metterlo
|
||||
@msg its type is to come in as outlines, which only its page has il suo testo deve arrivare come contorni, che ha solo la sua pagina
|
||||
@msg the art on them differs from the page's in {_1}% of it l'illustrazione dei livelli differisce da quella della pagina per il {_1}% della sua superficie
|
||||
@msg the art on them differs from the page's in {_1}% of it: they come from its editing data all the same l'illustrazione dei livelli differisce da quella della pagina per il {_1}% della sua superficie: provengono comunque dai suoi dati di modifica
|
||||
@msg `to` must be "corner" or "smooth" `to` deve essere "corner" o "smooth"
|
||||
@msg BMP files are RGB or grayscale: colorModel rgb or gray I file BMP sono RGB o in scala di grigi: colorModel rgb o gray
|
||||
@msg Blend colors: select at least three objects with filled colours Fondi colori: seleziona almeno tre oggetti con riempimento colore
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -3705,6 +3705,7 @@ theme Appearance 外観
|
|||
@msg its editing data is for a different page 編集データが別のページのものです
|
||||
@msg its page has text, and its layers have no text objects to put it in ページにテキストがありますが、レイヤーにはそれを入れるテキストオブジェクトがありません
|
||||
@msg the art on them differs from the page's in {_1}% of it レイヤー上のアートの {_1}% がページのアートと異なります
|
||||
@msg the art on them differs from the page's in {_1}% of it: they come from its editing data all the same レイヤー上のアートの {_1}% がページのアートと異なりますが、レイヤーは編集データから読み込まれました
|
||||
@msg its Windows metafile preview was opened instead, which may simplify the art 代わりに Windows メタファイルのプレビューを開いたため、アートが簡略化されている場合があります
|
||||
@msg its preview was placed instead, at screen resolution 代わりにプレビューを画面解像度で配置しました
|
||||
@msg shadings of more than 4096 patches or triangles were left out パッチまたは三角形が 4096 個を超えるシェーディングは除外されました
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -3683,6 +3683,7 @@ theme Appearance 외관
|
|||
@msg its editing data is for a different page 편집 데이터가 다른 페이지용입니다
|
||||
@msg its page has text, and its layers have no text objects to put it in 페이지에 텍스트가 있지만 레이어에는 이를 넣을 텍스트 개체가 없습니다
|
||||
@msg the art on them differs from the page's in {_1}% of it 이들의 아트웍이 페이지와 {_1}% 다릅니다
|
||||
@msg the art on them differs from the page's in {_1}% of it: they come from its editing data all the same 이들의 아트웍이 페이지와 {_1}% 다릅니다. 그래도 편집 데이터에서 가져왔습니다
|
||||
@msg its Windows metafile preview was opened instead, which may simplify the art 대신 Windows 메타파일 미리 보기를 열었으며, 아트웍이 단순화될 수 있습니다
|
||||
@msg its preview was placed instead, at screen resolution 대신 미리 보기를 화면 해상도로 가져왔습니다
|
||||
@msg shadings of more than 4096 patches or triangles were left out 패치나 삼각형이 4096개를 넘는 셰이딩은 제외했습니다
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -3048,6 +3048,7 @@ axis Both Ambos
|
|||
@msg its page has text, and its layers have no text objects to put it in sua página tem texto, e suas camadas não têm objetos de texto para recebê-lo
|
||||
@msg its type is to come in as outlines, which only its page has seu texto deve entrar como contornos, que somente sua página possui
|
||||
@msg the art on them differs from the page's in {_1}% of it a arte nelas difere da página em {_1}% do conteúdo
|
||||
@msg the art on them differs from the page's in {_1}% of it: they come from its editing data all the same a arte nelas difere da página em {_1}% do conteúdo: mesmo assim elas vêm dos seus dados de edição
|
||||
@msg `to` must be "corner" or "smooth" `to` deve ser "corner" ou "smooth"
|
||||
@msg BMP files are RGB or grayscale: colorModel rgb or gray arquivos BMP são RGB ou em tons de cinza: colorModel rgb ou gray
|
||||
@msg Blend colors: select at least three objects with filled colours Mesclar cores: selecione pelo menos três objetos com preenchimento de cor
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
|
|
@ -3386,6 +3386,7 @@ axis Both Оба
|
|||
@msg its page has text, and its layers have no text objects to put it in на его странице есть текст, а на слоях нет текстовых объектов, куда его поместить
|
||||
@msg its type is to come in as outlines, which only its page has его текст должен прийти в виде контуров, а они есть только на его странице
|
||||
@msg the art on them differs from the page's in {_1}% of it изображение на слоях отличается от изображения на странице на {_1} % площади
|
||||
@msg the art on them differs from the page's in {_1}% of it: they come from its editing data all the same изображение на слоях отличается от изображения на странице на {_1} % площади: тем не менее слои взяты из его данных для редактирования
|
||||
@msg `to` must be "corner" or "smooth" `to` должно быть "corner" или "smooth"
|
||||
@msg BMP files are RGB or grayscale: colorModel rgb or gray файлы BMP бывают RGB или в оттенках серого: colorModel rgb или gray
|
||||
@msg Blend colors: select at least three objects with filled colours Смешивание цветов: выберите не менее трёх объектов с цветными заливками
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -3374,6 +3374,7 @@ axis Both Обидві
|
|||
@msg its page has text, and its layers have no text objects to put it in на його сторінці є текст, а на шарах немає текстових об’єктів, куди його помістити
|
||||
@msg its type is to come in as outlines, which only its page has його текст має прийти у вигляді контурів, а вони є лише на його сторінці
|
||||
@msg the art on them differs from the page's in {_1}% of it зображення на шарах відрізняється від зображення на сторінці на {_1} % площі
|
||||
@msg the art on them differs from the page's in {_1}% of it: they come from its editing data all the same зображення на шарах відрізняється від зображення на сторінці на {_1} % площі: проте шари взято з його даних для редагування
|
||||
@msg `to` must be "corner" or "smooth" `to` має бути "corner" або "smooth"
|
||||
@msg BMP files are RGB or grayscale: colorModel rgb or gray Файли BMP бувають RGB або у відтінках сірого: colorModel rgb або gray
|
||||
@msg Blend colors: select at least three objects with filled colours Змішування кольорів: виберіть не менше трьох об’єктів із кольоровими заливками
|
||||
|
|
|
|||
|
Can't render this file because it has a wrong number of fields in line 13.
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue