ttsvg.c (10932B)
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> 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 /* 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 */