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:
parent
a062258e76
commit
80fa7cea93
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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))});
|
||||
}
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user