diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a211306 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "klamath_rs_ext" +version = "0.2.0" +authors = ["jan "] +edition = "2021" + + +[lib] +name = "klamath_rs_ext" +crate-type = ["cdylib", "rlib"] + + +[dependencies] +byteorder = "^1" diff --git a/klamath_rs_ext/__init__.py b/klamath_rs_ext/__init__.py new file mode 100644 index 0000000..d042d92 --- /dev/null +++ b/klamath_rs_ext/__init__.py @@ -0,0 +1,6 @@ +from .basic import pack_int2 as pack_int2 +from .basic import pack_int4 as pack_int4 + + +__version__ = 0.2 + diff --git a/klamath_rs_ext/basic.py b/klamath_rs_ext/basic.py new file mode 100644 index 0000000..aabc246 --- /dev/null +++ b/klamath_rs_ext/basic.py @@ -0,0 +1,92 @@ +from collections.abc import Sequence +import ctypes +from pathlib import Path +from itertools import chain + +import numpy +from numpy.typing import NDArray + +from .klamath_rs_ext import lib, ffi + + +CONV_TABLE_i16 = { + numpy.float64: lib.f64_to_i16, + numpy.float32: lib.f32_to_i16, + numpy.int64: lib.i64_to_i16, + numpy.int32: lib.i32_to_i16, + numpy.int16: lib.i16_to_i16, + numpy.uint64: lib.u64_to_i16, + numpy.uint32: lib.u32_to_i16, + numpy.uint16: lib.u16_to_i16, + } + +CONV_TABLE_i32 = { + numpy.float64: lib.f64_to_i32, + numpy.float32: lib.f32_to_i32, + numpy.int64: lib.i64_to_i32, + numpy.int32: lib.i32_to_i32, + numpy.uint64: lib.u64_to_i32, + numpy.uint32: lib.u32_to_i32, + } + + + +def pack_int2(data: NDArray[numpy.integer] | Sequence[int] | int) -> bytes: + arr = numpy.atleast_1d(data) + + for dtype in CONV_TABLE_i16.keys(): + if arr.dtype != dtype: + continue + + arr = numpy.require(arr, requirements=('C_CONTIGUOUS', 'ALIGNED', 'WRITEABLE', 'OWNDATA')) + if arr is data: + arr = numpy.array(arr, copy=True) + + fn = CONV_TABLE_i16[dtype] + buf = ffi.from_buffer(ffi.typeof(fn).args[0], arr, require_writable=True) + result = fn(buf, arr.size) + + if result != 0: + raise ValueError(f'Invalid value for conversion to Int2: {result}') + + i2arr = arr.view('>i2')[::arr.itemsize // 2] + return i2arr.tobytes() + + if arr.dtype == numpy.dtype('>i2'): + return arr.tobytes() + + if (arr > 32767).any() or (arr < -32768).any(): + raise Exception(f'int2 data out of range: {arr}') + + return arr.astype('>i2').tobytes() + + +def pack_int4(data: NDArray[numpy.integer] | Sequence[int] | int) -> bytes: + arr = numpy.atleast_1d(data) + + for dtype in CONV_TABLE_i32.keys(): + if arr.dtype != dtype: + continue + + arr = numpy.require(arr, requirements=('C_CONTIGUOUS', 'ALIGNED', 'WRITEABLE', 'OWNDATA')) + if arr is data: + arr = numpy.array(arr, copy=True) + + fn = CONV_TABLE_i32[dtype] + buf = ffi.from_buffer(ffi.typeof(fn).args[0], arr, require_writable=True) + result = fn(buf, arr.size) + + if result != 0: + raise ValueError(f'Invalid value for conversion to Int4: {result}') + + i4arr = arr.view('>i4')[::arr.itemsize // 4] + return i4arr.tobytes() + + if arr.dtype == numpy.dtype('>i4'): + return arr.tobytes() + + if (arr > 2147483647).any() or (arr < -2147483648).any(): + raise Exception(f'int4 data out of range: {arr}') + + return arr.astype('>i4').tobytes() + diff --git a/klamath_rs_ext/py.typed b/klamath_rs_ext/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..06aa1af --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,37 @@ +[build-system] +requires = ["maturin>1.0,<2.0"] +build-backend = "maturin" + +[project] +name = "klamath_rs_ext" +description = "Compiled extensions for klamath GDS library" +#readme = "README.md" +#license = { file = "LICENSE.md" } +authors = [ + { name="Jan Petykiewicz", email="jan@mpxd.net" }, + ] +homepage = "https://mpxd.net/code/jan/klamath-rs" +repository = "https://mpxd.net/code/jan/klamath-rs" +classifiers = [ + "Programming Language :: Python :: 3", + "Development Status :: 4 - Beta", +# "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Intended Audience :: Information Technology", + "Intended Audience :: Manufacturing", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + "Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)", + ] +requires-python = ">=3.11" +#include = [ +# "LICENSE.md" +# ] +dynamic = ["version"] +dependencies = [ + "cffi", + ] + +[tool.maturin] +bindings = "cffi" + diff --git a/src/basic.rs b/src/basic.rs new file mode 100644 index 0000000..1019be1 --- /dev/null +++ b/src/basic.rs @@ -0,0 +1,393 @@ +/// +/// Functionality for parsing and writing basic data types +/// +use byteorder::{ByteOrder, BigEndian}; +use std::io; +use std::fmt; + +pub type OResult = Result; + +pub type IResult<'a, O> = Result<(&'a [u8], O), (&'a [u8], ErrType)>; + +#[derive(Debug)] +pub enum ErrType { + Incomplete(Option), + Failed(String), +} + +pub fn fail(input: &[u8], msg: String) -> IResult { + Err((input, ErrType::Failed(msg))) +} + +pub fn incomplete(input: &[u8], size: Option) -> IResult { + Err((input, ErrType::Incomplete(size))) +} + +pub fn take_bytes>(input: &[u8], count: CC) -> IResult<&[u8]> { + let cc = count.into(); + if input.len() < cc { + incomplete(input, Some(cc)) + } else { + let (taken, input) = input.split_at(cc); + Ok((input, taken)) + } +} + + +/* + * Parse functions + */ +pub fn parse_u16(input: &[u8]) -> IResult { + let (input, buf) = take_bytes(input, 2_usize)?; + let val = BigEndian::read_u16(&buf); + Ok((input, val)) +} + +pub fn parse_int2(input: &[u8]) -> IResult { + let (input, buf) = take_bytes(input, 2_usize)?; + let val = BigEndian::read_i16(&buf); + Ok((input, val)) +} + +pub fn parse_int4(input: &[u8]) -> IResult { + let (input, buf) = take_bytes(input, 4_usize)?; + let val = BigEndian::read_i32(&buf); + Ok((input, val)) +} + +/// Convert GDS REAL8 to IEEE float64 +pub fn decode_real8(int: u64) -> f64 { + let neg = int & 0x8000_0000_0000_0000; + let exp = (int >> 56) & 0x7f; + let mut mant = (int & 0x00ff_ffff_ffff_ffff) as f64; + if neg != 0 { + mant *= -1.0 + } + let exp2 = 4 * (exp as i32 - 64) - 56; + mant * 2_f64.powi(exp2) +} + +pub fn parse_real8(input: &[u8]) -> IResult { + let (input, buf) = take_bytes(input, 8_usize)?; + let data = BigEndian::read_u64(&buf); + Ok((input, decode_real8(data))) +} + + +pub fn parse_datetime(input: &[u8]) -> IResult<[i16; 6]> { + let mut buf = [0_i16; 6]; + let mut input = input; + for ii in 0..6 { + (input, buf[ii]) = parse_int2(input)?; + } + buf[0] += 1900; // Year is from 1900 + Ok((input, buf)) +} + + +pub fn parse_bitarray(input: &[u8]) -> IResult<[bool; 16]> { + let mut bits = [false; 16]; + let (input, val) = parse_int2(input)?; + for ii in 0..16 { + bits[ii] = ((val >> (16 - 1 - ii)) & 0x01) == 1; + } + Ok((input, bits)) +} + + +pub fn parse_ascii(input: &[u8], length: u16) -> IResult> { + let length = length as usize; + let (input, data) = take_bytes(input, length)?; + let last = data[length - 1]; + let true_length = if last == 0 { length - 1 } else { length }; + let vec = data[..true_length].to_vec(); + Ok((input, vec)) +} + + +/* + * Pack functions + */ + +pub fn bitarray2int(bits: &[bool; 16]) -> u16 { + let mut int: u16 = 0; + for ii in 0..16 { + int |= (bits[ii] as u16) << (16 - 1 - ii); + } + int +} + +pub fn pack_bitarray(buf: &mut [u8], bits: &[bool; 16]) { + BigEndian::write_u16(buf, bitarray2int(bits)); +} + + +pub fn pack_int2(buf: &mut [u8], int: i16) { + BigEndian::write_i16(buf, int); +} + +pub fn pack_int4(buf: &mut [u8], int: i32) { + BigEndian::write_i32(buf, int); +} + +pub fn pack_real8(buf: &mut [u8], fnum: f64) -> Result<(), FloatTooBigError> { + BigEndian::write_u64(buf, encode_real8(fnum)?); + Ok(()) +} + +pub fn pack_ascii(buf: &mut [u8], data: &[u8]) -> usize { + let len = data.len(); + buf[..len].copy_from_slice(data); + if len % 2 == 1 { + buf[len] = 0; + len + 1 + } else { + len + } +} + + +pub fn pack_datetime(buf: &mut [u8], date: &[i16; 6]) { + assert!(buf.len() >= 6 * 2); + let year = date[0] - 1900; + pack_int2(buf, year); + for ii in 1..6 { + pack_int2(&mut buf[(2 * ii)..], date[ii]); + } +} + + +#[derive(Debug, Clone)] +pub struct FloatTooBigError { + float_value: f64, +} +impl fmt::Display for FloatTooBigError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Float {0} is too large for Real8", self.float_value) + } +} + +/// Convert from float64 to GDS REAL8 representation. +pub fn encode_real8(fnum: f64) -> Result { + // Split the ieee float bitfields + let ieee = fnum.to_bits(); + let sign = ieee & 0x8000_0000_0000_0000; + let ieee_exp = (ieee >> 52) as i32 & 0x7ff; + let ieee_mant = ieee & 0x000f_ffff_ffff_ffff; + + let subnorm = (ieee_exp == 0) & (ieee_mant != 0); + if (ieee_exp == 0) & (ieee_mant == 0) { + return Ok(0) + } + + // IEEE normal double is (1 + ieee_mant / 2^52) * 2^(ieee_exp - 1023) + // IEEE subnormal double is (ieee_mant / 2^52) * 2^(-1022) + // GDS real8 is (gds_mant / 2^(7*8)) * 16^(gds_exp - 64) + // = (gds_mant / 2^56) * 2^(4 * gds_exp - 256) + + // Convert exponent. + let exp2 = if subnorm { -1022 } else {ieee_exp + 1 - 1023}; // +1 is due to mantissa (1.xxxx in IEEE vs 0.xxxxx in GDSII) + let mut exp16 = exp2 / 4; + let rest = exp2 % 4; + + // Compensate for exponent coarseness + let comp = rest != 0; + let mut shift; + if comp { + exp16 += 1; + shift = 4 - rest; + } else { + shift = rest; + } + shift -= 3; // account for gds bit position + + // add leading one + let mut gds_mant_unshifted = ieee_mant; + if !subnorm { + gds_mant_unshifted += 0x10_0000_0000_0000; + } + + let mut gds_mant = if shift > 0 { + gds_mant_unshifted >> shift + } else { + gds_mant_unshifted << -shift + }; + + // add gds exponent bias + let mut gds_exp = exp16 + 64; + + if gds_exp < -14 { + // number is too small + return Ok(0) + } + + let neg_biased = gds_exp < 0; + if neg_biased { + gds_mant >>= gds_exp * 4; + gds_exp = 0; + } + + let too_big = (gds_exp > 0x7f) & !subnorm; + if too_big { + return Err(FloatTooBigError{float_value: fnum}); + } + + let gds_exp_bits = (gds_exp as u64) << 56; + + let real8 = sign | gds_exp_bits | gds_mant; + Ok(real8) +} + + + +#[cfg(test)] +mod tests { + #[test] + fn test_parse_bitarray() { + use crate::basic::parse_bitarray; + + //assert!(parse_bitarray(b"59") == 13625); + assert_eq!(parse_bitarray(b"\x00\x00").unwrap().1, [false; 16]); + assert_eq!(parse_bitarray(b"\xff\xff").unwrap().1, [true; 16]); + + let arr_0001 = parse_bitarray(b"\x00\x01").unwrap().1; + for (ii, &vv) in arr_0001.iter().enumerate() { + assert_eq!(ii == 15, vv); + } + + let arr_8000 = parse_bitarray(b"\x80\x00").unwrap().1; + for (ii, &vv) in arr_8000.iter().enumerate() { + assert_eq!(ii == 0, vv); + } + } + + #[test] + fn test_parse_int2() { + use crate::basic::parse_int2; + assert_eq!(parse_int2(b"59").unwrap().1, 13625); + assert_eq!(parse_int2(b"\0\0").unwrap().1, 0); + assert_eq!(parse_int2(b"\xff\xff").unwrap().1, -1); + } + + #[test] + fn test_parse_int4() { + use crate::basic::parse_int4; + assert_eq!(parse_int4(b"4321").unwrap().1, 875770417); + } + + #[test] + fn test_decode_real8() { + use crate::basic::decode_real8; + + // zeroes + assert_eq!(decode_real8(0x0), 0.0); + assert_eq!(decode_real8(1<<63), 0.0); // negative + assert_eq!(decode_real8(0xff << 56), 0.0); // denormalized + + assert_eq!(decode_real8(0x4110 << 48), 1.0); + assert_eq!(decode_real8(0xC120 << 48), -2.0); + } + + #[test] + #[should_panic] + fn test_encode_real8_panic() { + use crate::basic::encode_real8; + encode_real8(1e80).unwrap(); + } + + + #[test] + fn test_parse_real8() { + use crate::basic:: parse_real8; + + assert_eq!(0.0, parse_real8(&[0; 8]).unwrap().1); + assert_eq!(1.0, parse_real8(&[0x41, 0x10, 0, 0, 0, 0, 0, 0]).unwrap().1); + assert_eq!(-2.0, parse_real8(&[0xC1, 0x20, 0, 0, 0, 0, 0, 0]).unwrap().1); + } + + #[test] + fn test_parse_ascii() { + use crate::basic::parse_ascii; + + assert_eq!(parse_ascii(b"12345", 5).unwrap().1, b"12345"); + assert_eq!(parse_ascii(b"12345\0", 6).unwrap().1, b"12345"); // strips trailing null byte + assert_eq!(parse_ascii(b"123456", 6).unwrap().1, b"123456"); + } + + #[test] + fn test_pack_bitarray() { + use crate::basic::pack_bitarray; + let mut buf = [10; 3]; + let mut bools = [false; 16]; + bools[1] = true; + bools[2] = true; + bools[11] = true; + + pack_bitarray(&mut buf, &bools); + assert_eq!(buf[0], 0b0110_0000); + assert_eq!(buf[1], 0b0001_0000); + assert_eq!(buf[2], 10); + } + + #[test] + fn test_pack_int2() { + use crate::basic::pack_int2; + let mut buf = [10; 3 * 2]; + pack_int2(&mut buf, -3); + pack_int2(&mut buf[2..], 2); + pack_int2(&mut buf[4..], -1); + assert_eq!(buf[0..2], [0xFF, 0xFD]); + assert_eq!(buf[2..4], [0x00, 0x02]); + assert_eq!(buf[4..6], [0xFF, 0xFF]); + } + + #[test] + fn test_pack_int4() { + use crate::basic::pack_int4; + let mut buf = [10; 3 * 4]; + pack_int4(&mut buf, -3); + pack_int4(&mut buf[4..], 2); + pack_int4(&mut buf[8..], -1); + assert_eq!(buf[0..4], [0xFF, 0xFF, 0xFF, 0xFD]); + assert_eq!(buf[4..8], [0x00, 0x00, 0x00, 0x02]); + assert_eq!(buf[8..12], [0xFF, 0xFF, 0xFF, 0xFF]); + } + + #[test] + fn test_encode_real8() { + use crate::basic::{encode_real8, decode_real8}; + const REALS: [f64; 5] = [1.0, -2.0, 1e-9, 1e-3, 1e-12]; + for vv in REALS { + print!("{vv}\n"); + assert!((decode_real8(encode_real8(vv).unwrap()) - vv).abs() < f64::EPSILON); + } + } + + #[test] + fn test_pack_real8() { + use crate::basic::{pack_real8, parse_real8}; + const COUNT: usize = 7; + const REALS: [f64; COUNT] = [0.0, 1.0, -1.0, 0.5, 1e-9, 1e-3, 1e-12]; + let mut buf = [10; 8 * COUNT]; + for (ii, &vv) in REALS.iter().enumerate() { + pack_real8(&mut buf[ii * 8..], vv).unwrap(); + } + for (ii, &vv) in REALS.iter().enumerate() { + print!("{vv}\n"); + let parsed_val = parse_real8(&buf[ii * 8..]).unwrap().1; + assert!((parsed_val - vv).abs() < f64::EPSILON); + } + } + + #[test] + fn test_pack_ascii() { + use crate::basic::pack_ascii; + let mut buf = [10; 12]; + pack_ascii(&mut buf[0..], "4321".as_bytes()); + pack_ascii(&mut buf[6..], "321".as_bytes()); + assert_eq!(&buf[0..4], "4321".as_bytes()); + assert_eq!(&buf[4..6], [10, 10]); + assert_eq!(&buf[6..9], "321".as_bytes()); + assert_eq!(&buf[9..], [0, 10, 10]); + } +} diff --git a/src/elements.rs b/src/elements.rs new file mode 100644 index 0000000..b711da2 --- /dev/null +++ b/src/elements.rs @@ -0,0 +1,532 @@ +/// +/// Functionality for reading/writing elements (geometry, text labels, +/// structure references) and associated properties. +/// + +use crate::records::{BOX, BOUNDARY, NODE, PATH, TEXT, SREF, AREF, + DATATYPE, PATHTYPE, BOXTYPE, NODETYPE, TEXTTYPE, + LAYER, XY, WIDTH, COLROW, PRESENTATION, STRING, + STRANS, MAG, ANGLE, PROPATTR, PROPVALUE, + ENDEL, BGNEXTN, ENDEXTN, SNAME, + }; + +use crate::records; +use crate::record::{RecordHeader, Record}; +use crate::basic::{OResult, IResult, fail}; + +use std::collections::HashMap; +use std::io::Write; + + +/// +/// Read element properties. +/// +/// Assumes PROPATTR records have unique values. +/// Stops reading after consuming ENDEL record. +/// +/// Args: +/// stream: Stream to read from. +/// +/// Returns: +/// propattr: -> propvalue mapping +/// +pub fn read_properties(input: &[u8]) -> IResult>> { + let mut properties = HashMap::new(); + + let (mut input, mut header) = RecordHeader::read(input)?; + while header.tag != ENDEL::tag() { + if header.tag == PROPATTR::tag() { + let result = PROPATTR::read_data(input, header.data_size)?; + input = result.0; + let key = result.1; + let result = PROPVALUE::read(input)?; + input = result.0; + let value = result.1; + assert!(!properties.contains_key(&key), "Duplicate property key: {}", key); + properties.insert(key, value); + } + (input, header) = RecordHeader::read(input)?; + } + Ok((input, properties)) +} + + +/// +/// Write element properties. +/// +/// This is does _not_ write the ENDEL record. +/// +/// Args: +/// stream: Stream to write to. +/// +pub fn write_properties(ww: &mut W, properties: &HashMap::>) -> OResult { + let mut size = 0; + for (key, value) in properties { + size += PROPATTR::write(ww, key)?; + size += PROPVALUE::write(ww, value)?; + } + Ok(size) +} + +pub trait Element { + /// + /// Read from a stream to construct this object. + /// Consumes up to (and including) the ENDEL record. + /// + fn read(input: &[u8]) -> IResult where Self: Sized; + + /// + /// Write this element to a stream. + /// Finishes with an ENDEL record. + /// + fn write(&self, ww: &mut W) -> OResult; +} + +/// +/// Datastructure representing +/// an instance of a structure (SREF / structure reference) or +/// an array of instances (AREF / array reference). +/// Type is determined by the presence of the `colrow` tuple. +/// +/// Transforms are applied to each individual instance (_not_ +/// to the instance's origin location || array vectors). +/// +#[derive(Debug, Clone)] +pub struct Reference { + /// Name of the structure being referenced. + struct_name: Vec, + /// Whether to mirror the pattern (negate y-values / flip across x-axis). Default false. + invert_y: bool, + /// Scaling factor (default 1) + mag: f64, + /// Rotation (degrees counterclockwise) + angle_deg: f64, + + /// (For SREF) Location in the parent structure corresponding to the instance's origin (0, 0). + /// (For AREF) 3 locations: + /// [`offset`, + /// `offset + col_basis_vector * colrow[0]`, + /// `offset + row_basis_vector * colrow[1]`] + /// which define the first instance's offset and the array's basis vectors. + /// Note that many GDS implementations only support manhattan basis vectors, and some + /// assume a certain axis mapping (e.g. x->columns, y->rows) and "reinterpret" the + /// basis vectors to match it. + xy: Vec, + + /// Number of columns and rows (AREF) || None (SREF) + colrow: Option<(i16, i16)>, + /// Properties associated with this reference. + properties: HashMap::>, +} + +impl Element for Reference { + fn read(input: &[u8]) -> IResult { + let mut invert_y = false; + let mut mag = 1.0; + let mut angle_deg = 0.0; + let mut colrow = None; + let (input, struct_name) = SNAME::skip_and_read(input)?; + + let (mut input, mut header) = RecordHeader::read(input)?; + while header.tag != records::RTAG_XY { + match header.tag { + records::RTAG_STRANS => { + let result = STRANS::read_data(input, header.data_size)?; + input = result.0; + invert_y = result.1[0]; + }, + records::RTAG_MAG => + {(input, mag) = MAG::read_data(input, header.data_size)?;}, + records::RTAG_ANGLE => + {(input, angle_deg) = ANGLE::read_data(input, header.data_size)?;}, + records::RTAG_COLROW => { + let result = COLROW::read_data(input, header.data_size)?; + input = result.0; + colrow = Some((result.1[0], result.1[1])); + }, + _ => + return fail(input, format!("Unexpected tag {:04x}", header.tag)), + }; + (input, header) = RecordHeader::read(input)?; + } + let (input, xy) = XY::read_data(input, header.data_size)?; + let (input, properties) = read_properties(input)?; + Ok((input, Reference{ + struct_name: struct_name, + xy: xy, + properties: properties, + colrow: colrow, + invert_y: invert_y, + mag: mag, + angle_deg: angle_deg + })) + } + + fn write(&self, ww: &mut W) -> OResult { + let mut size = 0; + size += match self.colrow { + None => SREF::write(ww, &())?, + Some(_) => AREF::write(ww, &())?, + }; + + size += SNAME::write(ww, &self.struct_name)?; + if self.angle_deg != 0.0 || self.mag != 1.0 || self.invert_y { + let strans = { + let mut arr = [false; 16]; + arr[0] = self.invert_y; + arr + }; + size += STRANS::write(ww, &strans)?; + if self.mag != 1.0 { + size += MAG::write(ww, &self.mag)?; + } + if self.angle_deg != 0.0 { + size += ANGLE::write(ww, &self.angle_deg)?; + } + } + + if let Some(cr) = self.colrow { + size += COLROW::write(ww, &vec!{cr.0, cr.1})?; + } + + size += XY::write(ww, &self.xy)?; + size += write_properties(ww, &self.properties)?; + size += ENDEL::write(ww, &())?; + Ok(size) + } +} + +impl Reference { + pub fn check(&self) { + if self.colrow.is_some() { + assert!(self.xy.len() != 6, "colrow is Some, so expected size-6 xy. Got {:?}", self.xy); + } else { + assert!(self.xy.len() != 2, "Expected size-2 xy. Got {:?}", self.xy); + } + } +} + + +/// +/// Datastructure representing a Boundary element. +/// +#[derive(Debug, Clone)] +pub struct Boundary { + /// (layer, data_type) tuple + layer: (i16, i16), + /// Ordered vertices of the shape. First and last points should be identical. Order x0, y0, x1,... + xy: Vec, + /// Properties for the element. + properties: HashMap::>, +} + +impl Element for Boundary { + fn read(input: &[u8]) -> IResult { + let (input, layer) = LAYER::skip_and_read(input)?; + let (input, dtype) = DATATYPE::read(input)?; + let (input, xy) = XY::read(input)?; + let (input, properties) = read_properties(input)?; + Ok((input, Boundary{ + layer: (layer, dtype), + xy: xy, + properties: properties, + })) + } + + fn write(&self, ww: &mut W) -> OResult { + let mut size = 0; + size += BOUNDARY::write(ww, &())?; + size += LAYER::write(ww, &self.layer.0)?; + size += DATATYPE::write(ww, &self.layer.1)?; + size += XY::write(ww, &self.xy)?; + size += write_properties(ww, &self.properties)?; + size += ENDEL::write(ww, &())?; + Ok(size) + } +} + + +/// +/// Datastructure representing a Path element. +/// +/// If `path_type < 4`, `extension` values are not written. +/// During read, `exension` defaults to (0, 0) even if unused. +/// +#[derive(Debug, Clone)] +pub struct Path { + /// (layer, data_type) tuple + layer: (i16, i16), + /// End-cap type (0: flush, 1: circle, 2: square, 4: custom) + path_type: i16, + /// Path width + width: i32, + /// Extension when using path_type=4. Ignored otherwise. + extension: (i32, i32), + /// Path centerline coordinates. [x0, y0, x1, y1,...] + xy: Vec, + /// Properties for the element. + properties: HashMap::>, +} + +impl Element for Path { + fn read(input: &[u8]) -> IResult { + let mut path_type = 0; + let mut width = 0; + let mut bgn_ext = 0; + let mut end_ext = 0; + let (input, layer) = LAYER::skip_and_read(input)?; + let (input, dtype) = DATATYPE::read(input)?; + + let (mut input, mut header) = RecordHeader::read(&input)?; + while header.tag != records::RTAG_XY { + match header.tag { + records::RTAG_PATHTYPE => + {(input, path_type) = PATHTYPE::read_data(input, header.data_size)?;}, + records::RTAG_WIDTH => + {(input, width) = WIDTH::read_data(input, header.data_size)?;}, + records::RTAG_BGNEXTN => + {(input, bgn_ext) = BGNEXTN::read_data(input, header.data_size)?;}, + records::RTAG_ENDEXTN => + {(input, end_ext) = ENDEXTN::read_data(input, header.data_size)?;}, + _ => + return fail(input, format!("Unexpected tag {:04x}", header.tag)), + }; + (input, header) = RecordHeader::read(&input)?; + } + let (input, xy) = XY::read_data(input, header.data_size)?; + let (input, properties) = read_properties(input)?; + Ok((input, Path{ + layer: (layer, dtype), + xy: xy, + properties: properties, + extension: (bgn_ext, end_ext), + path_type: path_type, + width: width, + })) + } + + fn write(&self, ww: &mut W) -> OResult { + let mut size = 0; + size += PATH::write(ww, &())?; + size += LAYER::write(ww, &self.layer.0)?; + size += DATATYPE::write(ww, &self.layer.1)?; + if self.path_type != 0 { + size += PATHTYPE::write(ww, &self.path_type)?; + } + if self.width != 0 { + size += WIDTH::write(ww, &self.width)?; + } + + if self.path_type < 4 { + let (bgn_ext, end_ext) = self.extension; + if bgn_ext != 0 { + size += BGNEXTN::write(ww, &bgn_ext)?; + } + if end_ext != 0 { + size += ENDEXTN::write(ww, &end_ext)?; + } + } + size += XY::write(ww, &self.xy)?; + size += write_properties(ww, &self.properties)?; + size += ENDEL::write(ww, &())?; + Ok(size) + } +} + + +/// +/// Datastructure representing a Box element. Rarely used. +/// +#[derive(Debug, Clone)] +pub struct GDSBox { + /// (layer, box_type) tuple + layer: (i16, i16), + /// Box coordinates (5 pairs) + xy: Vec, + /// Properties for the element. + properties: HashMap::>, +} + +impl Element for GDSBox { + fn read(input: &[u8]) -> IResult { + let (input, layer) = LAYER::skip_and_read(input)?; + let (input, dtype) = BOXTYPE::read(input)?; + let (input, xy) = XY::read(input)?; + let (input, properties) = read_properties(input)?; + Ok((input, GDSBox{ + layer: (layer, dtype), + xy: xy, + properties: properties, + })) + } + + fn write(&self, ww: &mut W) -> OResult { + let mut size = 0; + size += BOX::write(ww, &())?; + size += LAYER::write(ww, &self.layer.0)?; + size += BOXTYPE::write(ww, &self.layer.1)?; + size += XY::write(ww, &self.xy)?; + size += write_properties(ww, &self.properties)?; + size += ENDEL::write(ww, &())?; + Ok(size) + } +} + + +/// +/// Datastructure representing a Node element. Rarely used. +/// +#[derive(Debug, Clone)] +pub struct Node { + /// (layer, box_type) tuple + layer: (i16, i16), + /// 1-50 pairs of coordinates. + xy: Vec, + /// Properties for the element. + properties: HashMap::>, +} + +impl Element for Node { + fn read(input: &[u8]) -> IResult { + let (input, layer) = LAYER::skip_and_read(input)?; + let (input, dtype) = NODETYPE::read(input)?; + let (input, xy) = XY::read(input)?; + let (input, properties) = read_properties(input)?; + Ok((input, Node{ + layer: (layer, dtype), + xy: xy, + properties: properties, + })) + } + + fn write(&self, ww: &mut W) -> OResult { + let mut size = 0; + size += NODE::write(ww, &())?; + size += LAYER::write(ww, &self.layer.0)?; + size += NODETYPE::write(ww, &self.layer.1)?; + size += XY::write(ww, &self.xy)?; + size += write_properties(ww, &self.properties)?; + size += ENDEL::write(ww, &())?; + Ok(size) + } +} + +/// +/// Datastructure representing a text label. +/// +#[derive(Debug, Clone)] +pub struct Text { + /// (layer, node_type) tuple + layer: (i16, i16), + + /// Bit array. Default all zeros. + /// bits 0-1: 00 left/01 center/10 right + /// bits 2-3: 00 top/01 middle/10 bottom + /// bits 4-5: font number + presentation: [bool; 16], + + /// Default 0 + path_type: i16, + /// Default 0 + width: i32, + /// Vertical inversion. Default false. + invert_y: bool, + /// Scaling factor. Default 1. + mag: f64, + /// Rotation (ccw). Default 0. + angle_deg: f64, + /// Position (1 pair only) + xy: Vec, + /// Text content + string: Vec, + /// Properties for the element. + properties: HashMap::> +} + +impl Element for Text { + fn read(input: &[u8]) -> IResult { + let mut path_type = 0; + let mut presentation = [false; 16]; + let mut invert_y = false; + let mut width = 0; + let mut mag = 1.0; + let mut angle_deg = 0.0; + let (input, layer) = LAYER::skip_and_read(input)?; + let (input, dtype) = TEXTTYPE::read(input)?; + + let (mut input, mut header) = RecordHeader::read(input)?; + while header.tag != records::RTAG_XY { + match header.tag { + records::RTAG_PRESENTATION => + {(input, presentation) = PRESENTATION::read_data(input, header.data_size)?;}, + records::RTAG_PATHTYPE => + {(input, path_type) = PATHTYPE::read_data(input, header.data_size)?;}, + records::RTAG_WIDTH => + {(input, width) = WIDTH::read_data(input, header.data_size)?;}, + records::RTAG_STRANS => { + let result = STRANS::read_data(input, header.data_size)?; + input = result.0; + invert_y = result.1[0]; + }, + records::RTAG_MAG => + {(input, mag) = MAG::read_data(input, header.data_size)?;}, + records::RTAG_ANGLE => + {(input, angle_deg) = ANGLE::read_data(input, header.data_size)?;}, + _ => + return fail(input, format!("Unexpected tag {:04x}", header.tag)), + } + (input, header) = RecordHeader::read(input)?; + } + let (input, xy) = XY::read_data(input, header.data_size)?; + + let (input, string) = STRING::read(input)?; + let (input, properties) = read_properties(input)?; + Ok((input, Text{ + layer: (layer, dtype), + xy: xy, + properties: properties, + string: string, + presentation: presentation, + path_type: path_type, + width: width, + invert_y: invert_y, + mag: mag, + angle_deg: angle_deg, + })) + } + + fn write(&self, ww: &mut W) -> OResult { + let mut size = 0; + size += TEXT::write(ww, &())?; + size += LAYER::write(ww, &self.layer.0)?; + size += TEXTTYPE::write(ww, &self.layer.1)?; + if self.presentation.iter().any(|&x| x) { + size += PRESENTATION::write(ww, &self.presentation)?; + } + if self.path_type != 0 { + size += PATHTYPE::write(ww, &self.path_type)?; + } + if self.width != 0 { + size += WIDTH::write(ww, &self.width)? + } + if self.angle_deg != 0.0 || self.mag != 1.0 || self.invert_y { + let strans = { + let mut arr = [false; 16]; + arr[0] = self.invert_y; + arr + }; + size += STRANS::write(ww, &strans)?; + if self.mag != 1.0 { + size += MAG::write(ww, &self.mag)?; + } + if self.angle_deg != 0.0 { + size += ANGLE::write(ww, &self.angle_deg)?; + } + } + size += XY::write(ww, &self.xy)?; + size += STRING::write(ww, &self.string)?; + size += write_properties(ww, &self.properties)?; + size += ENDEL::write(ww, &())?; + Ok(size) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..cf8365c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,86 @@ +extern crate byteorder; + +pub mod basic; +pub mod record; +pub mod records; +pub mod elements; +pub mod library; + + +use byteorder::{ByteOrder, BigEndian}; +use std::mem::size_of; + + +macro_rules! impl_i16be { + ( $tt:ty, $arr:ident, $size:ident ) => { + { + let sl = unsafe { std::slice::from_raw_parts_mut($arr, $size) }; + for xx in sl.iter_mut() { + if *xx < i16::MIN as $tt { return *xx } + if *xx > i16::MAX as $tt { return *xx } + + let mut buf = [0; size_of::<$tt>()]; + BigEndian::write_i16(&mut buf, *xx as i16); + *xx = <$tt>::from_le_bytes(buf); + } + 0 as $tt + } + } +} + + +macro_rules! impl_i32be { + ( $tt:ty, $arr:ident, $size:ident ) => { + { + let sl = unsafe { std::slice::from_raw_parts_mut($arr, $size) }; + for xx in sl.iter_mut() { + if *xx < i32::MIN as $tt { return *xx } + if *xx > i32::MAX as $tt { return *xx } + + let mut buf = [0; size_of::<$tt>()]; + BigEndian::write_i32(&mut buf, *xx as i32); + *xx = <$tt>::from_le_bytes(buf); + } + 0 as $tt + } + } +} + + +#[no_mangle] +pub extern "C" fn f64_to_i16(arr: *mut f64, size: usize) -> f64 { impl_i16be!(f64, arr, size) } +#[no_mangle] +pub extern "C" fn f64_to_i32(arr: *mut f64, size: usize) -> f64 { impl_i32be!(f64, arr, size) } + +#[no_mangle] +pub extern "C" fn f32_to_i16(arr: *mut f32, size: usize) -> f32 { impl_i16be!(f32, arr, size) } +#[no_mangle] +pub extern "C" fn f32_to_i32(arr: *mut f32, size: usize) -> f32 { impl_i32be!(f32, arr, size) } + +#[no_mangle] +pub extern "C" fn u64_to_i16(arr: *mut u64, size: usize) -> u64 { impl_i16be!(u64, arr, size) } +#[no_mangle] +pub extern "C" fn u64_to_i32(arr: *mut u64, size: usize) -> u64 { impl_i32be!(u64, arr, size) } + +#[no_mangle] +pub extern "C" fn i64_to_i16(arr: *mut i64, size: usize) -> i64 { impl_i16be!(i64, arr, size) } +#[no_mangle] +pub extern "C" fn i64_to_i32(arr: *mut i64, size: usize) -> i64 { impl_i32be!(i64, arr, size) } + +#[no_mangle] +pub extern "C" fn u32_to_i16(arr: *mut u32, size: usize) -> u32 { impl_i16be!(u32, arr, size) } +#[no_mangle] +pub extern "C" fn u32_to_i32(arr: *mut u32, size: usize) -> u32 { impl_i32be!(u32, arr, size) } + +#[no_mangle] +pub extern "C" fn i32_to_i16(arr: *mut i32, size: usize) -> i32 { impl_i16be!(i32, arr, size) } +#[no_mangle] +pub extern "C" fn i32_to_i32(arr: *mut i32, size: usize) -> i32 { impl_i32be!(i32, arr, size) } + + +#[no_mangle] +pub extern "C" fn u16_to_i16(arr: *mut u16, size: usize) -> u16 { impl_i16be!(u16, arr, size) } + +#[no_mangle] +pub extern "C" fn i16_to_i16(arr: *mut i16, size: usize) -> i16 { impl_i16be!(i16, arr, size) } + diff --git a/src/library.rs b/src/library.rs new file mode 100644 index 0000000..e5b5758 --- /dev/null +++ b/src/library.rs @@ -0,0 +1,393 @@ +/// +/// File-level read/write functionality. +/// + +use std::io::Write; +use std::collections::HashMap; + +pub use crate::record; +pub use crate::record::{RecordHeader, Record}; +pub use crate::records; +pub use crate::elements; +pub use crate::elements::{Element}; +pub use crate::basic::{IResult, OResult, take_bytes, fail}; + + +const DEFAULT_DATE: [i16; 6] = [1900, 0, 0, 0, 0, 0]; + + +/// +/// Representation of the GDS file header. +/// +/// File header records: HEADER BGNLIB LIBNAME UNITS +/// Optional records are ignored if present and never written. +/// +/// Version is written as `600`. +/// +#[derive(Debug, Clone)] +pub struct FileHeader { + /// Number of user units in one database unit + user_units_per_db_unit: f64, + /// Number of meters in one database unit + meters_per_db_unit: f64, + /// Last-modified time [y, m, d, h, m, s] + mod_time: [i16; 6], + /// Last-accessed time [y, m, d, h, m, s] + acc_time: [i16; 6], + /// Library name + name: Vec, +} + +impl FileHeader { + pub fn new(name: &[u8], meters_per_db_unit: f64, user_units_per_db_unit: f64) -> Self { + FileHeader{ + mod_time: [0, 1, 1, 0, 0, 0], + acc_time: [0, 1, 1, 0, 0, 0], + name: name.to_owned(), + user_units_per_db_unit: user_units_per_db_unit, + meters_per_db_unit: meters_per_db_unit, + } + } + + /// Read and construct a header from the provided input. + /// + /// Args: + /// input: Seekable input to read from + /// + /// Returns: + /// FileHeader object + /// + pub fn read(input: &[u8]) -> IResult { + let (input, _version) = records::HEADER::read(input)?; + let (input, [mod_time, acc_time]) = records::BGNLIB::read(input)?; + let (input, name) = records::LIBNAME::skip_and_read(input)?; + let (input, (uu, dbu)) = records::UNITS::skip_and_read(input)?; + + Ok((input, FileHeader{ + mod_time: mod_time, + acc_time: acc_time, + name: name, + user_units_per_db_unit: uu, + meters_per_db_unit: dbu, + })) + } + + /// Write the header to a input + /// + /// Args: + /// input: input to write to + /// + /// Returns: + /// number of bytes written + /// + pub fn write(&self, ww: &mut W) -> OResult { + let mut size = 0; + size += records::HEADER::write(ww, &600)?; + size += records::BGNLIB::write(ww, &[self.mod_time, self.acc_time])?; + size += records::LIBNAME::write(ww, &self.name)?; + size += records::UNITS::write(ww, &(self.user_units_per_db_unit, self.meters_per_db_unit))?; + Ok(size) + } +} + + +/// +/// Scan through a GDS file, building a table of +/// {b'structure_name': byte_offset}. +/// The intent of this function is to enable random access +/// and/or partial (structure-by-structure) reads. +/// +/// Args: +/// input: Seekable input to read from. Should be positioned +/// before the first structure record, but possibly +/// already past the file header. +/// +pub fn scan_structs(input: &[u8]) -> IResult, usize>> { + let input_size = input.len(); + let mut positions = HashMap::new(); + + let (mut input, mut header) = RecordHeader::read(input)?; + while header.tag != records::RTAG_ENDLIB { + (input, _) = take_bytes(input, header.data_size)?; + if header.tag == records::RTAG_BGNSTR { + let name; + (input, name) = records::STRNAME::read(input)?; + if positions.contains_key(&name) { + return fail(input, format!("Duplicate structure name: {:?}", name)); + } + let position = input_size - input.len(); + positions.insert(name, position); + } + (input, header) = RecordHeader::read(input)?; + } + Ok((input, positions)) +} + + +#[derive(Debug, Clone)] +pub struct Cell { + name: Vec, + + boundaries: Vec, + paths: Vec, + nodes: Vec, + boxes: Vec, + texts: Vec, + refs: Vec, +} + +impl Cell { + /// Build an empty cell + pub fn new(name: Vec) -> Self { + Cell{ + name: name, + boundaries: Vec::new(), + paths: Vec::new(), + nodes: Vec::new(), + boxes: Vec::new(), + texts: Vec::new(), + refs: Vec::new(), + } + } + + /// Skip to the next structure and attempt to read it. + /// + /// Args: + /// input: Seekable input to read from. + /// + /// Returns: + /// (name, elements) if a structure was found. + /// None if no structure was found before the end of the library. + /// + pub fn read(input: &[u8]) -> IResult> { + let (input, success) = records::BGNSTR::skip_past(input)?; + if !success { + return Ok((input, None)) + } + + let (input, name) = records::STRNAME::read(input)?; + let mut cell = Cell::new(name); + let (input, _) = cell.read_elements(input)?; + Ok((input, Some(cell))) + } + + /// Read elements from the input until an ENDSTR + /// record is encountered. The ENDSTR record is also + /// consumed. + /// + /// Args: + /// input: Seekable input to read from. + /// + /// Returns: + /// List of element objects. + /// + pub fn read_elements<'a>(&mut self, input: &'a [u8]) -> IResult<'a, ()> { + let (mut input, mut header) = RecordHeader::read(input)?; + while header.tag != records::RTAG_ENDSTR { + match header.tag { + records::RTAG_BOUNDARY => { + let boundary; + (input, _) = records::BOUNDARY::read(input)?; + (input, boundary) = elements::Boundary::read(input)?; + self.boundaries.push(boundary); + }, + records::RTAG_PATH => { + let path; + (input, _) = records::PATH::read(input)?; + (input, path) = elements::Path::read(input)?; + self.paths.push(path); + }, + records::RTAG_NODE => { + let node; + (input, _) = records::NODE::read(input)?; + (input, node) = elements::Node::read(input)?; + self.nodes.push(node); + }, + records::RTAG_BOX => { + let gds_box; + (input, _) = records::BOX::read(input)?; + (input, gds_box) = elements::GDSBox::read(input)?; + self.boxes.push(gds_box); + }, + records::RTAG_TEXT => { + let txt; + (input, _) = records::TEXT::read(input)?; + (input, txt) = elements::Text::read(input)?; + self.texts.push(txt); + }, + records::RTAG_SREF => { + let sref; + (input, _) = records::SREF::read(input)?; + (input, sref) = elements::Reference::read(input)?; + self.refs.push(sref); + }, + records::RTAG_AREF => { + let aref; + (input, _) = records::AREF::read(input)?; + (input, aref) = elements::Reference::read(input)?; + self.refs.push(aref); + }, + _ => { + // don't care, skip + (input, _) = take_bytes(input, header.data_size)?; + } + } + (input, header) = RecordHeader::read(input)?; + } + Ok((input, ())) + } + + /// + /// Write a structure to the provided input. + /// + /// Args: + /// name: Structure name (ascii-encoded). + /// elements: List of Elements containing the geometry and text in this struct. + /// cre_time: Creation time (optional). + /// mod_time: Modification time (optional). + /// + /// Return: + /// Number of bytes written + /// + pub fn write( + &self, + ww: &mut W, + cre_time: Option<[i16; 6]>, + mod_time: Option<[i16; 6]>, + ) -> OResult { + let mut size = 0; + size += records::BGNSTR::write(ww, &[cre_time.unwrap_or(DEFAULT_DATE), + mod_time.unwrap_or(DEFAULT_DATE)])?; + size += records::STRNAME::write(ww, &self.name)?; + size += self.write_elements(ww)?; + size += records::ENDSTR::write(ww, &())?; + Ok(size) + } + + pub fn write_elements(&self, ww: &mut W) -> OResult { + let mut size = 0; + for boundary in &self.boundaries { + size += boundary.write(ww)?; + } + for path in &self.paths { + size += path.write(ww)?; + } + for node in &self.nodes { + size += node.write(ww)?; + } + for gds_box in &self.boxes { + size += gds_box.write(ww)?; + } + for text in &self.texts { + size += text.write(ww)?; + } + for reference in &self.refs { + size += reference.write(ww)?; + } + Ok(size) + } +} + + +/* +/// +/// Scan through a GDS file, building a table of instance counts +/// `{b'structure_name': {b'ref_name': count}}`. +/// +/// This is intended to provide a fast overview of the file's +/// contents without performing a full read of all elements. +/// +/// Args: +/// input: Seekable input to read from. Should be positioned +/// before the first structure record, but possibly +/// already past the file header. +/// +pub fn scan_hierarchy(input: &[u8]) -> IResult, HashMap::, u32>>> { + let mut structures = HashMap::new(); + + let mut ref_name = None; + let mut ref_count = None; + let (mut input, mut header) = RecordHeader::read(input)?; + let mut cur_structure = HashMap::new(); + while header.tag != records::RTAG_ENDLIB { + match header.tag { + records::RTAG_BGNSTR => { + (input, _) = take_bytes(input, header.data_size)?; + let result = records::STRNAME::read(input)?; + input = result.0; + let name = result.1; + if structures.contains_key(&name) { + return fail(input, format!("Duplicate structure name: {:?}", name)); + } + let mut cur_structure = HashMap::new(); + structures.insert(name, cur_structure); + ref_name = None; + ref_count = None; + }, + records::RTAG_SNAME => { + let result = records::SNAME::read_data(input, header.data_size)?; + input = result.0; + ref_name = Some(result.1); + }, + records::RTAG_COLROW => { + let result = records::COLROW::read_data(input, header.data_size)?; + input = result.0; + let (col, row) = (result.1[0], result.1[1]); + ref_count = Some((col * row) as u32); + }, + records::RTAG_ENDEL => { + *cur_structure.entry(ref_name.unwrap()).or_insert(0) += ref_count.unwrap_or(1); + }, + _ => { + (input, _) = take_bytes(input, header.data_size)?; + }, + } + (input, header) = RecordHeader::read(input)?; + } + Ok((input, structures)) +} +*/ + +/* +pub fn count_ref(input: &[u8]) -> IResult, u32))> { + let (input, found_struc) = records::BGNSTR.skip_past(input)?; + if !found_struc { + return Ok((input, None)) + } + let mut cur_structure = HashMap::new(); + let (input, name) = records::STRNAME::read(input)?; + if structures.contains_key(&name) { + return fail(input, format!("Duplicate structure name: {:?}", name)); + } + + let (mut input, mut header) = RecordHeader::read(input)?; + while header.tag != records::RTAG_ENDSTR { + let mut ref_name = None; + let mut ref_count = None; + while header.tag != records::RTAG_ENDEL { + match header.tag { + records::RTAG_SNAME => { + let result = records::SNAME::read_data(input1, header.data_size)?; + input1 = result.0; + ref_name = Some(result.1); + }, + records::RTAG_COLROW => { + let result = records::COLROW::read_data(input1, header.data_size)?; + input1 = result.0; + let (col, row) = (result.1[0], result.1[1]); + ref_count = Some((col * row) as u32); + }, + _ => { + (input1, _) = take_bytes(input1, header.data_size)?; + }, + } + (input1, header) = RecordHeader::read(input1)?; + } + // got ENDEL, update count for this reference + *cur_structure.entry(ref_name.unwrap()).or_insert(0) += ref_count.unwrap_or(1); + (input1, header) = RecordHeader::read(input1)?; + } + structures.insert(name, cur_structure); + (input, header) = RecordHeader::read(input1)?; +} +*/ diff --git a/src/record.rs b/src/record.rs new file mode 100644 index 0000000..41921c4 --- /dev/null +++ b/src/record.rs @@ -0,0 +1,393 @@ +/// +/// Generic record-level read/write functionality. +/// +use std::io::Write; +use std::convert::TryInto; +use byteorder::{ByteOrder, BigEndian}; + + +use crate::basic::{pack_datetime, pack_bitarray, pack_ascii, pack_int2, pack_int4, pack_real8}; #[warn(unused_imports)] +use crate::basic::{parse_datetime, parse_bitarray, parse_ascii, parse_int2, parse_int4, parse_real8}; #[warn(unused_imports)] +use crate::basic::{OResult, IResult, fail, parse_u16, take_bytes}; +use crate::records; + + +//#[no_mangle] +//pub extern "C" fn write_record_header( + + +#[repr(C)] +pub struct RecordHeader { + pub tag: u16, + pub data_size: u16, +} + +impl RecordHeader { + pub fn read(input: &[u8]) -> IResult { + let (input, size) = parse_u16(input)?; + let (input, tag) = parse_u16(input)?; + Ok((input, RecordHeader{tag:tag, data_size:size - 4})) + } + + pub fn pack_into(&self) -> [u8; 4] { + assert!(self.data_size < 0xffff - 4, "Record too big!"); + let vals = [self.data_size, self.tag]; + let mut buf = [0x77; 4]; + BigEndian::write_u16_into(&vals, &mut buf); + buf + } + + pub fn write(&self, ww: &mut W) -> OResult { + let bytes = self.pack_into(); + ww.write(&bytes) + } +} + + +pub trait RecordData { + type BareData; + type InData : ?Sized; + type ByteData : AsRef<[u8]>; + + fn read(input: &[u8], size: u16) -> IResult; + fn pack_into(buf: &mut [u8], data: &Self::InData); + //fn size(data: &Self::BareData) -> u16; + fn pack(data: &Self::InData) -> Self::ByteData; +} + + +pub trait Record { + fn tag() -> u16; + fn expected_size() -> Option; + + fn check_size(actual_size: u16) -> Result<(), String> { + match Self::expected_size() { + Some(size) => if size == actual_size { + Ok(()) + } else { + Err(format!("Expected record size {}, got {}", size, actual_size)) + }, + None => Ok(()), + } + } + + fn read_header(input: &[u8]) -> IResult { + RecordHeader::read(input) + } + + fn write_header(ww: &mut W, data_size: u16) -> OResult { + RecordHeader{tag: Self::tag(), data_size: data_size}.write(ww) + } + + fn read_data(input: &[u8], size: u16) -> IResult { + RData::read(input, size) + } + + fn pack_data(buf: &mut [u8], data: &RData::InData) { + RData::pack_into(buf, data) + } + + /// + /// Skip to the end of the next occurence of this record. + /// + /// Return: + /// True if the record was encountered and skipped. + /// False if the end of the library was reached. + /// + fn skip_past(input: &[u8]) -> IResult { + let original_input = input; + let (mut input, mut header) = RecordHeader::read(input)?; + while header.tag != Self::tag() { + (input, _) = take_bytes(input, header.data_size)?; + if header.tag == records::RTAG_ENDLIB { + return Ok((original_input, false)) + } + (input, header) = RecordHeader::read(input)?; + } + (input, _) = take_bytes(input, header.data_size)?; + Ok((input, true)) + } + + fn skip_and_read(input: &[u8]) -> IResult { + let (mut input, mut header) = RecordHeader::read(input)?; + while header.tag != Self::tag() { + (input, _) = take_bytes(input, header.data_size)?; + (input, header) = RecordHeader::read(input)?; + } + let (input, data) = Self::read_data(input, header.data_size)?; + Ok((input, data)) + } + + fn expect_header(input: &[u8]) -> IResult { + let (input, header) = RecordHeader::read(input)?; + if header.tag != Self::tag() { + fail(input, format!("Unexpected record! Got tag 0x{:04x}, expected 0x{:04x}", header.tag, Self::tag())) + } else { + Ok((input, header.data_size)) + } + } + + fn read(input: &[u8]) -> IResult { + let (input, size) = Self::expect_header(input)?; + Self::check_size(size).unwrap(); + let (input, data) = Self::read_data(input, size)?; + Ok((input, data)) + } + + fn write(ww: &mut W, data: &RData::InData) -> OResult { + let packed_data = RData::pack(data); + let data_bytes = packed_data.as_ref(); + let len: u16 = data_bytes.len().try_into().expect("Record longer than max size (u16)!"); + let mut size = 0; + size += Self::write_header(ww, len)?; + size += ww.write(data_bytes)?; + Ok(size) + } +} + +pub struct BitArray; +impl RecordData for BitArray { + type BareData = [bool; 16]; + type InData = [bool; 16]; + type ByteData = [u8; 2]; + + fn read(input: &[u8], size: u16) -> IResult { + assert!(size == 2); + parse_bitarray(input) + } + + fn pack_into(buf: &mut [u8], data: &Self::InData) { + pack_bitarray(buf, data); + } + + fn pack(data: &Self::InData) -> Self::ByteData { + let mut buf = [0; 2]; + Self::pack_into(&mut buf, data); + buf + } +} + + +pub struct Int2; +impl RecordData for Int2 { + type BareData = i16; + type InData = i16; + type ByteData = [u8; 2]; + + fn read(input: &[u8], size: u16) -> IResult { + assert!(size == 2); + parse_int2(input) + } + + fn pack_into(buf: &mut [u8], data: &Self::InData) { + pack_int2(buf, *data) + } + + fn pack(data: &Self::InData) -> Self::ByteData { + let mut buf = [0; 2]; + Self::pack_into(&mut buf, data); + buf + } +} + +pub struct Int4; +impl RecordData for Int4 { + type BareData = i32; + type InData = i32; + type ByteData = [u8; 4]; + + fn read(input: &[u8], size: u16) -> IResult { + assert!(size == 4); + parse_int4(input) + } + + fn pack_into(buf: &mut [u8], data: &Self::InData) { + pack_int4(buf, *data) + } + + fn pack(data: &Self::InData) -> Self::ByteData { + let mut buf = [0; 4]; + Self::pack_into(&mut buf, data); + buf + } +} + + +pub struct Int2Array; +impl RecordData for Int2Array { + type BareData = Vec; + type InData = [i16]; + type ByteData = Vec; + + fn read(input: &[u8], size: u16) -> IResult { + assert!(size % 2 == 0, "Record must contain an integer quantity of integers"); + let mut buf = Vec::with_capacity(size as usize / 2); + let mut input = input; + for ii in 0..buf.len() { + (input, buf[ii]) = parse_int2(input)?; + } + Ok((input, buf)) + } + + fn pack_into(buf: &mut [u8], data: &Self::InData) { + BigEndian::write_i16_into(&data, buf) + } + + fn pack(data: &Self::InData) -> Self::ByteData { + let mut buf = Vec::with_capacity(data.len() * 2); + Self::pack_into(&mut buf, data); + buf + } +} + +pub struct Int4Array; +impl RecordData for Int4Array { + type BareData = Vec; + type InData = [i32]; + type ByteData = Vec; + + fn read(input: &[u8], size: u16) -> IResult { + assert!(size % 4 == 0, "Record must contain an integer quantity of integers"); + let mut buf = Vec::with_capacity(size as usize / 4); + let mut input = input; + for ii in 0..buf.len() { + (input, buf[ii]) = parse_int4(input)?; + } + Ok((input, buf)) + } + + fn pack_into(buf: &mut [u8], data: &Self::InData) { + BigEndian::write_i32_into(&data, buf) + } + + fn pack(data: &Self::InData) -> Self::ByteData { + let mut buf = Vec::with_capacity(data.len() * 4); + Self::pack_into(&mut buf, data); + buf + } +} + +pub struct Real8; +impl RecordData for Real8 { + type BareData = f64; + type InData = f64; + type ByteData = [u8; 8]; + + fn read(input: &[u8], size: u16) -> IResult { + assert!(size == 8); + parse_real8(input) + } + + fn pack_into(buf: &mut [u8], data: &Self::InData) { + pack_real8(buf, *data).expect(&format!("Float {0} too big for Real8", data)) + } + + fn pack(data: &Self::InData) -> Self::ByteData { + let mut buf = [0; 8]; + Self::pack_into(&mut buf, data); + buf + } +} + +pub struct Real8Pair; +impl RecordData for Real8Pair { + type BareData = (f64, f64); + type InData = (f64, f64); + type ByteData = [u8; 2 * 8]; + + fn read(input: &[u8], size: u16) -> IResult { + assert!(size == 2 * 8); + let (input, data0) = parse_real8(input)?; + let (input, data1) = parse_real8(input)?; + Ok((input, (data0, data1))) + } + + fn pack_into(buf: &mut [u8], data: &Self::InData) { + pack_real8(&mut buf[8 * 0..], data.0).expect(&format!("Float.0 {0} too big for Real8", data.0)); + pack_real8(&mut buf[8 * 1..], data.1).expect(&format!("Float.1 {0} too big for Real8", data.1)); + } + + fn pack(data: &Self::InData) -> Self::ByteData { + let mut buf = [0; 2 * 8]; + Self::pack_into(&mut buf, data); + buf + } + + //fn write(ww: &mut W, data: &Self::BareData) -> OResult { + // let mut buf = [u8; 2 * 6 * 2]; + // Self::pack_into(&mut buf, data) + //} +} + +pub struct ASCII; +impl RecordData for ASCII { + type BareData = Vec; + type InData = [u8]; + type ByteData = Vec; + + fn read(input: &[u8], size: u16) -> IResult { + parse_ascii(input, size) + } + + fn pack_into(buf: &mut [u8], data: &Self::InData) { + pack_ascii(buf, data); + } + + fn pack(data: &Self::InData) -> Self::ByteData { + let mut buf = Vec::with_capacity(data.len() * 4); + Self::pack_into(&mut buf, data); + buf + } +} + + +pub struct DateTimePair; +impl RecordData for DateTimePair { + type BareData = [[i16; 6]; 2]; + type InData = [[i16; 6]; 2]; + type ByteData = [u8; 2 * 6 * 2]; + + fn read(input: &[u8], size: u16) -> IResult { + assert!(size == 2 * 6 * 2); + let (input, data0) = parse_datetime(input)?; + let (input, data1) = parse_datetime(input)?; + Ok((input, [data0, data1])) + } + + fn pack_into(buf: &mut [u8], data: &Self::InData) { + pack_datetime(&mut buf[6 * 2 * 0..], &data[0]); + pack_datetime(&mut buf[6 * 2 * 1..], &data[1]); + } + + fn pack(data: &Self::InData) -> Self::ByteData { + let mut buf = [0; 2 * 6 * 2]; + Self::pack_into(&mut buf, data); + buf + } + + //fn write(ww: &mut W, data: &Self::BareData) -> OResult { + // let mut buf = [u8; 2 * 6 * 2]; + // Self::pack_into(&mut buf, data) + //} +} + +pub struct Empty; +impl RecordData for Empty { + type BareData = (); + type InData = (); + type ByteData = [u8; 0]; + + fn read(input: &[u8], size: u16) -> IResult { + assert!(size == 0); + Ok((input, ())) + } + + fn pack_into(_buf: &mut [u8], _data: &Self::InData) { + } + + fn pack(_data: &Self::InData) -> Self::ByteData { + [] + } + //fn write(ww: &mut W, data: &Self::BareData) { + //} +} diff --git a/src/records.rs b/src/records.rs new file mode 100644 index 0000000..4fe21fa --- /dev/null +++ b/src/records.rs @@ -0,0 +1,547 @@ +/// +/// Record type and tag definitions +/// + +use crate::record::{Record, Int2, Int4, Int2Array, Int4Array, Real8, Real8Pair, DateTimePair, BitArray, ASCII, Empty}; + +//use std::io::Write; + +// record tags +pub const RTAG_HEADER: u16 = 0x0002; +pub const RTAG_BGNLIB: u16 = 0x0102; +pub const RTAG_LIBNAME: u16 = 0x0206; +pub const RTAG_UNITS: u16 = 0x0305; // (user_units_per_db_unit, db_units_per_meter) +pub const RTAG_ENDLIB: u16 = 0x0400; +pub const RTAG_BGNSTR: u16 = 0x0502; +pub const RTAG_STRNAME: u16 = 0x0606; +pub const RTAG_ENDSTR: u16 = 0x0700; +pub const RTAG_BOUNDARY: u16 = 0x0800; +pub const RTAG_PATH: u16 = 0x0900; +pub const RTAG_SREF: u16 = 0x0a00; +pub const RTAG_AREF: u16 = 0x0b00; +pub const RTAG_TEXT: u16 = 0x0c00; +pub const RTAG_LAYER: u16 = 0x0d02; +pub const RTAG_DATATYPE: u16 = 0x0e02; +pub const RTAG_WIDTH: u16 = 0x0f03; +pub const RTAG_XY: u16 = 0x1003; +pub const RTAG_ENDEL: u16 = 0x1100; +pub const RTAG_SNAME: u16 = 0x1206; +pub const RTAG_COLROW: u16 = 0x1302; +pub const RTAG_NODE: u16 = 0x1500; +pub const RTAG_TEXTTYPE: u16 = 0x1602; +pub const RTAG_PRESENTATION: u16 = 0x1701; +pub const RTAG_SPACING: u16 = 0x1802; // unused; not sure about 02 +pub const RTAG_STRING: u16 = 0x1906; +pub const RTAG_STRANS: u16 = 0x1a01; +pub const RTAG_MAG: u16 = 0x1b05; +pub const RTAG_ANGLE: u16 = 0x1c05; +pub const RTAG_UINTEGER: u16 = 0x1d02; // unused; not sure about 02 +pub const RTAG_USTRING: u16 = 0x1e06; // unused; not sure about 06 +pub const RTAG_REFLIBS: u16 = 0x1f06; +pub const RTAG_FONTS: u16 = 0x2006; +pub const RTAG_PATHTYPE: u16 = 0x2102; +pub const RTAG_GENERATIONS: u16 = 0x2202; +pub const RTAG_ATTRTABLE: u16 = 0x2306; +pub const RTAG_STYPTABLE: u16 = 0x2406; // unused; not sure about 06 +pub const RTAG_STRTYPE: u16 = 0x2502; // unused +pub const RTAG_ELFLAGS: u16 = 0x2601; +pub const RTAG_ELKEY: u16 = 0x2703; // unused +pub const RTAG_LINKTYPE: u16 = 0x2803; // unused +pub const RTAG_LINKKEYS: u16 = 0x2903; // unused +pub const RTAG_NODETYPE: u16 = 0x2a02; +pub const RTAG_PROPATTR: u16 = 0x2b02; +pub const RTAG_PROPVALUE: u16 = 0x2c06; +pub const RTAG_BOX: u16 = 0x2d00; +pub const RTAG_BOXTYPE: u16 = 0x2e02; +pub const RTAG_PLEX: u16 = 0x2f03; +pub const RTAG_BGNEXTN: u16 = 0x3003; +pub const RTAG_ENDEXTN: u16 = 0x3103; +pub const RTAG_TAPENUM: u16 = 0x3202; +pub const RTAG_TAPECODE: u16 = 0x3302; +pub const RTAG_STRCLASS: u16 = 0x3401; +pub const RTAG_RESERVED: u16 = 0x3503; +pub const RTAG_FORMAT: u16 = 0x3602; +pub const RTAG_MASK: u16 = 0x3706; // list of Layers and dtypes +pub const RTAG_ENDMASKS: u16 = 0x3800; // end of MASKS records +pub const RTAG_LIBDIRSIZE: u16 = 0x3902; +pub const RTAG_SRFNAME: u16 = 0x3a06; +pub const RTAG_LIBSECUR: u16 = 0x3b02; +pub const RTAG_BORDER: u16 = 0x3c00; +pub const RTAG_SOFTFENCE: u16 = 0x3d00; +pub const RTAG_HARDFENCE: u16 = 0x3f00; +pub const RTAG_SOFTWIRE: u16 = 0x3f00; +pub const RTAG_HARDWIRE: u16 = 0x4000; +pub const RTAG_PATHPORT: u16 = 0x4100; +pub const RTAG_NODEPORT: u16 = 0x4200; +pub const RTAG_USERCONSTRAINT: u16 = 0x4300; +pub const RTAG_SPACERERROR: u16 = 0x4400; +pub const RTAG_CONTACT: u16 = 0x4500; + +/* +// data types +pub const DATA_TYPE_NONE: u16 = 0x00; +pub const DATA_TYPE_BIT: u16 = 0x01; +pub const DATA_TYPE_INT16: u16 = 0x02; +pub const DATA_TYPE_INT32: u16 = 0x03; +pub const DATA_TYPE_REAL32: u16 = 0x04; +pub const DATA_TYPE_REAL64: u16 = 0x05; +pub const DATA_TYPE_STR: u16 = 0x06; + +pub const MAX_DATA_SIZE: usize = 8; + +/// Returns the size of the given data type in bytes. +pub fn data_size(t: u16) -> Option { + match t { + x if x == DATA_TYPE_NONE => 0, + x if x == DATA_TYPE_BIT => 2, + x if x == DATA_TYPE_INT16 => 2, + x if x == DATA_TYPE_INT32 => 4, + x if x == DATA_TYPE_REAL32 => 4, + x if x == DATA_TYPE_REAL64 => 8, + _ => 0 + } +*/ + +pub struct HEADER; +impl Record for HEADER { + fn tag() -> u16 { RTAG_HEADER } + fn expected_size() -> Option { Some(2) } +} +//impl Record for HEADER; + +pub struct BGNLIB; +impl Record for BGNLIB { + fn tag() -> u16 { RTAG_BGNLIB } + fn expected_size() -> Option { Some(2 * 6) } +} + +pub struct LIBNAME; +impl Record for LIBNAME { + fn tag() -> u16 { RTAG_LIBNAME } + fn expected_size() -> Option { None } +} + +pub struct UNITS; +impl Record for UNITS { + // (user_units_per_db_unit, db_units_per_meter) + fn tag() -> u16 { RTAG_UNITS } + fn expected_size() -> Option { Some(2 * 8) } +} + +pub struct ENDLIB; +impl Record for ENDLIB { + fn tag() -> u16 { RTAG_ENDLIB } + fn expected_size() -> Option { Some(0) } +} + +pub struct BGNSTR; +impl Record for BGNSTR { + fn tag() -> u16 { RTAG_BGNSTR } + fn expected_size() -> Option { Some(2 * 6) } +} + +pub struct STRNAME; +impl Record for STRNAME { + fn tag() -> u16 { RTAG_STRNAME } + fn expected_size() -> Option { Some(2 * 6) } +} + +pub struct ENDSTR; +impl Record for ENDSTR { + fn tag() -> u16 { RTAG_ENDSTR } + fn expected_size() -> Option { Some(0) } +} + +pub struct BOUNDARY; +impl Record for BOUNDARY { + fn tag() -> u16 { RTAG_BOUNDARY } + fn expected_size() -> Option { Some(0) } +} + +pub struct PATH; +impl Record for PATH { + fn tag() -> u16 { RTAG_PATH } + fn expected_size() -> Option { Some(0) } +} + +pub struct SREF; +impl Record for SREF { + fn tag() -> u16 { RTAG_SREF } + fn expected_size() -> Option { Some(0) } +} + +pub struct AREF; +impl Record for AREF { + fn tag() -> u16 { RTAG_AREF } + fn expected_size() -> Option { Some(0) } +} + +pub struct TEXT; +impl Record for TEXT { + fn tag() -> u16 { RTAG_TEXT } + fn expected_size() -> Option { Some(0) } +} + +pub struct LAYER; +impl Record for LAYER { + fn tag() -> u16 { RTAG_LAYER } + fn expected_size() -> Option { Some(2) } +} + +pub struct DATATYPE; +impl Record for DATATYPE { + fn tag() -> u16 { RTAG_DATATYPE } + fn expected_size() -> Option { Some(2) } +} + +pub struct WIDTH; +impl Record for WIDTH { + fn tag() -> u16 { RTAG_WIDTH } + fn expected_size() -> Option { Some(4) } +} + +pub struct XY; +impl Record for XY { + fn tag() -> u16 { RTAG_XY } + fn expected_size() -> Option { None } +} + +pub struct ENDEL; +impl Record for ENDEL { + fn tag() -> u16 { RTAG_ENDEL } + fn expected_size() -> Option { Some(0) } +} + +pub struct SNAME; +impl Record for SNAME { + fn tag() -> u16 { RTAG_SNAME } + fn expected_size() -> Option { None } +} + +pub struct COLROW; +impl Record for COLROW { + fn tag() -> u16 { RTAG_COLROW } + fn expected_size() -> Option { Some(4) } +} + +pub struct NODE; +impl Record for NODE { + fn tag() -> u16 { RTAG_NODE } + fn expected_size() -> Option { Some(0) } +} + +pub struct TEXTTYPE; +impl Record for TEXTTYPE { + fn tag() -> u16 { RTAG_TEXTTYPE } + fn expected_size() -> Option { Some(2) } +} + +pub struct PRESENTATION; +impl Record for PRESENTATION { + fn tag() -> u16 { RTAG_PRESENTATION } + fn expected_size() -> Option { Some(2) } +} + +pub struct SPACING; +impl Record for SPACING { + fn tag() -> u16 { RTAG_SPACING } + fn expected_size() -> Option { Some(2) } +} + +pub struct STRING; +impl Record for STRING { + fn tag() -> u16 { RTAG_STRING } + fn expected_size() -> Option { None } +} + +pub struct STRANS; +impl Record for STRANS { + fn tag() -> u16 { RTAG_STRANS } + fn expected_size() -> Option { Some(2) } +} + +pub struct MAG; +impl Record for MAG { + fn tag() -> u16 { RTAG_MAG } + fn expected_size() -> Option { Some(8) } +} + +pub struct ANGLE; +impl Record for ANGLE { + fn tag() -> u16 { RTAG_ANGLE } + fn expected_size() -> Option { Some(8) } +} + +pub struct UINTEGER; +impl Record for UINTEGER { + fn tag() -> u16 { RTAG_UINTEGER } + fn expected_size() -> Option { Some(2) } +} + +pub struct USTRING; +impl Record for USTRING { + fn tag() -> u16 { RTAG_USTRING } + fn expected_size() -> Option { None } +} + +pub struct REFLIBS; +impl Record for REFLIBS { + fn tag() -> u16 { RTAG_REFLIBS } + fn expected_size() -> Option { None } +} +impl REFLIBS { + pub fn check_size(actual_size: usize) -> Result<(), String> { + if actual_size % 44 == 0 { + Ok(()) + } else { + Err(format!("Expected record size divisible by 44, got {}", actual_size)) + } + } +} + +pub struct FONTS; +impl Record for FONTS { + fn tag() -> u16 { RTAG_FONTS } + fn expected_size() -> Option { None } +} +impl FONTS { + pub fn check_size(actual_size: usize) -> Result<(), String> { + if actual_size % 44 == 0 { + Ok(()) + } else { + Err(format!("Expected record size divisible by 44, got {}", actual_size)) + } + } +} + +pub struct PATHTYPE; +impl Record for PATHTYPE { + fn tag() -> u16 { RTAG_PATHTYPE } + fn expected_size() -> Option { Some(2) } +} + +pub struct GENERATIONS; +impl Record for GENERATIONS { + fn tag() -> u16 { RTAG_GENERATIONS } + fn expected_size() -> Option { Some(2) } +} + +pub struct ATTRTABLE; +impl Record for ATTRTABLE { + fn tag() -> u16 { RTAG_ATTRTABLE } + fn expected_size() -> Option { None } +} +impl ATTRTABLE { + pub fn check_size(actual_size: usize) -> Result<(), String> { + if actual_size % 44 == 0 { + Ok(()) + } else { + Err(format!("Expected record size divisible by 44, got {}", actual_size)) + } + } +} + +pub struct STYPTABLE; +impl Record for STYPTABLE { + fn tag() -> u16 { RTAG_STYPTABLE } + fn expected_size() -> Option { None } +} + +pub struct STRTYPE; +impl Record for STRTYPE { + fn tag() -> u16 { RTAG_STRTYPE } + fn expected_size() -> Option { None } +} + +pub struct ELFLAGS; +impl Record for ELFLAGS { + fn tag() -> u16 { RTAG_ELFLAGS } + fn expected_size() -> Option { Some(2) } +} + +pub struct ELKEY; +impl Record for ELKEY { + fn tag() -> u16 { RTAG_ELKEY } + fn expected_size() -> Option { Some(2) } +} + +pub struct LINKTYPE; +impl Record for LINKTYPE { + fn tag() -> u16 { RTAG_LINKTYPE } + fn expected_size() -> Option { Some(2) } +} + +pub struct LINKKEYS; +impl Record for LINKKEYS { + fn tag() -> u16 { RTAG_LINKKEYS } + fn expected_size() -> Option { Some(2) } +} + +pub struct NODETYPE; +impl Record for NODETYPE { + fn tag() -> u16 { RTAG_NODETYPE } + fn expected_size() -> Option { Some(2) } +} + +pub struct PROPATTR; +impl Record for PROPATTR { + fn tag() -> u16 { RTAG_PROPATTR } + fn expected_size() -> Option { Some(2) } +} + +pub struct PROPVALUE; +impl Record for PROPVALUE { + fn tag() -> u16 { RTAG_PROPVALUE } + fn expected_size() -> Option { Some(2) } +} + +pub struct BOX; +impl Record for BOX { + fn tag() -> u16 { RTAG_BOX } + fn expected_size() -> Option { Some(0) } +} + +pub struct BOXTYPE; +impl Record for BOXTYPE { + fn tag() -> u16 { RTAG_BOXTYPE } + fn expected_size() -> Option { Some(2) } +} + +pub struct PLEX; +impl Record for PLEX { + fn tag() -> u16 { RTAG_PLEX } + fn expected_size() -> Option { Some(4) } +} + +pub struct BGNEXTN; +impl Record for BGNEXTN { + fn tag() -> u16 { RTAG_BGNEXTN } + fn expected_size() -> Option { Some(4) } +} + +pub struct ENDEXTN; +impl Record for ENDEXTN { + fn tag() -> u16 { RTAG_ENDEXTN } + fn expected_size() -> Option { Some(4) } +} + +pub struct TAPENUM; +impl Record for TAPENUM { + fn tag() -> u16 { RTAG_TAPENUM } + fn expected_size() -> Option { Some(2) } +} + +pub struct TAPECODE; +impl Record for TAPECODE { + fn tag() -> u16 { RTAG_TAPECODE } + fn expected_size() -> Option { Some(2 * 6) } +} + +pub struct STRCLASS; +impl Record for STRCLASS { + fn tag() -> u16 { RTAG_STRCLASS } + fn expected_size() -> Option { Some(2) } +} + +pub struct RESERVED; +impl Record for RESERVED { + fn tag() -> u16 { RTAG_RESERVED } + fn expected_size() -> Option { Some(2) } +} + +pub struct FORMAT; +impl Record for FORMAT { + fn tag() -> u16 { RTAG_FORMAT } + fn expected_size() -> Option { Some(2) } +} + +pub struct MASK; +impl Record for MASK { + fn tag() -> u16 { RTAG_MASK } + fn expected_size() -> Option { None } +} + +/// End of MASKS records +pub struct ENDMASKS; +impl Record for ENDMASKS { + fn tag() -> u16 { RTAG_ENDMASKS } + fn expected_size() -> Option { Some(0) } +} + +pub struct LIBDIRSIZE; +impl Record for LIBDIRSIZE { + fn tag() -> u16 { RTAG_LIBDIRSIZE } + fn expected_size() -> Option { Some(2) } +} + +pub struct SRFNAME; +impl Record for SRFNAME { + fn tag() -> u16 { RTAG_SRFNAME } + fn expected_size() -> Option { None } +} + +pub struct LIBSECUR; +impl Record for LIBSECUR { + fn tag() -> u16 { RTAG_LIBSECUR } + fn expected_size() -> Option { Some(2) } +} + +pub struct BORDER; +impl Record for BORDER { + fn tag() -> u16 { RTAG_BORDER } + fn expected_size() -> Option { Some(0) } +} + +pub struct SOFTFENCE; +impl Record for SOFTFENCE { + fn tag() -> u16 { RTAG_SOFTFENCE } + fn expected_size() -> Option { Some(0) } +} + +pub struct HARDFENCE; +impl Record for HARDFENCE { + fn tag() -> u16 { RTAG_HARDFENCE } + fn expected_size() -> Option { Some(0) } +} + +pub struct SOFTWIRE; +impl Record for SOFTWIRE { + fn tag() -> u16 { RTAG_SOFTWIRE } + fn expected_size() -> Option { Some(0) } +} + +pub struct HARDWIRE; +impl Record for HARDWIRE { + fn tag() -> u16 { RTAG_HARDWIRE } + fn expected_size() -> Option { Some(0) } +} + +pub struct PATHPORT; +impl Record for PATHPORT { + fn tag() -> u16 { RTAG_PATHPORT } + fn expected_size() -> Option { Some(0) } +} + +pub struct NODEPORT; +impl Record for NODEPORT { + fn tag() -> u16 { RTAG_NODEPORT } + fn expected_size() -> Option { Some(0) } +} + +pub struct USERCONSTRAINT; +impl Record for USERCONSTRAINT { + fn tag() -> u16 { RTAG_USERCONSTRAINT } + fn expected_size() -> Option { Some(0) } +} + +pub struct SPACERERROR; +impl Record for SPACERERROR { + fn tag() -> u16 { RTAG_SPACERERROR } + fn expected_size() -> Option { Some(0) } +} + +pub struct CONTACT; +impl Record for CONTACT { + fn tag() -> u16 { RTAG_CONTACT } + fn expected_size() -> Option { Some(0) } +}