1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
//! The module with the crate's default drawer.

use crate::Drawer;
use std::path::Path;
use xml_writer::XmlWriter;

use std::fs::File;

use super::embedder::PlacedTreeItem;

pub type Result = std::io::Result<()>;

const X_MARGIN: f32 = 10.0;
const Y_MARGIN: f32 = 25.0;
const Y_FACTOR: f32 = 3.5;
const FONT_X_SIZE: f32 = 10.0;
const FONT_Y_SIZE: f32 = 10.0;

///
/// The `SvgDrawer` type provides the transformation of the embedding information into the Svg
/// format.
///
#[derive(Debug, Default)]
pub struct SvgDrawer;

impl SvgDrawer {
    /// Method to create a fresh instance of the `SvgDrawer` type.
    pub fn new() -> Self {
        Self
    }

    fn scale_y(y: usize) -> f32 {
        y as f32 * FONT_Y_SIZE * Y_FACTOR + Y_MARGIN
    }

    fn scale_x(x: usize) -> f32 {
        x as f32 * FONT_X_SIZE + X_MARGIN
    }

    fn measure_string(str: &str) -> f32 {
        str.len() as f32 * FONT_X_SIZE
    }
}

///
/// The concrete implementation of the `Drawer` trait for `SvgDrawer`.
///
impl Drawer for SvgDrawer {
    ///
    /// The concrete implementation of the `Drawer::draw` trait method.
    /// The realization is as it is - with no way to configure for instance the font used.
    /// This decision was mode for the sake of simplicity.
    ///
    /// Anyway it should be easy to provide ones own Drawer implementation that fits the concrete
    /// use case better.
    /// When using the Layouter API you can set the Drawer instance by calling the `with_drawer`
    /// method.
    ///
    /// # Panics
    ///
    /// The method should not panic. If you encounter a panic this should be originated from
    /// bugs in coding. Please report such panics.
    ///
    /// # Complexity
    ///
    /// The algorithm is of time complexity class O(n).
    ///
    fn draw(&self, file_name: &Path, embedding: &[PlacedTreeItem]) -> Result {
        let file = File::create(file_name)?;
        let mut xml = XmlWriter::new(file);

        xml.dtd("UTF-8")?;
        xml.begin_elem("svg")?;
        xml.attr("xmlns", "http://www.w3.org/2000/svg")?;
        xml.attr("version", "1.1")?;
        xml.attr("lang", "en")?;

        const STRING_FONT: &str = "font-family: 'Courier'; font-style: normal";
        const EMPHASIZE_FONT: &str =
            "font-family: 'Courier'; font-weight: bold; font-style: normal";

        let tree_depth = embedding
            .iter()
            .fold(0, |acc, e| if e.y_order > acc { e.y_order } else { acc });
        let tree_width = embedding.iter().fold(0, |acc, e| {
            if e.x_extent_children > acc {
                e.x_extent_children
            } else {
                acc
            }
        });

        let img_width = Self::scale_x(tree_width);
        let img_height = Self::scale_y(tree_depth + 1);

        xml.attr("width", format!("{}", img_width).as_str())?;
        xml.attr("height", format!("{}", img_height).as_str())?;

        // Draw on a white rectangle to be visible also on black backgrounds.
        xml.begin_elem("rect")?;
        xml.attr("x", "0")?;
        xml.attr("y", "0")?;
        xml.attr("width", format!("{}", img_width).as_str())?;
        xml.attr("height", format!("{}", img_height).as_str())?;
        xml.attr("fill", "white")?;
        xml.end_elem()?;

        for data in embedding {
            let font = if data.is_emphasized {
                EMPHASIZE_FONT
            } else {
                STRING_FONT
            };
            let szx = Self::measure_string(&data.text);
            let x = Self::scale_x(data.x_center) - szx / 2.0;
            let y = Self::scale_y(data.y_order);
            xml.begin_elem("text")?;
            xml.attr("x", format!("{}", x).as_str())?;
            xml.attr("y", format!("{}", y).as_str())?;
            xml.attr("style", font)?;
            xml.text(data.text.as_str())?;
            xml.end_elem()?;

            if let Some(parent_index) = data.parent {
                let parent_data = embedding.iter().find(|e| e.ord == parent_index).unwrap();

                // Draw a line from the nodes parent down to this node
                xml.begin_elem("line")?;
                xml.attr(
                    "x1",
                    format!("{}", (Self::scale_x(parent_data.x_center))).as_str(),
                )?;
                xml.attr(
                    "y1",
                    format!("{}", (Self::scale_y(parent_data.y_order) + FONT_Y_SIZE)).as_str(),
                )?;
                xml.attr("x2", format!("{}", (Self::scale_x(data.x_center))).as_str())?;
                xml.attr("y2", format!("{}", (y - FONT_Y_SIZE)).as_str())?;
                xml.attr("stroke", "black")?;
                xml.end_elem()?;
            }
        }

        xml.end_elem()?;
        xml.close()?;
        xml.flush()?;

        Ok(())
    }
}