mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-29 04:07:12 -06:00
Clarify EPS import and interpreter stages for strict Clippy
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
use super::containers::close;
|
||||
use super::document::TEXT_SLOT;
|
||||
use super::types::{FLIP, IMAGE_TOO_LARGE, LINKED_PREVIEWS, MAX_PIXELS, MAX_POINTS, MAX_SIDE, NON_NATIVE_ART, NON_NATIVE_ART_UNREAD, Place};
|
||||
use super::*;
|
||||
use super::{
|
||||
Affine, Appearance, AppearanceItem, Arc, BezPath, CANVAS_CENTRE, FillLayer, ImageBlob, ImageObject, Inks, Instance, LinkInfo, Node, NodeKind,
|
||||
Obj, OpacityMask, Paint, PathData, Point, Reader, Rect, StrokeLayer, V, ps,
|
||||
};
|
||||
|
||||
impl<'a> Reader<'a> {
|
||||
/// A text object (`/AI11Text`): an empty group standing for it, named after its story.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::document::slot_of;
|
||||
use super::ops::{paints_apart, path_rule};
|
||||
use super::types::{Frame, GState, Kind, MAX_DEPTH, MAX_NODES};
|
||||
use super::*;
|
||||
use super::{AppearanceItem, Arc, BezPath, BlendMode, FillRule, HashSet, LayerColor, Node, NodeId, NodeKind, Obj, Paint, PathEl, Rc, Reader, V, obj};
|
||||
|
||||
impl<'a> Reader<'a> {
|
||||
// ---- frames ----------------------------------------------------------------------------
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::art::{four, sane, two};
|
||||
use super::types::{FLIP, Kind, MAX_HEX, MAX_PATTERNS, MAX_STACK, PatternHead, Place, Raster, SKIPPED};
|
||||
use super::*;
|
||||
use super::{Arc, Node, PatternDef, Reader, Rect, Tok, V, marker};
|
||||
|
||||
impl<'a> Reader<'a> {
|
||||
pub(super) fn push(&mut self, v: V) -> Result<(), String> {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::art::{element_name, elements, join_and, sane};
|
||||
use super::containers::drop_commented_copies;
|
||||
use super::types::{FLIP, Kind, MAX_DEPTH};
|
||||
use super::*;
|
||||
use super::{Affine, Artboard, ColorMode, Document, NodeKind, Obj, OpacityMask, Paint, Point, Reader, Rect, Structure, Swatch, Unit, V, obj};
|
||||
|
||||
/// `%AI5_RulerUnits` → [`Unit`] (Illustrator 7 spec table 3; centimeters is 4; pixels is 6 in later files).
|
||||
fn ruler_unit(v: u8) -> Option<Unit> {
|
||||
|
||||
@@ -1,11 +1,53 @@
|
||||
use super::containers::close;
|
||||
use super::document::slot_of;
|
||||
use super::types::{BLENDS, FLIP, Frame, IGNORED, Kind, MAX_DEPTH};
|
||||
use super::*;
|
||||
use super::{
|
||||
Affine, Appearance, AppearanceItem, Arc, BlendMode, Dash, FillLayer, FillRule, Gradient, GradientDef, HashMap, HashSet, Instance, Knockout,
|
||||
LayerColor, LineCap, LineJoin, Node, NodeId, NodeKind, Paint, Reader, Rect, StrokeLayer, V, obj, paint,
|
||||
};
|
||||
|
||||
impl<'a> Reader<'a> {
|
||||
// ---- operators -------------------------------------------------------------------------
|
||||
|
||||
fn end_array(&mut self) -> Result<(), String> {
|
||||
// The innermost `[` still on the stack (the stack is cut back by operators).
|
||||
let mut at = self.stack.len();
|
||||
while let Some(m) = self.marks.pop() {
|
||||
if matches!(self.stack.get(m), Some(V::Mark)) {
|
||||
at = m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
let items: Vec<V> = self.stack.split_off(at).into_iter().skip(1).collect();
|
||||
self.push(V::Arr(items))
|
||||
}
|
||||
|
||||
fn add_object_entry(&mut self) {
|
||||
if let Some(Frame { kind: Kind::Obj(o), base, .. }) = self.frames.last_mut() {
|
||||
obj::add_entry(o, &mut self.stack, *base);
|
||||
// Binary data in ASCII85 follows, up to its `~>`: a `/Binary` dictionary's
|
||||
// (`/ASCII85Decode ,`) or a non-native art object's (`/ForeignObject`'s
|
||||
// `/Data ,`, the PDF it keeps), in comment lines. ASCII85 has `_`, so a line
|
||||
// of it may start with `%_` and read as hidden tokens: a `(` there would open
|
||||
// a string that swallows the rest of the layer.
|
||||
let key = o.entries.last().and_then(|(k, _)| k.as_deref());
|
||||
if o.ty == "Binary" && key == Some("ASCII85Decode") {
|
||||
self.lex.skip_past(b"~>");
|
||||
} else if o.ty == "ForeignObject" && key == Some("Data") {
|
||||
self.foreign_data = Some(self.lex.skip_past(b"~>").to_vec());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn open_mask_object(&mut self, hidden: bool) -> Result<(), String> {
|
||||
if !matches!(self.frames.last(), Some(Frame { kind: Kind::Obj(o), .. }) if o.ty == "Mask") {
|
||||
// `X=` also introduces legacy text frame art, read by `text_frames`.
|
||||
return Ok(());
|
||||
}
|
||||
self.end_path_quietly();
|
||||
self.open(Kind::Mask, hidden)
|
||||
}
|
||||
|
||||
pub(super) fn op(&mut self, w: &str, hidden: bool) -> Result<(), String> {
|
||||
if self.blend_record.is_some() {
|
||||
self.end_blend();
|
||||
@@ -18,45 +60,14 @@ impl<'a> Reader<'a> {
|
||||
self.marks.push(self.stack.len());
|
||||
return self.push(V::Mark);
|
||||
}
|
||||
"]" => {
|
||||
// The innermost `[` still on the stack (the stack is cut back by operators).
|
||||
let mut at = self.stack.len();
|
||||
while let Some(m) = self.marks.pop() {
|
||||
if matches!(self.stack.get(m), Some(V::Mark)) {
|
||||
at = m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
let items: Vec<V> = self.stack.split_off(at).into_iter().skip(1).collect();
|
||||
return self.push(V::Arr(items));
|
||||
}
|
||||
"]" => return self.end_array(),
|
||||
":" => return self.begin_obj(hidden),
|
||||
"," => {
|
||||
if let Some(Frame { kind: Kind::Obj(o), base, .. }) = self.frames.last_mut() {
|
||||
obj::add_entry(o, &mut self.stack, *base);
|
||||
// Binary data in ASCII85 follows, up to its `~>`: a `/Binary` dictionary's
|
||||
// (`/ASCII85Decode ,`) or a non-native art object's (`/ForeignObject`'s
|
||||
// `/Data ,`, the PDF it keeps), in comment lines. ASCII85 has `_`, so a line
|
||||
// of it may start with `%_` and read as hidden tokens: a `(` there would open
|
||||
// a string that swallows the rest of the layer.
|
||||
let key = o.entries.last().and_then(|(k, _)| k.as_deref());
|
||||
if o.ty == "Binary" && key == Some("ASCII85Decode") {
|
||||
self.lex.skip_past(b"~>");
|
||||
} else if o.ty == "ForeignObject" && key == Some("Data") {
|
||||
self.foreign_data = Some(self.lex.skip_past(b"~>").to_vec());
|
||||
}
|
||||
}
|
||||
self.add_object_entry();
|
||||
return Ok(());
|
||||
}
|
||||
";" => return self.end_obj(),
|
||||
"X=" => {
|
||||
if !matches!(self.frames.last(), Some(Frame { kind: Kind::Obj(o), .. }) if o.ty == "Mask") {
|
||||
// `X=` also introduces legacy text frame art, read by `text_frames`.
|
||||
return Ok(());
|
||||
}
|
||||
self.end_path_quietly();
|
||||
return self.open(Kind::Mask, hidden);
|
||||
}
|
||||
"X=" => return self.open_mask_object(hidden),
|
||||
"X+" if self.frames.iter().any(|f| matches!(f.kind, Kind::Mask)) => return self.end_mask(),
|
||||
"X+" => return Ok(()),
|
||||
_ => {}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Affine, BlendMode, Dash, FillRule, Knockout, LayerColor, LineCap, LineJoin, Node, Obj, Paint, Rect, V};
|
||||
|
||||
/// Deepest nesting of layers, groups and dictionaries.
|
||||
pub(super) const MAX_DEPTH: usize = 256;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use super::ops::MAX_DATA;
|
||||
use super::*;
|
||||
use super::{
|
||||
DecodeSettings, Decoder, DecoderContext, DictRef, EncodingMode, ImageBlob, Key, Obj, PsError, Rc, Res, find, hex_decode, jpeg_components, ps,
|
||||
ps_err,
|
||||
};
|
||||
|
||||
/// A file object: the program itself, or data a filter decoded.
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use super::filters::{Filter, Predictor, Stream, decode, fax_settings, io};
|
||||
use super::pixels::{Pixels, Spec, by_rows, by_samples, masked, source};
|
||||
use super::*;
|
||||
use super::{
|
||||
Affine, Color, DictRef, HashMap, ImageBlob, ImageObject, Inks, Interp, Key, Lexer, Node, NodeId, NodeKind, Obj, Op, OpacityMask, Paint, PsError,
|
||||
Rc, RefCell, Res, Shared, Space, find, matrix_of, process, ps_err,
|
||||
};
|
||||
|
||||
/// Most bytes one filter or image reads.
|
||||
pub(super) const MAX_DATA: usize = 1 << 28;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::ops::Source;
|
||||
use super::*;
|
||||
use super::{Affine, Obj, Res, Space, ps_err};
|
||||
|
||||
/// An image to decode.
|
||||
pub(super) struct Spec {
|
||||
|
||||
@@ -1,10 +1,100 @@
|
||||
use super::paint::is_mesh;
|
||||
use super::state::{GState, Space};
|
||||
use super::*;
|
||||
use super::{
|
||||
Affine, Color, FillRule, Interp, Key, LineCap, LineJoin, MAX_DASH_ENTRIES, MAX_DASHES, MAX_GSAVE, MAX_PATH, Obj, Op, Paint, Point, PsError, Rc,
|
||||
Res, Shape, Vec2, matrix_obj, ps_err,
|
||||
};
|
||||
|
||||
impl Interp<'_> {
|
||||
// ---------- the operators ----------
|
||||
|
||||
fn replace_path_with_stroke_outline(&mut self) -> Res {
|
||||
let bp = self.take_path();
|
||||
let mut st = self.stroke_layer()?;
|
||||
// A dash pattern far finer than the path would make countless pieces.
|
||||
let period: f64 = st.dash.as_ref().map_or(0.0, |d| d.pattern.iter().sum());
|
||||
let periods = if st.dash.is_some() && period > 0.0 { bp.perimeter(1e-3) / period } else { 0.0 };
|
||||
if st.dash.is_some() && !(period > 0.0 && periods < MAX_DASHES) {
|
||||
st.dash = None;
|
||||
}
|
||||
let tol = (0.01 / self.scale().max(1e-9)).clamp(1e-4, 1.0);
|
||||
let outline = vectorcraft_effects::stroke::line_outline(&bp, &st, st.width, tol);
|
||||
// The path, each entry of the dash pattern in each period, and the outline count as elements.
|
||||
let dashes = st.dash.as_ref().map_or(0.0, |d| periods * d.pattern.len() as f64) as usize;
|
||||
self.spend_elements(bp.elements().len().saturating_add(dashes).saturating_add(outline.elements().len()))?;
|
||||
if outline.elements().len() >= MAX_PATH {
|
||||
return Err(PsError::Limit("a path has too many points"));
|
||||
}
|
||||
self.g.cur = outline.elements().iter().rev().find_map(|e| e.end_point());
|
||||
self.g.start = self.g.cur;
|
||||
self.g.path = outline;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn push_hsb_color(&mut self) -> Res {
|
||||
let [r, g, b] = self.current_rgb();
|
||||
let (max, min) = (r.max(g).max(b), r.min(g).min(b));
|
||||
let d = max - min;
|
||||
let h = if d <= 0.0 {
|
||||
0.0
|
||||
} else if max == r {
|
||||
((g - b) / d).rem_euclid(6.0) / 6.0
|
||||
} else if max == g {
|
||||
((b - r) / d + 2.0) / 6.0
|
||||
} else {
|
||||
((r - g) / d + 4.0) / 6.0
|
||||
};
|
||||
let s = if max > 0.0 { d / max } else { 0.0 };
|
||||
for v in [h, s, max] {
|
||||
self.push_num(f64::from(v))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_current_color(&mut self) -> Res {
|
||||
if let Space::Pattern(base) = &*self.g.space {
|
||||
self.set_pattern(base.clone())?;
|
||||
} else {
|
||||
let mut components = vec![0.0; self.g.space.n()];
|
||||
for slot in components.iter_mut().rev() {
|
||||
*slot = self.pop_num()?;
|
||||
}
|
||||
self.set_comps(components)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transform_point(&mut self, op: Op) -> Res {
|
||||
let m = match self.stack.last() {
|
||||
Some(Obj::Array { .. }) => self.pop_matrix()?,
|
||||
_ => self.g.ctm,
|
||||
};
|
||||
let [x, y] = self.nums()?;
|
||||
let inverse = matches!(op, Op::ITransform | Op::IDTransform);
|
||||
if inverse && m.determinant().abs() < 1e-12 {
|
||||
return ps_err("undefinedresult", "");
|
||||
}
|
||||
let m = if inverse { m.inverse() } else { m };
|
||||
let p = if matches!(op, Op::DTransform | Op::IDTransform) {
|
||||
let v = m * Point::new(x, y) - m * Point::ZERO;
|
||||
Point::new(v.x, v.y)
|
||||
} else {
|
||||
m * Point::new(x, y)
|
||||
};
|
||||
self.push_num(p.x)?;
|
||||
self.push_num(p.y)
|
||||
}
|
||||
|
||||
// `[/Pattern <current space>] setcolorspace setcolor`, unless already in a pattern space.
|
||||
fn set_pattern_color(&mut self) -> Res {
|
||||
if !matches!(*self.g.space, Space::Pattern(_)) {
|
||||
self.g.space = Rc::new(Space::Pattern(Some(self.g.space.clone())));
|
||||
self.g.space_obj = Obj::array(vec![Obj::name("Pattern"), self.g.space_obj.clone()]);
|
||||
}
|
||||
let Space::Pattern(base) = &*self.g.space else { return ps_err("typecheck", "") };
|
||||
self.set_pattern(base.clone())
|
||||
}
|
||||
|
||||
pub fn graphics_op(&mut self, op: Op) -> Res {
|
||||
use Op::*;
|
||||
match op {
|
||||
@@ -26,28 +116,7 @@ impl Interp<'_> {
|
||||
self.g.clips.clear();
|
||||
}
|
||||
ExecForm => self.exec_form()?,
|
||||
StrokePath => {
|
||||
let bp = self.take_path();
|
||||
let mut st = self.stroke_layer()?;
|
||||
// A dash pattern far finer than the path would make countless pieces.
|
||||
let period: f64 = st.dash.as_ref().map_or(0.0, |d| d.pattern.iter().sum());
|
||||
let periods = if st.dash.is_some() && period > 0.0 { bp.perimeter(1e-3) / period } else { 0.0 };
|
||||
if st.dash.is_some() && !(period > 0.0 && periods < MAX_DASHES) {
|
||||
st.dash = None;
|
||||
}
|
||||
let tol = (0.01 / self.scale().max(1e-9)).clamp(1e-4, 1.0);
|
||||
let outline = vectorcraft_effects::stroke::line_outline(&bp, &st, st.width, tol);
|
||||
// The path, each entry of the dash pattern in each period, and the outline count
|
||||
// as elements.
|
||||
let dashes = st.dash.as_ref().map_or(0.0, |d| periods * d.pattern.len() as f64) as usize;
|
||||
self.spend_elements(bp.elements().len().saturating_add(dashes).saturating_add(outline.elements().len()))?;
|
||||
if outline.elements().len() >= MAX_PATH {
|
||||
return Err(PsError::Limit("a path has too many points"));
|
||||
}
|
||||
self.g.cur = outline.elements().iter().rev().find_map(|e| e.end_point());
|
||||
self.g.start = self.g.cur;
|
||||
self.g.path = outline;
|
||||
}
|
||||
StrokePath => self.replace_path_with_stroke_outline()?,
|
||||
PathForAll => self.path_for_all()?,
|
||||
UFill | UEoFill | UStroke => self.paint_user_path(op)?,
|
||||
UAppend => {
|
||||
@@ -58,24 +127,7 @@ impl Interp<'_> {
|
||||
SetBBox => {
|
||||
self.nums::<4>()?;
|
||||
}
|
||||
CurrentHsbColor => {
|
||||
let [r, g, b] = self.current_rgb();
|
||||
let (max, min) = (r.max(g).max(b), r.min(g).min(b));
|
||||
let d = max - min;
|
||||
let h = if d <= 0.0 {
|
||||
0.0
|
||||
} else if max == r {
|
||||
((g - b) / d).rem_euclid(6.0) / 6.0
|
||||
} else if max == g {
|
||||
((b - r) / d + 2.0) / 6.0
|
||||
} else {
|
||||
((r - g) / d + 4.0) / 6.0
|
||||
};
|
||||
let s = if max > 0.0 { d / max } else { 0.0 };
|
||||
for v in [h, s, max] {
|
||||
self.push_num(f64::from(v))?;
|
||||
}
|
||||
}
|
||||
CurrentHsbColor => self.push_hsb_color()?,
|
||||
CurrentColorRendering => {
|
||||
let d = crate::import::obj::Dict::from([(Key::name("ColorRenderingType"), Obj::Int(1))]);
|
||||
self.push(Obj::dict(d))?;
|
||||
@@ -201,32 +253,13 @@ impl Interp<'_> {
|
||||
};
|
||||
self.push(o)?;
|
||||
}
|
||||
SetColor => {
|
||||
if let Space::Pattern(base) = &*self.g.space {
|
||||
self.set_pattern(base.clone())?;
|
||||
} else {
|
||||
let n = self.g.space.n();
|
||||
let mut c = vec![0.0; n];
|
||||
for slot in c.iter_mut().rev() {
|
||||
*slot = self.pop_num()?;
|
||||
}
|
||||
self.set_comps(c)?;
|
||||
}
|
||||
}
|
||||
SetColor => self.set_current_color()?,
|
||||
CurrentColor => {
|
||||
for v in self.g.comps.clone() {
|
||||
self.push_num(v)?;
|
||||
}
|
||||
}
|
||||
// `[/Pattern <current space>] setcolorspace setcolor`, unless in a pattern space already.
|
||||
SetPattern => {
|
||||
if !matches!(*self.g.space, Space::Pattern(_)) {
|
||||
self.g.space = Rc::new(Space::Pattern(Some(self.g.space.clone())));
|
||||
self.g.space_obj = Obj::array(vec![Obj::name("Pattern"), self.g.space_obj.clone()]);
|
||||
}
|
||||
let Space::Pattern(base) = &*self.g.space else { return ps_err("typecheck", "") };
|
||||
self.set_pattern(base.clone())?;
|
||||
}
|
||||
SetPattern => self.set_pattern_color()?,
|
||||
MakePattern => {
|
||||
let m = self.pop_matrix()?;
|
||||
let d = self.pop_dict()?;
|
||||
@@ -347,26 +380,7 @@ impl Interp<'_> {
|
||||
None => self.g.ctm *= m,
|
||||
}
|
||||
}
|
||||
Transform | ITransform | DTransform | IDTransform => {
|
||||
let m = match self.stack.last() {
|
||||
Some(Obj::Array { .. }) => self.pop_matrix()?,
|
||||
_ => self.g.ctm,
|
||||
};
|
||||
let [x, y] = self.nums()?;
|
||||
let inverse = matches!(op, ITransform | IDTransform);
|
||||
if inverse && m.determinant().abs() < 1e-12 {
|
||||
return ps_err("undefinedresult", "");
|
||||
}
|
||||
let m = if inverse { m.inverse() } else { m };
|
||||
let p = if matches!(op, DTransform | IDTransform) {
|
||||
let v = m * Point::new(x, y) - m * Point::ZERO;
|
||||
Point::new(v.x, v.y)
|
||||
} else {
|
||||
m * Point::new(x, y)
|
||||
};
|
||||
self.push_num(p.x)?;
|
||||
self.push_num(p.y)?;
|
||||
}
|
||||
Transform | ITransform | DTransform | IDTransform => self.transform_point(op)?,
|
||||
InvertMatrix => {
|
||||
let out = self.pop_array()?;
|
||||
let m = self.pop_matrix()?;
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
use super::state::{GState, Space, process, tiling_hash};
|
||||
use super::*;
|
||||
use super::{
|
||||
Affine, Arc, Color, DictRef, FUNCTION_OPS, Gradient, GradientGeom, GradientKind, GradientPaint, GradientStop, HashSet, Interp, Key, LIMIT,
|
||||
MAX_APART, MAX_FUNCTION_VISITS, MAX_HIVAL, MAX_INKS, MAX_STITCH_STOPS, NESTED_PATTERNS, Node, NodeId, NodeKind, Obj, Paint, PatternDef, Point,
|
||||
PsError, REGISTRATION, Rc, Rect, Res, SHADING_SAMPLES, Shared, UNKNOWN_FUNCTIONS, UNKNOWN_SHADINGS, Vec2, clamp_between, ps_err,
|
||||
};
|
||||
|
||||
impl Interp<'_> {
|
||||
// ---------- colour ----------
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use super::state::{ccw_turn, turn};
|
||||
use super::*;
|
||||
use super::{
|
||||
Affine, BezPath, Dash, FillRule, HAIRLINE, Interp, Key, MAX_GSAVE, MAX_NEST, MAX_PATH, Obj, Op, PathEl, Point, PsError, Rect, Res, Shape,
|
||||
StrokeLayer, Vec2, deep_clips, ps_err,
|
||||
};
|
||||
|
||||
impl Interp<'_> {
|
||||
/// User space → document space.
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use super::*;
|
||||
use super::{
|
||||
Affine, Appearance, AppearanceItem, Arc, BezPath, Clip, Color, DefaultHasher, DictRef, Document, Drawn, FAR_AWAY, FillLayer, FillRule, Hash,
|
||||
HashMap, HashSet, Hasher, LIMIT, LineCap, LineJoin, MAX_DRAWN, MAX_NEST, MAX_NODES, Node, NodeId, NodeKind, Obj, Paint, PathData, Point, Rc,
|
||||
Rect, Shared, StrokeLayer, TOO_MUCH,
|
||||
};
|
||||
|
||||
/// A colour space.
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
+137
-126
@@ -502,6 +502,138 @@ impl<'a> Interp<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
fn arithmetic(&mut self, op: Op) -> Res {
|
||||
let (b, a) = (self.pop()?, self.pop()?);
|
||||
let integer = match (&a, &b) {
|
||||
(Obj::Int(x), Obj::Int(y)) => match op {
|
||||
Op::Add => x.checked_add(*y).map(Obj::Int),
|
||||
Op::Sub => x.checked_sub(*y).map(Obj::Int),
|
||||
_ => x.checked_mul(*y).map(Obj::Int),
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
let result = match integer {
|
||||
Some(value) => value,
|
||||
None => {
|
||||
let (x, y) = (a.as_num().ok_or(PsError::Ps("typecheck", String::new()))?, b.as_num().ok_or(PsError::Ps("typecheck", String::new()))?);
|
||||
let value = match op {
|
||||
Op::Add => x + y,
|
||||
Op::Sub => x - y,
|
||||
_ => x * y,
|
||||
};
|
||||
if !value.is_finite() {
|
||||
return ps_err("undefinedresult", "");
|
||||
}
|
||||
Obj::Real(value)
|
||||
}
|
||||
};
|
||||
self.push(result)
|
||||
}
|
||||
|
||||
fn math_function(&mut self, op: Op) -> Res {
|
||||
let result = match op {
|
||||
Op::Sqrt => self.pop_num()?.sqrt(),
|
||||
Op::Sin => self.pop_num()?.to_radians().sin(),
|
||||
Op::Cos => self.pop_num()?.to_radians().cos(),
|
||||
Op::Ln => self.pop_num()?.ln(),
|
||||
Op::Log => self.pop_num()?.log10(),
|
||||
Op::Exp => {
|
||||
let [base, exponent] = self.nums()?;
|
||||
base.powf(exponent)
|
||||
}
|
||||
_ => {
|
||||
let [num, den] = self.nums()?;
|
||||
num.atan2(den).to_degrees().rem_euclid(360.0)
|
||||
}
|
||||
};
|
||||
if !result.is_finite() {
|
||||
return ps_err("undefinedresult", "");
|
||||
}
|
||||
self.push(Obj::Real(result))
|
||||
}
|
||||
|
||||
fn logical_op(&mut self, op: Op) -> Res {
|
||||
let (b, a) = (self.pop()?, self.pop()?);
|
||||
let result = match (a, b) {
|
||||
(Obj::Bool(x), Obj::Bool(y)) => Obj::Bool(match op {
|
||||
Op::And => x && y,
|
||||
Op::Or => x || y,
|
||||
_ => x ^ y,
|
||||
}),
|
||||
(Obj::Int(x), Obj::Int(y)) => Obj::Int(match op {
|
||||
Op::And => x & y,
|
||||
Op::Or => x | y,
|
||||
_ => x ^ y,
|
||||
}),
|
||||
_ => return ps_err("typecheck", ""),
|
||||
};
|
||||
self.push(result)
|
||||
}
|
||||
|
||||
fn for_loop(&mut self) -> Res {
|
||||
let p = self.pop_proc()?;
|
||||
let (limit, inc, init) = (self.pop()?, self.pop()?, self.pop()?);
|
||||
match (&init, &inc, &limit) {
|
||||
(Obj::Int(i0), Obj::Int(d), Obj::Int(l)) => {
|
||||
let (mut i, d, l) = (*i0, *d, *l);
|
||||
while (d >= 0 && i <= l) || (d < 0 && i >= l) {
|
||||
self.push(Obj::Int(i))?;
|
||||
if !self.body(&p)? {
|
||||
break;
|
||||
}
|
||||
i = i.checked_add(d).ok_or(PsError::Ps("rangecheck", String::new()))?;
|
||||
if d == 0 {
|
||||
self.tick()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let n = |o: &Obj| o.as_num().filter(|v| v.is_finite()).ok_or(PsError::Ps("typecheck", String::new()));
|
||||
let (i0, d, l) = (n(&init)?, n(&inc)?, n(&limit)?);
|
||||
let mut k = 0.0;
|
||||
loop {
|
||||
let i = i0 + d * k;
|
||||
if !((d >= 0.0 && i <= l) || (d < 0.0 && i >= l)) {
|
||||
break;
|
||||
}
|
||||
self.push(Obj::Real(i))?;
|
||||
if !self.body(&p)? {
|
||||
break;
|
||||
}
|
||||
k += 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn search_string(&mut self, op: Op) -> Res {
|
||||
let seek = self.pop_str()?.to_vec();
|
||||
let s = self.pop_str()?;
|
||||
let text = s.to_vec();
|
||||
self.spend((text.len() / 64) as u64)?;
|
||||
let at = if op == Op::Search { super::lex::find(&text, &seek) } else { text.starts_with(&seek).then_some(0) };
|
||||
match at {
|
||||
Some(i) => {
|
||||
let o = Obj::Str(s);
|
||||
if op == Op::Search {
|
||||
self.push_interval(o.clone(), i + seek.len(), text.len() - i - seek.len())?;
|
||||
self.push_interval(o.clone(), i, seek.len())?;
|
||||
self.push_interval(o, 0, i)?;
|
||||
} else {
|
||||
self.push_interval(o.clone(), seek.len(), text.len() - seek.len())?;
|
||||
self.push_interval(o, 0, seek.len())?;
|
||||
}
|
||||
self.push(Obj::Bool(true))?;
|
||||
}
|
||||
None => {
|
||||
self.push(Obj::Str(s))?;
|
||||
self.push(Obj::Bool(false))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn op_inner(&mut self, op: Op) -> Res {
|
||||
use Op::*;
|
||||
match op {
|
||||
@@ -558,34 +690,7 @@ impl<'a> Interp<'a> {
|
||||
let n = self.stack.iter().rev().position(|o| matches!(o, Obj::Mark)).ok_or(PsError::Ps("unmatchedmark", String::new()))?;
|
||||
self.push(Obj::Int(n as i64))?;
|
||||
}
|
||||
Add | Sub | Mul => {
|
||||
let (b, a) = (self.pop()?, self.pop()?);
|
||||
let r = match (&a, &b) {
|
||||
(Obj::Int(x), Obj::Int(y)) => match op {
|
||||
Add => x.checked_add(*y).map(Obj::Int),
|
||||
Sub => x.checked_sub(*y).map(Obj::Int),
|
||||
_ => x.checked_mul(*y).map(Obj::Int),
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
let r = match r {
|
||||
Some(r) => r,
|
||||
None => {
|
||||
let (x, y) =
|
||||
(a.as_num().ok_or(PsError::Ps("typecheck", String::new()))?, b.as_num().ok_or(PsError::Ps("typecheck", String::new()))?);
|
||||
let r = match op {
|
||||
Add => x + y,
|
||||
Sub => x - y,
|
||||
_ => x * y,
|
||||
};
|
||||
if !r.is_finite() {
|
||||
return ps_err("undefinedresult", "");
|
||||
}
|
||||
Obj::Real(r)
|
||||
}
|
||||
};
|
||||
self.push(r)?;
|
||||
}
|
||||
Add | Sub | Mul => self.arithmetic(op)?,
|
||||
Div => {
|
||||
let [a, b] = self.nums()?;
|
||||
if b == 0.0 {
|
||||
@@ -603,27 +708,7 @@ impl<'a> Interp<'a> {
|
||||
Obj::Real(r) => self.push(Obj::Real(if op == Neg { -r } else { r.abs() }))?,
|
||||
_ => return ps_err("typecheck", ""),
|
||||
},
|
||||
Sqrt | Sin | Cos | Exp | Ln | Log | Atan => {
|
||||
let r = match op {
|
||||
Sqrt => self.pop_num()?.sqrt(),
|
||||
Sin => self.pop_num()?.to_radians().sin(),
|
||||
Cos => self.pop_num()?.to_radians().cos(),
|
||||
Ln => self.pop_num()?.ln(),
|
||||
Log => self.pop_num()?.log10(),
|
||||
Exp => {
|
||||
let [b, e] = self.nums()?;
|
||||
b.powf(e)
|
||||
}
|
||||
_ => {
|
||||
let [num, den] = self.nums()?;
|
||||
num.atan2(den).to_degrees().rem_euclid(360.0)
|
||||
}
|
||||
};
|
||||
if !r.is_finite() {
|
||||
return ps_err("undefinedresult", "");
|
||||
}
|
||||
self.push(Obj::Real(r))?;
|
||||
}
|
||||
Sqrt | Sin | Cos | Exp | Ln | Log | Atan => self.math_function(op)?,
|
||||
Round | Floor | Ceiling | Truncate => match self.pop()? {
|
||||
Obj::Int(i) => self.push(Obj::Int(i))?,
|
||||
Obj::Real(r) => self.push(Obj::Real(match op {
|
||||
@@ -678,23 +763,7 @@ impl<'a> Interp<'a> {
|
||||
};
|
||||
self.push(Obj::Bool(r))?;
|
||||
}
|
||||
And | Or | Xor => {
|
||||
let (b, a) = (self.pop()?, self.pop()?);
|
||||
let r = match (a, b) {
|
||||
(Obj::Bool(x), Obj::Bool(y)) => Obj::Bool(match op {
|
||||
And => x && y,
|
||||
Or => x || y,
|
||||
_ => x ^ y,
|
||||
}),
|
||||
(Obj::Int(x), Obj::Int(y)) => Obj::Int(match op {
|
||||
And => x & y,
|
||||
Or => x | y,
|
||||
_ => x ^ y,
|
||||
}),
|
||||
_ => return ps_err("typecheck", ""),
|
||||
};
|
||||
self.push(r)?;
|
||||
}
|
||||
And | Or | Xor => self.logical_op(op)?,
|
||||
Not => match self.pop()? {
|
||||
Obj::Bool(b) => self.push(Obj::Bool(!b))?,
|
||||
Obj::Int(i) => self.push(Obj::Int(!i))?,
|
||||
@@ -723,41 +792,7 @@ impl<'a> Interp<'a> {
|
||||
let pick = if self.pop_bool()? { yes } else { no };
|
||||
self.call(pick)?;
|
||||
}
|
||||
For => {
|
||||
let p = self.pop_proc()?;
|
||||
let (limit, inc, init) = (self.pop()?, self.pop()?, self.pop()?);
|
||||
match (&init, &inc, &limit) {
|
||||
(Obj::Int(i0), Obj::Int(d), Obj::Int(l)) => {
|
||||
let (mut i, d, l) = (*i0, *d, *l);
|
||||
while (d >= 0 && i <= l) || (d < 0 && i >= l) {
|
||||
self.push(Obj::Int(i))?;
|
||||
if !self.body(&p)? {
|
||||
break;
|
||||
}
|
||||
i = i.checked_add(d).ok_or(PsError::Ps("rangecheck", String::new()))?;
|
||||
if d == 0 {
|
||||
self.tick()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let n = |o: &Obj| o.as_num().filter(|v| v.is_finite()).ok_or(PsError::Ps("typecheck", String::new()));
|
||||
let (i0, d, l) = (n(&init)?, n(&inc)?, n(&limit)?);
|
||||
let mut k = 0.0;
|
||||
loop {
|
||||
let i = i0 + d * k;
|
||||
if !((d >= 0.0 && i <= l) || (d < 0.0 && i >= l)) {
|
||||
break;
|
||||
}
|
||||
self.push(Obj::Real(i))?;
|
||||
if !self.body(&p)? {
|
||||
break;
|
||||
}
|
||||
k += 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
For => self.for_loop()?,
|
||||
Repeat => {
|
||||
let p = self.pop_proc()?;
|
||||
let n = self.pop_int()?;
|
||||
@@ -955,31 +990,7 @@ impl<'a> Interp<'a> {
|
||||
self.stack.truncate(at);
|
||||
self.push(Obj::Array { items, exec: false })?;
|
||||
}
|
||||
Search | AnchorSearch => {
|
||||
let seek = self.pop_str()?.to_vec();
|
||||
let s = self.pop_str()?;
|
||||
let text = s.to_vec();
|
||||
self.spend((text.len() / 64) as u64)?;
|
||||
let at = if op == Search { super::lex::find(&text, &seek) } else { text.starts_with(&seek).then_some(0) };
|
||||
match at {
|
||||
Some(i) => {
|
||||
let o = Obj::Str(s);
|
||||
if op == Search {
|
||||
self.push_interval(o.clone(), i + seek.len(), text.len() - i - seek.len())?;
|
||||
self.push_interval(o.clone(), i, seek.len())?;
|
||||
self.push_interval(o, 0, i)?;
|
||||
} else {
|
||||
self.push_interval(o.clone(), seek.len(), text.len() - seek.len())?;
|
||||
self.push_interval(o, 0, seek.len())?;
|
||||
}
|
||||
self.push(Obj::Bool(true))?;
|
||||
}
|
||||
None => {
|
||||
self.push(Obj::Str(s))?;
|
||||
self.push(Obj::Bool(false))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Search | AnchorSearch => self.search_string(op)?,
|
||||
NewDict => {
|
||||
self.pop_count()?;
|
||||
self.push(Obj::dict(Default::default()))?;
|
||||
|
||||
@@ -4,7 +4,7 @@ use super::slots::{
|
||||
ART_LEFT_OUT, Content, MAX_PAIRS, MAX_SLOTS, MAX_TEXT, OUTLINE_REACH, TEXT_LEFT_OUT, TYPE_LEFT_OUT, collect_slots, extent, few, fill_slots,
|
||||
text_objects,
|
||||
};
|
||||
use super::*;
|
||||
use super::{Affine, Arc, BTreeSet, Document, Node, NodeId, NodeKind, PathData, Rect, Scaling, Story, Structure, Texts, Unit, Vec2, ai};
|
||||
|
||||
/// Which box of the editing copy is the page.
|
||||
#[derive(Clone, Copy)]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::build::Frame;
|
||||
use super::slots::OUTLINE_REACH;
|
||||
use super::*;
|
||||
use super::{Arc, Document, Node, NodeKind, Rect};
|
||||
|
||||
/// `doc` with its layers that don't print hidden, when it has any.
|
||||
fn as_printed(doc: &Document) -> Option<Document> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::build::{Page, build};
|
||||
use super::slots::LAYERS_UNREAD;
|
||||
use super::*;
|
||||
use super::{Affine, Arc, Cow, Document, Imported, Node, NodeKind, PathData, Rect, Vec2, ai};
|
||||
|
||||
/// The EPS `visible` (read as its page) read through its editing copy instead (its private data, or
|
||||
/// its program in the legacy format, see [`editing_copy`]): its layers (the
|
||||
@@ -217,7 +217,8 @@ pub(super) fn closed_where_ends_meet(mut path: PathData) -> PathData {
|
||||
|
||||
#[cfg(test)]
|
||||
mod closed_ends_tests {
|
||||
use super::*;
|
||||
use super::{PathData, closed_where_ends_meet};
|
||||
use vectorcraft_geom::Point;
|
||||
|
||||
/// A path drawn round to its start (the last anchor on the first) is closed there, its first
|
||||
/// anchor taking the last one's incoming handle; one whose ends are apart stays open.
|
||||
|
||||
@@ -1,6 +1,44 @@
|
||||
use super::layers::closed_where_ends_meet;
|
||||
use super::slots::{Content, SlotInfo, assign, covered, extent, join_pieces, letters, same_type};
|
||||
use super::*;
|
||||
use super::{Affine, Arc, Node, NodeId, NodeKind, Point, Rect, Story, TextKind};
|
||||
|
||||
/// Match page objects to the nearest slot whose story describes them, including hidden slots.
|
||||
fn match_page_objects(infos: &[SlotInfo], lines: &[Option<Vec<(String, Point)>>], shown: &[Vec<Arc<Node>>]) -> Vec<Option<usize>> {
|
||||
let mut pairs: Vec<(f64, usize, usize)> = vec![];
|
||||
for (k, _) in infos.iter().enumerate() {
|
||||
let Some(l) = lines.get(k).and_then(Option::as_ref) else { continue };
|
||||
for (j, object) in shown.iter().enumerate() {
|
||||
if let Some(d) = object.iter().filter_map(|n| same_type(n, l)).min_by(f64::total_cmp) {
|
||||
pairs.push((d, k, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
pairs.sort_by(|a, b| a.0.total_cmp(&b.0));
|
||||
let mut owner = vec![None; shown.len()];
|
||||
for (_, k, j) in pairs {
|
||||
if let Some(o) = owner.get_mut(j).filter(|o| o.is_none()) {
|
||||
*o = Some(k);
|
||||
}
|
||||
}
|
||||
owner
|
||||
}
|
||||
|
||||
fn frame_text_from_art(info: &SlotInfo, node: &mut Node, region: &mut Option<Rect>) {
|
||||
if let (Some(path), NodeKind::Text(text)) = (&info.art_path, &mut node.kind) {
|
||||
let size = text.runs.iter().map(|r| r.style.size).fold(0.0, f64::max);
|
||||
match &mut text.kind {
|
||||
TextKind::Area { frame } => {
|
||||
*frame = path.clone();
|
||||
*region = path.bounds();
|
||||
}
|
||||
TextKind::OnPath { path: on, .. } => {
|
||||
*on = closed_where_ends_meet(path.clone());
|
||||
*region = path.bounds().map(|r| r.inflate(size * 1.5, size * 1.5));
|
||||
}
|
||||
TextKind::Point => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// What goes into each of `infos`' slots: the page's type that the file's text document puts where the
|
||||
/// page has it, else type made from the text document, else (for a file whose text document can't say)
|
||||
@@ -21,24 +59,7 @@ pub(super) fn plan(
|
||||
Some(s.line_starts(t).into_iter().map(|(text, (x, y))| (text, to_doc * Point::new(x, y))).collect())
|
||||
})
|
||||
.collect();
|
||||
// The page's objects that are where a slot's lines are, nearest first. A slot that doesn't show
|
||||
// has them too: a file's page may draw what its layers have hidden since.
|
||||
let mut pairs: Vec<(f64, usize, usize)> = vec![];
|
||||
for (k, _) in infos.iter().enumerate() {
|
||||
let Some(l) = lines.get(k).and_then(Option::as_ref) else { continue };
|
||||
for (j, object) in shown.iter().enumerate() {
|
||||
if let Some(d) = object.iter().filter_map(|n| same_type(n, l)).min_by(f64::total_cmp) {
|
||||
pairs.push((d, k, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
pairs.sort_by(|a, b| a.0.total_cmp(&b.0));
|
||||
let mut owner: Vec<Option<usize>> = vec![None; shown.len()];
|
||||
for (_, k, j) in pairs {
|
||||
if let Some(o) = owner.get_mut(j).filter(|o| o.is_none()) {
|
||||
*o = Some(k);
|
||||
}
|
||||
}
|
||||
let owner = match_page_objects(infos, &lines, shown);
|
||||
// Type the text document makes, shown or not: the page's pieces of it aren't needed.
|
||||
let mut content: Vec<Content> = infos.iter().map(|_| Content::Empty).collect();
|
||||
// Where the made type that doesn't show lies, by slot.
|
||||
@@ -51,20 +72,7 @@ pub(super) fn plan(
|
||||
let mut region = story.region(t, to_doc);
|
||||
// Area type and type on a path in the object's own frame: the text document's frame
|
||||
// of a copied object keeps its original's path.
|
||||
if let (Some(path), NodeKind::Text(text)) = (&info.art_path, &mut node.kind) {
|
||||
let size = text.runs.iter().map(|r| r.style.size).fold(0.0, f64::max);
|
||||
match &mut text.kind {
|
||||
TextKind::Area { frame } => {
|
||||
*frame = path.clone();
|
||||
region = path.bounds();
|
||||
}
|
||||
TextKind::OnPath { path: on, .. } => {
|
||||
*on = closed_where_ends_meet(path.clone());
|
||||
region = path.bounds().map(|r| r.inflate(size * 1.5, size * 1.5));
|
||||
}
|
||||
TextKind::Point => {}
|
||||
}
|
||||
}
|
||||
frame_text_from_art(info, &mut node, &mut region);
|
||||
if let (false, Some(r)) = (info.shown, region) {
|
||||
hidden_at.push((k, r));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::build::MADE;
|
||||
use super::*;
|
||||
use super::{AppearanceItem, Arc, BTreeSet, BlendMode, Document, Node, NodeId, NodeKind, PathData, Point, Rect, ai, slot_of};
|
||||
|
||||
/// The notes that say the import left out something the file has (see [`is_loss`]).
|
||||
pub(super) const TEXT_LEFT_OUT: &str = "text objects on hidden layers or hidden objects couldn't be read, so they are left out";
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use super::writer::Writer;
|
||||
use super::*;
|
||||
use super::{
|
||||
Affine, AreaOptions, BezPath, CharPosition, CharStyle, Color, Gradient, GradientKind, Paint, ParaStyle, PathEffect, StrokeLayer, TextKind,
|
||||
TextObject, Write, latin, ps, stroke,
|
||||
};
|
||||
|
||||
/// A gradient definition: `%AI5_BeginGradient: (name)`, `(name) type stops Bd`, `[`, the stops
|
||||
/// (`colorSpec colorStyle midPoint rampPoint %_Bs`, each with its midpoint to the next stop), `BD`,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Write, latin};
|
||||
|
||||
/// The setup's fonts (§3.1, §3.3): `%%IncludeFont` for each, the encoding strings are in (`TE`),
|
||||
/// and each font re-encoded to it as `_name` (`TZ`; a TrueType font takes the encoding as its own,
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
use super::encoding::{
|
||||
Ink, average, direct_fill, direct_stroke, finite, font_of, invertible, latin_string, lost_attributes, segment_point, unit, writable,
|
||||
};
|
||||
use super::*;
|
||||
use super::{
|
||||
Affine, AnchorKind, AppearanceItem, Arc, BezPath, BlendMode, CharStyle, Cms, Color, Document, ELLIPTICAL, FREEFORM, FillLayer, FillRule,
|
||||
Gradient, GradientKind, GradientPaint, HEX_LINE, HIDDEN, HIDDEN_LAYER, HIDDEN_SHOWN, HiddenObjects, IMAGE_ALPHA, ImageBlob, Justify, LARGE_IMAGE,
|
||||
LAYER_COLORS, LAYER_NAME, LIVE, LayerColor, LegacyAiOptions, LineCap, LineJoin, MAX_IMAGE_PIXELS, MAX_NEST, NAMES, Node, NodeKind, PATTERNS,
|
||||
Paint, ParaDirection, ParaStyle, PathData, Point, Rect, STROKES, SUBLAYERS, Shape, StrokeLayer, SubPath, TITLE_AND_COLOR_NAMES, TRANSPARENCY,
|
||||
TYPE, TYPE_ATTRIBUTES, TextKind, TextObject, UNDECODED, Vec2, Write, latin, ps, stroke,
|
||||
};
|
||||
|
||||
pub(super) struct Writer<'a> {
|
||||
doc: &'a Document,
|
||||
|
||||
Reference in New Issue
Block a user