1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
//! Writing and modifying OpenType tables
//!
//! This crate provides a collection of types correlating to what is described
//! in the [OpenType spec][spec], along with the logic to serialize these types
//! into binary font tables. It is a companion to [`read-fonts`], which provides
//! efficient zero-allocation parsing of these types. It is intended to be used
//! as the basis for font engineering tools such as compilers.
//!
//! ## 'write' versus 'read' types
//!
//! Both `write-fonts` and [`read-fonts`] make heavy use of code generation, and
//! they have a similar structure, where a `tables` module contains a submodule for
//! each supported [table][table-directory], and that module contains items for
//! each table, record, flagset or enum described in the spec. This means that
//! there are (for instance) two distinct `ValueRecord` types, one defined in
//! `read_fonts::tables::gpos`, and one defined in `write_fonts::tables::gpos`.
//!
//! The reason for the distinct types is that it allows us to dramatically
//! simplify the scope of `read-fonts`; the types in that crate are generally
//! just typed views into raw slices of bytes and cannot be modified, whereas
//! the types in `write-fonts` are generally familiar Rust structs and enums.
//!
//! ## Loading and modifying fonts
//!
//! Although `write-fonts` does not contain any parsing logic, it does offer the
//! [`FromTableRef`] and [`ToOwnedTable`] traits (similar to `std`'s `From` & `Into`)
//! for converting from a `read_fonts` type to its `write-fonts` equivalent.
//! This means that you can read an existing font table into `write-fonts`;
//! under the hood we will use `read-fonts` to parse the font, and then convert
//! that to the `write-fonts` version. In general you do not need to think about
//! this conversion yourself; tables implement the [`FontRead`] trait from
//! `read-fonts`, which handles the reading + conversion logic for you.
//!
//! When loading and modifying fonts, you will likely need to interact with both
//! `write-fonts` and `read-fonts` directly. To avoid having to manage both of
//! these dependencies, there is a "read" feature on `write-fonts` that reexports
//! `read-fonts` as `read` at the crate root:
//!
//! ```toml
//! # Cargo.toml
//! [dependencies]
//! write-fonts = { version = "*", features = ["read"] }
//! ```
//!
//! ```no_compile
//! // main.rs
//! use write_fonts::read::FontRef;
//! ```
//!
//! ## Writing subtables
//!
//! A font table commonly contains some set of subtables which are referenced
//! in the font binary as offsets relative to the position (within the file) of
//! the parent table; and these subtables can themselves contain subtables, and
//! so on. We refer to the entire structure of tables as the 'table graph'.
//! A consequence of this structure is that compiling
//! a table is not as simple as just sequentially writing out the bytes of each
//! field; it also involves computing an ordering for the subtables, determining
//! their position in the final binary, and correctly writing that position in
//! the appropriate location in any tables that reference that subtable.
//!
//! As most subtable positions (offsets) are stored as 16-bit integers,
//! it is possible in certain cases that offsets overflow. The task of finding
//! a suitable ordering for each table in the table graph is called "table packing".
//! `write-fonts` handles the packing of tables at serialization time, based
//! on the [hb-repacker] implementation from [HarfBuzz].
//!
//! # Examples
//!
//! Create an 'hhea' table
//! ```no_run
//! use write_fonts::{tables::hhea::Hhea, types::{FWord, UfWord}};
//!
//! let my_table = Hhea {
//!     ascender: FWord::new(700),
//!     descender: FWord::new(-195),
//!     line_gap: FWord::new(0),
//!     advance_width_max: UfWord::new(1200),
//!     min_left_side_bearing: FWord::new(-80),
//!     min_right_side_bearing: FWord::new(-420),
//!     x_max_extent: FWord::new(1122),
//!     caret_slope_rise: 1,
//!     caret_slope_run: 0,
//!     caret_offset: 0,
//!     number_of_long_metrics: 301,
//! };
//!
//! let _bytes = write_fonts::dump_table(&my_table).expect("failed to write bytes");
//! ```
//!
//! Read/modify/write an existing font
//! ```no_run
//! # let path_to_my_font_file = std::path::Path::new("");
//! # fn seconds_since_font_epoch() -> LongDateTime { todo!() }
//! use read_fonts::{FontRef, TableProvider};
//! use write_fonts::{
//!     from_obj::ToOwnedTable,
//!     tables::head::Head,
//!     types::LongDateTime,
//!     FontBuilder,
//! };
//! let font_bytes = std::fs::read(path_to_my_font_file).unwrap();
//! let font = FontRef::new(&font_bytes).expect("failed to read font data");
//! let mut head: Head = font.head().expect("missing 'head' table").to_owned_table();
//! head.modified  = seconds_since_font_epoch();
//! let new_bytes = FontBuilder::new()
//!     .add_table(&head)
//!     .unwrap() // errors if we can't compile 'head', unlikely here
//!     .copy_missing_tables(font)
//!     .build();
//! std::fs::write("mynewfont.ttf", &new_bytes).unwrap();
//! ```
//!
//! [`read-fonts`]: https://docs.rs/read-fonts/
//! [spec]: https://learn.microsoft.com/en-us/typography/opentype/spec/
//! [table-directory]: https://learn.microsoft.com/en-us/typography/opentype/spec/otff#table-directory
//! [`FontRead`]: read_fonts::FontRead
//! [hb-repacker]: https://github.com/harfbuzz/harfbuzz/blob/main/docs/repacker.md
//! [HarfBuzz]: https://harfbuzz.github.io
//! [`FromTableRef`]: from_obj::FromTableRef
//! [`ToOwnedTable`]: from_obj::ToOwnedTable

mod collections;
pub mod error;
mod font_builder;
pub mod from_obj;
mod graph;
mod offsets;
pub mod pens;
mod round;
mod table_type;
pub mod tables;
mod util;
pub mod validate;
mod write;

#[cfg(test)]
mod codegen_test;
#[cfg(test)]
mod hex_diff;

pub use font_builder::{BuilderError, FontBuilder};
pub use offsets::{NullableOffsetMarker, OffsetMarker};
pub use round::OtRound;
pub use write::{dump_table, FontWrite, TableWriter};

/// Rexport of the common font types
pub extern crate font_types as types;
/// Reexport the read_fonts crate, if requested
#[cfg(feature = "read")]
pub extern crate read_fonts as read;

/// types used in autogenerated code.
#[allow(unused_imports)]
pub(crate) mod codegen_prelude {
    use std::num::TryFromIntError;

    pub use super::from_obj::{FromObjRef, FromTableRef, ToOwnedObj, ToOwnedTable};
    pub use super::offsets::{NullableOffsetMarker, OffsetMarker, WIDTH_16, WIDTH_24, WIDTH_32};
    pub use super::table_type::TableType;
    pub use super::validate::{Validate, ValidationCtx};
    pub use super::write::{FontWrite, TableWriter};
    pub use std::collections::BTreeSet;
    pub use types::*;

    pub use read_fonts::{
        FontData, FontRead, FontReadWithArgs, ReadArgs, ReadError, ResolveOffset, TopLevelTable,
    };

    /// checked conversion to u16
    pub fn array_len<T: super::collections::HasLen>(s: &T) -> Result<u16, TryFromIntError> {
        s.checked_len()
    }

    pub fn plus_one(val: &usize) -> Result<u16, TryFromIntError> {
        val.saturating_add(1).try_into()
    }
}