Merge pull request #5131 from harfbuzz/using2

Add hb_ft_face_create_from_blob_or_fail() et al
This commit is contained in:
Behdad Esfahbod 2025-03-11 13:37:48 -06:00 committed by GitHub
commit d014efd03d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 411 additions and 73 deletions

View file

@ -347,6 +347,7 @@ HB_CORETEXT_TAG_MORT
HB_CORETEXT_TAG_MORX
hb_coretext_face_create
hb_coretext_face_create_from_file_or_fail
hb_coretext_face_create_from_blob_or_fail
hb_coretext_font_create
hb_coretext_face_get_cg_font
hb_coretext_font_get_ct_font
@ -367,6 +368,7 @@ hb_face_count
hb_face_t
hb_face_create
hb_face_create_or_fail
hb_face_create_or_fail_using
hb_face_create_from_file_or_fail
hb_face_create_from_file_or_fail_using
hb_face_list_loaders
@ -536,6 +538,7 @@ hb_ft_face_create
hb_ft_face_create_cached
hb_ft_face_create_referenced
hb_ft_face_create_from_file_or_fail
hb_ft_face_create_from_blob_or_fail
hb_ft_font_create
hb_ft_font_create_referenced
hb_ft_font_changed

View file

@ -160,30 +160,68 @@ release_data (void *info, const void *data, size_t size)
hb_blob_destroy ((hb_blob_t *) info);
}
static CGFontRef
create_cg_font (CFArrayRef ct_font_desc_array, unsigned int index)
{
auto ct_font_desc = (CFArrayGetCount (ct_font_desc_array) > index) ?
(CTFontDescriptorRef) CFArrayGetValueAtIndex (ct_font_desc_array, index) : nullptr;
if (unlikely (!ct_font_desc))
{
CFRelease (ct_font_desc_array);
return nullptr;
}
auto ct_font = ct_font_desc ? CTFontCreateWithFontDescriptor (ct_font_desc, 0, nullptr) : nullptr;
CFRelease (ct_font_desc_array);
if (unlikely (!ct_font))
return nullptr;
auto cg_font = ct_font ? CTFontCopyGraphicsFont (ct_font, nullptr) : nullptr;
CFRelease (ct_font);
return cg_font;
}
static CGFontRef
create_cg_font (hb_blob_t *blob, unsigned int index)
{
hb_blob_make_immutable (blob);
unsigned int blob_length;
const char *blob_data = hb_blob_get_data (blob, &blob_length);
if (unlikely (!blob_length))
DEBUG_MSG (CORETEXT, blob, "Empty blob");
if (unlikely (index != 0))
{
auto ct_font_desc_array = CTFontManagerCreateFontDescriptorsFromData (CFDataCreate (kCFAllocatorDefault, (const UInt8 *) blob_data, blob_length));
if (unlikely (!ct_font_desc_array))
return nullptr;
return create_cg_font (ct_font_desc_array, index);
}
hb_blob_reference (blob);
CGDataProviderRef provider = CGDataProviderCreateWithData (blob, blob_data, blob_length, &release_data);
CGFontRef cg_font = nullptr;
if (likely (provider))
{
cg_font = CGFontCreateWithDataProvider (provider);
if (unlikely (!cg_font))
DEBUG_MSG (CORETEXT, blob, "CGFontCreateWithDataProvider() failed");
CGDataProviderRelease (provider);
}
return cg_font;
}
static CGFontRef
create_cg_font (hb_face_t *face)
{
CGFontRef cg_font = nullptr;
if (face->destroy == _hb_cg_font_release)
{
cg_font = CGFontRetain ((CGFontRef) face->user_data);
}
else
{
hb_blob_t *blob = hb_face_reference_blob (face);
unsigned int blob_length;
const char *blob_data = hb_blob_get_data (blob, &blob_length);
if (unlikely (!blob_length))
DEBUG_MSG (CORETEXT, face, "Face has empty blob");
CGDataProviderRef provider = CGDataProviderCreateWithData (blob, blob_data, blob_length, &release_data);
if (likely (provider))
{
cg_font = CGFontCreateWithDataProvider (provider);
if (unlikely (!cg_font))
DEBUG_MSG (CORETEXT, face, "Face CGFontCreateWithDataProvider() failed");
CGDataProviderRelease (provider);
}
cg_font = create_cg_font (blob, face->index);
hb_blob_destroy (blob);
}
return cg_font;
}
@ -377,22 +415,39 @@ hb_coretext_face_create_from_file_or_fail (const char *file_name,
CFRelease (url);
return nullptr;
}
auto ct_font_desc = (CFArrayGetCount (ct_font_desc_array) > index) ?
(CTFontDescriptorRef) CFArrayGetValueAtIndex (ct_font_desc_array, index) : nullptr;
if (unlikely (!ct_font_desc))
{
CFRelease (ct_font_desc_array);
CFRelease (url);
return nullptr;
}
auto cg_font = create_cg_font (ct_font_desc_array, index);
CFRelease (url);
auto ct_font = ct_font_desc ? CTFontCreateWithFontDescriptor (ct_font_desc, 0, nullptr) : nullptr;
CFRelease (ct_font_desc_array);
if (unlikely (!ct_font))
hb_face_t *face = hb_coretext_face_create (cg_font);
CFRelease (cg_font);
if (unlikely (hb_face_is_immutable (face)))
return nullptr;
auto cg_font = ct_font ? CTFontCopyGraphicsFont (ct_font, nullptr) : nullptr;
CFRelease (ct_font);
return face;
}
/**
* hb_coretext_face_create_from_blob_or_fail:
* @blob: A blob containing the font data
* @index: The index of the face within the blob
*
* Creates an #hb_face_t face object from the specified
* blob and face index.
*
* This is similar in functionality to hb_face_create_from_blob_or_fail(),
* but uses the CoreText library for loading the font data.
*
* Return value: (transfer full): The new face object, or `NULL` if
* no face is found at the specified index or the blob cannot be read.
*
* XSince: REPLACEME
*/
hb_face_t *
hb_coretext_face_create_from_blob_or_fail (hb_blob_t *blob,
unsigned int index)
{
auto cg_font = create_cg_font (blob, index);
if (unlikely (!cg_font))
return nullptr;

View file

@ -84,6 +84,10 @@ HB_EXTERN hb_face_t *
hb_coretext_face_create_from_file_or_fail (const char *file_name,
unsigned int index);
HB_EXTERN hb_face_t *
hb_coretext_face_create_from_blob_or_fail (hb_blob_t *blob,
unsigned int index);
HB_EXTERN hb_font_t *
hb_coretext_font_create (CTFontRef ct_font);

View file

@ -79,14 +79,14 @@ hb_face_count (hb_blob_t *blob)
if (unlikely (!blob))
return 0;
/* TODO We shouldn't be sanitizing blob. Port to run sanitizer and return if not sane. */
/* Make API signature const after. */
hb_blob_t *sanitized = hb_sanitize_context_t ().sanitize_blob<OT::OpenTypeFontFile> (hb_blob_reference (blob));
const OT::OpenTypeFontFile& ot = *sanitized->as<OT::OpenTypeFontFile> ();
unsigned int ret = ot.get_face_count ();
hb_blob_destroy (sanitized);
hb_sanitize_context_t c (blob);
return ret;
const char *start = hb_blob_get_data (blob, nullptr);
auto *ot = reinterpret_cast<OT::OpenTypeFontFile *> (const_cast<char *> (start));
if (unlikely (!ot->sanitize (&c)))
return 0;
return ot->get_face_count ();
}
/*
@ -328,20 +328,47 @@ hb_face_create_from_file_or_fail (const char *file_name,
static struct supported_face_loaders_t {
char name[9];
hb_face_t * (*func) (const char *font_file, unsigned face_index);
hb_face_t * (*from_file) (const char *font_file, unsigned face_index);
hb_face_t * (*from_blob) (hb_blob_t *blob, unsigned face_index);
} supported_face_loaders[] =
{
{"ot",
#ifndef HB_NO_OPEN
{"ot", hb_face_create_from_file_or_fail},
hb_face_create_from_file_or_fail,
#else
nullptr,
#endif
hb_face_create_or_fail
},
#ifdef HAVE_FREETYPE
{"ft", hb_ft_face_create_from_file_or_fail},
{"ft",
hb_ft_face_create_from_file_or_fail,
hb_ft_face_create_from_blob_or_fail
},
#endif
#ifdef HAVE_CORETEXT
{"coretext", hb_coretext_face_create_from_file_or_fail},
{"coretext",
hb_coretext_face_create_from_file_or_fail,
hb_coretext_face_create_from_blob_or_fail
},
#endif
};
static const char *get_default_loader_name ()
{
static hb_atomic_ptr_t<const char> static_loader_name;
const char *loader_name = static_loader_name.get_acquire ();
if (!loader_name)
{
loader_name = getenv ("HB_FACE_LOADER");
if (!loader_name)
loader_name = "";
if (!static_loader_name.cmpexch (nullptr, loader_name))
loader_name = static_loader_name.get_acquire ();
}
return loader_name;
}
/**
* hb_face_create_from_file_or_fail_using:
* @file_name: A font filename
@ -353,7 +380,7 @@ static struct supported_face_loaders_t {
* is used.
*
* For example, the FreeType ("ft") loader might be able to load
* .woff and .woff2 files if FreeType is built with those features,
* WOFF and WOFF2 files if FreeType is built with those features,
* whereas the OpenType ("ot") loader will not.
*
* Return value: (transfer full): The new face object, or `NULL` if
@ -366,20 +393,11 @@ hb_face_create_from_file_or_fail_using (const char *file_name,
unsigned int index,
const char *loader_name)
{
// Duplicated in hb_face_create_or_fail_using
bool retry = false;
if (!loader_name || !*loader_name)
{
static hb_atomic_ptr_t<const char> static_funcs_name;
loader_name = static_funcs_name.get_acquire ();
if (!loader_name)
{
loader_name = getenv ("HB_FACE_LOADER");
if (!loader_name)
loader_name = "";
if (!static_funcs_name.cmpexch (nullptr, loader_name))
loader_name = static_funcs_name.get_acquire ();
}
loader_name = get_default_loader_name ();
retry = true;
}
if (loader_name && !*loader_name) loader_name = nullptr;
@ -387,8 +405,58 @@ hb_face_create_from_file_or_fail_using (const char *file_name,
retry:
for (unsigned i = 0; i < ARRAY_LENGTH (supported_face_loaders); i++)
{
if (!loader_name || !strcmp (supported_face_loaders[i].name, loader_name))
return supported_face_loaders[i].func (file_name, index);
if (!loader_name || (supported_face_loaders[i].from_file && !strcmp (supported_face_loaders[i].name, loader_name)))
return supported_face_loaders[i].from_file (file_name, index);
}
if (retry)
{
retry = false;
loader_name = nullptr;
goto retry;
}
return nullptr;
}
/**
* hb_face_create_or_fail_using:
* @blob: #hb_blob_t to work upon
* @index: The index of the face within @blob
* @loader_name: (nullable): The name of the loader to use, or `NULL`
*
* A thin wrapper around the face loader functions registered with HarfBuzz.
* If @loader_name is `NULL` or the empty string, the first available loader
* is used.
*
* For example, the FreeType ("ft") loader might be able to load
* WOFF and WOFF2 files if FreeType is built with those features,
* whereas the OpenType ("ot") loader will not.
*
* Return value: (transfer full): The new face object, or `NULL` if
* the loader fails to load the face.
*
* XSince: REPLACEME
**/
hb_face_t *
hb_face_create_or_fail_using (hb_blob_t *blob,
unsigned int index,
const char *loader_name)
{
// Duplicated in hb_face_create_from_file_or_fail_using
bool retry = false;
if (!loader_name || !*loader_name)
{
loader_name = get_default_loader_name ();
retry = true;
}
if (loader_name && !*loader_name) loader_name = nullptr;
retry:
for (unsigned i = 0; i < ARRAY_LENGTH (supported_face_loaders); i++)
{
if (!loader_name || (supported_face_loaders[i].from_blob && !strcmp (supported_face_loaders[i].name, loader_name)))
return supported_face_loaders[i].from_blob (blob, index);
}
if (retry)

View file

@ -63,6 +63,11 @@ HB_EXTERN hb_face_t *
hb_face_create_or_fail (hb_blob_t *blob,
unsigned int index);
HB_EXTERN hb_face_t *
hb_face_create_or_fail_using (hb_blob_t *blob,
unsigned int index,
const char *loader_name);
HB_EXTERN hb_face_t *
hb_face_create_from_file_or_fail (const char *file_name,
unsigned int index);

View file

@ -2321,6 +2321,21 @@ static struct supported_font_funcs_t {
#endif
};
static const char *get_default_funcs_name ()
{
static hb_atomic_ptr_t<const char> static_funcs_name;
const char *name = static_funcs_name.get_acquire ();
if (!name)
{
name = getenv ("HB_FONT_FUNCS");
if (!name)
name = "";
if (!static_funcs_name.cmpexch (nullptr, name))
name = static_funcs_name.get_acquire ();
}
return name;
}
/**
* hb_font_set_funcs_using:
* @font: #hb_font_t to work upon
@ -2345,16 +2360,7 @@ hb_font_set_funcs_using (hb_font_t *font,
if (!name || !*name)
{
static hb_atomic_ptr_t<const char> static_funcs_name;
name = static_funcs_name.get_acquire ();
if (!name)
{
name = getenv ("HB_FONT_FUNCS");
if (!name)
name = "";
if (!static_funcs_name.cmpexch (nullptr, name))
name = static_funcs_name.get_acquire ();
}
name = get_default_funcs_name ();
retry = true;
}
if (name && !*name) name = nullptr;

View file

@ -1610,7 +1610,8 @@ destroy_ft_library (void *arg)
* font file and face index.
*
* This is similar in functionality to hb_face_create_from_file_or_fail(),
* but uses the FreeType library for loading the font file.
* but uses the FreeType library for loading the font file. This can
* be useful, for example, to load WOFF and WOFF2 font data.
*
* Return value: (transfer full): The new face object, or `NULL` if
* no face is found at the specified index or the file cannot be read.
@ -1647,6 +1648,75 @@ hb_ft_face_create_from_file_or_fail (const char *file_name,
return face;
}
static hb_user_data_key_t ft_blob_key = {0};
static void
_destroy_blob (void *p)
{
hb_blob_destroy ((hb_blob_t *) p);
}
/**
* hb_ft_face_create_from_blob_or_fail:
* @blob: A blob
* @index: The index of the face within the blob
*
* Creates an #hb_face_t face object from the specified
* font blob and face index.
*
* This is similar in functionality to hb_face_create_from_blob_or_fail(),
* but uses the FreeType library for loading the font blob. This can
* be useful, for example, to load WOFF and WOFF2 font data.
*
* Return value: (transfer full): The new face object, or `NULL` if
* loading fails (eg. blob does not contain valid font data).
*
* XSince: REPLACEME
*/
hb_face_t *
hb_ft_face_create_from_blob_or_fail (hb_blob_t *blob,
unsigned int index)
{
FT_Library ft_library = reference_ft_library ();
if (unlikely (!ft_library))
{
DEBUG_MSG (FT, ft_library, "reference_ft_library failed");
return nullptr;
}
hb_blob_make_immutable (blob);
unsigned blob_size;
const char *blob_data = hb_blob_get_data (blob, &blob_size);
FT_Face ft_face;
if (unlikely (FT_New_Memory_Face (ft_library,
(const FT_Byte *) blob_data,
blob_size,
index,
&ft_face)))
return nullptr;
hb_face_t *face = hb_ft_face_create_referenced (ft_face);
FT_Done_Face (ft_face);
ft_face->generic.data = ft_library;
ft_face->generic.finalizer = finalize_ft_library;
if (hb_face_is_immutable (face))
return nullptr;
// Hook the blob to the hb_face_t, since FT_Face still needs it.
hb_blob_reference (blob);
if (!hb_face_set_user_data (face, &ft_blob_key, blob, _destroy_blob, true))
{
hb_blob_destroy (blob);
hb_face_destroy (face);
return nullptr;
}
return face;
}
static void
_release_blob (void *arg)
{

View file

@ -88,6 +88,10 @@ HB_EXTERN hb_face_t *
hb_ft_face_create_from_file_or_fail (const char *file_name,
unsigned int index);
HB_EXTERN hb_face_t *
hb_ft_face_create_from_blob_or_fail (hb_blob_t *blob,
unsigned int index);
/*
* hb-font from ft-face.
*/

View file

@ -241,9 +241,14 @@ hb_test_add_data_func_flavor (const char *test_path,
gconstpointer test_data,
hb_test_data_func_t test_func)
{
char *path = g_strdup_printf ("%s/%s", test_path, flavor);
hb_test_add_data_func (path, test_data, test_func);
g_free (path);
if (flavor && *flavor)
{
char *path = g_strdup_printf ("%s/%s", test_path, flavor);
hb_test_add_data_func (path, test_data, test_func);
g_free (path);
}
else
hb_test_add_data_func (test_path, test_data, test_func);
}
#define hb_test_add_data_flavor(UserData, Flavor, Func) hb_test_add_data_func_flavor (#Func, Flavor, UserData, Func)
@ -305,27 +310,38 @@ G_STMT_START { \
} G_STMT_END
static inline hb_face_t *
hb_test_open_font_file (const char *font_path)
static inline char *
hb_test_resolve_path (const char *path)
{
#if GLIB_CHECK_VERSION(2,37,2)
char *path = g_test_build_filename (G_TEST_DIST, font_path, NULL);
#else
char *path = g_strdup (font_path);
if (path[0] != '/')
return g_test_build_filename (G_TEST_DIST, path, NULL);
#endif
return g_strdup (path);
}
static inline hb_face_t *
hb_test_open_font_file_with_index (const char *font_path, unsigned face_index)
{
char *path = hb_test_resolve_path (font_path);
hb_blob_t *blob = hb_blob_create_from_file_or_fail (path);
hb_face_t *face;
if (!blob)
g_error ("Font %s not found.", path);
face = hb_face_create (blob, 0);
face = hb_face_create (blob, face_index);
hb_blob_destroy (blob);
g_free (path);
return face;
}
static inline hb_face_t *
hb_test_open_font_file (const char *font_path)
{
return hb_test_open_font_file_with_index (font_path, 0);
}
HB_END_DECLS

View file

@ -18,6 +18,7 @@ tests = [
'test-draw.c',
'test-draw-varc.c',
'test-extents.c',
'test-face.c',
'test-font.c',
'test-font-scale.c',
'test-get-table-tags.c',

106
test/api/test-face.c Normal file
View file

@ -0,0 +1,106 @@
/*
* This is part of HarfBuzz, a text shaping library.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and its documentation for any purpose, provided that the
* above copyright notice and the following two paragraphs appear in
* all copies of this software.
*
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
* Author(s): Behdad Esfahbod
*/
#include "hb-test.h"
/* Unit tests for hb-face.h */
#define FONT_FILE "fonts/Roboto-Regular.ac.ttf"
static const char *font_file = NULL;
static unsigned int face_index = 0;
hb_face_t *master_face = NULL;
#define HEAD_TAG HB_TAG ('h', 'e', 'a', 'd')
hb_blob_t *master_head = NULL;
static void
test_face (hb_face_t *face)
{
g_assert_nonnull (face);
hb_blob_t *head = hb_face_reference_table (face, HEAD_TAG);
unsigned int length;
unsigned int master_length;
const char *data = hb_blob_get_data (head, &length);
const char *master_data = hb_blob_get_data (master_head, &master_length);
g_assert_cmpmem (data, length, master_data, master_length);
hb_blob_destroy (head);
}
static void
test_create_from_file_using (const void *user_data)
{
const char *loader = user_data;
hb_face_t *face = hb_face_create_from_file_or_fail_using (font_file, face_index, loader);
test_face (face);
hb_face_destroy (face);
}
static void
test_create_from_blob_using (const void *user_data)
{
const char *loader = user_data;
hb_blob_t *blob = hb_blob_create_from_file_or_fail (font_file);
hb_face_t *face = hb_face_create_or_fail_using (blob, face_index, loader);
hb_blob_destroy (blob);
test_face (face);
hb_face_destroy (face);
}
int
main (int argc, char **argv)
{
hb_test_init (&argc, &argv);
font_file = FONT_FILE;
if (argc > 1)
font_file = argv[1];
if (argc > 2)
face_index = atoi (argv[2]);
master_face = hb_test_open_font_file_with_index (font_file, face_index);
master_head = hb_face_reference_table (master_face, HEAD_TAG);
g_assert (hb_blob_get_length (master_head) > 0);
font_file = hb_test_resolve_path (font_file);
hb_test_add_flavor ("", test_create_from_file_using);
hb_test_add_flavor ("", test_create_from_blob_using);
for (const char **loaders = hb_face_list_loaders (); *loaders; loaders++)
{
hb_test_add_flavor (*loaders, test_create_from_file_using);
hb_test_add_flavor (*loaders, test_create_from_blob_using);
}
int ret = hb_test_run();
hb_blob_destroy (master_head);
hb_face_destroy (master_face);
g_free ((char *) font_file);
return ret;
}