dwrite: Implement support for underlines.

Signed-off-by: Nikolay Sivov <nsivov@codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
Nikolay Sivov 2016-01-15 00:24:23 +03:00 committed by Alexandre Julliard
parent cfbac8d2ed
commit c5fce83f3d
2 changed files with 203 additions and 22 deletions

View File

@ -190,7 +190,8 @@ struct layout_effective_run {
FLOAT align_dx; /* adjustment from text alignment */
FLOAT width; /* run width */
UINT16 *clustermap; /* effective clustermap, allocated separately, is not reused from nominal map */
UINT32 line;
UINT32 line; /* 0-based line index in line metrics array */
BOOL underlined; /* set if this run is underlined */
};
struct layout_effective_inline {
@ -206,6 +207,12 @@ struct layout_effective_inline {
UINT32 line;
};
struct layout_underline {
struct list entry;
const struct layout_effective_run *run;
DWRITE_UNDERLINE u;
};
struct layout_strikethrough {
struct list entry;
const struct layout_effective_run *run;
@ -244,6 +251,7 @@ struct dwrite_textlayout {
/* lists ready to use by Draw() */
struct list eruns;
struct list inlineobjects;
struct list underlines;
struct list strikethrough;
USHORT recompute;
@ -459,6 +467,7 @@ static void free_layout_eruns(struct dwrite_textlayout *layout)
struct layout_effective_inline *in, *in2;
struct layout_effective_run *cur, *cur2;
struct layout_strikethrough *s, *s2;
struct layout_underline *u, *u2;
LIST_FOR_EACH_ENTRY_SAFE(cur, cur2, &layout->eruns, struct layout_effective_run, entry) {
list_remove(&cur->entry);
@ -471,6 +480,11 @@ static void free_layout_eruns(struct dwrite_textlayout *layout)
heap_free(in);
}
LIST_FOR_EACH_ENTRY_SAFE(u, u2, &layout->underlines, struct layout_underline, entry) {
list_remove(&u->entry);
heap_free(u);
}
LIST_FOR_EACH_ENTRY_SAFE(s, s2, &layout->strikethrough, struct layout_strikethrough, entry) {
list_remove(&s->entry);
heap_free(s);
@ -1002,7 +1016,7 @@ static inline BOOL layout_get_strikethrough_from_pos(struct dwrite_textlayout *l
static inline BOOL layout_get_underline_from_pos(struct dwrite_textlayout *layout, UINT32 pos)
{
struct layout_range_header *h = get_layout_range_header_by_pos(&layout->ranges, pos);
struct layout_range_header *h = get_layout_range_header_by_pos(&layout->underline_ranges, pos);
return ((struct layout_range_bool*)h)->value;
}
@ -1022,6 +1036,24 @@ static BOOL is_same_splitting_params(const struct layout_final_splitting_params
left->effect == right->effect;
}
static void layout_get_erun_font_metrics(struct dwrite_textlayout *layout, struct layout_effective_run *erun,
DWRITE_FONT_METRICS *metrics)
{
memset(metrics, 0, sizeof(*metrics));
if (is_layout_gdi_compatible(layout)) {
HRESULT hr = IDWriteFontFace_GetGdiCompatibleMetrics(
erun->run->u.regular.run.fontFace,
erun->run->u.regular.run.fontEmSize,
layout->ppdip,
&layout->transform,
metrics);
if (FAILED(hr))
WARN("failed to get font metrics, 0x%08x\n", hr);
}
else
IDWriteFontFace_GetMetrics(erun->run->u.regular.run.fontFace, metrics);
}
/* Effective run is built from consecutive clusters of a single nominal run, 'first_cluster' is 0 based cluster index,
'cluster_count' indicates how many clusters to add, including first one. */
static HRESULT layout_add_effective_run(struct dwrite_textlayout *layout, const struct layout_run *r, UINT32 first_cluster,
@ -1105,32 +1137,21 @@ static HRESULT layout_add_effective_run(struct dwrite_textlayout *layout, const
run->clustermap[i] = r->u.regular.clustermap[start + i] - r->u.regular.clustermap[start];
run->effect = params->effect;
run->underlined = params->underline;
list_add_tail(&layout->eruns, &run->entry);
/* Strikethrough style is guaranteed to be consistent within effective run,
it's width equals to run width, thikness and offset are derived from
font metrics, rest of the values are from layout or run itself */
if (params->strikethrough) {
DWRITE_FONT_METRICS metrics = { 0 };
struct layout_strikethrough *s;
DWRITE_FONT_METRICS metrics;
s = heap_alloc(sizeof(*s));
if (!s)
return E_OUTOFMEMORY;
if (is_layout_gdi_compatible(layout)) {
HRESULT hr = IDWriteFontFace_GetGdiCompatibleMetrics(
r->u.regular.run.fontFace,
r->u.regular.run.fontEmSize,
layout->ppdip,
&layout->transform,
&metrics);
if (FAILED(hr))
WARN("failed to get font metrics, 0x%08x\n", hr);
}
else
IDWriteFontFace_GetMetrics(r->u.regular.run.fontFace, &metrics);
layout_get_erun_font_metrics(layout, run, &metrics);
s->s.width = get_cluster_range_width(layout, first_cluster, first_cluster + cluster_count);
s->s.thickness = SCALE_FONT_METRIC(metrics.strikethroughThickness, r->u.regular.run.fontEmSize, &metrics);
/* Negative offset moves it above baseline as Y coordinate grows downward. */
@ -1185,6 +1206,20 @@ static inline struct layout_effective_run *layout_get_next_erun(struct dwrite_te
return LIST_ENTRY(e, struct layout_effective_run, entry);
}
static inline struct layout_effective_run *layout_get_prev_erun(struct dwrite_textlayout *layout,
const struct layout_effective_run *cur)
{
struct list *e;
if (!cur)
e = list_tail(&layout->eruns);
else
e = list_prev(&layout->eruns, &cur->entry);
if (!e)
return NULL;
return LIST_ENTRY(e, struct layout_effective_run, entry);
}
static inline struct layout_effective_inline *layout_get_next_inline_run(struct dwrite_textlayout *layout,
const struct layout_effective_inline *cur)
{
@ -1423,13 +1458,107 @@ static void layout_apply_par_alignment(struct dwrite_textlayout *layout)
}
}
struct layout_underline_splitting_params {
const WCHAR *locale; /* points to range data, no additional allocation */
IUnknown *effect; /* does not hold another reference */
};
static void init_u_splitting_params_from_erun(struct layout_effective_run *erun,
struct layout_underline_splitting_params *params)
{
params->locale = erun->run->u.regular.descr.localeName;
params->effect = erun->effect;
}
static BOOL is_same_u_splitting(struct layout_underline_splitting_params *left,
struct layout_underline_splitting_params *right)
{
return left->effect == right->effect && !strcmpiW(left->locale, right->locale);
}
static HRESULT layout_add_underline(struct dwrite_textlayout *layout, struct layout_effective_run *first,
struct layout_effective_run *last)
{
struct layout_underline_splitting_params params, prev_params;
struct layout_effective_run *cur;
DWRITE_FONT_METRICS metrics;
FLOAT thickness, offset;
if (first == layout_get_prev_erun(layout, last)) {
layout_get_erun_font_metrics(layout, first, &metrics);
thickness = SCALE_FONT_METRIC(metrics.underlineThickness, first->run->u.regular.run.fontEmSize, &metrics);
offset = SCALE_FONT_METRIC(metrics.underlinePosition, first->run->u.regular.run.fontEmSize, &metrics);
}
else {
FLOAT width = 0.0f;
/* Single underline is added for consecutive underlined runs. In this case underline parameters are
calculated as weighted average, where run width acts as a weight. */
thickness = offset = 0.0f;
cur = first;
do {
layout_get_erun_font_metrics(layout, cur, &metrics);
thickness += SCALE_FONT_METRIC(metrics.underlineThickness, cur->run->u.regular.run.fontEmSize, &metrics) * cur->width;
offset += SCALE_FONT_METRIC(metrics.underlinePosition, cur->run->u.regular.run.fontEmSize, &metrics) * cur->width;
width += cur->width;
cur = layout_get_next_erun(layout, cur);
} while (cur != last);
thickness /= width;
offset /= width;
}
cur = first;
prev_params = params;
do {
struct layout_effective_run *next, *w;
struct layout_underline *u;
init_u_splitting_params_from_erun(cur, &prev_params);
while ((next = layout_get_next_erun(layout, cur)) != last) {
init_u_splitting_params_from_erun(next, &params);
if (!is_same_u_splitting(&prev_params, &params))
break;
cur = next;
}
u = heap_alloc(sizeof(*u));
if (!u)
return E_OUTOFMEMORY;
w = cur;
u->u.width = 0.0f;
while (w != next) {
u->u.width += w->width;
w = layout_get_next_erun(layout, w);
}
u->u.thickness = thickness;
/* Font metrics convention is to have it negative when below baseline, for rendering
however Y grows from baseline down for horizontal baseline. */
u->u.offset = -offset;
u->u.runHeight = 0.0f; /* FIXME */
u->u.readingDirection = layout->format.readingdir;
u->u.flowDirection = layout->format.flow;
u->u.localeName = cur->run->u.regular.descr.localeName;
u->u.measuringMode = layout->measuringmode;
u->run = cur;
list_add_tail(&layout->underlines, &u->entry);
cur = next;
} while (cur != last);
return S_OK;
}
static HRESULT layout_compute_effective_runs(struct dwrite_textlayout *layout)
{
BOOL is_rtl = layout->format.readingdir == DWRITE_READING_DIRECTION_RIGHT_TO_LEFT;
struct layout_final_splitting_params prev_params, params;
struct layout_effective_run *erun, *first_underlined;
struct layout_effective_inline *inrun;
struct layout_effective_run *erun;
const struct layout_run *run;
DWRITE_LINE_METRICS metrics;
FLOAT width, origin_x, origin_y;
@ -1478,17 +1607,21 @@ static HRESULT layout_compute_effective_runs(struct dwrite_textlayout *layout)
UINT32 strlength, last_cluster, index;
FLOAT descent, trailingspacewidth;
struct layout_final_splitting_params *p;
if (!overflow) {
width += layout->clustermetrics[i].width;
metrics.length += layout->clustermetrics[i].length;
last_cluster = i;
p = &params;
}
else
else {
last_cluster = i ? i - 1 : i;
p = &prev_params;
}
if (i >= start) {
hr = layout_add_effective_run(layout, run, start, last_cluster - start + 1, line, origin_x, &prev_params);
hr = layout_add_effective_run(layout, run, start, last_cluster - start + 1, line, origin_x, p);
if (FAILED(hr))
return hr;
/* we don't need to update origin for next run as we're going to wrap */
@ -1571,6 +1704,8 @@ static HRESULT layout_compute_effective_runs(struct dwrite_textlayout *layout)
/* Now all line info is here, update effective runs positions in flow direction */
erun = layout_get_next_erun(layout, NULL);
first_underlined = erun && erun->underlined ? erun : NULL;
inrun = layout_get_next_inline_run(layout, NULL);
origin_y = 0.0f;
@ -1582,6 +1717,13 @@ static HRESULT layout_compute_effective_runs(struct dwrite_textlayout *layout)
while (erun && erun->line == line) {
erun->origin_y = origin_y;
erun = layout_get_next_erun(layout, erun);
if (first_underlined && (!erun || !erun->underlined)) {
layout_add_underline(layout, first_underlined, erun);
first_underlined = NULL;
}
else if (!first_underlined && erun && erun->underlined)
first_underlined = erun;
}
/* Same for inline runs */
@ -2913,6 +3055,7 @@ static HRESULT WINAPI dwritetextlayout_Draw(IDWriteTextLayout2 *iface,
struct layout_effective_inline *inlineobject;
struct layout_effective_run *run;
struct layout_strikethrough *s;
struct layout_underline *u;
FLOAT det = 0.0f, ppdip = 0.0f;
DWRITE_MATRIX m = { 0 };
HRESULT hr;
@ -2994,7 +3137,15 @@ static HRESULT WINAPI dwritetextlayout_Draw(IDWriteTextLayout2 *iface,
inlineobject->effect);
}
/* TODO: 3. Underlines */
/* 3. Underlines */
LIST_FOR_EACH_ENTRY(u, &This->underlines, struct layout_underline, entry) {
IDWriteTextRenderer_DrawUnderline(renderer,
context,
u->run->origin_x + run->align_dx + origin_x,
SNAP_COORD(u->run->origin_y + origin_y),
&u->u,
u->run->effect);
}
/* 4. Strikethrough */
LIST_FOR_EACH_ENTRY(s, &This->strikethrough, struct layout_strikethrough, entry) {
@ -4059,6 +4210,7 @@ static HRESULT init_textlayout(const WCHAR *str, UINT32 len, IDWriteTextFormat *
layout->minwidth = 0.0f;
list_init(&layout->eruns);
list_init(&layout->inlineobjects);
list_init(&layout->underlines);
list_init(&layout->strikethrough);
list_init(&layout->runs);
list_init(&layout->ranges);

View File

@ -1461,7 +1461,7 @@ static void test_Draw(void)
flush_sequence(sequences, RENDERER_ID);
hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, 0.0, 0.0);
ok(hr == S_OK, "got 0x%08x\n", hr);
ok_sequence(sequences, RENDERER_ID, draw_seq, "draw test", TRUE);
ok_sequence(sequences, RENDERER_ID, draw_seq, "draw test", FALSE);
IDWriteTextLayout_Release(layout);
/* with reduced width DrawGlyphRun() is called for every line */
@ -4336,6 +4336,14 @@ static const struct drawcall_entry drawunderline3_seq[] = {
{ DRAW_LAST_KIND }
};
static const struct drawcall_entry drawunderline4_seq[] = {
{ DRAW_GLYPHRUN, {'a',0} },
{ DRAW_GLYPHRUN, {'e',0} },
{ DRAW_UNDERLINE, {0}, {'e','n','-','u','s',0} },
{ DRAW_STRIKETHROUGH },
{ DRAW_LAST_KIND }
};
static void test_SetUnderline(void)
{
static const WCHAR encaW[] = {'e','n','-','C','A',0};
@ -4399,7 +4407,7 @@ todo_wine
flush_sequence(sequences, RENDERER_ID);
hr = IDWriteTextLayout_Draw(layout, NULL, &testrenderer, 0.0f, 0.0f);
ok(hr == S_OK, "got 0x%08x\n", hr);
ok_sequence(sequences, RENDERER_ID, drawunderline2_seq, "draw underline test 2", TRUE);
ok_sequence(sequences, RENDERER_ID, drawunderline2_seq, "draw underline test 2", FALSE);
/* now set different locale for second char, draw again */
range.startPosition = 0;
@ -4414,6 +4422,27 @@ todo_wine
IDWriteTextLayout_Release(layout);
/* 2 characters, same font properties, first with strikethrough, both underlined */
hr = IDWriteFactory_CreateTextLayout(factory, strW, 2, format, 1000.0f, 1000.0f, &layout);
ok(hr == S_OK, "got 0x%08x\n", hr);
range.startPosition = 0;
range.length = 1;
hr = IDWriteTextLayout_SetStrikethrough(layout, TRUE, range);
ok(hr == S_OK, "got 0x%08x\n", hr);
range.startPosition = 0;
range.length = 2;
hr = IDWriteTextLayout_SetUnderline(layout, TRUE, range);
ok(hr == S_OK, "got 0x%08x\n", hr);
flush_sequence(sequences, RENDERER_ID);
hr = IDWriteTextLayout_Draw(layout, NULL, &testrenderer, 0.0f, 0.0f);
ok(hr == S_OK, "got 0x%08x\n", hr);
ok_sequence(sequences, RENDERER_ID, drawunderline4_seq, "draw underline test 4", FALSE);
IDWriteTextLayout_Release(layout);
IDWriteTextFormat_Release(format);
IDWriteFactory_Release(factory);
}