tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 */