dwrite: Implement single adjustment, GPOS lookup 1.

Signed-off-by: Nikolay Sivov <nsivov@codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
Nikolay Sivov 2019-02-01 13:48:09 +03:00 committed by Alexandre Julliard
parent 1d8a2ba4f7
commit 623f059b07
3 changed files with 311 additions and 0 deletions

View File

@ -1316,7 +1316,9 @@ static HRESULT WINAPI dwritetextanalyzer_GetGlyphPlacements(IDWriteTextAnalyzer2
context.u.pos.glyphs = glyphs;
context.u.pos.glyph_props = glyph_props;
context.glyph_count = glyph_count;
context.emsize = emSize;
context.advances = advances;
context.offsets = offsets;
context.language_tag = get_opentype_language(locale);
hr = shape_get_positions(&context, scriptprops->scripttags, scriptprops->ops->gpos_features);
@ -1378,7 +1380,9 @@ static HRESULT WINAPI dwritetextanalyzer_GetGdiCompatibleGlyphPlacements(IDWrite
context.u.pos.glyphs = glyphs;
context.u.pos.glyph_props = glyph_props;
context.glyph_count = glyph_count;
context.emsize = emSize * ppdip;
context.advances = advances;
context.offsets = offsets;
context.language_tag = get_opentype_language(locale);
hr = shape_get_positions(&context, scriptprops->scripttags, scriptprops->ops->gpos_features);

View File

@ -393,7 +393,9 @@ struct scriptshaping_context
} u;
unsigned int glyph_count;
float emsize;
float *advances;
DWRITE_GLYPH_OFFSET *offsets;
};
struct shaping_font_ops

View File

@ -489,6 +489,18 @@ enum gpos_lookup_type
GPOS_LOOKUP_EXTENSION_POSITION = 9,
};
enum gpos_value_format
{
GPOS_VALUE_X_PLACEMENT = 0x1,
GPOS_VALUE_Y_PLACEMENT = 0x2,
GPOS_VALUE_X_ADVANCE = 0x4,
GPOS_VALUE_Y_ADVANCE = 0x8,
GPOS_VALUE_X_PLACEMENT_DEVICE = 0x10,
GPOS_VALUE_Y_PLACEMENT_DEVICE = 0x20,
GPOS_VALUE_X_ADVANCE_DEVICE = 0x40,
GPOS_VALUE_Y_ADVANCE_DEVICE = 0x80,
};
enum OPENTYPE_PLATFORM_ID
{
OPENTYPE_PLATFORM_UNICODE = 0,
@ -526,6 +538,54 @@ struct ot_lookup_table
WORD subtable[1];
};
#define GLYPH_NOT_COVERED ((unsigned int)~0u)
struct ot_coverage_format1
{
WORD format;
WORD glyph_count;
WORD glyphs[1];
};
struct ot_coverage_range
{
WORD start_glyph;
WORD end_glyph;
WORD startcoverage_index;
};
struct ot_coverage_format2
{
WORD format;
WORD range_count;
struct ot_coverage_range ranges[1];
};
struct ot_gpos_device_table
{
WORD start_size;
WORD end_size;
WORD format;
WORD values[1];
};
struct ot_gpos_singlepos_format1
{
WORD format;
WORD coverage;
WORD value_format;
WORD value[1];
};
struct ot_gpos_singlepos_format2
{
WORD format;
WORD coverage;
WORD value_format;
WORD value_count;
WORD values[1];
};
typedef struct {
WORD SubstFormat;
WORD Coverage;
@ -2635,6 +2695,201 @@ static unsigned int opentype_layout_get_glyph_class(const struct dwrite_fonttabl
return glyph_class;
}
struct coverage_compare_format1_context
{
UINT16 glyph;
const UINT16 *table_base;
unsigned int *coverage_index;
};
static int coverage_compare_format1(const void *left, const void *right)
{
const struct coverage_compare_format1_context *context = left;
UINT16 glyph = GET_BE_WORD(*(UINT16 *)right);
int ret;
ret = context->glyph - glyph;
if (!ret)
*context->coverage_index = (UINT16 *)right - context->table_base;
return ret;
}
static int coverage_compare_format2(const void *g, const void *r)
{
const struct ot_coverage_range *range = r;
UINT16 glyph = *(UINT16 *)g;
if (glyph < GET_BE_WORD(range->start_glyph))
return -1;
else if (glyph > GET_BE_WORD(range->end_glyph))
return 1;
else
return 0;
}
static unsigned int opentype_layout_is_glyph_covered(const struct dwrite_fonttable *table, DWORD coverage,
UINT16 glyph)
{
WORD format = table_read_be_word(table, coverage), count;
count = table_read_be_word(table, coverage + 2);
if (format == 1)
{
const struct ot_coverage_format1 *format1 = table_read_ensure(table, coverage,
FIELD_OFFSET(struct ot_coverage_format1, glyphs[count]));
struct coverage_compare_format1_context context;
unsigned int coverage_index = GLYPH_NOT_COVERED;
if (format1)
{
context.glyph = glyph;
context.table_base = format1->glyphs;
context.coverage_index = &coverage_index;
bsearch(&context, format1->glyphs, count, sizeof(glyph), coverage_compare_format1);
}
return coverage_index;
}
else if (format == 2)
{
const struct ot_coverage_format2 *format2 = table_read_ensure(table, coverage,
FIELD_OFFSET(struct ot_coverage_format2, ranges[count]));
if (format2)
{
const struct ot_coverage_range *range = bsearch(&glyph, format2->ranges, count,
sizeof(struct ot_coverage_range), coverage_compare_format2);
return range && glyph <= GET_BE_WORD(range->end_glyph) ?
GET_BE_WORD(range->startcoverage_index) + glyph - GET_BE_WORD(range->start_glyph) :
GLYPH_NOT_COVERED;
}
}
else
WARN("Unknown coverage format %u.\n", format);
return -1;
}
static inline unsigned int dwrite_popcount(unsigned int x)
{
#ifdef HAVE___BUILTIN_POPCOUNT
return __builtin_popcount(x);
#else
x -= x >> 1 & 0x55555555;
x = (x & 0x33333333) + (x >> 2 & 0x33333333);
return ((x + (x >> 4)) & 0x0f0f0f0f) * 0x01010101 >> 24;
#endif
}
static float opentype_scale_gpos_be_value(WORD value, float emsize, UINT16 upem)
{
return (short)GET_BE_WORD(value) * emsize / upem;
}
static int opentype_layout_gpos_get_dev_value(const struct scriptshaping_context *context, unsigned int offset)
{
const struct scriptshaping_cache *cache = context->cache;
unsigned int start_size, end_size, format, value_word;
unsigned int index, ppem, mask;
int value;
if (!offset)
return 0;
start_size = table_read_be_word(&cache->gpos.table, offset);
end_size = table_read_be_word(&cache->gpos.table, offset + FIELD_OFFSET(struct ot_gpos_device_table, end_size));
ppem = context->emsize;
if (ppem < start_size || ppem > end_size)
return 0;
format = table_read_be_word(&cache->gpos.table, offset + FIELD_OFFSET(struct ot_gpos_device_table, format));
if (format < 1 || format > 3)
return 0;
index = ppem - start_size;
value_word = table_read_be_word(&cache->gpos.table, offset +
FIELD_OFFSET(struct ot_gpos_device_table, values[index >> (4 - format)]));
mask = 0xffff >> (16 - (1 << format));
value = (value_word >> ((index % (4 - format)) * (1 << format))) & mask;
if ((unsigned int)value >= ((mask + 1) >> 1))
value -= mask + 1;
return value;
}
static void opentype_layout_apply_gpos_value(struct scriptshaping_context *context, unsigned int table_offset,
WORD value_format, const WORD *values, unsigned int glyph)
{
const struct scriptshaping_cache *cache = context->cache;
DWRITE_GLYPH_OFFSET *offset = &context->offsets[glyph];
float *advance = &context->advances[glyph];
if (!value_format)
return;
if (value_format & GPOS_VALUE_X_PLACEMENT)
{
offset->advanceOffset += opentype_scale_gpos_be_value(*values, context->emsize, cache->upem);
values++;
}
if (value_format & GPOS_VALUE_Y_PLACEMENT)
{
offset->ascenderOffset += opentype_scale_gpos_be_value(*values, context->emsize, cache->upem);
values++;
}
if (value_format & GPOS_VALUE_X_ADVANCE)
{
*advance += opentype_scale_gpos_be_value(*values, context->emsize, cache->upem);
values++;
}
if (value_format & GPOS_VALUE_Y_ADVANCE)
{
values++;
}
if (value_format & GPOS_VALUE_X_PLACEMENT_DEVICE)
{
offset->advanceOffset += opentype_layout_gpos_get_dev_value(context, table_offset + GET_BE_WORD(*values));
values++;
}
if (value_format & GPOS_VALUE_Y_PLACEMENT_DEVICE)
{
offset->ascenderOffset += opentype_layout_gpos_get_dev_value(context, table_offset + GET_BE_WORD(*values));
values++;
}
if (value_format & GPOS_VALUE_X_ADVANCE_DEVICE)
{
*advance += opentype_layout_gpos_get_dev_value(context, table_offset + GET_BE_WORD(*values));
values++;
}
if (value_format & GPOS_VALUE_Y_ADVANCE_DEVICE)
{
values++;
}
}
static unsigned int opentype_layout_get_gpos_subtable(const struct scriptshaping_cache *cache,
unsigned int lookup_offset, unsigned int subtable)
{
WORD lookup_type = table_read_be_word(&cache->gpos.table, lookup_offset);
unsigned int subtable_offset = table_read_be_word(&cache->gpos.table, lookup_offset +
FIELD_OFFSET(struct ot_lookup_table, subtable[subtable]));
if (lookup_type == GPOS_LOOKUP_EXTENSION_POSITION)
{
const struct ot_gpos_extensionpos_format1 *format1 = table_read_ensure(&cache->gpos.table,
lookup_offset + subtable_offset, sizeof(*format1));
subtable_offset += GET_BE_DWORD(format1->extension_offset);
}
return lookup_offset + subtable_offset;
}
struct lookup
{
unsigned int offset;
@ -2677,6 +2932,56 @@ static BOOL glyph_iterator_match(const struct glyph_iterator *iter)
static BOOL opentype_layout_apply_gpos_single_adjustment(struct scriptshaping_context *context,
struct glyph_iterator *iter, const struct lookup *lookup)
{
struct scriptshaping_cache *cache = context->cache;
WORD format, value_format, value_len, coverage;
unsigned int i;
for (i = 0; i < lookup->subtable_count; ++i)
{
unsigned int subtable_offset = opentype_layout_get_gpos_subtable(cache, lookup->offset, i);
unsigned int coverage_index;
format = table_read_be_word(&cache->gpos.table, subtable_offset);
coverage = table_read_be_word(&cache->gpos.table, subtable_offset +
FIELD_OFFSET(struct ot_gpos_singlepos_format1, coverage));
value_format = table_read_be_word(&cache->gpos.table, subtable_offset +
FIELD_OFFSET(struct ot_gpos_singlepos_format1, value_format));
value_len = dwrite_popcount(value_format);
if (format == 1)
{
const struct ot_gpos_singlepos_format1 *format1 = table_read_ensure(&cache->gpos.table, subtable_offset,
FIELD_OFFSET(struct ot_gpos_singlepos_format1, value[value_len]));
coverage_index = opentype_layout_is_glyph_covered(&cache->gpos.table, subtable_offset + coverage,
context->u.pos.glyphs[iter->pos]);
if (coverage_index == GLYPH_NOT_COVERED)
continue;
opentype_layout_apply_gpos_value(context, subtable_offset, value_format, format1->value, iter->pos);
break;
}
else if (format == 2)
{
WORD value_count = table_read_be_word(&cache->gpos.table, subtable_offset +
FIELD_OFFSET(struct ot_gpos_singlepos_format2, value_count));
const struct ot_gpos_singlepos_format2 *format2 = table_read_ensure(&cache->gpos.table, subtable_offset,
FIELD_OFFSET(struct ot_gpos_singlepos_format2, values) + value_count * value_len * sizeof(WORD));
coverage_index = opentype_layout_is_glyph_covered(&cache->gpos.table, subtable_offset + coverage,
context->u.pos.glyphs[iter->pos]);
if (coverage_index == GLYPH_NOT_COVERED || coverage_index >= value_count)
continue;
opentype_layout_apply_gpos_value(context, subtable_offset, value_format,
&format2->values[coverage_index * value_len], iter->pos);
break;
}
else
WARN("Unknown single adjustment format %u.\n", format);
}
return FALSE;
}