1use euclid::Transform2D;
2use font_kit::font::Font;
3use font_kit::hinting::HintingOptions;
4use font_kit::outline::OutlineSink;
5use pathfinder_geometry::vector::Vector2F;
6use raqote::{DrawOptions, DrawTarget, PathBuilder, Point, Source};
7use rustybuzz::{Face, UnicodeBuffer};
8
9
10fn split_path(target: &str) -> Vec<String> {
11 let split_chars = [' ', ','];
12 let mut buffer = String::new();
13 let mut output: Vec<String> = Vec::new();
14
15 for character in target.chars() {
16 if split_chars.contains(&character) {
17 output.push(buffer.clone());
18 buffer.clear();
19 } else if character.is_alphabetic() {
20 output.push(buffer.clone());
21 buffer.clear();
22 buffer.push(character);
23
24 } else {
25 buffer.push(character);
26 }
27 }
28 if !buffer.is_empty() {
29 output.push(buffer);
30 }
31
32 output.retain(|x| !x.is_empty());
34
35 output
36}
37
38pub fn create_path_from_string(svg_raw_path: &str) -> raqote::Path {
58 let mut elements_values = split_path(svg_raw_path).into_iter().peekable();
65
66 let mut elements: Vec<(char, Vec<f32>)> = Vec::new();
67
68
69 while let Some(element) = elements_values.next() {
70 let mut args: Vec<f32> = Vec::new();
71 let command = element.chars().nth(0).unwrap();
72 if command == 'Z' {
73 elements.push(('Z', vec![]));
74 continue;
75 }
76
77 let first_arg = element.chars().skip(1).collect::<String>();
78 if !first_arg.is_empty() {
79 args.push(first_arg.parse::<f32>().expect("Failed to parse number"));
80 }
81 while elements_values.peek().unwrap().chars().nth(0).unwrap().is_digit(10) {
82 let v = elements_values.next().unwrap();
83 args.push(v.parse::<f32>().expect("Failed to parse number"));
84 }
85
86 elements.push((command, args));
87
88 }
89
90 let mut path = PathBuilder::new();
93
94 let mut last_x = 0.0;
95 let mut last_y = 0.0;
96
97 for (command, values) in elements.into_iter() {
98 match command.to_string().as_str() {
99 "m" => {
100 last_x += values[0];
101 last_y += values[1];
102 path.move_to(last_x, last_y);
103 }
104 "M" => {
105 last_x = values[0];
106 last_y = values[1];
107 path.move_to(last_x, last_y);
108 }
109 "l" => {
110 last_x += values[0];
111 last_y += values[1];
112 path.line_to(last_x, last_y);
113 }
114 "L" => {
115 last_x = values[0];
116 last_y = values[1];
117 path.line_to(last_x, last_y);
118 }
119 "v" => {
120 last_y += values[0];
121 path.line_to(last_x, last_y);
122 }
123 "V" => {
124 last_y = values[0];
125 path.line_to(last_x, last_y);
126 }
127 "h" => {
128 last_x += values[0];
129 path.line_to(last_x, last_y);
130 }
131 "H" => {
132 last_x = values[0];
133 path.line_to(last_x, last_y);
134 }
135 "c" => {
136 let x1 = last_x + values[0];
137 let y1 = last_y + values[1];
138 let x2 = last_x + values[2];
139 let y2 = last_y + values[3];
140 last_x += values[4];
141 last_y += values[5];
142 path.cubic_to(x1, y1, x2, y2, last_x, last_y);
143 }
144 "C" => {
145 let x1 = values[0];
146 let y1 = values[1];
147 let x2 = values[2];
148 let y2 = values[3];
149 last_x = values[4];
150 last_y = values[5];
151 path.cubic_to(x1, y1, x2, y2, last_x, last_y);
152 }
153 "s" => {
154 let x1 = last_x + values[0];
155 let y1 = last_y + values[1];
156 last_x += values[2];
157 last_y += values[3];
158 path.quad_to(x1, y1, last_x, last_y);
159 }
160 "S" => {
161 let x1 = values[0];
162 let y1 = values[1];
163 last_x = values[2];
164 last_y = values[3];
165 path.quad_to(x1, y1, last_x, last_y);
166 }
167 _ => {}
168 }
169 }
170
171 path.finish()
172}
173
174pub fn build_circle(radius: f32, x: f32, y: f32) -> raqote::Path {
190 let mut pb = PathBuilder::new();
191
192 let x = x - radius;
193 let y = y + radius;
194
195 let offset = 0.5522847498 * radius;
196
197 pb.move_to(x + radius, y);
198
199 pb.cubic_to(
200 x + radius + offset,
201 y,
202 x + (radius * 2.),
203 y - radius + offset,
204 x + (radius * 2.),
205 y - radius,
206 );
207
208 pb.cubic_to(
209 x + (radius * 2.),
210 y - radius - offset,
211 x + radius + offset,
212 y - (radius * 2.),
213 x + radius,
214 y - (radius * 2.),
215 );
216
217 pb.cubic_to(
218 x + radius - offset,
219 y - (radius * 2.),
220 x,
221 y - radius - offset,
222 x,
223 y - radius,
224 );
225
226 pb.cubic_to(x, y - offset, x + radius - offset, y, x + radius, y);
227
228 pb.finish()
229}
230
231pub fn create_text_ligatures(
275 text: &str,
276 x: f32,
277 y: f32,
278 font_path: &str,
279 font_size: f32,
280 ctx: &mut DrawTarget,
281 source: &Source<'_>,
282) {
283 let line_height = (font_size / 72.) * 96.;
286
287 let lines = text.split("\n").collect::<Vec<&str>>();
288 let lines = lines.iter();
289
290 let font_data = std::fs::read(font_path).unwrap();
291
292 let face = Face::from_slice(&font_data, 0).unwrap();
293 let font = Font::from_bytes(font_data.clone().into(), 0).unwrap();
294
295 let mut lo = y;
296 for (_li, line) in lines.enumerate() {
299 let mut x = x;
300 let mut buffer = UnicodeBuffer::new();
301 buffer.push_str(&line);
302 let glyph_buffer = rustybuzz::shape(&face, &[], buffer);
303
304 for (i, glyph) in glyph_buffer.glyph_infos().iter().enumerate() {
305 let glyph_pos = glyph_buffer.glyph_positions()[i];
306 let glyph_id = glyph.glyph_id;
307
308 let mut path_builder = PathBuilder::new();
310
311 pub struct MySink<'a> {
312 path_builder: &'a mut PathBuilder,
313 }
314
315 impl<'a> OutlineSink for MySink<'a> {
316 fn move_to(&mut self, to: Vector2F) {
317 self.path_builder.move_to(to.x(), to.y());
318 }
319
320 fn line_to(&mut self, to: Vector2F) {
321 self.path_builder.line_to(to.x(), to.y());
322 }
323
324 fn cubic_curve_to(
325 &mut self,
326 ctrl: pathfinder_geometry::line_segment::LineSegment2F,
327 to: Vector2F,
328 ) {
329 self.path_builder.cubic_to(
330 ctrl.from().x(),
331 ctrl.from().y(),
332 ctrl.to().x(),
333 ctrl.to().y(),
334 to.x(),
335 to.y(),
336 );
337 }
338 fn quadratic_curve_to(&mut self, ctrl: Vector2F, to: Vector2F) {
339 self.path_builder
340 .quad_to(ctrl.x(), ctrl.y(), to.x(), to.y());
341 }
342
343 fn close(&mut self) {
344 self.path_builder.close();
345 }
346 }
347
348 let _ = font.outline(
349 glyph_id,
350 HintingOptions::None,
351 &mut MySink {
352 path_builder: &mut path_builder,
353 },
354 );
355
356 let path = path_builder.finish();
357
358 let path = path.transform(&Transform2D::new(
359 line_height / 2048.,
360 0.0,
361 0.0,
362 -line_height / 2048.,
363 x,
364 y + lo - (line_height),
365 ));
366
367 ctx.fill(&path, &source, &DrawOptions::new());
368
369 x += glyph_pos.x_advance as f32 / 64. }
372
373 lo += line_height;
382 }
383}
384
385pub fn create_text(
432 text: &str,
433 x: f32,
434 y: f32,
435 font: &Font,
436 font_size: f32,
437 ctx: &mut DrawTarget,
438 source: &Source<'_>,
439) {
440 let line_height = (font_size / 72.) * 96.;
443
444 let lines = text.split("\n").collect::<Vec<&str>>();
445 let lines = lines.iter();
446
447 let mut y = y;
448 for line in lines {
449 ctx.draw_text(
450 &font,
451 font_size,
452 line,
453 Point::new(x, y),
454 source,
455 &DrawOptions::new(),
456 );
457 y += line_height;
458 }
459}