dwrite: Implement ApplyCharacterSpacing().

This commit is contained in:
Nikolay Sivov 2015-04-27 00:20:32 +03:00 committed by Alexandre Julliard
parent 4a1ebd7369
commit 14158a71db
2 changed files with 379 additions and 2 deletions

View File

@ -1076,14 +1076,229 @@ static HRESULT WINAPI dwritetextanalyzer_GetGdiCompatibleGlyphPlacements(IDWrite
return E_NOTIMPL;
}
static inline FLOAT get_cluster_advance(const FLOAT *advances, UINT32 start, UINT32 end)
{
FLOAT advance = 0.0;
for (; start < end; start++)
advance += advances[start];
return advance;
}
static void apply_single_glyph_spacing(FLOAT leading_spacing, FLOAT trailing_spacing,
FLOAT min_advance_width, UINT32 g, FLOAT const *advances, DWRITE_GLYPH_OFFSET const *offsets,
DWRITE_SHAPING_GLYPH_PROPERTIES const *props, FLOAT *modified_advances, DWRITE_GLYPH_OFFSET *modified_offsets)
{
BOOL reduced = leading_spacing < 0.0 || trailing_spacing < 0.0;
FLOAT advance = advances[g];
FLOAT origin = 0.0;
if (props[g].isZeroWidthSpace) {
modified_advances[g] = advances[g];
modified_offsets[g] = offsets[g];
return;
}
/* first apply negative spacing and check if we hit minimum width */
if (leading_spacing < 0.0) {
advance += leading_spacing;
origin -= leading_spacing;
}
if (trailing_spacing < 0.0)
advance += trailing_spacing;
if (advance < min_advance_width) {
FLOAT half = (min_advance_width - advance) / 2.0;
if (!reduced)
origin -= half;
else if (leading_spacing < 0.0 && trailing_spacing < 0.0)
origin -= half;
else if (leading_spacing < 0.0)
origin -= min_advance_width - advance;
advance = min_advance_width;
}
/* now apply positive spacing adjustments */
if (leading_spacing > 0.0) {
advance += leading_spacing;
origin -= leading_spacing;
}
if (trailing_spacing > 0.0)
advance += trailing_spacing;
modified_advances[g] = advance;
modified_offsets[g].advanceOffset = offsets[g].advanceOffset - origin;
/* ascender is never touched, it's orthogonal to reading direction and is not
affected by advance adjustments */
modified_offsets[g].ascenderOffset = offsets[g].ascenderOffset;
}
static void apply_cluster_spacing(FLOAT leading_spacing, FLOAT trailing_spacing, FLOAT min_advance_width,
UINT32 start, UINT32 end, FLOAT const *advances, DWRITE_GLYPH_OFFSET const *offsets,
FLOAT *modified_advances, DWRITE_GLYPH_OFFSET *modified_offsets)
{
BOOL reduced = leading_spacing < 0.0 || trailing_spacing < 0.0;
FLOAT advance = get_cluster_advance(advances, start, end);
FLOAT origin = 0.0;
UINT16 g;
modified_advances[start] = advances[start];
modified_advances[end-1] = advances[end-1];
/* first apply negative spacing and check if we hit minimum width */
if (leading_spacing < 0.0) {
advance += leading_spacing;
modified_advances[start] += leading_spacing;
origin -= leading_spacing;
}
if (trailing_spacing < 0.0) {
advance += trailing_spacing;
modified_advances[end-1] += trailing_spacing;
}
if (advance < min_advance_width) {
/* additional spacing is only applied to leading and trailing glyph */
FLOAT half = (min_advance_width - advance) / 2.0;
if (!reduced) {
origin -= half;
modified_advances[start] += half;
modified_advances[end-1] += half;
}
else if (leading_spacing < 0.0 && trailing_spacing < 0.0) {
origin -= half;
modified_advances[start] += half;
modified_advances[end-1] += half;
}
else if (leading_spacing < 0.0) {
origin -= min_advance_width - advance;
modified_advances[start] += min_advance_width - advance;
}
else
modified_advances[end-1] += min_advance_width - advance;
advance = min_advance_width;
}
/* now apply positive spacing adjustments */
if (leading_spacing > 0.0) {
modified_advances[start] += leading_spacing;
origin -= leading_spacing;
}
if (trailing_spacing > 0.0)
modified_advances[end-1] += trailing_spacing;
for (g = start; g < end; g++) {
if (g == start) {
modified_offsets[g].advanceOffset = offsets[g].advanceOffset - origin;
modified_offsets[g].ascenderOffset = offsets[g].ascenderOffset;
}
else if (g == end - 1)
/* trailing glyph offset is not adjusted */
modified_offsets[g] = offsets[g];
else {
/* for all glyphs within a cluster use original advances and offsets */
modified_advances[g] = advances[g];
modified_offsets[g] = offsets[g];
}
}
}
static inline UINT32 get_cluster_length(UINT16 const *clustermap, UINT32 start, UINT32 text_len)
{
UINT16 g = clustermap[start];
UINT32 length = 1;
while (start < text_len && clustermap[++start] == g)
length++;
return length;
}
/* Applies spacing adjustments to clusters.
Adjustments are applied in the following order:
1. Negative adjustments
Leading and trailing spacing could be negative, at this step
only negative ones are actually applied. Leading spacing is only
applied to leading glyph, trailing - to trailing glyph.
2. Minimum advance width
Advances could only be reduced at this point or unchanged. In any
case it's checked if cluster advance width is less than minimum width.
If it's the case advance width is incremented up to minimum value.
Important part is the direction in which this increment is applied;
it depends on from which directions total cluster advance was trimmed
at step 1. So it could be incremented from leading, trailing, or both
sides. When applied to both sides, each side gets half of difference
that bring advance to minimum width.
3. Positive adjustments
After minimum width rule was applied, positive spacing is applied in same
way as negative ones on step 1.
Glyph offset for leading glyph is adjusted too in a way that glyph origin
keeps its position in coordinate system where initial advance width is counted
from 0.
Glyph properties
It's known that isZeroWidthSpace property keeps initial advance from changing.
TODO: test other properties; make isZeroWidthSpace work properly for clusters
with more than one glyph.
*/
static HRESULT WINAPI dwritetextanalyzer1_ApplyCharacterSpacing(IDWriteTextAnalyzer2 *iface,
FLOAT leading_spacing, FLOAT trailing_spacing, FLOAT min_advance_width, UINT32 len,
UINT32 glyph_count, UINT16 const *clustermap, FLOAT const *advances, DWRITE_GLYPH_OFFSET const *offsets,
DWRITE_SHAPING_GLYPH_PROPERTIES const *props, FLOAT *modified_advances, DWRITE_GLYPH_OFFSET *modified_offsets)
{
FIXME("(%.2f %.2f %.2f %u %u %p %p %p %p %p %p): stub\n", leading_spacing, trailing_spacing, min_advance_width,
UINT16 start;
TRACE("(%.2f %.2f %.2f %u %u %p %p %p %p %p %p)\n", leading_spacing, trailing_spacing, min_advance_width,
len, glyph_count, clustermap, advances, offsets, props, modified_advances, modified_offsets);
return E_NOTIMPL;
if (min_advance_width < 0.0) {
memset(modified_advances, 0, glyph_count*sizeof(*modified_advances));
return E_INVALIDARG;
}
/* minimum advance is not applied if no adjustments were made */
if (leading_spacing == 0.0 && trailing_spacing == 0.0) {
memmove(modified_advances, advances, glyph_count*sizeof(*advances));
memmove(modified_offsets, offsets, glyph_count*sizeof(*offsets));
return S_OK;
}
start = 0;
for (start = 0; start < len;) {
UINT32 length = get_cluster_length(clustermap, start, len);
if (length == 1) {
UINT32 g = clustermap[start];
apply_single_glyph_spacing(leading_spacing, trailing_spacing, min_advance_width,
g, advances, offsets, props, modified_advances, modified_offsets);
}
else {
UINT32 g_start, g_end;
g_start = clustermap[start];
g_end = (start + length < len) ? clustermap[start + length] : glyph_count;
apply_cluster_spacing(leading_spacing, trailing_spacing, min_advance_width,
g_start, g_end, advances, offsets, modified_advances, modified_offsets);
}
start += length;
}
return S_OK;
}
static HRESULT WINAPI dwritetextanalyzer1_GetBaseline(IDWriteTextAnalyzer2 *iface, IDWriteFontFace *face,

View File

@ -1449,6 +1449,167 @@ static void test_GetGlyphPlacements(void)
DELETE_FONTFILE(path);
}
struct spacing_test {
FLOAT leading;
FLOAT trailing;
FLOAT min_advance;
FLOAT advances[3];
FLOAT offsets[3];
FLOAT modified_advances[3];
FLOAT modified_offsets[3];
BOOL single_cluster;
BOOL is_ZWS[3];
};
static const struct spacing_test spacing_tests[] = {
{ 0.0, 0.0, 0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, { 2.0, 3.0 } }, /* 0 */
{ 0.0, 0.0, 2.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, { 2.0, 3.0 } },
{ 1.0, 0.0, 0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 11.0, 12.0 }, { 3.0, 4.0 } },
{ 1.0, 1.0, 0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 12.0, 13.0 }, { 3.0, 4.0 } },
{ 1.0, -1.0, 0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, { 3.0, 4.0 } },
{ 0.0, -10.0, 0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 0.0, 1.0 }, { 2.0, 3.0 } }, /* 5 */
{ -5.0, -4.0, 5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 5.0, 5.0 }, { -1.0, -0.5 } },
{ -5.0, -5.0, 5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 5.0, 5.0 }, { -0.5, 0.0 } },
{ 2.0, 0.0, 5.0, { 1.0, 2.0 }, { 2.0, 3.0 }, { 7.0, 7.0 }, { 6.0, 6.5 } },
{ 2.0, 1.0, 5.0, { 1.0, 2.0 }, { 2.0, 3.0 }, { 8.0, 8.0 }, { 6.0, 6.5 } },
{ 2.0, -10.0, 5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 7.0, 7.0 }, { 4.0, 5.0 } }, /* 10 */
{ 1.0, -10.0, 5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 6.0, 6.0 }, { 3.0, 4.0 } },
{ -10.0, 1.0, 5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 6.0, 6.0 }, { -3.0, -3.0 } },
{ 0.0, -10.0, 5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 5.0, 5.0 }, { 2.0, 3.0 } },
{ 1.0, -10.0, -5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 0.0, 0.0 }, { 2.0, 3.0 } },
{ -10.0, 1.0, 5.0, { 8.0, 11.0 }, { 2.0, 3.0 }, { 6.0, 6.0 }, { -1.0, -3.0 } }, /* 15 */
/* cluster of more than 1 glyph */
{ 0.0, 0.0, 0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, { 2.0, 3.0 }, TRUE },
{ 1.0, 0.0, 0.0, { 10.0, 11.0 }, { 2.0, 3.5 }, { 11.0, 11.0 }, { 3.0, 3.5 }, TRUE },
{ 1.0, 1.0, 0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 11.0, 12.0 }, { 3.0, 3.0 }, TRUE },
{ 1.0, -1.0, 0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 11.0, 10.0 }, { 3.0, 3.0 }, TRUE },
{ 0.0, -10.0, 0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 1.0 }, { 2.0, 3.0 }, TRUE }, /* 20 */
{ 0.0, -10.0, 5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 1.0 }, { 2.0, 3.0 }, TRUE },
{ 1.0, -10.0, -5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 0.0, 0.0 }, { 2.0, 3.0 }, TRUE },
{ -5.0, -10.0, 4.0, { 10.0, 11.0, 12.0 }, { 2.0, 3.0, 4.0 }, { 5.0, 11.0, 2.0 }, { -3.0, 3.0, 4.0 }, TRUE },
{ -10.0, -10.0, 4.0, { 10.0, 11.0, 12.0 }, { 2.0, 3.0, 4.0 }, { 0.0, 11.0, 2.0 }, { -8.0, 3.0, 4.0 }, TRUE },
{ -10.0, -10.0, 5.0, { 10.0, 1.0, 12.0 }, { 2.0, 3.0, 4.0 }, { 1.0, 1.0, 3.0 }, { -7.0, 3.0, 4.0 }, TRUE }, /* 25 */
{ -10.0, 1.0, 5.0, { 10.0, 1.0, 2.0 }, { 2.0, 3.0, 4.0 }, { 2.0, 1.0, 3.0 }, { -6.0, 3.0, 4.0 }, TRUE },
{ 1.0, -10.0, 5.0, { 2.0, 1.0, 10.0 }, { 2.0, 3.0, 4.0 }, { 3.0, 1.0, 2.0 }, { 3.0, 3.0, 4.0 }, TRUE },
{ -10.0, -10.0, 5.0, { 11.0, 1.0, 11.0 }, { 2.0, 3.0, 4.0 }, { 2.0, 1.0, 2.0 }, { -7.0, 3.0, 4.0 }, TRUE },
/* isZeroWidthSpace set */
{ 1.0, 0.0, 0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 12.0 }, { 2.0, 4.0 }, FALSE, { TRUE, FALSE } },
{ 1.0, 1.0, 0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 13.0 }, { 2.0, 4.0 }, FALSE, { TRUE, FALSE } }, /* 30 */
{ 1.0, -1.0, 0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, { 3.0, 3.0 }, FALSE, { FALSE, TRUE } },
{ 0.0, -10.0, 0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 1.0 }, { 2.0, 3.0 }, FALSE, { TRUE, FALSE } },
{ -5.0, -4.0, 5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 5.0, 11.0 }, { -1.0, 3.0 }, FALSE, { FALSE, TRUE } },
{ -5.0, -5.0, 5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, { 2.0, 3.0 }, FALSE, { TRUE, TRUE } },
{ 2.0, 0.0, 5.0, { 1.0, 2.0 }, { 2.0, 3.0 }, { 7.0, 2.0 }, { 6.0, 3.0 }, FALSE, { FALSE, TRUE } }, /* 35 */
{ 2.0, 1.0, 5.0, { 1.0, 2.0 }, { 2.0, 3.0 }, { 8.0, 2.0 }, { 6.0, 3.0 }, FALSE, { FALSE, TRUE } },
{ 2.0, -10.0, 5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, { 2.0, 3.0 }, FALSE, { TRUE, TRUE } },
{ 1.0, -10.0, 5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 6.0, 11.0 }, { 3.0, 3.0 }, FALSE, { FALSE, TRUE } },
{ -10.0, 1.0, 5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, { 2.0, 3.0 }, FALSE, { TRUE, TRUE } },
{ 0.0, -10.0, 5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 5.0, 11.0 }, { 2.0, 3.0 }, FALSE, { FALSE, TRUE } }, /* 40 */
{ 1.0, -10.0, -5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 0.0, 0.0 }, { 2.0, 3.0 }, FALSE, { TRUE, FALSE } },
{ -10.0, 1.0, 5.0, { 8.0, 11.0 }, { 2.0, 3.0 }, { 6.0, 11.0 }, { -1.0, 3.0 }, FALSE, { FALSE, TRUE } },
};
static void test_ApplyCharacterSpacing(void)
{
DWRITE_SHAPING_GLYPH_PROPERTIES props[3];
IDWriteTextAnalyzer1 *analyzer1;
IDWriteTextAnalyzer *analyzer;
UINT16 clustermap[2];
HRESULT hr;
int i;
hr = IDWriteFactory_CreateTextAnalyzer(factory, &analyzer);
ok(hr == S_OK, "got 0x%08x\n", hr);
hr = IDWriteTextAnalyzer_QueryInterface(analyzer, &IID_IDWriteTextAnalyzer1, (void**)&analyzer1);
IDWriteTextAnalyzer_Release(analyzer);
if (hr != S_OK) {
win_skip("ApplyCharacterSpacing() is not supported.\n");
return;
}
for (i = 0; i < sizeof(spacing_tests)/sizeof(spacing_tests[0]); i++) {
const struct spacing_test *ptr = spacing_tests + i;
DWRITE_GLYPH_OFFSET offsets[3];
UINT32 glyph_count;
FLOAT advances[3];
offsets[0].advanceOffset = ptr->offsets[0];
offsets[1].advanceOffset = ptr->offsets[1];
offsets[2].advanceOffset = ptr->offsets[2];
/* Ascender offsets are never thouched as spacing applies in reading direction only,
we'll only test them to see if they are not changed */
offsets[0].ascenderOffset = 23.0;
offsets[1].ascenderOffset = 32.0;
offsets[2].ascenderOffset = 31.0;
glyph_count = ptr->advances[2] > 0.0 ? 3 : 2;
if (ptr->single_cluster) {
clustermap[0] = 0;
clustermap[1] = 0;
}
else {
/* trivial case with one glyph per cluster */
clustermap[0] = 0;
clustermap[1] = 1;
}
advances[0] = advances[1] = 123.45;
memset(props, 0, sizeof(props));
props[0].isZeroWidthSpace = ptr->is_ZWS[0];
props[1].isZeroWidthSpace = ptr->is_ZWS[1];
props[2].isZeroWidthSpace = ptr->is_ZWS[2];
hr = IDWriteTextAnalyzer1_ApplyCharacterSpacing(analyzer1,
ptr->leading,
ptr->trailing,
ptr->min_advance,
sizeof(clustermap)/sizeof(clustermap[0]),
glyph_count,
clustermap,
ptr->advances,
offsets,
props,
advances,
offsets);
/* invalid argument cases */
if (ptr->min_advance < 0.0)
ok(hr == E_INVALIDARG, "%d: got 0x%08x\n", i, hr);
else
ok(hr == S_OK, "%d: got 0x%08x\n", i, hr);
if (hr == S_OK) {
ok(ptr->modified_advances[0] == advances[0], "%d: got advance[0] %.2f, expected %.2f\n", i, advances[0], ptr->modified_advances[0]);
ok(ptr->modified_advances[1] == advances[1], "%d: got advance[1] %.2f, expected %.2f\n", i, advances[1], ptr->modified_advances[1]);
if (glyph_count > 2)
ok(ptr->modified_advances[2] == advances[2], "%d: got advance[2] %.2f, expected %.2f\n", i, advances[2], ptr->modified_advances[2]);
ok(ptr->modified_offsets[0] == offsets[0].advanceOffset, "%d: got offset[0] %.2f, expected %.2f\n", i,
offsets[0].advanceOffset, ptr->modified_offsets[0]);
ok(ptr->modified_offsets[1] == offsets[1].advanceOffset, "%d: got offset[1] %.2f, expected %.2f\n", i,
offsets[1].advanceOffset, ptr->modified_offsets[1]);
if (glyph_count > 2)
ok(ptr->modified_offsets[2] == offsets[2].advanceOffset, "%d: got offset[2] %.2f, expected %.2f\n", i,
offsets[2].advanceOffset, ptr->modified_offsets[2]);
ok(offsets[0].ascenderOffset == 23.0, "%d: unexpected ascenderOffset %.2f\n", i, offsets[0].ascenderOffset);
ok(offsets[1].ascenderOffset == 32.0, "%d: unexpected ascenderOffset %.2f\n", i, offsets[1].ascenderOffset);
ok(offsets[2].ascenderOffset == 31.0, "%d: unexpected ascenderOffset %.2f\n", i, offsets[2].ascenderOffset);
}
else {
ok(ptr->modified_advances[0] == advances[0], "%d: got advance[0] %.2f, expected %.2f\n", i, advances[0], ptr->modified_advances[0]);
ok(ptr->modified_advances[1] == advances[1], "%d: got advance[1] %.2f, expected %.2f\n", i, advances[1], ptr->modified_advances[1]);
ok(ptr->offsets[0] == offsets[0].advanceOffset, "%d: got offset[0] %.2f, expected %.2f\n", i,
offsets[0].advanceOffset, ptr->modified_offsets[0]);
ok(ptr->offsets[1] == offsets[1].advanceOffset, "%d: got offset[1] %.2f, expected %.2f\n", i,
offsets[1].advanceOffset, ptr->modified_offsets[1]);
ok(offsets[0].ascenderOffset == 23.0, "%d: unexpected ascenderOffset %.2f\n", i, offsets[0].ascenderOffset);
ok(offsets[1].ascenderOffset == 32.0, "%d: unexpected ascenderOffset %.2f\n", i, offsets[1].ascenderOffset);
}
}
IDWriteTextAnalyzer1_Release(analyzer1);
}
START_TEST(analyzer)
{
HRESULT hr;
@ -1472,6 +1633,7 @@ START_TEST(analyzer)
test_numbersubstitution();
test_GetTypographicFeatures();
test_GetGlyphPlacements();
test_ApplyCharacterSpacing();
IDWriteFactory_Release(factory);
}