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, LayerBytes, 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    pub(crate) bytes: LayerBytes,
45    #[cfg(fuzzing)]
46    pub(crate) layer_order: Vec<crate::decoder::fuzzing::LayerOrdering>,
47}
48
49pub type ParsedLayer01<'a> = Layer01<'a, Parsed>;
50
51impl<'a, S: DecodeState> Layer01<'a, S> {
52    #[must_use]
53    pub fn name(&self) -> &'a str {
54        self.name
55    }
56
57    #[must_use]
58    pub fn extent(&self) -> Extent {
59        self.extent
60    }
61
62    /// The bytes the layer spent on the wire, by what they hold.
63    #[must_use]
64    pub fn bytes(&self) -> LayerBytes {
65        self.bytes
66    }
67}
68
69impl ParsedLayer01<'_> {
70    /// Returns the decoded geometry buffer for this layer.
71    ///
72    /// Provides access to the columnar geometry arrays (vertex buffer, offset arrays, geometry
73    /// types) for advanced use cases such as building typed arrays for WebAssembly or
74    /// performing spatial indexing. For iterating feature geometries as `geo_types` values,
75    /// prefer [`iter_features`](Self::iter_features) instead.
76    #[must_use]
77    pub fn geometry_values(&self) -> &GeometryValues {
78        &self.geometry
79    }
80
81    #[must_use]
82    pub fn feature_count(&self) -> usize {
83        self.geometry.vector_types.len()
84    }
85
86    /// The layer's property columns, in wire order.
87    #[must_use]
88    pub fn properties(&self) -> &[ParsedProperty<'_>] {
89        &self.properties
90    }
91}
92
93/// Column definition
94#[derive(Debug, PartialEq)]
95pub struct Column<'a> {
96    pub(crate) typ: ColumnType,
97    pub(crate) name: Option<&'a str>,
98    pub(crate) children: Vec<Self>,
99}
100
101/// Column data type, as stored in the tile.
102#[derive(Debug, Clone, Copy, PartialEq, TryFromPrimitive)]
103#[repr(u8)]
104pub enum ColumnType {
105    Id = 0b0000_0000,
106    OptId = 0b0000_0001,
107    LongId = 0b0000_0010,
108    OptLongId = 0b0000_0011,
109    Geometry = 0b0000_0100,
110    Bool = 0b0000_1010,
111    OptBool = 0b0000_1011,
112    I32 = 0b0001_0000,
113    OptI32 = 0b0001_0001,
114    U32 = 0b0001_0010,
115    OptU32 = 0b0001_0011,
116    I64 = 0b0001_0100,
117    OptI64 = 0b0001_0101,
118    U64 = 0b0001_0110,
119    OptU64 = 0b0001_0111,
120    F32 = 0b0001_1000,
121    OptF32 = 0b0001_1001,
122    F64 = 0b0001_1010,
123    OptF64 = 0b0001_1011,
124    Str = 0b0001_1100,
125    OptStr = 0b0001_1101,
126    SharedDict = 0b0001_1110,
127}
128
129impl Column<'_> {
130    /// Parse a single column definition
131    pub(crate) fn from_bytes<'a>(
132        input: &'a [u8],
133        _parser: &mut Parser,
134    ) -> MltRefResult<'a, Column<'a>> {
135        let (mut input, typ) = ColumnType::from_bytes(input)?;
136        let name = if typ.has_name() {
137            let pair = parse_string(input)?;
138            input = pair.0;
139            Some(pair.1)
140        } else {
141            None
142        };
143
144        Ok((
145            input,
146            Column {
147                typ,
148                name,
149                children: Vec::new(),
150            },
151        ))
152    }
153}
154
155impl ColumnType {
156    /// Parse a column type from u8
157    pub(crate) fn from_bytes(input: &[u8]) -> MltRefResult<'_, Self> {
158        let (input, value) = parse_u8(input)?;
159        let value = Self::try_from(value).or(Err(ParsingColumnType(value)))?;
160        Ok((input, value))
161    }
162
163    pub(crate) fn write_to<W: Write>(self, writer: &mut W) -> io::Result<()> {
164        writer.write_u8(self as u8)?;
165        Ok(())
166    }
167
168    /// Returns true if the column definition includes a name field in the serialized format.
169    /// Note: ID and Geometry columns use implicit naming and do not include a name field.
170    #[must_use]
171    pub(crate) fn has_name(self) -> bool {
172        !matches!(
173            self,
174            Self::Id | Self::OptId | Self::LongId | Self::OptLongId | Self::Geometry
175        )
176    }
177
178    /// Check if the column holds the feature ids.
179    #[must_use]
180    pub(crate) fn is_id(self) -> bool {
181        matches!(
182            self,
183            Self::Id | Self::OptId | Self::LongId | Self::OptLongId
184        )
185    }
186
187    /// Check if the column type has a presence stream
188    #[must_use]
189    pub(crate) fn is_optional(self) -> bool {
190        (self as u8) & OPTIONAL_FLAG != 0
191    }
192}
193
194#[cfg(test)]
195mod tests {
196    use rstest::rstest;
197
198    use super::ColumnType;
199    use crate::MltError;
200
201    #[rstest]
202    fn column_type_rejects_unassigned(#[values(5, 6, 7, 8, 9, 12, 13, 14, 15, 31)] byte: u8) {
203        let err = ColumnType::from_bytes(&[byte]).unwrap_err();
204        assert!(matches!(err, MltError::ParsingColumnType(b) if b == byte));
205    }
206}