From c5fce83f3db14858f38cc9cf460ceade450d6b15 Mon Sep 17 00:00:00 2001 From: Nikolay Sivov Date: Fri, 15 Jan 2016 00:24:23 +0300 Subject: [PATCH] dwrite: Implement support for underlines. Signed-off-by: Nikolay Sivov Signed-off-by: Alexandre Julliard --- dlls/dwrite/layout.c | 192 +++++++++++++++++++++++++++++++++---- dlls/dwrite/tests/layout.c | 33 ++++++- 2 files changed, 203 insertions(+), 22 deletions(-) diff --git a/dlls/dwrite/layout.c b/dlls/dwrite/layout.c index 943fef920a7..95568272a38 100644 --- a/dlls/dwrite/layout.c +++ b/dlls/dwrite/layout.c @@ -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, ¶ms); + if (!is_same_u_splitting(&prev_params, ¶ms)) + 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 = ¶ms; } - 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); diff --git a/dlls/dwrite/tests/layout.c b/dlls/dwrite/tests/layout.c index 07829bd3094..919e6e9399a 100644 --- a/dlls/dwrite/tests/layout.c +++ b/dlls/dwrite/tests/layout.c @@ -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); }