allsorts_subset_browser/tables/
svg.rs1#![deny(missing_docs)]
2
3use std::convert::TryFrom;
8use std::io::Read;
9
10use flate2::read::GzDecoder;
11
12use crate::binary::read::{
13 ReadArray, ReadBinary, ReadBinaryDep, ReadCtxt, ReadFixedSizeDep, ReadScope,
14};
15use crate::bitmap::{
16 Bitmap, BitmapGlyph, EncapsulatedBitmap, EncapsulatedFormat, Metrics, OriginOffset,
17};
18use crate::error::ParseError;
19use crate::size;
20
21const GZIP_HEADER: &[u8] = &[0x1F, 0x8B, 0x08];
22
23pub struct SvgTable<'a> {
25 pub version: u16,
27 pub document_records: ReadArray<'a, SVGDocumentRecord<'a>>,
38}
39
40pub struct SVGDocumentRecord<'a> {
42 pub start_glyph_id: u16,
44 pub end_glyph_id: u16,
48 pub svg_document: &'a [u8],
54}
55
56impl<'a> SvgTable<'a> {
57 pub fn lookup_glyph(&self, glyph_id: u16) -> Result<Option<SVGDocumentRecord<'a>>, ParseError> {
59 for record in self.document_records.iter_res() {
60 let record = record?;
61 if glyph_id >= record.start_glyph_id && glyph_id <= record.end_glyph_id {
62 return Ok(Some(record));
63 }
64 }
65 Ok(None)
66 }
67}
68
69impl<'b> ReadBinary for SvgTable<'b> {
70 type HostType<'a> = SvgTable<'a>;
71
72 fn read<'a>(ctxt: &mut ReadCtxt<'a>) -> Result<Self::HostType<'a>, ParseError> {
73 let scope = ctxt.scope();
74 let version = ctxt.read_u16be()?;
75 ctxt.check(version == 0)?;
76 let document_records_offset = usize::try_from(ctxt.read_u32be()?)?;
77
78 let records_scope = scope.offset(document_records_offset);
79 let mut records_ctxt = records_scope.ctxt();
80 let num_records = records_ctxt.read_u16be().map(usize::from)?;
81 let document_records = records_ctxt.read_array_dep(num_records, records_scope)?;
82
83 Ok(SvgTable {
84 version,
85 document_records,
86 })
87 }
88}
89
90impl<'b> ReadBinaryDep for SVGDocumentRecord<'b> {
91 type Args<'a> = ReadScope<'a>;
92 type HostType<'a> = SVGDocumentRecord<'a>;
93
94 fn read_dep<'a>(
95 ctxt: &mut ReadCtxt<'a>,
96 scope: ReadScope<'a>,
97 ) -> Result<Self::HostType<'a>, ParseError> {
98 let start_glyph_id = ctxt.read_u16be()?;
99 let end_glyph_id = ctxt.read_u16be()?;
100 let svg_doc_offset = usize::try_from(ctxt.read_u32be()?)?;
101 let svg_doc_length = usize::try_from(ctxt.read_u32be()?)?;
102 let svg_data = scope.offset_length(svg_doc_offset, svg_doc_length)?;
103 let svg_document = svg_data.data();
104
105 Ok(SVGDocumentRecord {
106 start_glyph_id,
107 end_glyph_id,
108 svg_document,
109 })
110 }
111}
112impl<'a> ReadFixedSizeDep for SVGDocumentRecord<'a> {
113 fn size(_: Self::Args<'_>) -> usize {
114 (2 * size::U16) + (2 * size::U32)
120 }
121}
122
123impl<'a> TryFrom<&SVGDocumentRecord<'a>> for BitmapGlyph {
124 type Error = ParseError;
125
126 fn try_from(svg_record: &SVGDocumentRecord<'a>) -> Result<Self, ParseError> {
127 let data = if svg_record.svg_document.starts_with(GZIP_HEADER) {
131 let mut gz = GzDecoder::new(svg_record.svg_document);
132 let mut uncompressed = Vec::with_capacity(svg_record.svg_document.len());
133 gz.read_to_end(&mut uncompressed)
134 .map_err(|_err| ParseError::CompressionError)?;
135 uncompressed.into_boxed_slice()
136 } else {
137 Box::from(svg_record.svg_document)
138 };
139
140 let encapsulated = EncapsulatedBitmap {
141 format: EncapsulatedFormat::Svg,
142 data,
143 };
144 Ok(BitmapGlyph {
145 bitmap: Bitmap::Encapsulated(encapsulated),
146 metrics: Metrics::HmtxVmtx(OriginOffset { x: 0, y: 0 }),
147 ppem_x: None,
148 ppem_y: None,
149 })
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156 use crate::font_data::FontData;
157 use crate::tables::FontTableProvider;
158 use crate::tag;
159 use crate::tests::read_fixture;
160
161 #[test]
162 fn test_read_svg() {
163 let buffer = read_fixture("tests/fonts/opentype/TwitterColorEmoji-SVGinOT.ttf");
164 let scope = ReadScope::new(&buffer);
165 let font_file = scope
166 .read::<FontData<'_>>()
167 .expect("unable to parse font file");
168 let table_provider = font_file
169 .table_provider(0)
170 .expect("unable to create font provider");
171 let svg_data = table_provider
172 .read_table_data(tag::SVG)
173 .expect("unable to read SVG table data");
174 let svg = ReadScope::new(&svg_data).read::<SvgTable<'_>>().unwrap();
175
176 let records = svg
177 .document_records
178 .iter_res()
179 .into_iter()
180 .collect::<Result<Vec<_>, _>>()
181 .unwrap();
182 assert_eq!(records.len(), 3075);
183
184 let record = &records[0];
185 assert_eq!(record.start_glyph_id, 5);
186 assert_eq!(record.end_glyph_id, 5);
187 assert_eq!(record.svg_document.len(), 751);
188 let doc = std::str::from_utf8(record.svg_document).unwrap();
189 assert_eq!(&doc[0..43], "<?xml version='1.0' encoding='UTF-8'?>\n<svg");
190 }
191
192 #[test]
193 fn test_read_gzipped_svg() {
194 let buffer = read_fixture("tests/fonts/svg/gzipped.ttf");
195 let scope = ReadScope::new(&buffer);
196 let font_file = scope
197 .read::<FontData<'_>>()
198 .expect("unable to parse font file");
199 let table_provider = font_file
200 .table_provider(0)
201 .expect("unable to create font provider");
202 let svg_data = table_provider
203 .read_table_data(tag::SVG)
204 .expect("unable to read SVG table data");
205 let svg = ReadScope::new(&svg_data).read::<SvgTable<'_>>().unwrap();
206 let record = svg
207 .document_records
208 .iter_res()
209 .into_iter()
210 .nth(0)
211 .unwrap()
212 .unwrap();
213
214 assert!(record.svg_document.starts_with(GZIP_HEADER));
216 match BitmapGlyph::try_from(&record) {
218 Ok(BitmapGlyph {
219 bitmap: Bitmap::Encapsulated(EncapsulatedBitmap { data, .. }),
220 ..
221 }) => {
222 let doc = std::str::from_utf8(&data).unwrap();
223 assert_eq!(&doc[0..42], r#"<?xml version="1.0" encoding="UTF-8"?><svg"#);
224 }
225 _ => panic!("did not get expected result"),
226 }
227 }
228}