diff --git a/docs/harfbuzz-sections.txt b/docs/harfbuzz-sections.txt index 650c07a98..4a5d1d87d 100644 --- a/docs/harfbuzz-sections.txt +++ b/docs/harfbuzz-sections.txt @@ -955,6 +955,8 @@ hb_subset_serialize_object_t hb_subset_serialize_or_fail hb_subset_input_override_name_table +hb_subset_cff_get_charstring_data +hb_subset_cff2_get_charstring_data
diff --git a/src/gen-def.py b/src/gen-def.py index bc5a40137..063f859ad 100755 --- a/src/gen-def.py +++ b/src/gen-def.py @@ -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) diff --git a/src/hb-subset.cc b/src/hb-subset.cc index fbdf1b4f9..b1e4d82aa 100644 --- a/src/hb-subset.cc +++ b/src/hb-subset.cc @@ -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 +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 \ No newline at end of file diff --git a/src/hb-subset.h b/src/hb-subset.h index 71276c7a6..3e1405a3f 100644 --- a/src/hb-subset.h +++ b/src/hb-subset.h @@ -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 * diff --git a/test/api/test-subset.c b/test/api/test-subset.c index 312c15663..b22c76a24 100644 --- a/test/api/test-subset.c +++ b/test/api/test-subset.c @@ -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(); }