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