use crate::nurbs::reader::INT_WIDTHS;
use crate::sab::{int_le_at, Record};
use cadmpeg_core::decode::View;
pub(crate) fn owned_subtype_defs(bytes: &[u8], int_width: usize) -> Vec<(usize, &[u8])> {
let mut owned = Vec::new();
let mut depth = 0usize;
let mut pos = 0usize;
while pos < bytes.len() {
match bytes[pos] {
0x0f => {
if depth == 0 && matches!(bytes.get(pos + 1), Some(0x0d | 0x0e)) {
let len = usize::from(*bytes.get(pos + 2).unwrap_or(&0));
if let Some(name) = bytes.get(pos + 3..pos + 3 + len) {
owned.push((pos, name));
}
}
depth += 1;
}
0x10 => depth = depth.saturating_sub(1),
_ => {}
}
match next_token(bytes, pos, int_width) {
Some(next) => pos = next,
None => break,
}
}
owned
}
pub(crate) fn find_owned_subtype_marker<'n>(
bytes: &[u8],
names: &[&'n [u8]],
int_width: usize,
) -> Option<(usize, &'n [u8])> {
let owned = owned_subtype_defs(bytes, int_width);
names.iter().copied().find_map(|name| {
owned
.iter()
.find(|(_, owned_name)| *owned_name == name)
.map(|(start, _)| (*start, name))
})
}
pub(crate) fn find_owned_intcurve_subtype(
bytes: &[u8],
modern: &[u8],
int_width: usize,
) -> Option<(usize, usize)> {
let legacy: &[u8] = match modern {
b"blend_int_cur" => b"bldcur",
b"spring_int_cur" => b"blndsprngcur",
b"exact_int_cur" => b"exactcur",
b"law_int_cur" => b"lawintcur",
b"off_int_cur" => b"offintcur",
b"offset_int_cur" => b"offsetintcur",
b"off_surf_int_cur" => b"offsurfintcur",
b"para_silh_int_cur" => b"parasil",
b"par_int_cur" => b"parcur",
b"proj_int_cur" => b"projcur",
b"surf_int_cur" => b"surfcur",
b"int_int_cur" => b"surfintcur",
b"skin_int_cur" => b"d5c2_cur",
b"subset_int_cur" => b"subsetintcur",
_ => b"",
};
let candidates: Vec<&[u8]> = [modern, legacy]
.into_iter()
.filter(|name| !name.is_empty())
.collect();
find_owned_subtype_marker(bytes, &candidates, int_width)
.map(|(marker, name)| (marker, name.len()))
}
pub(crate) fn decode_cache_resolving_refs<T>(
bytes: &[u8],
active_bytes: &[u8],
tables: &SubtypeTables,
seen: &mut Vec<usize>,
decode_inline: fn(&[u8], usize) -> Option<T>,
int_width: usize,
) -> Option<T> {
if let Some(decoded) = decode_inline(bytes, int_width) {
return Some(decoded);
}
let table = tables.for_width(int_width);
for index in subtype_refs(bytes, int_width) {
if seen.contains(&index) {
continue;
}
let target = *table.get(index)?;
seen.push(index);
if let Some(decoded) = decode_cache_resolving_refs(
subtype_span(active_bytes, target, int_width)?,
active_bytes,
tables,
seen,
decode_inline,
int_width,
) {
return Some(decoded);
}
}
None
}
pub struct SubtypeTables {
tables: [Vec<usize>; INT_WIDTHS.len()],
}
impl SubtypeTables {
pub fn from_records(records: &[Record], bytes: &[u8]) -> Self {
Self {
tables: INT_WIDTHS.map(|walk_width| {
let mut table = Vec::new();
for record in records {
collect_defs_in_span(
bytes,
record.offset,
record.offset + record.len,
walk_width,
&mut table,
);
}
table
}),
}
}
pub fn from_stream(bytes: &[u8]) -> Self {
Self {
tables: INT_WIDTHS.map(|walk_width| {
let mut table = Vec::new();
collect_defs_in_span(bytes, 0, bytes.len(), walk_width, &mut table);
table
}),
}
}
pub fn for_width(&self, int_width: usize) -> &[usize] {
INT_WIDTHS
.iter()
.position(|&width| width == int_width)
.map_or(&[], |slot| self.tables[slot].as_slice())
}
pub fn index_of_offset(&self, int_width: usize, offset: usize) -> Option<usize> {
self.for_width(int_width)
.iter()
.position(|candidate| *candidate == offset)
}
}
fn collect_defs_in_span(
bytes: &[u8],
start: usize,
end: usize,
int_width: usize,
table: &mut Vec<usize>,
) {
let end = end.min(bytes.len());
let mut pos = start;
while pos < end {
if bytes[pos] == 0x0f && matches!(bytes.get(pos + 1), Some(0x0d | 0x0e)) {
let len = usize::from(*bytes.get(pos + 2).unwrap_or(&0));
if let Some(name) = bytes.get(pos + 3..pos + 3 + len) {
if name != b"ref" {
table.push(pos);
}
}
}
match next_token(bytes, pos, int_width) {
Some(next) => pos = next,
None => return,
}
}
}
pub(crate) fn subtype_refs(bytes: &[u8], int_width: usize) -> Vec<usize> {
let mut refs = Vec::new();
let marker = b"\x0f\x0d\x03ref\x04";
let mut pos = 0usize;
while pos < bytes.len() {
if bytes[pos..].starts_with(marker) {
if let Some(index) = int_le_at(bytes, pos + marker.len(), int_width) {
if index >= 0 {
refs.push(index as usize);
}
}
} else if bytes.get(pos) == Some(&0x0f)
&& bytes.get(pos + 1) == Some(&0x04)
&& bytes.get(pos + 2 + int_width) == Some(&0x10)
{
if let Some(index) = int_le_at(bytes, pos + 2, int_width) {
if index >= 0 {
refs.push(index as usize);
}
}
}
match next_token(bytes, pos, int_width) {
Some(next) => pos = next,
None => break,
}
}
refs
}
pub fn subtype_span(bytes: &[u8], start: usize, int_width: usize) -> Option<&[u8]> {
let mut depth = 0usize;
let mut pos = start;
while pos < bytes.len() {
match bytes[pos] {
0x0f => depth += 1,
0x10 => {
depth = depth.checked_sub(1)?;
if depth == 0 {
return bytes.get(start..=pos);
}
}
_ => {}
}
pos = next_token(bytes, pos, int_width)?;
}
None
}
pub(crate) fn next_token(bytes: &[u8], pos: usize, int_width: usize) -> Option<usize> {
let tag = *bytes.get(pos)?;
let fixed = match tag {
0x02 => 2,
0x03 => 3,
0x04 | 0x0c | 0x15 => 1 + int_width,
0x06 | 0x17 => 9,
0x05 => 5,
0x0a | 0x0b | 0x0f | 0x10 | 0x11 => 1,
0x13 | 0x14 => 25,
0x16 => 17,
0x07 | 0x0d | 0x0e => 2 + usize::from(*bytes.get(pos + 1)?),
0x08 => 3 + usize::from(View::u16_le_at(bytes, pos + 1)?),
0x09 | 0x12 => {
let length = int_le_at(bytes, pos + 1, int_width)?;
1 + int_width + usize::try_from(length).ok()?
}
_ => return None,
};
let next = pos.checked_add(fixed)?;
(next <= bytes.len()).then_some(next)
}
#[cfg(test)]
mod ownership_tests {
use super::*;
fn open(bytes: &mut Vec<u8>, name: &[u8]) {
bytes.push(0x0f);
bytes.push(0x0d);
bytes.push(u8::try_from(name.len()).expect("short name"));
bytes.extend_from_slice(name);
}
#[test]
fn a_nested_intcurve_construction_does_not_own_the_record() {
for int_width in [4usize, 8] {
let mut bytes = Vec::new();
open(&mut bytes, b"defm_int_cur");
bytes.push(0x04);
bytes.extend_from_slice(&vec![0u8; int_width]);
open(&mut bytes, b"int_int_cur");
bytes.push(0x10);
bytes.push(0x10);
assert_eq!(
find_owned_intcurve_subtype(&bytes, b"defm_int_cur", int_width),
Some((0, b"defm_int_cur".len()))
);
assert_eq!(
find_owned_intcurve_subtype(&bytes, b"int_int_cur", int_width),
None
);
assert_eq!(
crate::nurbs::toks::owned_construction_subtype(&crate::nurbs::toks::lex_test_span(
&bytes, int_width
))
.as_deref(),
Some("defm_int_cur")
);
}
}
}