1 // Copyright 2019 Google LLC.
2 // Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
3
4 #include "src/pdf/SkPDFGraphicStackState.h"
5
6 #include "include/core/SkStream.h"
7 #include "include/pathops/SkPathOps.h"
8 #include "src/pdf/SkPDFUtils.h"
9
to_path(const SkRect & r)10 static SkPath to_path(const SkRect& r) {
11 SkPath p;
12 p.addRect(r);
13 return p;
14 }
15
emit_pdf_color(SkColor4f color,SkWStream * result)16 static void emit_pdf_color(SkColor4f color, SkWStream* result) {
17 SkASSERT(color.fA == 1); // We handle alpha elsewhere.
18 SkPDFUtils::AppendColorComponentF(color.fR, result);
19 result->writeText(" ");
20 SkPDFUtils::AppendColorComponentF(color.fG, result);
21 result->writeText(" ");
22 SkPDFUtils::AppendColorComponentF(color.fB, result);
23 result->writeText(" ");
24 }
25
rect_intersect(SkRect u,SkRect v)26 static SkRect rect_intersect(SkRect u, SkRect v) {
27 if (u.isEmpty() || v.isEmpty()) { return {0, 0, 0, 0}; }
28 return u.intersect(v) ? u : SkRect{0, 0, 0, 0};
29 }
30
31 // Test to see if the clipstack is a simple rect, If so, we can avoid all PathOps code
32 // and speed thing up.
is_rect(const SkClipStack & clipStack,const SkRect & bounds,SkRect * dst)33 static bool is_rect(const SkClipStack& clipStack, const SkRect& bounds, SkRect* dst) {
34 SkRect currentClip = bounds;
35 SkClipStack::Iter iter(clipStack, SkClipStack::Iter::kBottom_IterStart);
36 while (const SkClipStack::Element* element = iter.next()) {
37 SkRect elementRect{0, 0, 0, 0};
38 switch (element->getDeviceSpaceType()) {
39 case SkClipStack::Element::DeviceSpaceType::kEmpty:
40 break;
41 case SkClipStack::Element::DeviceSpaceType::kRect:
42 elementRect = element->getDeviceSpaceRect();
43 break;
44 default:
45 return false;
46 }
47 switch (element->getOp()) {
48 case kReplace_SkClipOp:
49 currentClip = rect_intersect(bounds, elementRect);
50 break;
51 case SkClipOp::kIntersect:
52 currentClip = rect_intersect(currentClip, elementRect);
53 break;
54 default:
55 return false;
56 }
57 }
58 *dst = currentClip;
59 return true;
60 }
61
is_complex_clip(const SkClipStack & stack)62 static bool is_complex_clip(const SkClipStack& stack) {
63 SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart);
64 while (const SkClipStack::Element* element = iter.next()) {
65 switch (element->getOp()) {
66 case SkClipOp::kDifference:
67 case SkClipOp::kIntersect:
68 break;
69 default:
70 return true;
71 }
72 }
73 return false;
74 }
75
76 template <typename F>
apply_clip(const SkClipStack & stack,const SkRect & outerBounds,F fn)77 static void apply_clip(const SkClipStack& stack, const SkRect& outerBounds, F fn) {
78 // assumes clipstack is not complex.
79 constexpr SkRect kHuge{-30000, -30000, 30000, 30000};
80 SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart);
81 SkRect bounds = outerBounds;
82 while (const SkClipStack::Element* element = iter.next()) {
83 SkPath operand;
84 element->asDeviceSpacePath(&operand);
85 SkPathOp op;
86 switch (element->getOp()) {
87 case SkClipOp::kDifference: op = kDifference_SkPathOp; break;
88 case SkClipOp::kIntersect: op = kIntersect_SkPathOp; break;
89 default: SkASSERT(false); return;
90 }
91 if (op == kDifference_SkPathOp ||
92 operand.isInverseFillType() ||
93 !kHuge.contains(operand.getBounds()))
94 {
95 Op(to_path(bounds), operand, op, &operand);
96 }
97 SkASSERT(!operand.isInverseFillType());
98 fn(operand);
99 if (!bounds.intersect(operand.getBounds())) {
100 return; // return early;
101 }
102 }
103 }
104
append_clip_path(const SkPath & clipPath,SkWStream * wStream)105 static void append_clip_path(const SkPath& clipPath, SkWStream* wStream) {
106 SkPDFUtils::EmitPath(clipPath, SkPaint::kFill_Style, wStream);
107 SkPath::FillType clipFill = clipPath.getFillType();
108 NOT_IMPLEMENTED(clipFill == SkPath::kInverseEvenOdd_FillType, false);
109 NOT_IMPLEMENTED(clipFill == SkPath::kInverseWinding_FillType, false);
110 if (clipFill == SkPath::kEvenOdd_FillType) {
111 wStream->writeText("W* n\n");
112 } else {
113 wStream->writeText("W n\n");
114 }
115 }
116
append_clip(const SkClipStack & clipStack,const SkIRect & bounds,SkWStream * wStream)117 static void append_clip(const SkClipStack& clipStack,
118 const SkIRect& bounds,
119 SkWStream* wStream) {
120 // The bounds are slightly outset to ensure this is correct in the
121 // face of floating-point accuracy and possible SkRegion bitmap
122 // approximations.
123 SkRect outsetBounds = SkRect::Make(bounds.makeOutset(1, 1));
124
125 SkRect clipStackRect;
126 if (is_rect(clipStack, outsetBounds, &clipStackRect)) {
127 SkPDFUtils::AppendRectangle(clipStackRect, wStream);
128 wStream->writeText("W* n\n");
129 return;
130 }
131
132 if (is_complex_clip(clipStack)) {
133 SkPath clipPath;
134 (void)clipStack.asPath(&clipPath);
135 if (Op(clipPath, to_path(outsetBounds), kIntersect_SkPathOp, &clipPath)) {
136 append_clip_path(clipPath, wStream);
137 }
138 // If Op() fails (pathological case; e.g. input values are
139 // extremely large or NaN), emit no clip at all.
140 } else {
141 apply_clip(clipStack, outsetBounds, [wStream](const SkPath& path) {
142 append_clip_path(path, wStream);
143 });
144 }
145 }
146
147 ////////////////////////////////////////////////////////////////////////////////
148
updateClip(const SkClipStack * clipStack,const SkIRect & bounds)149 void SkPDFGraphicStackState::updateClip(const SkClipStack* clipStack, const SkIRect& bounds) {
150 uint32_t clipStackGenID = clipStack ? clipStack->getTopmostGenID()
151 : SkClipStack::kWideOpenGenID;
152 if (clipStackGenID == currentEntry()->fClipStackGenID) {
153 return;
154 }
155 while (fStackDepth > 0) {
156 this->pop();
157 if (clipStackGenID == currentEntry()->fClipStackGenID) {
158 return;
159 }
160 }
161 SkASSERT(currentEntry()->fClipStackGenID == SkClipStack::kWideOpenGenID);
162 if (clipStackGenID != SkClipStack::kWideOpenGenID) {
163 SkASSERT(clipStack);
164 this->push();
165
166 currentEntry()->fClipStackGenID = clipStackGenID;
167 append_clip(*clipStack, bounds, fContentStream);
168 }
169 }
170
171
updateMatrix(const SkMatrix & matrix)172 void SkPDFGraphicStackState::updateMatrix(const SkMatrix& matrix) {
173 if (matrix == currentEntry()->fMatrix) {
174 return;
175 }
176
177 if (currentEntry()->fMatrix.getType() != SkMatrix::kIdentity_Mask) {
178 SkASSERT(fStackDepth > 0);
179 SkASSERT(fEntries[fStackDepth].fClipStackGenID ==
180 fEntries[fStackDepth -1].fClipStackGenID);
181 this->pop();
182
183 SkASSERT(currentEntry()->fMatrix.getType() == SkMatrix::kIdentity_Mask);
184 }
185 if (matrix.getType() == SkMatrix::kIdentity_Mask) {
186 return;
187 }
188
189 this->push();
190 SkPDFUtils::AppendTransform(matrix, fContentStream);
191 currentEntry()->fMatrix = matrix;
192 }
193
updateDrawingState(const SkPDFGraphicStackState::Entry & state)194 void SkPDFGraphicStackState::updateDrawingState(const SkPDFGraphicStackState::Entry& state) {
195 // PDF treats a shader as a color, so we only set one or the other.
196 if (state.fShaderIndex >= 0) {
197 if (state.fShaderIndex != currentEntry()->fShaderIndex) {
198 SkPDFUtils::ApplyPattern(state.fShaderIndex, fContentStream);
199 currentEntry()->fShaderIndex = state.fShaderIndex;
200 }
201 } else {
202 if (state.fColor != currentEntry()->fColor ||
203 currentEntry()->fShaderIndex >= 0) {
204 emit_pdf_color(state.fColor, fContentStream);
205 fContentStream->writeText("RG ");
206 emit_pdf_color(state.fColor, fContentStream);
207 fContentStream->writeText("rg\n");
208 currentEntry()->fColor = state.fColor;
209 currentEntry()->fShaderIndex = -1;
210 }
211 }
212
213 if (state.fGraphicStateIndex != currentEntry()->fGraphicStateIndex) {
214 SkPDFUtils::ApplyGraphicState(state.fGraphicStateIndex, fContentStream);
215 currentEntry()->fGraphicStateIndex = state.fGraphicStateIndex;
216 }
217
218 if (state.fTextScaleX) {
219 if (state.fTextScaleX != currentEntry()->fTextScaleX) {
220 SkScalar pdfScale = state.fTextScaleX * 100;
221 SkPDFUtils::AppendScalar(pdfScale, fContentStream);
222 fContentStream->writeText(" Tz\n");
223 currentEntry()->fTextScaleX = state.fTextScaleX;
224 }
225 }
226 }
227
push()228 void SkPDFGraphicStackState::push() {
229 SkASSERT(fStackDepth < kMaxStackDepth);
230 fContentStream->writeText("q\n");
231 ++fStackDepth;
232 fEntries[fStackDepth] = fEntries[fStackDepth - 1];
233 }
234
pop()235 void SkPDFGraphicStackState::pop() {
236 SkASSERT(fStackDepth > 0);
237 fContentStream->writeText("Q\n");
238 fEntries[fStackDepth] = SkPDFGraphicStackState::Entry();
239 --fStackDepth;
240 }
241
drainStack()242 void SkPDFGraphicStackState::drainStack() {
243 if (fContentStream) {
244 while (fStackDepth) {
245 this->pop();
246 }
247 }
248 SkASSERT(fStackDepth == 0);
249 }
250
251