Skip to main content

libreda_oasis/
writer.rs

1// Copyright (c) 2018-2021 Thomas Kramer.
2// SPDX-FileCopyrightText: 2022 Thomas Kramer <code@tkramer.ch>
3//
4// SPDX-License-Identifier: AGPL-3.0-or-later
5
6//! Write `Layout` data structures into the OASIS file format.
7
8use crate::Modal;
9use libreda_db::layout::prelude::*;
10
11use crate::base_types::*;
12
13use libreda_db::layout::traits::LayoutBase;
14use std::borrow::Borrow;
15use std::io::Write;
16
17/// Type of integrity checksum used while writing the OASIS stream.
18#[derive(Copy, Clone, Debug, Default)]
19pub enum OASISValidationScheme {
20    #[default]
21    None,
22    // CHECKSUM32, // Not supported yet.
23    // CRC32 // Not supported yet.
24}
25
26/// Configuration for the output stream format.
27#[derive(Clone, Debug, Default)]
28pub struct OASISWriterConfig {
29    /// Put offset table at start or at end?
30    /// TODO: Table offset at start is not supported yet.
31    table_offsets_at_start: bool,
32
33    /// Tells if IDs are given implicitly by a counter or are explicitly written in the file.
34    pub explicit_ids: bool,
35
36    /// Type of integrity checksum used while writing the OASIS stream. Default is `None` (0).
37    pub validation_scheme: OASISValidationScheme,
38}
39
40#[derive(Copy, Clone, Debug, Default)]
41struct Offset {
42    flag: UInt,
43    offset: UInt,
44}
45
46#[derive(Clone, Debug, Default)]
47struct OffsetTable {
48    cellname: Offset,
49    textstring: Offset,
50    propname: Offset,
51    propstring: Offset,
52    layername: Offset,
53    xname: Offset,
54}
55
56/// TODO
57fn write_offset_table<W: Write>(writer: &mut W, t: &OffsetTable) -> Result<(), OASISWriteError> {
58    for o in &[
59        t.cellname,
60        t.textstring,
61        t.propname,
62        t.propstring,
63        t.layername,
64        t.xname,
65    ] {
66        write_unsigned_integer(writer, o.flag)?;
67        write_unsigned_integer(writer, o.offset)?;
68    }
69
70    Ok(())
71}
72
73/// Return a integer with the bit at position `index` set depending on the `value`.
74fn bit(index: UInt, value: bool) -> UInt {
75    (value as UInt) << index
76}
77
78/// Write a `Path`.
79fn write_path<W: Write, C>(
80    writer: &mut W,
81    modal: &mut Modal<C>,
82    p: &Path<Coord>,
83    layer_index: UInt,
84    layer_datatype: UInt,
85) -> Result<(), OASISWriteError> {
86    trace!("Write PATH record");
87    write_unsigned_integer(writer, 22)?; // PATH record.
88
89    if !p.is_empty() {
90        let offset = p.points.points.first().unwrap(); // There must be a point, because this is checked in the condition above.
91        let point_list = PointList::from_points_explicit(p.points.points.iter().copied());
92
93        let (x, y) = offset.into();
94        let half_width = (p.width as UInt) / 2; // FXIME: Width is rounded down to next even number.
95
96        let (start_ext, end_ext) = match p.path_type {
97            PathEndType::Flat => (0, 0),
98            PathEndType::Extended(s, e) => (s, e),
99            PathEndType::Round => unimplemented!("Round path endings are not supported yet."), // TODO: Convert paths with round ends into polygons.
100        };
101
102        // TODO: This part is the same as for 'rectangle': Merge it?
103        let write_layer_number = modal.layer != Some(layer_index);
104        let write_datatype = modal.layer != Some(layer_datatype);
105        let write_x = modal.geometry_x != x;
106        let write_y = modal.geometry_y != y;
107        let write_repetition = false; // TODO: Detect repetitions. This would lead to more compressed outputs!
108
109        let write_point_list = Some(&point_list) != modal.path_point_list.as_ref();
110        let write_half_width_present = modal.path_halfwidth != Some(half_width);
111        let write_extension_scheme_present = modal.path_start_extension != Some(start_ext)
112            || modal.path_end_extension != Some(end_ext);
113
114        let path_info_byte = bit(7, write_extension_scheme_present)
115            | bit(6, write_half_width_present)
116            | bit(5, write_point_list)
117            | bit(4, write_x)
118            | bit(3, write_y)
119            | bit(2, write_repetition)
120            | bit(1, write_datatype)
121            | bit(0, write_layer_number);
122        write_byte(writer, path_info_byte as u8)?;
123
124        if write_layer_number {
125            write_unsigned_integer(writer, layer_index)?
126        };
127        if write_datatype {
128            write_unsigned_integer(writer, layer_datatype)?
129        };
130        if write_half_width_present {
131            write_unsigned_integer(writer, half_width)?
132        };
133        if write_extension_scheme_present {
134            let ss = match start_ext {
135                0 => 0b01,                                          // Zero-length extension.
136                x if x == half_width as SInt => 0b10,               // Half-width extension.
137                x if Some(x) == modal.path_start_extension => 0b00, // Use modal variable.
138                _ => 0b11,                                          // Explicit encoding.
139            };
140            let ee = match end_ext {
141                0 => 0b01,                                        // Zero-length extension.
142                x if x == half_width as SInt => 0b10,             // Half-width extension.
143                x if Some(x) == modal.path_end_extension => 0b00, // Use modal variable.
144                _ => 0b11,                                        // Explicit encoding.
145            };
146
147            let extension_scheme = (ss << 2) | ee;
148
149            // Write extension-scheme.
150            write_byte(writer, extension_scheme)?;
151
152            if ss == 0b11 {
153                // Explicit start extension.
154                write_signed_integer(writer, start_ext)?;
155            }
156
157            if ee == 0b11 {
158                // Explicit end extension.
159                write_signed_integer(writer, end_ext)?;
160            }
161        };
162        if write_point_list {
163            write_pointlist(writer, &point_list)?
164        };
165        if write_x {
166            write_signed_integer(writer, x)?
167        };
168        if write_y {
169            write_signed_integer(writer, y)?
170        };
171        if write_repetition {
172            modal.repetition = None;
173            unimplemented!();
174        };
175
176        // Remember last written point list.
177        modal.path_point_list = Some(point_list);
178        modal.path_halfwidth = Some(half_width);
179        modal.path_start_extension = Some(start_ext);
180        modal.path_end_extension = Some(end_ext);
181
182        modal.geometry_x = x;
183        modal.geometry_y = y;
184
185        modal.datatype = Some(layer_datatype);
186        modal.layer = Some(layer_index);
187    } else {
188        // Empty path, don't write anything.
189    }
190    Ok(())
191}
192
193/// Write a `Rect`.
194fn write_rect<W: Write, C>(
195    writer: &mut W,
196    modal: &mut Modal<C>,
197    r: &Rect<Coord>,
198    layer_index: UInt,
199    layer_datatype: UInt,
200) -> Result<(), OASISWriteError> {
201    trace!("Write RECT record");
202    write_unsigned_integer(writer, 20)?;
203
204    // write_rectangle(r, layer_info.index, layer_info.datatype)?;
205    let width = r.width() as UInt;
206    let height = r.height() as UInt;
207    let (x, y) = r.lower_left.into();
208
209    let is_square = width == height;
210    let write_layer_number = modal.layer != Some(layer_index);
211    let write_datatype = modal.datatype != Some(layer_datatype);
212    let write_width = modal.geometry_w != Some(width);
213    let write_height = !is_square && modal.geometry_h != Some(height);
214    let write_x = modal.geometry_x != x;
215    let write_y = modal.geometry_y != y;
216    let write_repetition = false; // TODO: Detect repetitions. This would lead to more compressed outputs!
217    let rectangle_info_byte = bit(7, is_square)
218        | bit(6, write_width)
219        | bit(5, write_height)
220        | bit(4, write_x)
221        | bit(3, write_y)
222        | bit(2, write_repetition)
223        | bit(1, write_datatype)
224        | bit(0, write_layer_number);
225    write_byte(writer, rectangle_info_byte as u8)?;
226
227    if write_layer_number {
228        write_unsigned_integer(writer, layer_index)?
229    };
230    if write_datatype {
231        write_unsigned_integer(writer, layer_datatype)?
232    };
233    if write_width {
234        write_unsigned_integer(writer, width)?
235    };
236    if write_height {
237        write_unsigned_integer(writer, height)?
238    };
239    if write_x {
240        write_signed_integer(writer, x)?
241    };
242    if write_y {
243        write_signed_integer(writer, y)?
244    };
245    if write_repetition {
246        unimplemented!();
247    };
248
249    modal.geometry_w = Some(width);
250    modal.geometry_h = Some(height);
251    modal.geometry_x = x;
252    modal.geometry_y = y;
253
254    modal.datatype = Some(layer_datatype);
255    modal.layer = Some(layer_index);
256    Ok(())
257}
258
259/// Write a `SimplePolygon`.
260fn write_simple_polygon<W: Write, C>(
261    writer: &mut W,
262    modal: &mut Modal<C>,
263    p: &SimplePolygon<Coord>,
264    layer_index: UInt,
265    layer_datatype: UInt,
266) -> Result<(), OASISWriteError> {
267    trace!("Write POLYGON record");
268
269    write_unsigned_integer(writer, 21)?;
270
271    if p.len() > 1 {
272        let offset = p.points().first().unwrap(); // There must be a point, because this is checked in the condition above.
273        let point_list = PointList::from_points_explicit(p.points().iter().copied());
274
275        let (x, y) = offset.into();
276
277        // TODO: This part is the same as for 'rectangle': Merge it?
278        let write_layer_number = modal.layer != Some(layer_index);
279        let write_datatype = modal.layer != Some(layer_datatype);
280        let write_x = modal.geometry_x != x;
281        let write_y = modal.geometry_y != y;
282        let write_repetition = false; // TODO: Detect repetitions. This would lead to more compressed outputs!
283
284        let is_pointlist_present = Some(&point_list) != modal.polygon_point_list.as_ref();
285
286        let polygon_info_byte = bit(5, is_pointlist_present)
287            | bit(4, write_x)
288            | bit(3, write_y)
289            | bit(2, write_repetition)
290            | bit(1, write_datatype)
291            | bit(0, write_layer_number);
292        write_byte(writer, polygon_info_byte as u8)?;
293        if write_layer_number {
294            write_unsigned_integer(writer, layer_index)?
295        };
296        if write_datatype {
297            write_unsigned_integer(writer, layer_datatype)?
298        };
299        if is_pointlist_present {
300            write_pointlist(writer, &point_list)?
301        };
302        if write_x {
303            write_signed_integer(writer, x)?
304        };
305        if write_y {
306            write_signed_integer(writer, y)?
307        };
308        if write_repetition {
309            modal.repetition = None;
310            unimplemented!();
311        };
312
313        // Remember last written point list.
314        modal.polygon_point_list = Some(point_list);
315
316        modal.geometry_x = x;
317        modal.geometry_y = y;
318
319        modal.datatype = Some(layer_datatype);
320        modal.layer = Some(layer_index);
321    } else {
322        // Empty polygon: Skip it.
323    }
324    Ok(())
325}
326
327/// Write a `Text`.
328fn write_text<W: Write, C>(
329    writer: &mut W,
330    modal: &mut Modal<C>,
331    text: &Text<Coord>,
332    layer_index: UInt,
333    layer_datatype: UInt,
334) -> Result<(), OASISWriteError> {
335    trace!("TEXT record");
336
337    write_unsigned_integer(writer, 19)?; // Text record.
338
339    let text_string = text.text();
340    let x = text.x();
341    let y = text.y();
342
343    // Construct text-info byte.
344    // No need to write the text if the modal variable currently contains it.
345    let write_text_reference = modal.text_string.as_ref() != Some(text_string);
346    // Write a reference number instead of a text string?
347    // TODO: Create a lookup-table for strings on-the-fly.
348    let write_reference_number = false;
349
350    let write_textlayer = modal.textlayer != Some(layer_index);
351    let write_texttype = modal.texttype != Some(layer_datatype);
352    let write_x = modal.text_x != x;
353    let write_y = modal.text_y != y;
354
355    let write_repetition = false; // TODO: Detect repetitions. This would lead to more compressed outputs!
356
357    let text_info_byte = bit(6, write_text_reference)
358        | bit(5, write_reference_number)
359        | bit(4, write_x)
360        | bit(3, write_y)
361        | bit(2, write_repetition)
362        | bit(1, write_texttype)
363        | bit(0, write_textlayer);
364    write_byte(writer, text_info_byte as u8)?;
365
366    if write_text_reference {
367        if write_reference_number {
368            // Text must be present in a TEXT STRING record.
369            unimplemented!()
370        } else {
371            // Write text explicitly.
372            write_ascii_string(writer, text_string.as_bytes())?
373        }
374    }
375
376    if write_textlayer {
377        write_unsigned_integer(writer, layer_index)?
378    };
379    if write_texttype {
380        write_unsigned_integer(writer, layer_datatype)?
381    };
382    if write_x {
383        write_signed_integer(writer, x)?
384    };
385    if write_y {
386        write_signed_integer(writer, y)?
387    };
388    if write_repetition {
389        modal.repetition = None;
390        unimplemented!();
391    };
392
393    // Update modal variables.
394    modal.text_x = x;
395    modal.text_y = y;
396    modal.texttype = Some(layer_datatype);
397    modal.textlayer = Some(layer_index);
398    if write_text_reference {
399        modal.text_string = Some(text_string.clone());
400    }
401    Ok(())
402}
403
404/// Serialize the `layout` to the `writer` in the OASIS format.
405/// The `conf` structure is used for controlling the output format.
406pub fn write_layout<W: Write, L: LayoutBase<Coord = SInt>>(
407    writer: &mut W,
408    layout: &L,
409    conf: &OASISWriterConfig,
410) -> Result<(), OASISWriteError> {
411    trace!("Write layout to OASIS");
412
413    // Modal variables.
414    let mut modal = Modal::default();
415
416    write_magic(writer)?;
417    // Write START record.
418    write_unsigned_integer(writer, 1)?;
419    // Write version string.
420    write_ascii_string(writer, "1.0".as_bytes())?;
421
422    // Write resolution.
423    if layout.dbu().is_zero() {
424        return Err(OASISWriteError::DbuIsZero);
425    }
426    let resolution = Real::PositiveWholeNumber(layout.dbu() as u32);
427    write_real(writer, resolution)?;
428
429    // Put offset table at start or at end?
430    let offset_flag = if conf.table_offsets_at_start { 0 } else { 1 };
431    write_unsigned_integer(writer, offset_flag)?;
432    if conf.table_offsets_at_start {
433        // table-offsets is stored in START record.
434        write_offset_table(writer, &OffsetTable::default())?; // TODO: Construct proper offset table.
435    }
436
437    // Define counters for implicit IDs.
438    // TODO: Writing implicit IDs is not supported yet.
439    let mut _cellname_id_counter = 0..;
440    let mut _textstrings_id_counter = 0..;
441    let mut _propname_id_counter = 0..;
442    let mut _propstrings_id_counter = 0..;
443
444    // Loop through all cells.
445    for cell in layout.each_cell() {
446        let cell_name_raw = layout.cell_name(&cell);
447        let cell_name: &str = cell_name_raw.borrow();
448        trace!("write cell '{:?}'", cell_name);
449
450        // Write CELL record (identified by name).
451        // TODO: Support CELL record 13 by ID.
452        write_unsigned_integer(writer, 14)?;
453        write_name_string(writer, cell_name.as_bytes())?;
454
455        modal.reset();
456
457        // Write all shapes inside this cell.
458        for layer_id in layout.each_layer() {
459            trace!("write shapes on layer '{:?}'", layer_id);
460            let layer_info = layout.layer_info(&layer_id);
461            let layer_index = layer_info.index;
462            let layer_datatype = layer_info.datatype;
463
464            // Clone all shapes.
465            // TODO: This is a hack around the current layout trait. Find a solution where geometries need not be cloned.
466            let mut shapes = Vec::new();
467            layout.for_each_shape(&cell, &layer_id, |_id, geo| shapes.push(geo.clone()));
468
469            for geometry in &shapes {
470                match geometry {
471                    Geometry::Rect(r) => {
472                        write_rect(writer, &mut modal, r, layer_index, layer_datatype)?
473                    }
474                    Geometry::SimplePolygon(p) => {
475                        write_simple_polygon(writer, &mut modal, p, layer_index, layer_datatype)?
476                    }
477                    Geometry::SimpleRPolygon(p) =>
478                    // TODO: Don't convert to generic polygon but write a manhattanized polygon. This saves space.
479                    {
480                        write_simple_polygon(
481                            writer,
482                            &mut modal,
483                            &p.to_simple_polygon(),
484                            layer_index,
485                            layer_datatype,
486                        )?
487                    }
488                    Geometry::Polygon(p) => {
489                        if !p.interiors.is_empty() {
490                            // TODO: Cut the polygon such that the results have no holes.
491                            unimplemented!("Polygons with holes are not supported yet.");
492                        }
493                        write_simple_polygon(
494                            writer,
495                            &mut modal,
496                            &p.exterior,
497                            layer_index,
498                            layer_datatype,
499                        )?;
500                    }
501                    Geometry::Path(p) => {
502                        write_path(writer, &mut modal, p, layer_index, layer_datatype)?
503                    }
504                    Geometry::Text(text) => {
505                        write_text(writer, &mut modal, text, layer_index, layer_datatype)?
506                    }
507                    Geometry::Edge(_) => {
508                        // Edge is not written to layout.
509                    }
510                    Geometry::Point(_) => {
511                        // Not written to layout.
512                    }
513                }
514            }
515        }
516
517        // Loop through all instances.
518        for inst in layout.each_cell_instance(&cell) {
519            // Write PLACEMENT records.
520            let placement_cell = layout.template_cell(&inst);
521
522            let tf = layout.get_transform(&inst);
523
524            if tf.magnification == 1 {
525                write_unsigned_integer(writer, 17)?; // PLACEMENT record (with simple representation of rotation and magnification).
526
527                let (x, y) = tf.displacement.into();
528
529                // Prepare encoding of the angle in the AA bits of the placement info byte.
530                let aa = match tf.rotation {
531                    Angle::R0 => 0,
532                    Angle::R90 => 1,
533                    Angle::R180 => 2,
534                    Angle::R270 => 3,
535                };
536
537                let is_explicit_reference =  // C
538                    // Use an implicit reference if the current placement cell is the same as the last.
539                    modal.placement_cell.as_ref() != Some(&placement_cell);
540                // For now, always use references by cell name.
541                let is_cell_ref_present = false; // N
542
543                let is_flip = tf.mirror; // F
544
545                let write_x = modal.placement_x != x; // X
546                let write_y = modal.placement_y != y; // Y
547                let write_repetition = false; // R
548
549                let placement_info_byte = bit(0, is_flip)
550                    | (aa << 1)
551                    | bit(3, write_repetition)
552                    | bit(4, write_y)
553                    | bit(5, write_x)
554                    | bit(6, is_cell_ref_present)
555                    | bit(7, is_explicit_reference);
556
557                write_byte(writer, placement_info_byte as u8)?;
558
559                if is_explicit_reference {
560                    if is_cell_ref_present {
561                        // Write placement cell reference number.
562                        unimplemented!();
563                    } else {
564                        // Write placement cell name.
565                        // TODO: Return error instead of expect or generate cell name!
566                        let n = layout.cell_name(&placement_cell);
567                        let name: &str = n.borrow();
568                        write_name_string(writer, name.as_bytes())?;
569                    }
570                }
571
572                if write_x {
573                    write_signed_integer(writer, x)?
574                };
575                if write_y {
576                    write_signed_integer(writer, y)?
577                };
578                if write_repetition {
579                    modal.repetition = None;
580                    unimplemented!();
581                };
582
583                modal.placement_cell = Some(placement_cell);
584                modal.placement_x = x;
585                modal.placement_y = y;
586            } else {
587                unimplemented!("Magnifications other than 1 are not supported yet.");
588            }
589        }
590    }
591
592    // Write END record.
593    let mut end_record_buf = Vec::new();
594    write_unsigned_integer(&mut end_record_buf, 2)?; // END record.
595
596    if !conf.table_offsets_at_start {
597        // TODO: Write meaningful offset table instead of dummy.
598        write_offset_table(&mut end_record_buf, &OffsetTable::default())?;
599    }
600
601    // Write padding string: Sized such that the total byte length of the END record is exactly 256 bytes.
602    // (Including record ID)
603    // Validation scheme: -1
604    // Validation signature: -1/-4
605    // Length field of padding string: 2
606    let padding_length = 256 - end_record_buf.len() - 1 - 1 - 2;
607    writer.write_all(end_record_buf.as_slice())?;
608    write_byte_string(writer, vec![0u8; padding_length].as_slice())?;
609
610    // Write validation scheme.
611    // TODO: Support CRC32, CHECKSUM32.
612    let validation_scheme = match conf.validation_scheme {
613        OASISValidationScheme::None => 0,
614    };
615    write_unsigned_integer(writer, validation_scheme)?;
616
617    // Write validation signature.
618    match conf.validation_scheme {
619        OASISValidationScheme::None => {
620            write_unsigned_integer(writer, 0)?;
621        }
622    };
623
624    // Finalize output.
625    writer.flush()?;
626
627    Ok(())
628}