| /**************************************************************************** |
| * |
| * ttsvg.c |
| * |
| * OpenType SVG Color (specification). |
| * |
| * Copyright (C) 2022 by |
| * David Turner, Robert Wilhelm, Werner Lemberg, and Moazin Khatti. |
| * |
| * This file is part of the FreeType project, and may only be used, |
| * modified, and distributed under the terms of the FreeType project |
| * license, LICENSE.TXT. By continuing to use, modify, or distribute |
| * this file you indicate that you have read the license and |
| * understand and accept it fully. |
| * |
| */ |
| |
| |
| /************************************************************************** |
| * |
| * 'SVG' table specification: |
| * |
| * https://docs.microsoft.com/en-us/typography/opentype/spec/svg |
| * |
| */ |
| |
| #include <ft2build.h> |
| #include <freetype/internal/ftstream.h> |
| #include <freetype/internal/ftobjs.h> |
| #include <freetype/internal/ftdebug.h> |
| #include <freetype/tttags.h> |
| #include <freetype/ftgzip.h> |
| #include <freetype/otsvg.h> |
| |
| |
| #ifdef FT_CONFIG_OPTION_SVG |
| |
| #include "ttsvg.h" |
| |
| |
| /* NOTE: These table sizes are given by the specification. */ |
| #define SVG_TABLE_HEADER_SIZE 10U |
| #define SVG_DOCUMENT_RECORD_SIZE 12U |
| #define SVG_DOCUMENT_LIST_MINIMUM_SIZE 2U + SVG_DOCUMENT_RECORD_SIZE |
| #define SVG_MINIMUM_SIZE SVG_TABLE_HEADER_SIZE + \ |
| SVG_DOCUMENT_LIST_MINIMUM_SIZE |
| |
| |
| typedef struct Svg_ |
| { |
| FT_UShort version; /* table version (starting at 0) */ |
| FT_UShort num_entries; /* number of SVG document records */ |
| |
| FT_Byte* svg_doc_list; /* pointer to the start of SVG Document List */ |
| |
| void* table; /* memory that backs up SVG */ |
| FT_ULong table_size; |
| |
| } Svg; |
| |
| |
| /************************************************************************** |
| * |
| * The macro FT_COMPONENT is used in trace mode. It is an implicit |
| * parameter of the FT_TRACE() and FT_ERROR() macros, usued to print/log |
| * messages during execution. |
| */ |
| #undef FT_COMPONENT |
| #define FT_COMPONENT ttsvg |
| |
| |
| FT_LOCAL_DEF( FT_Error ) |
| tt_face_load_svg( TT_Face face, |
| FT_Stream stream ) |
| { |
| FT_Error error; |
| FT_Memory memory = face->root.memory; |
| |
| FT_ULong table_size; |
| FT_Byte* table = NULL; |
| FT_Byte* p = NULL; |
| Svg* svg = NULL; |
| FT_ULong offsetToSVGDocumentList; |
| |
| |
| error = face->goto_table( face, TTAG_SVG, stream, &table_size ); |
| if ( error ) |
| goto NoSVG; |
| |
| if ( table_size < SVG_MINIMUM_SIZE ) |
| goto InvalidTable; |
| |
| if ( FT_FRAME_EXTRACT( table_size, table ) ) |
| goto NoSVG; |
| |
| /* Allocate memory for the SVG object */ |
| if ( FT_NEW( svg ) ) |
| goto NoSVG; |
| |
| p = table; |
| svg->version = FT_NEXT_USHORT( p ); |
| offsetToSVGDocumentList = FT_NEXT_ULONG( p ); |
| |
| if ( offsetToSVGDocumentList < SVG_TABLE_HEADER_SIZE || |
| offsetToSVGDocumentList > table_size - |
| SVG_DOCUMENT_LIST_MINIMUM_SIZE ) |
| goto InvalidTable; |
| |
| svg->svg_doc_list = (FT_Byte*)( table + offsetToSVGDocumentList ); |
| |
| p = svg->svg_doc_list; |
| svg->num_entries = FT_NEXT_USHORT( p ); |
| |
| FT_TRACE3(( "version: %d\n", svg->version )); |
| FT_TRACE3(( "number of entries: %d\n", svg->num_entries )); |
| |
| if ( offsetToSVGDocumentList + |
| svg->num_entries * SVG_DOCUMENT_RECORD_SIZE > table_size ) |
| goto InvalidTable; |
| |
| svg->table = table; |
| svg->table_size = table_size; |
| |
| face->svg = svg; |
| face->root.face_flags |= FT_FACE_FLAG_SVG; |
| |
| return FT_Err_Ok; |
| |
| InvalidTable: |
| error = FT_THROW( Invalid_Table ); |
| |
| NoSVG: |
| FT_FRAME_RELEASE( table ); |
| FT_FREE( svg ); |
| face->svg = NULL; |
| |
| return error; |
| } |
| |
| |
| FT_LOCAL_DEF( void ) |
| tt_face_free_svg( TT_Face face ) |
| { |
| FT_Memory memory = face->root.memory; |
| FT_Stream stream = face->root.stream; |
| |
| Svg* svg = (Svg*)face->svg; |
| |
| |
| if ( svg ) |
| { |
| FT_FRAME_RELEASE( svg->table ); |
| FT_FREE( svg ); |
| } |
| } |
| |
| |
| typedef struct Svg_doc_ |
| { |
| FT_UShort start_glyph_id; |
| FT_UShort end_glyph_id; |
| |
| FT_ULong offset; |
| FT_ULong length; |
| |
| } Svg_doc; |
| |
| |
| static Svg_doc |
| extract_svg_doc( FT_Byte* stream ) |
| { |
| Svg_doc doc; |
| |
| |
| doc.start_glyph_id = FT_NEXT_USHORT( stream ); |
| doc.end_glyph_id = FT_NEXT_USHORT( stream ); |
| |
| doc.offset = FT_NEXT_ULONG( stream ); |
| doc.length = FT_NEXT_ULONG( stream ); |
| |
| return doc; |
| } |
| |
| |
| static FT_Int |
| compare_svg_doc( Svg_doc doc, |
| FT_UInt glyph_index ) |
| { |
| if ( glyph_index < doc.start_glyph_id ) |
| return -1; |
| else if ( glyph_index > doc.end_glyph_id ) |
| return 1; |
| else |
| return 0; |
| } |
| |
| |
| 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 ) |
| { |
| FT_Error error; |
| |
| Svg_doc start_doc; |
| Svg_doc mid_doc; |
| Svg_doc end_doc; |
| |
| FT_Bool found = FALSE; |
| FT_UInt i = 0; |
| |
| FT_UInt start_index = 0; |
| FT_UInt end_index = num_entries - 1; |
| FT_Int comp_res; |
| |
| |
| /* search algorithm */ |
| if ( num_entries == 0 ) |
| { |
| error = FT_THROW( Invalid_Table ); |
| return error; |
| } |
| |
| start_doc = extract_svg_doc( stream + start_index * 12 ); |
| end_doc = extract_svg_doc( stream + end_index * 12 ); |
| |
| if ( ( compare_svg_doc( start_doc, glyph_index ) == -1 ) || |
| ( compare_svg_doc( end_doc, glyph_index ) == 1 ) ) |
| { |
| error = FT_THROW( Invalid_Glyph_Index ); |
| return error; |
| } |
| |
| while ( start_index <= end_index ) |
| { |
| i = ( start_index + end_index ) / 2; |
| mid_doc = extract_svg_doc( stream + i * 12 ); |
| comp_res = compare_svg_doc( mid_doc, glyph_index ); |
| |
| if ( comp_res == 1 ) |
| { |
| start_index = i + 1; |
| start_doc = extract_svg_doc( stream + start_index * 4 ); |
| } |
| else if ( comp_res == -1 ) |
| { |
| end_index = i - 1; |
| end_doc = extract_svg_doc( stream + end_index * 4 ); |
| } |
| else |
| { |
| found = TRUE; |
| break; |
| } |
| } |
| /* search algorithm end */ |
| |
| if ( found != TRUE ) |
| { |
| FT_TRACE5(( "SVG glyph not found\n" )); |
| error = FT_THROW( Invalid_Glyph_Index ); |
| } |
| else |
| { |
| *doc_offset = mid_doc.offset; |
| *doc_length = mid_doc.length; |
| |
| *start_glyph = mid_doc.start_glyph_id; |
| *end_glyph = mid_doc.end_glyph_id; |
| |
| error = FT_Err_Ok; |
| } |
| |
| return error; |
| } |
| |
| |
| FT_LOCAL_DEF( FT_Error ) |
| tt_face_load_svg_doc( FT_GlyphSlot glyph, |
| FT_UInt glyph_index ) |
| { |
| FT_Byte* doc_list; /* pointer to the SVG doc list */ |
| FT_UShort num_entries; /* total number of entries in doc list */ |
| FT_ULong doc_offset; |
| FT_ULong doc_length; |
| |
| FT_UShort start_glyph_id; |
| FT_UShort end_glyph_id; |
| |
| FT_Error error = FT_Err_Ok; |
| TT_Face face = (TT_Face)glyph->face; |
| FT_Memory memory = face->root.memory; |
| Svg* svg = (Svg*)face->svg; |
| |
| FT_SVG_Document svg_document = (FT_SVG_Document)glyph->other; |
| |
| |
| FT_ASSERT( !( svg == NULL ) ); |
| |
| doc_list = svg->svg_doc_list; |
| num_entries = FT_NEXT_USHORT( doc_list ); |
| |
| error = find_doc( doc_list, num_entries, glyph_index, |
| &doc_offset, &doc_length, |
| &start_glyph_id, &end_glyph_id ); |
| if ( error != FT_Err_Ok ) |
| goto Exit; |
| |
| doc_list = svg->svg_doc_list; /* reset, so we can use it again */ |
| doc_list = (FT_Byte*)( doc_list + doc_offset ); |
| |
| if ( ( doc_list[0] == 0x1F ) && ( doc_list[1] == 0x8B ) |
| && ( doc_list[2] == 0x08 ) ) |
| { |
| #ifdef FT_CONFIG_OPTION_USE_ZLIB |
| |
| FT_ULong uncomp_size; |
| FT_Byte* uncomp_buffer; |
| |
| |
| /* |
| * Get the size of the original document. This helps in allotting the |
| * buffer to accommodate the uncompressed version. The last 4 bytes |
| * of the compressed document are equal to the original size modulo |
| * 2^32. Since the size of SVG documents is less than 2^32 bytes we |
| * can use this accurately. The four bytes are stored in |
| * little-endian format. |
| */ |
| FT_TRACE4(( "SVG document is GZIP compressed\n" )); |
| uncomp_size = (FT_ULong)doc_list[doc_length - 1] << 24 | |
| (FT_ULong)doc_list[doc_length - 2] << 16 | |
| (FT_ULong)doc_list[doc_length - 3] << 8 | |
| (FT_ULong)doc_list[doc_length - 4]; |
| |
| if ( FT_QALLOC( uncomp_buffer, uncomp_size ) ) |
| goto Exit; |
| |
| error = FT_Gzip_Uncompress( memory, |
| uncomp_buffer, |
| &uncomp_size, |
| doc_list, |
| doc_length ); |
| if ( error ) |
| { |
| FT_FREE( uncomp_buffer ); |
| error = FT_THROW( Invalid_Table ); |
| goto Exit; |
| } |
| |
| glyph->internal->flags |= FT_GLYPH_OWN_GZIP_SVG; |
| |
| doc_list = uncomp_buffer; |
| doc_length = uncomp_size; |
| |
| #else /* !FT_CONFIG_OPTION_USE_ZLIB */ |
| |
| error = FT_THROW( Unimplemented_Feature ); |
| goto Exit; |
| |
| #endif /* !FT_CONFIG_OPTION_USE_ZLIB */ |
| } |
| |
| svg_document->svg_document = doc_list; |
| svg_document->svg_document_length = doc_length; |
| |
| svg_document->metrics = glyph->face->size->metrics; |
| svg_document->units_per_EM = glyph->face->units_per_EM; |
| |
| svg_document->start_glyph_id = start_glyph_id; |
| svg_document->end_glyph_id = end_glyph_id; |
| |
| svg_document->transform.xx = 0x10000; |
| svg_document->transform.xy = 0; |
| svg_document->transform.yx = 0; |
| svg_document->transform.yy = 0x10000; |
| |
| svg_document->delta.x = 0; |
| svg_document->delta.y = 0; |
| |
| FT_TRACE5(( "start_glyph_id: %d\n", start_glyph_id )); |
| FT_TRACE5(( "end_glyph_id: %d\n", end_glyph_id )); |
| FT_TRACE5(( "svg_document:\n" )); |
| FT_TRACE5(( " %.*s\n", (FT_UInt)doc_length, doc_list )); |
| |
| glyph->other = svg_document; |
| |
| Exit: |
| return error; |
| } |
| |
| #else /* !FT_CONFIG_OPTION_SVG */ |
| |
| /* ANSI C doesn't like empty source files */ |
| typedef int _tt_svg_dummy; |
| |
| #endif /* !FT_CONFIG_OPTION_SVG */ |
| |
| |
| /* END */ |