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