class SkPDFDevice;
class SkPDFDict;
class SkPDFFont;
+class SkPDFFormXObject;
class SkPDFGraphicState;
class SkPDFObject;
class SkPDFShader;
SkTScopedPtr<ContentEntry> fContentEntries;
ContentEntry* fCurrentContentEntry;
+ SkRefPtr<SkPDFFormXObject> fDstFormXObject;
// For use by the DeviceFactory.
SkPDFDevice(const SkISize& layerSize, const SkClipStack& existingClipStack,
void init();
void cleanUp();
+ void createFormXObjectFromDevice(SkRefPtr<SkPDFFormXObject>* xobject);
// If the paint or clip is such that we shouldn't draw anything, these
// return false and do not create a content entry.
const SkRegion& clipRegion,
const SkMatrix& matrix,
const SkPaint& paint);
+ void finishContentEntry(const SkPaint& paint);
void populateGraphicStateEntryFromPaint(const SkMatrix& matrix,
const SkClipStack& clipStack,
const SkRegion& clipRegion,
const SkPaint& paint,
bool hasText,
GraphicStateEntry* entry);
+ int addGraphicStateResource(SkPDFGraphicState* gs);
void updateFont(const SkPaint& paint, uint16_t glyphID);
int getFontResourceIndex(SkTypeface* typeface, uint16_t glyphID);
#include "SkTemplates.h"
#include "SkThread.h"
+class SkPDFFormXObject;
+
/** \class SkPDFGraphicState
SkPaint objects roughly correspond to graphic state dictionaries that can
- be installed. So that a given dictionary is only output to the pdf file
- once, we want to canonicalize them. Static methods in this class manage
+ be installed. So that a given dictionary is only output to the pdf file
+ once, we want to canonicalize them. Static methods in this class manage
a weakly referenced set of SkPDFGraphicState objects: when the last
reference to a SkPDFGraphicState is removed, it removes itself from the
static set of objects.
public:
virtual ~SkPDFGraphicState();
+ virtual void getResources(SkTDArray<SkPDFObject*>* resourceList);
+
// Override emitObject and getOutputSize so that we can populate
// the dictionary on demand.
virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
/** Get the graphic state for the passed SkPaint. The reference count of
* the object is incremented and it is the caller's responsibility to
- * unreference it when done. This is needed to accommodate the weak
+ * unreference it when done. This is needed to accommodate the weak
* reference pattern used when the returned object is new and has no
* other references.
* @param paint The SkPaint to emulate.
*/
static SkPDFGraphicState* getGraphicStateForPaint(const SkPaint& paint);
+ /** Make a graphic state that only sets the passed soft mask. The
+ * reference count of the object is incremented and it is the caller's
+ * responsibility to unreference it when done.
+ * @param sMask The form xobject to use as a soft mask.
+ * @param invert Indicates if the alpha of the sMask should be inverted.
+ */
+ static SkPDFGraphicState* getSMaskGraphicState(SkPDFFormXObject* sMask,
+ bool invert);
+
+ /** Get a graphic state that only unsets the soft mask. The reference
+ * count of the object is incremented and it is the caller's responsibility
+ * to unreference it when done. This is needed to accommodate the weak
+ * reference pattern used when the returned object is new and has no
+ * other references.
+ */
+ static SkPDFGraphicState* getNoSMaskGraphicState();
+
private:
const SkPaint fPaint;
+ SkTDArray<SkPDFObject*> fResources;
bool fPopulated;
+ bool fSMask;
class GSCanonicalEntry {
public:
static SkTDArray<GSCanonicalEntry>& canonicalPaints();
static SkMutex& canonicalPaintsMutex();
+ SkPDFGraphicState();
explicit SkPDFGraphicState(const SkPaint& paint);
void populateDict();
static void PaintPath(SkPaint::Style style, SkPath::FillType fill,
SkWStream* content);
static void StrokePath(SkWStream* content);
+ static void DrawFormXObject(int objectIndex, SkWStream* content);
+ static void ApplyGraphicState(int objectIndex, SkWStream* content);
};
#endif
}
if (state.fGraphicStateIndex != currentEntry()->fGraphicStateIndex) {
- fContentStream->writeText("/G");
- fContentStream->writeDecAsText(state.fGraphicStateIndex);
- fContentStream->writeText(" gs\n");
+ SkPDFUtils::ApplyGraphicState(state.fGraphicStateIndex, fContentStream);
currentEntry()->fGraphicStateIndex = state.fGraphicStateIndex;
}
}
internalDrawPaint(paint);
+ finishContentEntry(paint);
}
void SkPDFDevice::drawPaint(const SkDraw& d, const SkPaint& paint) {
}
internalDrawPaint(newPaint);
+ finishContentEntry(newPaint);
}
void SkPDFDevice::internalDrawPaint(const SkPaint& paint) {
default:
SkASSERT(false);
}
+ finishContentEntry(*paint);
}
void SkPDFDevice::drawRect(const SkDraw& d, const SkRect& r,
SkPDFUtils::AppendRectangle(r, &fCurrentContentEntry->fContent);
SkPDFUtils::PaintPath(paint.getStyle(), SkPath::kWinding_FillType,
&fCurrentContentEntry->fContent);
+ finishContentEntry(paint);
}
void SkPDFDevice::drawPath(const SkDraw& d, const SkPath& path,
SkPDFUtils::EmitPath(path, &fCurrentContentEntry->fContent);
SkPDFUtils::PaintPath(paint.getStyle(), path.getFillType(),
&fCurrentContentEntry->fContent);
+ finishContentEntry(paint);
}
void SkPDFDevice::drawBitmap(const SkDraw& d, const SkBitmap& bitmap,
drawRect(d, r, paint);
}
}
+ finishContentEntry(textPaint);
}
void SkPDFDevice::drawPosText(const SkDraw& d, const void* text, size_t len,
fCurrentContentEntry->fContent.writeText(" Tj\n");
}
fCurrentContentEntry->fContent.writeText("ET\n");
+ finishContentEntry(textPaint);
}
void SkPDFDevice::drawTextOnPath(const SkDraw& d, const void* text, size_t len,
SkPDFDevice* pdfDevice = static_cast<SkPDFDevice*>(device);
SkPDFFormXObject* xobject = new SkPDFFormXObject(pdfDevice);
fXObjectResources.push(xobject); // Transfer reference.
- fCurrentContentEntry->fContent.writeText("/X");
- fCurrentContentEntry->fContent.writeDecAsText(
- fXObjectResources.count() - 1);
- fCurrentContentEntry->fContent.writeText(" Do\n");
+ SkPDFUtils::DrawFormXObject(fXObjectResources.count() - 1,
+ &fCurrentContentEntry->fContent);
+ finishContentEntry(paint);
}
const SkRefPtr<SkPDFDict>& SkPDFDevice::getResourceDict() {
return result;
}
+void SkPDFDevice::createFormXObjectFromDevice(
+ SkRefPtr<SkPDFFormXObject>* xobject) {
+ *xobject = new SkPDFFormXObject(this);
+ (*xobject)->unref(); // SkRefPtr and new both took a reference.
+ cleanUp(); // Reset this device to have no content.
+ init();
+}
+
bool SkPDFDevice::setUpContentEntry(const SkClipStack* clipStack,
const SkRegion& clipRegion,
const SkMatrix& matrix,
fCurrentContentEntry = fContentEntries.get();
}
- // TODO(vandebo) For the following modes, we need to calculate various
- // operations between the source and destination shape:
- // SrcIn, DstIn, SrcOut, DstOut, SrcAtop, DestAtop, Xor.
+ // For the following modes, we use both source and destination, but
+ // we use one as a smask for the other, so we have to make form xobjects
+ // out of both of them: SrcIn, DstIn, SrcOut, DstOut.
+ if (xfermode == SkXfermode::kSrcIn_Mode ||
+ xfermode == SkXfermode::kDstIn_Mode ||
+ xfermode == SkXfermode::kSrcOut_Mode ||
+ xfermode == SkXfermode::kDstOut_Mode) {
+ SkASSERT(fDstFormXObject.get() == NULL);
+ createFormXObjectFromDevice(&fDstFormXObject);
+ }
+
+ // TODO(vandebo) Figure out how/if we can handle the following modes:
+ // SrcAtop, DestAtop, Xor.
// These xfer modes don't draw source at all.
if (xfermode == SkXfermode::kClear_Mode ||
return setUpContentEntry(clipStack, clipRegion, matrix, paint, true);
}
+void SkPDFDevice::finishContentEntry(const SkPaint& paint) {
+ SkXfermode::Mode xfermode = SkXfermode::kSrcOver_Mode;
+ if (paint.getXfermode()) {
+ paint.getXfermode()->asMode(&xfermode);
+ }
+ if (xfermode != SkXfermode::kSrcIn_Mode &&
+ xfermode != SkXfermode::kDstIn_Mode &&
+ xfermode != SkXfermode::kSrcOut_Mode &&
+ xfermode != SkXfermode::kDstOut_Mode) {
+ SkASSERT(fDstFormXObject.get() == NULL);
+ return;
+ }
+
+ SkRefPtr<SkPDFFormXObject> srcFormXObject;
+ createFormXObjectFromDevice(&srcFormXObject);
+
+ SkMatrix identity;
+ identity.reset();
+ SkPaint stockPaint;
+ setUpContentEntry(&fExistingClipStack, fExistingClipRegion, identity,
+ stockPaint);
+
+ SkRefPtr<SkPDFGraphicState> sMaskGS;
+ if (xfermode == SkXfermode::kSrcIn_Mode ||
+ xfermode == SkXfermode::kSrcOut_Mode) {
+ sMaskGS = SkPDFGraphicState::getSMaskGraphicState(
+ fDstFormXObject.get(), xfermode == SkXfermode::kSrcOut_Mode);
+ fXObjectResources.push(srcFormXObject.get());
+ srcFormXObject->ref();
+ } else {
+ sMaskGS = SkPDFGraphicState::getSMaskGraphicState(
+ srcFormXObject.get(), xfermode == SkXfermode::kDstOut_Mode);
+ fXObjectResources.push(fDstFormXObject.get());
+ fDstFormXObject->ref();
+ }
+ sMaskGS->unref(); // SkRefPtr and getSMaskGraphicState both took a ref.
+ SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()),
+ &fCurrentContentEntry->fContent);
+
+ SkPDFUtils::DrawFormXObject(fXObjectResources.count() - 1,
+ &fCurrentContentEntry->fContent);
+
+ sMaskGS = SkPDFGraphicState::getNoSMaskGraphicState();
+ sMaskGS->unref(); // SkRefPtr and getSMaskGraphicState both took a ref.
+ SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()),
+ &fCurrentContentEntry->fContent);
+
+ fDstFormXObject = NULL;
+ finishContentEntry(stockPaint);
+}
+
void SkPDFDevice::populateGraphicStateEntryFromPaint(
const SkMatrix& matrix,
const SkClipStack& clipStack,
newGraphicState = SkPDFGraphicState::getGraphicStateForPaint(newPaint);
}
newGraphicState->unref(); // getGraphicState and SkRefPtr both took a ref.
- // newGraphicState has been canonicalized so we can directly compare
- // pointers.
- int resourceIndex = fGraphicStateResources.find(newGraphicState.get());
- if (resourceIndex < 0) {
- resourceIndex = fGraphicStateResources.count();
- fGraphicStateResources.push(newGraphicState.get());
- newGraphicState->ref();
- }
+ int resourceIndex = addGraphicStateResource(newGraphicState.get());
entry->fGraphicStateIndex = resourceIndex;
if (hasText) {
}
}
+int SkPDFDevice::addGraphicStateResource(SkPDFGraphicState* gs) {
+ // Assumes that gs has been canonicalized (so we can directly compare
+ // pointers).
+ int result = fGraphicStateResources.find(gs);
+ if (result < 0) {
+ result = fGraphicStateResources.count();
+ fGraphicStateResources.push(gs);
+ gs->ref();
+ }
+ return result;
+}
+
void SkPDFDevice::updateFont(const SkPaint& paint, uint16_t glyphID) {
SkTypeface* typeface = paint.getTypeface();
if (fCurrentContentEntry->fState.fFont == NULL ||
}
fXObjectResources.push(image); // Transfer reference.
- fCurrentContentEntry->fContent.writeText("/X");
- fCurrentContentEntry->fContent.writeDecAsText(
- fXObjectResources.count() - 1);
- fCurrentContentEntry->fContent.writeText(" Do\n");
+ SkPDFUtils::DrawFormXObject(fXObjectResources.count() - 1,
+ &fCurrentContentEntry->fContent);
+ finishContentEntry(paint);
}
* limitations under the License.
*/
+#include "SkPDFFormXObject.h"
#include "SkPDFGraphicState.h"
#include "SkPDFUtils.h"
#include "SkStream.h"
#include "SkTypes.h"
-namespace {
-
-const char* blendModeFromXfermode(SkXfermode::Mode mode) {
+static const char* blend_mode_from_xfermode(SkXfermode::Mode mode) {
switch (mode) {
case SkXfermode::kSrcOver_Mode: return "Normal";
case SkXfermode::kMultiply_Mode: return "Multiply";
case SkXfermode::kSrc_Mode:
case SkXfermode::kDst_Mode:
case SkXfermode::kDstOver_Mode:
- return "Normal";
-
- // TODO(vandebo) Figure out if we can support more of these modes.
case SkXfermode::kSrcIn_Mode:
case SkXfermode::kDstIn_Mode:
case SkXfermode::kSrcOut_Mode:
case SkXfermode::kDstOut_Mode:
+ return "Normal";
+
+ // TODO(vandebo) Figure out if we can support more of these modes.
case SkXfermode::kSrcATop_Mode:
case SkXfermode::kDstATop_Mode:
case SkXfermode::kXor_Mode:
return NULL;
}
-}
-
SkPDFGraphicState::~SkPDFGraphicState() {
SkAutoMutexAcquire lock(canonicalPaintsMutex());
- int index = find(fPaint);
- SkASSERT(index >= 0);
- canonicalPaints().removeShuffle(index);
+ if (!fSMask) {
+ int index = find(fPaint);
+ SkASSERT(index >= 0);
+ canonicalPaints().removeShuffle(index);
+ }
+ fResources.unrefAll();
+}
+
+void SkPDFGraphicState::getResources(SkTDArray<SkPDFObject*>* resourceList) {
+ resourceList->setReserve(resourceList->count() + fResources.count());
+ for (int i = 0; i < fResources.count(); i++) {
+ resourceList->push(fResources[i]);
+ fResources[i]->ref();
+ fResources[i]->getResources(resourceList);
+ }
}
void SkPDFGraphicState::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
}
// static
+SkPDFGraphicState* SkPDFGraphicState::getSMaskGraphicState(
+ SkPDFFormXObject* sMask, bool invert) {
+ // The practical chances of using the same mask more than once are unlikely
+ // enough that it's not worth canonicalizing.
+ SkAutoMutexAcquire lock(canonicalPaintsMutex());
+
+ SkRefPtr<SkPDFDict> sMaskDict = new SkPDFDict("Mask");
+ sMaskDict->unref(); // SkRefPtr and new both took a reference.
+ sMaskDict->insert("S", new SkPDFName("Alpha"))->unref();
+ sMaskDict->insert("G", new SkPDFObjRef(sMask))->unref();
+
+ SkPDFGraphicState* result = new SkPDFGraphicState;
+ result->fPopulated = true;
+ result->fSMask = true;
+ result->insert("Type", new SkPDFName("ExtGState"))->unref();
+ result->insert("SMask", sMaskDict.get());
+ result->fResources.push(sMask);
+ sMask->ref();
+
+ if (invert) {
+ // Acrobat crashes if we use a type 0 function, kpdf crashes if we use
+ // a type 2 function, so we use a type 4 function.
+ SkRefPtr<SkPDFArray> domainAndRange = new SkPDFArray;
+ domainAndRange->unref(); // SkRefPtr and new both took a reference.
+ domainAndRange->reserve(2);
+ domainAndRange->append(new SkPDFInt(0))->unref();
+ domainAndRange->append(new SkPDFInt(1))->unref();
+
+ static const char psInvert[] = "{1 exch sub}";
+ SkRefPtr<SkMemoryStream> psInvertStream =
+ new SkMemoryStream(&psInvert, strlen(psInvert), true);
+ psInvertStream->unref(); // SkRefPtr and new both took a reference.
+
+ SkRefPtr<SkPDFStream> invertFunc =
+ new SkPDFStream(psInvertStream.get());
+ result->fResources.push(invertFunc.get()); // Pass the ref from new.
+ invertFunc->insert("FunctionType", new SkPDFInt(4))->unref();
+ invertFunc->insert("Domain", domainAndRange.get());
+ invertFunc->insert("Range", domainAndRange.get());
+
+ sMaskDict->insert("TR", new SkPDFObjRef(invertFunc.get()))->unref();
+ }
+
+ return result;
+}
+
+// static
+SkPDFGraphicState* SkPDFGraphicState::getNoSMaskGraphicState() {
+ SkAutoMutexAcquire lock(canonicalPaintsMutex());
+ static SkPDFGraphicState* noSMaskGS = NULL;
+ if (!noSMaskGS) {
+ noSMaskGS = new SkPDFGraphicState;
+ noSMaskGS->fPopulated = true;
+ noSMaskGS->fSMask = true;
+ noSMaskGS->insert("Type", new SkPDFName("ExtGState"))->unref();
+ noSMaskGS->insert("SMask", new SkPDFName("None"))->unref();
+ }
+ noSMaskGS->ref();
+ return noSMaskGS;
+}
+
+// static
int SkPDFGraphicState::find(const SkPaint& paint) {
GSCanonicalEntry search(&paint);
return canonicalPaints().find(search);
}
+SkPDFGraphicState::SkPDFGraphicState()
+ : fPopulated(false),
+ fSMask(false) {
+}
+
SkPDFGraphicState::SkPDFGraphicState(const SkPaint& paint)
: fPaint(paint),
- fPopulated(false) {
+ fPopulated(false),
+ fSMask(false) {
}
// populateDict and operator== have to stay in sync with each other.
fPaint.getXfermode()->asMode(&xfermode);
// If we don't support the mode, just use kSrcOver_Mode.
if (xfermode < 0 || xfermode > SkXfermode::kLastMode ||
- blendModeFromXfermode(xfermode) == NULL) {
+ blend_mode_from_xfermode(xfermode) == NULL) {
xfermode = SkXfermode::kSrcOver_Mode;
NOT_IMPLEMENTED("unsupported xfermode", false);
}
- insert("BM", new SkPDFName(blendModeFromXfermode(xfermode)))->unref();
+ insert("BM",
+ new SkPDFName(blend_mode_from_xfermode(xfermode)))->unref();
}
}
aXfermode->asMode(&aXfermodeName);
}
if (aXfermodeName < 0 || aXfermodeName > SkXfermode::kLastMode ||
- blendModeFromXfermode(aXfermodeName) == NULL) {
+ blend_mode_from_xfermode(aXfermodeName) == NULL) {
aXfermodeName = SkXfermode::kSrcOver_Mode;
}
- const char* aXfermodeString = blendModeFromXfermode(aXfermodeName);
+ const char* aXfermodeString = blend_mode_from_xfermode(aXfermodeName);
SkASSERT(aXfermodeString != NULL);
SkXfermode::Mode bXfermodeName = SkXfermode::kSrcOver_Mode;
bXfermode->asMode(&bXfermodeName);
}
if (bXfermodeName < 0 || bXfermodeName > SkXfermode::kLastMode ||
- blendModeFromXfermode(bXfermodeName) == NULL) {
+ blend_mode_from_xfermode(bXfermodeName) == NULL) {
bXfermodeName = SkXfermode::kSrcOver_Mode;
}
- const char* bXfermodeString = blendModeFromXfermode(bXfermodeName);
+ const char* bXfermodeString = blend_mode_from_xfermode(bXfermodeName);
SkASSERT(bXfermodeString != NULL);
return strcmp(aXfermodeString, bXfermodeString) == 0;
SkPDFUtils::PaintPath(
SkPaint::kStroke_Style, SkPath::kWinding_FillType, content);
}
+
+// static
+void SkPDFUtils::DrawFormXObject(int objectIndex, SkWStream* content) {
+ content->writeText("/X");
+ content->writeDecAsText(objectIndex);
+ content->writeText(" Do\n");
+}
+
+// static
+void SkPDFUtils::ApplyGraphicState(int objectIndex, SkWStream* content) {
+ content->writeText("/G");
+ content->writeDecAsText(objectIndex);
+ content->writeText(" gs\n");
+}