blob: 145552dc13aca12b5cc1fd19404896379eb0f6b2 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
3 * Copyright 2011 Google Inc.
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
reed@google.com5c3d1472011-02-22 19:12:23 +00008#ifndef SkClipStack_DEFINED
9#define SkClipStack_DEFINED
10
11#include "SkDeque.h"
bsalomon@google.com8a98e3b2012-11-29 21:05:13 +000012#include "SkPath.h"
13#include "SkRect.h"
reed@google.com5c3d1472011-02-22 19:12:23 +000014#include "SkRegion.h"
robertphillips@google.com46f93502012-08-07 15:38:08 +000015#include "SkTDArray.h"
reed@google.com5c3d1472011-02-22 19:12:23 +000016
reed@google.com5c3d1472011-02-22 19:12:23 +000017
robertphillips@google.com4c2a2f72012-07-24 22:07:50 +000018// Because a single save/restore state can have multiple clips, this class
19// stores the stack depth (fSaveCount) and clips (fDeque) separately.
20// Each clip in fDeque stores the stack state to which it belongs
21// (i.e., the fSaveCount in force when it was added). Restores are thus
22// implemented by removing clips from fDeque that have an fSaveCount larger
23// then the freshly decremented count.
ctguil@chromium.org7ffb1b22011-03-15 21:27:08 +000024class SK_API SkClipStack {
reed@google.com5c3d1472011-02-22 19:12:23 +000025public:
bsalomon@google.com8a98e3b2012-11-29 21:05:13 +000026 enum BoundsType {
27 // The bounding box contains all the pixels that can be written to
28 kNormal_BoundsType,
29 // The bounding box contains all the pixels that cannot be written to.
30 // The real bound extends out to infinity and all the pixels outside
31 // of the bound can be written to. Note that some of the pixels inside
32 // the bound may also be writeable but all pixels that cannot be
33 // written to are guaranteed to be inside.
34 kInsideOut_BoundsType
35 };
36
37 class Element {
38 public:
39 enum Type {
40 //!< This element makes the clip empty (regardless of previous elements).
41 kEmpty_Type,
42 //!< This element combines a rect with the current clip using a set operation
43 kRect_Type,
44 //!< This element combines a path with the current clip using a set operation
45 kPath_Type,
46 };
47
48 Element() {
49 this->initCommon(0, SkRegion::kReplace_Op, false);
50 this->setEmpty();
51 }
52
53 Element(const SkRect& rect, SkRegion::Op op, bool doAA) {
54 this->initRect(0, rect, op, doAA);
55 }
56
57 Element(const SkPath& path, SkRegion::Op op, bool doAA) {
58 this->initPath(0, path, op, doAA);
59 }
60
bsalomon@google.com8182fa02012-12-04 14:06:06 +000061 bool operator== (const Element& element) const {
62 if (this == &element) {
63 return true;
64 }
65 if (fOp != element.fOp ||
66 fType != element.fType ||
67 fDoAA != element.fDoAA ||
68 fSaveCount != element.fSaveCount) {
69 return false;
70 }
71 switch (fType) {
72 case kPath_Type:
73 return fPath == element.fPath;
74 case kRect_Type:
75 return fRect == element.fRect;
76 case kEmpty_Type:
77 return true;
78 default:
79 SkDEBUGFAIL("Unexpected type.");
80 return false;
81 }
82 }
83 bool operator!= (const Element& element) const { return !(*this == element); }
84
bsalomon@google.com8a98e3b2012-11-29 21:05:13 +000085 //!< Call to get the type of the clip element.
86 Type getType() const { return fType; }
87
88 //!< Call if getType() is kPath to get the path.
89 const SkPath& getPath() const { return fPath; }
90
91 //!< Call if getType() is kRect to get the rect.
92 const SkRect& getRect() const { return fRect; }
93
94 //!< Call if getType() is not kEmpty to get the set operation used to combine this element.
95 SkRegion::Op getOp() const { return fOp; }
96
97 /** If getType() is not kEmpty this indicates whether the clip shape should be anti-aliased
98 when it is rasterized. */
99 bool isAA() const { return fDoAA; }
100
bsalomon@google.comc6b3e482012-12-07 20:43:52 +0000101 //!< Inverts the fill of the clip shape. Note that a kEmpty element remains kEmpty.
102 void invertShapeFillType();
103
104 //!< Sets the set operation represented by the element.
bsalomon@google.com8182fa02012-12-04 14:06:06 +0000105 void setOp(SkRegion::Op op) { fOp = op; }
106
bsalomon@google.com8a98e3b2012-11-29 21:05:13 +0000107 /** The GenID can be used by clip stack clients to cache representations of the clip. The
108 ID corresponds to the set of clip elements up to and including this element within the
109 stack not to the element itself. That is the same clip path in different stacks will
110 have a different ID since the elements produce different clip result in the context of
111 their stacks. */
112 int32_t getGenID() const { return fGenID; }
113
bsalomon@google.com8182fa02012-12-04 14:06:06 +0000114 /**
115 * Gets the bounds of the clip element, either the rect or path bounds. (Whether the shape
116 * is inverse filled is not considered.)
117 */
118 const SkRect& getBounds() const {
119 static const SkRect kEmpty = { 0, 0, 0, 0 };
120 switch (fType) {
121 case kRect_Type:
122 return fRect;
123 case kPath_Type:
124 return fPath.getBounds();
125 case kEmpty_Type:
126 return kEmpty;
127 default:
128 SkDEBUGFAIL("Unexpected type.");
129 return kEmpty;
130 }
131 }
132
133 /**
134 * Conservatively checks whether the clip shape contains the rect param. (Whether the shape
135 * is inverse filled is not considered.)
136 */
137 bool contains(const SkRect& rect) const {
138 switch (fType) {
139 case kRect_Type:
140 return fRect.contains(rect);
141 case kPath_Type:
142 return fPath.conservativelyContainsRect(rect);
143 case kEmpty_Type:
144 return false;
145 default:
146 SkDEBUGFAIL("Unexpected type.");
147 return false;
148 }
149 }
150
151 /**
152 * Is the clip shape inverse filled.
153 */
154 bool isInverseFilled() const {
155 return kPath_Type == fType && fPath.isInverseFillType();
156 }
157
bsalomon@google.com8a98e3b2012-11-29 21:05:13 +0000158 private:
159 friend class SkClipStack;
160
161 SkPath fPath;
162 SkRect fRect;
163 int fSaveCount; // save count of stack when this element was added.
164 SkRegion::Op fOp;
165 Type fType;
166 bool fDoAA;
167
168 /* fFiniteBoundType and fFiniteBound are used to incrementally update the clip stack's
169 bound. When fFiniteBoundType is kNormal_BoundsType, fFiniteBound represents the
170 conservative bounding box of the pixels that aren't clipped (i.e., any pixels that can be
171 drawn to are inside the bound). When fFiniteBoundType is kInsideOut_BoundsType (which
172 occurs when a clip is inverse filled), fFiniteBound represents the conservative bounding
173 box of the pixels that _are_ clipped (i.e., any pixels that cannot be drawn to are inside
174 the bound). When fFiniteBoundType is kInsideOut_BoundsType the actual bound is the
175 infinite plane. This behavior of fFiniteBoundType and fFiniteBound is required so that we
176 can capture the cancelling out of the extensions to infinity when two inverse filled
177 clips are Booleaned together. */
178 SkClipStack::BoundsType fFiniteBoundType;
179 SkRect fFiniteBound;
skia.committer@gmail.comc3d7d902012-11-30 02:01:24 +0000180
bsalomon@google.com8a98e3b2012-11-29 21:05:13 +0000181 // When element is applied to the previous elements in the stack is the result known to be
182 // equivalent to a single rect intersection? IIOW, is the clip effectively a rectangle.
183 bool fIsIntersectionOfRects;
184
185 int fGenID;
186
187 Element(int saveCount) {
188 this->initCommon(saveCount, SkRegion::kReplace_Op, false);
189 this->setEmpty();
190 }
191
192 Element(int saveCount, const SkRect& rect, SkRegion::Op op, bool doAA) {
193 this->initRect(saveCount, rect, op, doAA);
194 }
195
196 Element(int saveCount, const SkPath& path, SkRegion::Op op, bool doAA) {
197 this->initPath(saveCount, path, op, doAA);
198 }
199
200 void initCommon(int saveCount, SkRegion::Op op, bool doAA) {
201 fSaveCount = saveCount;
202 fOp = op;
203 fDoAA = doAA;
204 // A default of inside-out and empty bounds means the bounds are effectively void as it
205 // indicates that nothing is known to be outside the clip.
206 fFiniteBoundType = kInsideOut_BoundsType;
207 fFiniteBound.setEmpty();
208 fIsIntersectionOfRects = false;
209 fGenID = kInvalidGenID;
210 }
211
212 void initRect(int saveCount, const SkRect& rect, SkRegion::Op op, bool doAA) {
213 fRect = rect;
214 fType = kRect_Type;
215 this->initCommon(saveCount, op, doAA);
216 }
217
218 void initPath(int saveCount, const SkPath& path, SkRegion::Op op, bool doAA) {
219 fPath = path;
220 fType = kPath_Type;
221 this->initCommon(saveCount, op, doAA);
222 }
223
224 void setEmpty() {
225 fType = kEmpty_Type;
226 fFiniteBound.setEmpty();
227 fFiniteBoundType = kNormal_BoundsType;
228 fIsIntersectionOfRects = false;
229 fRect.setEmpty();
230 fPath.reset();
231 fGenID = kEmptyGenID;
232 }
233
234 // All Element methods below are only used within SkClipStack.cpp
235 inline void checkEmpty() const;
bsalomon@google.com8a98e3b2012-11-29 21:05:13 +0000236 inline bool canBeIntersectedInPlace(int saveCount, SkRegion::Op op) const;
237 /* This method checks to see if two rect clips can be safely merged into one. The issue here
skia.committer@gmail.comc3d7d902012-11-30 02:01:24 +0000238 is that to be strictly correct all the edges of the resulting rect must have the same
bsalomon@google.com8a98e3b2012-11-29 21:05:13 +0000239 anti-aliasing. */
240 bool rectRectIntersectAllowed(const SkRect& newR, bool newAA) const;
241 /** Determines possible finite bounds for the Element given the previous element of the
242 stack */
243 void updateBoundAndGenID(const Element* prior);
244 // The different combination of fill & inverse fill when combining bounding boxes
245 enum FillCombo {
246 kPrev_Cur_FillCombo,
247 kPrev_InvCur_FillCombo,
248 kInvPrev_Cur_FillCombo,
249 kInvPrev_InvCur_FillCombo
250 };
251 // per-set operation functions used by updateBoundAndGenID().
252 inline void combineBoundsDiff(FillCombo combination, const SkRect& prevFinite);
253 inline void combineBoundsXOR(int combination, const SkRect& prevFinite);
254 inline void combineBoundsUnion(int combination, const SkRect& prevFinite);
255 inline void combineBoundsIntersection(int combination, const SkRect& prevFinite);
256 inline void combineBoundsRevDiff(int combination, const SkRect& prevFinite);
257 };
258
reed@google.com5c3d1472011-02-22 19:12:23 +0000259 SkClipStack();
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +0000260 SkClipStack(const SkClipStack& b);
robertphillips@google.comcc6493b2012-07-26 18:39:13 +0000261 explicit SkClipStack(const SkRect& r);
robertphillips@google.com641f8b12012-07-31 19:15:58 +0000262 explicit SkClipStack(const SkIRect& r);
vandebo@chromium.org610f7162012-03-14 18:34:15 +0000263 ~SkClipStack();
reed@google.com5c3d1472011-02-22 19:12:23 +0000264
vandebo@chromium.org1e1c36f2011-05-03 16:26:09 +0000265 SkClipStack& operator=(const SkClipStack& b);
266 bool operator==(const SkClipStack& b) const;
267 bool operator!=(const SkClipStack& b) const { return !(*this == b); }
268
reed@google.com5c3d1472011-02-22 19:12:23 +0000269 void reset();
270
271 int getSaveCount() const { return fSaveCount; }
272 void save();
273 void restore();
274
robertphillips@google.com607fe072012-07-24 13:54:00 +0000275 /**
276 * getBounds places the current finite bound in its first parameter. In its
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000277 * second, it indicates which kind of bound is being returned. If
robertphillips@google.com7b112892012-07-31 15:18:21 +0000278 * 'canvFiniteBound' is a normal bounding box then it encloses all writeable
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000279 * pixels. If 'canvFiniteBound' is an inside out bounding box then it
robertphillips@google.com607fe072012-07-24 13:54:00 +0000280 * encloses all the un-writeable pixels and the true/normal bound is the
robertphillips@google.com4c2a2f72012-07-24 22:07:50 +0000281 * infinite plane. isIntersectionOfRects is an optional parameter
robertphillips@google.com7b112892012-07-31 15:18:21 +0000282 * that is true if 'canvFiniteBound' resulted from an intersection of rects.
robertphillips@google.com607fe072012-07-24 13:54:00 +0000283 */
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000284 void getBounds(SkRect* canvFiniteBound,
robertphillips@google.com4c2a2f72012-07-24 22:07:50 +0000285 BoundsType* boundType,
286 bool* isIntersectionOfRects = NULL) const;
robertphillips@google.com607fe072012-07-24 13:54:00 +0000287
bsalomon@google.com3ab43d52012-10-11 19:39:09 +0000288 /**
289 * Takes an input rect in device space and conservatively clips it to the
290 * clip-stack. If false is returned then the rect does not intersect the
291 * clip and is unmodified.
292 */
293 bool intersectRectWithClip(SkRect* devRect) const;
294
junov@chromium.org8cdf0f52012-12-12 17:58:15 +0000295 /**
296 * Returns true if the input rect in device space is entirely contained
297 * by the clip. A return value of false does not guarantee that the rect
298 * is not contained by the clip.
299 */
300 bool quickContains(const SkRect& devRect) const;
301
reed@google.com115d9312012-05-16 18:50:40 +0000302 void clipDevRect(const SkIRect& ir, SkRegion::Op op) {
reed@google.com5c3d1472011-02-22 19:12:23 +0000303 SkRect r;
304 r.set(ir);
reed@google.com00177082011-10-12 14:34:30 +0000305 this->clipDevRect(r, op, false);
reed@google.com5c3d1472011-02-22 19:12:23 +0000306 }
reed@google.com00177082011-10-12 14:34:30 +0000307 void clipDevRect(const SkRect&, SkRegion::Op, bool doAA);
308 void clipDevPath(const SkPath&, SkRegion::Op, bool doAA);
reed@google.com0557d9e2012-08-16 15:59:59 +0000309 // An optimized version of clipDevRect(emptyRect, kIntersect, ...)
310 void clipEmpty();
reed@google.com5c3d1472011-02-22 19:12:23 +0000311
robertphillips@google.comcc6493b2012-07-26 18:39:13 +0000312 /**
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000313 * isWideOpen returns true if the clip state corresponds to the infinite
robertphillips@google.comcc6493b2012-07-26 18:39:13 +0000314 * plane (i.e., draws are not limited at all)
315 */
316 bool isWideOpen() const;
317
robertphillips@google.com46f93502012-08-07 15:38:08 +0000318 /**
319 * Add a callback function that will be called whenever a clip state
320 * is no longer viable. This will occur whenever restore
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000321 * is called or when a clipDevRect or clipDevPath call updates the
robertphillips@google.com46f93502012-08-07 15:38:08 +0000322 * clip within an existing save/restore state. Each clip state is
323 * represented by a unique generation ID.
324 */
325 typedef void (*PFPurgeClipCB)(int genID, void* data);
326 void addPurgeClipCallback(PFPurgeClipCB callback, void* data) const;
327
328 /**
329 * Remove a callback added earlier via addPurgeClipCallback
330 */
331 void removePurgeClipCallback(PFPurgeClipCB callback, void* data) const;
332
333 /**
334 * The generation ID has three reserved values to indicate special
bsalomon@google.come8ca6c62012-11-07 21:19:10 +0000335 * (potentially ignorable) cases
robertphillips@google.com46f93502012-08-07 15:38:08 +0000336 */
337 static const int32_t kInvalidGenID = 0;
338 static const int32_t kEmptyGenID = 1; // no pixels writeable
339 static const int32_t kWideOpenGenID = 2; // all pixels writeable
340
robertphillips@google.com73e71022012-08-09 18:10:49 +0000341 int32_t getTopmostGenID() const;
342
robertphillips@google.com52cb2c72012-07-16 18:52:29 +0000343public:
344 class Iter {
reed@google.com5c3d1472011-02-22 19:12:23 +0000345 public:
robertphillips@google.com52cb2c72012-07-16 18:52:29 +0000346 enum IterStart {
robertphillips@google.com80214e22012-07-20 15:33:18 +0000347 kBottom_IterStart = SkDeque::Iter::kFront_IterStart,
348 kTop_IterStart = SkDeque::Iter::kBack_IterStart
robertphillips@google.com52cb2c72012-07-16 18:52:29 +0000349 };
350
bsalomon@google.comd302f142011-03-03 13:54:13 +0000351 /**
352 * Creates an uninitialized iterator. Must be reset()
353 */
robertphillips@google.com52cb2c72012-07-16 18:52:29 +0000354 Iter();
bsalomon@google.comd302f142011-03-03 13:54:13 +0000355
robertphillips@google.com52cb2c72012-07-16 18:52:29 +0000356 Iter(const SkClipStack& stack, IterStart startLoc);
reed@google.com5c3d1472011-02-22 19:12:23 +0000357
bsalomon@google.com8182fa02012-12-04 14:06:06 +0000358 /**
359 * Return the clip element for this iterator. If next()/prev() returns NULL, then the
360 * iterator is done.
361 */
362 const Element* next();
363 const Element* prev();
reed@google.com5c3d1472011-02-22 19:12:23 +0000364
365 /**
bsalomon@google.com8182fa02012-12-04 14:06:06 +0000366 * Moves the iterator to the topmost element with the specified RegionOp and returns that
367 * element. If no clip element with that op is found, the first element is returned.
reed@google.com5c3d1472011-02-22 19:12:23 +0000368 */
bsalomon@google.com8182fa02012-12-04 14:06:06 +0000369 const Element* skipToTopmost(SkRegion::Op op);
robertphillips@google.com5836b6d2012-07-18 12:06:15 +0000370
371 /**
bsalomon@google.comd302f142011-03-03 13:54:13 +0000372 * Restarts the iterator on a clip stack.
373 */
robertphillips@google.com52cb2c72012-07-16 18:52:29 +0000374 void reset(const SkClipStack& stack, IterStart startLoc);
bsalomon@google.comd302f142011-03-03 13:54:13 +0000375
reed@google.com5c3d1472011-02-22 19:12:23 +0000376 private:
robertphillips@google.com5836b6d2012-07-18 12:06:15 +0000377 const SkClipStack* fStack;
robertphillips@google.com5836b6d2012-07-18 12:06:15 +0000378 SkDeque::Iter fIter;
robertphillips@google.com52cb2c72012-07-16 18:52:29 +0000379 };
380
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000381 /**
robertphillips@google.com80214e22012-07-20 15:33:18 +0000382 * The B2TIter iterates from the bottom of the stack to the top.
383 * It inherits privately from Iter to prevent access to reverse iteration.
384 */
385 class B2TIter : private Iter {
robertphillips@google.com52cb2c72012-07-16 18:52:29 +0000386 public:
robertphillips@google.com80214e22012-07-20 15:33:18 +0000387 B2TIter() {}
robertphillips@google.com52cb2c72012-07-16 18:52:29 +0000388
389 /**
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000390 * Wrap Iter's 2 parameter ctor to force initialization to the
robertphillips@google.com80214e22012-07-20 15:33:18 +0000391 * beginning of the deque/bottom of the stack
robertphillips@google.com52cb2c72012-07-16 18:52:29 +0000392 */
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000393 B2TIter(const SkClipStack& stack)
robertphillips@google.com80214e22012-07-20 15:33:18 +0000394 : INHERITED(stack, kBottom_IterStart) {
robertphillips@google.com52cb2c72012-07-16 18:52:29 +0000395 }
396
robertphillips@google.com52cb2c72012-07-16 18:52:29 +0000397 using Iter::next;
398
399 /**
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000400 * Wrap Iter::reset to force initialization to the
robertphillips@google.com80214e22012-07-20 15:33:18 +0000401 * beginning of the deque/bottom of the stack
robertphillips@google.com52cb2c72012-07-16 18:52:29 +0000402 */
403 void reset(const SkClipStack& stack) {
robertphillips@google.com80214e22012-07-20 15:33:18 +0000404 this->INHERITED::reset(stack, kBottom_IterStart);
robertphillips@google.com52cb2c72012-07-16 18:52:29 +0000405 }
406
407 private:
408
409 typedef Iter INHERITED;
reed@google.com5c3d1472011-02-22 19:12:23 +0000410 };
411
robertphillips@google.com607fe072012-07-24 13:54:00 +0000412 /**
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000413 * GetConservativeBounds returns a conservative bound of the current clip.
414 * Since this could be the infinite plane (if inverse fills were involved) the
415 * maxWidth and maxHeight parameters can be used to limit the returned bound
robertphillips@google.com607fe072012-07-24 13:54:00 +0000416 * to the expected drawing area. Similarly, the offsetX and offsetY parameters
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000417 * allow the caller to offset the returned bound to account for translated
robertphillips@google.com607fe072012-07-24 13:54:00 +0000418 * drawing areas (i.e., those resulting from a saveLayer). For finite bounds,
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000419 * the translation (+offsetX, +offsetY) is applied before the clamp to the
robertphillips@google.com607fe072012-07-24 13:54:00 +0000420 * maximum rectangle: [0,maxWidth) x [0,maxHeight).
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000421 * isIntersectionOfRects is an optional parameter that is true when
robertphillips@google.com641f8b12012-07-31 19:15:58 +0000422 * 'devBounds' is the result of an intersection of rects. In this case
423 * 'devBounds' is the exact answer/clip.
robertphillips@google.com607fe072012-07-24 13:54:00 +0000424 */
425 void getConservativeBounds(int offsetX,
426 int offsetY,
427 int maxWidth,
428 int maxHeight,
robertphillips@google.com7b112892012-07-31 15:18:21 +0000429 SkRect* devBounds,
robertphillips@google.com4c2a2f72012-07-24 22:07:50 +0000430 bool* isIntersectionOfRects = NULL) const;
robertphillips@google.com607fe072012-07-24 13:54:00 +0000431
reed@google.com5c3d1472011-02-22 19:12:23 +0000432private:
robertphillips@google.com52cb2c72012-07-16 18:52:29 +0000433 friend class Iter;
reed@google.com5c3d1472011-02-22 19:12:23 +0000434
435 SkDeque fDeque;
436 int fSaveCount;
robertphillips@google.com46f93502012-08-07 15:38:08 +0000437
438 // Generation ID for the clip stack. This is incremented for each
439 // clipDevRect and clipDevPath call. 0 is reserved to indicate an
440 // invalid ID.
441 static int32_t gGenID;
442
443 struct ClipCallbackData {
444 PFPurgeClipCB fCallback;
445 void* fData;
446
447 friend bool operator==(const ClipCallbackData& a,
448 const ClipCallbackData& b) {
449 return a.fCallback == b.fCallback && a.fData == b.fData;
450 }
451 };
452
453 mutable SkTDArray<ClipCallbackData> fCallbackData;
454
455 /**
commit-bot@chromium.org6fbe54c2013-06-11 11:01:48 +0000456 * Restore the stack back to the specified save count.
457 */
458 void restoreTo(int saveCount);
459
460 /**
bsalomon@google.com9128edc2012-11-29 18:58:19 +0000461 * Invoke all the purge callbacks passing in element's generation ID.
robertphillips@google.com46f93502012-08-07 15:38:08 +0000462 */
bsalomon@google.com9128edc2012-11-29 18:58:19 +0000463 void purgeClip(Element* element);
robertphillips@google.com46f93502012-08-07 15:38:08 +0000464
465 /**
466 * Return the next unique generation ID.
467 */
468 static int32_t GetNextGenID();
reed@google.com5c3d1472011-02-22 19:12:23 +0000469};
470
471#endif