• 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 
to_path(const SkRect & r)11 static SkPath to_path(const SkRect& r) {
12     SkPath p;
13     p.addRect(r);
14     return p;
15 }
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         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 
is_complex_clip(const SkClipStack & stack)63 static 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 
77 template <typename F>
apply_clip(const SkClipStack & stack,const SkRect & outerBounds,F fn)78 static 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 
append_clip_path(const SkPath & clipPath,SkWStream * wStream)106 static 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 
append_clip(const SkClipStack & clipStack,const SkIRect & bounds,SkWStream * wStream)118 static 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 
updateClip(const SkClipStack * clipStack,const SkIRect & bounds)150 void 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 
updateMatrix(const SkMatrix & matrix)173 void 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 
updateDrawingState(const SkPDFGraphicStackState::Entry & state)195 void 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 
push()229 void SkPDFGraphicStackState::push() {
230     SkASSERT(fStackDepth < kMaxStackDepth);
231     fContentStream->writeText("q\n");
232     ++fStackDepth;
233     fEntries[fStackDepth] = fEntries[fStackDepth - 1];
234 }
235 
pop()236 void SkPDFGraphicStackState::pop() {
237     SkASSERT(fStackDepth > 0);
238     fContentStream->writeText("Q\n");
239     fEntries[fStackDepth] = SkPDFGraphicStackState::Entry();
240     --fStackDepth;
241 }
242 
drainStack()243 void SkPDFGraphicStackState::drainStack() {
244     if (fContentStream) {
245         while (fStackDepth) {
246             this->pop();
247         }
248     }
249     SkASSERT(fStackDepth == 0);
250 }
251 
252