diff --git a/.github/workflows/linux.yaml b/.github/workflows/linux.yaml index 3599ba5..740971b 100644 --- a/.github/workflows/linux.yaml +++ b/.github/workflows/linux.yaml @@ -33,5 +33,5 @@ jobs: - name: Run the application run: | - ./target/release/fontmanager + ./target/release/display-fonts shell: bash \ No newline at end of file diff --git a/.github/workflows/macos.yaml b/.github/workflows/macos.yaml index 950bf86..d3ee8fe 100644 --- a/.github/workflows/macos.yaml +++ b/.github/workflows/macos.yaml @@ -32,6 +32,5 @@ jobs: - name: Run the application run: | - ./target/release/fontmanager + ./target/release/display-fonts shell: bash - if: ${{ matrix.os != 'windows-latest' }} diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 05aff3d..9f9f815 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -32,5 +32,5 @@ jobs: - name: Run the application (Windows) run: | - .\target\release\fontmanager.exe + .\target\release\display-fonts.exe shell: cmd \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index d4d3af8..15019ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,15 @@ name = "fontmanager" version = "0.1.0" edition = "2021" +[[bin]] +name = "display-fonts" +path = "src/bin/display-fonts.rs" + +[[bin]] +name = "generate-svg" +path = "src/bin/generate-svg.rs" + + [dependencies] colog = "1.3.0" font-kit = "0.14.2" diff --git a/src/bin/display-fonts.rs b/src/bin/display-fonts.rs new file mode 100644 index 0000000..d2f2eff --- /dev/null +++ b/src/bin/display-fonts.rs @@ -0,0 +1,34 @@ +use prettytable::{Attr, Cell, Row, Table}; +use fontmanager::font_manager::FontManager; + +fn main() { + colog::init(); + + let manager = FontManager::new(); + + let mut table = Table::new(); + table.set_format(*prettytable::format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); + table.set_titles(Row::new(vec![ + Cell::new("Family").with_style(Attr::Bold), + Cell::new("Style").with_style(Attr::Bold), + Cell::new("Weight").with_style(Attr::Bold), + Cell::new("Stretch").with_style(Attr::Bold), + Cell::new("Monospaced").with_style(Attr::Bold), + Cell::new("Path").with_style(Attr::Bold), + Cell::new("Index").with_style(Attr::Bold), + ])); + + for info in manager.available_fonts() { + table.add_row(Row::new(vec![ + Cell::new(&info.family), + Cell::new(&format!("{}", &info.style)), + Cell::new(&info.weight.to_string()), + Cell::new(&info.stretch.to_string()), + Cell::new(&info.monospaced.to_string()), + Cell::new(&info.path.to_str().unwrap()), + Cell::new(&info.index.unwrap_or(0).to_string()), + ])); + } + + table.printstd(); +} \ No newline at end of file diff --git a/src/bin/generate-svg.rs b/src/bin/generate-svg.rs new file mode 100644 index 0000000..94d6d6b --- /dev/null +++ b/src/bin/generate-svg.rs @@ -0,0 +1,108 @@ +use freetype::Face; +use fontmanager::font_manager::{FontManager, FontStyle}; + +// const TEST_STRING1: &str = "A B C D E\n \u{EA84} a b c d e"; + +const TEST_STRING: &str = r"A B C D E F G H I J K L M N O P Q R S T U V W X Y Z +a b c d e f g h i j k l m n o p q r s t u v w x y z +0 1 2 3 4 5 6 7 8 9 ( ) $ % @ & ¢ € [ \ ] ^ _ ` { | } ~ < > # = + - * / : ; , . ! ? +¡ ¿ ˆ ˜ ¨ ´ ` ˘ ˙ ˚ ˝ ˛ ˇ ˆ ˇ ˘ ˙ ˚ ˛ ˜ ˝ ˇ ˘ ˙ ˚ ˛ ˜ ˝ ˇ ˘ ˙ ˚ ˛ ˜ ˝ +À Á Â Ã Ä Å Æ Ç È É Ê Ë Ì Í Î Ï Ð Ñ Ò Ó Ô Õ Ö Ø Ù Ú Û Ü Ý Þ ß +à á â ã ä å æ ç è é ê ë ì í î ï ð ñ ò ó ô õ ö ø ù ú û ü ý þ ÿ +Ā ā Ă ă Ą ą Ć ć Ĉ ĉ Ċ ċ Č č Ď ď Đ đ Ē ē Ĕ ĕ Ė ė Ę ę Ě ě Ĝ ĝ Ğ ğ +Ġ ġ Ģ ģ Ĥ ĥ Ħ ħ Ĩ ĩ Ī ī Ĭ ĭ Į į İ ı IJ ij Ĵ ĵ Ķ ķ ĸ Ĺ ĺ Ļ ļ Ľ ľ +Ŀ ŀ Ł ł Ń ń Ņ ņ Ň ň ʼn Ŋ ŋ Ō ō Ŏ ŏ Ő ő Œ œ Ŕ ŕ Ŗ ŗ Ř ř Ś ś Ŝ ŝ +Ş ş Š š Ţ ţ Ť ť Ŧ ŧ Ũ ũ Ū ū Ŭ ŭ Ů ů Ű ű Ų ų Ŵ ŵ Ŷ ŷ Ÿ Ź ź Ż ż Ž ž +ſ ƀ Ɓ Ƃ ƃ Ƅ ƅ Ɔ Ƈ ƈ Ɖ Ɗ Ƌ ƌ ƍ Ǝ Ə Ɛ Ƒ ƒ Ɠ Ɣ ƕ Ɩ Ɨ Ƙ ƙ ƚ ƛ Ɯ Ɲ ƞ Ɵ +Ơ ơ Ƣ ƣ Ƥ ƥ Ʀ Ƨ ƨ Ʃ ƪ ƫ Ƭ ƭ Ʈ Ư ư Ʊ Ʋ Ƴ ƴ Ƶ ƶ Ʒ Ƹ ƹ ƺ ƻ Ƽ ƽ ƾ ƿ +ǀ ǁ ǂ ǃ DŽ Dž dž LJ Lj lj NJ Nj nj Ǎ ǎ Ǐ ǐ Ǒ ǒ Ǔ ǔ Ǖ ǖ Ǘ ǘ Ǚ ǚ Ǜ ǜ ǝ +Ǟ ǟ Ǡ ǡ Ǣ ǣ Ǥ ǥ Ǧ ǧ Ǩ ǩ Ǫ ǫ Ǭ ǭ Ǯ ǯ ǰ DZ Dz dz Ǵ ǵ Ƕ Ƿ Ǹ ǹ Ǻ ǻ +Ǽ ǽ Ǿ ǿ Ȁ ȁ Ȃ ȃ Ȅ ȅ Ȇ ȇ Ȉ ȉ Ȋ ȋ Ȍ ȍ Ȏ ȏ Ȑ ȑ Ȓ ȓ Ȕ ȕ Ȗ ȗ Ș ș Ț ț +Ȝ ȝ Ȟ ȟ Ƞ ȡ Ȣ ȣ Ȥ ȥ Ȧ ȧ Ȩ ȩ Ȫ ȫ Ȭ ȭ Ȯ ȯ Ȱ ȱ Ȳ ȳ ȴ ȵ ȶ ȷ ȸ ȹ Ⱥ +Ȼ ȼ Ƚ Ⱦ ȿ ɀ Ɂ ɂ Ƀ Ʉ Ʌ Ɇ ɇ Ɉ ɉ Ɋ ɋ Ɍ ɍ Ɏ ɏ ɐ ɑ ɒ ɓ ɔ ɕ ɖ ɗ ɘ ə ɚ +ɜ ɝ ɞ ɟ ɠ ɡ ɢ ɣ ɤ ɥ ɦ ɧ ɨ ɩ ɪ ɫ ɬ ɭ ɮ ɯ ɰ ɱ ɲ ɳ ɴ ɵ ɶ ɷ ɸ ɹ +Hello world from the Gosub FontManager system! +"; + +fn main() { + colog::init(); + + let manager = FontManager::new(); + + let info = manager.find(vec!["Nimbus Sans Narrow"], FontStyle::Normal); + let face = manager.load(info.unwrap()).unwrap(); + char_to_svg(face, TEST_STRING); +} + +fn char_to_svg(face: Face, content: &str) { + face.set_char_size(10 * 64, 0, 10, 0).unwrap(); + + println!(""); + println!(""); + println!(""); + + let mut x_pos = -155.0; + let mut y_pos = 10.0; + + for c in content.chars() { + if c == '\n' { + x_pos = -155.0; + y_pos += 10.0; + continue; + } + + if c == ' ' { + x_pos += 2.5; + continue; + } + + face.load_char(c as usize, freetype::face::LoadFlag::NO_SCALE).unwrap(); + + let glyph = face.glyph(); + let metrics = glyph.metrics(); + // let xmin = metrics.horiBearingX - 5; + let width = metrics.width + 10; + // let ymin = -metrics.horiBearingY - 5; + // let height = metrics.height + 10; + // let scale_factor = 10.0 / width as f32; + let scale_factor = 0.0056; + + let outline = glyph.outline().unwrap(); + + for contour in outline.contours_iter() { + let start = contour.start(); + + // dbg!(x_pos, y_pos); + println!("", (x_pos - 1.0), (y_pos - 1.0), scale_factor); + + println!( + ""); + println!(""); + } + + x_pos += width as f32 * scale_factor; + x_pos += 1.0; + } + println!(""); +} + + +fn draw_curve(curve: freetype::outline::Curve) { + match curve { + freetype::outline::Curve::Line(pt) => println!("L {} {}", pt.x, -pt.y), + freetype::outline::Curve::Bezier2(pt1, pt2) => { + println!("Q {} {} {} {}", pt1.x, -pt1.y, pt2.x, -pt2.y) + } + freetype::outline::Curve::Bezier3(pt1, pt2, pt3) => println!( + "C {} {} {} {} {} {}", + pt1.x, -pt1.y, pt2.x, -pt2.y, pt3.x, -pt3.y + ), + } +} \ No newline at end of file diff --git a/src/main.rs b/src/font_manager.rs similarity index 57% rename from src/main.rs rename to src/font_manager.rs index dc7a207..61cf5f2 100644 --- a/src/main.rs +++ b/src/font_manager.rs @@ -9,10 +9,12 @@ use font_kit::properties::Properties; use font_kit::source::SystemSource; use freetype::{Face, Library}; use log::info; -use prettytable::{Attr, Cell, Row, Table}; + +#[allow(dead_code)] +const LOG_TARGET: &str = "font-manager"; #[derive(Clone, Debug)] -enum FontStyle { +pub enum FontStyle { Normal, Italic, Oblique, @@ -29,28 +31,37 @@ impl Display for FontStyle { } #[derive(Clone, Debug)] -struct FontInfo { +pub struct FontInfo { + /// Family name of the font (e.g. "Arial") pub family: String, + /// Style of the font pub style: FontStyle, + /// Weight (400 normal, 700 bold) pub weight: f32, + /// Stretch (1.0 normal, < 1.0 condensed) pub stretch: f32, + /// Font is monospaced pub monospaced: bool, - + /// Path to the font file pub path: PathBuf, + /// Index of the face in the font-file pub index: Option, } -struct FontManager { +#[allow(unused)] +pub struct FontManager { source: SystemSource, ft_library: Library, - info: Vec, + /// Vec of all font-info structures found + font_info: Vec, + /// List of all font handles handles: Vec, /// Cache of font faces that are loaded through freetype face_cache: Arc>>, } impl FontManager { - fn new() -> Self { + pub fn new() -> Self { let library = Library::init().expect("unable to init freetype library"); let source = SystemSource::new(); @@ -66,12 +77,21 @@ impl FontManager { FontManager { source, ft_library: library, - info: font_info, + font_info: font_info, handles, face_cache: Arc::new(Mutex::new(HashMap::new())), } } + /// Returns all available fonts + pub fn available_fonts(&self) -> Vec { + let mut fonts = self.font_info.clone(); + fonts.sort_by_key(|fi| fi.family.clone()); + + fonts + } + + /// Returns the first font that matches the given family and style pub fn find(&self, families: Vec<&str>, style: FontStyle) -> Option { let mut f = Vec::new(); for family in families { @@ -100,10 +120,11 @@ impl FontManager { } } + /// Load the font face for the given font info pub fn load(&self, font_info: FontInfo) -> Result { let cache_key = format!("{}:{}", font_info.family, font_info.style); if let Some(font_face) = self.face_cache.lock().unwrap().get(&cache_key) { - info!(target: "font", "Font loaded from cache: {}", cache_key); + info!(target: LOG_TARGET, "Font loaded from cache: {}", cache_key); // @todo: Can we somehow return the face within the cache so we don't need to copy it? return Ok(font_face.clone()); } @@ -116,15 +137,14 @@ impl FontManager { } }; - - info!(target: "font", + info!(target: LOG_TARGET, "Font loaded: {} (number of glyphs: {})", face.family_name().unwrap_or("Unknown".parse()?), face.num_glyphs() ); // @todo: same here.. we use a clone to store into cache, but can we just use the data we loaded through freetype? - info!(target: "font", "Caching font face: {}", cache_key); + info!(target: LOG_TARGET, "Caching font face: {}", cache_key); self.face_cache.lock().unwrap().insert(cache_key.clone(), face.clone()); Ok(face) } @@ -159,70 +179,3 @@ impl FontManager { } } } - -const TEST_STRING: &str = r"A B C D E F G H I J K L M N O P Q R S T U V W X Y Z -a b c d e f g h i j k l m n o p q r s t u v w x y z -0 1 2 3 4 5 6 7 8 9 ( ) $ % @ & ¢ € [ \ ] ^ _ ` { | } ~ < > # = + - * / : ; , . ! ? -¡ ¿ ˆ ˜ ¨ ´ ` ˘ ˙ ˚ ˝ ˛ ˇ ˆ ˇ ˘ ˙ ˚ ˛ ˜ ˝ ˇ ˘ ˙ ˚ ˛ ˜ ˝ ˇ ˘ ˙ ˚ ˛ ˜ ˝ -À Á Â Ã Ä Å Æ Ç È É Ê Ë Ì Í Î Ï Ð Ñ Ò Ó Ô Õ Ö Ø Ù Ú Û Ü Ý Þ ß -à á â ã ä å æ ç è é ê ë ì í î ï ð ñ ò ó ô õ ö ø ù ú û ü ý þ ÿ -Ā ā Ă ă Ą ą Ć ć Ĉ ĉ Ċ ċ Č č Ď ď Đ đ Ē ē Ĕ ĕ Ė ė Ę ę Ě ě Ĝ ĝ Ğ ğ -Ġ ġ Ģ ģ Ĥ ĥ Ħ ħ Ĩ ĩ Ī ī Ĭ ĭ Į į İ ı IJ ij Ĵ ĵ Ķ ķ ĸ Ĺ ĺ Ļ ļ Ľ ľ -Ŀ ŀ Ł ł Ń ń Ņ ņ Ň ň ʼn Ŋ ŋ Ō ō Ŏ ŏ Ő ő Œ œ Ŕ ŕ Ŗ ŗ Ř ř Ś ś Ŝ ŝ -Ş ş Š š Ţ ţ Ť ť Ŧ ŧ Ũ ũ Ū ū Ŭ ŭ Ů ů Ű ű Ų ų Ŵ ŵ Ŷ ŷ Ÿ Ź ź Ż ż Ž ž -ſ ƀ Ɓ Ƃ ƃ Ƅ ƅ Ɔ Ƈ ƈ Ɖ Ɗ Ƌ ƌ ƍ Ǝ Ə Ɛ Ƒ ƒ Ɠ Ɣ ƕ Ɩ Ɨ Ƙ ƙ ƚ ƛ Ɯ Ɲ ƞ Ɵ -Ơ ơ Ƣ ƣ Ƥ ƥ Ʀ Ƨ ƨ Ʃ ƪ ƫ Ƭ ƭ Ʈ Ư ư Ʊ Ʋ Ƴ ƴ Ƶ ƶ Ʒ Ƹ ƹ ƺ ƻ Ƽ ƽ ƾ ƿ -ǀ ǁ ǂ ǃ DŽ Dž dž LJ Lj lj NJ Nj nj Ǎ ǎ Ǐ ǐ Ǒ ǒ Ǔ ǔ Ǖ ǖ Ǘ ǘ Ǚ ǚ Ǜ ǜ ǝ -Ǟ ǟ Ǡ ǡ Ǣ ǣ Ǥ ǥ Ǧ ǧ Ǩ ǩ Ǫ ǫ Ǭ ǭ Ǯ ǯ ǰ DZ Dz dz Ǵ ǵ Ƕ Ƿ Ǹ ǹ Ǻ ǻ -Ǽ ǽ Ǿ ǿ Ȁ ȁ Ȃ ȃ Ȅ ȅ Ȇ ȇ Ȉ ȉ Ȋ ȋ Ȍ ȍ Ȏ ȏ Ȑ ȑ Ȓ ȓ Ȕ ȕ Ȗ ȗ Ș ș Ț ț -Ȝ ȝ Ȟ ȟ Ƞ ȡ Ȣ ȣ Ȥ ȥ Ȧ ȧ Ȩ ȩ Ȫ ȫ Ȭ ȭ Ȯ ȯ Ȱ ȱ Ȳ ȳ ȴ ȵ ȶ ȷ ȸ ȹ Ⱥ -Ȼ ȼ Ƚ Ⱦ ȿ ɀ Ɂ ɂ Ƀ Ʉ Ʌ Ɇ ɇ Ɉ ɉ Ɋ ɋ Ɍ ɍ Ɏ ɏ ɐ ɑ ɒ ɓ ɔ ɕ ɖ ɗ ɘ ə ɚ -ɛ ɜ ɝ ɞ ɟ ɠ ɡ ɢ ɣ ɤ ɥ ɦ ɧ ɨ ɩ ɪ ɫ ɬ ɭ ɮ ɯ ɰ ɱ ɲ ɳ ɴ ɵ ɶ ɷ ɸ ɹ -Hello world from the Gosub FontManager system! -"; - -fn main() { - colog::init(); - - let manager = FontManager::new(); - - print_font_table(&manager); - - let info = manager.find(vec!["arial"], FontStyle::Normal); - dbg!(&info); - let info = manager.find(vec!["serif"], FontStyle::Normal); - dbg!(&info); - let info = manager.find(vec!["monospace"], FontStyle::Normal); - dbg!(&info); - - char_to_svg(&manager, "Arial", "Regular", ''); -} - -fn print_font_table(manager: &FontManager) { - let mut table = Table::new(); - table.set_format(*prettytable::format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); - table.set_titles(Row::new(vec![ - Cell::new("PostScript Name").with_style(Attr::Bold), - Cell::new("Name").with_style(Attr::Bold), - Cell::new("Family").with_style(Attr::Bold), - Cell::new("Path").with_style(Attr::Bold), - Cell::new("Monospaced").with_style(Attr::Bold), - Cell::new("Style").with_style(Attr::Bold), - Cell::new("Weight").with_style(Attr::Bold), - Cell::new("Stretch").with_style(Attr::Bold), - ])); - - for info in manager.info.iter() { - table.add_row(Row::new(vec![ - Cell::new(&info.family), - Cell::new(&format!("{}", &info.style)), - Cell::new(&info.weight.to_string()), - Cell::new(&info.stretch.to_string()), - Cell::new(&info.monospaced.to_string()), - Cell::new(&info.path.to_str().unwrap()), - Cell::new(&info.index.unwrap_or(0).to_string()), - ])); - } - - table.printstd(); -} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..78c3f4b --- /dev/null +++ b/src/lib.rs @@ -0,0 +1 @@ +pub mod font_manager; \ No newline at end of file diff --git a/src/test.rs b/src/test.rs deleted file mode 100644 index 7a1d238..0000000 --- a/src/test.rs +++ /dev/null @@ -1,65 +0,0 @@ -// We need to deal with the following: -// - fontconfig: what about Windows and MacOS, they use different systems -// - can we somehow not copy fontfaces but use references or something? -// - deal when font is not found (it now defaults to a certain font?) - -fn blaatmain() { - colog::init(); - - let fm = FontManager::new(); - let f = fm.find("Arial", Some("Regular")).expect("unable to find font"); - let face = fm.load(f).expect("unable to load font"); - let f = fm.find("Arial", Some("Regular")).expect("unable to find font"); - let _face = fm.load(f).expect("unable to load font"); - let f = fm.find("Arial", Some("Bold")).expect("unable to find font"); - let _face = fm.load(f).expect("unable to load font"); - let f = fm.find("Arial", Some("Regular")).expect("unable to find font"); - let _face = fm.load(f).expect("unable to load font"); - let f = fm.find("Monospace", Some("Bold")).expect("unable to find font"); - let face = fm.load(f).expect("unable to load font"); - - face.set_char_size(40 * 64, 0, 50, 0).unwrap(); - face.load_char('@' as usize, freetype::face::LoadFlag::NO_SCALE).unwrap(); - - let glyph = face.glyph(); - let metrics = glyph.metrics(); - let xmin = metrics.horiBearingX - 5; - let width = metrics.width + 10; - let ymin = -metrics.horiBearingY - 5; - let height = metrics.height + 10; - let outline = glyph.outline().unwrap(); - - println!(""); - println!(""); - println!( - "", - xmin, ymin, width, height - ); - - for contour in outline.contours_iter() { - let start = contour.start(); - println!( - ""); - } - println!(""); -} - -fn draw_curve(curve: freetype::outline::Curve) { - match curve { - freetype::outline::Curve::Line(pt) => println!("L {} {}", pt.x, -pt.y), - freetype::outline::Curve::Bezier2(pt1, pt2) => { - println!("Q {} {} {} {}", pt1.x, -pt1.y, pt2.x, -pt2.y) - } - freetype::outline::Curve::Bezier3(pt1, pt2, pt3) => println!( - "C {} {} {} {} {} {}", - pt1.x, -pt1.y, pt2.x, -pt2.y, pt3.x, -pt3.y - ), - } -} \ No newline at end of file