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 ) tt_face_load_svg(TT_Face face,FT_Stream stream)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 ) tt_face_free_svg(TT_Face face)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 extract_svg_doc(FT_Byte * stream)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 compare_svg_doc(Svg_doc doc,FT_UInt glyph_index)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 find_doc(FT_Byte * document_records,FT_UShort num_entries,FT_UInt glyph_index,FT_ULong * doc_offset,FT_ULong * doc_length,FT_UShort * start_glyph,FT_UShort * end_glyph)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 ) tt_face_load_svg_doc(FT_GlyphSlot glyph,FT_UInt glyph_index)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