/**************************************************************************** * * hvfload.c * * HVF glyph loading (body). * * Copyright (C) 2025-2026 by * Apple Inc. * written by Deborah Goldsmith * * 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. * */ #include #include #include #include #include "hvfload.h" #include "hvfobjs.h" #include "hvferror.h" #undef FT_COMPONENT #define FT_COMPONENT hvfload #ifdef FT_CONFIG_OPTION_HVF /************************************************************************** * * HVF Coordinate Conversion Macros * */ /* Convert HVF coordinates to FreeType 26.6 fixed point */ /* using a pre-calculated scale factor. */ #define HVF_COORD_TO_FIXED( coord, scale_fixed ) \ ( (FT_F26Dot6)( (coord) * (scale_fixed) ) ) /* Pre-calculate scale factor for efficient coordinate conversion. */ /* FreeType scale factors are 16.16 fixed point; convert to floating */ /* point for HVF. */ #define FT_SCALE_TO_HVF_SCALE_FIXED( scale, apply_scaling ) \ ( (apply_scaling) ? (HVFXYCoord)(scale) / 65536.0 \ : (HVFXYCoord)64.0 ) /* Cache management: clear cache after this many glyph loads. */ /* TODO: Future enhancement: make this a settable property on */ /* the HVF driver. */ #define HVF_CACHE_CLEAR_COUNT 17 /************************************************************************** * * HVF axis values setup with TrueType integration * */ static FT_Error hvf_set_variation_axes( HVF_Face face ) { FT_Error error = FT_Err_Ok; int axis_count; FT_UInt i; FT_UInt max_axes; HVFAxisValue axis_value; /* Only the HVF API calls need `@available` protection. */ if ( HVF_IS_AVAILABLE ) { /* Get HVF axis count for current part. */ axis_count = HVF_render_part_axis_count( (HVFPartRenderer*)face->renderer ); if ( axis_count <= 0 ) { /* No axes for this part - this is normal for non-variable glyphs. */ return FT_Err_Ok; } /* Calculate maximum axes to set - only set axes that have */ /* pre-stored coordinates. */ /* */ /* Note: `HVF_set_render_part` already sets all axes to */ /* default (0.0), so we only need to override axes */ /* that have actual variation coordinates. */ max_axes = 0; if ( face->axis_coords && face->num_axes > 0 ) { max_axes = face->num_axes; if ( max_axes > (FT_UInt)axis_count ) max_axes = (FT_UInt)axis_count; } /* Set only axes with pre-stored coordinates */ /* (`HVF_set_render_part` handles defaults). */ for ( i = 0; i < max_axes; i++ ) { int hvf_result; /* Use pre-converted HVF axis coordinate directly */ /* (no conversion needed). */ axis_value = ((HVFAxisValue*)face->axis_coords)[i]; FT_TRACE5(( "hvf_set_variation_axes:" " axis %u = %f (pre-converted HVF coord)\n", i, axis_value )); /* Set the axis value in HVF renderer. */ hvf_result = HVF_set_axis_value( (HVFPartRenderer*)face->renderer, (int)i, axis_value ); #ifdef FT_DEBUG_LEVEL_TRACE if ( hvf_result != 0 ) { /* Continue with other axes even if one fails. */ FT_TRACE1(( "hvf_set_variation_axes:" " HVF_set_axis_value failed for axis %u (error %d)\n", i, hvf_result )); } #else FT_UNUSED( hvf_result ); #endif } /* Remaining axes already set to defaults by `HVF_set_render_part` - */ /* no action needed. */ FT_TRACE3(( "hvf_set_variation_axes:" " configured %u of %d axis value%s\n" " " " using pre-converted HVF coordinates\n", max_axes, axis_count, axis_count == 1 ? "" : "s" )); } else { FT_TRACE3(( "hvf_set_variation_axes:" " HVF not available at runtime\n" )); return FT_THROW( Unimplemented_Feature ); } return error; } /************************************************************************** * * HVF instruction callback - using `FT_GlyphLoader` * */ static HVFPartRenderAction hvf_render_callback( HVFPartRenderInstruction instruction, const HVFPartRenderParameters* params, void* user_data ) { HVF_RenderContext* ctx = (HVF_RenderContext*)user_data; FT_Error error; switch ( instruction ) { case HVFPartRenderInstructionBeginPart: FT_TRACE5(( "hvf_render: BeginPart (part %zu)\n", params->beginPart.partInfo.partId )); return HVFPartRenderActionContinue; case HVFPartRenderInstructionBeginPath: FT_TRACE5(( "hvf_render: BeginPath\n" )); ctx->path_begun = 1; return HVFPartRenderActionContinue; case HVFPartRenderInstructionAddPoint: { FT_Int n; /* Move-to or line-to point with pre-calculated scaling factors. */ error = FT_GLYPHLOADER_CHECK_POINTS( ctx->loader, 1, 0 ); if ( error ) return HVFPartRenderActionStop; n = ctx->outline->n_points; ctx->outline->points[n].x = HVF_COORD_TO_FIXED( params->addPoint.pt.x, ctx->x_scale_fixed ); ctx->outline->points[n].y = HVF_COORD_TO_FIXED( params->addPoint.pt.y, ctx->y_scale_fixed ); ctx->outline->tags[n] = FT_CURVE_TAG_ON; ctx->outline->n_points++; FT_TRACE5(( "hvf_render: AddPoint (%.2f, %.2f)\n", (double)ctx->outline->points[n].x / 64, (double)ctx->outline->points[n].y / 64 )); return HVFPartRenderActionContinue; } case HVFPartRenderInstructionAddQuad: { FT_Int n; /* Quadratic curve with pre-calculated scaling factors. */ error = FT_GLYPHLOADER_CHECK_POINTS( ctx->loader, 2, 0 ); if ( error ) return HVFPartRenderActionStop; n = ctx->outline->n_points; /* Control point. */ ctx->outline->points[n].x = HVF_COORD_TO_FIXED( params->addQuad.offpt.x, ctx->x_scale_fixed ); ctx->outline->points[n].y = HVF_COORD_TO_FIXED( params->addQuad.offpt.y, ctx->y_scale_fixed ); ctx->outline->tags[n] = FT_CURVE_TAG_CONIC; /* End point. */ ctx->outline->points[n + 1].x = HVF_COORD_TO_FIXED( params->addQuad.onpt.x, ctx->x_scale_fixed ); ctx->outline->points[n + 1].y = HVF_COORD_TO_FIXED( params->addQuad.onpt.y, ctx->y_scale_fixed ); ctx->outline->tags[n + 1] = FT_CURVE_TAG_ON; ctx->outline->n_points += 2; FT_TRACE5(( "hvf_render: AddQuad (%.2f,%.2f) (%.2f,%.2f)\n", (double)ctx->outline->points[n].x / 64, (double)ctx->outline->points[n].y / 64, (double)ctx->outline->points[n + 1].x / 64, (double)ctx->outline->points[n + 1].y / 64 )); return HVFPartRenderActionContinue; } case HVFPartRenderInstructionAddCubic: { FT_Int n; /* Cubic curve with pre-calculated scaling factors. */ error = FT_GLYPHLOADER_CHECK_POINTS( ctx->loader, 3, 0 ); if ( error ) return HVFPartRenderActionStop; n = ctx->outline->n_points; /* First control point */ ctx->outline->points[n].x = HVF_COORD_TO_FIXED( params->addCubic.cp1.x, ctx->x_scale_fixed ); ctx->outline->points[n].y = HVF_COORD_TO_FIXED( params->addCubic.cp1.y, ctx->y_scale_fixed ); ctx->outline->tags[n] = FT_CURVE_TAG_CUBIC; /* Second control point */ ctx->outline->points[n + 1].x = HVF_COORD_TO_FIXED( params->addCubic.cp2.x, ctx->x_scale_fixed ); ctx->outline->points[n + 1].y = HVF_COORD_TO_FIXED( params->addCubic.cp2.y, ctx->y_scale_fixed ); ctx->outline->tags[n + 1] = FT_CURVE_TAG_CUBIC; /* End point */ ctx->outline->points[n + 2].x = HVF_COORD_TO_FIXED( params->addCubic.onpt.x, ctx->x_scale_fixed ); ctx->outline->points[n + 2].y = HVF_COORD_TO_FIXED( params->addCubic.onpt.y, ctx->y_scale_fixed ); ctx->outline->tags[n + 2] = FT_CURVE_TAG_ON; ctx->outline->n_points += 3; FT_TRACE5(( "hvf_render:" " AddCubic (%.2f,%.2f) (%.2f,%.2f) (%.2f,%.2f)\n", (double)ctx->outline->points[n].x / 64, (double)ctx->outline->points[n].y / 64, (double)ctx->outline->points[n + 1].x / 64, (double)ctx->outline->points[n + 1].y / 64, (double)ctx->outline->points[n + 2].x / 64, (double)ctx->outline->points[n + 2].y / 64 )); return HVFPartRenderActionContinue; } case HVFPartRenderInstructionClosePath: if ( ctx->path_begun && ctx->outline->n_points > 0 ) { /* Check space for contour. */ error = FT_GLYPHLOADER_CHECK_POINTS( ctx->loader, 0, 1 ); if ( error ) return HVFPartRenderActionStop; /* Close current contour. */ ctx->outline->contours[ctx->outline->n_contours++] = (FT_UShort)( ctx->outline->n_points - 1 ); ctx->path_begun = 0; } FT_TRACE5(( "hvf_render: ClosePath\n" )); return HVFPartRenderActionContinue; case HVFPartRenderInstructionEndPath: FT_TRACE5(( "hvf_render: EndPath\n" )); return HVFPartRenderActionContinue; case HVFPartRenderInstructionEndPart: FT_TRACE5(( "hvf_render: EndPart (part %zu)\n", params->endPart.partInfo.partId )); return HVFPartRenderActionContinue; case HVFPartRenderInstructionStop: FT_TRACE5(( "hvf_render: Stop\n" )); return HVFPartRenderActionStop; default: FT_TRACE1(( "hvf_render: Unknown instruction %d\n", instruction )); return HVFPartRenderActionStop; } } /************************************************************************** * * @Function: * hvf_slot_load_glyph * * @Description: * Load a glyph using HVF rendering with TrueType integration. * * @Input: * glyph :: * The glyph slot. * * size :: * The size object. * * glyph_index :: * The glyph index. * * load_flags :: * Load flags. * * @Return: * FreeType error code. 0 means success. */ FT_LOCAL_DEF( FT_Error ) hvf_slot_load_glyph( FT_GlyphSlot glyph, FT_Size size, FT_UInt glyph_index, FT_Int32 load_flags ) { HVF_Face face = (HVF_Face)glyph->face; FT_GlyphLoader loader = glyph->internal->loader; HVF_RenderContext context; FT_Error error = FT_Err_Ok; FT_Bool apply_scaling = !( load_flags & FT_LOAD_NO_SCALE ); if ( !face->renderer ) { FT_ERROR(( "hvf_slot_load_glyph: HVF renderer not initialized\n" )); return FT_THROW( Invalid_Handle ); } if ( glyph_index >= (FT_UInt)face->root.root.num_glyphs ) return FT_THROW( Invalid_Glyph_Index ); /* Initialize render context. */ context.loader = loader; context.path_begun = 0; /* Pre-calculate scaling factors based on `FT_LOAD_NO_SCALE` flag. */ context.x_scale_fixed = FT_SCALE_TO_HVF_SCALE_FIXED( size->metrics.x_scale, apply_scaling ); context.y_scale_fixed = FT_SCALE_TO_HVF_SCALE_FIXED( size->metrics.y_scale, apply_scaling ); FT_TRACE3(( "hvf_slot_load_glyph:" " %s\n" " " " (x_scale_fixed=%.1f, y_scale_fixed=%.1f)\n", apply_scaling ? "scaling enabled" : "FT_LOAD_NO_SCALE - no scaling applied", context.x_scale_fixed, context.y_scale_fixed )); /* Initialize glyph loader. */ FT_GlyphLoader_Rewind( loader ); context.outline = &loader->current.outline; /* Only the HVF API calls need `@available` protection. */ if ( HVF_IS_AVAILABLE ) { int hvf_result; /* Configure HVF renderer for this glyph. */ hvf_result = HVF_set_render_part( (HVFPartRenderer*)face->renderer, (HVFPartIndex)glyph_index ); if ( hvf_result != 0 ) { FT_TRACE1(( "hvf_slot_load_glyph:" " HVF_set_render_part failed (error %d)\n", hvf_result )); return FT_THROW( Invalid_Glyph_Index ); } /* Set variation axis values for this glyph */ /* (using TT_Face infrastructure). */ error = hvf_set_variation_axes( face ); #ifdef FT_DEBUG_LEVEL_TRACE if ( error ) { /* Continue with rendering even if axis setting fails. */ FT_TRACE1(( "hvf_slot_load_glyph:" " hvf_set_variation_axes failed (error %d)\n", error )); } #endif error = FT_Err_Ok; /* axis failure is non-fatal */ /* Render the glyph using HVF. */ hvf_result = HVF_render_current_part( (HVFPartRenderer*)face->renderer, hvf_render_callback, &context ); if ( hvf_result != 0 ) { FT_TRACE1(( "hvf_slot_load_glyph:" " HVF_render_current_part failed (%d)\n", hvf_result )); return FT_THROW( Invalid_Glyph_Index ); } } else { FT_TRACE3(( "hvf_slot_load_glyph: HVF not available at runtime\n" )); return FT_THROW( Unimplemented_Feature ); } /* Close any open path. */ if ( context.path_begun && context.outline->n_points > 0 ) { /* Check space for contour. */ error = FT_GLYPHLOADER_CHECK_POINTS( loader, 0, 1 ); if ( !error ) { context.outline->contours[context.outline->n_contours++] = (FT_UShort)( context.outline->n_points - 1 ); } } /* Finalize glyph data with `FT_GlyphLoader`. */ FT_GlyphLoader_Add( loader ); /* Set up glyph slot. */ glyph->format = FT_GLYPH_FORMAT_OUTLINE; glyph->outline = loader->base.outline; /* Nearly all HVF glyphs have overlapping contours, */ /* since it is a stroke-based format */ glyph->outline.flags |= FT_OUTLINE_OVERLAP; /* Set glyph metrics - get from TrueType infrastructure when possible. */ { FT_BBox bbox; FT_UShort advance_width = 0; FT_Short left_bearing = 0; TT_Face tt_face = (TT_Face)face; SFNT_Service sfnt = (SFNT_Service)tt_face->sfnt; FT_Bool has_vertical_info; FT_Pos top; /* vertical top side bearing */ FT_Pos advance; /* vertical advance height */ /* Get horizontal metrics from TrueType tables if available. */ if ( sfnt && glyph_index < (FT_UInt)face->root.root.num_glyphs ) sfnt->get_metrics( tt_face, 0, glyph_index, &left_bearing, &advance_width ); else { /* Fallback to reasonable defaults. */ advance_width = (FT_UShort)( size->metrics.x_ppem ); left_bearing = 0; } glyph->metrics.horiAdvance = advance_width; glyph->linearHoriAdvance = advance_width; /* Get vertical metrics from vmtx table or synthesize them, */ /* consistent with TrueType and CFF drivers for SFNT-based fonts. */ has_vertical_info = FT_BOOL( tt_face->vertical_info && tt_face->vertical.number_Of_VMetrics > 0 ); if ( has_vertical_info ) { FT_Short tsb = 0; FT_UShort ah = 0; sfnt->get_metrics( tt_face, 1, glyph_index, &tsb, &ah ); top = tsb; advance = ah; } else { /* Use OS/2 or horizontal header for vertical advance. */ if ( tt_face->os2.version != 0xFFFFU ) advance = (FT_Pos)( tt_face->os2.sTypoAscender - tt_face->os2.sTypoDescender ); else advance = (FT_Pos)( tt_face->horizontal.Ascender - tt_face->horizontal.Descender ); top = 0; /* computed after bbox is known */ } glyph->linearVertAdvance = advance; /* Scale table-derived metrics if needed. */ /* Outline coordinates are already in the target coordinate */ /* space (scaled in callback, or 26.6 font units for NO_SCALE) */ /* so bbox-derived metrics need no additional scaling. */ if ( apply_scaling ) { FT_Fixed x_scale = size->metrics.x_scale; FT_Fixed y_scale = size->metrics.y_scale; glyph->metrics.horiAdvance = FT_MulFix( glyph->metrics.horiAdvance, x_scale ); top = FT_MulFix( top, y_scale ); advance = FT_MulFix( advance, y_scale ); } /* Compute width, height, and bearing metrics from the glyph */ /* outline bounding box, consistent with TrueType and CFF */ /* drivers. */ FT_Outline_Get_CBox( &glyph->outline, &bbox ); glyph->metrics.width = bbox.xMax - bbox.xMin; glyph->metrics.height = bbox.yMax - bbox.yMin; glyph->metrics.horiBearingX = bbox.xMin; glyph->metrics.horiBearingY = bbox.yMax; /* Finalize vertical metrics. */ /* vertBearingX centers the glyph horizontally for vertical */ /* layout, matching both TrueType and CFF drivers. */ /* When no vmtx data is available, vertBearingY centers the */ /* glyph vertically within the advance height. */ if ( !has_vertical_info ) top = ( advance - glyph->metrics.height ) / 2; glyph->metrics.vertBearingX = glyph->metrics.horiBearingX - glyph->metrics.horiAdvance / 2; glyph->metrics.vertBearingY = top; glyph->metrics.vertAdvance = advance; } /* Cache management - clear cache every */ /* `HVF_CACHE_CLEAR_COUNT` glyphs. */ face->cache_count++; if ( face->cache_count >= HVF_CACHE_CLEAR_COUNT ) { if ( HVF_IS_AVAILABLE ) { HVF_clear_part_cache( (HVFPartRenderer*)face->renderer ); face->cache_count = 0; FT_TRACE3(( "hvf_slot_load_glyph: cleared HVF cache\n" )); } #ifdef FT_DEBUG_LEVEL_TRACE else FT_TRACE3(( "hvf_slot_load_glyph:" " HVF not available for cache clearing\n" )); #endif } FT_TRACE2(( "hvf_slot_load_glyph: glyph %u loaded successfully\n", glyph_index )); return error; } #else /* !FT_CONFIG_OPTION_HVF */ FT_LOCAL_DEF( FT_Error ) hvf_slot_load_glyph( FT_GlyphSlot glyph, FT_Size size, FT_UInt glyph_index, FT_Int32 load_flags ) { FT_UNUSED( glyph ); FT_UNUSED( size ); FT_UNUSED( glyph_index ); FT_UNUSED( load_flags ); return FT_THROW( Unimplemented_Feature ); } #endif /* !FT_CONFIG_OPTION_HVF */ /* END */