PDFDocumentImage.cpp [plain text]
#include "config.h"
#include "PDFDocumentImage.h"
#if USE(CG)
#if PLATFORM(IOS_FAMILY)
#include <CoreGraphics/CoreGraphics.h>
#include <ImageIO/ImageIO.h>
#endif
#include "GraphicsContext.h"
#include "ImageBuffer.h"
#include "ImageObserver.h"
#include "IntRect.h"
#include "Length.h"
#include "NotImplemented.h"
#include "SharedBuffer.h"
#include <CoreGraphics/CGContext.h>
#include <CoreGraphics/CGPDFDocument.h>
#include <pal/spi/cg/CoreGraphicsSPI.h>
#include <wtf/MathExtras.h>
#include <wtf/RAMSize.h>
#include <wtf/RetainPtr.h>
#include <wtf/StdLibExtras.h>
#include <wtf/text/TextStream.h>
#if !PLATFORM(COCOA)
#include "ImageSourceCG.h"
#endif
namespace WebCore {
PDFDocumentImage::PDFDocumentImage(ImageObserver* observer)
: Image(observer)
{
}
PDFDocumentImage::~PDFDocumentImage() = default;
String PDFDocumentImage::filenameExtension() const
{
return "pdf";
}
FloatSize PDFDocumentImage::size(ImageOrientation) const
{
FloatSize expandedCropBoxSize = FloatSize(expandedIntSize(m_cropBox.size()));
if (m_rotationDegrees == 90 || m_rotationDegrees == 270)
return expandedCropBoxSize.transposedSize();
return expandedCropBoxSize;
}
void PDFDocumentImage::computeIntrinsicDimensions(Length& intrinsicWidth, Length& intrinsicHeight, FloatSize& intrinsicRatio)
{
Image::computeIntrinsicDimensions(intrinsicWidth, intrinsicHeight, intrinsicRatio);
intrinsicRatio = FloatSize();
}
EncodedDataStatus PDFDocumentImage::dataChanged(bool allDataReceived)
{
ASSERT(!m_document);
if (allDataReceived && !m_document) {
createPDFDocument();
if (pageCount()) {
m_hasPage = true;
computeBoundsForCurrentPage();
}
}
return m_document ? EncodedDataStatus::Complete : EncodedDataStatus::Unknown;
}
void PDFDocumentImage::setPdfImageCachingPolicy(PDFImageCachingPolicy pdfImageCachingPolicy)
{
if (m_pdfImageCachingPolicy == pdfImageCachingPolicy)
return;
m_pdfImageCachingPolicy = pdfImageCachingPolicy;
destroyDecodedData();
}
bool PDFDocumentImage::cacheParametersMatch(GraphicsContext& context, const FloatRect& dstRect, const FloatRect& srcRect) const
{
if (srcRect != m_cachedSourceRect)
return false;
if (dstRect.size() != m_cachedDestinationRect.size())
return false;
FloatRect movedCachedImageRect = m_cachedImageRect;
movedCachedImageRect.move(FloatSize(dstRect.location() - m_cachedDestinationRect.location()));
FloatRect dirtyRect = intersection(context.clipBounds(), dstRect);
if (!movedCachedImageRect.contains(dirtyRect))
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();
if (hScale != vScale) {
float minimumScale = std::max((dstRect.width() - 0.5) / srcRect.width(), (dstRect.height() - 0.5) / srcRect.height());
float maximumScale = std::min((dstRect.width() + 0.5) / srcRect.width(), (dstRect.height() + 0.5) / srcRect.height());
if (minimumScale <= maximumScale) {
hScale = std::min(hScale, vScale);
vScale = hScale;
}
}
context.translate(dstRect.location() - srcRect.location());
context.scale(FloatSize(hScale, -vScale));
context.translate(0, -srcRect.height());
}
static const size_t s_maxCachedImageSide = 4 * 1024;
static const size_t s_maxCachedImageArea = s_maxCachedImageSide * s_maxCachedImageSide;
static const size_t s_maxDecodedDataSize = s_maxCachedImageArea * 4;
static size_t s_allDecodedDataSize = 0;
static FloatRect cachedImageRect(GraphicsContext& context, const FloatRect& dstRect)
{
FloatRect dirtyRect = context.clipBounds();
FloatSize maxSize = s_maxCachedImageSide / context.scaleFactor();
FloatPoint minLocation = FloatPoint(dirtyRect.center() - maxSize / 2);
return intersection(unionRect(dirtyRect, FloatRect(minLocation, maxSize)), dstRect);
}
void PDFDocumentImage::decodedSizeChanged(size_t newCachedBytes)
{
if (!m_cachedBytes && !newCachedBytes)
return;
if (imageObserver())
imageObserver()->decodedSizeChanged(*this, -static_cast<long long>(m_cachedBytes) + newCachedBytes);
ASSERT(s_allDecodedDataSize >= m_cachedBytes);
s_allDecodedDataSize -= m_cachedBytes;
s_allDecodedDataSize += newCachedBytes;
m_cachedBytes = newCachedBytes;
}
void PDFDocumentImage::updateCachedImageIfNeeded(GraphicsContext& context, const FloatRect& dstRect, const FloatRect& srcRect)
{
bool forceUpdateCachedImage = m_pdfImageCachingPolicy == PDFImageCachingClipBoundsOnly || !m_cachedImageBuffer;
if (!forceUpdateCachedImage && cacheParametersMatch(context, dstRect, srcRect)) {
m_cachedImageRect.move(FloatSize(dstRect.location() - m_cachedDestinationRect.location()));
m_cachedDestinationRect = dstRect;
return;
}
switch (m_pdfImageCachingPolicy) {
case PDFImageCachingDisabled:
return;
case PDFImageCachingBelowMemoryLimit:
m_cachedImageRect = cachedImageRect(context, dstRect);
break;
case PDFImageCachingClipBoundsOnly:
m_cachedImageRect = intersection(context.clipBounds(), dstRect);
break;
case PDFImageCachingEnabled:
m_cachedImageRect = dstRect;
break;
}
FloatSize cachedImageSize = FloatRect(enclosingIntRect(m_cachedImageRect)).size();
if (m_pdfImageCachingPolicy == PDFImageCachingBelowMemoryLimit) {
IntSize scaledSize = ImageBuffer::compatibleBufferSize(cachedImageSize, context);
if (s_allDecodedDataSize + scaledSize.unclampedArea() * 4 - m_cachedBytes > s_maxDecodedDataSize) {
destroyDecodedData();
return;
}
}
m_cachedImageBuffer = ImageBuffer::createCompatibleBuffer(cachedImageSize, context);
if (!m_cachedImageBuffer) {
destroyDecodedData();
return;
}
auto& bufferContext = m_cachedImageBuffer->context();
transformContextForPainting(bufferContext, dstRect, FloatRect(m_cachedImageRect.location(), srcRect.size()));
drawPDFPage(bufferContext);
m_cachedTransform = context.getCTM(GraphicsContext::DefinitelyIncludeDeviceScale);
m_cachedDestinationRect = dstRect;
m_cachedSourceRect = srcRect;
++m_cachingCountForTesting;
IntSize internalSize = m_cachedImageBuffer->internalSize();
decodedSizeChanged(internalSize.unclampedArea() * 4);
}
ImageDrawResult PDFDocumentImage::draw(GraphicsContext& context, const FloatRect& dstRect, const FloatRect& srcRect, const ImagePaintingOptions& options)
{
if (!m_document || !m_hasPage)
return ImageDrawResult::DidNothing;
updateCachedImageIfNeeded(context, dstRect, srcRect);
{
GraphicsContextStateSaver stateSaver(context);
context.setCompositeOperation(options.compositeOperator());
if (m_cachedImageBuffer) {
context.drawImageBuffer(*m_cachedImageBuffer, m_cachedImageRect);
}
else {
transformContextForPainting(context, dstRect, srcRect);
drawPDFPage(context);
}
}
if (imageObserver())
imageObserver()->didDraw(*this);
return ImageDrawResult::DidDraw;
}
void PDFDocumentImage::destroyDecodedData(bool)
{
m_cachedImageBuffer = nullptr;
m_cachedImageRect = FloatRect();
decodedSizeChanged(0);
}
#if !USE(PDFKIT_FOR_PDFDOCUMENTIMAGE)
void PDFDocumentImage::createPDFDocument()
{
RetainPtr<CGDataProviderRef> dataProvider = adoptCF(CGDataProviderCreateWithCFData(data()->createCFData().get()));
m_document = adoptCF(CGPDFDocumentCreateWithProvider(dataProvider.get()));
}
void PDFDocumentImage::computeBoundsForCurrentPage()
{
ASSERT(pageCount() > 0);
CGPDFPageRef cgPage = CGPDFDocumentGetPage(m_document.get(), 1);
CGRect mediaBox = CGPDFPageGetBoxRect(cgPage, kCGPDFMediaBox);
CGRect r = CGPDFPageGetBoxRect(cgPage, kCGPDFCropBox);
if (!CGRectIsEmpty(r))
m_cropBox = r;
else
m_cropBox = mediaBox;
m_rotationDegrees = CGPDFPageGetRotationAngle(cgPage);
}
unsigned PDFDocumentImage::pageCount() const
{
return CGPDFDocumentGetNumberOfPages(m_document.get());
}
static void applyRotationForPainting(GraphicsContext& context, FloatSize size, int rotationDegrees)
{
if (rotationDegrees == 90)
context.translate(0, size.height());
else if (rotationDegrees == 180)
context.translate(size);
else if (rotationDegrees == 270)
context.translate(size.width(), 0);
context.rotate(-deg2rad(static_cast<float>(rotationDegrees)));
}
void PDFDocumentImage::drawPDFPage(GraphicsContext& context)
{
applyRotationForPainting(context, size(), m_rotationDegrees);
context.translate(-m_cropBox.location());
#if USE(DIRECT2D)
notImplemented();
#else
#if PLATFORM(COCOA)
CGContextDrawPDFPageWithAnnotations(context.platformContext(), CGPDFDocumentGetPage(m_document.get(), 1), nullptr);
#else
CGContextDrawPDFPage(context.platformContext(), CGPDFDocumentGetPage(m_document.get(), 1));
#endif
#endif
}
#endif // !USE(PDFKIT_FOR_PDFDOCUMENTIMAGE)
#if PLATFORM(MAC)
RetainPtr<CFMutableDataRef> PDFDocumentImage::convertPostScriptDataToPDF(RetainPtr<CFDataRef>&& postScriptData)
{
CGPSConverterCallbacks callbacks = { };
auto converter = adoptCF(CGPSConverterCreate(0, &callbacks, 0));
auto provider = adoptCF(CGDataProviderCreateWithCFData(postScriptData.get()));
auto pdfData = adoptCF(CFDataCreateMutable(kCFAllocatorDefault, 0));
auto consumer = adoptCF(CGDataConsumerCreateWithCFData(pdfData.get()));
CGPSConverterConvert(converter.get(), provider.get(), consumer.get(), 0);
return pdfData;
}
#endif
void PDFDocumentImage::dump(TextStream& ts) const
{
Image::dump(ts);
ts.dumpProperty("page-count", pageCount());
ts.dumpProperty("crop-box", m_cropBox);
if (m_rotationDegrees)
ts.dumpProperty("rotation", m_rotationDegrees);
}
}
#endif // USE(CG)