1
0
Fork 0
mirror of https://github.com/alacritty/alacritty.git synced 2024-11-18 13:55:23 -05:00

Bump version to 0.5.0-rc2

This commit is contained in:
Christian Duerr 2020-07-19 20:35:15 +00:00 committed by GitHub
parent fa79758f56
commit 714bbb769e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 172 additions and 3394 deletions

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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

48
Cargo.lock generated
View file

@ -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"

View file

@ -2,7 +2,6 @@
members = [
"alacritty",
"alacritty_terminal",
"font",
]
[profile.release]

View file

@ -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

View file

@ -1,6 +1,6 @@
[package]
name = "alacritty"
version = "0.5.0-rc1"
version = "0.5.0-rc2"
authors = ["Christian Duerr <contact@christianduerr.com>", "Joe Wilm <joe@jwilm.com>"]
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"

View file

@ -1,6 +1,6 @@
use std::fmt;
use font::Size;
use crossfont::Size;
use log::error;
use serde::de::Visitor;
use serde::{Deserialize, Deserializer};

View file

@ -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,

View file

@ -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<window::Error> for Error {
}
}
impl From<font::Error> for Error {
fn from(val: font::Error) -> Self {
impl From<crossfont::Error> 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<RenderableCell> = terminal.renderable_cells(config).collect();
let search_regex = search_regex.map(|regex| Self::format_search(&regex));
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<String>,
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);
(

View file

@ -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<T> 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<usize> {
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<N: Notify + OnResize> Processor<N> {
&self.config,
&self.mouse,
self.modifiers,
self.search_state.regex.as_ref(),
&self.search_state,
);
}
});

View file

@ -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<i8>,
/// 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<GlyphCache, font::Error>
) -> Result<GlyphCache, crossfont::Error>
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<FontKey, font::Error> {
) -> Result<FontKey, crossfont::Error> {
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<font::Metrics, font::Error> {
let mut rasterizer = font::Rasterizer::new(dpr as f32, font.use_thin_strokes())?;
pub fn static_metrics(font: Font, dpr: f64) -> Result<crossfont::Metrics, crossfont::Error> {
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, &regular_desc, font.size)?;
rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size })?;

View file

@ -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;

View file

@ -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;

View file

@ -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<glutin::ContextError> for Error {
}
}
impl From<font::Error> for Error {
fn from(val: font::Error) -> Self {
impl From<crossfont::Error> 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");
}

View file

@ -1,6 +1,6 @@
[package]
name = "alacritty_terminal"
version = "0.5.0-rc1"
version = "0.10.0-rc2"
authors = ["Christian Duerr <contact@christianduerr.com>", "Joe Wilm <joe@jwilm.com>"]
license = "Apache-2.0"
description = "Library for writing terminal emulators"

View file

@ -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"

View file

@ -28,7 +28,7 @@ However, it does allow configuration of many aspects of the terminal.</p>
<url type="homepage">https://github.com/alacritty/alacritty</url>
<url type="bugtracker">https://github.com/alacritty/alacritty/issues</url>
<releases>
<release version="0.5.0-rc1" date="2019-06-16" unix_timestamp="1560694196"/>
<release version="0.5.0-rc2" date="2019-06-16" unix_timestamp="1560694196"/>
</releases>
<update_contact>https://github.com/alacritty/alacritty/blob/master/CONTRIBUTING.md#contact</update_contact>
<developer_name>Joe Wilm</developer_name>

View file

@ -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

View file

@ -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

View file

@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.5.0-rc1</string>
<string>0.5.0-rc2</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>

View file

@ -2,7 +2,7 @@
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<Product Name="Alacritty" Id="*" UpgradeCode="87c21c74-dbd5-4584-89d5-46d9cd0c40a7" Language="1033" Codepage="1252" Version="0.5.0-rc1" Manufacturer="Alacritty">
<Product Name="Alacritty" Id="*" UpgradeCode="87c21c74-dbd5-4584-89d5-46d9cd0c40a7" Language="1033" Codepage="1252" Version="0.5.0-rc2" Manufacturer="Alacritty">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine"/>
<MajorUpgrade AllowSameVersionUpgrades="yes" DowngradeErrorMessage="A newer version of [ProductName] is already installed."/>
<Icon Id="AlacrittyIco" SourceFile="..\extra\windows\alacritty.ico"/>

View file

@ -1,31 +0,0 @@
[package]
name = "font"
version = "0.1.0"
authors = ["Christian Duerr <contact@christianduerr.com>", "Joe Wilm <joe@jwilm.com>"]
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"]

View file

@ -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<u8> {
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<u8>) -> Vec<u8> {
bytes
}
#[cfg(target_endian = "little")]
pub fn extract_rgb(bytes: &[u8]) -> Vec<u8> {
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<u8>) -> Vec<u8> {
bytes
.into_iter()
.enumerate()
.filter(|&(index, _)| ((index) % 4) != 0)
.map(|(_, val)| val)
.collect::<Vec<_>>()
}

View file

@ -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<FontKey, Font>,
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<Rasterizer, Error> {
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<Metrics, Error> {
let font = self.fonts.get(&key).ok_or(Error::FontNotLoaded)?;
Ok(font.metrics())
}
fn load_font(&mut self, desc: &FontDesc, size: Size) -> Result<FontKey, Error> {
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<RasterizedGlyph, Error> {
// 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<Font, Error> {
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<Font, Error> {
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<Font, Error> {
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<Result<RasterizedGlyph, Error>> {
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<Font>,
}
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<String> {
// 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<Descriptor> {
// Convert language type &Vec<String> -> CFArray.
let langarr: CFArray<CFString> = {
let tmp: Vec<CFString> =
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<Descriptor>.
list.into_iter().map(|fontdesc| Descriptor::new(fontdesc.clone())).collect()
}
/// Get descriptors for family name.
pub fn descriptors_for_family(family: &str) -> Vec<Descriptor> {
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::<Vec<_>>();
// 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<f64, ()> {
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<RasterizedGlyph, Error> {
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<u32> {
// 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<u32> {
// 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::<Vec<_>>();
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!();
}
}
}
}
}

View file

@ -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<FontKey, Font>,
keys: HashMap<FontDesc, FontKey>,
device_pixel_ratio: f32,
available_fonts: FontCollection,
fallback_sequence: Option<FontFallback>,
}
impl DirectWriteRasterizer {
fn rasterize_glyph(
&self,
face: &FontFace,
size: Size,
c: char,
) -> Result<RasterizedGlyph, Error> {
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<u16, Error> {
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<dwrote::Font> {
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<DirectWriteRasterizer, Error> {
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<Metrics, Error> {
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<FontKey, Error> {
// 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<RasterizedGlyph, Error> {
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<dwrote::Font> 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<Weight> for FontWeight {
fn from(weight: Weight) -> FontWeight {
match weight {
Weight::Bold => FontWeight::Bold,
Weight::Normal => FontWeight::Regular,
}
}
}
impl From<Slant> 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<str>, u32) {
(Cow::Borrowed(self.locale), self.length)
}
fn get_paragraph_reading_direction(&self) -> dwrite::DWRITE_READING_DIRECTION {
dwrite::DWRITE_READING_DIRECTION_LEFT_TO_RIGHT
}
}

View file

@ -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<bool, ()> {
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)
}
}
}
}

View file

@ -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)
}
}
}

View file

@ -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<Self::Item> {
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)
}
}
}

View file

@ -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<Pattern> {
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<FontSet> {
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<FontSet> {
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<isize> 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<isize> 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!();
}
}
}

View file

@ -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");
}
}

View file

@ -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<bool> {
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<isize> {
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<Rgba> {
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<HintStyle> {
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<LcdFilter> {
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<f64> {
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<Self::Item> {
let res = self.get_value(self.index);
self.index += 1;
res
}
#[inline]
fn nth(&mut self, n: usize) -> Option<Self::Item> {
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<Self::Item> {
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::Item> {
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<Width> {
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<FcMatrix> {
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<PathBuf> {
unsafe { self.get_string(b"file\0").nth(index) }.map(From::from)
}
pub fn ft_face_location(&self, index: usize) -> Option<FTFaceLocation> {
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());
}
}
}

View file

@ -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<FallbackFont>,
coverage: CharSet,
}
struct FaceLoadingProperties {
load_flags: LoadFlag,
render_mode: freetype::RenderMode,
lcd_filter: c_uint,
non_scalable: Option<f32>,
colored: bool,
embolden: bool,
matrix: Option<Matrix>,
pixelsize_fixup_factor: Option<f64>,
ft_face: Rc<FTFace>,
}
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<FontKey, FaceLoadingProperties>,
ft_faces: HashMap<FTFaceLocation, Rc<FTFace>>,
fallback_lists: HashMap<FontKey, FallbackList>,
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<FreeTypeRasterizer, Error> {
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<Metrics, Error> {
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<FontKey, Error> {
self.get_face(desc, size)
}
fn get_glyph(&mut self, glyph_key: GlyphKey) -> Result<RasterizedGlyph, Error> {
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<FontKey, Error> {
// 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<FallbackFont> = 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<FullMetrics, Error> {
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<Rc<FTFace>, 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<Option<FontKey>, 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<FontKey, Error> {
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<FontKey, Error> {
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<RasterizedGlyph, Error> {
// 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<u8>, 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::<u8>::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<freetype::Error> for Error {
fn from(val: freetype::Error) -> Error {
Error::FreeType(val)
}
}
unsafe impl Send for FreeTypeRasterizer {}

View file

@ -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<S>(name: S, style: Style) -> FontDesc
where
S: Into<String>,
{
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<T: Into<Size>> Add<T> for Size {
type Output = Size;
fn add(self, other: T) -> Size {
Size(self.0.saturating_add(other.into().0))
}
}
impl<T: Into<Size>> Mul<T> for Size {
type Output = Size;
fn mul(self, other: T) -> Size {
Size(self.0 * other.into().0)
}
}
impl From<f32> 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<u8>),
/// RGBA pixels with premultiplied alpha.
RGBA(Vec<u8>),
}
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<Self, Self::Err>
where
Self: Sized;
/// Get `Metrics` for the given `FontKey`.
fn metrics(&self, _: FontKey, _: Size) -> Result<Metrics, Self::Err>;
/// Load the font described by `FontDesc` and `Size`.
fn load_font(&mut self, _: &FontDesc, _: Size) -> Result<FontKey, Self::Err>;
/// Rasterize the glyph described by `GlyphKey`..
fn get_glyph(&mut self, _: GlyphKey) -> Result<RasterizedGlyph, Self::Err>;
/// Update the Rasterizer's DPI factor.
fn update_dpr(&mut self, device_pixel_ratio: f32);
}