diff --git a/Cargo.toml b/Cargo.toml index d38f186..c894ff6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,25 @@ [package] name = "profont" +description = "The ProFont monospace font for use with the embedded-graphics crate." version = "0.1.0" authors = ["Wesley Moore "] +documentation = "https://docs.rs/crate/profont" +repository = "https://github.com/wezm/profont" + +readme = "README.md" +license = "MIT" + +keywords = ["bitmap", "monochrome", "font", "monospace"] +categories = ["rendering", "embedded", "no-std"] + [dependencies] embedded-graphics = "0.4" + +font-kit = { version = "0.1", optional = true } +euclid = { version = "0.19", optional = true } +image = { version = "0.20", optional = true, default-features = false, features = ["png_codec"] } + +[features] +default-features = [] +exe = ["font-kit", "euclid", "image"] diff --git a/LICENCE b/LICENCE index 7203bd9..73e5015 100644 --- a/LICENCE +++ b/LICENCE @@ -1,6 +1,6 @@ -ProFont MIT License +Copyright (c) 2018 Wesley Moore Copyright (c) 2014 Tobias Jung, Carl Osterwald, Stephen C. Gilardi, Andrew Welch Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/README.md b/README.md index d1de87c..ec43334 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,13 @@ # ProFont -[The ProFont programming font](https://web.archive.org/web/20180412214402/http://tobiasjung.name/profont/) -for [embedded-graphics](https://github.com/jamwaffles/embedded-graphics). +[The ProFont monospace font][profont] for use with the [embedded-graphics] crate. + +[![crates.io](https://img.shields.io/crates/v/profont.svg)](https://crates.io/crates/profont) +[![Documentation](https://docs.rs/profont/badge.svg)][crate-docs] + +Photo of ProFront on an ePaper display + +`profont` is licensed under the [MIT license][MIT]. ## Specimens @@ -34,3 +40,8 @@ ProFont is available in 7 sizes: ### 24 Point ![24 Point ProFont](https://raw.githubusercontent.com/wezm/profont/master/data/ProFont24Point.png) + +[embedded-graphics]: https://github.com/jamwaffles/embedded-graphics +[profont]: https://web.archive.org/web/20180412214402/http://tobiasjung.name/profont/ +[MIT]: https://github.com/wezm/profont/blob/master/LICENSE +[crate-docs]: https://docs.rs/crate/profont diff --git a/data/IMG_2198.jpg b/data/IMG_2198.jpg new file mode 100644 index 0000000..59d0c18 Binary files /dev/null and b/data/IMG_2198.jpg differ diff --git a/data/Makefile b/data/Makefile index 555bae4..ae20618 100644 --- a/data/Makefile +++ b/data/Makefile @@ -3,7 +3,18 @@ RAW = ${PNG:.png=.raw} all: ${RAW} +pngs: + cargo run --release -- 7 + cargo run --release -- 9 + cargo run --release -- 10 + cargo run --release -- 12 + cargo run --release -- 14 + cargo run --release -- 18 + cargo run --release -- 24 + .SUFFIXES: .png .raw .png.raw: convert ${.IMPSRC} -depth 1 gray:${.TARGET} + +.PHONY: pngs diff --git a/data/ProFont10Point.png b/data/ProFont10Point.png index 19f3709..9d65b77 100644 Binary files a/data/ProFont10Point.png and b/data/ProFont10Point.png differ diff --git a/data/ProFont10Point.raw b/data/ProFont10Point.raw index 61da9fd..80b0925 100644 Binary files a/data/ProFont10Point.raw and b/data/ProFont10Point.raw differ diff --git a/data/ProFont12Point.png b/data/ProFont12Point.png index 7a0946e..66d9b3a 100644 Binary files a/data/ProFont12Point.png and b/data/ProFont12Point.png differ diff --git a/data/ProFont12Point.raw b/data/ProFont12Point.raw index 917c2d7..c27910c 100644 Binary files a/data/ProFont12Point.raw and b/data/ProFont12Point.raw differ diff --git a/data/ProFont14Point.png b/data/ProFont14Point.png index c52c2aa..28a73d7 100644 Binary files a/data/ProFont14Point.png and b/data/ProFont14Point.png differ diff --git a/data/ProFont14Point.raw b/data/ProFont14Point.raw index 5386823..23d7411 100644 Binary files a/data/ProFont14Point.raw and b/data/ProFont14Point.raw differ diff --git a/data/ProFont18Point.png b/data/ProFont18Point.png index 7b54e9a..0ae3b3f 100644 Binary files a/data/ProFont18Point.png and b/data/ProFont18Point.png differ diff --git a/data/ProFont18Point.raw b/data/ProFont18Point.raw index fc913be..220b057 100644 Binary files a/data/ProFont18Point.raw and b/data/ProFont18Point.raw differ diff --git a/data/ProFont24Point.png b/data/ProFont24Point.png index c0f1b18..3658958 100644 Binary files a/data/ProFont24Point.png and b/data/ProFont24Point.png differ diff --git a/data/ProFont24Point.raw b/data/ProFont24Point.raw index af2bc93..e2fedf1 100644 Binary files a/data/ProFont24Point.raw and b/data/ProFont24Point.raw differ diff --git a/data/ProFont7Point.png b/data/ProFont7Point.png index 32d8a63..f1e3e64 100644 Binary files a/data/ProFont7Point.png and b/data/ProFont7Point.png differ diff --git a/data/ProFont7Point.raw b/data/ProFont7Point.raw index 325460e..8cecb78 100644 Binary files a/data/ProFont7Point.raw and b/data/ProFont7Point.raw differ diff --git a/data/ProFont9Point.png b/data/ProFont9Point.png index a8fb0dd..7d15a6c 100644 Binary files a/data/ProFont9Point.png and b/data/ProFont9Point.png differ diff --git a/data/ProFont9Point.raw b/data/ProFont9Point.raw index 18407a6..c688196 100644 Binary files a/data/ProFont9Point.raw and b/data/ProFont9Point.raw differ diff --git a/data/ProFontIIx.ttf b/data/ProFontIIx.ttf new file mode 100644 index 0000000..bb58c86 Binary files /dev/null and b/data/ProFontIIx.ttf differ diff --git a/src/lib.rs b/src/lib.rs index cb330c0..aa29289 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,50 @@ -//! The ProFont programming font. Image data taken from the -//! [ProFont homepage](https://web.archive.org/web/20180412214402/http://tobiasjung.name/profont/) +//! The [ProFont](https://web.archive.org/web/20180412214402/http://tobiasjung.name/profont/) +//! monospace programming font for use with +//! [embedded-graphics](https://github.com/jamwaffles/embedded-graphics). Font data taken from the +//! [ProFont homepage](https://web.archive.org/web/20180412214402/http://tobiasjung.name/profont/). +//! +//! ### Synopsis +//! +//! Assuming `display` is something that implements the [Drawing +//! trait](https://docs.rs/embedded-graphics/0.4.4/embedded_graphics/trait.Drawing.html) +//! +//! ```no_run +//! display.draw( +//! ProFont24Point::render_str("Hello World") +//! .with_stroke(Some(Color::Red)) +//! .with_fill(Some(Color::White)) +//! .translate(Coord::new(10, 10)) +//! .into_iter(), +//! ); +//! ``` +//! +//! For a more complete example see [the example in the ssd1675 +//! crate](https://github.com/wezm/ssd1675/blob/master/examples/raspberry_pi_inky_phat.rs). +//! +//! ### Glyph Coverage +//! +//! This crate provides support for [ISO/IEC 8859-1](https://en.wikipedia.org/wiki/ISO/IEC_8859-1) +//! (latin1), although do note that the font is missing a few glyphs in this range. extern crate embedded_graphics; use embedded_graphics::fonts::font_builder::{FontBuilder, FontBuilderConf}; -#[cfg(test)] -pub(crate) mod mock_display; +const CHARS_PER_ROW: u32 = 32; + +fn char_offset_impl(c: char) -> u32 { + let fallback = '?' as u32 - ' ' as u32; + if c < ' ' { + return fallback; + } + if c <= '~' { + return c as u32 - ' ' as u32; + } + if c < '\u{00A0}' || c > 'ÿ' { + return fallback; + } + c as u32 - ' ' as u32 - 33 +} #[derive(Debug, Copy, Clone)] pub enum ProFont7PointConf {} @@ -14,23 +52,13 @@ impl FontBuilderConf for ProFont7PointConf { const FONT_IMAGE: &'static [u8] = include_bytes!("../data/ProFont7Point.raw"); const CHAR_HEIGHT: u32 = 9; const CHAR_WIDTH: u32 = 5; - const FONT_IMAGE_WIDTH: u32 = 200; + const FONT_IMAGE_WIDTH: u32 = Self::CHAR_WIDTH * CHARS_PER_ROW; fn char_offset(c: char) -> u32 { - let fallback = '?' as u32 - ' ' as u32; - if c < ' ' { - return fallback; - } - if c <= '~' { - return c as u32 - ' ' as u32; - } - if c < '¡' || c > 'ÿ' { - return fallback; - } - c as u32 - ' ' as u32 - 34 + char_offset_impl(c) } } -/// The 7 point size. +/// The 7 point size with a character size of 5x9 pixels. pub type ProFont7Point<'a, C> = FontBuilder<'a, C, ProFont7PointConf>; #[derive(Debug, Copy, Clone)] @@ -39,391 +67,86 @@ impl FontBuilderConf for ProFont9PointConf { const FONT_IMAGE: &'static [u8] = include_bytes!("../data/ProFont9Point.raw"); const CHAR_HEIGHT: u32 = 11; const CHAR_WIDTH: u32 = 6; - const FONT_IMAGE_WIDTH: u32 = 240; + const FONT_IMAGE_WIDTH: u32 = Self::CHAR_WIDTH * CHARS_PER_ROW; fn char_offset(c: char) -> u32 { - let fallback = '?' as u32 - ' ' as u32; - if c < ' ' { - return fallback; - } - if c <= '~' { - return c as u32 - ' ' as u32; - } - if c < '¡' || c > 'ÿ' { - return fallback; - } - c as u32 - ' ' as u32 - 34 + char_offset_impl(c) } } -/// The 9 point size. +/// The 9 point size with a character size of 6x11 pixels. pub type ProFont9Point<'a, C> = FontBuilder<'a, C, ProFont9PointConf>; +#[derive(Debug, Copy, Clone)] +pub enum ProFont10PointConf {} +impl FontBuilderConf for ProFont10PointConf { + const FONT_IMAGE: &'static [u8] = include_bytes!("../data/ProFont10Point.raw"); + const CHAR_HEIGHT: u32 = 13; + const CHAR_WIDTH: u32 = 7; + const FONT_IMAGE_WIDTH: u32 = Self::CHAR_WIDTH * CHARS_PER_ROW; + fn char_offset(c: char) -> u32 { + char_offset_impl(c) + } +} + +/// The 10 point size with a character size of 7x13 pixels. +pub type ProFont10Point<'a, C> = FontBuilder<'a, C, ProFont10PointConf>; + +#[derive(Debug, Copy, Clone)] +pub enum ProFont12PointConf {} +impl FontBuilderConf for ProFont12PointConf { + const FONT_IMAGE: &'static [u8] = include_bytes!("../data/ProFont12Point.raw"); + const CHAR_HEIGHT: u32 = 15; + const CHAR_WIDTH: u32 = 8; + const FONT_IMAGE_WIDTH: u32 = Self::CHAR_WIDTH * CHARS_PER_ROW; + fn char_offset(c: char) -> u32 { + char_offset_impl(c) + } +} + +/// The 12 point size with a character size of 8x15 pixels. +pub type ProFont12Point<'a, C> = FontBuilder<'a, C, ProFont12PointConf>; + +#[derive(Debug, Copy, Clone)] +pub enum ProFont14PointConf {} +impl FontBuilderConf for ProFont14PointConf { + const FONT_IMAGE: &'static [u8] = include_bytes!("../data/ProFont14Point.raw"); + const CHAR_HEIGHT: u32 = 18; + const CHAR_WIDTH: u32 = 10; + const FONT_IMAGE_WIDTH: u32 = Self::CHAR_WIDTH * CHARS_PER_ROW; + fn char_offset(c: char) -> u32 { + char_offset_impl(c) + } +} + +/// The 14 point size with a character size of 10x18 pixels. +pub type ProFont14Point<'a, C> = FontBuilder<'a, C, ProFont14PointConf>; + #[derive(Debug, Copy, Clone)] pub enum ProFont18PointConf {} impl FontBuilderConf for ProFont18PointConf { const FONT_IMAGE: &'static [u8] = include_bytes!("../data/ProFont18Point.raw"); const CHAR_HEIGHT: u32 = 22; const CHAR_WIDTH: u32 = 12; - const FONT_IMAGE_WIDTH: u32 = 480; + const FONT_IMAGE_WIDTH: u32 = Self::CHAR_WIDTH * CHARS_PER_ROW; fn char_offset(c: char) -> u32 { - let fallback = '?' as u32 - ' ' as u32; - if c < ' ' { - return fallback; - } - if c <= '~' { - return c as u32 - ' ' as u32; - } - if c < '¡' || c > 'ÿ' { - return fallback; - } - c as u32 - ' ' as u32 - 34 + char_offset_impl(c) } } -/// The 18 point size. +/// The 18 point size with a character size of 12x22 pixels. pub type ProFont18Point<'a, C> = FontBuilder<'a, C, ProFont18PointConf>; #[derive(Debug, Copy, Clone)] pub enum ProFont24PointConf {} impl FontBuilderConf for ProFont24PointConf { const FONT_IMAGE: &'static [u8] = include_bytes!("../data/ProFont24Point.raw"); - const CHAR_HEIGHT: u32 = 29; + const CHAR_HEIGHT: u32 = 30; const CHAR_WIDTH: u32 = 16; - const FONT_IMAGE_WIDTH: u32 = 640; + const FONT_IMAGE_WIDTH: u32 = Self::CHAR_WIDTH * CHARS_PER_ROW; fn char_offset(c: char) -> u32 { - let fallback = '?' as u32 - ' ' as u32; - if c < ' ' { - return fallback; - } - if c <= '~' { - return c as u32 - ' ' as u32; - } - if c < '¡' || c > 'ÿ' { - return fallback; - } - c as u32 - ' ' as u32 - 34 + char_offset_impl(c) } } -/// The 24 point size. +/// The 24 point size with a character size of 16x30 pixels. pub type ProFont24Point<'a, C> = FontBuilder<'a, C, ProFont24PointConf>; - -#[cfg(test)] -mod tests { - use super::*; - use embedded_graphics::coord::Coord; - use embedded_graphics::dev::TestPixelColor; - use embedded_graphics::fonts::Font; - use mock_display::Display; - use embedded_graphics::style::Style; - use embedded_graphics::style::WithStyle; - use embedded_graphics::transform::Transform; - use embedded_graphics::unsignedcoord::UnsignedCoord; - use embedded_graphics::Drawing; - - #[test] - fn off_screen_text_does_not_infinite_loop() { - let text: ProFont9Point = ProFont9Point::render_str("Hello World!") - .with_style(Style::with_stroke(1u8.into())) - .translate(Coord::new(5, -10)); - let mut it = text.into_iter(); - - assert_eq!(it.next(), None); - } - - #[test] - fn unstroked_text_does_not_infinite_loop() { - let text: ProFont9Point = ProFont9Point::render_str("Hello World!") - .with_style(Style::with_stroke(1u8.into())) - .translate(Coord::new(5, -10)); - let mut it = text.into_iter(); - - assert_eq!(it.next(), None); - } - - #[test] - fn text_dimensions() { - let hello: ProFont9Point = ProFont9Point::render_str("Hello World!"); - let empty: ProFont9Point = ProFont9Point::render_str(""); - - assert_eq!(hello.dimensions(), UnsignedCoord::new(72, 8)); - assert_eq!(empty.dimensions(), UnsignedCoord::new(0, 0)); - } - - #[test] - fn default_style() { - let mut display_default = Display::default(); - display_default.draw(ProFont9Point::render_str("Mm").into_iter()); - - let mut display_full_style = Display::default(); - display_full_style.draw( - ProFont9Point::render_str("Mm") - .with_stroke(Some(1u8.into())) - .with_fill(Some(0u8.into())) - .into_iter(), - ); - - let mut display_stroke = Display::default(); - display_stroke.draw( - ProFont9Point::render_str("Mm") - .with_stroke(Some(1u8.into())) - .into_iter(), - ); - - let mut display_fill = Display::default(); - display_fill.draw( - ProFont9Point::render_str("Mm") - .with_fill(Some(0u8.into())) - .into_iter(), - ); - - assert_eq!(display_default, display_full_style); - assert_eq!(display_default, display_stroke); - assert_eq!(display_default, display_fill); - } - - #[test] - fn correct_m() { - let mut display = Display::default(); - display.draw( - ProFont9Point::render_str("Mm") - .with_stroke(Some(1u8.into())) - .into_iter(), - ); - - assert_eq!( - display, - Display([ - [1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - // - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]) - ); - } - - #[test] - fn correct_inverse_coloured_m() { - let mut display = Display::default(); - display.draw( - ProFont9Point::render_str("Mm") - .with_stroke(Some(0u8.into())) - .with_fill(Some(1u8.into())) - .into_iter(), - ); - - assert_eq!( - display, - Display([ - [0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - // - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]) - ); - } - - // tests if black on white has really the same behaviour as white on black - #[test] - fn compare_inverse_coloured_m() { - let mut display_inverse = Display::default(); - display_inverse.draw( - ProFont9Point::render_str("Mm") - .with_stroke(Some(0u8.into())) - .with_fill(Some(1u8.into())) - .into_iter(), - ); - - let mut display_normal = Display::default(); - display_normal.draw( - ProFont9Point::render_str("Mm") - .with_stroke(Some(1u8.into())) - .with_fill(Some(0u8.into())) - .into_iter(), - ); - - for (x, y) in display_inverse.0[0..8] - .iter() - .zip(display_normal.0[0..8].iter()) - { - for (x2, y2) in x[0..12].iter().zip(y[0..12].iter()) { - assert_ne!(x2, y2); - } - } - } - - #[test] - fn correct_ascii_borders() { - let mut display = Display::default(); - display.draw( - ProFont9Point::render_str(" ~") - .with_stroke(Some(1u8.into())) - .into_iter(), - ); - - assert_eq!( - display, - Display([ - [0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - // - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]) - ); - } - - #[test] - fn correct_dollar_y() { - let mut display = Display::default(); - display.draw( - ProFont9Point::render_str("$y") - .with_stroke(Some(1u8.into())) - .into_iter(), - ); - - assert_eq!( - display, - Display([ - [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - // - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]) - ); - } - - #[test] - fn correct_latin1() { - let mut display = Display::default(); - display.draw( - ProFont9Point::render_str("¡ÿ") - .with_stroke(Some(1u8.into())) - .into_iter(), - ); - - assert_eq!( - display, - Display([ - [0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - // - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]) - ); - } - - #[test] - fn dont_panic() { - #[cfg_attr(rustfmt, rustfmt_skip)] - let two_question_marks = Display( - [ - [0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - // - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - ); - - let mut display = Display::default(); - display.draw( - ProFont9Point::render_str("\0\n") - .with_stroke(Some(1u8.into())) - .into_iter(), - ); - assert_eq!(display, two_question_marks); - - let mut display = Display::default(); - display.draw( - ProFont9Point::render_str("\x7F\u{A0}") - .with_stroke(Some(1u8.into())) - .into_iter(), - ); - assert_eq!(display, two_question_marks); - - let mut display = Display::default(); - display.draw( - ProFont9Point::render_str("Ā💣") - .with_stroke(Some(1u8.into())) - .into_iter(), - ); - assert_eq!(display, two_question_marks); - } -} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..15b7cfb --- /dev/null +++ b/src/main.rs @@ -0,0 +1,149 @@ +#[cfg(feature = "exe")] +fn main () { + exe::main(); +} + +#[cfg(not(feature = "exe"))] +fn main() { + println!("profont must be compiled with the exe feature to enable the binary."); +} + +#[cfg(feature = "exe")] +mod exe { + extern crate euclid; + extern crate font_kit; + extern crate image; + + use self::euclid::{Point2D, Rect, Size2D}; + use self::font_kit::canvas::{Canvas, Format, RasterizationOptions}; + use self::font_kit::font::Font; + use self::font_kit::hinting::HintingOptions; + use self::image::Luma; + use std::char; + use std::env; + use std::sync::Arc; + + struct Glyph { + id: u32, + raster_rect: Rect, + } + + pub fn main() { + let font_data = include_bytes!("../data/ProFontIIx.ttf").to_vec(); + let font = Font::from_bytes(Arc::new(font_data), 0).expect("error loading font"); + + let font_size: f32 = env::args() + .nth(1) + .expect("font size argument must be supplied on command line") + .parse() + .expect("invalid font size"); + + let metrics = font.metrics(); + let offset = (metrics.descent as f32 / metrics.units_per_em as f32 * font_size).round() as i32; + + // Print latin 1 characters + let basic = (' ' as u32..='~' as u32) + .into_iter() + .map(|c| char::from_u32(c).unwrap()); + let extended = ('\u{00A0}' as u32..='ÿ' as u32) + .into_iter() + .map(|c| char::from_u32(c).unwrap()); + let all_chars: Vec<_> = basic.chain(extended).collect(); + + // Get the raster bounds of all chars + let glyphs: Vec<_> = all_chars + .iter() + .map(|&chr| { + font.glyph_for_char(chr).map(|glyph_id| { + let raster_rect = font + .raster_bounds( + glyph_id, + font_size, + &Point2D::zero(), + HintingOptions::None, + RasterizationOptions::Bilevel, + ).expect("unable to get raster bounds"); + Glyph { + id: glyph_id, + raster_rect, + } + }) + }).collect(); + + // Work out how big the glyphs are + let char_size = Size2D::new( + glyphs + .iter() + .map(|glyph| { + glyph + .as_ref() + .map(|glyph| glyph.raster_rect.size.width) + .unwrap_or(0) + }).max() + .unwrap(), + glyphs + .iter() + .map(|glyph| { + glyph + .as_ref() + .map(|glyph| glyph.raster_rect.size.height) + .unwrap_or(0) + }).max() + .unwrap(), + ).to_u32(); + + // Render the glyphs + let row_size = 32; + let img_size = Size2D::new( + char_size.width * row_size, + (char_size.height as f64 * glyphs.len() as f64 / row_size as f64).ceil() as u32, + ); + let mut imgbuf = image::GrayImage::new(img_size.width, img_size.height); + + for (i, (_chr, glyph)) in all_chars.iter().zip(glyphs.iter()).enumerate() { + if let Some(glyph) = glyph { + let mut canvas = Canvas::new(&glyph.raster_rect.size.to_u32(), Format::A8); + + font.rasterize_glyph( + &mut canvas, + glyph.id, + font_size, + &glyph.raster_rect.origin.to_f32(), + HintingOptions::None, + RasterizationOptions::Bilevel, + ).expect("error rasterizing glyph"); + + let col = i as u32 % row_size; + let row = i as u32 / row_size; + let img_x = col * char_size.width; + let img_y = row * char_size.height + char_size.height; + + // Copy onto image + for y in (0u32..glyph.raster_rect.size.height as u32) + .into_iter() + .rev() + { + let (row_start, row_end) = + (y as usize * canvas.stride, (y + 1) as usize * canvas.stride); + let row = &canvas.pixels[row_start..row_end]; + for x in 0u32..glyph.raster_rect.size.width as u32 { + let val = row[x as usize]; + if val != 0 { + let pixel_x = img_x as i32 + x as i32 + glyph.raster_rect.origin.x; + let pixel_y = img_y as i32 - glyph.raster_rect.size.height + y as i32 + - glyph.raster_rect.origin.y + + offset; + if pixel_x >= 0 && pixel_y >= 0 { + imgbuf.put_pixel(pixel_x as u32, pixel_y as u32, Luma([0xFFu8])); + } + } + } + } + } + } + + let filename = format!("ProFont{}Point.png", font_size); + imgbuf.save(&filename).expect("error saving PNG"); + println!("Wrote {} with character size of {}", filename, char_size); + } +}