From 80fa7cea93974b0480f35f7a5260ce78ba50420f Mon Sep 17 00:00:00 2001 From: Hal Canary Date: Wed, 28 Jun 2017 16:04:20 -0400 Subject: SkPDF: simplify SkPDFGraphicState - Separate graphic state objects for Stroke and Fill. - SkPDFGraphicState::GetGraphicStateForPaint simplified. - No more SkPDFGraphicState objects.Simplify SkPDFCanon. All PDFs render the same. Most PDFs are slightly smaller, especially those from captured web pages. Change-Id: Id9605c1d7495645da558d5f378ba585cdc201bba Reviewed-on: https://skia-review.googlesource.com/21343 Reviewed-by: Ben Wagner Commit-Queue: Hal Canary --- src/pdf/SkPDFCanon.cpp | 20 +---- src/pdf/SkPDFCanon.h | 36 +------- src/pdf/SkPDFDevice.cpp | 6 +- src/pdf/SkPDFGraphicState.cpp | 187 ++++++++++++++++++------------------------ src/pdf/SkPDFGraphicState.h | 58 ++++++------- src/pdf/SkPDFTypes.cpp | 4 + src/pdf/SkPDFTypes.h | 3 + 7 files changed, 120 insertions(+), 194 deletions(-) diff --git a/src/pdf/SkPDFCanon.cpp b/src/pdf/SkPDFCanon.cpp index 5e8a2cb0c2..3ecd474069 100644 --- a/src/pdf/SkPDFCanon.cpp +++ b/src/pdf/SkPDFCanon.cpp @@ -12,9 +12,7 @@ //////////////////////////////////////////////////////////////////////////////// -SkPDFCanon::~SkPDFCanon() { - fGraphicStateRecords.foreach ([](WrapGS w) { w.fPtr->unref(); }); -} +SkPDFCanon::~SkPDFCanon() {} //////////////////////////////////////////////////////////////////////////////// @@ -56,19 +54,3 @@ void SkPDFCanon::addImageShader(sk_sp pdfShader, SkPDFShader::State state) { fImageShaderRecords.emplace_back(ShaderRec{std::move(state), std::move(pdfShader)}); } - -//////////////////////////////////////////////////////////////////////////////// - -const SkPDFGraphicState* SkPDFCanon::findGraphicState( - const SkPDFGraphicState& key) const { - const WrapGS* ptr = fGraphicStateRecords.find(WrapGS(&key)); - return ptr ? ptr->fPtr : nullptr; -} - -void SkPDFCanon::addGraphicState(const SkPDFGraphicState* state) { - SkASSERT(state); - WrapGS w(SkRef(state)); - SkASSERT(!fGraphicStateRecords.contains(w)); - fGraphicStateRecords.add(w); -} - diff --git a/src/pdf/SkPDFCanon.h b/src/pdf/SkPDFCanon.h index 0aac2b5789..d876443c17 100644 --- a/src/pdf/SkPDFCanon.h +++ b/src/pdf/SkPDFCanon.h @@ -20,19 +20,6 @@ struct SkAdvancedTypefaceMetrics; /** * The SkPDFCanon canonicalizes objects across PDF pages * (SkPDFDevices) and across draw calls. - * - * The PDF backend works correctly if: - * - There is no more than one SkPDFCanon for each thread. - * - Every SkPDFDevice is given a pointer to a SkPDFCanon on creation. - * - All SkPDFDevices in a document share the same SkPDFCanon. - * The SkPDFDocument class makes this happen by owning a single - * SkPDFCanon. - * - * The addFoo() methods will ref the Foo; the canon's destructor will - * call foo->unref() on all of these objects. - * - * The findFoo() methods do not change the ref count of the Foo - * objects. */ class SkPDFCanon : SkNoncopyable { public: @@ -47,15 +34,15 @@ public: sk_sp findImageShader(const SkPDFShader::State&) const; void addImageShader(sk_sp, SkPDFShader::State); - const SkPDFGraphicState* findGraphicState(const SkPDFGraphicState&) const; - void addGraphicState(const SkPDFGraphicState*); - SkTHashMap> fPDFBitmapMap; SkTHashMap> fTypefaceMetrics; SkTHashMap> fFontDescriptors; SkTHashMap> fFontMap; + SkTHashMap> fStrokeGSMap; + SkTHashMap> fFillGSMap; + sk_sp fPixelSerializer; sk_sp fInvertFunction; sk_sp fNoSmaskGraphicState; @@ -69,22 +56,5 @@ private: SkTArray fFunctionShaderRecords; SkTArray fAlphaShaderRecords; SkTArray fImageShaderRecords; - - struct WrapGS { - explicit WrapGS(const SkPDFGraphicState* ptr = nullptr) : fPtr(ptr) {} - const SkPDFGraphicState* fPtr; - bool operator==(const WrapGS& rhs) const { - SkASSERT(fPtr); - SkASSERT(rhs.fPtr); - return *fPtr == *rhs.fPtr; - } - struct Hash { - uint32_t operator()(const WrapGS& w) const { - SkASSERT(w.fPtr); - return w.fPtr->hash(); - } - }; - }; - SkTHashSet fGraphicStateRecords; }; #endif // SkPDFCanon_DEFINED diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp index 5fc914d3ab..6888f54ca5 100644 --- a/src/pdf/SkPDFDevice.cpp +++ b/src/pdf/SkPDFDevice.cpp @@ -653,7 +653,9 @@ void SkPDFDevice::drawPoints(SkCanvas::PointMode mode, SkPaint passedPaint = srcPaint; remove_color_filter(&passedPaint); replace_srcmode_on_opaque_paint(&passedPaint); - + if (SkCanvas::kPoints_PointMode != mode) { + passedPaint.setStyle(SkPaint::kStroke_Style); + } if (count == 0) { return; } @@ -2188,7 +2190,7 @@ void SkPDFDevice::populateGraphicStateEntryFromPaint( } } - sk_sp newGraphicState; + sk_sp newGraphicState; if (color == paint.getColor()) { newGraphicState = SkPDFGraphicState::GetGraphicStateForPaint(fDocument->canon(), paint); } else { diff --git a/src/pdf/SkPDFGraphicState.cpp b/src/pdf/SkPDFGraphicState.cpp index 6b7c502ccc..cab071c747 100644 --- a/src/pdf/SkPDFGraphicState.cpp +++ b/src/pdf/SkPDFGraphicState.cpp @@ -12,66 +12,53 @@ #include "SkPDFGraphicState.h" #include "SkPDFUtils.h" -static const char* as_blend_mode(SkBlendMode mode) { +static const char* as_pdf_blend_mode_name(SkBlendMode mode) { + // PDF32000.book section 11.3.5 "Blend Mode" switch (mode) { - case SkBlendMode::kSrcOver: - return "Normal"; - case SkBlendMode::kMultiply: - return "Multiply"; - 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::kHue: - return "Hue"; - case SkBlendMode::kSaturation: - return "Saturation"; - case SkBlendMode::kColor: - return "Color"; - case SkBlendMode::kLuminosity: - return "Luminosity"; + 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"; + } +} - // These are handled in SkPDFDevice::setUpContentEntry. - case SkBlendMode::kClear: - case SkBlendMode::kSrc: - case SkBlendMode::kDst: - case SkBlendMode::kDstOver: - case SkBlendMode::kSrcIn: - case SkBlendMode::kDstIn: - case SkBlendMode::kSrcOut: - case SkBlendMode::kDstOut: - case SkBlendMode::kSrcATop: - case SkBlendMode::kDstATop: - case SkBlendMode::kModulate: - 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; + } +} - // TODO(vandebo): Figure out if we can support more of these modes. - case SkBlendMode::kXor: - case SkBlendMode::kPlus: - return nullptr; +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; } - return nullptr; } // If a SkXfermode is unsupported in PDF, this function returns // SrcOver, otherwise, it returns that Xfermode as a Mode. -static SkBlendMode mode_for_pdf(SkBlendMode mode) { +static uint8_t pdf_blend_mode(SkBlendMode mode) { switch (mode) { case SkBlendMode::kSrcOver: case SkBlendMode::kMultiply: @@ -89,37 +76,53 @@ static SkBlendMode mode_for_pdf(SkBlendMode mode) { case SkBlendMode::kSaturation: case SkBlendMode::kColor: case SkBlendMode::kLuminosity: - // Mode is suppported and handled by pdf graphics state. - return mode; + return SkToU8((unsigned)mode); default: - return SkBlendMode::kSrcOver; // Default mode. + return SkToU8((unsigned)SkBlendMode::kSrcOver); } } -SkPDFGraphicState::SkPDFGraphicState(const SkPaint& p) - : fStrokeWidth(p.getStrokeWidth()) - , fStrokeMiter(p.getStrokeMiter()) - , fAlpha(p.getAlpha()) - , fStrokeCap(SkToU8(p.getStrokeCap())) - , fStrokeJoin(SkToU8(p.getStrokeJoin())) - , fMode(SkToU8((unsigned)mode_for_pdf(p.getBlendMode()))) {} - -sk_sp SkPDFGraphicState::GetGraphicStateForPaint(SkPDFCanon* canon, - const SkPaint& paint) { +sk_sp SkPDFGraphicState::GetGraphicStateForPaint(SkPDFCanon* canon, + const SkPaint& p) { SkASSERT(canon); - SkPDFGraphicState key(paint); - if (const SkPDFGraphicState* canonGS = canon->findGraphicState(key)) { - // The returned SkPDFGraphicState must be made non-const, - // since the emitObject() interface is non-const. But We - // promise that there is no way to mutate this object from - // here on out. - return sk_sp(SkRef(const_cast(canonGS))); + 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; } - sk_sp pdfGraphicState(new SkPDFGraphicState(paint)); - canon->addGraphicState(pdfGraphicState.get()); - return pdfGraphicState; } +//////////////////////////////////////////////////////////////////////////////// + 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. @@ -161,41 +164,7 @@ sk_sp SkPDFGraphicState::GetSMaskGraphicState( } sMaskDict->insertObjRef("TR", invertFunction); } - auto result = sk_make_sp("ExtGState"); result->insertObject("SMask", std::move(sMaskDict)); return result; } - -void SkPDFGraphicState::emitObject( - SkWStream* stream, - const SkPDFObjNumMap& objNumMap) const { - auto dict = sk_make_sp("ExtGState"); - - SkScalar alpha = SkIntToScalar(fAlpha) / 0xFF; - dict->insertScalar("CA", alpha); - dict->insertScalar("ca", alpha); - - SkPaint::Cap strokeCap = (SkPaint::Cap)fStrokeCap; - SkPaint::Join strokeJoin = (SkPaint::Join)fStrokeJoin; - - static_assert(SkPaint::kButt_Cap == 0, "paint_cap_mismatch"); - static_assert(SkPaint::kRound_Cap == 1, "paint_cap_mismatch"); - static_assert(SkPaint::kSquare_Cap == 2, "paint_cap_mismatch"); - static_assert(SkPaint::kCapCount == 3, "paint_cap_mismatch"); - SkASSERT(strokeCap >= 0 && strokeCap <= 2); - dict->insertInt("LC", strokeCap); - - static_assert(SkPaint::kMiter_Join == 0, "paint_join_mismatch"); - static_assert(SkPaint::kRound_Join == 1, "paint_join_mismatch"); - static_assert(SkPaint::kBevel_Join == 2, "paint_join_mismatch"); - static_assert(SkPaint::kJoinCount == 3, "paint_join_mismatch"); - SkASSERT(strokeJoin >= 0 && strokeJoin <= 2); - dict->insertInt("LJ", strokeJoin); - - dict->insertScalar("LW", fStrokeWidth); - dict->insertScalar("ML", fStrokeMiter); - dict->insertBool("SA", true); // SA = Auto stroke adjustment. - dict->insertName("BM", as_blend_mode((SkBlendMode)fMode)); - dict->emitObject(stream, objNumMap); -} diff --git a/src/pdf/SkPDFGraphicState.h b/src/pdf/SkPDFGraphicState.h index 50b22686ac..4323252bcd 100644 --- a/src/pdf/SkPDFGraphicState.h +++ b/src/pdf/SkPDFGraphicState.h @@ -20,24 +20,15 @@ class SkPDFCanon; be installed. So that a given dictionary is only output to the pdf file once, we want to canonicalize them. */ -class SkPDFGraphicState final : public SkPDFObject { - -public: +namespace SkPDFGraphicState { enum SkPDFSMaskMode { kAlpha_SMaskMode, kLuminosity_SMaskMode }; - // Override emitObject so that we can populate the dictionary on - // demand. - void emitObject(SkWStream* stream, - const SkPDFObjNumMap& objNumMap) const override; - /** Get the graphic state for the passed SkPaint. - * @param paint The SkPaint to emulate. */ - static sk_sp GetGraphicStateForPaint(SkPDFCanon* canon, - const SkPaint& paint); + sk_sp GetGraphicStateForPaint(SkPDFCanon*, const SkPaint&); /** Make a graphic state that only sets the passed soft mask. * @param sMask The form xobject to use as a soft mask. @@ -46,29 +37,34 @@ public: * * These are not de-duped. */ - static sk_sp GetSMaskGraphicState(sk_sp sMask, - bool invert, - SkPDFSMaskMode sMaskMode, - SkPDFCanon* canon); - - static sk_sp MakeInvertFunction(); + sk_sp GetSMaskGraphicState(sk_sp sMask, + bool invert, + SkPDFSMaskMode sMaskMode, + SkPDFCanon* canon); - bool operator==(const SkPDFGraphicState& rhs) const { - return 0 == memcmp(&fStrokeWidth, &rhs.fStrokeWidth, 12); - } - uint32_t hash() const { return SkOpts::hash(&fStrokeWidth, 12); } + sk_sp MakeInvertFunction(); +} -private: - const SkScalar fStrokeWidth; - const SkScalar fStrokeMiter; - const uint8_t fAlpha; - const uint8_t fStrokeCap; // SkPaint::Cap - const uint8_t fStrokeJoin; // SkPaint::Join - const uint8_t fMode; // SkBlendMode - - SkPDFGraphicState(const SkPaint&); +SK_BEGIN_REQUIRE_DENSE +struct SkPDFStrokeGraphicState { + SkScalar fStrokeWidth; + SkScalar fStrokeMiter; + uint8_t fStrokeCap; // SkPaint::Cap + uint8_t fStrokeJoin; // SkPaint::Join + uint8_t fAlpha; + uint8_t fBlendMode; + bool operator==(const SkPDFStrokeGraphicState& o) const { return !memcmp(this, &o, sizeof(o)); } + bool operator!=(const SkPDFStrokeGraphicState& o) const { return !(*this == o); } +}; +SK_END_REQUIRE_DENSE - typedef SkPDFDict INHERITED; +SK_BEGIN_REQUIRE_DENSE +struct SkPDFFillGraphicState { + uint8_t fAlpha; + uint8_t fBlendMode; + bool operator==(const SkPDFFillGraphicState& o) const { return !memcmp(this, &o, sizeof(o)); } + bool operator!=(const SkPDFFillGraphicState& o) const { return !(*this == o); } }; +SK_END_REQUIRE_DENSE #endif diff --git a/src/pdf/SkPDFTypes.cpp b/src/pdf/SkPDFTypes.cpp index 614a6eb2f2..c256ff925d 100644 --- a/src/pdf/SkPDFTypes.cpp +++ b/src/pdf/SkPDFTypes.cpp @@ -384,6 +384,10 @@ void SkPDFDict::addResources(SkPDFObjNumMap* catalog) const { int SkPDFDict::size() const { return fRecords.count(); } +void SkPDFDict::reserve(int n) { + fRecords.reserve(n); +} + void SkPDFDict::insertObjRef(const char key[], sk_sp objSp) { fRecords.emplace_back(Record{SkPDFUnion::Name(key), SkPDFUnion::ObjRef(std::move(objSp))}); } diff --git a/src/pdf/SkPDFTypes.h b/src/pdf/SkPDFTypes.h index 06e4858fbe..af5cdaf4f7 100644 --- a/src/pdf/SkPDFTypes.h +++ b/src/pdf/SkPDFTypes.h @@ -247,6 +247,9 @@ public: */ int size() const; + /** Preallocate space for n key-value pairs */ + void reserve(int n); + /** Add the value to the dictionary with the given key. * @param key The text of the key for this dictionary entry. * @param value The value for this dictionary entry. -- cgit v1.2.3