1 | /**************************************************************************** |
2 | * |
3 | * ttsvg.c |
4 | * |
5 | * OpenType SVG Color (specification). |
6 | * |
7 | * Copyright (C) 2022-2023 by |
8 | * David Turner, Robert Wilhelm, Werner Lemberg, and Moazin Khatti. |
9 | * |
10 | * This file is part of the FreeType project, and may only be used, |
11 | * modified, and distributed under the terms of the FreeType project |
12 | * license, LICENSE.TXT. By continuing to use, modify, or distribute |
13 | * this file you indicate that you have read the license and |
14 | * understand and accept it fully. |
15 | * |
16 | */ |
17 | |
18 | |
19 | /************************************************************************** |
20 | * |
21 | * 'SVG' table specification: |
22 | * |
23 | * https://docs.microsoft.com/en-us/typography/opentype/spec/svg |
24 | * |
25 | */ |
26 | |
27 | #include <ft2build.h> |
28 | #include <freetype/internal/ftstream.h> |
29 | #include <freetype/internal/ftobjs.h> |
30 | #include <freetype/internal/ftdebug.h> |
31 | #include <freetype/tttags.h> |
32 | #include <freetype/ftgzip.h> |
33 | #include <freetype/otsvg.h> |
34 | |
35 | |
36 | #ifdef FT_CONFIG_OPTION_SVG |
37 | |
38 | #include "ttsvg.h" |
39 | |
40 | |
41 | /* NOTE: These table sizes are given by the specification. */ |
42 | #define (10U) |
43 | #define SVG_DOCUMENT_RECORD_SIZE (12U) |
44 | #define SVG_DOCUMENT_LIST_MINIMUM_SIZE (2U + SVG_DOCUMENT_RECORD_SIZE) |
45 | #define SVG_MINIMUM_SIZE (SVG_TABLE_HEADER_SIZE + \ |
46 | SVG_DOCUMENT_LIST_MINIMUM_SIZE) |
47 | |
48 | |
49 | typedef struct Svg_ |
50 | { |
51 | FT_UShort version; /* table version (starting at 0) */ |
52 | FT_UShort num_entries; /* number of SVG document records */ |
53 | |
54 | FT_Byte* svg_doc_list; /* pointer to the start of SVG Document List */ |
55 | |
56 | void* table; /* memory that backs up SVG */ |
57 | FT_ULong table_size; |
58 | |
59 | } Svg; |
60 | |
61 | |
62 | /************************************************************************** |
63 | * |
64 | * The macro FT_COMPONENT is used in trace mode. It is an implicit |
65 | * parameter of the FT_TRACE() and FT_ERROR() macros, usued to print/log |
66 | * messages during execution. |
67 | */ |
68 | #undef FT_COMPONENT |
69 | #define FT_COMPONENT ttsvg |
70 | |
71 | |
72 | FT_LOCAL_DEF( FT_Error ) |
73 | tt_face_load_svg( TT_Face face, |
74 | FT_Stream stream ) |
75 | { |
76 | FT_Error error; |
77 | FT_Memory memory = face->root.memory; |
78 | |
79 | FT_ULong table_size; |
80 | FT_Byte* table = NULL; |
81 | FT_Byte* p = NULL; |
82 | Svg* svg = NULL; |
83 | FT_ULong offsetToSVGDocumentList; |
84 | |
85 | |
86 | error = face->goto_table( face, TTAG_SVG, stream, &table_size ); |
87 | if ( error ) |
88 | goto NoSVG; |
89 | |
90 | if ( table_size < SVG_MINIMUM_SIZE ) |
91 | goto InvalidTable; |
92 | |
93 | if ( FT_FRAME_EXTRACT( table_size, table ) ) |
94 | goto NoSVG; |
95 | |
96 | /* Allocate memory for the SVG object */ |
97 | if ( FT_NEW( svg ) ) |
98 | goto NoSVG; |
99 | |
100 | p = table; |
101 | svg->version = FT_NEXT_USHORT( p ); |
102 | offsetToSVGDocumentList = FT_NEXT_ULONG( p ); |
103 | |
104 | if ( offsetToSVGDocumentList < SVG_TABLE_HEADER_SIZE || |
105 | offsetToSVGDocumentList > table_size - |
106 | SVG_DOCUMENT_LIST_MINIMUM_SIZE ) |
107 | goto InvalidTable; |
108 | |
109 | svg->svg_doc_list = (FT_Byte*)( table + offsetToSVGDocumentList ); |
110 | |
111 | p = svg->svg_doc_list; |
112 | svg->num_entries = FT_NEXT_USHORT( p ); |
113 | |
114 | FT_TRACE3(( "version: %d\n" , svg->version )); |
115 | FT_TRACE3(( "number of entries: %d\n" , svg->num_entries )); |
116 | |
117 | if ( offsetToSVGDocumentList + 2U + |
118 | svg->num_entries * SVG_DOCUMENT_RECORD_SIZE > table_size ) |
119 | goto InvalidTable; |
120 | |
121 | svg->table = table; |
122 | svg->table_size = table_size; |
123 | |
124 | face->svg = svg; |
125 | face->root.face_flags |= FT_FACE_FLAG_SVG; |
126 | |
127 | return FT_Err_Ok; |
128 | |
129 | InvalidTable: |
130 | error = FT_THROW( Invalid_Table ); |
131 | |
132 | NoSVG: |
133 | FT_FRAME_RELEASE( table ); |
134 | FT_FREE( svg ); |
135 | face->svg = NULL; |
136 | |
137 | return error; |
138 | } |
139 | |
140 | |
141 | FT_LOCAL_DEF( void ) |
142 | tt_face_free_svg( TT_Face face ) |
143 | { |
144 | FT_Memory memory = face->root.memory; |
145 | FT_Stream stream = face->root.stream; |
146 | |
147 | Svg* svg = (Svg*)face->svg; |
148 | |
149 | |
150 | if ( svg ) |
151 | { |
152 | FT_FRAME_RELEASE( svg->table ); |
153 | FT_FREE( svg ); |
154 | } |
155 | } |
156 | |
157 | |
158 | typedef struct Svg_doc_ |
159 | { |
160 | FT_UShort start_glyph_id; |
161 | FT_UShort end_glyph_id; |
162 | |
163 | FT_ULong offset; |
164 | FT_ULong length; |
165 | |
166 | } Svg_doc; |
167 | |
168 | |
169 | static Svg_doc |
170 | ( FT_Byte* stream ) |
171 | { |
172 | Svg_doc doc; |
173 | |
174 | |
175 | doc.start_glyph_id = FT_NEXT_USHORT( stream ); |
176 | doc.end_glyph_id = FT_NEXT_USHORT( stream ); |
177 | |
178 | doc.offset = FT_NEXT_ULONG( stream ); |
179 | doc.length = FT_NEXT_ULONG( stream ); |
180 | |
181 | return doc; |
182 | } |
183 | |
184 | |
185 | static FT_Int |
186 | compare_svg_doc( Svg_doc doc, |
187 | FT_UInt glyph_index ) |
188 | { |
189 | if ( glyph_index < doc.start_glyph_id ) |
190 | return -1; |
191 | else if ( glyph_index > doc.end_glyph_id ) |
192 | return 1; |
193 | else |
194 | return 0; |
195 | } |
196 | |
197 | |
198 | static FT_Error |
199 | find_doc( FT_Byte* document_records, |
200 | FT_UShort num_entries, |
201 | FT_UInt glyph_index, |
202 | FT_ULong *doc_offset, |
203 | FT_ULong *doc_length, |
204 | FT_UShort *start_glyph, |
205 | FT_UShort *end_glyph ) |
206 | { |
207 | FT_Error error; |
208 | |
209 | Svg_doc start_doc; |
210 | Svg_doc mid_doc = { 0, 0, 0, 0 }; /* pacify compiler */ |
211 | Svg_doc end_doc; |
212 | |
213 | FT_Bool found = FALSE; |
214 | FT_UInt i = 0; |
215 | |
216 | FT_UInt start_index = 0; |
217 | FT_UInt end_index = num_entries - 1; |
218 | FT_Int comp_res; |
219 | |
220 | |
221 | /* search algorithm */ |
222 | if ( num_entries == 0 ) |
223 | { |
224 | error = FT_THROW( Invalid_Table ); |
225 | return error; |
226 | } |
227 | |
228 | start_doc = extract_svg_doc( document_records + start_index * 12 ); |
229 | end_doc = extract_svg_doc( document_records + end_index * 12 ); |
230 | |
231 | if ( ( compare_svg_doc( start_doc, glyph_index ) == -1 ) || |
232 | ( compare_svg_doc( end_doc, glyph_index ) == 1 ) ) |
233 | { |
234 | error = FT_THROW( Invalid_Glyph_Index ); |
235 | return error; |
236 | } |
237 | |
238 | while ( start_index <= end_index ) |
239 | { |
240 | i = ( start_index + end_index ) / 2; |
241 | mid_doc = extract_svg_doc( document_records + i * 12 ); |
242 | comp_res = compare_svg_doc( mid_doc, glyph_index ); |
243 | |
244 | if ( comp_res == 1 ) |
245 | { |
246 | start_index = i + 1; |
247 | start_doc = extract_svg_doc( document_records + start_index * 4 ); |
248 | } |
249 | else if ( comp_res == -1 ) |
250 | { |
251 | end_index = i - 1; |
252 | end_doc = extract_svg_doc( document_records + end_index * 4 ); |
253 | } |
254 | else |
255 | { |
256 | found = TRUE; |
257 | break; |
258 | } |
259 | } |
260 | /* search algorithm end */ |
261 | |
262 | if ( found != TRUE ) |
263 | { |
264 | FT_TRACE5(( "SVG glyph not found\n" )); |
265 | error = FT_THROW( Invalid_Glyph_Index ); |
266 | } |
267 | else |
268 | { |
269 | *doc_offset = mid_doc.offset; |
270 | *doc_length = mid_doc.length; |
271 | |
272 | *start_glyph = mid_doc.start_glyph_id; |
273 | *end_glyph = mid_doc.end_glyph_id; |
274 | |
275 | error = FT_Err_Ok; |
276 | } |
277 | |
278 | return error; |
279 | } |
280 | |
281 | |
282 | FT_LOCAL_DEF( FT_Error ) |
283 | tt_face_load_svg_doc( FT_GlyphSlot glyph, |
284 | FT_UInt glyph_index ) |
285 | { |
286 | FT_Error error = FT_Err_Ok; |
287 | TT_Face face = (TT_Face)glyph->face; |
288 | FT_Memory memory = face->root.memory; |
289 | Svg* svg = (Svg*)face->svg; |
290 | |
291 | FT_Byte* doc_list; |
292 | FT_ULong doc_limit; |
293 | |
294 | FT_Byte* doc; |
295 | FT_ULong doc_offset; |
296 | FT_ULong doc_length; |
297 | FT_UShort doc_start_glyph_id; |
298 | FT_UShort doc_end_glyph_id; |
299 | |
300 | FT_SVG_Document svg_document = (FT_SVG_Document)glyph->other; |
301 | |
302 | |
303 | FT_ASSERT( !( svg == NULL ) ); |
304 | |
305 | doc_list = svg->svg_doc_list; |
306 | |
307 | error = find_doc( doc_list + 2, svg->num_entries, glyph_index, |
308 | &doc_offset, &doc_length, |
309 | &doc_start_glyph_id, &doc_end_glyph_id ); |
310 | if ( error != FT_Err_Ok ) |
311 | goto Exit; |
312 | |
313 | doc_limit = svg->table_size - |
314 | (FT_ULong)( doc_list - (FT_Byte*)svg->table ); |
315 | if ( doc_offset > doc_limit || |
316 | doc_length > doc_limit - doc_offset ) |
317 | { |
318 | error = FT_THROW( Invalid_Table ); |
319 | goto Exit; |
320 | } |
321 | |
322 | doc = doc_list + doc_offset; |
323 | |
324 | if ( doc_length > 6 && |
325 | doc[0] == 0x1F && |
326 | doc[1] == 0x8B && |
327 | doc[2] == 0x08 ) |
328 | { |
329 | #ifdef FT_CONFIG_OPTION_USE_ZLIB |
330 | |
331 | FT_ULong uncomp_size; |
332 | FT_Byte* uncomp_buffer = NULL; |
333 | |
334 | |
335 | /* |
336 | * Get the size of the original document. This helps in allotting the |
337 | * buffer to accommodate the uncompressed version. The last 4 bytes |
338 | * of the compressed document are equal to the original size modulo |
339 | * 2^32. Since the size of SVG documents is less than 2^32 bytes we |
340 | * can use this accurately. The four bytes are stored in |
341 | * little-endian format. |
342 | */ |
343 | FT_TRACE4(( "SVG document is GZIP compressed\n" )); |
344 | uncomp_size = (FT_ULong)doc[doc_length - 1] << 24 | |
345 | (FT_ULong)doc[doc_length - 2] << 16 | |
346 | (FT_ULong)doc[doc_length - 3] << 8 | |
347 | (FT_ULong)doc[doc_length - 4]; |
348 | |
349 | if ( FT_QALLOC( uncomp_buffer, uncomp_size ) ) |
350 | goto Exit; |
351 | |
352 | error = FT_Gzip_Uncompress( memory, |
353 | uncomp_buffer, |
354 | &uncomp_size, |
355 | doc, |
356 | doc_length ); |
357 | if ( error ) |
358 | { |
359 | FT_FREE( uncomp_buffer ); |
360 | error = FT_THROW( Invalid_Table ); |
361 | goto Exit; |
362 | } |
363 | |
364 | glyph->internal->flags |= FT_GLYPH_OWN_GZIP_SVG; |
365 | |
366 | doc = uncomp_buffer; |
367 | doc_length = uncomp_size; |
368 | |
369 | #else /* !FT_CONFIG_OPTION_USE_ZLIB */ |
370 | |
371 | error = FT_THROW( Unimplemented_Feature ); |
372 | goto Exit; |
373 | |
374 | #endif /* !FT_CONFIG_OPTION_USE_ZLIB */ |
375 | } |
376 | |
377 | svg_document->svg_document = doc; |
378 | svg_document->svg_document_length = doc_length; |
379 | |
380 | svg_document->metrics = glyph->face->size->metrics; |
381 | svg_document->units_per_EM = glyph->face->units_per_EM; |
382 | |
383 | svg_document->start_glyph_id = doc_start_glyph_id; |
384 | svg_document->end_glyph_id = doc_end_glyph_id; |
385 | |
386 | svg_document->transform.xx = 0x10000; |
387 | svg_document->transform.xy = 0; |
388 | svg_document->transform.yx = 0; |
389 | svg_document->transform.yy = 0x10000; |
390 | |
391 | svg_document->delta.x = 0; |
392 | svg_document->delta.y = 0; |
393 | |
394 | FT_TRACE5(( "start_glyph_id: %d\n" , doc_start_glyph_id )); |
395 | FT_TRACE5(( "end_glyph_id: %d\n" , doc_end_glyph_id )); |
396 | FT_TRACE5(( "svg_document:\n" )); |
397 | FT_TRACE5(( " %.*s\n" , (FT_UInt)doc_length, doc )); |
398 | |
399 | glyph->other = svg_document; |
400 | |
401 | Exit: |
402 | return error; |
403 | } |
404 | |
405 | #else /* !FT_CONFIG_OPTION_SVG */ |
406 | |
407 | /* ANSI C doesn't like empty source files */ |
408 | typedef int tt_svg_dummy_; |
409 | |
410 | #endif /* !FT_CONFIG_OPTION_SVG */ |
411 | |
412 | |
413 | /* END */ |
414 | |