Skip to main content

ttf_rs/tables/
name.rs

1use crate::error::Result;
2use crate::stream::{FontReader, FontWriter};
3use crate::tables::{TtfTable, TtfTableWrite};
4use std::collections::HashMap;
5
6/// NAME table - Naming table
7#[derive(Debug, Clone)]
8pub struct NameTable {
9    pub format: u16,
10    pub count: u16,
11    pub string_offset: u16,
12    pub name_records: Vec<NameRecord>,
13    pub string_data: HashMap<(u16, u16, u16, u16), Vec<u8>>, // (platform_id, encoding_id, language_id, name_id) -> string data
14}
15
16#[derive(Debug, Clone)]
17pub struct NameRecord {
18    pub platform_id: u16,
19    pub encoding_id: u16,
20    pub language_id: u16,
21    pub name_id: u16,
22    pub length: u16,
23    pub offset: u16,
24}
25
26impl NameRecord {
27    pub const COPYRIGHT_NOTICE: u16 = 0;
28    pub const FONT_FAMILY_NAME: u16 = 1;
29    pub const FONT_SUBFAMILY_NAME: u16 = 2;
30    pub const UNIQUE_FONT_ID: u16 = 3;
31    pub const FULL_FONT_NAME: u16 = 4;
32    pub const VERSION_STRING: u16 = 5;
33    pub const POSTSCRIPT_NAME: u16 = 6;
34    pub const TRADEMARK: u16 = 7;
35    pub const MANUFACTURER_NAME: u16 = 8;
36    pub const DESIGNER: u16 = 9;
37    pub const DESCRIPTION: u16 = 10;
38    pub const VENDOR_URL: u16 = 11;
39    pub const DESIGNER_URL: u16 = 12;
40    pub const LICENSE_DESCRIPTION: u16 = 13;
41    pub const LICENSE_URL: u16 = 14;
42    // IDs 15-16 are reserved
43    pub const TYPOGRAPHIC_FAMILY_NAME: u16 = 16;
44    pub const TYPOGRAPHIC_SUBFAMILY_NAME: u16 = 17;
45    pub const COMPATIBLE_FULL_NAME: u16 = 18;
46    pub const SAMPLE_TEXT: u16 = 19;
47    pub const POSTSCRIPT_CID: u16 = 20;
48    pub const WWS_FAMILY_NAME: u16 = 21;
49    pub const WWS_SUBFAMILY_NAME: u16 = 22;
50    pub const LIGHT_BACKGROUND_PALETTE: u16 = 23;
51    pub const DARK_BACKGROUND_PALETTE: u16 = 24;
52}
53
54impl NameTable {
55    pub fn get_name(&self, name_id: u16) -> Option<(&NameRecord, String)> {
56        self.name_records
57            .iter()
58            .find(|r| r.name_id == name_id)
59            .map(|record| (record, String::new())) // String would need to be extracted from string storage
60    }
61
62    pub fn get_font_name(&self) -> Option<&NameRecord> {
63        self.name_records
64            .iter()
65            .find(|r| r.name_id == NameRecord::FONT_FAMILY_NAME)
66    }
67
68    pub fn get_full_name(&self) -> Option<&NameRecord> {
69        self.name_records
70            .iter()
71            .find(|r| r.name_id == NameRecord::FULL_FONT_NAME)
72    }
73
74    pub fn get_postscript_name(&self) -> Option<&NameRecord> {
75        self.name_records
76            .iter()
77            .find(|r| r.name_id == NameRecord::POSTSCRIPT_NAME)
78    }
79
80    /// Set or update a name record with the given value
81    pub fn set_name(&mut self, name: &str, platform_id: u16, encoding_id: u16, language_id: u16, name_id: u16) {
82        // Encode the string
83        let name_bytes: Vec<u16> = name.encode_utf16().collect();
84        let mut name_data = Vec::new();
85        for code_unit in name_bytes {
86            name_data.extend_from_slice(&code_unit.to_be_bytes());
87        }
88
89        let key = (platform_id, encoding_id, language_id, name_id);
90        self.string_data.insert(key, name_data.clone());
91
92        // Remove existing record with same key
93        self.name_records.retain(|r| {
94            !(r.platform_id == platform_id
95                && r.encoding_id == encoding_id
96                && r.language_id == language_id
97                && r.name_id == name_id)
98        });
99
100        // Add new record
101        self.name_records.push(NameRecord {
102            platform_id,
103            encoding_id,
104            language_id,
105            name_id,
106            length: name_data.len() as u16,
107            offset: 0, // Will be calculated during write
108        });
109
110        self.count = self.name_records.len() as u16;
111    }
112}
113
114impl TtfTable for NameTable {
115    fn from_reader(reader: &mut FontReader, _length: u32) -> Result<Self> {
116        let format = reader.read_u16()?;
117        let count = reader.read_u16()?;
118        let string_offset = reader.read_u16()?;
119
120        let mut name_records = Vec::with_capacity(count as usize);
121        for _ in 0..count {
122            name_records.push(NameRecord {
123                platform_id: reader.read_u16()?,
124                encoding_id: reader.read_u16()?,
125                language_id: reader.read_u16()?,
126                name_id: reader.read_u16()?,
127                length: reader.read_u16()?,
128                offset: reader.read_u16()?,
129            });
130        }
131
132        // Note: Actual string data would be read at string_offset
133        // For now, we just parse the records
134
135        Ok(NameTable {
136            format,
137            count,
138            string_offset,
139            name_records,
140            string_data: HashMap::new(),
141        })
142    }
143}
144
145impl TtfTableWrite for NameTable {
146    fn table_tag() -> &'static [u8; 4] {
147        b"name"
148    }
149
150    fn write(&self, writer: &mut FontWriter) -> Result<()> {
151        // Calculate string data size and offsets
152        let header_size = 6 + (self.name_records.len() * 12);
153        let mut current_offset = header_size as u16;
154        let mut all_string_data = Vec::new();
155
156        // First, collect all string data and update offsets
157        let mut updated_records = self.name_records.clone();
158        for record in &mut updated_records {
159            let key = (record.platform_id, record.encoding_id, record.language_id, record.name_id);
160            if let Some(data) = self.string_data.get(&key) {
161                record.offset = current_offset;
162                record.length = data.len() as u16;
163                all_string_data.extend_from_slice(data);
164                current_offset += data.len() as u16;
165            }
166        }
167
168        // Write header
169        writer.write_u16(self.format);
170        writer.write_u16(updated_records.len() as u16);
171        writer.write_u16(header_size as u16);
172
173        // Write name records
174        for record in &updated_records {
175            writer.write_u16(record.platform_id);
176            writer.write_u16(record.encoding_id);
177            writer.write_u16(record.language_id);
178            writer.write_u16(record.name_id);
179            writer.write_u16(record.length);
180            writer.write_u16(record.offset);
181        }
182
183        // Write string data
184        writer.write_bytes(&all_string_data);
185
186        Ok(())
187    }
188}