1use pyo3::prelude::*;
6
7mod autolayout;
8mod config;
9mod doc;
10mod error;
11mod md;
12mod emit;
13mod esc;
14mod mathwidth;
15mod md_spike;
16mod mermaid;
17mod plot;
18mod layout;
19mod preset;
20mod sequence;
21mod state;
22mod table;
23#[cfg(all(feature = "tectonic", target_os = "linux"))]
24mod tectonic;
25mod texenv;
26
27fn verr(e: crate::error::Error) -> pyo3::PyErr {
30 pyo3::exceptions::PyValueError::new_err(e.to_string())
31}
32
33#[pyfunction]
39fn flowchart_to_tikz(src: &str) -> PyResult<String> {
40 mermaid::flowchart_to_tikz(src, 1).map_err(verr)
41}
42
43#[pyfunction]
51#[allow(clippy::too_many_arguments)]
52#[pyo3(signature = (title, ylabel, log_y, group_labels, series_names, values, mins, maxs, refline, legend="auto"))]
53fn barchart_to_tikz(
54 title: &str,
55 ylabel: &str,
56 log_y: bool,
57 group_labels: Vec<String>,
58 series_names: Vec<String>,
59 values: Vec<Vec<f64>>,
60 mins: Vec<Vec<f64>>,
61 maxs: Vec<Vec<f64>>,
62 refline: Option<(f64, f64, f64, String)>,
63 legend: &str,
64) -> PyResult<String> {
65 let legend = plot::LegendPos::parse(legend).map_err(verr)?;
66 plot::barchart(
67 title,
68 ylabel,
69 log_y,
70 &group_labels,
71 &series_names,
72 &values,
73 &mins,
74 &maxs,
75 refline,
76 legend,
77 )
78 .map_err(verr)
79}
80
81#[pyfunction]
84#[pyo3(signature = (title, xlabel, ylabel, xs, names, yss, errs, legend="auto"))]
85fn lineplot_to_tikz(
86 title: &str,
87 xlabel: &str,
88 ylabel: &str,
89 xs: Vec<f64>,
90 names: Vec<String>,
91 yss: Vec<Vec<f64>>,
92 errs: Vec<Vec<f64>>,
93 legend: &str,
94) -> PyResult<String> {
95 let legend = plot::LegendPos::parse(legend).map_err(verr)?;
96 if names.len() != yss.len() || names.len() != errs.len() {
97 return Err(verr(crate::error::Error::data(
98 "lineplot: names/yss/errs length mismatch",
99 format!("names={} yss={} errs={}", names.len(), yss.len(), errs.len()),
100 )));
101 }
102 let series: Vec<(&str, Vec<f64>, Vec<f64>)> = names
103 .iter()
104 .zip(yss.into_iter())
105 .zip(errs.into_iter())
106 .map(|((n, y), e)| (n.as_str(), y, e))
107 .collect();
108 plot::lineplot(title, xlabel, ylabel, &xs, &series, legend).map_err(verr)
109}
110
111#[pyfunction]
114fn preset_to_tikz(src: &str) -> PyResult<String> {
115 preset::preset_to_tikz(src).map_err(verr)
116}
117
118#[pyfunction]
121fn parse_config(src: &str) -> PyResult<std::collections::HashMap<String, String>> {
122 let cfg = config::Config::from_toml(src)
123 .map_err(|e| pyo3::exceptions::PyValueError::new_err(e))?;
124 let mut m = std::collections::HashMap::new();
125 m.insert("class".to_string(), cfg.document.class.clone());
126 m.insert(
127 "class_options".to_string(),
128 cfg.document.class_options.join(","),
129 );
130 m.insert("columns".to_string(), cfg.document.columns.to_string());
131 m.insert("margins".to_string(), cfg.document.margins.clone());
132 m.insert("template_preamble_file".to_string(), cfg.template.preamble_file.clone());
133 m.insert("template_preamble_append".to_string(), cfg.template.preamble_append.join("\n"));
134 m.insert(
135 "template_abstract_before_maketitle".to_string(),
136 cfg.template.abstract_before_maketitle.to_string(),
137 );
138 m.insert("float_pos_default".to_string(), format!("{:?}", cfg.floats.pos_default).to_lowercase());
139 m.insert("float_topfraction".to_string(), cfg.floats.topfraction.to_string());
140 m.insert("float_bottomfraction".to_string(), cfg.floats.bottomfraction.to_string());
141 m.insert("float_textfraction".to_string(), cfg.floats.textfraction.to_string());
142 m.insert("float_floatpagefraction".to_string(), cfg.floats.floatpagefraction.to_string());
143 m.insert("float_topnumber".to_string(), cfg.floats.topnumber.to_string());
144 m.insert("float_bottomnumber".to_string(), cfg.floats.bottomnumber.to_string());
145 m.insert("float_barrier_sections".to_string(), cfg.floats.barrier_sections.to_string());
146 m.insert("typography_keep_with_next".to_string(), cfg.typography.keep_with_next.to_string());
147 m.insert("typography_min_lines".to_string(), cfg.typography.min_lines.to_string());
148 m.insert("template_skeleton".to_string(), cfg.template.skeleton.clone());
149 for kind in ["diagram", "plot", "table", "picture", "default"] {
150 let span = cfg.spans.for_kind(kind);
151 m.insert(format!("span_{kind}"), span.latex_env().to_string());
152 m.insert(format!("width_{kind}"), span.latex_width().to_string());
153 }
154 Ok(m)
155}
156
157#[pyfunction]
163fn build_document(md_text: &str, toml_src: &str, spec_json: &str) -> PyResult<String> {
164 let spec: doc::BuildSpec = serde_json::from_str(spec_json)
165 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("spec: {e}")))?;
166 let r = doc::build_document(md_text, toml_src, &spec).map_err(verr)?;
167 serde_json::to_string(&r)
168 .map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))
169}
170
171#[pyfunction]
178fn suggest_layout(md_text: &str, toml_src: &str, spec_json: &str) -> PyResult<String> {
179 let spec: doc::BuildSpec = serde_json::from_str(spec_json)
180 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("spec: {e}")))?;
181 autolayout::suggest_layout_json(md_text, toml_src, &spec).map_err(verr)
182}
183
184#[pyfunction]
187fn bib_safe(bib_src: &str) -> String {
188 table::bib_safe(bib_src)
189}
190
191#[pyfunction]
193fn bib_keys(bib_src: &str) -> Vec<String> {
194 let mut v: Vec<String> = table::bib_keys_of(bib_src).into_iter().collect();
195 v.sort();
196 v
197}
198
199#[pyfunction]
201fn tikz_needed(tex: &str) -> bool {
202 doc::tikz_needed(tex)
203}
204
205#[pyfunction]
207fn plots_needed(tex: &str) -> bool {
208 doc::plots_needed(tex)
209}
210
211#[pyfunction]
214fn tex_requirements(has_tikz: bool, has_plots: bool) -> Vec<String> {
215 doc::tex_requirements(has_tikz, has_plots)
216}
217
218#[pyfunction]
222fn used_packages(tex: &str) -> Vec<String> {
223 texenv::used_packages(tex)
224}
225
226#[pyfunction]
228fn document_class(tex: &str) -> Option<String> {
229 texenv::document_class(tex)
230}
231
232#[pyfunction]
235fn package_needs(names: Vec<String>, class: Option<String>) -> Vec<(String, String)> {
236 texenv::package_needs(&names, class.as_deref())
237}
238
239#[pyfunction]
242fn tex_extra_packages(tex: &str) -> Vec<String> {
243 doc::tex_extra_packages(tex)
244}
245
246#[cfg(all(feature = "tectonic", target_os = "linux"))]
250#[pyfunction]
251fn compile_tectonic_pdf(tex: &str) -> PyResult<Vec<u8>> {
252 crate::tectonic::compile_tectonic(tex).map_err(pyo3::exceptions::PyRuntimeError::new_err)
253}
254
255#[pymodule]
257fn _core(m: &Bound<'_, PyModule>) -> PyResult<()> {
258 m.add_function(wrap_pyfunction!(flowchart_to_tikz, m)?)?;
259 m.add_function(wrap_pyfunction!(barchart_to_tikz, m)?)?;
260 m.add_function(wrap_pyfunction!(lineplot_to_tikz, m)?)?;
261 m.add_function(wrap_pyfunction!(preset_to_tikz, m)?)?;
262 m.add_function(wrap_pyfunction!(parse_config, m)?)?;
263 m.add_function(wrap_pyfunction!(build_document, m)?)?;
264 m.add_function(wrap_pyfunction!(bib_safe, m)?)?;
265 m.add_function(wrap_pyfunction!(bib_keys, m)?)?;
266 m.add_function(wrap_pyfunction!(tex_requirements, m)?)?;
267 m.add_function(wrap_pyfunction!(tikz_needed, m)?)?;
268 m.add_function(wrap_pyfunction!(plots_needed, m)?)?;
269 m.add_function(wrap_pyfunction!(tex_extra_packages, m)?)?;
270 m.add_function(wrap_pyfunction!(used_packages, m)?)?;
271 m.add_function(wrap_pyfunction!(document_class, m)?)?;
272 m.add_function(wrap_pyfunction!(package_needs, m)?)?;
273 m.add_function(wrap_pyfunction!(suggest_layout, m)?)?;
274 #[cfg(all(feature = "tectonic", target_os = "linux"))]
275 m.add_function(wrap_pyfunction!(compile_tectonic_pdf, m)?)?;
276 Ok(())
277}