/**************************************************************************** * * ttcolr.c * * TrueType and OpenType colored glyph layer support (body). * * Copyright (C) 2018-2022 by * David Turner, Robert Wilhelm, Dominik Röttsches, and Werner Lemberg. * * Originally written by Shao Yu Zhang . * * 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. * */ /************************************************************************** * * `COLR' table specification: * * https://www.microsoft.com/typography/otspec/colr.htm * */ #include #include #include #include #include #include #include /* the next two code lines are a temporary hack, to be removed together */ /* with `VARIABLE_COLRV1_ENABLED` and related code as soon as variable */ /* 'COLR' support is complete and tested */ #include "../truetype/ttobjs.h" #include "../truetype/ttdriver.h" #ifdef TT_CONFIG_OPTION_COLOR_LAYERS #include "ttcolr.h" /* NOTE: These are the table sizes calculated through the specs. */ #define BASE_GLYPH_SIZE 6U #define BASE_GLYPH_PAINT_RECORD_SIZE 6U #define LAYER_V1_LIST_PAINT_OFFSET_SIZE 4U #define LAYER_V1_LIST_NUM_LAYERS_SIZE 4U #define COLOR_STOP_SIZE 6U #define LAYER_SIZE 4U #define COLR_HEADER_SIZE 14U #define VARIABLE_COLRV1_ENABLED \ ( ((TT_Driver)FT_FACE_DRIVER( face ))->root.clazz == \ &tt_driver_class && \ ((TT_Driver)FT_FACE_DRIVER( face ))->enable_variable_colrv1 ) typedef enum FT_PaintFormat_Internal_ { FT_COLR_PAINTFORMAT_INTERNAL_VAR_SOLID = 3, FT_COLR_PAINTFORMAT_INTERNAL_VAR_LINEAR_GRADIENT = 5, FT_COLR_PAINTFORMAT_INTERNAL_VAR_RADIAL_GRADIENT = 7, FT_COLR_PAINTFORMAT_INTERNAL_VAR_SWEEP_GRADIENT = 9, FT_COLR_PAINTFORMAT_INTERNAL_SCALE_CENTER = 18, FT_COLR_PAINTFORMAT_INTERNAL_SCALE_UNIFORM = 20, FT_COLR_PAINTFORMAT_INTERNAL_SCALE_UNIFORM_CENTER = 22, FT_COLR_PAINTFORMAT_INTERNAL_ROTATE_CENTER = 26, FT_COLR_PAINTFORMAT_INTERNAL_SKEW_CENTER = 30 } FT_PaintFormat_Internal; typedef struct BaseGlyphRecord_ { FT_UShort gid; FT_UShort first_layer_index; FT_UShort num_layers; } BaseGlyphRecord; typedef struct BaseGlyphV1Record_ { FT_UShort gid; /* Offset from start of BaseGlyphV1List, i.e., from base_glyphs_v1. */ FT_ULong paint_offset; } BaseGlyphV1Record; typedef struct Colr_ { FT_UShort version; FT_UShort num_base_glyphs; FT_UShort num_layers; FT_Byte* base_glyphs; FT_Byte* layers; FT_ULong num_base_glyphs_v1; /* Points at beginning of BaseGlyphV1List. */ FT_Byte* base_glyphs_v1; FT_ULong num_layers_v1; FT_Byte* layers_v1; FT_Byte* clip_list; /* * Paint tables start at the minimum of the end of the LayerList and the * end of the BaseGlyphList. Record this location in a field here for * safety checks when accessing paint tables. */ FT_Byte* paints_start_v1; /* Item Variation Store for variable 'COLR' v1. */ GX_ItemVarStoreRec var_store; GX_DeltaSetIdxMapRec delta_set_idx_map; /* The memory that backs up the `COLR' table. */ void* table; FT_ULong table_size; } Colr; /************************************************************************** * * The macro FT_COMPONENT is used in trace mode. It is an implicit * parameter of the FT_TRACE() and FT_ERROR() macros, used to print/log * messages during execution. */ #undef FT_COMPONENT #define FT_COMPONENT ttcolr FT_LOCAL_DEF( FT_Error ) tt_face_load_colr( TT_Face face, FT_Stream stream ) { FT_Error error; FT_Memory memory = face->root.memory; FT_Byte* table = NULL; FT_Byte* p = NULL; /* Needed for reading array lengths in referenced tables. */ FT_Byte* p1 = NULL; Colr* colr = NULL; FT_ULong base_glyph_offset, layer_offset; FT_ULong base_glyphs_offset_v1, num_base_glyphs_v1; FT_ULong layer_offset_v1, num_layers_v1, clip_list_offset; FT_ULong var_idx_map_offset, var_store_offset; FT_ULong table_size; FT_ULong colr_offset_in_stream; /* `COLR' always needs `CPAL' */ if ( !face->cpal ) return FT_THROW( Invalid_File_Format ); error = face->goto_table( face, TTAG_COLR, stream, &table_size ); if ( error ) goto NoColr; colr_offset_in_stream = FT_STREAM_POS(); if ( table_size < COLR_HEADER_SIZE ) goto InvalidTable; if ( FT_FRAME_EXTRACT( table_size, table ) ) goto NoColr; p = table; if ( FT_NEW( colr ) ) goto NoColr; colr->version = FT_NEXT_USHORT( p ); if ( colr->version != 0 && colr->version != 1 ) goto InvalidTable; colr->num_base_glyphs = FT_NEXT_USHORT( p ); base_glyph_offset = FT_NEXT_ULONG( p ); if ( base_glyph_offset >= table_size ) goto InvalidTable; if ( colr->num_base_glyphs * BASE_GLYPH_SIZE > table_size - base_glyph_offset ) goto InvalidTable; layer_offset = FT_NEXT_ULONG( p ); colr->num_layers = FT_NEXT_USHORT( p ); if ( layer_offset >= table_size ) goto InvalidTable; if ( colr->num_layers * LAYER_SIZE > table_size - layer_offset ) goto InvalidTable; if ( colr->version == 1 ) { base_glyphs_offset_v1 = FT_NEXT_ULONG( p ); if ( base_glyphs_offset_v1 >= table_size ) goto InvalidTable; p1 = (FT_Byte*)( table + base_glyphs_offset_v1 ); num_base_glyphs_v1 = FT_PEEK_ULONG( p1 ); if ( num_base_glyphs_v1 * BASE_GLYPH_PAINT_RECORD_SIZE > table_size - base_glyphs_offset_v1 ) goto InvalidTable; colr->num_base_glyphs_v1 = num_base_glyphs_v1; colr->base_glyphs_v1 = p1; layer_offset_v1 = FT_NEXT_ULONG( p ); if ( layer_offset_v1 >= table_size ) goto InvalidTable; if ( layer_offset_v1 ) { p1 = (FT_Byte*)( table + layer_offset_v1 ); num_layers_v1 = FT_PEEK_ULONG( p1 ); if ( num_layers_v1 * LAYER_V1_LIST_PAINT_OFFSET_SIZE > table_size - layer_offset_v1 ) goto InvalidTable; colr->num_layers_v1 = num_layers_v1; colr->layers_v1 = p1; colr->paints_start_v1 = FT_MIN( colr->base_glyphs_v1 + colr->num_base_glyphs_v1 * BASE_GLYPH_PAINT_RECORD_SIZE, colr->layers_v1 + colr->num_layers_v1 * LAYER_V1_LIST_PAINT_OFFSET_SIZE ); } else { colr->num_layers_v1 = 0; colr->layers_v1 = 0; colr->paints_start_v1 = colr->base_glyphs_v1 + colr->num_base_glyphs_v1 * BASE_GLYPH_PAINT_RECORD_SIZE; } clip_list_offset = FT_NEXT_ULONG( p ); if ( clip_list_offset >= table_size ) goto InvalidTable; if ( clip_list_offset ) colr->clip_list = (FT_Byte*)( table + clip_list_offset ); else colr->clip_list = 0; colr->var_store.dataCount = 0; colr->var_store.varData = NULL; colr->var_store.axisCount = 0; colr->var_store.regionCount = 0; colr->var_store.varRegionList = 0; colr->delta_set_idx_map.mapCount = 0; colr->delta_set_idx_map.outerIndex = NULL; colr->delta_set_idx_map.innerIndex = NULL; #ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT if ( face->variation_support & TT_FACE_FLAG_VAR_FVAR && VARIABLE_COLRV1_ENABLED ) { FT_Service_MultiMasters mm = (FT_Service_MultiMasters)face->mm; var_idx_map_offset = FT_NEXT_ULONG( p ); if ( var_idx_map_offset >= table_size ) goto InvalidTable; var_store_offset = FT_NEXT_ULONG( p ); if ( var_store_offset >= table_size ) goto InvalidTable; if ( var_store_offset ) { /* If variation info has not been initialized yet, try doing so, */ /* otherwise loading the variation store will fail as it */ /* requires access to `blend` for checking the number of axes. */ if ( !face->blend ) if ( mm->get_mm_var( FT_FACE( face ), NULL ) ) goto InvalidTable; /* Try loading `VarIdxMap` and `VarStore`. */ error = mm->load_item_var_store( FT_FACE( face ), colr_offset_in_stream + var_store_offset, &colr->var_store ); if ( error != FT_Err_Ok ) goto InvalidTable; } if ( colr->var_store.axisCount && var_idx_map_offset ) { error = mm->load_delta_set_idx_map( FT_FACE( face ), colr_offset_in_stream + var_idx_map_offset, &colr->delta_set_idx_map, &colr->var_store, table_size ); if ( error != FT_Err_Ok ) goto InvalidTable; } } #endif /* TT_CONFIG_OPTION_GX_VAR_SUPPORT */ } colr->base_glyphs = (FT_Byte*)( table + base_glyph_offset ); colr->layers = (FT_Byte*)( table + layer_offset ); colr->table = table; colr->table_size = table_size; face->colr = colr; return FT_Err_Ok; InvalidTable: #ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT if ( VARIABLE_COLRV1_ENABLED ) { FT_Service_MultiMasters mm = (FT_Service_MultiMasters)face->mm; mm->done_delta_set_idx_map( FT_FACE( face ), &colr->delta_set_idx_map ); mm->done_item_var_store( FT_FACE( face ), &colr->var_store ); } #endif error = FT_THROW( Invalid_Table ); NoColr: FT_FRAME_RELEASE( table ); FT_FREE( colr ); return error; } FT_LOCAL_DEF( void ) tt_face_free_colr( TT_Face face ) { FT_Stream stream = face->root.stream; FT_Memory memory = face->root.memory; Colr* colr = (Colr*)face->colr; if ( colr ) { #ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT if ( VARIABLE_COLRV1_ENABLED ) { FT_Service_MultiMasters mm = (FT_Service_MultiMasters)face->mm; mm->done_delta_set_idx_map( FT_FACE( face ), &colr->delta_set_idx_map ); mm->done_item_var_store( FT_FACE( face ), &colr->var_store ); } #endif FT_FRAME_RELEASE( colr->table ); FT_FREE( colr ); } } static FT_Bool find_base_glyph_record( FT_Byte* base_glyph_begin, FT_UInt num_base_glyph, FT_UInt glyph_id, BaseGlyphRecord* record ) { FT_UInt min = 0; FT_UInt max = num_base_glyph; while ( min < max ) { FT_UInt mid = min + ( max - min ) / 2; FT_Byte* p = base_glyph_begin + mid * BASE_GLYPH_SIZE; FT_UShort gid = FT_NEXT_USHORT( p ); if ( gid < glyph_id ) min = mid + 1; else if (gid > glyph_id ) max = mid; else { record->gid = gid; record->first_layer_index = FT_NEXT_USHORT( p ); record->num_layers = FT_NEXT_USHORT( p ); return 1; } } return 0; } FT_LOCAL_DEF( FT_Bool ) tt_face_get_colr_layer( TT_Face face, FT_UInt base_glyph, FT_UInt *aglyph_index, FT_UInt *acolor_index, FT_LayerIterator* iterator ) { Colr* colr = (Colr*)face->colr; BaseGlyphRecord glyph_record; if ( !colr ) return 0; if ( !iterator->p ) { FT_ULong offset; /* first call to function */ iterator->layer = 0; if ( !find_base_glyph_record( colr->base_glyphs, colr->num_base_glyphs, base_glyph, &glyph_record ) ) return 0; if ( glyph_record.num_layers ) iterator->num_layers = glyph_record.num_layers; else return 0; offset = LAYER_SIZE * glyph_record.first_layer_index; if ( offset + LAYER_SIZE * glyph_record.num_layers > colr->table_size ) return 0; iterator->p = colr->layers + offset; } if ( iterator->layer >= iterator->num_layers ) return 0; *aglyph_index = FT_NEXT_USHORT( iterator->p ); *acolor_index = FT_NEXT_USHORT( iterator->p ); if ( *aglyph_index >= (FT_UInt)( FT_FACE( face )->num_glyphs ) || ( *acolor_index != 0xFFFF && *acolor_index >= face->palette_data.num_palette_entries ) ) return 0; iterator->layer++; return 1; } static FT_Bool read_color_line( FT_Byte* color_line_p, FT_ColorLine* colorline, FT_Bool read_variable ) { FT_Byte* p = color_line_p; FT_PaintExtend paint_extend; paint_extend = (FT_PaintExtend)FT_NEXT_BYTE( p ); if ( paint_extend > FT_COLR_PAINT_EXTEND_REFLECT ) return 0; colorline->extend = paint_extend; colorline->color_stop_iterator.num_color_stops = FT_NEXT_USHORT( p ); colorline->color_stop_iterator.p = p; colorline->color_stop_iterator.current_color_stop = 0; colorline->color_stop_iterator.read_variable = read_variable; return 1; } /* * Read a paint offset for `FT_Paint*` objects that have them and check * whether it is within reasonable limits within the font and the COLR * table. * * Return 1 on success, 0 on failure. */ static FT_Bool get_child_table_pointer ( Colr* colr, FT_Byte* paint_base, FT_Byte** p, FT_Byte** child_table_pointer ) { FT_UInt32 paint_offset; FT_Byte* child_table_p; if ( !child_table_pointer ) return 0; paint_offset = FT_NEXT_UOFF3( *p ); if ( !paint_offset ) return 0; child_table_p = (FT_Byte*)( paint_base + paint_offset ); if ( child_table_p < colr->paints_start_v1 || child_table_p >= ( (FT_Byte*)colr->table + colr->table_size ) ) return 0; *child_table_pointer = child_table_p; return 1; } #ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT static FT_Bool get_deltas_for_var_index_base ( TT_Face face, Colr* colr, FT_ULong var_index_base, FT_UInt num_deltas, FT_ItemVarDelta* deltas ) { FT_Error error = FT_Err_Ok; FT_UInt outer_index = 0; FT_UInt inner_index = 0; FT_ULong loop_var_index = var_index_base; FT_Service_MultiMasters mm = (FT_Service_MultiMasters)face->mm; FT_UInt i = 0; if ( !VARIABLE_COLRV1_ENABLED ) { FT_ASSERT( 0 ); return 0; } if ( var_index_base == 0xFFFFFFFF ) { for ( i = 0; i < num_deltas; ++i ) deltas[i] = 0; return 1; } for ( i = 0; i < num_deltas; ++i ) { loop_var_index = var_index_base + i; if ( colr->delta_set_idx_map.innerIndex ) { if ( loop_var_index >= colr->delta_set_idx_map.mapCount ) loop_var_index = colr->delta_set_idx_map.mapCount - 1; outer_index = colr->delta_set_idx_map.outerIndex[loop_var_index]; inner_index = colr->delta_set_idx_map.innerIndex[loop_var_index]; } else { /* TODO: Direct lookup case not implemented or tested yet. */ FT_ASSERT( 0 ); error = FT_THROW( Unimplemented_Feature ); return error; } deltas[i] = mm->get_item_delta( FT_FACE( face ), &colr->var_store, outer_index, inner_index ); } return 1; } #endif /* TT_CONFIG_OPTION_GX_VAR_SUPPORT */ static FT_Bool read_paint( TT_Face face, Colr* colr, FT_Byte* p, FT_COLR_Paint* apaint ) { FT_Byte* paint_base = p; FT_Byte* child_table_p = NULL; FT_Bool do_read_var = FALSE; FT_ULong var_index_base = 0; /* Longest varIndexBase offset is 5 in the spec. */ FT_ItemVarDelta item_deltas[6] = { 0, 0, 0, 0, 0, 0 }; if ( !p || !colr || !colr->table ) return 0; if ( p < colr->paints_start_v1 || p >= ( (FT_Byte*)colr->table + colr->table_size ) ) return 0; apaint->format = (FT_PaintFormat)FT_NEXT_BYTE( p ); if ( apaint->format >= FT_COLR_PAINT_FORMAT_MAX ) return 0; if ( apaint->format == FT_COLR_PAINTFORMAT_COLR_LAYERS ) { /* Initialize layer iterator/ */ FT_Byte num_layers; FT_UInt32 first_layer_index; num_layers = FT_NEXT_BYTE( p ); if ( num_layers > colr->num_layers_v1 ) return 0; first_layer_index = FT_NEXT_ULONG( p ); if ( first_layer_index + num_layers > colr->num_layers_v1 ) return 0; apaint->u.colr_layers.layer_iterator.num_layers = num_layers; apaint->u.colr_layers.layer_iterator.layer = 0; /* TODO: Check whether pointer is outside colr? */ apaint->u.colr_layers.layer_iterator.p = colr->layers_v1 + LAYER_V1_LIST_NUM_LAYERS_SIZE + LAYER_V1_LIST_PAINT_OFFSET_SIZE * first_layer_index; return 1; } else if ( apaint->format == FT_COLR_PAINTFORMAT_SOLID || (FT_PaintFormat_Internal)apaint->format == FT_COLR_PAINTFORMAT_INTERNAL_VAR_SOLID ) { apaint->u.solid.color.palette_index = FT_NEXT_USHORT( p ); apaint->u.solid.color.alpha = FT_NEXT_SHORT( p ); #ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT if ( (FT_PaintFormat_Internal)apaint->format == FT_COLR_PAINTFORMAT_INTERNAL_VAR_SOLID && VARIABLE_COLRV1_ENABLED ) { var_index_base = FT_NEXT_ULONG( p ); if ( !get_deltas_for_var_index_base( face, colr, var_index_base, 1, item_deltas ) ) return 0; apaint->u.solid.color.alpha += item_deltas[0]; } #endif apaint->format = FT_COLR_PAINTFORMAT_SOLID; return 1; } else if ( apaint->format == FT_COLR_PAINTFORMAT_COLR_GLYPH ) { apaint->u.colr_glyph.glyphID = FT_NEXT_USHORT( p ); return 1; } /* * Grouped below here are all paint formats that have an offset to a * child paint table as the first entry (for example, a color line or a * child paint table). Retrieve that and determine whether that paint * offset is valid first. */ if ( !get_child_table_pointer( colr, paint_base, &p, &child_table_p ) ) return 0; if ( apaint->format == FT_COLR_PAINTFORMAT_LINEAR_GRADIENT || ( do_read_var = ( (FT_PaintFormat_Internal)apaint->format == FT_COLR_PAINTFORMAT_INTERNAL_VAR_LINEAR_GRADIENT ) ) ) { if ( !read_color_line( child_table_p, &apaint->u.linear_gradient.colorline, do_read_var ) ) return 0; /* * In order to support variations expose these as FT_Fixed 16.16 values so * that we can support fractional values after interpolation. */ apaint->u.linear_gradient.p0.x = INT_TO_FIXED( FT_NEXT_SHORT( p ) ); apaint->u.linear_gradient.p0.y = INT_TO_FIXED( FT_NEXT_SHORT( p ) ); apaint->u.linear_gradient.p1.x = INT_TO_FIXED( FT_NEXT_SHORT( p ) ); apaint->u.linear_gradient.p1.y = INT_TO_FIXED( FT_NEXT_SHORT( p ) ); apaint->u.linear_gradient.p2.x = INT_TO_FIXED( FT_NEXT_SHORT( p ) ); apaint->u.linear_gradient.p2.y = INT_TO_FIXED( FT_NEXT_SHORT( p ) ); apaint->format = FT_COLR_PAINTFORMAT_LINEAR_GRADIENT; return 1; } else if ( apaint->format == FT_COLR_PAINTFORMAT_RADIAL_GRADIENT || ( do_read_var = ( (FT_PaintFormat_Internal)apaint->format == FT_COLR_PAINTFORMAT_INTERNAL_VAR_RADIAL_GRADIENT ) ) ) { FT_Pos tmp; if ( !read_color_line( child_table_p, &apaint->u.radial_gradient.colorline, do_read_var ) ) return 0; /* In the OpenType specification, `r0` and `r1` are defined as */ /* `UFWORD`. Since FreeType doesn't have a corresponding 16.16 */ /* format we convert to `FWORD` and replace negative values with */ /* (32bit) `FT_INT_MAX`. */ apaint->u.radial_gradient.c0.x = INT_TO_FIXED( FT_NEXT_SHORT( p ) ); apaint->u.radial_gradient.c0.y = INT_TO_FIXED( FT_NEXT_SHORT( p ) ); tmp = INT_TO_FIXED( FT_NEXT_SHORT( p ) ); apaint->u.radial_gradient.r0 = tmp < 0 ? FT_INT_MAX : tmp; apaint->u.radial_gradient.c1.x = INT_TO_FIXED( FT_NEXT_SHORT( p ) ); apaint->u.radial_gradient.c1.y = INT_TO_FIXED( FT_NEXT_SHORT( p ) ); tmp = INT_TO_FIXED( FT_NEXT_SHORT( p ) ); apaint->u.radial_gradient.r1 = tmp < 0 ? FT_INT_MAX : tmp; apaint->format = FT_COLR_PAINTFORMAT_RADIAL_GRADIENT; return 1; } else if ( apaint->format == FT_COLR_PAINTFORMAT_SWEEP_GRADIENT || ( do_read_var = ( (FT_PaintFormat_Internal)apaint->format == FT_COLR_PAINTFORMAT_INTERNAL_VAR_SWEEP_GRADIENT ) ) ) { if ( !read_color_line( child_table_p, &apaint->u.sweep_gradient.colorline, do_read_var) ) return 0; apaint->u.sweep_gradient.center.x = INT_TO_FIXED( FT_NEXT_SHORT( p ) ); apaint->u.sweep_gradient.center.y = INT_TO_FIXED( FT_NEXT_SHORT( p ) ); apaint->u.sweep_gradient.start_angle = F2DOT14_TO_FIXED( FT_NEXT_SHORT( p ) ); apaint->u.sweep_gradient.end_angle = F2DOT14_TO_FIXED( FT_NEXT_SHORT( p ) ); apaint->format = FT_COLR_PAINTFORMAT_SWEEP_GRADIENT; return 1; } if ( apaint->format == FT_COLR_PAINTFORMAT_GLYPH ) { apaint->u.glyph.paint.p = child_table_p; apaint->u.glyph.paint.insert_root_transform = 0; apaint->u.glyph.glyphID = FT_NEXT_USHORT( p ); return 1; } else if ( apaint->format == FT_COLR_PAINTFORMAT_TRANSFORM ) { apaint->u.transform.paint.p = child_table_p; apaint->u.transform.paint.insert_root_transform = 0; if ( !get_child_table_pointer( colr, paint_base, &p, &child_table_p ) ) return 0; p = child_table_p; /* * The following matrix coefficients are encoded as * OpenType 16.16 fixed-point values. */ apaint->u.transform.affine.xx = FT_NEXT_LONG( p ); apaint->u.transform.affine.yx = FT_NEXT_LONG( p ); apaint->u.transform.affine.xy = FT_NEXT_LONG( p ); apaint->u.transform.affine.yy = FT_NEXT_LONG( p ); apaint->u.transform.affine.dx = FT_NEXT_LONG( p ); apaint->u.transform.affine.dy = FT_NEXT_LONG( p ); return 1; } else if ( apaint->format == FT_COLR_PAINTFORMAT_TRANSLATE ) { apaint->u.translate.paint.p = child_table_p; apaint->u.translate.paint.insert_root_transform = 0; apaint->u.translate.dx = INT_TO_FIXED( FT_NEXT_SHORT( p ) ); apaint->u.translate.dy = INT_TO_FIXED( FT_NEXT_SHORT( p ) ); return 1; } else if ( apaint->format == FT_COLR_PAINTFORMAT_SCALE || (FT_PaintFormat_Internal)apaint->format == FT_COLR_PAINTFORMAT_INTERNAL_SCALE_CENTER || (FT_PaintFormat_Internal)apaint->format == FT_COLR_PAINTFORMAT_INTERNAL_SCALE_UNIFORM || (FT_PaintFormat_Internal)apaint->format == FT_COLR_PAINTFORMAT_INTERNAL_SCALE_UNIFORM_CENTER ) { apaint->u.scale.paint.p = child_table_p; apaint->u.scale.paint.insert_root_transform = 0; /* All scale paints get at least one scale value. */ apaint->u.scale.scale_x = F2DOT14_TO_FIXED( FT_NEXT_SHORT( p ) ); /* Non-uniform ones read an extra y value. */ if ( apaint->format == FT_COLR_PAINTFORMAT_SCALE || (FT_PaintFormat_Internal)apaint->format == FT_COLR_PAINTFORMAT_INTERNAL_SCALE_CENTER ) apaint->u.scale.scale_y = F2DOT14_TO_FIXED( FT_NEXT_SHORT( p ) ); else apaint->u.scale.scale_y = apaint->u.scale.scale_x; /* Scale paints that have a center read center coordinates, */ /* otherwise the center is (0,0). */ if ( (FT_PaintFormat_Internal)apaint->format == FT_COLR_PAINTFORMAT_INTERNAL_SCALE_CENTER || (FT_PaintFormat_Internal)apaint->format == FT_COLR_PAINTFORMAT_INTERNAL_SCALE_UNIFORM_CENTER ) { apaint->u.scale.center_x = INT_TO_FIXED( FT_NEXT_SHORT ( p ) ); apaint->u.scale.center_y = INT_TO_FIXED( FT_NEXT_SHORT ( p ) ); } else { apaint->u.scale.center_x = 0; apaint->u.scale.center_y = 0; } /* FT 'COLR' v1 API output format always returns fully defined */ /* structs; we thus set the format to the public API value. */ apaint->format = FT_COLR_PAINTFORMAT_SCALE; return 1; } else if ( apaint->format == FT_COLR_PAINTFORMAT_ROTATE || (FT_PaintFormat_Internal)apaint->format == FT_COLR_PAINTFORMAT_INTERNAL_ROTATE_CENTER ) { apaint->u.rotate.paint.p = child_table_p; apaint->u.rotate.paint.insert_root_transform = 0; apaint->u.rotate.angle = F2DOT14_TO_FIXED( FT_NEXT_SHORT( p ) ); if ( (FT_PaintFormat_Internal)apaint->format == FT_COLR_PAINTFORMAT_INTERNAL_ROTATE_CENTER ) { apaint->u.rotate.center_x = INT_TO_FIXED( FT_NEXT_SHORT( p ) ); apaint->u.rotate.center_y = INT_TO_FIXED( FT_NEXT_SHORT( p ) ); } else { apaint->u.rotate.center_x = 0; apaint->u.rotate.center_y = 0; } apaint->format = FT_COLR_PAINTFORMAT_ROTATE; return 1; } else if ( apaint->format == FT_COLR_PAINTFORMAT_SKEW || (FT_PaintFormat_Internal)apaint->format == FT_COLR_PAINTFORMAT_INTERNAL_SKEW_CENTER ) { apaint->u.skew.paint.p = child_table_p; apaint->u.skew.paint.insert_root_transform = 0; apaint->u.skew.x_skew_angle = F2DOT14_TO_FIXED( FT_NEXT_SHORT( p ) ); apaint->u.skew.y_skew_angle = F2DOT14_TO_FIXED( FT_NEXT_SHORT( p ) ); if ( (FT_PaintFormat_Internal)apaint->format == FT_COLR_PAINTFORMAT_INTERNAL_SKEW_CENTER ) { apaint->u.skew.center_x = INT_TO_FIXED( FT_NEXT_SHORT( p ) ); apaint->u.skew.center_y = INT_TO_FIXED( FT_NEXT_SHORT( p ) ); } else { apaint->u.skew.center_x = 0; apaint->u.skew.center_y = 0; } apaint->format = FT_COLR_PAINTFORMAT_SKEW; return 1; } else if ( apaint->format == FT_COLR_PAINTFORMAT_COMPOSITE ) { FT_UInt composite_mode; apaint->u.composite.source_paint.p = child_table_p; apaint->u.composite.source_paint.insert_root_transform = 0; composite_mode = FT_NEXT_BYTE( p ); if ( composite_mode >= FT_COLR_COMPOSITE_MAX ) return 0; apaint->u.composite.composite_mode = (FT_Composite_Mode)composite_mode; if ( !get_child_table_pointer( colr, paint_base, &p, &child_table_p ) ) return 0; apaint->u.composite.backdrop_paint.p = child_table_p; apaint->u.composite.backdrop_paint.insert_root_transform = 0; return 1; } return 0; } static FT_Bool find_base_glyph_v1_record( FT_Byte * base_glyph_begin, FT_UInt num_base_glyph, FT_UInt glyph_id, BaseGlyphV1Record *record ) { FT_UInt min = 0; FT_UInt max = num_base_glyph; while ( min < max ) { FT_UInt mid = min + ( max - min ) / 2; /* * `base_glyph_begin` is the beginning of `BaseGlyphV1List`; * skip `numBaseGlyphV1Records` by adding 4 to start binary search * in the array of `BaseGlyphV1Record`. */ FT_Byte *p = base_glyph_begin + 4 + mid * BASE_GLYPH_PAINT_RECORD_SIZE; FT_UShort gid = FT_NEXT_USHORT( p ); if ( gid < glyph_id ) min = mid + 1; else if (gid > glyph_id ) max = mid; else { record->gid = gid; record->paint_offset = FT_NEXT_ULONG ( p ); return 1; } } return 0; } FT_LOCAL_DEF( FT_Bool ) tt_face_get_colr_glyph_paint( TT_Face face, FT_UInt base_glyph, FT_Color_Root_Transform root_transform, FT_OpaquePaint* opaque_paint ) { Colr* colr = (Colr*)face->colr; BaseGlyphV1Record base_glyph_v1_record; FT_Byte* p; if ( !colr || !colr->table ) return 0; if ( colr->version < 1 || !colr->num_base_glyphs_v1 || !colr->base_glyphs_v1 ) return 0; if ( opaque_paint->p ) return 0; if ( !find_base_glyph_v1_record( colr->base_glyphs_v1, colr->num_base_glyphs_v1, base_glyph, &base_glyph_v1_record ) ) return 0; if ( !base_glyph_v1_record.paint_offset || base_glyph_v1_record.paint_offset > colr->table_size ) return 0; p = (FT_Byte*)( colr->base_glyphs_v1 + base_glyph_v1_record.paint_offset ); if ( p >= ( (FT_Byte*)colr->table + colr->table_size ) ) return 0; opaque_paint->p = p; if ( root_transform == FT_COLOR_INCLUDE_ROOT_TRANSFORM ) opaque_paint->insert_root_transform = 1; else opaque_paint->insert_root_transform = 0; return 1; } FT_LOCAL_DEF( FT_Bool ) tt_face_get_color_glyph_clipbox( TT_Face face, FT_UInt base_glyph, FT_ClipBox* clip_box ) { Colr* colr; FT_Byte *p, *p1, *clip_base, *limit; FT_Byte clip_list_format; FT_ULong num_clip_boxes, i; FT_UShort gid_start, gid_end; FT_UInt32 clip_box_offset; FT_Byte format; const FT_Byte num_corners = 4; FT_Vector corners[4]; FT_Byte j; FT_BBox font_clip_box; colr = (Colr*)face->colr; if ( !colr ) return 0; if ( !colr->clip_list ) return 0; p = colr->clip_list; /* Limit points to the first byte after the end of the color table. */ /* Thus, in subsequent limit checks below we need to check whether the */ /* read pointer is strictly greater than a position offset by certain */ /* field sizes to the left of that position. */ limit = (FT_Byte*)colr->table + colr->table_size; /* Check whether we can extract one `uint8` and one `uint32`. */ if ( p > limit - ( 1 + 4 ) ) return 0; clip_base = p; clip_list_format = FT_NEXT_BYTE ( p ); /* Format byte used here to be able to upgrade ClipList for >16bit */ /* glyph ids; for now we can expect it to be 0. */ if ( !( clip_list_format == 1 ) ) return 0; num_clip_boxes = FT_NEXT_ULONG( p ); /* Check whether we can extract two `uint16` and one `Offset24`, */ /* `num_clip_boxes` times. */ if ( colr->table_size / ( 2 + 2 + 3 ) < num_clip_boxes || p > limit - ( 2 + 2 + 3 ) * num_clip_boxes ) return 0; for ( i = 0; i < num_clip_boxes; ++i ) { gid_start = FT_NEXT_USHORT( p ); gid_end = FT_NEXT_USHORT( p ); clip_box_offset = FT_NEXT_UOFF3( p ); if ( base_glyph >= gid_start && base_glyph <= gid_end ) { p1 = (FT_Byte*)( clip_base + clip_box_offset ); /* Check whether we can extract one `uint8`. */ if ( p1 > limit - 1 ) return 0; format = FT_NEXT_BYTE( p1 ); if ( format > 1 ) return 0; /* Check whether we can extract four `FWORD`. */ if ( p1 > limit - ( 2 + 2 + 2 + 2 ) ) return 0; /* `face->root.size->metrics.x_scale` and `y_scale` are factors */ /* that scale a font unit value in integers to a 26.6 fixed value */ /* according to the requested size, see for example */ /* `ft_recompute_scaled_metrics`. */ font_clip_box.xMin = FT_MulFix( FT_NEXT_SHORT( p1 ), face->root.size->metrics.x_scale ); font_clip_box.yMin = FT_MulFix( FT_NEXT_SHORT( p1 ), face->root.size->metrics.x_scale ); font_clip_box.xMax = FT_MulFix( FT_NEXT_SHORT( p1 ), face->root.size->metrics.x_scale ); font_clip_box.yMax = FT_MulFix( FT_NEXT_SHORT( p1 ), face->root.size->metrics.x_scale ); /* Make 4 corner points (xMin, yMin), (xMax, yMax) and transform */ /* them. If we we would only transform two corner points and */ /* span a rectangle based on those, the rectangle may become too */ /* small to cover the glyph. */ corners[0].x = font_clip_box.xMin; corners[1].x = font_clip_box.xMin; corners[2].x = font_clip_box.xMax; corners[3].x = font_clip_box.xMax; corners[0].y = font_clip_box.yMin; corners[1].y = font_clip_box.yMax; corners[2].y = font_clip_box.yMax; corners[3].y = font_clip_box.yMin; for ( j = 0; j < num_corners; ++j ) { if ( face->root.internal->transform_flags & 1 ) FT_Vector_Transform( &corners[j], &face->root.internal->transform_matrix ); if ( face->root.internal->transform_flags & 2 ) { corners[j].x += face->root.internal->transform_delta.x; corners[j].y += face->root.internal->transform_delta.y; } } clip_box->bottom_left = corners[0]; clip_box->top_left = corners[1]; clip_box->top_right = corners[2]; clip_box->bottom_right = corners[3]; return 1; } } return 0; } FT_LOCAL_DEF( FT_Bool ) tt_face_get_paint_layers( TT_Face face, FT_LayerIterator* iterator, FT_OpaquePaint* opaque_paint ) { FT_Byte* p = NULL; FT_Byte* p_first_layer = NULL; FT_Byte* p_paint = NULL; FT_UInt32 paint_offset; Colr* colr; if ( iterator->layer == iterator->num_layers ) return 0; colr = (Colr*)face->colr; if ( !colr ) return 0; /* * We have an iterator pointing at a paint offset as part of the * `paintOffset` array in `LayerV1List`. */ p = iterator->p; /* * First ensure that p is within COLRv1. */ if ( p < colr->layers_v1 || p >= ( (FT_Byte*)colr->table + colr->table_size ) ) return 0; /* * Do a cursor sanity check of the iterator. Counting backwards from * where it stands, we need to end up at a position after the beginning * of the `LayerV1List` table and not after the end of the * `LayerV1List`. */ p_first_layer = p - iterator->layer * LAYER_V1_LIST_PAINT_OFFSET_SIZE - LAYER_V1_LIST_NUM_LAYERS_SIZE; if ( p_first_layer < (FT_Byte*)colr->layers_v1 ) return 0; if ( p_first_layer >= (FT_Byte*)( colr->layers_v1 + LAYER_V1_LIST_NUM_LAYERS_SIZE + colr->num_layers_v1 * LAYER_V1_LIST_PAINT_OFFSET_SIZE ) ) return 0; paint_offset = FT_NEXT_ULONG( p ); opaque_paint->insert_root_transform = 0; p_paint = (FT_Byte*)( colr->layers_v1 + paint_offset ); if ( p_paint < colr->paints_start_v1 || p_paint >= ( (FT_Byte*)colr->table + colr->table_size ) ) return 0; opaque_paint->p = p_paint; iterator->p = p; iterator->layer++; return 1; } FT_LOCAL_DEF( FT_Bool ) tt_face_get_colorline_stops( TT_Face face, FT_ColorStop* color_stop, FT_ColorStopIterator *iterator ) { Colr* colr = (Colr*)face->colr; FT_Byte* p; FT_Long var_index_base; FT_Int item_deltas[2]; if ( !colr || !colr->table ) return 0; if ( iterator->current_color_stop >= iterator->num_color_stops ) return 0; if ( iterator->p + ( ( iterator->num_color_stops - iterator->current_color_stop ) * COLOR_STOP_SIZE ) > ( (FT_Byte *)colr->table + colr->table_size ) ) return 0; /* Iterator points at first `ColorStop` of `ColorLine`. */ p = iterator->p; color_stop->stop_offset = (FT_Fixed)FT_NEXT_SHORT( p ) << 2; color_stop->color.palette_index = FT_NEXT_USHORT( p ); color_stop->color.alpha = FT_NEXT_SHORT( p ); if ( iterator->read_variable ) { /* Pointer p needs to be advanced independently of whether we intend */ /* to take variable deltas into account or not. Otherwise iteration */ /* would fail due to wrong offsets. */ var_index_base = FT_NEXT_ULONG( p ); #ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT if ( VARIABLE_COLRV1_ENABLED ) { if ( !get_deltas_for_var_index_base( face, colr, var_index_base, 2, item_deltas ) ) return 0; color_stop->stop_offset += (FT_Fixed)item_deltas[0] << 2; color_stop->color.alpha += item_deltas[1]; } #endif } iterator->p = p; iterator->current_color_stop++; return 1; } FT_LOCAL_DEF( FT_Bool ) tt_face_get_paint( TT_Face face, FT_OpaquePaint opaque_paint, FT_COLR_Paint* paint ) { Colr* colr = (Colr*)face->colr; FT_OpaquePaint next_paint; FT_Matrix ft_root_scale; if ( !colr || !colr->base_glyphs_v1 || !colr->table ) return 0; if ( opaque_paint.insert_root_transform ) { /* 'COLR' v1 glyph information is returned in unscaled coordinates, * i.e., `FT_Size` is not applied or multiplied into the values. When * client applications draw color glyphs, they can request to include * a top-level transform, which includes the active `x_scale` and * `y_scale` information for scaling the glyph, as well the additional * transform and translate configured through `FT_Set_Transform`. * This allows client applications to apply this top-level transform * to the graphics context first and only once, then have gradient and * contour scaling applied correctly when performing the additional * drawing operations for subsequenct paints. Prepare this initial * transform here. */ paint->format = FT_COLR_PAINTFORMAT_TRANSFORM; next_paint.p = opaque_paint.p; next_paint.insert_root_transform = 0; paint->u.transform.paint = next_paint; /* `x_scale` and `y_scale` are in 26.6 format, representing the scale * factor to get from font units to requested size. However, expected * return values are in 16.16, so we shift accordingly with rounding. */ ft_root_scale.xx = ( face->root.size->metrics.x_scale + 32 ) >> 6; ft_root_scale.xy = 0; ft_root_scale.yx = 0; ft_root_scale.yy = ( face->root.size->metrics.y_scale + 32 ) >> 6; if ( face->root.internal->transform_flags & 1 ) FT_Matrix_Multiply( &face->root.internal->transform_matrix, &ft_root_scale ); paint->u.transform.affine.xx = ft_root_scale.xx; paint->u.transform.affine.xy = ft_root_scale.xy; paint->u.transform.affine.yx = ft_root_scale.yx; paint->u.transform.affine.yy = ft_root_scale.yy; /* The translation is specified in 26.6 format and, according to the * documentation of `FT_Set_Translate`, is performed on the character * size given in the last call to `FT_Set_Char_Size`. The * 'PaintTransform' paint table's `FT_Affine23` format expects * values in 16.16 format, thus we need to shift by 10 bits. */ if ( face->root.internal->transform_flags & 2 ) { paint->u.transform.affine.dx = face->root.internal->transform_delta.x * ( 1 << 10 ); paint->u.transform.affine.dy = face->root.internal->transform_delta.y * ( 1 << 10 ); } else { paint->u.transform.affine.dx = 0; paint->u.transform.affine.dy = 0; } return 1; } return read_paint( face, colr, opaque_paint.p, paint ); } FT_LOCAL_DEF( FT_Error ) tt_face_colr_blend_layer( TT_Face face, FT_UInt color_index, FT_GlyphSlot dstSlot, FT_GlyphSlot srcSlot ) { FT_Error error; FT_UInt x, y; FT_Byte b, g, r, alpha; FT_ULong size; FT_Byte* src; FT_Byte* dst; if ( !dstSlot->bitmap.buffer ) { /* Initialize destination of color bitmap */ /* with the size of first component. */ dstSlot->bitmap_left = srcSlot->bitmap_left; dstSlot->bitmap_top = srcSlot->bitmap_top; dstSlot->bitmap.width = srcSlot->bitmap.width; dstSlot->bitmap.rows = srcSlot->bitmap.rows; dstSlot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA; dstSlot->bitmap.pitch = (int)dstSlot->bitmap.width * 4; dstSlot->bitmap.num_grays = 256; size = dstSlot->bitmap.rows * (unsigned int)dstSlot->bitmap.pitch; error = ft_glyphslot_alloc_bitmap( dstSlot, size ); if ( error ) return error; FT_MEM_ZERO( dstSlot->bitmap.buffer, size ); } else { /* Resize destination if needed such that new component fits. */ FT_Int x_min, x_max, y_min, y_max; x_min = FT_MIN( dstSlot->bitmap_left, srcSlot->bitmap_left ); x_max = FT_MAX( dstSlot->bitmap_left + (FT_Int)dstSlot->bitmap.width, srcSlot->bitmap_left + (FT_Int)srcSlot->bitmap.width ); y_min = FT_MIN( dstSlot->bitmap_top - (FT_Int)dstSlot->bitmap.rows, srcSlot->bitmap_top - (FT_Int)srcSlot->bitmap.rows ); y_max = FT_MAX( dstSlot->bitmap_top, srcSlot->bitmap_top ); if ( x_min != dstSlot->bitmap_left || x_max != dstSlot->bitmap_left + (FT_Int)dstSlot->bitmap.width || y_min != dstSlot->bitmap_top - (FT_Int)dstSlot->bitmap.rows || y_max != dstSlot->bitmap_top ) { FT_Memory memory = face->root.memory; FT_UInt width = (FT_UInt)( x_max - x_min ); FT_UInt rows = (FT_UInt)( y_max - y_min ); FT_UInt pitch = width * 4; FT_Byte* buf = NULL; FT_Byte* p; FT_Byte* q; size = rows * pitch; if ( FT_ALLOC( buf, size ) ) return error; p = dstSlot->bitmap.buffer; q = buf + (int)pitch * ( y_max - dstSlot->bitmap_top ) + 4 * ( dstSlot->bitmap_left - x_min ); for ( y = 0; y < dstSlot->bitmap.rows; y++ ) { FT_MEM_COPY( q, p, dstSlot->bitmap.width * 4 ); p += dstSlot->bitmap.pitch; q += pitch; } ft_glyphslot_set_bitmap( dstSlot, buf ); dstSlot->bitmap_top = y_max; dstSlot->bitmap_left = x_min; dstSlot->bitmap.width = width; dstSlot->bitmap.rows = rows; dstSlot->bitmap.pitch = (int)pitch; dstSlot->internal->flags |= FT_GLYPH_OWN_BITMAP; dstSlot->format = FT_GLYPH_FORMAT_BITMAP; } } if ( color_index == 0xFFFF ) { if ( face->have_foreground_color ) { b = face->foreground_color.blue; g = face->foreground_color.green; r = face->foreground_color.red; alpha = face->foreground_color.alpha; } else { if ( face->palette_data.palette_flags && ( face->palette_data.palette_flags[face->palette_index] & FT_PALETTE_FOR_DARK_BACKGROUND ) ) { /* white opaque */ b = 0xFF; g = 0xFF; r = 0xFF; alpha = 0xFF; } else { /* black opaque */ b = 0x00; g = 0x00; r = 0x00; alpha = 0xFF; } } } else { b = face->palette[color_index].blue; g = face->palette[color_index].green; r = face->palette[color_index].red; alpha = face->palette[color_index].alpha; } /* XXX Convert if srcSlot.bitmap is not grey? */ src = srcSlot->bitmap.buffer; dst = dstSlot->bitmap.buffer + dstSlot->bitmap.pitch * ( dstSlot->bitmap_top - srcSlot->bitmap_top ) + 4 * ( srcSlot->bitmap_left - dstSlot->bitmap_left ); for ( y = 0; y < srcSlot->bitmap.rows; y++ ) { for ( x = 0; x < srcSlot->bitmap.width; x++ ) { int aa = src[x]; int fa = alpha * aa / 255; int fb = b * fa / 255; int fg = g * fa / 255; int fr = r * fa / 255; int ba2 = 255 - fa; int bb = dst[4 * x + 0]; int bg = dst[4 * x + 1]; int br = dst[4 * x + 2]; int ba = dst[4 * x + 3]; dst[4 * x + 0] = (FT_Byte)( bb * ba2 / 255 + fb ); dst[4 * x + 1] = (FT_Byte)( bg * ba2 / 255 + fg ); dst[4 * x + 2] = (FT_Byte)( br * ba2 / 255 + fr ); dst[4 * x + 3] = (FT_Byte)( ba * ba2 / 255 + fa ); } src += srcSlot->bitmap.pitch; dst += dstSlot->bitmap.pitch; } return FT_Err_Ok; } #else /* !TT_CONFIG_OPTION_COLOR_LAYERS */ /* ANSI C doesn't like empty source files */ typedef int _tt_colr_dummy; #endif /* !TT_CONFIG_OPTION_COLOR_LAYERS */ /* EOF */