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 SVG_TABLE_HEADER_SIZE (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 extract_svg_doc( 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