• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /****************************************************************************
2  *
3  * ttsvg.c
4  *
5  *   OpenType SVG Color (specification).
6  *
7  * Copyright (C) 2022 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 +
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 * stream,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*    stream,
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;
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( stream + start_index * 12 );
229     end_doc   = extract_svg_doc( stream + 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( stream + 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( stream + start_index * 4 );
248       }
249       else if ( comp_res == -1 )
250       {
251         end_index = i - 1;
252         end_doc   = extract_svg_doc( stream + 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_Byte*   doc_list;        /* pointer to the SVG doc list         */
287     FT_UShort  num_entries;     /* total number of entries in doc list */
288     FT_ULong   doc_offset;
289     FT_ULong   doc_length;
290 
291     FT_UShort  start_glyph_id;
292     FT_UShort  end_glyph_id;
293 
294     FT_Error   error  = FT_Err_Ok;
295     TT_Face    face   = (TT_Face)glyph->face;
296     FT_Memory  memory = face->root.memory;
297     Svg*       svg    = (Svg*)face->svg;
298 
299     FT_SVG_Document  svg_document = (FT_SVG_Document)glyph->other;
300 
301 
302     FT_ASSERT( !( svg == NULL ) );
303 
304     doc_list    = svg->svg_doc_list;
305     num_entries = FT_NEXT_USHORT( doc_list );
306 
307     error = find_doc( doc_list, num_entries, glyph_index,
308                                 &doc_offset, &doc_length,
309                                 &start_glyph_id, &end_glyph_id );
310     if ( error != FT_Err_Ok )
311       goto Exit;
312 
313     doc_list = svg->svg_doc_list;      /* reset, so we can use it again */
314     doc_list = (FT_Byte*)( doc_list + doc_offset );
315 
316     if ( ( doc_list[0] == 0x1F ) && ( doc_list[1] == 0x8B )
317                                  && ( doc_list[2] == 0x08 ) )
318     {
319 #ifdef FT_CONFIG_OPTION_USE_ZLIB
320 
321       FT_ULong  uncomp_size;
322       FT_Byte*  uncomp_buffer = NULL;
323 
324 
325       /*
326        * Get the size of the original document.  This helps in allotting the
327        * buffer to accommodate the uncompressed version.  The last 4 bytes
328        * of the compressed document are equal to the original size modulo
329        * 2^32.  Since the size of SVG documents is less than 2^32 bytes we
330        * can use this accurately.  The four bytes are stored in
331        * little-endian format.
332        */
333       FT_TRACE4(( "SVG document is GZIP compressed\n" ));
334       uncomp_size = (FT_ULong)doc_list[doc_length - 1] << 24 |
335                     (FT_ULong)doc_list[doc_length - 2] << 16 |
336                     (FT_ULong)doc_list[doc_length - 3] << 8  |
337                     (FT_ULong)doc_list[doc_length - 4];
338 
339       if ( FT_QALLOC( uncomp_buffer, uncomp_size ) )
340         goto Exit;
341 
342       error = FT_Gzip_Uncompress( memory,
343                                   uncomp_buffer,
344                                   &uncomp_size,
345                                   doc_list,
346                                   doc_length );
347       if ( error )
348       {
349         FT_FREE( uncomp_buffer );
350         error = FT_THROW( Invalid_Table );
351         goto Exit;
352       }
353 
354       glyph->internal->flags |= FT_GLYPH_OWN_GZIP_SVG;
355 
356       doc_list   = uncomp_buffer;
357       doc_length = uncomp_size;
358 
359 #else /* !FT_CONFIG_OPTION_USE_ZLIB */
360 
361       error = FT_THROW( Unimplemented_Feature );
362       goto Exit;
363 
364 #endif /* !FT_CONFIG_OPTION_USE_ZLIB */
365     }
366 
367     svg_document->svg_document        = doc_list;
368     svg_document->svg_document_length = doc_length;
369 
370     svg_document->metrics      = glyph->face->size->metrics;
371     svg_document->units_per_EM = glyph->face->units_per_EM;
372 
373     svg_document->start_glyph_id = start_glyph_id;
374     svg_document->end_glyph_id   = end_glyph_id;
375 
376     svg_document->transform.xx = 0x10000;
377     svg_document->transform.xy = 0;
378     svg_document->transform.yx = 0;
379     svg_document->transform.yy = 0x10000;
380 
381     svg_document->delta.x = 0;
382     svg_document->delta.y = 0;
383 
384     FT_TRACE5(( "start_glyph_id: %d\n", start_glyph_id ));
385     FT_TRACE5(( "end_glyph_id:   %d\n", end_glyph_id ));
386     FT_TRACE5(( "svg_document:\n" ));
387     FT_TRACE5(( " %.*s\n", (FT_UInt)doc_length, doc_list ));
388 
389     glyph->other = svg_document;
390 
391   Exit:
392     return error;
393   }
394 
395 #else /* !FT_CONFIG_OPTION_SVG */
396 
397   /* ANSI C doesn't like empty source files */
398   typedef int  _tt_svg_dummy;
399 
400 #endif /* !FT_CONFIG_OPTION_SVG */
401 
402 
403 /* END */
404