Skip to main content

read_fonts/tables/
bitmap.rs

1//! Common bitmap (EBLC/EBDT/CBLC/CBDT) types.
2
3use crate::{array, offset::CheckedOffset};
4
5include!("../../generated/generated_bitmap.rs");
6
7impl BitmapSize {
8    /// Returns the bitmap location information for the given glyph.
9    ///
10    /// The `offset_data` parameter is provided by the `offset_data()` method
11    /// of the parent `Eblc` or `Cblc` table.
12    ///
13    /// The resulting [`BitmapLocation`] value is used by the `data()` method
14    /// in the associated `Ebdt` or `Cbdt` table to extract the bitmap data.
15    pub fn location(
16        &self,
17        offset_data: FontData,
18        glyph_id: GlyphId,
19    ) -> Result<BitmapLocation, ReadError> {
20        if !(self.start_glyph_index()..=self.end_glyph_index()).contains(&glyph_id) {
21            return Err(ReadError::OutOfBounds);
22        }
23        let subtable_list = self.index_subtable_list(offset_data)?;
24        let mut location = BitmapLocation {
25            bit_depth: self.bit_depth,
26            ..BitmapLocation::default()
27        };
28        for record in subtable_list.index_subtable_records() {
29            let subtable = record.index_subtable(subtable_list.offset_data())?;
30            if !(record.first_glyph_index()..=record.last_glyph_index()).contains(&glyph_id) {
31                continue;
32            }
33            // glyph index relative to the first glyph in the subtable
34            let glyph_ix =
35                glyph_id.to_u32() as usize - record.first_glyph_index().to_u32() as usize;
36            match &subtable {
37                IndexSubtable::Format1(st) => {
38                    location.format = st.image_format();
39                    let [first_offset, second_offset] =
40                        array::get_pair(st.sbit_offsets(), glyph_ix)?.map(|o| o.get() as usize);
41                    let base = CheckedOffset::new(st.image_data_offset() as usize);
42                    let start = base.add(first_offset).ok_or_oob()?;
43                    let end = base.add(second_offset).ok_or_oob()?;
44                    location.data_offset = start;
45                    if end < start {
46                        return Err(ReadError::OutOfBounds);
47                    }
48                    location.data_size = end - start;
49                }
50                IndexSubtable::Format2(st) => {
51                    location.format = st.image_format();
52                    let data_size = st.image_size() as usize;
53                    location.data_size = data_size;
54                    location.data_offset = CheckedOffset::new(glyph_ix)
55                        .mul(data_size)
56                        .add(st.image_data_offset() as usize)
57                        .ok_or_oob()?;
58                    location.metrics =
59                        Some(*st.big_metrics().first().ok_or(ReadError::OutOfBounds)?);
60                }
61                IndexSubtable::Format3(st) => {
62                    location.format = st.image_format();
63                    let [first_offset, second_offset] =
64                        array::get_pair(st.sbit_offsets(), glyph_ix)?.map(|o| o.get() as usize);
65                    let base = CheckedOffset::new(st.image_data_offset() as usize);
66                    let start = base.add(first_offset).ok_or_oob()?;
67                    let end = base.add(second_offset).ok_or_oob()?;
68                    location.data_offset = start;
69                    if end < start {
70                        return Err(ReadError::OutOfBounds);
71                    }
72                    location.data_size = end - start;
73                }
74                IndexSubtable::Format4(st) => {
75                    location.format = st.image_format();
76                    let array = st.glyph_array();
77                    let array_ix = match array
78                        .binary_search_by(|x| x.glyph_id().to_u32().cmp(&glyph_id.to_u32()))
79                    {
80                        Ok(ix) => ix,
81                        _ => return Err(ReadError::InvalidCollectionIndex(glyph_id.to_u32())),
82                    };
83                    let [first_offset, second_offset] =
84                        array::get_pair(array, array_ix)?.map(|p| p.sbit_offset() as usize);
85                    let base = CheckedOffset::new(st.image_data_offset() as usize);
86                    let start = base.add(first_offset).ok_or_oob()?;
87                    let end = base.add(second_offset).ok_or_oob()?;
88                    location.data_offset = start;
89                    if end < start {
90                        return Err(ReadError::OutOfBounds);
91                    }
92                    location.data_size = end - start;
93                }
94                IndexSubtable::Format5(st) => {
95                    location.format = st.image_format();
96                    let array = st.glyph_array();
97                    let array_ix = match array
98                        .binary_search_by(|gid| gid.get().to_u32().cmp(&glyph_id.to_u32()))
99                    {
100                        Ok(ix) => ix,
101                        _ => return Err(ReadError::InvalidCollectionIndex(glyph_id.to_u32())),
102                    };
103                    let data_size = st.image_size() as usize;
104                    location.data_size = data_size;
105                    location.data_offset = CheckedOffset::new(array_ix)
106                        .mul(data_size)
107                        .add(st.image_data_offset() as usize)
108                        .ok_or_oob()?;
109                    location.metrics =
110                        Some(*st.big_metrics().first().ok_or(ReadError::OutOfBounds)?);
111                }
112            }
113            return Ok(location);
114        }
115        Err(ReadError::OutOfBounds)
116    }
117
118    /// Returns the [IndexSubtableList] associated with this size.
119    ///
120    /// The `offset_data` parameter is provided by the `offset_data()` method
121    /// of the parent `Eblc` or `Cblc` table.
122    pub fn index_subtable_list<'a>(
123        &self,
124        offset_data: FontData<'a>,
125    ) -> Result<IndexSubtableList<'a>, ReadError> {
126        let start = self.index_subtable_list_offset() as usize;
127        // FreeType ignores the declared size and bounds reads by the end of the
128        // parent table. Some fonts, such as ProggyClean, rely on this behavior
129        // because they set indexTablesSize to the size of the index subtable
130        // record array alone, excluding the referenced subtables.
131        //
132        // See `tt_sbit_decoder_init` and `tt_sbit_decoder_load_image` in FreeType.
133        // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/VER-2-13-3/src/sfnt/ttsbit.c#L1241>
134        let data = offset_data.split_off(start).ok_or(ReadError::OutOfBounds)?;
135        IndexSubtableList::read(data, self.number_of_index_subtables())
136    }
137}
138
139#[derive(Clone, Default)]
140pub struct BitmapLocation {
141    /// Format of EBDT/CBDT image data.
142    pub format: u16,
143    /// Offset in bytes from the start of the EBDT/CBDT table.
144    pub data_offset: usize,
145    /// Size of the image data in bytes.
146    pub data_size: usize,
147    /// Bit depth from the associated size. Required for computing image data
148    /// size when unspecified.
149    pub bit_depth: u8,
150    /// Full metrics, if present in the EBLC/CBLC table.
151    pub metrics: Option<BigGlyphMetrics>,
152}
153
154impl BitmapLocation {
155    /// Returns true if the location references an empty bitmap glyph such as
156    /// a space.
157    pub fn is_empty(&self) -> bool {
158        self.data_size == 0
159    }
160}
161
162#[derive(Copy, Clone, PartialEq, Eq, Debug)]
163pub enum BitmapDataFormat {
164    /// The full bitmap is tightly packed according to the bit depth.
165    BitAligned,
166    /// Each row of the data is aligned to a byte boundary.
167    ByteAligned,
168    Png,
169}
170
171#[derive(Clone)]
172pub enum BitmapMetrics {
173    Small(SmallGlyphMetrics),
174    Big(BigGlyphMetrics),
175}
176
177#[derive(Clone)]
178pub struct BitmapData<'a> {
179    pub metrics: BitmapMetrics,
180    pub content: BitmapContent<'a>,
181}
182
183#[derive(Clone)]
184pub enum BitmapContent<'a> {
185    Data(BitmapDataFormat, &'a [u8]),
186    Composite(&'a [BdtComponent]),
187}
188
189pub(crate) fn bitmap_data<'a>(
190    offset_data: FontData<'a>,
191    location: &BitmapLocation,
192    is_color: bool,
193) -> Result<BitmapData<'a>, ReadError> {
194    let start = location.data_offset;
195    let end = CheckedOffset::new(start)
196        .add(location.data_size)
197        .ok_or_oob()?;
198    let mut image_data = offset_data
199        .slice(start..end)
200        .ok_or(ReadError::OutOfBounds)?
201        .cursor();
202    match location.format {
203        // Small metrics, byte-aligned data
204        // <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-1-small-metrics-byte-aligned-data>
205        1 => {
206            let metrics = read_small_metrics(&mut image_data)?;
207            // The data for each row is padded to a byte boundary
208            let pitch = (metrics.width as usize * location.bit_depth as usize).div_ceil(8);
209            let height = metrics.height as usize;
210            let data = image_data.read_array::<u8>(pitch * height)?;
211            Ok(BitmapData {
212                metrics: BitmapMetrics::Small(metrics),
213                content: BitmapContent::Data(BitmapDataFormat::ByteAligned, data),
214            })
215        }
216        // Small metrics, bit-aligned data
217        // <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-2-small-metrics-bit-aligned-data>
218        2 => {
219            let metrics = read_small_metrics(&mut image_data)?;
220            let width = metrics.width as usize * location.bit_depth as usize;
221            let height = metrics.height as usize;
222            // The data is tightly packed
223            let data = image_data.read_array::<u8>((width * height).div_ceil(8))?;
224            Ok(BitmapData {
225                metrics: BitmapMetrics::Small(metrics),
226                content: BitmapContent::Data(BitmapDataFormat::BitAligned, data),
227            })
228        }
229        // Format 3 is obsolete
230        // <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-3-obsolete>
231        // Format 4 is not supported
232        // <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-4-not-supported-metrics-in-eblc-compressed-data>
233        // ---
234        // Metrics in EBLC/CBLC, bit-aligned image data only
235        // <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-5-metrics-in-eblc-bit-aligned-image-data-only>
236        5 => {
237            let metrics = location.metrics.ok_or(ReadError::MalformedData(
238                "expected metrics from location table",
239            ))?;
240            let width = metrics.width as usize * location.bit_depth as usize;
241            let height = metrics.height as usize;
242            // The data is tightly packed
243            let data = image_data.read_array::<u8>((width * height).div_ceil(8))?;
244            Ok(BitmapData {
245                metrics: BitmapMetrics::Big(metrics),
246                content: BitmapContent::Data(BitmapDataFormat::BitAligned, data),
247            })
248        }
249        // Big metrics, byte-aligned data
250        // <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-6-big-metrics-byte-aligned-data>
251        6 => {
252            let metrics = read_big_metrics(&mut image_data)?;
253            // The data for each row is padded to a byte boundary
254            let pitch = (metrics.width as usize * location.bit_depth as usize).div_ceil(8);
255            let height = metrics.height as usize;
256            let data = image_data.read_array::<u8>(pitch * height)?;
257            Ok(BitmapData {
258                metrics: BitmapMetrics::Big(metrics),
259                content: BitmapContent::Data(BitmapDataFormat::ByteAligned, data),
260            })
261        }
262        // Big metrics, bit-aligned data
263        // <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format7-big-metrics-bit-aligned-data>
264        7 => {
265            let metrics = read_big_metrics(&mut image_data)?;
266            let width = metrics.width as usize * location.bit_depth as usize;
267            let height = metrics.height as usize;
268            // The data is tightly packed
269            let data = image_data.read_array::<u8>((width * height).div_ceil(8))?;
270            Ok(BitmapData {
271                metrics: BitmapMetrics::Big(metrics),
272                content: BitmapContent::Data(BitmapDataFormat::BitAligned, data),
273            })
274        }
275        // Small metrics, component data
276        // <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-8-small-metrics-component-data>
277        8 => {
278            let metrics = read_small_metrics(&mut image_data)?;
279            let _pad = image_data.read::<u8>()?;
280            let count = image_data.read::<u16>()? as usize;
281            let components = image_data.read_array::<BdtComponent>(count)?;
282            Ok(BitmapData {
283                metrics: BitmapMetrics::Small(metrics),
284                content: BitmapContent::Composite(components),
285            })
286        }
287        // Big metrics, component data
288        // <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-9-big-metrics-component-data>
289        9 => {
290            let metrics = read_big_metrics(&mut image_data)?;
291            let count = image_data.read::<u16>()? as usize;
292            let components = image_data.read_array::<BdtComponent>(count)?;
293            Ok(BitmapData {
294                metrics: BitmapMetrics::Big(metrics),
295                content: BitmapContent::Composite(components),
296            })
297        }
298        // Small metrics, PNG image data
299        // <https://learn.microsoft.com/en-us/typography/opentype/spec/cbdt#format-17-small-metrics-png-image-data>
300        17 if is_color => {
301            let metrics = read_small_metrics(&mut image_data)?;
302            let data_len = image_data.read::<u32>()? as usize;
303            let data = image_data.read_array::<u8>(data_len)?;
304            Ok(BitmapData {
305                metrics: BitmapMetrics::Small(metrics),
306                content: BitmapContent::Data(BitmapDataFormat::Png, data),
307            })
308        }
309        // Big metrics, PNG image data
310        // <https://learn.microsoft.com/en-us/typography/opentype/spec/cbdt#format-18-big-metrics-png-image-data>
311        18 if is_color => {
312            let metrics = read_big_metrics(&mut image_data)?;
313            let data_len = image_data.read::<u32>()? as usize;
314            let data = image_data.read_array::<u8>(data_len)?;
315            Ok(BitmapData {
316                metrics: BitmapMetrics::Big(metrics),
317                content: BitmapContent::Data(BitmapDataFormat::Png, data),
318            })
319        }
320        // Metrics in CBLC table, PNG image data
321        // <https://learn.microsoft.com/en-us/typography/opentype/spec/cbdt#format-19-metrics-in-cblc-table-png-image-data>
322        19 if is_color => {
323            let metrics = location.metrics.ok_or(ReadError::MalformedData(
324                "expected metrics from location table",
325            ))?;
326            let data_len = image_data.read::<u32>()? as usize;
327            let data = image_data.read_array::<u8>(data_len)?;
328            Ok(BitmapData {
329                metrics: BitmapMetrics::Big(metrics),
330                content: BitmapContent::Data(BitmapDataFormat::Png, data),
331            })
332        }
333        _ => Err(ReadError::MalformedData("unexpected bitmap data format")),
334    }
335}
336
337fn read_small_metrics(cursor: &mut Cursor) -> Result<SmallGlyphMetrics, ReadError> {
338    Ok(cursor.read_array::<SmallGlyphMetrics>(1)?[0])
339}
340
341fn read_big_metrics(cursor: &mut Cursor) -> Result<BigGlyphMetrics, ReadError> {
342    Ok(cursor.read_array::<BigGlyphMetrics>(1)?[0])
343}
344
345#[cfg(feature = "experimental_traverse")]
346impl SbitLineMetrics {
347    pub(crate) fn traversal_type<'a>(&self, data: FontData<'a>) -> FieldType<'a> {
348        FieldType::Record(self.traverse(data))
349    }
350}
351
352/// [IndexSubtables](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#indexsubtables) format type.
353#[derive(Clone)]
354pub enum IndexSubtable<'a> {
355    Format1(IndexSubtable1<'a>),
356    Format2(IndexSubtable2<'a>),
357    Format3(IndexSubtable3<'a>),
358    Format4(IndexSubtable4<'a>),
359    Format5(IndexSubtable5<'a>),
360}
361
362impl<'a> IndexSubtable<'a> {
363    ///Return the `FontData` used to resolve offsets for this table.
364    pub fn offset_data(&self) -> FontData<'a> {
365        match self {
366            Self::Format1(item) => item.offset_data(),
367            Self::Format2(item) => item.offset_data(),
368            Self::Format3(item) => item.offset_data(),
369            Self::Format4(item) => item.offset_data(),
370            Self::Format5(item) => item.offset_data(),
371        }
372    }
373
374    /// Format of this IndexSubTable.
375    pub fn index_format(&self) -> u16 {
376        match self {
377            Self::Format1(item) => item.index_format(),
378            Self::Format2(item) => item.index_format(),
379            Self::Format3(item) => item.index_format(),
380            Self::Format4(item) => item.index_format(),
381            Self::Format5(item) => item.index_format(),
382        }
383    }
384
385    /// Format of EBDT image data.
386    pub fn image_format(&self) -> u16 {
387        match self {
388            Self::Format1(item) => item.image_format(),
389            Self::Format2(item) => item.image_format(),
390            Self::Format3(item) => item.image_format(),
391            Self::Format4(item) => item.image_format(),
392            Self::Format5(item) => item.image_format(),
393        }
394    }
395
396    /// Offset to image data in EBDT table.
397    pub fn image_data_offset(&self) -> u32 {
398        match self {
399            Self::Format1(item) => item.image_data_offset(),
400            Self::Format2(item) => item.image_data_offset(),
401            Self::Format3(item) => item.image_data_offset(),
402            Self::Format4(item) => item.image_data_offset(),
403            Self::Format5(item) => item.image_data_offset(),
404        }
405    }
406}
407
408impl ReadArgs for IndexSubtable<'_> {
409    type Args = (GlyphId16, GlyphId16);
410}
411impl<'a> FontRead<'a> for IndexSubtable<'a> {
412    fn read_with_args(data: FontData<'a>, args: Self::Args) -> Result<Self, ReadError> {
413        let format: u16 = data.read_at(0usize)?;
414        match format {
415            IndexSubtable1::FORMAT => FontRead::read_with_args(data, args).map(Self::Format1),
416            IndexSubtable2::FORMAT => FontRead::read(data).map(Self::Format2),
417            IndexSubtable3::FORMAT => FontRead::read_with_args(data, args).map(Self::Format3),
418            IndexSubtable4::FORMAT => FontRead::read(data).map(Self::Format4),
419            IndexSubtable5::FORMAT => FontRead::read(data).map(Self::Format5),
420            other => Err(ReadError::InvalidFormat(other.into())),
421        }
422    }
423}
424
425impl<'a> MinByteRange<'a> for IndexSubtable<'a> {
426    fn min_byte_range(&self) -> Range<usize> {
427        match self {
428            Self::Format1(item) => item.min_byte_range(),
429            Self::Format2(item) => item.min_byte_range(),
430            Self::Format3(item) => item.min_byte_range(),
431            Self::Format4(item) => item.min_byte_range(),
432            Self::Format5(item) => item.min_byte_range(),
433        }
434    }
435
436    fn min_table_bytes(&self) -> &'a [u8] {
437        match self {
438            Self::Format1(item) => item.min_table_bytes(),
439            Self::Format2(item) => item.min_table_bytes(),
440            Self::Format3(item) => item.min_table_bytes(),
441            Self::Format4(item) => item.min_table_bytes(),
442            Self::Format5(item) => item.min_table_bytes(),
443        }
444    }
445}
446
447#[cfg(feature = "experimental_traverse")]
448impl<'a> IndexSubtable<'a> {
449    fn dyn_inner<'b>(&'b self) -> &'b dyn SomeTable<'a> {
450        match self {
451            Self::Format1(table) => table,
452            Self::Format2(table) => table,
453            Self::Format3(table) => table,
454            Self::Format4(table) => table,
455            Self::Format5(table) => table,
456        }
457    }
458}
459
460#[cfg(feature = "experimental_traverse")]
461impl std::fmt::Debug for IndexSubtable<'_> {
462    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
463        self.dyn_inner().fmt(f)
464    }
465}
466
467#[cfg(feature = "experimental_traverse")]
468impl<'a> SomeTable<'a> for IndexSubtable<'a> {
469    fn type_name(&self) -> &str {
470        self.dyn_inner().type_name()
471    }
472    fn get_field(&self, idx: usize) -> Option<Field<'a>> {
473        self.dyn_inner().get_field(idx)
474    }
475}
476
477#[cfg(test)]
478mod tests {
479    use super::*;
480    use crate::{types::GlyphId16, FontData};
481    use font_test_data::bebuffer::BeBuffer;
482
483    fn bitmap_size_location(data: &[u8], glyph_id: GlyphId) -> Result<BitmapLocation, ReadError> {
484        let data = FontData::new(data);
485        let size = data.read_ref_at::<BitmapSize>(0).unwrap();
486        size.location(data, glyph_id)
487    }
488
489    fn bitmap_size_with_subtable(subtable: &[u8]) -> BeBuffer {
490        BeBuffer::new()
491            .push(BitmapSize::RAW_BYTE_LEN as u32)
492            .push((IndexSubtableRecord::RAW_BYTE_LEN + subtable.len()) as u32)
493            .push(1u32)
494            .push(0u32)
495            .extend([0u8; SbitLineMetrics::RAW_BYTE_LEN])
496            .extend([0u8; SbitLineMetrics::RAW_BYTE_LEN])
497            .push(GlyphId16::new(0))
498            .push(GlyphId16::new(0))
499            .push(0u8)
500            .push(0u8)
501            .push(1u8)
502            .push(0u8)
503            .push(GlyphId16::new(0))
504            .push(GlyphId16::new(0))
505            .push(IndexSubtableRecord::RAW_BYTE_LEN as u32)
506            .extend(subtable.iter().copied())
507    }
508
509    #[test]
510    fn short_index_tables_size_is_ignored() {
511        // Some fonts (e.g. ProggyClean) set indexTablesSize to the size of
512        // the index subtable array alone, excluding the subtables it points
513        // to. Ensure we still resolve the subtables.
514        let subtable = BeBuffer::new()
515            .push(2u16) // index format
516            .push(5u16) // image format
517            .push(0u32) // image data offset
518            .push(4u32) // image size
519            .extend([0u8; 8]); // big metrics
520        let subtable = subtable.data();
521        let data = BeBuffer::new()
522            .push(BitmapSize::RAW_BYTE_LEN as u32)
523            // indexTablesSize covering only the record array
524            .push(IndexSubtableRecord::RAW_BYTE_LEN as u32)
525            .push(1u32)
526            .push(0u32)
527            .extend([0u8; SbitLineMetrics::RAW_BYTE_LEN])
528            .extend([0u8; SbitLineMetrics::RAW_BYTE_LEN])
529            .push(GlyphId16::new(0))
530            .push(GlyphId16::new(1))
531            .push(0u8)
532            .push(0u8)
533            .push(1u8)
534            .push(0u8)
535            .push(GlyphId16::new(0))
536            .push(GlyphId16::new(1))
537            .push(IndexSubtableRecord::RAW_BYTE_LEN as u32)
538            .extend(subtable.iter().copied());
539        let location = bitmap_size_location(data.data(), GlyphId::new(1)).unwrap();
540        assert_eq!(location.data_offset, 4);
541        assert_eq!(location.data_size, 4);
542    }
543
544    #[test]
545    fn format_2_truncated_metrics_returns_error_instead_of_panicking() {
546        let data =
547            bitmap_size_with_subtable(&BeBuffer::new().push(2u16).push(5u16).push(0u32).push(1u32));
548        // Just don't panic!
549        let result = bitmap_size_location(data.data(), GlyphId::new(0));
550        assert!(matches!(result, Err(ReadError::OutOfBounds)));
551    }
552
553    #[test]
554    fn format_4_sbit_offsets_are_relative_to_image_data_offset() {
555        let data = bitmap_size_with_subtable(
556            &BeBuffer::new()
557                .push(4u16) // index format
558                .push(17u16) // image format
559                .push(1000u32) // image data offset
560                .push(1u32) // num glyphs
561                .push(GlyphId16::new(0))
562                .push(20u16) // sbit offset
563                .push(GlyphId16::new(1))
564                .push(30u16), // sbit offset of the following glyph
565        );
566        let location = bitmap_size_location(data.data(), GlyphId::new(0)).unwrap();
567        assert_eq!(location.data_offset, 1020);
568        assert_eq!(location.data_size, 10);
569    }
570
571    #[test]
572    fn format_5_truncated_metrics_returns_error_instead_of_panicking() {
573        let data = bitmap_size_with_subtable(
574            &BeBuffer::new()
575                .push(5u16)
576                .push(5u16)
577                .push(0u32)
578                .push(1u32)
579                .push(0u32),
580        );
581        // Just don't panic!
582        let result = bitmap_size_location(data.data(), GlyphId::new(0));
583        assert!(result.is_err());
584    }
585}