(root)/
freetype-2.13.2/
src/
sfnt/
ttsvg.c
       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 )
      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 )
     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
     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
     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
     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 )
     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 */