1//! An [SVG Table](https://docs.microsoft.com/en-us/typography/opentype/spec/svg) implementation.
2
3use crate::parser::{FromData, LazyArray16, NumFrom, Offset, Offset32, Stream};
4use crate::GlyphId;
5
6/// An [SVG documents](
7/// https://docs.microsoft.com/en-us/typography/opentype/spec/svg#svg-document-list).
8#[derive(Clone, Copy, Debug)]
9pub struct SvgDocument<'a> {
10 /// The SVG document data.
11 ///
12 /// Can be stored as a string or as a gzip compressed data, aka SVGZ.
13 pub data: &'a [u8],
14 /// The first glyph ID for the range covered by this record.
15 pub start_glyph_id: GlyphId,
16 /// The last glyph ID, *inclusive*, for the range covered by this record.
17 pub end_glyph_id: GlyphId,
18}
19
20impl SvgDocument<'_> {
21 /// Returns the glyphs range.
22 pub fn glyphs_range(&self) -> core::ops::RangeInclusive<GlyphId> {
23 self.start_glyph_id..=self.end_glyph_id
24 }
25}
26
27#[derive(Clone, Copy)]
28struct SvgDocumentRecord {
29 start_glyph_id: GlyphId,
30 end_glyph_id: GlyphId,
31 svg_doc_offset: Option<Offset32>,
32 svg_doc_length: u32,
33}
34
35impl SvgDocumentRecord {
36 fn glyphs_range(&self) -> core::ops::RangeInclusive<GlyphId> {
37 self.start_glyph_id..=self.end_glyph_id
38 }
39}
40
41impl FromData for SvgDocumentRecord {
42 const SIZE: usize = 12;
43
44 #[inline]
45 fn parse(data: &[u8]) -> Option<Self> {
46 let mut s: Stream<'_> = Stream::new(data);
47 Some(SvgDocumentRecord {
48 start_glyph_id: s.read::<GlyphId>()?,
49 end_glyph_id: s.read::<GlyphId>()?,
50 svg_doc_offset: s.read::<Option<Offset32>>()?,
51 svg_doc_length: s.read::<u32>()?,
52 })
53 }
54}
55
56/// A list of [SVG documents](
57/// https://docs.microsoft.com/en-us/typography/opentype/spec/svg#svg-document-list).
58#[derive(Clone, Copy)]
59pub struct SvgDocumentsList<'a> {
60 data: &'a [u8],
61 records: LazyArray16<'a, SvgDocumentRecord>,
62}
63
64impl<'a> SvgDocumentsList<'a> {
65 /// Returns SVG document data at index.
66 ///
67 /// `index` is not a GlyphId. You should use [`find()`](SvgDocumentsList::find) instead.
68 #[inline]
69 pub fn get(&self, index: u16) -> Option<SvgDocument<'a>> {
70 let record = self.records.get(index)?;
71 let offset = record.svg_doc_offset?.to_usize();
72 self.data
73 .get(offset..offset + usize::num_from(record.svg_doc_length))
74 .map(|data| SvgDocument {
75 data,
76 start_glyph_id: record.start_glyph_id,
77 end_glyph_id: record.end_glyph_id,
78 })
79 }
80
81 /// Returns a SVG document data by glyph ID.
82 #[inline]
83 pub fn find(&self, glyph_id: GlyphId) -> Option<SvgDocument<'a>> {
84 let index = self
85 .records
86 .into_iter()
87 .position(|v| v.glyphs_range().contains(&glyph_id))?;
88 self.get(index as u16)
89 }
90
91 /// Returns the number of SVG documents in the list.
92 pub fn len(&self) -> u16 {
93 self.records.len()
94 }
95
96 /// Checks if the list is empty.
97 pub fn is_empty(&self) -> bool {
98 self.records.is_empty()
99 }
100}
101
102impl core::fmt::Debug for SvgDocumentsList<'_> {
103 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
104 write!(f, "SvgDocumentsList {{ ... }}")
105 }
106}
107
108impl<'a> IntoIterator for SvgDocumentsList<'a> {
109 type Item = SvgDocument<'a>;
110 type IntoIter = SvgDocumentsListIter<'a>;
111
112 #[inline]
113 fn into_iter(self) -> Self::IntoIter {
114 SvgDocumentsListIter {
115 list: self,
116 index: 0,
117 }
118 }
119}
120
121/// An iterator over [`SvgDocumentsList`] values.
122#[derive(Clone, Copy)]
123#[allow(missing_debug_implementations)]
124pub struct SvgDocumentsListIter<'a> {
125 list: SvgDocumentsList<'a>,
126 index: u16,
127}
128
129impl<'a> Iterator for SvgDocumentsListIter<'a> {
130 type Item = SvgDocument<'a>;
131
132 #[inline]
133 fn next(&mut self) -> Option<Self::Item> {
134 if self.index < self.list.len() {
135 self.index += 1;
136 self.list.get(self.index - 1)
137 } else {
138 None
139 }
140 }
141
142 #[inline]
143 fn count(self) -> usize {
144 usize::from(self.list.len().saturating_sub(self.index))
145 }
146}
147
148/// An [SVG Table](https://docs.microsoft.com/en-us/typography/opentype/spec/svg).
149#[derive(Clone, Copy, Debug)]
150pub struct Table<'a> {
151 /// A list of SVG documents.
152 pub documents: SvgDocumentsList<'a>,
153}
154
155impl<'a> Table<'a> {
156 /// Parses a table from raw data.
157 pub fn parse(data: &'a [u8]) -> Option<Self> {
158 let mut s: Stream<'_> = Stream::new(data);
159 s.skip::<u16>(); // version
160 let doc_list_offset: Offset32 = s.read::<Option<Offset32>>()??;
161
162 let mut s: Stream<'_> = Stream::new_at(data, offset:doc_list_offset.to_usize())?;
163 let count: u16 = s.read::<u16>()?;
164 let records: LazyArray16<'_, SvgDocumentRecord> = s.read_array16::<SvgDocumentRecord>(count)?;
165
166 Some(Table {
167 documents: SvgDocumentsList {
168 data: &data[doc_list_offset.0 as usize..],
169 records,
170 },
171 })
172 }
173}
174