xref: /aosp_15_r20/external/skia/src/pdf/SkPDFGraphicStackState.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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