Skip to main content

font_subset/font/
os2.rs

1//! OS/2 table parsing.
2
3use core::{fmt, ops};
4
5use super::types::Cursor;
6use crate::{
7    alloc::Vec,
8    write::{VecExt, WriteTable},
9    ParseError, ParseErrorKind, TableTag,
10};
11
12/// Basic font face category.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub enum FontCategory {
15    /// Regular (aka normal) font face.
16    Regular,
17    /// Bold font face.
18    Bold,
19    /// Italic font face.
20    Italic,
21    /// Bold + italic font face.
22    BoldAndItalic,
23}
24
25impl fmt::Display for FontCategory {
26    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
27        formatter.write_str(self.as_str())
28    }
29}
30
31impl FontCategory {
32    /// Returns lower-cased human-readable category description, e.g. "regular".
33    pub const fn as_str(self) -> &'static str {
34        match self {
35            Self::Regular => "regular",
36            Self::Bold => "bold",
37            Self::Italic => "italic",
38            Self::BoldAndItalic => "bold italic",
39        }
40    }
41}
42
43/// Embedding permissions recorded in the OS/2 font table.
44///
45/// See [the Microsoft docs](https://learn.microsoft.com/en-us/typography/opentype/spec/os2)
46/// for details on what these permissions mean.
47#[derive(Debug, Clone, Copy)]
48pub enum EmbeddingPermissions {
49    /// Installable embedding.
50    Installable,
51    /// Restricted license embedding.
52    RestrictedLicense,
53    /// Preview & print embedding.
54    PreviewAndPrint,
55    /// Editable embedding.
56    Editable,
57}
58
59impl EmbeddingPermissions {
60    /// Are these permissions lenient? [`Self::Installable`] and [`Self::Editable`] permissions are considered
61    /// lenient, while the others are restrictive. YMMV depending on the use case,
62    /// so be sure to consult the font license if in doubt.
63    pub fn is_lenient(self) -> bool {
64        matches!(self, Self::Installable | Self::Editable)
65    }
66}
67
68/// Usage permissions for a font recorded in its OS/2 table.
69#[derive(Debug, Clone, Copy)]
70pub struct UsagePermissions {
71    pub(crate) raw: u16,
72    /// Embedding permissions.
73    pub embedding: EmbeddingPermissions,
74    /// If set, only bitmap embedding is allowed (as opposed to embedding bitmaps and outlines
75    /// ordinarily encoded in font glyphs). If the font doesn't contain bitmaps, no embedding is allowed at all.
76    pub embed_only_bitmaps: bool,
77    /// If set, the font can be subset during embedding.
78    pub allow_subsetting: bool,
79}
80
81impl UsagePermissions {
82    fn parse(cursor: &mut Cursor<'_>) -> Result<Self, ParseError> {
83        const EMBEDDING_MASK: u16 = 0x0f;
84        const SUBSETTING_MASK: u16 = 0x0100;
85        const EMBED_BITMAPS_MASK: u16 = 0x0200;
86
87        cursor.read_u16_checked(|raw| {
88            let raw_embedding = raw & EMBEDDING_MASK;
89            let embedding = match raw_embedding {
90                0 => EmbeddingPermissions::Installable,
91                2 => EmbeddingPermissions::RestrictedLicense,
92                4 => EmbeddingPermissions::PreviewAndPrint,
93                8 => EmbeddingPermissions::Editable,
94                _ => {
95                    return Err(ParseErrorKind::UnexpectedValue {
96                        name: "usage_permissions",
97                        expected: "one of 0, 2, 4, or 8".into(),
98                        actual: raw_embedding.into(),
99                    })
100                }
101            };
102
103            let can_subset = raw & SUBSETTING_MASK == 0;
104            let embed_only_bitmaps = raw & EMBED_BITMAPS_MASK != 0;
105
106            Ok(Self {
107                raw,
108                embedding,
109                embed_only_bitmaps,
110                allow_subsetting: can_subset,
111            })
112        })
113    }
114}
115
116#[derive(Debug, Clone, Copy)]
117pub(crate) struct Os2Table<'a> {
118    version: u16,
119    /// xAvgCharWidth, usWeightClass, usWidthClass
120    not_parsed_after_version: [u8; 6],
121    pub(super) usage_permissions: UsagePermissions,
122    /// ySubscriptXSize ..= PANOSE
123    not_parsed_after_permissions: [u8; 32],
124    unicode_ranges: u128,
125    vendor_id: [u8; 4],
126    selection: u16,
127    pub(super) first_char_index: u16,
128    pub(super) last_char_index: u16,
129    /// sTypoAscender ..= usWinDescent
130    not_parsed_after_char_index: [u8; 10],
131    code_page_ranges: u64,
132    not_parsed_tail: &'a [u8],
133}
134
135impl<'a> Os2Table<'a> {
136    #[cfg_attr(
137        feature = "tracing",
138        tracing::instrument(level = "debug", err, skip(cursor), fields(range = ?cursor.range()))
139    )]
140    pub(super) fn parse(mut cursor: Cursor<'a>) -> Result<Self, ParseError> {
141        let version = cursor.read_u16_checked(|version| {
142            if !(2..=5).contains(&version) {
143                return Err(ParseErrorKind::UnexpectedValue {
144                    name: "version",
145                    expected: "value between 2 and 5".into(),
146                    actual: version.into(),
147                });
148            }
149            Ok(version)
150        })?;
151        #[cfg(feature = "tracing")]
152        tracing::debug!(version, "parsed table version");
153
154        let not_parsed_after_version = cursor.read_byte_array::<6>()?;
155        let usage_permissions = UsagePermissions::parse(&mut cursor)?;
156        let not_parsed_after_permissions = cursor.read_byte_array::<32>()?;
157        let unicode_ranges = cursor.read_u128()?;
158        let vendor_id = cursor.read_byte_array::<4>()?;
159        let selection = cursor.read_u16()?;
160        let first_char_index = cursor.read_u16()?;
161        let last_char_index = cursor.read_u16()?;
162        let not_parsed_after_char_index = cursor.read_byte_array::<10>()?;
163        let code_page_ranges = cursor.read_u64()?;
164
165        #[cfg(feature = "tracing")]
166        tracing::debug!(
167            ?usage_permissions,
168            unicode_ranges,
169            selection,
170            first_char_index,
171            last_char_index,
172            code_page_ranges,
173            "parsed basic info"
174        );
175
176        Ok(Self {
177            version,
178            not_parsed_after_version,
179            usage_permissions,
180            not_parsed_after_permissions,
181            unicode_ranges,
182            vendor_id,
183            selection,
184            first_char_index,
185            last_char_index,
186            not_parsed_after_char_index,
187            code_page_ranges,
188            not_parsed_tail: cursor.bytes(),
189        })
190    }
191
192    pub(super) fn category(&self) -> FontCategory {
193        const ITALIC_MASK: u16 = 1;
194        const BOLD_MASK: u16 = 32;
195
196        let is_italic = self.selection & ITALIC_MASK != 0;
197        let is_bold = self.selection & BOLD_MASK != 0;
198        match (is_bold, is_italic) {
199            (false, false) => FontCategory::Regular,
200            (true, false) => FontCategory::Bold,
201            (false, true) => FontCategory::Italic,
202            (true, true) => FontCategory::BoldAndItalic,
203        }
204    }
205
206    pub(crate) fn subset(&mut self, char_range: ops::RangeInclusive<char>) {
207        // Mark that the font doesn't support any specific Unicode / code page ranges. This is the safest option.
208        self.unicode_ranges = 0;
209        self.code_page_ranges = 0;
210
211        self.first_char_index = u16::try_from(*char_range.start()).unwrap_or(u16::MAX);
212        self.last_char_index = u16::try_from(*char_range.end()).unwrap_or(u16::MAX);
213    }
214}
215
216impl WriteTable for Os2Table<'_> {
217    fn tag(&self) -> TableTag {
218        TableTag::OS2
219    }
220
221    fn write_to_vec(&self, buffer: &mut Vec<u8>) {
222        buffer.write_u16(self.version);
223        buffer.extend_from_slice(&self.not_parsed_after_version);
224        buffer.write_u16(self.usage_permissions.raw);
225        buffer.extend_from_slice(&self.not_parsed_after_permissions);
226        buffer.extend_from_slice(&self.unicode_ranges.to_be_bytes());
227        buffer.extend_from_slice(&self.vendor_id);
228        buffer.write_u16(self.selection);
229        buffer.write_u16(self.first_char_index);
230        buffer.write_u16(self.last_char_index);
231        buffer.extend_from_slice(&self.not_parsed_after_char_index);
232        buffer.write_u64(self.code_page_ranges);
233        buffer.extend_from_slice(self.not_parsed_tail);
234    }
235}