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.
4 #include "src/pdf/SkPDFGraphicStackState.h"
6 #include "include/core/SkStream.h"
7 #include "include/pathops/SkPathOps.h"
8 #include "src/pdf/SkPDFUtils.h"
9 #include "src/utils/SkClipStackUtils.h"
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(" ");
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};
26 // Test to see if the clipstack is a simple rect, If so, we can avoid all PathOps code
27 // and speed thing up.
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:
36 case SkClipStack::Element::DeviceSpaceType::kRect:
37 elementRect = element->getDeviceSpaceRect();
42 if (element->isReplaceOp()) {
43 currentClip = rect_intersect(bounds, elementRect);
44 } else if (element->getOp() == SkClipOp::kIntersect) {
45 currentClip = rect_intersect(currentClip, elementRect);
54 // TODO: When there's no expanding clip ops, this function may not be necessary anymore.
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)) {
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()) {
75 element->asDeviceSpacePath(&operand);
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;
82 if (op == kDifference_SkPathOp ||
83 operand.isInverseFillType() ||
84 !kHuge.contains(operand.getBounds()))
86 Op(SkPath::Rect(bounds), operand, op, &operand);
88 SkASSERT(!operand.isInverseFillType());
90 if (!bounds.intersect(operand.getBounds())) {
91 return; // return early;
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");
104 wStream->writeText("W n\n");
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
114 SkRect outsetBounds = SkRect::Make(bounds.makeOutset(1, 1));
116 SkRect clipStackRect;
117 if (is_rect(clipStack, outsetBounds, &clipStackRect)) {
118 SkPDFUtils::AppendRectangle(clipStackRect, wStream);
119 wStream->writeText("W* n\n");
123 if (is_complex_clip(clipStack)) {
125 SkClipStack_AsPath(clipStack, &clipPath);
126 if (Op(clipPath, SkPath::Rect(outsetBounds), kIntersect_SkPathOp, &clipPath)) {
127 append_clip_path(clipPath, wStream);
129 // If Op() fails (pathological case; e.g. input values are
130 // extremely large or NaN), emit no clip at all.
132 apply_clip(clipStack, outsetBounds, [wStream](const SkPath& path) {
133 append_clip_path(path, wStream);
138 ////////////////////////////////////////////////////////////////////////////////
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) {
146 while (fStackDepth > 0) {
148 if (clipStackGenID == currentEntry()->fClipStackGenID) {
152 SkASSERT(currentEntry()->fClipStackGenID == SkClipStack::kWideOpenGenID);
153 if (clipStackGenID != SkClipStack::kWideOpenGenID) {
157 currentEntry()->fClipStackGenID = clipStackGenID;
158 append_clip(*clipStack, bounds, fContentStream);
163 void SkPDFGraphicStackState::updateMatrix(const SkMatrix& matrix) {
164 if (matrix == currentEntry()->fMatrix) {
168 if (currentEntry()->fMatrix.getType() != SkMatrix::kIdentity_Mask) {
169 SkASSERT(fStackDepth > 0);
170 SkASSERT(fEntries[fStackDepth].fClipStackGenID ==
171 fEntries[fStackDepth -1].fClipStackGenID);
174 SkASSERT(currentEntry()->fMatrix.getType() == SkMatrix::kIdentity_Mask);
176 if (matrix.getType() == SkMatrix::kIdentity_Mask) {
181 SkPDFUtils::AppendTransform(matrix, fContentStream);
182 currentEntry()->fMatrix = matrix;
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;
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;
204 if (state.fGraphicStateIndex != currentEntry()->fGraphicStateIndex) {
205 SkPDFUtils::ApplyGraphicState(state.fGraphicStateIndex, fContentStream);
206 currentEntry()->fGraphicStateIndex = state.fGraphicStateIndex;
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;
219 void SkPDFGraphicStackState::push() {
220 SkASSERT(fStackDepth < kMaxStackDepth);
221 fContentStream->writeText("q\n");
223 fEntries[fStackDepth] = fEntries[fStackDepth - 1];
226 void SkPDFGraphicStackState::pop() {
227 SkASSERT(fStackDepth > 0);
228 fContentStream->writeText("Q\n");
229 fEntries[fStackDepth] = SkPDFGraphicStackState::Entry();
233 void SkPDFGraphicStackState::drainStack() {
234 if (fContentStream) {
235 while (fStackDepth) {
239 SkASSERT(fStackDepth == 0);