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()
}
}