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