Skip to main content

mlt_core/decoder/
model01.rs

1//! Column model and parsing specific to tag `0x01` (v1) layers.
2//!
3//! v2 replaces both types below with `model02::ColumnType02` and
4//! `model02::GeoLayout`; the resulting [`super::Layer01`] is shared.
5
6use std::io;
7use std::io::Write;
8
9use num_enum::TryFromPrimitive;
10
11use crate::MltError::ParsingColumnType;
12use crate::decoder::{Geometry, GeometryValues, Id, ParsedProperty, Property};
13use crate::tile::Extent;
14use crate::utils::{BinarySerializer as _, parse_string, parse_u8};
15use crate::{DecodeState, Lazy, MltRefResult, Parsed, Parser};
16
17/// Bit 0 of the column type byte: the column has a presence stream.
18pub(crate) const OPTIONAL_FLAG: u8 = 0b0000_0001;
19
20/// Mask of the column type byte holding the base [`ColumnType`], i.e. everything
21/// [`OPTIONAL_FLAG`] does not claim.
22pub(crate) const BASE_TYPE_MASK: u8 = 0b1111_1110;
23
24/// Representation of an MLT feature table layer during decoding.
25///
26/// Holds what every layer version has. A v1 layer is exactly this; a v2 layer is
27/// this plus the columns only v2 carries, which `Layer02` adds around it.
28///
29/// The type parameter `S` controls how columns are stored:
30///
31/// - `Layer01<'a>` / `Layer01<'a, Lazy>` (default) - columns are `LazyParsed` enums
32///   that may be raw or decoded. Use [`Layer01::decode_all`] to transition to `Layer01<Parsed>`.
33///
34/// - `Layer01<'a, Parsed>` - all columns are fully decoded. The fields `id`, `geometry`, and
35///   `properties` hold the parsed types directly, allowing infallible readonly access.
36///   There is a `ParsedLayer01<'a>` type alias for this.
37#[derive(Debug, Clone)]
38pub struct Layer01<'a, S: DecodeState = Lazy> {
39    pub(crate) name: &'a str,
40    pub(crate) extent: Extent,
41    pub(crate) id: Option<Id<'a, S>>,
42    pub(crate) geometry: Geometry<'a, S>,
43    pub(crate) properties: Vec<Property<'a, S>>,
44    #[cfg(fuzzing)]
45    pub(crate) layer_order: Vec<crate::decoder::fuzzing::LayerOrdering>,
46}
47
48pub type ParsedLayer01<'a> = Layer01<'a, Parsed>;
49
50impl<'a, S: DecodeState> Layer01<'a, S> {
51    #[must_use]
52    pub fn name(&self) -> &'a str {
53        self.name
54    }
55
56    #[must_use]
57    pub fn extent(&self) -> Extent {
58        self.extent
59    }
60}
61
62impl ParsedLayer01<'_> {
63    /// Returns the decoded geometry buffer for this layer.
64    ///
65    /// Provides access to the columnar geometry arrays (vertex buffer, offset arrays, geometry
66    /// types) for advanced use cases such as building typed arrays for WebAssembly or
67    /// performing spatial indexing. For iterating feature geometries as `geo_types` values,
68    /// prefer [`iter_features`](Self::iter_features) instead.
69    #[must_use]
70    pub fn geometry_values(&self) -> &GeometryValues {
71        &self.geometry
72    }
73
74    #[must_use]
75    pub fn feature_count(&self) -> usize {
76        self.geometry.vector_types.len()
77    }
78
79    /// The layer's property columns, in wire order.
80    #[must_use]
81    pub fn properties(&self) -> &[ParsedProperty<'_>] {
82        &self.properties
83    }
84}
85
86/// Column definition
87#[derive(Debug, PartialEq)]
88pub struct Column<'a> {
89    pub(crate) typ: ColumnType,
90    pub(crate) name: Option<&'a str>,
91    pub(crate) children: Vec<Self>,
92}
93
94/// Column data type, as stored in the tile.
95#[derive(Debug, Clone, Copy, PartialEq, TryFromPrimitive)]
96#[repr(u8)]
97pub enum ColumnType {
98    Id = 0b0000_0000,
99    OptId = 0b0000_0001,
100    LongId = 0b0000_0010,
101    OptLongId = 0b0000_0011,
102    Geometry = 0b0000_0100,
103    Bool = 0b0000_1010,
104    OptBool = 0b0000_1011,
105    I32 = 0b0001_0000,
106    OptI32 = 0b0001_0001,
107    U32 = 0b0001_0010,
108    OptU32 = 0b0001_0011,
109    I64 = 0b0001_0100,
110    OptI64 = 0b0001_0101,
111    U64 = 0b0001_0110,
112    OptU64 = 0b0001_0111,
113    F32 = 0b0001_1000,
114    OptF32 = 0b0001_1001,
115    F64 = 0b0001_1010,
116    OptF64 = 0b0001_1011,
117    Str = 0b0001_1100,
118    OptStr = 0b0001_1101,
119    SharedDict = 0b0001_1110,
120}
121
122impl Column<'_> {
123    /// Parse a single column definition
124    pub(crate) fn from_bytes<'a>(
125        input: &'a [u8],
126        _parser: &mut Parser,
127    ) -> MltRefResult<'a, Column<'a>> {
128        let (mut input, typ) = ColumnType::from_bytes(input)?;
129        let name = if typ.has_name() {
130            let pair = parse_string(input)?;
131            input = pair.0;
132            Some(pair.1)
133        } else {
134            None
135        };
136
137        Ok((
138            input,
139            Column {
140                typ,
141                name,
142                children: Vec::new(),
143            },
144        ))
145    }
146}
147
148impl ColumnType {
149    /// Parse a column type from u8
150    pub(crate) fn from_bytes(input: &[u8]) -> MltRefResult<'_, Self> {
151        let (input, value) = parse_u8(input)?;
152        let value = Self::try_from(value).or(Err(ParsingColumnType(value)))?;
153        Ok((input, value))
154    }
155
156    pub(crate) fn write_to<W: Write>(self, writer: &mut W) -> io::Result<()> {
157        writer.write_u8(self as u8)?;
158        Ok(())
159    }
160
161    /// Returns true if the column definition includes a name field in the serialized format.
162    /// Note: ID and Geometry columns use implicit naming and do not include a name field.
163    #[must_use]
164    pub(crate) fn has_name(self) -> bool {
165        !matches!(
166            self,
167            Self::Id | Self::OptId | Self::LongId | Self::OptLongId | Self::Geometry
168        )
169    }
170
171    /// Check if the column type has a presence stream
172    #[must_use]
173    pub(crate) fn is_optional(self) -> bool {
174        (self as u8) & OPTIONAL_FLAG != 0
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use rstest::rstest;
181
182    use super::ColumnType;
183    use crate::MltError;
184
185    #[rstest]
186    fn column_type_rejects_unassigned(#[values(5, 6, 7, 8, 9, 12, 13, 14, 15, 31)] byte: u8) {
187        let err = ColumnType::from_bytes(&[byte]).unwrap_err();
188        assert!(matches!(err, MltError::ParsingColumnType(b) if b == byte));
189    }
190}