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:
parent
fa79758f56
commit
714bbb769e
35 changed files with 172 additions and 3394 deletions
|
@ -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
|
||||
|
|
3
.github/pull_request_template.md
vendored
3
.github/pull_request_template.md
vendored
|
@ -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.
|
||||
|
|
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## 0.5.0-rc2
|
||||
|
||||
### Added
|
||||
|
||||
- Separate search label for backward search
|
||||
|
||||
### Fixed
|
||||
|
||||
- Selection tracking in search without vi mode
|
||||
- Crash when resizing below search label length
|
||||
|
||||
## 0.5.0-rc1
|
||||
|
||||
### Packaging
|
||||
|
|
|
@ -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
48
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
members = [
|
||||
"alacritty",
|
||||
"alacritty_terminal",
|
||||
"font",
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::fmt;
|
||||
|
||||
use font::Size;
|
||||
use crossfont::Size;
|
||||
use log::error;
|
||||
use serde::de::Visitor;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(®ex));
|
||||
let visual_bell_intensity = terminal.visual_bell.intensity();
|
||||
let background_color = terminal.background_color();
|
||||
let cursor_point = terminal.grid().cursor.point;
|
||||
let metrics = self.glyph_cache.font_metrics();
|
||||
let glyph_cache = &mut self.glyph_cache;
|
||||
let size_info = self.size_info;
|
||||
|
@ -474,20 +472,6 @@ impl Display {
|
|||
None
|
||||
};
|
||||
|
||||
// Update IME position.
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
let point = match &search_regex {
|
||||
Some(regex) => {
|
||||
let column = min(regex.len() + SEARCH_LABEL.len() - 1, terminal.cols().0 - 1);
|
||||
Point::new(terminal.screen_lines() - 1, Column(column))
|
||||
},
|
||||
None => terminal.grid().cursor.point,
|
||||
};
|
||||
|
||||
self.window.update_ime_position(point, &self.size_info);
|
||||
}
|
||||
|
||||
// Drop terminal as early as possible to free lock.
|
||||
drop(terminal);
|
||||
|
||||
|
@ -592,9 +576,30 @@ impl Display {
|
|||
self.renderer.draw_rects(&size_info, rects);
|
||||
}
|
||||
|
||||
self.draw_search(config, &size_info, message_bar_lines, search_regex);
|
||||
self.draw_render_timer(config, &size_info);
|
||||
|
||||
// Handle search and IME positioning.
|
||||
let ime_position = match search_state.regex() {
|
||||
Some(regex) => {
|
||||
let search_label = match search_state.direction() {
|
||||
Direction::Right => FORWARD_SEARCH_LABEL,
|
||||
Direction::Left => BACKWARD_SEARCH_LABEL,
|
||||
};
|
||||
|
||||
let search_text = Self::format_search(&size_info, regex, search_label);
|
||||
|
||||
// Render the search bar.
|
||||
self.draw_search(config, &size_info, message_bar_lines, &search_text);
|
||||
|
||||
// Compute IME position.
|
||||
Point::new(size_info.lines() - 1, Column(search_text.len() - 1))
|
||||
},
|
||||
None => cursor_point,
|
||||
};
|
||||
|
||||
// Update IME position.
|
||||
self.window.update_ime_position(ime_position, &self.size_info);
|
||||
|
||||
// Frame event should be requested before swaping buffers, since it requires surface
|
||||
// `commit`, which is done by swap buffers under the hood.
|
||||
#[cfg(not(any(target_os = "macos", windows)))]
|
||||
|
@ -616,20 +621,33 @@ impl Display {
|
|||
}
|
||||
|
||||
/// Format search regex to account for the cursor and fullwidth characters.
|
||||
fn format_search(search_regex: &str) -> String {
|
||||
fn format_search(size_info: &SizeInfo, search_regex: &str, search_label: &str) -> String {
|
||||
// Add spacers for wide chars.
|
||||
let mut text = String::with_capacity(search_regex.len());
|
||||
let mut formatted_regex = String::with_capacity(search_regex.len());
|
||||
for c in search_regex.chars() {
|
||||
text.push(c);
|
||||
formatted_regex.push(c);
|
||||
if c.width() == Some(2) {
|
||||
text.push(' ');
|
||||
formatted_regex.push(' ');
|
||||
}
|
||||
}
|
||||
|
||||
// Add cursor to show whitespace.
|
||||
text.push('_');
|
||||
formatted_regex.push('_');
|
||||
|
||||
text
|
||||
// Truncate beginning of the search regex if it exceeds the viewport width.
|
||||
let num_cols = size_info.cols().0;
|
||||
let label_len = search_label.len();
|
||||
let regex_len = formatted_regex.len();
|
||||
let truncate_len = min((regex_len + label_len).saturating_sub(num_cols), regex_len);
|
||||
let truncated_regex = &formatted_regex[truncate_len..];
|
||||
|
||||
// Add search label to the beginning of the search regex.
|
||||
let mut bar_text = format!("{}{}", search_label, truncated_regex);
|
||||
|
||||
// Make sure the label alone doesn't exceed the viewport width.
|
||||
bar_text.truncate(num_cols);
|
||||
|
||||
bar_text
|
||||
}
|
||||
|
||||
/// Draw current search regex.
|
||||
|
@ -638,25 +656,13 @@ impl Display {
|
|||
config: &Config,
|
||||
size_info: &SizeInfo,
|
||||
message_bar_lines: usize,
|
||||
search_regex: Option<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);
|
||||
(
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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, ®ular_desc, font.size)?;
|
||||
rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size })?;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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"]
|
|
@ -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<_>>()
|
||||
}
|
|
@ -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!();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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!();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 {}
|
226
font/src/lib.rs
226
font/src/lib.rs
|
@ -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);
|
||||
}
|
Loading…
Reference in a new issue