Merge pull request #5143 from dotCooCoo/fix/ai-os7

.ai import: images past 2^25 pixels in the editing data keep the layers
This commit is contained in:
BFlat
2026-11-09 04:29:23 +08:00
committed by GitHub
40 changed files with 591 additions and 82 deletions
@@ -720,7 +720,7 @@ fn a_file_whose_pdf_part_cant_be_read_outlines_its_type_when_asked() {
assert!(paths > 0, "the outlines are there: {r}");
}
/// An image too large to read from the editing data of a file whose PDF part can't be read is
/// An image whose data is cut short in the editing data of a file whose PDF part can't be read is
/// left out, and both notes say so.
#[test]
fn a_file_whose_pdf_part_cant_be_read_notes_an_image_left_out() {
@@ -729,7 +729,7 @@ fn a_file_whose_pdf_part_cant_be_read_notes_an_image_left_out() {
let mut s = Session::new();
let e = open(&mut s, name, &file, json!({"editingData": false})).unwrap_err().to_string();
let r = open(&mut s, name, &file, json!({})).unwrap();
assert_eq!(r["warnings"], json!([unread_pdf_part(&e), "an image too large to read from the editing data was left out"]), "{r}");
assert_eq!(r["warnings"], json!([unread_pdf_part(&e), "an image whose data is cut short was left out"]), "{r}");
}
/// A copied file whose PDF part can't be read pastes the objects of its editing data when asked
+10
View File
@@ -4819,6 +4819,16 @@ fn arb_ai_token() -> impl Strategy<Value = String> {
"\n%AI5_BeginRaster\n",
"\n%AI5_EndRaster\n",
"\n%%BeginData: 12\rXI\n",
// Image operands past 2^25 pixels, of odd 1-bit rows, and past the hard limits: as
// many pixels as a u32 side allows, more samples than the bytes allowed, sizes that
// overflow when multiplied out, and a matrix that maps the box to nothing.
"[1 0 0 1 0 0] 0 0 6000 6000 6000 6000 1 1 0 0 1 0 4 4 0 0 ",
"[1 0 0 1 0 0] 0 0 17 13 17 13 1 1 0 0 1 0 4 4 0 0 ",
"[1 0 0 1 0 0] 0 0 9 9 65536 32769 1 1 0 0 1 0 4 4 0 0 ",
"[1 0 0 1 0 0] 0 0 9 9 23171 23171 8 4 1 0 1 0 4 4 0 0 ",
"[1 0 0 1 0 0] 0 0 9 9 4294967295 4294967295 8 4 1 0 1 0 4 4 0 0 ",
"[1 0 0 1 0 0] 0 0 9 9 1e308 1e308 8 3 1 0 0 0 4 4 0 0 ",
"[0 0 0 0 0 0] 0 0 9 9 46341 46341 1 1 0 0 1 0 4 4 0 0 ",
"\n%%EndData\n",
"\n%AI5_BeginGradient: (G)\n",
"\n%AI14_BeginSymbol\n",
+5 -3
View File
@@ -40,11 +40,13 @@ pub(crate) use envelope::MAX_CHECKED;
pub(crate) use envelope::{WARPED_TYPE_DRAWN, WarpedType};
pub(crate) use frames::text_frames;
pub(crate) use paint::MAX_STOPS as MAX_GRADIENT_STOPS;
#[cfg(test)]
pub(crate) use read::with_image_limits;
pub use read::{
BindingRef, Brushes, IMAGE_TOO_LARGE, PaletteSwatches, PlacedPage, Structure, active_artboard, artboards, bindings_note, brushes, graphic_styles,
palette, read, read_beside, variables,
BindingRef, Brushes, PaletteSwatches, PlacedPage, Structure, active_artboard, artboards, bindings_note, brushes, graphic_styles, palette, read,
read_beside, variables,
};
pub(crate) use read::{MAX_SYMBOLS, join_and, read_art_style, slot_frame, slot_of, symbol_art_member};
pub(crate) use read::{MAX_SYMBOLS, join_and, read_art_style, read_over_page, slot_frame, slot_of, symbol_art_member};
pub(crate) use read::is_look_slot;
+25 -4
View File
@@ -34,7 +34,9 @@
//! - Images: `%AI5_BeginRaster`, the colour space `XN`, `[matrix] bounds w h bits type alpha …`
//! and the samples after `XI`. 8-bit CMYK samples keep their ink amounts in a CMYK TIFF, and
//! an alpha channel that hides part of such an image becomes its opacity mask; other samples
//! become an RGBA PNG.
//! become an RGBA PNG. Images larger than 2^25 pixels keep their samples' channels (1-bit gray as
//! a 1-bit PNG), and are averaged down past a target size ([`raster`]); past the hard limits, one
//! is a box that the page's art replaces when there is a page ([`read_over_page`]).
//! - Linked images in the version 7 form (§8.2, §8.3): `(path) modified XG`, then `XF` with the
//! operands `XI` takes and no samples. Each is a linked image of that file showing a placeholder
//! over the image's place until its file is found. An `XF` without a file name before it, or
@@ -134,6 +136,7 @@ mod document;
mod masks;
mod on_path;
mod ops;
mod raster;
mod types;
pub(crate) use art::join_and;
@@ -143,10 +146,12 @@ pub use document::{
};
pub(crate) use document::{slot_frame, slot_of};
pub(crate) use ops::read_art_style;
#[cfg(test)]
pub(crate) use raster::with_image_limits;
pub use types::PlacedPage;
use types::{BLEND_BUDGET, BrushArtColors, FLIP, Frame, GState, Graph, Kind, PatternHead, PendingEnvelope, Place, PlacedTail, Raster, SymbolHead};
#[cfg(test)]
pub use types::{GRAPH_PLAIN, GRAPH_UNENDED, LINKED_PLACEHOLDERS, NON_NATIVE_ART, NON_NATIVE_ART_UNREAD, VERSIONED_UNENDED};
pub use types::{IMAGE_TOO_LARGE, PlacedPage};
pub(super) use types::{blend_mode, blend_name};
#[path = "legacy_text.rs"]
@@ -235,19 +240,32 @@ pub(super) fn remember_xml_id(xml_ids: &mut HashMap<String, Option<NodeId>>, byt
/// when `large_canvas` is set: an EPS's page is drawn at the full size, but a `.ai`'s PDF part may
/// keep the reduced one, which the PDF import reads as it is (#2031).
pub fn read(data: &[u8], large_canvas: bool) -> Result<Structure, String> {
read_with(data, large_canvas, false)
}
/// [`read`] for a file whose page art replaces the objects the reader doesn't read (see
/// [`Reader::unread_box`]): an image past the hard limits ([`raster::Limits`]) is then such an
/// object, where [`read`] leaves it out with a note.
pub(crate) fn read_over_page(data: &[u8], large_canvas: bool) -> Result<Structure, String> {
read_with(data, large_canvas, true)
}
fn read_with(data: &[u8], large_canvas: bool, page_art: bool) -> Result<Structure, String> {
let mut r = Reader::with_names(data);
r.large_canvas = large_canvas.then_some(1.0);
r.page_art = page_art;
r.run()?;
r.finish()
}
/// [`read`] for editing data kept apart from the program `ps` it came with (an EPS's private
/// data): the program's header gives the author and the custom colors the editing data's header
/// doesn't.
pub fn read_beside(data: &[u8], large_canvas: bool, ps: &[u8]) -> Result<Structure, String> {
/// doesn't. `page_art`: as [`read_over_page`] reads a file.
pub fn read_beside(data: &[u8], large_canvas: bool, ps: &[u8], page_art: bool) -> Result<Structure, String> {
let header = Reader::header_of(ps);
let mut r = Reader::with_names(data);
r.large_canvas = large_canvas.then_some(1.0);
r.page_art = page_art;
// The program's custom colors have swatches of their names, which no gradient or pattern takes.
for n in &header.custom_colors {
r.global_names.reserve(&n.name);
@@ -587,6 +605,8 @@ struct Reader<'a> {
/// (see [`Reader::ensure_space`]); 1 (no scaling) when the file doesn't declare one, `None`
/// when the declared factor is not applied ([`read`]).
large_canvas: Option<f64>,
/// The file has page art to replace the objects that aren't read ([`read_over_page`]).
page_art: bool,
/// The declared `%AI24_LargeCanvasScale` (1 without one), applied or not.
canvas_scale: f64,
/// `%AI5_RulerUnits` from the header (Illustrator 7 spec table 3, plus centimeters and pixels).
@@ -756,6 +776,7 @@ impl<'a> Reader<'a> {
art_size: None,
template_center: None,
large_canvas: None,
page_art: false,
canvas_scale: 1.0,
ruler_units: None,
pattern: None,
+117 -26
View File
@@ -1,8 +1,9 @@
use super::containers::close;
use super::document::TEXT_SLOT;
use super::raster::{self, Limits, Shape};
use super::types::{
FLIP, Frame, IMAGE_TOO_LARGE, Kind, LINKED_PLACEHOLDERS, LINKED_PREVIEWS, MAX_PIXELS, MAX_PLACED_ELSEWHERE, MAX_POINTS, MAX_SIDE, NON_NATIVE_ART,
NON_NATIVE_ART_UNREAD, PLACED_CROP, PLACED_PAGE, Place, PlacedPage, PlacedTail,
FLIP, Frame, IMAGE_TOO_LARGE, Kind, LARGE_IMAGES, LINKED_PLACEHOLDERS, LINKED_PREVIEWS, MAX_PIXELS, MAX_PLACED_ELSEWHERE, MAX_POINTS, MAX_SIDE,
NON_NATIVE_ART, NON_NATIVE_ART_UNREAD, PLACED_CROP, PLACED_PAGE, Place, PlacedPage, PlacedTail,
};
use super::{
Affine, AnchorKind, Appearance, AppearanceItem, Arc, BezPath, CANVAS_CENTRE, FillLayer, ImageBlob, ImageObject, Inks, Instance, LinkInfo, Node,
@@ -377,7 +378,7 @@ impl<'a> Reader<'a> {
Some(before) if !vals.iter().any(|v| matches!(v, V::Arr(_))) => before,
_ => vals,
};
self.image_from(vals, data)
self.image_from(vals, data, false)
}
/// The matrix and the numbers of an image's operands (`[a b c d tx ty] llx lly urx ury w h bits
@@ -411,8 +412,22 @@ impl<'a> Reader<'a> {
}
}
/// An image of operands `vals` and samples `data`.
pub(super) fn image_from(&mut self, vals: Vec<V>, data: &[u8]) -> Result<(), String> {
/// The bytes of samples (colour and alpha) that the image of `XI` operands `vals` reads: 0 when
/// it isn't read (operands missing, or past the hard limits).
pub(super) fn sample_bytes(&self, vals: &[V]) -> usize {
let space = self.raster.as_ref().and_then(|r| r.space.clone()).unwrap_or_default();
let n = Self::nums(vals);
let [_, _, _, _, w, h, bits, kind, alpha, ..] = n.as_slice() else { return 0 };
let shape = Shape { w: *w as u64, h: *h as u64, channels: channels(&space, *kind) as u64, bits: *bits as u64, alpha: *alpha > 0.0 };
match (shape.step(&Limits::get()), shape.lens()) {
(Some(_), Some((colour, alpha))) => usize::try_from(colour + alpha).unwrap_or(0),
_ => 0,
}
}
/// An image of operands `vals` and samples `data` (`decoded`: bytes already, whatever the
/// operands say).
pub(super) fn image_from(&mut self, vals: Vec<V>, data: &[u8], decoded: bool) -> Result<(), String> {
// A file `XG` named before this image isn't the file of a later `XF`.
self.image_link = None;
let space = self.raster.as_ref().and_then(|r| r.space.clone()).unwrap_or_default();
@@ -427,41 +442,42 @@ impl<'a> Reader<'a> {
// ImageMask 1: the image is transparent and takes a color (version 7 specification §8.1).
let image_mask = rest.first() == Some(&1.0);
let (w, h) = (*w as u64, *h as u64);
if w.saturating_mul(h) > MAX_PIXELS {
self.image_left_out(IMAGE_TOO_LARGE);
return Ok(());
}
if w == 0 || h == 0 || ![1.0, 8.0].contains(bits) {
self.image_left_out("an image VectorCraft can't read was left out");
return Ok(());
}
let channels: usize = match space.as_str() {
"DeviceGray" => 1,
"DeviceCMYK" => 4,
"DeviceRGB" => 3,
_ => match *kind as i64 {
1 => 1,
4 => 4,
_ => 3,
},
let channels = channels(&space, *kind);
// The size is checked before anything is decoded or allocated.
let shape = Shape { w, h, channels: channels as u64, bits: *bits as u64, alpha: *alpha > 0.0 };
let (Some(k), Some((need, alpha_len))) = (shape.step(&Limits::for_channels(channels)), shape.lens()) else {
return self.too_large_image(&m, w, h);
};
let decoded;
let data = if *binary == 0.0 {
let digits: Vec<u8> = data.iter().copied().filter(|b| b.is_ascii_hexdigit()).collect();
decoded = crate::import::lex::hex_decode(&digits).unwrap_or_default();
decoded.as_slice()
// Within the limits, these fit in memory (see `Limits::bytes`).
let (need, alpha_len) = (need as usize, alpha_len as usize);
let hex;
let data = if *binary == 0.0 && !decoded {
let (max, mut half) = (need + alpha_len, None);
let mut out = Vec::with_capacity(max.min(data.len() / 2));
raster::push_hex(&mut out, &mut half, data, max);
raster::end_hex(&mut out, half, max);
hex = out;
hex.as_slice()
} else {
data
};
let row = if *bits == 1.0 { (w as usize * channels).div_ceil(8) } else { w as usize * channels };
let need = row.saturating_mul(h as usize);
let row = shape.row().unwrap_or(0) as usize;
// An alpha channel follows the colour samples, one byte a pixel.
let alpha_len = if *alpha > 0.0 { (w * h) as usize } else { 0 };
let (Some(samples), opacity) = (data.get(..need), data.get(need..need.saturating_add(alpha_len))) else {
self.image_left_out("an image whose data is cut short was left out");
return Ok(());
};
let opacity = opacity.filter(|_| alpha_len > 0);
let gray1 = *bits == 1.0 && channels == 1 && opacity.is_none();
if k > 1 || w * h > raster::SMALL || (gray1 && !image_mask) {
// An image mask keeps the color that colorizes it.
let colorize = (image_mask && channels == 1 && *bits == 1.0).then(|| self.mask_paint());
return self.large_image(samples, opacity, shape, k, &m, colorize);
}
// CMYK samples keep their ink amounts as a CMYK TIFF, as the PDF importer keeps a CMYK
// image's, and an alpha channel that hides part of the image becomes its opacity mask.
if channels == 4
@@ -595,6 +611,67 @@ impl<'a> Reader<'a> {
self.add_image_colorized(im, None, colorize, hidden)
}
/// An image of `s` past [`raster::SMALL`] pixels, or reduced by the step `k`, or 1-bit gray:
/// 1-bit gray at full size is a 1-bit PNG of its rows as they are; other images keep their
/// channels a byte each (gray or RGB in a PNG with the alpha channel, CMYK in a TIFF with the
/// alpha channel as its opacity mask), averaged over blocks of `k` × `k` pixels. The matrix
/// `m` places the image where its full size would be.
fn large_image(&mut self, samples: &[u8], opacity: Option<&[u8]>, s: Shape, k: u64, m: &[f64], colorize: Option<Paint>) -> Result<(), String> {
let (ow, oh) = (s.w.div_ceil(k) as u32, s.h.div_ceil(k) as u32);
if k == 1 && s.bits == 1 && s.channels == 1 && opacity.is_none() {
let Some(png) = raster::gray1_png(samples, ow, oh, s.row().unwrap_or(0) as usize) else {
return self.too_large_image(m, s.w, s.h);
};
return self.add_raster(ImageBlob::new("image/png", png), None, m, (ow, oh), colorize);
}
let cmyk = s.channels == 4;
let (pixels, mask) = raster::reduce(samples, opacity, s, k, !cmyk);
let blob = if cmyk {
Inks::new(ow, oh, pixels).and_then(|i| ImageBlob::cmyk_tiff(&i))
} else {
use image::ImageEncoder as _;
let colour = match (s.channels, opacity.is_some()) {
(1, false) => image::ExtendedColorType::L8,
(1, true) => image::ExtendedColorType::La8,
(_, false) => image::ExtendedColorType::Rgb8,
(_, true) => image::ExtendedColorType::Rgba8,
};
let mut png = vec![];
image::codecs::png::PngEncoder::new(&mut png).write_image(&pixels, ow, oh, colour).ok().map(|()| ImageBlob::new("image/png", png))
};
// One that can't be encoded is left to the page's art, as one past the hard limits.
let Some(blob) = blob else {
return self.too_large_image(m, s.w, s.h);
};
// Each pixel as read stands for a block of the full size's.
let (sx, sy) = (s.w as f64 / f64::from(ow), s.h as f64 / f64::from(oh));
let m: Vec<f64> = match m {
[a, b, c, d, tx, ty] => vec![a * sx, b * sx, c * sy, d * sy, *tx, *ty],
other => other.to_vec(),
};
if k > 1 {
self.warn(&format!("an embedded image of {} × {} pixels was reduced to {ow} × {oh} pixels to keep the layers", s.w, s.h));
}
self.add_raster(blob, mask.as_deref(), &m, (ow, oh), colorize)
}
/// An image of `w` × `h` pixels past the hard limits, which matrix `m` maps into art space: a
/// box for the page's art to replace ([`Reader::unread_box`]), or, without page art, left out
/// with a note.
fn too_large_image(&mut self, m: &[f64], w: u64, h: u64) -> Result<(), String> {
if !self.page_art {
self.image_left_out(IMAGE_TOO_LARGE);
return Ok(());
}
let [a, b, c, d, tx, ty] = m else {
self.image_left_out("an image VectorCraft can't read was left out");
return Ok(());
};
let xf = self.space() * Affine::new([*a, *b, *c, *d, *tx, *ty]) * FLIP;
let bounds = xf.transform_rect_bbox(Rect::new(0.0, 0.0, w as f64, h as f64));
self.unread_box(LARGE_IMAGES, bounds, false).map(|_| ())
}
/// Add the image `blob` (`w` × `h` pixels), which the matrix `m` maps into art space, with
/// `alpha` (its alpha channel, one byte a pixel) as its opacity mask when it hides part of the
/// image, and `colorize` as the color that colorizes it.
@@ -978,6 +1055,20 @@ fn cmyk_rgb([c, m, y, k]: [u8; 4]) -> [u8; 3] {
[rgb[0], rgb[1], rgb[2]]
}
/// The colour channels of an image in the colour space `space` (`XN`), else of image type `kind`.
fn channels(space: &str, kind: f64) -> usize {
match space {
"DeviceGray" => 1,
"DeviceCMYK" => 4,
"DeviceRGB" => 3,
_ => match kind as i64 {
1 => 1,
4 => 4,
_ => 3,
},
}
}
/// `a`, `a and b`, `a, b and c`.
pub(crate) fn join_and(items: &[&str]) -> String {
match items {
+6 -6
View File
@@ -3,8 +3,9 @@ use std::collections::HashMap;
use super::super::brush;
use super::art::{four, sane, two};
use super::ops::Dictionaries;
use super::raster::{end_hex, push_hex};
use super::types::{
FLIP, GState, Kind, MAX_DEPTH, MAX_HEX, MAX_LISTED, MAX_NODES, MAX_NOTE, MAX_PATTERNS, MAX_PLACED_FOLDER, MAX_STACK, MAX_SUBREADERS, MAX_SYMBOLS,
FLIP, GState, Kind, MAX_DEPTH, MAX_LISTED, MAX_NODES, MAX_NOTE, MAX_PATTERNS, MAX_PLACED_FOLDER, MAX_STACK, MAX_SUBREADERS, MAX_SYMBOLS,
PatternHead, Place, Raster, SKIPPED, SymbolHead, VERSIONED_UNENDED,
};
use super::{Arc, BrushArtColors, Global, Lexer, Node, NodeKind, PatternDef, Reader, Rect, Tok, V, marker, obj};
@@ -52,9 +53,7 @@ impl<'a> Reader<'a> {
&& !line.is_empty()
&& line.bytes().all(|b| b.is_ascii_hexdigit())
{
if r.hex.len() < MAX_HEX {
r.hex.extend_from_slice(line.as_bytes());
}
push_hex(&mut r.samples, &mut r.half, line.as_bytes(), r.max);
return Ok(());
}
// The header's comments (AI7 specification §2.1.1; DSC 3.0).
@@ -182,8 +181,9 @@ impl<'a> Reader<'a> {
} else if line.starts_with("AI5_EndRaster") {
// A file `XG` named in this raster object is no later one's.
self.image_link = None;
if let Some(Raster { operands: Some(vals), hex, .. }) = self.raster.take() {
return self.image_from(vals, &hex);
if let Some(Raster { operands: Some(vals), mut samples, half, max, .. }) = self.raster.take() {
end_hex(&mut samples, half, max);
return self.image_from(vals, &samples, true);
}
} else if line.starts_with("AI5_BeginPlace") {
// One that is still open (no `~` ended it) isn't read.
+2
View File
@@ -390,8 +390,10 @@ impl<'a> Reader<'a> {
"TZ" => self.tz(&vals),
// An image whose samples follow in comment lines (read at `%AI5_EndRaster`).
"XI" if self.raster.is_some() => {
let max = self.sample_bytes(&vals);
if let Some(r) = self.raster.as_mut() {
r.operands = Some(vals);
r.max = max;
}
}
// A linked image (§8.2, §8.3): `(path) modified XG` names the file of the `XF` image after
+268
View File
@@ -0,0 +1,268 @@
//! The size of an embedded image (`XI`), checked before its samples are decoded: read at full size
//! up to a target number of pixels, averaged down in whole steps past it, and not read at all past
//! the hard limits ([`Limits`]).
use std::io::Write as _;
/// Images up to this many pixels read as they always have: an RGBA PNG, or a CMYK TIFF.
pub(super) const SMALL: u64 = 1 << 25;
/// Most bytes of one image's samples, its alpha channel included.
pub(super) const MAX_BYTES: u64 = 512 << 20;
/// Most pixels of one image.
pub(super) const MAX_PIXELS: u64 = 1 << 31;
/// Longest side of an image as it comes in (pixels).
const MAX_SIDE: u64 = 65_535;
/// How large an image is read.
#[derive(Clone, Copy, Debug, PartialEq)]
pub(super) struct Limits {
/// Most pixels of an image as it comes in; larger ones are reduced.
pub(super) target: u64,
/// Most pixels of an image read at all.
pub(super) pixels: u64,
/// Most bytes of an image's samples, its alpha channel included.
pub(super) bytes: u64,
}
#[cfg(test)]
thread_local! {
static TEST_LIMITS: std::cell::Cell<Option<Limits>> = const { std::cell::Cell::new(None) };
}
impl Limits {
/// The limits on this machine: [`target`] pixels, [`MAX_PIXELS`] and [`MAX_BYTES`].
pub(super) fn get() -> Self {
#[cfg(test)]
if let Some(l) = TEST_LIMITS.with(std::cell::Cell::get) {
return l;
}
static TARGET: std::sync::OnceLock<u64> = std::sync::OnceLock::new();
let target = *TARGET.get_or_init(|| target(vectorcraft_render::installed_memory()));
Self { target, pixels: MAX_PIXELS, bytes: MAX_BYTES }
}
/// The limits for an image of `channels` colour channels: a CMYK image comes in at most as
/// large as a CMYK TIFF holds ([`vectorcraft_doc::cmyk::MAX_CMYK_PIXELS`]).
pub(super) fn for_channels(channels: usize) -> Self {
let mut l = Self::get();
if channels == 4 {
l.target = l.target.min(vectorcraft_doc::cmyk::MAX_CMYK_PIXELS);
}
l
}
}
/// `f` run with images read under `target` pixels, `pixels` and `bytes` in place of this machine's
/// limits.
#[cfg(test)]
pub(crate) fn with_image_limits<T>(target: u64, pixels: u64, bytes: u64, f: impl FnOnce() -> T) -> T {
TEST_LIMITS.with(|l| l.set(Some(Limits { target, pixels, bytes })));
let out = f();
TEST_LIMITS.with(|l| l.set(None));
out
}
/// Most pixels of an image as it comes in, on a machine with `memory` bytes installed (`None`: not
/// known): a thirty-second of it, from 2^26 to 2^28 pixels (an RGBA decode of at most 1 GiB); on the
/// web, a quarter of the largest image the renderer decodes, at most 2^27.
fn target(memory: Option<u64>) -> u64 {
if cfg!(target_arch = "wasm32") {
(1u64 << 27).min(vectorcraft_doc::MAX_RASTER_ALLOC / 4)
} else {
memory.map_or(1 << 26, |m| (m / 32).clamp(1 << 26, 1 << 28))
}
}
/// An image's layout from its `XI` operands.
#[derive(Clone, Copy, Debug)]
pub(super) struct Shape {
pub(super) w: u64,
pub(super) h: u64,
/// Colour channels a pixel: 1, 3 or 4.
pub(super) channels: u64,
/// Bits a channel: 1 or 8.
pub(super) bits: u64,
/// An alpha channel, a byte a pixel, follows the colour samples.
pub(super) alpha: bool,
}
impl Shape {
/// Bytes in a row of colour samples (a 1-bit row is padded to a whole byte).
pub(super) fn row(&self) -> Option<u64> {
Some(self.w.checked_mul(self.channels)?.checked_mul(self.bits)?.div_ceil(8))
}
/// Bytes of colour samples, and of the alpha channel.
pub(super) fn lens(&self) -> Option<(u64, u64)> {
let colour = self.row()?.checked_mul(self.h)?;
let alpha = if self.alpha { self.w.checked_mul(self.h)? } else { 0 };
colour.checked_add(alpha)?;
Some((colour, alpha))
}
/// The step `k` the image is reduced by (1: full size): the smallest that brings it within
/// `limits.target` pixels and [`MAX_SIDE`] a side. `None` past `limits.pixels` or
/// `limits.bytes`.
pub(super) fn step(&self, limits: &Limits) -> Option<u64> {
let pixels = self.w.checked_mul(self.h)?;
let (colour, alpha) = self.lens()?;
if pixels > limits.pixels || colour + alpha > limits.bytes {
return None;
}
let fits = |k: u64| {
let (w, h) = (self.w.div_ceil(k), self.h.div_ceil(k));
w <= MAX_SIDE && h <= MAX_SIDE && w.saturating_mul(h) <= limits.target.max(1)
};
// No step below these brings it within the limits.
let mut k = ((pixels as f64 / limits.target.max(1) as f64).sqrt().floor() as u64)
.max(self.w.div_ceil(MAX_SIDE))
.max(self.h.div_ceil(MAX_SIDE))
.max(1);
while !fits(k) {
k += 1;
}
Some(k)
}
}
/// The samples of the image `s` (rows of [`Shape::row`] bytes; a set bit is the channel at full
/// strength) and its `alpha` channel averaged over blocks of `k` × `k` pixels into a byte a channel,
/// each block at the right and bottom edges over the pixels it has. With `inline`, the alpha follows
/// each pixel's channels; otherwise it comes back on its own.
pub(super) fn reduce(samples: &[u8], alpha: Option<&[u8]>, s: Shape, k: u64, inline: bool) -> (Vec<u8>, Option<Vec<u8>>) {
let (w, h, k) = (s.w as usize, s.h as usize, k.max(1) as usize);
let (ch, row) = (s.channels as usize, s.row().unwrap_or(0) as usize);
let (ow, oh) = (w.div_ceil(k), h.div_ceil(k));
let n = ch + usize::from(alpha.is_some());
let mut out = Vec::with_capacity(ow * oh * if inline { n } else { ch });
let mut alpha_out = (alpha.is_some() && !inline).then(|| Vec::with_capacity(ow * oh));
let mut sums = vec![0u64; ow * n];
for oy in 0..oh {
sums.fill(0);
let rows = oy * k..((oy + 1) * k).min(h);
let tall = rows.len() as u64;
for y in rows {
let line = samples.get(y * row..(y + 1) * row).unwrap_or_default();
let opacity = alpha.and_then(|a| a.get(y * w..(y + 1) * w));
for x in 0..w {
let at = x / k * n;
for c in 0..ch {
let i = x * ch + c;
let v =
if s.bits == 1 { line.get(i / 8).map_or(0, |b| ((b >> (7 - i % 8)) & 1) * 255) } else { line.get(i).copied().unwrap_or(0) };
sums[at + c] += u64::from(v);
}
if alpha.is_some() {
sums[at + ch] += u64::from(opacity.and_then(|o| o.get(x)).copied().unwrap_or(255));
}
}
}
for ox in 0..ow {
let count = ((((ox + 1) * k).min(w) - ox * k) as u64 * tall).max(1);
let avg = |sum: u64| ((sum + count / 2) / count) as u8;
out.extend(sums[ox * n..ox * n + ch].iter().map(|v| avg(*v)));
if alpha.is_some() {
let a = avg(sums[ox * n + ch]);
match alpha_out.as_mut() {
Some(o) => o.push(a),
None => out.push(a),
}
}
}
}
(out, alpha_out)
}
/// A grayscale PNG of 1-bit rows as they are (`row` bytes each, a set bit white): `w` × `h`
/// pixels without expanding them.
pub(super) fn gray1_png(samples: &[u8], w: u32, h: u32, row: usize) -> Option<Vec<u8>> {
let mut z = flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::default());
for y in 0..h as usize {
// Filter type 0 (none), then the row.
z.write_all(&[0]).ok()?;
z.write_all(samples.get(y * row..(y + 1) * row)?).ok()?;
}
let data = z.finish().ok()?;
let mut png = b"\x89PNG\r\n\x1a\n".to_vec();
// Bit depth 1, colour type 0 (gray), deflate, filter method 0, no interlacing.
let ihdr = [&w.to_be_bytes()[..], &h.to_be_bytes(), &[1, 0, 0, 0, 0]].concat();
for (ty, body) in [(b"IHDR", ihdr.as_slice()), (b"IDAT", data.as_slice()), (b"IEND", &[])] {
png.extend_from_slice(&u32::try_from(body.len()).ok()?.to_be_bytes());
png.extend_from_slice(ty);
png.extend_from_slice(body);
let mut crc = flate2::Crc::new();
crc.update(ty);
crc.update(body);
png.extend_from_slice(&crc.sum().to_be_bytes());
}
Some(png)
}
/// Decode the hex digits in `text` onto `out`, `half` holding a digit that waits for its pair, up
/// to `max` bytes; other characters are skipped.
pub(super) fn push_hex(out: &mut Vec<u8>, half: &mut Option<u8>, text: &[u8], max: usize) {
for &b in text {
if out.len() >= max {
return;
}
let Some(v) = char::from(b).to_digit(16) else { continue };
match half.take() {
Some(high) => out.push(high << 4 | v as u8),
None => *half = Some(v as u8),
}
}
}
/// The last digit of [`push_hex`]'s, without its pair, as the byte it starts.
pub(super) fn end_hex(out: &mut Vec<u8>, half: Option<u8>, max: usize) {
if let Some(high) = half
&& out.len() < max
{
out.push(high << 4);
}
}
#[cfg(test)]
mod tests {
use super::*;
/// A CMYK image is reduced to the size a CMYK TIFF holds, whatever the target.
#[test]
fn cmyk_images_come_in_at_most_as_large_as_a_cmyk_tiff() {
let shape = |channels| Shape { w: 9000, h: 9000, channels, bits: 8, alpha: false };
let (rgb, cmyk) = with_image_limits(1 << 28, MAX_PIXELS, MAX_BYTES * 4, || {
(shape(3).step(&Limits::for_channels(3)), shape(4).step(&Limits::for_channels(4)))
});
assert_eq!(rgb, Some(1));
let k = cmyk.unwrap();
assert_eq!(k, 2);
assert!(9000u64.div_ceil(k).pow(2) <= vectorcraft_doc::cmyk::MAX_CMYK_PIXELS);
}
/// The step keeps each side within 65,535 pixels, and sizes past the limits or overflowing
/// aren't read.
#[test]
fn steps_keep_sides_within_the_limit() {
let limits = Limits { target: 1 << 28, pixels: MAX_PIXELS, bytes: MAX_BYTES };
let long = Shape { w: 200_000, h: 1, channels: 1, bits: 1, alpha: false };
assert_eq!(long.step(&limits), Some(4));
let huge = Shape { w: u64::MAX, h: 2, channels: 3, bits: 8, alpha: true };
assert_eq!(huge.step(&limits), None);
let many = Shape { w: 1 << 16, h: 1 << 16, channels: 1, bits: 1, alpha: false };
assert_eq!(many.step(&limits), None);
}
/// Hex digits are decoded across calls, other characters skipped, up to the bytes asked for.
#[test]
fn hex_is_decoded_up_to_the_bytes_asked_for() {
let (mut out, mut half) = (vec![], None);
push_hex(&mut out, &mut half, b"F f0 1", 8);
push_hex(&mut out, &mut half, b"2a", 8);
end_hex(&mut out, half, 8);
assert_eq!(out, [0xff, 0x01, 0x2a]);
let (mut out, mut half) = (vec![], None);
push_hex(&mut out, &mut half, b"0102030", 2);
end_hex(&mut out, half, 2);
assert_eq!(out, [1, 2]);
}
}
+13 -8
View File
@@ -21,17 +21,18 @@ pub(super) const BLEND_BUDGET: usize = 50_000;
/// Most points in one path.
pub(super) const MAX_POINTS: usize = 4_000_000;
/// Largest image read (pixels).
/// Largest placed file's preview read (pixels).
pub(super) const MAX_PIXELS: u64 = 1 << 25;
/// Most placed files whose other placement ([`super::Structure::placed_elsewhere`]) is kept.
pub(super) const MAX_PLACED_ELSEWHERE: usize = 4096;
/// The note for an image past [`MAX_PIXELS`]: the file then opens from its PDF part, which reads
/// larger images (#1479), unless that part can't be read either; then the image is left out.
pub const IMAGE_TOO_LARGE: &str = "an image too large to read from the editing data was left out";
/// The note for an image past the hard limits ([`super::raster::Limits`]) in a file without page
/// art to replace it.
pub(super) const IMAGE_TOO_LARGE: &str = "an image too large to read from the editing data was left out";
/// Most hex digits of an image's samples read from comment lines (CMYK and alpha at the largest size).
pub(super) const MAX_HEX: usize = (MAX_PIXELS as usize) * 10;
/// What an image past the hard limits is, as the notes name it: with page art, its box becomes
/// that art (#1479).
pub(super) const LARGE_IMAGES: &str = "very large images";
/// Largest side of the document and its artboards (points).
pub(super) const MAX_SIDE: f64 = 1e6;
@@ -281,8 +282,12 @@ pub(super) struct Raster {
/// `XI`'s operands when its samples follow as hex in comment lines (`%FF00…`) up to
/// `%AI5_EndRaster`, the form the version 7 specification documents (§8).
pub(super) operands: Option<Vec<V>>,
/// Those hex digits.
pub(super) hex: Vec<u8>,
/// The bytes of those hex digits, up to `max`.
pub(super) samples: Vec<u8>,
/// The bytes of samples the image reads (0 when it isn't read).
pub(super) max: usize,
/// A digit waiting for its pair.
pub(super) half: Option<u8>,
}
/// A placed file's operands, from `` ` `` and its preview, until `~`.
+25 -2
View File
@@ -783,7 +783,7 @@ fn pattern_names_are_shown_up_to_their_newline() {
// Nor the name of a custom color the program of an EPS gives, or None's.
let ps = b"%!PS-Adobe-3.0\n%%CMYKCustomColor: 0 0 1 0 (Outer Ink)\n%%EndComments\n";
let data = stream(&format!("{}{}{}", def("Outer Ink\\n1", 8), def("[None]\\n1", 8), layer(&filled("Outer Ink\\n1"))));
let doc = super::read_beside(data.as_bytes(), true, ps).unwrap().doc;
let doc = super::read_beside(data.as_bytes(), true, ps, false).unwrap().doc;
assert_eq!(doc.patterns.iter().map(|p| p.name.as_str()).collect::<Vec<_>>(), ["Outer Ink 2", "[None] 2"]);
assert!(matches!(doc.swatch("Outer Ink").unwrap().paint, Paint::Solid { .. }));
assert!(matches!(fill(&art(&doc)[0]), Paint::Pattern { pattern, .. } if pattern == "Outer Ink 2"));
@@ -2613,6 +2613,29 @@ fn a_drawn_look_that_is_an_image_stands_for_its_object() {
assert!(r.warnings.iter().any(|w| w.contains("drawn look (1)")), "{:?}", r.warnings);
}
/// Samples in hex comment lines after `XI` (the version 7 specification's form) are read up to the
/// bytes the image needs; an image past the hard limits isn't read, whatever lines follow it.
#[test]
fn hex_comment_line_samples_are_read_up_to_the_image() {
let raster = |(w, h): (u32, u32), lines: &str| {
let s = format!(
"%AI5_BeginLayer\n1 1 1 1 0 0 1 0 79 128 255 0 50 0 Lb\n(L) Ln\n%AI5_File:\n%AI5_BeginRaster\n() 1 XG\n/DeviceRGB XN\n\
[ 1 0 0 1 0 100 ] {w} {h} 0 Xh\n[ 1 0 0 1 0 100 ] 0 0 {w} {h} {w} {h} 8 3 0 0 0 0 XI\n{lines}%AI5_EndRaster\nN\nLB\n%AI5_EndLayer--\n"
);
let mut out = stream("").into_bytes();
let at = out.windows(14).position(|w| w == b"%%PageTrailer\n").unwrap();
out.splice(at..at, s.into_bytes());
out
};
// 2 × 1: red, green, then digits past the image's six bytes.
let r = read(&raster((2, 1), "%FF00\n%0000FF\n%00FFFFFF\n")).unwrap();
let img = pixels(&r.doc).0;
assert_eq!((img.get_pixel(0, 0).0, img.get_pixel(1, 0).0), ([255, 0, 0, 255], [0, 255, 0, 255]));
let r = read(&raster((70_000, 70_000), "%FF0000\n%00FF00\n")).unwrap();
assert!(art(&r.doc).is_empty());
assert!(r.warnings.iter().any(|w| w == "an image too large to read from the editing data was left out"), "{:?}", r.warnings);
}
/// The name and opacity mask after an image follow the operators that end its raster: `XH`, the
/// versioned `Xr` and the `N` after `%AI5_EndRaster`.
#[test]
@@ -2911,7 +2934,7 @@ fn a_version_7_linked_image_without_its_file_name_is_left_out_with_a_warning() {
let r = read(stream7(&layer7("L", &raster7("(a.tif) 0 XG\n", m, (100_000, 100_000), "XF", ""))).as_bytes()).unwrap();
let a = art(&r.doc);
assert!(matches!(&a[..], [n] if matches!(&n.kind, NodeKind::Image(im) if (im.width, im.height) == (100_000, 100_000))), "{a:?}");
assert!(!r.warnings.iter().any(|w| w == read::IMAGE_TOO_LARGE), "{:?}", r.warnings);
assert!(!r.warnings.iter().any(|w| w == "an image too large to read from the editing data was left out"), "{:?}", r.warnings);
let twice = raster7("(a.tif) 0 XG\n(b.tif) 0 XG\n", m, (2, 2), "XF", "");
let r = read(stream7(&layer7("L", &twice)).as_bytes()).unwrap();
let a = art(&r.doc);
+3 -1
View File
@@ -59,7 +59,9 @@ pub(super) fn build(
) -> Result<(Document, Vec<String>), String> {
// An EPS (its page is its art) draws a Large Canvas page at full size; a `.ai` keeps its art
// space, as its PDF part may (#2031).
build_from(ai::read(data, matches!(page, Page::Art))?, data, visible, page, outlined, trust, unit)
let large_canvas = matches!(page, Page::Art);
let structure = if visible.is_some() { ai::read_over_page(data, large_canvas)? } else { ai::read(data, large_canvas)? };
build_from(structure, data, visible, page, outlined, trust, unit)
}
/// [`build`] without a page, for a `.ai` whose PDF part can't be read, its first page `page`
+5 -11
View File
@@ -24,7 +24,7 @@ pub(in super::super) fn layered(ps: &[u8], visible: Imported, trust: bool, illus
// can't be used.
// Private data kept apart from the program: the program's own header gives the author and
// custom colors the private data's header may lack.
let read = |d: &[u8]| if matches!(data, Ok(Cow::Owned(_))) { ai::read_beside(d, true, ps) } else { ai::read(d, true) };
let read = |d: &[u8]| if matches!(data, Ok(Cow::Owned(_))) { ai::read_beside(d, true, ps, true) } else { ai::read_over_page(d, true) };
let (tree, result) = match data.as_deref() {
Ok(d) => match read(d) {
Ok(s) => (layer_tree(&s.doc.layers), build_from(s, d, Some(&visible.document), Page::Art, false, trust, 1.0)),
@@ -160,14 +160,9 @@ pub fn layered_ai_from(
(visible, all, false)
};
let built = structure
.unwrap_or_else(|| ai::read(&data, false).map(Box::new))
.unwrap_or_else(|| ai::read_over_page(&data, false).map(Box::new))
.and_then(|structure| build_from(*structure, &data, Some(&visible), Page::Artboard, outlined, trust, unit));
match built {
// An image too large for the editing data's reader would be missing from the layers, while
// the PDF part has it: the file comes in as its PDF part instead (#1479).
Ok((_, notes)) if notes.iter().any(|n| n == ai::IMAGE_TOO_LARGE) => {
from_pdf_part(visible, warnings, "an image is too large to read from them")
}
Ok((mut done, notes)) => {
done.setup.bleed = visible.setup.bleed;
let mut all = warnings;
@@ -193,14 +188,13 @@ pub fn ai_layers_alone(private: &[u8], pages: &[(f64, f64)], unit: f64) -> Resul
if !has_layers(&data) {
return Err(AiLayers { data: Ok(data), structure: None });
}
let structure = match ai::read(&data, false) {
// An image past the hard limits is read as an unread box, which the PDF part's art replaces.
let structure = match ai::read_over_page(&data, false) {
Ok(structure) => structure,
Err(why) => return Err(AiLayers { data: Ok(data), structure: Some(Err(why)) }),
};
match build_without_page(structure, &data, pages, unit) {
// An image too large for the editing data's reader is missing from the layers: the PDF
// part has it (as in `layered_ai`).
Ok(Ok((doc, notes))) if !notes.iter().any(|n| n == ai::IMAGE_TOO_LARGE) => Ok((doc, notes)),
Ok(Ok((doc, notes))) => Ok((doc, notes)),
Ok(_) => Err(AiLayers { data: Ok(data), structure: None }),
Err(structure) => Err(AiLayers { data: Ok(data), structure: Some(Ok(structure)) }),
}
+83 -19
View File
@@ -1215,27 +1215,89 @@ fn in_points(mut d: Document, k: f64) -> Document {
d
}
/// #1479: an image larger than the editing data's reader takes would be missing from the layers,
/// while the PDF part has it (it reads larger images): the file comes in as its PDF part, with a
/// note saying why, rather than without the image.
/// A 1-bit gray image of `w` × `h` pixels in the format's operators, its samples `rows` (binary,
/// rows padded to whole bytes, a set bit white), placed by the matrix `m`.
fn raster_1bit(m: &str, (w, h): (u32, u32), rows: &str) -> String {
format!(
"%AI5_File:\n%AI5_BeginRaster\n() 1 XG\n/DeviceGray XN\n[ {m} ] {w} {h} 0 Xh\n\
[ {m} ] 0 0 {w} {h} {w} {h} 1 1 0 0 1 0 4 4 0 0\n%%BeginData: {}\rXI\n{rows}%%EndData\r\nXH\r\n%AI5_EndRaster\r\nN",
rows.len()
)
}
/// #1479: an image past 2^25 pixels keeps the layers, at full size; a 1-bit one is a 1-bit PNG of
/// its rows.
#[test]
fn an_ai_file_with_an_image_too_large_for_its_editing_data_comes_in_as_its_pdf_part() {
let raster = |w: u32, h: u32| {
format!(
"%AI5_File:\n%AI5_BeginRaster\n() 1 XG\n/DeviceGray XN\n[ 1 0 0 1 0 100 ] {w} {h} 0 Xh\n\
[ 1 0 0 1 0 100 ] 0 0 {w} {h} {w} {h} 8 1 0 0 1 0 4 4 0 0\n%%BeginData: 1\rXI\n\u{0}%%EndData\r\nXH\r\n%AI5_EndRaster\r\nN"
)
};
// 6,000 × 6,000 pixels: past the reader's 2^25.
let art = native(&layer("Texture", true, &format!("{}\n{}", square(10, 10), raster(6000, 6000))));
fn an_ai_file_with_an_image_past_2_25_pixels_keeps_its_layers() {
// 6,000 × 6,000 pixels, each row black and white in turn ('U' is 01010101).
let image = raster_1bit("0.0005 0 0 0.0005 50 14", (6000, 6000), &"U".repeat(750 * 6000));
let art = native(&(layer("Live", true, &square(10, 10)) + &layer("Texture", true, &image)));
let (d, warnings, read) = crate::layered_ai(&private(&art), pdf_part(10.0, 86.0), vec![], false, true, 1.0);
assert!(!read, "the PDF part is used: {warnings:?}");
assert!(warnings.iter().any(|w| w.contains("an image is too large to read from them")), "{warnings:?}");
assert_eq!(d.layers[0].children().map_or(0, |c| c.len()), 1, "the PDF part's art");
// An image the reader takes keeps the layers.
let art = native(&layer("Texture", true, &format!("{}\n{}", square(10, 10), raster(1, 1))));
let (_, warnings, read) = crate::layered_ai(&private(&art), pdf_part(10.0, 86.0), vec![], false, true, 1.0);
assert!(read, "{warnings:?}");
assert_eq!(names(&d), ["Live", "Texture"]);
assert_eq!(kinds(&d.layers[0]), ["path"]);
let [image] = d.layers[1].children().unwrap().as_slice() else { panic!("{:?}", d.layers[1]) };
let NodeKind::Image(im) = &image.kind else { panic!("{image:?}") };
assert_eq!((im.width, im.height), (6000, 6000));
let png = &d.images[&im.key].bytes;
// Bit depth 1, colour type 0 (gray).
assert_eq!(png.get(24..26), Some([1, 0].as_slice()));
let gray = image::load_from_memory(png).unwrap().to_luma8();
assert_eq!((gray.get_pixel(0, 0)[0], gray.get_pixel(1, 0)[0], gray.get_pixel(5999, 5999)[0]), (0, 255, 255));
assert!(warnings.iter().all(|w| !w.contains("image")), "{warnings:?}");
}
/// An image past the target size is averaged down by the smallest whole step that fits, a 1-bit
/// one into gray coverage, the blocks at its edges over the pixels they have; it covers the box
/// the full size would.
#[test]
fn an_ai_image_past_the_target_size_is_reduced_and_keeps_the_layers() {
// 17 × 13 pixels: rows black, and in turn (the first one) white but for columns 0, 8 and 16.
let rows: String = (0..13).map(|y| if y % 2 == 0 { "\u{7f}\u{7f}\0" } else { "\0\0\0" }).collect();
let image = raster_1bit("1 0 0 1 50 14", (17, 13), &rows);
let art = native(&(layer("Live", true, &square(10, 10)) + &layer("Texture", true, &image)));
let (d, warnings, read) = crate::import::ai::with_image_limits(20, 1 << 31, 512 << 20, || {
crate::layered_ai(&private(&art), pdf_part(10.0, 86.0), vec![], false, true, 1.0)
});
assert!(read, "{warnings:?}");
assert_eq!(names(&d), ["Live", "Texture"]);
assert!(warnings.contains(&"an embedded image of 17 × 13 pixels was reduced to 5 × 4 pixels to keep the layers".to_string()), "{warnings:?}");
let [image] = d.layers[1].children().unwrap().as_slice() else { panic!("{:?}", d.layers[1]) };
let NodeKind::Image(im) = &image.kind else { panic!("{image:?}") };
assert_eq!((im.width, im.height), (5, 4));
let full = im.xf.transform_rect_bbox(vectorcraft_geom::Rect::new(0.0, 0.0, 5.0, 4.0));
assert!(
(full.x0 - 50.0).abs() < 1e-9 && (full.x1 - 67.0).abs() < 1e-9 && (full.y0 - 86.0).abs() < 1e-9 && (full.y1 - 99.0).abs() < 1e-9,
"{full:?}"
);
let gray = image::load_from_memory(&d.images[&im.key].bytes).unwrap().to_luma8();
// Steps of 4: blocks of rows 0–11 are half white rows, the last block row 12 alone; blocks of
// columns 0–3 and 8–11 have 3 white columns of 4, and the last block column 16 alone.
let rows: Vec<Vec<u8>> = (0..4).map(|y| (0..5).map(|x| gray.get_pixel(x, y)[0]).collect()).collect();
assert_eq!(rows, [vec![96, 128, 96, 128, 0], vec![96, 128, 96, 128, 0], vec![96, 128, 96, 128, 0], vec![191, 255, 191, 255, 0]]);
}
/// An image past the hard limits is a box that the page's art inside it replaces, and the
/// layers stay; without page art (the editing data alone), it is left out with a note.
#[test]
fn an_ai_image_past_the_hard_limits_comes_in_as_the_pages_art() {
let image = raster_1bit("1 0 0 1 50 14", (4, 4), &"\0".repeat(4));
let art = native(&(layer("Live", true, &square(10, 10)) + &layer("Texture", true, &image)));
let (d, notes, read) =
crate::import::ai::with_image_limits(20, 15, 512 << 20, || crate::layered_ai(&private(&art), pdf_two_squares(), vec![], false, false, 1.0));
assert!(read, "{notes:?}");
assert_eq!(names(&d), ["Live", "Texture"]);
assert_eq!(kinds(&d.layers[1]), ["group[1]"]);
replacement_is_cyan(&d, 1, 0);
assert!(notes.iter().any(|n| n.contains("very large images") && n.contains("they came in as the page's art")), "{notes:?}");
// Past the bytes the samples may take, too.
let (_, notes, read) =
crate::import::ai::with_image_limits(20, 1 << 31, 1, || crate::layered_ai(&private(&art), pdf_two_squares(), vec![], false, false, 1.0));
assert!(read && notes.iter().any(|n| n.contains("very large images")), "{notes:?}");
let (d, notes) = crate::import::ai::with_image_limits(20, 15, 512 << 20, || crate::ai_alone(&private(&art))).unwrap();
assert_eq!(names(&d), ["Live", "Texture"]);
assert!(d.layers[1].children().unwrap().is_empty());
assert!(notes.iter().any(|n| n == "an image too large to read from the editing data was left out"), "{notes:?}");
}
/// A `.ai` whose layers hold a placed PDF and no text object: its PDF part draws the placed file,
@@ -1322,7 +1384,9 @@ fn an_ai_file_that_comes_in_as_its_pdf_part_keeps_the_swatches_of_its_palette()
let mut part = pdf_part(10.0, 86.0);
let gold = vectorcraft_color::Paint::solid(vectorcraft_color::Color::cmyk(0.0, 0.2, 0.9, 0.1));
part.swatches.push(vectorcraft_color::swatch::Swatch { name: "Gold ink".into(), paint: gold, global: true, spot: true });
let (d, warnings, read) = crate::layered_ai(&private(&art), part, vec![], false, true, 1.0);
// An image past 2^25 pixels isn't read here, and the page has no art of its own in its box.
let (d, warnings, read) =
crate::import::ai::with_image_limits(1 << 26, 1 << 25, 512 << 20, || crate::layered_ai(&private(&art), part, vec![], false, true, 1.0));
assert!(!read, "the PDF part is used: {warnings:?}");
let swatches: Vec<&str> = d.swatches.iter().map(|s| s.name.as_str()).collect();
assert_eq!(swatches, ["[None]", "White", "Gold ink"], "{warnings:?}");
+1
View File
@@ -4580,6 +4580,7 @@ axis Both И двете
@msg gradient “{name}” isn't defined in the file: a plain colour stands in for it градиентът „{name}“ не е дефиниран във файла: заменен е с обикновен цвят
@msg an image VectorCraft can't read was left out изображение, което VectorCraft не може да разчете, беше пропуснато
@msg an image whose data is cut short was left out изображение с непълни данни беше пропуснато
@msg an embedded image of {_1} × {_2} pixels was reduced to {_3} × {_4} pixels to keep the layers вградено изображение от {_1} × {_2} пиксела беше намалено до {_3} × {_4} пиксела, за да се запазят слоевете
@msg linked pictures show the low-resolution preview saved in the file until their files are found: relink them in the Links panel свързаните картини показват записания във файла предварителен преглед с ниска резолюция, докато файловете им бъдат намерени: презакачете ги в панела „Връзки“
@msg linked type areas past VectorCraft's limits were left out свързаните текстови области извън ограниченията на VectorCraft бяха пропуснати
@msg type that changes writing direction inside a text object opens in the direction of its first character текст, който сменя посоката на писане в рамките на текстов обект, се отваря в посоката на първия си знак
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -4978,6 +4978,7 @@ imageTrace Outline Obris
@msg gradient “{name}” isn't defined in the file: a plain colour stands in for it gradijent „{name}” nije definisan u datoteci: umjesto njega koristi se obična boja
@msg an image VectorCraft can't read was left out izostavljena je slika koju VectorCraft ne može pročitati
@msg an image whose data is cut short was left out izostavljena je slika čiji su podaci skraćeni
@msg an embedded image of {_1} × {_2} pixels was reduced to {_3} × {_4} pixels to keep the layers ugrađena slika od {_1} × {_2} piksela smanjena je na {_3} × {_4} piksela kako bi se zadržali slojevi
@msg linked pictures show the low-resolution preview saved in the file until their files are found: relink them in the Links panel povezane slike prikazuju pregled niske rezolucije sačuvan u datoteci dok se njihove datoteke ne pronađu: ponovo ih povežite u panelu Veze
@msg linked type areas past VectorCraft's limits were left out povezana područja teksta izvan ograničenja programa VectorCraft su izostavljena
@msg type that changes writing direction inside a text object opens in the direction of its first character tekst koji mijenja smjer pisanja unutar tekstualnog objekta otvara se u smjeru svog prvog znaka
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5036,6 +5036,7 @@ digits Malayalam Malaiàlam
@msg an image VectorCraft can't read was left out s’ha omès una imatge que VectorCraft no pot llegir
@msg graphs come in as plain art, without their data or settings els gràfics s’importen com a il·lustració normal, sense les seves dades ni la seva configuració
@msg an image whose data is cut short was left out s’ha omès una imatge les dades de la qual estan tallades
@msg an embedded image of {_1} × {_2} pixels was reduced to {_3} × {_4} pixels to keep the layers una imatge incrustada de {_1} × {_2} píxels s’ha reduït a {_3} × {_4} píxels per conservar les capes
@msg an object can't be read no es pot llegir un objecte
@msg an object in perspective would leave the edited grid un objecte en perspectiva sortiria de la quadrícula editada
@msg An object takes one 3D effect. Edit the existing effect's options, or remove it first. Un objecte admet un efecte 3D. Editeu les opcions de l’efecte existent o suprimiu-lo primer.
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5045,6 +5045,7 @@ imageTrace Outline Obrys
@msg an image VectorCraft can't read was left out obrázek, který VectorCraft neumí přečíst, byl vynechán
@msg graphs come in as plain art, without their data or settings grafy se načtou jako obyčejná grafika, bez svých dat a nastavení
@msg an image whose data is cut short was left out obrázek s useknutými daty byl vynechán
@msg an embedded image of {_1} × {_2} pixels was reduced to {_3} × {_4} pixels to keep the layers vložený obrázek o rozměrech {_1} × {_2} pixelů byl zmenšen na {_3} × {_4} pixelů, aby se zachovaly vrstvy
@msg linked pictures show the low-resolution preview saved in the file until their files are found: relink them in the Links panel propojené obrázky se zobrazují jako náhled v nízkém rozlišení uložený v souboru, dokud se nenajdou jejich soubory: znovu je propojte v panelu Propojení
@msg linked type areas past VectorCraft's limits were left out propojené textové oblasti nad limity aplikace VectorCraft byly vynechány
@msg type that changes writing direction inside a text object opens in the direction of its first character text, který mění směr psaní uvnitř textového objektu, se otevře ve směru svého prvního znaku
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -4668,6 +4668,7 @@ imageTrace Outline Kontur
@msg gradient “{name}” isn't defined in the file: a plain colour stands in for it gradienten »{name}« er ikke defineret i filen: der bruges en flad farve i stedet
@msg an image VectorCraft can't read was left out et billede, som VectorCraft ikke kan læse, blev udeladt
@msg an image whose data is cut short was left out et billede, hvis data er afkortet, blev udeladt
@msg an embedded image of {_1} × {_2} pixels was reduced to {_3} × {_4} pixels to keep the layers et indlejret billede på {_1} × {_2} pixel blev formindsket til {_3} × {_4} pixel for at bevare lagene
@msg linked pictures show the low-resolution preview saved in the file until their files are found: relink them in the Links panel linkede billeder viser den gemte forhåndsvisning i lav opløsning, indtil deres filer findes: link dem igen i Links-panelet
@msg linked type areas past VectorCraft's limits were left out sammenkædede tekstområder ud over VectorCrafts grænser blev udeladt
@msg type that changes writing direction inside a text object opens in the direction of its first character tekst, der skifter skriveretning inde i et tekstobjekt, åbnes i retningen for sit første tegn
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -4652,6 +4652,7 @@ theme Appearance Erscheinungsbild
@msg an image VectorCraft can't read was left out ein Bild, das VectorCraft nicht lesen kann, wurde ausgelassen
@msg graphs come in as plain art, without their data or settings Diagramme werden als einfache Grafik übernommen, ohne ihre Daten und Einstellungen
@msg an image whose data is cut short was left out ein Bild mit abgeschnittenen Daten wurde ausgelassen
@msg an embedded image of {_1} × {_2} pixels was reduced to {_3} × {_4} pixels to keep the layers ein eingebettetes Bild mit {_1} × {_2} Pixeln wurde auf {_3} × {_4} Pixel verkleinert, damit die Ebenen erhalten bleiben
@msg linked pictures show the low-resolution preview saved in the file until their files are found: relink them in the Links panel verknüpfte Bilder zeigen die in der Datei gespeicherte Vorschau in niedriger Auflösung, bis ihre Dateien gefunden werden: Verknüpfen Sie sie im Bedienfeld „Verknüpfungen“ neu
@msg linked type areas past VectorCraft's limits were left out verkettete Textbereiche jenseits der Grenzen von VectorCraft wurden ausgelassen
@msg type that changes writing direction inside a text object opens in the direction of its first character Text, der innerhalb eines Textobjekts die Schreibrichtung wechselt, wird in der Richtung seines ersten Zeichens geöffnet
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -4685,6 +4685,7 @@ digits Malayalam Μαλαγιάλαμ
@msg an image VectorCraft can't read was left out παραλείφθηκε εικόνα που δεν μπορεί να διαβάσει το VectorCraft
@msg graphs come in as plain art, without their data or settings τα γραφήματα εισάγονται ως απλή γραφική, χωρίς τα δεδομένα και τις ρυθμίσεις τους
@msg an image whose data is cut short was left out παραλείφθηκε εικόνα με περικομμένα δεδομένα
@msg an embedded image of {_1} × {_2} pixels was reduced to {_3} × {_4} pixels to keep the layers μια ενσωματωμένη εικόνα {_1} × {_2} pixel μειώθηκε σε {_3} × {_4} pixel για να διατηρηθούν τα επίπεδα
@msg linked pictures show the low-resolution preview saved in the file until their files are found: relink them in the Links panel οι συνδεδεμένες εικόνες εμφανίζουν την προεπισκόπηση χαμηλής ανάλυσης που έχει αποθηκευτεί, μέχρι να βρεθούν τα αρχεία τους: επανασυνδέστε τις στον πίνακα Σύνδεσμοι
@msg linked type areas past VectorCraft's limits were left out οι συνδεδεμένες περιοχές κειμένου πέρα από τα όρια του VectorCraft παραλείφθηκαν
@msg type that changes writing direction inside a text object opens in the direction of its first character το κείμενο που αλλάζει κατεύθυνση γραφής μέσα σε ένα αντικείμενο κειμένου ανοίγει με την κατεύθυνση του πρώτου χαρακτήρα του
Can't render this file because it contains an unexpected character in line 168 and column 5.
+1
View File
@@ -4736,6 +4736,7 @@ axis Both Ambos
@msg an image VectorCraft can't read was left out se omitió una imagen que VectorCraft no puede leer
@msg graphs come in as plain art, without their data or settings los gráficos se importan como ilustración normal, sin sus datos ni sus ajustes
@msg an image whose data is cut short was left out se omitió una imagen con datos incompletos
@msg an embedded image of {_1} × {_2} pixels was reduced to {_3} × {_4} pixels to keep the layers una imagen incrustada de {_1} × {_2} píxeles se redujo a {_3} × {_4} píxeles para conservar las capas
@msg linked pictures show the low-resolution preview saved in the file until their files are found: relink them in the Links panel las imágenes vinculadas muestran la vista previa de baja resolución guardada en el archivo hasta que se encuentren sus archivos: vuelva a vincularlas en el panel Vínculos
@msg linked type areas past VectorCraft's limits were left out se omitieron las áreas de texto enlazadas que superan los límites de VectorCraft
@msg type that changes writing direction inside a text object opens in the direction of its first character el texto que cambia de dirección de escritura dentro de un objeto de texto se abre en la dirección de su primer carácter
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -4947,6 +4947,7 @@ imageTrace Outline Ääriviiva
@msg an image VectorCraft can't read was left out kuva, jota VectorCraft ei voi lukea, jätettiin pois
@msg graphs come in as plain art, without their data or settings kaaviot tuodaan tavallisena grafiikkana ilman niiden tietoja ja asetuksia
@msg an image whose data is cut short was left out kuva, jonka data on katkennut, jätettiin pois
@msg an embedded image of {_1} × {_2} pixels was reduced to {_3} × {_4} pixels to keep the layers upotettu {_1} × {_2} kuvapisteen kuva pienennettiin kokoon {_3} × {_4} kuvapistettä, jotta tasot säilyvät
@msg linked pictures show the low-resolution preview saved in the file until their files are found: relink them in the Links panel linkitetyt kuvat näyttävät tiedostoon tallennetun matalan tarkkuuden esikatselun, kunnes niiden tiedostot löytyvät: linkitä ne uudelleen Linkit-paneelissa
@msg linked type areas past VectorCraft's limits were left out VectorCraftin rajat ylittävät linkitetyt tekstialueet jätettiin pois
@msg type that changes writing direction inside a text object opens in the direction of its first character tekstiobjektin sisällä kirjoitussuuntaa vaihtava teksti avautuu ensimmäisen merkkinsä suuntaan
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -4724,6 +4724,7 @@ axis Both Les deux
@msg an image VectorCraft can't read was left out une image que VectorCraft ne peut pas lire a été ignorée
@msg graphs come in as plain art, without their data or settings les graphes sont importés comme illustration ordinaire, sans leurs données ni leurs réglages
@msg an image whose data is cut short was left out une image aux données tronquées a été ignorée
@msg an embedded image of {_1} × {_2} pixels was reduced to {_3} × {_4} pixels to keep the layers une image incorporée de {_1} × {_2} pixels a été réduite à {_3} × {_4} pixels pour conserver les calques
@msg linked pictures show the low-resolution preview saved in the file until their files are found: relink them in the Links panel les images liées affichent l’aperçu basse résolution enregistré dans le fichier tant que leurs fichiers ne sont pas trouvés : liez-les à nouveau dans le panneau Liens
@msg linked type areas past VectorCraft's limits were left out les zones de texte liées au-delà des limites de VectorCraft ont été omises
@msg type that changes writing direction inside a text object opens in the direction of its first character le texte qui change de sens d’écriture dans un objet texte s’ouvre dans le sens de son premier caractère
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -4748,6 +4748,7 @@ imageTrace Outline Körvonal
@msg an image VectorCraft can't read was left out kimaradt egy kép, amelyet a VectorCraft nem tud olvasni
@msg graphs come in as plain art, without their data or settings a diagramok egyszerű grafikaként kerülnek be, adataik és beállításaik nélkül
@msg an image whose data is cut short was left out kimaradt egy csonka adatú kép
@msg an embedded image of {_1} × {_2} pixels was reduced to {_3} × {_4} pixels to keep the layers egy {_1} × {_2} képpontos beágyazott kép {_3} × {_4} képpontosra lett kicsinyítve, hogy a rétegek megmaradjanak
@msg linked pictures show the low-resolution preview saved in the file until their files are found: relink them in the Links panel a csatolt képek a fájlban mentett kis felbontású előnézetet mutatják, amíg a fájljaik elő nem kerülnek: csatold őket újra a Csatolások panelen
@msg linked type areas past VectorCraft's limits were left out a VectorCraft korlátain túli csatolt szövegterületek kimaradtak
@msg type that changes writing direction inside a text object opens in the direction of its first character a szövegobjektumon belül írásirányt váltó szöveg az első karakterének irányában nyílik meg
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -4668,6 +4668,7 @@ imageTrace Outline Kontur
@msg gradient “{name}” isn't defined in the file: a plain colour stands in for it gradien “{name}” tidak didefinisikan dalam berkas: warna polos dipakai sebagai pengganti
@msg an image VectorCraft can't read was left out gambar yang tidak dapat dibaca VectorCraft tidak disertakan
@msg an image whose data is cut short was left out gambar yang datanya terpotong tidak disertakan
@msg an embedded image of {_1} × {_2} pixels was reduced to {_3} × {_4} pixels to keep the layers gambar tersemat berukuran {_1} × {_2} piksel diperkecil menjadi {_3} × {_4} piksel agar lapisannya tetap terjaga
@msg linked pictures show the low-resolution preview saved in the file until their files are found: relink them in the Links panel gambar tertaut menampilkan pratinjau resolusi rendah yang tersimpan dalam berkas sampai berkasnya ditemukan: tautkan ulang di panel Tautan
@msg linked type areas past VectorCraft's limits were left out area teks tertaut di luar batas VectorCraft dihilangkan
@msg type that changes writing direction inside a text object opens in the direction of its first character teks yang mengubah arah penulisan di dalam objek teks dibuka dengan arah karakter pertamanya
Can't render this file because it contains an unexpected character in line 2270 and column 5.
+1
View File
@@ -4778,6 +4778,7 @@ axis Both Entrambi
@msg an image VectorCraft can't read was left out un'immagine che VectorCraft non può leggere è stata omessa
@msg graphs come in as plain art, without their data or settings i grafici vengono importati come grafica semplice, senza i loro dati e le loro impostazioni
@msg an image whose data is cut short was left out un'immagine con dati incompleti è stata omessa
@msg an embedded image of {_1} × {_2} pixels was reduced to {_3} × {_4} pixels to keep the layers un'immagine incorporata di {_1} × {_2} pixel è stata ridotta a {_3} × {_4} pixel per mantenere i livelli
@msg linked pictures show the low-resolution preview saved in the file until their files are found: relink them in the Links panel le immagini collegate mostrano l'anteprima a bassa risoluzione salvata nel file finché i loro file non vengono trovati: ricollegale nel pannello Collegamenti
@msg linked type areas past VectorCraft's limits were left out le aree di testo concatenate oltre i limiti di VectorCraft sono state omesse
@msg type that changes writing direction inside a text object opens in the direction of its first character il testo che cambia direzione di scrittura all’interno di un oggetto di testo si apre nella direzione del suo primo carattere
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -4462,6 +4462,7 @@ swatch Bright Violet ビビッドバイオレット
@msg an image VectorCraft can't read was left out VectorCraft で読み込めない画像は除外されました
@msg graphs come in as plain art, without their data or settings グラフは通常のアートとして読み込まれ、データと設定は保持されません
@msg an image whose data is cut short was left out データが途中で切れている画像は除外されました
@msg an embedded image of {_1} × {_2} pixels was reduced to {_3} × {_4} pixels to keep the layers レイヤーを保持するため、{_1} × {_2} ピクセルの埋め込み画像を {_3} × {_4} ピクセルに縮小しました
@msg linked pictures show the low-resolution preview saved in the file until their files are found: relink them in the Links panel リンク画像は、元のファイルが見つかるまで、ファイルに保存された低解像度のプレビューで表示されます:リンクパネルで再リンクしてください
@msg linked type areas past VectorCraft's limits were left out VectorCraft の上限を超えるリンクされたテキストエリアは省略されました
@msg type that changes writing direction inside a text object opens in the direction of its first character テキストオブジェクト内で書字方向が変わる文字は、最初の文字の方向で開きます
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -4472,6 +4472,7 @@ font-technology Variable ცვლადი
@msg an image VectorCraft can't read was left out გამოსახულება, რომელსაც VectorCraft ვერ კითხულობს, გამოტოვებულია
@msg graphs come in as plain art, without their data or settings დიაგრამები შემოდის როგორც ჩვეულებრივი გრაფიკა, მათი მონაცემებისა და პარამეტრების გარეშე
@msg an image whose data is cut short was left out გამოსახულება, რომლის მონაცემებიც შეკვეცილია, გამოტოვებულია
@msg an embedded image of {_1} × {_2} pixels was reduced to {_3} × {_4} pixels to keep the layers ჩაშენებული გამოსახულება ზომით {_1} × {_2} პიქსელი შემცირდა {_3} × {_4} პიქსელამდე, რომ შრეები შენარჩუნდეს
@msg its editing data doesn't say where its art is მისი რედაქტირების მონაცემები არ მიუთითებს, სად არის მისი ნახატი
@msg this file was saved without PDF compatibility, and its editing data can't be read ({why}) ეს ფაილი PDF-თავსებადობის გარეშეა შენახული და მისი რედაქტირების მონაცემების წაკითხვა ვერ ხერხდება ({why})
@msg the PDF part couldn't be read ({_1}): the file opened from its editing data PDF ნაწილის წაკითხვა ვერ მოხერხდა ({_1}): ფაილი რედაქტირების მონაცემებიდან გაიხსნა
Can't render this file because it is too large.
+1
View File
@@ -4389,6 +4389,7 @@ theme Appearance 외관
@msg an image VectorCraft can't read was left out VectorCraft가 읽을 수 없는 이미지는 제외했습니다
@msg graphs come in as plain art, without their data or settings 그래프는 데이터와 설정 없이 일반 아트로 가져왔습니다
@msg an image whose data is cut short was left out 데이터가 잘린 이미지는 제외했습니다
@msg an embedded image of {_1} × {_2} pixels was reduced to {_3} × {_4} pixels to keep the layers 레이어를 유지하기 위해 {_1} × {_2} 픽셀의 포함된 이미지를 {_3} × {_4} 픽셀로 줄였습니다
@msg linked pictures show the low-resolution preview saved in the file until their files are found: relink them in the Links panel 연결된 그림은 해당 파일을 찾을 때까지 파일에 저장된 저해상도 미리 보기로 표시됩니다: 링크 패널에서 다시 연결하세요
@msg linked type areas past VectorCraft's limits were left out VectorCraft 한도를 넘는 연결된 문자 영역은 제외되었습니다
@msg type that changes writing direction inside a text object opens in the direction of its first character 문자 개체 안에서 쓰기 방향이 바뀌는 문자는 첫 글자의 방향으로 열립니다
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -4680,6 +4680,7 @@ imageTrace Outline Contour
@msg gradient “{name}” isn't defined in the file: a plain colour stands in for it verloop „{name}” is niet gedefinieerd in het bestand: er is een effen kleur voor gebruikt
@msg an image VectorCraft can't read was left out een afbeelding die VectorCraft niet kan lezen, is weggelaten
@msg an image whose data is cut short was left out een afbeelding waarvan de gegevens zijn afgekapt, is weggelaten
@msg an embedded image of {_1} × {_2} pixels was reduced to {_3} × {_4} pixels to keep the layers een ingesloten afbeelding van {_1} × {_2} pixels is verkleind tot {_3} × {_4} pixels om de lagen te behouden
@msg linked pictures show the low-resolution preview saved in the file until their files are found: relink them in the Links panel gekoppelde afbeeldingen tonen het in het bestand opgeslagen voorbeeld met lage resolutie totdat hun bestanden zijn gevonden: koppel ze opnieuw in het paneel Koppelingen
@msg linked type areas past VectorCraft's limits were left out gekoppelde tekstvlakken buiten de limieten van VectorCraft zijn weggelaten
@msg type that changes writing direction inside a text object opens in the direction of its first character tekst die binnen een tekstobject van schrijfrichting verandert, wordt geopend in de richting van het eerste teken
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -4870,6 +4870,7 @@ imageTrace Outline Kontur
@msg an image VectorCraft can't read was left out pominięto obraz, którego VectorCraft nie potrafi odczytać
@msg graphs come in as plain art, without their data or settings wykresy są wczytywane jako zwykła grafika, bez ich danych i ustawień
@msg an image whose data is cut short was left out pominięto obraz z uciętymi danymi
@msg an embedded image of {_1} × {_2} pixels was reduced to {_3} × {_4} pixels to keep the layers osadzony obraz o wymiarach {_1} × {_2} pikseli zmniejszono do {_3} × {_4} pikseli, aby zachować warstwy
@msg linked pictures show the low-resolution preview saved in the file until their files are found: relink them in the Links panel połączone obrazy pokazują zapisany w pliku podgląd niskiej rozdzielczości, dopóki ich pliki nie zostaną znalezione: połącz je ponownie w panelu Łącza
@msg linked type areas past VectorCraft's limits were left out połączone obszary tekstu poza limitami VectorCraft zostały pominięte
@msg type that changes writing direction inside a text object opens in the direction of its first character tekst zmieniający kierunek pisania wewnątrz obiektu tekstowego otwiera się w kierunku swojego pierwszego znaku
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -4315,6 +4315,7 @@ axis Both Ambos
@msg an image VectorCraft can't read was left out uma imagem que o VectorCraft não consegue ler foi omitida
@msg graphs come in as plain art, without their data or settings os gráficos são importados como arte comum, sem seus dados nem configurações
@msg an image whose data is cut short was left out uma imagem com dados incompletos foi omitida
@msg an embedded image of {_1} × {_2} pixels was reduced to {_3} × {_4} pixels to keep the layers uma imagem incorporada de {_1} × {_2} pixels foi reduzida para {_3} × {_4} pixels para manter as camadas
@msg its editing data doesn't say where its art is seus dados de edição não indicam onde está a arte
@msg this file was saved without PDF compatibility, and its editing data can't be read ({why}) este arquivo foi salvo sem compatibilidade com PDF e seus dados de edição não podem ser lidos ({why})
@msg the PDF part couldn't be read ({_1}): the file opened from its editing data não foi possível ler a parte PDF ({_1}): o arquivo foi aberto a partir dos seus dados de edição
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -4773,6 +4773,7 @@ axis Both Оба
@msg an image VectorCraft can't read was left out изображение, которое VectorCraft не может прочитать, пропущено
@msg graphs come in as plain art, without their data or settings диаграммы импортируются как обычная графика, без их данных и настроек
@msg an image whose data is cut short was left out изображение с обрезанными данными пропущено
@msg an embedded image of {_1} × {_2} pixels was reduced to {_3} × {_4} pixels to keep the layers встроенное изображение {_1} × {_2} пикселей уменьшено до {_3} × {_4} пикселей, чтобы сохранить слои
@msg linked pictures show the low-resolution preview saved in the file until their files are found: relink them in the Links panel связанные изображения показываются как сохранённый в файле просмотр низкого разрешения, пока их файлы не найдены: восстановите связи на панели «Связи»
@msg linked type areas past VectorCraft's limits were left out связанные текстовые области за пределами ограничений VectorCraft пропущены
@msg type that changes writing direction inside a text object opens in the direction of its first character текст, меняющий направление письма внутри текстового объекта, открывается в направлении своего первого символа
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -4635,6 +4635,7 @@ axis Both Båda
@msg an image VectorCraft can't read was left out en bild som VectorCraft inte kan läsa utelämnades
@msg graphs come in as plain art, without their data or settings diagram läses in som vanlig grafik, utan sina data och inställningar
@msg an image whose data is cut short was left out en bild med avkortade data utelämnades
@msg an embedded image of {_1} × {_2} pixels was reduced to {_3} × {_4} pixels to keep the layers en inbäddad bild på {_1} × {_2} pixlar förminskades till {_3} × {_4} pixlar för att behålla lagren
@msg linked pictures show the low-resolution preview saved in the file until their files are found: relink them in the Links panel länkade bilder visar förhandsgranskningen med låg upplösning som sparats i filen tills deras filer hittas: länka om dem i panelen Länkar
@msg linked type areas past VectorCraft's limits were left out länkade textområden utöver VectorCrafts gränser utelämnades
@msg type that changes writing direction inside a text object opens in the direction of its first character text som byter skrivriktning inuti ett textobjekt öppnas i riktningen för sitt första tecken
Can't render this file because it contains an unexpected character in line 5 and column 25.
+1
View File
@@ -4473,6 +4473,7 @@ axis Both Her İkisi
@msg an image VectorCraft can't read was left out VectorCraft'ın okuyamadığı bir resim dışarıda bırakıldı
@msg graphs come in as plain art, without their data or settings grafikler, verileri ve ayarları olmadan düz çizim olarak içe aktarılır
@msg an image whose data is cut short was left out verileri kısa kesilen bir resim dışarıda bırakıldı
@msg an embedded image of {_1} × {_2} pixels was reduced to {_3} × {_4} pixels to keep the layers katmanları korumak için {_1} × {_2} piksellik gömülü bir resim {_3} × {_4} piksele küçültüldü
@msg linked pictures show the low-resolution preview saved in the file until their files are found: relink them in the Links panel bağlantılı resimler, dosyaları bulunana kadar dosyaya kaydedilen düşük çözünürlüklü önizlemeyi gösterir: bunları Bağlantılar panelinde yeniden bağlayın
@msg linked type areas past VectorCraft's limits were left out VectorCraft sınırlarını aşan bağlantılı metin alanları dışarıda bırakıldı
@msg type that changes writing direction inside a text object opens in the direction of its first character bir metin nesnesi içinde yazı yönünü değiştiren metin, ilk karakterinin yönünde açılır
Can't render this file because it contains an unexpected character in line 5 and column 25.
+1
View File
@@ -4762,6 +4762,7 @@ axis Both Обидві
@msg an image VectorCraft can't read was left out зображення, яке VectorCraft не може прочитати, пропущено
@msg graphs come in as plain art, without their data or settings діаграми імпортуються як звичайна графіка, без їхніх даних і налаштувань
@msg an image whose data is cut short was left out зображення з обрізаними даними пропущено
@msg an embedded image of {_1} × {_2} pixels was reduced to {_3} × {_4} pixels to keep the layers вбудоване зображення {_1} × {_2} пікселів зменшено до {_3} × {_4} пікселів, щоб зберегти шари
@msg linked pictures show the low-resolution preview saved in the file until their files are found: relink them in the Links panel пов’язані зображення показуються як збережений у файлі перегляд низької роздільності, доки їхні файли не знайдено: відновіть зв’язки на панелі «Зв’язки»
@msg linked type areas past VectorCraft's limits were left out пов’язані текстові області понад обмеження VectorCraft пропущено
@msg type that changes writing direction inside a text object opens in the direction of its first character текст, що змінює напрямок письма всередині текстового об’єкта, відкривається в напрямку свого першого символу
Can't render this file because it contains an unexpected character in line 182 and column 5.
+1
View File
@@ -4400,6 +4400,7 @@ theme Appearance Giao diện
@msg an image VectorCraft can't read was left out một ảnh VectorCraft không đọc được đã bị bỏ qua
@msg graphs come in as plain art, without their data or settings biểu đồ được nhập dưới dạng hình vẽ thường, không kèm dữ liệu và thiết lập của chúng
@msg an image whose data is cut short was left out một ảnh bị cắt ngắn dữ liệu đã bị bỏ qua
@msg an embedded image of {_1} × {_2} pixels was reduced to {_3} × {_4} pixels to keep the layers một ảnh nhúng {_1} × {_2} điểm ảnh đã được thu nhỏ còn {_3} × {_4} điểm ảnh để giữ các lớp
@msg an object can't be read không thể đọc một đối tượng
@msg an object in perspective would leave the edited grid đối tượng trong phối cảnh sẽ rời khỏi lưới đang chỉnh sửa
@msg An object takes one 3D effect. Edit the existing effect's options, or remove it first. Một đối tượng chỉ nhận một hiệu ứng 3D. Hãy chỉnh sửa tùy chọn của hiệu ứng hiện có hoặc xóa nó trước.
Can't render this file because it contains an unexpected character in line 204 and column 5.
+1
View File
@@ -4186,6 +4186,7 @@ imageTrace Outline 轮廓
@msg an image VectorCraft can't read was left out 已略去 VectorCraft 无法读取的图像
@msg graphs come in as plain art, without their data or settings 图表作为普通图稿导入,不保留其数据和设置
@msg an image whose data is cut short was left out 已略去数据不完整的图像
@msg an embedded image of {_1} × {_2} pixels was reduced to {_3} × {_4} pixels to keep the layers 为保留图层,{_1} × {_2} 像素的嵌入图像已缩小为 {_3} × {_4} 像素
@msg gradient “{name}” isn't defined in the file: a plain colour stands in for it 文件中未定义渐变“{name}”:已改用纯色代替
@msg brushes VectorCraft can't read yet were left out of the Brushes panel ({_1}) VectorCraft 尚无法读取的画笔未加入“画笔”面板 ({_1})
@msg objects with several fills or strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet 带有多个填色或描边、效果或画笔的对象已按其绘制外观导入 ({_1}):其外观尚不可编辑
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2037,6 +2037,7 @@ file-filter Templates 範本
@msg an image VectorCraft can't read was left out 已略過 VectorCraft 無法讀取的影像
@msg graphs come in as plain art, without their data or settings 圖表以一般圖稿匯入,不保留其資料與設定
@msg an image whose data is cut short was left out 已略過資料不完整的影像
@msg an embedded image of {_1} × {_2} pixels was reduced to {_3} × {_4} pixels to keep the layers 為保留圖層,{_1} × {_2} 像素的內嵌影像已縮小為 {_3} × {_4} 像素
@msg gradient “{name}” isn't defined in the file: a plain colour stands in for it 檔案中未定義漸層「{name}」:已改用單色替代
@msg brushes VectorCraft can't read yet were left out of the Brushes panel ({_1}) VectorCraft 尚無法讀取的筆刷未加入「筆刷」面板 ({_1})
@msg objects with several fills or strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet 具有多個填色或筆畫、效果或筆刷的物件已依其繪製外觀匯入 ({_1}):其外觀目前尚無法編輯
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -1323,6 +1323,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
`document.open`, `file.place` and `document.pdfInfo` reject a `cropTo`, `crop`, `password`, `colorMode` or `textAs` that is not a string, such as `"textAs": true`, and `document.pdfInfo` rejects a `thumbnailSize` that is not a number; the error names the param, and nothing opens or is placed. A param left out or given as null keeps its default (`crates/engine/src/cmd/fileio/tests_pdfimport.rs`).
When the PDF content of a `.ai` has a page for each artboard, the layers are compared with every page on its artboard (until the pages compared take 8,388,608 pixels at the comparison's scale, about 67 letter-size pages, or number 128): art that only the layers or only a later page draws is handled as art that differs on the first page is, and a text object that a later page doesn't draw is left out unless its layer doesn't print (`crates/eps/src/import/tests_native.rs`).
When the editing data of a `.ai` or EPS is for a page of another size than the file's, the warning that its layers weren't read gives both sizes in points, and the scale of the editing data's page to the file's page (1:10 for a tenth of its size) when their sides are in proportion (#2034; `crates/eps/src/import/tests_native.rs`).
A `.ai` or EPS opened from its editing data keeps its layers when it embeds an image of more than 2^25 pixels (part of #1479). The image comes in at full size up to a target size (2^26 to 2^28 pixels on the desktop, by installed memory; 2^27 on the web), a 1-bit gray image as a 1-bit PNG. A larger image is averaged down by the smallest whole step that fits, a 1-bit one into shades of gray, with a note giving both sizes. An image of more than 2^31 pixels or 512 MiB of samples is an object the reader can't read, so its box comes in as the page's art; without page art, it is left out with a note (`crates/eps/src/import/tests_native.rs`).
A `.ai`, or a PDF saved with Illustrator's editing data, whose editing data can't be decompressed opens from its PDF part with a warning that its layers weren't read and why, and an export or Save As to its own path asks first (`acknowledgeLoss`), as for a file whose layers the reader can't read (`crates/eps/src/import/tests_native.rs`, `crates/engine/src/cmd/fileio/tests_aiimport.rs`).
A `.ai` or EPS opened from its editing data with one object on a shown layer that the reader can't read (an unknown operator inside a group, a pattern fill whose pattern it doesn't have, a symbol instance, a placed file without a usable preview) keeps its layers and groups: in that object's place, its layer gets a group of the page's objects inside the object's box, named after it, with a warning that lists what came in as the page's art and an export or Save As to its own path asking first (`acknowledgeLoss`). The file still opens from its page or PDF part when no box can be found, when live art or another unread object shares the page art inside it, when the object spans two artboards or lies on an artboard that has no page, when no page art lies inside its box, when that page art is inside a page group with a clip, mask, transparency or appearance, when the object holds a frame of threaded type, when the page art uses a pattern, image or swatch that the page lacks or that differs from the editing data's, or when the page art doesn't carry the object's transparency (#4806; `crates/eps/src/import/tests_native.rs`, `crates/engine/src/cmd/fileio/tests_aiimport.rs`).
A `.ai` or EPS opened from its editing data with one object on a shown layer that the reader can't read (an unknown operator inside a group, a pattern fill whose pattern it doesn't have, a symbol instance, a placed file without a usable preview) keeps its layers and groups: in that object's place, its layer gets a group of the page's objects inside the object's box, named after it, with a warning that lists what came in as the page's art and an export or Save As to its own path asking first (`acknowledgeLoss`). The file still opens from its page or PDF part when no box can be found, when live art or another unread object shares the page art inside it, when the object spans two artboards, or when the page art doesn't carry the object's transparency (#4806; `crates/eps/src/import/tests_native.rs`, `crates/engine/src/cmd/fileio/tests_aiimport.rs`).