raqote_utils/
lib.rs

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    // remove empty strings
33    output.retain(|x| !x.is_empty());
34    
35    output
36}
37
38/// Create a raqote::Path from a Svg path data string
39///
40/// # Arguments
41///
42/// * `svg_raw_path` - A string slice that holds the Svg path data
43///
44///---
45///
46/// supports the following Svg path data commands:
47/// m,M,l,L,v,V,h,H,c,C,s,S
48///
49/// # Example
50///
51/// ```
52/// use raqote_utils::create_path_from_string;
53///  
54/// let Letter = create_path_from_string("M105 57.0273V453.751H252.659C448.259 461.723 428.124 276.022 352.856 253.513V243.197C424.768 204.274 423.809 54.6826 252.659 57.0273H105Z");
55/// ```
56// TODO: Implement ops  T, t, A, a
57pub fn create_path_from_string(svg_raw_path: &str) -> raqote::Path {
58    // let svg_regex = format!(
59    //     r"(?:[mMlL]\s?{nr} {nr}|[vVhH]\s?{nr}|[cC]\s?{nr} {nr} {nr} {nr} {nr} {nr}|[Ss]\s?{nr} {nr} {nr} {nr})",
60    //     nr = r"(?:d?[1-9]\d*(?:\.\d*)?)"
61    // );
62    // let reg = Regex::new(svg_regex.as_str()); 
63
64    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    // println!("{:?}", elements);
91
92    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
174/// Create a raqote::Path for a circle
175///
176/// # Arguments
177///
178/// * `radius` - The radius of the circle
179/// * `x` - The x coordinate of the center of the circle
180/// * `y` - The y coordinate of the center of the circle
181///
182/// # Example
183///
184/// ```
185/// use raqote_utils::build_circle;
186///
187/// let circle = build_circle(100.0, 100.0, 100.0);
188/// ```
189pub 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
231/// <div class="warning">
232///   This method is W.I.P. are may not work as expected
233/// </div>
234///
235/// Write text to screen with ligatures
236///
237/// # Arguments
238///
239/// text: Text to write to screen
240///
241/// x: X cordinate of text
242///
243/// y: Y cordinate of text
244///
245/// font_path: path of the font to use for rendering text,
246///
247/// font_size: font size in pt
248///
249/// ctx: Draw target to draw text to
250///
251/// # Example
252///
253/// ```
254/// use raqote_utils::create_text_ligatures;
255/// use raqote::*;
256///
257/// let mut dt = DrawTarget::new(512, 512);
258///
259/// create_text_ligatures(
260///     "Hello, World\nline2",
261///     50.,
262///     50.,
263///     "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
264///     35.,
265///     &mut dt,
266///     &Source::Solid(SolidSource {
267///         r: 0x00,
268///         g: 0x00,
269///         b: 0x00,
270///         a: 0xFF,
271///     }),
272/// );
273/// ```
274pub 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    // convert font_size to px from em
284
285    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    // let x = x;
297
298    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            // Get the glyph path using font-kit
309            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. // + glyph_pos.x_advance as f32 / (64. * 2.5);
370            // println!("{x}, {:?}", glyph_pos);
371        }
372
373        // ctx.draw_text(
374        //     &font,
375        //     font_size,
376        //     line,
377        //     Point::new(x, y),
378        //     source,
379        //     &DrawOptions::new(),
380        // );
381        lo += line_height;
382    }
383}
384
385/// Write text to screen
386///
387/// # Arguments
388///
389/// text: Text to write to screen
390///
391/// x: X cordinate of text
392///
393/// y: Y cordinate of text
394///
395/// font: Font to use for rendering text,
396///
397/// font_size: font size in pt
398///
399/// ctx: Draw target to draw text to
400///
401/// # Example
402///
403/// ```
404/// use font_kit::{properties::Properties, family_name::FamilyName, source::SystemSource};
405/// use raqote_utils::create_text;
406/// use raqote::*;
407///
408/// let mut dt = DrawTarget::new(512, 512);
409///
410/// let font = SystemSource::new()
411///     .select_best_match(&[FamilyName::SansSerif], &Properties::new())
412///     .unwrap()
413///     .load()
414///     .unwrap();
415///
416/// create_text(
417///     "Hello, World\nline2",
418///     50.,
419///     50.,
420///     &font,
421///     35.,
422///     &mut dt,
423///     &Source::Solid(SolidSource {
424///         r: 0x00,
425///         g: 0x00,
426///         b: 0x00,
427///         a: 0xFF,
428///     }),
429/// );
430/// ```
431pub 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    // convert font_size to px from em
441
442    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}