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