write_fonts/
lib.rs

1//! Writing and modifying OpenType tables
2//!
3//! This crate provides a collection of types correlating to what is described
4//! in the [OpenType spec][spec], along with the logic to serialize these types
5//! into binary font tables. It is a companion to [`read-fonts`], which provides
6//! efficient zero-allocation parsing of these types. It is intended to be used
7//! as the basis for font engineering tools such as compilers.
8//!
9//! ## 'write' versus 'read' types
10//!
11//! Both `write-fonts` and [`read-fonts`] make heavy use of code generation, and
12//! they have a similar structure, where a `tables` module contains a submodule for
13//! each supported [table][table-directory], and that module contains items for
14//! each table, record, flagset or enum described in the spec. This means that
15//! there are (for instance) two distinct `ValueRecord` types, one defined in
16//! `read_fonts::tables::gpos`, and one defined in `write_fonts::tables::gpos`.
17//!
18//! The reason for the distinct types is that it allows us to dramatically
19//! simplify the scope of `read-fonts`; the types in that crate are generally
20//! just typed views into raw slices of bytes and cannot be modified, whereas
21//! the types in `write-fonts` are generally familiar Rust structs and enums.
22//!
23//! ## Loading and modifying fonts
24//!
25//! When modifying a font, you will typically start by reading the font data
26//! using `read-fonts`. When you come to write the font out, however, you will
27//! quickly discover that tables generated by `read-fonts` have different
28//! types to those required by `write-fonts`. This is because `read-fonts` types
29//! borrow their data from an underlying backing store, but `write-fonts` types
30//! need to own their data. In order to modify a table parsed by `read-fonts`,
31//! you will need to convert it to a `write-fonts` type. This can be done with
32//! the `FromTableRef` and `ToOwnedTable` traits: bring these traits into scope
33//! and call `.to_owned_table()` or `.to_owned_obj()` on the table or subtable
34//! you want to modify. For example:
35//!
36//! ```no_run
37//! use read_fonts::{FontRef, TableProvider};
38//! use write_fonts::{from_obj::ToOwnedTable, tables::head::Head, types::LongDateTime};
39//! // ...
40//! # let font: FontRef<'static> = todo!();
41//! # fn seconds_since_font_epoch() -> font_types::LongDateTime { todo!() }
42//! let mut head: Head = font.head().expect("missing 'head' table").to_owned_table();
43//! head.modified = seconds_since_font_epoch();
44//! ```
45//!
46//! (For a full example, see [below](#examples).)
47//!
48//! When loading and modifying fonts, you will likely need to interact with both
49//! `write-fonts` and `read-fonts` directly. To avoid having to manage both of
50//! these dependencies, there is a "read" feature on `write-fonts` that reexports
51//! `read-fonts` as `read` at the crate root:
52//!
53//! ```toml
54//! # Cargo.toml
55//! [dependencies]
56//! write-fonts = { version = "*", features = ["read"] }
57//! ```
58//!
59//! ```no_compile
60//! // main.rs
61//! use write_fonts::read::FontRef;
62//! ```
63//!
64//! ## Writing subtables
65//!
66//! A font table commonly contains some set of subtables which are referenced
67//! in the font binary as offsets relative to the position (within the file) of
68//! the parent table; and these subtables can themselves contain subtables, and
69//! so on. We refer to the entire structure of tables as the 'table graph'.
70//! A consequence of this structure is that compiling
71//! a table is not as simple as just sequentially writing out the bytes of each
72//! field; it also involves computing an ordering for the subtables, determining
73//! their position in the final binary, and correctly writing that position in
74//! the appropriate location in any tables that reference that subtable.
75//!
76//! As most subtable positions (offsets) are stored as 16-bit integers,
77//! it is possible in certain cases that offsets overflow. The task of finding
78//! a suitable ordering for each table in the table graph is called "table packing".
79//! `write-fonts` handles the packing of tables at serialization time, based
80//! on the [hb-repacker] implementation from [HarfBuzz].
81//!
82//! # Examples
83//!
84//! Create an 'hhea' table
85//! ```no_run
86//! use write_fonts::{tables::hhea::Hhea, types::{FWord, UfWord}};
87//!
88//! let my_table = Hhea {
89//!     ascender: FWord::new(700),
90//!     descender: FWord::new(-195),
91//!     line_gap: FWord::new(0),
92//!     advance_width_max: UfWord::new(1200),
93//!     min_left_side_bearing: FWord::new(-80),
94//!     min_right_side_bearing: FWord::new(-420),
95//!     x_max_extent: FWord::new(1122),
96//!     caret_slope_rise: 1,
97//!     caret_slope_run: 0,
98//!     caret_offset: 0,
99//!     number_of_h_metrics: 301,
100//! };
101//!
102//! let _bytes = write_fonts::dump_table(&my_table).expect("failed to write bytes");
103//! ```
104//!
105//! Read/modify/write an existing font
106//! ```no_run
107//! # let path_to_my_font_file = std::path::Path::new("");
108//! # fn seconds_since_font_epoch() -> LongDateTime { todo!() }
109//! use read_fonts::{FontRef, TableProvider};
110//! use write_fonts::{
111//!     from_obj::ToOwnedTable,
112//!     tables::head::Head,
113//!     types::LongDateTime,
114//!     FontBuilder,
115//! };
116//! let font_bytes = std::fs::read(path_to_my_font_file).unwrap();
117//! let font = FontRef::new(&font_bytes).expect("failed to read font data");
118//! let mut head: Head = font.head().expect("missing 'head' table").to_owned_table();
119//! head.modified  = seconds_since_font_epoch();
120//! let new_bytes = FontBuilder::new()
121//!     .add_table(&head)
122//!     .unwrap() // errors if we can't compile 'head', unlikely here
123//!     .copy_missing_tables(font)
124//!     .build();
125//! std::fs::write("mynewfont.ttf", &new_bytes).unwrap();
126//! ```
127//!
128//! [`read-fonts`]: https://docs.rs/read-fonts/
129//! [spec]: https://learn.microsoft.com/en-us/typography/opentype/spec/
130//! [table-directory]: https://learn.microsoft.com/en-us/typography/opentype/spec/otff#table-directory
131//! [`FontRead`]: read_fonts::FontRead
132//! [hb-repacker]: https://github.com/harfbuzz/harfbuzz/blob/main/docs/repacker.md
133//! [HarfBuzz]: https://harfbuzz.github.io
134//! [`FromTableRef`]: from_obj::FromTableRef
135//! [`ToOwnedTable`]: from_obj::ToOwnedTable
136
137#![cfg_attr(docsrs, feature(doc_auto_cfg))]
138
139mod collections;
140pub mod error;
141mod font_builder;
142pub mod from_obj;
143mod graph;
144mod offsets;
145mod round;
146mod table_type;
147pub mod tables;
148mod util;
149pub mod validate;
150mod write;
151
152#[cfg(test)]
153mod codegen_test;
154#[cfg(test)]
155mod hex_diff;
156
157pub use font_builder::{BuilderError, FontBuilder};
158pub use offsets::{NullableOffsetMarker, OffsetMarker};
159pub use round::OtRound;
160pub use write::{dump_table, FontWrite, TableWriter};
161
162/// Rexport of the common font types
163pub extern crate font_types as types;
164/// Reexport the read_fonts crate, if requested
165#[cfg(feature = "read")]
166pub extern crate read_fonts as read;
167
168/// types used in autogenerated code.
169#[allow(unused_imports)]
170pub(crate) mod codegen_prelude {
171    use std::num::TryFromIntError;
172
173    pub use super::from_obj::{FromObjRef, FromTableRef, ToOwnedObj, ToOwnedTable};
174    pub use super::offsets::{NullableOffsetMarker, OffsetMarker, WIDTH_16, WIDTH_24, WIDTH_32};
175    pub use super::table_type::TableType;
176    pub use super::validate::{Validate, ValidationCtx};
177    pub use super::write::{FontWrite, TableWriter};
178    pub use std::collections::BTreeSet;
179    pub use types::*;
180
181    pub use read_fonts::{
182        FontData, FontRead, FontReadWithArgs, ReadArgs, ReadError, ResolveOffset, TopLevelTable,
183    };
184
185    /// checked conversion to u16
186    pub fn array_len<T: super::collections::HasLen>(s: &T) -> usize {
187        s.len()
188    }
189
190    pub fn plus_one(val: &usize) -> usize {
191        val.saturating_add(1)
192    }
193}