/*
|
* Copyright 2011 Google Inc.
|
*
|
* Use of this source code is governed by a BSD-style license that can be
|
* found in the LICENSE file.
|
*/
|
|
#ifndef SkPDFDevice_DEFINED
|
#define SkPDFDevice_DEFINED
|
|
#include "SkBitmap.h"
|
#include "SkCanvas.h"
|
#include "SkClipStack.h"
|
#include "SkClipStackDevice.h"
|
#include "SkData.h"
|
#include "SkKeyedImage.h"
|
#include "SkPDFTypes.h"
|
#include "SkPaint.h"
|
#include "SkRect.h"
|
#include "SkRefCnt.h"
|
#include "SkStream.h"
|
#include "SkTHash.h"
|
#include "SkTextBlobPriv.h"
|
|
#include <vector>
|
|
class SkGlyphRunList;
|
class SkKeyedImage;
|
class SkPDFArray;
|
class SkPDFDevice;
|
class SkPDFDict;
|
class SkPDFDocument;
|
class SkPDFFont;
|
class SkPDFObject;
|
class SkPath;
|
class SkRRect;
|
struct SkPDFIndirectReference;
|
|
/**
|
* \class SkPDFDevice
|
*
|
* An SkPDFDevice is the drawing context for a page or layer of PDF
|
* content.
|
*/
|
class SkPDFDevice final : public SkClipStackDevice {
|
public:
|
/**
|
* @param pageSize Page size in point units.
|
* 1 point == 127/360 mm == 1/72 inch
|
* @param document A non-null pointer back to the
|
* PDFDocument object. The document is responsible for
|
* de-duplicating across pages (via the SkPDFDocument) and
|
* for early serializing of large immutable objects, such
|
* as images (via SkPDFDocument::serialize()).
|
* @param initialTransform Transform to be applied to the entire page.
|
*/
|
SkPDFDevice(SkISize pageSize, SkPDFDocument* document,
|
const SkMatrix& initialTransform = SkMatrix::I());
|
|
sk_sp<SkPDFDevice> makeCongruentDevice() {
|
return sk_make_sp<SkPDFDevice>(this->size(), fDocument);
|
}
|
|
~SkPDFDevice() override;
|
|
/**
|
* These are called inside the per-device-layer loop for each draw call.
|
* When these are called, we have already applied any saveLayer
|
* operations, and are handling any looping from the paint.
|
*/
|
void drawPaint(const SkPaint& paint) override;
|
void drawPoints(SkCanvas::PointMode mode,
|
size_t count, const SkPoint[],
|
const SkPaint& paint) override;
|
void drawRect(const SkRect& r, const SkPaint& paint) override;
|
void drawOval(const SkRect& oval, const SkPaint& paint) override;
|
void drawRRect(const SkRRect& rr, const SkPaint& paint) override;
|
void drawPath(const SkPath& origpath, const SkPaint& paint, bool pathIsMutable) override;
|
void drawBitmapRect(const SkBitmap& bitmap, const SkRect* src,
|
const SkRect& dst, const SkPaint&, SkCanvas::SrcRectConstraint) override;
|
void drawSprite(const SkBitmap& bitmap, int x, int y,
|
const SkPaint& paint) override;
|
|
void drawImageRect(const SkImage*,
|
const SkRect* src,
|
const SkRect& dst,
|
const SkPaint&,
|
SkCanvas::SrcRectConstraint) override;
|
void drawGlyphRunList(const SkGlyphRunList& glyphRunList) override;
|
void drawVertices(const SkVertices*, const SkVertices::Bone bones[], int boneCount, SkBlendMode,
|
const SkPaint&) override;
|
void drawDevice(SkBaseDevice*, int x, int y,
|
const SkPaint&) override;
|
|
// PDF specific methods.
|
|
/** Create the resource dictionary for this device. Destructive. */
|
std::unique_ptr<SkPDFDict> makeResourceDict();
|
|
/** return annotations (link to urls and destinations) or nulltpr */
|
std::unique_ptr<SkPDFArray> getAnnotations();
|
|
/** Add our named destinations to the supplied dictionary.
|
* @param dict Dictionary to add destinations to.
|
* @param page The PDF object representing the page for this device.
|
*/
|
void appendDestinations(SkPDFDict* dict, SkPDFIndirectReference page) const;
|
|
/** Returns a SkStream with the page contents.
|
*/
|
std::unique_ptr<SkStreamAsset> content();
|
|
SkISize size() const { return this->imageInfo().dimensions(); }
|
SkIRect bounds() const { return this->imageInfo().bounds(); }
|
|
// It is important to not confuse GraphicStateEntry with SkPDFGraphicState, the
|
// later being our representation of an object in the PDF file.
|
struct GraphicStateEntry {
|
SkMatrix fMatrix = SkMatrix::I();
|
uint32_t fClipStackGenID = SkClipStack::kWideOpenGenID;
|
SkColor4f fColor = {0, 0, 0, 1};
|
SkScalar fTextScaleX = 1; // Zero means we don't care what the value is.
|
SkPaint::Style fTextFill = SkPaint::kFill_Style; // Only if TextScaleX is non-zero.
|
int fShaderIndex = -1;
|
int fGraphicStateIndex = -1;
|
};
|
|
void DrawGlyphRunAsPath(SkPDFDevice* dev, const SkGlyphRun& glyphRun, SkPoint offset);
|
|
protected:
|
sk_sp<SkSurface> makeSurface(const SkImageInfo&, const SkSurfaceProps&) override;
|
|
void drawAnnotation(const SkRect&, const char key[], SkData* value) override;
|
|
void drawSpecial(SkSpecialImage*, int x, int y, const SkPaint&,
|
SkImage*, const SkMatrix&) override;
|
sk_sp<SkSpecialImage> makeSpecial(const SkBitmap&) override;
|
sk_sp<SkSpecialImage> makeSpecial(const SkImage*) override;
|
sk_sp<SkSpecialImage> snapSpecial() override;
|
SkImageFilterCache* getImageFilterCache() override;
|
|
private:
|
struct RectWithData {
|
SkRect rect;
|
sk_sp<SkData> data;
|
};
|
|
struct NamedDestination {
|
sk_sp<SkData> nameData;
|
SkPoint point;
|
};
|
|
// TODO(vandebo): push most of SkPDFDevice's state into a core object in
|
// order to get the right access levels without using friend.
|
friend class ScopedContentEntry;
|
|
SkMatrix fInitialTransform;
|
|
std::vector<RectWithData> fLinkToURLs;
|
std::vector<RectWithData> fLinkToDestinations;
|
std::vector<NamedDestination> fNamedDestinations;
|
|
SkTHashSet<SkPDFIndirectReference> fGraphicStateResources;
|
SkTHashSet<SkPDFIndirectReference> fXObjectResources;
|
SkTHashSet<SkPDFIndirectReference> fShaderResources;
|
SkTHashSet<SkPDFIndirectReference> fFontResources;
|
int fNodeId;
|
|
SkDynamicMemoryWStream fContent;
|
SkDynamicMemoryWStream fContentBuffer;
|
bool fNeedsExtraSave = false;
|
struct GraphicStackState {
|
GraphicStackState(SkDynamicMemoryWStream* s = nullptr);
|
void updateClip(const SkClipStack* clipStack, const SkIRect& bounds);
|
void updateMatrix(const SkMatrix& matrix);
|
void updateDrawingState(const SkPDFDevice::GraphicStateEntry& state);
|
void push();
|
void pop();
|
void drainStack();
|
SkPDFDevice::GraphicStateEntry* currentEntry() { return &fEntries[fStackDepth]; }
|
// Must use stack for matrix, and for clip, plus one for no matrix or clip.
|
static constexpr int kMaxStackDepth = 2;
|
SkPDFDevice::GraphicStateEntry fEntries[kMaxStackDepth + 1];
|
int fStackDepth = 0;
|
SkDynamicMemoryWStream* fContentStream;
|
};
|
GraphicStackState fActiveStackState;
|
SkPDFDocument* fDocument;
|
|
////////////////////////////////////////////////////////////////////////////
|
|
SkBaseDevice* onCreateDevice(const CreateInfo&, const SkPaint*) override;
|
|
// Set alpha to true if making a transparency group form x-objects.
|
SkPDFIndirectReference makeFormXObjectFromDevice(bool alpha = false);
|
|
void drawFormXObjectWithMask(SkPDFIndirectReference xObject,
|
SkPDFIndirectReference sMask,
|
SkBlendMode,
|
bool invertClip);
|
|
// If the paint or clip is such that we shouldn't draw anything, this
|
// returns nullptr and does not create a content entry.
|
// setUpContentEntry and finishContentEntry can be used directly, but
|
// the preferred method is to use the ScopedContentEntry helper class.
|
SkDynamicMemoryWStream* setUpContentEntry(const SkClipStack* clipStack,
|
const SkMatrix& matrix,
|
const SkPaint& paint,
|
SkScalar,
|
SkPDFIndirectReference* dst);
|
void finishContentEntry(const SkClipStack*, SkBlendMode, SkPDFIndirectReference, SkPath*);
|
bool isContentEmpty();
|
|
void internalDrawGlyphRun(const SkGlyphRun& glyphRun, SkPoint offset, const SkPaint& runPaint);
|
void drawGlyphRunAsPath(const SkGlyphRun& glyphRun, SkPoint offset, const SkPaint& runPaint);
|
|
void internalDrawImageRect(SkKeyedImage,
|
const SkRect* src,
|
const SkRect& dst,
|
const SkPaint&,
|
const SkMatrix& canvasTransformationMatrix);
|
|
void internalDrawPath(const SkClipStack&,
|
const SkMatrix&,
|
const SkPath&,
|
const SkPaint&,
|
bool pathIsMutable);
|
|
void internalDrawPathWithFilter(const SkClipStack& clipStack,
|
const SkMatrix& ctm,
|
const SkPath& origPath,
|
const SkPaint& paint);
|
|
bool handleInversePath(const SkPath& origPath, const SkPaint& paint, bool pathIsMutable);
|
|
void addSMaskGraphicState(sk_sp<SkPDFDevice> maskDevice, SkDynamicMemoryWStream*);
|
void clearMaskOnGraphicState(SkDynamicMemoryWStream*);
|
void setGraphicState(SkPDFIndirectReference gs, SkDynamicMemoryWStream*);
|
void drawFormXObject(SkPDFIndirectReference xObject, SkDynamicMemoryWStream*);
|
|
bool hasEmptyClip() const { return this->cs().isEmpty(this->bounds()); }
|
|
void reset();
|
|
typedef SkClipStackDevice INHERITED;
|
};
|
|
#endif
|