///|
fn pdf_truetype_is_cmap_tag(data : BytesView, offset : Int) -> Bool {
offset >= 0 &&
offset <= data.length() - 4 &&
data[offset].to_int() == 0x63 &&
data[offset + 1].to_int() == 0x6D &&
data[offset + 2].to_int() == 0x61 &&
data[offset + 3].to_int() == 0x70
}
///|
fn pdf_truetype_collect_cmap_records(
data : BytesView,
cmap_offset : Int,
records : Array[(Int, Int)],
) -> Unit {
match pdf_truetype_read_u16(data, cmap_offset + 2) {
Some(record_count) => {
let mut index = 0
let mut done = false
while !done && index < record_count {
let record_offset = cmap_offset + 4 + index * 8
match
(
pdf_truetype_read_u16(data, record_offset),
pdf_truetype_read_u16(data, record_offset + 2),
pdf_truetype_read_u32_as_int(data, record_offset + 4),
) {
(Some(platform_id), Some(encoding_id), Some(_)) => {
records.insert(0, (platform_id, encoding_id))
index += 1
}
_ => done = true
}
}
}
None => ()
}
}
///|
fn pdf_truetype_cmap_glyph_from_entries(
entries : ArrayView[@truetype.PdfTrueTypeCMapGlyph],
codepoint : Int,
) -> Int? {
for entry in entries {
if entry.codepoint == codepoint {
break Some(entry.glyph_index)
}
} nobreak {
None
}
}
///|
/// Merged cmap mappings being read, in the order their codepoints first
/// appear, with where each codepoint's mapping is. A font's cmap maps
/// thousands of codepoints (tens of thousands for CJK), which a scan of
/// the mappings so far for each one made quadratic.
priv struct PdfTrueTypeCMapBuilder {
entries : Array[@truetype.PdfTrueTypeCMapGlyph]
positions : Map[Int, Int]
/// The steps the reading has left: a mapping read is one, and so is a
/// record, segment or group looked at. A cmap's tables overlap, and map
/// a codepoint a few times over; a hostile one (its records, segments or
/// groups the same again and again, mapping or not) would be read
/// without end.
mut budget : Int
}
///|
/// The steps a cmap is read for: every codepoint eight times over.
let pdf_truetype_cmap_budget : Int = 8 * 0x110000
///|
/// Spend a step of the reading's budget: false once it is spent.
fn PdfTrueTypeCMapBuilder::step(self : PdfTrueTypeCMapBuilder) -> Bool {
if self.budget <= 0 {
false
} else {
self.budget -= 1
true
}
}
///|
/// Map `codepoint` to `glyph_index`, replacing an earlier mapping of it in
/// place. False once the reading's budget is spent, when nothing more is
/// mapped.
fn pdf_truetype_push_cmap_glyph(
entries : PdfTrueTypeCMapBuilder,
codepoint : Int,
glyph_index : Int,
) -> Bool {
if !entries.step() {
return false
}
let normalized = pdf_truetype_normalize_u16(glyph_index)
match entries.positions.get(codepoint) {
Some(index) =>
entries.entries[index] = { codepoint, glyph_index: normalized, }
None => {
entries.positions[codepoint] = entries.entries.length()
entries.entries.push({ codepoint, glyph_index: normalized, })
}
}
true
}
///|
fn pdf_truetype_cmap_format0_glyphs(
data : BytesView,
subtable_offset : Int,
entries : PdfTrueTypeCMapBuilder,
) -> Unit {
for codepoint in 0..<256 {
let offset = subtable_offset + 6 + codepoint
if offset >= data.length() {
break
}
if !pdf_truetype_push_cmap_glyph(entries, codepoint, data[offset].to_int()) {
break
}
}
}
///|
fn pdf_truetype_cmap_format6_glyphs(
data : BytesView,
subtable_offset : Int,
entries : PdfTrueTypeCMapBuilder,
) -> Unit {
match
(
pdf_truetype_read_u16(data, subtable_offset + 6),
pdf_truetype_read_u16(data, subtable_offset + 8),
) {
(Some(first_code), Some(entry_count)) =>
for index in 0..
if !pdf_truetype_push_cmap_glyph(
entries,
first_code + index,
glyph_index,
) {
break
}
None => break
}
}
_ => ()
}
}
///|
fn pdf_truetype_cmap_format4_glyphs(
data : BytesView,
subtable_offset : Int,
entries : PdfTrueTypeCMapBuilder,
) -> Unit {
match pdf_truetype_read_u16(data, subtable_offset + 6) {
Some(seg_count_x2) => {
let seg_count = seg_count_x2 / 2
let end_codes_offset = subtable_offset + 14
let start_codes_offset = end_codes_offset + seg_count * 2 + 2
let id_delta_offset = start_codes_offset + seg_count * 2
let id_range_offset_offset = id_delta_offset + seg_count * 2
let glyph_index_array_start = id_range_offset_offset + seg_count * 2
for segment in 0..
if start_code <= end_code {
for codepoint in start_code..<=end_code {
if codepoint == 0xFFFF {
continue
}
if range_offset == 0 {
if !pdf_truetype_push_cmap_glyph(
entries,
codepoint,
codepoint + delta,
) {
break
}
} else {
let glyph_offset = glyph_index_array_start +
(
segment -
seg_count +
range_offset / 2 +
(codepoint - start_code)
) *
2
match pdf_truetype_read_i16(data, glyph_offset) {
Some(value) => {
let base = if value == 0 { codepoint } else { value }
if !pdf_truetype_push_cmap_glyph(
entries,
codepoint,
base + delta,
) {
break
}
}
None => break
}
}
}
}
_ => break
}
}
}
None => ()
}
}
///|
/// The entries of `size` bytes a 32-bit subtable (formats 10, 12 and 13:
/// a `length` at byte 4) has room for after its `header` bytes, at most
/// `declared`: within the length it gives itself, and within the data.
fn pdf_truetype_cmap32_entries(
data : BytesView,
subtable_offset : Int,
header : Int,
size : Int,
declared : Int,
) -> Int {
guard pdf_truetype_read_u32_as_int(data, subtable_offset + 4) is Some(length) else {
return 0
}
// (an offset near 2^31 would wrap)
guard subtable_offset >= 0 && subtable_offset <= data.length() - header else {
return 0
}
let room = length.min(data.length() - subtable_offset) - header
if room <= 0 {
0
} else {
declared.min(room / size)
}
}
///|
/// Formats 12 (segmented coverage) and 13 (many-to-one): groups of
/// consecutive codepoints, each mapped to consecutive glyphs (format 12)
/// or all to one glyph (format 13, `many_to_one`), which reach the
/// codepoints beyond the BMP. The formats have their groups in order and
/// apart; one that is not (or that ends past U+10FFFF, or maps past glyph
/// 0xFFFF) is passed over.
fn pdf_truetype_cmap_groups_glyphs(
data : BytesView,
subtable_offset : Int,
entries : PdfTrueTypeCMapBuilder,
many_to_one~ : Bool,
) -> Unit {
guard pdf_truetype_read_u32_as_int(data, subtable_offset + 12)
is Some(declared) else {
return
}
let group_count = pdf_truetype_cmap32_entries(
data, subtable_offset, 16, 12, declared,
)
// the lowest codepoint the next group may start at
let mut next = 0
for group in 0.. 0x10FFFF ||
glyph > 0xFFFF ||
last_glyph > 0xFFFF {
continue
}
for codepoint in start..<=end {
let glyph_index = if many_to_one {
glyph
} else {
glyph + codepoint - start
}
if !pdf_truetype_push_cmap_glyph(entries, codepoint, glyph_index) {
return
}
}
next = end + 1
}
}
///|
/// Format 10 (trimmed array): the glyphs of a run of codepoints from a
/// first one, which may be beyond the BMP.
fn pdf_truetype_cmap_format10_glyphs(
data : BytesView,
subtable_offset : Int,
entries : PdfTrueTypeCMapBuilder,
) -> Unit {
guard (
pdf_truetype_read_u32_as_int(data, subtable_offset + 12),
pdf_truetype_read_u32_as_int(data, subtable_offset + 16),
)
is (Some(first_code), Some(declared)) else {
return
}
let count = pdf_truetype_cmap32_entries(
data, subtable_offset, 20, 2, declared,
)
for index in 0.. 0x10FFFF - index {
break
}
match pdf_truetype_read_u16(data, subtable_offset + 20 + index * 2) {
Some(glyph_index) =>
if !pdf_truetype_push_cmap_glyph(
entries,
first_code + index,
glyph_index,
) {
break
}
None => break
}
}
}
///|
fn pdf_truetype_cmap_subtable_glyphs(
data : BytesView,
subtable_offset : Int,
entries : PdfTrueTypeCMapBuilder,
) -> Unit {
match pdf_truetype_read_u16(data, subtable_offset) {
Some(0) => pdf_truetype_cmap_format0_glyphs(data, subtable_offset, entries)
Some(4) => pdf_truetype_cmap_format4_glyphs(data, subtable_offset, entries)
Some(6) => pdf_truetype_cmap_format6_glyphs(data, subtable_offset, entries)
Some(10) =>
pdf_truetype_cmap_format10_glyphs(data, subtable_offset, entries)
Some(12) =>
pdf_truetype_cmap_groups_glyphs(
data,
subtable_offset,
entries,
many_to_one=false,
)
Some(13) =>
pdf_truetype_cmap_groups_glyphs(
data,
subtable_offset,
entries,
many_to_one=true,
)
_ => ()
}
}
///|
fn pdf_truetype_identity_cmap_glyphs() -> Array[@truetype.PdfTrueTypeCMapGlyph] {
[
for codepoint in 0..<256 => { codepoint, glyph_index: codepoint, }
]
}
///|
/// Return merged TrueType cmap Unicode-to-glyph mappings.
///
/// This ports the cmap-table reader used by `Cpdftruetype.parse` before the
/// later width/subset writer stages. Source-supported cmap formats 0, 4, and 6
/// are decoded, and the 32-bit formats 10, 12 and 13 (which reach the
/// codepoints beyond the BMP) besides; later encoding subtables replace earlier mappings for the same
/// codepoint, matching the source `Hashtbl.add`/`find` behavior. A font with no
/// cmap table follows the source fallback of mapping byte codes `0..255` to the
/// same glyph index.
pub fn pdf_truetype_cmap_glyphs(
data : BytesView,
) -> Array[@truetype.PdfTrueTypeCMapGlyph] {
let entries = PdfTrueTypeCMapBuilder::{
entries: [],
positions: {},
budget: pdf_truetype_cmap_budget,
}
match pdf_truetype_read_u16(data, 4) {
Some(table_count) => {
let mut cmap_offset = -1
let mut table_index = 0
while cmap_offset < 0 && table_index < table_count {
let table_offset = 12 + table_index * 16
if table_offset > data.length() - 16 {
table_index = table_count
} else {
if pdf_truetype_is_cmap_tag(data, table_offset) {
match pdf_truetype_read_u32_as_int(data, table_offset + 8) {
Some(offset) => cmap_offset = offset
None => ()
}
}
table_index += 1
}
}
if cmap_offset < 0 {
pdf_truetype_identity_cmap_glyphs()
} else {
match pdf_truetype_read_u16(data, cmap_offset + 2) {
Some(record_count) => {
for record_index in 0..
pdf_truetype_cmap_subtable_glyphs(
data,
cmap_offset + subtable_offset,
entries,
)
None => break
}
}
entries.entries
}
None => []
}
}
}
None => []
}
}
///|
/// Look up one Unicode codepoint in the merged TrueType cmap mappings.
pub fn pdf_truetype_cmap_glyph(data : BytesView, codepoint : Int) -> Int? {
let entries = pdf_truetype_cmap_glyphs(data)
for entry in entries {
if entry.codepoint == codepoint {
break Some(entry.glyph_index)
}
} nobreak {
None
}
}
///|
/// Return the TrueType cmap platform and encoding IDs.
///
/// This ports `Cpdftruetype.cmaps`, which is used as a PDF/UA verification
/// helper. The source function catches parse failures and returns the records
/// collected before the failure; this function likewise ignores malformed or
/// incomplete parts of the font after complete cmap records have been read.
pub fn pdf_truetype_cmaps(data : BytesView) -> Array[(Int, Int)] {
let records : Array[(Int, Int)] = []
match pdf_truetype_read_u16(data, 4) {
Some(table_count) => {
let mut index = 0
let mut done = false
while !done && index < table_count {
let table_offset = 12 + index * 16
if table_offset > data.length() - 16 {
done = true
} else {
if pdf_truetype_is_cmap_tag(data, table_offset) {
match pdf_truetype_read_u32_as_int(data, table_offset + 8) {
Some(cmap_offset) =>
pdf_truetype_collect_cmap_records(data, cmap_offset, records)
None => ()
}
done = true
}
index += 1
}
}
}
None => ()
}
records
}