use crate::canvas::{Canvas, Item};
use crate::scene::{Point, Segment, Transform};
use crate::tex::{Cat, Token};
use crate::Error;
use super::{tikra_tokens, Engine};
#[derive(Debug, Clone)]
pub struct PictureFading {
pub items: Vec<Item>,
pub min: Point,
pub max: Point,
}
impl PictureFading {
fn center(&self) -> Point {
self.min.lerp(self.max, 0.5)
}
fn mask(&self, t: &Transform) -> Item {
let c = self.center();
let t = t.compose(&Transform::translation(-c.x, -c.y));
let mut items = self.items.clone();
items.iter_mut().for_each(|i| i.transform(&t));
Item::FadePicture { items }
}
fn fit(&self, lo: Point, hi: Point) -> Transform {
let side = 50.0 * 2.54 / 72.0;
let c = lo.lerp(hi, 0.5);
Transform::translation(c.x, c.y).compose(&Transform::scaling((hi.x - lo.x).max(1e-9) / side, (hi.y - lo.y).max(1e-9) / side))
}
}
impl Engine {
pub fn declare_fading(&mut self) -> Result<(), Error> {
let name = self.argument_string()?;
let code = self.argument()?;
self.fading_from(name.trim().to_string(), code)
}
fn fading_from(&mut self, name: String, code: Vec<Token>) -> Result<(), Error> {
let first = code.iter().find(|t| !t.is_space()).cloned();
let shading = code.iter().any(|t| t.is_cs("pgfuseshading"));
let code = match first {
Some(Token::Cs(n)) if &*n == "tikz" || &*n == "begin" => code,
Some(Token::Cs(n)) if &*n == "pgfuseshading" || &*n == "pgftext" || shading => {
let mut v = tikra_tokens("\\begin{pgfpicture}");
v.extend(code);
v.extend(tikra_tokens("\\end{pgfpicture}"));
v
}
_ => {
let mut v = tikra_tokens("\\begin{pgfpicture}\\pgftext");
v.push(Token::Char('{', Cat::Begin));
v.extend(code);
v.push(Token::Char('}', Cat::End));
v.extend(tikra_tokens("\\end{pgfpicture}"));
v
}
};
let canvas = self.draw_apart(code)?;
let Canvas { items, min, max, .. } = canvas;
if let (Some(min), Some(max)) = (min, max) {
self.picture_fadings.insert(name, PictureFading { items, min, max });
}
Ok(())
}
pub fn draw_apart(&mut self, code: Vec<Token>) -> Result<Canvas, Error> {
let saved_canvas = std::mem::take(&mut self.canvas);
let saved_boxes = std::mem::take(&mut self.boxes);
let saved_layout = std::mem::take(&mut self.layout);
let saved_words = std::mem::take(&mut self.words);
let saved_depth = std::mem::replace(&mut self.picture_depth, 0);
let saved_frozen = std::mem::replace(&mut self.canvas_frozen, false);
self.begin_group();
let r = self.run_tokens(code);
while self.picture_depth > 0 {
self.end_picture();
}
self.end_group();
let canvas = super::typeset(&self.boxes, &self.layout, &self.words, 0.0);
self.canvas = saved_canvas;
self.boxes = saved_boxes;
self.layout = saved_layout;
self.words = saved_words;
self.picture_depth = saved_depth;
self.canvas_frozen = saved_frozen;
r?;
Ok(canvas)
}
pub fn fading_from_picture(&mut self) -> Result<(), Error> {
let options = self.optional()?.unwrap_or_default();
let mut name = String::new();
let mut rest = Vec::new();
for item in crate::keys::split_list(&options) {
if item.key == "name" {
name = self.tex.expand_to_string(item.value.unwrap_or_default())?.trim().to_string();
} else {
if !rest.is_empty() {
rest.push(Token::other(','));
}
rest.extend(tikra_tokens(&item.key));
if let Some(v) = item.value {
rest.push(Token::other('='));
rest.push(Token::Char('{', Cat::Begin));
rest.extend(v);
rest.push(Token::Char('}', Cat::End));
}
}
}
let mut body = Vec::new();
while let Some(t) = self.tex.get_token() {
if t.is_cs("end") {
let arg = self.tex.read_undelimited()?;
if crate::tex::tokens_to_string(&arg).trim() == "tikzfadingfrompicture" {
break;
}
body.push(t);
body.push(Token::Char('{', Cat::Begin));
body.extend(arg);
body.push(Token::Char('}', Cat::End));
continue;
}
body.push(t);
}
let mut code = tikra_tokens("\\begin{tikzpicture}[");
code.extend(rest);
code.extend(tikra_tokens("]"));
code.extend(body);
code.extend(tikra_tokens("\\end{tikzpicture}"));
self.fading_from(name, code)
}
pub fn set_fading(&mut self) -> Result<(), Error> {
let name = self.argument_string()?;
let code = self.argument()?;
let t = self.fading_transform(code)?;
self.open_fading(name.trim(), t);
Ok(())
}
pub fn set_fading_for_path(&mut self, stroked: bool) -> Result<(), Error> {
let name = self.argument_string()?;
let code = self.argument()?;
let mut c = Canvas::default();
c.include_path(&self.pgf_path.segments);
let (Some(mut lo), Some(mut hi)) = (c.min, c.max) else { return Ok(()) };
if stroked {
let w = self.st.line_width / 2.0;
(lo, hi) = (Point::new(lo.x - w, lo.y - w), Point::new(hi.x + w, hi.y + w));
}
let extra = self.fading_transform(code)?;
let (fit, inner) = match self.picture_fadings.get(name.trim()) {
Some(f) => (f.fit(lo, hi), self.st.transform.inverse().compose(&extra)),
None => (Transform::default(), extra),
};
let t = fit.compose(&Transform { e: 0.0, f: 0.0, ..inner });
self.open_fading(name.trim(), t);
Ok(())
}
fn fading_transform(&mut self, code: Vec<Token>) -> Result<Transform, Error> {
self.begin_group();
let r = self.run_tokens(code);
let t = self.st.transform;
self.end_group();
r?;
Ok(t)
}
pub fn open_fading(&mut self, name: &str, t: Transform) {
if let Some(mask) = self.natural_fading(name, &t) {
self.open_in_scope(mask);
}
}
pub fn close_path_fadings(&mut self, fadings: Vec<(String, usize, bool, Transform)>, settings: (Transform, bool)) {
let (fading_transform, fit_now) = settings;
for (name, start, fit, t) in fadings.into_iter().rev() {
let start = start.min(self.canvas.items.len());
let fit = fit && fit_now;
let t = t.compose(&fading_transform);
if start == self.canvas.items.len() {
self.carried_fadings.push((name, fit, t));
continue;
}
let mask = match self.st.path_box.filter(|_| fit) {
Some((lo, hi)) => {
let segs = [Segment::MoveTo(lo), Segment::LineTo(Point::new(hi.x, lo.y)), Segment::LineTo(hi), Segment::LineTo(Point::new(lo.x, hi.y)), Segment::Close];
if self.picture_fadings.contains_key(&name) {
self.picture_fading_for(&name, &segs, true)
} else {
self.fading_for(&name, &segs).map(|mask| Item::Fade { mask })
}
}
None => self.natural_fading(&name, &t),
};
if let Some(mask) = mask {
self.canvas.items.insert(start, mask);
self.canvas.items.push(Item::End);
}
}
}
fn natural_fading(&mut self, name: &str, t: &Transform) -> Option<Item> {
if let Some(f) = self.picture_fadings.get(name) {
return Some(f.mask(t));
}
let k = 2.0 * 2.54 / 72.0;
if let Some(mask) = super::output::library_fading(name, &t.compose(&Transform::scaling(k, k))) {
return Some(Item::Fade { mask });
}
match self.fadings.get(name).cloned() {
Some(mut sh) => {
let (crate::canvas::Shading::Axial { transform, .. } | crate::canvas::Shading::Radial { transform, .. }) = &mut sh;
*transform = t.compose(transform);
Some(Item::Fade { mask: sh })
}
None => {
self.warn(format!("fading '{name}' not supported: drawn without it"));
None
}
}
}
pub fn picture_fading_for(&self, name: &str, segs: &[Segment], fit: bool) -> Option<Item> {
let f = self.picture_fadings.get(name)?;
let mut c = Canvas::default();
c.include_path(segs);
let (lo, hi) = (c.min?, c.max?);
let t = if fit { f.fit(lo, hi) } else { self.st.transform };
let t = t.compose(&self.st.fading_transform);
Some(f.mask(&t))
}
}