1// font-kit/src/outline.rs
2//
3// Copyright © 2020 The Pathfinder Project Developers.
4//
5// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8// option. This file may not be copied, modified, or distributed
9// except according to those terms.
10
11//! Bézier paths.
12
13use pathfinder_geometry::line_segment::LineSegment2F;
14use pathfinder_geometry::vector::Vector2F;
15use std::mem;
16
17/// Receives Bézier path rendering commands.
18pub trait OutlineSink {
19 /// Moves the pen to a point.
20 fn move_to(&mut self, to: Vector2F);
21 /// Draws a line to a point.
22 fn line_to(&mut self, to: Vector2F);
23 /// Draws a quadratic Bézier curve to a point.
24 fn quadratic_curve_to(&mut self, ctrl: Vector2F, to: Vector2F);
25 /// Draws a cubic Bézier curve to a point.
26 fn cubic_curve_to(&mut self, ctrl: LineSegment2F, to: Vector2F);
27 /// Closes the path, returning to the first point in it.
28 fn close(&mut self);
29}
30
31/// A glyph vector outline or path.
32#[derive(Clone, PartialEq, Debug)]
33pub struct Outline {
34 /// The individual subpaths that make up this outline.
35 pub contours: Vec<Contour>,
36}
37
38/// A single curve or subpath within a glyph outline.
39#[derive(Clone, PartialEq, Debug)]
40pub struct Contour {
41 /// Positions of each point.
42 ///
43 /// This must have the same length as the `flags` field.
44 pub positions: Vec<Vector2F>,
45 /// Flags that specify what type of point the corresponding position represents.
46 ///
47 /// This must have the same length as the `positions` field.
48 pub flags: Vec<PointFlags>,
49}
50
51bitflags! {
52 /// Flags that specify what type of point the corresponding position represents.
53 #[derive(Clone, Debug, PartialEq)]
54 pub struct PointFlags: u8 {
55 /// This point is the control point of a quadratic Bézier curve or the first control point
56 /// of a cubic Bézier curve.
57 ///
58 /// This flag is mutually exclusive with `CONTROL_POINT_1`.
59 const CONTROL_POINT_0 = 0x01;
60 /// This point is the second control point of a cubic Bézier curve.
61 ///
62 /// This flag is mutually exclusive with `CONTROL_POINT_0`.
63 const CONTROL_POINT_1 = 0x02;
64 }
65}
66
67/// Accumulates Bézier path rendering commands into an `Outline` structure.
68#[derive(Clone, Debug)]
69pub struct OutlineBuilder {
70 outline: Outline,
71 current_contour: Contour,
72}
73
74impl Default for Outline {
75 fn default() -> Self {
76 Self::new()
77 }
78}
79
80impl Outline {
81 /// Creates a new empty outline.
82 #[inline]
83 pub fn new() -> Outline {
84 Outline { contours: vec![] }
85 }
86
87 /// Sends this outline to an `OutlineSink`.
88 pub fn copy_to<S>(&self, sink: &mut S)
89 where
90 S: OutlineSink,
91 {
92 for contour: &Contour in &self.contours {
93 contour.copy_to(sink);
94 }
95 }
96}
97
98impl Default for Contour {
99 fn default() -> Self {
100 Self::new()
101 }
102}
103
104impl Contour {
105 /// Creates a new empty contour.
106 #[inline]
107 pub fn new() -> Contour {
108 Contour {
109 positions: vec![],
110 flags: vec![],
111 }
112 }
113
114 /// Adds a new point with the given flags to the contour.
115 #[inline]
116 pub fn push(&mut self, position: Vector2F, flags: PointFlags) {
117 self.positions.push(position);
118 self.flags.push(flags);
119 }
120
121 /// Sends this contour to an `OutlineSink`.
122 pub fn copy_to<S>(&self, sink: &mut S)
123 where
124 S: OutlineSink,
125 {
126 debug_assert_eq!(self.positions.len(), self.flags.len());
127 if self.positions.is_empty() {
128 return;
129 }
130 sink.move_to(self.positions[0]);
131
132 let mut iter = self.positions[1..].iter().zip(self.flags[1..].iter());
133 while let Some((&position_0, flags_0)) = iter.next() {
134 if flags_0.is_empty() {
135 sink.line_to(position_0);
136 continue;
137 }
138
139 let (&position_1, flags_1) = iter.next().expect("Invalid outline!");
140 if flags_1.is_empty() {
141 sink.quadratic_curve_to(position_0, position_1);
142 continue;
143 }
144
145 let (&position_2, flags_2) = iter.next().expect("Invalid outline!");
146 debug_assert!(flags_2.is_empty());
147 sink.cubic_curve_to(LineSegment2F::new(position_0, position_1), position_2);
148 }
149
150 sink.close();
151 }
152}
153
154impl Default for OutlineBuilder {
155 fn default() -> Self {
156 Self::new()
157 }
158}
159
160impl OutlineBuilder {
161 /// Creates a new empty `OutlineBuilder`.
162 #[inline]
163 pub fn new() -> OutlineBuilder {
164 OutlineBuilder {
165 outline: Outline::new(),
166 current_contour: Contour::new(),
167 }
168 }
169
170 /// Consumes this outline builder and returns the resulting outline.
171 #[inline]
172 pub fn into_outline(self) -> Outline {
173 self.outline
174 }
175
176 /// Resets the outline builder and returns the old outline.
177 #[inline]
178 pub fn take_outline(&mut self) -> Outline {
179 assert!(self.current_contour.positions.is_empty());
180 self.current_contour = Contour::new();
181 mem::replace(&mut self.outline, src:Outline::new())
182 }
183}
184
185impl OutlineSink for OutlineBuilder {
186 #[inline]
187 fn move_to(&mut self, to: Vector2F) {
188 self.current_contour.push(to, PointFlags::empty());
189 }
190
191 #[inline]
192 fn line_to(&mut self, to: Vector2F) {
193 self.current_contour.push(to, PointFlags::empty());
194 }
195
196 #[inline]
197 fn quadratic_curve_to(&mut self, ctrl: Vector2F, to: Vector2F) {
198 self.current_contour.push(ctrl, PointFlags::CONTROL_POINT_0);
199 self.current_contour.push(to, PointFlags::empty());
200 }
201
202 #[inline]
203 fn cubic_curve_to(&mut self, ctrl: LineSegment2F, to: Vector2F) {
204 self.current_contour
205 .push(ctrl.from(), PointFlags::CONTROL_POINT_0);
206 self.current_contour
207 .push(ctrl.to(), PointFlags::CONTROL_POINT_1);
208 self.current_contour.push(to, PointFlags::empty());
209 }
210
211 #[inline]
212 fn close(&mut self) {
213 self.outline
214 .contours
215 .push(mem::replace(&mut self.current_contour, Contour::new()));
216 }
217}
218