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