1/*
2 * Copyright 2011 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "src/pdf/SkPDFGraphicState.h"
9
10#include "include/core/SkData.h"
11#include "include/core/SkPaint.h"
12#include "include/docs/SkPDFDocument.h"
13#include "include/private/SkTo.h"
14#include "src/pdf/SkPDFDocumentPriv.h"
15#include "src/pdf/SkPDFFormXObject.h"
16#include "src/pdf/SkPDFUtils.h"
17
18static const char* as_pdf_blend_mode_name(SkBlendMode mode) {
19 const char* name = SkPDFUtils::BlendModeName(mode);
20 SkASSERT(name);
21 return name;
22}
23
24static int to_stroke_cap(uint8_t cap) {
25 // PDF32000.book section 8.4.3.3 "Line Cap Style"
26 switch ((SkPaint::Cap)cap) {
27 case SkPaint::kButt_Cap: return 0;
28 case SkPaint::kRound_Cap: return 1;
29 case SkPaint::kSquare_Cap: return 2;
30 default: SkASSERT(false); return 0;
31 }
32}
33
34static int to_stroke_join(uint8_t join) {
35 // PDF32000.book section 8.4.3.4 "Line Join Style"
36 switch ((SkPaint::Join)join) {
37 case SkPaint::kMiter_Join: return 0;
38 case SkPaint::kRound_Join: return 1;
39 case SkPaint::kBevel_Join: return 2;
40 default: SkASSERT(false); return 0;
41 }
42}
43
44// If a SkXfermode is unsupported in PDF, this function returns
45// SrcOver, otherwise, it returns that Xfermode as a Mode.
46static uint8_t pdf_blend_mode(SkBlendMode mode) {
47 if (!SkPDFUtils::BlendModeName(mode)
48 || SkBlendMode::kXor == mode
49 || SkBlendMode::kPlus == mode)
50 {
51 mode = SkBlendMode::kSrcOver;
52 }
53 return SkToU8((unsigned)mode);
54}
55
56SkPDFIndirectReference SkPDFGraphicState::GetGraphicStateForPaint(SkPDFDocument* doc,
57 const SkPaint& p) {
58 SkASSERT(doc);
59 if (SkPaint::kFill_Style == p.getStyle()) {
60 SkPDFFillGraphicState fillKey = {p.getColor4f().fA, pdf_blend_mode(p.getBlendMode())};
61 auto& fillMap = doc->fFillGSMap;
62 if (SkPDFIndirectReference* statePtr = fillMap.find(fillKey)) {
63 return *statePtr;
64 }
65 SkPDFDict state;
66 state.reserve(2);
67 state.insertColorComponentF("ca", fillKey.fAlpha);
68 state.insertName("BM", as_pdf_blend_mode_name((SkBlendMode)fillKey.fBlendMode));
69 SkPDFIndirectReference ref = doc->emit(state);
70 fillMap.set(fillKey, ref);
71 return ref;
72 } else {
73 SkPDFStrokeGraphicState strokeKey = {
74 p.getStrokeWidth(),
75 p.getStrokeMiter(),
76 p.getColor4f().fA,
77 SkToU8(p.getStrokeCap()),
78 SkToU8(p.getStrokeJoin()),
79 pdf_blend_mode(p.getBlendMode())
80 };
81 auto& sMap = doc->fStrokeGSMap;
82 if (SkPDFIndirectReference* statePtr = sMap.find(strokeKey)) {
83 return *statePtr;
84 }
85 SkPDFDict state;
86 state.reserve(8);
87 state.insertColorComponentF("CA", strokeKey.fAlpha);
88 state.insertColorComponentF("ca", strokeKey.fAlpha);
89 state.insertInt("LC", to_stroke_cap(strokeKey.fStrokeCap));
90 state.insertInt("LJ", to_stroke_join(strokeKey.fStrokeJoin));
91 state.insertScalar("LW", strokeKey.fStrokeWidth);
92 state.insertScalar("ML", strokeKey.fStrokeMiter);
93 state.insertBool("SA", true); // SA = Auto stroke adjustment.
94 state.insertName("BM", as_pdf_blend_mode_name((SkBlendMode)strokeKey.fBlendMode));
95 SkPDFIndirectReference ref = doc->emit(state);
96 sMap.set(strokeKey, ref);
97 return ref;
98 }
99}
100
101////////////////////////////////////////////////////////////////////////////////
102
103static SkPDFIndirectReference make_invert_function(SkPDFDocument* doc) {
104 // Acrobat crashes if we use a type 0 function, kpdf crashes if we use
105 // a type 2 function, so we use a type 4 function.
106 static const char psInvert[] = "{1 exch sub}";
107 // Do not copy the trailing '\0' into the SkData.
108 auto invertFunction = SkData::MakeWithoutCopy(psInvert, strlen(psInvert));
109
110 std::unique_ptr<SkPDFDict> dict = SkPDFMakeDict();
111 dict->insertInt("FunctionType", 4);
112 dict->insertObject("Domain", SkPDFMakeArray(0, 1));
113 dict->insertObject("Range", SkPDFMakeArray(0, 1));
114 return SkPDFStreamOut(std::move(dict), SkMemoryStream::Make(std::move(invertFunction)), doc);
115}
116
117SkPDFIndirectReference SkPDFGraphicState::GetSMaskGraphicState(SkPDFIndirectReference sMask,
118 bool invert,
119 SkPDFSMaskMode sMaskMode,
120 SkPDFDocument* doc) {
121 // The practical chances of using the same mask more than once are unlikely
122 // enough that it's not worth canonicalizing.
123 auto sMaskDict = SkPDFMakeDict("Mask");
124 if (sMaskMode == kAlpha_SMaskMode) {
125 sMaskDict->insertName("S", "Alpha");
126 } else if (sMaskMode == kLuminosity_SMaskMode) {
127 sMaskDict->insertName("S", "Luminosity");
128 }
129 sMaskDict->insertRef("G", sMask);
130 if (invert) {
131 // let the doc deduplicate this object.
132 if (doc->fInvertFunction == SkPDFIndirectReference()) {
133 doc->fInvertFunction = make_invert_function(doc);
134 }
135 sMaskDict->insertRef("TR", doc->fInvertFunction);
136 }
137 SkPDFDict result("ExtGState");
138 result.insertObject("SMask", std::move(sMaskDict));
139 return doc->emit(result);
140}
141