blob: 00af49272da75ade5b0886cabcb0d793160107de [file] [log] [blame]
/*
* Copyright (C) 2004, 2005, 2006, 2013 Apple Computer, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#define _USE_MATH_DEFINES 1
#include "config.h"
#include "PDFDocumentImage.h"
#if USE(CG)
#include "GraphicsContext.h"
#include "ImageBuffer.h"
#include "ImageObserver.h"
#include "Length.h"
#include "SharedBuffer.h"
#include <CoreGraphics/CGContext.h>
#include <CoreGraphics/CGPDFDocument.h>
#include <wtf/MathExtras.h>
#include <wtf/RetainPtr.h>
#if !PLATFORM(MAC)
#include "ImageSourceCG.h"
#endif
namespace WebCore {
PDFDocumentImage::PDFDocumentImage(ImageObserver* observer)
: Image(observer)
, m_cachedBytes(0)
, m_rotation(0.0f)
, m_hasPage(false)
{
}
PDFDocumentImage::~PDFDocumentImage()
{
}
String PDFDocumentImage::filenameExtension() const
{
return "pdf";
}
IntSize PDFDocumentImage::size() const
{
const float sina = sinf(-m_rotation);
const float cosa = cosf(-m_rotation);
const float width = m_cropBox.size().width();
const float height = m_cropBox.size().height();
const float rotWidth = fabsf(width * cosa - height * sina);
const float rotHeight = fabsf(width * sina + height * cosa);
return expandedIntSize(FloatSize(rotWidth, rotHeight));
}
void PDFDocumentImage::computeIntrinsicDimensions(Length& intrinsicWidth, Length& intrinsicHeight, FloatSize& intrinsicRatio)
{
// 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).
Image::computeIntrinsicDimensions(intrinsicWidth, intrinsicHeight, intrinsicRatio);
intrinsicRatio = FloatSize();
}
bool PDFDocumentImage::dataChanged(bool allDataReceived)
{
ASSERT(!m_document);
if (allDataReceived && !m_document) {
createPDFDocument();
if (pageCount()) {
m_hasPage = true;
computeBoundsForCurrentPage();
}
}
return m_document; // Return true if size is available.
}
void PDFDocumentImage::applyRotationForPainting(GraphicsContext* context) const
{
// Rotate the crop box and calculate bounding box.
float sina = sinf(-m_rotation);
float cosa = cosf(-m_rotation);
float width = m_cropBox.width();
float height = m_cropBox.height();
// Calculate rotated x and y edges of the crop box. If they're negative, it means part of the image has
// been rotated outside of the bounds and we need to shift over the image so it lies inside the bounds again.
CGPoint rx = CGPointMake(width * cosa, width * sina);
CGPoint ry = CGPointMake(-height * sina, height * cosa);
// Adjust so we are at the crop box origin.
const CGFloat zero = 0;
context->translate(floorf(-std::min(zero, std::min(rx.x, ry.x))), floorf(-std::min(zero, std::min(rx.y, ry.y))));
context->rotate(-m_rotation);
}
bool PDFDocumentImage::cacheParametersMatch(GraphicsContext* context, const FloatRect& dstRect, const FloatRect& srcRect) const
{
if (dstRect.size() != m_cachedDestinationSize)
return false;
if (srcRect != m_cachedSourceRect)
return false;
AffineTransform::DecomposedType decomposedTransform;
context->getCTM(GraphicsContext::DefinitelyIncludeDeviceScale).decompose(decomposedTransform);
AffineTransform::DecomposedType cachedDecomposedTransform;
m_cachedTransform.decompose(cachedDecomposedTransform);
if (decomposedTransform.scaleX != cachedDecomposedTransform.scaleX || decomposedTransform.scaleY != cachedDecomposedTransform.scaleY)
return false;
return true;
}
static void transformContextForPainting(GraphicsContext* context, const FloatRect& dstRect, const FloatRect& srcRect)
{
float hScale = dstRect.width() / srcRect.width();
float vScale = dstRect.height() / srcRect.height();
context->translate(srcRect.x() * hScale, srcRect.y() * vScale);
context->scale(FloatSize(hScale, -vScale));
context->translate(0, -srcRect.height());
}
void PDFDocumentImage::updateCachedImageIfNeeded(GraphicsContext* context, const FloatRect& dstRect, const FloatRect& srcRect)
{
// If we have an existing image, reuse it if we're doing a low-quality paint, even if cache parameters don't match;
// we'll rerender when we do the subsequent high-quality paint.
InterpolationQuality interpolationQuality = context->imageInterpolationQuality();
bool useLowQualityInterpolation = interpolationQuality == InterpolationNone || interpolationQuality == InterpolationLow;
if (!m_cachedImageBuffer || (!cacheParametersMatch(context, dstRect, srcRect) && !useLowQualityInterpolation)) {
m_cachedImageBuffer = context->createCompatibleBuffer(enclosingIntRect(dstRect).size());
if (!m_cachedImageBuffer)
return;
GraphicsContext* bufferContext = m_cachedImageBuffer->context();
if (!bufferContext) {
m_cachedImageBuffer = nullptr;
return;
}
transformContextForPainting(bufferContext, dstRect, srcRect);
drawPDFPage(bufferContext);
m_cachedTransform = context->getCTM(GraphicsContext::DefinitelyIncludeDeviceScale);
m_cachedDestinationSize = dstRect.size();
m_cachedSourceRect = srcRect;
IntSize internalSize = m_cachedImageBuffer->internalSize();
size_t oldCachedBytes = m_cachedBytes;
m_cachedBytes = internalSize.width() * internalSize.height() * 4;
if (imageObserver())
imageObserver()->decodedSizeChanged(this, safeCast<int>(m_cachedBytes) - safeCast<int>(oldCachedBytes));
}
}
void PDFDocumentImage::draw(GraphicsContext* context, const FloatRect& dstRect, const FloatRect& srcRect, ColorSpace, CompositeOperator op, BlendMode, ImageOrientationDescription)
{
if (!m_document || !m_hasPage)
return;
updateCachedImageIfNeeded(context, dstRect, srcRect);
{
GraphicsContextStateSaver stateSaver(*context);
context->setCompositeOperation(op);
if (m_cachedImageBuffer)
context->drawImageBuffer(m_cachedImageBuffer.get(), ColorSpaceDeviceRGB, dstRect);
else {
transformContextForPainting(context, dstRect, srcRect);
drawPDFPage(context);
}
}
if (imageObserver())
imageObserver()->didDraw(this);
}
void PDFDocumentImage::destroyDecodedData(bool)
{
m_cachedImageBuffer = nullptr;
if (imageObserver())
imageObserver()->decodedSizeChanged(this, -safeCast<int>(m_cachedBytes));
m_cachedBytes = 0;
}
unsigned PDFDocumentImage::decodedSize() const
{
// FIXME: PDFDocumentImage is underreporting decoded sizes because this
// only includes the cached image and nothing else.
return m_cachedBytes;
}
#if !USE(PDFKIT_FOR_PDFDOCUMENTIMAGE)
void PDFDocumentImage::createPDFDocument()
{
RetainPtr<CGDataProviderRef> dataProvider = adoptCF(CGDataProviderCreateWithCFData(data()->createCFData().get()));
m_document = CGPDFDocumentCreateWithProvider(dataProvider.get());
}
void PDFDocumentImage::computeBoundsForCurrentPage()
{
CGPDFPageRef cgPage = CGPDFDocumentGetPage(m_document.get(), 1);
CGRect mediaBox = CGPDFPageGetBoxRect(cgPage, kCGPDFMediaBox);
// Get crop box (not always there). If not, use media box.
CGRect r = CGPDFPageGetBoxRect(cgPage, kCGPDFCropBox);
if (!CGRectIsEmpty(r))
m_cropBox = r;
else
m_cropBox = mediaBox;
m_rotation = deg2rad(static_cast<float>(CGPDFPageGetRotationAngle(cgPage)));
}
unsigned PDFDocumentImage::pageCount() const
{
return CGPDFDocumentGetNumberOfPages(m_document.get());
}
void PDFDocumentImage::drawPDFPage(GraphicsContext* context)
{
applyRotationForPainting(context);
context->translate(-m_cropBox.x(), -m_cropBox.y());
// CGPDF pages are indexed from 1.
CGContextDrawPDFPage(context->platformContext(), CGPDFDocumentGetPage(m_document.get(), 1));
}
#endif // !USE(PDFKIT_FOR_PDFDOCUMENTIMAGE)
}
#endif // USE(CG)