Merge XFA to PDFium master at 4dc95e7 on 10/28/2014
[pdfium.git] / xfa_test / pdf / paint_aggregator.cc
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "pdf/paint_aggregator.h"
6
7 #include <algorithm>
8
9 #include "base/logging.h"
10
11 // ----------------------------------------------------------------------------
12 // ALGORITHM NOTES
13 //
14 // We attempt to maintain a scroll rect in the presence of invalidations that
15 // are contained within the scroll rect.  If an invalidation crosses a scroll
16 // rect, then we just treat the scroll rect as an invalidation rect.
17 //
18 // For invalidations performed prior to scrolling and contained within the
19 // scroll rect, we offset the invalidation rects to account for the fact that
20 // the consumer will perform scrolling before painting.
21 //
22 // We only support scrolling along one axis at a time.  A diagonal scroll will
23 // therefore be treated as an invalidation.
24 // ----------------------------------------------------------------------------
25
26 PaintAggregator::PaintUpdate::PaintUpdate() {
27 }
28
29 PaintAggregator::PaintUpdate::~PaintUpdate() {
30 }
31
32 PaintAggregator::InternalPaintUpdate::InternalPaintUpdate() :
33     synthesized_scroll_damage_rect_(false) {
34 }
35
36 PaintAggregator::InternalPaintUpdate::~InternalPaintUpdate() {
37 }
38
39 pp::Rect PaintAggregator::InternalPaintUpdate::GetScrollDamage() const {
40   // Should only be scrolling in one direction at a time.
41   DCHECK(!(scroll_delta.x() && scroll_delta.y()));
42
43   pp::Rect damaged_rect;
44
45   // Compute the region we will expose by scrolling, and paint that into a
46   // shared memory section.
47   if (scroll_delta.x()) {
48     int32_t dx = scroll_delta.x();
49     damaged_rect.set_y(scroll_rect.y());
50     damaged_rect.set_height(scroll_rect.height());
51     if (dx > 0) {
52       damaged_rect.set_x(scroll_rect.x());
53       damaged_rect.set_width(dx);
54     } else {
55       damaged_rect.set_x(scroll_rect.right() + dx);
56       damaged_rect.set_width(-dx);
57     }
58   } else {
59     int32_t dy = scroll_delta.y();
60     damaged_rect.set_x(scroll_rect.x());
61     damaged_rect.set_width(scroll_rect.width());
62     if (dy > 0) {
63       damaged_rect.set_y(scroll_rect.y());
64       damaged_rect.set_height(dy);
65     } else {
66       damaged_rect.set_y(scroll_rect.bottom() + dy);
67       damaged_rect.set_height(-dy);
68     }
69   }
70
71   // In case the scroll offset exceeds the width/height of the scroll rect
72   return scroll_rect.Intersect(damaged_rect);
73 }
74
75 PaintAggregator::PaintAggregator() {
76 }
77
78 bool PaintAggregator::HasPendingUpdate() const {
79   return !update_.scroll_rect.IsEmpty() || !update_.paint_rects.empty();
80 }
81
82 void PaintAggregator::ClearPendingUpdate() {
83   update_ = InternalPaintUpdate();
84 }
85
86 PaintAggregator::PaintUpdate PaintAggregator::GetPendingUpdate() {
87   // Convert the internal paint update to the external one, which includes a
88   // bit more precomputed info for the caller.
89   PaintUpdate ret;
90   ret.scroll_delta = update_.scroll_delta;
91   ret.scroll_rect = update_.scroll_rect;
92   ret.has_scroll = ret.scroll_delta.x() != 0 || ret.scroll_delta.y() != 0;
93
94   // Include the scroll damage (if any) in the paint rects.
95   // Code invalidates damaged rect here, it pick it up from the list of paint
96   // rects in the next block.
97   if (ret.has_scroll  && !update_.synthesized_scroll_damage_rect_) {
98     update_.synthesized_scroll_damage_rect_ = true;
99     pp::Rect scroll_damage = update_.GetScrollDamage();
100     InvalidateRectInternal(scroll_damage, false);
101   }
102
103   ret.paint_rects.reserve(update_.paint_rects.size() + 1);
104   for (size_t i = 0; i < update_.paint_rects.size(); i++)
105     ret.paint_rects.push_back(update_.paint_rects[i]);
106
107   return ret;
108 }
109
110 void PaintAggregator::SetIntermediateResults(
111     const std::vector<ReadyRect>& ready,
112     const std::vector<pp::Rect>& pending) {
113   update_.ready_rects.insert(
114       update_.ready_rects.end(), ready.begin(), ready.end());
115   update_.paint_rects = pending;
116 }
117
118 std::vector<PaintAggregator::ReadyRect> PaintAggregator::GetReadyRects() const {
119   return update_.ready_rects;
120 }
121
122 void PaintAggregator::InvalidateRect(const pp::Rect& rect) {
123   InvalidateRectInternal(rect, true);
124 }
125
126 void PaintAggregator::ScrollRect(const pp::Rect& clip_rect,
127                                  const pp::Point& amount) {
128   // We only support scrolling along one axis at a time.
129   if (amount.x() != 0 && amount.y() != 0) {
130     InvalidateRect(clip_rect);
131     return;
132   }
133
134   // We can only scroll one rect at a time.
135   if (!update_.scroll_rect.IsEmpty() && update_.scroll_rect != clip_rect) {
136     InvalidateRect(clip_rect);
137     return;
138   }
139
140   // Again, we only support scrolling along one axis at a time.  Make sure this
141   // update doesn't scroll on a different axis than any existing one.
142   if ((amount.x() && update_.scroll_delta.y()) ||
143       (amount.y() && update_.scroll_delta.x())) {
144     InvalidateRect(clip_rect);
145     return;
146   }
147
148   // The scroll rect is new or isn't changing (though the scroll amount may
149   // be changing).
150   update_.scroll_rect = clip_rect;
151   update_.scroll_delta += amount;
152
153   // We might have just wiped out a pre-existing scroll.
154   if (update_.scroll_delta == pp::Point()) {
155     update_.scroll_rect = pp::Rect();
156     return;
157   }
158
159   // Adjust any paint rects that intersect the scroll. For the portion of the
160   // paint that is inside the scroll area, move it by the scroll amount and
161   // replace the existing paint with it. For the portion (if any) that is
162   // outside the scroll, just invalidate it.
163   std::vector<pp::Rect> leftover_rects;
164   for (size_t i = 0; i < update_.paint_rects.size(); ++i) {
165     if (!update_.scroll_rect.Intersects(update_.paint_rects[i]))
166       continue;
167
168     pp::Rect intersection =
169         update_.paint_rects[i].Intersect(update_.scroll_rect);
170     pp::Rect rect = update_.paint_rects[i];
171     while (!rect.IsEmpty()) {
172       pp::Rect leftover = rect.Subtract(intersection);
173       if (leftover.IsEmpty())
174         break;
175       // Don't want to call InvalidateRectInternal now since it'll modify
176       // update_.paint_rects, so keep track of this and do it below.
177       leftover_rects.push_back(leftover);
178       rect = rect.Subtract(leftover);
179     }
180
181     update_.paint_rects[i] = ScrollPaintRect(intersection, amount);
182
183     // The rect may have been scrolled out of view.
184     if (update_.paint_rects[i].IsEmpty()) {
185       update_.paint_rects.erase(update_.paint_rects.begin() + i);
186       i--;
187     }
188   }
189
190   for (size_t i = 0; i < leftover_rects.size(); ++i)
191     InvalidateRectInternal(leftover_rects[i], false);
192
193   for (size_t i = 0; i < update_.ready_rects.size(); ++i) {
194     if (update_.scroll_rect.Contains(update_.ready_rects[i].rect)) {
195       update_.ready_rects[i].rect =
196           ScrollPaintRect(update_.ready_rects[i].rect, amount);
197     }
198   }
199
200   if (update_.synthesized_scroll_damage_rect_) {
201     pp::Rect damage = update_.GetScrollDamage();
202     InvalidateRect(damage);
203   }
204 }
205
206 pp::Rect PaintAggregator::ScrollPaintRect(const pp::Rect& paint_rect,
207                                           const pp::Point& amount) const {
208   pp::Rect result = paint_rect;
209   result.Offset(amount);
210   result = update_.scroll_rect.Intersect(result);
211   return result;
212 }
213
214 void PaintAggregator::InvalidateScrollRect() {
215   pp::Rect scroll_rect = update_.scroll_rect;
216   update_.scroll_rect = pp::Rect();
217   update_.scroll_delta = pp::Point();
218   InvalidateRect(scroll_rect);
219 }
220
221 void PaintAggregator::InvalidateRectInternal(const pp::Rect& rect_old,
222                                              bool check_scroll) {
223   pp::Rect rect = rect_old;
224   // Check if any rects that are ready to be painted overlap.
225   for (size_t i = 0; i < update_.ready_rects.size(); ++i) {
226     const pp::Rect& existing_rect = update_.ready_rects[i].rect;
227     if (rect.Intersects(existing_rect)) {
228       // Re-invalidate in case the union intersects other paint rects.
229       rect = existing_rect.Union(rect);
230       update_.ready_rects.erase(update_.ready_rects.begin() + i);
231       break;
232     }
233   }
234
235   bool add_paint = true;
236
237   // Combine overlapping paints using smallest bounding box.
238   for (size_t i = 0; i < update_.paint_rects.size(); ++i) {
239     const pp::Rect& existing_rect = update_.paint_rects[i];
240     if (existing_rect.Contains(rect))  // Optimize out redundancy.
241       add_paint = false;
242     if (rect.Intersects(existing_rect) || rect.SharesEdgeWith(existing_rect)) {
243       // Re-invalidate in case the union intersects other paint rects.
244       pp::Rect combined_rect = existing_rect.Union(rect);
245       update_.paint_rects.erase(update_.paint_rects.begin() + i);
246       InvalidateRectInternal(combined_rect, check_scroll);
247       add_paint = false;
248     }
249   }
250
251   if (add_paint) {
252     // Add a non-overlapping paint.
253     update_.paint_rects.push_back(rect);
254   }
255
256   // If the new paint overlaps with a scroll, then also invalidate the rect in
257   // its new position.
258   if (check_scroll &&
259       !update_.scroll_rect.IsEmpty() &&
260       update_.scroll_rect.Intersects(rect)) {
261     InvalidateRectInternal(ScrollPaintRect(rect, update_.scroll_delta), false);
262   }
263 }