/*
* Copyright 2011 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkPDFFormXObject.h"
#include "SkPDFGraphicState.h"
#include "SkPDFUtils.h"
#include "SkStream.h"
#include "SkTypes.h"
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::kScreen_Mode: return "Screen";
case SkXfermode::kOverlay_Mode: return "Overlay";
case SkXfermode::kDarken_Mode: return "Darken";
case SkXfermode::kLighten_Mode: return "Lighten";
case SkXfermode::kColorDodge_Mode: return "ColorDodge";
case SkXfermode::kColorBurn_Mode: return "ColorBurn";
case SkXfermode::kHardLight_Mode: return "HardLight";
case SkXfermode::kSoftLight_Mode: return "SoftLight";
case SkXfermode::kDifference_Mode: return "Difference";
case SkXfermode::kExclusion_Mode: return "Exclusion";
case SkXfermode::kHue_Mode: return "Hue";
case SkXfermode::kSaturation_Mode: return "Saturation";
case SkXfermode::kColor_Mode: return "Color";
case SkXfermode::kLuminosity_Mode: return "Luminosity";
// These are handled in SkPDFDevice::setUpContentEntry.
case SkXfermode::kClear_Mode:
case SkXfermode::kSrc_Mode:
case SkXfermode::kDst_Mode:
case SkXfermode::kDstOver_Mode:
case SkXfermode::kSrcIn_Mode:
case SkXfermode::kDstIn_Mode:
case SkXfermode::kSrcOut_Mode:
case SkXfermode::kDstOut_Mode:
case SkXfermode::kSrcATop_Mode:
case SkXfermode::kDstATop_Mode:
case SkXfermode::kModulate_Mode:
return "Normal";
// TODO(vandebo): Figure out if we can support more of these modes.
case SkXfermode::kXor_Mode:
case SkXfermode::kPlus_Mode:
return NULL;
}
return NULL;
}
SkPDFGraphicState::~SkPDFGraphicState() {
SkAutoMutexAcquire lock(CanonicalPaintsMutex());
if (!fSMask) {
int index = Find(fPaint);
SkASSERT(index >= 0);
SkASSERT(CanonicalPaints()[index].fGraphicState == this);
CanonicalPaints().removeShuffle(index);
}
fResources.unrefAll();
}
void SkPDFGraphicState::getResources(
const SkTSet<SkPDFObject*>& knownResourceObjects,
SkTSet<SkPDFObject*>* newResourceObjects) {
GetResourcesHelper(&fResources, knownResourceObjects, newResourceObjects);
}
void SkPDFGraphicState::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
bool indirect) {
populateDict();
SkPDFDict::emitObject(stream, catalog, indirect);
}
// static
size_t SkPDFGraphicState::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
populateDict();
return SkPDFDict::getOutputSize(catalog, indirect);
}
// static
SkTDArray<SkPDFGraphicState::GSCanonicalEntry>&
SkPDFGraphicState::CanonicalPaints() {
// This initialization is only thread safe with gcc.
static SkTDArray<SkPDFGraphicState::GSCanonicalEntry> gCanonicalPaints;
return gCanonicalPaints;
}
// static
SkBaseMutex& SkPDFGraphicState::CanonicalPaintsMutex() {
// This initialization is only thread safe with gcc or when
// POD-style mutex initialization is used.
SK_DECLARE_STATIC_MUTEX(gCanonicalPaintsMutex);
return gCanonicalPaintsMutex;
}
// static
SkPDFGraphicState* SkPDFGraphicState::GetGraphicStateForPaint(
const SkPaint& paint) {
SkAutoMutexAcquire lock(CanonicalPaintsMutex());
int index = Find(paint);
if (index >= 0) {
CanonicalPaints()[index].fGraphicState->ref();
return CanonicalPaints()[index].fGraphicState;
}
GSCanonicalEntry newEntry(new SkPDFGraphicState(paint));
CanonicalPaints().push(newEntry);
return newEntry.fGraphicState;
}
// static
SkPDFObject* SkPDFGraphicState::GetInvertFunction() {
// This assumes that canonicalPaintsMutex is held.
static SkPDFStream* invertFunction = NULL;
if (!invertFunction) {
// 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.
SkAutoTUnref<SkPDFArray> domainAndRange(new SkPDFArray);
domainAndRange->reserve(2);
domainAndRange->appendInt(0);
domainAndRange->appendInt(1);
static const char psInvert[] = "{1 exch sub}";
SkAutoTUnref<SkMemoryStream> psInvertStream(
new SkMemoryStream(&psInvert, strlen(psInvert), true));
invertFunction = new SkPDFStream(psInvertStream.get());
invertFunction->insertInt("FunctionType", 4);
invertFunction->insert("Domain", domainAndRange.get());
invertFunction->insert("Range", domainAndRange.get());
}
return invertFunction;
}
// static
SkPDFGraphicState* SkPDFGraphicState::GetSMaskGraphicState(
SkPDFFormXObject* sMask, bool invert, SkPDFSMaskMode sMaskMode) {
// The practical chances of using the same mask more than once are unlikely
// enough that it's not worth canonicalizing.
SkAutoMutexAcquire lock(CanonicalPaintsMutex());
SkAutoTUnref<SkPDFDict> sMaskDict(new SkPDFDict("Mask"));
if (sMaskMode == kAlpha_SMaskMode) {
sMaskDict->insertName("S", "Alpha");
} else if (sMaskMode == kLuminosity_SMaskMode) {
sMaskDict->insertName("S", "Luminosity");
}
sMaskDict->insert("G", new SkPDFObjRef(sMask))->unref();
SkPDFGraphicState* result = new SkPDFGraphicState;
result->fPopulated = true;
result->fSMask = true;
result->insertName("Type", "ExtGState");
result->insert("SMask", sMaskDict.get());
result->fResources.push(sMask);
sMask->ref();
if (invert) {
SkPDFObject* invertFunction = GetInvertFunction();
result->fResources.push(invertFunction);
invertFunction->ref();
sMaskDict->insert("TR", new SkPDFObjRef(invertFunction))->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->insertName("Type", "ExtGState");
noSMaskGS->insertName("SMask", "None");
}
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),
fSMask(false) {
}
// populateDict and operator== have to stay in sync with each other.
void SkPDFGraphicState::populateDict() {
if (!fPopulated) {
fPopulated = true;
insertName("Type", "ExtGState");
SkAutoTUnref<SkPDFScalar> alpha(
new SkPDFScalar(SkScalarDiv(fPaint.getAlpha(), 0xFF)));
insert("CA", alpha.get());
insert("ca", alpha.get());
SK_COMPILE_ASSERT(SkPaint::kButt_Cap == 0, paint_cap_mismatch);
SK_COMPILE_ASSERT(SkPaint::kRound_Cap == 1, paint_cap_mismatch);
SK_COMPILE_ASSERT(SkPaint::kSquare_Cap == 2, paint_cap_mismatch);
SK_COMPILE_ASSERT(SkPaint::kCapCount == 3, paint_cap_mismatch);
SkASSERT(fPaint.getStrokeCap() >= 0 && fPaint.getStrokeCap() <= 2);
insertInt("LC", fPaint.getStrokeCap());
SK_COMPILE_ASSERT(SkPaint::kMiter_Join == 0, paint_join_mismatch);
SK_COMPILE_ASSERT(SkPaint::kRound_Join == 1, paint_join_mismatch);
SK_COMPILE_ASSERT(SkPaint::kBevel_Join == 2, paint_join_mismatch);
SK_COMPILE_ASSERT(SkPaint::kJoinCount == 3, paint_join_mismatch);
SkASSERT(fPaint.getStrokeJoin() >= 0 && fPaint.getStrokeJoin() <= 2);
insertInt("LJ", fPaint.getStrokeJoin());
insertScalar("LW", fPaint.getStrokeWidth());
insertScalar("ML", fPaint.getStrokeMiter());
insert("SA", new SkPDFBool(true))->unref(); // Auto stroke adjustment.
SkXfermode::Mode xfermode = SkXfermode::kSrcOver_Mode;
// If asMode fails, default to kSrcOver_Mode.
if (fPaint.getXfermode())
fPaint.getXfermode()->asMode(&xfermode);
// If we don't support the mode, just use kSrcOver_Mode.
if (xfermode < 0 || xfermode > SkXfermode::kLastMode ||
blend_mode_from_xfermode(xfermode) == NULL) {
xfermode = SkXfermode::kSrcOver_Mode;
NOT_IMPLEMENTED("unsupported xfermode", false);
}
insertName("BM", blend_mode_from_xfermode(xfermode));
}
}
// We're only interested in some fields of the SkPaint, so we have a custom
// operator== function.
bool SkPDFGraphicState::GSCanonicalEntry::operator==(
const SkPDFGraphicState::GSCanonicalEntry& gs) const {
const SkPaint* a = fPaint;
const SkPaint* b = gs.fPaint;
SkASSERT(a != NULL);
SkASSERT(b != NULL);
if (SkColorGetA(a->getColor()) != SkColorGetA(b->getColor()) ||
a->getStrokeCap() != b->getStrokeCap() ||
a->getStrokeJoin() != b->getStrokeJoin() ||
a->getStrokeWidth() != b->getStrokeWidth() ||
a->getStrokeMiter() != b->getStrokeMiter()) {
return false;
}
SkXfermode::Mode aXfermodeName = SkXfermode::kSrcOver_Mode;
SkXfermode* aXfermode = a->getXfermode();
if (aXfermode) {
aXfermode->asMode(&aXfermodeName);
}
if (aXfermodeName < 0 || aXfermodeName > SkXfermode::kLastMode ||
blend_mode_from_xfermode(aXfermodeName) == NULL) {
aXfermodeName = SkXfermode::kSrcOver_Mode;
}
const char* aXfermodeString = blend_mode_from_xfermode(aXfermodeName);
SkASSERT(aXfermodeString != NULL);
SkXfermode::Mode bXfermodeName = SkXfermode::kSrcOver_Mode;
SkXfermode* bXfermode = b->getXfermode();
if (bXfermode) {
bXfermode->asMode(&bXfermodeName);
}
if (bXfermodeName < 0 || bXfermodeName > SkXfermode::kLastMode ||
blend_mode_from_xfermode(bXfermodeName) == NULL) {
bXfermodeName = SkXfermode::kSrcOver_Mode;
}
const char* bXfermodeString = blend_mode_from_xfermode(bXfermodeName);
SkASSERT(bXfermodeString != NULL);
return strcmp(aXfermodeString, bXfermodeString) == 0;
}