diff --git a/CMakeLists.txt b/CMakeLists.txt index ce70f6b5e..f6651fd27 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -215,6 +215,9 @@ cmake_dependent_option(FT_REQUIRE_BROTLI "Require support of compressed WOFF2 fonts." OFF "NOT FT_DISABLE_BROTLI" OFF) +option(FT_DISABLE_HVF + "Disable support of HVF vector fonts." OFF) + option(FT_ENABLE_ERROR_STRINGS "Enable support for meaningful error descriptions." OFF) @@ -322,6 +325,36 @@ if (NOT FT_DISABLE_BROTLI) endif () endif () +if (NOT FT_DISABLE_HVF) + if (APPLE) + # Check for HVF header + check_include_file("hvf/Scaler.h" HAVE_HVF_HEADER) + + if (HAVE_HVF_HEADER) + # Find the HVF library + find_library(HVF_LIBRARY NAMES hvf) + if (HVF_LIBRARY) + set(HVF_FOUND TRUE) + + # Apple platforms: Enable runtime availability checking + if (CMAKE_C_COMPILER_ID MATCHES "Clang") + set(HVF_RUNTIME_AVAILABLE TRUE) + message(STATUS "Found HVF: Apple platform with clang and HVF SDK") + else() + message(STATUS "Found HVF: Apple platform (clang recommended for __builtin_available syntax)") + endif() + else() + set(HVF_FOUND FALSE) + endif() + else() + set(HVF_FOUND FALSE) + endif() + else() + # HVF is not supported + set(HVF_FOUND FALSE) + endif() +endif() + # Create the configuration file if (UNIX AND NOT WIN32) check_include_file("unistd.h" HAVE_UNISTD_H) @@ -388,6 +421,11 @@ if (BROTLIDEC_FOUND) "/\\* +(#define +FT_CONFIG_OPTION_USE_BROTLI) +\\*/" "\\1" FTOPTION_H "${FTOPTION_H}") endif () +if (HVF_FOUND) + string(REGEX REPLACE + "/\\* +(#define +FT_CONFIG_OPTION_HVF) +\\*/" "\\1" + FTOPTION_H "${FTOPTION_H}") +endif () if (FT_ENABLE_ERROR_STRINGS) string(REGEX REPLACE @@ -437,6 +475,7 @@ set(BASE_SRCS src/cff/cff.c src/cid/type1cid.c src/gzip/ftgzip.c + src/hvf/hvf.c src/lzw/ftlzw.c src/pcf/pcf.c src/pfr/pfr.c @@ -505,6 +544,10 @@ else () C_VISIBILITY_PRESET hidden) endif () +if (HVF_RUNTIME_AVAILABLE) + target_compile_definitions(freetype PRIVATE HVF_RUNTIME_AVAILABLE=1) +endif () + if (BUILD_SHARED_LIBS) set_target_properties(freetype PROPERTIES VERSION ${LIBRARY_VERSION} @@ -582,6 +625,9 @@ if (BROTLIDEC_FOUND) target_include_directories(freetype PRIVATE ${BROTLIDEC_INCLUDE_DIRS}) list(APPEND PKGCONFIG_REQUIRES_PRIVATE "libbrotlidec") endif () +if (HVF_FOUND) + target_link_libraries(freetype PRIVATE ${HVF_LIBRARY}) +endif () # Installation diff --git a/builds/unix/configure.raw b/builds/unix/configure.raw index 45a9be6e4..3953b2d94 100644 --- a/builds/unix/configure.raw +++ b/builds/unix/configure.raw @@ -556,6 +556,39 @@ if test x"$with_brotli" = xyes -a "$have_brotli" = no; then AC_MSG_ERROR([brotli support requested but library not found]) fi +# check for HVF support + +# HVF support detection with platform-specific handling +case "$host" in + *-apple-darwin*|*-apple-macos*) + # Apple platforms: Check for HVF headers in SDK (available macOS 15.4+, iOS 18.4+) + AC_CHECK_HEADER([hvf/Scaler.h], [ + # Check if we're using clang for runtime availability checking + if test "x$GCC" = "xyes" && $CC --version | grep -q "clang"; then + # Enable HVF runtime availability checking + CFLAGS="$CFLAGS -DHVF_RUNTIME_AVAILABLE=1" + have_hvf="yes (Apple platform with clang and HVF SDK)" + else + # Allow building without runtime availability checking + have_hvf="yes (Apple platform with HVF SDK, no runtime checking)" + fi + # Add dylib linkage for HVF (it's a dylib, not a framework) + hvf_libspriv="-lhvf" + hvf_libsstaticconf="$hvf_libspriv" + HVF_CFLAGS="" + HVF_LIBS="$hvf_libspriv" + ], [ + have_hvf="no" + ]) + ;; + *) + have_hvf="no" + hvf_reqpriv= + hvf_libspriv= + hvf_libsstaticconf= + ;; +esac + # Checks for the demo programs. # @@ -1044,7 +1077,8 @@ PKGCONFIG_REQUIRES_PRIVATE="$zlib_reqpriv, \ $bzip2_reqpriv, \ $libpng_reqpriv, \ $harfbuzz_reqpriv, \ - $brotli_reqpriv" + $brotli_reqpriv, \ + $hvf_reqpriv" # beautify PKGCONFIG_REQUIRES_PRIVATE=`echo "$PKGCONFIG_REQUIRES_PRIVATE" \ | sed -e 's/^ *//' \ @@ -1060,6 +1094,7 @@ PKGCONFIG_LIBS_PRIVATE="$zlib_libspriv \ $libpng_libspriv \ $harfbuzz_libspriv \ $brotli_libspriv \ + $hvf_libspriv \ $ft2_extra_libs" # beautify PKGCONFIG_LIBS_PRIVATE=`echo "$PKGCONFIG_LIBS_PRIVATE" \ @@ -1073,6 +1108,7 @@ LIBSSTATIC_CONFIG="-lfreetype \ $libpng_libsstaticconf \ $harfbuzz_libsstaticconf \ $brotli_libsstaticconf \ + $hvf_libsstaticconf \ $ft2_extra_libs" # remove -L/usr/lib and -L/usr/lib64 since `freetype-config' adds them later # on if necessary; also beautify @@ -1172,6 +1208,13 @@ if test "$have_brotli" != no; then else ftoption_unset FT_CONFIG_OPTION_USE_BROTLI fi +if test "$have_hvf" != no; then + CFLAGS="$CFLAGS $HVF_CFLAGS" + LDFLAGS="$LDFLAGS $HVF_LIBS" + ftoption_set FT_CONFIG_OPTION_HVF +else + ftoption_unset FT_CONFIG_OPTION_HVF +fi if test "$have_pthread" != no; then CFLAGS="$CFLAGS $PTHREAD_CFLAGS" @@ -1212,6 +1255,7 @@ Library configuration: libpng: $have_libpng harfbuzz: $have_harfbuzz brotli: $have_brotli + hvf: $have_hvf pthread: $have_pthread ]) diff --git a/builds/windows/vc2010/freetype.vcxproj b/builds/windows/vc2010/freetype.vcxproj index 671d12450..4efd9d460 100644 --- a/builds/windows/vc2010/freetype.vcxproj +++ b/builds/windows/vc2010/freetype.vcxproj @@ -475,6 +475,7 @@ + diff --git a/builds/windows/visualc/freetype.vcproj b/builds/windows/visualc/freetype.vcproj index a16782c23..6a5b9d697 100644 --- a/builds/windows/visualc/freetype.vcproj +++ b/builds/windows/visualc/freetype.vcproj @@ -493,6 +493,10 @@ RelativePath="..\..\..\src\base\ftwinfnt.c" > + + diff --git a/builds/windows/visualce/freetype.vcproj b/builds/windows/visualce/freetype.vcproj index e271462bd..c4b4533b6 100644 --- a/builds/windows/visualce/freetype.vcproj +++ b/builds/windows/visualce/freetype.vcproj @@ -3612,6 +3612,10 @@ RelativePath="..\..\..\src\base\ftpatent.c" > + + diff --git a/devel/ftoption.h b/devel/ftoption.h index 1d4ad342b..11f612d56 100644 --- a/devel/ftoption.h +++ b/devel/ftoption.h @@ -318,6 +318,22 @@ FT_BEGIN_HEADER #define FT_CONFIG_OPTION_USE_HARFBUZZ_DYNAMIC + /************************************************************************** + * + * HVF support. + * + * FreeType can use Apple's HVF (Hierarchical Variable Font) library + * to render glyphs from fonts containing 'hvgl' tables. + * + * Define this macro if you want to enable this 'feature'. + * + * If you use a build system like cmake or the `configure` script, + * options set by those programs have precedence, overwriting the value + * here with the configured one. + */ +/* #define FT_CONFIG_OPTION_HVF */ + + /************************************************************************** * * Brotli support. diff --git a/include/freetype/config/ftmodule.h b/include/freetype/config/ftmodule.h index b315baba8..028e52cc3 100644 --- a/include/freetype/config/ftmodule.h +++ b/include/freetype/config/ftmodule.h @@ -15,6 +15,7 @@ FT_USE_MODULE( FT_Driver_ClassRec, tt_driver_class ) FT_USE_MODULE( FT_Driver_ClassRec, t1_driver_class ) FT_USE_MODULE( FT_Driver_ClassRec, cff_driver_class ) FT_USE_MODULE( FT_Driver_ClassRec, t1cid_driver_class ) +FT_USE_MODULE( FT_Driver_ClassRec, hvf_driver_class ) FT_USE_MODULE( FT_Driver_ClassRec, pfr_driver_class ) FT_USE_MODULE( FT_Driver_ClassRec, t42_driver_class ) FT_USE_MODULE( FT_Driver_ClassRec, winfnt_driver_class ) diff --git a/include/freetype/config/ftoption.h b/include/freetype/config/ftoption.h index fadfb9187..7e6bca873 100644 --- a/include/freetype/config/ftoption.h +++ b/include/freetype/config/ftoption.h @@ -334,6 +334,22 @@ FT_BEGIN_HEADER /* #define FT_CONFIG_OPTION_USE_BROTLI */ + /************************************************************************** + * + * HVF support. + * + * FreeType can use Apple's HVF (Hierarchical Variable Font) library + * to render glyphs from fonts containing 'hvgl' tables. + * + * Define this macro if you want to enable this 'feature'. + * + * If you use a build system like cmake or the `configure` script, + * options set by those programs have precedence, overwriting the value + * here with the configured one. + */ +/* #define FT_CONFIG_OPTION_HVF */ + + /************************************************************************** * * Glyph Postscript Names handling diff --git a/include/freetype/internal/fttrace.h b/include/freetype/internal/fttrace.h index 3fd592800..8dfaed350 100644 --- a/include/freetype/internal/fttrace.h +++ b/include/freetype/internal/fttrace.h @@ -120,6 +120,11 @@ FT_TRACE_DEF( cidload ) FT_TRACE_DEF( cidobjs ) FT_TRACE_DEF( cidparse ) + /* HVF driver component */ +FT_TRACE_DEF( hvfdrv ) +FT_TRACE_DEF( hvfobjs ) +FT_TRACE_DEF( hvfload ) + /* Windows font component */ FT_TRACE_DEF( winfnt ) diff --git a/include/freetype/internal/services/svfntfmt.h b/include/freetype/internal/services/svfntfmt.h index 690fdc2a2..a6c4c5564 100644 --- a/include/freetype/internal/services/svfntfmt.h +++ b/include/freetype/internal/services/svfntfmt.h @@ -40,6 +40,7 @@ FT_BEGIN_HEADER #define FT_FONT_FORMAT_TYPE_42 "Type 42" #define FT_FONT_FORMAT_CID "CID Type 1" #define FT_FONT_FORMAT_CFF "CFF" +#define FT_FONT_FORMAT_HVF "HVF" #define FT_FONT_FORMAT_PFR "PFR" #define FT_FONT_FORMAT_WINFNT "Windows FNT" diff --git a/include/freetype/tttags.h b/include/freetype/tttags.h index 56bb0a3ee..c66784eeb 100644 --- a/include/freetype/tttags.h +++ b/include/freetype/tttags.h @@ -64,6 +64,8 @@ FT_BEGIN_HEADER #define TTAG_GSUB FT_MAKE_TAG( 'G', 'S', 'U', 'B' ) #define TTAG_gvar FT_MAKE_TAG( 'g', 'v', 'a', 'r' ) #define TTAG_HVAR FT_MAKE_TAG( 'H', 'V', 'A', 'R' ) +#define TTAG_hvgl FT_MAKE_TAG( 'h', 'v', 'g', 'l' ) +#define TTAG_hvpm FT_MAKE_TAG( 'h', 'v', 'p', 'm' ) #define TTAG_hdmx FT_MAKE_TAG( 'h', 'd', 'm', 'x' ) #define TTAG_head FT_MAKE_TAG( 'h', 'e', 'a', 'd' ) #define TTAG_hhea FT_MAKE_TAG( 'h', 'h', 'e', 'a' ) diff --git a/meson.build b/meson.build index 09382fbef..76c02817c 100644 --- a/meson.build +++ b/meson.build @@ -401,6 +401,35 @@ if brotli_dep.found() ft2_deps += [brotli_dep] endif +# HVF support (Apple platforms only) +hvf_dep = disabler() +hvf_found = false + +if not get_option('hvf').disabled() + if host_machine.system() == 'darwin' + # Check for HVF header availability + if cc.has_header('hvf/Scaler.h') + # Find the HVF library + hvf_dep = cc.find_library('hvf', required: get_option('hvf')) + + if hvf_dep.found() + hvf_found = true + ftoption_command += ['--enable=FT_CONFIG_OPTION_HVF'] + ft2_deps += [hvf_dep] + + # Check for Clang compiler for runtime availability support + if cc.get_id() == 'clang' + ft2_defines += ['-DHVF_RUNTIME_AVAILABLE=1'] + endif + endif + elif get_option('hvf').enabled() + error('HVF support was enabled but hvf/Scaler.h header was not found') + endif + elif get_option('hvf').enabled() + error('HVF support is only available on Apple platforms (macOS/iOS)') + endif +endif + if get_option('error_strings') ftoption_command += ['--enable=FT_CONFIG_OPTION_ERROR_STRINGS'] endif @@ -524,6 +553,7 @@ summary({'Zlib': zlib_option, 'Png': libpng_dep.found(), 'HarfBuzz': harfbuzz_opt, 'Brotli': brotli_dep.found(), + 'HVF': hvf_found, }, bool_yn: true, section: 'Used Libraries') # EOF diff --git a/meson_options.txt b/meson_options.txt index c9e48356d..23472d2bc 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -53,6 +53,11 @@ option('zlib', 'disabled', 'enabled' ], description: 'Support reading gzip-compressed font files') +option('hvf', + type: 'feature', + value: 'auto', + description: 'Support Apple Hierarchical Variable Font (HVF) format on Apple platforms') + option('error_strings', type: 'boolean', value: false, diff --git a/modules.cfg b/modules.cfg index fa8592b15..ee7637504 100644 --- a/modules.cfg +++ b/modules.cfg @@ -49,6 +49,12 @@ FONT_MODULES += cff # This driver needs the `psaux', `pshinter', and `psnames' modules. FONT_MODULES += cid +# Apple Hierarchical Variable Font (HVF) driver. +# +# This driver needs the `sfnt' module and Apple's HVF library. +# Controlled by FT_CONFIG_OPTION_HVF in ftoption.h. +FONT_MODULES += hvf + # PFR/TrueDoc font driver. See optional extension ftpfr.c below also. FONT_MODULES += pfr diff --git a/src/hvf/hvf.c b/src/hvf/hvf.c new file mode 100644 index 000000000..25f986a9a --- /dev/null +++ b/src/hvf/hvf.c @@ -0,0 +1,25 @@ +/**************************************************************************** + * + * hvf.c + * + * FreeType HVF driver component (body only). + * + * Copyright (C) 2025 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. + * + */ + +#define FT_MAKE_OPTION_SINGLE_OBJECT + +#include "hvfdrv.c" +#include "hvfload.c" +#include "hvfobjs.c" + +/* END */ diff --git a/src/hvf/hvfdrv.c b/src/hvf/hvfdrv.c new file mode 100644 index 000000000..e14f7f7e0 --- /dev/null +++ b/src/hvf/hvfdrv.c @@ -0,0 +1,633 @@ +/**************************************************************************** + * + * hvfdrv.c + * + * HVF font driver implementation (body). + * + * Copyright (C) 2025 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 /* For TT_Face */ +#include + +#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT +#include +#include +#include +#endif + +#include "hvfdrv.h" +#include "hvfobjs.h" +#include "hvfload.h" +#include "hvferror.h" + + /************************************************************************** + * + * 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 hvfdrv + +#ifdef FT_CONFIG_OPTION_HVF +#define PUT_HVF_MODULE( a ) a +#else +#define PUT_HVF_MODULE( a ) NULL +#endif + +#ifdef FT_CONFIG_OPTION_HVF + + /************************************************************************** + * + * FACE MANAGEMENT + * + */ + + /************************************************************************** + * + * @Function: + * hvf_get_kerning + * + * @Description: + * Get kerning vector between two glyphs. Delegates to SFNT service + * since HVF fonts are TrueType/OpenType fonts. + * + * @Input: + * face :: + * A handle to the source face object. + * + * left_glyph :: + * The index of the left glyph in the kern pair. + * + * right_glyph :: + * The index of the right glyph in the kern pair. + * + * @Output: + * kerning :: + * The kerning vector in font units. + * + * @Return: + * FreeType error code. 0 means success. + */ + static FT_Error + hvf_get_kerning( FT_Face face, + FT_UInt left_glyph, + FT_UInt right_glyph, + FT_Vector* kerning ) + { + TT_Face tt_face = (TT_Face)face; + SFNT_Service sfnt = (SFNT_Service)tt_face->sfnt; + + kerning->x = 0; + kerning->y = 0; + + if ( sfnt ) + { + /* Use 'kern' table if available since that can be faster; otherwise */ + /* use GPOS kerning pairs if available. */ + if ( tt_face->kern_avail_bits ) + kerning->x = sfnt->get_kerning( tt_face, + left_glyph, + right_glyph ); +#ifdef TT_CONFIG_OPTION_GPOS_KERNING + else if ( tt_face->num_gpos_lookups_kerning ) + kerning->x = sfnt->get_gpos_kerning( tt_face, + left_glyph, + right_glyph ); +#endif + } + + return FT_Err_Ok; + } + + /************************************************************************** + * + * @Function: + * hvf_get_advances + * + * @Description: + * Get advance widths/heights for a range of glyphs. Delegates to + * TrueType infrastructure since HVF fonts are TrueType/OpenType fonts. + * + * @Input: + * face :: + * A handle to the source face object. + * + * start :: + * The first glyph index. + * + * count :: + * The number of advance values to retrieve. + * + * flags :: + * Loading flags (vertical/horizontal layout). + * + * @Output: + * advances :: + * The advance values in 16.16 format. + * + * @Return: + * FreeType error code. 0 means success. + */ + static FT_Error + hvf_get_advances( FT_Face face, + FT_UInt start, + FT_UInt count, + FT_Int32 flags, + FT_Fixed *advances ) + { + TT_Face tt_face = (TT_Face)face; + FT_Bool horz; + FT_UInt nn; + + if ( !FT_IS_SFNT( face ) ) + return FT_THROW( Unimplemented_Feature ); + + horz = !( flags & FT_LOAD_VERTICAL_LAYOUT ); + + if ( horz ) + { + /* Check availability of horizontal metrics */ + if ( !tt_face->horizontal.number_Of_HMetrics ) + return FT_THROW( Unimplemented_Feature ); + +#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT + /* no fast retrieval for blended MM fonts without HVAR table */ + if ( ( FT_IS_NAMED_INSTANCE( face ) || FT_IS_VARIATION( face ) ) && + !( tt_face->variation_support & TT_FACE_FLAG_VAR_HADVANCE ) ) + return FT_THROW( Unimplemented_Feature ); +#endif + } + else /* vertical */ + { + /* check whether we have data from the `vmtx' table at all */ + if ( !tt_face->vertical_info ) + return FT_THROW( Unimplemented_Feature ); + +#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT + /* no fast retrieval for blended MM fonts without VVAR table */ + if ( ( FT_IS_NAMED_INSTANCE( face ) || FT_IS_VARIATION( face ) ) && + !( tt_face->variation_support & TT_FACE_FLAG_VAR_VADVANCE ) ) + return FT_THROW( Unimplemented_Feature ); +#endif + } + + /* proceed to fast advances */ + for ( nn = 0; nn < count; nn++ ) + { + FT_UShort aw; + FT_Short dummy; + + ( (SFNT_Service)tt_face->sfnt )->get_metrics( tt_face, + !horz, + start + nn, + &dummy, + &aw ); + + FT_TRACE5(( " idx %u: advance %s %d font unit%s\n", + start + nn, + horz ? "width" : "height", + aw, + aw == 1 ? "" : "s" )); + advances[nn] = aw; + } + + return FT_Err_Ok; + } + + + /************************************************************************** + * + * SERVICE DEFINITIONS + * + */ + +#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT + + /* Service delegation functions following CFF module pattern */ + FT_CALLBACK_DEF( FT_Error ) + hvf_set_mm_blend( FT_Face face, /* HVF_Face */ + FT_UInt num_coords, + FT_Fixed* coords ) + { + TT_Face tt_face = (TT_Face)face; + FT_Service_MultiMasters mm = (FT_Service_MultiMasters)tt_face->mm; + HVF_Face hvf_face = (HVF_Face)face; + FT_Error error; + + /* Call the underlying service */ + error = mm->set_mm_blend( face, num_coords, coords ); + + /* error == -1 means "no change"; early exit */ + if ( error == -1 ) + return FT_Err_Ok; + + /* If successful, refresh our cached HVF coordinates */ + if ( !error ) + { + FT_Error refresh_error = hvf_refresh_axis_coordinates( hvf_face ); + if ( refresh_error ) + { + FT_TRACE3(( "hvf_set_mm_blend: failed to refresh HVF coordinates (%d)\n", + refresh_error )); + /* Don't fail the entire operation, just log the issue */ + } + } + + return error; + } + + FT_CALLBACK_DEF( FT_Error ) + hvf_get_mm_blend( FT_Face face, /* HVF_Face */ + FT_UInt num_coords, + FT_Fixed* coords ) + { + TT_Face tt_face = (TT_Face)face; + FT_Service_MultiMasters mm = (FT_Service_MultiMasters)tt_face->mm; + + return mm->get_mm_blend( face, num_coords, coords ); + } + + FT_CALLBACK_DEF( FT_Error ) + hvf_get_mm_var( FT_Face face, /* HVF_Face */ + FT_MM_Var** master ) + { + TT_Face tt_face = (TT_Face)face; + FT_Service_MultiMasters mm = (FT_Service_MultiMasters)tt_face->mm; + + return mm->get_mm_var( face, master ); + } + + FT_CALLBACK_DEF( FT_Error ) + hvf_set_var_design( FT_Face face, /* HVF_Face */ + FT_UInt num_coords, + FT_Fixed* coords ) + { + TT_Face tt_face = (TT_Face)face; + FT_Service_MultiMasters mm = (FT_Service_MultiMasters)tt_face->mm; + HVF_Face hvf_face = (HVF_Face)face; + FT_Error error; + + /* Call the underlying service */ + error = mm->set_var_design( face, num_coords, coords ); + + /* error == -1 means "no change"; early exit */ + if ( error == -1 ) + return FT_Err_Ok; + + /* If successful, refresh our cached HVF coordinates */ + if ( !error || error == -2 ) + { + FT_Error refresh_error = hvf_refresh_axis_coordinates( hvf_face ); + if ( refresh_error ) + { + FT_TRACE3(( "hvf_set_var_design: failed to refresh HVF coordinates (%d)\n", + refresh_error )); + /* Don't fail the entire operation, just log the issue */ + } + } + + return error; + } + + FT_CALLBACK_DEF( FT_Error ) + hvf_get_var_design( FT_Face face, /* HVF_Face */ + FT_UInt num_coords, + FT_Fixed* coords ) + { + TT_Face tt_face = (TT_Face)face; + FT_Service_MultiMasters mm = (FT_Service_MultiMasters)tt_face->mm; + + return mm->get_var_design( face, num_coords, coords ); + } + + FT_CALLBACK_DEF( FT_Error ) + hvf_set_named_instance( FT_Face face, /* HVF_Face */ + FT_UInt instance_index ) + { + TT_Face tt_face = (TT_Face)face; + FT_Service_MultiMasters mm = (FT_Service_MultiMasters)tt_face->mm; + HVF_Face hvf_face = (HVF_Face)face; + FT_Error error; + + /* Call the underlying service */ + error = mm->set_named_instance( face, instance_index ); + + /* error == -1 means "no change"; early exit */ + if ( error == -1 ) + return FT_Err_Ok; + + /* If successful, refresh our cached HVF coordinates */ + if ( !error ) + { + FT_Error refresh_error = hvf_refresh_axis_coordinates( hvf_face ); + if ( refresh_error ) + { + FT_TRACE3(( "hvf_set_named_instance: failed to refresh HVF coordinates (%d)\n", + refresh_error )); + /* Don't fail the entire operation, just log the issue */ + } + } + + return error; + } + + FT_CALLBACK_DEF( FT_Error ) + hvf_get_default_named_instance( FT_Face face, /* HVF_Face */ + FT_UInt *instance_index ) + { + TT_Face tt_face = (TT_Face)face; + FT_Service_MultiMasters mm = (FT_Service_MultiMasters)tt_face->mm; + + return mm->get_default_named_instance( face, instance_index ); + } + + FT_CALLBACK_DEF( void ) + hvf_construct_ps_name( FT_Face face ) /* HVF_Face */ + { + TT_Face tt_face = (TT_Face)face; + FT_Service_MultiMasters mm = (FT_Service_MultiMasters)tt_face->mm; + + mm->construct_ps_name( face ); + } + + FT_CALLBACK_DEF( FT_Error ) + hvf_load_delta_set_index_mapping( FT_Face face, /* HVF_Face */ + FT_ULong offset, + GX_DeltaSetIdxMap map, + GX_ItemVarStore itemStore, + FT_ULong table_len ) + { + TT_Face tt_face = (TT_Face)face; + FT_Service_MultiMasters mm = (FT_Service_MultiMasters)tt_face->mm; + + return mm->load_delta_set_idx_map( face, offset, map, itemStore, table_len ); + } + + FT_CALLBACK_DEF( FT_Error ) + hvf_load_item_variation_store( FT_Face face, /* HVF_Face */ + FT_ULong offset, + GX_ItemVarStore itemStore ) + { + TT_Face tt_face = (TT_Face)face; + FT_Service_MultiMasters mm = (FT_Service_MultiMasters)tt_face->mm; + + return mm->load_item_var_store( face, offset, itemStore ); + } + + FT_CALLBACK_DEF( FT_ItemVarDelta ) + hvf_get_item_delta( FT_Face face, /* HVF_Face */ + GX_ItemVarStore itemStore, + FT_UInt outerIndex, + FT_UInt innerIndex ) + { + TT_Face tt_face = (TT_Face)face; + FT_Service_MultiMasters mm = (FT_Service_MultiMasters)tt_face->mm; + + return mm->get_item_delta( face, itemStore, outerIndex, innerIndex ); + } + + FT_CALLBACK_DEF( void ) + hvf_done_item_variation_store( FT_Face face, /* HVF_Face */ + GX_ItemVarStore itemStore ) + { + TT_Face tt_face = (TT_Face)face; + FT_Service_MultiMasters mm = (FT_Service_MultiMasters)tt_face->mm; + + mm->done_item_var_store( face, itemStore ); + } + + FT_CALLBACK_DEF( void ) + hvf_done_delta_set_index_map( FT_Face face, /* HVF_Face */ + GX_DeltaSetIdxMap deltaSetIdxMap ) + { + TT_Face tt_face = (TT_Face)face; + FT_Service_MultiMasters mm = (FT_Service_MultiMasters)tt_face->mm; + + mm->done_delta_set_idx_map( face, deltaSetIdxMap ); + } + + FT_CALLBACK_DEF( FT_Error ) + hvf_get_var_blend( FT_Face face, /* HVF_Face */ + FT_UInt* num_coords, + FT_Fixed** coords, + FT_Fixed** normalizedcoords, + FT_MM_Var** mm_var ) + { + TT_Face tt_face = (TT_Face)face; + FT_Service_MultiMasters mm = (FT_Service_MultiMasters)tt_face->mm; + + return mm->get_var_blend( face, num_coords, coords, normalizedcoords, mm_var ); + } + + FT_CALLBACK_DEF( void ) + hvf_done_blend( FT_Face face ) /* HVF_Face */ + { + TT_Face tt_face = (TT_Face)face; + FT_Service_MultiMasters mm = (FT_Service_MultiMasters)tt_face->mm; + + mm->done_blend( face ); + } + + FT_CALLBACK_DEF( FT_Error ) + hvf_hadvance_adjust( FT_Face face, /* HVF_Face */ + FT_UInt gindex, + FT_Int *avalue ) + { + TT_Face tt_face = (TT_Face)face; + FT_Service_MetricsVariations var = (FT_Service_MetricsVariations)tt_face->tt_var; + + return var->hadvance_adjust( face, gindex, avalue ); + } + + FT_CALLBACK_DEF( FT_Error ) + hvf_vadvance_adjust( FT_Face face, /* HVF_Face */ + FT_UInt gindex, + FT_Int *avalue ) + { + TT_Face tt_face = (TT_Face)face; + FT_Service_MetricsVariations var = (FT_Service_MetricsVariations)tt_face->tt_var; + + return var->vadvance_adjust( face, gindex, avalue ); + } + + FT_CALLBACK_DEF( void ) + hvf_apply_mvar( FT_Face face ) /* HVF_Face */ + { + TT_Face tt_face = (TT_Face)face; + FT_Service_MetricsVariations var = (FT_Service_MetricsVariations)tt_face->tt_var; + + var->metrics_adjust( face ); + } + + FT_CALLBACK_DEF( void ) + hvf_size_reset_height( FT_Size size ) + { + /* Delegate to size's face services */ + TT_Face tt_face = (TT_Face)size->face; + FT_Service_MetricsVariations var = (FT_Service_MetricsVariations)tt_face->tt_var; + + if ( var->size_reset ) + var->size_reset( size ); + } + + FT_DEFINE_SERVICE_MULTIMASTERSREC( + hvf_service_gx_multi_masters, + + NULL, /* FT_Get_MM_Func get_mm */ + NULL, /* FT_Set_MM_Design_Func set_mm_design */ + hvf_set_mm_blend, /* FT_Set_MM_Blend_Func set_mm_blend */ + hvf_get_mm_blend, /* FT_Get_MM_Blend_Func get_mm_blend */ + hvf_get_mm_var, /* FT_Get_MM_Var_Func get_mm_var */ + hvf_set_var_design, /* FT_Set_Var_Design_Func set_var_design */ + hvf_get_var_design, /* FT_Get_Var_Design_Func get_var_design */ + hvf_set_named_instance, /* FT_Set_Named_Instance_Func set_named_instance */ + hvf_get_default_named_instance, + /* FT_Get_Default_Named_Instance_Func get_default_named_instance */ + NULL, /* FT_Set_MM_WeightVector_Func set_mm_weightvector */ + NULL, /* FT_Get_MM_WeightVector_Func get_mm_weightvector */ + + hvf_construct_ps_name, /* FT_Construct_PS_Name_Func construct_ps_name */ + hvf_load_delta_set_index_mapping, + /* FT_Var_Load_Delta_Set_Idx_Map_Func load_delta_set_idx_map */ + hvf_load_item_variation_store, + /* FT_Var_Load_Item_Var_Store_Func load_item_variation_store */ + hvf_get_item_delta, /* FT_Var_Get_Item_Delta_Func get_item_delta */ + hvf_done_item_variation_store, + /* FT_Var_Done_Item_Var_Store_Func done_item_variation_store */ + hvf_done_delta_set_index_map, + /* FT_Var_Done_Delta_Set_Idx_Map_Func done_delta_set_index_map */ + hvf_get_var_blend, /* FT_Get_Var_Blend_Func get_var_blend */ + hvf_done_blend /* FT_Done_Blend_Func done_blend */ + ) + + FT_DEFINE_SERVICE_METRICSVARIATIONSREC( + hvf_service_metrics_variations, + + hvf_hadvance_adjust, /* FT_HAdvance_Adjust_Func hadvance_adjust */ + NULL, /* FT_LSB_Adjust_Func lsb_adjust */ + NULL, /* FT_RSB_Adjust_Func rsb_adjust */ + + hvf_vadvance_adjust, /* FT_VAdvance_Adjust_Func vadvance_adjust */ + NULL, /* FT_TSB_Adjust_Func tsb_adjust */ + NULL, /* FT_BSB_Adjust_Func bsb_adjust */ + NULL, /* FT_VOrg_Adjust_Func vorg_adjust */ + + hvf_apply_mvar, /* FT_Metrics_Adjust_Func metrics_adjust */ + hvf_size_reset_height /* FT_Size_Reset_Func size_reset */ + ) + +#endif /* TT_CONFIG_OPTION_GX_VAR_SUPPORT */ + +#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT + FT_DEFINE_SERVICEDESCREC3( + hvf_services, + + FT_SERVICE_ID_FONT_FORMAT, FT_FONT_FORMAT_HVF, + FT_SERVICE_ID_MULTI_MASTERS, &hvf_service_gx_multi_masters, + FT_SERVICE_ID_METRICS_VARIATIONS, &hvf_service_metrics_variations ) +#else + FT_DEFINE_SERVICEDESCREC1( + hvf_services, + + FT_SERVICE_ID_FONT_FORMAT, FT_FONT_FORMAT_HVF ) +#endif + + FT_CALLBACK_DEF( FT_Module_Interface ) + hvf_get_interface( FT_Module driver, /* HVF_Driver */ + const char* hvf_interface ) + { + FT_Library library; + FT_Module_Interface result; + FT_Module sfntd; + SFNT_Service sfnt; + + FT_TRACE5(( "hvf_get_interface: Looking up service %s\n", hvf_interface )); + result = ft_service_list_lookup( hvf_services, hvf_interface ); + if ( result ) + return result; + + FT_TRACE3(( "hvf_get_interface: Did not find service %s; falling back\n", hvf_interface )); + if ( !driver ) + return NULL; + library = driver->library; + if ( !library ) + return NULL; + + /* only return the default interface from the SFNT module */ + sfntd = FT_Get_Module( library, "sfnt" ); + if ( sfntd ) + { + sfnt = (SFNT_Service)( sfntd->clazz->module_interface ); + if ( sfnt ) + return sfnt->get_interface( driver, hvf_interface ); + } + + return 0; + } + +#endif /* FT_CONFIG_OPTION_HVF */ + + + /************************************************************************** + * + * DRIVER INTERFACE + * + */ + + FT_DEFINE_DRIVER( + hvf_driver_class, + + FT_MODULE_FONT_DRIVER | + FT_MODULE_DRIVER_SCALABLE | + FT_MODULE_DRIVER_HAS_HINTER, + + sizeof ( FT_DriverRec ), + + "hvf", /* driver name */ + 0x10000L, /* driver version == 1.0 */ + 0x20000L, /* driver requires FreeType 2.0 or above */ + + NULL, /* module-specific interface */ + + NULL, /* FT_Module_Constructor module_init */ + NULL, /* FT_Module_Destructor module_done */ + PUT_HVF_MODULE( hvf_get_interface ), /* FT_Module_Requester get_interface */ + + sizeof ( HVF_FaceRec ), + sizeof ( FT_SizeRec ), + sizeof ( FT_GlyphSlotRec ), + + PUT_HVF_MODULE( hvf_face_init ), /* FT_Face_InitFunc init_face */ + PUT_HVF_MODULE( hvf_face_done ), /* FT_Face_DoneFunc done_face */ + NULL, /* FT_Size_InitFunc init_size */ + NULL, /* FT_Size_DoneFunc done_size */ + NULL, /* FT_Slot_InitFunc init_slot */ + NULL, /* FT_Slot_DoneFunc done_slot */ + + PUT_HVF_MODULE( hvf_slot_load_glyph ), /* FT_Slot_LoadFunc load_glyph */ + + PUT_HVF_MODULE( hvf_get_kerning ), /* FT_Face_GetKerningFunc get_kerning */ + NULL, /* FT_Face_AttachFunc attach_file */ + PUT_HVF_MODULE( hvf_get_advances ), /* FT_Face_GetAdvancesFunc get_advances */ + + NULL, /* FT_Size_RequestFunc request_size */ + NULL /* FT_Size_SelectFunc select_size */ + ) + +/* END */ diff --git a/src/hvf/hvfdrv.h b/src/hvf/hvfdrv.h new file mode 100644 index 000000000..1d96c9ba9 --- /dev/null +++ b/src/hvf/hvfdrv.h @@ -0,0 +1,38 @@ +/**************************************************************************** + * + * hvfdrv.h + * + * Apple Hierarchical Variable Font (HVF) driver interface (specification only). + * + * Copyright (C) 2025 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. + * + */ + + +#ifndef HVFDRV_H_ +#define HVFDRV_H_ + + +#include + + +FT_BEGIN_HEADER + + + FT_DECLARE_DRIVER( hvf_driver_class ) + + +FT_END_HEADER + +#endif /* HVFDRV_H_ */ + + +/* END */ diff --git a/src/hvf/hvferror.h b/src/hvf/hvferror.h new file mode 100644 index 000000000..7a1131c2b --- /dev/null +++ b/src/hvf/hvferror.h @@ -0,0 +1,40 @@ +/**************************************************************************** + * + * hvferror.h + * + * HVF error codes (specification only). + * + * Copyright (C) 2025 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. + * + */ + + /************************************************************************** + * + * This file is used to define the HVF error enumeration constants. + * + */ + +#ifndef HVFERROR_H_ +#define HVFERROR_H_ + +#include + +#undef FTERRORS_H_ + +#undef FT_ERR_PREFIX +#define FT_ERR_PREFIX HVF_Err_ +#define FT_ERR_BASE FT_Mod_Err_HVF + +#include + +#endif /* HVFERROR_H_ */ + +/* END */ diff --git a/src/hvf/hvfload.c b/src/hvf/hvfload.c new file mode 100644 index 000000000..40e50cecc --- /dev/null +++ b/src/hvf/hvfload.c @@ -0,0 +1,449 @@ +/**************************************************************************** + * + * hvfload.c + * + * HVF glyph loading (body). + * + * Copyright (C) 2025 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 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 hvf_result; + int axis_count; + FT_UInt i; + FT_UInt max_axes; + HVFAxisValue axis_value; + + /* Only the HVF API calls need @available protection */ + HVF_IF_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 set 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++ ) + { + /* 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 ); + if ( hvf_result != 0 ) + { + FT_TRACE1(( "hvf_set_variation_axes: HVF_set_axis_value failed for axis %u (%d)\n", + i, hvf_result )); + /* Continue with other axes even if one fails */ + } + } + + /* 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 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: + { + /* Move-to or Line-to point with pre-calculated scaling factors */ + error = FT_GLYPHLOADER_CHECK_POINTS( ctx->loader, ctx->outline->n_points + 1, ctx->outline->n_contours ); + if ( error ) + return HVFPartRenderActionStop; + + FT_Int 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: + { + /* Quadratic curve with pre-calculated scaling factors */ + error = FT_GLYPHLOADER_CHECK_POINTS( ctx->loader, ctx->outline->n_points + 2, ctx->outline->n_contours ); + if ( error ) + return HVFPartRenderActionStop; + + FT_Int 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: + { + /* Cubic curve with pre-calculated scaling factors */ + error = FT_GLYPHLOADER_CHECK_POINTS( ctx->loader, ctx->outline->n_points + 3, ctx->outline->n_contours ); + if ( error ) + return HVFPartRenderActionStop; + + FT_Int 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, ctx->outline->n_points, ctx->outline->n_contours + 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: + * Loads 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; + int hvf_result; + 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.face = face; + 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 (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 */ + HVF_IF_AVAILABLE { + /* 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 (%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 ); + if ( error ) + { + FT_TRACE1(( "hvf_slot_load_glyph: hvf_set_variation_axes failed (%d)\n", + error )); + /* Continue with rendering even if axis setting fails */ + } + + /* 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, context.outline->n_points, context.outline->n_contours + 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_UShort advance_width = 0; + FT_Short left_bearing = 0; + TT_Face tt_face = (TT_Face)face; + SFNT_Service sfnt = (SFNT_Service)tt_face->sfnt; + + /* Get 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; + } + + /* Set up unscaled metrics */ + glyph->metrics.width = advance_width; + glyph->metrics.height = size->metrics.y_ppem; + glyph->metrics.horiBearingX = left_bearing; + glyph->metrics.horiBearingY = glyph->metrics.height; + glyph->metrics.horiAdvance = advance_width; + glyph->metrics.vertBearingX = glyph->metrics.width / 2; + glyph->metrics.vertBearingY = 0; + glyph->metrics.vertAdvance = glyph->metrics.height; + } + + /* Scale metrics if scaling was applied (coordinates already scaled in callback) */ + if ( apply_scaling ) + { + FT_Size_Metrics* metrics = &size->metrics; + + /* Scale metrics using size metrics - coordinates already scaled in callback */ + glyph->metrics.width = FT_MulFix( glyph->metrics.width, metrics->x_scale ); + glyph->metrics.height = FT_MulFix( glyph->metrics.height, metrics->y_scale ); + glyph->metrics.horiBearingX = FT_MulFix( glyph->metrics.horiBearingX, metrics->x_scale ); + glyph->metrics.horiBearingY = FT_MulFix( glyph->metrics.horiBearingY, metrics->y_scale ); + glyph->metrics.horiAdvance = FT_MulFix( glyph->metrics.horiAdvance, metrics->x_scale ); + glyph->metrics.vertBearingX = FT_MulFix( glyph->metrics.vertBearingX, metrics->x_scale ); + glyph->metrics.vertBearingY = FT_MulFix( glyph->metrics.vertBearingY, metrics->y_scale ); + glyph->metrics.vertAdvance = FT_MulFix( glyph->metrics.vertAdvance, metrics->y_scale ); + } + + /* Cache management - clear cache every HVF_CACHE_CLEAR_COUNT glyphs */ + face->cache_count++; + if ( face->cache_count >= HVF_CACHE_CLEAR_COUNT ) + { + HVF_IF_AVAILABLE { + HVF_clear_part_cache( (HVFPartRenderer*)face->renderer ); + face->cache_count = 0; + FT_TRACE3(( "hvf_slot_load_glyph: cleared HVF cache\n" )); + } else { + FT_TRACE3(( "hvf_slot_load_glyph: HVF not available for cache clearing\n" )); + } + } + + FT_TRACE2(( "hvf_slot_load_glyph: glyph %u loaded successfully\n", + glyph_index )); + + return error; + } + +#endif /* FT_CONFIG_OPTION_HVF */ + +/* END */ diff --git a/src/hvf/hvfload.h b/src/hvf/hvfload.h new file mode 100644 index 000000000..f8d182751 --- /dev/null +++ b/src/hvf/hvfload.h @@ -0,0 +1,39 @@ +/**************************************************************************** + * + * hvfload.h + * + * HVF glyph loading (specification). + * + * Copyright (C) 2025 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. + * + */ + +#ifndef HVFLOAD_H_ +#define HVFLOAD_H_ + +#include +#include + +FT_BEGIN_HEADER + +#ifdef FT_CONFIG_OPTION_HVF + FT_LOCAL( FT_Error ) + hvf_slot_load_glyph( FT_GlyphSlot glyph, + FT_Size size, + FT_UInt glyph_index, + FT_Int32 load_flags ); +#endif /* FT_CONFIG_OPTION_HVF */ + +FT_END_HEADER + +#endif /* HVFLOAD_H_ */ + +/* END */ diff --git a/src/hvf/hvfobjs.c b/src/hvf/hvfobjs.c new file mode 100644 index 000000000..4c04458a2 --- /dev/null +++ b/src/hvf/hvfobjs.c @@ -0,0 +1,425 @@ +/**************************************************************************** + * + * hvfobjs.c + * + * HVF objects manager (body). + * + * Copyright (C) 2025 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 + +#include "hvfobjs.h" +#include "hvferror.h" + +#undef FT_COMPONENT +#define FT_COMPONENT hvfobjs + +#ifdef FT_CONFIG_OPTION_HVF + + /************************************************************************** + * + * FACE MANAGEMENT + * + */ + + static FT_Error + hvf_load_tables( HVF_Face face ) + { + FT_Error error; + FT_Stream stream; + TT_Face tt_face = (TT_Face)face; /* Cast to access TT_Face members */ + + /* Use the stream from TT_Face root */ + stream = FT_FACE_STREAM( face ); + + /* Load required hvgl table using memory mapping */ + error = tt_face->goto_table( tt_face, TTAG_hvgl, stream, &face->hvgl_size ); + if ( error ) + { + FT_TRACE2(( "hvf_load_tables: missing required 'hvgl' table\n" )); + return error; + } + + /* Memory-map the hvgl table data instead of copying it */ + if ( FT_FRAME_EXTRACT( face->hvgl_size, face->hvgl_data ) ) + { + face->hvgl_size = 0; + FT_TRACE2(( "hvf_load_tables: failed to extract 'hvgl' table\n" )); + return FT_THROW( Invalid_Table ); + } + + FT_TRACE2(( "hvf_load_tables: memory-mapped 'hvgl' table (%lu bytes)\n", + face->hvgl_size )); + + /* Load optional hvpm table using memory mapping */ + error = tt_face->goto_table( tt_face, TTAG_hvpm, stream, &face->hvpm_size ); + if ( error ) + { + face->hvpm_data = NULL; /* Ensure NULL for pointer check */ + face->hvpm_size = 0; + FT_TRACE2(( "hvf_load_tables: no 'hvpm' table found (optional)\n" )); + } + else + { + /* Memory-map the hvpm table data instead of copying it */ + if ( FT_FRAME_EXTRACT( face->hvpm_size, face->hvpm_data ) ) + { + face->hvpm_data = NULL; + face->hvpm_size = 0; + FT_TRACE2(( "hvf_load_tables: failed to extract 'hvpm' table\n" )); + } + else + { + FT_TRACE2(( "hvf_load_tables: memory-mapped 'hvpm' table (%lu bytes)\n", + face->hvpm_size )); + } + } + + return FT_Err_Ok; + } + + static FT_Error + hvf_init_renderer( HVF_Face face ) + { + FT_Error error = FT_Err_Ok; + FT_Memory memory = FT_FACE_MEMORY( face ); + size_t storage_size; + int hvf_result; + + if ( !face->hvgl_data ) + return FT_THROW( Invalid_Table ); + + /* Create HVF renderer - only once per face */ + if ( !face->renderer ) + { + HVF_IF_AVAILABLE { + /* Get required storage size */ + storage_size = HVF_part_renderer_storage_size(); + if ( storage_size == 0 ) + { + FT_TRACE1(( "hvf_init_renderer: HVF_part_renderer_storage_size failed\n" )); + return FT_THROW( Invalid_Table ); + } + + /* Allocate storage for renderer */ + if ( FT_ALLOC( face->renderer, storage_size ) ) + return error; + + /* Initialize HVF renderer with hvgl and optional hvpm table data */ + hvf_result = HVF_open_part_renderer( face->hvgl_data, + face->hvgl_size, + face->hvpm_data, /* NULL if not available */ + face->hvpm_size, /* 0 if not available */ + face->renderer, + storage_size ); + if ( hvf_result != 0 ) + { + FT_TRACE1(( "hvf_init_renderer: HVF_open_part_renderer failed (%d)\n", + hvf_result )); + FT_FREE( face->renderer ); + return FT_THROW( Invalid_Table ); + } + + face->cache_count = 0; + FT_TRACE2(( "hvf_init_renderer: HVF renderer initialized (%s hvpm)\n", + face->hvpm_data ? "with" : "without" )); + } else { + FT_TRACE3(( "hvf_init_renderer: HVF not available at runtime\n" )); + return FT_THROW( Invalid_Table ); + } + } + + return error; + } + + static FT_Error + hvf_init_axis_coordinates( HVF_Face face ) + { + FT_Error error = FT_Err_Ok; + FT_Memory memory = FT_FACE_MEMORY( face ); + FT_MM_Var* mm_var = NULL; + + /* Initialize axis data */ + face->num_axes = 0; + face->axis_coords = NULL; + + /* Check if this is a variable font and get axis information */ + error = FT_Get_MM_Var( (FT_Face)face, &mm_var ); + if ( error ) + { + /* Not a variable font or error getting variation info - that's fine */ + FT_TRACE3(( "hvf_init_axis_coordinates: not a variable font or no MM info: %x\n", error )); + return FT_Err_Ok; + } + + if ( mm_var && mm_var->num_axis > 0 ) + { + face->num_axes = mm_var->num_axis; + + /* Allocate storage for HVF axis coordinates */ + if ( !( FT_MEM_QNEW_ARRAY( *(HVFAxisValue**)&face->axis_coords, face->num_axes ) ) ) + { + face->num_axes = 0; + goto Cleanup; + } + + /* Initialize coordinates using the shared refresh function */ + error = hvf_refresh_axis_coordinates( face ); + if ( error ) + { + FT_FREE( face->axis_coords ); + face->num_axes = 0; + goto Cleanup; + } + } + + Cleanup: + if ( mm_var ) + FT_Done_MM_Var( FT_FACE_LIBRARY( face ), mm_var ); + + return error; + } + + FT_LOCAL_DEF( FT_Error ) + hvf_refresh_axis_coordinates( HVF_Face face ) + { + FT_Error error = FT_Err_Ok; + FT_Memory memory = FT_FACE_MEMORY( face ); + FT_Fixed* ft_coords = NULL; + FT_UInt i; + + /* Only refresh if we have cached coordinates */ + if ( !face->axis_coords || face->num_axes == 0 ) + return FT_Err_Ok; + + /* Allocate temporary storage for current FreeType coordinates */ + if ( !( FT_MEM_QNEW_ARRAY( ft_coords, face->num_axes ) ) ) + return error; + + /* Get current variation coordinates */ + error = FT_Get_Var_Blend_Coordinates( (FT_Face)face, face->num_axes, ft_coords ); + if ( error ) + { + /* If we can't get coordinates, set to defaults */ + FT_ARRAY_ZERO( (HVFAxisValue*)face->axis_coords, face->num_axes ); + FT_TRACE3(( "hvf_refresh_axis_coordinates: could not get blend coordinates, using defaults\n" )); + error = FT_Err_Ok; /* Not fatal */ + } + else + { + /* Convert FreeType coordinates to HVF coordinates */ + for ( i = 0; i < face->num_axes; i++ ) + { + ((HVFAxisValue*)face->axis_coords)[i] = FT_COORD_TO_HVF_AXIS( ft_coords[i] ); + FT_TRACE5(( "hvf_refresh_axis_coordinates: axis %u: FT coord %ld -> HVF coord %f\n", + i, ft_coords[i], ((HVFAxisValue*)face->axis_coords)[i] )); + } + FT_TRACE3(( "hvf_refresh_axis_coordinates: refreshed %u axis coordinates\n", face->num_axes )); + + /* Clear HVF renderer cache since axis coordinates changed */ + /* Cached parts were rendered with old axis values and are now invalid */ + if ( face->renderer ) + { + HVF_IF_AVAILABLE { + HVF_clear_part_cache( (HVFPartRenderer*)face->renderer ); + face->cache_count = 0; + FT_TRACE4(( "hvf_refresh_axis_coordinates: cleared HVF cache due to axis change\n" )); + } else { + FT_TRACE3(( "hvf_refresh_axis_coordinates: HVF not available at runtime\n" )); + } + } + } + + /* Free temporary FreeType coordinate storage */ + FT_FREE( ft_coords ); + + return error; + } + + /************************************************************************** + * + * @Function: + * hvf_face_init + * + * @Description: + * Initializes a given HVF face object. + * + * @Input: + * stream :: + * The source font stream. + * + * face_index :: + * The index of the font face in the resource. + * + * num_params :: + * Number of additional generic parameters. Ignored. + * + * params :: + * Additional generic parameters. Ignored. + * + * @InOut: + * face :: + * The newly built face object. + * + * @Return: + * FreeType error code. 0 means success. + */ + FT_LOCAL_DEF( FT_Error ) + hvf_face_init( FT_Stream stream, + FT_Face face, + FT_Int typeface_index, + FT_Int num_params, + FT_Parameter* parameters ) + { + FT_Error error; + FT_Library library = FT_FACE_LIBRARY( face ); + SFNT_Service sfnt; + TT_Face tt_face = (TT_Face)face; /* Cast to access TT_Face members */ + + FT_TRACE2(( "HVF driver\n" )); + + /* Check HVF availability at runtime (Apple platforms only) */ + HVF_IF_AVAILABLE { + /* HVF is available - continue with initialization */ + } else { + FT_TRACE2(( "hvf_face_init: HVF not available at runtime\n" )); + return FT_THROW( Unknown_File_Format ); + } + + /* Check for SFNT wrapper */ + sfnt = (SFNT_Service)FT_Get_Module_Interface( library, "sfnt" ); + if ( !sfnt ) + { + FT_ERROR(( "hvf_face_init: cannot access `sfnt' module\n" )); + error = FT_THROW( Missing_Module ); + goto Exit; + } + + /* create input stream from resource */ + if ( FT_STREAM_SEEK( 0 ) ) + goto Exit; + + /* check that we have a valid HVF font */ + FT_TRACE2(( " " )); + error = sfnt->init_face( stream, tt_face, typeface_index, num_params, parameters ); + if ( error ) + { + FT_TRACE1(( "hvf_face_init: sfnt initialization failed (%d)\n", error )); + goto Exit; + } + + /* Verify this is actually an HVF font by checking for hvgl table */ + error = tt_face->goto_table( tt_face, TTAG_hvgl, stream, NULL ); + if ( error ) + { + FT_TRACE1(( "hvf_face_init: not an HVF font (missing 'hvgl' table)\n" )); + error = FT_THROW( Unknown_File_Format ); + goto Exit; + } + + /* Mark face as scalable and set HVF-specific flags */ + face->face_flags |= FT_FACE_FLAG_SCALABLE; + + /* If we are performing a simple font format check, exit immediately. */ + if ( typeface_index < 0 ) + return FT_Err_Ok; + + /* Load font directory */ + error = sfnt->load_face( stream, tt_face, typeface_index, num_params, parameters ); + if ( error ) + goto Exit; + + /* Load HVF-specific tables */ + error = hvf_load_tables( (HVF_Face)face ); + if ( error ) + goto Exit; + + /* Initialize HVF renderer */ + error = hvf_init_renderer( (HVF_Face)face ); + if ( error ) + goto Exit; + + /* Get variation axis information once during face initialization */ + error = hvf_init_axis_coordinates( (HVF_Face)face ); + if ( error ) + goto Exit; + + FT_TRACE2(( "hvf_face_init: HVF face initialized successfully\n" )); + + Exit: + return error; + } + + /************************************************************************** + * + * @Function: + * hvf_face_done + * + * @Description: + * Finalizes a given face object. + * + * @Input: + * face :: + * A pointer to the face object to destroy. + */ + FT_LOCAL_DEF( void ) + hvf_face_done( FT_Face face ) + { + FT_Memory memory; + FT_Stream stream; + TT_Face tt_face; + HVF_Face hvf_face; + SFNT_Service sfnt; + + if ( !face ) + return; + + hvf_face = (HVF_Face)face; + tt_face = (TT_Face)face; + memory = FT_FACE_MEMORY( face ); + stream = FT_FACE_STREAM( face ); + sfnt = (SFNT_Service)tt_face->sfnt; /* Use inherited SFNT service pointer */ + + /* Cleanup HVF renderer */ + if ( hvf_face->renderer ) + { + HVF_IF_AVAILABLE { + HVF_close_part_renderer( (HVFPartRenderer*)hvf_face->renderer ); + } else { + FT_TRACE3(( "hvf_face_done: HVF not available at runtime\n" )); + } + FT_FREE( hvf_face->renderer ); + } + + /* Release axis coordinates storage */ + if ( hvf_face->axis_coords ) + FT_FREE( hvf_face->axis_coords ); + + /* Release memory-mapped table data */ + if ( hvf_face->hvgl_data ) + FT_FRAME_RELEASE( hvf_face->hvgl_data ); + if ( hvf_face->hvpm_data ) + FT_FRAME_RELEASE( hvf_face->hvpm_data ); + + /* Cleanup SFNT (this will clean up the TT_Face base class) */ + if ( sfnt ) + sfnt->done_face( tt_face ); + } + +#endif /* FT_CONFIG_OPTION_HVF */ + +/* END */ diff --git a/src/hvf/hvfobjs.h b/src/hvf/hvfobjs.h new file mode 100644 index 000000000..4818d1b27 --- /dev/null +++ b/src/hvf/hvfobjs.h @@ -0,0 +1,139 @@ +/**************************************************************************** + * + * hvfobjs.h + * + * HVF objects manager (specification). + * + * Copyright (C) 2025 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. + * + */ + +#ifndef HVFOBJS_H_ +#define HVFOBJS_H_ + +#include +#include +#include +#include /* For TT_FaceRec */ + +FT_BEGIN_HEADER + + /************************************************************************** + * + * @type: + * HVF_Face + * + * @description: + * A handle to an HVF face object. Always available for compatibility + * (following SVG module pattern). + */ + typedef struct HVF_FaceRec_* HVF_Face; + + /************************************************************************** + * + * @struct: + * HVF_FaceRec + * + * @description: + * HVF face record. This structure inherits from TT_FaceRec + * instead of FT_FaceRec because HVF fonts are TrueType/OpenType fonts + * with additional HVF tables, and the HVF driver calls into SFNT functions + * that require TT_Face access. + * + * NOTE: Structure is always available (like SVG module) with all fields + * always present. Only the implementation functions are conditionally compiled. + */ + typedef struct HVF_FaceRec_ + { + TT_FaceRec root; /* Inherit from TT_FaceRec */ + + void* renderer; /* HVFPartRenderer storage or NULL */ + + /* HVF-specific table data (memory-mapped) */ + FT_Byte* hvgl_data; /* hvgl table data */ + FT_ULong hvgl_size; + FT_Byte* hvpm_data; /* hvpm table data, NULL if not present */ + FT_ULong hvpm_size; + + /* Cache management */ + FT_UInt cache_count; /* Clear cache every N glyphs */ + + /* Variation axis data (allocated once in hvf_face_init) */ + FT_UInt num_axes; /* Number of variation axes */ + void* axis_coords; /* Pre-converted HVF axis coordinates (HVFAxisValue* when configured) */ + + } HVF_FaceRec; + + /* Conditional declarations - only when HVF is enabled */ +#ifdef FT_CONFIG_OPTION_HVF + +#include + + /* Runtime availability checking for Apple platforms */ +#ifdef HVF_RUNTIME_AVAILABLE + /* Single macro that encapsulates the __builtin_available check for C/C++ */ +#define HVF_IF_AVAILABLE \ + if (__builtin_available(macOS 15.4, iOS 18.4, *)) + +#else /* !HVF_RUNTIME_AVAILABLE */ + /* Non-Apple platforms: No runtime check needed */ +#define HVF_IF_AVAILABLE \ + if (1) + +#endif /* HVF_RUNTIME_AVAILABLE */ + + /************************************************************************** + * + * @struct: + * HVF_RenderContext + * + * @description: + * Context for HVF rendering callbacks. + */ + typedef struct HVF_RenderContext_ + { + FT_GlyphLoader loader; /* Standard FreeType loader */ + FT_Outline* outline; /* Points to loader->current.outline */ + FT_Bool path_begun; /* Path state tracking */ + + HVF_Face face; /* Reference to face */ + + /* Pre-calculated scaling factors for efficient coordinate conversion */ + HVFXYCoord x_scale_fixed; /* Pre-calculated: x_scale * 65536.0 (or just 65536.0) */ + HVFXYCoord y_scale_fixed; /* Pre-calculated: y_scale * 65536.0 (or just 65536.0) */ + + } HVF_RenderContext; + + /* Function declarations */ + FT_LOCAL( FT_Error ) + hvf_face_init( FT_Stream stream, + FT_Face face, + FT_Int typeface_index, + FT_Int num_params, + FT_Parameter* parameters ); + + FT_LOCAL( void ) + hvf_face_done( FT_Face face ); + + FT_LOCAL( FT_Error ) + hvf_refresh_axis_coordinates( HVF_Face face ); + + /* Convert FreeType normalized coordinates to HVF axis values */ +#define FT_COORD_TO_HVF_AXIS( coord ) \ + ( (HVFAxisValue)(coord) / 65536.0 ) + +#endif /* FT_CONFIG_OPTION_HVF */ + +FT_END_HEADER + +#endif /* HVFOBJS_H_ */ + +/* END */ diff --git a/src/hvf/module.mk b/src/hvf/module.mk new file mode 100644 index 000000000..181530c6f --- /dev/null +++ b/src/hvf/module.mk @@ -0,0 +1,22 @@ +# +# FreeType 2 HVF module definition +# + +# Copyright (C) 2025 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. + +FTMODULE_H_COMMANDS += HVF_DRIVER + +define HVF_DRIVER +$(OPEN_DRIVER) FT_Driver_ClassRec, hvf_driver_class $(CLOSE_DRIVER) +$(ECHO_DRIVER)hvf $(ECHO_DRIVER_DESC)Apple HVF fonts$(ECHO_DRIVER_DONE) +endef + +# EOF diff --git a/src/hvf/rules.mk b/src/hvf/rules.mk new file mode 100644 index 000000000..5d3b38150 --- /dev/null +++ b/src/hvf/rules.mk @@ -0,0 +1,66 @@ +# +# FreeType 2 HVF driver configuration rules +# + +# Copyright (C) 2025 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. + +# HVF driver directory +# +HVF_DIR := $(SRC_DIR)/hvf + +# compilation flags for the driver +# +HVF_COMPILE := $(CC) $(ANSIFLAGS) \ + $I$(subst /,$(COMPILER_SEP),$(HVF_DIR)) \ + $(INCLUDE_FLAGS) \ + $(FT_CFLAGS) + +# HVF driver sources (i.e., C files) +# +HVF_DRV_SRC := $(HVF_DIR)/hvfdrv.c \ + $(HVF_DIR)/hvfload.c \ + $(HVF_DIR)/hvfobjs.c + +# HVF driver headers +# +HVF_DRV_H := $(HVF_DIR)/hvfdrv.h \ + $(HVF_DIR)/hvfload.h \ + $(HVF_DIR)/hvfobjs.h \ + $(HVF_DIR)/hvferror.h + +# HVF driver object(s) +# +# HVF_DRV_OBJ_M is used during `multi' builds +# HVF_DRV_OBJ_S is used during `single' builds +# +HVF_DRV_OBJ_M := $(HVF_DRV_SRC:$(HVF_DIR)/%.c=$(OBJ_DIR)/%.$O) +HVF_DRV_OBJ_S := $(OBJ_DIR)/hvf.$O + +# HVF driver source file for single build +# +HVF_DRV_SRC_S := $(HVF_DIR)/hvf.c + +# HVF driver - single object +# +$(HVF_DRV_OBJ_S): $(HVF_DRV_SRC_S) $(HVF_DRV_SRC) $(FREETYPE_H) $(HVF_DRV_H) + $(HVF_COMPILE) $T$(subst /,$(COMPILER_SEP),$@ $(HVF_DRV_SRC_S)) + +# HVF driver - multiple objects +# +$(OBJ_DIR)/%.$O: $(HVF_DIR)/%.c $(FREETYPE_H) $(HVF_DRV_H) + $(HVF_COMPILE) $T$(subst /,$(COMPILER_SEP),$@ $<) + +# update main driver object lists +# +DRV_OBJS_S += $(HVF_DRV_OBJ_S) +DRV_OBJS_M += $(HVF_DRV_OBJ_M) + +# EOF diff --git a/src/sfnt/sfobjs.c b/src/sfnt/sfobjs.c index 6af35787e..acf79e5cd 100644 --- a/src/sfnt/sfobjs.c +++ b/src/sfnt/sfobjs.c @@ -723,6 +723,7 @@ /* note that `glyf' or `CFF2' have precedence */ if ( face->goto_table( face, TTAG_glyf, stream, 0 ) && face->goto_table( face, TTAG_CFF2, stream, 0 ) && + face->goto_table( face, TTAG_hvgl, stream, 0 ) && !face->goto_table( face, TTAG_CFF, stream, 0 ) ) num_instances = 0; diff --git a/src/truetype/ttobjs.c b/src/truetype/ttobjs.c index 183c8949c..77f222db7 100644 --- a/src/truetype/ttobjs.c +++ b/src/truetype/ttobjs.c @@ -734,6 +734,9 @@ /* a `loca' table is not valid */ if ( face->glyf_len && FT_ERR_EQ( error, Table_Missing ) ) goto Exit; + /* if both glyf and loca tables are missing, not a valid file */ + if ( face->glyf_len == 0 && FT_ERR_EQ( error, Locations_Missing) ) + goto Bad_Format; if ( error ) goto Exit; }