| // Copyright 2019 Google LLC. |
| // Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. |
| |
| #include "src/pdf/SkPDFGraphicStackState.h" |
| |
| #include "include/core/SkStream.h" |
| #include "include/pathops/SkPathOps.h" |
| #include "src/pdf/SkPDFUtils.h" |
| #include "src/utils/SkClipStackUtils.h" |
| |
| static void emit_pdf_color(SkColor4f color, SkWStream* result) { |
| SkASSERT(color.fA == 1); // We handle alpha elsewhere. |
| SkPDFUtils::AppendColorComponentF(color.fR, result); |
| result->writeText(" "); |
| SkPDFUtils::AppendColorComponentF(color.fG, result); |
| result->writeText(" "); |
| SkPDFUtils::AppendColorComponentF(color.fB, result); |
| result->writeText(" "); |
| } |
| |
| static SkRect rect_intersect(SkRect u, SkRect v) { |
| if (u.isEmpty() || v.isEmpty()) { return {0, 0, 0, 0}; } |
| return u.intersect(v) ? u : SkRect{0, 0, 0, 0}; |
| } |
| |
| // Test to see if the clipstack is a simple rect, If so, we can avoid all PathOps code |
| // and speed thing up. |
| static bool is_rect(const SkClipStack& clipStack, const SkRect& bounds, SkRect* dst) { |
| SkRect currentClip = bounds; |
| SkClipStack::Iter iter(clipStack, SkClipStack::Iter::kBottom_IterStart); |
| while (const SkClipStack::Element* element = iter.next()) { |
| SkRect elementRect{0, 0, 0, 0}; |
| switch (element->getDeviceSpaceType()) { |
| case SkClipStack::Element::DeviceSpaceType::kEmpty: |
| break; |
| case SkClipStack::Element::DeviceSpaceType::kRect: |
| elementRect = element->getDeviceSpaceRect(); |
| break; |
| default: |
| return false; |
| } |
| if (element->isReplaceOp()) { |
| currentClip = rect_intersect(bounds, elementRect); |
| } else if (element->getOp() == SkClipOp::kIntersect) { |
| currentClip = rect_intersect(currentClip, elementRect); |
| } else { |
| return false; |
| } |
| } |
| *dst = currentClip; |
| return true; |
| } |
| |
| // TODO: When there's no expanding clip ops, this function may not be necessary anymore. |
| static bool is_complex_clip(const SkClipStack& stack) { |
| SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart); |
| while (const SkClipStack::Element* element = iter.next()) { |
| if (element->isReplaceOp() || |
| (element->getOp() != SkClipOp::kDifference && |
| element->getOp() != SkClipOp::kIntersect)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| template <typename F> |
| static void apply_clip(const SkClipStack& stack, const SkRect& outerBounds, F fn) { |
| // assumes clipstack is not complex. |
| constexpr SkRect kHuge{-30000, -30000, 30000, 30000}; |
| SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart); |
| SkRect bounds = outerBounds; |
| while (const SkClipStack::Element* element = iter.next()) { |
| SkPath operand; |
| element->asDeviceSpacePath(&operand); |
| SkPathOp op; |
| switch (element->getOp()) { |
| case SkClipOp::kDifference: op = kDifference_SkPathOp; break; |
| case SkClipOp::kIntersect: op = kIntersect_SkPathOp; break; |
| default: SkASSERT(false); return; |
| } |
| if (op == kDifference_SkPathOp || |
| operand.isInverseFillType() || |
| !kHuge.contains(operand.getBounds())) |
| { |
| Op(SkPath::Rect(bounds), operand, op, &operand); |
| } |
| SkASSERT(!operand.isInverseFillType()); |
| fn(operand); |
| if (!bounds.intersect(operand.getBounds())) { |
| return; // return early; |
| } |
| } |
| } |
| |
| static void append_clip_path(const SkPath& clipPath, SkWStream* wStream) { |
| SkPDFUtils::EmitPath(clipPath, SkPaint::kFill_Style, wStream); |
| SkPathFillType clipFill = clipPath.getFillType(); |
| NOT_IMPLEMENTED(clipFill == SkPathFillType::kInverseEvenOdd, false); |
| NOT_IMPLEMENTED(clipFill == SkPathFillType::kInverseWinding, false); |
| if (clipFill == SkPathFillType::kEvenOdd) { |
| wStream->writeText("W* n\n"); |
| } else { |
| wStream->writeText("W n\n"); |
| } |
| } |
| |
| static void append_clip(const SkClipStack& clipStack, |
| const SkIRect& bounds, |
| SkWStream* wStream) { |
| // The bounds are slightly outset to ensure this is correct in the |
| // face of floating-point accuracy and possible SkRegion bitmap |
| // approximations. |
| SkRect outsetBounds = SkRect::Make(bounds.makeOutset(1, 1)); |
| |
| SkRect clipStackRect; |
| if (is_rect(clipStack, outsetBounds, &clipStackRect)) { |
| SkPDFUtils::AppendRectangle(clipStackRect, wStream); |
| wStream->writeText("W* n\n"); |
| return; |
| } |
| |
| if (is_complex_clip(clipStack)) { |
| SkPath clipPath; |
| SkClipStack_AsPath(clipStack, &clipPath); |
| if (Op(clipPath, SkPath::Rect(outsetBounds), kIntersect_SkPathOp, &clipPath)) { |
| append_clip_path(clipPath, wStream); |
| } |
| // If Op() fails (pathological case; e.g. input values are |
| // extremely large or NaN), emit no clip at all. |
| } else { |
| apply_clip(clipStack, outsetBounds, [wStream](const SkPath& path) { |
| append_clip_path(path, wStream); |
| }); |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| void SkPDFGraphicStackState::updateClip(const SkClipStack* clipStack, const SkIRect& bounds) { |
| uint32_t clipStackGenID = clipStack ? clipStack->getTopmostGenID() |
| : SkClipStack::kWideOpenGenID; |
| if (clipStackGenID == currentEntry()->fClipStackGenID) { |
| return; |
| } |
| while (fStackDepth > 0) { |
| this->pop(); |
| if (clipStackGenID == currentEntry()->fClipStackGenID) { |
| return; |
| } |
| } |
| SkASSERT(currentEntry()->fClipStackGenID == SkClipStack::kWideOpenGenID); |
| if (clipStackGenID != SkClipStack::kWideOpenGenID) { |
| SkASSERT(clipStack); |
| this->push(); |
| |
| currentEntry()->fClipStackGenID = clipStackGenID; |
| append_clip(*clipStack, bounds, fContentStream); |
| } |
| } |
| |
| |
| void SkPDFGraphicStackState::updateMatrix(const SkMatrix& matrix) { |
| if (matrix == currentEntry()->fMatrix) { |
| return; |
| } |
| |
| if (currentEntry()->fMatrix.getType() != SkMatrix::kIdentity_Mask) { |
| SkASSERT(fStackDepth > 0); |
| SkASSERT(fEntries[fStackDepth].fClipStackGenID == |
| fEntries[fStackDepth -1].fClipStackGenID); |
| this->pop(); |
| |
| SkASSERT(currentEntry()->fMatrix.getType() == SkMatrix::kIdentity_Mask); |
| } |
| if (matrix.getType() == SkMatrix::kIdentity_Mask) { |
| return; |
| } |
| |
| this->push(); |
| SkPDFUtils::AppendTransform(matrix, fContentStream); |
| currentEntry()->fMatrix = matrix; |
| } |
| |
| void SkPDFGraphicStackState::updateDrawingState(const SkPDFGraphicStackState::Entry& state) { |
| // PDF treats a shader as a color, so we only set one or the other. |
| if (state.fShaderIndex >= 0) { |
| if (state.fShaderIndex != currentEntry()->fShaderIndex) { |
| SkPDFUtils::ApplyPattern(state.fShaderIndex, fContentStream); |
| currentEntry()->fShaderIndex = state.fShaderIndex; |
| } |
| } else if (state.fColor != currentEntry()->fColor || currentEntry()->fShaderIndex >= 0) { |
| emit_pdf_color(state.fColor, fContentStream); |
| fContentStream->writeText("RG "); |
| emit_pdf_color(state.fColor, fContentStream); |
| fContentStream->writeText("rg\n"); |
| currentEntry()->fColor = state.fColor; |
| currentEntry()->fShaderIndex = -1; |
| } |
| |
| if (state.fGraphicStateIndex != currentEntry()->fGraphicStateIndex) { |
| SkPDFUtils::ApplyGraphicState(state.fGraphicStateIndex, fContentStream); |
| currentEntry()->fGraphicStateIndex = state.fGraphicStateIndex; |
| } |
| |
| if (state.fTextScaleX) { |
| if (state.fTextScaleX != currentEntry()->fTextScaleX) { |
| SkScalar pdfScale = state.fTextScaleX * 100; |
| SkPDFUtils::AppendScalar(pdfScale, fContentStream); |
| fContentStream->writeText(" Tz\n"); |
| currentEntry()->fTextScaleX = state.fTextScaleX; |
| } |
| } |
| } |
| |
| void SkPDFGraphicStackState::push() { |
| SkASSERT(fStackDepth < kMaxStackDepth); |
| fContentStream->writeText("q\n"); |
| ++fStackDepth; |
| fEntries[fStackDepth] = fEntries[fStackDepth - 1]; |
| } |
| |
| void SkPDFGraphicStackState::pop() { |
| SkASSERT(fStackDepth > 0); |
| fContentStream->writeText("Q\n"); |
| fEntries[fStackDepth] = SkPDFGraphicStackState::Entry(); |
| --fStackDepth; |
| } |
| |
| void SkPDFGraphicStackState::drainStack() { |
| if (fContentStream) { |
| while (fStackDepth) { |
| this->pop(); |
| } |
| } |
| SkASSERT(fStackDepth == 0); |
| } |