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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
//! Things to do with the document being created, its size, its borders, etc
use polygonical::point::Point;
use crate::convert;
use crate::error::Error;
/// Describe borders around the edge of a page. These don't prevent you drawing off the side of the page
/// they are here to help you keep track of them, they can freely be set to zero.
pub struct Borders {
pub top: i32,
pub bottom: i32,
pub left: i32,
pub right: i32,
}
impl Borders {
/// Create a default border of half an inch on all sides
pub fn default(dpi: i32) -> Borders {
Borders::even(0.5, dpi)
}
/// Create a border of size on all four sides
pub fn even(size: f64, dpi: i32) -> Borders {
Borders {
top: convert::inches_to_pixels(size, dpi),
bottom: convert::inches_to_pixels(size, dpi),
left: convert::inches_to_pixels(size, dpi),
right: convert::inches_to_pixels(size, dpi),
}
}
pub fn rotate(&self) -> Borders {
Borders {
top: self.left,
bottom: self.right,
left: self.bottom,
right: self.top,
}
}
}
macro_rules! paper_size {
// There isn't a way to concatenate identifiers in function names so we have to provide both.
// this sucks, but the long_name should always be equal to ${name}_with_border
($($name:ident, $long_name:ident: $width:expr, $height:expr,)*) => {
$(
#[allow(non_snake_case)]
pub fn $name(dpi:i32) -> Page {
Page::$long_name(dpi, Borders::default(dpi))
}
#[allow(non_snake_case)]
pub fn $long_name(dpi: i32, border: Borders) -> Page {
Page {
dpi,
width: convert::inches_to_pixels($width, dpi),
height: convert::inches_to_pixels($height, dpi),
borders: border,
}
}
)*
};
}
/// Page is used to define the size of an svg you wish to create.
pub struct Page {
/// Pixels per inch for calculating conversions
pub dpi: i32,
/// Width of the page in pixels
pub width: i32,
/// Height of the page in pixels
pub height: i32,
/// Borders represent the padding around the edge of the page that should be left alone.
/// Note: it is up to you to deal with this, nothing will stop you drawing over the border areas
pub borders: Borders,
}
impl Page {
/// Construct a page given a name for it and the dpi and margin information.
///
/// The name can either be something like A3 or Letter or 200mmx200in to create a 200 millimetre by 200 inch svg
/// Margin is always in inches. It doesn't stop you drawing over the edge of the page.
pub fn build_page(name: &str, dpi: i32, margin: f64) -> Result<Page, Error> {
let border = Borders::even(margin, dpi);
// WARN: if we ever get a paper size with an x this will break. At the moment it is fine.
if name.contains('x') {
let parts: Vec<&str> = name.split('x').collect();
if parts.len() == 2 {
let width = convert::parse_length(parts[0], dpi)?;
let height = convert::parse_length(parts[1], dpi)?;
Ok(Page {
dpi,
width,
height,
borders: border,
})
} else {
Err(Error::UnknownPaper(name.to_string()))
}
} else {
match name.to_lowercase().as_str() {
// TODO: add more paper sizes
"a5" => Ok(Page::A5_with_border(dpi, border)),
"a4" => Ok(Page::A4_with_border(dpi, border)),
"a3" => Ok(Page::A3_with_border(dpi, border)),
"letter" => Ok(Page::letter_with_border(dpi, border)),
_ => Err(Error::UnknownPaper(name.to_string())),
}
}
}
// Create paper sizes using macros to avoid duplication
// TODO: add more paper sizes (make sure to keep with the same pattern)
paper_size!(
A5, A5_with_border: 5.8, 8.27,
A4, A4_with_border: 8.27, 11.7,
A3, A3_with_border: 11.7, 16.5,
letter, letter_with_border: 8.5, 11.0,
);
/// Rotate this page through 90 degrees, portrait to landscape and landscape to portrait.
pub fn rotate(&self) -> Page {
Page {
dpi: self.dpi,
width: self.height,
height: self.width,
borders: self.borders.rotate(),
}
}
/// returns true if the height of the page is greater than its width
/// Note: a square page will return false
pub fn is_portrait(&self) -> bool {
self.height > self.width
}
/// returns true if the width of the page is greater than the height
/// Note: a square page will return false
pub fn is_landscape(&self) -> bool {
self.width > self.height
}
/// Return the point in the document that matches to the borders in the top left corner
pub fn top_left(&self) -> Point {
Point::new(self.borders.left, self.borders.top)
}
/// Return the point in the document that matches to the borders in the top left corner
pub fn top_right(&self) -> Point {
Point::new(self.width - self.borders.right, self.borders.top)
}
/// Return the point in the document that matches to the borders in the bottom left corner
pub fn bottom_left(&self) -> Point {
Point::new(self.borders.left, self.height - self.borders.bottom)
}
/// Return the point in the document that matches to the borders in the bottom right corner
pub fn bottom_right(&self) -> Point {
Point::new(
self.width - self.borders.right,
self.height - self.borders.bottom,
)
}
/// Return the center point of the document including the borders
pub fn center(&self) -> Point {
Point::new(
self.borders.left + ((self.width - self.borders.right - self.borders.left) / 2),
self.borders.top + ((self.height - self.borders.top - self.borders.bottom) / 2),
)
}
/// Return the point on the left border in the middle vertically
pub fn center_left(&self) -> Point {
Point::new(
self.borders.left,
self.borders.top + ((self.height - self.borders.top - self.borders.bottom) / 2),
)
}
/// Return the point on the right border in the middle vertically
pub fn center_right(&self) -> Point {
Point::new(
self.width - self.borders.right,
self.borders.top + ((self.height - self.borders.top - self.borders.bottom) / 2),
)
}
/// Return the point on the top border in the middle horizontally
pub fn center_top(&self) -> Point {
Point::new(
self.borders.left + ((self.width - self.borders.right - self.borders.left) / 2),
self.borders.top,
)
}
/// Return the point on the bottom border in the middle horizontally
pub fn center_bottom(&self) -> Point {
Point::new(
self.borders.left + ((self.width - self.borders.right - self.borders.left) / 2),
self.height - self.borders.bottom,
)
}
/// Return the width of the page minus the borders in pixels
pub fn display_width_px(&self) -> i32 {
self.width - self.borders.right - self.borders.left
}
/// Return the width of the page minus the borders in pixels
pub fn display_height_px(&self) -> i32 {
self.height - self.borders.top - self.borders.bottom
}
}