use super::index::CffIndex;
use crate::parser::{ParseError, ParseResult};
const MAX_SUBR_DEPTH: u8 = 64;
pub fn cff_subr_bias(count: usize) -> i32 {
if count < 1240 {
107
} else if count < 33900 {
1131
} else {
32768
}
}
pub fn desubroutinize(
charstring: &[u8],
global_subrs: &CffIndex,
global_subrs_data: &[u8],
local_subrs: &CffIndex,
local_subrs_data: &[u8],
) -> ParseResult<Vec<u8>> {
let mut output = Vec::with_capacity(charstring.len());
let mut state = DesubState::default();
desubroutinize_inner(
charstring,
global_subrs,
global_subrs_data,
local_subrs,
local_subrs_data,
0,
&mut state,
&mut output,
)?;
Ok(output)
}
#[derive(Default)]
struct DesubState {
operand_positions: Vec<usize>,
hint_count: usize,
hint_mask_bytes: usize,
seen_hint_mask: bool,
}
#[allow(clippy::too_many_arguments)]
fn desubroutinize_inner(
charstring: &[u8],
global_subrs: &CffIndex,
global_subrs_data: &[u8],
local_subrs: &CffIndex,
local_subrs_data: &[u8],
depth: u8,
state: &mut DesubState,
output: &mut Vec<u8>,
) -> ParseResult<bool> {
if depth >= MAX_SUBR_DEPTH {
return Err(ParseError::SyntaxError {
position: 0,
message: format!(
"CFF subroutine recursion depth exceeded (max {}, got {})",
MAX_SUBR_DEPTH, depth
),
});
}
let mut offset = 0;
while offset < charstring.len() {
let b0 = charstring[offset];
match b0 {
10 => {
let (biased_index, operand_start) =
pop_last_operand(output, &mut state.operand_positions)?;
output.truncate(operand_start);
let actual_index = biased_index + cff_subr_bias(local_subrs.count());
let subr_data = subr_item(local_subrs, local_subrs_data, actual_index, offset)?;
let saw_endchar = desubroutinize_inner(
subr_data,
global_subrs,
global_subrs_data,
local_subrs,
local_subrs_data,
depth + 1,
state,
output,
)?;
if saw_endchar {
return Ok(true);
}
offset += 1;
}
29 => {
let (biased_index, operand_start) =
pop_last_operand(output, &mut state.operand_positions)?;
output.truncate(operand_start);
let actual_index = biased_index + cff_subr_bias(global_subrs.count());
let subr_data = subr_item(global_subrs, global_subrs_data, actual_index, offset)?;
let saw_endchar = desubroutinize_inner(
subr_data,
global_subrs,
global_subrs_data,
local_subrs,
local_subrs_data,
depth + 1,
state,
output,
)?;
if saw_endchar {
return Ok(true);
}
offset += 1;
}
11 => {
return Ok(false);
}
14 => {
output.push(14);
state.operand_positions.clear();
return Ok(true);
}
1 | 3 | 18 | 23 => {
state.hint_count += state.operand_positions.len() / 2;
output.push(b0);
offset += 1;
state.operand_positions.clear();
}
19 | 20 => {
if !state.seen_hint_mask {
state.hint_count += state.operand_positions.len() / 2;
state.hint_mask_bytes = state.hint_count.div_ceil(8);
state.seen_hint_mask = true;
}
output.push(b0);
offset += 1;
let mask_end = offset + state.hint_mask_bytes;
if mask_end > charstring.len() {
return Err(ParseError::SyntaxError {
position: offset,
message: "Truncated hintmask/cntrmask data".to_string(),
});
}
output.extend_from_slice(&charstring[offset..mask_end]);
offset = mask_end;
state.operand_positions.clear();
}
12 => {
if offset + 1 >= charstring.len() {
return Err(ParseError::SyntaxError {
position: offset,
message: "Truncated 2-byte operator".to_string(),
});
}
output.push(12);
output.push(charstring[offset + 1]);
offset += 2;
state.operand_positions.clear();
}
0 | 2 | 4..=9 | 13 | 15..=17 | 21 | 22 | 24..=27 | 30 | 31 => {
output.push(b0);
offset += 1;
state.operand_positions.clear();
}
28 => {
if offset + 2 >= charstring.len() {
return Err(ParseError::SyntaxError {
position: offset,
message: "Truncated 3-byte integer".to_string(),
});
}
state.operand_positions.push(output.len());
output.extend_from_slice(&charstring[offset..offset + 3]);
offset += 3;
}
32..=246 => {
state.operand_positions.push(output.len());
output.push(b0);
offset += 1;
}
247..=254 => {
if offset + 1 >= charstring.len() {
return Err(ParseError::SyntaxError {
position: offset,
message: "Truncated 2-byte integer".to_string(),
});
}
state.operand_positions.push(output.len());
output.extend_from_slice(&charstring[offset..offset + 2]);
offset += 2;
}
255 => {
if offset + 4 >= charstring.len() {
return Err(ParseError::SyntaxError {
position: offset,
message: "Truncated 5-byte fixed-point".to_string(),
});
}
state.operand_positions.push(output.len());
output.extend_from_slice(&charstring[offset..offset + 5]);
offset += 5;
}
}
}
Ok(false)
}
fn subr_item<'a>(
index: &CffIndex,
data: &'a [u8],
subr_index: i32,
position: usize,
) -> ParseResult<&'a [u8]> {
if subr_index < 0 {
return Err(ParseError::SyntaxError {
position,
message: format!("Negative subr index {} after bias", subr_index),
});
}
index
.get_item(subr_index as usize, data)
.ok_or_else(|| ParseError::SyntaxError {
position,
message: format!("Subr index {} out of range", subr_index),
})
}
fn pop_last_operand(
output: &[u8],
operand_positions: &mut Vec<usize>,
) -> ParseResult<(i32, usize)> {
let start = operand_positions
.pop()
.ok_or_else(|| ParseError::SyntaxError {
position: 0,
message: "callsubr/callgsubr with empty operand stack".to_string(),
})?;
let value = decode_type2_number(&output[start..])?;
Ok((value, start))
}
fn decode_type2_number(bytes: &[u8]) -> ParseResult<i32> {
if bytes.is_empty() {
return Err(ParseError::SyntaxError {
position: 0,
message: "Empty number encoding".to_string(),
});
}
let b0 = bytes[0];
match b0 {
32..=246 => Ok(b0 as i32 - 139),
247..=250 => {
if bytes.len() < 2 {
return Err(ParseError::SyntaxError {
position: 0,
message: "Truncated 2-byte positive number".to_string(),
});
}
Ok((b0 as i32 - 247) * 256 + bytes[1] as i32 + 108)
}
251..=254 => {
if bytes.len() < 2 {
return Err(ParseError::SyntaxError {
position: 0,
message: "Truncated 2-byte negative number".to_string(),
});
}
Ok(-(b0 as i32 - 251) * 256 - bytes[1] as i32 - 108)
}
28 => {
if bytes.len() < 3 {
return Err(ParseError::SyntaxError {
position: 0,
message: "Truncated 3-byte integer".to_string(),
});
}
Ok(i16::from_be_bytes([bytes[1], bytes[2]]) as i32)
}
255 => {
if bytes.len() < 5 {
return Err(ParseError::SyntaxError {
position: 0,
message: "Truncated 5-byte fixed-point number".to_string(),
});
}
let fixed = i32::from_be_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]);
Ok(fixed >> 16)
}
_ => Err(ParseError::SyntaxError {
position: 0,
message: format!("Byte {} is not a valid Type 2 number encoding", b0),
}),
}
}