Skip to content

Commit b28a9ae

Browse files
committed
Add an api to construct font data
1 parent 987123d commit b28a9ae

File tree

1 file changed

+136
-0
lines changed

1 file changed

+136
-0
lines changed

core-graphics/src/font.rs

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,126 @@ impl CGFont {
140140
None
141141
}
142142
}
143+
144+
/// Will construct a Vec<u8> containing a font that has the tables and table contents of the
145+
/// CGFont. This will not necessarily be the exact same bytes as the original font but should
146+
/// be functionally equivalent.
147+
///
148+
/// Manually reconstructing a font is necessary because CoreGraphics does not provide a method
149+
/// to retrieve the actual underlying data.
150+
pub fn construct_font_data(&self) -> Vec<u8> {
151+
construct_font_data(self)
152+
}
153+
}
154+
155+
fn calc_table_checksum(table: &[u8], skip_checksum_adjust: bool) -> u32 {
156+
use std::convert::TryInto;
157+
let mut sum = std::num::Wrapping(0);
158+
let mut i = 0;
159+
let mut chunks = table.chunks_exact(4);
160+
for chunk in &mut chunks {
161+
if skip_checksum_adjust && i == 2 {
162+
163+
} else {
164+
let val = u32::from_be_bytes(chunk.try_into().unwrap());
165+
sum += std::num::Wrapping(val)
166+
}
167+
i += 1;
168+
}
169+
170+
// The table will be zero padded to be 4 byte aligned when written out
171+
// so compute the checksum as if that were the case.
172+
let mut val = [0; 4];
173+
val[0..chunks.remainder().len()].copy_from_slice(chunks.remainder());
174+
let val = u32::from_be_bytes(val);
175+
sum += std::num::Wrapping(val);
176+
177+
sum.0
178+
}
179+
180+
fn max_pow2_less_than_equal(a: i32) -> i32 {
181+
let x = 1;
182+
let mut shift = 0;
183+
while (x << (shift + 1)) <= a {
184+
shift+=1;
185+
}
186+
shift
187+
}
188+
189+
// This code is inspired by the code in mozilla-central/gfx/2d/ScaledFontMac.cpp
190+
fn construct_font_data(font: &CGFont) -> Vec<u8> {
191+
struct TableRecord {
192+
tag: u32,
193+
checksum: u32,
194+
offset: u32,
195+
length: u32,
196+
data: CFData,
197+
}
198+
199+
let tags = font.copy_table_tags();
200+
let count = tags.len();
201+
let mut records = Vec::with_capacity(tags.len() as usize);
202+
let mut offset: u32 = 0;
203+
offset += std::mem::size_of::<u32>() as u32 * 3;
204+
offset += std::mem::size_of::<u32>() as u32 * 4 * count as u32;
205+
let mut cff = false;
206+
for tag in tags.iter() {
207+
let data = font.copy_table_for_tag(*tag).unwrap();
208+
let skip_checksum_adjust = *tag == 0x68656164; // 'head'
209+
210+
if *tag == 0x43464620 { // 'CFF '
211+
cff = true;
212+
}
213+
let checksum = calc_table_checksum(data.bytes(), skip_checksum_adjust);
214+
records.push(TableRecord { tag: *tag, offset, length: data.len() as u32, data: data.clone(), checksum});
215+
offset += data.len() as u32;
216+
// 32 bit align the tables
217+
offset = (offset + 3) & !3;
218+
}
219+
220+
let mut buf: Vec<u8> = Vec::new();
221+
if cff {
222+
buf.extend_from_slice(&0x4f54544fu32.to_be_bytes());
223+
} else {
224+
buf.extend_from_slice(&0x00010000u32.to_be_bytes());
225+
}
226+
227+
buf.extend_from_slice(&(count as u16).to_be_bytes());
228+
let max_pow2_count = max_pow2_less_than_equal(count as i32);
229+
buf.extend_from_slice(&((1u16 << max_pow2_count) * 16).to_be_bytes());
230+
buf.extend_from_slice(&(max_pow2_count as u16).to_be_bytes());
231+
buf.extend_from_slice(&((count as u16 - (1 << max_pow2_count)) * 16).to_be_bytes());
232+
233+
// write table record entries
234+
for rec in &records {
235+
buf.extend_from_slice(&rec.tag.to_be_bytes());
236+
buf.extend_from_slice(&rec.checksum.to_be_bytes());
237+
buf.extend_from_slice(&rec.offset.to_be_bytes());
238+
buf.extend_from_slice(&rec.length.to_be_bytes());
239+
}
240+
241+
// write tables
242+
let mut checksum_adjustment_offset = 0;
243+
for rec in &records {
244+
if rec.tag == 0x68656164 { // 'head'
245+
checksum_adjustment_offset = buf.len() + 2 * 4;
246+
}
247+
assert!(buf.len() == rec.offset as usize);
248+
buf.extend_from_slice(rec.data.bytes());
249+
// align
250+
let extra = ((buf.len() + 3) & !3) - buf.len();
251+
buf.extend_from_slice(&[0;4][0..extra]);
252+
}
253+
254+
// clear the checksumAdjust field before checksumming the whole font
255+
for b in &mut buf[checksum_adjustment_offset..checksum_adjustment_offset+4] {
256+
*b = 0;
257+
}
258+
let font_check_sum = (0xb1b0afba_u32.wrapping_sub(
259+
calc_table_checksum(&buf, false))).to_be_bytes();
260+
(&mut buf[checksum_adjustment_offset..checksum_adjustment_offset+4]).copy_from_slice(&font_check_sum);
261+
262+
buf
143263
}
144264

145265
#[link(name = "CoreGraphics", kind = "framework")]
@@ -174,3 +294,19 @@ extern {
174294
fn CGFontCopyVariations(font: ::sys::CGFontRef) -> CFDictionaryRef;
175295
fn CGFontCopyVariationAxes(font: ::sys::CGFontRef) -> CFArrayRef;
176296
}
297+
298+
#[cfg(test)]
299+
mod test {
300+
use core_foundation::string::CFString;
301+
use crate::font::*;
302+
#[test]
303+
fn construct_font_data() {
304+
use std::sync::Arc;
305+
306+
let font = CGFont::from_name(&CFString::from_static_string("Helvetica")).unwrap();
307+
let data = font.construct_font_data();
308+
let data_provider = crate::data_provider::CGDataProvider::from_buffer(Arc::new(data));
309+
let font = CGFont::from_data_provider(data_provider).unwrap();
310+
assert_eq!(font.postscript_name(), "Helvetica");
311+
}
312+
}

0 commit comments

Comments
 (0)