From 714bbb769ec1ff4a32e594aa6d67d13b98814caa Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sun, 19 Jul 2020 20:35:15 +0000 Subject: [PATCH] Bump version to 0.5.0-rc2 --- .builds/rustfmt.yml | 4 +- .github/pull_request_template.md | 3 + CHANGELOG.md | 11 + CONTRIBUTING.md | 16 +- Cargo.lock | 48 +- Cargo.toml | 1 - README.md | 8 +- alacritty/Cargo.toml | 10 +- alacritty/src/config/font.rs | 2 +- alacritty/src/cursor.rs | 4 +- alacritty/src/display.rs | 104 +-- alacritty/src/event.rs | 22 +- alacritty/src/renderer/mod.rs | 34 +- alacritty/src/renderer/rects.rs | 2 +- alacritty/src/url.rs | 3 +- alacritty/src/window.rs | 12 +- alacritty_terminal/Cargo.toml | 2 +- extra/alacritty.man | 2 +- .../linux/io.alacritty.Alacritty.appdata.xml | 2 +- extra/linux/redhat/alacritty.spec | 2 +- extra/linux/snap/snapcraft.yaml | 2 +- extra/osx/Alacritty.app/Contents/Info.plist | 2 +- extra/windows/wix/alacritty.wxs | 2 +- font/Cargo.toml | 31 - font/src/darwin/byte_order.rs | 57 -- font/src/darwin/mod.rs | 634 -------------- font/src/directwrite/mod.rs | 335 -------- font/src/ft/fc/char_set.rs | 70 -- font/src/ft/fc/config.rs | 29 - font/src/ft/fc/font_set.rs | 87 -- font/src/ft/fc/mod.rs | 337 -------- font/src/ft/fc/object_set.rs | 48 -- font/src/ft/fc/pattern.rs | 627 -------------- font/src/ft/mod.rs | 787 ------------------ font/src/lib.rs | 226 ----- 35 files changed, 172 insertions(+), 3394 deletions(-) delete mode 100644 font/Cargo.toml delete mode 100644 font/src/darwin/byte_order.rs delete mode 100644 font/src/darwin/mod.rs delete mode 100644 font/src/directwrite/mod.rs delete mode 100644 font/src/ft/fc/char_set.rs delete mode 100644 font/src/ft/fc/config.rs delete mode 100644 font/src/ft/fc/font_set.rs delete mode 100644 font/src/ft/fc/mod.rs delete mode 100644 font/src/ft/fc/object_set.rs delete mode 100644 font/src/ft/fc/pattern.rs delete mode 100644 font/src/ft/mod.rs delete mode 100644 font/src/lib.rs diff --git a/.builds/rustfmt.yml b/.builds/rustfmt.yml index fe863e47..52836217 100644 --- a/.builds/rustfmt.yml +++ b/.builds/rustfmt.yml @@ -3,8 +3,8 @@ sources: - https://github.com/alacritty/alacritty tasks: - rustup: | - curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain nightly --profile minimal - $HOME/.cargo/bin/rustup component add rustfmt + curl https://sh.rustup.rs -sSf | sh -s -- -y --profile minimal --default-toolchain none + $HOME/.cargo/bin/rustup toolchain install nightly -c rustfmt - rustfmt: | cd alacritty $HOME/.cargo/bin/cargo fmt -- --check diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 802ca57a..9dc048b0 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -4,6 +4,9 @@ Please make sure to document all user-facing changes in the If support for a new escape sequence was added, it should be documented in `./docs/escape_support.md`. +Since `alacritty_terminal`'s version always tracks the next release, make sure +that the version is bumped according to semver when necessary. + Draft PRs are always welcome, though unless otherwise requested PRs will not be reviewed until all required and optional CI steps are successful and they have left the draft stage. diff --git a/CHANGELOG.md b/CHANGELOG.md index 5acf7a30..8e9603d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.5.0-rc2 + +### Added + +- Separate search label for backward search + +### Fixed + +- Selection tracking in search without vi mode +- Crash when resizing below search label length + ## 0.5.0-rc1 ### Packaging diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6e426513..d87a23e7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -45,6 +45,9 @@ issues. Please note that the minimum supported version of Alacritty is Rust 1.41.0. All patches are expected to work with the minimum supported version. +Since `alacritty_terminal`'s version always tracks the next release, make sure that the version is +bumped according to semver when necessary. + ### Testing To make sure no regressions were introduced, all tests should be run before sending a pull request. @@ -128,10 +131,11 @@ The exact steps for an exemplary `0.2.0` release might look like this: major issues are found in the release candidates 9. In the branch, the version is bumped to `0.2.0` 10. The new commit in the branch is tagged as `v0.2.0` - 11. A GitHub release is created for the `v0.2.0` tag - 12. The changelog since the last stable release (**not** RC) is added to the GitHub release + 11. The new version is published to crates.io + 12. A GitHub release is created for the `v0.2.0` tag + 13. The changelog since the last stable release (**not** RC) is added to the GitHub release description - 13. The `-dev` is stripped from the `0.2.0-dev` changelog entries on master + 14. The `-dev` is stripped from the `0.2.0-dev` changelog entries on master On master and with new planned releases, only the minor version is bumped. This makes it possible to create bug fix releases by incrementing the patch version of a previous minor release, without @@ -145,6 +149,12 @@ The exact steps for an exemplary `0.2.3` release might look like this: 6. Follow Steps 5-12 of the regular release's example 7. The release's changelog is ported back to master, removing fixes from the `0.2.3` release +The `alacritty_terminal` crate is released in synchronization with `alacritty`, keeping the `-dev` +and `-rcX` version suffix identical across the two crates. As soon as the new Alacritty stable +release is made, releases are tagged as `alacritty_terminal_vX.Y.Z` and pushed to crates.io. During +a release, only the patch version is bumped on master, since there haven't been any changes since +the last release yet. + # Contact If there are any outstanding questions about contributing to Alacritty, they can be asked on the diff --git a/Cargo.lock b/Cargo.lock index 4380ae35..37ae18cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,15 +20,15 @@ dependencies = [ [[package]] name = "alacritty" -version = "0.5.0-rc1" +version = "0.5.0-rc2" dependencies = [ - "alacritty_terminal 0.5.0-rc1", + "alacritty_terminal 0.10.0-rc2", "clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)", "copypasta 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossfont 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "embed-resource 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "font 0.1.0", "gl_generator 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", "glutin 0.24.1 (registry+https://github.com/rust-lang/crates.io-index)", "image 0.23.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -52,7 +52,7 @@ dependencies = [ [[package]] name = "alacritty_terminal" -version = "0.5.0-rc1" +version = "0.10.0-rc2" dependencies = [ "base64 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -413,6 +413,26 @@ dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "crossfont" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cocoa 0.20.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-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "core-graphics 0.19.2 (registry+https://github.com/rust-lang/crates.io-index)", + "core-text 15.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "dwrote 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "euclid 0.20.14 (registry+https://github.com/rust-lang/crates.io-index)", + "foreign-types 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "freetype-rs 0.26.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "servo-fontconfig 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "deflate" version = "0.8.6" @@ -552,25 +572,6 @@ name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "font" -version = "0.1.0" -dependencies = [ - "cocoa 0.20.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-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "core-graphics 0.19.2 (registry+https://github.com/rust-lang/crates.io-index)", - "core-text 15.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "dwrote 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "euclid 0.20.14 (registry+https://github.com/rust-lang/crates.io-index)", - "foreign-types 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "freetype-rs 0.26.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "servo-fontconfig 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "foreign-types" version = "0.3.2" @@ -2316,6 +2317,7 @@ dependencies = [ "checksum core-video-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "34ecad23610ad9757664d644e369246edde1803fcb43ed72876565098a5d3828" "checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" "checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +"checksum crossfont 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb710de01349371230ec5f5e65410826682448dfad14d97b473a69d850f686bd" "checksum deflate 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)" = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" "checksum derivative 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cb582b60359da160a9477ee80f15c8d784c477e69c217ef2cdd4169c24ea380f" "checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" diff --git a/Cargo.toml b/Cargo.toml index b904b2f8..0198afd7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,6 @@ members = [ "alacritty", "alacritty_terminal", - "font", ] [profile.release] diff --git a/README.md b/README.md index f6dba7bb..58c52688 100644 --- a/README.md +++ b/README.md @@ -95,13 +95,7 @@ nix-env -iA nixos.alacritty zypper in alacritty ``` -### Pop!\_OS / Ubuntu - -> If you're not running Pop!_OS, you'll have to add a third party repository first: -> -> ```sh -> add-apt-repository ppa:mmstick76/alacritty -> ``` +### Pop!\_OS ```sh apt install alacritty diff --git a/alacritty/Cargo.toml b/alacritty/Cargo.toml index c2b6572b..797a134d 100644 --- a/alacritty/Cargo.toml +++ b/alacritty/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "alacritty" -version = "0.5.0-rc1" +version = "0.5.0-rc2" authors = ["Christian Duerr ", "Joe Wilm "] license = "Apache-2.0" description = "GPU-accelerated terminal emulator" @@ -8,8 +8,12 @@ readme = "../README.md" homepage = "https://github.com/alacritty/alacritty" edition = "2018" +[dependencies.alacritty_terminal] +path = "../alacritty_terminal" +version = "0.10.0-dev" +default-features = false + [dependencies] -alacritty_terminal = { path = "../alacritty_terminal", default-features = false } clap = "2" log = { version = "0.4", features = ["std"] } time = "0.1.40" @@ -20,7 +24,7 @@ serde_json = "1" glutin = { version = "0.24.0", features = ["serde"] } notify = "4" parking_lot = "0.10.2" -font = { path = "../font", features = ["force_system_fontconfig"] } +crossfont = { version = "0.1.0", features = ["force_system_fontconfig"] } urlocator = "0.1.3" copypasta = { version = "0.7.0", default-features = false } libc = "0.2" diff --git a/alacritty/src/config/font.rs b/alacritty/src/config/font.rs index f718587c..9982352f 100644 --- a/alacritty/src/config/font.rs +++ b/alacritty/src/config/font.rs @@ -1,6 +1,6 @@ use std::fmt; -use font::Size; +use crossfont::Size; use log::error; use serde::de::Visitor; use serde::{Deserialize, Deserializer}; diff --git a/alacritty/src/cursor.rs b/alacritty/src/cursor.rs index 25a2f657..2ee2916c 100644 --- a/alacritty/src/cursor.rs +++ b/alacritty/src/cursor.rs @@ -2,9 +2,9 @@ use std::cmp; -use alacritty_terminal::ansi::CursorStyle; +use crossfont::{BitmapBuffer, Metrics, RasterizedGlyph}; -use font::{BitmapBuffer, Metrics, RasterizedGlyph}; +use alacritty_terminal::ansi::CursorStyle; pub fn get_cursor_glyph( cursor: CursorStyle, diff --git a/alacritty/src/display.rs b/alacritty/src/display.rs index ab6ed7e6..ad22c852 100644 --- a/alacritty/src/display.rs +++ b/alacritty/src/display.rs @@ -21,22 +21,19 @@ use unicode_width::UnicodeWidthChar; use wayland_client::{Display as WaylandDisplay, EventQueue}; #[cfg(target_os = "macos")] -use font::set_font_smoothing; -use font::{self, Rasterize, Rasterizer}; +use crossfont::set_font_smoothing; +use crossfont::{self, Rasterize, Rasterizer}; use alacritty_terminal::event::{EventListener, OnResize}; -#[cfg(not(windows))] -use alacritty_terminal::grid::Dimensions; -use alacritty_terminal::index::Line; -#[cfg(not(windows))] use alacritty_terminal::index::{Column, Point}; +use alacritty_terminal::index::{Direction, Line}; use alacritty_terminal::selection::Selection; use alacritty_terminal::term::{RenderableCell, SizeInfo, Term, TermMode}; use crate::config::font::Font; use crate::config::window::StartupMode; use crate::config::Config; -use crate::event::Mouse; +use crate::event::{Mouse, SearchState}; use crate::message_bar::MessageBuffer; use crate::meter::Meter; use crate::renderer::rects::{RenderLines, RenderRect}; @@ -44,7 +41,8 @@ use crate::renderer::{self, GlyphCache, QuadRenderer}; use crate::url::{Url, Urls}; use crate::window::{self, Window}; -const SEARCH_LABEL: &str = "Search: "; +const FORWARD_SEARCH_LABEL: &str = "Search: "; +const BACKWARD_SEARCH_LABEL: &str = "Backward Search: "; #[derive(Debug)] pub enum Error { @@ -52,7 +50,7 @@ pub enum Error { Window(window::Error), /// Error dealing with fonts. - Font(font::Error), + Font(crossfont::Error), /// Error in renderer. Render(renderer::Error), @@ -89,8 +87,8 @@ impl From for Error { } } -impl From for Error { - fn from(val: font::Error) -> Self { +impl From for Error { + fn from(val: crossfont::Error) -> Self { Error::Font(val) } } @@ -454,12 +452,12 @@ impl Display { config: &Config, mouse: &Mouse, mods: ModifiersState, - search_regex: Option<&String>, + search_state: &SearchState, ) { let grid_cells: Vec = terminal.renderable_cells(config).collect(); - let search_regex = search_regex.map(|regex| Self::format_search(®ex)); let visual_bell_intensity = terminal.visual_bell.intensity(); let background_color = terminal.background_color(); + let cursor_point = terminal.grid().cursor.point; let metrics = self.glyph_cache.font_metrics(); let glyph_cache = &mut self.glyph_cache; let size_info = self.size_info; @@ -474,20 +472,6 @@ impl Display { None }; - // Update IME position. - #[cfg(not(windows))] - { - let point = match &search_regex { - Some(regex) => { - let column = min(regex.len() + SEARCH_LABEL.len() - 1, terminal.cols().0 - 1); - Point::new(terminal.screen_lines() - 1, Column(column)) - }, - None => terminal.grid().cursor.point, - }; - - self.window.update_ime_position(point, &self.size_info); - } - // Drop terminal as early as possible to free lock. drop(terminal); @@ -592,9 +576,30 @@ impl Display { self.renderer.draw_rects(&size_info, rects); } - self.draw_search(config, &size_info, message_bar_lines, search_regex); self.draw_render_timer(config, &size_info); + // Handle search and IME positioning. + let ime_position = match search_state.regex() { + Some(regex) => { + let search_label = match search_state.direction() { + Direction::Right => FORWARD_SEARCH_LABEL, + Direction::Left => BACKWARD_SEARCH_LABEL, + }; + + let search_text = Self::format_search(&size_info, regex, search_label); + + // Render the search bar. + self.draw_search(config, &size_info, message_bar_lines, &search_text); + + // Compute IME position. + Point::new(size_info.lines() - 1, Column(search_text.len() - 1)) + }, + None => cursor_point, + }; + + // Update IME position. + self.window.update_ime_position(ime_position, &self.size_info); + // Frame event should be requested before swaping buffers, since it requires surface // `commit`, which is done by swap buffers under the hood. #[cfg(not(any(target_os = "macos", windows)))] @@ -616,20 +621,33 @@ impl Display { } /// Format search regex to account for the cursor and fullwidth characters. - fn format_search(search_regex: &str) -> String { + fn format_search(size_info: &SizeInfo, search_regex: &str, search_label: &str) -> String { // Add spacers for wide chars. - let mut text = String::with_capacity(search_regex.len()); + let mut formatted_regex = String::with_capacity(search_regex.len()); for c in search_regex.chars() { - text.push(c); + formatted_regex.push(c); if c.width() == Some(2) { - text.push(' '); + formatted_regex.push(' '); } } // Add cursor to show whitespace. - text.push('_'); + formatted_regex.push('_'); - text + // Truncate beginning of the search regex if it exceeds the viewport width. + let num_cols = size_info.cols().0; + let label_len = search_label.len(); + let regex_len = formatted_regex.len(); + let truncate_len = min((regex_len + label_len).saturating_sub(num_cols), regex_len); + let truncated_regex = &formatted_regex[truncate_len..]; + + // Add search label to the beginning of the search regex. + let mut bar_text = format!("{}{}", search_label, truncated_regex); + + // Make sure the label alone doesn't exceed the viewport width. + bar_text.truncate(num_cols); + + bar_text } /// Draw current search regex. @@ -638,25 +656,13 @@ impl Display { config: &Config, size_info: &SizeInfo, message_bar_lines: usize, - search_regex: Option, + text: &str, ) { - let search_regex = match search_regex { - Some(search_regex) => search_regex, - None => return, - }; let glyph_cache = &mut self.glyph_cache; - - let label_len = SEARCH_LABEL.len(); let num_cols = size_info.cols().0; - // Truncate beginning of text when it exceeds viewport width. - let text_len = search_regex.len(); - let truncate_len = min((text_len + label_len).saturating_sub(num_cols), text_len); - let text = &search_regex[truncate_len..]; - // Assure text length is at least num_cols. - let padding_len = num_cols.saturating_sub(label_len); - let text = format!("{}{:<2$}", SEARCH_LABEL, text, padding_len); + let text = format!("{:<1$}", text, num_cols); let fg = config.colors.search_bar_foreground(); let bg = config.colors.search_bar_background(); @@ -711,7 +717,7 @@ fn dynamic_padding(padding: f32, dimension: f32, cell_dimension: f32) -> f32 { /// Calculate the cell dimensions based on font metrics. #[inline] -fn compute_cell_size(config: &Config, metrics: &font::Metrics) -> (f32, f32) { +fn compute_cell_size(config: &Config, metrics: &crossfont::Metrics) -> (f32, f32) { let offset_x = f64::from(config.ui_config.font.offset.x); let offset_y = f64::from(config.ui_config.font.offset.y); ( diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs index f06b818e..56e5b841 100644 --- a/alacritty/src/event.rs +++ b/alacritty/src/event.rs @@ -25,8 +25,8 @@ use log::info; use serde_json as json; #[cfg(target_os = "macos")] -use font::set_font_smoothing; -use font::{self, Size}; +use crossfont::set_font_smoothing; +use crossfont::{self, Size}; use alacritty_terminal::config::LOG_TARGET_CONFIG; use alacritty_terminal::event::{Event as TerminalEvent, EventListener, Notify, OnResize}; @@ -99,6 +99,16 @@ impl SearchState { fn new() -> Self { Self::default() } + + /// Search regex text if a search is active. + pub fn regex(&self) -> Option<&String> { + self.regex.as_ref() + } + + /// Direction of the search from the search origin. + pub fn direction(&self) -> Direction { + self.direction + } } impl Default for SearchState { @@ -402,6 +412,11 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon #[inline] fn push_search(&mut self, c: char) { if let Some(regex) = self.search_state.regex.as_mut() { + // Prevent previous search selections from sticking around when not in vi mode. + if !self.terminal.mode().contains(TermMode::VI) { + self.terminal.selection = None; + } + regex.push(c); self.update_search(); } @@ -586,6 +601,7 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> { fn absolute_origin(&self) -> Point { let mut relative_origin = self.search_state.origin; relative_origin.line = min(relative_origin.line, self.terminal.screen_lines() - 1); + relative_origin.col = min(relative_origin.col, self.terminal.cols() - 1); let mut origin = self.terminal.visible_to_buffer(relative_origin); origin.line = (origin.line as isize + self.search_state.display_offset_delta) as usize; origin @@ -831,7 +847,7 @@ impl Processor { &self.config, &self.mouse, self.modifiers, - self.search_state.regex.as_ref(), + &self.search_state, ); } }); diff --git a/alacritty/src/renderer/mod.rs b/alacritty/src/renderer/mod.rs index 09682e6e..58d43406 100644 --- a/alacritty/src/renderer/mod.rs +++ b/alacritty/src/renderer/mod.rs @@ -9,18 +9,14 @@ use std::ptr; use std::sync::mpsc; use std::time::Duration; -use fnv::FnvHasher; -use font::{ +use crossfont::{ BitmapBuffer, FontDesc, FontKey, GlyphKey, Rasterize, RasterizedGlyph, Rasterizer, Size, Slant, Style, Weight, }; +use fnv::FnvHasher; use log::{error, info}; use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; -use crate::cursor; -use crate::gl; -use crate::gl::types::*; -use crate::renderer::rects::RenderRect; use alacritty_terminal::config::Cursor; use alacritty_terminal::index::{Column, Line}; use alacritty_terminal::term::cell::{self, Flags}; @@ -28,11 +24,15 @@ use alacritty_terminal::term::color::Rgb; use alacritty_terminal::term::{CursorKey, RenderableCell, RenderableCellContent, SizeInfo}; use alacritty_terminal::thread; -pub mod rects; - use crate::config::font::{Font, FontDescription}; use crate::config::ui_config::{Delta, UIConfig}; use crate::config::window::{StartupMode, WindowConfig}; +use crate::cursor; +use crate::gl; +use crate::gl::types::*; +use crate::renderer::rects::RenderRect; + +pub mod rects; // Shader paths for live reload. static TEXT_SHADER_F_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../res/text.f.glsl"); @@ -166,13 +166,13 @@ pub struct GlyphCache { bold_italic_key: FontKey, /// Font size. - font_size: font::Size, + font_size: crossfont::Size, /// Glyph offset. glyph_offset: Delta, /// Font metrics. - metrics: font::Metrics, + metrics: crossfont::Metrics, } impl GlyphCache { @@ -180,7 +180,7 @@ impl GlyphCache { mut rasterizer: Rasterizer, font: &Font, loader: &mut L, - ) -> Result + ) -> Result where L: LoadGlyph, { @@ -222,7 +222,7 @@ impl GlyphCache { fn compute_font_keys( font: &Font, rasterizer: &mut Rasterizer, - ) -> Result<(FontKey, FontKey, FontKey, FontKey), font::Error> { + ) -> Result<(FontKey, FontKey, FontKey, FontKey), crossfont::Error> { let size = font.size; // Load regular font. @@ -261,7 +261,7 @@ impl GlyphCache { rasterizer: &mut Rasterizer, description: &FontDesc, size: Size, - ) -> Result { + ) -> Result { match rasterizer.load_font(description, size) { Ok(font) => Ok(font), Err(err) => { @@ -316,7 +316,7 @@ impl GlyphCache { font: &Font, dpr: f64, loader: &mut L, - ) -> Result<(), font::Error> { + ) -> Result<(), crossfont::Error> { // Update dpi scaling. self.rasterizer.update_dpr(dpr as f32); @@ -341,7 +341,7 @@ impl GlyphCache { Ok(()) } - pub fn font_metrics(&self) -> font::Metrics { + pub fn font_metrics(&self) -> crossfont::Metrics { self.metrics } @@ -354,8 +354,8 @@ impl GlyphCache { } /// Calculate font metrics without access to a glyph cache. - pub fn static_metrics(font: Font, dpr: f64) -> Result { - let mut rasterizer = font::Rasterizer::new(dpr as f32, font.use_thin_strokes())?; + pub fn static_metrics(font: Font, dpr: f64) -> Result { + let mut rasterizer = crossfont::Rasterizer::new(dpr as f32, font.use_thin_strokes())?; let regular_desc = GlyphCache::make_desc(&font.normal(), Slant::Normal, Weight::Normal); let regular = Self::load_regular_font(&mut rasterizer, ®ular_desc, font.size)?; rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size })?; diff --git a/alacritty/src/renderer/rects.rs b/alacritty/src/renderer/rects.rs index 15f181e2..5cd856ab 100644 --- a/alacritty/src/renderer/rects.rs +++ b/alacritty/src/renderer/rects.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use font::Metrics; +use crossfont::Metrics; use alacritty_terminal::index::{Column, Point}; use alacritty_terminal::term::cell::Flags; diff --git a/alacritty/src/url.rs b/alacritty/src/url.rs index 08a85f1b..f972099d 100644 --- a/alacritty/src/url.rs +++ b/alacritty/src/url.rs @@ -1,11 +1,10 @@ use std::cmp::min; use std::mem; +use crossfont::Metrics; use glutin::event::{ElementState, ModifiersState}; use urlocator::{UrlLocation, UrlLocator}; -use font::Metrics; - use alacritty_terminal::index::{Column, Point}; use alacritty_terminal::term::cell::Flags; use alacritty_terminal::term::color::Rgb; diff --git a/alacritty/src/window.rs b/alacritty/src/window.rs index 89f16b7a..81a61218 100644 --- a/alacritty/src/window.rs +++ b/alacritty/src/window.rs @@ -33,9 +33,7 @@ use glutin::{self, ContextBuilder, PossiblyCurrent, WindowedContext}; #[cfg(windows)] use winapi::shared::minwindef::WORD; -#[cfg(not(windows))] use alacritty_terminal::index::Point; -#[cfg(not(windows))] use alacritty_terminal::term::SizeInfo; use crate::config::window::{Decorations, StartupMode, WindowConfig}; @@ -57,7 +55,7 @@ pub enum Error { ContextCreation(glutin::CreationError), /// Error dealing with fonts. - Font(font::Error), + Font(crossfont::Error), /// Error manipulating the rendering context. Context(glutin::ContextError), @@ -98,8 +96,8 @@ impl From for Error { } } -impl From for Error { - fn from(val: font::Error) -> Self { +impl From for Error { + fn from(val: crossfont::Error) -> Self { Error::Font(val) } } @@ -410,6 +408,10 @@ impl Window { self.window().set_ime_position(PhysicalPosition::new(nspot_x, nspot_y)); } + /// No-op, since Windows does not support IME positioning. + #[cfg(windows)] + pub fn update_ime_position(&mut self, _point: Point, _size_info: &SizeInfo) {} + pub fn swap_buffers(&self) { self.windowed_context.swap_buffers().expect("swap buffers"); } diff --git a/alacritty_terminal/Cargo.toml b/alacritty_terminal/Cargo.toml index 48fc573b..80236f1d 100644 --- a/alacritty_terminal/Cargo.toml +++ b/alacritty_terminal/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "alacritty_terminal" -version = "0.5.0-rc1" +version = "0.10.0-rc2" authors = ["Christian Duerr ", "Joe Wilm "] license = "Apache-2.0" description = "Library for writing terminal emulators" diff --git a/extra/alacritty.man b/extra/alacritty.man index fffe0995..62345286 100644 --- a/extra/alacritty.man +++ b/extra/alacritty.man @@ -1,4 +1,4 @@ -.TH ALACRITTY "1" "August 2018" "alacritty 0.5.0-rc1" "User Commands" +.TH ALACRITTY "1" "August 2018" "alacritty 0.5.0-rc2" "User Commands" .SH NAME alacritty \- a cross-platform, gpu-accelerated terminal emulator .SH "SYNOPSIS" diff --git a/extra/linux/io.alacritty.Alacritty.appdata.xml b/extra/linux/io.alacritty.Alacritty.appdata.xml index 7d31a023..32e7c9b3 100644 --- a/extra/linux/io.alacritty.Alacritty.appdata.xml +++ b/extra/linux/io.alacritty.Alacritty.appdata.xml @@ -28,7 +28,7 @@ However, it does allow configuration of many aspects of the terminal.

https://github.com/alacritty/alacritty https://github.com/alacritty/alacritty/issues - + https://github.com/alacritty/alacritty/blob/master/CONTRIBUTING.md#contact Joe Wilm diff --git a/extra/linux/redhat/alacritty.spec b/extra/linux/redhat/alacritty.spec index a17f1f05..6ec6f650 100644 --- a/extra/linux/redhat/alacritty.spec +++ b/extra/linux/redhat/alacritty.spec @@ -1,5 +1,5 @@ Name: alacritty -Version: 0.5.0-rc1 +Version: 0.5.0-rc2 Release: 1%{?dist} Summary: A cross-platform, GPU enhanced terminal emulator License: ASL 2.0 diff --git a/extra/linux/snap/snapcraft.yaml b/extra/linux/snap/snapcraft.yaml index 31752e94..0172602f 100644 --- a/extra/linux/snap/snapcraft.yaml +++ b/extra/linux/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: alacritty -version: '0.5.0-rc1' +version: '0.5.0-rc2' summary: Modern, GPU accelerated terminal emulator description: | Alacritty is a terminal emulator with a strong focus on simplicity and diff --git a/extra/osx/Alacritty.app/Contents/Info.plist b/extra/osx/Alacritty.app/Contents/Info.plist index 5c1ad061..ce1d0ba4 100644 --- a/extra/osx/Alacritty.app/Contents/Info.plist +++ b/extra/osx/Alacritty.app/Contents/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.5.0-rc1 + 0.5.0-rc2 CFBundleSupportedPlatforms MacOSX diff --git a/extra/windows/wix/alacritty.wxs b/extra/windows/wix/alacritty.wxs index d851148c..87836fb5 100644 --- a/extra/windows/wix/alacritty.wxs +++ b/extra/windows/wix/alacritty.wxs @@ -2,7 +2,7 @@ - + diff --git a/font/Cargo.toml b/font/Cargo.toml deleted file mode 100644 index 89696a47..00000000 --- a/font/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -[package] -name = "font" -version = "0.1.0" -authors = ["Christian Duerr ", "Joe Wilm "] -description = "Font rendering using the best available solution per platform" -license = "Apache-2.0" -edition = "2018" - -[dependencies] -euclid = "0.20" -libc = "0.2" -foreign-types = "0.5" -log = "0.4" - -[target.'cfg(not(any(target_os = "macos", windows)))'.dependencies] -servo-fontconfig = "0.5.1" -freetype-rs = "0.26" - -[target.'cfg(target_os = "macos")'.dependencies] -cocoa = "0.20.1" -core-foundation = "0.7" -core-text = "15" -core-graphics = "0.19" -core-foundation-sys = "0.7" - -[target.'cfg(windows)'.dependencies] -dwrote = { version = "0.11" } -winapi = { version = "0.3", features = ["impl-default"] } - -[features] -force_system_fontconfig = ["servo-fontconfig/force_system_lib"] diff --git a/font/src/darwin/byte_order.rs b/font/src/darwin/byte_order.rs deleted file mode 100644 index 3cd767f0..00000000 --- a/font/src/darwin/byte_order.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! Constants for bitmap byte order. - -#![allow(non_upper_case_globals)] -pub const kCGBitmapByteOrder32Little: u32 = 2 << 12; -pub const kCGBitmapByteOrder32Big: u32 = 4 << 12; - -#[cfg(target_endian = "little")] -pub const kCGBitmapByteOrder32Host: u32 = kCGBitmapByteOrder32Little; - -#[cfg(target_endian = "big")] -pub const kCGBitmapByteOrder32Host: u32 = kCGBitmapByteOrder32Big; - -#[cfg(target_endian = "little")] -pub fn extract_rgba(bytes: &[u8]) -> Vec { - let pixels = bytes.len() / 4; - let mut rgb = Vec::with_capacity(pixels * 4); - - for i in 0..pixels { - let offset = i * 4; - rgb.push(bytes[offset + 2]); - rgb.push(bytes[offset + 1]); - rgb.push(bytes[offset]); - rgb.push(bytes[offset + 3]); - } - - rgb -} - -#[cfg(target_endian = "big")] -pub fn extract_rgba(bytes: Vec) -> Vec { - bytes -} - -#[cfg(target_endian = "little")] -pub fn extract_rgb(bytes: &[u8]) -> Vec { - let pixels = bytes.len() / 4; - let mut rgb = Vec::with_capacity(pixels * 3); - - for i in 0..pixels { - let offset = i * 4; - rgb.push(bytes[offset + 2]); - rgb.push(bytes[offset + 1]); - rgb.push(bytes[offset]); - } - - rgb -} - -#[cfg(target_endian = "big")] -pub fn extract_rgb(bytes: Vec) -> Vec { - bytes - .into_iter() - .enumerate() - .filter(|&(index, _)| ((index) % 4) != 0) - .map(|(_, val)| val) - .collect::>() -} diff --git a/font/src/darwin/mod.rs b/font/src/darwin/mod.rs deleted file mode 100644 index 0194925f..00000000 --- a/font/src/darwin/mod.rs +++ /dev/null @@ -1,634 +0,0 @@ -//! Font rendering based on CoreText. - -#![allow(improper_ctypes)] -use std::collections::HashMap; -use std::path::PathBuf; -use std::ptr; - -use core_foundation::array::{CFArray, CFIndex}; -use core_foundation::string::CFString; -use core_graphics::base::kCGImageAlphaPremultipliedFirst; -use core_graphics::color_space::CGColorSpace; -use core_graphics::context::CGContext; -use core_graphics::font::{CGFont, CGGlyph}; -use core_graphics::geometry::{CGPoint, CGRect, CGSize}; -use core_text::font::{ - cascade_list_for_languages as ct_cascade_list_for_languages, - new_from_descriptor as ct_new_from_descriptor, CTFont, -}; -use core_text::font_collection::create_for_family; -use core_text::font_collection::get_family_names as ct_get_family_names; -use core_text::font_descriptor::kCTFontColorGlyphsTrait; -use core_text::font_descriptor::kCTFontDefaultOrientation; -use core_text::font_descriptor::kCTFontHorizontalOrientation; -use core_text::font_descriptor::kCTFontVerticalOrientation; -use core_text::font_descriptor::SymbolicTraitAccessors; -use core_text::font_descriptor::{CTFontDescriptor, CTFontOrientation}; - -use cocoa::base::{id, nil, NO}; -use cocoa::foundation::{NSOperatingSystemVersion, NSProcessInfo, NSString, NSUserDefaults}; - -use euclid::{Point2D, Rect, Size2D}; - -use log::{trace, warn}; - -pub mod byte_order; -use byte_order::kCGBitmapByteOrder32Host; - -use super::{ - BitmapBuffer, FontDesc, FontKey, GlyphKey, Metrics, RasterizedGlyph, Size, Slant, Style, Weight, -}; - -/// Font descriptor. -/// -/// The descriptor provides data about a font and supports creating a font. -#[derive(Debug)] -pub struct Descriptor { - family_name: String, - font_name: String, - style_name: String, - display_name: String, - font_path: PathBuf, - - ct_descriptor: CTFontDescriptor, -} - -impl Descriptor { - fn new(desc: CTFontDescriptor) -> Descriptor { - Descriptor { - family_name: desc.family_name(), - font_name: desc.font_name(), - style_name: desc.style_name(), - display_name: desc.display_name(), - font_path: desc.font_path().unwrap_or_else(PathBuf::new), - ct_descriptor: desc, - } - } -} - -/// Rasterizer, the main type exported by this package. -/// -/// Given a fontdesc, can rasterize fonts. -pub struct Rasterizer { - fonts: HashMap, - keys: HashMap<(FontDesc, Size), FontKey>, - device_pixel_ratio: f32, - use_thin_strokes: bool, -} - -/// Errors occurring when using the core text rasterizer. -#[derive(Debug)] -pub enum Error { - /// Tried to rasterize a glyph but it was not available. - MissingGlyph(char), - - /// Couldn't find font matching description. - MissingFont(FontDesc), - - /// Requested an operation with a FontKey that isn't known to the rasterizer. - FontNotLoaded, -} - -impl ::std::error::Error for Error { - fn description(&self) -> &str { - match *self { - Error::MissingGlyph(ref _c) => "Unable to find the requested glyph", - Error::MissingFont(ref _desc) => "Unable to find the requested font", - Error::FontNotLoaded => "Tried to operate on font that hasn't been loaded", - } - } -} - -impl ::std::fmt::Display for Error { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - match *self { - Error::MissingGlyph(ref c) => write!(f, "Glyph not found for char {:?}", c), - Error::MissingFont(ref desc) => write!(f, "Unable to find the font {}", desc), - Error::FontNotLoaded => f.write_str("Tried to use a font that hasn't been loaded"), - } - } -} - -impl crate::Rasterize for Rasterizer { - type Err = Error; - - fn new(device_pixel_ratio: f32, use_thin_strokes: bool) -> Result { - Ok(Rasterizer { - fonts: HashMap::new(), - keys: HashMap::new(), - device_pixel_ratio, - use_thin_strokes, - }) - } - - /// Get metrics for font specified by FontKey. - fn metrics(&self, key: FontKey, _size: Size) -> Result { - let font = self.fonts.get(&key).ok_or(Error::FontNotLoaded)?; - - Ok(font.metrics()) - } - - fn load_font(&mut self, desc: &FontDesc, size: Size) -> Result { - let scaled_size = Size::new(size.as_f32_pts() * self.device_pixel_ratio); - self.keys.get(&(desc.to_owned(), scaled_size)).map(|k| Ok(*k)).unwrap_or_else(|| { - let font = self.get_font(desc, size)?; - let key = FontKey::next(); - - self.fonts.insert(key, font); - self.keys.insert((desc.clone(), scaled_size), key); - - Ok(key) - }) - } - - /// Get rasterized glyph for given glyph key. - fn get_glyph(&mut self, glyph: GlyphKey) -> Result { - // Get loaded font. - let font = self.fonts.get(&glyph.font_key).ok_or(Error::FontNotLoaded)?; - - // First try the font itself as a direct hit. - self.maybe_get_glyph(glyph, font).unwrap_or_else(|| { - // Then try fallbacks. - for fallback in &font.fallbacks { - if let Some(result) = self.maybe_get_glyph(glyph, &fallback) { - // Found a fallback. - return result; - } - } - // No fallback, give up. - Err(Error::MissingGlyph(glyph.c)) - }) - } - - fn update_dpr(&mut self, device_pixel_ratio: f32) { - self.device_pixel_ratio = device_pixel_ratio; - } -} - -impl Rasterizer { - fn get_specific_face( - &mut self, - desc: &FontDesc, - style: &str, - size: Size, - ) -> Result { - let descriptors = descriptors_for_family(&desc.name[..]); - for descriptor in descriptors { - if descriptor.style_name == style { - // Found the font we want. - let scaled_size = f64::from(size.as_f32_pts()) * f64::from(self.device_pixel_ratio); - let font = descriptor.to_font(scaled_size, true); - return Ok(font); - } - } - - Err(Error::MissingFont(desc.to_owned())) - } - - fn get_matching_face( - &mut self, - desc: &FontDesc, - slant: Slant, - weight: Weight, - size: Size, - ) -> Result { - let bold = match weight { - Weight::Bold => true, - _ => false, - }; - let italic = match slant { - Slant::Normal => false, - _ => true, - }; - let scaled_size = f64::from(size.as_f32_pts()) * f64::from(self.device_pixel_ratio); - - let descriptors = descriptors_for_family(&desc.name[..]); - for descriptor in descriptors { - let font = descriptor.to_font(scaled_size, true); - if font.is_bold() == bold && font.is_italic() == italic { - // Found the font we want. - return Ok(font); - } - } - - Err(Error::MissingFont(desc.to_owned())) - } - - fn get_font(&mut self, desc: &FontDesc, size: Size) -> Result { - match desc.style { - Style::Specific(ref style) => self.get_specific_face(desc, style, size), - Style::Description { slant, weight } => { - self.get_matching_face(desc, slant, weight, size) - }, - } - } - - /// Helper to try and get a glyph for a given font. Used for font fallback. - fn maybe_get_glyph( - &self, - glyph: GlyphKey, - font: &Font, - ) -> Option> { - let scaled_size = self.device_pixel_ratio * glyph.size.as_f32_pts(); - font.get_glyph(glyph.c, f64::from(scaled_size), self.use_thin_strokes) - .map(|r| Some(Ok(r))) - .unwrap_or_else(|e| match e { - Error::MissingGlyph(_) => None, - _ => Some(Err(e)), - }) - } -} - -/// Specifies the intended rendering orientation of the font for obtaining glyph metrics. -#[derive(Debug)] -pub enum FontOrientation { - Default = kCTFontDefaultOrientation as isize, - Horizontal = kCTFontHorizontalOrientation as isize, - Vertical = kCTFontVerticalOrientation as isize, -} - -impl Default for FontOrientation { - fn default() -> FontOrientation { - FontOrientation::Default - } -} - -/// A font. -#[derive(Clone)] -pub struct Font { - ct_font: CTFont, - cg_font: CGFont, - fallbacks: Vec, -} - -unsafe impl Send for Font {} - -/// Set subpixel anti-aliasing on macOS. -/// -/// Sub-pixel anti-aliasing has been disabled since macOS Mojave by default. This function allows -/// overriding the global `CGFontRenderingFontSmoothingDisabled` setting on a per-application basis -/// to re-enable it. -/// -/// This is a no-op on systems running High Sierra or earlier (< 10.14.0). -pub fn set_font_smoothing(enable: bool) { - let min_macos_version = NSOperatingSystemVersion::new(10, 14, 0); - unsafe { - // Check that we're running at least Mojave (10.14.0+). - if !NSProcessInfo::processInfo(nil).isOperatingSystemAtLeastVersion(min_macos_version) { - return; - } - - let key = NSString::alloc(nil).init_str("CGFontRenderingFontSmoothingDisabled"); - if enable { - id::standardUserDefaults().setBool_forKey_(NO, key); - } else { - id::standardUserDefaults().removeObject_forKey_(key); - } - } -} - -/// List all family names. -pub fn get_family_names() -> Vec { - // CFArray of CFStringRef. - let names = ct_get_family_names(); - let mut owned_names = Vec::new(); - - for name in names.iter() { - owned_names.push(name.to_string()); - } - - owned_names -} - -/// Return fallback descriptors for font/language list. -fn cascade_list_for_languages(ct_font: &CTFont, languages: &[String]) -> Vec { - // Convert language type &Vec -> CFArray. - let langarr: CFArray = { - let tmp: Vec = - languages.iter().map(|language| CFString::new(&language)).collect(); - CFArray::from_CFTypes(&tmp) - }; - - // CFArray of CTFontDescriptorRef (again). - let list = ct_cascade_list_for_languages(ct_font, &langarr); - - // Convert CFArray to Vec. - list.into_iter().map(|fontdesc| Descriptor::new(fontdesc.clone())).collect() -} - -/// Get descriptors for family name. -pub fn descriptors_for_family(family: &str) -> Vec { - let mut out = Vec::new(); - - trace!("Family: {}", family); - let ct_collection = create_for_family(family).unwrap_or_else(|| { - // Fallback to Menlo if we can't find the config specified font family. - warn!("Unable to load specified font {}, falling back to Menlo", &family); - create_for_family("Menlo").expect("Menlo exists") - }); - - // CFArray of CTFontDescriptorRef (i think). - let descriptors = ct_collection.get_descriptors(); - if let Some(descriptors) = descriptors { - for descriptor in descriptors.iter() { - out.push(Descriptor::new(descriptor.clone())); - } - } - - out -} - -impl Descriptor { - /// Create a Font from this descriptor. - pub fn to_font(&self, size: f64, load_fallbacks: bool) -> Font { - let ct_font = ct_new_from_descriptor(&self.ct_descriptor, size); - let cg_font = ct_font.copy_to_CGFont(); - - let fallbacks = if load_fallbacks { - descriptors_for_family("Menlo") - .into_iter() - .find(|d| d.font_name == "Menlo-Regular") - .map(|descriptor| { - let menlo = ct_new_from_descriptor(&descriptor.ct_descriptor, size); - - // TODO fixme, hardcoded en for english. - let mut fallbacks = cascade_list_for_languages(&menlo, &["en".to_owned()]) - .into_iter() - .filter(|desc| !desc.font_path.as_os_str().is_empty()) - .map(|desc| desc.to_font(size, false)) - .collect::>(); - - // TODO, we can't use apple's proposed - // .Apple Symbol Fallback (filtered out below), - // but not having these makes us not able to render - // many chars. We add the symbols back in. - // Investigate if we can actually use the .-prefixed - // fallbacks somehow. - if let Some(descriptor) = - descriptors_for_family("Apple Symbols").into_iter().next() - { - fallbacks.push(descriptor.to_font(size, false)) - }; - - // Include Menlo in the fallback list as well. - fallbacks.insert(0, Font { - cg_font: menlo.copy_to_CGFont(), - ct_font: menlo, - fallbacks: Vec::new(), - }); - - fallbacks - }) - .unwrap_or_else(Vec::new) - } else { - Vec::new() - }; - - Font { ct_font, cg_font, fallbacks } - } -} - -impl Font { - /// The the bounding rect of a glyph. - pub fn bounding_rect_for_glyph( - &self, - orientation: FontOrientation, - index: u32, - ) -> Rect { - let cg_rect = self - .ct_font - .get_bounding_rects_for_glyphs(orientation as CTFontOrientation, &[index as CGGlyph]); - - Rect::new( - Point2D::new(cg_rect.origin.x, cg_rect.origin.y), - Size2D::new(cg_rect.size.width, cg_rect.size.height), - ) - } - - pub fn metrics(&self) -> Metrics { - let average_advance = self.glyph_advance('0'); - - let ascent = self.ct_font.ascent() as f64; - let descent = self.ct_font.descent() as f64; - let leading = self.ct_font.leading() as f64; - let line_height = (ascent + descent + leading + 0.5).floor(); - - // Strikeout and underline metrics. - // CoreText doesn't provide strikeout so we provide our own. - let underline_position = (self.ct_font.underline_position() - descent) as f32; - let underline_thickness = self.ct_font.underline_thickness() as f32; - let strikeout_position = (line_height / 2. - descent) as f32; - let strikeout_thickness = underline_thickness; - - Metrics { - average_advance, - line_height, - descent: -(descent as f32), - underline_position, - underline_thickness, - strikeout_position, - strikeout_thickness, - } - } - - pub fn is_bold(&self) -> bool { - self.ct_font.symbolic_traits().is_bold() - } - - pub fn is_italic(&self) -> bool { - self.ct_font.symbolic_traits().is_italic() - } - - pub fn is_colored(&self) -> bool { - (self.ct_font.symbolic_traits() & kCTFontColorGlyphsTrait) != 0 - } - - fn glyph_advance(&self, character: char) -> f64 { - let index = self.glyph_index(character).unwrap(); - - let indices = [index as CGGlyph]; - - unsafe { - self.ct_font.get_advances_for_glyphs( - FontOrientation::Default as _, - &indices[0], - ptr::null_mut(), - 1, - ) - } - } - - pub fn get_glyph( - &self, - character: char, - _size: f64, - use_thin_strokes: bool, - ) -> Result { - let glyph_index = - self.glyph_index(character).ok_or_else(|| Error::MissingGlyph(character))?; - - let bounds = self.bounding_rect_for_glyph(Default::default(), glyph_index); - - let rasterized_left = bounds.origin.x.floor() as i32; - let rasterized_width = - (bounds.origin.x - f64::from(rasterized_left) + bounds.size.width).ceil() as u32; - let rasterized_descent = (-bounds.origin.y).ceil() as i32; - let rasterized_ascent = (bounds.size.height + bounds.origin.y).ceil() as i32; - let rasterized_height = (rasterized_descent + rasterized_ascent) as u32; - - if rasterized_width == 0 || rasterized_height == 0 { - return Ok(RasterizedGlyph { - c: ' ', - width: 0, - height: 0, - top: 0, - left: 0, - buf: BitmapBuffer::RGB(Vec::new()), - }); - } - - let mut cg_context = CGContext::create_bitmap_context( - None, - rasterized_width as usize, - rasterized_height as usize, - 8, // bits per component - rasterized_width as usize * 4, - &CGColorSpace::create_device_rgb(), - kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host, - ); - - let is_colored = self.is_colored(); - - // Set background color for graphics context. - let bg_a = if is_colored { 0.0 } else { 1.0 }; - cg_context.set_rgb_fill_color(0.0, 0.0, 0.0, bg_a); - - let context_rect = CGRect::new( - &CGPoint::new(0.0, 0.0), - &CGSize::new(f64::from(rasterized_width), f64::from(rasterized_height)), - ); - - cg_context.fill_rect(context_rect); - - if use_thin_strokes { - cg_context.set_font_smoothing_style(16); - } - - cg_context.set_allows_font_smoothing(true); - cg_context.set_should_smooth_fonts(true); - cg_context.set_allows_font_subpixel_quantization(true); - cg_context.set_should_subpixel_quantize_fonts(true); - cg_context.set_allows_font_subpixel_positioning(true); - cg_context.set_should_subpixel_position_fonts(true); - cg_context.set_allows_antialiasing(true); - cg_context.set_should_antialias(true); - - // Set fill color to white for drawing the glyph. - cg_context.set_rgb_fill_color(1.0, 1.0, 1.0, 1.0); - let rasterization_origin = - CGPoint { x: f64::from(-rasterized_left), y: f64::from(rasterized_descent) }; - - self.ct_font.draw_glyphs( - &[glyph_index as CGGlyph], - &[rasterization_origin], - cg_context.clone(), - ); - - let rasterized_pixels = cg_context.data().to_vec(); - - let buf = if is_colored { - BitmapBuffer::RGBA(byte_order::extract_rgba(&rasterized_pixels)) - } else { - BitmapBuffer::RGB(byte_order::extract_rgb(&rasterized_pixels)) - }; - - Ok(RasterizedGlyph { - c: character, - left: rasterized_left, - top: (bounds.size.height + bounds.origin.y).ceil() as i32, - width: rasterized_width as i32, - height: rasterized_height as i32, - buf, - }) - } - - fn glyph_index(&self, character: char) -> Option { - // Encode this char as utf-16. - let mut buf = [0; 2]; - let encoded: &[u16] = character.encode_utf16(&mut buf); - // And use the utf-16 buffer to get the index. - self.glyph_index_utf16(encoded) - } - - fn glyph_index_utf16(&self, encoded: &[u16]) -> Option { - // Output buffer for the glyph. for non-BMP glyphs, like - // emojis, this will be filled with two chars the second - // always being a 0. - let mut glyphs: [CGGlyph; 2] = [0; 2]; - - let res = unsafe { - self.ct_font.get_glyphs_for_characters( - encoded.as_ptr(), - glyphs.as_mut_ptr(), - encoded.len() as CFIndex, - ) - }; - - if res { - Some(u32::from(glyphs[0])) - } else { - None - } - } -} - -#[cfg(test)] -mod tests { - use super::BitmapBuffer; - - #[test] - fn get_family_names() { - let names = super::get_family_names(); - assert!(names.contains(&String::from("Menlo"))); - assert!(names.contains(&String::from("Monaco"))); - } - - #[test] - fn get_descriptors_and_build_font() { - let list = super::descriptors_for_family("Menlo"); - assert!(!list.is_empty()); - println!("{:?}", list); - - // Check to_font. - let fonts = list.iter().map(|desc| desc.to_font(72., false)).collect::>(); - - for font in fonts { - // Get a glyph. - for c in &['a', 'b', 'c', 'd'] { - let glyph = font.get_glyph(*c, 72., false).unwrap(); - - let buf = match &glyph.buf { - BitmapBuffer::RGB(buf) => buf, - BitmapBuffer::RGBA(buf) => buf, - }; - - // Debug the glyph.. sigh. - for row in 0..glyph.height { - for col in 0..glyph.width { - let index = ((glyph.width * 3 * row) + (col * 3)) as usize; - let value = buf[index]; - let c = match value { - 0..=50 => ' ', - 51..=100 => '.', - 101..=150 => '~', - 151..=200 => '*', - 201..=255 => '#', - }; - print!("{}", c); - } - println!(); - } - } - } - } -} diff --git a/font/src/directwrite/mod.rs b/font/src/directwrite/mod.rs deleted file mode 100644 index d139b35e..00000000 --- a/font/src/directwrite/mod.rs +++ /dev/null @@ -1,335 +0,0 @@ -//! Rasterization powered by DirectWrite. - -use std::borrow::Cow; -use std::collections::HashMap; -use std::ffi::OsString; -use std::fmt::{self, Display, Formatter}; -use std::os::windows::ffi::OsStringExt; - -use dwrote::{ - FontCollection, FontFace, FontFallback, FontStretch, FontStyle, FontWeight, GlyphOffset, - GlyphRunAnalysis, TextAnalysisSource, TextAnalysisSourceMethods, DWRITE_GLYPH_RUN, -}; - -use winapi::shared::ntdef::{HRESULT, LOCALE_NAME_MAX_LENGTH}; -use winapi::um::dwrite; -use winapi::um::winnls::GetUserDefaultLocaleName; - -use super::{ - BitmapBuffer, FontDesc, FontKey, GlyphKey, Metrics, RasterizedGlyph, Size, Slant, Style, Weight, -}; - -/// Cached DirectWrite font. -struct Font { - face: FontFace, - family_name: String, - weight: FontWeight, - style: FontStyle, - stretch: FontStretch, -} - -pub struct DirectWriteRasterizer { - fonts: HashMap, - keys: HashMap, - device_pixel_ratio: f32, - available_fonts: FontCollection, - fallback_sequence: Option, -} - -impl DirectWriteRasterizer { - fn rasterize_glyph( - &self, - face: &FontFace, - size: Size, - c: char, - ) -> Result { - let glyph_index = self.get_glyph_index(face, c)?; - - let em_size = em_size(size); - - let glyph_run = DWRITE_GLYPH_RUN { - fontFace: unsafe { face.as_ptr() }, - fontEmSize: em_size, - glyphCount: 1, - glyphIndices: &glyph_index, - glyphAdvances: &0.0, - glyphOffsets: &GlyphOffset::default(), - isSideways: 0, - bidiLevel: 0, - }; - - let rendering_mode = face.get_recommended_rendering_mode_default_params( - em_size, - self.device_pixel_ratio, - dwrote::DWRITE_MEASURING_MODE_NATURAL, - ); - - let glyph_analysis = GlyphRunAnalysis::create( - &glyph_run, - self.device_pixel_ratio, - None, - rendering_mode, - dwrote::DWRITE_MEASURING_MODE_NATURAL, - 0.0, - 0.0, - ) - .map_err(Error::DirectWriteError)?; - - let bounds = glyph_analysis - .get_alpha_texture_bounds(dwrote::DWRITE_TEXTURE_CLEARTYPE_3x1) - .map_err(Error::DirectWriteError)?; - - let buf = glyph_analysis - .create_alpha_texture(dwrote::DWRITE_TEXTURE_CLEARTYPE_3x1, bounds) - .map_err(Error::DirectWriteError)?; - - Ok(RasterizedGlyph { - c, - width: (bounds.right - bounds.left) as i32, - height: (bounds.bottom - bounds.top) as i32, - top: -bounds.top, - left: bounds.left, - buf: BitmapBuffer::RGB(buf), - }) - } - - fn get_loaded_font(&self, font_key: FontKey) -> Result<&Font, Error> { - self.fonts.get(&font_key).ok_or(Error::FontNotLoaded) - } - - fn get_glyph_index(&self, face: &FontFace, c: char) -> Result { - let idx = *face - .get_glyph_indices(&[c as u32]) - .first() - // DirectWrite returns 0 if the glyph does not exist in the font. - .filter(|glyph_index| **glyph_index != 0) - .ok_or_else(|| Error::MissingGlyph(c))?; - - Ok(idx) - } - - fn get_fallback_font(&self, loaded_font: &Font, c: char) -> Option { - let fallback = self.fallback_sequence.as_ref()?; - - let mut buf = [0u16; 2]; - c.encode_utf16(&mut buf); - - let length = c.len_utf16() as u32; - let utf16_codepoints = &buf[..length as usize]; - - let locale = get_current_locale(); - - let text_analysis_source_data = TextAnalysisSourceData { locale: &locale, length }; - let text_analysis_source = TextAnalysisSource::from_text( - Box::new(text_analysis_source_data), - Cow::Borrowed(utf16_codepoints), - ); - - let fallback_result = fallback.map_characters( - &text_analysis_source, - 0, - length, - &self.available_fonts, - Some(&loaded_font.family_name), - loaded_font.weight, - loaded_font.style, - loaded_font.stretch, - ); - - fallback_result.mapped_font - } -} - -impl crate::Rasterize for DirectWriteRasterizer { - type Err = Error; - - fn new(device_pixel_ratio: f32, _: bool) -> Result { - Ok(DirectWriteRasterizer { - fonts: HashMap::new(), - keys: HashMap::new(), - device_pixel_ratio, - available_fonts: FontCollection::system(), - fallback_sequence: FontFallback::get_system_fallback(), - }) - } - - fn metrics(&self, key: FontKey, size: Size) -> Result { - let face = &self.get_loaded_font(key)?.face; - let vmetrics = face.metrics().metrics0(); - - let scale = em_size(size) * self.device_pixel_ratio / f32::from(vmetrics.designUnitsPerEm); - - let underline_position = f32::from(vmetrics.underlinePosition) * scale; - let underline_thickness = f32::from(vmetrics.underlineThickness) * scale; - - let strikeout_position = f32::from(vmetrics.strikethroughPosition) * scale; - let strikeout_thickness = f32::from(vmetrics.strikethroughThickness) * scale; - - let ascent = f32::from(vmetrics.ascent) * scale; - let descent = -f32::from(vmetrics.descent) * scale; - let line_gap = f32::from(vmetrics.lineGap) * scale; - - let line_height = f64::from(ascent - descent + line_gap); - - // Since all monospace characters have the same width, we use `!` for horizontal metrics. - let c = '!'; - let glyph_index = self.get_glyph_index(face, c)?; - - let glyph_metrics = face.get_design_glyph_metrics(&[glyph_index], false); - let hmetrics = glyph_metrics.first().ok_or_else(|| Error::MissingGlyph(c))?; - - let average_advance = f64::from(hmetrics.advanceWidth) * f64::from(scale); - - Ok(Metrics { - descent, - average_advance, - line_height, - underline_position, - underline_thickness, - strikeout_position, - strikeout_thickness, - }) - } - - fn load_font(&mut self, desc: &FontDesc, _size: Size) -> Result { - // Fast path if face is already loaded. - if let Some(key) = self.keys.get(desc) { - return Ok(*key); - } - - let family = self - .available_fonts - .get_font_family_by_name(&desc.name) - .ok_or_else(|| Error::MissingFont(desc.clone()))?; - - let font = match desc.style { - Style::Description { weight, slant } => { - // This searches for the "best" font - should mean we don't have to worry about - // fallbacks if our exact desired weight/style isn't available. - Ok(family.get_first_matching_font(weight.into(), FontStretch::Normal, slant.into())) - }, - Style::Specific(ref style) => { - let mut idx = 0; - let count = family.get_font_count(); - - loop { - if idx == count { - break Err(Error::MissingFont(desc.clone())); - } - - let font = family.get_font(idx); - - if font.face_name() == *style { - break Ok(font); - } - - idx += 1; - } - }, - }?; - - let key = FontKey::next(); - self.keys.insert(desc.clone(), key); - self.fonts.insert(key, font.into()); - - Ok(key) - } - - fn get_glyph(&mut self, glyph: GlyphKey) -> Result { - let loaded_font = self.get_loaded_font(glyph.font_key)?; - - match self.rasterize_glyph(&loaded_font.face, glyph.size, glyph.c) { - Err(err @ Error::MissingGlyph(_)) => { - let fallback_font = self.get_fallback_font(&loaded_font, glyph.c).ok_or(err)?; - self.rasterize_glyph(&fallback_font.create_font_face(), glyph.size, glyph.c) - }, - result => result, - } - } - - fn update_dpr(&mut self, device_pixel_ratio: f32) { - self.device_pixel_ratio = device_pixel_ratio; - } -} - -#[derive(Debug)] -pub enum Error { - MissingFont(FontDesc), - MissingGlyph(char), - FontNotLoaded, - DirectWriteError(HRESULT), -} - -impl std::error::Error for Error {} - -impl Display for Error { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Error::MissingGlyph(c) => write!(f, "Glyph not found for char {:?}", c), - Error::MissingFont(desc) => write!(f, "Unable to find the font {}", desc), - Error::FontNotLoaded => f.write_str("Tried to use a font that hasn't been loaded"), - Error::DirectWriteError(hresult) => { - write!(f, "A DirectWrite rendering error occurred: {:#X}", hresult) - }, - } - } -} - -fn em_size(size: Size) -> f32 { - size.as_f32_pts() * (96.0 / 72.0) -} - -impl From for Font { - fn from(font: dwrote::Font) -> Font { - Font { - face: font.create_font_face(), - family_name: font.family_name(), - weight: font.weight(), - style: font.style(), - stretch: font.stretch(), - } - } -} - -impl From for FontWeight { - fn from(weight: Weight) -> FontWeight { - match weight { - Weight::Bold => FontWeight::Bold, - Weight::Normal => FontWeight::Regular, - } - } -} - -impl From for FontStyle { - fn from(slant: Slant) -> FontStyle { - match slant { - Slant::Oblique => FontStyle::Oblique, - Slant::Italic => FontStyle::Italic, - Slant::Normal => FontStyle::Normal, - } - } -} - -fn get_current_locale() -> String { - let mut buf = vec![0u16; LOCALE_NAME_MAX_LENGTH]; - let len = unsafe { GetUserDefaultLocaleName(buf.as_mut_ptr(), buf.len() as i32) as usize }; - - // `len` includes null byte, which we don't need in Rust. - OsString::from_wide(&buf[..len - 1]).into_string().expect("Locale not valid unicode") -} - -/// Font fallback information for dwrote's TextAnalysisSource. -struct TextAnalysisSourceData<'a> { - locale: &'a str, - length: u32, -} - -impl TextAnalysisSourceMethods for TextAnalysisSourceData<'_> { - fn get_locale_name(&self, _text_position: u32) -> (Cow, u32) { - (Cow::Borrowed(self.locale), self.length) - } - - fn get_paragraph_reading_direction(&self) -> dwrite::DWRITE_READING_DIRECTION { - dwrite::DWRITE_READING_DIRECTION_LEFT_TO_RIGHT - } -} diff --git a/font/src/ft/fc/char_set.rs b/font/src/ft/fc/char_set.rs deleted file mode 100644 index 9579b7b9..00000000 --- a/font/src/ft/fc/char_set.rs +++ /dev/null @@ -1,70 +0,0 @@ -use std::ptr::NonNull; - -use foreign_types::{foreign_type, ForeignType, ForeignTypeRef}; - -use super::ffi::FcCharSetCreate; -use super::ffi::{ - FcBool, FcCharSet, FcCharSetAddChar, FcCharSetCopy, FcCharSetCount, FcCharSetDestroy, - FcCharSetHasChar, FcCharSetMerge, FcCharSetSubtract, FcCharSetUnion, -}; - -foreign_type! { - pub unsafe type CharSet { - type CType = FcCharSet; - fn drop = FcCharSetDestroy; - fn clone = FcCharSetCopy; - } -} - -impl CharSet { - pub fn new() -> Self { - Self::default() - } -} - -impl Default for CharSet { - fn default() -> Self { - CharSet(unsafe { NonNull::new(FcCharSetCreate()).unwrap() }) - } -} - -impl CharSetRef { - pub fn add(&mut self, glyph: char) -> bool { - unsafe { FcCharSetAddChar(self.as_ptr(), glyph as _) == 1 } - } - - pub fn has_char(&self, glyph: char) -> bool { - unsafe { FcCharSetHasChar(self.as_ptr(), glyph as _) == 1 } - } - - pub fn count(&self) -> u32 { - unsafe { FcCharSetCount(self.as_ptr()) as u32 } - } - - pub fn union(&self, other: &CharSetRef) -> CharSet { - unsafe { - let ptr = FcCharSetUnion(self.as_ptr() as _, other.as_ptr() as _); - CharSet::from_ptr(ptr) - } - } - - pub fn subtract(&self, other: &CharSetRef) -> CharSet { - unsafe { - let ptr = FcCharSetSubtract(self.as_ptr() as _, other.as_ptr() as _); - CharSet::from_ptr(ptr) - } - } - - pub fn merge(&self, other: &CharSetRef) -> Result { - unsafe { - // Value is just an indicator whether something was added or not. - let mut value: FcBool = 0; - let res = FcCharSetMerge(self.as_ptr() as _, other.as_ptr() as _, &mut value); - if res == 0 { - Err(()) - } else { - Ok(value != 0) - } - } - } -} diff --git a/font/src/ft/fc/config.rs b/font/src/ft/fc/config.rs deleted file mode 100644 index ac87a284..00000000 --- a/font/src/ft/fc/config.rs +++ /dev/null @@ -1,29 +0,0 @@ -use foreign_types::{foreign_type, ForeignTypeRef}; - -use super::ffi::{FcConfig, FcConfigDestroy, FcConfigGetCurrent, FcConfigGetFonts}; -use super::{FontSetRef, SetName}; - -foreign_type! { - pub unsafe type Config { - type CType = FcConfig; - fn drop = FcConfigDestroy; - } -} - -impl Config { - /// Get the current configuration. - pub fn get_current() -> &'static ConfigRef { - unsafe { ConfigRef::from_ptr(FcConfigGetCurrent()) } - } -} - -impl ConfigRef { - /// Returns one of the two sets of fonts from the configuration as - /// specified by `set`. - pub fn get_fonts(&self, set: SetName) -> &FontSetRef { - unsafe { - let ptr = FcConfigGetFonts(self.as_ptr(), set as u32); - FontSetRef::from_ptr(ptr) - } - } -} diff --git a/font/src/ft/fc/font_set.rs b/font/src/ft/fc/font_set.rs deleted file mode 100644 index 2f003312..00000000 --- a/font/src/ft/fc/font_set.rs +++ /dev/null @@ -1,87 +0,0 @@ -use std::ops::Deref; -use std::ptr::NonNull; - -use foreign_types::{foreign_type, ForeignType, ForeignTypeRef}; -use log::trace; - -use super::{ConfigRef, ObjectSetRef, PatternRef}; - -use super::ffi::{FcFontSet, FcFontSetDestroy, FcFontSetList}; - -foreign_type! { - pub unsafe type FontSet { - type CType = FcFontSet; - fn drop = FcFontSetDestroy; - } -} - -impl FontSet { - pub fn list( - config: &ConfigRef, - source: &mut FontSetRef, - pattern: &PatternRef, - objects: &ObjectSetRef, - ) -> FontSet { - let raw = unsafe { - FcFontSetList( - config.as_ptr(), - &mut source.as_ptr(), - 1, // nsets. - pattern.as_ptr(), - objects.as_ptr(), - ) - }; - FontSet(NonNull::new(raw).unwrap()) - } -} - -/// Iterator over a font set. -pub struct Iter<'a> { - font_set: &'a FontSetRef, - num_fonts: usize, - current: usize, -} - -impl<'a> IntoIterator for &'a FontSet { - type IntoIter = Iter<'a>; - type Item = &'a PatternRef; - - fn into_iter(self) -> Iter<'a> { - let num_fonts = unsafe { (*self.as_ptr()).nfont as isize }; - - trace!("Number of fonts is {}", num_fonts); - - Iter { font_set: self.deref(), num_fonts: num_fonts as _, current: 0 } - } -} - -impl<'a> IntoIterator for &'a FontSetRef { - type IntoIter = Iter<'a>; - type Item = &'a PatternRef; - - fn into_iter(self) -> Iter<'a> { - let num_fonts = unsafe { (*self.as_ptr()).nfont as isize }; - - trace!("Number of fonts is {}", num_fonts); - - Iter { font_set: self, num_fonts: num_fonts as _, current: 0 } - } -} - -impl<'a> Iterator for Iter<'a> { - type Item = &'a PatternRef; - - fn next(&mut self) -> Option { - if self.current == self.num_fonts { - None - } else { - let pattern = unsafe { - let ptr = *(*self.font_set.as_ptr()).fonts.add(self.current); - PatternRef::from_ptr(ptr) - }; - - self.current += 1; - Some(pattern) - } - } -} diff --git a/font/src/ft/fc/mod.rs b/font/src/ft/fc/mod.rs deleted file mode 100644 index 1058bea3..00000000 --- a/font/src/ft/fc/mod.rs +++ /dev/null @@ -1,337 +0,0 @@ -use std::fmt; -use std::ptr; - -use foreign_types::{ForeignType, ForeignTypeRef}; - -use fontconfig::fontconfig as ffi; - -use ffi::FcResultNoMatch; -use ffi::{FcFontList, FcFontMatch, FcFontSort}; -use ffi::{FcMatchFont, FcMatchPattern, FcMatchScan}; -use ffi::{FcSetApplication, FcSetSystem}; -use ffi::{FC_SLANT_ITALIC, FC_SLANT_OBLIQUE, FC_SLANT_ROMAN}; -use ffi::{FC_WEIGHT_BLACK, FC_WEIGHT_BOLD, FC_WEIGHT_EXTRABLACK, FC_WEIGHT_EXTRABOLD}; -use ffi::{FC_WEIGHT_BOOK, FC_WEIGHT_MEDIUM, FC_WEIGHT_REGULAR, FC_WEIGHT_SEMIBOLD}; -use ffi::{FC_WEIGHT_EXTRALIGHT, FC_WEIGHT_LIGHT, FC_WEIGHT_THIN}; - -pub mod config; -pub use config::{Config, ConfigRef}; - -pub mod font_set; -pub use font_set::{FontSet, FontSetRef}; - -pub mod object_set; -pub use object_set::{ObjectSet, ObjectSetRef}; - -pub mod char_set; -pub use char_set::{CharSet, CharSetRef}; - -pub mod pattern; -pub use pattern::{FTFaceLocation, Pattern, PatternHash, PatternRef}; - -/// Find the font closest matching the provided pattern. -/// -/// The returned pattern is the result of Pattern::render_prepare. -pub fn font_match(config: &ConfigRef, pattern: &PatternRef) -> Option { - unsafe { - // What is this result actually used for? Seems redundant with - // return type. - let mut result = FcResultNoMatch; - let ptr = FcFontMatch(config.as_ptr(), pattern.as_ptr(), &mut result); - - if ptr.is_null() { - None - } else { - Some(Pattern::from_ptr(ptr)) - } - } -} - -/// List fonts by closeness to the pattern. -pub fn font_sort(config: &ConfigRef, pattern: &PatternRef) -> Option { - unsafe { - // What is this result actually used for? Seems redundant with - // return type. - let mut result = FcResultNoMatch; - - let mut charsets: *mut _ = ptr::null_mut(); - let ptr = FcFontSort( - config.as_ptr(), - pattern.as_ptr(), - 1, // Trim font list. - &mut charsets, - &mut result, - ); - - if ptr.is_null() { - None - } else { - Some(FontSet::from_ptr(ptr)) - } - } -} - -/// List fonts matching pattern. -pub fn font_list( - config: &ConfigRef, - pattern: &PatternRef, - objects: &ObjectSetRef, -) -> Option { - unsafe { - let ptr = FcFontList(config.as_ptr(), pattern.as_ptr(), objects.as_ptr()); - - if ptr.is_null() { - None - } else { - Some(FontSet::from_ptr(ptr)) - } - } -} - -/// Available font sets. -#[derive(Debug, Copy, Clone)] -pub enum SetName { - System = FcSetSystem as isize, - Application = FcSetApplication as isize, -} - -/// When matching, how to match. -#[derive(Debug, Copy, Clone)] -pub enum MatchKind { - Font = FcMatchFont as isize, - Pattern = FcMatchPattern as isize, - Scan = FcMatchScan as isize, -} - -#[derive(Debug, Copy, Clone)] -pub enum Slant { - Italic = FC_SLANT_ITALIC as isize, - Oblique = FC_SLANT_OBLIQUE as isize, - Roman = FC_SLANT_ROMAN as isize, -} - -#[derive(Debug, Copy, Clone)] -pub enum Weight { - Thin = FC_WEIGHT_THIN as isize, - Extralight = FC_WEIGHT_EXTRALIGHT as isize, - Light = FC_WEIGHT_LIGHT as isize, - Book = FC_WEIGHT_BOOK as isize, - Regular = FC_WEIGHT_REGULAR as isize, - Medium = FC_WEIGHT_MEDIUM as isize, - Semibold = FC_WEIGHT_SEMIBOLD as isize, - Bold = FC_WEIGHT_BOLD as isize, - Extrabold = FC_WEIGHT_EXTRABOLD as isize, - Black = FC_WEIGHT_BLACK as isize, - Extrablack = FC_WEIGHT_EXTRABLACK as isize, -} - -#[derive(Debug, Copy, Clone)] -pub enum Width { - Ultracondensed, - Extracondensed, - Condensed, - Semicondensed, - Normal, - Semiexpanded, - Expanded, - Extraexpanded, - Ultraexpanded, - Other(i32), -} - -impl Width { - fn to_isize(self) -> isize { - match self { - Width::Ultracondensed => 50, - Width::Extracondensed => 63, - Width::Condensed => 75, - Width::Semicondensed => 87, - Width::Normal => 100, - Width::Semiexpanded => 113, - Width::Expanded => 125, - Width::Extraexpanded => 150, - Width::Ultraexpanded => 200, - Width::Other(value) => value as isize, - } - } -} - -impl From for Width { - fn from(value: isize) -> Self { - match value { - 50 => Width::Ultracondensed, - 63 => Width::Extracondensed, - 75 => Width::Condensed, - 87 => Width::Semicondensed, - 100 => Width::Normal, - 113 => Width::Semiexpanded, - 125 => Width::Expanded, - 150 => Width::Extraexpanded, - 200 => Width::Ultraexpanded, - _ => Width::Other(value as _), - } - } -} - -/// Subpixel geometry. -#[derive(Debug)] -pub enum Rgba { - Unknown, - Rgb, - Bgr, - Vrgb, - Vbgr, - None, -} - -impl Rgba { - fn to_isize(&self) -> isize { - match *self { - Rgba::Unknown => 0, - Rgba::Rgb => 1, - Rgba::Bgr => 2, - Rgba::Vrgb => 3, - Rgba::Vbgr => 4, - Rgba::None => 5, - } - } -} - -impl fmt::Display for Rgba { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(match *self { - Rgba::Unknown => "unknown", - Rgba::Rgb => "rgb", - Rgba::Bgr => "bgr", - Rgba::Vrgb => "vrgb", - Rgba::Vbgr => "vbgr", - Rgba::None => "none", - }) - } -} - -impl From for Rgba { - fn from(val: isize) -> Rgba { - match val { - 1 => Rgba::Rgb, - 2 => Rgba::Bgr, - 3 => Rgba::Vrgb, - 4 => Rgba::Vbgr, - 5 => Rgba::None, - _ => Rgba::Unknown, - } - } -} - -/// Hinting Style. -#[derive(Debug, Copy, Clone)] -pub enum HintStyle { - None, - Slight, - Medium, - Full, -} - -impl fmt::Display for HintStyle { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(match *self { - HintStyle::None => "none", - HintStyle::Slight => "slight", - HintStyle::Medium => "medium", - HintStyle::Full => "full", - }) - } -} - -/// Lcd filter, used to reduce color fringing with subpixel rendering. -pub enum LcdFilter { - None, - Default, - Light, - Legacy, -} - -impl fmt::Display for LcdFilter { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(match *self { - LcdFilter::None => "none", - LcdFilter::Default => "default", - LcdFilter::Light => "light", - LcdFilter::Legacy => "legacy", - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn font_match() { - let mut pattern = Pattern::new(); - pattern.add_family("monospace"); - pattern.add_style("regular"); - - let config = Config::get_current(); - pattern.config_substitute(config, MatchKind::Pattern); - pattern.default_substitute(); - let font = super::font_match(config, &pattern).expect("match font monospace"); - - print!("index={:?}; ", font.index()); - print!("family={:?}; ", font.family()); - print!("style={:?}; ", font.style()); - print!("antialias={:?}; ", font.antialias()); - print!("autohint={:?}; ", font.autohint()); - print!("hinting={:?}; ", font.hinting()); - print!("rgba={:?}; ", font.rgba()); - print!("embeddedbitmap={:?}; ", font.embeddedbitmap()); - print!("lcdfilter={:?}; ", font.lcdfilter()); - print!("hintstyle={:?}", font.hintstyle()); - println!(); - } - - #[test] - fn font_sort() { - let mut pattern = Pattern::new(); - pattern.add_family("monospace"); - pattern.set_slant(Slant::Italic); - - let config = Config::get_current(); - pattern.config_substitute(config, MatchKind::Pattern); - pattern.default_substitute(); - let fonts = super::font_sort(config, &pattern).expect("sort font monospace"); - - for font in fonts.into_iter().take(10) { - let font = pattern.render_prepare(&config, &font); - print!("index={:?}; ", font.index()); - print!("family={:?}; ", font.family()); - print!("style={:?}; ", font.style()); - print!("rgba={:?}", font.rgba()); - print!("rgba={:?}", font.rgba()); - println!(); - } - } - - #[test] - fn font_sort_with_glyph() { - let mut charset = CharSet::new(); - charset.add('💖'); - let mut pattern = Pattern::new(); - pattern.add_charset(&charset); - drop(charset); - - let config = Config::get_current(); - pattern.config_substitute(config, MatchKind::Pattern); - pattern.default_substitute(); - let fonts = super::font_sort(config, &pattern).expect("font_sort"); - - for font in fonts.into_iter().take(10) { - let font = pattern.render_prepare(&config, &font); - print!("index={:?}; ", font.index()); - print!("family={:?}; ", font.family()); - print!("style={:?}; ", font.style()); - print!("rgba={:?}", font.rgba()); - println!(); - } - } -} diff --git a/font/src/ft/fc/object_set.rs b/font/src/ft/fc/object_set.rs deleted file mode 100644 index 74faabbf..00000000 --- a/font/src/ft/fc/object_set.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::ptr::NonNull; - -use libc::c_char; - -use super::ffi::{FcObjectSet, FcObjectSetAdd, FcObjectSetCreate, FcObjectSetDestroy}; -use foreign_types::{foreign_type, ForeignTypeRef}; - -foreign_type! { - pub unsafe type ObjectSet { - type CType = FcObjectSet; - fn drop = FcObjectSetDestroy; - } -} - -impl ObjectSet { - pub fn new() -> Self { - Self::default() - } -} - -impl Default for ObjectSet { - fn default() -> Self { - ObjectSet(unsafe { NonNull::new(FcObjectSetCreate()).unwrap() }) - } -} - -impl ObjectSetRef { - fn add(&mut self, property: &[u8]) { - unsafe { - FcObjectSetAdd(self.as_ptr(), property.as_ptr() as *mut c_char); - } - } - - #[inline] - pub fn add_file(&mut self) { - self.add(b"file\0"); - } - - #[inline] - pub fn add_index(&mut self) { - self.add(b"index\0"); - } - - #[inline] - pub fn add_style(&mut self) { - self.add(b"style\0"); - } -} diff --git a/font/src/ft/fc/pattern.rs b/font/src/ft/fc/pattern.rs deleted file mode 100644 index 19d72b47..00000000 --- a/font/src/ft/fc/pattern.rs +++ /dev/null @@ -1,627 +0,0 @@ -use std::ffi::{CStr, CString}; -use std::fmt; -use std::mem; -use std::path::PathBuf; -use std::ptr::{self, NonNull}; -use std::str; - -use foreign_types::{foreign_type, ForeignType, ForeignTypeRef}; -use libc::{c_char, c_double, c_int}; - -use super::ffi::FcMatrix; -use super::ffi::FcResultMatch; -use super::ffi::{FcBool, FcFontRenderPrepare, FcPatternGetBool, FcPatternGetDouble}; -use super::ffi::{FcChar8, FcConfigSubstitute, FcDefaultSubstitute, FcPattern, FcPatternHash}; -use super::ffi::{ - FcPatternAddCharSet, FcPatternDestroy, FcPatternDuplicate, FcPatternGetCharSet, - FcPatternGetMatrix, -}; -use super::ffi::{FcPatternAddDouble, FcPatternAddString, FcPatternCreate, FcPatternGetString}; -use super::ffi::{FcPatternAddInteger, FcPatternGetInteger, FcPatternPrint}; - -use super::{CharSetRef, ConfigRef, HintStyle, LcdFilter, MatchKind, Rgba, Slant, Weight, Width}; - -pub struct StringPropertyIter<'a> { - pattern: &'a PatternRef, - object: &'a [u8], - index: usize, -} - -impl<'a> StringPropertyIter<'a> { - fn new<'b>(pattern: &'b PatternRef, object: &'b [u8]) -> StringPropertyIter<'b> { - StringPropertyIter { pattern, object, index: 0 } - } - - fn get_value(&self, index: usize) -> Option<&'a str> { - let mut value: *mut FcChar8 = ptr::null_mut(); - - let result = unsafe { - FcPatternGetString( - self.pattern.as_ptr(), - self.object.as_ptr() as *mut c_char, - index as c_int, - &mut value, - ) - }; - - if result == FcResultMatch { - // Transmute here is to extend lifetime of the str to that of the iterator. - // - // Potential unsafety? What happens if the pattern is modified while this ptr is - // borrowed out? - Some(unsafe { - mem::transmute(CStr::from_ptr(value as *const c_char).to_str().unwrap()) - }) - } else { - None - } - } -} - -/// Iterator over integer properties. -pub struct BooleanPropertyIter<'a> { - pattern: &'a PatternRef, - object: &'a [u8], - index: usize, -} - -impl<'a> BooleanPropertyIter<'a> { - fn new<'b>(pattern: &'b PatternRef, object: &'b [u8]) -> BooleanPropertyIter<'b> { - BooleanPropertyIter { pattern, object, index: 0 } - } - - fn get_value(&self, index: usize) -> Option { - let mut value: FcBool = 0; - - let result = unsafe { - FcPatternGetBool( - self.pattern.as_ptr(), - self.object.as_ptr() as *mut c_char, - index as c_int, - &mut value, - ) - }; - - if result == FcResultMatch { - Some(value != 0) - } else { - None - } - } -} - -/// Iterator over integer properties. -pub struct IntPropertyIter<'a> { - pattern: &'a PatternRef, - object: &'a [u8], - index: usize, -} - -impl<'a> IntPropertyIter<'a> { - fn new<'b>(pattern: &'b PatternRef, object: &'b [u8]) -> IntPropertyIter<'b> { - IntPropertyIter { pattern, object, index: 0 } - } - - fn get_value(&self, index: usize) -> Option { - let mut value = 0 as c_int; - - let result = unsafe { - FcPatternGetInteger( - self.pattern.as_ptr(), - self.object.as_ptr() as *mut c_char, - index as c_int, - &mut value, - ) - }; - - if result == FcResultMatch { - Some(value as isize) - } else { - None - } - } -} - -pub struct RgbaPropertyIter<'a> { - inner: IntPropertyIter<'a>, -} - -impl<'a> RgbaPropertyIter<'a> { - fn new<'b>(pattern: &'b PatternRef, object: &'b [u8]) -> RgbaPropertyIter<'b> { - RgbaPropertyIter { inner: IntPropertyIter::new(pattern, object) } - } - - #[inline] - fn inner<'b>(&'b mut self) -> &'b mut IntPropertyIter<'a> { - &mut self.inner - } - - fn get_value(&self, index: usize) -> Option { - self.inner.get_value(index).map(Rgba::from) - } -} - -pub struct HintStylePropertyIter<'a> { - inner: IntPropertyIter<'a>, -} - -impl<'a> HintStylePropertyIter<'a> { - fn new(pattern: &PatternRef) -> HintStylePropertyIter { - HintStylePropertyIter { inner: IntPropertyIter::new(pattern, b"hintstyle\0") } - } - - #[inline] - fn inner<'b>(&'b mut self) -> &'b mut IntPropertyIter<'a> { - &mut self.inner - } - - fn get_value(&self, index: usize) -> Option { - self.inner.get_value(index).and_then(|hint_style| { - Some(match hint_style { - 0 => HintStyle::None, - 1 => HintStyle::Slight, - 2 => HintStyle::Medium, - 3 => HintStyle::Full, - _ => return None, - }) - }) - } -} - -pub struct LcdFilterPropertyIter<'a> { - inner: IntPropertyIter<'a>, -} - -impl<'a> LcdFilterPropertyIter<'a> { - fn new(pattern: &PatternRef) -> LcdFilterPropertyIter { - LcdFilterPropertyIter { inner: IntPropertyIter::new(pattern, b"lcdfilter\0") } - } - - #[inline] - fn inner<'b>(&'b mut self) -> &'b mut IntPropertyIter<'a> { - &mut self.inner - } - - fn get_value(&self, index: usize) -> Option { - self.inner.get_value(index).and_then(|hint_style| { - Some(match hint_style { - 0 => LcdFilter::None, - 1 => LcdFilter::Default, - 2 => LcdFilter::Light, - 3 => LcdFilter::Legacy, - _ => return None, - }) - }) - } -} - -/// Iterator over integer properties. -pub struct DoublePropertyIter<'a> { - pattern: &'a PatternRef, - object: &'a [u8], - index: usize, -} - -impl<'a> DoublePropertyIter<'a> { - fn new<'b>(pattern: &'b PatternRef, object: &'b [u8]) -> DoublePropertyIter<'b> { - DoublePropertyIter { pattern, object, index: 0 } - } - - fn get_value(&self, index: usize) -> Option { - let mut value = f64::from(0); - - let result = unsafe { - FcPatternGetDouble( - self.pattern.as_ptr(), - self.object.as_ptr() as *mut c_char, - index as c_int, - &mut value, - ) - }; - - if result == FcResultMatch { - Some(value as f64) - } else { - None - } - } -} - -/// Implement debug for a property iterator. -macro_rules! impl_property_iter_debug { - ($iter:ty => $item:ty) => { - impl<'a> fmt::Debug for $iter { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "[")?; - for i in 0.. { - match self.get_value(i) { - Some(val) => { - if i > 0 { - write!(f, ", {}", val)?; - } else { - write!(f, "{}", val)?; - } - }, - _ => break, - } - } - write!(f, "]") - } - } - }; -} - -/// Implement Iterator and Debug for a property iterator. -macro_rules! impl_property_iter { - ($($iter:ty => $item:ty),*) => { - $( - impl<'a> Iterator for $iter { - type Item = $item; - - fn next(&mut self) -> Option { - let res = self.get_value(self.index); - self.index += 1; - res - } - - #[inline] - fn nth(&mut self, n: usize) -> Option { - self.index += n; - self.next() - } - } - impl_property_iter_debug!($iter => $item); - )* - } -} - -/// Implement Iterator and Debug for a property iterator which internally relies -/// on another property iterator. -macro_rules! impl_derived_property_iter { - ($($iter:ty => $item:ty),*) => { - $( - impl<'a> Iterator for $iter { - type Item = $item; - - fn next(&mut self) -> Option { - let index = { self.inner().index }; - let res = self.get_value(index); - self.inner().index += 1; - res - } - - #[inline] - fn nth(&mut self, n: usize) -> Option { - self.inner().index += n; - self.next() - } - } - impl_property_iter_debug!($iter => $item); - )* - } -} - -// Basic Iterators. -impl_property_iter! { - StringPropertyIter<'a> => &'a str, - IntPropertyIter<'a> => isize, - DoublePropertyIter<'a> => f64, - BooleanPropertyIter<'a> => bool -} - -// Derived Iterators. -impl_derived_property_iter! { - RgbaPropertyIter<'a> => Rgba, - HintStylePropertyIter<'a> => HintStyle, - LcdFilterPropertyIter<'a> => LcdFilter -} - -foreign_type! { - pub unsafe type Pattern { - type CType = FcPattern; - fn drop = FcPatternDestroy; - fn clone = FcPatternDuplicate; - } -} - -macro_rules! string_accessor { - ($([$getter:ident, $setter:ident] => $object_name:expr),*) => { - $( - #[inline] - pub fn $setter(&mut self, value: &str) -> bool { - unsafe { - self.add_string($object_name, value) - } - } - - #[inline] - pub fn $getter(&self) -> StringPropertyIter { - unsafe { - self.get_string($object_name) - } - } - )* - } -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct PatternHash(pub u32); - -#[derive(Hash, Eq, PartialEq, Debug)] -pub struct FTFaceLocation { - pub path: PathBuf, - pub index: isize, -} - -impl FTFaceLocation { - pub fn new(path: PathBuf, index: isize) -> Self { - Self { path, index } - } -} - -impl Pattern { - pub fn new() -> Self { - Self::default() - } -} - -impl Default for Pattern { - fn default() -> Self { - Pattern(unsafe { NonNull::new(FcPatternCreate()).unwrap() }) - } -} - -macro_rules! pattern_get_integer { - ($($method:ident() => $property:expr),+) => { - $( - pub fn $method(&self) -> IntPropertyIter { - unsafe { - self.get_integer($property) - } - } - )+ - }; -} - -macro_rules! boolean_getter { - ($($method:ident() => $property:expr),*) => { - $( - pub fn $method(&self) -> BooleanPropertyIter { - unsafe { - self.get_boolean($property) - } - } - )* - } -} - -macro_rules! double_getter { - ($($method:ident() => $property:expr),*) => { - $( - pub fn $method(&self) -> DoublePropertyIter { - unsafe { - self.get_double($property) - } - } - )* - } -} - -impl PatternRef { - boolean_getter! { - antialias() => b"antialias\0", - hinting() => b"hinting\0", - verticallayout() => b"verticallayout\0", - autohint() => b"autohint\0", - globaladvance() => b"globaladvance\0", - scalable() => b"scalable\0", - symbol() => b"symbol\0", - color() => b"color\0", - minspace() => b"minspace\0", - embolden() => b"embolden\0", - embeddedbitmap() => b"embeddedbitmap\0", - decorative() => b"decorative\0" - } - - double_getter! { - size() => b"size\0", - aspect() => b"aspect\0", - pixelsize() => b"pixelsize\0", - pixelsizefixupfactor() => b"pixelsizefixupfactor\0", - scale() => b"scale\0", - dpi() => b"dpi\0" - } - - string_accessor! { - [family, add_family] => b"family\0", - [familylang, add_familylang] => b"familylang\0", - [style, add_style] => b"style\0", - [stylelang, add_stylelang] => b"stylelang\0", - [fullname, add_fullname] => b"fullname\0", - [fullnamelang, add_fullnamelang] => b"fullnamelang\0", - [foundry, add_foundry] => b"foundry\0", - [capability, add_capability] => b"capability\0", - [fontformat, add_fontformat] => b"fontformat\0", - [fontfeatures, add_fontfeatures] => b"fontfeatures\0", - [namelang, add_namelang] => b"namelang\0", - [postscriptname, add_postscriptname] => b"postscriptname\0" - } - - pattern_get_integer! { - index() => b"index\0" - } - - /// Prints the pattern to stdout. - /// - /// FontConfig doesn't expose a way to iterate over all members of a pattern; - /// instead, we just defer to FcPatternPrint. Otherwise, this could have been - /// a `fmt::Debug` impl. - pub fn print(&self) { - unsafe { FcPatternPrint(self.as_ptr()) } - } - - /// Add a string value to the pattern. - /// - /// If the returned value is `true`, the value is added at the end of - /// any existing list, otherwise it is inserted at the beginning. - /// - /// # Unsafety - /// - /// `object` is not checked to be a valid null-terminated string. - unsafe fn add_string(&mut self, object: &[u8], value: &str) -> bool { - let value = CString::new(&value[..]).unwrap(); - let value = value.as_ptr(); - - FcPatternAddString(self.as_ptr(), object.as_ptr() as *mut c_char, value as *mut FcChar8) - == 1 - } - - unsafe fn add_integer(&self, object: &[u8], int: isize) -> bool { - FcPatternAddInteger(self.as_ptr(), object.as_ptr() as *mut c_char, int as c_int) == 1 - } - - unsafe fn add_double(&self, object: &[u8], value: f64) -> bool { - FcPatternAddDouble(self.as_ptr(), object.as_ptr() as *mut c_char, value as c_double) == 1 - } - - unsafe fn get_string<'a>(&'a self, object: &'a [u8]) -> StringPropertyIter<'a> { - StringPropertyIter::new(self, object) - } - - unsafe fn get_integer<'a>(&'a self, object: &'a [u8]) -> IntPropertyIter<'a> { - IntPropertyIter::new(self, object) - } - - unsafe fn get_double<'a>(&'a self, object: &'a [u8]) -> DoublePropertyIter<'a> { - DoublePropertyIter::new(self, object) - } - - unsafe fn get_boolean<'a>(&'a self, object: &'a [u8]) -> BooleanPropertyIter<'a> { - BooleanPropertyIter::new(self, object) - } - - pub fn hintstyle(&self) -> HintStylePropertyIter { - HintStylePropertyIter::new(self) - } - - pub fn lcdfilter(&self) -> LcdFilterPropertyIter { - LcdFilterPropertyIter::new(self) - } - - pub fn set_slant(&mut self, slant: Slant) -> bool { - unsafe { self.add_integer(b"slant\0", slant as isize) } - } - - pub fn add_pixelsize(&mut self, size: f64) -> bool { - unsafe { self.add_double(b"pixelsize\0", size) } - } - - pub fn set_weight(&mut self, weight: Weight) -> bool { - unsafe { self.add_integer(b"weight\0", weight as isize) } - } - - pub fn set_width(&mut self, width: Width) -> bool { - unsafe { self.add_integer(b"width\0", width.to_isize()) } - } - - pub fn get_width(&self) -> Option { - unsafe { self.get_integer(b"width\0").next().map(Width::from) } - } - - pub fn rgba(&self) -> RgbaPropertyIter { - RgbaPropertyIter::new(self, b"rgba\0") - } - - pub fn set_rgba(&self, rgba: &Rgba) -> bool { - unsafe { self.add_integer(b"rgba\0", rgba.to_isize()) } - } - - pub fn render_prepare(&self, config: &ConfigRef, request: &PatternRef) -> Pattern { - unsafe { - let ptr = FcFontRenderPrepare(config.as_ptr(), self.as_ptr(), request.as_ptr()); - Pattern::from_ptr(ptr) - } - } - - pub fn hash(&self) -> PatternHash { - unsafe { PatternHash(FcPatternHash(self.as_ptr())) } - } - - /// Add charset to the pattern. - /// - /// The referenced charset is copied by Fontconfig internally using - /// FcValueSave so that no references to application provided memory are - /// retained. That is, the CharSet can be safely dropped immediately - /// after being added to the pattern. - pub fn add_charset(&self, charset: &CharSetRef) -> bool { - unsafe { - FcPatternAddCharSet( - self.as_ptr(), - b"charset\0".as_ptr() as *mut c_char, - charset.as_ptr(), - ) == 1 - } - } - - /// Get charset from the pattern. - pub fn get_charset(&self) -> Option<&CharSetRef> { - unsafe { - let mut charset = ptr::null_mut(); - - let result = FcPatternGetCharSet( - self.as_ptr(), - b"charset\0".as_ptr() as *mut c_char, - 0, - &mut charset, - ); - - if result == FcResultMatch { - Some(&*(charset as *const CharSetRef)) - } else { - None - } - } - } - - /// Get matrix from the pattern. - pub fn get_matrix(&self) -> Option { - unsafe { - let mut matrix = ptr::null_mut(); - let result = FcPatternGetMatrix( - self.as_ptr(), - b"matrix\0".as_ptr() as *mut c_char, - 0, - &mut matrix, - ); - - if result == FcResultMatch { - Some(*matrix) - } else { - None - } - } - } - - pub fn file(&self, index: usize) -> Option { - unsafe { self.get_string(b"file\0").nth(index) }.map(From::from) - } - - pub fn ft_face_location(&self, index: usize) -> Option { - match (self.file(index), self.index().next()) { - (Some(path), Some(index)) => Some(FTFaceLocation::new(path, index)), - _ => None, - } - } - - pub fn config_substitute(&mut self, config: &ConfigRef, kind: MatchKind) { - unsafe { - FcConfigSubstitute(config.as_ptr(), self.as_ptr(), kind as u32); - } - } - - pub fn default_substitute(&mut self) { - unsafe { - FcDefaultSubstitute(self.as_ptr()); - } - } -} diff --git a/font/src/ft/mod.rs b/font/src/ft/mod.rs deleted file mode 100644 index dd15c4bb..00000000 --- a/font/src/ft/mod.rs +++ /dev/null @@ -1,787 +0,0 @@ -//! Rasterization powered by FreeType and Fontconfig. - -use std::cmp::{min, Ordering}; -use std::collections::HashMap; -use std::fmt::{self, Display, Formatter}; -use std::rc::Rc; - -use freetype::face::LoadFlag; -use freetype::tt_os2::TrueTypeOS2Table; -use freetype::{self, Library, Matrix}; -use freetype::{freetype_sys, Face as FTFace}; -use libc::{c_long, c_uint}; -use log::{debug, trace}; - -pub mod fc; - -use fc::{CharSet, FTFaceLocation, Pattern, PatternHash, PatternRef}; - -use super::{ - BitmapBuffer, FontDesc, FontKey, GlyphKey, Metrics, Rasterize, RasterizedGlyph, Size, Slant, - Style, Weight, -}; - -struct FallbackFont { - pattern: Pattern, - key: FontKey, -} - -impl FallbackFont { - fn new(pattern: Pattern, key: FontKey) -> FallbackFont { - Self { pattern, key } - } -} - -impl FontKey { - fn from_pattern_hashes(lhs: PatternHash, rhs: PatternHash) -> Self { - // XOR two hashes to get a font ID. - Self { token: lhs.0.rotate_left(1) ^ rhs.0 } - } -} - -#[derive(Default)] -struct FallbackList { - list: Vec, - coverage: CharSet, -} - -struct FaceLoadingProperties { - load_flags: LoadFlag, - render_mode: freetype::RenderMode, - lcd_filter: c_uint, - non_scalable: Option, - colored: bool, - embolden: bool, - matrix: Option, - pixelsize_fixup_factor: Option, - ft_face: Rc, -} - -impl fmt::Debug for FaceLoadingProperties { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.debug_struct("Face") - .field("ft_face", &self.ft_face) - .field("load_flags", &self.load_flags) - .field("render_mode", &match self.render_mode { - freetype::RenderMode::Normal => "Normal", - freetype::RenderMode::Light => "Light", - freetype::RenderMode::Mono => "Mono", - freetype::RenderMode::Lcd => "Lcd", - freetype::RenderMode::LcdV => "LcdV", - freetype::RenderMode::Max => "Max", - }) - .field("lcd_filter", &self.lcd_filter) - .finish() - } -} - -/// Rasterizes glyphs for a single font face. -pub struct FreeTypeRasterizer { - library: Library, - faces: HashMap, - ft_faces: HashMap>, - fallback_lists: HashMap, - device_pixel_ratio: f32, -} - -#[inline] -fn to_freetype_26_6(f: f32) -> isize { - ((1i32 << 6) as f32 * f).round() as isize -} - -#[inline] -fn to_fixedpoint_16_6(f: f64) -> c_long { - (f * 65536.0) as c_long -} - -impl Rasterize for FreeTypeRasterizer { - type Err = Error; - - fn new(device_pixel_ratio: f32, _: bool) -> Result { - let library = Library::init()?; - - unsafe { - // Initialize default properties, like user preferred interpreter. - freetype_sys::FT_Set_Default_Properties(library.raw()); - }; - - Ok(FreeTypeRasterizer { - faces: HashMap::new(), - ft_faces: HashMap::new(), - fallback_lists: HashMap::new(), - library, - device_pixel_ratio, - }) - } - - fn metrics(&self, key: FontKey, _size: Size) -> Result { - let face = &mut self.faces.get(&key).ok_or(Error::FontNotLoaded)?; - let full = self.full_metrics(&face)?; - - let height = (full.size_metrics.height / 64) as f64; - let descent = (full.size_metrics.descender / 64) as f32; - - // Get underline position and thickness in device pixels. - let x_scale = full.size_metrics.x_scale as f32 / 65536.0; - let mut underline_position = f32::from(face.ft_face.underline_position()) * x_scale / 64.; - let mut underline_thickness = f32::from(face.ft_face.underline_thickness()) * x_scale / 64.; - - // Fallback for bitmap fonts which do not provide underline metrics. - if underline_position == 0. { - underline_thickness = (descent.abs() / 5.).round(); - underline_position = descent / 2.; - } - - // Get strikeout position and thickness in device pixels. - let (strikeout_position, strikeout_thickness) = - match TrueTypeOS2Table::from_face(&mut (*face.ft_face).clone()) { - Some(os2) => { - let strikeout_position = f32::from(os2.y_strikeout_position()) * x_scale / 64.; - let strikeout_thickness = f32::from(os2.y_strikeout_size()) * x_scale / 64.; - (strikeout_position, strikeout_thickness) - }, - _ => { - // Fallback if font doesn't provide info about strikeout. - trace!("Using fallback strikeout metrics"); - let strikeout_position = height as f32 / 2. + descent; - (strikeout_position, underline_thickness) - }, - }; - - Ok(Metrics { - average_advance: full.cell_width, - line_height: height, - descent, - underline_position, - underline_thickness, - strikeout_position, - strikeout_thickness, - }) - } - - fn load_font(&mut self, desc: &FontDesc, size: Size) -> Result { - self.get_face(desc, size) - } - - fn get_glyph(&mut self, glyph_key: GlyphKey) -> Result { - self.get_rendered_glyph(glyph_key) - } - - fn update_dpr(&mut self, device_pixel_ratio: f32) { - self.device_pixel_ratio = device_pixel_ratio; - } -} - -pub trait IntoFontconfigType { - type FcType; - fn into_fontconfig_type(&self) -> Self::FcType; -} - -impl IntoFontconfigType for Slant { - type FcType = fc::Slant; - - fn into_fontconfig_type(&self) -> Self::FcType { - match *self { - Slant::Normal => fc::Slant::Roman, - Slant::Italic => fc::Slant::Italic, - Slant::Oblique => fc::Slant::Oblique, - } - } -} - -impl IntoFontconfigType for Weight { - type FcType = fc::Weight; - - fn into_fontconfig_type(&self) -> Self::FcType { - match *self { - Weight::Normal => fc::Weight::Regular, - Weight::Bold => fc::Weight::Bold, - } - } -} - -struct FullMetrics { - size_metrics: freetype::ffi::FT_Size_Metrics, - cell_width: f64, -} - -impl FreeTypeRasterizer { - /// Load a font face according to `FontDesc`. - fn get_face(&mut self, desc: &FontDesc, size: Size) -> Result { - // Adjust for DPR. - let size = f64::from(size.as_f32_pts() * self.device_pixel_ratio * 96. / 72.); - - let config = fc::Config::get_current(); - let mut pattern = Pattern::new(); - pattern.add_family(&desc.name); - pattern.add_pixelsize(size); - - // Add style to a pattern. - match desc.style { - Style::Description { slant, weight } => { - // Match nearest font. - pattern.set_weight(weight.into_fontconfig_type()); - pattern.set_slant(slant.into_fontconfig_type()); - }, - Style::Specific(ref style) => { - // If a name was specified, try and load specifically that font. - pattern.add_style(style); - }, - } - - // Hash requested pattern. - let hash = pattern.hash(); - - pattern.config_substitute(config, fc::MatchKind::Pattern); - pattern.default_substitute(); - - // Get font list using pattern. First font is the primary one while the rest are fallbacks. - let matched_fonts = - fc::font_sort(&config, &pattern).ok_or_else(|| Error::MissingFont(desc.to_owned()))?; - let mut matched_fonts = matched_fonts.into_iter(); - - let primary_font = - matched_fonts.next().ok_or_else(|| Error::MissingFont(desc.to_owned()))?; - - // We should render patterns to get values like `pixelsizefixupfactor`. - let primary_font = pattern.render_prepare(config, primary_font); - - // Hash pattern together with request pattern to include requested font size in the hash. - let primary_font_key = FontKey::from_pattern_hashes(hash, primary_font.hash()); - - // Return if we already have the same primary font. - if self.fallback_lists.contains_key(&primary_font_key) { - return Ok(primary_font_key); - } - - // Load font if we haven't loaded it yet. - if !self.faces.contains_key(&primary_font_key) { - self.face_from_pattern(&primary_font, primary_font_key) - .and_then(|pattern| pattern.ok_or_else(|| Error::MissingFont(desc.to_owned())))?; - } - - // Coverage for fallback fonts. - let coverage = CharSet::new(); - let empty_charset = CharSet::new(); - - let list: Vec = matched_fonts - .map(|fallback_font| { - let charset = fallback_font.get_charset().unwrap_or(&empty_charset); - - // Use original pattern to preserve loading flags. - let fallback_font = pattern.render_prepare(config, fallback_font); - let fallback_font_key = FontKey::from_pattern_hashes(hash, fallback_font.hash()); - - let _ = coverage.merge(&charset); - - FallbackFont::new(fallback_font, fallback_font_key) - }) - .collect(); - - self.fallback_lists.insert(primary_font_key, FallbackList { list, coverage }); - - Ok(primary_font_key) - } - - fn full_metrics(&self, face_load_props: &FaceLoadingProperties) -> Result { - let ft_face = &face_load_props.ft_face; - let size_metrics = ft_face.size_metrics().ok_or(Error::MissingSizeMetrics)?; - - let width = match ft_face.load_char('0' as usize, face_load_props.load_flags) { - Ok(_) => ft_face.glyph().metrics().horiAdvance / 64, - Err(_) => size_metrics.max_advance / 64, - } as f64; - - Ok(FullMetrics { size_metrics, cell_width: width }) - } - - fn load_ft_face(&mut self, ft_face_location: FTFaceLocation) -> Result, Error> { - let mut ft_face = self.library.new_face(&ft_face_location.path, ft_face_location.index)?; - if ft_face.has_color() { - unsafe { - // Select the colored bitmap size to use from the array of available sizes. - freetype_sys::FT_Select_Size(ft_face.raw_mut(), 0); - } - } - - let ft_face = Rc::new(ft_face); - self.ft_faces.insert(ft_face_location, Rc::clone(&ft_face)); - - Ok(ft_face) - } - - fn face_from_pattern( - &mut self, - pattern: &PatternRef, - font_key: FontKey, - ) -> Result, Error> { - if let Some(ft_face_location) = pattern.ft_face_location(0) { - if self.faces.get(&font_key).is_some() { - return Ok(Some(font_key)); - } - - trace!("Got font path={:?}, index={:?}", ft_face_location.path, ft_face_location.index); - - let ft_face = match self.ft_faces.get(&ft_face_location) { - Some(ft_face) => Rc::clone(ft_face), - None => self.load_ft_face(ft_face_location)?, - }; - - let non_scalable = if pattern.scalable().next().unwrap_or(true) { - None - } else { - Some(pattern.pixelsize().next().expect("has 1+ pixelsize") as f32) - }; - - let embolden = pattern.embolden().next().unwrap_or(false); - - let matrix = pattern.get_matrix().map(|matrix| { - // Convert Fontconfig matrix to FreeType matrix. - let xx = to_fixedpoint_16_6(matrix.xx); - let xy = to_fixedpoint_16_6(matrix.xy); - let yx = to_fixedpoint_16_6(matrix.yx); - let yy = to_fixedpoint_16_6(matrix.yy); - - Matrix { xx, xy, yx, yy } - }); - - let pixelsize_fixup_factor = pattern.pixelsizefixupfactor().next(); - - let face = FaceLoadingProperties { - load_flags: Self::ft_load_flags(pattern), - render_mode: Self::ft_render_mode(pattern), - lcd_filter: Self::ft_lcd_filter(pattern), - non_scalable, - colored: ft_face.has_color(), - embolden, - matrix, - pixelsize_fixup_factor, - ft_face, - }; - - debug!("Loaded Face {:?}", face); - - self.faces.insert(font_key, face); - - Ok(Some(font_key)) - } else { - Ok(None) - } - } - - fn face_for_glyph(&mut self, glyph_key: GlyphKey) -> Result { - if let Some(face) = self.faces.get(&glyph_key.font_key) { - let index = face.ft_face.get_char_index(glyph_key.c as usize); - - if index != 0 { - return Ok(glyph_key.font_key); - } - } - - Ok(self.load_face_with_glyph(glyph_key).unwrap_or(glyph_key.font_key)) - } - - fn load_face_with_glyph(&mut self, glyph: GlyphKey) -> Result { - let fallback_list = self.fallback_lists.get(&glyph.font_key).unwrap(); - - // Check whether glyph is presented in any fallback font. - if !fallback_list.coverage.has_char(glyph.c) { - return Ok(glyph.font_key); - } - - for fallback_font in &fallback_list.list { - let font_key = fallback_font.key; - let font_pattern = &fallback_font.pattern; - match self.faces.get(&font_key) { - Some(face) => { - let index = face.ft_face.get_char_index(glyph.c as usize); - - // We found something in a current face, so let's use it. - if index != 0 { - return Ok(font_key); - } - }, - None => { - if font_pattern.get_charset().map(|cs| cs.has_char(glyph.c)) != Some(true) { - continue; - } - - let pattern = font_pattern.clone(); - let key = self.face_from_pattern(&pattern, font_key)?.unwrap(); - - return Ok(key); - }, - } - } - - // You can hit this return, if you're failing to get charset from a pattern. - Ok(glyph.font_key) - } - - fn get_rendered_glyph(&mut self, glyph_key: GlyphKey) -> Result { - // Render a normal character if it's not a cursor. - let font_key = self.face_for_glyph(glyph_key)?; - let face = &self.faces[&font_key]; - let index = face.ft_face.get_char_index(glyph_key.c as usize); - let pixelsize = face - .non_scalable - .unwrap_or_else(|| glyph_key.size.as_f32_pts() * self.device_pixel_ratio * 96. / 72.); - - if !face.colored { - face.ft_face.set_char_size(to_freetype_26_6(pixelsize), 0, 0, 0)?; - } - - unsafe { - let ft_lib = self.library.raw(); - freetype::ffi::FT_Library_SetLcdFilter(ft_lib, face.lcd_filter); - } - - face.ft_face.load_glyph(index as u32, face.load_flags)?; - - let glyph = face.ft_face.glyph(); - - // Generate synthetic bold. - if face.embolden { - unsafe { - freetype_sys::FT_GlyphSlot_Embolden(glyph.raw() - as *const freetype_sys::FT_GlyphSlotRec - as *mut freetype_sys::FT_GlyphSlotRec); - } - } - - // Transform glyphs with the matrix from Fontconfig. Primarily used to generate italics. - if let Some(matrix) = face.matrix.as_ref() { - let glyph = face.ft_face.raw().glyph; - - unsafe { - // Check that the glyph is a vectorial outline, not a bitmap. - if (*glyph).format == freetype_sys::FT_GLYPH_FORMAT_OUTLINE { - let outline = &(*glyph).outline; - - freetype_sys::FT_Outline_Transform(outline, matrix); - } - } - } - - glyph.render_glyph(face.render_mode)?; - - let (pixel_height, pixel_width, buf) = Self::normalize_buffer(&glyph.bitmap())?; - - let rasterized_glyph = RasterizedGlyph { - c: glyph_key.c, - top: glyph.bitmap_top(), - left: glyph.bitmap_left(), - width: pixel_width, - height: pixel_height, - buf, - }; - - if face.colored { - let fixup_factor = if let Some(pixelsize_fixup_factor) = face.pixelsize_fixup_factor { - pixelsize_fixup_factor - } else { - // Fallback if user has bitmap scaling disabled. - let metrics = face.ft_face.size_metrics().ok_or(Error::MissingSizeMetrics)?; - f64::from(pixelsize) / f64::from(metrics.y_ppem) - }; - Ok(downsample_bitmap(rasterized_glyph, fixup_factor)) - } else { - Ok(rasterized_glyph) - } - } - - fn ft_load_flags(pattern: &PatternRef) -> LoadFlag { - let antialias = pattern.antialias().next().unwrap_or(true); - let autohint = pattern.autohint().next().unwrap_or(false); - let hinting = pattern.hinting().next().unwrap_or(true); - let rgba = pattern.rgba().next().unwrap_or(fc::Rgba::Unknown); - let embedded_bitmaps = pattern.embeddedbitmap().next().unwrap_or(true); - let scalable = pattern.scalable().next().unwrap_or(true); - let color = pattern.color().next().unwrap_or(false); - - // Disable hinting if so was requested. - let hintstyle = if hinting { - pattern.hintstyle().next().unwrap_or(fc::HintStyle::Full) - } else { - fc::HintStyle::None - }; - - let mut flags = match (antialias, hintstyle, rgba) { - (false, fc::HintStyle::None, _) => LoadFlag::NO_HINTING | LoadFlag::MONOCHROME, - (false, ..) => LoadFlag::TARGET_MONO | LoadFlag::MONOCHROME, - (true, fc::HintStyle::None, _) => LoadFlag::NO_HINTING, - // `hintslight` does *not* use LCD hinting even when a subpixel mode - // is selected. - // - // According to the FreeType docs, - // - // > You can use a hinting algorithm that doesn't correspond to the - // > same rendering mode. As an example, it is possible to use the - // > ‘light’ hinting algorithm and have the results rendered in - // > horizontal LCD pixel mode. - // - // In practice, this means we can have `FT_LOAD_TARGET_LIGHT` with - // subpixel render modes like `FT_RENDER_MODE_LCD`. Libraries like - // cairo take the same approach and consider `hintslight` to always - // prefer `FT_LOAD_TARGET_LIGHT`. - (true, fc::HintStyle::Slight, _) => LoadFlag::TARGET_LIGHT, - (true, fc::HintStyle::Medium, _) => LoadFlag::TARGET_NORMAL, - // If LCD hinting is to be used, must select hintmedium or hintfull, - // have AA enabled, and select a subpixel mode. - (true, fc::HintStyle::Full, fc::Rgba::Rgb) - | (true, fc::HintStyle::Full, fc::Rgba::Bgr) => LoadFlag::TARGET_LCD, - (true, fc::HintStyle::Full, fc::Rgba::Vrgb) - | (true, fc::HintStyle::Full, fc::Rgba::Vbgr) => LoadFlag::TARGET_LCD_V, - // For non-rgba modes with Full hinting, just use the default hinting algorithm. - (true, fc::HintStyle::Full, fc::Rgba::Unknown) - | (true, fc::HintStyle::Full, fc::Rgba::None) => LoadFlag::TARGET_NORMAL, - }; - - // Non scalable fonts only have bitmaps, so disabling them entirely is likely not a - // desirable thing. Colored fonts aren't scalable, but also only have bitmaps. - if !embedded_bitmaps && scalable && !color { - flags |= LoadFlag::NO_BITMAP; - } - - // Use color for colored fonts. - if color { - flags |= LoadFlag::COLOR; - } - - // Force autohint if it was requested. - if autohint { - flags |= LoadFlag::FORCE_AUTOHINT; - } - - flags - } - - fn ft_render_mode(pat: &PatternRef) -> freetype::RenderMode { - let antialias = pat.antialias().next().unwrap_or(true); - let rgba = pat.rgba().next().unwrap_or(fc::Rgba::Unknown); - - match (antialias, rgba) { - (false, _) => freetype::RenderMode::Mono, - (_, fc::Rgba::Rgb) | (_, fc::Rgba::Bgr) => freetype::RenderMode::Lcd, - (_, fc::Rgba::Vrgb) | (_, fc::Rgba::Vbgr) => freetype::RenderMode::LcdV, - (true, _) => freetype::RenderMode::Normal, - } - } - - fn ft_lcd_filter(pat: &PatternRef) -> c_uint { - match pat.lcdfilter().next().unwrap_or(fc::LcdFilter::Default) { - fc::LcdFilter::None => freetype::ffi::FT_LCD_FILTER_NONE, - fc::LcdFilter::Default => freetype::ffi::FT_LCD_FILTER_DEFAULT, - fc::LcdFilter::Light => freetype::ffi::FT_LCD_FILTER_LIGHT, - fc::LcdFilter::Legacy => freetype::ffi::FT_LCD_FILTER_LEGACY, - } - } - - /// Given a FreeType `Bitmap`, returns packed buffer with 1 byte per LCD channel. - /// - /// The i32 value in the return type is the number of pixels per row. - fn normalize_buffer( - bitmap: &freetype::bitmap::Bitmap, - ) -> freetype::FtResult<(i32, i32, BitmapBuffer)> { - use freetype::bitmap::PixelMode; - - let buf = bitmap.buffer(); - let mut packed = Vec::with_capacity((bitmap.rows() * bitmap.width()) as usize); - let pitch = bitmap.pitch().abs() as usize; - match bitmap.pixel_mode()? { - PixelMode::Lcd => { - for i in 0..bitmap.rows() { - let start = (i as usize) * pitch; - let stop = start + bitmap.width() as usize; - packed.extend_from_slice(&buf[start..stop]); - } - Ok((bitmap.rows(), bitmap.width() / 3, BitmapBuffer::RGB(packed))) - }, - PixelMode::LcdV => { - for i in 0..bitmap.rows() / 3 { - for j in 0..bitmap.width() { - for k in 0..3 { - let offset = ((i as usize) * 3 + k) * pitch + (j as usize); - packed.push(buf[offset]); - } - } - } - Ok((bitmap.rows() / 3, bitmap.width(), BitmapBuffer::RGB(packed))) - }, - // Mono data is stored in a packed format using 1 bit per pixel. - PixelMode::Mono => { - fn unpack_byte(res: &mut Vec, byte: u8, mut count: u8) { - // Mono stores MSBit at top of byte - let mut bit = 7; - while count != 0 { - let value = ((byte >> bit) & 1) * 255; - // Push value 3x since result buffer should be 1 byte - // per channel. - res.push(value); - res.push(value); - res.push(value); - count -= 1; - bit -= 1; - } - }; - - for i in 0..(bitmap.rows() as usize) { - let mut columns = bitmap.width(); - let mut byte = 0; - let offset = i * bitmap.pitch().abs() as usize; - while columns != 0 { - let bits = min(8, columns); - unpack_byte(&mut packed, buf[offset + byte], bits as u8); - - columns -= bits; - byte += 1; - } - } - Ok((bitmap.rows(), bitmap.width(), BitmapBuffer::RGB(packed))) - }, - // Gray data is stored as a value between 0 and 255 using 1 byte per pixel. - PixelMode::Gray => { - for i in 0..bitmap.rows() { - let start = (i as usize) * pitch; - let stop = start + bitmap.width() as usize; - for byte in &buf[start..stop] { - packed.push(*byte); - packed.push(*byte); - packed.push(*byte); - } - } - Ok((bitmap.rows(), bitmap.width(), BitmapBuffer::RGB(packed))) - }, - PixelMode::Bgra => { - let buf_size = (bitmap.rows() * bitmap.width() * 4) as usize; - let mut i = 0; - while i < buf_size { - packed.push(buf[i + 2]); - packed.push(buf[i + 1]); - packed.push(buf[i]); - packed.push(buf[i + 3]); - i += 4; - } - Ok((bitmap.rows(), bitmap.width(), BitmapBuffer::RGBA(packed))) - }, - mode => panic!("unhandled pixel mode: {:?}", mode), - } - } -} - -/// Downscale a bitmap by a fixed factor. -/// -/// This will take the `bitmap_glyph` as input and return the glyph's content downscaled by -/// `fixup_factor`. -fn downsample_bitmap(mut bitmap_glyph: RasterizedGlyph, fixup_factor: f64) -> RasterizedGlyph { - // Only scale colored buffers which are bigger than required. - let bitmap_buffer = match (&bitmap_glyph.buf, fixup_factor.partial_cmp(&1.0)) { - (BitmapBuffer::RGBA(buffer), Some(Ordering::Less)) => buffer, - _ => return bitmap_glyph, - }; - - let bitmap_width = bitmap_glyph.width as usize; - let bitmap_height = bitmap_glyph.height as usize; - - let target_width = (bitmap_width as f64 * fixup_factor) as usize; - let target_height = (bitmap_height as f64 * fixup_factor) as usize; - - // Number of pixels in the input buffer, per pixel in the output buffer. - let downsampling_step = 1.0 / fixup_factor; - - let mut downsampled_buffer = Vec::::with_capacity(target_width * target_height * 4); - - for line_index in 0..target_height { - // Get the first and last line which will be consolidated in the current output pixel. - let line_index = line_index as f64; - let source_line_start = (line_index * downsampling_step).round() as usize; - let source_line_end = ((line_index + 1.) * downsampling_step).round() as usize; - - for column_index in 0..target_width { - // Get the first and last column which will be consolidated in the current output - // pixel. - let column_index = column_index as f64; - let source_column_start = (column_index * downsampling_step).round() as usize; - let source_column_end = ((column_index + 1.) * downsampling_step).round() as usize; - - let (mut r, mut g, mut b, mut a) = (0u32, 0u32, 0u32, 0u32); - let mut pixels_picked: u32 = 0; - - // Consolidate all pixels within the source rectangle into a single averaged pixel. - for source_line in source_line_start..source_line_end { - let source_pixel_index = source_line * bitmap_width; - - for source_column in source_column_start..source_column_end { - let offset = (source_pixel_index + source_column) * 4; - r += u32::from(bitmap_buffer[offset]); - g += u32::from(bitmap_buffer[offset + 1]); - b += u32::from(bitmap_buffer[offset + 2]); - a += u32::from(bitmap_buffer[offset + 3]); - pixels_picked += 1; - } - } - - // Add a single pixel to the output buffer for the downscaled source rectangle. - downsampled_buffer.push((r / pixels_picked) as u8); - downsampled_buffer.push((g / pixels_picked) as u8); - downsampled_buffer.push((b / pixels_picked) as u8); - downsampled_buffer.push((a / pixels_picked) as u8); - } - } - - bitmap_glyph.buf = BitmapBuffer::RGBA(downsampled_buffer); - - // Downscale the metrics. - bitmap_glyph.top = (f64::from(bitmap_glyph.top) * fixup_factor) as i32; - bitmap_glyph.left = (f64::from(bitmap_glyph.left) * fixup_factor) as i32; - bitmap_glyph.width = target_width as i32; - bitmap_glyph.height = target_height as i32; - - bitmap_glyph -} - -/// Errors occurring when using the freetype rasterizer. -#[derive(Debug)] -pub enum Error { - /// Error occurred within the FreeType library. - FreeType(freetype::Error), - - /// Couldn't find font matching description. - MissingFont(FontDesc), - - /// Tried to get size metrics from a Face that didn't have a size. - MissingSizeMetrics, - - /// Requested an operation with a FontKey that isn't known to the rasterizer. - FontNotLoaded, -} - -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Error::FreeType(err) => err.source(), - _ => None, - } - } -} - -impl Display for Error { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Error::FreeType(err) => err.fmt(f), - Error::MissingFont(err) => write!(f, "Unable to find the font {}", err), - Error::FontNotLoaded => f.write_str("Tried to use a font that hasn't been loaded"), - Error::MissingSizeMetrics => { - f.write_str("Tried to get size metrics from a face without a size") - }, - } - } -} - -impl From for Error { - fn from(val: freetype::Error) -> Error { - Error::FreeType(val) - } -} - -unsafe impl Send for FreeTypeRasterizer {} diff --git a/font/src/lib.rs b/font/src/lib.rs deleted file mode 100644 index 15cd46ff..00000000 --- a/font/src/lib.rs +++ /dev/null @@ -1,226 +0,0 @@ -//! Compatibility layer for different font engines. -//! -//! CoreText is used on Mac OS. -//! FreeType is used on everything that's not Mac OS. -//! Eventually, ClearType support will be available for windows. - -#![deny(clippy::all, clippy::if_not_else, clippy::enum_glob_use, clippy::wrong_pub_self_convention)] - -use std::fmt; -use std::ops::{Add, Mul}; -use std::sync::atomic::{AtomicUsize, Ordering}; - -// If target isn't macos or windows, reexport everything from ft. -#[cfg(not(any(target_os = "macos", windows)))] -pub mod ft; -#[cfg(not(any(target_os = "macos", windows)))] -pub use ft::{Error, FreeTypeRasterizer as Rasterizer}; - -#[cfg(windows)] -pub mod directwrite; -#[cfg(windows)] -pub use directwrite::{DirectWriteRasterizer as Rasterizer, Error}; - -// If target is macos, reexport everything from darwin. -#[cfg(target_os = "macos")] -mod darwin; -#[cfg(target_os = "macos")] -pub use darwin::*; - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct FontDesc { - name: String, - style: Style, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum Slant { - Normal, - Italic, - Oblique, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum Weight { - Normal, - Bold, -} - -/// Style of font. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum Style { - Specific(String), - Description { slant: Slant, weight: Weight }, -} - -impl fmt::Display for Style { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Style::Specific(ref s) => f.write_str(&s), - Style::Description { slant, weight } => { - write!(f, "slant={:?}, weight={:?}", slant, weight) - }, - } - } -} - -impl FontDesc { - pub fn new(name: S, style: Style) -> FontDesc - where - S: Into, - { - FontDesc { name: name.into(), style } - } -} - -impl fmt::Display for FontDesc { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} - {}", self.name, self.style) - } -} - -/// Identifier for a Font for use in maps/etc. -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] -pub struct FontKey { - token: u32, -} - -impl FontKey { - /// Get next font key for given size. - /// - /// The generated key will be globally unique. - pub fn next() -> FontKey { - static TOKEN: AtomicUsize = AtomicUsize::new(0); - - FontKey { token: TOKEN.fetch_add(1, Ordering::SeqCst) as _ } - } -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct GlyphKey { - pub c: char, - pub font_key: FontKey, - pub size: Size, -} - -/// Font size stored as integer. -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct Size(i16); - -impl Size { - /// Create a new `Size` from a f32 size in points. - pub fn new(size: f32) -> Size { - Size((size * Size::factor()) as i16) - } - - /// Scale factor between font "Size" type and point size. - #[inline] - pub fn factor() -> f32 { - 2.0 - } - - /// Get the f32 size in points. - pub fn as_f32_pts(self) -> f32 { - f32::from(self.0) / Size::factor() - } -} - -impl> Add for Size { - type Output = Size; - - fn add(self, other: T) -> Size { - Size(self.0.saturating_add(other.into().0)) - } -} - -impl> Mul for Size { - type Output = Size; - - fn mul(self, other: T) -> Size { - Size(self.0 * other.into().0) - } -} - -impl From for Size { - fn from(float: f32) -> Size { - Size::new(float) - } -} - -#[derive(Clone)] -pub struct RasterizedGlyph { - pub c: char, - pub width: i32, - pub height: i32, - pub top: i32, - pub left: i32, - pub buf: BitmapBuffer, -} - -#[derive(Clone, Debug)] -pub enum BitmapBuffer { - /// RGB alphamask. - RGB(Vec), - - /// RGBA pixels with premultiplied alpha. - RGBA(Vec), -} - -impl Default for RasterizedGlyph { - fn default() -> RasterizedGlyph { - RasterizedGlyph { - c: ' ', - width: 0, - height: 0, - top: 0, - left: 0, - buf: BitmapBuffer::RGB(Vec::new()), - } - } -} - -impl fmt::Debug for RasterizedGlyph { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("RasterizedGlyph") - .field("c", &self.c) - .field("width", &self.width) - .field("height", &self.height) - .field("top", &self.top) - .field("left", &self.left) - .field("buf", &self.buf) - .finish() - } -} - -#[derive(Copy, Clone)] -pub struct Metrics { - pub average_advance: f64, - pub line_height: f64, - pub descent: f32, - pub underline_position: f32, - pub underline_thickness: f32, - pub strikeout_position: f32, - pub strikeout_thickness: f32, -} - -pub trait Rasterize { - /// Errors occurring in Rasterize methods. - type Err: ::std::error::Error + Send + Sync + 'static; - - /// Create a new Rasterizer. - fn new(device_pixel_ratio: f32, use_thin_strokes: bool) -> Result - where - Self: Sized; - - /// Get `Metrics` for the given `FontKey`. - fn metrics(&self, _: FontKey, _: Size) -> Result; - - /// Load the font described by `FontDesc` and `Size`. - fn load_font(&mut self, _: &FontDesc, _: Size) -> Result; - - /// Rasterize the glyph described by `GlyphKey`.. - fn get_glyph(&mut self, _: GlyphKey) -> Result; - - /// Update the Rasterizer's DPI factor. - fn update_dpr(&mut self, device_pixel_ratio: f32); -}