[HarfBuzz] harfbuzz: Branch 'master' - 6 commits
Behdad Esfahbod
behdad at kemper.freedesktop.org
Wed Aug 1 03:28:54 UTC 2018
docs/harfbuzz-sections.txt | 1
src/hb-atomic-private.hh | 34 +
src/hb-ot-cmap-table.hh | 9
src/hb-ot-layout-common-private.hh | 7
src/hb-ot-layout.cc | 186 ++++------
src/hb-ot-layout.h | 7
test/api/fonts/clusterfuzz-testcase-minimized-hb-subset-fuzzer-5542653037903872 |binary
test/api/fonts/clusterfuzz-testcase-minimized-hb-subset-get-codepoints-fuzzer-5973295416475648 |binary
8 files changed, 137 insertions(+), 107 deletions(-)
New commits:
commit 3dd1b88765f6ce91bd0558a16cdd8cf0c1e15d1b
Author: Behdad Esfahbod <behdad at behdad.org>
Date: Tue Jul 31 19:33:37 2018 -0700
[atomic] Use CONSUME, not ACQUIRE, memory-order for get()
Although, all implementations just elevate that to ACQUIRE.
But requirement for us is just CONSUME.
diff --git a/src/hb-atomic-private.hh b/src/hb-atomic-private.hh
index 02cf6f38..e6a3a9a9 100644
--- a/src/hb-atomic-private.hh
+++ b/src/hb-atomic-private.hh
@@ -46,14 +46,14 @@
/* Defined externally, i.e. in config.h; must have typedef'ed hb_atomic_int_impl_t as well. */
-#elif !defined(HB_NO_MT) && defined(__ATOMIC_ACQUIRE)
+#elif !defined(HB_NO_MT) && defined(__ATOMIC_CONSUME)
/* C++11-style GCC primitives. */
typedef int hb_atomic_int_impl_t;
#define hb_atomic_int_impl_add(AI, V) __atomic_fetch_add (&(AI), (V), __ATOMIC_ACQ_REL)
-#define hb_atomic_ptr_impl_get(P) __atomic_load_n ((P), __ATOMIC_ACQUIRE)
+#define hb_atomic_ptr_impl_get(P) __atomic_load_n ((P), __ATOMIC_CONSUME)
static inline bool
_hb_atomic_ptr_impl_cmplexch (const void **P, const void *O_, const void *N)
{
@@ -71,7 +71,7 @@ _hb_atomic_ptr_impl_cmplexch (const void **P, const void *O_, const void *N)
typedef int hb_atomic_int_impl_t;
#define hb_atomic_int_impl_add(AI, V) (reinterpret_cast<std::atomic<int> *> (&AI)->fetch_add ((V), std::memory_order_acq_rel))
-#define hb_atomic_ptr_impl_get(P) (reinterpret_cast<std::atomic<void*> *> (P)->load (std::memory_order_acquire))
+#define hb_atomic_ptr_impl_get(P) (reinterpret_cast<std::atomic<void*> *> (P)->load (std::memory_order_consume))
static inline bool
_hb_atomic_ptr_impl_cmplexch (const void **P, const void *O_, const void *N)
{
commit 06b91d935da1a40ef9de6697717eb0af1015989e
Author: Behdad Esfahbod <behdad at behdad.org>
Date: Tue Jul 31 19:29:49 2018 -0700
Revert "[atomic] Make pointer get op relaxed instead of acquire"
This reverts commit b1e5650c67266dc158f22355fed206cd1c413f70.
After lots of head-scratching and finally finding the only truly
readable source to be the good old:
https://www.kernel.org/doc/Documentation/memory-barriers.txt
I've convinced myself that we need consume memory-ordering on get().
The location of memory-barrier in a load should be after, not before
the load. That needs fixing. I'll do that separately.
diff --git a/src/hb-atomic-private.hh b/src/hb-atomic-private.hh
index c860582d..02cf6f38 100644
--- a/src/hb-atomic-private.hh
+++ b/src/hb-atomic-private.hh
@@ -40,19 +40,20 @@
/* We need external help for these */
#if defined(hb_atomic_int_impl_add) \
+ && defined(hb_atomic_ptr_impl_get) \
&& defined(hb_atomic_ptr_impl_cmpexch)
/* Defined externally, i.e. in config.h; must have typedef'ed hb_atomic_int_impl_t as well. */
-#elif !defined(HB_NO_MT) && defined(__ATOMIC_RELAXED)
+#elif !defined(HB_NO_MT) && defined(__ATOMIC_ACQUIRE)
/* C++11-style GCC primitives. */
typedef int hb_atomic_int_impl_t;
#define hb_atomic_int_impl_add(AI, V) __atomic_fetch_add (&(AI), (V), __ATOMIC_ACQ_REL)
-#define hb_atomic_ptr_impl_get(P) __atomic_load_n ((P), __ATOMIC_RELAXED)
+#define hb_atomic_ptr_impl_get(P) __atomic_load_n ((P), __ATOMIC_ACQUIRE)
static inline bool
_hb_atomic_ptr_impl_cmplexch (const void **P, const void *O_, const void *N)
{
@@ -70,7 +71,7 @@ _hb_atomic_ptr_impl_cmplexch (const void **P, const void *O_, const void *N)
typedef int hb_atomic_int_impl_t;
#define hb_atomic_int_impl_add(AI, V) (reinterpret_cast<std::atomic<int> *> (&AI)->fetch_add ((V), std::memory_order_acq_rel))
-#define hb_atomic_ptr_impl_get(P) (reinterpret_cast<std::atomic<void*> *> (P)->load (std::memory_order_relaxed))
+#define hb_atomic_ptr_impl_get(P) (reinterpret_cast<std::atomic<void*> *> (P)->load (std::memory_order_acquire))
static inline bool
_hb_atomic_ptr_impl_cmplexch (const void **P, const void *O_, const void *N)
{
@@ -84,9 +85,22 @@ _hb_atomic_ptr_impl_cmplexch (const void **P, const void *O_, const void *N)
#include <windows.h>
+/* MinGW has a convoluted history of supporting MemoryBarrier
+ * properly. As such, define a function to wrap the whole
+ * thing. */
+static inline void _HBMemoryBarrier (void) {
+#if !defined(MemoryBarrier)
+ long dummy = 0;
+ InterlockedExchange (&dummy, 1);
+#else
+ MemoryBarrier ();
+#endif
+}
+
typedef LONG hb_atomic_int_impl_t;
#define hb_atomic_int_impl_add(AI, V) InterlockedExchangeAdd (&(AI), (V))
+#define hb_atomic_ptr_impl_get(P) (_HBMemoryBarrier (), (void *) *(P))
#define hb_atomic_ptr_impl_cmpexch(P,O,N) (InterlockedCompareExchangePointer ((void **) (P), (void *) (N), (void *) (O)) == (void *) (O))
@@ -95,6 +109,7 @@ typedef LONG hb_atomic_int_impl_t;
typedef int hb_atomic_int_impl_t;
#define hb_atomic_int_impl_add(AI, V) __sync_fetch_and_add (&(AI), (V))
+#define hb_atomic_ptr_impl_get(P) (void *) (__sync_synchronize (), *(P))
#define hb_atomic_ptr_impl_cmpexch(P,O,N) __sync_bool_compare_and_swap ((P), (O), (N))
@@ -104,9 +119,10 @@ typedef int hb_atomic_int_impl_t;
#include <mbarrier.h>
typedef unsigned int hb_atomic_int_impl_t;
-#define hb_atomic_int_impl_add(AI, V) (({__machine_rw_barrier ();}), atomic_add_int_nv (&(AI), (V)) - (V))
+#define hb_atomic_int_impl_add(AI, V) ( ({__machine_rw_barrier ();}), atomic_add_int_nv (&(AI), (V)) - (V))
-#define hb_atomic_ptr_impl_cmpexch(P,O,N) (({__machine_rw_barrier ();}), atomic_cas_ptr ((void **) (P), (void *) (O), (void *) (N)) == (void *) (O) ? true : false)
+#define hb_atomic_ptr_impl_get(P) ( ({__machine_rw_barrier ();}), (void *) *(P))
+#define hb_atomic_ptr_impl_cmpexch(P,O,N) ( ({__machine_rw_barrier ();}), atomic_cas_ptr ((void **) (P), (void *) (O), (void *) (N)) == (void *) (O) ? true : false)
#elif !defined(HB_NO_MT) && defined(__APPLE__)
@@ -122,6 +138,7 @@ typedef unsigned int hb_atomic_int_impl_t;
typedef int32_t hb_atomic_int_impl_t;
#define hb_atomic_int_impl_add(AI, V) (OSAtomicAdd32Barrier ((V), &(AI)) - (V))
+#define hb_atomic_ptr_impl_get(P) (OSMemoryBarrier (), (void *) *(P))
#if (MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_4 || __IPHONE_VERSION_MIN_REQUIRED >= 20100)
#define hb_atomic_ptr_impl_cmpexch(P,O,N) OSAtomicCompareAndSwapPtrBarrier ((void *) (O), (void *) (N), (void **) (P))
#else
@@ -154,6 +171,7 @@ static inline int _hb_compare_and_swaplp(volatile long* P, long O, long N) {
typedef int hb_atomic_int_impl_t;
#define hb_atomic_int_impl_add(AI, V) _hb_fetch_and_add (&(AI), (V))
+#define hb_atomic_ptr_impl_get(P) (__sync(), (void *) *(P))
#define hb_atomic_ptr_impl_cmpexch(P,O,N) _hb_compare_and_swaplp ((long*)(P), (long)(O), (long)(N))
#elif !defined(HB_NO_MT)
@@ -163,6 +181,7 @@ typedef int hb_atomic_int_impl_t;
typedef volatile int hb_atomic_int_impl_t;
#define hb_atomic_int_impl_add(AI, V) (((AI) += (V)) - (V))
+#define hb_atomic_ptr_impl_get(P) ((void *) *(P))
#define hb_atomic_ptr_impl_cmpexch(P,O,N) (* (void * volatile *) (P) == (void *) (O) ? (* (void * volatile *) (P) = (void *) (N), true) : false)
@@ -171,16 +190,13 @@ typedef volatile int hb_atomic_int_impl_t;
typedef int hb_atomic_int_impl_t;
#define hb_atomic_int_impl_add(AI, V) (((AI) += (V)) - (V))
+#define hb_atomic_ptr_impl_get(P) ((void *) *(P))
#define hb_atomic_ptr_impl_cmpexch(P,O,N) (* (void **) (P) == (void *) (O) ? (* (void **) (P) = (void *) (N), true) : false)
#endif
-#ifndef hb_atomic_ptr_impl_get
-#define hb_atomic_ptr_impl_get(P) ((void *) *(P))
-#endif
-
#ifndef HB_ATOMIC_INT_INIT
#define HB_ATOMIC_INT_INIT(V) {V}
#endif
commit 2bdd903c69eb3a34f3d3bf5e4f4c94cd66337117
Author: Garret Rieger <grieger at google.com>
Date: Tue Jul 31 17:44:02 2018 -0700
[subset] limit the max codepoint value to the unicode limit.
When collecting all codepoints in the cmap avoid using large amount of memory for fonts that declare coverage over all 32 bit integers.
diff --git a/src/hb-ot-cmap-table.hh b/src/hb-ot-cmap-table.hh
index 00f83528..67a9c7dd 100644
--- a/src/hb-ot-cmap-table.hh
+++ b/src/hb-ot-cmap-table.hh
@@ -37,6 +37,9 @@
*/
#define HB_OT_TAG_cmap HB_TAG('c','m','a','p')
+#ifndef HB_MAX_UNICODE_CODEPOINT_VALUE
+#define HB_MAX_UNICODE_CODEPOINT_VALUE 0x10FFFF
+#endif
namespace OT {
@@ -437,8 +440,10 @@ struct CmapSubtableLongSegmented
{
for (unsigned int i = 0; i < this->groups.len; i++) {
hb_set_add_range (out,
- this->groups[i].startCharCode,
- this->groups[i].endCharCode);
+ MIN ((unsigned int) this->groups[i].startCharCode,
+ (unsigned int) HB_MAX_UNICODE_CODEPOINT_VALUE),
+ MIN ((unsigned int) this->groups[i].endCharCode,
+ (unsigned int) HB_MAX_UNICODE_CODEPOINT_VALUE));
}
}
diff --git a/test/api/fonts/clusterfuzz-testcase-minimized-hb-subset-get-codepoints-fuzzer-5973295416475648 b/test/api/fonts/clusterfuzz-testcase-minimized-hb-subset-get-codepoints-fuzzer-5973295416475648
new file mode 100644
index 00000000..b506d2a5
Binary files /dev/null and b/test/api/fonts/clusterfuzz-testcase-minimized-hb-subset-get-codepoints-fuzzer-5973295416475648 differ
commit 7278d9df3093a87f99cec9b4cea38bd688c5d020
Author: Garret Rieger <grieger at google.com>
Date: Tue Jul 31 17:59:19 2018 -0700
[subset] Add hb_ot_layout_collect_features to harfbuzz-sections.txt.
Add the fuzzer test case for feature collection timeout.
diff --git a/docs/harfbuzz-sections.txt b/docs/harfbuzz-sections.txt
index b93cd1d6..5cf5a884 100644
--- a/docs/harfbuzz-sections.txt
+++ b/docs/harfbuzz-sections.txt
@@ -447,6 +447,7 @@ HB_OT_TAG_GPOS
HB_OT_TAG_GSUB
HB_OT_TAG_JSTF
hb_ot_layout_collect_lookups
+hb_ot_layout_collect_features
hb_ot_layout_feature_get_lookups
hb_ot_layout_feature_with_variations_get_lookups
hb_ot_layout_get_attach_points
diff --git a/test/api/fonts/clusterfuzz-testcase-minimized-hb-subset-fuzzer-5542653037903872 b/test/api/fonts/clusterfuzz-testcase-minimized-hb-subset-fuzzer-5542653037903872
new file mode 100644
index 00000000..6307ddd4
Binary files /dev/null and b/test/api/fonts/clusterfuzz-testcase-minimized-hb-subset-fuzzer-5542653037903872 differ
commit 89733755a48feef0a663e1ea7b8294949581ce7e
Author: Garret Rieger <grieger at google.com>
Date: Mon Jul 30 18:10:43 2018 -0700
[subset] use add_array to populate feature_indexes.
This is much faster then calling a bunch of individual add()'s.
diff --git a/src/hb-ot-layout-common-private.hh b/src/hb-ot-layout-common-private.hh
index 1cf530ea..76ad30cf 100644
--- a/src/hb-ot-layout-common-private.hh
+++ b/src/hb-ot-layout-common-private.hh
@@ -190,6 +190,11 @@ struct IndexArray : ArrayOf<Index>
}
return this->len;
}
+
+ inline void add_indexes_to (hb_set_t* output /* OUT */) const
+ {
+ output->add_array (arrayZ, len);
+ }
};
@@ -208,6 +213,8 @@ struct LangSys
unsigned int *feature_count /* IN/OUT */,
unsigned int *feature_indexes /* OUT */) const
{ return featureIndex.get_indexes (start_offset, feature_count, feature_indexes); }
+ inline void add_feature_indexes_to (hb_set_t *feature_indexes) const
+ { featureIndex.add_indexes_to (feature_indexes); }
inline bool has_required_feature (void) const { return reqFeatureIndex != 0xFFFFu; }
inline unsigned int get_required_feature_index (void) const
diff --git a/src/hb-ot-layout.cc b/src/hb-ot-layout.cc
index 7b30fc7b..db782348 100644
--- a/src/hb-ot-layout.cc
+++ b/src/hb-ot-layout.cc
@@ -520,6 +520,19 @@ hb_ot_layout_language_get_required_feature (hb_face_t *face,
return l.has_required_feature ();
}
+static void
+_hb_ot_layout_language_add_feature_indexes_to (hb_face_t *face,
+ hb_tag_t table_tag,
+ unsigned int script_index,
+ unsigned int language_index,
+ hb_set_t *feature_indexes /* OUT */)
+{
+ const OT::GSUBGPOS &g = get_gsubgpos_table (face, table_tag);
+ const OT::LangSys &l = g.get_script (script_index).get_lang_sys (language_index);
+ l.add_feature_indexes_to (feature_indexes);
+}
+
+
unsigned int
hb_ot_layout_language_get_feature_indexes (hb_face_t *face,
hb_tag_t table_tag,
@@ -677,24 +690,11 @@ _hb_ot_layout_collect_features_features (hb_face_t *face,
feature_indexes->add (required_feature_index);
/* All features */
- unsigned int feature_indices[32];
- unsigned int offset, len;
-
- offset = 0;
- do {
- len = ARRAY_LENGTH (feature_indices);
- hb_ot_layout_language_get_feature_indexes (face,
- table_tag,
- script_index,
- language_index,
- offset, &len,
- feature_indices);
-
- for (unsigned int i = 0; i < len; i++)
- feature_indexes->add (feature_indices[i]);
-
- offset += len;
- } while (len == ARRAY_LENGTH (feature_indices));
+ _hb_ot_layout_language_add_feature_indexes_to (face,
+ table_tag,
+ script_index,
+ language_index,
+ feature_indexes);
}
else
{
@@ -763,9 +763,9 @@ _hb_ot_layout_collect_features_languages (hb_face_t *face,
}
/**
- * hb_ot_layout_collect_lookups:
+ * hb_ot_layout_collect_features:
*
- * Since: 0.9.8
+ * Since: REPLACEME
**/
void
hb_ot_layout_collect_features (hb_face_t *face,
commit 7d92bef9c5afb319d125f60b0fce4763afeaa686
Author: Garret Rieger <grieger at google.com>
Date: Mon Jul 30 17:17:43 2018 -0700
[subset] collect features first, then use those to collect lookups.
diff --git a/src/hb-ot-layout.cc b/src/hb-ot-layout.cc
index b38dbc38..7b30fc7b 100644
--- a/src/hb-ot-layout.cc
+++ b/src/hb-ot-layout.cc
@@ -658,13 +658,12 @@ _hb_ot_layout_collect_lookups_lookups (hb_face_t *face,
}
static void
-_hb_ot_layout_collect_lookups_features (hb_face_t *face,
- hb_tag_t table_tag,
- unsigned int script_index,
- unsigned int language_index,
- const hb_tag_t *features,
- hb_set_t *visited_features,
- hb_set_t *lookup_indexes /* OUT */)
+_hb_ot_layout_collect_features_features (hb_face_t *face,
+ hb_tag_t table_tag,
+ unsigned int script_index,
+ unsigned int language_index,
+ const hb_tag_t *features,
+ hb_set_t *feature_indexes /* OUT */)
{
if (!features)
{
@@ -674,15 +673,8 @@ _hb_ot_layout_collect_lookups_features (hb_face_t *face,
script_index,
language_index,
&required_feature_index,
- nullptr)
- && !visited_features->has (required_feature_index))
- {
- _hb_ot_layout_collect_lookups_lookups (face,
- table_tag,
- required_feature_index,
- lookup_indexes);
- visited_features->add (required_feature_index);
- }
+ nullptr))
+ feature_indexes->add (required_feature_index);
/* All features */
unsigned int feature_indices[32];
@@ -699,14 +691,7 @@ _hb_ot_layout_collect_lookups_features (hb_face_t *face,
feature_indices);
for (unsigned int i = 0; i < len; i++)
- {
- if (visited_features->has (feature_indices[i])) continue;
- _hb_ot_layout_collect_lookups_lookups (face,
- table_tag,
- feature_indices[i],
- lookup_indexes);
- visited_features->add (feature_indices[i]);
- }
+ feature_indexes->add (feature_indices[i]);
offset += len;
} while (len == ARRAY_LENGTH (feature_indices));
@@ -722,30 +707,25 @@ _hb_ot_layout_collect_lookups_features (hb_face_t *face,
language_index,
*features,
&feature_index))
- _hb_ot_layout_collect_lookups_lookups (face,
- table_tag,
- feature_index,
- lookup_indexes);
+ feature_indexes->add (feature_index);
}
}
}
static void
-_hb_ot_layout_collect_lookups_languages (hb_face_t *face,
- hb_tag_t table_tag,
- unsigned int script_index,
- const hb_tag_t *languages,
- const hb_tag_t *features,
- hb_set_t *visited_features,
- hb_set_t *lookup_indexes /* OUT */)
+_hb_ot_layout_collect_features_languages (hb_face_t *face,
+ hb_tag_t table_tag,
+ unsigned int script_index,
+ const hb_tag_t *languages,
+ const hb_tag_t *features,
+ hb_set_t *feature_indexes /* OUT */)
{
- _hb_ot_layout_collect_lookups_features (face,
- table_tag,
- script_index,
- HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX,
- features,
- visited_features,
- lookup_indexes);
+ _hb_ot_layout_collect_features_features (face,
+ table_tag,
+ script_index,
+ HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX,
+ features,
+ feature_indexes);
if (!languages)
{
@@ -755,13 +735,12 @@ _hb_ot_layout_collect_lookups_languages (hb_face_t *face,
script_index,
0, nullptr, nullptr);
for (unsigned int language_index = 0; language_index < count; language_index++)
- _hb_ot_layout_collect_lookups_features (face,
- table_tag,
- script_index,
- language_index,
- features,
- visited_features,
- lookup_indexes);
+ _hb_ot_layout_collect_features_features (face,
+ table_tag,
+ script_index,
+ language_index,
+ features,
+ feature_indexes);
}
else
{
@@ -773,13 +752,12 @@ _hb_ot_layout_collect_lookups_languages (hb_face_t *face,
script_index,
*languages,
&language_index))
- _hb_ot_layout_collect_lookups_features (face,
- table_tag,
- script_index,
- language_index,
- features,
- visited_features,
- lookup_indexes);
+ _hb_ot_layout_collect_features_features (face,
+ table_tag,
+ script_index,
+ language_index,
+ features,
+ feature_indexes);
}
}
}
@@ -790,14 +768,13 @@ _hb_ot_layout_collect_lookups_languages (hb_face_t *face,
* Since: 0.9.8
**/
void
-hb_ot_layout_collect_lookups (hb_face_t *face,
- hb_tag_t table_tag,
- const hb_tag_t *scripts,
- const hb_tag_t *languages,
- const hb_tag_t *features,
- hb_set_t *lookup_indexes /* OUT */)
+hb_ot_layout_collect_features (hb_face_t *face,
+ hb_tag_t table_tag,
+ const hb_tag_t *scripts,
+ const hb_tag_t *languages,
+ const hb_tag_t *features,
+ hb_set_t *feature_indexes /* OUT */)
{
- hb_auto_t<hb_set_t> visited_features;
if (!scripts)
{
/* All scripts */
@@ -805,13 +782,12 @@ hb_ot_layout_collect_lookups (hb_face_t *face,
table_tag,
0, nullptr, nullptr);
for (unsigned int script_index = 0; script_index < count; script_index++)
- _hb_ot_layout_collect_lookups_languages (face,
- table_tag,
- script_index,
- languages,
- features,
- &visited_features,
- lookup_indexes);
+ _hb_ot_layout_collect_features_languages (face,
+ table_tag,
+ script_index,
+ languages,
+ features,
+ feature_indexes);
}
else
{
@@ -822,18 +798,36 @@ hb_ot_layout_collect_lookups (hb_face_t *face,
table_tag,
*scripts,
&script_index))
- _hb_ot_layout_collect_lookups_languages (face,
- table_tag,
- script_index,
- languages,
- features,
- &visited_features,
- lookup_indexes);
+ _hb_ot_layout_collect_features_languages (face,
+ table_tag,
+ script_index,
+ languages,
+ features,
+ feature_indexes);
}
}
}
/**
+ * hb_ot_layout_collect_lookups:
+ *
+ * Since: 0.9.8
+ **/
+void
+hb_ot_layout_collect_lookups (hb_face_t *face,
+ hb_tag_t table_tag,
+ const hb_tag_t *scripts,
+ const hb_tag_t *languages,
+ const hb_tag_t *features,
+ hb_set_t *lookup_indexes /* OUT */)
+{
+ hb_auto_t<hb_set_t> feature_indexes;
+ hb_ot_layout_collect_features (face, table_tag, scripts, languages, features, &feature_indexes);
+ for (hb_codepoint_t feature_index = HB_SET_VALUE_INVALID; hb_set_next (&feature_indexes, &feature_index);)
+ _hb_ot_layout_collect_lookups_lookups (face, table_tag, feature_index, lookup_indexes);
+}
+
+/**
* hb_ot_layout_lookup_collect_glyphs:
*
* Since: 0.9.7
diff --git a/src/hb-ot-layout.h b/src/hb-ot-layout.h
index f8597673..f81b9dd4 100644
--- a/src/hb-ot-layout.h
+++ b/src/hb-ot-layout.h
@@ -194,6 +194,13 @@ HB_EXTERN unsigned int
hb_ot_layout_table_get_lookup_count (hb_face_t *face,
hb_tag_t table_tag);
+HB_EXTERN void
+hb_ot_layout_collect_features (hb_face_t *face,
+ hb_tag_t table_tag,
+ const hb_tag_t *scripts,
+ const hb_tag_t *languages,
+ const hb_tag_t *features,
+ hb_set_t *feature_indexes /* OUT */);
HB_EXTERN void
hb_ot_layout_collect_lookups (hb_face_t *face,
More information about the HarfBuzz
mailing list