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