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