forked from harfbuzz/harfbuzz_rs
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add svg render methods to Font
- Loading branch information
Showing
4 changed files
with
148 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
use crate::{ | ||
bindings::{hb_codepoint_t, hb_glyph_to_svg_path}, | ||
font::Font, | ||
shape, Feature, GlyphPosition, HarfbuzzObject, UnicodeBuffer, | ||
}; | ||
#[derive(Debug)] | ||
pub struct GlyphInfoWithSvgPath { | ||
svg_path: String, | ||
position: GlyphPosition, | ||
} | ||
|
||
#[derive(Debug)] | ||
pub struct BoundingBox { | ||
height: f64, | ||
width: f64, | ||
} | ||
|
||
impl<'a> Font<'a> { | ||
pub fn glyph_to_svg_path(&mut self, glyph: hb_codepoint_t) -> String { | ||
const PATH_BUFFER_SIZE: u32 = 65536; // should be enough for most glyphs | ||
let mut path_buffer = vec![0_i8; PATH_BUFFER_SIZE as usize]; | ||
unsafe { | ||
hb_glyph_to_svg_path( | ||
self.as_raw(), | ||
glyph, | ||
path_buffer.as_mut_ptr(), | ||
PATH_BUFFER_SIZE, | ||
); | ||
} | ||
let end_pos = path_buffer.iter().position(|&x| x == 0).unwrap(); | ||
let u8_array: Vec<u8> = path_buffer[..end_pos].iter().map(|&x| x as u8).collect(); | ||
String::from_utf8(u8_array).unwrap() | ||
} | ||
fn sharp_text_to_glyphs( | ||
&mut self, | ||
text: &str, | ||
features: &[Feature], | ||
) -> Vec<GlyphInfoWithSvgPath> { | ||
self.set_scale(100, -100); | ||
|
||
let buffer = UnicodeBuffer::new() | ||
.add_str(text) | ||
.guess_segment_properties() | ||
.set_direction(crate::Direction::Ltr); | ||
let result_buffer = shape(self, buffer, features); | ||
let infos = result_buffer.get_glyph_infos(); | ||
let positions = result_buffer.get_glyph_positions(); | ||
let result: Vec<GlyphInfoWithSvgPath> = positions | ||
.iter() | ||
.zip(infos) | ||
.map(|(position, info)| GlyphInfoWithSvgPath { | ||
svg_path: self.glyph_to_svg_path(info.codepoint), | ||
position: *position, | ||
}) | ||
.collect(); | ||
result | ||
} | ||
pub fn render_svg_text(&mut self, text: &str, features: &[Feature]) -> String { | ||
let base_line = 24; | ||
let line_height = 1.0; | ||
let line_height_px = 100.0 * line_height; | ||
let rows: Vec<Vec<GlyphInfoWithSvgPath>> = text | ||
.split('\n') | ||
.map(|t| self.sharp_text_to_glyphs(t, features)) | ||
.collect(); | ||
|
||
let mut max_bounding = BoundingBox { | ||
height: 0.0, | ||
width: 0.0, | ||
}; | ||
|
||
let mut bounding = BoundingBox { | ||
height: line_height_px, | ||
width: 0.0, | ||
}; | ||
|
||
let paths: Vec<String> = rows | ||
.iter() | ||
.flat_map(|row| { | ||
let rendered_row: Vec<String> = row | ||
.iter() | ||
.map(|glyph| { | ||
let path: String = format!( | ||
r#"<path transform="translate({} {})" d="{}"></path>"#, | ||
bounding.width, | ||
bounding.height + glyph.position.y_advance as f64, | ||
glyph.svg_path | ||
); | ||
bounding.width += glyph.position.x_advance as f64; | ||
path | ||
}) | ||
.collect(); | ||
// set the maximum bounding box | ||
max_bounding.height = max_bounding.height.max(bounding.height as f64); | ||
max_bounding.width = max_bounding.width.max(bounding.width as f64); | ||
|
||
// reset row width | ||
bounding.height += line_height_px; | ||
bounding.width = 0.0; | ||
|
||
rendered_row | ||
}) | ||
.collect(); | ||
|
||
format!( | ||
r#"<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{}" height="{}" viewBox="0 0 {} {}">{}</svg>"#, | ||
max_bounding.width, | ||
max_bounding.height, | ||
max_bounding.width + base_line as f64, | ||
max_bounding.height + base_line as f64, | ||
paths.join("") | ||
) | ||
} | ||
} | ||
#[cfg(test)] | ||
mod test { | ||
|
||
use crate::Face; | ||
|
||
use super::*; | ||
|
||
#[test] | ||
fn test_glyph_to_svg() { | ||
let path = "testfiles/SourceSansVariable-Roman.ttf"; | ||
let face = Face::from_file(path, 0).unwrap(); | ||
let mut font = Font::new(face); | ||
let graph_str = font.glyph_to_svg_path(b'h' as u32); | ||
assert_eq!( | ||
graph_str, | ||
"M100,0L100,660L454,660L454,632L132,632L132,366L402,366L402,338L132,338L132,28L464,28L464,0L100,0ZM185,710L171,726L267,808L295,808L391,726L377,710L283,778L279,778L185,710ZM143,844Q144,860 149.5,877.5Q155,895 169.5,907.5Q184,920 211,920Q237,920 255.5,912Q274,904 289,893Q304,882 318.5,874Q333,866 353,866Q374,866 383,880.5Q392,895 395,918L419,916Q418,900 412.5,882.5Q407,865 392.5,852.5Q378,840 351,840Q325,840 306.5,848Q288,856 273,867Q258,878 243.5,886Q229,894 209,894Q188,894 179,879.5Q170,865 167,842L143,844Z") | ||
} | ||
#[test] | ||
fn test_svg_text() { | ||
let path = "testfiles/SourceSansVariable-Roman.ttf"; | ||
let face = Face::from_file(path, 0).unwrap(); | ||
let mut font = Font::new(face); | ||
let features = [Feature::new(b"liga", 1, 0..10000)]; | ||
let graph_str = font.render_svg_text("Hello World\nIt's me!\nliga feature fft!", &features); | ||
assert_eq!(graph_str.starts_with("<svg"), true); | ||
assert_eq!(graph_str.ends_with("</path></svg>"), true); | ||
} | ||
} |