use std::rc::Rc;
use std::string::String;
use std::sync::Arc;
use std::{fs::File, io::Write, path::Path};
use crate::generator::expression::expr::AnglePoint;
use crate::generator::expression::{Expression, PointExpr, ScalarExpr};
use crate::generator::Complex;
use crate::labels::get_special_char_latex;
use crate::projector::{
Output, Rendered, RenderedAngle, RenderedCircle, RenderedLine, RenderedPoint, RenderedRay,
RenderedSegment,
};
use crate::script::HashableArc;
use crate::script::figure::{MathChar, MathIndex};
use crate::script::figure::Style::{self, Bold, Dashed, Solid, Dotted};
fn get_point_name(
expr: &Arc<Expression<PointExpr>>,
output: &Output,
point: Complex,
scale: f64,
) -> String {
match output.map.get(&HashableArc::new(Arc::clone(expr))) {
Some(p) => {
format!("q{}", p.uuid)
}
None => {
format!("({}, {})", (point.real * scale), point.imaginary * scale)
}
}
}
fn styling(rendered: &Rendered, mode: Style) -> String {
match rendered {
Rendered::Point(_) => unreachable!(),
_ => match mode {
Dotted => "dotted".to_string(),
Dashed => "dashed".to_string(),
Bold => "ultra thick".to_string(),
Solid => "thin".to_string(),
},
}
}
fn points(point: &Rc<RenderedPoint>, scale: f64) -> String {
let position = point.position * scale;
let label_position = point.label_position * scale;
let mut label: String = String::default();
let mut seen = false;
let mut lower_last = false;
for char in &point.math_string.chars {
match char {
MathChar::Ascii(c) => {
label += &c.to_string();
}
MathChar::Special(c) => {
label += get_special_char_latex(c);
}
MathChar::SetIndex(i) => {
match i {
MathIndex::Normal => {
if seen {
label += "}";
}
lower_last = false;
}
MathIndex::Lower => {
seen = true;
label += "_{";
lower_last = true;
}
}
}
MathChar::Prime => {
label += "^{'}";
}
}
}
if lower_last {
label += "}";
}
format!(
r#"
\coordinate ({}) at ({}, {}); \fill[black] ({}) circle (1pt);
\node at ({}, {}) {{${}$}};
"#,
point.uuid, position.real, position.imaginary, point.uuid, label_position.real, label_position.imaginary, label
)
}
fn lines(line: &RenderedLine, scale: f64, rendered: &Rendered) -> String {
let pos1 = line.points.0 * scale;
let pos2 = line.points.1 * scale;
format!(
r#"
\begin{{scope}}
\coordinate (A) at ({},{});
\coordinate (B) at ({},{});
\tkzDrawSegment[{}](A,B)
\end{{scope}}
"#,
pos1.real,
pos1.imaginary,
pos2.real,
pos2.imaginary,
styling(rendered, line.style)
)
}
fn angles(angle: &RenderedAngle, scale: f64, output: &Output, rendered: &Rendered) -> String {
let no_arcs = String::from("l"); match &angle.expr.kind {
ScalarExpr::AnglePoint(AnglePoint { arm1, origin, arm2 }) => {
format!(
r#"
\begin{{scope}}
\coordinate (A) at {};
\coordinate (B) at {};
\coordinate (C) at {};
\tkzMarkAngle[size = 0.5,mark = none,arc={no_arcs},mkcolor = black, {}](A,B,C)
\end{{scope}}
"#,
get_point_name(arm1, output, angle.points.0, scale),
get_point_name(origin, output, angle.points.1, scale),
get_point_name(arm2, output, angle.points.2, scale),
styling(rendered, angle.style)
)
}
ScalarExpr::AngleLine(_) => {
format!(
r#"
\begin{{scope}}
\coordinate (A) at ({}, {});
\coordinate (B) at ({}, {});
\coordinate (C) at ({}, {});
\tkzMarkAngle[size = 2,mark = none,arc={no_arcs},mkcolor = black, {}](A,B,C)
\end{{scope}}
"#,
angle.points.0.real,
angle.points.0.imaginary,
angle.points.1.real,
angle.points.1.imaginary,
angle.points.2.real,
angle.points.2.imaginary,
styling(rendered, angle.style)
)
}
_ => unreachable!(),
}
}
fn segments(segment: &RenderedSegment, scale: f64, rendered: &Rendered) -> String {
let pos1 = segment.points.0 * scale;
let pos2 = segment.points.1 * scale;
format!(
r#"
\begin{{scope}}
\coordinate (A) at ({}, {});
\coordinate (B) at ({}, {});
\tkzDrawSegment[{}](A,B)
\end{{scope}}
"#,
pos1.real,
pos1.imaginary,
pos2.real,
pos2.imaginary,
styling(rendered, segment.style)
)
}
fn rays(ray: &RenderedRay, scale: f64, rendered: &Rendered) -> String {
let pos1 = ray.points.0 * scale;
let pos2 = ray.points.1 * scale;
format!(
r#"
\begin{{scope}}
\coordinate (A) at ({}, {});
\coordinate (B) at ({}, {});
\tkzDrawSegment[{}](A,B)
\end{{scope}}
"#,
pos1.real,
pos1.imaginary,
pos2.real,
pos2.imaginary,
styling(rendered, ray.style)
)
}
fn circles(circle: &RenderedCircle, scale: f64, rendered: &Rendered) -> String {
let pos1 = circle.center * scale;
let pos2 = circle.draw_point * scale;
format!(
r#"
\begin{{scope}}
\coordinate (A) at ({}, {});
\coordinate (B) at ({}, {});
\tkzDrawCircle[{}](A,B)
\end{{scope}}
"#,
pos1.real,
pos1.imaginary,
pos2.real,
pos2.imaginary,
styling(rendered, circle.style)
)
}
pub fn draw(target: &Path, canvas_size: (usize, usize), output: &Output) {
#[allow(clippy::cast_precision_loss)]
let scale = f64::min(10.0 / canvas_size.0 as f64, 10.0 / canvas_size.1 as f64);
let mut content = String::from(
r"
\documentclass{article}
\usepackage{tikz}
\usepackage{tkz-euclide}
\usetikzlibrary {angles,calc,quotes}
\begin{document}
\begin{tikzpicture}
",
);
for item in &output.vec_rendered {
content += &match item {
Rendered::Point(point) => points(point, scale),
Rendered::Line(line) => lines(line, scale, item),
Rendered::Angle(angle) => angles(angle, scale, output, item),
Rendered::Segment(segment) => segments(segment, scale, item),
Rendered::Ray(ray) => rays(ray, scale, item),
Rendered::Circle(circle) => circles(circle, scale, item),
}
}
content += "\\end{tikzpicture} \\end{document}";
let mut file = File::create(target).unwrap();
file.write_all(content.as_bytes()).unwrap();
}