From 5efdf2a04a7f6ca69a05469c351c8f0a9bb0dec1 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 14 Apr 2020 13:01:20 -0700 Subject: [PATCH] Implement the missing pieces of `TextMetrics` for canvas. This required a `font-kit` upgrade, and with it a `skribo` upgrade. --- Cargo.lock | 68 ++++----- Cargo.toml | 4 + c/Cargo.toml | 2 +- canvas/Cargo.toml | 4 +- canvas/src/text.rs | 218 ++++++++++++++++++++++------- examples/canvas_nanovg/Cargo.toml | 2 +- examples/canvas_nanovg/src/main.rs | 18 ++- examples/canvas_text/Cargo.toml | 2 +- text/Cargo.toml | 5 +- text/src/lib.rs | 79 +++-------- 10 files changed, 239 insertions(+), 163 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 74ae3f6c..6a453077 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -286,7 +286,7 @@ name = "canvas_nanovg" version = "0.1.0" dependencies = [ "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "font-kit 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "font-kit 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "gl 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", "image 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)", "jemallocator 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -307,7 +307,7 @@ dependencies = [ name = "canvas_text" version = "0.1.0" dependencies = [ - "font-kit 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "font-kit 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "gl 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", "pathfinder_canvas 0.1.0", "pathfinder_color 0.1.0", @@ -700,12 +700,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "dwrote" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "wio 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -797,22 +798,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "font-kit" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "core-graphics 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", "core-text 15.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "dwrote 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "euclid 0.20.7 (registry+https://github.com/rust-lang/crates.io-index)", + "dwrote 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "float-ord 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "freetype 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "lyon_path 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pathfinder_geometry 0.5.0", + "pathfinder_simd 0.5.0", "servo-fontconfig 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1284,24 +1286,6 @@ dependencies = [ "pathfinder_lottie 0.1.0", ] -[[package]] -name = "lyon_geom" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "euclid 0.20.7 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "lyon_path" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lyon_geom 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "lzma-rs" version = "0.1.2" @@ -1601,7 +1585,7 @@ name = "pathfinder_c" version = "0.1.0" dependencies = [ "cbindgen 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", - "font-kit 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "font-kit 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "gl 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1622,13 +1606,13 @@ dependencies = [ name = "pathfinder_canvas" version = "0.1.0" dependencies = [ - "font-kit 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "font-kit 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "pathfinder_color 0.1.0", "pathfinder_content 0.1.0", "pathfinder_geometry 0.5.0", "pathfinder_renderer 0.1.0", "pathfinder_text 0.1.0", - "skribo 0.0.1 (git+https://github.com/pcwalton/skribo.git?rev=7386321f763cecbce9c4ece3401c81de62305036)", + "skribo 0.0.1 (git+https://github.com/pcwalton/skribo.git?rev=fe9c968521b4a6ed98c9166a6e620028fea285c1)", ] [[package]] @@ -1842,12 +1826,11 @@ name = "pathfinder_text" version = "0.1.0" dependencies = [ "euclid 0.20.7 (registry+https://github.com/rust-lang/crates.io-index)", - "font-kit 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lyon_path 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", + "font-kit 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "pathfinder_content 0.1.0", "pathfinder_geometry 0.5.0", "pathfinder_renderer 0.1.0", - "skribo 0.0.1 (git+https://github.com/pcwalton/skribo.git?rev=7386321f763cecbce9c4ece3401c81de62305036)", + "skribo 0.0.1 (git+https://github.com/pcwalton/skribo.git?rev=fe9c968521b4a6ed98c9166a6e620028fea285c1)", ] [[package]] @@ -2295,13 +2278,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "skribo" version = "0.0.1" -source = "git+https://github.com/pcwalton/skribo.git?rev=7386321f763cecbce9c4ece3401c81de62305036#7386321f763cecbce9c4ece3401c81de62305036" +source = "git+https://github.com/pcwalton/skribo.git?rev=fe9c968521b4a6ed98c9166a6e620028fea285c1#fe9c968521b4a6ed98c9166a6e620028fea285c1" dependencies = [ - "euclid 0.20.7 (registry+https://github.com/rust-lang/crates.io-index)", - "font-kit 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "font-kit 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "harfbuzz 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "harfbuzz-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "pathfinder_geometry 0.5.0", "unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2805,6 +2788,14 @@ dependencies = [ "x11-dl 2.18.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "wio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ws2_32-sys" version = "0.2.1" @@ -2913,7 +2904,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum dispatch 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" "checksum dlib 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "77e51249a9d823a4cb79e3eca6dcd756153e8ed0157b6c04775d04bf1b13b76a" "checksum downcast-rs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "52ba6eb47c2131e784a38b726eb54c1e1484904f013e576a25354d0124161af6" -"checksum dwrote 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0bd1369e02db5e9b842a9b67bce8a2fcc043beafb2ae8a799dd482d46ea1ff0d" +"checksum dwrote 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bcdf488e3a52a7aa30a05732a3e58420e22acb4b2b75635a561fc6ffbcab59ef" "checksum egl 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a373bc9844200b1ff15bd1b245931d1c20d09d06e4ec09f361171f29a4b0752d" "checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" "checksum env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" @@ -2925,7 +2916,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum flate2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6bd6d6f4752952feb71363cffc9ebac9411b75b87c6ab6058c40c8900cf43c0f" "checksum float-cmp 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "75224bec9bfe1a65e2d34132933f2de7fe79900c96a0174307554244ece8150e" "checksum float-ord 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7bad48618fdb549078c333a7a8528acb57af271d0433bdecd523eb620628364e" -"checksum font-kit 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "09b6e2b877097ffd0abac6597fea26fccb5ed7eb9da0a4094f11ccc8aba64efb" +"checksum font-kit 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8f953474ebbe3460775ed2da52435477cc029493284d6ceb635598586a2c6298" "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" "checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" "checksum freetype 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "11926b2b410b469d0e9399eca4cbbe237a9ef02176c485803b29216307e8e028" @@ -2979,8 +2970,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum line_drawing 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5cc7ad3d82c845bdb5dde34ffdcc7a5fb4d2996e1e1ee0f19c33bc80e15196b9" "checksum lock_api 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "79b2de95ecb4691949fea4716ca53cdbcfccb2c612e19644a8bad05edcf9f47b" "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" -"checksum lyon_geom 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ca04310c9807612a311506106000b6eccb2e27bca9bfb594ce80fb8a31231f9d" -"checksum lyon_path 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0bcb57ac24a5428539e2c7c0592766d5933c937d703f430990c669c00de96862" "checksum lzma-rs 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ad0606857a51b9088eb75b52d8431b7b7c8656849cc6cb96dde9f3d18a1a4b58" "checksum lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084" "checksum malloc_buf 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" @@ -3063,7 +3052,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum shared_library 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" "checksum simplecss 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "596554e63596d556a0dbd681416342ca61c75f1a45203201e7e77d3fa2fa9014" "checksum siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" -"checksum skribo 0.0.1 (git+https://github.com/pcwalton/skribo.git?rev=7386321f763cecbce9c4ece3401c81de62305036)" = "" +"checksum skribo 0.0.1 (git+https://github.com/pcwalton/skribo.git?rev=fe9c968521b4a6ed98c9166a6e620028fea285c1)" = "" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" "checksum smallvec 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc" "checksum smithay-client-toolkit 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "93960e8975909fcb14cc755de93af2149d8b8f4eb368315537d40cfd0f324054" @@ -3119,6 +3108,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum winapi-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4ccfbf554c6ad11084fb7517daca16cfdcaccbdadba4fc336f032a8b12c2ad80" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" "checksum winit 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)" = "65a5c1a5ef76ac31cc97ad29489acdbed2178f3fc12ca00ee6cb11d60adb5a3a" +"checksum wio 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" "checksum x11-dl 2.18.5 (registry+https://github.com/rust-lang/crates.io-index)" = "2bf981e3a5b3301209754218f962052d4d9ee97e478f4d26d4a6eced34c1fef8" "checksum xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" diff --git a/Cargo.toml b/Cargo.toml index f1cf1654..db37ef4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,3 +64,7 @@ default-members = [ "utils/svg-to-skia", "utils/convert", ] + +[patch.crates-io] +pathfinder_geometry = { path = "geometry" } +pathfinder_simd = { path = "simd" } diff --git a/c/Cargo.toml b/c/Cargo.toml index 5e1d4345..c3e2a0f1 100644 --- a/c/Cargo.toml +++ b/c/Cargo.toml @@ -9,7 +9,7 @@ build = "build.rs" crate-type = ["staticlib"] [dependencies] -font-kit = "0.5" +font-kit = "0.6" foreign-types = "0.3" gl = "0.14" libc = "0.2" diff --git a/canvas/Cargo.toml b/canvas/Cargo.toml index d8293135..7f4a47b1 100644 --- a/canvas/Cargo.toml +++ b/canvas/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" crate-type = ["rlib", "staticlib"] [dependencies] -font-kit = { version = "0.5", optional = true } +font-kit = { version = "0.6", optional = true } [dependencies.pathfinder_color] path = "../color" @@ -28,7 +28,7 @@ optional = true [dependencies.skribo] git = "https://github.com/pcwalton/skribo.git" -rev = "7386321f763cecbce9c4ece3401c81de62305036" +rev = "fe9c968521b4a6ed98c9166a6e620028fea285c1" optional = true [features] diff --git a/canvas/src/text.rs b/canvas/src/text.rs index 2c668346..d37855d7 100644 --- a/canvas/src/text.rs +++ b/canvas/src/text.rs @@ -8,18 +8,18 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use crate::{CanvasRenderingContext2D, TextAlign, TextBaseline}; +use crate::{CanvasRenderingContext2D, State, TextAlign, TextBaseline}; +use font_kit::canvas::RasterizationOptions; use font_kit::family_name::FamilyName; use font_kit::handle::Handle; use font_kit::hinting::HintingOptions; use font_kit::loaders::default::Font; -use font_kit::metrics::Metrics; use font_kit::properties::Properties; use font_kit::source::{Source, SystemSource}; use font_kit::sources::mem::MemSource; use pathfinder_geometry::transform2d::Transform2F; use pathfinder_geometry::util; -use pathfinder_geometry::vector::Vector2F; +use pathfinder_geometry::vector::{Vector2F, vec2f}; use pathfinder_renderer::paint::PaintId; use pathfinder_text::{SceneExt, TextRenderMode}; use skribo::{FontCollection, FontFamily, FontRef, Layout, TextStyle}; @@ -40,7 +40,9 @@ impl CanvasRenderingContext2D { } pub fn measure_text(&self, string: &str) -> TextMetrics { - TextMetrics { width: self.layout_text(string).width() } + let mut metrics = self.layout_text(string).metrics(); + metrics.make_origin_relative(&self.current_state); + metrics } pub fn fill_layout(&mut self, layout: &Layout, transform: Transform2F) { @@ -69,25 +71,7 @@ impl CanvasRenderingContext2D { let clip_path = self.current_state.clip_path; let blend_mode = self.current_state.global_composite_operation.to_blend_mode(); - match self.current_state.text_align { - TextAlign::Left => {}, - TextAlign::Right => position.set_x(position.x() - layout.width()), - TextAlign::Center => position.set_x(position.x() - layout.width() * 0.5), - } - - match self.current_state.text_baseline { - TextBaseline::Alphabetic => {} - TextBaseline::Top => position.set_y(position.y() + layout.ascent()), - TextBaseline::Middle => { - position.set_y(position.y() + util::lerp(layout.ascent(), layout.descent(), 0.5)) - } - TextBaseline::Bottom => position.set_y(position.y() + layout.descent()), - TextBaseline::Ideographic => { - position.set_y(position.y() + layout.ideographic_baseline()) - } - TextBaseline::Hanging => position.set_y(position.y() + layout.hanging_baseline()), - } - + position += layout.metrics().text_origin(&self.current_state); let transform = self.current_state.transform * Transform2F::from_translation(position); // TODO(pcwalton): Report errors. @@ -151,10 +135,46 @@ impl CanvasRenderingContext2D { } } -// TODO(pcwalton): Support other fields. +/// Represents the dimensions of a piece of text in the canvas. #[derive(Clone, Copy, Debug)] pub struct TextMetrics { + /// The calculated width of a segment of inline text in pixels. pub width: f32, + /// The distance from the alignment point given by the `text_align` state to the left side of + /// the bounding rectangle of the given text, in pixels. The distance is measured parallel to + /// the baseline. + pub actual_bounding_box_left: f32, + /// The distance from the alignment point given by the `text_align` state to the right side of + /// the bounding rectangle of the given text, in pixels. The distance is measured parallel to + /// the baseline. + pub actual_bounding_box_right: f32, + /// The distance from the horizontal line indicated by the `text_baseline` state to the top of + /// the highest bounding rectangle of all the fonts used to render the text, in pixels. + pub font_bounding_box_ascent: f32, + /// The distance from the horizontal line indicated by the `text_baseline` state to the bottom + /// of the highest bounding rectangle of all the fonts used to render the text, in pixels. + pub font_bounding_box_descent: f32, + /// The distance from the horizontal line indicated by the `text_baseline` state to the top of + /// the bounding rectangle used to render the text, in pixels. + pub actual_bounding_box_ascent: f32, + /// The distance from the horizontal line indicated by the `text_baseline` state to the bottom + /// of the bounding rectangle used to render the text, in pixels. + pub actual_bounding_box_descent: f32, + /// The distance from the horizontal line indicated by the `text_baseline` state to the top of + /// the em square in the line box, in pixels. + pub em_height_ascent: f32, + /// The distance from the horizontal line indicated by the `text_baseline` state to the bottom + /// of the em square in the line box, in pixels. + pub em_height_descent: f32, + /// The distance from the horizontal line indicated by the `text_baseline` state to the hanging + /// baseline of the line box, in pixels. + pub hanging_baseline: f32, + /// The distance from the horizontal line indicated by the `text_baseline` state to the + /// alphabetic baseline of the line box, in pixels. + pub alphabetic_baseline: f32, + /// The distance from the horizontal line indicated by the `text_baseline` state to the + /// ideographic baseline of the line box, in pixels. + pub ideographic_baseline: f32, } #[cfg(feature = "pf-text")] @@ -196,17 +216,108 @@ impl CanvasFontContext { // Text layout utilities +impl TextMetrics { + fn text_origin(&self, state: &State) -> Vector2F { + let x = match state.text_align { + TextAlign::Left => 0.0, + TextAlign::Right => -self.width, + TextAlign::Center => -0.5 * self.width, + }; + + let y = match state.text_baseline { + TextBaseline::Alphabetic => 0.0, + TextBaseline::Top => self.em_height_ascent, + TextBaseline::Middle => util::lerp(self.em_height_ascent, self.em_height_descent, 0.5), + TextBaseline::Bottom => self.em_height_descent, + TextBaseline::Ideographic => self.ideographic_baseline, + TextBaseline::Hanging => self.hanging_baseline, + }; + + vec2f(x, y) + } + + fn make_origin_relative(&mut self, state: &State) { + let text_origin = self.text_origin(state); + self.actual_bounding_box_left += text_origin.x(); + self.actual_bounding_box_right += text_origin.x(); + self.font_bounding_box_ascent -= text_origin.y(); + self.font_bounding_box_descent -= text_origin.y(); + self.actual_bounding_box_ascent -= text_origin.y(); + self.actual_bounding_box_descent -= text_origin.y(); + self.em_height_ascent -= text_origin.y(); + self.em_height_descent -= text_origin.y(); + self.hanging_baseline -= text_origin.y(); + self.alphabetic_baseline -= text_origin.y(); + self.ideographic_baseline -= text_origin.y(); + } +} + pub trait LayoutExt { + fn metrics(&self) -> TextMetrics; fn width(&self) -> f32; - fn fold_metric(&self, get: G, fold: F) -> f32 where G: FnMut(&Metrics) -> f32, - F: FnMut(f32, f32) -> f32; - fn ascent(&self) -> f32; - fn descent(&self) -> f32; + fn actual_bounding_box_left(&self) -> f32; + fn actual_bounding_box_right(&self) -> f32; fn hanging_baseline(&self) -> f32; fn ideographic_baseline(&self) -> f32; } impl LayoutExt for Layout { + // NB: This does not return origin-relative values. To get those, call `make_origin_relative()` + // afterward. + fn metrics(&self) -> TextMetrics { + let (mut em_height_ascent, mut em_height_descent) = (0.0, 0.0); + let (mut font_bounding_box_ascent, mut font_bounding_box_descent) = (0.0, 0.0); + let (mut actual_bounding_box_ascent, mut actual_bounding_box_descent) = (0.0, 0.0); + + let mut last_font: Option> = None; + for glyph in &self.glyphs { + match last_font { + Some(ref last_font) if Arc::ptr_eq(&last_font, &glyph.font.font) => {} + _ => { + let font = glyph.font.font.clone(); + + let font_metrics = font.metrics(); + let scale_factor = self.size / font_metrics.units_per_em as f32; + em_height_ascent = (font_metrics.ascent * scale_factor).max(em_height_ascent); + em_height_descent = + (font_metrics.descent * scale_factor).min(em_height_descent); + font_bounding_box_ascent = (font_metrics.bounding_box.max_y() * + scale_factor).max(font_bounding_box_ascent); + font_bounding_box_descent = (font_metrics.bounding_box.min_y() * + scale_factor).min(font_bounding_box_descent); + + last_font = Some(font); + } + } + + let font = last_font.as_ref().unwrap(); + let glyph_rect = font.raster_bounds(glyph.glyph_id, + self.size, + Transform2F::default(), + HintingOptions::None, + RasterizationOptions::GrayscaleAa).unwrap(); + actual_bounding_box_ascent = + (glyph_rect.max_y() as f32).max(actual_bounding_box_ascent); + actual_bounding_box_descent = + (glyph_rect.min_y() as f32).min(actual_bounding_box_descent); + } + + TextMetrics { + width: self.width(), + actual_bounding_box_left: self.actual_bounding_box_left(), + actual_bounding_box_right: self.actual_bounding_box_right(), + font_bounding_box_ascent, + font_bounding_box_descent, + actual_bounding_box_ascent, + actual_bounding_box_descent, + em_height_ascent, + em_height_descent, + alphabetic_baseline: 0.0, + hanging_baseline: self.hanging_baseline(), + ideographic_baseline: self.ideographic_baseline(), + } + } + fn width(&self) -> f32 { let last_glyph = match self.glyphs.last() { None => return 0.0, @@ -217,32 +328,41 @@ impl LayoutExt for Layout { let font_metrics = last_glyph.font.font.metrics(); let glyph_rect = last_glyph.font.font.typographic_bounds(glyph_id).unwrap(); let scale_factor = self.size / font_metrics.units_per_em as f32; - last_glyph.offset.x + glyph_rect.max_x() * scale_factor + last_glyph.offset.x() + glyph_rect.max_x() * scale_factor } - fn fold_metric(&self, mut get: G, mut fold: F) -> f32 where G: FnMut(&Metrics) -> f32, - F: FnMut(f32, f32) -> f32 { - let (mut last_font_seen, mut value) = (None, 0.0); - for glyph in &self.glyphs { - if let Some(ref last_font_seen) = last_font_seen { - if Arc::ptr_eq(last_font_seen, &glyph.font.font) { - continue; - } - } - let font_metrics = glyph.font.font.metrics(); - let scale_factor = self.size / font_metrics.units_per_em as f32; - value = fold(value, get(&font_metrics) * scale_factor); - last_font_seen = Some(glyph.font.font.clone()); - } - value + fn actual_bounding_box_left(&self) -> f32 { + let first_glyph = match self.glyphs.get(0) { + None => return 0.0, + Some(first_glyph) => first_glyph, + }; + + let glyph_id = first_glyph.glyph_id; + let glyph_rect = first_glyph.font + .font + .raster_bounds(glyph_id, + self.size, + Transform2F::default(), + HintingOptions::None, + RasterizationOptions::GrayscaleAa).unwrap(); + first_glyph.offset.x() + glyph_rect.min_x() as f32 } - fn ascent(&self) -> f32 { - self.fold_metric(|metrics| metrics.ascent, f32::max) - } + fn actual_bounding_box_right(&self) -> f32 { + let last_glyph = match self.glyphs.last() { + None => return 0.0, + Some(last_glyph) => last_glyph, + }; - fn descent(&self) -> f32 { - self.fold_metric(|metrics| metrics.descent, f32::min) + let glyph_id = last_glyph.glyph_id; + let glyph_rect = last_glyph.font + .font + .raster_bounds(glyph_id, + self.size, + Transform2F::default(), + HintingOptions::None, + RasterizationOptions::GrayscaleAa).unwrap(); + last_glyph.offset.x() + glyph_rect.max_x() as f32 } fn hanging_baseline(&self) -> f32 { diff --git a/examples/canvas_nanovg/Cargo.toml b/examples/canvas_nanovg/Cargo.toml index 74fe8fd5..4f07895e 100644 --- a/examples/canvas_nanovg/Cargo.toml +++ b/examples/canvas_nanovg/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" [dependencies] arrayvec = "0.5" -font-kit = "0.5" +font-kit = "0.6" gl = "0.14" sdl2 = "0.33" sdl2-sys = "0.33" diff --git a/examples/canvas_nanovg/src/main.rs b/examples/canvas_nanovg/src/main.rs index f414e502..2f6c4934 100644 --- a/examples/canvas_nanovg/src/main.rs +++ b/examples/canvas_nanovg/src/main.rs @@ -235,6 +235,8 @@ fn draw_paragraph(context: &mut CanvasRenderingContext2D, context.set_font(&[FONT_NAME_REGULAR, FONT_NAME_EMOJI][..]); context.set_font_size(18.0); context.set_fill_style(ColorU::white()); + context.set_text_align(TextAlign::Left); + context.set_text_baseline(TextBaseline::Alphabetic); let main_text = MultilineTextBox::new(context, PARAGRAPH_TEXT, origin + vec2f(0.0, 24.0), @@ -270,6 +272,8 @@ fn draw_paragraph(context: &mut CanvasRenderingContext2D, // Fade out the tooltip when close to it. context.set_font_size(11.0); + context.set_text_align(TextAlign::Left); + context.set_text_baseline(TextBaseline::Alphabetic); let tooltip_origin = main_text.bounds.lower_left() + vec2f(0.0, 38.0); let tooltip = MultilineTextBox::new(context, HOVER_TEXT, tooltip_origin, 150.0, 18.0); let mouse_vector = mouse_position.clamp(tooltip.bounds.origin(), @@ -341,7 +345,7 @@ impl MultilineTextBox { while let Some(mut paragraph) = text.pop_front() { while !paragraph.is_empty() { - let mut line = Line::new(origin, max_width, line_height); + let mut line = Line::new(origin, max_width); line.layout(context, &mut paragraph, space_width); origin += vec2f(0.0, line_height); @@ -384,8 +388,8 @@ impl MultilineTextBox { } impl Line { - fn new(origin: Vector2F, max_width: f32, line_height: f32) -> Line { - Line { words: vec![], origin, ascent: line_height, descent: 0.0, width: 0.0, max_width } + fn new(origin: Vector2F, max_width: f32) -> Line { + Line { words: vec![], origin, ascent: 0.0, descent: 0.0, width: 0.0, max_width } } fn layout(&mut self, @@ -398,8 +402,8 @@ impl Line { word_origin_x += space_width; } - let word_width = context.measure_text(&word).width; - let new_line_width = word_origin_x + word_width; + let word_metrics = context.measure_text(&word); + let new_line_width = word_origin_x + word_metrics.width; if self.width != 0.0 && new_line_width > self.max_width { text.push_front(word); return; @@ -407,6 +411,8 @@ impl Line { self.words.push(Word { text: word, origin_x: word_origin_x }); self.width = new_line_width; + self.ascent = self.ascent.max(word_metrics.em_height_ascent); + self.descent = self.descent.min(word_metrics.em_height_descent); } } @@ -427,7 +433,7 @@ impl Line { fn bounds(&self) -> RectF { RectF::new(self.origin - vec2f(0.0, self.ascent), - vec2f(self.width, self.ascent + self.descent)) + vec2f(self.width, self.ascent - self.descent)) } fn hit_test(&self, context: &CanvasRenderingContext2D, mut mouse_position: Vector2F) diff --git a/examples/canvas_text/Cargo.toml b/examples/canvas_text/Cargo.toml index f5da5294..dad3dbec 100644 --- a/examples/canvas_text/Cargo.toml +++ b/examples/canvas_text/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Patrick Walton "] edition = "2018" [dependencies] -font-kit = "0.5" +font-kit = "0.6" gl = "0.14" sdl2 = "0.33" sdl2-sys = "0.33" diff --git a/text/Cargo.toml b/text/Cargo.toml index 6283061a..a2eb9ecf 100644 --- a/text/Cargo.toml +++ b/text/Cargo.toml @@ -6,8 +6,7 @@ edition = "2018" [dependencies] euclid = "0.20" -font-kit = "0.5" -lyon_path = "0.14" +font-kit = "0.6" [dependencies.pathfinder_content] path = "../content" @@ -20,4 +19,4 @@ path = "../renderer" [dependencies.skribo] git = "https://github.com/pcwalton/skribo.git" -rev = "7386321f763cecbce9c4ece3401c81de62305036" +rev = "fe9c968521b4a6ed98c9166a6e620028fea285c1" diff --git a/text/src/lib.rs b/text/src/lib.rs index 09368ca2..f8dfef4b 100644 --- a/text/src/lib.rs +++ b/text/src/lib.rs @@ -8,15 +8,14 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use euclid::Angle; -use euclid::default::{Point2D, Vector2D}; use font_kit::error::GlyphLoadingError; use font_kit::hinting::HintingOptions; use font_kit::loader::Loader; -use lyon_path::builder::{FlatPathBuilder, PathBuilder, Build}; +use font_kit::outline::OutlineSink; use pathfinder_content::effects::BlendMode; use pathfinder_content::outline::{Contour, Outline}; use pathfinder_content::stroke::{OutlineStrokeToFill, StrokeStyle}; +use pathfinder_geometry::line_segment::LineSegment2F; use pathfinder_geometry::transform2d::Transform2F; use pathfinder_geometry::vector::Vector2F; use pathfinder_renderer::paint::PaintId; @@ -105,7 +104,7 @@ impl SceneExt for Scene { paint_id: PaintId) -> Result<(), GlyphLoadingError> { for glyph in &layout.glyphs { - let offset = Vector2F::new(glyph.offset.x, glyph.offset.y); + let offset = glyph.offset; let font = &*glyph.font.font; // FIXME(pcwalton): Cache this! let scale = style.size / (font.metrics().units_per_em as f32); @@ -174,75 +173,33 @@ impl OutlinePathBuilder { } } - fn convert_point(&self, point: Point2D) -> Vector2F { - self.transform * Vector2F::new(point.x, point.y) - } -} - -impl PathBuilder for OutlinePathBuilder { - fn quadratic_bezier_to(&mut self, ctrl: Point2D, to: Point2D) { - let (ctrl, to) = (self.convert_point(ctrl), self.convert_point(to)); - self.current_contour.push_quadratic(ctrl, to); - } - - fn cubic_bezier_to(&mut self, ctrl0: Point2D, ctrl1: Point2D, to: Point2D) { - let (ctrl0, ctrl1) = (self.convert_point(ctrl0), self.convert_point(ctrl1)); - let to = self.convert_point(to); - self.current_contour.push_cubic(ctrl0, ctrl1, to); - } - - fn arc(&mut self, - _center: Point2D, - _radii: Vector2D, - _sweep_angle: Angle, - _x_rotation: Angle) { - // TODO(pcwalton): Arcs. - } -} - -impl Build for OutlinePathBuilder { - type PathType = Outline; fn build(mut self) -> Outline { self.flush_current_contour(); self.outline } - - fn build_and_reset(&mut self) -> Outline { - self.flush_current_contour(); - mem::replace(&mut self.outline, Outline::new()) - } - } -impl FlatPathBuilder for OutlinePathBuilder { - - fn move_to(&mut self, to: Point2D) { +impl OutlineSink for OutlinePathBuilder { + fn move_to(&mut self, to: Vector2F) { self.flush_current_contour(); - let to = self.convert_point(to); - self.current_contour.push_endpoint(to); + self.current_contour.push_endpoint(self.transform * to); } - fn line_to(&mut self, to: Point2D) { - let to = self.convert_point(to); - self.current_contour.push_endpoint(to); + fn line_to(&mut self, to: Vector2F) { + self.current_contour.push_endpoint(self.transform * to); + } + + fn quadratic_curve_to(&mut self, ctrl: Vector2F, to: Vector2F) { + self.current_contour.push_quadratic(self.transform * ctrl, self.transform * to); + } + + fn cubic_curve_to(&mut self, ctrl: LineSegment2F, to: Vector2F) { + self.current_contour.push_cubic(self.transform * ctrl.from(), + self.transform * ctrl.to(), + self.transform * to); } fn close(&mut self) { self.current_contour.close(); } - - fn current_position(&self) -> Point2D { - if self.current_contour.is_empty() { - return Point2D::new(0.0, 0.0) - } - - let point_index = if self.current_contour.is_closed() { - 0 - } else { - self.current_contour.len() - 1 - }; - - let point = self.current_contour.position_of(point_index); - Point2D::new(point.x(), point.y()) - } }