From 4051ed328b618e28cf1df276899eefa225225c76 Mon Sep 17 00:00:00 2001 From: Chris Wilson Date: Tue, 25 Aug 2009 20:51:06 +0100 Subject: [PATCH] [tessellator] Special case rectilinear tessellation For the frequent cases where we know in advance that we are dealing with a rectilinear path, but can not use the simple region code, implement a variant of the Bentley-Ottmann tessellator. The advantages here are that edge comparison is very simple (we only have vertical edges) and there are no intersection, though possible overlaps. The idea is the same, maintain a y-x sorted queue of start/stop events that demarcate traps and sweep through the active edges at each event, looking for completed traps. The motivation for this was noticing a performance regression in box-fill-outline with the self-intersection work: 1.9.2 to HEAD^: 3.66x slowdown HEAD^ to HEAD: 5.38x speedup 1.9.2 to HEAD: 1.57x speedup The cause of which was choosing to use spans instead of the region handling code, as the complex polygon was no longer being tessellated. --- src/Makefile.sources | 1 + src/cairo-bentley-ottmann-rectilinear.c | 582 +++++++++++++++++++++ src/cairo-bentley-ottmann.c | 5 +- src/cairo-combsort-private.h | 4 +- src/cairo-path-fill.c | 100 +--- src/cairo-path-stroke.c | 1 + src/cairo-surface-fallback.c | 34 +- src/cairo-traps.c | 2 + src/cairoint.h | 18 +- test/ft-text-vertical-layout-type1.ref.png | Bin 3647 -> 3644 bytes test/ft-text-vertical-layout-type3.ref.png | Bin 3607 -> 3608 bytes 11 files changed, 631 insertions(+), 116 deletions(-) create mode 100644 src/cairo-bentley-ottmann-rectilinear.c diff --git a/src/Makefile.sources b/src/Makefile.sources index b8404e3dd..0e7b54b58 100644 --- a/src/Makefile.sources +++ b/src/Makefile.sources @@ -99,6 +99,7 @@ cairo_sources = \ cairo-atomic.c \ cairo-base85-stream.c \ cairo-bentley-ottmann.c \ + cairo-bentley-ottmann-rectilinear.c \ cairo.c \ cairo-cache.c \ cairo-clip.c \ diff --git a/src/cairo-bentley-ottmann-rectilinear.c b/src/cairo-bentley-ottmann-rectilinear.c new file mode 100644 index 000000000..c7e738b6f --- /dev/null +++ b/src/cairo-bentley-ottmann-rectilinear.c @@ -0,0 +1,582 @@ +/* + * Copyright © 2004 Carl Worth + * Copyright © 2006 Red Hat, Inc. + * Copyright © 2008 Chris Wilson + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + * The Original Code is the cairo graphics library. + * + * The Initial Developer of the Original Code is Carl Worth + * + * Contributor(s): + * Carl D. Worth + * Chris Wilson + */ + +/* Provide definitions for standalone compilation */ +#include "cairoint.h" + +#include "cairo-combsort-private.h" + +typedef struct _cairo_bo_edge cairo_bo_edge_t; +typedef struct _cairo_bo_trap cairo_bo_trap_t; + +/* A deferred trapezoid of an edge */ +struct _cairo_bo_trap { + cairo_bo_edge_t *right; + int32_t top; +}; + +struct _cairo_bo_edge { + cairo_edge_t edge; + cairo_bo_edge_t *prev; + cairo_bo_edge_t *next; + cairo_bo_trap_t deferred_trap; +}; + +typedef enum { + CAIRO_BO_EVENT_TYPE_START, + CAIRO_BO_EVENT_TYPE_STOP +} cairo_bo_event_type_t; + +typedef struct _cairo_bo_event { + cairo_bo_event_type_t type; + cairo_point_t point; + cairo_bo_edge_t *edge; +} cairo_bo_event_t; + +typedef struct _cairo_bo_sweep_line { + cairo_bo_event_t **events; + cairo_bo_edge_t *head; + cairo_bo_edge_t *stopped; + int32_t current_y; + cairo_bo_edge_t *current_edge; +} cairo_bo_sweep_line_t; + +static inline int +_cairo_point_compare (const cairo_point_t *a, + const cairo_point_t *b) +{ + int cmp; + + cmp = a->y - b->y; + if (likely (cmp)) + return cmp; + + return a->x - b->x; +} + +static inline int +_cairo_bo_edge_compare (const cairo_bo_edge_t *a, + const cairo_bo_edge_t *b) +{ + int cmp; + + cmp = a->edge.line.p1.x - b->edge.line.p1.x; + if (likely (cmp)) + return cmp; + + return b->edge.bottom - a->edge.bottom; +} + +static inline int +cairo_bo_event_compare (const cairo_bo_event_t *a, + const cairo_bo_event_t *b) +{ + int cmp; + + cmp = _cairo_point_compare (&a->point, &b->point); + if (likely (cmp)) + return cmp; + + cmp = a->type - b->type; + if (cmp) + return cmp; + + return a - b; +} + +static inline cairo_bo_event_t * +_cairo_bo_event_dequeue (cairo_bo_sweep_line_t *sweep_line) +{ + return *sweep_line->events++; +} + +CAIRO_COMBSORT_DECLARE (_cairo_bo_event_queue_sort, + cairo_bo_event_t *, + cairo_bo_event_compare) + +static void +_cairo_bo_sweep_line_init (cairo_bo_sweep_line_t *sweep_line, + cairo_bo_event_t **events, + int num_events) +{ + _cairo_bo_event_queue_sort (events, num_events); + events[num_events] = NULL; + sweep_line->events = events; + + sweep_line->head = NULL; + sweep_line->current_y = INT32_MIN; + sweep_line->current_edge = NULL; +} + +static void +_cairo_bo_sweep_line_insert (cairo_bo_sweep_line_t *sweep_line, + cairo_bo_edge_t *edge) +{ + if (sweep_line->current_edge != NULL) { + cairo_bo_edge_t *prev, *next; + int cmp; + + cmp = _cairo_bo_edge_compare (sweep_line->current_edge, edge); + if (cmp < 0) { + prev = sweep_line->current_edge; + next = prev->next; + while (next != NULL && _cairo_bo_edge_compare (next, edge) < 0) + prev = next, next = prev->next; + + prev->next = edge; + edge->prev = prev; + edge->next = next; + if (next != NULL) + next->prev = edge; + } else if (cmp > 0) { + next = sweep_line->current_edge; + prev = next->prev; + while (prev != NULL && _cairo_bo_edge_compare (prev, edge) > 0) + next = prev, prev = next->prev; + + next->prev = edge; + edge->next = next; + edge->prev = prev; + if (prev != NULL) + prev->next = edge; + else + sweep_line->head = edge; + } else { + prev = sweep_line->current_edge; + edge->prev = prev; + edge->next = prev->next; + if (prev->next != NULL) + prev->next->prev = edge; + prev->next = edge; + } + } else { + sweep_line->head = edge; + } + + sweep_line->current_edge = edge; +} + +static void +_cairo_bo_sweep_line_delete (cairo_bo_sweep_line_t *sweep_line, + cairo_bo_edge_t *edge) +{ + if (edge->prev != NULL) + edge->prev->next = edge->next; + else + sweep_line->head = edge->next; + + if (edge->next != NULL) + edge->next->prev = edge->prev; + + if (sweep_line->current_edge == edge) + sweep_line->current_edge = edge->prev ? edge->prev : edge->next; +} + +static inline cairo_bool_t +edges_collinear (const cairo_bo_edge_t *a, const cairo_bo_edge_t *b) +{ + return a->edge.line.p1.x == b->edge.line.p1.x; +} + +static cairo_status_t +_cairo_bo_edge_end_trap (cairo_bo_edge_t *left, + int32_t bot, + cairo_traps_t *traps) +{ + cairo_bo_trap_t *trap = &left->deferred_trap; + + /* Only emit (trivial) non-degenerate trapezoids with positive height. */ + if (likely (trap->top < bot)) { + _cairo_traps_add_trap (traps, + trap->top, bot, + &left->edge.line, &trap->right->edge.line); + } + + trap->right = NULL; + + return _cairo_traps_status (traps); +} + +/* Start a new trapezoid at the given top y coordinate, whose edges + * are `edge' and `edge->next'. If `edge' already has a trapezoid, + * then either add it to the traps in `traps', if the trapezoid's + * right edge differs from `edge->next', or do nothing if the new + * trapezoid would be a continuation of the existing one. */ +static inline cairo_status_t +_cairo_bo_edge_start_or_continue_trap (cairo_bo_edge_t *left, + cairo_bo_edge_t *right, + int top, + cairo_traps_t *traps) +{ + cairo_status_t status; + + if (left->deferred_trap.right == right) + return CAIRO_STATUS_SUCCESS; + + if (left->deferred_trap.right != NULL) { + if (right != NULL && edges_collinear (left->deferred_trap.right, right)) + { + /* continuation on right, so just swap edges */ + left->deferred_trap.right = right; + return CAIRO_STATUS_SUCCESS; + } + + status = _cairo_bo_edge_end_trap (left, top, traps); + if (unlikely (status)) + return status; + } + + if (right != NULL && ! edges_collinear (left, right)) { + left->deferred_trap.top = top; + left->deferred_trap.right = right; + } + + return CAIRO_STATUS_SUCCESS; +} + +static inline cairo_status_t +_active_edges_to_traps (cairo_bo_edge_t *left, + int32_t top, + cairo_fill_rule_t fill_rule, + cairo_traps_t *traps) +{ + cairo_bo_edge_t *right; + cairo_status_t status; + + if (fill_rule == CAIRO_FILL_RULE_WINDING) { + while (left != NULL) { + int in_out; + + /* Greedily search for the closing edge, so that we generate the + * maximal span width with the minimal number of trapezoids. + */ + in_out = left->edge.dir; + + /* Check if there is a co-linear edge with an existing trap */ + right = left->next; + if (left->deferred_trap.right == NULL) { + while (right != NULL && right->deferred_trap.right == NULL) + right = right->next; + + if (right != NULL && edges_collinear (left, right)) { + /* continuation on left */ + left->deferred_trap = right->deferred_trap; + right->deferred_trap.right = NULL; + } + } + + /* End all subsumed traps */ + right = left->next; + while (right != NULL) { + if (right->deferred_trap.right != NULL) { + status = _cairo_bo_edge_end_trap (right, top, traps); + if (unlikely (status)) + return status; + } + + in_out += right->edge.dir; + if (in_out == 0) { + /* skip co-linear edges */ + if (right->next == NULL || + ! edges_collinear (right, right->next)) + { + break; + } + } + + right = right->next; + } + + status = _cairo_bo_edge_start_or_continue_trap (left, right, + top, traps); + if (unlikely (status)) + return status; + + left = right; + if (left != NULL) + left = left->next; + } + } else { + while (left != NULL) { + int in_out = 0; + + right = left->next; + while (right != NULL) { + if (right->deferred_trap.right != NULL) { + status = _cairo_bo_edge_end_trap (right, top, traps); + if (unlikely (status)) + return status; + } + + if ((in_out++ & 1) == 0) { + cairo_bo_edge_t *next; + cairo_bool_t skip = FALSE; + + /* skip co-linear edges */ + next = right->next; + if (next != NULL) + skip = edges_collinear (right, next); + + if (! skip) + break; + } + + right = right->next; + } + + status = _cairo_bo_edge_start_or_continue_trap (left, right, + top, traps); + if (unlikely (status)) + return status; + + left = right; + if (left != NULL) + left = left->next; + } + } + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +_cairo_bentley_ottmann_tessellate_rectilinear (cairo_bo_event_t **start_events, + int num_events, + cairo_fill_rule_t fill_rule, + cairo_traps_t *traps) +{ + cairo_bo_sweep_line_t sweep_line; + cairo_bo_event_t *event; + cairo_status_t status; + + _cairo_bo_sweep_line_init (&sweep_line, start_events, num_events); + + while ((event = _cairo_bo_event_dequeue (&sweep_line))) { + if (event->point.y != sweep_line.current_y) { + status = _active_edges_to_traps (sweep_line.head, + sweep_line.current_y, + fill_rule, traps); + if (unlikely (status)) + return status; + + sweep_line.current_y = event->point.y; + } + + switch (event->type) { + case CAIRO_BO_EVENT_TYPE_START: + _cairo_bo_sweep_line_insert (&sweep_line, event->edge); + break; + + case CAIRO_BO_EVENT_TYPE_STOP: + _cairo_bo_sweep_line_delete (&sweep_line, event->edge); + + if (event->edge->deferred_trap.right != NULL) { + status = _cairo_bo_edge_end_trap (event->edge, + sweep_line.current_y, + traps); + if (unlikely (status)) + return status; + } + + break; + } + } + + return CAIRO_STATUS_SUCCESS; +} + +cairo_status_t +_cairo_bentley_ottmann_tessellate_rectilinear_polygon (cairo_traps_t *traps, + const cairo_polygon_t *polygon, + cairo_fill_rule_t fill_rule) +{ + cairo_status_t status; + cairo_bo_event_t stack_events[CAIRO_STACK_ARRAY_LENGTH (cairo_bo_event_t)]; + cairo_bo_event_t *events; + cairo_bo_event_t *stack_event_ptrs[ARRAY_LENGTH (stack_events) + 1]; + cairo_bo_event_t **event_ptrs; + cairo_bo_edge_t stack_edges[ARRAY_LENGTH (stack_events)]; + cairo_bo_edge_t *edges; + int num_events; + int i, j; + + if (unlikely (polygon->num_edges == 0)) + return CAIRO_STATUS_SUCCESS; + + num_events = 2 * polygon->num_edges; + + events = stack_events; + event_ptrs = stack_event_ptrs; + edges = stack_edges; + if (num_events > ARRAY_LENGTH (stack_events)) { + events = _cairo_malloc_ab_plus_c (num_events, + sizeof (cairo_bo_event_t) + + sizeof (cairo_bo_edge_t) + + sizeof (cairo_bo_event_t *), + sizeof (cairo_bo_event_t *)); + if (unlikely (events == NULL)) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + event_ptrs = (cairo_bo_event_t **) (events + num_events); + edges = (cairo_bo_edge_t *) (event_ptrs + num_events + 1); + } + + for (i = j = 0; i < polygon->num_edges; i++) { + edges[i].edge = polygon->edges[i]; + edges[i].deferred_trap.right = NULL; + edges[i].prev = NULL; + edges[i].next = NULL; + + event_ptrs[j] = &events[j]; + events[j].type = CAIRO_BO_EVENT_TYPE_START; + events[j].point.y = polygon->edges[i].top; + events[j].point.x = polygon->edges[i].line.p1.x; + events[j].edge = &edges[i]; + j++; + + event_ptrs[j] = &events[j]; + events[j].type = CAIRO_BO_EVENT_TYPE_STOP; + events[j].point.y = polygon->edges[i].bottom; + events[j].point.x = polygon->edges[i].line.p1.x; + events[j].edge = &edges[i]; + j++; + } + + status = _cairo_bentley_ottmann_tessellate_rectilinear (event_ptrs, j, + fill_rule, traps); + if (events != stack_events) + free (events); + + traps->is_rectilinear = TRUE; + + return status; +} + +cairo_status_t +_cairo_bentley_ottmann_tessellate_rectilinear_traps (cairo_traps_t *traps, + cairo_fill_rule_t fill_rule) +{ + cairo_bo_event_t stack_events[CAIRO_STACK_ARRAY_LENGTH (cairo_bo_event_t)]; + cairo_bo_event_t *events; + cairo_bo_event_t *stack_event_ptrs[ARRAY_LENGTH (stack_events) + 1]; + cairo_bo_event_t **event_ptrs; + cairo_bo_edge_t stack_edges[ARRAY_LENGTH (stack_events)]; + cairo_bo_edge_t *edges; + cairo_status_t status; + int i, j, k; + + if (unlikely (traps->num_traps == 0)) + return CAIRO_STATUS_SUCCESS; + + assert (traps->is_rectilinear); + + i = 4 * traps->num_traps; + + events = stack_events; + event_ptrs = stack_event_ptrs; + edges = stack_edges; + if (i > ARRAY_LENGTH (stack_events)) { + events = _cairo_malloc_ab_plus_c (i, + sizeof (cairo_bo_event_t) + + sizeof (cairo_bo_edge_t) + + sizeof (cairo_bo_event_t *), + sizeof (cairo_bo_event_t *)); + if (unlikely (events == NULL)) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + event_ptrs = (cairo_bo_event_t **) (events + i); + edges = (cairo_bo_edge_t *) (event_ptrs + i + 1); + } + + for (i = j = k = 0; i < traps->num_traps; i++) { + edges[k].edge.top = traps->traps[i].top; + edges[k].edge.bottom = traps->traps[i].bottom; + edges[k].edge.line = traps->traps[i].left; + edges[k].edge.dir = 1; + edges[k].deferred_trap.right = NULL; + edges[k].prev = NULL; + edges[k].next = NULL; + + event_ptrs[j] = &events[j]; + events[j].type = CAIRO_BO_EVENT_TYPE_START; + events[j].point.y = traps->traps[i].top; + events[j].point.x = traps->traps[i].left.p1.x; + events[j].edge = &edges[k]; + j++; + + event_ptrs[j] = &events[j]; + events[j].type = CAIRO_BO_EVENT_TYPE_STOP; + events[j].point.y = traps->traps[i].bottom; + events[j].point.x = traps->traps[i].left.p1.x; + events[j].edge = &edges[k]; + j++; + k++; + + edges[k].edge.top = traps->traps[i].top; + edges[k].edge.bottom = traps->traps[i].bottom; + edges[k].edge.line = traps->traps[i].right; + edges[k].edge.dir = -1; + edges[k].deferred_trap.right = NULL; + edges[k].prev = NULL; + edges[k].next = NULL; + + event_ptrs[j] = &events[j]; + events[j].type = CAIRO_BO_EVENT_TYPE_START; + events[j].point.y = traps->traps[i].top; + events[j].point.x = traps->traps[i].right.p1.x; + events[j].edge = &edges[k]; + j++; + + event_ptrs[j] = &events[j]; + events[j].type = CAIRO_BO_EVENT_TYPE_STOP; + events[j].point.y = traps->traps[i].bottom; + events[j].point.x = traps->traps[i].right.p1.x; + events[j].edge = &edges[k]; + j++; + k++; + } + + _cairo_traps_clear (traps); + status = _cairo_bentley_ottmann_tessellate_rectilinear (event_ptrs, j, + fill_rule, + traps); + traps->is_rectilinear = TRUE; + + if (events != stack_events) + free (events); + + return status; +} diff --git a/src/cairo-bentley-ottmann.c b/src/cairo-bentley-ottmann.c index 3d3a3d60b..99f4655d7 100644 --- a/src/cairo-bentley-ottmann.c +++ b/src/cairo-bentley-ottmann.c @@ -2002,7 +2002,8 @@ _compute_clipped_trapezoid_edges (const cairo_traps_t *traps, } cairo_status_t -_cairo_bentley_ottmann_tessellate_traps (cairo_traps_t *traps) +_cairo_bentley_ottmann_tessellate_traps (cairo_traps_t *traps, + cairo_fill_rule_t fill_rule) { int intersections; cairo_status_t status; @@ -2054,7 +2055,7 @@ _cairo_bentley_ottmann_tessellate_traps (cairo_traps_t *traps) _cairo_traps_clear (traps); status = _cairo_bentley_ottmann_tessellate_bo_edges (event_ptrs, num_events, - CAIRO_FILL_RULE_WINDING, + fill_rule, traps, &intersections); diff --git a/src/cairo-combsort-private.h b/src/cairo-combsort-private.h index d2fbb72b9..ce31257ec 100644 --- a/src/cairo-combsort-private.h +++ b/src/cairo-combsort-private.h @@ -56,7 +56,7 @@ NAME (TYPE *base, unsigned int nmemb) \ int swapped; \ do { \ gap = _cairo_combsort_newgap (gap); \ - swapped = 0; \ + swapped = gap > 1; \ for (i = 0; i < nmemb-gap ; i++) { \ j = i + gap; \ if (CMP (base[i], base[j]) > 0 ) { \ @@ -67,5 +67,5 @@ NAME (TYPE *base, unsigned int nmemb) \ swapped = 1; \ } \ } \ - } while (gap > 1 || swapped); \ + } while (swapped); \ } diff --git a/src/cairo-path-fill.c b/src/cairo-path-fill.c index 46148cf56..5b82a4009 100644 --- a/src/cairo-path-fill.c +++ b/src/cairo-path-fill.c @@ -155,14 +155,6 @@ _cairo_path_fixed_fill_to_traps (const cairo_path_fixed_t *path, cairo_polygon_t polygon; cairo_status_t status; - if (path->is_rectilinear) { - status = _cairo_path_fixed_fill_rectilinear_to_traps (path, - fill_rule, - traps); - if (status != CAIRO_INT_STATUS_UNSUPPORTED) - return status; - } - _cairo_polygon_init (&polygon); status = _cairo_path_fixed_fill_to_polygon (path, tolerance, @@ -170,15 +162,21 @@ _cairo_path_fixed_fill_to_traps (const cairo_path_fixed_t *path, if (unlikely (status)) goto CLEANUP; - status = _cairo_bentley_ottmann_tessellate_polygon (traps, &polygon, - fill_rule); + if (path->is_rectilinear) { + status = _cairo_bentley_ottmann_tessellate_rectilinear_polygon (traps, + &polygon, + fill_rule); + } else { + status = _cairo_bentley_ottmann_tessellate_polygon (traps, + &polygon, + fill_rule); + } CLEANUP: _cairo_polygon_fini (&polygon); return status; } - /* This special-case filler supports only a path that describes a * device-axis aligned rectangle. It exists to avoid the overhead of * the general tessellator when drawing very common rectangles. @@ -186,86 +184,6 @@ _cairo_path_fixed_fill_to_traps (const cairo_path_fixed_t *path, * If the path described anything but a device-axis aligned rectangle, * this function will return %CAIRO_INT_STATUS_UNSUPPORTED. */ -cairo_int_status_t -_cairo_path_fixed_fill_rectilinear_to_traps (const cairo_path_fixed_t *path, - cairo_fill_rule_t fill_rule, - cairo_traps_t *traps) -{ - cairo_box_t box; - - assert (path->is_rectilinear); - - if (_cairo_path_fixed_is_box (path, &box)) { - if (box.p1.x > box.p2.x) { - cairo_fixed_t t; - - t = box.p1.x; - box.p1.x = box.p2.x; - box.p2.x = t; - } - - if (box.p1.y > box.p2.y) { - cairo_fixed_t t; - - t = box.p1.y; - box.p1.y = box.p2.y; - box.p2.y = t; - } - - return _cairo_traps_tessellate_rectangle (traps, &box.p1, &box.p2); - } else if (fill_rule == CAIRO_FILL_RULE_WINDING) { - cairo_path_fixed_iter_t iter; - int last_cw = -1; - - /* Support a series of rectangles as can be expected to describe a - * GdkRegion clip region during exposes. - */ - _cairo_path_fixed_iter_init (&iter, path); - while (_cairo_path_fixed_iter_is_fill_box (&iter, &box)) { - cairo_status_t status; - int cw = 0; - - if (box.p1.x > box.p2.x) { - cairo_fixed_t t; - - t = box.p1.x; - box.p1.x = box.p2.x; - box.p2.x = t; - - cw = ! cw; - } - - if (box.p1.y > box.p2.y) { - cairo_fixed_t t; - - t = box.p1.y; - box.p1.y = box.p2.y; - box.p2.y = t; - - cw = ! cw; - } - - if (last_cw < 0) { - last_cw = cw; - } else if (last_cw != cw) { - _cairo_traps_clear (traps); - return CAIRO_INT_STATUS_UNSUPPORTED; - } - - status = _cairo_traps_tessellate_rectangle (traps, - &box.p1, &box.p2); - if (unlikely (status)) - return status; - } - if (_cairo_path_fixed_iter_at_end (&iter)) - return CAIRO_STATUS_SUCCESS; - - _cairo_traps_clear (traps); - } - - return CAIRO_INT_STATUS_UNSUPPORTED; -} - cairo_region_t * _cairo_path_fixed_fill_rectilinear_to_region (const cairo_path_fixed_t *path, cairo_fill_rule_t fill_rule, diff --git a/src/cairo-path-stroke.c b/src/cairo-path-stroke.c index 208e39030..d37c597b5 100644 --- a/src/cairo-path-stroke.c +++ b/src/cairo-path-stroke.c @@ -2027,6 +2027,7 @@ _cairo_path_fixed_stroke_rectilinear_to_traps (const cairo_path_fixed_t *path, else status = _cairo_rectilinear_stroker_emit_segments (&rectilinear_stroker); + traps->is_rectilinear = 1; BAIL: _cairo_rectilinear_stroker_fini (&rectilinear_stroker); diff --git a/src/cairo-surface-fallback.c b/src/cairo-surface-fallback.c index 8f13bfbb7..b97eb1932 100644 --- a/src/cairo-surface-fallback.c +++ b/src/cairo-surface-fallback.c @@ -727,7 +727,12 @@ _clip_and_composite_trapezoids (const cairo_pattern_t *src, /* No fast path, exclude self-intersections and clip trapezoids. */ if (traps->has_intersections) { - status = _cairo_bentley_ottmann_tessellate_traps (traps); + if (traps->is_rectilinear) + status = _cairo_bentley_ottmann_tessellate_rectilinear_traps (traps, + CAIRO_FILL_RULE_WINDING); + else + status = _cairo_bentley_ottmann_tessellate_traps (traps, + CAIRO_FILL_RULE_WINDING); if (unlikely (status)) return status; } @@ -1152,20 +1157,7 @@ _cairo_surface_fallback_fill (cairo_surface_t *surface, _cairo_polygon_init (&polygon); _cairo_polygon_limit (&polygon, &box); - if (path->is_rectilinear) { - status = _cairo_path_fixed_fill_rectilinear_to_traps (path, - fill_rule, - &traps); - if (likely (status == CAIRO_STATUS_SUCCESS)) - goto DO_TRAPS; - - if (unlikely (_cairo_status_is_error (status))) - goto CLEANUP; - } - - status = _cairo_path_fixed_fill_to_polygon (path, - tolerance, - &polygon); + status = _cairo_path_fixed_fill_to_polygon (path, tolerance, &polygon); if (unlikely (status)) goto CLEANUP; @@ -1177,6 +1169,18 @@ _cairo_surface_fallback_fill (cairo_surface_t *surface, goto CLEANUP; } + if (path->is_rectilinear) { + status = _cairo_bentley_ottmann_tessellate_rectilinear_polygon (&traps, + &polygon, + fill_rule); + if (likely (status == CAIRO_STATUS_SUCCESS)) + goto DO_TRAPS; + + if (unlikely (_cairo_status_is_error (status))) + goto CLEANUP; + } + + if (antialias != CAIRO_ANTIALIAS_NONE && _cairo_surface_check_span_renderer (op, source, surface, antialias, NULL)) diff --git a/src/cairo-traps.c b/src/cairo-traps.c index 5f04ce160..a5974cc23 100644 --- a/src/cairo-traps.c +++ b/src/cairo-traps.c @@ -51,6 +51,7 @@ _cairo_traps_init (cairo_traps_t *traps) traps->status = CAIRO_STATUS_SUCCESS; traps->maybe_region = 1; + traps->is_rectilinear = 0; traps->num_traps = 0; @@ -76,6 +77,7 @@ _cairo_traps_clear (cairo_traps_t *traps) traps->status = CAIRO_STATUS_SUCCESS; traps->maybe_region = 1; + traps->is_rectilinear = 0; traps->num_traps = 0; traps->has_intersections = FALSE; diff --git a/src/cairoint.h b/src/cairoint.h index 439acfb8d..7312f7e16 100644 --- a/src/cairoint.h +++ b/src/cairoint.h @@ -956,6 +956,7 @@ typedef struct _cairo_traps { unsigned int maybe_region : 1; /* hint: 0 implies that it cannot be */ unsigned int has_limits : 1; unsigned int has_intersections : 1; + unsigned int is_rectilinear : 1; int num_traps; int traps_size; @@ -1616,11 +1617,6 @@ _cairo_path_fixed_fill_rectilinear_to_region (const cairo_path_fixed_t *path, cairo_fill_rule_t fill_rule, const cairo_rectangle_int_t *extents); -cairo_private cairo_int_status_t -_cairo_path_fixed_fill_rectilinear_to_traps (const cairo_path_fixed_t *path, - cairo_fill_rule_t fill_rule, - cairo_traps_t *traps); - cairo_private cairo_status_t _cairo_path_fixed_fill_to_traps (const cairo_path_fixed_t *path, cairo_fill_rule_t fill_rule, @@ -2376,13 +2372,23 @@ _cairo_traps_add_trap (cairo_traps_t *traps, cairo_fixed_t top, cairo_fixed_t bottom, cairo_line_t *left, cairo_line_t *right); +cairo_private cairo_status_t +_cairo_bentley_ottmann_tessellate_rectilinear_polygon (cairo_traps_t *traps, + const cairo_polygon_t *polygon, + cairo_fill_rule_t fill_rule); + cairo_private cairo_status_t _cairo_bentley_ottmann_tessellate_polygon (cairo_traps_t *traps, const cairo_polygon_t *polygon, cairo_fill_rule_t fill_rule); cairo_private cairo_status_t -_cairo_bentley_ottmann_tessellate_traps (cairo_traps_t *traps); +_cairo_bentley_ottmann_tessellate_traps (cairo_traps_t *traps, + cairo_fill_rule_t fill_rule); + +cairo_private cairo_status_t +_cairo_bentley_ottmann_tessellate_rectilinear_traps (cairo_traps_t *traps, + cairo_fill_rule_t fill_rule); cairo_private int _cairo_traps_contain (const cairo_traps_t *traps, diff --git a/test/ft-text-vertical-layout-type1.ref.png b/test/ft-text-vertical-layout-type1.ref.png index 6f0df7b3464c811776e75a451955a74420cd8fa5..f1c12a9f320e7f4f30777f93471e10301664e7cd 100644 GIT binary patch delta 3015 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 JPDHLkV1i*Envwtj delta 3054 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 diff --git a/test/ft-text-vertical-layout-type3.ref.png b/test/ft-text-vertical-layout-type3.ref.png index 94048f1c48344f0e9d3bf5ef0886ed43c09e01f2..1bda421c471970cfe6ff93fda995495cb0b59733 100644 GIT binary patch delta 3560 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 delta 3559 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