font_subset/
lib.rs

1//! OpenType font subsetting.
2//!
3//! This is a simple, no-std-compatible library that provides OpenType font *subsetting*, i.e.,
4//! retaining only glyphs and other related data that correspond to specific chars. The subset can then be
5//! saved in the OpenType (`.otf` / `.ttf`) or WOFF2 format.
6//!
7//! As an example, it is possible to subset visible ASCII chars (`' '..='~'`) from a font that originally supported
8//! multiple languages. Subsetting may lead to significant space savings; e.g., a subset of Roboto (the standard
9//! sans-serif font for Android) with visible ASCII chars occupies just 19 kB in the OpenType format
10//! (and 11 kB in the WOFF2 format) vs the original 457 kB.
11//!
12//! The motivating use case for this library is embedding the produced font as a data URL in HTML or SVG,
13//! so that it's guaranteed to be rendered in the same way across platforms.
14//!
15//! # Features
16//!
17//! - Can read and write fonts to/from OpenType and WOFF2 (the latter via an opt-in crate feature).
18//! - Supports [variable fonts]. This allows to embed a continuous set of fonts across one or more dimensions, such as font weight
19//!   or width, into a single file.
20//! - Provides general info about fonts, e.g., font naming / license info and variation axis parameters.
21//! - Single dependency for WOFF2 (de)compression; no-std compatible.
22//!
23//! # Design philosophy
24//!
25//! - **Keep parsing simple.** The library generally assumes that the input is well-formed, and defers parsing
26//!   when possible. For example, simple glyph data is not parsed at all because it's not necessary for subsetting;
27//!   it can be copied as opaque bytes.
28//! - **Keep focus.** The library is focused on subsetting. For example, it doesn't strive to parse all tables from
29//!   the OpenType spec.
30//! - **Keep dependencies lean.** The library has the only opt-in dependency ([`brotli`]) to support WOFF2 serialization.
31//!   The library is unconditionally no-std-compatible.
32//!
33//! # Known limitations
34//!
35//! - Subsetting drops advanced layout tables like `GPOS`, `kern` etc.
36//! - Some table data (e.g., `maxp` fields like "maximum points in a non-composite glyph") are not updated
37//!   in the subset font. Looks like some other subsetters (e.g., [`allsorts`]) do not update them either.
38//!
39//! # Alternatives and similar tools
40//!
41//! - [`allsorts`] is a library working with OpenType / WOFF / WOFF2 fonts, including
42//!   their subsetting. It's more versatile, but at the cost of having more deps and requiring
43//!   the standard library (although there is [a no-std fork](https://crates.io/crates/allsorts_no_std)).
44//!   Its subsetting logic also produces fonts not parseable by browsers because of a missing `OS/2` table.
45//! - [`subsetter`] is a specialized subsetting library. but it's geared towards PDF subsetting specifically
46//!   (i.e., again not covering the motivating SVG use case).
47//!
48//! # Crate features
49//!
50//! ## `std`
51//!
52//! *(On by default)*
53//!
54//! Enables `std`-specific functionality, such as `Error` trait implementations for error types.
55//!
56//! ## `woff2`
57//!
58//! *(Off by default)*
59//!
60//! Enables writing fonts in the WOFF2 format.
61//!
62//! ## `tracing`
63//!
64//! *(Off by default)*
65//!
66//! Enables logging / tracing via the `tracing` facade, mostly on the `DEBUG` and `TRACE` levels.
67//!
68//! [variable fonts]: https://learn.microsoft.com/en-us/typography/opentype/spec/otvaroverview
69//! [`brotli`]: https://crates.io/crates/brotli
70//! [`allsorts`]: https://crates.io/crates/allsorts
71//! [`subsetter`]: https://crates.io/crates/subsetter
72//!
73//! # Examples
74//!
75//! ## Subsetting
76//!
77//! ```
78//! # use std::collections::BTreeSet;
79//! use font_subset::{Font, FontReader};
80//!
81//! let font_bytes = // font in the OpenType or WOFF2 format
82//! # include_bytes!("../examples/FiraMono-Regular.ttf");
83//! // Create a font reader. This starts parsing and, in case of WOFF2,
84//! // decompresses the font data.
85//! let reader = FontReader::new(font_bytes)?;
86//! // Parse the font from the reader.
87//! let font: Font<'_> = reader.read()?;
88//! // Ensure that the font license permits embedding and subsetting.
89//! let permissions = font.permissions();
90//! assert!(permissions.embedding.is_lenient());
91//! assert!(permissions.allow_subsetting);
92//!
93//! let retained_chars: BTreeSet<char> = (' '..='~').collect();
94//! // Create a subset.
95//! let subset = font.subset(&retained_chars)?;
96//! // Serialize the subset in OpenType and WOFF2 formats.
97//! let ttf: Vec<u8> = subset.to_opentype();
98//! println!("OpenType size: {}", ttf.len());
99//! # assert!(ttf.len() < 20 * 1_024);
100//!
101//! let woff2: Vec<u8> = subset.to_woff2();
102//! println!("WOFF2 size: {}", woff2.len());
103//! # assert!(woff2.len() < 15 * 1_024);
104//! # Ok::<_, font_subset::ParseError>(())
105//! ```
106//!
107//! ## Getting info about font
108//!
109//! ```
110//! use font_subset::OpenTypeReader;
111//!
112//! let font_bytes = // font in the OpenType format
113//! # include_bytes!("../examples/RobotoMono-VariableFont_wght.ttf");
114//! let font_reader = OpenTypeReader::new(font_bytes)?;
115//! for (table, bytes) in font_reader.raw_tables() {
116//!     println!("{table} length: {} B", bytes.len());
117//! }
118//!
119//! // Parse the font to get table-specific info.
120//! let font = font_reader.read()?;
121//! println!("Name: {:?}", font.naming().family);
122//! println!("Subfamily: {:?}", font.naming().subfamily);
123//! println!("License: {:?}", font.naming().license);
124//! let covered_chars: Vec<_> = font.char_ranges().collect();
125//! println!("Covered chars: {covered_chars:?}");
126//! println!("Glyph count: {}", font.glyph_count());
127//!
128//! let variation_axes = font.variation_axes().unwrap_or(&[]);
129//! for axis in variation_axes {
130//!     println!("Variation axis: {:?} [{}]", axis.name, axis.tag);
131//!     println!(
132//!         "Range: {:?} (default: {})",
133//!         axis.min_value..=axis.max_value,
134//!         axis.default_value
135//!     );
136//! }
137//! # Ok::<_, font_subset::ParseError>(())
138//! ```
139
140#![cfg_attr(not(feature = "std"), no_std)]
141// Documentation settings.
142#![cfg_attr(docsrs, feature(doc_cfg))]
143#![doc(html_root_url = "https://docs.rs/font-subset/0.1.0")]
144
145#[macro_use]
146mod errors;
147#[macro_use]
148mod utils;
149mod font;
150mod subset;
151#[cfg(test)]
152mod testonly;
153mod write;
154
155mod alloc {
156    #[cfg(not(feature = "std"))]
157    extern crate alloc as std;
158
159    pub(crate) use std::{
160        borrow::Cow,
161        boxed::Box,
162        collections::{BTreeMap, BTreeSet},
163        format,
164        string::{String, ToString},
165        vec,
166        vec::Vec,
167    };
168}
169
170#[cfg(feature = "woff2")]
171pub use crate::font::Woff2Reader;
172pub use crate::{
173    errors::{ParseError, ParseErrorKind, Warning, WarningKind, Warnings},
174    font::{
175        EmbeddingPermissions, FileFormat, Fixed, Font, FontCategory, FontMetrics, FontNaming,
176        FontReader, LongDateTime, OpenTypeReader, OwnedFont, TableTag, UsagePermissions,
177        VariationAxis, VariationAxisTag,
178    },
179};
180
181#[cfg(doctest)]
182doc_comment::doctest!("../README.md");