From 9a175914d72187d0c3f50ddad50c9569649c3072 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Wed, 7 Aug 2013 17:26:13 -0400 Subject: [PATCH] [uniscribe] Support feature ranges As Khaled pointed out, right now setting any features, turns kern and possibly other features off. --- src/hb-uniscribe.cc | 232 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 199 insertions(+), 33 deletions(-) diff --git a/src/hb-uniscribe.cc b/src/hb-uniscribe.cc index 7f32770f1..6e36e4ee0 100644 --- a/src/hb-uniscribe.cc +++ b/src/hb-uniscribe.cc @@ -229,7 +229,6 @@ free_uniscribe_funcs (void) free (uniscribe_funcs); } - static hb_uniscribe_shaper_funcs_t * hb_uniscribe_shaper_get_funcs (void) { @@ -258,6 +257,39 @@ retry: } +struct active_feature_t { + OPENTYPE_FEATURE_RECORD rec; + unsigned int order; + + static int cmp (const active_feature_t *a, const active_feature_t *b) { + return a->rec.tagFeature < b->rec.tagFeature ? -1 : a->rec.tagFeature > b->rec.tagFeature ? 1 : + a->order < b->order ? -1 : a->order > b->order ? 1 : + a->rec.lParameter < b->rec.lParameter ? -1 : a->rec.lParameter > b->rec.lParameter ? 1 : + 0; + } + bool operator== (const active_feature_t *f) { + return cmp (this, f) == 0; + } +}; + +struct feature_event_t { + unsigned int index; + bool start; + active_feature_t feature; + + static int cmp (const feature_event_t *a, const feature_event_t *b) { + return a->index < b->index ? -1 : a->index > b->index ? 1 : + a->start < b->start ? -1 : a->start > b->start ? 1 : + active_feature_t::cmp (&a->feature, &b->feature); + } +}; + +struct range_record_t { + TEXTRANGE_PROPERTIES props; + unsigned int index_first; /* == start */ + unsigned int index_last; /* == end - 1 */ +}; + HB_SHAPER_DATA_ENSURE_DECLARE(uniscribe, face) HB_SHAPER_DATA_ENSURE_DECLARE(uniscribe, font) @@ -567,6 +599,125 @@ _hb_uniscribe_shape (hb_shape_plan_t *shape_plan, hb_uniscribe_shaper_font_data_t *font_data = HB_SHAPER_DATA_GET (font); hb_uniscribe_shaper_funcs_t *funcs = face_data->funcs; + /* + * Set up features. + */ + hb_auto_array_t feature_records; + hb_auto_array_t range_records; + if (num_features) + { + /* Sort features by start/end events. */ + hb_auto_array_t feature_events; + for (unsigned int i = 0; i < num_features; i++) + { + active_feature_t feature; + feature.rec.tagFeature = hb_uint32_swap (features[i].tag); + feature.rec.lParameter = features[i].value; + feature.order = i; + + feature_event_t *event; + + event = feature_events.push (); + if (unlikely (!event)) + goto fail_features; + event->index = features[i].start; + event->start = true; + event->feature = feature; + + event = feature_events.push (); + if (unlikely (!event)) + goto fail_features; + event->index = features[i].end; + event->start = false; + event->feature = feature; + } + feature_events.sort (); + /* Add a strategic final event. */ + { + active_feature_t feature; + feature.rec.tagFeature = 0; + feature.rec.lParameter = 0; + feature.order = num_features + 1; + + feature_event_t *event = feature_events.push (); + if (unlikely (!event)) + goto fail_features; + event->index = 0; /* This value does magic. */ + event->start = false; + event->feature = feature; + } + + /* Scan events and save features for each range. */ + hb_auto_array_t active_features; + unsigned int last_index = 0; + for (unsigned int i = 0; i < feature_events.len; i++) + { + feature_event_t *event = &feature_events[i]; + + if (event->index != last_index) + { + /* Save a snapshot of active features and the range. */ + range_record_t *range = range_records.push (); + if (unlikely (!range)) + goto fail_features; + + unsigned int offset = feature_records.len; + + active_features.sort (); + for (unsigned int j = 0; j < active_features.len; j++) + { + if (!j || active_features[j].rec.tagFeature != feature_records[feature_records.len - 1].tagFeature) + { + OPENTYPE_FEATURE_RECORD *feature = feature_records.push (); + if (unlikely (!feature)) + goto fail_features; + *feature = active_features[j].rec; + } + else + { + /* Overrides value for existing feature. */ + feature_records[feature_records.len - 1].lParameter = active_features[j].rec.lParameter; + } + } + + /* Will convert to pointer after all is ready, since feature_records.array + * may move as we grow it. */ + range->props.potfRecords = reinterpret_cast (offset); + range->props.cotfRecords = feature_records.len - offset; + range->index_first = last_index; + range->index_last = event->index - 1; + + last_index = event->index; + } + + if (event->start) { + active_feature_t *feature = active_features.push (); + if (unlikely (!feature)) + goto fail_features; + *feature = event->feature; + } else { + active_feature_t *feature = active_features.find (&event->feature); + if (feature) + active_features.remove (feature - active_features.array); + } + } + + if (!range_records.len) /* No active feature found. */ + goto fail_features; + + /* Fixup the pointers. */ + for (unsigned int i = 0; i < range_records.len; i++) + { + range_record_t *range = &range_records[i]; + range->props.potfRecords = feature_records.array + reinterpret_cast (range->props.potfRecords); + } + } + else + { + fail_features: + num_features = 0; + } + #define FAIL(...) \ HB_STMT_START { \ DEBUG_MSG (UNISCRIBE, NULL, __VA_ARGS__); \ @@ -591,7 +742,8 @@ retry: WCHAR *pchars = (WCHAR *) scratch; unsigned int chars_len = 0; - for (unsigned int i = 0; i < buffer->len; i++) { + for (unsigned int i = 0; i < buffer->len; i++) + { hb_codepoint_t c = buffer->info[i].codepoint; buffer->info[i].utf16_index() = chars_len; if (likely (c < 0x10000)) @@ -608,6 +760,20 @@ retry: ALLOCATE_ARRAY (WORD, log_clusters, chars_len); ALLOCATE_ARRAY (SCRIPT_CHARPROP, char_props, chars_len); + if (num_features) + { + /* Need log_clusters to assign features. */ + chars_len = 0; + for (unsigned int i = 0; i < buffer->len; i++) + { + hb_codepoint_t c = buffer->info[i].codepoint; + unsigned int cluster = buffer->info[i].cluster; + log_clusters[chars_len++] = cluster; + if (c >= 0x10000 && c < 0x110000) + log_clusters[chars_len++] = cluster; /* Surrogates. */ + } + } + /* On Windows, we don't care about alignment...*/ unsigned int glyphs_size = scratch_size / (sizeof (WORD) + sizeof (SCRIPT_GLYPHPROP) + @@ -659,30 +825,9 @@ retry: #undef MAX_ITEMS - hb_auto_array_t feature_records; - hb_auto_array_t range_records; + OPENTYPE_TAG language_tag = hb_uint32_swap (hb_ot_tag_from_language (buffer->props.language)); hb_auto_array_t range_properties; hb_auto_array_t range_char_counts; - if (num_features) - { - for (unsigned int i = 0; i < num_features; i++) - { - OPENTYPE_FEATURE_RECORD *record = feature_records.push (); - if (likely (record)) - { - record->tagFeature = hb_uint32_swap (features[i].tag); - record->lParameter = features[i].value; - } - } - TEXTRANGE_PROPERTIES *range_record = range_records.push (); - if (likely (range_record)) - { - range_record->potfRecords = feature_records.array; - range_record->cotfRecords = feature_records.len; - } - } - - OPENTYPE_TAG language_tag = hb_uint32_swap (hb_ot_tag_from_language (buffer->props.language)); unsigned int glyphs_offset = 0; unsigned int glyphs_len; @@ -695,18 +840,39 @@ retry: if (num_features) { - /* XXX We currently ignore feature ranges and apply all features to all characters. - * Also, turning off a previously turned-on feature doesn't work. We have to do - * that part ourselves and pass the active features down to Uniscribe. */ - - range_char_counts.shrink (0); range_properties.shrink (0); + range_char_counts.shrink (0); - range_char_counts.push (); /* Can't fail. */ - range_properties.push (); /* Can't fail. */ + range_record_t *last_range = &range_records[0]; - range_char_counts.array[0] = item_chars_len; - range_properties.array[0] = range_records.array; + for (unsigned int k = chars_offset; k < chars_offset + item_chars_len; k++) + { + range_record_t *range = last_range; + while (log_clusters[k] < range->index_first) + range--; + while (log_clusters[k] > range->index_last) + range++; + if (!range_properties.len || + &range->props != range_properties[range_properties.len - 1]) + { + TEXTRANGE_PROPERTIES **props = range_properties.push (); + int *c = range_char_counts.push (); + if (unlikely (!props || !c)) + { + range_properties.shrink (0); + range_char_counts.shrink (0); + break; + } + *props = &range->props; + *c = 1; + } + else + { + range_char_counts[range_char_counts.len - 1]++; + } + + last_range = range; + } } retry_shape: