[iOS] Work around bug 136593 by disabling the PDFDocumentImage live resize optimizati...
[WebKit-https.git] / Source / WebCore / platform / graphics / cg / PDFDocumentImage.cpp
1 /*
2  * Copyright (C) 2004, 2005, 2006, 2013 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 #define _USE_MATH_DEFINES 1
27 #include "config.h"
28 #include "PDFDocumentImage.h"
29
30 #if USE(CG)
31
32 #if PLATFORM(IOS)
33 #import <CoreGraphics/CGContextPrivate.h>
34 #import <CoreGraphics/CGContextGState.h>
35 #import <CoreGraphics/CoreGraphics.h>
36 #import <ImageIO/ImageIO.h>
37 #endif
38
39 #include "GraphicsContext.h"
40 #include "ImageBuffer.h"
41 #include "ImageObserver.h"
42 #include "IntRect.h"
43 #include "Length.h"
44 #include "SharedBuffer.h"
45 #include <CoreGraphics/CGContext.h>
46 #include <CoreGraphics/CGPDFDocument.h>
47 #include <wtf/MathExtras.h>
48 #include <wtf/RetainPtr.h>
49
50 #if !PLATFORM(COCOA)
51 #include "ImageSourceCG.h"
52 #endif
53
54 namespace WebCore {
55
56 PDFDocumentImage::PDFDocumentImage(ImageObserver* observer)
57     : Image(observer)
58     , m_cachedBytes(0)
59     , m_rotationDegrees(0)
60     , m_hasPage(false)
61 {
62 }
63
64 PDFDocumentImage::~PDFDocumentImage()
65 {
66 }
67
68 String PDFDocumentImage::filenameExtension() const
69 {
70     return "pdf";
71 }
72
73 FloatSize PDFDocumentImage::size() const
74 {
75     FloatSize expandedCropBoxSize = FloatSize(expandedIntSize(m_cropBox.size()));
76
77     if (m_rotationDegrees == 90 || m_rotationDegrees == 270)
78         return expandedCropBoxSize.transposedSize();
79     return expandedCropBoxSize;
80 }
81
82 void PDFDocumentImage::computeIntrinsicDimensions(Length& intrinsicWidth, Length& intrinsicHeight, FloatSize& intrinsicRatio)
83 {
84     // FIXME: If we want size negotiation with PDF documents as-image, this is the place to implement it (https://bugs.webkit.org/show_bug.cgi?id=12095).
85     Image::computeIntrinsicDimensions(intrinsicWidth, intrinsicHeight, intrinsicRatio);
86     intrinsicRatio = FloatSize();
87 }
88
89 bool PDFDocumentImage::dataChanged(bool allDataReceived)
90 {
91     ASSERT(!m_document);
92     if (allDataReceived && !m_document) {
93         createPDFDocument();
94
95         if (pageCount()) {
96             m_hasPage = true;
97             computeBoundsForCurrentPage();
98         }
99     }
100     return m_document; // Return true if size is available.
101 }
102
103 bool PDFDocumentImage::cacheParametersMatch(GraphicsContext* context, const FloatRect& dstRect, const FloatRect& srcRect) const
104 {
105     if (dstRect.size() != m_cachedDestinationSize)
106         return false;
107
108     if (srcRect != m_cachedSourceRect)
109         return false;
110
111     AffineTransform::DecomposedType decomposedTransform;
112     context->getCTM(GraphicsContext::DefinitelyIncludeDeviceScale).decompose(decomposedTransform);
113
114     AffineTransform::DecomposedType cachedDecomposedTransform;
115     m_cachedTransform.decompose(cachedDecomposedTransform);
116     if (decomposedTransform.scaleX != cachedDecomposedTransform.scaleX || decomposedTransform.scaleY != cachedDecomposedTransform.scaleY)
117         return false;
118
119     return true;
120 }
121
122 static void transformContextForPainting(GraphicsContext* context, const FloatRect& dstRect, const FloatRect& srcRect)
123 {
124     float hScale = dstRect.width() / srcRect.width();
125     float vScale = dstRect.height() / srcRect.height();
126
127     if (hScale != vScale) {
128         float minimumScale = std::max((dstRect.width() - 0.5) / srcRect.width(), (dstRect.height() - 0.5) / srcRect.height());
129         float maximumScale = std::min((dstRect.width() + 0.5) / srcRect.width(), (dstRect.height() + 0.5) / srcRect.height());
130
131         // If the difference between the two scales is due to integer rounding of image sizes,
132         // use the smaller of the two original scales to ensure that the image fits inside the
133         // space originally allocated for it.
134         if (minimumScale <= maximumScale) {
135             hScale = std::min(hScale, vScale);
136             vScale = hScale;
137         }
138     }
139
140     context->translate(srcRect.x() * hScale, srcRect.y() * vScale);
141     context->scale(FloatSize(hScale, -vScale));
142     context->translate(0, -srcRect.height());
143 }
144
145 void PDFDocumentImage::updateCachedImageIfNeeded(GraphicsContext* context, const FloatRect& dstRect, const FloatRect& srcRect)
146 {
147 #if PLATFORM(IOS)
148     // On iOS, some clients use low-quality image interpolation always, which throws off this optimization,
149     // as we never get the subsequent high-quality paint. Since live resize is rare on iOS, disable the optimization.
150     // FIXME (136593): It's also possible to do the wrong thing here if CSS specifies low-quality interpolation via the "image-rendering"
151     // property, on all platforms. We should only do this optimization if we're actually in a ImageQualityController live resize,
152     // and are guaranteed to do a high-quality paint later.
153     bool repaintIfNecessary = true;
154 #else
155     // If we have an existing image, reuse it if we're doing a low-quality paint, even if cache parameters don't match;
156     // we'll rerender when we do the subsequent high-quality paint.
157     InterpolationQuality interpolationQuality = context->imageInterpolationQuality();
158     bool repaintIfNecessary = interpolationQuality != InterpolationNone && interpolationQuality != InterpolationLow;
159 #endif
160
161     if (!m_cachedImageBuffer || (!cacheParametersMatch(context, dstRect, srcRect) && repaintIfNecessary)) {
162         m_cachedImageBuffer = context->createCompatibleBuffer(FloatRect(enclosingIntRect(dstRect)).size());
163         if (!m_cachedImageBuffer)
164             return;
165         GraphicsContext* bufferContext = m_cachedImageBuffer->context();
166         if (!bufferContext) {
167             m_cachedImageBuffer = nullptr;
168             return;
169         }
170
171         transformContextForPainting(bufferContext, dstRect, srcRect);
172         drawPDFPage(bufferContext);
173
174         m_cachedTransform = context->getCTM(GraphicsContext::DefinitelyIncludeDeviceScale);
175         m_cachedDestinationSize = dstRect.size();
176         m_cachedSourceRect = srcRect;
177
178         IntSize internalSize = m_cachedImageBuffer->internalSize();
179         size_t oldCachedBytes = m_cachedBytes;
180         m_cachedBytes = internalSize.width() * internalSize.height() * 4;
181
182         if (imageObserver())
183             imageObserver()->decodedSizeChanged(this, safeCast<int>(m_cachedBytes) - safeCast<int>(oldCachedBytes));
184     }
185 }
186
187 void PDFDocumentImage::draw(GraphicsContext* context, const FloatRect& dstRect, const FloatRect& srcRect, ColorSpace, CompositeOperator op, BlendMode, ImageOrientationDescription)
188 {
189     if (!m_document || !m_hasPage)
190         return;
191
192     updateCachedImageIfNeeded(context, dstRect, srcRect);
193
194     {
195         GraphicsContextStateSaver stateSaver(*context);
196         context->setCompositeOperation(op);
197
198         if (m_cachedImageBuffer)
199             context->drawImageBuffer(m_cachedImageBuffer.get(), ColorSpaceDeviceRGB, dstRect);
200         else {
201             transformContextForPainting(context, dstRect, srcRect);
202             drawPDFPage(context);
203         }
204     }
205
206     if (imageObserver())
207         imageObserver()->didDraw(this);
208 }
209
210 void PDFDocumentImage::destroyDecodedData(bool)
211 {
212     m_cachedImageBuffer = nullptr;
213
214     if (imageObserver())
215         imageObserver()->decodedSizeChanged(this, -safeCast<int>(m_cachedBytes));
216
217     m_cachedBytes = 0;
218 }
219
220 #if !USE(PDFKIT_FOR_PDFDOCUMENTIMAGE)
221
222 void PDFDocumentImage::createPDFDocument()
223 {
224     RetainPtr<CGDataProviderRef> dataProvider = adoptCF(CGDataProviderCreateWithCFData(data()->createCFData().get()));
225     m_document = adoptCF(CGPDFDocumentCreateWithProvider(dataProvider.get()));
226 }
227
228 void PDFDocumentImage::computeBoundsForCurrentPage()
229 {
230     CGPDFPageRef cgPage = CGPDFDocumentGetPage(m_document.get(), 1);
231     CGRect mediaBox = CGPDFPageGetBoxRect(cgPage, kCGPDFMediaBox);
232
233     // Get crop box (not always there). If not, use media box.
234     CGRect r = CGPDFPageGetBoxRect(cgPage, kCGPDFCropBox);
235     if (!CGRectIsEmpty(r))
236         m_cropBox = r;
237     else
238         m_cropBox = mediaBox;
239
240     m_rotationDegrees = CGPDFPageGetRotationAngle(cgPage);
241 }
242
243 unsigned PDFDocumentImage::pageCount() const
244 {
245     return CGPDFDocumentGetNumberOfPages(m_document.get());
246 }
247
248 static void applyRotationForPainting(GraphicsContext* context, FloatSize size, int rotationDegrees)
249 {
250     if (rotationDegrees == 90)
251         context->translate(0, size.height());
252     else if (rotationDegrees == 180)
253         context->translate(size.width(), size.height());
254     else if (rotationDegrees == 270)
255         context->translate(size.width(), 0);
256
257     context->rotate(-deg2rad(static_cast<float>(rotationDegrees)));
258 }
259
260 void PDFDocumentImage::drawPDFPage(GraphicsContext* context)
261 {
262     applyRotationForPainting(context, size(), m_rotationDegrees);
263
264     context->translate(-m_cropBox.x(), -m_cropBox.y());
265
266     // CGPDF pages are indexed from 1.
267     CGContextDrawPDFPage(context->platformContext(), CGPDFDocumentGetPage(m_document.get(), 1));
268 }
269
270 #endif // !USE(PDFKIT_FOR_PDFDOCUMENTIMAGE)
271
272 }
273
274 #endif // USE(CG)