diff --git a/Cargo.toml b/Cargo.toml index 221606a..efd7855 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,16 +1,16 @@ # Copyright © 2018-2022 The Fonterator Contributors. -# -# Licensed under any of: -# - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) -# - MIT License (https://mit-license.org/) -# - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt) -# At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, -# LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt). +# +# Licensed under any of: +# - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) +# - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt) +# - MIT License (https://mit-license.org/) +# At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, +# LICENSE_BOOST_1_0.txt, and LICENSE_MIT.txt). [package] name = "fonterator" version = "0.9.0" -license = "Apache-2.0 OR Zlib" +license = "Apache-2.0 OR BSL-1.0 OR MIT" description = "Load fonts as vector graphics in pure Rust with advanced text layout." repository = "https://github.com/ardaku/fonterator" @@ -20,7 +20,7 @@ include = ["Cargo.toml", "README.md", "LICENSE-ZLIB", "LICENSE-APACHE", "src/*"] categories = ["gui", "rendering"] keywords = ["font", "truetype", "opentype", "ttf", "otf"] readme = "README.md" -edition = "2018" +edition = "2021" [dependencies] footile = "0.7" # For vector path operations @@ -29,6 +29,10 @@ rustybuzz = "0.5" # For text shaping ttf-parser = "0.15" # For reading TTF/OTF files unicode-script = "0.5" # For calculating text direction. +[dependencies.zip] +version = "0.6" +optional = true + [dev-dependencies] svg = "0.12" png_pong = "0.8" @@ -38,28 +42,6 @@ pix = "0.13" all-features = true default-target = "x86_64-unknown-linux-gnu" -[[example]] -name = "directions" -path = "examples/directions.rs" -required-features = ["monospace-font"] - -[[example]] -name = "image" -path = "examples/image.rs" -required-features = ["monospace-font"] - -[[example]] -name = "main" -path = "examples/main.rs" -required-features = ["monospace-font"] - -[[example]] -name = "raster" -path = "examples/raster.rs" -required-features = ["monospace-font"] - [features] -default = [] -docs-rs = [] -monospace-font = [] -normal-font = [] +default = ["quantii-sans"] +quantii-sans = ["dep:zip"] diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..5086ecb --- /dev/null +++ b/build.rs @@ -0,0 +1,5 @@ +//! build.rs for downloading fonts + +fn main() { + +} diff --git a/examples/directions.rs b/examples/directions.rs index 3a6e3cf..ceba61d 100644 --- a/examples/directions.rs +++ b/examples/directions.rs @@ -2,12 +2,12 @@ use fonterator as font; // For parsing font file. use footile::{FillRule, Plotter}; // For rendering font text. -use pointy::Transform; use pix::matte::Matte8; use pix::ops::SrcOver; use pix::rgb::{Rgba8p, SRgba8}; use pix::Raster; -use png_pong::{Encoder, PngRaster}; // For saving PNG +use png_pong::{Encoder, PngRaster}; +use pointy::Transform; // For saving PNG const FONT_SIZE: f32 = 32.0; diff --git a/examples/image.rs b/examples/image.rs index 4124fea..73df861 100644 --- a/examples/image.rs +++ b/examples/image.rs @@ -1,4 +1,4 @@ -use footile::{PathOp}; +use footile::PathOp; use svg::{ node::element::{path::Data, Group, Path, Style}, Document, Node, diff --git a/examples/main.rs b/examples/main.rs index 9bf1dfc..b898863 100644 --- a/examples/main.rs +++ b/examples/main.rs @@ -1,10 +1,10 @@ use footile::{FillRule, Plotter}; -use pointy::Transform; use pix::matte::Matte8; use pix::ops::SrcOver; use pix::rgb::{Rgba8p, SRgba8}; use pix::Raster; -use png_pong::{Encoder, PngRaster}; // For saving PNG +use png_pong::{Encoder, PngRaster}; +use pointy::Transform; // For saving PNG fn main() { // Load the default FontGroup (font and fallbacks). diff --git a/examples/raster.rs b/examples/raster.rs index a89cbb2..3571564 100644 --- a/examples/raster.rs +++ b/examples/raster.rs @@ -1,10 +1,10 @@ use footile::{FillRule, Plotter}; -use pointy::Transform; use pix::matte::Matte8; use pix::ops::SrcOver; use pix::rgb::{Rgba8p, SRgba8}; use pix::Raster; -use png_pong::{Encoder, PngRaster}; // For saving PNG +use png_pong::{Encoder, PngRaster}; +use pointy::Transform; // For saving PNG const FONT_SIZE: f32 = 200.0; diff --git a/src/font.rs b/src/font.rs index a48ba85..fb9774d 100644 --- a/src/font.rs +++ b/src/font.rs @@ -512,49 +512,3 @@ impl Iterator for TextPathIterator<'_, '_> { } }*/ } - -/// Get a monospace font. Requires feature = "monospace-font", enabled by default. -#[cfg(feature = "monospace-font")] -pub fn monospace_font() -> Font<'static> { - const FONTA: &[u8] = include_bytes!("font/dejavu/SansMono.ttf"); - const FONTB: &[u8] = include_bytes!("font/noto/SansDevanagari.ttf"); - const FONTC: &[u8] = include_bytes!("font/noto/SansHebrew.ttf"); - const FONTD: &[u8] = include_bytes!("font/droid/SansFallback.ttf"); - - Font::new() - .push(FONTA) - .unwrap() - .push(FONTB) - .unwrap() - .push(FONTC) - .unwrap() - .push(FONTD) - .unwrap() -} - -/// Get a normal font. Requires feature = "normal-font". -#[cfg(feature = "normal-font")] -pub fn normal_font() -> Font<'static> { - const FONTA: &[u8] = include_bytes!("font/dejavu/Sans.ttf"); - const FONTB: &[u8] = include_bytes!("font/noto/SansDevanagari.ttf"); - const FONTC: &[u8] = include_bytes!("font/noto/SansHebrew.ttf"); - const FONTD: &[u8] = include_bytes!("font/droid/SansFallback.ttf"); - - Font::new() - .push(FONTA) - .unwrap() - .push(FONTB) - .unwrap() - .push(FONTC) - .unwrap() - .push(FONTD) - .unwrap() -} - -#[cfg(any(feature = "monospace-font", feature = "normal-font"))] -/// Get a text string of the licenses that must be included in a binary program -/// for using the font. Requires either feature = "monospace-font" or feature -/// = "normal-font" -pub fn licenses() -> &'static str { - include_str!("bin-licenses.txt") -} diff --git a/src/lib.rs b/src/lib.rs index e789dab..635bd6e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,11 +2,11 @@ // // Licensed under any of: // - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) -// - MIT License (https://mit-license.org/) // - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt) -// At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, -// LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt). -//! TODO: Top-level Documentation. +// - MIT License (https://mit-license.org/) +// At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, +// LICENSE_BOOST_1_0.txt, and LICENSE_MIT.txt). +//! Render text with TTF fonts using footile. #![doc( html_logo_url = "https://ardaku.github.io/mm/logo.svg", @@ -30,9 +30,9 @@ variant_size_differences )] -mod direction; -mod font; -mod shape; +// mod direction; +// mod font; mod render; +mod shape; -pub use font::*; +pub use shape::Font; diff --git a/src/render.rs b/src/render.rs index fbf58dc..92159fe 100644 --- a/src/render.rs +++ b/src/render.rs @@ -1,8 +1,88 @@ +// Copyright © 2018-2022 The Fonterator Contributors. +// +// Licensed under any of: +// - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) +// - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt) +// - MIT License (https://mit-license.org/) +// At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, +// LICENSE_BOOST_1_0.txt, and LICENSE_MIT.txt). //! Rendering TTF glyphs with footile use footile::PathOp; +use pointy::Pt; use rustybuzz::Face; +use ttf_parser::{GlyphId, OutlineBuilder}; + +struct Outliner<'a> { + // Path to write out to. + path: &'a mut Vec, + // Scale to 1.0 = 1 em + scale: f32, + // Where to draw the glyph X + glyph_x: f32, + // Where to draw the glyph Y + glyph_y: f32, +} + +impl OutlineBuilder for Outliner<'_> { + fn move_to(&mut self, x: f32, y: f32) { + let x = x + self.glyph_x; + let y = y + self.glyph_y; + self.path + .push(PathOp::Move(Pt::new(x * self.scale, y * self.scale))); + } + + fn line_to(&mut self, x: f32, y: f32) { + let x = x + self.glyph_x; + let y = y + self.glyph_y; + self.path + .push(PathOp::Line(Pt::new(x * self.scale, y * self.scale))); + } + + fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) { + let x1 = x1 + self.glyph_x; + let y1 = y1 + self.glyph_y; + let x = x + self.glyph_x; + let y = y + self.glyph_y; + self.path.push(PathOp::Quad( + Pt::new(x1 * self.scale, y1 * self.scale), + Pt::new(x * self.scale, y * self.scale), + )); + } + + fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { + let x1 = x1 + self.glyph_x; + let y1 = y1 + self.glyph_y; + let x2 = x2 + self.glyph_x; + let y2 = y2 + self.glyph_y; + let x = x + self.glyph_x; + let y = y + self.glyph_y; + self.path.push(PathOp::Cubic( + Pt::new(x1 * self.scale, y1 * self.scale), + Pt::new(x2 * self.scale, y2 * self.scale), + Pt::new(x * self.scale, y * self.scale), + )); + } + + fn close(&mut self) { + self.path.push(PathOp::Close()); + } +} /// Build a path -pub(crate) fn build_path(path_buffer: &mut Vec, face: &Face<'_>, glyph_x: f32, glyph_y: f32, glyph_id: u16) { +pub(crate) fn build_path( + path: &mut Vec, + face: &Face<'_>, + glyph_x: f32, + glyph_y: f32, + glyph_id: u16, + scale: f32, +) { + let mut outliner = Outliner { + path, + scale, + glyph_x, + glyph_y, + }; + face.outline_glyph(GlyphId(glyph_id), &mut outliner); } diff --git a/src/shape.rs b/src/shape.rs index b0f8aed..93c51d7 100644 --- a/src/shape.rs +++ b/src/shape.rs @@ -1,10 +1,23 @@ +// Copyright © 2018-2022 The Fonterator Contributors. +// +// Licensed under any of: +// - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) +// - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt) +// - MIT License (https://mit-license.org/) +// At your choosing (See accompanying files LICENSE_APACHE_2_0.txt, +// LICENSE_BOOST_1_0.txt, and LICENSE_MIT.txt). //! Fonterator's text shaping with rustybuzz -use rustybuzz::{Face, UnicodeBuffer, GlyphBuffer}; -use footile::PathOp; use crate::render; +use footile::PathOp; +use rustybuzz::{Face, GlyphBuffer, UnicodeBuffer}; +use std::fmt; -fn glyph_buffer_with_text(face: &Face<'_>, glyph_buffer: GlyphBuffer, text: &str) -> GlyphBuffer { +fn glyph_buffer_with_text( + face: &Face<'_>, + glyph_buffer: GlyphBuffer, + text: &str, +) -> GlyphBuffer { let mut unicode_buffer = glyph_buffer.clear(); unicode_buffer.push_str(text); rustybuzz::shape(&face, &[], unicode_buffer) @@ -28,6 +41,15 @@ pub struct Font<'a> { path_buffer: Vec, } +impl fmt::Debug for Font<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Font") + .field("glyph_buffer", &self.glyph_buffer) + .field("path_buffer", &self.path_buffer) + .finish_non_exhaustive() + } +} + impl<'a> Font<'a> { /// Load a font from a TTF file pub fn new(ttf: &'a [u8]) -> Result { @@ -35,28 +57,29 @@ impl<'a> Font<'a> { let glyph_buffer = new_glyph_buffer(&face); let path_buffer = Vec::new(); - Ok(Self { face, glyph_buffer, path_buffer }) + Ok(Self { + face, + glyph_buffer, + path_buffer, + }) } - /// Render text including shaping and layout - /// - /// # Parameters - /// - `text`: The UTF-8 text to render - /// - `row`: The available rendering width in ems - pub fn render<'b: 'a>(&'b mut self, text: &mut &str, row: f32) - -> impl Iterator + 'b + 'a - { - // FIXME: Lookahead - let consumed = (*text).len(); - let piece = &(*text)[..consumed]; - + /// Simple text rendering + pub fn render<'b: 'a>( + &'b mut self, + text: &str, + ) -> impl Iterator + 'b + 'a { let mut glyph_buffer = new_glyph_buffer(&self.face); std::mem::swap(&mut glyph_buffer, &mut self.glyph_buffer); - self.glyph_buffer = glyph_buffer_with_text(&self.face, glyph_buffer, piece); - - *text = &text[consumed..]; - - Render { font: self, glyph_index: 0, advance_x: 0.0, advance_y: 0.0 } + self.glyph_buffer = + glyph_buffer_with_text(&self.face, glyph_buffer, text); + + Render { + font: self, + glyph_index: 0, + advance_x: 0.0, + advance_y: 0.0, + } } } @@ -100,7 +123,14 @@ impl Iterator for Render<'_, '_> { let glyph_x = self.advance_x + to_f32(scale, glyph_position.x_offset); let glyph_y = self.advance_y + to_f32(scale, glyph_position.y_offset); - render::build_path(&mut self.font.path_buffer, &self.font.face, glyph_x, glyph_y, glyph_id); + render::build_path( + &mut self.font.path_buffer, + &self.font.face, + glyph_x, + glyph_y, + glyph_id, + scale, + ); self.glyph_index += 1;