xref: /aosp_15_r20/external/freetype/src/sfnt/ttsvg.c (revision 63949dbd25bcc50c4e1178497ff9e9574d44fc5a)
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