/* * 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 "SkPDFGraphicState.h" #include "SkData.h" #include "SkPDFCanon.h" #include "SkPDFFormXObject.h" #include "SkPDFUtils.h" #include "SkPaint.h" #include "SkTo.h" static const char* as_pdf_blend_mode_name(SkBlendMode mode) { // PDF32000.book section 11.3.5 "Blend Mode" switch (mode) { case SkBlendMode::kScreen: return "Screen"; case SkBlendMode::kOverlay: return "Overlay"; case SkBlendMode::kDarken: return "Darken"; case SkBlendMode::kLighten: return "Lighten"; case SkBlendMode::kColorDodge: return "ColorDodge"; case SkBlendMode::kColorBurn: return "ColorBurn"; case SkBlendMode::kHardLight: return "HardLight"; case SkBlendMode::kSoftLight: return "SoftLight"; case SkBlendMode::kDifference: return "Difference"; case SkBlendMode::kExclusion: return "Exclusion"; case SkBlendMode::kMultiply: return "Multiply"; case SkBlendMode::kHue: return "Hue"; case SkBlendMode::kSaturation: return "Saturation"; case SkBlendMode::kColor: return "Color"; case SkBlendMode::kLuminosity: return "Luminosity"; // Other blendmodes are either unsupported or handled in // SkPDFDevice::setUpContentEntry. default: return "Normal"; } } static int to_stroke_cap(uint8_t cap) { // PDF32000.book section 8.4.3.3 "Line Cap Style" switch ((SkPaint::Cap)cap) { case SkPaint::kButt_Cap: return 0; case SkPaint::kRound_Cap: return 1; case SkPaint::kSquare_Cap: return 2; default: SkASSERT(false); return 0; } } static int to_stroke_join(uint8_t join) { // PDF32000.book section 8.4.3.4 "Line Join Style" switch ((SkPaint::Join)join) { case SkPaint::kMiter_Join: return 0; case SkPaint::kRound_Join: return 1; case SkPaint::kBevel_Join: return 2; default: SkASSERT(false); return 0; } } // If a SkXfermode is unsupported in PDF, this function returns // SrcOver, otherwise, it returns that Xfermode as a Mode. static uint8_t pdf_blend_mode(SkBlendMode mode) { switch (mode) { case SkBlendMode::kSrcOver: case SkBlendMode::kMultiply: case SkBlendMode::kScreen: case SkBlendMode::kOverlay: case SkBlendMode::kDarken: case SkBlendMode::kLighten: case SkBlendMode::kColorDodge: case SkBlendMode::kColorBurn: case SkBlendMode::kHardLight: case SkBlendMode::kSoftLight: case SkBlendMode::kDifference: case SkBlendMode::kExclusion: case SkBlendMode::kHue: case SkBlendMode::kSaturation: case SkBlendMode::kColor: case SkBlendMode::kLuminosity: return SkToU8((unsigned)mode); default: return SkToU8((unsigned)SkBlendMode::kSrcOver); } } sk_sp SkPDFGraphicState::GetGraphicStateForPaint(SkPDFCanon* canon, const SkPaint& p) { SkASSERT(canon); if (SkPaint::kFill_Style == p.getStyle()) { SkPDFFillGraphicState fillKey = {p.getAlpha(), pdf_blend_mode(p.getBlendMode())}; auto& fillMap = canon->fFillGSMap; if (sk_sp* statePtr = fillMap.find(fillKey)) { return *statePtr; } auto state = sk_make_sp(); state->reserve(2); state->insertScalar("ca", fillKey.fAlpha / 255.0f); state->insertName("BM", as_pdf_blend_mode_name((SkBlendMode)fillKey.fBlendMode)); fillMap.set(fillKey, state); return state; } else { SkPDFStrokeGraphicState strokeKey = { p.getStrokeWidth(), p.getStrokeMiter(), SkToU8(p.getStrokeCap()), SkToU8(p.getStrokeJoin()), p.getAlpha(), pdf_blend_mode(p.getBlendMode())}; auto& sMap = canon->fStrokeGSMap; if (sk_sp* statePtr = sMap.find(strokeKey)) { return *statePtr; } auto state = sk_make_sp(); state->reserve(8); state->insertScalar("CA", strokeKey.fAlpha / 255.0f); state->insertScalar("ca", strokeKey.fAlpha / 255.0f); state->insertInt("LC", to_stroke_cap(strokeKey.fStrokeCap)); state->insertInt("LJ", to_stroke_join(strokeKey.fStrokeJoin)); state->insertScalar("LW", strokeKey.fStrokeWidth); state->insertScalar("ML", strokeKey.fStrokeMiter); state->insertBool("SA", true); // SA = Auto stroke adjustment. state->insertName("BM", as_pdf_blend_mode_name((SkBlendMode)strokeKey.fBlendMode)); sMap.set(strokeKey, state); return state; } } //////////////////////////////////////////////////////////////////////////////// static sk_sp make_invert_function() { // 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. auto domainAndRange = sk_make_sp(); domainAndRange->reserve(2); domainAndRange->appendInt(0); domainAndRange->appendInt(1); static const char psInvert[] = "{1 exch sub}"; // Do not copy the trailing '\0' into the SkData. auto invertFunction = sk_make_sp( SkData::MakeWithoutCopy(psInvert, strlen(psInvert))); invertFunction->dict()->insertInt("FunctionType", 4); invertFunction->dict()->insertObject("Domain", domainAndRange); invertFunction->dict()->insertObject("Range", std::move(domainAndRange)); return invertFunction; } sk_sp SkPDFGraphicState::GetSMaskGraphicState( sk_sp sMask, bool invert, SkPDFSMaskMode sMaskMode, SkPDFCanon* canon) { // The practical chances of using the same mask more than once are unlikely // enough that it's not worth canonicalizing. auto sMaskDict = sk_make_sp("Mask"); if (sMaskMode == kAlpha_SMaskMode) { sMaskDict->insertName("S", "Alpha"); } else if (sMaskMode == kLuminosity_SMaskMode) { sMaskDict->insertName("S", "Luminosity"); } sMaskDict->insertObjRef("G", std::move(sMask)); if (invert) { // Instead of calling SkPDFGraphicState::MakeInvertFunction, // let the canon deduplicate this object. sk_sp& invertFunction = canon->fInvertFunction; if (!invertFunction) { invertFunction = make_invert_function(); } sMaskDict->insertObjRef("TR", invertFunction); } auto result = sk_make_sp("ExtGState"); result->insertObject("SMask", std::move(sMaskDict)); return result; }