diff --git a/docs/harfbuzz-sections.txt b/docs/harfbuzz-sections.txt index 7082a55e5..ab966de36 100644 --- a/docs/harfbuzz-sections.txt +++ b/docs/harfbuzz-sections.txt @@ -354,6 +354,7 @@ hb_directwrite_face_get_font_face hb_face_count hb_face_t hb_face_create +hb_reference_table_func_t hb_face_create_for_tables hb_face_get_empty hb_face_reference @@ -362,6 +363,8 @@ hb_face_set_user_data hb_face_get_user_data hb_face_make_immutable hb_face_is_immutable +hb_get_table_tags_func_t +hb_face_set_get_table_tags_func hb_face_get_table_tags hb_face_set_glyph_count hb_face_get_glyph_count @@ -491,7 +494,6 @@ hb_font_get_variation_glyph_func_t hb_font_funcs_set_variation_glyph_func hb_font_funcs_t hb_font_t -hb_reference_table_func_t hb_font_get_font_extents_func_t hb_font_get_font_h_extents_func_t hb_font_funcs_set_font_h_extents_func diff --git a/src/hb-coretext.cc b/src/hb-coretext.cc index a87cb5cd0..070b8be02 100644 --- a/src/hb-coretext.cc +++ b/src/hb-coretext.cc @@ -48,6 +48,8 @@ /* https://developer.apple.com/documentation/coretext/1508745-ctfontcreatewithgraphicsfont */ #define HB_CORETEXT_DEFAULT_FONT_SIZE 12.f +static CTFontRef create_ct_font (CGFontRef cg_font, CGFloat font_size); + static void release_table_data (void *user_data) { @@ -76,6 +78,52 @@ _hb_cg_reference_table (hb_face_t *face HB_UNUSED, hb_tag_t tag, void *user_data release_table_data); } +static unsigned +_hb_cg_get_table_tags (const hb_face_t *face HB_UNUSED, + unsigned int start_offset, + unsigned int *table_count, + hb_tag_t *table_tags, + void *user_data) +{ + CGFontRef cg_font = reinterpret_cast (user_data); + + CTFontRef ct_font = create_ct_font (cg_font, (CGFloat) HB_CORETEXT_DEFAULT_FONT_SIZE); + + auto arr = CTFontCopyAvailableTables (ct_font, kCTFontTableOptionNoOptions); + + unsigned population = (unsigned) CFArrayGetCount (arr); + unsigned end_offset; + + if (!table_count) + goto done; + + if (unlikely (start_offset >= population)) + { + *table_count = 0; + goto done; + } + + end_offset = start_offset + *table_count; + if (unlikely (end_offset < start_offset)) + { + *table_count = 0; + goto done; + } + end_offset= hb_min (end_offset, (unsigned) population); + + *table_count = end_offset - start_offset; + for (unsigned i = start_offset; i < end_offset; i++) + { + CTFontTableTag tag = (CTFontTableTag)(uintptr_t) CFArrayGetValueAtIndex (arr, i); + table_tags[i - start_offset] = tag; + } + +done: + CFRelease (arr); + CFRelease (ct_font); + return population; +} + static void _hb_cg_font_release (void *data) { @@ -294,7 +342,9 @@ _hb_coretext_shaper_face_data_destroy (hb_coretext_face_data_t *data) hb_face_t * hb_coretext_face_create (CGFontRef cg_font) { - return hb_face_create_for_tables (_hb_cg_reference_table, CGFontRetain (cg_font), _hb_cg_font_release); + hb_face_t *face = hb_face_create_for_tables (_hb_cg_reference_table, CGFontRetain (cg_font), _hb_cg_font_release); + hb_face_set_get_table_tags_func (face, _hb_cg_get_table_tags, cg_font, nullptr); + return face; } /** diff --git a/src/hb-face-builder.cc b/src/hb-face-builder.cc index 84b14d28d..beea89ed2 100644 --- a/src/hb-face-builder.cc +++ b/src/hb-face-builder.cc @@ -42,7 +42,7 @@ struct face_table_info_t { hb_blob_t* data; - signed order; + unsigned order; }; struct hb_face_builder_data_t @@ -153,6 +153,50 @@ _hb_face_builder_reference_table (hb_face_t *face HB_UNUSED, hb_tag_t tag, void return hb_blob_reference (data->tables[tag].data); } +static unsigned +_hb_face_builder_get_table_tags (const hb_face_t *face HB_UNUSED, + unsigned int start_offset, + unsigned int *table_count, + hb_tag_t *table_tags, + void *user_data) +{ + hb_face_builder_data_t *data = (hb_face_builder_data_t *) user_data; + + unsigned population = data->tables.get_population (); + + if (!table_count) + return population; + + if (unlikely (start_offset >= population)) + { + if (table_count) + *table_count = 0; + return population; + } + + // Sort the tags. + hb_vector_t sorted_tags; + data->tables.keys () | hb_sink (sorted_tags); + if (unlikely (sorted_tags.in_error ())) + { + // Not much to do... + } + sorted_tags.qsort ([] (const void* a, const void* b) { + return * (hb_tag_t *) a < * (hb_tag_t *) b ? -1 : + * (hb_tag_t *) a == * (hb_tag_t *) b ? 0 : + +1; + }); + + auto array = sorted_tags.as_array ().sub_array (start_offset, table_count); + auto out = hb_array (table_tags, *table_count); + + + array.iter () + | hb_sink (out) + ; + + return population; +} + /** * hb_face_builder_create: @@ -171,9 +215,16 @@ hb_face_builder_create () hb_face_builder_data_t *data = _hb_face_builder_data_create (); if (unlikely (!data)) return hb_face_get_empty (); - return hb_face_create_for_tables (_hb_face_builder_reference_table, - data, - _hb_face_builder_data_destroy); + hb_face_t *face = hb_face_create_for_tables (_hb_face_builder_reference_table, + data, + _hb_face_builder_data_destroy); + + hb_face_set_get_table_tags_func (face, + _hb_face_builder_get_table_tags, + data, + nullptr); + + return face; } /** @@ -199,7 +250,7 @@ hb_face_builder_add_table (hb_face_t *face, hb_tag_t tag, hb_blob_t *blob) hb_face_builder_data_t *data = (hb_face_builder_data_t *) face->user_data; hb_blob_t* previous = data->tables.get (tag).data; - if (!data->tables.set (tag, face_table_info_t {hb_blob_reference (blob), -1})) + if (!data->tables.set (tag, face_table_info_t {hb_blob_reference (blob), (unsigned) -1})) { hb_blob_destroy (blob); return false; diff --git a/src/hb-face.cc b/src/hb-face.cc index e34071058..63a9259db 100644 --- a/src/hb-face.cc +++ b/src/hb-face.cc @@ -90,10 +90,6 @@ DEFINE_NULL_INSTANCE (hb_face_t) = { HB_OBJECT_HEADER_STATIC, - nullptr, /* reference_table_func */ - nullptr, /* user_data */ - nullptr, /* destroy */ - 0, /* index */ 1000, /* upem */ 0, /* num_glyphs */ @@ -110,8 +106,9 @@ DEFINE_NULL_INSTANCE (hb_face_t) = * * Variant of hb_face_create(), built for those cases where it is more * convenient to provide data for individual tables instead of the whole font - * data. With the caveat that hb_face_get_table_tags() does not currently work - * with faces created this way. + * data. With the caveat that hb_face_get_table_tags() would not work + * with faces created this way. You can address that by calling the + * hb_face_set_get_table_tags_func() function and setting the appropriate callback. * * Creates a new face object from the specified @user_data and @reference_table_func, * with the @destroy callback. @@ -194,6 +191,22 @@ _hb_face_for_data_reference_table (hb_face_t *face HB_UNUSED, hb_tag_t tag, void return blob; } +static unsigned +_hb_face_for_data_get_table_tags (const hb_face_t *face HB_UNUSED, + unsigned int start_offset, + unsigned int *table_count, + hb_tag_t *table_tags, + void *user_data) +{ + hb_face_for_data_closure_t *data = (hb_face_for_data_closure_t *) user_data; + + const OT::OpenTypeFontFile &ot_file = *data->blob->as (); + const OT::OpenTypeFontFace &ot_face = ot_file.get_face (data->index); + + return ot_face.get_table_tags (start_offset, table_count, table_tags); +} + + /** * hb_face_create: * @blob: #hb_blob_t to work upon @@ -240,6 +253,10 @@ hb_face_create (hb_blob_t *blob, face = hb_face_create_for_tables (_hb_face_for_data_reference_table, closure, _hb_face_for_data_closure_destroy); + hb_face_set_get_table_tags_func (face, + _hb_face_for_data_get_table_tags, + closure, + nullptr); face->index = index; @@ -306,6 +323,9 @@ hb_face_destroy (hb_face_t *face) face->data.fini (); face->table.fini (); + if (face->get_table_tags_destroy) + face->get_table_tags_destroy (face->get_table_tags_user_data); + if (face->destroy) face->destroy (face->user_data); @@ -547,6 +567,37 @@ hb_face_get_glyph_count (const hb_face_t *face) return face->get_num_glyphs (); } +/** + * hb_face_set_get_table_tags_func: + * @face: A face object + * @func: (closure user_data) (destroy destroy) (scope notified): The table-tag-fetching function + * @user_data: A pointer to the user data, to be destroyed by @destroy when not needed anymore + * @destroy: (nullable): A callback to call when @func is not needed anymore + * + * Sets the table-tag-fetching function for the specified face object. + * + * XSince: REPLACEME + */ +HB_EXTERN void +hb_face_set_get_table_tags_func (hb_face_t *face, + hb_get_table_tags_func_t func, + void *user_data, + hb_destroy_func_t destroy) +{ + if (hb_object_is_immutable (face)) + { + if (destroy) + destroy (user_data); + } + + if (face->get_table_tags_destroy) + face->get_table_tags_destroy (face->get_table_tags_user_data); + + face->get_table_tags_func = func; + face->get_table_tags_user_data = user_data; + face->get_table_tags_destroy = destroy; +} + /** * hb_face_get_table_tags: * @face: A face object @@ -568,19 +619,14 @@ hb_face_get_table_tags (const hb_face_t *face, unsigned int *table_count, /* IN/OUT */ hb_tag_t *table_tags /* OUT */) { - if (face->destroy != (hb_destroy_func_t) _hb_face_for_data_closure_destroy) + if (!face->get_table_tags_func) { if (table_count) *table_count = 0; return 0; } - hb_face_for_data_closure_t *data = (hb_face_for_data_closure_t *) face->user_data; - - const OT::OpenTypeFontFile &ot_file = *data->blob->as (); - const OT::OpenTypeFontFace &ot_face = ot_file.get_face (data->index); - - return ot_face.get_table_tags (start_offset, table_count, table_tags); + return face->get_table_tags_func (face, start_offset, table_count, table_tags, face->get_table_tags_user_data); } diff --git a/src/hb-face.h b/src/hb-face.h index 2e54ccf13..53406cd7c 100644 --- a/src/hb-face.h +++ b/src/hb-face.h @@ -135,6 +135,34 @@ hb_face_set_glyph_count (hb_face_t *face, HB_EXTERN unsigned int hb_face_get_glyph_count (const hb_face_t *face); + +/** + * hb_get_table_tags_func_t: + * @face: A face object + * @start_offset: The index of first table tag to retrieve + * @table_count: (inout): Input = the maximum number of table tags to return; + * Output = the actual number of table tags returned (may be zero) + * @table_tags: (out) (array length=table_count): The array of table tags found + * @user_data: User data pointer passed by the caller + * + * Callback function for hb_face_get_table_tags(). + * + * Return value: Total number of tables, or zero if it is not possible to list + * + * XSince: REPLACEME + */ +typedef unsigned int (*hb_get_table_tags_func_t) (const hb_face_t *face, + unsigned int start_offset, + unsigned int *table_count, /* IN/OUT */ + hb_tag_t *table_tags /* OUT */, + void *user_data); + +HB_EXTERN void +hb_face_set_get_table_tags_func (hb_face_t *face, + hb_get_table_tags_func_t func, + void *user_data, + hb_destroy_func_t destroy); + HB_EXTERN unsigned int hb_face_get_table_tags (const hb_face_t *face, unsigned int start_offset, diff --git a/src/hb-face.hh b/src/hb-face.hh index aff3ff0d0..640156832 100644 --- a/src/hb-face.hh +++ b/src/hb-face.hh @@ -48,13 +48,17 @@ struct hb_face_t { hb_object_header_t header; + unsigned int index; /* Face index in a collection, zero-based. */ + mutable hb_atomic_int_t upem; /* Units-per-EM. */ + mutable hb_atomic_int_t num_glyphs; /* Number of glyphs. */ + hb_reference_table_func_t reference_table_func; void *user_data; hb_destroy_func_t destroy; - unsigned int index; /* Face index in a collection, zero-based. */ - mutable hb_atomic_int_t upem; /* Units-per-EM. */ - mutable hb_atomic_int_t num_glyphs; /* Number of glyphs. */ + hb_get_table_tags_func_t get_table_tags_func; + void *get_table_tags_user_data; + hb_destroy_func_t get_table_tags_destroy; hb_shaper_object_dataset_t data;/* Various shaper data. */ hb_ot_face_t table; /* All the face's tables. */ diff --git a/src/hb-ft.cc b/src/hb-ft.cc index 3de4a6d5d..f6d8bf413 100644 --- a/src/hb-ft.cc +++ b/src/hb-ft.cc @@ -1104,6 +1104,45 @@ _hb_ft_reference_table (hb_face_t *face HB_UNUSED, hb_tag_t tag, void *user_data buffer, hb_free); } +static unsigned +_hb_ft_get_table_tags (const hb_face_t *face HB_UNUSED, + unsigned int start_offset, + unsigned int *table_count, + hb_tag_t *table_tags, + void *user_data) +{ + FT_Face ft_face = (FT_Face) user_data; + + FT_ULong population = 0; + FT_Sfnt_Table_Info (ft_face, + 0, // table_index; ignored + nullptr, + &population); + + if (!table_count) + return population; + else + *table_count = 0; + + if (unlikely (start_offset >= population)) + return population; + + unsigned end_offset = hb_min (start_offset + *table_count, (unsigned) population); + if (unlikely (end_offset < start_offset)) + return population; + + *table_count = end_offset - start_offset; + for (unsigned i = start_offset; i < end_offset; i++) + { + FT_ULong tag = 0, length; + FT_Sfnt_Table_Info (ft_face, i, &tag, &length); + table_tags[i - start_offset] = tag; + } + + return population; +} + + /** * hb_ft_face_create: * @ft_face: (destroy destroy) (scope notified): FT_Face to work upon @@ -1145,6 +1184,7 @@ hb_ft_face_create (FT_Face ft_face, hb_blob_destroy (blob); } else { face = hb_face_create_for_tables (_hb_ft_reference_table, ft_face, destroy); + hb_face_set_get_table_tags_func (face, _hb_ft_get_table_tags, ft_face, nullptr); } hb_face_set_index (face, ft_face->face_index); diff --git a/test/api/meson.build b/test/api/meson.build index 76126a8de..2daf30561 100644 --- a/test/api/meson.build +++ b/test/api/meson.build @@ -20,6 +20,7 @@ tests = [ 'test-extents.c', 'test-font.c', 'test-font-scale.c', + 'test-get-table-tags.c', 'test-glyph-names.c', 'test-instance-cff2.c', 'test-map.c', diff --git a/test/api/test-get-table-tags.c b/test/api/test-get-table-tags.c new file mode 100644 index 000000000..f60dbf432 --- /dev/null +++ b/test/api/test-get-table-tags.c @@ -0,0 +1,176 @@ +/* + * Copyright © 2024 Google, Inc. + * + * 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" + +#include +#ifdef HAVE_FREETYPE +#include +#endif +#ifdef HAVE_CORETEXT +#include "hb-coretext.h" +#endif + +static void +_test_get_table_tags (hb_face_t *face) +{ + g_assert_cmpuint (15u, ==, hb_face_get_table_tags (face, 2, NULL, NULL)); + + unsigned count; + hb_tag_t tags[3]; + + count = sizeof (tags) / sizeof (tags[0]); + g_assert_cmpuint (15u, ==, hb_face_get_table_tags (face, 2, &count, tags)); + + g_assert_cmpuint (3u, ==, count); + g_assert_cmpuint (tags[0], ==, HB_TAG ('c', 'v', 't', ' ')); + g_assert_cmpuint (tags[1], ==, HB_TAG ('f', 'p', 'g', 'm')); + g_assert_cmpuint (tags[2], ==, HB_TAG ('g', 'a', 's', 'p')); + + count = sizeof (tags) / sizeof (tags[0]); + g_assert_cmpuint (15u, ==, hb_face_get_table_tags (face, 14, &count, tags)); + g_assert_cmpuint (1u, ==, count); + g_assert_cmpuint (tags[0], ==, HB_TAG ('p', 'r', 'e', 'p')); +} + +static void +test_get_table_tags_default (void) +{ + hb_face_t *face = hb_test_open_font_file ("fonts/Roboto-Regular.abc.ttf"); + + _test_get_table_tags (face); + + hb_face_destroy (face); +} + +static void +test_get_table_tags_builder (void) +{ + hb_face_t *source = hb_test_open_font_file ("fonts/Roboto-Regular.abc.ttf"); + hb_face_t *face = hb_face_builder_create (); + + hb_tag_t tags[4]; + unsigned total_count = hb_face_get_table_tags (source, 0, NULL, NULL); + for (unsigned start_offset = 0; + start_offset < total_count; + start_offset += sizeof (tags) / sizeof (tags[0])) + { + unsigned count = sizeof (tags) / sizeof (tags[0]); + unsigned new_total_count = hb_face_get_table_tags (source, start_offset, &count, tags); + g_assert_cmpuint (total_count, ==, new_total_count); + + for (unsigned i = 0; i < count; i++) + { + hb_blob_t *blob = hb_face_reference_table (source, tags[i]); + hb_face_builder_add_table (face, tags[i], blob); + hb_blob_destroy (blob); + } + } + + hb_face_destroy (source); + + _test_get_table_tags (face); + + hb_face_destroy (face); +} + +#ifdef HAVE_FREETYPE +static void +test_get_table_tags_ft (void) +{ + hb_face_t *source = hb_test_open_font_file ("fonts/Roboto-Regular.abc.ttf"); + hb_blob_t *blob = hb_face_reference_blob (source); + hb_face_destroy (source); + + FT_Error error; + + FT_Library ft_library; + error = FT_Init_FreeType (&ft_library); + g_assert_cmpint (0, ==, error); + + FT_Face ft_face; + error = FT_New_Memory_Face (ft_library, + (const FT_Byte *) hb_blob_get_data (blob, NULL), + hb_blob_get_length (blob), + 0, + &ft_face); + g_assert_cmpint (0, ==, error); + + hb_face_t *face = hb_ft_face_create (ft_face, NULL); + + _test_get_table_tags (face); + + hb_face_destroy (face); + FT_Done_Face (ft_face); + FT_Done_FreeType (ft_library); + hb_blob_destroy (blob); +} +#endif + +#ifdef HAVE_CORETEXT +static void +test_get_table_tags_ct (void) +{ + hb_face_t *source = hb_test_open_font_file ("fonts/Roboto-Regular.abc.ttf"); + hb_blob_t *blob = hb_face_reference_blob (source); + hb_face_destroy (source); + + CGDataProviderRef provider = CGDataProviderCreateWithData (blob, + hb_blob_get_data (blob, NULL), + hb_blob_get_length (blob), + NULL); + assert (provider); + + CGFontRef cg_font = CGFontCreateWithDataProvider (provider); + assert (cg_font); + CGDataProviderRelease (provider); + + hb_face_t *face = hb_coretext_face_create (cg_font); + + _test_get_table_tags (face); + + hb_face_destroy (face); + CGFontRelease (cg_font); + hb_blob_destroy (blob); +} +#endif + +int +main (int argc, char **argv) +{ + hb_test_init (&argc, &argv); + + hb_test_add (test_get_table_tags_default); + hb_test_add (test_get_table_tags_builder); +#ifdef HAVE_FREETYPE + hb_test_add (test_get_table_tags_ft); +#endif +#ifdef HAVE_CORETEXT + hb_test_add (test_get_table_tags_ct); +#endif + + return hb_test_run(); +}