use crate::canvas::{Canvas, Fill, Item, Paint, Stroke, TileItems};
use crate::color::Color;
use crate::scene::{Point, Segment, Transform};
use crate::tex::Token;
use crate::{Error, PT};
use super::Engine;
#[derive(Debug, Clone)]
pub struct UserPattern {
items: Vec<Item>,
origin: Point,
size: Point,
transform: Transform,
colored: bool,
lazy: Option<std::rc::Rc<LazyPattern>>,
}
#[derive(Debug)]
struct LazyPattern {
origin: Vec<Token>,
size: Vec<Token>,
code: Vec<Token>,
tile_options: Vec<Token>,
defaults: Vec<Token>,
tikz: bool,
}
impl UserPattern {
fn lazy(p: LazyPattern, colored: bool) -> UserPattern {
UserPattern { items: Vec::new(), origin: Point::default(), size: Point::new(PT, PT), transform: Transform::default(), colored, lazy: Some(std::rc::Rc::new(p)) }
}
}
impl Engine {
pub fn declare_pattern(&mut self, colored: bool) -> Result<(), Error> {
let vars = self.optional()?.filter(|v| v.iter().any(|t| !t.is_space()));
let name = self.argument_string()?;
if vars.is_some() {
let origin = self.argument()?;
let _upper_right = self.argument()?;
let size = self.argument()?;
let code = self.argument()?;
let lazy = LazyPattern { origin, size, code, tile_options: Vec::new(), defaults: Vec::new(), tikz: false };
self.user_patterns.insert(name.trim().to_string(), UserPattern::lazy(lazy, colored));
return Ok(());
}
let origin = self.point_arg()?;
let _upper_right = self.point_arg()?;
let size = self.point_arg()?;
let code = self.argument()?;
let items = self.draw_tile(code)?;
self.user_patterns.insert(name.trim().to_string(), UserPattern { items, origin, size, transform: Transform::default(), colored, lazy: None });
Ok(())
}
pub fn tikz_declare_pattern(&mut self, tikz: bool) -> Result<(), Error> {
let spec = self.argument()?;
let (mut name, mut colored, mut lazy) = (String::new(), false, false);
let mut p = LazyPattern { origin: Vec::new(), size: Vec::new(), code: Vec::new(), tile_options: Vec::new(), defaults: Vec::new(), tikz };
for item in crate::keys::split_list(&spec) {
let v = item.value.clone().unwrap_or_default();
match item.key.as_str() {
"name" => name = self.tex.expand_to_string(v)?.trim().to_string(),
"type" => colored = self.tex.expand_to_string(v)?.trim() == "colored",
"bottom left" => p.origin = v,
"bounding box" => {
let text = crate::tex::tokens_to_string(&v);
let first = text.split(" and ").next().unwrap_or("").trim().to_string();
p.origin = super::tikra_tokens(&first);
}
"tile size" => p.size = v,
"tile transformation" => p.tile_options = v,
"code" => p.code = v,
"parameters" => lazy = true,
"defaults" => {
p.defaults = v;
lazy = true;
}
_ => {}
}
}
let pattern = UserPattern::lazy(p, colored);
let pattern = if lazy { pattern } else { self.instantiate(&pattern, "")? };
self.user_patterns.insert(name, pattern);
Ok(())
}
fn instantiate(&mut self, p: &UserPattern, options: &str) -> Result<UserPattern, Error> {
let Some(lazy) = p.lazy.clone() else { return Ok(p.clone()) };
self.begin_group();
let r = (|| {
self.process_keys(lazy.defaults.clone(), Some("/pgf/pattern keys"))?;
self.process_keys(super::tikra_tokens(options), Some("/pgf/pattern keys"))?;
let point = |e: &mut Engine, v: &[Token], default: Point| -> Result<Point, Error> {
if v.iter().all(|t| t.is_space()) {
return Ok(default);
}
if !lazy.tikz {
return e.point_of(v.to_vec());
}
let text = e.math_string(v.to_vec())?;
let t = text.trim().trim_start_matches('(').trim_end_matches(')').to_string();
Ok(e.coordinate(&t)?.p)
};
let origin = point(self, &lazy.origin, Point::default())?;
let size = point(self, &lazy.size, Point::new(PT, PT))?;
let items = self.draw_tile(lazy.code.clone())?;
self.st.transform = Transform::default();
self.st.pending = Transform::default();
let transform = if lazy.tikz {
self.process_keys(lazy.tile_options.clone(), Some("/tikz"))?;
self.st.pending
} else {
self.run_tokens(lazy.tile_options.clone())?;
self.st.transform
};
Ok(UserPattern { items, origin, size, transform, colored: p.colored, lazy: None })
})();
self.end_group();
r
}
pub(super) fn draw_tile(&mut self, code: Vec<Token>) -> Result<Vec<Item>, Error> {
let saved_canvas = std::mem::take(&mut self.canvas);
let saved_depth = std::mem::replace(&mut self.picture_depth, 1);
let saved_frozen = std::mem::replace(&mut self.canvas_frozen, false);
self.begin_group();
self.begin_scope();
self.st.transform = Transform::default();
let r = self.run_tokens(code);
self.end_scope();
self.end_group();
let Canvas { items, .. } = std::mem::replace(&mut self.canvas, saved_canvas);
self.picture_depth = saved_depth;
self.canvas_frozen = saved_frozen;
r?;
Ok(items)
}
pub fn pattern_paint(&mut self, name: &str, color: Color) -> Option<Paint> {
let name = name.trim();
let (base, options) = match name.split_once('[') {
Some((b, o)) => (b.trim(), o.trim_end().trim_end_matches(']')),
None => (name, ""),
};
let p = match self.user_patterns.get(base).cloned() {
Some(p) => match self.instantiate(&p, options) {
Ok(p) => p,
Err(e) => {
self.warn(format!("pattern '{base}': {e}"));
return None;
}
},
None => self.meta_pattern(name)?,
};
let mut items = p.items;
if !p.colored {
for i in items.iter_mut() {
if let Item::Path { fill, stroke, .. } = i {
if let Some(f) = fill {
f.paint = Paint::Solid(color);
}
if let Some(s) = stroke {
s.color = color;
}
}
}
}
Some(Paint::Tile { items: TileItems(std::rc::Rc::new(items)), origin: p.origin, size: p.size, transform: p.transform })
}
fn meta_pattern(&mut self, spec: &str) -> Option<UserPattern> {
let (kind, options) = match spec.split_once('[') {
Some((k, o)) => (k.trim(), o.trim_end().trim_end_matches(']')),
None => (spec, ""),
};
if !matches!(kind, "Lines" | "Hatch" | "Dots" | "Stars") {
return None;
}
let star = kind == "Stars";
let (mut d, mut angle, mut xs, mut ys) = (if star { 3.0 * 0.1 } else { 3.0 * PT }, 0.0, 0.0, 0.0);
let (mut lw, mut r, mut points) = (self.st.line_width, if star { 0.1 } else { 0.5 * PT }, 5.0);
for item in crate::keys::split_list(&super::tikra_tokens(options)) {
let v = crate::tex::tokens_to_string(&item.value.unwrap_or_default());
let v = v.trim().trim_start_matches('{').trim_end_matches('}').to_string();
match item.key.as_str() {
"distance" => d = self.length(&v).ok()?,
"angle" => angle = self.number(&v).ok()?,
"xshift" => xs = self.length(&v).ok()?,
"yshift" => ys = self.length(&v).ok()?,
"line width" => lw = self.length(&v).ok()?,
"radius" => r = self.length(&v).ok()?,
"points" => points = self.number(&v).ok()?,
_ => {}
}
}
let black = Color::BLACK;
let stroke = Stroke { width: lw, color: black, ..self.stroke() };
let fill = Fill { paint: Paint::Solid(black), opacity: 1.0, even_odd: false };
let p = Point::new;
let h = d / 2.0;
let items = match kind {
"Lines" | "Hatch" => {
let mut segs = vec![Segment::MoveTo(p(-h, 0.0)), Segment::LineTo(p(h, 0.0))];
if kind == "Hatch" {
segs.extend([Segment::MoveTo(p(0.0, -h)), Segment::LineTo(p(0.0, h))]);
}
vec![Item::Path { segments: segs, fill: None, stroke: Some(stroke) }]
}
"Dots" => {
let mut segs = vec![Segment::MoveTo(p(r, 0.0))];
segs.extend(super::path::arc_segments(Point::default(), r, r, 0.0, 360.0, &Transform::default()));
segs.push(Segment::Close);
vec![Item::Path { segments: segs, fill: Some(fill), stroke: None }]
}
_ => {
let a = 180.0 / points;
let polar = |deg: f64, len: f64| p(len * deg.to_radians().cos(), len * deg.to_radians().sin());
let mut segs = vec![Segment::MoveTo(polar(90.0, r))];
for i in 1..=(points as usize) {
let i = i as f64;
segs.push(Segment::LineTo(polar(90.0 + 2.0 * i * a - a, 0.5 * r)));
segs.push(Segment::LineTo(polar(90.0 + 2.0 * i * a, r)));
}
segs.push(Segment::Close);
vec![Item::Path { segments: segs, fill: Some(fill), stroke: None }]
}
};
let transform = Transform::translation(xs, ys).compose(&Transform::rotation(angle));
Some(UserPattern { items, origin: p(-h, -h), size: p(d, d), transform, colored: false, lazy: None })
}
pub fn set_fill_pattern(&mut self) -> Result<(), Error> {
let name = self.argument_string()?;
let color = self.argument_string()?;
let c = self.color(color.trim()).unwrap_or(Color::BLACK);
self.st.pattern_name = Some(name.trim().to_string());
self.st.pattern_color = Some(c);
self.st.fill_pattern = true;
Ok(())
}
}
impl Engine {
pub fn arrows_declare(&mut self) -> Result<(), Error> {
let start = self.argument_string()?;
let end = self.argument_string()?;
let extents = self.argument()?;
let code = self.argument()?;
let text = crate::tex::tokens_to_string(&extents);
let extent = |e: &mut Engine, name: &str| -> Result<f64, Error> {
let Some(rest) = text.split(&format!("\\{name}")).nth(1) else { return Ok(0.0) };
let arg = rest.trim_start().strip_prefix('{').and_then(|r| r.split_once('}')).map_or("0pt", |(a, _)| a);
Ok(e.length(arg)? / PT)
};
let (left, right) = (extent(self, "pgfarrowsleftextend")?, extent(self, "pgfarrowsrightextend")?);
let items = self.draw_tile(code)?;
let to_pt = Transform::scaling(1.0 / PT, 1.0 / PT);
let paths = items
.into_iter()
.filter_map(|i| match i {
Item::Path { segments, fill, stroke } => {
let segs = crate::canvas::transform_segments(&segments, &to_pt);
Some((segs, fill.is_some(), stroke.is_some(), stroke.map_or(0.0, |s| s.width / PT)))
}
_ => None,
})
.collect();
super::arrows::declare_tip(super::arrows::DeclaredTip { names: (start.trim().to_string(), end.trim().to_string()), left, right, paths });
Ok(())
}
}