use crate::scene::{Point, Transform};
use crate::tex::{self, Cat, Token};
use crate::Error;
use super::{macro_from, tikra_tokens, Engine};
struct Child {
options: Vec<Token>,
body: Vec<Token>,
define: Vec<Token>,
}
impl Engine {
pub fn children(&mut self, parent: Option<String>) -> Result<(), Error> {
let Some(info) = self.last_node.clone() else {
return Err(Error::Figure("child without a node before it".into()));
};
let parent = match parent {
Some(p) => p,
None => {
self.tree_count += 1;
let n = format!("tikra@tree@{}", self.tree_count);
self.alias_last_node(&n);
self.nodes.insert(n.clone(), info);
n
}
};
let children = self.collect_children()?;
self.begin_group();
let r = self.grow_children(&parent, children);
self.end_group();
r
}
fn collect_children(&mut self) -> Result<Vec<Child>, Error> {
let mut out = Vec::new();
let mut first = true;
loop {
if !first {
let mut read = Vec::new();
let mut word = String::new();
while let Some(t) = self.tex.get_token() {
match &t {
Token::Char(c, Cat::Letter) => {
word.push(*c);
read.push(t);
}
x if x.is_space() && word.is_empty() => read.push(t),
_ => {
self.tex.back(t);
break;
}
}
}
if word != "child" {
self.tex.push_tokens(read);
return Ok(out);
}
}
first = false;
let options = match self.tex.next_nonspace() {
Some(t) if t.is_char('[') => self.tex.read_until_char(']', false)?,
Some(t) => {
self.tex.back(t);
Vec::new()
}
None => Vec::new(),
};
let mut peek = Vec::new();
let mut word = String::new();
while let Some(t) = self.tex.get_token() {
match &t {
Token::Char(c, Cat::Letter) => {
word.push(*c);
peek.push(t);
}
x if x.is_space() && word.is_empty() => peek.push(t),
_ => {
self.tex.back(t);
break;
}
}
}
if word == "foreach" {
let var = self.tex.next_nonspace().ok_or_else(|| Error::Figure("child foreach without a variable".into()))?;
loop {
match self.tex.next_nonspace() {
Some(t) if t.is_char('i') => {
if let Some(n) = self.tex.get_token() {
if n.is_char('n') {
break;
}
}
}
Some(_) => {}
None => return Err(Error::Figure("child foreach without 'in'".into())),
}
}
let list = self.tex.read_undelimited()?;
let list = self.tex.expand_fully(list)?;
let body = match self.tex.next_nonspace() {
Some(Token::Char(_, Cat::Begin)) => self.tex.read_group_body()?,
Some(t) => {
self.tex.back(t);
Vec::new()
}
None => Vec::new(),
};
for item in super::foreach_items(&list) {
let mut b = vec![Token::cs("def"), var.clone(), Token::Char('{', Cat::Begin)];
b.extend(item);
b.push(Token::Char('}', Cat::End));
out.push(Child { options: options.clone(), body: body.clone(), define: b });
}
continue;
}
self.tex.push_tokens(peek);
let body = match self.tex.next_nonspace() {
Some(Token::Char(_, Cat::Begin)) => self.tex.read_group_body()?,
Some(t) => {
self.tex.back(t);
Vec::new()
}
None => Vec::new(),
};
out.push(Child { options, body, define: Vec::new() });
}
}
fn grow_children(&mut self, parent: &str, children: Vec<Child>) -> Result<(), Error> {
self.st.tree_level += 1;
let level = self.st.tree_level;
self.st.pending = Transform::default();
self.process_keys(tikra_tokens(&format!("level/.try={level},level {level}/.try")), Some("/tikz"))?;
self.st.apply_pending();
let n = children.len();
let Some(info) = self.nodes.get(parent).cloned() else { return Ok(()) };
let anchor = self.st.growth_anchor.clone();
let origin = info.anchor(&anchor).unwrap_or(info.center());
let local = self.st.transform.inverse().apply(origin);
self.st.transform = self.st.transform.compose(&Transform::translation(local.x, local.y));
for (k, child) in children.into_iter().enumerate() {
self.begin_group();
let r = self.grow_child(parent, k + 1, n, child);
self.end_group();
r?;
}
Ok(())
}
fn grow_child(&mut self, parent: &str, k: usize, n: usize, child: Child) -> Result<(), Error> {
self.tex.set_meaning(&Token::cs("tikzparentnode"), macro_from(parent), false);
self.st.pending = Transform::default();
self.st.child_missing = false;
self.run_tokens(child.define)?;
let mut options = tikra_tokens("every child/.try,");
options.extend(child.options);
self.process_keys(options, Some("/tikz"))?;
self.st.apply_pending();
if self.st.child_missing {
return Ok(());
}
self.st.transform = self.grow(k, n)?;
let mut body = keys_trimmed(&child.body);
let mut path_options = Vec::new();
if body.first().is_some_and(|t| t.is_char('[')) {
if let Some(end) = body.iter().position(|t| t.is_char(']')) {
path_options = body[..=end].to_vec();
body = keys_trimmed(&body[end + 1..]);
}
}
let (kind, rest) = split_first_word(&body);
let mut statement = tikra_tokens("\\path");
statement.extend(path_options);
statement.extend(tikra_tokens(&format!(" {} [name={parent}-{k},every child node/.try]", if kind == "node" { "node" } else { "coordinate" })));
if kind == "node" || kind == "coordinate" {
statement.extend(rest);
} else {
statement.extend(body);
}
statement.extend(tikra_tokens(";"));
self.tree_edge_out = None;
self.tree_child_names.push(None);
let r = self.run_tokens(statement);
let named = self.tree_child_names.pop().flatten().filter(|n| !n.is_empty());
r?;
let child_name = named.unwrap_or_else(|| format!("{parent}-{k}"));
self.tex.set_meaning(&Token::cs("tikzchildnode"), macro_from(&child_name), false);
let (edge_options, edge_nodes) = self.tree_edge_out.take().unwrap_or_default();
let pa = self.st.parent_anchor.clone();
let ca = self.st.child_anchor.clone();
self.tex.define_string("tikzparentanchor", &pa, false);
self.tex.define_string("tikzchildanchor", &ca, false);
let mut edge = tikra_tokens("\\path[edge from parent/.try,");
edge.extend(edge_options);
edge.extend(tikra_tokens("] "));
match self.st.edge_from_parent_path.clone() {
Some(p) => {
edge.extend(p);
edge.extend(edge_nodes);
}
None => {
edge.extend(tikra_tokens(&format!("({parent}{pa}) -- ")));
edge.extend(edge_nodes);
edge.extend(tikra_tokens(&format!(" ({child_name}{ca})")));
}
}
edge.extend(tikra_tokens(";"));
self.st.transform = Transform::default();
self.run_tokens(edge)
}
}
impl Engine {
fn grow(&mut self, k: usize, n: usize) -> Result<Transform, Error> {
let polar = |angle: f64, d: f64| {
let (s, c) = angle.to_radians().sin_cos();
Point::new(d * c, d * s)
};
let shift = |t: &Transform, p: Point| t.compose(&Transform::translation(p.x, p.y));
let (g, ld, sd) = (self.st.grow_angle, self.st.level_distance, self.st.sibling_distance);
let (k, n) = (k as f64, n as f64);
let growth = self.tex.expand_to_string(vec![Token::cs("tikra@growth")])?;
let sibling_angle = |e: &mut Engine| -> Result<f64, Error> {
let v = crate::keys::value(&e.tex, "/tikz/sibling angle").unwrap_or_default();
e.number_of(v)
};
let t = self.st.transform;
if let Some(f) = self.st.growth_function.clone() {
for (name, v) in [("tikznumberofchildren", n), ("tikznumberofcurrentchild", k)] {
if let tex::Meaning::Count(r) = self.tex.meaning_of(name) {
self.tex.set_count(r, v as i64, false);
}
}
self.run_tokens(f)?;
return Ok(self.st.transform);
}
let (kind, arg) = growth.trim().split_once(' ').unwrap_or((growth.trim(), ""));
Ok(match kind {
"cyclic" => {
let a = sibling_angle(self)? * (-0.5 - 0.5 * n + k);
t.compose(&Transform::rotation(a)).compose(&Transform::translation(ld, 0.0))
}
"ccw" | "cw" => {
let start = self.number(arg)?;
let step = sibling_angle(self)? * (k - 1.0);
shift(&t, polar(if kind == "ccw" { start + step } else { start - step }, ld))
}
"three" => {
let points: Vec<&str> = arg.split('|').collect();
let mut p = Vec::new();
for q in points.iter().take(3) {
let c = self.coordinate(q)?;
p.push(t.inverse().apply(c.p));
}
let [a, b, c] = p[..] else { return Ok(t) };
let (b, c) = (b.minus(a), c.minus(b));
shift(&t, Point::new(a.x + (n - 1.0) * b.x + (k - 1.0) * c.x, a.y + (n - 1.0) * b.y + (k - 1.0) * c.y))
}
_ => {
let mut t = shift(&t, polar(g, ld));
if self.st.special_level != Some(self.st.tree_level) {
let (left, right) = if self.st.grow_swapped { (g + 90.0, g - 90.0) } else { (g - 90.0, g + 90.0) };
t = shift(&shift(&t, polar(left, (n + 1.0) * sd / 2.0)), polar(right, k * sd));
}
t
}
})
}
}
const LEVEL_DISTANCE: u32 = 4030;
const SIBLING_DISTANCE: u32 = 4031;
pub fn define(e: &mut Engine) {
e.tex.define("tikzleveldistance", crate::tex::Meaning::Dimen(LEVEL_DISTANCE));
e.tex.define("tikzsiblingdistance", crate::tex::Meaning::Dimen(SIBLING_DISTANCE));
set_distance_registers(e);
}
pub fn set_distance_registers(e: &mut Engine) {
let sp = |cm: f64| (cm / crate::PT * 65536.0).round() as i64;
let (l, s) = (sp(e.st.level_distance), sp(e.st.sibling_distance));
e.tex.set_dimen(LEVEL_DISTANCE, l, false);
e.tex.set_dimen(SIBLING_DISTANCE, s, false);
}
pub const DEFINITIONS: &str = r"
\def\tikra@growth{}
\def\tikra@growthree one child at#1(#2)#3and two children at#4(#5)#6and#7(#8)\relax{\def\tikra@growth{three #2|#5|#8}}
\tikzset{
grow cyclic/.style={edge from parent/.append style={reset cm},tikra@growth=cyclic},
tikra@growth/.code={\def\tikra@growth{#1}},
sibling angle/.initial=20,
counterclockwise from/.code={\def\tikra@growth{ccw #1}},
clockwise from/.code={\def\tikra@growth{cw #1}},
grow via three points/.code={\tikra@growthree#1\relax},
grow siblings on line/.code={\def\tikra@growth{}},
edge from parent fork down/.style={edge from parent path={(\tikzparentnode\tikzparentanchor) -- +(0pt,-.5\tikzleveldistance) -| (\tikzchildnode\tikzchildanchor)}},
edge from parent fork up/.style={edge from parent path={(\tikzparentnode\tikzparentanchor) -- +(0pt,.5\tikzleveldistance) -| (\tikzchildnode\tikzchildanchor)}},
edge from parent fork left/.style={edge from parent path={(\tikzparentnode\tikzparentanchor) -- +(-.5\tikzleveldistance,0pt) |- (\tikzchildnode\tikzchildanchor)}},
edge from parent fork right/.style={edge from parent path={(\tikzparentnode\tikzparentanchor) -- +(.5\tikzleveldistance,0pt) |- (\tikzchildnode\tikzchildanchor)}},
}
";
fn keys_trimmed(v: &[Token]) -> Vec<Token> {
crate::keys::trim(v).to_vec()
}
fn split_first_word(v: &[Token]) -> (String, Vec<Token>) {
let mut word = String::new();
let mut i = 0;
while i < v.len() {
match &v[i] {
Token::Char(c, Cat::Letter) => word.push(*c),
_ => break,
}
i += 1;
}
(word, v[i..].to_vec())
}
#[allow(dead_code)]
fn show(v: &[Token]) -> String {
tex::tokens_to_string(v)
}