diff --git a/core-graphics/src/font.rs b/core-graphics/src/font.rs index adf7a1493..4eab648ae 100644 --- a/core-graphics/src/font.rs +++ b/core-graphics/src/font.rs @@ -140,6 +140,126 @@ impl CGFont { None } } + + /// Will construct a Vec containing a font that has the tables and table contents of the + /// CGFont. This will not necessarily be the exact same bytes as the original font but should + /// be functionally equivalent. + /// + /// Manually reconstructing a font is necessary because CoreGraphics does not provide a method + /// to retrieve the actual underlying data. + pub fn construct_font_data(&self) -> Vec { + construct_font_data(self) + } +} + +fn calc_table_checksum(table: &[u8], skip_checksum_adjust: bool) -> u32 { + use std::convert::TryInto; + let mut sum = std::num::Wrapping(0); + let mut i = 0; + let mut chunks = table.chunks_exact(4); + for chunk in &mut chunks { + if skip_checksum_adjust && i == 2 { + + } else { + let val = u32::from_be_bytes(chunk.try_into().unwrap()); + sum += std::num::Wrapping(val) + } + i += 1; + } + + // The table will be zero padded to be 4 byte aligned when written out + // so compute the checksum as if that were the case. + let mut val = [0; 4]; + val[0..chunks.remainder().len()].copy_from_slice(chunks.remainder()); + let val = u32::from_be_bytes(val); + sum += std::num::Wrapping(val); + + sum.0 +} + +fn max_pow2_less_than_equal(a: i32) -> i32 { + let x = 1; + let mut shift = 0; + while (x << (shift + 1)) <= a { + shift+=1; + } + shift +} + +// This code is inspired by the code in mozilla-central/gfx/2d/ScaledFontMac.cpp +fn construct_font_data(font: &CGFont) -> Vec { + struct TableRecord { + tag: u32, + checksum: u32, + offset: u32, + length: u32, + data: CFData, + } + + let tags = font.copy_table_tags(); + let count = tags.len(); + let mut records = Vec::with_capacity(tags.len() as usize); + let mut offset: u32 = 0; + offset += std::mem::size_of::() as u32 * 3; + offset += std::mem::size_of::() as u32 * 4 * count as u32; + let mut cff = false; + for tag in tags.iter() { + let data = font.copy_table_for_tag(*tag).unwrap(); + let skip_checksum_adjust = *tag == 0x68656164; // 'head' + + if *tag == 0x43464620 { // 'CFF ' + cff = true; + } + let checksum = calc_table_checksum(data.bytes(), skip_checksum_adjust); + records.push(TableRecord { tag: *tag, offset, length: data.len() as u32, data: data.clone(), checksum}); + offset += data.len() as u32; + // 32 bit align the tables + offset = (offset + 3) & !3; + } + + let mut buf: Vec = Vec::new(); + if cff { + buf.extend_from_slice(&0x4f54544fu32.to_be_bytes()); + } else { + buf.extend_from_slice(&0x00010000u32.to_be_bytes()); + } + + buf.extend_from_slice(&(count as u16).to_be_bytes()); + let max_pow2_count = max_pow2_less_than_equal(count as i32); + buf.extend_from_slice(&((1u16 << max_pow2_count) * 16).to_be_bytes()); + buf.extend_from_slice(&(max_pow2_count as u16).to_be_bytes()); + buf.extend_from_slice(&((count as u16 - (1 << max_pow2_count)) * 16).to_be_bytes()); + + // write table record entries + for rec in &records { + buf.extend_from_slice(&rec.tag.to_be_bytes()); + buf.extend_from_slice(&rec.checksum.to_be_bytes()); + buf.extend_from_slice(&rec.offset.to_be_bytes()); + buf.extend_from_slice(&rec.length.to_be_bytes()); + } + + // write tables + let mut checksum_adjustment_offset = 0; + for rec in &records { + if rec.tag == 0x68656164 { // 'head' + checksum_adjustment_offset = buf.len() + 2 * 4; + } + assert!(buf.len() == rec.offset as usize); + buf.extend_from_slice(rec.data.bytes()); + // align + let extra = ((buf.len() + 3) & !3) - buf.len(); + buf.extend_from_slice(&[0;4][0..extra]); + } + + // clear the checksumAdjust field before checksumming the whole font + for b in &mut buf[checksum_adjustment_offset..checksum_adjustment_offset+4] { + *b = 0; + } + let font_check_sum = (0xb1b0afba_u32.wrapping_sub( + calc_table_checksum(&buf, false))).to_be_bytes(); + (&mut buf[checksum_adjustment_offset..checksum_adjustment_offset+4]).copy_from_slice(&font_check_sum); + + buf } #[link(name = "CoreGraphics", kind = "framework")] @@ -174,3 +294,19 @@ extern { fn CGFontCopyVariations(font: ::sys::CGFontRef) -> CFDictionaryRef; fn CGFontCopyVariationAxes(font: ::sys::CGFontRef) -> CFArrayRef; } + +#[cfg(test)] +mod test { + use core_foundation::string::CFString; + use crate::font::*; + #[test] + fn construct_font_data() { + use std::sync::Arc; + + let font = CGFont::from_name(&CFString::from_static_string("Helvetica")).unwrap(); + let data = font.construct_font_data(); + let data_provider = crate::data_provider::CGDataProvider::from_buffer(Arc::new(data)); + let font = CGFont::from_data_provider(data_provider).unwrap(); + assert_eq!(font.postscript_name(), "Helvetica"); + } +}