Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 0 additions & 41 deletions src/tables/appid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,44 +58,3 @@ pub(crate) fn decode_modern_split_stream(
unknown,
})
}

#[cfg(test)]
mod tests {
use super::*;
use crate::bitwriter::BitWriter;

#[test]
fn roundtrip_acad_appid() {
let mut w = BitWriter::new();
let s = b"ACAD";
w.write_bs_u(s.len() as u16);
for b in s {
w.write_rc(*b);
}
w.write_b(false);
w.write_bs(0);
w.write_b(false);
w.write_rc(0x00);
let bytes = w.into_bytes();
let mut c = BitCursor::new(&bytes);
let a = decode(&mut c, Version::R2000).unwrap();
assert_eq!(a.header.name, "ACAD");
}

#[test]
fn r2007_split_stream_appid_reads_name_from_string_stream() {
let mut body = BitWriter::new();
body.write_bs_u(0); // no EED
body.write_bl(0); // no reactors
body.write_b(true); // no xdictionary
body.write_b(false); // no binary data
body.write_b(false); // 64-flag
body.write_b(false); // xref dependent
body.write_rc(0x00); // trailing unknown
let bits = crate::string_stream::tests::bits_of(&body);
let payload = crate::string_stream::tests::build_payload(&bits, &["AcadAnnotative"]);
let a = decode_modern_split_stream(&payload, 8, Version::R2018).unwrap();
assert_eq!(a.header.name, "AcadAnnotative");
assert_eq!(a.unknown, 0);
}
}
171 changes: 0 additions & 171 deletions src/tables/ltype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,174 +280,3 @@ fn skip_to(c: &mut BitCursor<'_>, bit: usize) -> Result<()> {
}
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
use crate::bitwriter::BitWriter;

fn write_header(w: &mut BitWriter, name: &[u8]) {
w.write_bs_u(name.len() as u16);
for b in name {
w.write_rc(*b);
}
w.write_b(false);
w.write_bs(0);
w.write_b(false);
}

#[test]
fn roundtrip_dashed_ltype() {
let mut w = BitWriter::new();
write_header(&mut w, b"DASHED");
let desc = b"Dashed ___ __ ";
w.write_bs_u(desc.len() as u16);
for b in desc {
w.write_rc(*b);
}
w.write_bd(0.75); // pattern length
w.write_rc(b'A'); // alignment
w.write_rc(3); // 3 dashes
for (length, shape_flag) in [(0.5_f64, 0i16), (-0.125, 0), (0.125, 0)] {
w.write_bd(length);
w.write_bs(0); // complex shapecode
w.write_rd(0.0); // x_offset
w.write_rd(0.0); // y_offset
w.write_bd(1.0); // scale
w.write_bd(0.0); // rotation
w.write_bs(shape_flag);
}
for _ in 0..LTYPE_TEXT_AREA_BYTES {
w.write_rc(0);
}
let bytes = w.into_bytes();
let mut c = BitCursor::new(&bytes);
let l = decode(&mut c, Version::R2000).unwrap();
assert_eq!(l.header.name, "DASHED");
assert_eq!(l.dashes.len(), 3);
assert_eq!(l.dashes[0].length, 0.5);
assert_eq!(l.dashes[1].length, -0.125);
assert_eq!(l.alignment, b'A');
assert!(matches!(l.dashes[0].text, DashText::None));
}

#[test]
fn roundtrip_text_dash() {
let mut w = BitWriter::new();
write_header(&mut w, b"TEXTLINE");
w.write_bs_u(0); // empty description
w.write_bd(1.0);
w.write_rc(b'A');
w.write_rc(1);
// Dash whose shape flag marks embedded text. §20.4.58 keeps the
// characters in the record's 256-byte text area, not inline, so
// the dash record itself is the same seven fields as any other.
w.write_bd(0.25);
w.write_bs(0); // complex shapecode
w.write_rd(0.1);
w.write_rd(-0.05);
w.write_bd(0.5);
w.write_bd(0.0);
w.write_bs(0x02); // shape flag: has text
for _ in 0..LTYPE_TEXT_AREA_BYTES {
w.write_rc(0);
}
let end = w.position_bits();
let bytes = w.into_bytes();
let mut c = BitCursor::new(&bytes);
let l = decode(&mut c, Version::R2000).unwrap();
assert!(l.dashes[0].has_text());
assert_eq!(l.dashes[0].x_offset, 0.1);
assert_eq!(l.dashes[0].y_offset, -0.05);
assert_eq!(c.position_bits(), end);
}

#[test]
fn r2018_split_stream_ltype_reads_name_and_description_from_string_stream() {
let mut w = BitWriter::new();
// Common object prefix: EED terminator, reactor count,
// xdictionary flag, AcDs binary-data flag.
w.write_bs_u(0);
w.write_bl(0);
w.write_b(true);
w.write_b(false);
// Common table-entry flags.
w.write_b(false); // 64 flag
w.write_b(false); // xdep
w.write_bd(1.0); // pattern length
w.write_rc(b'A'); // alignment
w.write_rc(0); // no dashes

let bits = crate::string_stream::tests::bits_of(&w);
let payload =
crate::string_stream::tests::build_payload(&bits, &["Continuous", "Solid line"]);
let entry = decode_modern_split_stream(&payload, 8, Version::R2018).unwrap();
assert_eq!(entry.header.name, "Continuous");
assert_eq!(entry.description, "Solid line");
assert_eq!(entry.alignment, b'A');
assert!(entry.dashes.is_empty());
}

/// A dash record is `BD` length, `BS` shape code, two `RD`
/// offsets, `BD` scale, `BD` rotation, `BS` shape flag — §20.4.58's
/// order, with the two raw doubles in the middle. The 512-byte text
/// area follows only when some dash sets `shapeflag & 0x02`.
#[test]
fn r2018_split_stream_ltype_reads_a_dash_record() {
let mut w = BitWriter::new();
w.write_bs_u(0);
w.write_b(true);
w.write_b(false);
w.write_b(false);
w.write_b(false);
w.write_bs(0);
w.write_bd(2.0); // pattern length
w.write_rc(b'A');
w.write_rc(2); // two dashes
for (len, shape) in [(0.5f64, 0i16), (-0.25, 0)] {
w.write_bd(len);
w.write_bs(shape);
w.write_rd(0.0);
w.write_rd(0.0);
w.write_bd(1.0);
w.write_bd(0.0);
w.write_bs(0); // shape flag — no text area
}
let bits = crate::string_stream::tests::bits_of(&w);
let payload = crate::string_stream::tests::build_payload(&bits, &["DASHED", ""]);
let entry = decode_modern_split_stream(&payload, 8, Version::R2018).unwrap();
assert_eq!(entry.header.name, "DASHED");
assert_eq!(entry.pattern_length, 2.0);
assert_eq!(entry.dashes.len(), 2);
assert_eq!(entry.dashes[0].length, 0.5);
assert_eq!(entry.dashes[1].length, -0.25);
assert_eq!(entry.dashes[0].scale, 1.0);
}

#[test]
fn rejects_oversized_dash_count() {
let mut w = BitWriter::new();
write_header(&mut w, b"EVIL");
w.write_rc(0);
w.write_rs(0);
w.write_bs_u(0);
w.write_bd(1.0);
w.write_rc(b'A');
// num_dashes is an RC (u8) — 256 wraps to 0, so the cap is
// enforced at or above 256. The worst case the decoder can
// see from a malformed stream is 0xFF = 255, which is within
// the cap. We validate the check is *present* by unit-asserting
// the MAX_DASHES constant instead of trying to overflow.
w.write_rc(255);
// (don't bother filling dashes; decode will read garbage and
// return an error from the bit cursor, which is also fine.)
let bytes = w.into_bytes();
let mut c = BitCursor::new(&bytes);
// Either returns Err from truncated stream OR from cap — both
// acceptable; we just need to confirm decode doesn't allocate
// past the cap.
let err = decode(&mut c, Version::R2000);
assert!(err.is_err(), "expected error for adversarial dash count");
assert_eq!(MAX_DASHES, 256);
}
}
27 changes: 0 additions & 27 deletions src/tables/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,30 +119,3 @@ pub(crate) fn read_tv(c: &mut BitCursor<'_>, version: Version) -> Result<String>
Ok(crate::code_page::decode_active(&bytes))
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::bitwriter::BitWriter;

#[test]
fn roundtrip_entry_header() {
let mut w = BitWriter::new();
// TV name
let s = b"MyLayer";
w.write_bs_u(s.len() as u16);
for b in s {
w.write_rc(*b);
}
w.write_b(false); // 64-flag
w.write_bs(0);
w.write_b(false); // not xref dependent
let bytes = w.into_bytes();
let mut c = BitCursor::new(&bytes);
let h = read_table_entry_header(&mut c, Version::R2000).unwrap();
assert_eq!(h.name, "MyLayer");
assert!(!h.flag_64);
assert_eq!(h.xref_index_plus_1, 0);
assert!(!h.is_xref_dependent);
}
}
136 changes: 0 additions & 136 deletions src/tables/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,139 +174,3 @@ pub(crate) fn decode_modern_split_stream(
bigfont_filename,
})
}

#[cfg(test)]
mod tests {
use super::*;
use crate::bitwriter::BitWriter;

fn write_header(w: &mut BitWriter, name: &[u8]) {
w.write_bs_u(name.len() as u16);
for b in name {
w.write_rc(*b);
}
w.write_b(false);
w.write_bs(0);
w.write_b(false);
}

#[test]
fn roundtrip_standard_style() {
let mut w = BitWriter::new();
write_header(&mut w, b"Standard");
w.write_b(false); // shape file
w.write_b(false); // vertical
w.write_bd(0.0); // fixed_height — prompt
w.write_bd(1.0); // width factor
w.write_bd(0.0); // oblique
w.write_rc(0); // generation normal
w.write_bd(2.5); // last_height
let font = b"arial.ttf";
w.write_bs_u(font.len() as u16);
for b in font {
w.write_rc(*b);
}
w.write_bs_u(0); // no bigfont
let bytes = w.into_bytes();
let mut c = BitCursor::new(&bytes);
let s = decode(&mut c, Version::R2000).unwrap();
assert_eq!(s.header.name, "Standard");
assert_eq!(s.width_factor, 1.0);
assert_eq!(s.last_height, 2.5);
assert!(s.font_filename.starts_with("arial"));
assert!(s.bigfont_filename.is_empty());
assert!(!s.is_shape_file());
assert!(!s.is_vertical());
}

#[test]
fn r2007_split_stream_style_reads_names_from_string_stream() {
// Data stream: common object prefix, then the STYLE field body.
let mut body = BitWriter::new();
body.write_bs_u(0); // no EED
body.write_bl(0); // no reactors
body.write_b(true); // no xdictionary
body.write_b(false); // no binary data (R2013+)
body.write_b(true); // 64-flag
body.write_b(false); // xref dependent
body.write_b(true); // shape file
body.write_b(true); // vertical
body.write_bd(0.2); // fixed height
body.write_bd(1.0); // width factor
body.write_bd(0.0); // oblique angle
body.write_rc(0x04); // generation — upside down
body.write_bd(2.5); // last height
let bits = crate::string_stream::tests::bits_of(&body);

let payload = crate::string_stream::tests::build_payload(
&bits,
&["Standard", "arial.ttf", "bigfont.shx"],
);
let s = decode_modern_split_stream(&payload, 8, Version::R2018).unwrap();
assert_eq!(s.header.name, "Standard");
assert_eq!(s.font_filename, "arial.ttf");
assert_eq!(s.bigfont_filename, "bigfont.shx");
assert!(s.header.flag_64);
assert!(s.is_vertical());
assert!(s.is_shape_file());
assert_eq!(s.fixed_height, 0.2);
assert_eq!(s.width_factor, 1.0);
assert_eq!(s.oblique_angle, 0.0);
assert_eq!(s.generation, 0x04);
assert_eq!(s.last_height, 2.5);
}

#[test]
fn r2007_split_stream_style_rejects_misaligned_body() {
// One extra bit in the data stream must be caught, not silently
// absorbed — the field body no longer ends on the string start.
let mut body = BitWriter::new();
body.write_bs_u(0);
body.write_b(true);
body.write_b(false);
body.write_b(false);
body.write_b(false);
body.write_bs(0);
body.write_b(false);
body.write_b(false);
body.write_bd(0.0);
body.write_bd(1.0);
body.write_bd(0.0);
body.write_rc(0);
body.write_bd(0.0);
body.write_b(false); // stray bit
let bits = crate::string_stream::tests::bits_of(&body);
let payload = crate::string_stream::tests::build_payload(&bits, &["Standard", "", ""]);
let err = decode_modern_split_stream(&payload, 8, Version::R2018).unwrap_err();
assert!(
format!("{err}").contains("STYLE data fields ended"),
"{err}"
);
}

#[test]
fn roundtrip_vertical_shape_style() {
let mut w = BitWriter::new();
write_header(&mut w, b"TXT-V");
w.write_b(true); // shape file
w.write_b(true); // vertical
w.write_bd(0.2); // fixed height
w.write_bd(0.9); // width factor
w.write_bd(15.0_f64.to_radians());
w.write_rc(0x04); // upside-down
w.write_bd(0.2);
let font = b"txt.shx";
w.write_bs_u(font.len() as u16);
for b in font {
w.write_rc(*b);
}
w.write_bs_u(0);
let bytes = w.into_bytes();
let mut c = BitCursor::new(&bytes);
let s = decode(&mut c, Version::R2000).unwrap();
assert!(s.is_shape_file());
assert!(s.is_vertical());
assert_eq!(s.generation, 0x04);
assert!((s.oblique_angle - 15.0_f64.to_radians()).abs() < 1e-12);
}
}
Loading