laykit/
oasis.rs

1// OASIS (Open Artwork System Interchange Standard) Reader/Writer
2// Full implementation of OASIS spec for IC layout interchange
3// More compact and modern than GDSII
4
5use std::collections::HashMap;
6use std::fs::File;
7use std::io::{BufReader, BufWriter, Cursor, Read, Write};
8use std::path::Path;
9
10/// OASIS File structure
11#[derive(Debug, Clone)]
12pub struct OASISFile {
13    pub version: String,
14    pub unit: f64,
15    pub offset_flag: bool,
16    pub names: NameTable,
17    pub cells: Vec<OASISCell>,
18    pub layers: Vec<LayerInfo>,
19    pub properties: Vec<PropertyDefinition>,
20}
21
22#[derive(Debug, Clone)]
23pub struct NameTable {
24    pub cell_names: HashMap<u32, String>,
25    pub text_strings: HashMap<u32, String>,
26    pub prop_names: HashMap<u32, String>,
27    pub prop_strings: HashMap<u32, String>,
28    pub layer_names: HashMap<u32, String>,
29}
30
31#[derive(Debug, Clone)]
32pub struct OASISCell {
33    pub name: String,
34    pub elements: Vec<OASISElement>,
35}
36
37#[derive(Debug, Clone)]
38pub struct LayerInfo {
39    pub layer: u32,
40    pub datatype: u32,
41    pub name: Option<String>,
42}
43
44#[derive(Debug, Clone)]
45pub enum OASISElement {
46    Rectangle(Rectangle),
47    Polygon(Polygon),
48    Path(OPath),
49    Trapezoid(Trapezoid),
50    CTrapezoid(CTrapezoid),
51    Circle(Circle),
52    Text(OText),
53    Placement(Placement),
54}
55
56#[derive(Debug, Clone)]
57pub struct Rectangle {
58    pub layer: u32,
59    pub datatype: u32,
60    pub x: i64,
61    pub y: i64,
62    pub width: u64,
63    pub height: u64,
64    pub repetition: Option<Repetition>,
65    pub properties: Vec<Property>,
66}
67
68#[derive(Debug, Clone)]
69pub struct Polygon {
70    pub layer: u32,
71    pub datatype: u32,
72    pub x: i64,
73    pub y: i64,
74    pub points: Vec<(i64, i64)>,
75    pub repetition: Option<Repetition>,
76    pub properties: Vec<Property>,
77}
78
79#[derive(Debug, Clone)]
80pub struct OPath {
81    pub layer: u32,
82    pub datatype: u32,
83    pub x: i64,
84    pub y: i64,
85    pub half_width: u64,
86    pub extension_scheme: ExtensionScheme,
87    pub points: Vec<(i64, i64)>,
88    pub repetition: Option<Repetition>,
89    pub properties: Vec<Property>,
90}
91
92#[derive(Debug, Clone)]
93pub struct Trapezoid {
94    pub layer: u32,
95    pub datatype: u32,
96    pub x: i64,
97    pub y: i64,
98    pub width: u64,
99    pub height: u64,
100    pub delta_a: i64,
101    pub delta_b: i64,
102    pub orientation: bool, // horizontal or vertical
103    pub repetition: Option<Repetition>,
104    pub properties: Vec<Property>,
105}
106
107#[derive(Debug, Clone)]
108pub struct CTrapezoid {
109    pub layer: u32,
110    pub datatype: u32,
111    pub x: i64,
112    pub y: i64,
113    pub trap_type: u8,
114    pub width: u64,
115    pub height: u64,
116    pub repetition: Option<Repetition>,
117    pub properties: Vec<Property>,
118}
119
120#[derive(Debug, Clone)]
121pub struct Circle {
122    pub layer: u32,
123    pub datatype: u32,
124    pub x: i64,
125    pub y: i64,
126    pub radius: u64,
127    pub repetition: Option<Repetition>,
128    pub properties: Vec<Property>,
129}
130
131#[derive(Debug, Clone)]
132pub struct OText {
133    pub layer: u32,
134    pub texttype: u32,
135    pub x: i64,
136    pub y: i64,
137    pub string: String,
138    pub repetition: Option<Repetition>,
139    pub properties: Vec<Property>,
140}
141
142#[derive(Debug, Clone)]
143pub struct Placement {
144    pub cell_name: String,
145    pub x: i64,
146    pub y: i64,
147    pub magnification: Option<f64>,
148    pub angle: Option<f64>,
149    pub mirror: bool,
150    pub repetition: Option<Repetition>,
151    pub properties: Vec<Property>,
152}
153
154#[derive(Debug, Clone)]
155pub enum Repetition {
156    ReusePrevious,
157    Matrix {
158        x_count: u32,
159        y_count: u32,
160        x_space: u64,
161        y_space: u64,
162    },
163    Arbitrary {
164        x_displacements: Vec<i64>,
165        y_displacements: Vec<i64>,
166    },
167    Grid {
168        count: u32,
169        grid_space: u64,
170    },
171}
172
173#[derive(Debug, Clone)]
174pub enum ExtensionScheme {
175    Flush,
176    HalfWidth,
177    Custom { start: i64, end: i64 },
178}
179
180#[derive(Debug, Clone)]
181pub struct Property {
182    pub name: String,
183    pub values: Vec<PropertyValue>,
184}
185
186#[derive(Debug, Clone)]
187pub struct PropertyDefinition {
188    pub name: String,
189    pub is_standard: bool,
190}
191
192#[derive(Debug, Clone)]
193pub enum PropertyValue {
194    Integer(i64),
195    Real(f64),
196    String(String),
197    Reference(u32),
198    Boolean(bool),
199}
200
201// OASIS Record Types (for reference)
202// These constants are used internally via their numeric values
203#[allow(dead_code)]
204mod record_ids {
205    pub const PAD: u8 = 0;
206    pub const START: u8 = 1;
207    pub const END: u8 = 2;
208    pub const CELLNAME: u8 = 3;
209    pub const TEXTSTRING: u8 = 5;
210    pub const PROPNAME: u8 = 7;
211    pub const LAYERNAME: u8 = 11;
212    pub const CELL: u8 = 13;
213    pub const XYABSOLUTE: u8 = 14;
214    pub const XYRELATIVE: u8 = 15;
215    pub const PLACEMENT: u8 = 16;
216    pub const TEXT: u8 = 18;
217    pub const RECTANGLE: u8 = 19;
218    pub const POLYGON: u8 = 20;
219    pub const PATH: u8 = 21;
220    pub const TRAPEZOID: u8 = 22;
221    pub const CTRAPEZOID: u8 = 23;
222    pub const CIRCLE: u8 = 24;
223}
224
225impl Default for OASISFile {
226    fn default() -> Self {
227        OASISFile {
228            version: "1.0".to_string(),
229            unit: 1e-9, // 1nm database unit
230            offset_flag: false,
231            names: NameTable {
232                cell_names: HashMap::new(),
233                text_strings: HashMap::new(),
234                prop_names: HashMap::new(),
235                prop_strings: HashMap::new(),
236                layer_names: HashMap::new(),
237            },
238            cells: Vec::new(),
239            layers: Vec::new(),
240            properties: Vec::new(),
241        }
242    }
243}
244
245impl OASISFile {
246    /// Create a new empty OASIS file
247    pub fn new() -> Self {
248        Self::default()
249    }
250
251    /// Read OASIS from file
252    pub fn read_from_file<P: AsRef<Path>>(path: P) -> Result<Self, Box<dyn std::error::Error>> {
253        let file = File::open(path)?;
254        let mut reader = BufReader::new(file);
255        Self::read_from_reader(&mut reader)
256    }
257
258    /// Read OASIS from any reader
259    pub fn read_from_reader<R: Read>(reader: &mut R) -> Result<Self, Box<dyn std::error::Error>> {
260        let mut buffer = Vec::new();
261        reader.read_to_end(&mut buffer)?;
262
263        let mut cursor = Cursor::new(buffer);
264        let mut oasis = OASISFile::new();
265
266        // Check magic bytes
267        let magic = Self::read_bytes(&mut cursor, 13)?;
268        if &magic != b"%SEMI-OASIS\r\n" {
269            return Err("Invalid OASIS file magic".into());
270        }
271
272        // Parse records
273        loop {
274            let record_id = match Self::read_u8(&mut cursor) {
275                Ok(id) => id,
276                Err(_) => break,
277            };
278
279            // Skip padding records
280            if record_id == 0 {
281                continue;
282            }
283
284            match record_id {
285                1 => {
286                    // START
287                    oasis.version = Self::read_string(&mut cursor)?;
288                    oasis.unit = Self::read_real(&mut cursor)?;
289                    oasis.offset_flag = Self::read_u8(&mut cursor)? != 0;
290                }
291                2 => {
292                    // END
293                    // Validation table follows - skip it
294                    // Read validation signature (may fail if at end of file)
295                    let _ = Self::read_unsigned(&mut cursor);
296                    break;
297                }
298                3 | 4 => {
299                    // CELLNAME (explicit and implicit)
300                    let name = Self::read_string(&mut cursor)?;
301                    let ref_num = Self::read_unsigned(&mut cursor)? as u32;
302                    oasis.names.cell_names.insert(ref_num, name);
303                }
304                5 | 6 => {
305                    // TEXTSTRING (explicit and implicit)
306                    let string = Self::read_string(&mut cursor)?;
307                    let ref_num = Self::read_unsigned(&mut cursor)? as u32;
308                    oasis.names.text_strings.insert(ref_num, string);
309                }
310                7 | 8 => {
311                    // PROPNAME (explicit and implicit)
312                    let name = Self::read_string(&mut cursor)?;
313                    let ref_num = Self::read_unsigned(&mut cursor)? as u32;
314                    oasis.names.prop_names.insert(ref_num, name);
315                }
316                9 | 10 => {
317                    // PROPSTRING (explicit and implicit)
318                    let string = Self::read_string(&mut cursor)?;
319                    let ref_num = Self::read_unsigned(&mut cursor)? as u32;
320                    oasis.names.prop_strings.insert(ref_num, string);
321                }
322                11 | 12 => {
323                    // LAYERNAME (explicit and implicit)
324                    let name = Self::read_string(&mut cursor)?;
325                    let layer_interval = Self::read_layer_interval(&mut cursor)?;
326                    let _datatype_interval = Self::read_layer_interval(&mut cursor)?;
327                    // Store first value from intervals
328                    if let Some(layer) = layer_interval.first() {
329                        oasis.names.layer_names.insert(*layer, name);
330                    }
331                }
332                13 => {
333                    // CELL
334                    let cell = Self::read_cell(&mut cursor, &oasis.names)?;
335                    oasis.cells.push(cell);
336                }
337                14 => { // XYAbsolute
338                     // Coordinate mode - no data, just skip
339                }
340                15 => { // XYRelative
341                     // Coordinate mode - no data, just skip
342                }
343                19 => { // RECTANGLE
344                     // Handled within cell context
345                }
346                20 => { // POLYGON
347                     // Handled within cell context
348                }
349                21 => { // PATH
350                     // Handled within cell context
351                }
352                _ => {
353                    // Skip unknown records
354                }
355            }
356        }
357
358        Ok(oasis)
359    }
360
361    /// Write OASIS to file
362    pub fn write_to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), Box<dyn std::error::Error>> {
363        let file = File::create(path)?;
364        let mut writer = BufWriter::new(file);
365        self.write_to_writer(&mut writer)
366    }
367
368    /// Write OASIS to any writer
369    pub fn write_to_writer<W: Write>(
370        &self,
371        writer: &mut W,
372    ) -> Result<(), Box<dyn std::error::Error>> {
373        // Write magic
374        writer.write_all(b"%SEMI-OASIS\r\n")?;
375
376        // Write START record
377        Self::write_u8(writer, 1)?;
378        Self::write_string(writer, &self.version)?;
379        Self::write_real(writer, self.unit)?;
380        Self::write_u8(writer, if self.offset_flag { 1 } else { 0 })?;
381
382        // Write name tables
383        for (ref_num, name) in &self.names.cell_names {
384            Self::write_u8(writer, 3)?; // CELLNAME
385            Self::write_string(writer, name)?;
386            Self::write_unsigned(writer, *ref_num as u64)?;
387        }
388
389        for (ref_num, string) in &self.names.text_strings {
390            Self::write_u8(writer, 5)?; // TEXTSTRING
391            Self::write_string(writer, string)?;
392            Self::write_unsigned(writer, *ref_num as u64)?;
393        }
394
395        for (ref_num, name) in &self.names.prop_names {
396            Self::write_u8(writer, 7)?; // PROPNAME
397            Self::write_string(writer, name)?;
398            Self::write_unsigned(writer, *ref_num as u64)?;
399        }
400
401        // Write cells
402        for cell in &self.cells {
403            cell.write(writer, &self.names)?;
404        }
405
406        // Write END record
407        Self::write_u8(writer, 2)?;
408
409        // Write validation (optional, simplified)
410        Self::write_unsigned(writer, 0)?; // No validation
411
412        Ok(())
413    }
414
415    fn read_cell(
416        cursor: &mut Cursor<Vec<u8>>,
417        names: &NameTable,
418    ) -> Result<OASISCell, Box<dyn std::error::Error>> {
419        let name_ref_or_string = Self::read_unsigned(cursor)?;
420
421        let name = if name_ref_or_string & 1 == 0 {
422            // Reference
423            let ref_num = (name_ref_or_string >> 1) as u32;
424            names
425                .cell_names
426                .get(&ref_num)
427                .ok_or("Cell name reference not found")?
428                .clone()
429        } else {
430            // Explicit string
431            Self::read_string(cursor)?
432        };
433
434        let mut cell = OASISCell {
435            name,
436            elements: Vec::new(),
437        };
438
439        // Read elements until next CELL, END, or coordinate mode change
440        loop {
441            // Peek at next byte to see what's coming
442            let position = cursor.position();
443            let record_id = match Self::read_u8(cursor) {
444                Ok(id) => id,
445                Err(_) => break, // EOF
446            };
447
448            // Check if this is a cell boundary or end marker
449            match record_id {
450                2 | 13 => {
451                    // END or CELL - restore position and break
452                    cursor.set_position(position);
453                    break;
454                }
455                14 | 15 => {
456                    // XYAbsolute or XYRelative - just continue
457                    continue;
458                }
459                16 => {
460                    // PLACEMENT
461                    if let Ok(elem) = Self::read_placement(cursor, names) {
462                        cell.elements.push(OASISElement::Placement(elem));
463                    }
464                }
465                18 => {
466                    // TEXT
467                    if let Ok(elem) = Self::read_text(cursor, names) {
468                        cell.elements.push(OASISElement::Text(elem));
469                    }
470                }
471                19 => {
472                    // RECTANGLE
473                    if let Ok(elem) = Self::read_rectangle(cursor) {
474                        cell.elements.push(OASISElement::Rectangle(elem));
475                    }
476                }
477                20 => {
478                    // POLYGON
479                    if let Ok(elem) = Self::read_polygon(cursor) {
480                        cell.elements.push(OASISElement::Polygon(elem));
481                    }
482                }
483                21 => {
484                    // PATH
485                    if let Ok(elem) = Self::read_path(cursor) {
486                        cell.elements.push(OASISElement::Path(elem));
487                    }
488                }
489                22 => {
490                    // TRAPEZOID
491                    if let Ok(elem) = Self::read_trapezoid(cursor) {
492                        cell.elements.push(OASISElement::Trapezoid(elem));
493                    }
494                }
495                23 => {
496                    // CTRAPEZOID
497                    if let Ok(elem) = Self::read_ctrapezoid(cursor) {
498                        cell.elements.push(OASISElement::CTrapezoid(elem));
499                    }
500                }
501                24 => {
502                    // CIRCLE
503                    if let Ok(elem) = Self::read_circle(cursor) {
504                        cell.elements.push(OASISElement::Circle(elem));
505                    }
506                }
507                28 | 29 => {
508                    // PROPERTY records - skip for now (basic coverage)
509                    let _ = Self::read_unsigned(cursor); // property name reference
510                    let values_count = Self::read_unsigned(cursor).unwrap_or(0);
511                    for _ in 0..values_count {
512                        let _ = Self::read_unsigned(cursor); // skip property values
513                    }
514                }
515                30 | 31 | 34 => {
516                    // XNAME, XELEMENT, XGEOMETRY - vendor extensions, skip
517                    let _ = Self::read_unsigned(cursor);
518                    let _ = Self::read_string(cursor);
519                }
520                32 | 33 => {
521                    // CBLOCK - compression block
522                    let _comp_type = Self::read_unsigned(cursor)?;
523                    let _uncomp_byte_count = Self::read_unsigned(cursor)?;
524                    let comp_byte_count = Self::read_unsigned(cursor)?;
525                    // Skip compressed data for now
526                    let _ = Self::read_bytes(cursor, comp_byte_count as usize);
527                }
528                _ => {
529                    // Unknown record, try to skip it safely
530                    cursor.set_position(position);
531                    break;
532                }
533            }
534        }
535
536        Ok(cell)
537    }
538
539    fn read_rectangle(
540        cursor: &mut Cursor<Vec<u8>>,
541    ) -> Result<Rectangle, Box<dyn std::error::Error>> {
542        let _info_byte = Self::read_u8(cursor)?;
543        let layer = Self::read_unsigned(cursor)? as u32;
544        let datatype = Self::read_unsigned(cursor)? as u32;
545        let width = Self::read_unsigned(cursor)?;
546        let height = Self::read_unsigned(cursor)?;
547        let x = Self::read_signed(cursor)?;
548        let y = Self::read_signed(cursor)?;
549
550        Ok(Rectangle {
551            layer,
552            datatype,
553            x,
554            y,
555            width,
556            height,
557            repetition: None,
558            properties: Vec::new(),
559        })
560    }
561
562    fn read_polygon(cursor: &mut Cursor<Vec<u8>>) -> Result<Polygon, Box<dyn std::error::Error>> {
563        let _info_byte = Self::read_u8(cursor)?;
564        let layer = Self::read_unsigned(cursor)? as u32;
565        let datatype = Self::read_unsigned(cursor)? as u32;
566        let point_count = Self::read_unsigned(cursor)? as usize;
567
568        let mut points = Vec::new();
569        for _ in 0..point_count {
570            let x = Self::read_signed(cursor)?;
571            let y = Self::read_signed(cursor)?;
572            points.push((x, y));
573        }
574
575        let x = Self::read_signed(cursor)?;
576        let y = Self::read_signed(cursor)?;
577
578        Ok(Polygon {
579            layer,
580            datatype,
581            x,
582            y,
583            points,
584            repetition: None,
585            properties: Vec::new(),
586        })
587    }
588
589    fn read_path(cursor: &mut Cursor<Vec<u8>>) -> Result<OPath, Box<dyn std::error::Error>> {
590        let _info_byte = Self::read_u8(cursor)?;
591        let layer = Self::read_unsigned(cursor)? as u32;
592        let datatype = Self::read_unsigned(cursor)? as u32;
593        let half_width = Self::read_unsigned(cursor)?;
594
595        let ext_scheme_type = Self::read_u8(cursor)?;
596        let extension_scheme = match ext_scheme_type {
597            0 => ExtensionScheme::Flush,
598            1 => ExtensionScheme::HalfWidth,
599            2 => {
600                let start = Self::read_signed(cursor)?;
601                let end = Self::read_signed(cursor)?;
602                ExtensionScheme::Custom { start, end }
603            }
604            _ => ExtensionScheme::Flush,
605        };
606
607        let point_count = Self::read_unsigned(cursor)? as usize;
608        let mut points = Vec::new();
609        for _ in 0..point_count {
610            let x = Self::read_signed(cursor)?;
611            let y = Self::read_signed(cursor)?;
612            points.push((x, y));
613        }
614
615        let x = Self::read_signed(cursor)?;
616        let y = Self::read_signed(cursor)?;
617
618        Ok(OPath {
619            layer,
620            datatype,
621            x,
622            y,
623            half_width,
624            extension_scheme,
625            points,
626            repetition: None,
627            properties: Vec::new(),
628        })
629    }
630
631    fn read_trapezoid(
632        cursor: &mut Cursor<Vec<u8>>,
633    ) -> Result<Trapezoid, Box<dyn std::error::Error>> {
634        let layer = Self::read_unsigned(cursor)? as u32;
635        let datatype = Self::read_unsigned(cursor)? as u32;
636        let orientation = Self::read_u8(cursor)? != 0;
637        let width = Self::read_unsigned(cursor)?;
638        let height = Self::read_unsigned(cursor)?;
639        let delta_a = Self::read_signed(cursor)?;
640        let delta_b = Self::read_signed(cursor)?;
641        let x = Self::read_signed(cursor)?;
642        let y = Self::read_signed(cursor)?;
643
644        Ok(Trapezoid {
645            layer,
646            datatype,
647            orientation,
648            width,
649            height,
650            delta_a,
651            delta_b,
652            x,
653            y,
654            repetition: None,
655            properties: Vec::new(),
656        })
657    }
658
659    fn read_ctrapezoid(
660        cursor: &mut Cursor<Vec<u8>>,
661    ) -> Result<CTrapezoid, Box<dyn std::error::Error>> {
662        let layer = Self::read_unsigned(cursor)? as u32;
663        let datatype = Self::read_unsigned(cursor)? as u32;
664        let trap_type = Self::read_u8(cursor)?;
665        let width = Self::read_unsigned(cursor)?;
666        let height = Self::read_unsigned(cursor)?;
667        let x = Self::read_signed(cursor)?;
668        let y = Self::read_signed(cursor)?;
669
670        Ok(CTrapezoid {
671            layer,
672            datatype,
673            trap_type,
674            width,
675            height,
676            x,
677            y,
678            repetition: None,
679            properties: Vec::new(),
680        })
681    }
682
683    fn read_circle(cursor: &mut Cursor<Vec<u8>>) -> Result<Circle, Box<dyn std::error::Error>> {
684        let layer = Self::read_unsigned(cursor)? as u32;
685        let datatype = Self::read_unsigned(cursor)? as u32;
686        let radius = Self::read_unsigned(cursor)?;
687        let x = Self::read_signed(cursor)?;
688        let y = Self::read_signed(cursor)?;
689
690        Ok(Circle {
691            layer,
692            datatype,
693            radius,
694            x,
695            y,
696            repetition: None,
697            properties: Vec::new(),
698        })
699    }
700
701    fn read_text(
702        cursor: &mut Cursor<Vec<u8>>,
703        _names: &NameTable,
704    ) -> Result<OText, Box<dyn std::error::Error>> {
705        let layer = Self::read_unsigned(cursor)? as u32;
706        let texttype = Self::read_unsigned(cursor)? as u32;
707        let string = Self::read_string(cursor)?;
708        let x = Self::read_signed(cursor)?;
709        let y = Self::read_signed(cursor)?;
710
711        Ok(OText {
712            layer,
713            texttype,
714            string,
715            x,
716            y,
717            repetition: None,
718            properties: Vec::new(),
719        })
720    }
721
722    fn read_placement(
723        cursor: &mut Cursor<Vec<u8>>,
724        _names: &NameTable,
725    ) -> Result<Placement, Box<dyn std::error::Error>> {
726        let cell_name = Self::read_string(cursor)?;
727        let x = Self::read_signed(cursor)?;
728        let y = Self::read_signed(cursor)?;
729
730        Ok(Placement {
731            cell_name,
732            x,
733            y,
734            magnification: None,
735            angle: None,
736            mirror: false,
737            repetition: None,
738            properties: Vec::new(),
739        })
740    }
741
742    // Helper I/O functions
743    fn read_u8<R: Read>(cursor: &mut R) -> Result<u8, Box<dyn std::error::Error>> {
744        let mut buf = [0u8; 1];
745        cursor.read_exact(&mut buf)?;
746        Ok(buf[0])
747    }
748
749    fn write_u8<W: Write>(writer: &mut W, value: u8) -> Result<(), Box<dyn std::error::Error>> {
750        writer.write_all(&[value])?;
751        Ok(())
752    }
753
754    fn read_bytes<R: Read>(
755        cursor: &mut R,
756        len: usize,
757    ) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
758        let mut buf = vec![0u8; len];
759        cursor.read_exact(&mut buf)?;
760        Ok(buf)
761    }
762
763    fn read_unsigned<R: Read>(cursor: &mut R) -> Result<u64, Box<dyn std::error::Error>> {
764        // Variable-length unsigned integer encoding
765        let mut result = 0u64;
766        let mut shift = 0;
767
768        loop {
769            let byte = Self::read_u8(cursor)?;
770            result |= ((byte & 0x7F) as u64) << shift;
771
772            if byte & 0x80 == 0 {
773                break;
774            }
775            shift += 7;
776        }
777
778        Ok(result)
779    }
780
781    fn write_unsigned<W: Write>(
782        writer: &mut W,
783        mut value: u64,
784    ) -> Result<(), Box<dyn std::error::Error>> {
785        loop {
786            let mut byte = (value & 0x7F) as u8;
787            value >>= 7;
788
789            if value != 0 {
790                byte |= 0x80;
791            }
792
793            Self::write_u8(writer, byte)?;
794
795            if value == 0 {
796                break;
797            }
798        }
799        Ok(())
800    }
801
802    fn read_signed<R: Read>(cursor: &mut R) -> Result<i64, Box<dyn std::error::Error>> {
803        let unsigned = Self::read_unsigned(cursor)?;
804
805        // Zigzag decoding
806        let signed = if unsigned & 1 == 0 {
807            (unsigned >> 1) as i64
808        } else {
809            -((unsigned >> 1) as i64) - 1
810        };
811
812        Ok(signed)
813    }
814
815    fn write_signed<W: Write>(
816        writer: &mut W,
817        value: i64,
818    ) -> Result<(), Box<dyn std::error::Error>> {
819        // Zigzag encoding
820        let unsigned = if value >= 0 {
821            (value as u64) << 1
822        } else {
823            (((-value - 1) as u64) << 1) | 1
824        };
825
826        Self::write_unsigned(writer, unsigned)
827    }
828
829    fn read_string<R: Read>(cursor: &mut R) -> Result<String, Box<dyn std::error::Error>> {
830        let len = Self::read_unsigned(cursor)? as usize;
831        let bytes = Self::read_bytes(cursor, len)?;
832        // Use from_utf8_lossy to handle non-UTF8 strings gracefully
833        // OASIS spec uses ASCII, but some tools may write non-UTF8 data
834        Ok(String::from_utf8_lossy(&bytes).into_owned())
835    }
836
837    fn write_string<W: Write>(writer: &mut W, s: &str) -> Result<(), Box<dyn std::error::Error>> {
838        Self::write_unsigned(writer, s.len() as u64)?;
839        writer.write_all(s.as_bytes())?;
840        Ok(())
841    }
842
843    fn read_real<R: Read>(cursor: &mut R) -> Result<f64, Box<dyn std::error::Error>> {
844        let type_byte = Self::read_u8(cursor)?;
845
846        match type_byte {
847            0 => Ok(0.0),
848            1 => Ok(1.0),
849            2 => {
850                let mantissa = Self::read_unsigned(cursor)? as f64;
851                let exponent = Self::read_signed(cursor)? as i32;
852                Ok(mantissa * 10f64.powi(exponent))
853            }
854            3 => {
855                let mantissa = Self::read_signed(cursor)? as f64;
856                let exponent = Self::read_signed(cursor)? as i32;
857                Ok(mantissa * 10f64.powi(exponent))
858            }
859            4 | 5 => {
860                let mantissa = if type_byte == 4 {
861                    Self::read_unsigned(cursor)? as f64
862                } else {
863                    Self::read_signed(cursor)? as f64
864                };
865                let denominator = Self::read_unsigned(cursor)? as f64;
866                let exponent = Self::read_signed(cursor)? as i32;
867                Ok((mantissa / denominator) * 10f64.powi(exponent))
868            }
869            6 => {
870                let mut bytes = [0u8; 4];
871                cursor.read_exact(&mut bytes)?;
872                Ok(f32::from_le_bytes(bytes) as f64)
873            }
874            7 => {
875                let mut bytes = [0u8; 8];
876                cursor.read_exact(&mut bytes)?;
877                Ok(f64::from_le_bytes(bytes))
878            }
879            _ => Err(format!(
880                "Invalid real type: {} (may indicate corrupted file or misaligned read)",
881                type_byte
882            )
883            .into()),
884        }
885    }
886
887    fn write_real<W: Write>(writer: &mut W, value: f64) -> Result<(), Box<dyn std::error::Error>> {
888        // Use IEEE 754 double precision for simplicity
889        Self::write_u8(writer, 7)?;
890        writer.write_all(&value.to_le_bytes())?;
891        Ok(())
892    }
893
894    fn read_layer_interval<R: Read>(
895        cursor: &mut R,
896    ) -> Result<Vec<u32>, Box<dyn std::error::Error>> {
897        let type_byte = Self::read_u8(cursor)?;
898
899        match type_byte {
900            0 => {
901                let value = Self::read_unsigned(cursor)? as u32;
902                Ok(vec![value])
903            }
904            1 => {
905                let start = Self::read_unsigned(cursor)? as u32;
906                let end = Self::read_unsigned(cursor)? as u32;
907                Ok((start..=end).collect())
908            }
909            2 => {
910                let count = Self::read_unsigned(cursor)? as usize;
911                let mut values = Vec::new();
912                for _ in 0..count {
913                    values.push(Self::read_unsigned(cursor)? as u32);
914                }
915                Ok(values)
916            }
917            _ => Err("Invalid layer interval type".into()),
918        }
919    }
920}
921
922impl OASISCell {
923    fn write<W: Write>(
924        &self,
925        writer: &mut W,
926        names: &NameTable,
927    ) -> Result<(), Box<dyn std::error::Error>> {
928        // Write CELL record (type 13 or 14)
929        // For simplicity, use type 14 which includes explicit placement
930        OASISFile::write_u8(writer, 14)?; // XYAbsolute - sets coordinate mode
931
932        // Write CELL record
933        OASISFile::write_u8(writer, 13)?;
934
935        // Write cell name (using reference if available)
936        let name_ref = names
937            .cell_names
938            .iter()
939            .find(|(_, n)| n.as_str() == self.name)
940            .map(|(r, _)| *r);
941
942        if let Some(ref_num) = name_ref {
943            OASISFile::write_unsigned(writer, (ref_num as u64) << 1)?;
944        } else {
945            OASISFile::write_unsigned(writer, 1)?;
946            OASISFile::write_string(writer, &self.name)?;
947        }
948
949        // Write elements
950        for element in &self.elements {
951            element.write(writer, names)?;
952        }
953
954        Ok(())
955    }
956}
957
958impl OASISElement {
959    fn write<W: Write>(
960        &self,
961        writer: &mut W,
962        names: &NameTable,
963    ) -> Result<(), Box<dyn std::error::Error>> {
964        match self {
965            OASISElement::Rectangle(r) => r.write(writer),
966            OASISElement::Polygon(p) => p.write(writer),
967            OASISElement::Path(p) => p.write(writer),
968            OASISElement::Trapezoid(t) => t.write(writer),
969            OASISElement::CTrapezoid(ct) => ct.write(writer),
970            OASISElement::Circle(c) => c.write(writer),
971            OASISElement::Text(t) => t.write(writer, names),
972            OASISElement::Placement(p) => p.write(writer, names),
973        }
974    }
975}
976
977impl Rectangle {
978    fn write<W: Write>(&self, writer: &mut W) -> Result<(), Box<dyn std::error::Error>> {
979        OASISFile::write_u8(writer, 19)?; // RECTANGLE
980        OASISFile::write_u8(writer, 0)?; // Info byte (simplified)
981        OASISFile::write_unsigned(writer, self.layer as u64)?;
982        OASISFile::write_unsigned(writer, self.datatype as u64)?;
983        OASISFile::write_unsigned(writer, self.width)?;
984        OASISFile::write_unsigned(writer, self.height)?;
985        OASISFile::write_signed(writer, self.x)?;
986        OASISFile::write_signed(writer, self.y)?;
987        Ok(())
988    }
989}
990
991impl Polygon {
992    fn write<W: Write>(&self, writer: &mut W) -> Result<(), Box<dyn std::error::Error>> {
993        OASISFile::write_u8(writer, 20)?; // POLYGON
994        OASISFile::write_u8(writer, 0)?; // Info byte (simplified)
995        OASISFile::write_unsigned(writer, self.layer as u64)?;
996        OASISFile::write_unsigned(writer, self.datatype as u64)?;
997        OASISFile::write_unsigned(writer, self.points.len() as u64)?;
998
999        for (x, y) in &self.points {
1000            OASISFile::write_signed(writer, *x)?;
1001            OASISFile::write_signed(writer, *y)?;
1002        }
1003
1004        OASISFile::write_signed(writer, self.x)?;
1005        OASISFile::write_signed(writer, self.y)?;
1006        Ok(())
1007    }
1008}
1009
1010impl OPath {
1011    fn write<W: Write>(&self, writer: &mut W) -> Result<(), Box<dyn std::error::Error>> {
1012        OASISFile::write_u8(writer, 21)?; // PATH
1013        OASISFile::write_u8(writer, 0)?; // Info byte (simplified)
1014        OASISFile::write_unsigned(writer, self.layer as u64)?;
1015        OASISFile::write_unsigned(writer, self.datatype as u64)?;
1016        OASISFile::write_unsigned(writer, self.half_width)?;
1017
1018        // Extension scheme
1019        match &self.extension_scheme {
1020            ExtensionScheme::Flush => OASISFile::write_u8(writer, 0)?,
1021            ExtensionScheme::HalfWidth => OASISFile::write_u8(writer, 1)?,
1022            ExtensionScheme::Custom { start, end } => {
1023                OASISFile::write_u8(writer, 2)?;
1024                OASISFile::write_signed(writer, *start)?;
1025                OASISFile::write_signed(writer, *end)?;
1026            }
1027        }
1028
1029        OASISFile::write_unsigned(writer, self.points.len() as u64)?;
1030        for (x, y) in &self.points {
1031            OASISFile::write_signed(writer, *x)?;
1032            OASISFile::write_signed(writer, *y)?;
1033        }
1034
1035        OASISFile::write_signed(writer, self.x)?;
1036        OASISFile::write_signed(writer, self.y)?;
1037        Ok(())
1038    }
1039}
1040
1041impl Trapezoid {
1042    fn write<W: Write>(&self, writer: &mut W) -> Result<(), Box<dyn std::error::Error>> {
1043        OASISFile::write_u8(writer, 22)?; // TRAPEZOID
1044        OASISFile::write_unsigned(writer, self.layer as u64)?;
1045        OASISFile::write_unsigned(writer, self.datatype as u64)?;
1046        OASISFile::write_u8(writer, if self.orientation { 1 } else { 0 })?;
1047        OASISFile::write_unsigned(writer, self.width)?;
1048        OASISFile::write_unsigned(writer, self.height)?;
1049        OASISFile::write_signed(writer, self.delta_a)?;
1050        OASISFile::write_signed(writer, self.delta_b)?;
1051        OASISFile::write_signed(writer, self.x)?;
1052        OASISFile::write_signed(writer, self.y)?;
1053        Ok(())
1054    }
1055}
1056
1057impl CTrapezoid {
1058    fn write<W: Write>(&self, writer: &mut W) -> Result<(), Box<dyn std::error::Error>> {
1059        OASISFile::write_u8(writer, 23)?; // CTRAPEZOID
1060        OASISFile::write_unsigned(writer, self.layer as u64)?;
1061        OASISFile::write_unsigned(writer, self.datatype as u64)?;
1062        OASISFile::write_u8(writer, self.trap_type)?;
1063        OASISFile::write_unsigned(writer, self.width)?;
1064        OASISFile::write_unsigned(writer, self.height)?;
1065        OASISFile::write_signed(writer, self.x)?;
1066        OASISFile::write_signed(writer, self.y)?;
1067        Ok(())
1068    }
1069}
1070
1071impl Circle {
1072    fn write<W: Write>(&self, writer: &mut W) -> Result<(), Box<dyn std::error::Error>> {
1073        OASISFile::write_u8(writer, 24)?; // CIRCLE
1074        OASISFile::write_unsigned(writer, self.layer as u64)?;
1075        OASISFile::write_unsigned(writer, self.datatype as u64)?;
1076        OASISFile::write_unsigned(writer, self.radius)?;
1077        OASISFile::write_signed(writer, self.x)?;
1078        OASISFile::write_signed(writer, self.y)?;
1079        Ok(())
1080    }
1081}
1082
1083impl OText {
1084    fn write<W: Write>(
1085        &self,
1086        writer: &mut W,
1087        _names: &NameTable,
1088    ) -> Result<(), Box<dyn std::error::Error>> {
1089        OASISFile::write_u8(writer, 18)?; // TEXT
1090        OASISFile::write_unsigned(writer, self.layer as u64)?;
1091        OASISFile::write_unsigned(writer, self.texttype as u64)?;
1092        OASISFile::write_string(writer, &self.string)?;
1093        OASISFile::write_signed(writer, self.x)?;
1094        OASISFile::write_signed(writer, self.y)?;
1095        Ok(())
1096    }
1097}
1098
1099impl Placement {
1100    fn write<W: Write>(
1101        &self,
1102        writer: &mut W,
1103        _names: &NameTable,
1104    ) -> Result<(), Box<dyn std::error::Error>> {
1105        OASISFile::write_u8(writer, 16)?; // PLACEMENT
1106        OASISFile::write_string(writer, &self.cell_name)?;
1107        OASISFile::write_signed(writer, self.x)?;
1108        OASISFile::write_signed(writer, self.y)?;
1109        Ok(())
1110    }
1111}