1 | //! An [SVG Table](https://docs.microsoft.com/en-us/typography/opentype/spec/svg) implementation. |
2 | |
3 | use crate::parser::{FromData, LazyArray16, NumFrom, Offset, Offset32, Stream}; |
4 | use 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)] |
9 | pub 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 | |
20 | impl 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)] |
28 | struct SvgDocumentRecord { |
29 | start_glyph_id: GlyphId, |
30 | end_glyph_id: GlyphId, |
31 | svg_doc_offset: Option<Offset32>, |
32 | svg_doc_length: u32, |
33 | } |
34 | |
35 | impl SvgDocumentRecord { |
36 | fn glyphs_range(&self) -> core::ops::RangeInclusive<GlyphId> { |
37 | self.start_glyph_id..=self.end_glyph_id |
38 | } |
39 | } |
40 | |
41 | impl 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)] |
59 | pub struct SvgDocumentsList<'a> { |
60 | data: &'a [u8], |
61 | records: LazyArray16<'a, SvgDocumentRecord>, |
62 | } |
63 | |
64 | impl<'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 | |
102 | impl core::fmt::Debug for SvgDocumentsList<'_> { |
103 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { |
104 | write!(f, "SvgDocumentsList {{ ... }}" ) |
105 | } |
106 | } |
107 | |
108 | impl<'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)] |
124 | pub struct SvgDocumentsListIter<'a> { |
125 | list: SvgDocumentsList<'a>, |
126 | index: u16, |
127 | } |
128 | |
129 | impl<'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)] |
150 | pub struct Table<'a> { |
151 | /// A list of SVG documents. |
152 | pub documents: SvgDocumentsList<'a>, |
153 | } |
154 | |
155 | impl<'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 | |