1 | /* |
2 | * Copyright (C) 2016 Apple Inc. All rights reserved. |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions |
6 | * are met: |
7 | * 1. Redistributions of source code must retain the above copyright |
8 | * notice, this list of conditions and the following disclaimer. |
9 | * 2. Redistributions in binary form must reproduce the above copyright |
10 | * notice, this list of conditions and the following disclaimer in the |
11 | * documentation and/or other materials provided with the distribution. |
12 | * |
13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
14 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
20 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
21 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | #include "DisplayListRecorder.h" |
28 | |
29 | #include "DisplayList.h" |
30 | #include "DisplayListItems.h" |
31 | #include "GraphicsContext.h" |
32 | #include "Logging.h" |
33 | #include <wtf/MathExtras.h> |
34 | #include <wtf/text/TextStream.h> |
35 | |
36 | namespace WebCore { |
37 | namespace DisplayList { |
38 | |
39 | Recorder::Recorder(GraphicsContext& context, DisplayList& displayList, const GraphicsContextState& state, const FloatRect& initialClip, const AffineTransform& baseCTM) |
40 | : GraphicsContextImpl(context, initialClip, baseCTM) |
41 | , m_displayList(displayList) |
42 | { |
43 | LOG_WITH_STREAM(DisplayLists, stream << "\nRecording with clip " << initialClip); |
44 | m_stateStack.append(ContextState(state, baseCTM, initialClip)); |
45 | } |
46 | |
47 | Recorder::~Recorder() |
48 | { |
49 | ASSERT(m_stateStack.size() == 1); // If this fires, it indicates mismatched save/restore. |
50 | LOG(DisplayLists, "Recorded display list:\n%s" , m_displayList.description().data()); |
51 | } |
52 | |
53 | void Recorder::willAppendItem(const Item& item) |
54 | { |
55 | if (item.isDrawingItem() |
56 | #if USE(CG) |
57 | || item.type() == ItemType::ApplyStrokePattern || item.type() == ItemType::ApplyStrokePattern |
58 | #endif |
59 | ) { |
60 | GraphicsContextStateChange& stateChanges = currentState().stateChange; |
61 | GraphicsContextState::StateChangeFlags changesFromLastState = stateChanges.changesFromState(currentState().lastDrawingState); |
62 | if (changesFromLastState) { |
63 | LOG_WITH_STREAM(DisplayLists, stream << "pre-drawing, saving state " << GraphicsContextStateChange(stateChanges.m_state, changesFromLastState)); |
64 | m_displayList.append(SetState::create(stateChanges.m_state, changesFromLastState)); |
65 | stateChanges.m_changeFlags = 0; |
66 | currentState().lastDrawingState = stateChanges.m_state; |
67 | } |
68 | currentState().wasUsedForDrawing = true; |
69 | } |
70 | } |
71 | |
72 | void Recorder::updateState(const GraphicsContextState& state, GraphicsContextState::StateChangeFlags flags) |
73 | { |
74 | currentState().stateChange.accumulate(state, flags); |
75 | } |
76 | |
77 | void Recorder::clearShadow() |
78 | { |
79 | appendItem(ClearShadow::create()); |
80 | } |
81 | |
82 | void Recorder::setLineCap(LineCap lineCap) |
83 | { |
84 | appendItem(SetLineCap::create(lineCap)); |
85 | } |
86 | |
87 | void Recorder::setLineDash(const DashArray& dashArray, float dashOffset) |
88 | { |
89 | appendItem(SetLineDash::create(dashArray, dashOffset)); |
90 | } |
91 | |
92 | void Recorder::setLineJoin(LineJoin lineJoin) |
93 | { |
94 | appendItem(SetLineJoin::create(lineJoin)); |
95 | } |
96 | |
97 | void Recorder::setMiterLimit(float miterLimit) |
98 | { |
99 | appendItem(SetMiterLimit::create(miterLimit)); |
100 | } |
101 | |
102 | void Recorder::drawGlyphs(const Font& font, const GlyphBuffer& glyphBuffer, unsigned from, unsigned numGlyphs, const FloatPoint& startPoint, FontSmoothingMode smoothingMode) |
103 | { |
104 | DrawingItem& newItem = downcast<DrawingItem>(appendItem(DrawGlyphs::create(font, glyphBuffer.glyphs(from), glyphBuffer.advances(from), numGlyphs, FloatPoint(), toFloatSize(startPoint), smoothingMode))); |
105 | updateItemExtent(newItem); |
106 | } |
107 | |
108 | ImageDrawResult Recorder::drawImage(Image& image, const FloatRect& destination, const FloatRect& source, const ImagePaintingOptions& imagePaintingOptions) |
109 | { |
110 | DrawingItem& newItem = downcast<DrawingItem>(appendItem(DrawImage::create(image, destination, source, imagePaintingOptions))); |
111 | updateItemExtent(newItem); |
112 | return ImageDrawResult::DidRecord; |
113 | } |
114 | |
115 | ImageDrawResult Recorder::drawTiledImage(Image& image, const FloatRect& destination, const FloatPoint& source, const FloatSize& tileSize, const FloatSize& spacing, const ImagePaintingOptions& imagePaintingOptions) |
116 | { |
117 | DrawingItem& newItem = downcast<DrawingItem>(appendItem(DrawTiledImage::create(image, destination, source, tileSize, spacing, imagePaintingOptions))); |
118 | updateItemExtent(newItem); |
119 | return ImageDrawResult::DidRecord; |
120 | } |
121 | |
122 | ImageDrawResult Recorder::drawTiledImage(Image& image, const FloatRect& destination, const FloatRect& source, const FloatSize& tileScaleFactor, Image::TileRule hRule, Image::TileRule vRule, const ImagePaintingOptions& imagePaintingOptions) |
123 | { |
124 | DrawingItem& newItem = downcast<DrawingItem>(appendItem(DrawTiledScaledImage::create(image, destination, source, tileScaleFactor, hRule, vRule, imagePaintingOptions))); |
125 | updateItemExtent(newItem); |
126 | return ImageDrawResult::DidRecord; |
127 | } |
128 | |
129 | #if USE(CG) || USE(CAIRO) |
130 | void Recorder::drawNativeImage(const NativeImagePtr& image, const FloatSize& imageSize, const FloatRect& destRect, const FloatRect& srcRect, CompositeOperator op, BlendMode blendMode, ImageOrientation orientation) |
131 | { |
132 | DrawingItem& newItem = downcast<DrawingItem>(appendItem(DrawNativeImage::create(image, imageSize, destRect, srcRect, op, blendMode, orientation))); |
133 | updateItemExtent(newItem); |
134 | } |
135 | #endif |
136 | |
137 | void Recorder::drawPattern(Image& image, const FloatRect& destRect, const FloatRect& tileRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, CompositeOperator op, BlendMode blendMode) |
138 | { |
139 | DrawingItem& newItem = downcast<DrawingItem>(appendItem(DrawPattern::create(image, destRect, tileRect, patternTransform, phase, spacing, op, blendMode))); |
140 | updateItemExtent(newItem); |
141 | } |
142 | |
143 | void Recorder::save() |
144 | { |
145 | appendItem(Save::create()); |
146 | m_stateStack.append(m_stateStack.last().cloneForSave(m_displayList.itemCount() - 1)); |
147 | } |
148 | |
149 | void Recorder::restore() |
150 | { |
151 | if (!m_stateStack.size()) |
152 | return; |
153 | |
154 | bool stateUsedForDrawing = currentState().wasUsedForDrawing; |
155 | size_t saveIndex = currentState().saveItemIndex; |
156 | |
157 | m_stateStack.removeLast(); |
158 | // Have to avoid eliding nested Save/Restore when a descendant state contains drawing items. |
159 | currentState().wasUsedForDrawing |= stateUsedForDrawing; |
160 | |
161 | if (!stateUsedForDrawing && saveIndex) { |
162 | // This Save/Restore didn't contain any drawing items. Roll back to just before the last save. |
163 | m_displayList.removeItemsFromIndex(saveIndex); |
164 | return; |
165 | } |
166 | |
167 | appendItem(Restore::create()); |
168 | |
169 | if (saveIndex) { |
170 | Save& saveItem = downcast<Save>(m_displayList.itemAt(saveIndex)); |
171 | saveItem.setRestoreIndex(m_displayList.itemCount() - 1); |
172 | } |
173 | } |
174 | |
175 | void Recorder::translate(float x, float y) |
176 | { |
177 | currentState().translate(x, y); |
178 | appendItem(Translate::create(x, y)); |
179 | } |
180 | |
181 | void Recorder::rotate(float angleInRadians) |
182 | { |
183 | currentState().rotate(angleInRadians); |
184 | appendItem(Rotate::create(angleInRadians)); |
185 | } |
186 | |
187 | void Recorder::scale(const FloatSize& size) |
188 | { |
189 | currentState().scale(size); |
190 | appendItem(Scale::create(size)); |
191 | } |
192 | |
193 | void Recorder::concatCTM(const AffineTransform& transform) |
194 | { |
195 | currentState().concatCTM(transform); |
196 | appendItem(ConcatenateCTM::create(transform)); |
197 | } |
198 | |
199 | void Recorder::setCTM(const AffineTransform&) |
200 | { |
201 | WTFLogAlways("GraphicsContext::setCTM() is not compatible with DisplayList::Recorder." ); |
202 | } |
203 | |
204 | AffineTransform Recorder::getCTM(GraphicsContext::IncludeDeviceScale) |
205 | { |
206 | WTFLogAlways("GraphicsContext::getCTM() is not yet compatible with DisplayList::Recorder." ); |
207 | return { }; |
208 | } |
209 | |
210 | void Recorder::beginTransparencyLayer(float opacity) |
211 | { |
212 | DrawingItem& newItem = downcast<DrawingItem>(appendItem(BeginTransparencyLayer::create(opacity))); |
213 | updateItemExtent(newItem); |
214 | } |
215 | |
216 | void Recorder::endTransparencyLayer() |
217 | { |
218 | appendItem(EndTransparencyLayer::create()); |
219 | } |
220 | |
221 | void Recorder::drawRect(const FloatRect& rect, float borderThickness) |
222 | { |
223 | DrawingItem& newItem = downcast<DrawingItem>(appendItem(DrawRect::create(rect, borderThickness))); |
224 | updateItemExtent(newItem); |
225 | } |
226 | |
227 | void Recorder::drawLine(const FloatPoint& point1, const FloatPoint& point2) |
228 | { |
229 | DrawingItem& newItem = downcast<DrawingItem>(appendItem(DrawLine::create(point1, point2))); |
230 | updateItemExtent(newItem); |
231 | } |
232 | |
233 | void Recorder::drawLinesForText(const FloatPoint& point, float thickness, const DashArray& widths, bool printing, bool doubleLines) |
234 | { |
235 | DrawingItem& newItem = downcast<DrawingItem>(appendItem(DrawLinesForText::create(FloatPoint(), toFloatSize(point), thickness, widths, printing, doubleLines))); |
236 | updateItemExtent(newItem); |
237 | } |
238 | |
239 | void Recorder::drawDotsForDocumentMarker(const FloatRect& rect, DocumentMarkerLineStyle style) |
240 | { |
241 | DrawingItem& newItem = downcast<DrawingItem>(appendItem(DrawDotsForDocumentMarker::create(rect, style))); |
242 | updateItemExtent(newItem); |
243 | } |
244 | |
245 | void Recorder::drawEllipse(const FloatRect& rect) |
246 | { |
247 | DrawingItem& newItem = downcast<DrawingItem>(appendItem(DrawEllipse::create(rect))); |
248 | updateItemExtent(newItem); |
249 | } |
250 | |
251 | void Recorder::drawPath(const Path& path) |
252 | { |
253 | DrawingItem& newItem = downcast<DrawingItem>(appendItem(DrawPath::create(path))); |
254 | updateItemExtent(newItem); |
255 | } |
256 | |
257 | void Recorder::drawFocusRing(const Path& path, float width, float offset, const Color& color) |
258 | { |
259 | DrawingItem& newItem = downcast<DrawingItem>(appendItem(DrawFocusRingPath::create(path, width, offset, color))); |
260 | updateItemExtent(newItem); |
261 | } |
262 | |
263 | void Recorder::drawFocusRing(const Vector<FloatRect>& rects, float width, float offset, const Color& color) |
264 | { |
265 | DrawingItem& newItem = downcast<DrawingItem>(appendItem(DrawFocusRingRects::create(rects, width, offset, color))); |
266 | updateItemExtent(newItem); |
267 | } |
268 | |
269 | void Recorder::fillRect(const FloatRect& rect) |
270 | { |
271 | DrawingItem& newItem = downcast<DrawingItem>(appendItem(FillRect::create(rect))); |
272 | updateItemExtent(newItem); |
273 | } |
274 | |
275 | void Recorder::fillRect(const FloatRect& rect, const Color& color) |
276 | { |
277 | DrawingItem& newItem = downcast<DrawingItem>(appendItem(FillRectWithColor::create(rect, color))); |
278 | updateItemExtent(newItem); |
279 | } |
280 | |
281 | void Recorder::fillRect(const FloatRect& rect, Gradient& gradient) |
282 | { |
283 | DrawingItem& newItem = downcast<DrawingItem>(appendItem(FillRectWithGradient::create(rect, gradient))); |
284 | updateItemExtent(newItem); |
285 | } |
286 | |
287 | void Recorder::fillRect(const FloatRect& rect, const Color& color, CompositeOperator op, BlendMode blendMode) |
288 | { |
289 | DrawingItem& newItem = downcast<DrawingItem>(appendItem(FillCompositedRect::create(rect, color, op, blendMode))); |
290 | updateItemExtent(newItem); |
291 | } |
292 | |
293 | void Recorder::fillRoundedRect(const FloatRoundedRect& rect, const Color& color, BlendMode blendMode) |
294 | { |
295 | DrawingItem& newItem = downcast<DrawingItem>(appendItem(FillRoundedRect::create(rect, color, blendMode))); |
296 | updateItemExtent(newItem); |
297 | } |
298 | |
299 | void Recorder::fillRectWithRoundedHole(const FloatRect& rect, const FloatRoundedRect& roundedHoleRect, const Color& color) |
300 | { |
301 | DrawingItem& newItem = downcast<DrawingItem>(appendItem(FillRectWithRoundedHole::create(rect, roundedHoleRect, color))); |
302 | updateItemExtent(newItem); |
303 | } |
304 | |
305 | void Recorder::fillPath(const Path& path) |
306 | { |
307 | DrawingItem& newItem = downcast<DrawingItem>(appendItem(FillPath::create(path))); |
308 | updateItemExtent(newItem); |
309 | } |
310 | |
311 | void Recorder::fillEllipse(const FloatRect& rect) |
312 | { |
313 | DrawingItem& newItem = downcast<DrawingItem>(appendItem(FillEllipse::create(rect))); |
314 | updateItemExtent(newItem); |
315 | } |
316 | |
317 | void Recorder::strokeRect(const FloatRect& rect, float lineWidth) |
318 | { |
319 | DrawingItem& newItem = downcast<DrawingItem>(appendItem(StrokeRect::create(rect, lineWidth))); |
320 | updateItemExtent(newItem); |
321 | } |
322 | |
323 | void Recorder::strokePath(const Path& path) |
324 | { |
325 | DrawingItem& newItem = downcast<DrawingItem>(appendItem(StrokePath::create(path))); |
326 | updateItemExtent(newItem); |
327 | } |
328 | |
329 | void Recorder::strokeEllipse(const FloatRect& rect) |
330 | { |
331 | DrawingItem& newItem = downcast<DrawingItem>(appendItem(StrokeEllipse::create(rect))); |
332 | updateItemExtent(newItem); |
333 | } |
334 | |
335 | void Recorder::clearRect(const FloatRect& rect) |
336 | { |
337 | DrawingItem& newItem = downcast<DrawingItem>(appendItem(ClearRect::create(rect))); |
338 | updateItemExtent(newItem); |
339 | } |
340 | |
341 | #if USE(CG) |
342 | void Recorder::applyStrokePattern() |
343 | { |
344 | appendItem(ApplyStrokePattern::create()); |
345 | } |
346 | |
347 | void Recorder::applyFillPattern() |
348 | { |
349 | appendItem(ApplyFillPattern::create()); |
350 | } |
351 | #endif |
352 | |
353 | void Recorder::clip(const FloatRect& rect) |
354 | { |
355 | currentState().clipBounds.intersect(rect); |
356 | appendItem(Clip::create(rect)); |
357 | } |
358 | |
359 | void Recorder::clipOut(const FloatRect& rect) |
360 | { |
361 | appendItem(ClipOut::create(rect)); |
362 | } |
363 | |
364 | void Recorder::clipOut(const Path& path) |
365 | { |
366 | appendItem(ClipOutToPath::create(path)); |
367 | } |
368 | |
369 | void Recorder::clipPath(const Path& path, WindRule windRule) |
370 | { |
371 | currentState().clipBounds.intersect(path.fastBoundingRect()); |
372 | appendItem(ClipPath::create(path, windRule)); |
373 | } |
374 | |
375 | IntRect Recorder::clipBounds() |
376 | { |
377 | WTFLogAlways("Getting the clip bounds not yet supported with DisplayList::Recorder." ); |
378 | return IntRect(-2048, -2048, 4096, 4096); |
379 | } |
380 | |
381 | void Recorder::clipToImageBuffer(ImageBuffer&, const FloatRect&) |
382 | { |
383 | WTFLogAlways("GraphicsContext::clipToImageBuffer is not compatible with DisplayList::Recorder." ); |
384 | } |
385 | |
386 | void Recorder::applyDeviceScaleFactor(float deviceScaleFactor) |
387 | { |
388 | // FIXME: this changes the baseCTM, which will invalidate all of our cached extents. |
389 | // Assert that it's only called early on? |
390 | appendItem(ApplyDeviceScaleFactor::create(deviceScaleFactor)); |
391 | } |
392 | |
393 | FloatRect Recorder::roundToDevicePixels(const FloatRect& rect, GraphicsContext::RoundingMode) |
394 | { |
395 | WTFLogAlways("GraphicsContext::roundToDevicePixels() is not yet compatible with DisplayList::Recorder." ); |
396 | return rect; |
397 | } |
398 | |
399 | Item& Recorder::appendItem(Ref<Item>&& item) |
400 | { |
401 | willAppendItem(item.get()); |
402 | return m_displayList.append(WTFMove(item)); |
403 | } |
404 | |
405 | void Recorder::updateItemExtent(DrawingItem& item) const |
406 | { |
407 | if (Optional<FloatRect> rect = item.localBounds(graphicsContext())) |
408 | item.setExtent(extentFromLocalBounds(rect.value())); |
409 | } |
410 | |
411 | // FIXME: share with ShadowData |
412 | static inline float shadowPaintingExtent(float blurRadius) |
413 | { |
414 | // Blurring uses a Gaussian function whose std. deviation is m_radius/2, and which in theory |
415 | // extends to infinity. In 8-bit contexts, however, rounding causes the effect to become |
416 | // undetectable at around 1.4x the radius. |
417 | const float radiusExtentMultiplier = 1.4; |
418 | return ceilf(blurRadius * radiusExtentMultiplier); |
419 | } |
420 | |
421 | FloatRect Recorder::extentFromLocalBounds(const FloatRect& rect) const |
422 | { |
423 | FloatRect bounds = rect; |
424 | const ContextState& state = currentState(); |
425 | |
426 | FloatSize shadowOffset; |
427 | float shadowRadius; |
428 | Color shadowColor; |
429 | if (graphicsContext().getShadow(shadowOffset, shadowRadius, shadowColor)) { |
430 | FloatRect shadowExtent= bounds; |
431 | shadowExtent.move(shadowOffset); |
432 | shadowExtent.inflate(shadowPaintingExtent(shadowRadius)); |
433 | bounds.unite(shadowExtent); |
434 | } |
435 | |
436 | FloatRect clippedExtent = intersection(state.clipBounds, bounds); |
437 | return state.ctm.mapRect(clippedExtent); |
438 | } |
439 | |
440 | const Recorder::ContextState& Recorder::currentState() const |
441 | { |
442 | ASSERT(m_stateStack.size()); |
443 | return m_stateStack.last(); |
444 | } |
445 | |
446 | Recorder::ContextState& Recorder::currentState() |
447 | { |
448 | ASSERT(m_stateStack.size()); |
449 | return m_stateStack.last(); |
450 | } |
451 | |
452 | const AffineTransform& Recorder::ctm() const |
453 | { |
454 | return currentState().ctm; |
455 | } |
456 | |
457 | const FloatRect& Recorder::clipBounds() const |
458 | { |
459 | return currentState().clipBounds; |
460 | } |
461 | |
462 | void Recorder::ContextState::translate(float x, float y) |
463 | { |
464 | ctm.translate(x, y); |
465 | clipBounds.move(-x, -y); |
466 | } |
467 | |
468 | void Recorder::ContextState::rotate(float angleInRadians) |
469 | { |
470 | double angleInDegrees = rad2deg(static_cast<double>(angleInRadians)); |
471 | ctm.rotate(angleInDegrees); |
472 | |
473 | AffineTransform rotation; |
474 | rotation.rotate(angleInDegrees); |
475 | |
476 | if (Optional<AffineTransform> inverse = rotation.inverse()) |
477 | clipBounds = inverse.value().mapRect(clipBounds); |
478 | } |
479 | |
480 | void Recorder::ContextState::scale(const FloatSize& size) |
481 | { |
482 | ctm.scale(size); |
483 | clipBounds.scale(1 / size.width(), 1 / size.height()); |
484 | } |
485 | |
486 | void Recorder::ContextState::concatCTM(const AffineTransform& matrix) |
487 | { |
488 | ctm *= matrix; |
489 | |
490 | if (Optional<AffineTransform> inverse = matrix.inverse()) |
491 | clipBounds = inverse.value().mapRect(clipBounds); |
492 | } |
493 | |
494 | } // namespace DisplayList |
495 | } // namespace WebCore |
496 | |