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
36namespace WebCore {
37namespace DisplayList {
38
39Recorder::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
47Recorder::~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
53void 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
72void Recorder::updateState(const GraphicsContextState& state, GraphicsContextState::StateChangeFlags flags)
73{
74 currentState().stateChange.accumulate(state, flags);
75}
76
77void Recorder::clearShadow()
78{
79 appendItem(ClearShadow::create());
80}
81
82void Recorder::setLineCap(LineCap lineCap)
83{
84 appendItem(SetLineCap::create(lineCap));
85}
86
87void Recorder::setLineDash(const DashArray& dashArray, float dashOffset)
88{
89 appendItem(SetLineDash::create(dashArray, dashOffset));
90}
91
92void Recorder::setLineJoin(LineJoin lineJoin)
93{
94 appendItem(SetLineJoin::create(lineJoin));
95}
96
97void Recorder::setMiterLimit(float miterLimit)
98{
99 appendItem(SetMiterLimit::create(miterLimit));
100}
101
102void 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
108ImageDrawResult 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
115ImageDrawResult 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
122ImageDrawResult 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)
130void 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
137void 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
143void Recorder::save()
144{
145 appendItem(Save::create());
146 m_stateStack.append(m_stateStack.last().cloneForSave(m_displayList.itemCount() - 1));
147}
148
149void 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
175void Recorder::translate(float x, float y)
176{
177 currentState().translate(x, y);
178 appendItem(Translate::create(x, y));
179}
180
181void Recorder::rotate(float angleInRadians)
182{
183 currentState().rotate(angleInRadians);
184 appendItem(Rotate::create(angleInRadians));
185}
186
187void Recorder::scale(const FloatSize& size)
188{
189 currentState().scale(size);
190 appendItem(Scale::create(size));
191}
192
193void Recorder::concatCTM(const AffineTransform& transform)
194{
195 currentState().concatCTM(transform);
196 appendItem(ConcatenateCTM::create(transform));
197}
198
199void Recorder::setCTM(const AffineTransform&)
200{
201 WTFLogAlways("GraphicsContext::setCTM() is not compatible with DisplayList::Recorder.");
202}
203
204AffineTransform Recorder::getCTM(GraphicsContext::IncludeDeviceScale)
205{
206 WTFLogAlways("GraphicsContext::getCTM() is not yet compatible with DisplayList::Recorder.");
207 return { };
208}
209
210void Recorder::beginTransparencyLayer(float opacity)
211{
212 DrawingItem& newItem = downcast<DrawingItem>(appendItem(BeginTransparencyLayer::create(opacity)));
213 updateItemExtent(newItem);
214}
215
216void Recorder::endTransparencyLayer()
217{
218 appendItem(EndTransparencyLayer::create());
219}
220
221void Recorder::drawRect(const FloatRect& rect, float borderThickness)
222{
223 DrawingItem& newItem = downcast<DrawingItem>(appendItem(DrawRect::create(rect, borderThickness)));
224 updateItemExtent(newItem);
225}
226
227void Recorder::drawLine(const FloatPoint& point1, const FloatPoint& point2)
228{
229 DrawingItem& newItem = downcast<DrawingItem>(appendItem(DrawLine::create(point1, point2)));
230 updateItemExtent(newItem);
231}
232
233void 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
239void Recorder::drawDotsForDocumentMarker(const FloatRect& rect, DocumentMarkerLineStyle style)
240{
241 DrawingItem& newItem = downcast<DrawingItem>(appendItem(DrawDotsForDocumentMarker::create(rect, style)));
242 updateItemExtent(newItem);
243}
244
245void Recorder::drawEllipse(const FloatRect& rect)
246{
247 DrawingItem& newItem = downcast<DrawingItem>(appendItem(DrawEllipse::create(rect)));
248 updateItemExtent(newItem);
249}
250
251void Recorder::drawPath(const Path& path)
252{
253 DrawingItem& newItem = downcast<DrawingItem>(appendItem(DrawPath::create(path)));
254 updateItemExtent(newItem);
255}
256
257void 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
263void 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
269void Recorder::fillRect(const FloatRect& rect)
270{
271 DrawingItem& newItem = downcast<DrawingItem>(appendItem(FillRect::create(rect)));
272 updateItemExtent(newItem);
273}
274
275void Recorder::fillRect(const FloatRect& rect, const Color& color)
276{
277 DrawingItem& newItem = downcast<DrawingItem>(appendItem(FillRectWithColor::create(rect, color)));
278 updateItemExtent(newItem);
279}
280
281void Recorder::fillRect(const FloatRect& rect, Gradient& gradient)
282{
283 DrawingItem& newItem = downcast<DrawingItem>(appendItem(FillRectWithGradient::create(rect, gradient)));
284 updateItemExtent(newItem);
285}
286
287void 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
293void 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
299void 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
305void Recorder::fillPath(const Path& path)
306{
307 DrawingItem& newItem = downcast<DrawingItem>(appendItem(FillPath::create(path)));
308 updateItemExtent(newItem);
309}
310
311void Recorder::fillEllipse(const FloatRect& rect)
312{
313 DrawingItem& newItem = downcast<DrawingItem>(appendItem(FillEllipse::create(rect)));
314 updateItemExtent(newItem);
315}
316
317void Recorder::strokeRect(const FloatRect& rect, float lineWidth)
318{
319 DrawingItem& newItem = downcast<DrawingItem>(appendItem(StrokeRect::create(rect, lineWidth)));
320 updateItemExtent(newItem);
321}
322
323void Recorder::strokePath(const Path& path)
324{
325 DrawingItem& newItem = downcast<DrawingItem>(appendItem(StrokePath::create(path)));
326 updateItemExtent(newItem);
327}
328
329void Recorder::strokeEllipse(const FloatRect& rect)
330{
331 DrawingItem& newItem = downcast<DrawingItem>(appendItem(StrokeEllipse::create(rect)));
332 updateItemExtent(newItem);
333}
334
335void Recorder::clearRect(const FloatRect& rect)
336{
337 DrawingItem& newItem = downcast<DrawingItem>(appendItem(ClearRect::create(rect)));
338 updateItemExtent(newItem);
339}
340
341#if USE(CG)
342void Recorder::applyStrokePattern()
343{
344 appendItem(ApplyStrokePattern::create());
345}
346
347void Recorder::applyFillPattern()
348{
349 appendItem(ApplyFillPattern::create());
350}
351#endif
352
353void Recorder::clip(const FloatRect& rect)
354{
355 currentState().clipBounds.intersect(rect);
356 appendItem(Clip::create(rect));
357}
358
359void Recorder::clipOut(const FloatRect& rect)
360{
361 appendItem(ClipOut::create(rect));
362}
363
364void Recorder::clipOut(const Path& path)
365{
366 appendItem(ClipOutToPath::create(path));
367}
368
369void Recorder::clipPath(const Path& path, WindRule windRule)
370{
371 currentState().clipBounds.intersect(path.fastBoundingRect());
372 appendItem(ClipPath::create(path, windRule));
373}
374
375IntRect Recorder::clipBounds()
376{
377 WTFLogAlways("Getting the clip bounds not yet supported with DisplayList::Recorder.");
378 return IntRect(-2048, -2048, 4096, 4096);
379}
380
381void Recorder::clipToImageBuffer(ImageBuffer&, const FloatRect&)
382{
383 WTFLogAlways("GraphicsContext::clipToImageBuffer is not compatible with DisplayList::Recorder.");
384}
385
386void 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
393FloatRect Recorder::roundToDevicePixels(const FloatRect& rect, GraphicsContext::RoundingMode)
394{
395 WTFLogAlways("GraphicsContext::roundToDevicePixels() is not yet compatible with DisplayList::Recorder.");
396 return rect;
397}
398
399Item& Recorder::appendItem(Ref<Item>&& item)
400{
401 willAppendItem(item.get());
402 return m_displayList.append(WTFMove(item));
403}
404
405void 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
412static 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
421FloatRect 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
440const Recorder::ContextState& Recorder::currentState() const
441{
442 ASSERT(m_stateStack.size());
443 return m_stateStack.last();
444}
445
446Recorder::ContextState& Recorder::currentState()
447{
448 ASSERT(m_stateStack.size());
449 return m_stateStack.last();
450}
451
452const AffineTransform& Recorder::ctm() const
453{
454 return currentState().ctm;
455}
456
457const FloatRect& Recorder::clipBounds() const
458{
459 return currentState().clipBounds;
460}
461
462void Recorder::ContextState::translate(float x, float y)
463{
464 ctm.translate(x, y);
465 clipBounds.move(-x, -y);
466}
467
468void 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
480void Recorder::ContextState::scale(const FloatSize& size)
481{
482 ctm.scale(size);
483 clipBounds.scale(1 / size.width(), 1 / size.height());
484}
485
486void 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