283 lines
9.1 KiB
Rust
283 lines
9.1 KiB
Rust
|
/*
|
||
|
*File-level read/write functionality.
|
||
|
*/
|
||
|
|
||
|
use nom;
|
||
|
use std::io::Write;
|
||
|
use std::collections:HashMap;
|
||
|
|
||
|
use record;
|
||
|
use records;
|
||
|
use elements;
|
||
|
//from .records import HEADER, BGNLIB, ENDLIB, UNITS, LIBNAME
|
||
|
//from .records import BGNSTR, STRNAME, ENDSTR, SNAME, COLROW, ENDEL
|
||
|
//from .records import BOX, BOUNDARY, NODE, PATH, TEXT, SREF, AREF
|
||
|
//from .elements import Element, Reference, Text, Box, Boundary, Path, Node
|
||
|
|
||
|
|
||
|
pub struct FileHeader {
|
||
|
/*
|
||
|
* 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`.
|
||
|
*/
|
||
|
name: Vec<u8>, // Library name
|
||
|
user_units_per_db_unit: f64, // Number of user units in one database unit
|
||
|
meters_per_db_unit: f64, // Number of meters in one database unit
|
||
|
mod_time: [u16; 6], // Last-modified time [y, m, d, h, m, s]
|
||
|
acc_time: [u16; 6], // Last-accessed time [y, m, d, h, m, s]
|
||
|
}
|
||
|
|
||
|
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,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn read(input: &[u8]) -> IResult<&[u8], Self> {
|
||
|
/*
|
||
|
* Read and construct a header from the provided stream.
|
||
|
*
|
||
|
* Args:
|
||
|
* stream: Seekable stream to read from
|
||
|
*
|
||
|
* Returns:
|
||
|
* FileHeader object
|
||
|
*/
|
||
|
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)?;
|
||
|
|
||
|
FileHeader{
|
||
|
mod_time: mod_time,
|
||
|
acc_time: acc_time,
|
||
|
name: name,
|
||
|
user_units_per_db_unit: uu,
|
||
|
meters_per_db_unit: dbu,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn write<W: Write>(&self, ww: W) -> OWResult {
|
||
|
/*
|
||
|
* Write the header to a stream
|
||
|
*
|
||
|
* Args:
|
||
|
* stream: Stream to write to
|
||
|
*
|
||
|
* Returns:
|
||
|
* number of bytes written
|
||
|
*/
|
||
|
let mut size = 0;
|
||
|
size += records::HEADER.write(stream, 600)
|
||
|
size += records::BGNLIB.write(stream, [self.mod_time, self.acc_time])
|
||
|
size += records::LIBNAME.write(stream, self.name)
|
||
|
size += records::UNITS.write(stream, (self.user_units_per_db_unit, self.meters_per_db_unit))
|
||
|
Ok(size)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
pub fn scan_structs(input: &[u8]) -> HashMap::<Vec<u8>, usize> {
|
||
|
/*
|
||
|
* 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:
|
||
|
* stream: Seekable stream to read from. Should be positioned
|
||
|
* before the first structure record, but possibly
|
||
|
* already past the file header.
|
||
|
*/
|
||
|
let input_size = input.len();
|
||
|
let positions = HashMap{};
|
||
|
|
||
|
let (input, header) = RecordHeader.parse(input)?;
|
||
|
while header.tag != records::RTAG_ENDLIB {
|
||
|
let (input, _) = nom::bytes::streaming::take(size)(input)?;
|
||
|
if tag == records::RTAG_BGNSTR {
|
||
|
let (input, name) = records::STRNAME.read(input)?;
|
||
|
if positions.conains_key(name) {
|
||
|
return Err(format!("Duplicate structure name: {}", name));
|
||
|
}
|
||
|
let position = input_size - input.len();
|
||
|
positions.insert(name, position);
|
||
|
}
|
||
|
let (input, header) = RecordHeader.parse(input)?;
|
||
|
}
|
||
|
positions
|
||
|
}
|
||
|
|
||
|
|
||
|
pub struct Cell {
|
||
|
|
||
|
}
|
||
|
|
||
|
impl Cell {
|
||
|
pub fn write<W: Write>(
|
||
|
&self,
|
||
|
ww: Write,
|
||
|
name: &[u8],
|
||
|
cre_time: Option<[i16; 6]>,
|
||
|
mod_time: Option<[i16; 6]>,
|
||
|
) -> OWResult {
|
||
|
/*
|
||
|
* Write a structure to the provided stream.
|
||
|
*
|
||
|
* 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
|
||
|
*/
|
||
|
let mut size = 0;
|
||
|
size += BGNSTR.write(ww, (cre_time, mod_time))
|
||
|
size += STRNAME.write(ww, name)
|
||
|
size += cell.write(ww)
|
||
|
size += ENDSTR.write(ww)
|
||
|
Ok(size)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn try_read_struct(input: &[u8]) -> IResult<&[u8], Option<(Vec<u8>, Cell>)> {
|
||
|
/*
|
||
|
* Skip to the next structure and attempt to read it.
|
||
|
*
|
||
|
* Args:
|
||
|
* stream: Seekable stream to read from.
|
||
|
*
|
||
|
* Returns:
|
||
|
* (name, elements) if a structure was found.
|
||
|
* None if no structure was found before the end of the library.
|
||
|
*/
|
||
|
let (input, success) = records::BGNSTR.skip_past(input)?;
|
||
|
if !success {
|
||
|
return None
|
||
|
}
|
||
|
|
||
|
let (input, name) = records::STRNAME.read(input)?;
|
||
|
let (input, elements) = Cell::read_elements(input)?;
|
||
|
Some((name, elements))
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
pub fn read_elements(stream: BinaryIO) -> List[Element] {
|
||
|
/*
|
||
|
* Read elements from the stream until an ENDSTR
|
||
|
* record is encountered. The ENDSTR record is also
|
||
|
* consumed.
|
||
|
*
|
||
|
* Args:
|
||
|
* stream: Seekable stream to read from.
|
||
|
*
|
||
|
* Returns:
|
||
|
* List of element objects.
|
||
|
*/
|
||
|
let (input, header) = RecordHeader.parse(input)?;
|
||
|
while header.tag != records::RTAG_ENDSTR {
|
||
|
match header.tag {
|
||
|
records::RTAG_BOUNDARY => {
|
||
|
let (input, boundary) = records::BOUNDARY.read(input)?;
|
||
|
cell.boundaries.insert(boundary);
|
||
|
},
|
||
|
records::RTAG_PATH => {
|
||
|
let (input, path) = records::PATH.read(input)?;
|
||
|
cell.paths.insert(path);
|
||
|
},
|
||
|
records::RTAG_NODE => {
|
||
|
let (input, node) = records::NODE.read(input)?;
|
||
|
cell.nodes.insert(node);
|
||
|
},
|
||
|
records::RTAG_BOX => {
|
||
|
let (input, gds_box) = records::BOX.read(input)?;
|
||
|
cell.boxes.insert(gds_box);
|
||
|
},
|
||
|
records::RTAG_TEXT => {
|
||
|
let (input, txt) = records::TEXT.read(input)?;
|
||
|
cell.texts.insert(txt);
|
||
|
},
|
||
|
records::RTAG_SREF => {
|
||
|
let (input, sref) = records::SREF.read(input)?;
|
||
|
cell.refs.insert(sref);
|
||
|
},
|
||
|
records::RTAG_AREF => {
|
||
|
let (input, aref) = records::AREF.read(input)?;
|
||
|
cell.refs.insert(aref);
|
||
|
},
|
||
|
_ => {
|
||
|
// don't care, skip
|
||
|
let (input, _) = nom::bytes::streaming::take(size)(input)?;
|
||
|
}
|
||
|
}
|
||
|
let (input, header) = RecordHeader.parse(input)?;
|
||
|
}
|
||
|
Ok((input, data))
|
||
|
}
|
||
|
|
||
|
|
||
|
pub fn scan_hierarchy(input: &[u8]) -> IResult<&[u8], HashMap::<Vec<u8>, HashMap::<Vec<u8>, u32>>> {
|
||
|
/*
|
||
|
* 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:
|
||
|
* stream: Seekable stream to read from. Should be positioned
|
||
|
* before the first structure record, but possibly
|
||
|
* already past the file header.
|
||
|
*/
|
||
|
let structures = HashMap{};
|
||
|
|
||
|
let mut ref_name = None
|
||
|
let mut ref_count = None
|
||
|
let mut cur_structure = HashMap{};
|
||
|
|
||
|
let (input, header) = Record.read_header(stream)
|
||
|
while header.tag != records::RTAG_ENDLIB {
|
||
|
match header.tag {
|
||
|
records::RTAG_BGNSTR => {
|
||
|
let (input, _) = nom::bytes::streaming::take(size)(input)?;
|
||
|
let (input, name) = records::STRNAME.read(input)?;
|
||
|
if structures.contains_key(name) {
|
||
|
return Err(format!("Duplicate structure name: {}", name));
|
||
|
}
|
||
|
cur_structure = HashMap{};
|
||
|
structures.insert(name, cur_structure);
|
||
|
ref_name = None;
|
||
|
ref_count = None;
|
||
|
},
|
||
|
records::RTAG_SNAME => {
|
||
|
let (input, sname) = SNAME.read_data(input, header.data_size)?;
|
||
|
ref_name = Some(sname);
|
||
|
},
|
||
|
records::RTAG_COLROW => {
|
||
|
let (input, colrow) = COLROW.read_data(input, header.data_size);
|
||
|
ref_count = colrow[0] * colrow[1];
|
||
|
},
|
||
|
records::RTAG_ENDEL => {
|
||
|
*cur_structure.entry(ref_name.unwrap()).or_insert(0) += ref_count.unwrap_or(1);
|
||
|
},
|
||
|
_ => {
|
||
|
let (input, _) = nom::bytes::streaming::take(size)(input)?;
|
||
|
},
|
||
|
}
|
||
|
let (input, header) = RecordHeader.parse(input)?;
|
||
|
}
|
||
|
structures
|
||
|
}
|