1
#![warn(rust_2018_idioms)]
2
#![doc(html_logo_url = "https://github.com/yeslogic/allsorts/raw/master/allsorts.svg?sanitize=1")]
3
// Adds Cargo feature annotations to items in the rustdoc output. Nightly
4
// only, so behind a feature gate for now (which is enabled on docs.rs).
5
#![cfg_attr(docsrs, feature(doc_cfg))]
6

            
7
//! # Font parser, shaping engine, and subsetter
8
//!
9
//! Allsorts is a font parser, shaping engine, and subsetter for OpenType, WOFF, and WOFF2
10
//! written entirely in Rust. It was extracted from
11
//! [Prince](https://www.princexml.com/), a tool that typesets and lays out HTML
12
//! and CSS documents into PDF.
13
//!
14
//! The Allsorts shaping engine was developed in conjunction with [a specification
15
//! for OpenType shaping](https://github.com/n8willis/opentype-shaping-documents/),
16
//! which aims to specify OpenType font shaping behaviour.
17
//!
18
//! ## Features
19
//!
20
//! * **Parse** TrueType (`ttf`), OpenType (`otf`), WOFF, and WOFF2 files.
21
//! * **Shape** Arabic, Cyrillic, Greek, Hebrew, [Indic
22
//!   scripts](https://en.wikipedia.org/wiki/Languages_of_India) (Bengali,
23
//!   Devanagari, Gujarati, Gurmukhi, Kannada, Malayalam, Oriya, Sinhala, Tamil,
24
//!   Telugu), Khmer, Lao, Latin, Mongolian, Syriac, Thai, Tibetan, and other scripts.
25
//! * **Subset** from TrueType, OpenType, WOFF, and WOFF2 files into OpenType.
26
//!
27
//! ## What is font shaping?
28
//!
29
//! Font shaping is the process of taking text in the form of Unicode codepoints
30
//! and a font, and laying out glyphs from the font according to the text. This
31
//! involves honouring kerning, ligatures, and substitutions specified by the font.
32
//! For some languages this is relatively straightforward. For others, such as
33
//! Indic scripts it is quite complex. After shaping, another library such as
34
//! [Pathfinder](https://github.com/servo/pathfinder) or
35
//! [FreeType](https://www.freetype.org/) is responsible for rendering the glyphs.
36
//! To learn more about text rendering, Andrea Cognolato has a good [overview of
37
//! modern font rendering on
38
//! Linux](https://mrandri19.github.io/2019/07/24/modern-text-rendering-linux-overview.html).
39
//! The concepts remain similar on other platforms.
40
//!
41
//! ## Examples
42
//!
43
//! Refer to the [Allsorts Tools repository](https://github.com/yeslogic/allsorts-tools) for
44
//! a set of tools that exercise Allsorts font parsing, shaping, and subsetting.
45
//!
46
//! ## Unimplemented Features / Known Issues
47
//!
48
//! We don't currently support:
49
//!
50
//! * Unicode normalisation.
51
//!
52
//! Known limitations:
53
//!
54
//! * The crate is not extensively documented yet ([#5](https://github.com/yeslogic/allsorts/issues/5)).
55
//! * Allsorts does not do font lookup/matching. For this something like
56
//!   [font-kit](https://github.com/pcwalton/font-kit) is recommended.
57
//!
58
//! ## Development Status
59
//!
60
//! Allsorts is still under active development but reached its first release
61
//! milestone with its inclusion in Prince 13. In Prince it is responsible for
62
//! all font loading, and font shaping.
63
//!
64
//! Currently, the font parsing code is handwritten. It is planned for this to
65
//! eventually be replaced by machine generated code via our [declarative data
66
//! definition language project](https://github.com/yeslogic/fathom).
67
//!
68
//! ## Platform Support
69
//!
70
//! Allsorts CI runs tests on Linux, macOS, and Windows. Via Prince it is also
71
//! built for FreeBSD.
72
//!
73
//! ## Building and Testing
74
//!
75
//! **Minimum Supported Rust Version:** 1.66.0
76
//!
77
//! To build the crate ensure you have [Rust 1.66.0 or newer installed](https://www.rust-lang.org/tools/install).
78
//!
79
//! Build with `cargo build` and run the tests with `cargo test`.
80
//!
81
//! ### Cargo Features
82
//!
83
//! | Feature       | Description                              | Default Enabled | Extra Dependencies    |
84
//! |---------------|------------------------------------------|:---------------:|-----------------------|
85
//! | `outline`     | Enable code for accessing glyph outlines |        ✅       | `pathfinder_geometry` |
86
//! | `flate2_zlib` | Use the zlib backend to flate2           |        ✅       | `zlib`                |
87
//! | `flate2_rust` | Use the Rust backend to flate2           |        ❌       | `miniz_oxide`         |
88
//! | `prince`      | Enable Prince specific tests and code    |        ❌       |                       |
89
//!
90
//! **Note:** In our testing the `zlib` `flate2` backend was faster but you may
91
//! prefer the Rust backend for a pure Rust solution when compiling to WASM or
92
//! similar.
93
//!
94
//! ## Contributing
95
//!
96
//! Contributions are welcome, please refer to the
97
//! [contributing document](https://github.com/yeslogic/allsorts/blob/master/CONTRIBUTING.md)
98
//! for more details.
99
//!
100
//! ## Code of Conduct
101
//!
102
//! We aim to uphold the Rust community standards:
103
//!
104
//! > We are committed to providing a friendly, safe and welcoming environment for
105
//! > all, regardless of gender, sexual orientation, disability, ethnicity,
106
//! > religion, or similar personal characteristic.
107
//!
108
//! We follow the [Rust code of conduct](https://www.rust-lang.org/policies/code-of-conduct).
109
//!
110
//! ## Acknowledgements
111
//!
112
//! * [OpenType shaping documents](https://github.com/n8willis/opentype-shaping-documents/)
113
//!   forms the specification from which the shaping engine is implemented.
114
//! * [HarfBuzz](https://github.com/harfbuzz/harfbuzz) the widely used open source
115
//!   font shaping engine was used as reference for test output.
116
//! * The [Adobe Annotated OpenType Specification](https://github.com/adobe-type-tools/aots)
117
//!   test suite is used as part of the Allsorts test suite.
118
//! * [ttf-parser](https://github.com/RazrFalcon/ttf-parser) for CFF CharString parsing code.
119
//!
120
//! ## License
121
//!
122
//! Allsorts is distributed under the terms of the Apache License (Version 2.0).
123
//!
124
//! See [LICENSE](https://github.com/yeslogic/allsorts/blob/master/LICENSE) for details.
125

            
126
pub mod big5;
127
pub mod binary;
128
pub mod bitmap;
129
pub mod cff;
130
pub mod checksum;
131
mod crc32;
132
pub mod context;
133
pub mod error;
134
pub mod font;
135
pub mod font_data;
136
#[cfg(feature = "specimen")]
137
pub mod font_specimen;
138
pub mod gdef;
139
pub mod get_name;
140
pub mod hinting;
141
pub mod glyph_info;
142
pub mod glyph_position;
143
pub mod gpos;
144
pub mod gsub;
145
pub mod layout;
146
pub mod macroman;
147
pub mod outline;
148
pub mod post;
149
pub mod scripts;
150
pub mod size;
151
pub mod subset;
152
pub mod tables;
153
pub mod tag;
154
#[cfg(test)]
155
pub mod tests;
156
pub mod unicode;
157
pub mod variations;
158
pub mod woff;
159
pub mod woff2;
160

            
161
pub use font::Font;
162
pub use pathfinder_geometry;
163
pub use tinyvec;
164

            
165
pub const DOTTED_CIRCLE: char = '◌';
166
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
167

            
168
pub type GlyphId = u16;
169

            
170
#[macro_export]
171
macro_rules! read_table {
172
    ($source:expr, $tag:path, $t:ty, $index:expr) => {
173
        $source
174
            .read_table($tag, $index)?
175
            .ok_or(ParseError::MissingValue)?
176
            .scope()
177
            .read::<$t>()
178
    };
179
}
180

            
181
#[cfg(not(any(
182
    feature = "flate2_zlib",
183
    feature = "flate2_rust",
184
    feature = "flate2_zlib-rs"
185
)))]
186
compile_error!("Allsorts is being built without one of `flate2_zlib` or `flate2_rust` or `flate2_zlib-rs` Cargo features enabled. One of these must be enabled");
187

            
188
/// A trait for safe casting from u32 to usize
189
///
190
/// Rust doesn't implement `From<u32> for usize` because of 16-bit targets. They aren't supported
191
/// by Allsorts though, so this trait allows safe casting on 32-bit and greater platforms whilst
192
/// producing a compile time error on less than 32-bit targets.
193
pub(crate) trait SafeFrom<T>: Sized {
194
    /// A safe From impl for u32 into usize.
195
    fn safe_from(_: T) -> Self;
196
}
197

            
198
impl SafeFrom<u32> for usize {
199
    #[inline]
200
180684
    fn safe_from(v: u32) -> Self {
201
        #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))]
202
        {
203
180684
            v as usize
204
        }
205

            
206
        // Compiler error on 16-bit targets
207
180684
    }
208
}
209

            
210
/// Just like `TryFrom<N>`, but for numeric types not supported by the Rust's std.
211
pub(crate) trait TryNumFrom<T>: Sized {
212
    /// Casts between numeric types.
213
    fn try_num_from(_: T) -> Option<Self>;
214
}
215

            
216
impl TryNumFrom<f32> for u8 {
217
    fn try_num_from(v: f32) -> Option<Self> {
218
        i32::try_num_from(v).and_then(|v| u8::try_from(v).ok())
219
    }
220
}
221

            
222
impl TryNumFrom<f32> for i16 {
223
    fn try_num_from(v: f32) -> Option<Self> {
224
        i32::try_num_from(v).and_then(|v| i16::try_from(v).ok())
225
    }
226
}
227

            
228
impl TryNumFrom<f32> for u16 {
229
    fn try_num_from(v: f32) -> Option<Self> {
230
        i32::try_num_from(v).and_then(|v| u16::try_from(v).ok())
231
    }
232
}
233

            
234
impl TryNumFrom<f32> for i32 {
235
    fn try_num_from(v: f32) -> Option<Self> {
236
        // Based on https://github.com/rust-num/num-traits/blob/master/src/cast.rs
237

            
238
        // Float as int truncates toward zero, so we want to allow values
239
        // in the exclusive range `(MIN-1, MAX+1)`.
240

            
241
        // We can't represent `MIN-1` exactly, but there's no fractional part
242
        // at this magnitude, so we can just use a `MIN` inclusive boundary.
243
        const MIN: f32 = i32::MIN as f32;
244
        // We can't represent `MAX` exactly, but it will round up to exactly
245
        // `MAX+1` (a power of two) when we cast it.
246
        const MAX_P1: f32 = i32::MAX as f32;
247
        if (MIN..MAX_P1).contains(&v) {
248
            Some(v as i32)
249
        } else {
250
            None
251
        }
252
    }
253
}