pdf_canvas/
canvas.rs

1use crate::fontref::FontRef;
2use crate::fontsource::{BuiltinFont, FontSource};
3use crate::graphicsstate::*;
4use crate::outline::OutlineItem;
5use crate::textobject::TextObject;
6use std::collections::HashMap;
7use std::io::{self, Write};
8use std::sync::Arc;
9
10/// A visual area where content can be drawn (a page).
11///
12/// Provides methods for defining and stroking or filling paths, as
13/// well as placing text objects.
14///
15/// TODO Everything here that takes a `BuiltinFont` should take any
16/// `FontSource` instead.
17pub struct Canvas<'a> {
18    output: &'a mut dyn Write,
19    fonts: &'a mut HashMap<BuiltinFont, FontRef>,
20    outline_items: &'a mut Vec<OutlineItem>,
21}
22
23impl<'a> Canvas<'a> {
24    // Should not be called by user code.
25    pub(crate) fn new(
26        output: &'a mut dyn Write,
27        fonts: &'a mut HashMap<BuiltinFont, FontRef>,
28        outline_items: &'a mut Vec<OutlineItem>,
29    ) -> Self {
30        Canvas {
31            output,
32            fonts,
33            outline_items,
34        }
35    }
36
37    /// Append a closed rectangle with a corner at (x, y) and
38    /// extending width × height to the to the current path.
39    pub fn rectangle(
40        &mut self,
41        x: f32,
42        y: f32,
43        width: f32,
44        height: f32,
45    ) -> io::Result<()> {
46        writeln!(self.output, "{} {} {} {} re", x, y, width, height)
47    }
48    /// Set the line join style in the graphics state.
49    pub fn set_line_join_style(
50        &mut self,
51        style: JoinStyle,
52    ) -> io::Result<()> {
53        writeln!(
54            self.output,
55            "{} j",
56            match style {
57                JoinStyle::Miter => 0,
58                JoinStyle::Round => 1,
59                JoinStyle::Bevel => 2,
60            }
61        )
62    }
63    /// Set the line join style in the graphics state.
64    pub fn set_line_cap_style(&mut self, style: CapStyle) -> io::Result<()> {
65        writeln!(
66            self.output,
67            "{} J",
68            match style {
69                CapStyle::Butt => 0,
70                CapStyle::Round => 1,
71                CapStyle::ProjectingSquare => 2,
72            }
73        )
74    }
75    /// Set the line width in the graphics state.
76    pub fn set_line_width(&mut self, w: f32) -> io::Result<()> {
77        writeln!(self.output, "{} w", w)
78    }
79    /// Set color for stroking operations.
80    pub fn set_stroke_color(&mut self, color: Color) -> io::Result<()> {
81        let norm = |c| f32::from(c) / 255.0;
82        match color {
83            Color::RGB { red, green, blue } => writeln!(
84                self.output,
85                "{} {} {} SC",
86                norm(red),
87                norm(green),
88                norm(blue),
89            ),
90            Color::Gray { gray } => writeln!(self.output, "{} G", norm(gray)),
91        }
92    }
93    /// Set color for non-stroking operations.
94    pub fn set_fill_color(&mut self, color: Color) -> io::Result<()> {
95        let norm = |c| f32::from(c) / 255.0;
96        match color {
97            Color::RGB { red, green, blue } => writeln!(
98                self.output,
99                "{} {} {} sc",
100                norm(red),
101                norm(green),
102                norm(blue),
103            ),
104            Color::Gray { gray } => writeln!(self.output, "{} g", norm(gray)),
105        }
106    }
107
108    /// Modify the current transformation matrix for coordinates by
109    /// concatenating the specified matrix.
110    pub fn concat(&mut self, m: Matrix) -> io::Result<()> {
111        writeln!(self.output, "{} cm", m)
112    }
113
114    /// Append a straight line from (x1, y1) to (x2, y2) to the current path.
115    pub fn line(
116        &mut self,
117        x1: f32,
118        y1: f32,
119        x2: f32,
120        y2: f32,
121    ) -> io::Result<()> {
122        self.move_to(x1, y1)?;
123        self.line_to(x2, y2)
124    }
125    /// Begin a new subpath at the point (x, y).
126    pub fn move_to(&mut self, x: f32, y: f32) -> io::Result<()> {
127        write!(self.output, "{} {} m ", x, y)
128    }
129    /// Add a straight line from the current point to (x, y) to the
130    /// current path.
131    pub fn line_to(&mut self, x: f32, y: f32) -> io::Result<()> {
132        write!(self.output, "{} {} l ", x, y)
133    }
134    /// Add a Bézier curve from the current point to (x3, y3) with
135    /// (x1, y1) and (x2, y2) as Bézier controll points.
136    pub fn curve_to(
137        &mut self,
138        x1: f32,
139        y1: f32,
140        x2: f32,
141        y2: f32,
142        x3: f32,
143        y3: f32,
144    ) -> io::Result<()> {
145        writeln!(self.output, "{} {} {} {} {} {} c", x1, y1, x2, y2, x3, y3)
146    }
147    /// Add a circle approximated by four cubic Bézier curves to the
148    /// current path.  Based on
149    /// http://spencermortensen.com/articles/bezier-circle/
150    pub fn circle(&mut self, x: f32, y: f32, r: f32) -> io::Result<()> {
151        let top = y - r;
152        let bottom = y + r;
153        let left = x - r;
154        let right = x + r;
155        #[allow(clippy::excessive_precision)]
156        let c = 0.551_915_024_494;
157        let dist = r * c;
158        let up = y - dist;
159        let down = y + dist;
160        let leftp = x - dist;
161        let rightp = x + dist;
162        self.move_to(x, top)?;
163        self.curve_to(leftp, top, left, up, left, y)?;
164        self.curve_to(left, down, leftp, bottom, x, bottom)?;
165        self.curve_to(rightp, bottom, right, down, right, y)?;
166        self.curve_to(right, up, rightp, top, x, top)
167    }
168    /// Stroke the current path.
169    pub fn stroke(&mut self) -> io::Result<()> {
170        writeln!(self.output, "S")
171    }
172    /// Close and stroke the current path.
173    pub fn close_and_stroke(&mut self) -> io::Result<()> {
174        writeln!(self.output, "s")
175    }
176    /// Fill the current path.
177    pub fn fill(&mut self) -> io::Result<()> {
178        writeln!(self.output, "f")
179    }
180    /// Get a FontRef for a specific font.
181    pub fn get_font(&mut self, font: BuiltinFont) -> FontRef {
182        let next_n = self.fonts.len();
183        self.fonts
184            .entry(font)
185            .or_insert_with(|| {
186                FontRef::new(
187                    next_n,
188                    font.get_encoding().clone(),
189                    Arc::new(font.get_metrics()),
190                )
191            })
192            .clone()
193    }
194
195    /// Create a text object.
196    ///
197    /// The contents of the text object is defined by the function
198    /// render_text, by applying methods to the TextObject it gets as
199    /// an argument.
200    /// On success, return the value returned by render_text.
201    pub fn text<F, T>(&mut self, render_text: F) -> io::Result<T>
202    where
203        F: FnOnce(&mut TextObject) -> io::Result<T>,
204    {
205        writeln!(self.output, "BT")?;
206        let result = render_text(&mut TextObject::new(self.output))?;
207        writeln!(self.output, "ET")?;
208        Ok(result)
209    }
210    /// Utility method for placing a string of text.
211    pub fn left_text(
212        &mut self,
213        x: f32,
214        y: f32,
215        font: BuiltinFont,
216        size: f32,
217        text: &str,
218    ) -> io::Result<()> {
219        let font = self.get_font(font);
220        self.text(|t| {
221            t.set_font(&font, size)?;
222            t.pos(x, y)?;
223            t.show(text)
224        })
225    }
226    /// Utility method for placing a string of text.
227    pub fn right_text(
228        &mut self,
229        x: f32,
230        y: f32,
231        font: BuiltinFont,
232        size: f32,
233        text: &str,
234    ) -> io::Result<()> {
235        let font = self.get_font(font);
236        self.text(|t| {
237            let text_width = font.get_width(size, text);
238            t.set_font(&font, size)?;
239            t.pos(x - text_width, y)?;
240            t.show(text)
241        })
242    }
243    /// Utility method for placing a string of text.
244    pub fn center_text(
245        &mut self,
246        x: f32,
247        y: f32,
248        font: BuiltinFont,
249        size: f32,
250        text: &str,
251    ) -> io::Result<()> {
252        let font = self.get_font(font);
253        self.text(|t| {
254            let text_width = font.get_width(size, text);
255            t.set_font(&font, size)?;
256            t.pos(x - text_width / 2.0, y)?;
257            t.show(text)
258        })
259    }
260
261    /// Add an item for this page in the document outline.
262    ///
263    /// An outline item associates a name (contained in an ordered
264    /// tree) with a location in the document.  The PDF standard
265    /// supports several ways to specify an exact location on a page,
266    /// but this implementation currently only supports linking to a
267    /// specific page (the page that this Canvas is for).
268    pub fn add_outline(&mut self, title: &str) {
269        self.outline_items.push(OutlineItem::new(title));
270    }
271
272    /// Save the current graphics state.
273    /// The caller is responsible for restoring it later.
274    pub fn gsave(&mut self) -> io::Result<()> {
275        writeln!(self.output, "q")
276    }
277    /// Restor the current graphics state.
278    /// The caller is responsible for having saved it earlier.
279    pub fn grestore(&mut self) -> io::Result<()> {
280        writeln!(self.output, "Q")
281    }
282}