[PATCH 4/5] dwrite: Restructure line wrapping logic, fix the way breaking position is selected

Nikolay Sivov nsivov at codeweavers.com
Wed Jan 25 22:41:49 CST 2017


Signed-off-by: Nikolay Sivov <nsivov at codeweavers.com>
---
 dlls/dwrite/layout.c       | 301 ++++++++++++++++++++++++---------------------
 dlls/dwrite/tests/layout.c |   1 -
 2 files changed, 161 insertions(+), 141 deletions(-)

diff --git a/dlls/dwrite/layout.c b/dlls/dwrite/layout.c
index a80c49edc3..f40067166a 100644
--- a/dlls/dwrite/layout.c
+++ b/dlls/dwrite/layout.c
@@ -1230,7 +1230,7 @@ static HRESULT layout_add_effective_run(struct dwrite_textlayout *layout, const
         return E_OUTOFMEMORY;
 
     /* No need to iterate for that, use simple fact that:
-       <last cluster position> = first cluster position> + <sum of cluster lengths not including last one> */
+       <last cluster position> = <first cluster position> + <sum of cluster lengths not including last one> */
     last_cluster = first_cluster + cluster_count - 1;
     length = layout->clusters[last_cluster].position - layout->clusters[first_cluster].position +
         layout->clustermetrics[last_cluster].length;
@@ -1303,7 +1303,7 @@ static HRESULT layout_add_effective_run(struct dwrite_textlayout *layout, const
     return S_OK;
 }
 
-static HRESULT layout_set_line_metrics(struct dwrite_textlayout *layout, DWRITE_LINE_METRICS1 *metrics, UINT32 *line)
+static HRESULT layout_set_line_metrics(struct dwrite_textlayout *layout, DWRITE_LINE_METRICS1 *metrics)
 {
     if (!layout->line_alloc) {
         layout->line_alloc = 5;
@@ -1320,9 +1320,7 @@ static HRESULT layout_set_line_metrics(struct dwrite_textlayout *layout, DWRITE_
         layout->line_alloc *= 2;
     }
 
-    layout->lines[*line] = *metrics;
-    layout->metrics.lineCount += 1;
-    *line += 1;
+    layout->lines[layout->metrics.lineCount++] = *metrics;
     return S_OK;
 }
 
@@ -1689,10 +1687,10 @@ static HRESULT layout_add_underline(struct dwrite_textlayout *layout, struct lay
 }
 
 /* Adds zero width line, metrics are derived from font at specified text position. */
-static HRESULT layout_set_dummy_line_metrics(struct dwrite_textlayout *layout, UINT32 pos, UINT32 *line)
+static HRESULT layout_set_dummy_line_metrics(struct dwrite_textlayout *layout, UINT32 pos)
 {
+    DWRITE_LINE_METRICS1 metrics = { 0 };
     DWRITE_FONT_METRICS fontmetrics;
-    DWRITE_LINE_METRICS1 metrics;
     struct layout_range *range;
     IDWriteFontFace *fontface;
     IDWriteFont *font;
@@ -1716,181 +1714,202 @@ static HRESULT layout_set_dummy_line_metrics(struct dwrite_textlayout *layout, U
     layout_get_font_height(range->fontsize, &fontmetrics, &metrics.baseline, &metrics.height);
     IDWriteFontFace_Release(fontface);
 
-    metrics.length = 0;
-    metrics.trailingWhitespaceLength = 0;
-    metrics.newlineLength = 0;
-    metrics.isTrimmed = FALSE;
-    metrics.leadingBefore = 0.0f;
-    metrics.leadingAfter = 0.0f;
-    return layout_set_line_metrics(layout, &metrics, line);
+    return layout_set_line_metrics(layout, &metrics);
 }
 
-static HRESULT layout_compute_effective_runs(struct dwrite_textlayout *layout)
+static void layout_add_line(struct dwrite_textlayout *layout, UINT32 first_cluster, UINT32 last_cluster,
+        UINT32 *textpos)
 {
     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_final_splitting_params params, prev_params;
+    UINT32 strlength, index, start, pos = *textpos;
+    UINT32 line = layout->metrics.lineCount, i;
+    DWRITE_LINE_METRICS1 metrics = { 0 };
+    FLOAT descent, trailingspacewidth;
     const struct layout_run *run;
-    DWRITE_LINE_METRICS1 metrics;
-    FLOAT width, origin_x, origin_y;
-    UINT32 i, start, line, textpos;
+    FLOAT width, origin_x;
     HRESULT hr;
 
-    if (!(layout->recompute & RECOMPUTE_LINES))
-        return S_OK;
+    /* Take a look at clusters we got for this line in reverse order to set trailing properties for current line */
+    for (index = last_cluster, trailingspacewidth = 0.0f; index >= first_cluster; index--) {
+        DWRITE_CLUSTER_METRICS *cluster = &layout->clustermetrics[index];
+        struct layout_cluster *lc = &layout->clusters[index];
+        WCHAR ch;
 
-    free_layout_eruns(layout);
+        /* This also filters out clusters added from inline objects, those are never
+           treated as a white space. */
+        if (!cluster->isWhitespace)
+            break;
 
-    hr = layout_compute(layout);
-    if (FAILED(hr))
-        return hr;
+        /* Every isNewline cluster is also isWhitespace, but not every
+           newline character cluster has isNewline set, so go back to original string. */
+        ch = lc->run->u.regular.descr.string[lc->position];
+        if (cluster->length == 1 && lb_is_newline_char(ch))
+            metrics.newlineLength += cluster->length;
 
-    layout->metrics.lineCount = 0;
-    origin_x = is_rtl ? layout->metrics.layoutWidth : 0.0f;
-    line = 0;
-    memset(&metrics, 0, sizeof(metrics));
+        metrics.trailingWhitespaceLength += cluster->length;
+        trailingspacewidth += cluster->width;
+    }
 
-    layout_splitting_params_from_pos(layout, 0, &params);
-    prev_params = params;
+    /* Line metrics length includes trailing whitespace length too */
+    for (i = first_cluster; i <= last_cluster; i++)
+        metrics.length += layout->clustermetrics[i].length;
+
+    /* Ignore trailing whitespaces */
+    while (last_cluster > first_cluster) {
+        if (!layout->clustermetrics[last_cluster].isWhitespace)
+            break;
 
-    if (layout->cluster_count)
-        run = layout->clusters[0].run;
-    for (i = 0, start = 0, textpos = 0, width = 0.0f; i < layout->cluster_count; i++) {
-        BOOL overflow;
+        last_cluster--;
+    }
+
+    /* Does not include trailing space width */
+    width = get_cluster_range_width(layout, first_cluster, last_cluster + 1);
 
-        layout_splitting_params_from_pos(layout, textpos, &params);
+    layout_splitting_params_from_pos(layout, pos, &params);
+    prev_params = params;
+    run = layout->clusters[first_cluster].run;
+
+    /* Form runs from a range of clusters; this is what will be reported with DrawGlyphRun() */
+    origin_x = is_rtl ? layout->metrics.layoutWidth : 0.0f;
+    for (start = first_cluster, i = first_cluster; i <= last_cluster; i++) {
+        layout_splitting_params_from_pos(layout, pos, &params);
 
-        /* switched to next nominal run, at this point all previous pending clusters are already
-           checked for layout line overflow, so new effective run will fit in current line */
         if (run != layout->clusters[i].run || !is_same_splitting_params(&prev_params, &params)) {
             hr = layout_add_effective_run(layout, run, start, i - start, line, origin_x, &prev_params);
             if (FAILED(hr))
-                return hr;
+                return;
+
             origin_x += is_rtl ? -get_cluster_range_width(layout, start, i) :
-                                  get_cluster_range_width(layout, start, i);
+                get_cluster_range_width(layout, start, i);
             run = layout->clusters[i].run;
             start = i;
         }
 
-        overflow = layout->clustermetrics[i].canWrapLineAfter &&
-            (width + layout->clustermetrics[i].width > layout->metrics.layoutWidth) &&
-            (layout->format.wrapping != DWRITE_WORD_WRAPPING_NO_WRAP);
-        /* check if we got new */
-        if (overflow ||
-            layout->clustermetrics[i].isNewline || /* always wrap on new line */
-            i == layout->cluster_count - 1) /* end of the text */ {
-
-            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 {
-                last_cluster = i ? i - 1 : i;
-                p = &prev_params;
-            }
+        prev_params = params;
+        pos += layout->clustermetrics[i].length;
+    }
 
-            if (i >= start) {
-                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 */
-            }
+    /* Final run from what's left from cluster range */
+    hr = layout_add_effective_run(layout, run, start, i - start, line, origin_x, &prev_params);
+    if (FAILED(hr))
+        return;
 
-            /* take a look at clusters we got for this line in reverse order to set
-               trailing properties for current line */
-            strlength = metrics.length;
-            index = last_cluster;
-            trailingspacewidth = 0.0f;
-            while (strlength) {
-                DWRITE_CLUSTER_METRICS *cluster = &layout->clustermetrics[index];
-                struct layout_cluster *lc = &layout->clusters[index];
-                WCHAR ch;
-
-                /* This also filters out clusters added from inline objects, those are never
-                   treated as a white space. */
-                if (!cluster->isWhitespace)
-                    break;
-
-                /* Every isNewline cluster is also isWhitespace, but not every
-                   newline character cluster has isNewline set, so go back to original string. */
-                ch = lc->run->u.regular.descr.string[lc->position];
-                if (cluster->length == 1 && lb_is_newline_char(ch))
-                    metrics.newlineLength += cluster->length;
-
-                metrics.trailingWhitespaceLength += cluster->length;
-                trailingspacewidth += cluster->width;
-
-                strlength -= cluster->length;
-                index--;
-            }
+    /* Look for max baseline and descent for this line */
+    strlength = metrics.length - metrics.trailingWhitespaceLength;
+    index = last_cluster;
+    metrics.baseline = 0.0f;
+    descent = 0.0f;
+    while (strlength) {
+        DWRITE_CLUSTER_METRICS *cluster = &layout->clustermetrics[index];
+        const struct layout_run *cur = layout->clusters[index].run;
+        FLOAT cur_descent = cur->height - cur->baseline;
 
-            /* look for max baseline and descent for this line */
-            strlength = metrics.length;
-            index = last_cluster;
-            metrics.baseline = 0.0f;
-            descent = 0.0f;
-            while (strlength) {
-                DWRITE_CLUSTER_METRICS *cluster = &layout->clustermetrics[index];
-                const struct layout_run *cur = layout->clusters[index].run;
-                FLOAT cur_descent = cur->height - cur->baseline;
+        if (cur->baseline > metrics.baseline)
+            metrics.baseline = cur->baseline;
 
-                if (cur->baseline > metrics.baseline)
-                    metrics.baseline = cur->baseline;
+        if (cur_descent > descent)
+            descent = cur_descent;
 
-                if (cur_descent > descent)
-                    descent = cur_descent;
+        strlength -= cluster->length;
+        index--;
+    }
 
-                strlength -= cluster->length;
-                index--;
-            }
-            metrics.height = descent + metrics.baseline;
+    layout->metrics.width = max(width, layout->metrics.width);
+    layout->metrics.widthIncludingTrailingWhitespace = max(width + trailingspacewidth,
+        layout->metrics.widthIncludingTrailingWhitespace);
 
-            if (width > layout->metrics.widthIncludingTrailingWhitespace)
-                layout->metrics.widthIncludingTrailingWhitespace = width;
-            if (width - trailingspacewidth > layout->metrics.width)
-                layout->metrics.width = width - trailingspacewidth;
+    metrics.height = descent + metrics.baseline;
+    metrics.isTrimmed = width > layout->metrics.layoutWidth;
+    layout_set_line_metrics(layout, &metrics);
 
-            metrics.isTrimmed = width > layout->metrics.layoutWidth;
-            hr = layout_set_line_metrics(layout, &metrics, &line);
-            if (FAILED(hr))
-                return hr;
+    *textpos += metrics.length;
+}
 
-            width = layout->clustermetrics[i].width;
-            memset(&metrics, 0, sizeof(metrics));
-            origin_x = is_rtl ? layout->metrics.layoutWidth : 0.0f;
-            start = i;
-        }
-        else {
-            metrics.length += layout->clustermetrics[i].length;
+static HRESULT layout_compute_effective_runs(struct dwrite_textlayout *layout)
+{
+    BOOL is_rtl = layout->format.readingdir == DWRITE_READING_DIRECTION_RIGHT_TO_LEFT;
+    struct layout_effective_run *erun, *first_underlined;
+    UINT32 i, start, line, textpos, last_breaking_point;
+    struct layout_effective_inline *inrun;
+    FLOAT width, origin_y;
+    DWRITE_LINE_METRICS1 metrics;
+    HRESULT hr;
+
+    if (!(layout->recompute & RECOMPUTE_LINES))
+        return S_OK;
+
+    free_layout_eruns(layout);
+
+    hr = layout_compute(layout);
+    if (FAILED(hr))
+        return hr;
+
+    layout->metrics.lineCount = 0;
+    line = 0;
+    memset(&metrics, 0, sizeof(metrics));
+
+    layout->metrics.height = 0.0f;
+    layout->metrics.width = 0.0f;
+    layout->metrics.widthIncludingTrailingWhitespace = 0.0f;
+
+    last_breaking_point = ~0u;
+
+    for (i = 0, start = 0, width = 0.0f, textpos = 0; i < layout->cluster_count; i++) {
+        BOOL overflow = FALSE;
+
+        while (i < layout->cluster_count && !layout->clustermetrics[i].isNewline) {
+            /* Check for overflow */
+            overflow = ((width + layout->clustermetrics[i].width > layout->metrics.layoutWidth) &&
+                    (layout->format.wrapping != DWRITE_WORD_WRAPPING_NO_WRAP));
+            if (overflow)
+                break;
+
+            if (layout->clustermetrics[i].canWrapLineAfter)
+                last_breaking_point = i;
             width += layout->clustermetrics[i].width;
+            i++;
+        }
+        i = min(i, layout->cluster_count - 1);
+
+        if (overflow) {
+            /* Overflown on whitespace, ignore it */
+            if (layout->clustermetrics[i].isWhitespace && layout->clustermetrics[i].canWrapLineAfter)
+                i = i;
+            /* Use most recently found breaking point */
+            else if (last_breaking_point != ~0u) {
+                i = last_breaking_point;
+                last_breaking_point = ~0u;
+            }
+            else {
+                /* Otherwise proceed forward to next newline or breaking point */
+                for (; i < layout->cluster_count; i++)
+                    if (layout->clustermetrics[i].canWrapLineAfter || layout->clustermetrics[i].isNewline)
+                        break;
+            }
         }
+        i = min(i, layout->cluster_count - 1);
 
-        prev_params = params;
-        textpos += layout->clustermetrics[i].length;
+        layout_add_line(layout, start, i, &textpos);
+        start = i + 1;
+        width = 0.0f;
     }
 
     /* Add dummy line if:
        - there's no text, metrics come from first range in this case;
        - last ended with a mandatory break, metrics come from last text position.
     */
+    line = layout->metrics.lineCount - 1;
     if (layout->len == 0)
-        hr = layout_set_dummy_line_metrics(layout, 0, &line);
-    else if (layout->clustermetrics[layout->cluster_count-1].isNewline)
-        hr = layout_set_dummy_line_metrics(layout, layout->len-1, &line);
+        hr = layout_set_dummy_line_metrics(layout, 0);
+    else if (layout->clustermetrics[layout->cluster_count - 1].isNewline)
+        hr = layout_set_dummy_line_metrics(layout, layout->len - 1);
     if (FAILED(hr))
         return hr;
 
     layout->metrics.left = is_rtl ? layout->metrics.layoutWidth - layout->metrics.width : 0.0f;
     layout->metrics.top = 0.0f;
     layout->metrics.maxBidiReorderingDepth = 1; /* FIXME */
-    layout->metrics.height = 0.0f;
 
     /* Now all line info is here, update effective runs positions in flow direction */
     erun = layout_get_next_erun(layout, NULL);
@@ -1921,15 +1940,16 @@ static HRESULT layout_compute_effective_runs(struct dwrite_textlayout *layout)
             inrun->origin_y = origin_y - inrun->run->baseline;
             inrun = layout_get_next_inline_run(layout, inrun);
         }
-
-        layout->metrics.height += layout->lines[line].height;
     }
+    /* Use last line origin y + line descent as total content height */
+    line--;
+    layout->metrics.height = origin_y + layout->lines[line].height - layout->lines[line].baseline;
 
-    /* initial alignment is always leading */
+    /* Initial alignment is always leading */
     if (layout->format.textalignment != DWRITE_TEXT_ALIGNMENT_LEADING)
         layout_apply_text_alignment(layout);
 
-    /* initial paragraph alignment is always near */
+    /* Initial paragraph alignment is always near */
     if (layout->format.paralign != DWRITE_PARAGRAPH_ALIGNMENT_NEAR)
         layout_apply_par_alignment(layout);
 
@@ -1939,7 +1959,8 @@ static HRESULT layout_compute_effective_runs(struct dwrite_textlayout *layout)
     return hr;
 }
 
-static BOOL is_same_layout_attrvalue(struct layout_range_header const *h, enum layout_range_attr_kind attr, struct layout_range_attr_value *value)
+static BOOL is_same_layout_attrvalue(struct layout_range_header const *h, enum layout_range_attr_kind attr,
+        struct layout_range_attr_value *value)
 {
     struct layout_range_spacing const *range_spacing = (struct layout_range_spacing*)h;
     struct layout_range_iface const *range_iface = (struct layout_range_iface*)h;
diff --git a/dlls/dwrite/tests/layout.c b/dlls/dwrite/tests/layout.c
index 960ca2c7b6..15274b9450 100644
--- a/dlls/dwrite/tests/layout.c
+++ b/dlls/dwrite/tests/layout.c
@@ -2161,7 +2161,6 @@ todo_wine
     hr = IDWriteTextLayout_GetLineMetrics(layout, &line, 1, &count);
     ok(hr == S_OK, "got 0x%08x\n", hr);
     ok(count == 1, "got %u\n", count);
-todo_wine
     ok(line.length == 4, "got %u\n", line.length);
     ok(line.isTrimmed, "got %d\n", line.isTrimmed);
 
-- 
2.11.0




More information about the wine-patches mailing list