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