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 <bungeman@google.com>
Commit-Queue: Hal Canary <halcanary@google.com>
This commit is contained in:
Hal Canary 2017-06-28 16:04:20 -04:00 committed by Skia Commit-Bot
parent a062258e76
commit 80fa7cea93
7 changed files with 123 additions and 197 deletions

View File

@ -12,9 +12,7 @@
////////////////////////////////////////////////////////////////////////////////
SkPDFCanon::~SkPDFCanon() {
fGraphicStateRecords.foreach ([](WrapGS w) { w.fPtr->unref(); });
}
SkPDFCanon::~SkPDFCanon() {}
////////////////////////////////////////////////////////////////////////////////
@ -56,19 +54,3 @@ void SkPDFCanon::addImageShader(sk_sp<SkPDFObject> 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);
}

View File

@ -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<SkPDFObject> findImageShader(const SkPDFShader::State&) const;
void addImageShader(sk_sp<SkPDFObject>, SkPDFShader::State);
const SkPDFGraphicState* findGraphicState(const SkPDFGraphicState&) const;
void addGraphicState(const SkPDFGraphicState*);
SkTHashMap<SkBitmapKey, sk_sp<SkPDFObject>> fPDFBitmapMap;
SkTHashMap<uint32_t, std::unique_ptr<SkAdvancedTypefaceMetrics>> fTypefaceMetrics;
SkTHashMap<uint32_t, sk_sp<SkPDFDict>> fFontDescriptors;
SkTHashMap<uint64_t, sk_sp<SkPDFFont>> fFontMap;
SkTHashMap<SkPDFStrokeGraphicState, sk_sp<SkPDFDict>> fStrokeGSMap;
SkTHashMap<SkPDFFillGraphicState, sk_sp<SkPDFDict>> fFillGSMap;
sk_sp<SkPixelSerializer> fPixelSerializer;
sk_sp<SkPDFStream> fInvertFunction;
sk_sp<SkPDFDict> fNoSmaskGraphicState;
@ -69,22 +56,5 @@ private:
SkTArray<ShaderRec> fFunctionShaderRecords;
SkTArray<ShaderRec> fAlphaShaderRecords;
SkTArray<ShaderRec> 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<WrapGS, WrapGS::Hash> fGraphicStateRecords;
};
#endif // SkPDFCanon_DEFINED

View File

@ -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<SkPDFGraphicState> newGraphicState;
sk_sp<SkPDFDict> newGraphicState;
if (color == paint.getColor()) {
newGraphicState = SkPDFGraphicState::GetGraphicStateForPaint(fDocument->canon(), paint);
} else {

View File

@ -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";
// 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";
// TODO(vandebo): Figure out if we can support more of these modes.
case SkBlendMode::kXor:
case SkBlendMode::kPlus:
return nullptr;
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;
}
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> SkPDFGraphicState::GetGraphicStateForPaint(SkPDFCanon* canon,
const SkPaint& paint) {
sk_sp<SkPDFDict> 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<SkPDFGraphicState>(SkRef(const_cast<SkPDFGraphicState*>(canonGS)));
if (SkPaint::kFill_Style == p.getStyle()) {
SkPDFFillGraphicState fillKey = {p.getAlpha(), pdf_blend_mode(p.getBlendMode())};
auto& fillMap = canon->fFillGSMap;
if (sk_sp<SkPDFDict>* statePtr = fillMap.find(fillKey)) {
return *statePtr;
}
auto state = sk_make_sp<SkPDFDict>();
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<SkPDFDict>* statePtr = sMap.find(strokeKey)) {
return *statePtr;
}
auto state = sk_make_sp<SkPDFDict>();
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<SkPDFGraphicState> pdfGraphicState(new SkPDFGraphicState(paint));
canon->addGraphicState(pdfGraphicState.get());
return pdfGraphicState;
}
////////////////////////////////////////////////////////////////////////////////
static sk_sp<SkPDFStream> 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<SkPDFDict> SkPDFGraphicState::GetSMaskGraphicState(
}
sMaskDict->insertObjRef("TR", invertFunction);
}
auto result = sk_make_sp<SkPDFDict>("ExtGState");
result->insertObject("SMask", std::move(sMaskDict));
return result;
}
void SkPDFGraphicState::emitObject(
SkWStream* stream,
const SkPDFObjNumMap& objNumMap) const {
auto dict = sk_make_sp<SkPDFDict>("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);
}

View File

@ -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<SkPDFGraphicState> GetGraphicStateForPaint(SkPDFCanon* canon,
const SkPaint& paint);
sk_sp<SkPDFDict> 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<SkPDFDict> GetSMaskGraphicState(sk_sp<SkPDFObject> sMask,
bool invert,
SkPDFSMaskMode sMaskMode,
SkPDFCanon* canon);
sk_sp<SkPDFDict> GetSMaskGraphicState(sk_sp<SkPDFObject> sMask,
bool invert,
SkPDFSMaskMode sMaskMode,
SkPDFCanon* canon);
static sk_sp<SkPDFStream> MakeInvertFunction();
sk_sp<SkPDFStream> MakeInvertFunction();
}
bool operator==(const SkPDFGraphicState& rhs) const {
return 0 == memcmp(&fStrokeWidth, &rhs.fStrokeWidth, 12);
}
uint32_t hash() const { return SkOpts::hash(&fStrokeWidth, 12); }
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&);
typedef SkPDFDict INHERITED;
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
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

View File

@ -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<SkPDFObject> objSp) {
fRecords.emplace_back(Record{SkPDFUnion::Name(key), SkPDFUnion::ObjRef(std::move(objSp))});
}

View File

@ -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.