veusz/
page.rs

1use crate::api1::{cmd, AutoName};
2use crate::size::SizeUnit;
3use crate::style::marker::{Marker, MarkerFill, MarkerLine};
4use crate::style::plot::PlotLine;
5use crate::style::Color;
6use crate::CommandLineEmbeddingInterface;
7use std::borrow::Cow;
8use std::io::Write;
9
10#[derive(Default)]
11pub struct Page {
12    name: AutoName<Self>,
13    items: Vec<PageItem>,
14    width: Option<SizeUnit>,
15    height: Option<SizeUnit>,
16}
17
18impl Page {
19    pub fn add(&mut self, item: impl Into<PageItem>) {
20        self.items.push(item.into());
21    }
22
23    pub fn with_item(mut self, item: impl Into<PageItem>) -> Self {
24        self.add(item);
25        self
26    }
27
28    pub fn with_items(mut self, items: impl IntoIterator<Item = impl Into<PageItem>>) -> Self {
29        self.items.extend(items.into_iter().map(Into::into));
30        self
31    }
32
33    pub fn with_width(mut self, width: impl Into<Option<SizeUnit>>) -> Self {
34        self.width = width.into();
35        self
36    }
37
38    pub fn with_height(mut self, height: impl Into<Option<SizeUnit>>) -> Self {
39        self.height = height.into();
40        self
41    }
42}
43
44impl CommandLineEmbeddingInterface for Page {
45    fn write<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
46        cmd::Add("page", &self.name).write(writer)?;
47        cmd::ToUnique(&self.name).for_call(writer, |writer| {
48            for item in &self.items {
49                item.write(writer)?;
50            }
51            Ok(())
52        })?;
53
54        if let Some(width) = self.width {
55            cmd::Set("width", &width.to_string()).write(writer)?;
56        }
57
58        if let Some(height) = self.height {
59            cmd::Set("height", &height.to_string()).write(writer)?;
60        }
61
62        Ok(())
63    }
64}
65
66#[derive(derive_more::From)]
67pub enum PageItem {
68    Graph(Graph),
69    Grid(Grid),
70    Label(Label),
71}
72
73impl CommandLineEmbeddingInterface for PageItem {
74    fn write<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
75        match self {
76            PageItem::Graph(graph) => graph.write(writer),
77            PageItem::Grid(grid) => grid.write(writer),
78            PageItem::Label(label) => label.write(writer),
79        }
80    }
81}
82
83#[derive(Default)]
84pub struct Graph {
85    name: AutoName<Self>,
86    aspect: Option<AspectRatio>,
87    axes: Vec<Axis>,
88    xy_data: Vec<Xy>,
89}
90
91impl Graph {
92    pub fn set_aspect(&mut self, aspect: impl Into<AspectRatio>) {
93        self.aspect = Some(aspect.into());
94    }
95
96    pub fn with_aspect(mut self, aspect: impl Into<AspectRatio>) -> Self {
97        self.set_aspect(aspect);
98        self
99    }
100
101    pub fn add_axis(&mut self, axis: Axis) {
102        self.axes.push(axis);
103    }
104
105    pub fn with_xy_axis(mut self, x: impl Into<String>, y: impl Into<String>) -> Self {
106        self.add_axis(Axis::x(x));
107        self.add_axis(Axis::y(y));
108        self
109    }
110
111    pub fn with_axis(mut self, axis: Axis) -> Self {
112        self.add_axis(axis);
113        self
114    }
115
116    pub fn add_xy(&mut self, xy: Xy) {
117        self.xy_data.push(xy);
118    }
119
120    pub fn with_xy(mut self, xy: Xy) -> Self {
121        self.add_xy(xy);
122        self
123    }
124
125    pub fn with_xy_sets(mut self, sets: impl IntoIterator<Item = Xy>) -> Self {
126        self.xy_data.extend(sets);
127        self
128    }
129}
130
131impl CommandLineEmbeddingInterface for Graph {
132    fn write<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
133        cmd::Add("graph", &self.name).write(writer)?;
134        cmd::ToUnique(&self.name).for_call(writer, |writer| {
135            if let Some(aspect) = self.aspect {
136                match aspect {
137                    AspectRatio::Auto => cmd::Set("aspect", "Auto").write(writer)?,
138                    AspectRatio::Fix(value) => cmd::SetRaw("aspect", value).write(writer)?,
139                }
140            }
141            for axis in &self.axes {
142                axis.write(writer)?;
143            }
144            for xy in &self.xy_data {
145                xy.write(writer)?;
146            }
147            Ok(())
148        })
149    }
150}
151#[derive(derive_more::From, Copy, Clone, PartialEq)]
152pub enum AspectRatio {
153    Auto,
154    Fix(f64),
155}
156
157#[derive(Default)]
158pub struct Grid {
159    name: AutoName<Self>,
160    rows: Option<u32>,
161    columns: Option<u32>,
162    items: Vec<PageItem>,
163}
164
165impl Grid {
166    pub fn set_rows(&mut self, rows: u32) {
167        self.rows = Some(rows);
168    }
169
170    pub fn with_rows(mut self, rows: u32) -> Self {
171        self.set_rows(rows);
172        self
173    }
174
175    pub fn set_columns(&mut self, columns: u32) {
176        self.columns = Some(columns);
177    }
178
179    pub fn with_columns(mut self, columns: u32) -> Self {
180        self.set_columns(columns);
181        self
182    }
183
184    pub fn add(&mut self, item: impl Into<PageItem>) {
185        self.items.push(item.into());
186    }
187
188    pub fn with(mut self, item: impl Into<PageItem>) -> Self {
189        self.add(item);
190        self
191    }
192
193    pub fn with_items(mut self, items: impl IntoIterator<Item = impl Into<PageItem>>) -> Self {
194        self.items.extend(items.into_iter().map(Into::into));
195        self
196    }
197}
198
199impl CommandLineEmbeddingInterface for Grid {
200    fn write<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
201        cmd::Add("grid", &self.name).write(writer)?;
202        cmd::ToUnique(&self.name).for_call(writer, |writer| {
203            if let Some(rows) = self.rows {
204                cmd::SetRaw("rows", rows).write(writer)?;
205            }
206            if let Some(columns) = self.columns {
207                cmd::SetRaw("columns", columns).write(writer)?;
208            }
209            for item in &self.items {
210                item.write(writer)?;
211            }
212            Ok(())
213        })
214    }
215}
216
217#[derive(Copy, Clone, PartialOrd, PartialEq)]
218pub enum AxisDirection {
219    Vertical,
220    Horizontal,
221}
222
223pub struct Axis {
224    name: String,
225    label: String,
226    direction: Option<AxisDirection>,
227    min: Option<f64>,
228    max: Option<f64>,
229}
230
231impl Axis {
232    pub fn x(label: impl Into<String>) -> Self {
233        Self {
234            name: "x".into(),
235            label: label.into(),
236            direction: None,
237            min: None,
238            max: None,
239        }
240    }
241
242    pub fn y(label: impl Into<String>) -> Self {
243        Self {
244            name: "y".into(),
245            label: label.into(),
246            direction: Some(AxisDirection::Vertical),
247            min: None,
248            max: None,
249        }
250    }
251
252    pub fn with_min(mut self, min: impl Into<Option<f64>>) -> Self {
253        self.min = min.into();
254        self
255    }
256
257    pub fn with_max(mut self, max: impl Into<Option<f64>>) -> Self {
258        self.max = max.into();
259        self
260    }
261}
262
263impl CommandLineEmbeddingInterface for Axis {
264    fn write<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
265        cmd::Add("axis", &self.name).write(writer)?;
266        cmd::ToUnique(&self.name).for_call(writer, |writer| {
267            cmd::Set("label", &self.label).write(writer)?;
268
269            if let Some(direction) = &self.direction {
270                cmd::Set(
271                    "direction",
272                    match direction {
273                        AxisDirection::Vertical => "vertical",
274                        AxisDirection::Horizontal => "horizontal",
275                    },
276                )
277                .write(writer)?;
278            }
279
280            if let Some(min) = self.min {
281                cmd::SetRaw("min", min).write(writer)?;
282            }
283
284            if let Some(max) = self.max {
285                cmd::SetRaw("max", max).write(writer)?;
286            }
287
288            Ok(())
289        })
290    }
291}
292
293pub struct Xy {
294    name: AutoName<Self>,
295    color: Option<Color>,
296    marker: Option<Marker>,
297    marker_line: Option<MarkerLine>,
298    marker_fill: Option<MarkerFill>,
299    plot_line: Option<PlotLine>,
300    x_data: String,
301    y_data: String,
302}
303
304impl Xy {
305    pub fn data(x_data: impl Into<String>, y_data: impl Into<String>) -> Self {
306        Self {
307            name: AutoName::default(),
308            color: None,
309            marker: None,
310            marker_line: None,
311            marker_fill: None,
312            plot_line: None,
313            x_data: x_data.into(),
314            y_data: y_data.into(),
315        }
316    }
317
318    pub fn set_color(&mut self, color: Color) {
319        self.color = Some(color);
320    }
321
322    pub fn with_color(mut self, color: Color) -> Self {
323        self.set_color(color);
324        self
325    }
326
327    pub fn set_marker(&mut self, marker: Marker) {
328        self.marker = Some(marker);
329    }
330
331    pub fn with_marker(mut self, marker: Marker) -> Self {
332        self.set_marker(marker);
333        self
334    }
335
336    pub fn set_marker_line(&mut self, marker_line: MarkerLine) {
337        self.marker_line = Some(marker_line);
338    }
339
340    pub fn with_marker_line(mut self, marker_line: MarkerLine) -> Self {
341        self.set_marker_line(marker_line);
342        self
343    }
344
345    pub fn set_marker_fill(&mut self, marker_fill: MarkerFill) {
346        self.marker_fill = Some(marker_fill);
347    }
348
349    pub fn with_marker_fill(mut self, marker_fill: MarkerFill) -> Self {
350        self.set_marker_fill(marker_fill);
351        self
352    }
353
354    pub fn set_plot_line(&mut self, plot_line: PlotLine) {
355        self.plot_line = Some(plot_line);
356    }
357
358    pub fn with_plot_line(mut self, plot_line: PlotLine) -> Self {
359        self.set_plot_line(plot_line);
360        self
361    }
362}
363
364impl CommandLineEmbeddingInterface for Xy {
365    fn write<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
366        cmd::Add("xy", &self.name).write(writer)?;
367        cmd::ToUnique(&self.name).for_call(writer, |writer| {
368            if let Some(marker) = &self.marker {
369                cmd::Set(
370                    "marker",
371                    match marker {
372                        Marker::None => "none",
373                        Marker::Circle => "circle",
374                    },
375                )
376                .write(writer)?;
377            }
378
379            if let Some(color) = &self.color {
380                color.write(writer)?;
381            }
382
383            if let Some(marker_line) = &self.marker_line {
384                marker_line.write(writer)?;
385            }
386
387            if let Some(marker_fill) = &self.marker_fill {
388                marker_fill.write(writer)?;
389            }
390
391            if let Some(plot_line) = &self.plot_line {
392                plot_line.write(writer)?;
393            }
394
395            cmd::Set("xData", &self.x_data).write(writer)?;
396            cmd::Set("yData", &self.y_data).write(writer)?;
397
398            Ok(())
399        })
400    }
401}
402
403pub struct Label {
404    name: AutoName<Self>,
405    text: Cow<'static, str>,
406    x_positions: Vec<f64>,
407    y_positions: Vec<f64>,
408    align_horizontal: Option<Alignment>,
409    align_vertical: Option<Alignment>,
410    positioning: Option<Positioning>,
411    text_config: Option<TextConfig>,
412    // config: LabelConfig, // TODO
413}
414
415impl Label {
416    pub fn set_x_positions(&mut self, positions: impl Into<Vec<f64>>) {
417        self.x_positions = positions.into();
418    }
419
420    pub fn with_x_positions(mut self, positions: impl Into<Vec<f64>>) -> Self {
421        self.set_x_positions(positions);
422        self
423    }
424
425    pub fn set_y_positions(&mut self, positions: impl Into<Vec<f64>>) {
426        self.y_positions = positions.into();
427    }
428
429    pub fn with_y_positions(mut self, positions: impl Into<Vec<f64>>) -> Self {
430        self.set_y_positions(positions);
431        self
432    }
433
434    pub fn set_align_horizontal(&mut self, alignment: Alignment) {
435        self.align_horizontal = Some(alignment);
436    }
437
438    pub fn with_alignment_horizontal(mut self, alignemnt: Alignment) -> Self {
439        self.set_align_horizontal(alignemnt);
440        self
441    }
442
443    pub fn set_align_vertical(&mut self, alignment: Alignment) {
444        self.align_vertical = Some(alignment);
445    }
446
447    pub fn with_alignment_vertical(mut self, alignment: Alignment) -> Self {
448        self.set_align_vertical(alignment);
449        self
450    }
451
452    pub fn set_text_config(&mut self, text_config: impl Into<TextConfig>) {
453        self.text_config = Some(text_config.into());
454    }
455
456    pub fn with_text_config(mut self, text_config: impl Into<TextConfig>) -> Self {
457        self.set_text_config(text_config);
458        self
459    }
460
461    pub fn set_positioning(&mut self, positioning: impl Into<Positioning>) {
462        self.positioning = Some(positioning.into());
463    }
464
465    pub fn with_positioning(mut self, positioning: impl Into<Positioning>) -> Self {
466        self.set_positioning(positioning);
467        self
468    }
469}
470
471impl<I: Into<Cow<'static, str>>> From<I> for Label {
472    fn from(value: I) -> Self {
473        Self {
474            name: Default::default(),
475            text: value.into(),
476            x_positions: Vec::default(),
477            y_positions: Vec::default(),
478            align_horizontal: None,
479            align_vertical: None,
480            positioning: None,
481            text_config: None,
482        }
483    }
484}
485
486impl CommandLineEmbeddingInterface for Label {
487    fn write<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
488        cmd::Add("label", &self.name).write(writer)?;
489        cmd::ToUnique(&self.name).for_call(writer, |writer| {
490            cmd::Set("label", &self.text).write(writer)?;
491            if !self.x_positions.is_empty() {
492                cmd::SetData(
493                    "xPos",
494                    &self
495                        .x_positions
496                        .iter()
497                        .map(ToString::to_string)
498                        .collect::<Vec<_>>()
499                        .join(","),
500                )
501                .write(writer)?;
502            }
503            if !self.y_positions.is_empty() {
504                cmd::SetData(
505                    "yPos",
506                    &self
507                        .y_positions
508                        .iter()
509                        .map(ToString::to_string)
510                        .collect::<Vec<_>>()
511                        .join(","),
512                )
513                .write(writer)?;
514            }
515            if let Some(alignment) = &self.align_horizontal {
516                cmd::Set("alignHorz", alignment.as_str()).write(writer)?;
517            }
518            if let Some(alignment) = &self.align_vertical {
519                cmd::Set("alignVert", alignment.as_str()).write(writer)?;
520            }
521            if let Some(positioning) = &self.positioning {
522                cmd::Set("positioning", positioning.as_str()).write(writer)?;
523            }
524            if let Some(text_config) = &self.text_config {
525                if let Some(size) = &text_config.size {
526                    cmd::Set("Text/size", &size.to_string()).write(writer)?;
527                }
528            }
529            Ok(())
530        })
531    }
532}
533
534#[derive(Default)]
535pub struct TextConfig {
536    size: Option<TextSize>,
537}
538
539impl<T: Into<TextSize>> From<T> for TextConfig {
540    fn from(value: T) -> Self {
541        Self {
542            size: Some(value.into()),
543        }
544    }
545}
546
547pub enum TextSize {
548    Pt(f64),
549}
550
551impl ToString for TextSize {
552    fn to_string(&self) -> String {
553        match self {
554            TextSize::Pt(pt) => format!("{pt}pt"),
555        }
556    }
557}
558
559pub enum Alignment {
560    Top,
561    Bottom,
562    Left,
563    Right,
564    Center,
565}
566
567impl Alignment {
568    pub const fn as_str(&self) -> &'static str {
569        match self {
570            Alignment::Top => "top",
571            Alignment::Bottom => "bottom",
572            Alignment::Left => "left",
573            Alignment::Right => "right",
574            Alignment::Center => "centre",
575        }
576    }
577}
578
579pub enum Positioning {
580    Relative,
581    Axes,
582}
583
584impl Positioning {
585    pub const fn as_str(&self) -> &'static str {
586        match self {
587            Positioning::Relative => "relative",
588            Positioning::Axes => "axes",
589        }
590    }
591}