crossfont-0.8.0/.builds/freebsd.yml000064400000000000000000000014001046102023000153410ustar 00000000000000image: freebsd/latest packages: - devel/cmake - devel/pkgconf - print/freetype2 - x11-fonts/fontconfig - x11-fonts/dejavu sources: - https://github.com/alacritty/crossfont environment: PATH: /home/build/.cargo/bin:/bin:/usr/bin:/usr/local/bin tasks: - rustup: | curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable --profile minimal - test: | cd crossfont cargo test - msrv: | cd crossfont msrv=$(cat Cargo.toml | grep "rust-version" | sed 's/.*"\(.*\)".*/\1/') $HOME/.cargo/bin/rustup toolchain install --profile minimal $msrv rm Cargo.lock $HOME/.cargo/bin/cargo +$msrv test - clippy: | cd crossfont rustup component add clippy cargo clippy --all-targets crossfont-0.8.0/.builds/linux.yml000064400000000000000000000015001046102023000150670ustar 00000000000000image: archlinux packages: - pkg-config - cmake - freetype2 - fontconfig - dina-font sources: - https://github.com/alacritty/crossfont environment: PATH: /home/build/.cargo/bin:/usr/bin/ tasks: - rustup: | curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable --profile minimal - test: | cd crossfont cargo test - rustfmt: | cd crossfont rustup toolchain install nightly -c rustfmt cargo +nightly fmt -- --check - msrv: | cd crossfont msrv=$(cat Cargo.toml | grep "rust-version" | sed 's/.*"\(.*\)".*/\1/') $HOME/.cargo/bin/rustup toolchain install --profile minimal $msrv rm Cargo.lock $HOME/.cargo/bin/cargo +$msrv test - clippy: | cd crossfont rustup component add clippy cargo clippy --all-targets crossfont-0.8.0/.cargo_vcs_info.json0000644000000001360000000000100130610ustar { "git": { "sha1": "169fb221dcc40978b63f8a10c7cdc5d10a8f7ecc" }, "path_in_vcs": "" }crossfont-0.8.0/.github/workflows/ci.yml000064400000000000000000000011001046102023000163540ustar 00000000000000name: CI on: [push, pull_request] env: CARGO_TERM_COLOR: always jobs: build: strategy: matrix: os: [windows-latest, macos-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 - name: stable run: cargo test - name: msrv shell: bash run: | msrv=$(cat Cargo.toml | grep "rust-version" | sed 's/.*"\(.*\)".*/\1/') rustup default $msrv cargo test - name: clippy run: | rustup component add clippy cargo clippy --all-targets crossfont-0.8.0/.gitignore000064400000000000000000000000231046102023000136340ustar 00000000000000/target Cargo.lock crossfont-0.8.0/CHANGELOG.md000064400000000000000000000055601046102023000134700ustar 00000000000000# Changelog All notable changes to crossfont are documented in this file. The sections should follow the order `Added`, `Changed`, `Fixed`, and `Removed`. 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.8.0 ### Changed - **Breaking** fontconfig system library provider changed to yeslogic-fontcontconfig-sys - **Breaking** freetype-rs bumped to 0.36.0 ### Fixed - On macOS, `AppleFontSmoothing` not recognized when specified as string ## 0.7.0 ### Changed - `Size::as_px` and `Size::from_px` now use `f32` type ## 0.6.0 ### Changed - `Size` now uses 6 floating point digits precision instead of rounding to 0.5 - Add `Size::from_px`, `Size::as_px`, `Size::as_pt`, and `Size::scale` - Remove `Rasterizer::update_dpr`; users should scale fonts themselves ## 0.5.2 - Minimum Rust version has been bumped to 1.65 ## 0.5.1 ### Fixed - Font size of scalable colored glyphs - macOS underline metrics being relative to descent and not baseline ## 0.5.0 ### Added - On macOS, use the `AppleFontSmoothing` user default to decide whether fonts should be "smoothed" ### Changed - Renamed `darwin::Rasterizer` to `darwin::CoreTextRasterizer` ### Fixed - On macOS, `use_thin_strokes` and `set_font_smoothing` did not work since Big Sur ### Removed - `use_thin_strokes` parameter from `Rasterize::new` trait method - `set_font_smoothing` from the `darwin` module - `get_family_names` from the `darwin` module ## 0.4.2 ### Fixed - Crash on macOS when loading disabled font ## 0.4.1 ### Fixed - Fix 32-bit build with FreeType/Fontconfig backend ## 0.4.0 ### Added - FreeType proportional font metrics using `RasterizedGlyph::advance` and `Rasterize::kerning` ### Changed - Minimum Rust version has been bumped to 1.56.0 ## 0.3.2 ### Changed - Minimum Rust version has been bumped to 1.46.0 - Core Text backend uses a current font as the original fallback font instead of Menlo ### Fixed - Core Text backend ignoring style for font fallback ## 0.3.1 ### Fixed - Fontconfig not checking for fonts installed after `Rasterizer` creation - Crash with non-utf8 font paths on Linux - Bitmap rendering with FreeType 2.11.0 ## 0.3.0 ### Changed - FreeType font height metric will now use `(ascent - descent)` if it is bigger than height - Several types have been renamed to comply with the `upper_case_acronyms` clippy lint ## 0.2.0 ### Changed - The rasterizer's `Error` type is now shared across platforms - Missing glyphs are now returned as the content of the `MissingGlyph` error - `RasterizedGlyph`'s `c` and `buf` fields are now named `character` and `buffer` respectively - `GlyphKey`'s `c` field is now named `character` ## 0.1.1 ### Changed - Minimum Rust version has been bumped to 1.43.0 ### Fixed - Compilation with FreeType version below 2.8.0 on Linux/BSD crossfont-0.8.0/Cargo.toml0000644000000040500000000000100110560ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.65.0" name = "crossfont" version = "0.8.0" authors = [ "Christian Duerr ", "Joe Wilm ", ] description = "Cross platform native font loading and rasterization" documentation = "https://docs.rs/crossfont" readme = "README.md" keywords = ["font"] categories = [ "gui", "os", ] license = "Apache-2.0" repository = "https://github.com/alacritty/crossfont.git" [dependencies.foreign-types] version = "0.5" [dependencies.libc] version = "0.2" [dependencies.log] version = "0.4" [target."cfg(not(any(target_os = \"macos\", windows)))".dependencies.freetype-rs] version = "0.36.0" [target."cfg(not(any(target_os = \"macos\", windows)))".dependencies.yeslogic-fontconfig-sys] version = "5.0.0" [target."cfg(not(any(target_os = \"macos\", windows)))".build-dependencies.pkg-config] version = "0.3" [target."cfg(target_os = \"macos\")".dependencies.cocoa] version = "0.25.0" [target."cfg(target_os = \"macos\")".dependencies.core-foundation] version = "0.9.3" [target."cfg(target_os = \"macos\")".dependencies.core-foundation-sys] version = "0.8.4" [target."cfg(target_os = \"macos\")".dependencies.core-graphics] version = "0.23.1" [target."cfg(target_os = \"macos\")".dependencies.core-text] version = "20.1.0" [target."cfg(target_os = \"macos\")".dependencies.objc] version = "0.2.7" [target."cfg(target_os = \"macos\")".dependencies.once_cell] version = "1.12" [target."cfg(windows)".dependencies.dwrote] version = "0.11" [target."cfg(windows)".dependencies.winapi] version = "0.3" features = ["impl-default"] crossfont-0.8.0/Cargo.toml.orig000064400000000000000000000020311046102023000145340ustar 00000000000000[package] name = "crossfont" version = "0.8.0" description = "Cross platform native font loading and rasterization" authors = ["Christian Duerr ", "Joe Wilm "] repository = "https://github.com/alacritty/crossfont.git" documentation = "https://docs.rs/crossfont" license = "Apache-2.0" readme = "README.md" categories = ["gui", "os"] keywords = ["font"] edition = "2021" rust-version = "1.65.0" [dependencies] libc = "0.2" foreign-types = "0.5" log = "0.4" [target.'cfg(not(any(target_os = "macos", windows)))'.dependencies] yeslogic-fontconfig-sys = "5.0.0" freetype-rs = "0.36.0" [target.'cfg(not(any(target_os = "macos", windows)))'.build-dependencies] pkg-config = "0.3" [target.'cfg(target_os = "macos")'.dependencies] cocoa = "0.25.0" core-foundation = "0.9.3" core-text = "20.1.0" core-graphics = "0.23.1" core-foundation-sys = "0.8.4" objc = "0.2.7" once_cell = "1.12" [target.'cfg(windows)'.dependencies] dwrote = { version = "0.11" } winapi = { version = "0.3", features = ["impl-default"] } crossfont-0.8.0/LICENSE000064400000000000000000000251331046102023000126620ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2020 The Alacritty Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. crossfont-0.8.0/README.md000064400000000000000000000012371046102023000131330ustar 00000000000000# crossfont crossfont is a cross-platform Rust library for loading fonts and rasterizing glyphs, using native font engines whenever possible. ### Supported Backends | Platform | Backends | |----------|-------------| | Linux | Freetype | | BSD | Freetype | | Windows | DirectWrite | | macOS | Core Text | ### Known Issues Since crossfont was originally made solely for rendering monospace fonts in [Alacritty](https://github.com/alacritty/alacritty), there currently is only very limited support for proportional fonts. Loading a lot of different fonts might also lead to resource leakage since they are not explicitly dropped from the cache. crossfont-0.8.0/build.rs000064400000000000000000000005261046102023000133210ustar 00000000000000fn main() { // This libtool version maps to FreeType version 2.8.0, so we can use // `FT_Set_Default_Properties`. #[cfg(not(any(target_os = "macos", windows)))] if pkg_config::Config::new().atleast_version("20.0.14").probe("freetype2").is_ok() { println!("cargo:rustc-cfg=ft_set_default_properties_available") } } crossfont-0.8.0/rustfmt.toml000064400000000000000000000005461046102023000142570ustar 00000000000000format_code_in_doc_comments = true match_block_trailing_comma = true condense_wildcard_suffixes = true use_field_init_shorthand = true overflow_delimited_expr = true use_small_heuristics = "Max" normalize_comments = true reorder_impl_items = true use_try_shorthand = true newline_style = "Unix" format_strings = true wrap_comments = true comment_width = 100 crossfont-0.8.0/src/darwin/byte_order.rs000064400000000000000000000026301046102023000164310ustar 00000000000000//! Constants for bitmap byte order. #![allow(non_upper_case_globals)] pub const kCGBitmapByteOrder32Little: u32 = 2 << 12; pub const kCGBitmapByteOrder32Big: u32 = 4 << 12; #[cfg(target_endian = "little")] pub const kCGBitmapByteOrder32Host: u32 = kCGBitmapByteOrder32Little; #[cfg(target_endian = "big")] pub const kCGBitmapByteOrder32Host: u32 = kCGBitmapByteOrder32Big; #[cfg(target_endian = "little")] pub fn extract_rgba(bytes: &[u8]) -> Vec { let pixels = bytes.len() / 4; let mut rgb = Vec::with_capacity(pixels * 4); for i in 0..pixels { let offset = i * 4; rgb.push(bytes[offset + 2]); rgb.push(bytes[offset + 1]); rgb.push(bytes[offset]); rgb.push(bytes[offset + 3]); } rgb } #[cfg(target_endian = "big")] pub fn extract_rgba(bytes: Vec) -> Vec { bytes } #[cfg(target_endian = "little")] pub fn extract_rgb(bytes: &[u8]) -> Vec { let pixels = bytes.len() / 4; let mut rgb = Vec::with_capacity(pixels * 3); for i in 0..pixels { let offset = i * 4; rgb.push(bytes[offset + 2]); rgb.push(bytes[offset + 1]); rgb.push(bytes[offset]); } rgb } #[cfg(target_endian = "big")] pub fn extract_rgb(bytes: Vec) -> Vec { bytes .into_iter() .enumerate() .filter(|&(index, _)| ((index) % 4) != 0) .map(|(_, val)| val) .collect::>() } crossfont-0.8.0/src/darwin/mod.rs000064400000000000000000000434501046102023000150570ustar 00000000000000//! Font rendering based on CoreText. use std::collections::HashMap; use std::ffi::c_char; use std::ffi::CStr; use std::iter; use std::path::PathBuf; use std::ptr; use cocoa::base::{id, nil}; use cocoa::foundation::{NSInteger, NSString, NSUserDefaults}; use core_foundation::array::{CFArray, CFIndex}; use core_foundation::base::{CFType, ItemRef, TCFType}; use core_foundation::number::{CFNumber, CFNumberRef}; 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::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, new_from_name, CTFont, }; use core_text::font_collection::create_for_family; use core_text::font_descriptor::{ self, kCTFontColorGlyphsTrait, kCTFontDefaultOrientation, kCTFontEnabledAttribute, CTFontDescriptor, SymbolicTraitAccessors, }; use log::{trace, warn}; use objc::rc::autoreleasepool; use objc::{class, msg_send, sel, sel_impl}; use once_cell::sync::Lazy; pub mod byte_order; use byte_order::kCGBitmapByteOrder32Host; use super::{ BitmapBuffer, Error, FontDesc, FontKey, GlyphKey, Metrics, RasterizedGlyph, Size, Slant, Style, Weight, }; /// According to the documentation, the index of 0 must be a missing glyph character: /// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM07/appendixB.html const MISSING_GLYPH_INDEX: u32 = 0; /// Font descriptor. /// /// The descriptor provides data about a font and supports creating a font. #[derive(Debug)] struct Descriptor { style_name: String, font_path: PathBuf, ct_descriptor: CTFontDescriptor, } impl Descriptor { fn new(desc: CTFontDescriptor) -> Descriptor { Descriptor { style_name: desc.style_name(), font_path: desc.font_path().unwrap_or_default(), ct_descriptor: desc, } } /// Create a Font from this descriptor. fn to_font(&self, size: f64, load_fallbacks: bool) -> Font { let ct_font = ct_new_from_descriptor(&self.ct_descriptor, size); let fallbacks = if load_fallbacks { // TODO fixme, hardcoded en for english. let mut fallbacks = cascade_list_for_languages(&ct_font, &["en".to_owned()]) .into_iter() .filter(|desc| !desc.font_path.as_os_str().is_empty()) .map(|desc| desc.to_font(size, false)) .collect::>(); // TODO, we can't use apple's proposed // .Apple Symbol Fallback (filtered out below), // but not having these makes us not able to render // many chars. We add the symbols back in. // Investigate if we can actually use the .-prefixed // fallbacks somehow. if let Ok(apple_symbols) = new_from_name("Apple Symbols", size) { fallbacks.push(Font { ct_font: apple_symbols, fallbacks: Vec::new() }) }; fallbacks } else { Vec::new() }; Font { ct_font, fallbacks } } } /// CoreTextRasterizer, the main type exported by this package. /// /// Given a fontdesc, can rasterize fonts. pub struct CoreTextRasterizer { fonts: HashMap, keys: HashMap<(FontDesc, Size), FontKey>, } impl crate::Rasterize for CoreTextRasterizer { fn new() -> Result { Ok(CoreTextRasterizer { fonts: HashMap::new(), keys: HashMap::new() }) } /// Get metrics for font specified by FontKey. fn metrics(&self, key: FontKey, _size: Size) -> Result { let font = self.fonts.get(&key).ok_or(Error::UnknownFontKey)?; Ok(font.metrics()) } fn load_font(&mut self, desc: &FontDesc, size: Size) -> Result { let size = Size::new(size.as_pt()); self.keys.get(&(desc.to_owned(), 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(), size), key); Ok(key) }) } /// Get rasterized glyph for given glyph key. fn get_glyph(&mut self, glyph: GlyphKey) -> Result { // Get loaded font. let font = self.fonts.get(&glyph.font_key).ok_or(Error::UnknownFontKey)?; // Find a font where the given character is present. let (font, glyph_index) = iter::once(font) .chain(font.fallbacks.iter()) .find_map(|font| match font.glyph_index(glyph.character) { MISSING_GLYPH_INDEX => None, glyph_index => Some((font, glyph_index)), }) .unwrap_or((font, MISSING_GLYPH_INDEX)); let glyph = font.get_glyph(glyph.character, glyph_index); if glyph_index == MISSING_GLYPH_INDEX { Err(Error::MissingGlyph(glyph)) } else { Ok(glyph) } } fn kerning(&mut self, _left: GlyphKey, _right: GlyphKey) -> (f32, f32) { (0., 0.) } } impl CoreTextRasterizer { fn get_specific_face( &mut self, desc: &FontDesc, style: &str, size: Size, ) -> Result { let descriptors = descriptors_for_family(&desc.name[..]); for descriptor in descriptors { if descriptor.style_name == style { // Found the font we want. let size = f64::from(size.as_pt()); let font = descriptor.to_font(size, true); return Ok(font); } } Err(Error::FontNotFound(desc.to_owned())) } fn get_matching_face( &mut self, desc: &FontDesc, slant: Slant, weight: Weight, size: Size, ) -> Result { let bold = weight == Weight::Bold; let italic = slant != Slant::Normal; let size = f64::from(size.as_pt()); let descriptors = descriptors_for_family(&desc.name[..]); for descriptor in descriptors { let font = descriptor.to_font(size, true); if font.is_bold() == bold && font.is_italic() == italic { // Found the font we want. return Ok(font); } } Err(Error::FontNotFound(desc.to_owned())) } fn get_font(&mut self, desc: &FontDesc, size: Size) -> Result { match desc.style { Style::Specific(ref style) => self.get_specific_face(desc, style, size), Style::Description { slant, weight } => { self.get_matching_face(desc, slant, weight, size) }, } } } /// Return fallback descriptors for font/language list. fn cascade_list_for_languages(ct_font: &CTFont, languages: &[String]) -> Vec { // Convert language type &Vec -> CFArray. let langarr: CFArray = { let tmp: Vec = languages.iter().map(|language| CFString::new(language)).collect(); CFArray::from_CFTypes(&tmp) }; // CFArray of CTFontDescriptorRef (again). let list = ct_cascade_list_for_languages(ct_font, &langarr); // Convert CFArray to Vec. list.into_iter().filter(is_enabled).map(|fontdesc| Descriptor::new(fontdesc.clone())).collect() } /// Check if a font is enabled. fn is_enabled(fontdesc: &ItemRef<'_, CTFontDescriptor>) -> bool { unsafe { let descriptor = fontdesc.as_concrete_TypeRef(); let attr_val = font_descriptor::CTFontDescriptorCopyAttribute(descriptor, kCTFontEnabledAttribute); if attr_val.is_null() { return false; } let attr_val = CFType::wrap_under_create_rule(attr_val); let attr_val = CFNumber::wrap_under_get_rule(attr_val.as_CFTypeRef() as CFNumberRef); attr_val.to_i32().unwrap_or(0) != 0 } } /// Get descriptors for family name. fn descriptors_for_family(family: &str) -> Vec { let mut out = Vec::new(); trace!("Family: {}", family); let ct_collection = create_for_family(family).unwrap_or_else(|| { // Fallback to Menlo if we can't find the config specified font family. warn!("Unable to load specified font {}, falling back to Menlo", &family); create_for_family("Menlo").expect("Menlo exists") }); // CFArray of CTFontDescriptorRef (i think). let descriptors = ct_collection.get_descriptors(); if let Some(descriptors) = descriptors { for descriptor in descriptors.iter() { out.push(Descriptor::new(descriptor.clone())); } } out } // The AppleFontSmoothing user default controls font smoothing on macOS, which increases the stroke // width. By default it is unset, and the system behaves as though it is set to 2, which means a // medium level of font smoothing. The valid values are integers from 0 to 3. Any other type, // including a boolean, does not change the behavior. The Core Graphics call we use only supports // enabling or disabling font smoothing, so we will treat an integer 0 as disabling it, and any // other integer, or a missing value (the default), or a value of any other type, as leaving it // enabled. static FONT_SMOOTHING_ENABLED: Lazy = Lazy::new(|| { autoreleasepool(|| unsafe { let key = NSString::alloc(nil).init_str("AppleFontSmoothing"); let value: id = msg_send![id::standardUserDefaults(), objectForKey: key]; if msg_send![value, isKindOfClass: class!(NSNumber)] { let num_type: *const c_char = msg_send![value, objCType]; if num_type.is_null() { return true; } // NSNumber's objCType method returns one of these strings depending on the size: // q = quad (long long), l = long, i = int, s = short. // This is done to reject booleans, which are NSNumbers with an objCType of "c", but // macOS does not treat them the same as an integer 0 or 1 for this setting, // it just ignores it. let int_specifiers: [&[u8]; 4] = [b"q", b"l", b"i", b"s"]; if !int_specifiers.contains(&CStr::from_ptr(num_type).to_bytes()) { return true; } let smoothing: NSInteger = msg_send![value, integerValue]; smoothing != 0 } else if msg_send![value, isKindOfClass: class!(NSString)] { let smoothing: NSInteger = msg_send![value, integerValue]; smoothing != 0 } else { true } }) }); /// A font. #[derive(Clone)] struct Font { ct_font: CTFont, fallbacks: Vec, } unsafe impl Send for Font {} impl Font { fn metrics(&self) -> Metrics { let average_advance = self.glyph_advance('0'); let ascent = self.ct_font.ascent().round(); let descent = self.ct_font.descent().round(); let leading = self.ct_font.leading().round(); let line_height = ascent + descent + leading; // Strikeout and underline metrics. // CoreText doesn't provide strikeout so we provide our own. let underline_position = self.ct_font.underline_position() 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, } } fn is_bold(&self) -> bool { self.ct_font.symbolic_traits().is_bold() } fn is_italic(&self) -> bool { self.ct_font.symbolic_traits().is_italic() } 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); let indices = [index as CGGlyph]; unsafe { self.ct_font.get_advances_for_glyphs( kCTFontDefaultOrientation, &indices[0], ptr::null_mut(), 1, ) } } fn get_glyph(&self, character: char, glyph_index: u32) -> RasterizedGlyph { let bounds = self .ct_font .get_bounding_rects_for_glyphs(kCTFontDefaultOrientation, &[glyph_index as CGGlyph]); 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 RasterizedGlyph { character: ' ', width: 0, height: 0, top: 0, left: 0, advance: (0, 0), buffer: 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); cg_context.set_allows_font_smoothing(true); cg_context.set_should_smooth_fonts(*FONT_SMOOTHING_ENABLED); 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 buffer = if is_colored { BitmapBuffer::Rgba(byte_order::extract_rgba(&rasterized_pixels)) } else { BitmapBuffer::Rgb(byte_order::extract_rgb(&rasterized_pixels)) }; RasterizedGlyph { character, left: rasterized_left, top: (bounds.size.height + bounds.origin.y).ceil() as i32, width: rasterized_width as i32, height: rasterized_height as i32, advance: (0, 0), buffer, } } fn glyph_index(&self, character: char) -> u32 { // Encode this char as utf-16. let mut buffer = [0; 2]; let encoded: &[u16] = character.encode_utf16(&mut buffer); // And use the utf-16 buffer to get the index. self.glyph_index_utf16(encoded) } fn glyph_index_utf16(&self, encoded: &[u16]) -> 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 { u32::from(glyphs[0]) } else { MISSING_GLYPH_INDEX } } } #[cfg(test)] mod tests { use super::BitmapBuffer; #[test] fn get_descriptors_and_build_font() { let list = super::descriptors_for_family("Menlo"); assert!(!list.is_empty()); println!("{:?}", list); // Check to_font. let fonts = list.iter().map(|desc| desc.to_font(72., false)).collect::>(); for font in fonts { // Get a glyph. for character in &['a', 'b', 'c', 'd'] { let glyph_index = font.glyph_index(*character); let glyph = font.get_glyph(*character, glyph_index); let buffer = match &glyph.buffer { BitmapBuffer::Rgb(buffer) | BitmapBuffer::Rgba(buffer) => buffer, }; // 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 = buffer[index]; let c = match value { 0..=50 => ' ', 51..=100 => '.', 101..=150 => '~', 151..=200 => '*', 201..=255 => '#', }; print!("{}", c); } println!(); } } } } } crossfont-0.8.0/src/directwrite/mod.rs000064400000000000000000000240521046102023000161150ustar 00000000000000//! Rasterization powered by DirectWrite. use std::borrow::Cow; use std::collections::HashMap; use std::ffi::OsString; 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, Error, FontDesc, FontKey, GlyphKey, Metrics, RasterizedGlyph, Size, Slant, Style, Weight, }; /// DirectWrite uses 0 for missing glyph symbols. /// https://docs.microsoft.com/en-us/typography/opentype/spec/recom#glyph-0-the-notdef-glyph const MISSING_GLYPH_INDEX: u16 = 0; /// Cached DirectWrite font. struct Font { face: FontFace, family_name: String, weight: FontWeight, style: FontStyle, stretch: FontStretch, } pub struct DirectWriteRasterizer { fonts: HashMap, keys: HashMap, available_fonts: FontCollection, fallback_sequence: Option, } impl DirectWriteRasterizer { fn rasterize_glyph( &self, face: &FontFace, size: Size, character: char, glyph_index: u16, ) -> Result { let em_size = size.as_px(); 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, 1., dwrote::DWRITE_MEASURING_MODE_NATURAL, ); let glyph_analysis = GlyphRunAnalysis::create( &glyph_run, 1., None, rendering_mode, dwrote::DWRITE_MEASURING_MODE_NATURAL, 0.0, 0.0, )?; let bounds = glyph_analysis.get_alpha_texture_bounds(dwrote::DWRITE_TEXTURE_CLEARTYPE_3x1)?; let buffer = BitmapBuffer::Rgb( glyph_analysis.create_alpha_texture(dwrote::DWRITE_TEXTURE_CLEARTYPE_3x1, bounds)?, ); Ok(RasterizedGlyph { character, width: (bounds.right - bounds.left) as i32, height: (bounds.bottom - bounds.top) as i32, top: -bounds.top, left: bounds.left, advance: (0, 0), buffer, }) } fn get_loaded_font(&self, font_key: FontKey) -> Result<&Font, Error> { self.fonts.get(&font_key).ok_or(Error::UnknownFontKey) } fn get_glyph_index(&self, face: &FontFace, character: char) -> u16 { face.get_glyph_indices(&[character as u32]).first().copied().unwrap_or(MISSING_GLYPH_INDEX) } fn get_fallback_font(&self, loaded_font: &Font, character: char) -> Option { let fallback = self.fallback_sequence.as_ref()?; let mut buffer = [0u16; 2]; character.encode_utf16(&mut buffer); let length = character.len_utf16() as u32; let utf16_codepoints = &buffer[..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 { fn new() -> Result { Ok(DirectWriteRasterizer { fonts: HashMap::new(), keys: HashMap::new(), available_fonts: FontCollection::system(), fallback_sequence: FontFallback::get_system_fallback(), }) } fn metrics(&self, key: FontKey, size: Size) -> Result { let face = &self.get_loaded_font(key)?.face; let vmetrics = face.metrics().metrics0(); let scale = size.as_px() / 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 character = '!'; let glyph_index = self.get_glyph_index(face, character); let glyph_metrics = face.get_design_glyph_metrics(&[glyph_index], false); let hmetrics = glyph_metrics.first().ok_or(Error::MetricsNotFound)?; let average_advance = f64::from(hmetrics.advanceWidth) * f64::from(scale); Ok(Metrics { descent, average_advance, line_height, underline_position, underline_thickness, strikeout_position, strikeout_thickness, }) } fn load_font(&mut self, desc: &FontDesc, _size: Size) -> Result { // Fast path if face is already loaded. if let Some(key) = self.keys.get(desc) { return Ok(*key); } let family = self .available_fonts .get_font_family_by_name(&desc.name) .ok_or_else(|| Error::FontNotFound(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::FontNotFound(desc.clone())); } let font = family.get_font(idx); if font.face_name() == *style { break Ok(font); } idx += 1; } }, }?; let key = FontKey::next(); self.keys.insert(desc.clone(), key); self.fonts.insert(key, font.into()); Ok(key) } fn get_glyph(&mut self, glyph: GlyphKey) -> Result { let loaded_font = self.get_loaded_font(glyph.font_key)?; let loaded_fallback_font; let mut font = loaded_font; let mut glyph_index = self.get_glyph_index(&loaded_font.face, glyph.character); if glyph_index == MISSING_GLYPH_INDEX { if let Some(fallback_font) = self.get_fallback_font(loaded_font, glyph.character) { loaded_fallback_font = Font::from(fallback_font); glyph_index = self.get_glyph_index(&loaded_fallback_font.face, glyph.character); font = &loaded_fallback_font; } } let rasterized_glyph = self.rasterize_glyph(&font.face, glyph.size, glyph.character, glyph_index)?; if glyph_index == MISSING_GLYPH_INDEX { Err(Error::MissingGlyph(rasterized_glyph)) } else { Ok(rasterized_glyph) } } fn kerning(&mut self, _left: GlyphKey, _right: GlyphKey) -> (f32, f32) { (0., 0.) } } impl From for Font { fn from(font: dwrote::Font) -> Font { Font { face: font.create_font_face(), family_name: font.family_name(), weight: font.weight(), style: font.style(), stretch: font.stretch(), } } } impl From for FontWeight { fn from(weight: Weight) -> FontWeight { match weight { Weight::Bold => FontWeight::Bold, Weight::Normal => FontWeight::Regular, } } } impl From for FontStyle { fn from(slant: Slant) -> FontStyle { match slant { Slant::Oblique => FontStyle::Oblique, Slant::Italic => FontStyle::Italic, Slant::Normal => FontStyle::Normal, } } } fn get_current_locale() -> String { let mut buffer = vec![0u16; LOCALE_NAME_MAX_LENGTH]; let len = unsafe { GetUserDefaultLocaleName(buffer.as_mut_ptr(), buffer.len() as i32) as usize }; // `len` includes null byte, which we don't need in Rust. OsString::from_wide(&buffer[..len - 1]).into_string().expect("Locale not valid unicode") } /// Font fallback information for dwrote's TextAnalysisSource. struct TextAnalysisSourceData<'a> { locale: &'a str, length: u32, } impl TextAnalysisSourceMethods for TextAnalysisSourceData<'_> { fn get_locale_name(&self, _text_position: u32) -> (Cow, u32) { (Cow::Borrowed(self.locale), self.length) } fn get_paragraph_reading_direction(&self) -> dwrite::DWRITE_READING_DIRECTION { dwrite::DWRITE_READING_DIRECTION_LEFT_TO_RIGHT } } impl From for Error { fn from(hresult: HRESULT) -> Self { let message = format!("a DirectWrite rendering error occurred: {:X}", hresult); Error::PlatformError(message) } } crossfont-0.8.0/src/ft/fc/char_set.rs000064400000000000000000000033011046102023000155740ustar 00000000000000use 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) { unsafe { // Value is just an indicator whether something was added or not. let mut value: FcBool = 0; FcCharSetMerge(self.as_ptr() as _, other.as_ptr() as _, &mut value); } } } crossfont-0.8.0/src/ft/fc/config.rs000064400000000000000000000014111046102023000152510ustar 00000000000000use 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) } } } crossfont-0.8.0/src/ft/fc/font_set.rs000064400000000000000000000040601046102023000156300ustar 00000000000000use std::ops::Deref; use std::ptr::NonNull; use foreign_types::{foreign_type, ForeignType, ForeignTypeRef}; use log::trace; use super::{ConfigRef, ObjectSetRef, PatternRef}; use super::ffi::{FcFontSet, FcFontSetDestroy, FcFontSetList}; foreign_type! { pub unsafe type FontSet { type CType = FcFontSet; fn drop = FcFontSetDestroy; } } impl FontSet { pub fn list( config: &ConfigRef, source: &mut FontSetRef, pattern: &PatternRef, objects: &ObjectSetRef, ) -> FontSet { let raw = unsafe { FcFontSetList( config.as_ptr(), &mut source.as_ptr(), 1, // nsets. pattern.as_ptr(), objects.as_ptr(), ) }; FontSet(NonNull::new(raw).unwrap()) } } /// Iterator over a font set. pub struct Iter<'a> { font_set: &'a FontSetRef, num_fonts: usize, current: usize, } impl<'a> IntoIterator for &'a FontSet { type IntoIter = Iter<'a>; type Item = &'a PatternRef; fn into_iter(self) -> Iter<'a> { let num_fonts = unsafe { (*self.as_ptr()).nfont as isize }; trace!("Number of fonts is {}", num_fonts); Iter { font_set: self.deref(), num_fonts: num_fonts as _, current: 0 } } } impl<'a> IntoIterator for &'a FontSetRef { type IntoIter = Iter<'a>; type Item = &'a PatternRef; fn into_iter(self) -> Iter<'a> { let num_fonts = unsafe { (*self.as_ptr()).nfont as isize }; trace!("Number of fonts is {}", num_fonts); Iter { font_set: self, num_fonts: num_fonts as _, current: 0 } } } impl<'a> Iterator for Iter<'a> { type Item = &'a PatternRef; fn next(&mut self) -> Option { if self.current == self.num_fonts { None } else { let pattern = unsafe { let ptr = *(*self.font_set.as_ptr()).fonts.add(self.current); PatternRef::from_ptr(ptr) }; self.current += 1; Some(pattern) } } } crossfont-0.8.0/src/ft/fc/mod.rs000064400000000000000000000222001046102023000145620ustar 00000000000000use std::fmt; use std::ptr; use foreign_types::{ForeignType, ForeignTypeRef}; use fontconfig_sys as ffi; use ffi::constants::{FC_SLANT_ITALIC, FC_SLANT_OBLIQUE, FC_SLANT_ROMAN}; use ffi::constants::{FC_WEIGHT_BLACK, FC_WEIGHT_BOLD, FC_WEIGHT_EXTRABLACK, FC_WEIGHT_EXTRABOLD}; use ffi::constants::{FC_WEIGHT_BOOK, FC_WEIGHT_MEDIUM, FC_WEIGHT_REGULAR, FC_WEIGHT_SEMIBOLD}; use ffi::constants::{FC_WEIGHT_EXTRALIGHT, FC_WEIGHT_LIGHT, FC_WEIGHT_THIN}; use ffi::FcInitBringUptoDate; use ffi::FcResultNoMatch; use ffi::{FcFontList, FcFontMatch, FcFontSort}; use ffi::{FcMatchFont, FcMatchPattern, FcMatchScan}; use ffi::{FcSetApplication, FcSetSystem}; pub mod config; pub use config::{Config, ConfigRef}; pub mod font_set; pub use font_set::{FontSet, FontSetRef}; pub mod object_set; pub use object_set::{ObjectSet, ObjectSetRef}; pub mod char_set; pub use char_set::{CharSet, CharSetRef}; pub mod pattern; pub use pattern::{FtFaceLocation, Pattern, PatternHash, PatternRef}; /// Find the font closest matching the provided pattern. /// /// The returned pattern is the result of Pattern::render_prepare. pub fn font_match(config: &ConfigRef, pattern: &PatternRef) -> Option { unsafe { // What is this result actually used for? Seems redundant with // return type. let mut result = FcResultNoMatch; let ptr = FcFontMatch(config.as_ptr(), pattern.as_ptr(), &mut result); if ptr.is_null() { None } else { Some(Pattern::from_ptr(ptr)) } } } /// Reloads the Fontconfig configuration files. pub fn update_config() { unsafe { let _ = FcInitBringUptoDate(); } } /// List fonts by closeness to the pattern. pub fn font_sort(config: &ConfigRef, pattern: &PatternRef) -> Option { unsafe { // What is this result actually used for? Seems redundant with // return type. let mut result = FcResultNoMatch; let mut charsets: *mut _ = ptr::null_mut(); let ptr = FcFontSort( config.as_ptr(), pattern.as_ptr(), 1, // Trim font list. &mut charsets, &mut result, ); if ptr.is_null() { None } else { Some(FontSet::from_ptr(ptr)) } } } /// List fonts matching pattern. pub fn font_list( config: &ConfigRef, pattern: &PatternRef, objects: &ObjectSetRef, ) -> Option { unsafe { let ptr = FcFontList(config.as_ptr(), pattern.as_ptr(), objects.as_ptr()); if ptr.is_null() { None } else { Some(FontSet::from_ptr(ptr)) } } } /// Available font sets. #[derive(Debug, Copy, Clone)] pub enum SetName { System = FcSetSystem as isize, Application = FcSetApplication as isize, } /// When matching, how to match. #[derive(Debug, Copy, Clone)] pub enum MatchKind { Font = FcMatchFont as isize, Pattern = FcMatchPattern as isize, Scan = FcMatchScan as isize, } #[derive(Debug, Copy, Clone)] pub enum Slant { Italic = FC_SLANT_ITALIC as isize, Oblique = FC_SLANT_OBLIQUE as isize, Roman = FC_SLANT_ROMAN as isize, } #[derive(Debug, Copy, Clone)] pub enum Weight { Thin = FC_WEIGHT_THIN as isize, Extralight = FC_WEIGHT_EXTRALIGHT as isize, Light = FC_WEIGHT_LIGHT as isize, Book = FC_WEIGHT_BOOK as isize, Regular = FC_WEIGHT_REGULAR as isize, Medium = FC_WEIGHT_MEDIUM as isize, Semibold = FC_WEIGHT_SEMIBOLD as isize, Bold = FC_WEIGHT_BOLD as isize, Extrabold = FC_WEIGHT_EXTRABOLD as isize, Black = FC_WEIGHT_BLACK as isize, Extrablack = FC_WEIGHT_EXTRABLACK as isize, } #[derive(Debug, Copy, Clone)] pub enum Width { Ultracondensed, Extracondensed, Condensed, Semicondensed, Normal, Semiexpanded, Expanded, Extraexpanded, Ultraexpanded, Other(i32), } impl Width { fn to_isize(self) -> isize { match self { Width::Ultracondensed => 50, Width::Extracondensed => 63, Width::Condensed => 75, Width::Semicondensed => 87, Width::Normal => 100, Width::Semiexpanded => 113, Width::Expanded => 125, Width::Extraexpanded => 150, Width::Ultraexpanded => 200, Width::Other(value) => value as isize, } } } impl From for Width { fn from(value: isize) -> Self { match value { 50 => Width::Ultracondensed, 63 => Width::Extracondensed, 75 => Width::Condensed, 87 => Width::Semicondensed, 100 => Width::Normal, 113 => Width::Semiexpanded, 125 => Width::Expanded, 150 => Width::Extraexpanded, 200 => Width::Ultraexpanded, _ => Width::Other(value as _), } } } /// Subpixel geometry. #[derive(Debug)] pub enum Rgba { Unknown, Rgb, Bgr, Vrgb, Vbgr, None, } impl Rgba { fn to_isize(&self) -> isize { match *self { Rgba::Unknown => 0, Rgba::Rgb => 1, Rgba::Bgr => 2, Rgba::Vrgb => 3, Rgba::Vbgr => 4, Rgba::None => 5, } } } impl fmt::Display for Rgba { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(match *self { Rgba::Unknown => "unknown", Rgba::Rgb => "rgb", Rgba::Bgr => "bgr", Rgba::Vrgb => "vrgb", Rgba::Vbgr => "vbgr", Rgba::None => "none", }) } } impl From for Rgba { fn from(val: isize) -> Rgba { match val { 1 => Rgba::Rgb, 2 => Rgba::Bgr, 3 => Rgba::Vrgb, 4 => Rgba::Vbgr, 5 => Rgba::None, _ => Rgba::Unknown, } } } /// Hinting Style. #[derive(Debug, Copy, Clone)] pub enum HintStyle { None, Slight, Medium, Full, } impl fmt::Display for HintStyle { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(match *self { HintStyle::None => "none", HintStyle::Slight => "slight", HintStyle::Medium => "medium", HintStyle::Full => "full", }) } } /// Lcd filter, used to reduce color fringing with subpixel rendering. pub enum LcdFilter { None, Default, Light, Legacy, } impl fmt::Display for LcdFilter { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(match *self { LcdFilter::None => "none", LcdFilter::Default => "default", LcdFilter::Light => "light", LcdFilter::Legacy => "legacy", }) } } #[cfg(test)] mod tests { use super::*; #[test] fn font_match() { let mut pattern = Pattern::new(); pattern.add_family("monospace"); pattern.add_style("regular"); let config = Config::get_current(); pattern.config_substitute(config, MatchKind::Pattern); pattern.default_substitute(); let font = super::font_match(config, &pattern).expect("match font monospace"); print!("index={:?}; ", font.index()); print!("family={:?}; ", font.family()); print!("style={:?}; ", font.style()); print!("antialias={:?}; ", font.antialias()); print!("autohint={:?}; ", font.autohint()); print!("hinting={:?}; ", font.hinting()); print!("rgba={:?}; ", font.rgba()); print!("embeddedbitmap={:?}; ", font.embeddedbitmap()); print!("lcdfilter={:?}; ", font.lcdfilter()); print!("hintstyle={:?}", font.hintstyle()); println!(); } #[test] fn font_sort() { let mut pattern = Pattern::new(); pattern.add_family("monospace"); pattern.set_slant(Slant::Italic); let config = Config::get_current(); pattern.config_substitute(config, MatchKind::Pattern); pattern.default_substitute(); let fonts = super::font_sort(config, &pattern).expect("sort font monospace"); for font in fonts.into_iter().take(10) { let font = pattern.render_prepare(config, font); print!("index={:?}; ", font.index()); print!("family={:?}; ", font.family()); print!("style={:?}; ", font.style()); print!("rgba={:?}", font.rgba()); print!("rgba={:?}", font.rgba()); println!(); } } #[test] fn font_sort_with_glyph() { let mut charset = CharSet::new(); charset.add('💖'); let mut pattern = Pattern::new(); pattern.add_charset(&charset); drop(charset); let config = Config::get_current(); pattern.config_substitute(config, MatchKind::Pattern); pattern.default_substitute(); let fonts = super::font_sort(config, &pattern).expect("font_sort"); for font in fonts.into_iter().take(10) { let font = pattern.render_prepare(config, font); print!("index={:?}; ", font.index()); print!("family={:?}; ", font.family()); print!("style={:?}; ", font.style()); print!("rgba={:?}", font.rgba()); println!(); } } } crossfont-0.8.0/src/ft/fc/object_set.rs000064400000000000000000000016671046102023000161420ustar 00000000000000use 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"); } } crossfont-0.8.0/src/ft/fc/pattern.rs000064400000000000000000000430171046102023000154710ustar 00000000000000use 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? unsafe { mem::transmute(CStr::from_ptr(value as *const c_char).to_str().ok()?) } } else { None } } } /// Iterator over integer properties. pub struct BooleanPropertyIter<'a> { pattern: &'a PatternRef, object: &'a [u8], index: usize, } impl<'a> BooleanPropertyIter<'a> { fn new<'b>(pattern: &'b PatternRef, object: &'b [u8]) -> BooleanPropertyIter<'b> { BooleanPropertyIter { pattern, object, index: 0 } } fn get_value(&self, index: usize) -> Option { let mut value: FcBool = 0; let result = unsafe { FcPatternGetBool( self.pattern.as_ptr(), self.object.as_ptr() as *mut c_char, index as c_int, &mut value, ) }; if result == FcResultMatch { Some(value != 0) } else { None } } } /// Iterator over integer properties. pub struct IntPropertyIter<'a> { pattern: &'a PatternRef, object: &'a [u8], index: usize, } impl<'a> IntPropertyIter<'a> { fn new<'b>(pattern: &'b PatternRef, object: &'b [u8]) -> IntPropertyIter<'b> { IntPropertyIter { pattern, object, index: 0 } } fn get_value(&self, index: usize) -> Option { let mut value: c_int = 0; let result = unsafe { FcPatternGetInteger( self.pattern.as_ptr(), self.object.as_ptr() as *mut c_char, index as c_int, &mut value, ) }; if result == FcResultMatch { Some(value as isize) } else { None } } } pub struct RgbaPropertyIter<'a> { inner: IntPropertyIter<'a>, } impl<'a> RgbaPropertyIter<'a> { fn new<'b>(pattern: &'b PatternRef, object: &'b [u8]) -> RgbaPropertyIter<'b> { RgbaPropertyIter { inner: IntPropertyIter::new(pattern, object) } } #[inline] fn inner<'b>(&'b mut self) -> &'b mut IntPropertyIter<'a> { &mut self.inner } fn get_value(&self, index: usize) -> Option { self.inner.get_value(index).map(Rgba::from) } } pub struct HintStylePropertyIter<'a> { inner: IntPropertyIter<'a>, } impl<'a> HintStylePropertyIter<'a> { fn new(pattern: &PatternRef) -> HintStylePropertyIter { HintStylePropertyIter { inner: IntPropertyIter::new(pattern, b"hintstyle\0") } } #[inline] fn inner<'b>(&'b mut self) -> &'b mut IntPropertyIter<'a> { &mut self.inner } fn get_value(&self, index: usize) -> Option { self.inner.get_value(index).and_then(|hint_style| { Some(match hint_style { 0 => HintStyle::None, 1 => HintStyle::Slight, 2 => HintStyle::Medium, 3 => HintStyle::Full, _ => return None, }) }) } } pub struct LcdFilterPropertyIter<'a> { inner: IntPropertyIter<'a>, } impl<'a> LcdFilterPropertyIter<'a> { fn new(pattern: &PatternRef) -> LcdFilterPropertyIter { LcdFilterPropertyIter { inner: IntPropertyIter::new(pattern, b"lcdfilter\0") } } #[inline] fn inner<'b>(&'b mut self) -> &'b mut IntPropertyIter<'a> { &mut self.inner } fn get_value(&self, index: usize) -> Option { self.inner.get_value(index).and_then(|hint_style| { Some(match hint_style { 0 => LcdFilter::None, 1 => LcdFilter::Default, 2 => LcdFilter::Light, 3 => LcdFilter::Legacy, _ => return None, }) }) } } /// Iterator over integer properties. pub struct DoublePropertyIter<'a> { pattern: &'a PatternRef, object: &'a [u8], index: usize, } impl<'a> DoublePropertyIter<'a> { fn new<'b>(pattern: &'b PatternRef, object: &'b [u8]) -> DoublePropertyIter<'b> { DoublePropertyIter { pattern, object, index: 0 } } fn get_value(&self, index: usize) -> Option { let mut value = f64::from(0); let result = unsafe { FcPatternGetDouble( self.pattern.as_ptr(), self.object.as_ptr() as *mut c_char, index as c_int, &mut value, ) }; if result == FcResultMatch { Some(value) } else { None } } } /// Implement debug for a property iterator. macro_rules! impl_property_iter_debug { ($iter:ty => $item:ty) => { impl<'a> fmt::Debug for $iter { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "[")?; for i in 0.. { match self.get_value(i) { Some(val) => { if i > 0 { write!(f, ", {}", val)?; } else { write!(f, "{}", val)?; } }, _ => break, } } write!(f, "]") } } }; } /// Implement Iterator and Debug for a property iterator. macro_rules! impl_property_iter { ($($iter:ty => $item:ty),*) => { $( impl<'a> Iterator for $iter { type Item = $item; fn next(&mut self) -> Option { let res = self.get_value(self.index); self.index += 1; res } #[inline] fn nth(&mut self, n: usize) -> Option { self.index += n; self.next() } } impl_property_iter_debug!($iter => $item); )* } } /// Implement Iterator and Debug for a property iterator which internally relies /// on another property iterator. macro_rules! impl_derived_property_iter { ($($iter:ty => $item:ty),*) => { $( impl<'a> Iterator for $iter { type Item = $item; fn next(&mut self) -> Option { let index = { self.inner().index }; let res = self.get_value(index); self.inner().index += 1; res } #[inline] fn nth(&mut self, n: usize) -> Option { self.inner().index += n; self.next() } } impl_property_iter_debug!($iter => $item); )* } } // Basic Iterators. impl_property_iter! { StringPropertyIter<'a> => &'a str, IntPropertyIter<'a> => isize, DoublePropertyIter<'a> => f64, BooleanPropertyIter<'a> => bool } // Derived Iterators. impl_derived_property_iter! { RgbaPropertyIter<'a> => Rgba, HintStylePropertyIter<'a> => HintStyle, LcdFilterPropertyIter<'a> => LcdFilter } foreign_type! { pub unsafe type Pattern { type CType = FcPattern; fn drop = FcPatternDestroy; fn clone = FcPatternDuplicate; } } macro_rules! string_accessor { ($([$getter:ident, $setter:ident] => $object_name:expr),*) => { $( #[inline] pub fn $setter(&mut self, value: &str) -> bool { unsafe { self.add_string($object_name, value) } } #[inline] pub fn $getter(&self) -> StringPropertyIter { unsafe { self.get_string($object_name) } } )* } } #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct PatternHash(pub u32); #[derive(Hash, Eq, PartialEq, Debug)] pub struct FtFaceLocation { pub path: PathBuf, pub index: isize, } impl FtFaceLocation { pub fn new(path: PathBuf, index: isize) -> Self { Self { path, index } } } impl Pattern { pub fn new() -> Self { Self::default() } } impl Default for Pattern { fn default() -> Self { Pattern(unsafe { NonNull::new(FcPatternCreate()).unwrap() }) } } macro_rules! pattern_get_integer { ($($method:ident() => $property:expr),+) => { $( pub fn $method(&self) -> IntPropertyIter { unsafe { self.get_integer($property) } } )+ }; } macro_rules! boolean_getter { ($($method:ident() => $property:expr),*) => { $( pub fn $method(&self) -> BooleanPropertyIter { unsafe { self.get_boolean($property) } } )* } } macro_rules! double_getter { ($($method:ident() => $property:expr),*) => { $( pub fn $method(&self) -> DoublePropertyIter { unsafe { self.get_double($property) } } )* } } impl PatternRef { boolean_getter! { antialias() => b"antialias\0", hinting() => b"hinting\0", verticallayout() => b"verticallayout\0", autohint() => b"autohint\0", globaladvance() => b"globaladvance\0", scalable() => b"scalable\0", symbol() => b"symbol\0", color() => b"color\0", minspace() => b"minspace\0", embolden() => b"embolden\0", embeddedbitmap() => b"embeddedbitmap\0", decorative() => b"decorative\0" } double_getter! { size() => b"size\0", aspect() => b"aspect\0", pixelsize() => b"pixelsize\0", pixelsizefixupfactor() => b"pixelsizefixupfactor\0", scale() => b"scale\0", dpi() => b"dpi\0" } string_accessor! { [family, add_family] => b"family\0", [familylang, add_familylang] => b"familylang\0", [style, add_style] => b"style\0", [stylelang, add_stylelang] => b"stylelang\0", [fullname, add_fullname] => b"fullname\0", [fullnamelang, add_fullnamelang] => b"fullnamelang\0", [foundry, add_foundry] => b"foundry\0", [capability, add_capability] => b"capability\0", [fontformat, add_fontformat] => b"fontformat\0", [fontfeatures, add_fontfeatures] => b"fontfeatures\0", [namelang, add_namelang] => b"namelang\0", [postscriptname, add_postscriptname] => b"postscriptname\0" } pattern_get_integer! { index() => b"index\0" } /// Prints the pattern to stdout. /// /// FontConfig doesn't expose a way to iterate over all members of a pattern; /// instead, we just defer to FcPatternPrint. Otherwise, this could have been /// a `fmt::Debug` impl. pub fn print(&self) { unsafe { FcPatternPrint(self.as_ptr()) } } /// Add a string value to the pattern. /// /// If the returned value is `true`, the value is added at the end of /// any existing list, otherwise it is inserted at the beginning. /// /// # Unsafety /// /// `object` is not checked to be a valid null-terminated string. unsafe fn add_string(&mut self, object: &[u8], value: &str) -> bool { let value = CString::new(value).unwrap(); let value = value.as_ptr(); FcPatternAddString(self.as_ptr(), object.as_ptr() as *mut c_char, value as *mut FcChar8) == 1 } unsafe fn add_integer(&self, object: &[u8], int: isize) -> bool { FcPatternAddInteger(self.as_ptr(), object.as_ptr() as *mut c_char, int as c_int) == 1 } unsafe fn add_double(&self, object: &[u8], value: f64) -> bool { FcPatternAddDouble(self.as_ptr(), object.as_ptr() as *mut c_char, value as c_double) == 1 } unsafe fn get_string<'a>(&'a self, object: &'a [u8]) -> StringPropertyIter<'a> { StringPropertyIter::new(self, object) } unsafe fn get_integer<'a>(&'a self, object: &'a [u8]) -> IntPropertyIter<'a> { IntPropertyIter::new(self, object) } unsafe fn get_double<'a>(&'a self, object: &'a [u8]) -> DoublePropertyIter<'a> { DoublePropertyIter::new(self, object) } unsafe fn get_boolean<'a>(&'a self, object: &'a [u8]) -> BooleanPropertyIter<'a> { BooleanPropertyIter::new(self, object) } pub fn hintstyle(&self) -> HintStylePropertyIter { HintStylePropertyIter::new(self) } pub fn lcdfilter(&self) -> LcdFilterPropertyIter { LcdFilterPropertyIter::new(self) } pub fn set_slant(&mut self, slant: Slant) -> bool { unsafe { self.add_integer(b"slant\0", slant as isize) } } pub fn add_pixelsize(&mut self, size: f64) -> bool { unsafe { self.add_double(b"pixelsize\0", size) } } pub fn set_weight(&mut self, weight: Weight) -> bool { unsafe { self.add_integer(b"weight\0", weight as isize) } } pub fn set_width(&mut self, width: Width) -> bool { unsafe { self.add_integer(b"width\0", width.to_isize()) } } pub fn get_width(&self) -> Option { unsafe { self.get_integer(b"width\0").next().map(Width::from) } } pub fn rgba(&self) -> RgbaPropertyIter { RgbaPropertyIter::new(self, b"rgba\0") } pub fn set_rgba(&self, rgba: &Rgba) -> bool { unsafe { self.add_integer(b"rgba\0", rgba.to_isize()) } } pub fn render_prepare(&self, config: &ConfigRef, request: &PatternRef) -> Pattern { unsafe { let ptr = FcFontRenderPrepare(config.as_ptr(), self.as_ptr(), request.as_ptr()); Pattern::from_ptr(ptr) } } pub fn hash(&self) -> PatternHash { unsafe { PatternHash(FcPatternHash(self.as_ptr())) } } /// Add charset to the pattern. /// /// The referenced charset is copied by Fontconfig internally using /// FcValueSave so that no references to application provided memory are /// retained. That is, the CharSet can be safely dropped immediately /// after being added to the pattern. pub fn add_charset(&self, charset: &CharSetRef) -> bool { unsafe { FcPatternAddCharSet( self.as_ptr(), b"charset\0".as_ptr() as *mut c_char, charset.as_ptr(), ) == 1 } } /// Get charset from the pattern. pub fn get_charset(&self) -> Option<&CharSetRef> { unsafe { let mut charset = ptr::null_mut(); let result = FcPatternGetCharSet( self.as_ptr(), b"charset\0".as_ptr() as *mut c_char, 0, &mut charset, ); if result == FcResultMatch { Some(&*(charset as *const CharSetRef)) } else { None } } } /// Get matrix from the pattern. pub fn get_matrix(&self) -> Option { unsafe { let mut matrix = ptr::null_mut(); let result = FcPatternGetMatrix( self.as_ptr(), b"matrix\0".as_ptr() as *mut c_char, 0, &mut matrix, ); if result == FcResultMatch { Some(*matrix) } else { None } } } pub fn file(&self, index: usize) -> Option { unsafe { self.get_string(b"file\0").nth(index) }.map(From::from) } pub fn ft_face_location(&self, index: usize) -> Option { match (self.file(index), self.index().next()) { (Some(path), Some(index)) => Some(FtFaceLocation::new(path, index)), _ => None, } } pub fn config_substitute(&mut self, config: &ConfigRef, kind: MatchKind) { unsafe { FcConfigSubstitute(config.as_ptr(), self.as_ptr(), kind as u32); } } pub fn default_substitute(&mut self) { unsafe { FcDefaultSubstitute(self.as_ptr()); } } } crossfont-0.8.0/src/ft/mod.rs000064400000000000000000000760741046102023000142140ustar 00000000000000//! Rasterization powered by FreeType and Fontconfig. use std::cmp::{min, Ordering}; use std::collections::HashMap; use std::fmt::{self, Formatter}; use std::rc::Rc; use std::time::{Duration, Instant}; 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, Rgba}; use super::{ BitmapBuffer, Error, FontDesc, FontKey, GlyphKey, Metrics, Rasterize, RasterizedGlyph, Size, Slant, Style, Weight, }; /// FreeType uses 0 for the missing glyph: /// https://freetype.org/freetype2/docs/reference/ft2-base_interface.html#ft_get_char_index const MISSING_GLYPH_INDEX: u32 = 0; /// Delay before font config reload after creating the `Rasterizer`. const RELOAD_DELAY: Duration = Duration::from_secs(2); struct FallbackFont { pattern: Pattern, key: FontKey, } impl FallbackFont { fn new(pattern: Pattern, key: FontKey) -> FallbackFont { Self { pattern, key } } } impl FontKey { fn from_pattern_hashes(lhs: PatternHash, rhs: PatternHash) -> Self { // XOR two hashes to get a font ID. Self { token: lhs.0.rotate_left(1) ^ rhs.0 } } } #[derive(Default)] struct FallbackList { list: Vec, coverage: CharSet, } struct FaceLoadingProperties { load_flags: LoadFlag, render_mode: freetype::RenderMode, lcd_filter: c_uint, non_scalable: Option, colored_bitmap: bool, embolden: bool, matrix: Option, pixelsize_fixup_factor: Option, ft_face: Rc, rgba: Rgba, } 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", freetype::RenderMode::Sdf => "Sdf", }) .field("lcd_filter", &self.lcd_filter) .finish() } } /// Rasterizes glyphs for a single font face. pub struct FreeTypeRasterizer { loader: FreeTypeLoader, fallback_lists: HashMap, /// Rasterizer creation time stamp to delay lazy font config updates /// in `Rasterizer::load_font`. creation_timestamp: Option, } #[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 } #[inline] fn from_freetype_26_6(f: impl IntoF32) -> f32 { f.into_f32() / 64. } trait IntoF32 { fn into_f32(self) -> f32; } impl IntoF32 for f32 { fn into_f32(self) -> f32 { self } } impl IntoF32 for i32 { fn into_f32(self) -> f32 { self as f32 } } impl IntoF32 for i64 { fn into_f32(self) -> f32 { self as f32 } } impl Rasterize for FreeTypeRasterizer { fn new() -> Result { Ok(FreeTypeRasterizer { loader: FreeTypeLoader::new()?, fallback_lists: HashMap::new(), creation_timestamp: Some(Instant::now()), }) } fn metrics(&self, key: FontKey, _size: Size) -> Result { let face = &mut self.loader.faces.get(&key).ok_or(Error::UnknownFontKey)?; let full = self.full_metrics(face)?; let ascent = from_freetype_26_6(full.size_metrics.ascender); let descent = from_freetype_26_6(full.size_metrics.descender); let glyph_height = from_freetype_26_6(full.size_metrics.height) as f64; let global_glyph_height = (ascent - descent) as f64; let height = f64::max(glyph_height, global_glyph_height); // Get underline position and thickness in device pixels. let x_scale = full.size_metrics.x_scale as f32 / 65536.0; let ft_underline_position = face.ft_face.underline_position(); let mut underline_position = from_freetype_26_6(ft_underline_position as f32 * x_scale); let ft_underline_thickness = face.ft_face.underline_thickness(); let mut underline_thickness = from_freetype_26_6(ft_underline_thickness as f32 * x_scale); // 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) => ( from_freetype_26_6(os2.y_strikeout_position() as f32 * x_scale), from_freetype_26_6(os2.y_strikeout_size() as f32 * x_scale), ), _ => { // 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 { if self.creation_timestamp.map_or(true, |timestamp| timestamp.elapsed() > RELOAD_DELAY) { self.creation_timestamp = None; fc::update_config(); } self.get_face(desc, size) } fn get_glyph(&mut self, glyph_key: GlyphKey) -> Result { let font_key = self.face_for_glyph(glyph_key); let face = &self.loader.faces[&font_key]; let index = face.ft_face.get_char_index(glyph_key.character as usize).unwrap_or_default(); let pixelsize = face.non_scalable.unwrap_or_else(|| glyph_key.size.as_px()); if !face.colored_bitmap { face.ft_face.set_char_size(to_freetype_26_6(pixelsize), 0, 0, 0)?; } unsafe { let ft_lib = self.loader.library.raw(); freetype::ffi::FT_Library_SetLcdFilter(ft_lib, face.lcd_filter); } face.ft_face.load_glyph(index, 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); } } let advance = unsafe { // Transform glyphs with the matrix from Fontconfig. Primarily used to generate italics. let raw_glyph = face.ft_face.raw().glyph; if let Some(matrix) = face.matrix.as_ref() { // Check that the glyph is a vectorial outline, not a bitmap. if (*raw_glyph).format == freetype_sys::FT_GLYPH_FORMAT_OUTLINE { let outline = &(*raw_glyph).outline; freetype_sys::FT_Outline_Transform(outline, matrix); } } // Don't render bitmap glyphs, it results in error with freestype 2.11.0. if (*raw_glyph).format != freetype_sys::FT_GLYPH_FORMAT_BITMAP { glyph.render_glyph(face.render_mode)?; } let advance = (*raw_glyph).advance; (from_freetype_26_6(advance.x) as i32, from_freetype_26_6(advance.y) as i32) }; let (pixel_height, pixel_width, buffer) = Self::normalize_buffer(&glyph.bitmap(), &face.rgba)?; let mut rasterized_glyph = RasterizedGlyph { character: glyph_key.character, top: glyph.bitmap_top(), left: glyph.bitmap_left(), width: pixel_width, height: pixel_height, advance, buffer, }; if index == MISSING_GLYPH_INDEX { return Err(Error::MissingGlyph(rasterized_glyph)); } if face.colored_bitmap { let fixup_factor = match face.pixelsize_fixup_factor { Some(fixup_factor) => fixup_factor, None => { // Fallback if the user has bitmap scaling disabled. let metrics = face.ft_face.size_metrics().ok_or(Error::MetricsNotFound)?; f64::from(pixelsize) / f64::from(metrics.y_ppem) }, }; // Scale glyph advance. rasterized_glyph.advance.0 = (advance.0 as f64 * fixup_factor).round() as i32; rasterized_glyph.advance.1 = (advance.1 as f64 * fixup_factor).round() as i32; rasterized_glyph = downsample_bitmap(rasterized_glyph, fixup_factor); } Ok(rasterized_glyph) } fn kerning(&mut self, left: GlyphKey, right: GlyphKey) -> (f32, f32) { let font_key = self.face_for_glyph(left); let mut ft_face = (*self.loader.faces[&font_key].ft_face).clone(); if !freetype_sys::FT_HAS_KERNING(ft_face.raw_mut()) { return (0., 0.); } let left = ft_face.get_char_index(left.character as usize).unwrap_or_default(); let right = ft_face.get_char_index(right.character as usize).unwrap_or_default(); let mut kerning = freetype_sys::FT_Vector::default(); let mode = freetype_sys::FT_KERNING_DEFAULT; unsafe { freetype_sys::FT_Get_Kerning(ft_face.raw_mut(), left, right, mode, &mut kerning); } (from_freetype_26_6(kerning.x), from_freetype_26_6(kerning.y)) } } impl From for fc::Slant { fn from(slant: Slant) -> Self { match slant { Slant::Normal => fc::Slant::Roman, Slant::Italic => fc::Slant::Italic, Slant::Oblique => fc::Slant::Oblique, } } } impl From for fc::Weight { fn from(weight: Weight) -> Self { match weight { Weight::Normal => fc::Weight::Regular, Weight::Bold => fc::Weight::Bold, } } } struct FullMetrics { size_metrics: freetype::ffi::FT_Size_Metrics, cell_width: f64, } impl FreeTypeRasterizer { /// Load a font face according to `FontDesc`. fn get_face(&mut self, desc: &FontDesc, size: Size) -> Result { // Adjust for DPR. let size = f64::from(size.as_px()); 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()); pattern.set_slant(slant.into()); }, 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::FontNotFound(desc.to_owned()))?; let mut matched_fonts = matched_fonts.into_iter(); let primary_font = matched_fonts.next().ok_or_else(|| Error::FontNotFound(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.loader.faces.contains_key(&primary_font_key) { self.loader .face_from_pattern(&primary_font, primary_font_key) .and_then(|pattern| pattern.ok_or_else(|| Error::FontNotFound(desc.to_owned())))?; } // Coverage for fallback fonts. let coverage = CharSet::new(); let empty_charset = CharSet::new(); let list: Vec = matched_fonts .map(|fallback_font| { let charset = fallback_font.get_charset().unwrap_or(&empty_charset); // Use original pattern to preserve loading flags. let fallback_font = pattern.render_prepare(config, fallback_font); let fallback_font_key = FontKey::from_pattern_hashes(hash, fallback_font.hash()); coverage.merge(charset); FallbackFont::new(fallback_font, fallback_font_key) }) .collect(); self.fallback_lists.insert(primary_font_key, FallbackList { list, coverage }); Ok(primary_font_key) } fn full_metrics(&self, face_load_props: &FaceLoadingProperties) -> Result { let ft_face = &face_load_props.ft_face; let size_metrics = ft_face.size_metrics().ok_or(Error::MetricsNotFound)?; let width = match ft_face.load_char('0' as usize, face_load_props.load_flags) { Ok(_) => from_freetype_26_6(ft_face.glyph().metrics().horiAdvance), Err(_) => from_freetype_26_6(size_metrics.max_advance), }; Ok(FullMetrics { size_metrics, cell_width: width as f64 }) } fn face_for_glyph(&mut self, glyph_key: GlyphKey) -> FontKey { if let Some(face) = self.loader.faces.get(&glyph_key.font_key) { if face.ft_face.get_char_index(glyph_key.character as usize).is_some() { return glyph_key.font_key; } } self.load_face_with_glyph(glyph_key).unwrap_or(glyph_key.font_key) } fn load_face_with_glyph(&mut self, glyph: GlyphKey) -> Result { let fallback_list = self.fallback_lists.get(&glyph.font_key).unwrap(); // Check whether glyph is presented in any fallback font. if !fallback_list.coverage.has_char(glyph.character) { 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.loader.faces.get(&font_key) { Some(face) => { // We found something in a current face, so let's use it. if face.ft_face.get_char_index(glyph.character as usize).is_some() { return Ok(font_key); } }, None => { if !font_pattern.get_charset().map_or(false, |cs| cs.has_char(glyph.character)) { continue; } let pattern = font_pattern.clone(); if let Some(key) = self.loader.face_from_pattern(&pattern, font_key)? { return Ok(key); } }, } } // You can hit this return, if you're failing to get charset from a pattern. Ok(glyph.font_key) } /// 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, rgba: &Rgba, ) -> 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().unsigned_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; match rgba { Rgba::Bgr => { for j in (start..stop).step_by(3) { packed.push(buf[j + 2]); packed.push(buf[j + 1]); packed.push(buf[j]); } }, _ => 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 k = match rgba { Rgba::Vbgr => 2 - k, _ => k, }; let offset = ((i as usize) * 3 + k) * pitch + (j as usize); packed.push(buf[offset]); } } } Ok((bitmap.rows() / 3, bitmap.width(), BitmapBuffer::Rgb(packed))) }, // Mono data is stored in a packed format using 1 bit per pixel. PixelMode::Mono => { fn unpack_byte(res: &mut Vec, byte: u8, mut count: u8) { // Mono stores MSBit at top of byte let mut bit = 7; while count != 0 { let value = ((byte >> bit) & 1) * 255; // Push value 3x since result buffer should be 1 byte // per channel. res.push(value); res.push(value); res.push(value); count -= 1; bit -= 1; } } for i in 0..(bitmap.rows() as usize) { let mut columns = bitmap.width(); let mut byte = 0; let offset = i * bitmap.pitch().unsigned_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.buffer, fixup_factor.partial_cmp(&1.0)) { (BitmapBuffer::Rgba(buffer), Some(Ordering::Less)) => buffer, _ => return bitmap_glyph, }; let bitmap_width = bitmap_glyph.width as usize; let bitmap_height = bitmap_glyph.height as usize; let target_width = (bitmap_width as f64 * fixup_factor) as usize; let target_height = (bitmap_height as f64 * fixup_factor) as usize; // Number of pixels in the input buffer, per pixel in the output buffer. let downsampling_step = 1.0 / fixup_factor; let mut downsampled_buffer = Vec::::with_capacity(target_width * target_height * 4); for line_index in 0..target_height { // Get the first and last line which will be consolidated in the current output pixel. let line_index = line_index as f64; let source_line_start = (line_index * downsampling_step).round() as usize; let source_line_end = ((line_index + 1.) * downsampling_step).round() as usize; for column_index in 0..target_width { // Get the first and last column which will be consolidated in the current output // pixel. let column_index = column_index as f64; let source_column_start = (column_index * downsampling_step).round() as usize; let source_column_end = ((column_index + 1.) * downsampling_step).round() as usize; let (mut r, mut g, mut b, mut a) = (0u32, 0u32, 0u32, 0u32); let mut pixels_picked: u32 = 0; // Consolidate all pixels within the source rectangle into a single averaged pixel. for source_line in source_line_start..source_line_end { let source_pixel_index = source_line * bitmap_width; for source_column in source_column_start..source_column_end { let offset = (source_pixel_index + source_column) * 4; r += u32::from(bitmap_buffer[offset]); g += u32::from(bitmap_buffer[offset + 1]); b += u32::from(bitmap_buffer[offset + 2]); a += u32::from(bitmap_buffer[offset + 3]); pixels_picked += 1; } } // Add a single pixel to the output buffer for the downscaled source rectangle. downsampled_buffer.push((r / pixels_picked) as u8); downsampled_buffer.push((g / pixels_picked) as u8); downsampled_buffer.push((b / pixels_picked) as u8); downsampled_buffer.push((a / pixels_picked) as u8); } } bitmap_glyph.buffer = 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 } impl From for Error { fn from(val: freetype::Error) -> Error { Error::PlatformError(val.to_string()) } } unsafe impl Send for FreeTypeRasterizer {} struct FreeTypeLoader { library: Library, faces: HashMap, ft_faces: HashMap>, } impl FreeTypeLoader { fn new() -> Result { let library = Library::init()?; #[cfg(ft_set_default_properties_available)] unsafe { // Initialize default properties, like user preferred interpreter. freetype_sys::FT_Set_Default_Properties(library.raw()); }; Ok(FreeTypeLoader { library, faces: HashMap::new(), ft_faces: HashMap::new() }) } fn load_ft_face(&mut self, ft_face_location: FtFaceLocation) -> Result, Error> { let mut ft_face = self.library.new_face(&ft_face_location.path, ft_face_location.index)?; if ft_face.has_color() && !ft_face.is_scalable() { unsafe { // Select the colored bitmap size to use from the array of available sizes. freetype_sys::FT_Select_Size(ft_face.raw_mut(), 0); } } let ft_face = Rc::new(ft_face); self.ft_faces.insert(ft_face_location, Rc::clone(&ft_face)); Ok(ft_face) } fn face_from_pattern( &mut self, pattern: &PatternRef, font_key: FontKey, ) -> Result, Error> { if let Some(ft_face_location) = pattern.ft_face_location(0) { if self.faces.get(&font_key).is_some() { return Ok(Some(font_key)); } trace!("Got font path={:?}, index={:?}", ft_face_location.path, ft_face_location.index); let ft_face = match self.ft_faces.get(&ft_face_location) { Some(ft_face) => Rc::clone(ft_face), None => self.load_ft_face(ft_face_location)?, }; let non_scalable = if pattern.scalable().next().unwrap_or(true) { None } else { Some(pattern.pixelsize().next().expect("has 1+ pixelsize") as f32) }; let embolden = pattern.embolden().next().unwrap_or(false); let matrix = pattern.get_matrix().map(|matrix| { // Convert Fontconfig matrix to FreeType matrix. let xx = to_fixedpoint_16_6(matrix.xx); let xy = to_fixedpoint_16_6(matrix.xy); let yx = to_fixedpoint_16_6(matrix.yx); let yy = to_fixedpoint_16_6(matrix.yy); Matrix { xx, xy, yx, yy } }); let pixelsize_fixup_factor = pattern.pixelsizefixupfactor().next(); let rgba = pattern.rgba().next().unwrap_or(Rgba::Unknown); 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_bitmap: ft_face.has_color() && !ft_face.is_scalable(), embolden, matrix, pixelsize_fixup_factor, ft_face, rgba, }; debug!("Loaded Face {:?}", face); self.faces.insert(font_key, face); Ok(Some(font_key)) } else { Ok(None) } } 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(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, Rgba::Rgb) | (true, fc::HintStyle::Full, Rgba::Bgr) => { LoadFlag::TARGET_LCD }, (true, fc::HintStyle::Full, Rgba::Vrgb) | (true, fc::HintStyle::Full, Rgba::Vbgr) => { LoadFlag::TARGET_LCD_V }, // For non-rgba modes with Full hinting, just use the default hinting algorithm. (true, fc::HintStyle::Full, Rgba::Unknown) | (true, fc::HintStyle::Full, 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(Rgba::Unknown); match (antialias, rgba) { (false, _) => freetype::RenderMode::Mono, (_, Rgba::Rgb) | (_, Rgba::Bgr) => freetype::RenderMode::Lcd, (_, Rgba::Vrgb) | (_, 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, } } } crossfont-0.8.0/src/lib.rs000064400000000000000000000144261046102023000135630ustar 00000000000000//! Compatibility layer for different font engines. //! //! CoreText is used on macOS. //! DirectWrite is used on Windows. //! FreeType is used everywhere else. #![deny(clippy::all, clippy::if_not_else, clippy::enum_glob_use)] use std::fmt::{self, Display, Formatter}; use std::sync::atomic::{AtomicUsize, Ordering}; #[cfg(not(any(target_os = "macos", windows)))] pub mod ft; #[cfg(not(any(target_os = "macos", windows)))] pub use ft::FreeTypeRasterizer as Rasterizer; #[cfg(windows)] pub mod directwrite; #[cfg(windows)] pub use directwrite::DirectWriteRasterizer as Rasterizer; #[cfg(target_os = "macos")] pub mod darwin; #[cfg(target_os = "macos")] pub use darwin::CoreTextRasterizer as Rasterizer; /// Max font size in pt. /// /// The value is picked based on `u32` max, since we use 6 digits for fract. const MAX_FONT_PT_SIZE: f32 = 3999.; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct FontDesc { name: String, style: Style, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Slant { Normal, Italic, Oblique, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Weight { Normal, Bold, } /// Style of font. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Style { Specific(String), Description { slant: Slant, weight: Weight }, } impl fmt::Display for Style { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Style::Specific(ref s) => f.write_str(s), Style::Description { slant, weight } => { write!(f, "slant={:?}, weight={:?}", slant, weight) }, } } } impl FontDesc { pub fn new(name: S, style: Style) -> FontDesc where S: Into, { FontDesc { name: name.into(), style } } } impl fmt::Display for FontDesc { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} - {}", self.name, self.style) } } /// Identifier for a Font for use in maps/etc. #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub struct FontKey { token: u32, } impl FontKey { /// Get next font key for given size. /// /// The generated key will be globally unique. pub fn next() -> FontKey { static TOKEN: AtomicUsize = AtomicUsize::new(0); FontKey { token: TOKEN.fetch_add(1, Ordering::SeqCst) as _ } } } #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct GlyphKey { pub character: char, pub font_key: FontKey, pub size: Size, } /// Font size stored as base and fraction. #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Size(u32); impl Size { /// Create a new `Size` from a f32 size in points. /// /// The font size is automatically clamped to supported range of `[1.; 3999.]` pt. pub fn new(size: f32) -> Size { let size = size.clamp(1., MAX_FONT_PT_SIZE); Size((size * Self::factor()) as u32) } /// Create a new `Size` from px. /// /// The value will be clamped to the pt range of [`Size::new`]. pub fn from_px(size: f32) -> Self { let pt = size * 72. / 96.; Size::new(pt) } /// Scale font size by the given amount. pub fn scale(self, scale: f32) -> Self { Self::new(self.as_pt() * scale) } /// Get size in `px`. pub fn as_px(self) -> f32 { self.as_pt() * 96. / 72. } /// Get the size in `pt`. pub fn as_pt(self) -> f32 { (f64::from(self.0) / Size::factor() as f64) as f32 } /// Scale factor between font "Size" type and point size. #[inline] fn factor() -> f32 { 1_000_000. } } #[derive(Debug, Clone)] pub struct RasterizedGlyph { pub character: char, pub width: i32, pub height: i32, pub top: i32, pub left: i32, pub advance: (i32, i32), pub buffer: BitmapBuffer, } #[derive(Clone, Debug)] pub enum BitmapBuffer { /// RGB alphamask. Rgb(Vec), /// RGBA pixels with premultiplied alpha. Rgba(Vec), } impl Default for RasterizedGlyph { fn default() -> RasterizedGlyph { RasterizedGlyph { character: ' ', width: 0, height: 0, top: 0, left: 0, advance: (0, 0), buffer: BitmapBuffer::Rgb(Vec::new()), } } } #[derive(Debug, 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, } /// Errors occuring when using the rasterizer. #[derive(Debug)] pub enum Error { /// Unable to find a font matching the description. FontNotFound(FontDesc), /// Unable to find metrics for a font face. MetricsNotFound, /// The glyph could not be found in any font. MissingGlyph(RasterizedGlyph), /// Requested an operation with a FontKey that isn't known to the rasterizer. UnknownFontKey, /// Error from platfrom's font system. PlatformError(String), } impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } } impl Display for Error { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { Error::FontNotFound(font) => write!(f, "font {:?} not found", font), Error::MissingGlyph(glyph) => { write!(f, "glyph for character {:?} not found", glyph.character) }, Error::UnknownFontKey => f.write_str("invalid font key"), Error::MetricsNotFound => f.write_str("metrics not found"), Error::PlatformError(err) => write!(f, "{}", err), } } } pub trait Rasterize { /// Create a new Rasterizer. fn new() -> Result where Self: Sized; /// Get `Metrics` for the given `FontKey`. fn metrics(&self, _: FontKey, _: Size) -> Result; /// Load the font described by `FontDesc` and `Size`. fn load_font(&mut self, _: &FontDesc, _: Size) -> Result; /// Rasterize the glyph described by `GlyphKey`.. fn get_glyph(&mut self, _: GlyphKey) -> Result; /// Kerning between two characters. fn kerning(&mut self, left: GlyphKey, right: GlyphKey) -> (f32, f32); }