Merge pull request #5219 from harfbuzz/gid_data

[subset] Add API for retrieving the raw per glyph outline data from CFF and CFF2.
This commit is contained in:
Behdad Esfahbod 2025-03-27 16:12:14 -06:00 committed by GitHub
commit 0752e5852b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 226 additions and 0 deletions

View file

@ -955,6 +955,8 @@ hb_subset_serialize_object_t
hb_subset_serialize_or_fail
<SUBSECTION Private>
hb_subset_input_override_name_table
hb_subset_cff_get_charstring_data
hb_subset_cff2_get_charstring_data
</SECTION>
<SECTION>

View file

@ -21,6 +21,8 @@ if '--experimental-api' not in sys.argv:
experimental_symbols = \
"""hb_shape_justify
hb_subset_input_override_name_table
hb_subset_cff_get_charstring_data
hb_subset_cff2_get_charstring_data
""".splitlines ()
symbols = [x for x in symbols if x not in experimental_symbols]
symbols = "\n".join (symbols)

View file

@ -708,3 +708,68 @@ hb_subset_plan_execute_or_fail (hb_subset_plan_t *plan)
end:
return success ? hb_face_reference (plan->dest) : nullptr;
}
#ifdef HB_EXPERIMENTAL_API
#include "hb-ot-cff1-table.hh"
template<typename accel_t>
static hb_blob_t* get_charstrings_data(accel_t& accel, hb_codepoint_t glyph_index) {
if (!accel.is_valid()) {
return hb_blob_get_empty ();
}
hb_ubytes_t bytes = (*accel.charStrings)[glyph_index];
if (!bytes) {
return hb_blob_get_empty ();
}
hb_blob_t* cff_blob = accel.get_blob();
uint32_t length;
const char* cff_data = hb_blob_get_data(cff_blob, &length) ;
long int offset = (const char*) bytes.arrayZ - cff_data;
if (offset < 0 || offset > UINT32_MAX) {
return hb_blob_get_empty ();
}
return hb_blob_create_sub_blob(cff_blob, (uint32_t) offset, bytes.length);
}
/**
* hb_subset_cff_get_charstring_data:
* @face: A face object
* @glyph_index: Glyph index to get data for.
*
* Returns the raw outline data from the CFF/CFF2 table associated with the given glyph index.
*
* XSince: EXPERIMENTAL
**/
HB_EXTERN hb_blob_t*
hb_subset_cff_get_charstring_data(hb_face_t* face, hb_codepoint_t glyph_index) {
if (!_is_table_present(face, HB_TAG('C', 'F', 'F', ' '))) {
return hb_blob_get_empty ();
}
return get_charstrings_data(*face->table.cff1, glyph_index);
}
/**
* hb_subset_cff2_get_charstring_data:
* @face: A face object
* @glyph_index: Glyph index to get data for.
*
* Returns the raw outline data from the CFF/CFF2 table associated with the given glyph index.
*
* XSince: EXPERIMENTAL
**/
HB_EXTERN hb_blob_t*
hb_subset_cff2_get_charstring_data(hb_face_t* face, hb_codepoint_t glyph_index) {
if (!_is_table_present(face, HB_TAG('C', 'F', 'F', '2'))) {
return hb_blob_get_empty ();
}
return get_charstrings_data(*face->table.cff2, glyph_index);
}
#endif

View file

@ -224,6 +224,18 @@ hb_subset_input_override_name_table (hb_subset_input_t *input,
unsigned language_id,
const char *name_str,
int str_len);
/*
* Raw outline data access
*/
HB_EXTERN hb_blob_t*
hb_subset_cff_get_charstring_data(hb_face_t* face, hb_codepoint_t glyph_index);
HB_EXTERN hb_blob_t*
hb_subset_cff2_get_charstring_data(hb_face_t* face, hb_codepoint_t glyph_index);
#endif
HB_EXTERN hb_face_t *

View file

@ -228,6 +228,144 @@ test_subset_create_for_tables_face (void)
hb_face_destroy (face_ac);
}
#ifdef HB_EXPERIMENTAL_API
const uint8_t CFF2[226] = {
// From https://learn.microsoft.com/en-us/typography/opentype/spec/cff2
0x02, 0x00, 0x05, 0x00, 0x07, 0xCF, 0x0C, 0x24, 0xC3, 0x11, 0x9B, 0x18, 0x00, 0x00, 0x00, 0x00,
0x00, 0x26, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x01,
0x00, 0x02, 0xC0, 0x00, 0xE0, 0x00, 0x00, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xE0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x01, 0x01, 0x03, 0x05,
0x20, 0x0A, 0x20, 0x0A, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x05, 0xF7, 0x06, 0xDA, 0x12, 0x77,
0x9F, 0xF8, 0x6C, 0x9D, 0xAE, 0x9A, 0xF4, 0x9A, 0x95, 0x9F, 0xB3, 0x9F, 0x8B, 0x8B, 0x8B, 0x8B,
0x85, 0x9A, 0x8B, 0x8B, 0x97, 0x73, 0x8B, 0x8B, 0x8C, 0x80, 0x8B, 0x8B, 0x8B, 0x8D, 0x8B, 0x8B,
0x8C, 0x8A, 0x8B, 0x8B, 0x97, 0x17, 0x06, 0xFB, 0x8E, 0x95, 0x86, 0x9D, 0x8B, 0x8B, 0x8D, 0x17,
0x07, 0x77, 0x9F, 0xF8, 0x6D, 0x9D, 0xAD, 0x9A, 0xF3, 0x9A, 0x95, 0x9F, 0xB3, 0x9F, 0x08, 0xFB,
0x8D, 0x95, 0x09, 0x1E, 0xA0, 0x37, 0x5F, 0x0C, 0x09, 0x8B, 0x0C, 0x0B, 0xC2, 0x6E, 0x9E, 0x8C,
0x17, 0x0A, 0xDB, 0x57, 0xF7, 0x02, 0x8C, 0x17, 0x0B, 0xB3, 0x9A, 0x77, 0x9F, 0x82, 0x8A, 0x8D,
0x17, 0x0C, 0x0C, 0xDB, 0x95, 0x57, 0xF7, 0x02, 0x85, 0x8B, 0x8D, 0x17, 0x0C, 0x0D, 0xF7, 0x06,
0x13, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x1B, 0xBD, 0xBD, 0xEF, 0x8C, 0x10, 0x8B, 0x15, 0xF8,
0x88, 0x27, 0xFB, 0x5C, 0x8C, 0x10, 0x06, 0xF8, 0x88, 0x07, 0xFC, 0x88, 0xEF, 0xF7, 0x5C, 0x8C,
0x10, 0x06
};
static void
test_subset_cff2_get_charstring_data (void)
{
const uint8_t maxp_data[6] = {
0x00, 0x00, 0x50, 0x00,
0x00, 0x02 // numGlyphs
};
hb_blob_t* cff2 = hb_blob_create ((const char*) CFF2, 226, HB_MEMORY_MODE_READONLY, 0, 0);
hb_blob_t* maxp = hb_blob_create ((const char*) maxp_data, 6, HB_MEMORY_MODE_READONLY, 0, 0);
hb_face_t* builder = hb_face_builder_create ();
hb_face_builder_add_table (builder, HB_TAG('C', 'F', 'F', '2'), cff2);
hb_face_builder_add_table (builder, HB_TAG('m', 'a', 'x', 'p'), maxp);
hb_blob_t* face_blob = hb_face_reference_blob (builder);
hb_face_t* face = hb_face_create (face_blob, 0);
hb_blob_t* cs0 = hb_subset_cff2_get_charstring_data (face, 0);
unsigned int length;
const uint8_t* data = (const uint8_t*) hb_blob_get_data (cs0, &length);
g_assert (length == 2);
g_assert (data[0] == 0x20);
g_assert (data[1] == 0x0A);
hb_blob_t* cs1 = hb_subset_cff2_get_charstring_data (face, 1);
data = (const uint8_t*) hb_blob_get_data (cs1, &length);
g_assert (length == 2);
g_assert (data[0] == 0x20);
g_assert (data[1] == 0x0A);
hb_blob_t* cs2 = hb_subset_cff2_get_charstring_data (face, 2);
data = (const uint8_t*) hb_blob_get_data (cs2, &length);
g_assert (length == 0);
hb_blob_destroy (cff2);
hb_blob_destroy (maxp);
hb_face_destroy (builder);
hb_blob_destroy (face_blob);
hb_face_destroy (face);
hb_blob_destroy (cs0);
hb_blob_destroy (cs1);
hb_blob_destroy (cs2);
}
static void
test_subset_cff2_get_charstring_data_no_cff (void)
{
hb_face_t* builder = hb_face_builder_create ();
hb_blob_t* face_blob = hb_face_reference_blob (builder);
hb_face_t* face = hb_face_create (face_blob, 0);
hb_blob_t* cs0 = hb_subset_cff2_get_charstring_data (face, 0);
g_assert (hb_blob_get_length (cs0) == 0);
hb_face_destroy (builder);
hb_blob_destroy (face_blob);
hb_face_destroy (face);
hb_blob_destroy
(cs0);
}
static void
test_subset_cff2_get_charstring_data_invalid_cff2 (void)
{
// cff2 will parse as invalid since charstrings count doesn't not match num glyphs
// (because there is no maxp).
hb_blob_t* cff2 = hb_blob_create ((const char*) CFF2, 226, HB_MEMORY_MODE_READONLY, 0, 0);
hb_face_t* builder = hb_face_builder_create ();
hb_face_builder_add_table (builder, HB_TAG('C', 'F', 'F', '2'), cff2);
hb_blob_t* face_blob = hb_face_reference_blob (builder);
hb_face_t* face = hb_face_create (face_blob, 0);
hb_blob_t* cs0 = hb_subset_cff2_get_charstring_data (face, 0);
g_assert (hb_blob_get_length (cs0) == 0);
hb_blob_destroy (cff2);
hb_face_destroy (builder);
hb_blob_destroy (face_blob);
hb_face_destroy (face);
hb_blob_destroy (cs0);
}
static void
test_subset_cff2_get_charstring_data_lifetime (void)
{
const uint8_t maxp_data[6] = {
0x00, 0x00, 0x50, 0x00,
0x00, 0x02 // numGlyphs
};
hb_blob_t* cff2 = hb_blob_create ((const char*) CFF2, 226, HB_MEMORY_MODE_READONLY, 0, 0);
hb_blob_t* maxp = hb_blob_create ((const char*) maxp_data, 6, HB_MEMORY_MODE_READONLY, 0, 0);
hb_face_t* builder = hb_face_builder_create ();
hb_face_builder_add_table (builder, HB_TAG('C', 'F', 'F', '2'), cff2);
hb_face_builder_add_table (builder, HB_TAG('m', 'a', 'x', 'p'), maxp);
hb_blob_t* face_blob = hb_face_reference_blob (builder);
hb_face_t* face = hb_face_create (face_blob, 0);
hb_blob_t* cs0 = hb_subset_cff2_get_charstring_data (face, 0);
// Destroy the blob that cs0 is referencing via subblob to ensure lifetimes are being
// handled correctly.
hb_blob_destroy (cff2);
hb_blob_destroy (maxp);
hb_face_destroy (builder);
hb_blob_destroy (face_blob);
hb_face_destroy (face);
unsigned int length;
const uint8_t* data = (const uint8_t*) hb_blob_get_data (cs0, &length);
g_assert (length == 2);
g_assert (data[0] == 0x20);
g_assert (data[1] == 0x0A);
hb_blob_destroy (cs0);
}
#endif
int
main (int argc, char **argv)
{
@ -241,5 +379,12 @@ main (int argc, char **argv)
hb_test_add (test_subset_plan);
hb_test_add (test_subset_create_for_tables_face);
#ifdef HB_EXPERIMENTAL_API
hb_test_add (test_subset_cff2_get_charstring_data);
hb_test_add (test_subset_cff2_get_charstring_data_no_cff);
hb_test_add (test_subset_cff2_get_charstring_data_invalid_cff2);
hb_test_add (test_subset_cff2_get_charstring_data_lifetime);
#endif
return hb_test_run();
}