Add support for Format 12 cmaps

This commit is contained in:
Patrick Walton 2017-02-07 16:04:15 -08:00
parent 66d239cf2f
commit a6711af84f
2 changed files with 108 additions and 3 deletions

View File

@ -25,6 +25,7 @@ const MICROSOFT_ENCODING_ID_UNICODE_BMP: u16 = 1;
const MICROSOFT_ENCODING_ID_UNICODE_UCS4: u16 = 10; const MICROSOFT_ENCODING_ID_UNICODE_UCS4: u16 = 10;
const FORMAT_SEGMENT_MAPPING_TO_DELTA_VALUES: u16 = 4; const FORMAT_SEGMENT_MAPPING_TO_DELTA_VALUES: u16 = 4;
const FORMAT_SEGMENTED_COVERAGE: u16 = 12;
const MISSING_GLYPH: u16 = 0; const MISSING_GLYPH: u16 = 0;
@ -78,10 +79,24 @@ impl<'a> CmapTable<'a> {
// Check the mapping table format. // Check the mapping table format.
let format = try!(cmap_reader.read_u16::<BigEndian>().map_err(Error::eof)); let format = try!(cmap_reader.read_u16::<BigEndian>().map_err(Error::eof));
if format != FORMAT_SEGMENT_MAPPING_TO_DELTA_VALUES { match format {
return Err(Error::UnsupportedCmapFormat) FORMAT_SEGMENT_MAPPING_TO_DELTA_VALUES => {
self.glyph_ranges_for_codepoint_ranges_segment_mapping_format(cmap_reader,
codepoint_ranges)
}
FORMAT_SEGMENTED_COVERAGE => {
self.glyph_ranges_for_codepoint_ranges_segmented_coverage(cmap_reader,
codepoint_ranges)
}
_ => Err(Error::UnsupportedCmapFormat),
} }
}
fn glyph_ranges_for_codepoint_ranges_segment_mapping_format(
&self,
mut cmap_reader: &[u8],
codepoint_ranges: &[CodepointRange])
-> Result<GlyphRanges, Error> {
// Read the mapping table header. // Read the mapping table header.
let _length = try!(cmap_reader.read_u16::<BigEndian>().map_err(Error::eof)); let _length = try!(cmap_reader.read_u16::<BigEndian>().map_err(Error::eof));
let _language = try!(cmap_reader.read_u16::<BigEndian>().map_err(Error::eof)); let _language = try!(cmap_reader.read_u16::<BigEndian>().map_err(Error::eof));
@ -234,5 +249,82 @@ impl<'a> CmapTable<'a> {
Ok(glyph_ranges) Ok(glyph_ranges)
} }
fn glyph_ranges_for_codepoint_ranges_segmented_coverage(&self,
mut cmap_reader: &[u8],
codepoint_ranges: &[CodepointRange])
-> Result<GlyphRanges, Error> {
let _reserved = try!(cmap_reader.read_u16::<BigEndian>().map_err(Error::eof));
let _length = try!(cmap_reader.read_u32::<BigEndian>().map_err(Error::eof));
let _language = try!(cmap_reader.read_u32::<BigEndian>().map_err(Error::eof));
let num_groups = try!(cmap_reader.read_u32::<BigEndian>().map_err(Error::eof));
// Now perform the lookups.
let mut glyph_ranges = GlyphRanges::new();
for codepoint_range in codepoint_ranges {
let mut codepoint_range = *codepoint_range;
while codepoint_range.end >= codepoint_range.start {
// Binary search to find the segment.
let (mut low, mut high) = (0, num_groups);
let mut found_segment = None;
while low < high {
let mid = (low + high) / 2;
let mut reader = cmap_reader;
try!(reader.jump(mid as usize * mem::size_of::<[u32; 3]>())
.map_err(Error::eof));
let segment = Segment {
start_char_code: try!(reader.read_u32::<BigEndian>().map_err(Error::eof)),
end_char_code: try!(reader.read_u32::<BigEndian>().map_err(Error::eof)),
start_glyph_id: try!(reader.read_u32::<BigEndian>().map_err(Error::eof)),
};
if codepoint_range.start < segment.start_char_code {
high = mid
} else if codepoint_range.start > segment.end_char_code {
low = mid + 1
} else {
found_segment = Some(segment);
break
}
}
match found_segment {
None => {
glyph_ranges.ranges.push(MappedGlyphRange {
codepoint_start: codepoint_range.start,
glyphs: GlyphRange {
start: MISSING_GLYPH,
end: MISSING_GLYPH,
},
});
codepoint_range.start += 1
}
Some(segment) => {
let end = cmp::min(codepoint_range.end, segment.end_char_code);
glyph_ranges.ranges.push(MappedGlyphRange {
codepoint_start: codepoint_range.start,
glyphs: GlyphRange {
start: (segment.start_glyph_id + codepoint_range.start -
segment.start_char_code) as u16,
end: (segment.start_glyph_id + end - segment.start_char_code) as
u16,
},
});
codepoint_range.start = end + 1
}
}
}
}
Ok(glyph_ranges)
}
}
#[derive(Clone, Copy)]
struct Segment {
start_char_code: u32,
end_char_code: u32,
start_glyph_id: u32,
} }

View File

@ -58,6 +58,10 @@ const TTCF: u32 = ((b't' as u32) << 24) |
((b'c' as u32) << 8) | ((b'c' as u32) << 8) |
(b'f' as u32); (b'f' as u32);
const OTTO: u32 = ((b'O' as u32) << 24) |
((b'T' as u32) << 16) |
((b'T' as u32) << 8) |
(b'O' as u32);
const SFNT: u32 = ((b's' as u32) << 24) | const SFNT: u32 = ((b's' as u32) << 24) |
((b'f' as u32) << 16) | ((b'f' as u32) << 16) |
((b'n' as u32) << 8) | ((b'n' as u32) << 8) |
@ -111,6 +115,10 @@ impl<'a> Font<'a> {
} }
magic_number if SFNT_VERSIONS.contains(&magic_number) => Font::from_otf(bytes, 0), magic_number if SFNT_VERSIONS.contains(&magic_number) => Font::from_otf(bytes, 0),
0x0100 => Font::from_dfont(bytes), 0x0100 => Font::from_dfont(bytes),
OTTO => {
// TODO(pcwalton): Support CFF outlines.
Err(Error::UnsupportedCffOutlines)
}
_ => Err(Error::UnknownFormat), _ => Err(Error::UnknownFormat),
} }
} }
@ -122,7 +130,10 @@ impl<'a> Font<'a> {
let mut magic_number = try!(reader.read_u32::<BigEndian>().map_err(Error::eof)); let mut magic_number = try!(reader.read_u32::<BigEndian>().map_err(Error::eof));
// Check version. // Check version.
if !SFNT_VERSIONS.contains(&magic_number) { if magic_number == OTTO {
// TODO(pcwalton): Support CFF outlines.
return Err(Error::UnsupportedCffOutlines)
} else if !SFNT_VERSIONS.contains(&magic_number) {
return Err(Error::UnknownFormat) return Err(Error::UnknownFormat)
} }
@ -316,6 +327,8 @@ pub enum Error {
UnsupportedVersion, UnsupportedVersion,
/// The file was of a format we don't support. /// The file was of a format we don't support.
UnknownFormat, UnknownFormat,
/// The font has CFF outlines, which we don't yet support.
UnsupportedCffOutlines,
/// The font had a glyph format we don't support. /// The font had a glyph format we don't support.
UnsupportedGlyphFormat, UnsupportedGlyphFormat,
/// We don't support the declared version of the font's character map. /// We don't support the declared version of the font's character map.