blob: 1d94388d569ebaed78e7257ae0ccb4f04651f4e9 [file] [log] [blame]
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001/*
epoger@google.comec3ed6a2011-07-28 14:26:00 +00002 * Copyright 2011 Google Inc.
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00003 *
epoger@google.comec3ed6a2011-07-28 14:26:00 +00004 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00006 */
7
8#include "SkPDFDevice.h"
halcanarye06ca962016-09-09 05:34:55 -07009
halcanaryf59d18a2016-09-16 14:44:57 -070010#include "SkAdvancedTypefaceMetrics.h"
reedf70b5312016-03-04 16:36:20 -080011#include "SkAnnotationKeys.h"
Mike Reed986480a2017-01-13 22:43:16 +000012#include "SkBitmapDevice.h"
martina.kollarovab8d6af12016-06-29 05:12:31 -070013#include "SkBitmapKey.h"
Hal Canary7cbf5e32017-07-12 13:10:23 -040014#include "SkCanvas.h"
Hal Canaryd12a6762017-05-26 17:01:16 -040015#include "SkClipOpPriv.h"
Hal Canary1521c8a2018-03-28 09:51:00 -070016#include "SkClusterator.h"
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000017#include "SkColor.h"
halcanary287d22d2015-09-24 10:20:05 -070018#include "SkColorFilter.h"
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +000019#include "SkDraw.h"
vandebo@chromium.org28be72b2010-11-11 21:37:00 +000020#include "SkGlyphCache.h"
Herb Derby736db102018-07-19 12:52:16 -040021#include "SkGlyphRun.h"
brianosman04a44d02016-09-21 09:46:57 -070022#include "SkImageFilterCache.h"
Hal Canaryd12a6762017-05-26 17:01:16 -040023#include "SkJpegEncoder.h"
halcanary022c2bd2016-09-02 11:29:46 -070024#include "SkMakeUnique.h"
Mike Reed80747ef2018-01-23 15:29:32 -050025#include "SkMaskFilterBase.h"
halcanarydb0dcc72015-03-20 12:31:52 -070026#include "SkPDFBitmap.h"
halcanary7a14b312015-10-01 07:28:13 -070027#include "SkPDFCanon.h"
Hal Canary23564b92018-09-07 14:33:14 -040028#include "SkPDFDocumentPriv.h"
vandebo@chromium.org28be72b2010-11-11 21:37:00 +000029#include "SkPDFFont.h"
vandebo@chromium.orgeb6c7592010-10-26 19:54:45 +000030#include "SkPDFFormXObject.h"
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +000031#include "SkPDFGraphicState.h"
commit-bot@chromium.org47401352013-07-23 21:49:29 +000032#include "SkPDFResourceDict.h"
vandebo@chromium.orgda912d62011-03-08 18:31:02 +000033#include "SkPDFShader.h"
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +000034#include "SkPDFTypes.h"
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +000035#include "SkPDFUtils.h"
Hal Canaryd12a6762017-05-26 17:01:16 -040036#include "SkPath.h"
37#include "SkPathEffect.h"
38#include "SkPathOps.h"
Hal Canaryf50ff392016-09-30 10:25:39 -040039#include "SkPixelRef.h"
scroggo@google.coma8e33a92013-11-08 18:02:53 +000040#include "SkRRect.h"
Hal Canaryd12a6762017-05-26 17:01:16 -040041#include "SkRasterClip.h"
halcanary4871f222016-08-26 13:17:44 -070042#include "SkScopeExit.h"
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000043#include "SkString.h"
reed89443ab2014-06-27 11:34:19 -070044#include "SkSurface.h"
vandebo@chromium.org4e1cc6a2013-01-25 19:27:23 +000045#include "SkTemplates.h"
Florin Malitaab54e732018-07-27 09:47:15 -040046#include "SkTextBlob.h"
halcanarye06ca962016-09-09 05:34:55 -070047#include "SkTextFormatParams.h"
Hal Canaryc640d0d2018-06-13 09:59:02 -040048#include "SkTo.h"
Hal Canaryea60b952018-08-21 11:45:46 -040049#include "SkUTF.h"
halcanarya6814332015-05-27 08:53:36 -070050#include "SkXfermodeInterpretation.h"
Hal Canaryd12a6762017-05-26 17:01:16 -040051
Hal Canary9e41c212018-09-03 12:00:23 -040052#include <vector>
53
Hal Canaryd12a6762017-05-26 17:01:16 -040054#ifndef SK_PDF_MASK_QUALITY
55 // If MASK_QUALITY is in [0,100], will be used for JpegEncoder.
56 // Otherwise, just encode masks losslessly.
57 #define SK_PDF_MASK_QUALITY 50
58 // Since these masks are used for blurry shadows, we shouldn't need
59 // high quality. Raise this value if your shadows have visible JPEG
60 // artifacts.
61 // If SkJpegEncoder::Encode fails, we will fall back to the lossless
62 // encoding.
63#endif
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000064
65// Utility functions
66
Hal Canaryd791a6f2018-09-28 12:14:08 -040067static SkPath to_path(const SkRect& r) {
68 SkPath p;
69 p.addRect(r);
70 return p;
71}
72
Hal Canary51329c92017-06-27 14:28:37 -040073// This function destroys the mask and either frees or takes the pixels.
74sk_sp<SkImage> mask_to_greyscale_image(SkMask* mask) {
75 sk_sp<SkImage> img;
76 SkPixmap pm(SkImageInfo::Make(mask->fBounds.width(), mask->fBounds.height(),
77 kGray_8_SkColorType, kOpaque_SkAlphaType),
78 mask->fImage, mask->fRowBytes);
79 const int imgQuality = SK_PDF_MASK_QUALITY;
80 if (imgQuality <= 100 && imgQuality >= 0) {
81 SkDynamicMemoryWStream buffer;
82 SkJpegEncoder::Options jpegOptions;
83 jpegOptions.fQuality = imgQuality;
84 if (SkJpegEncoder::Encode(&buffer, pm, jpegOptions)) {
85 img = SkImage::MakeFromEncoded(buffer.detachAsData());
86 SkASSERT(img);
87 if (img) {
88 SkMask::FreeImage(mask->fImage);
89 }
90 }
91 }
92 if (!img) {
93 img = SkImage::MakeFromRaster(pm, [](const void* p, void*) { SkMask::FreeImage((void*)p); },
94 nullptr);
95 }
96 *mask = SkMask(); // destructive;
97 return img;
98}
99
Hal Canaryd425a1d2017-07-12 13:13:51 -0400100sk_sp<SkImage> alpha_image_to_greyscale_image(const SkImage* mask) {
101 int w = mask->width(), h = mask->height();
102 SkBitmap greyBitmap;
103 greyBitmap.allocPixels(SkImageInfo::Make(w, h, kGray_8_SkColorType, kOpaque_SkAlphaType));
104 if (!mask->readPixels(SkImageInfo::MakeA8(w, h),
105 greyBitmap.getPixels(), greyBitmap.rowBytes(), 0, 0)) {
106 return nullptr;
107 }
108 return SkImage::MakeFromBitmap(greyBitmap);
109}
110
Mike Reeda1361362017-03-07 09:37:29 -0500111static void draw_points(SkCanvas::PointMode mode,
112 size_t count,
113 const SkPoint* points,
114 const SkPaint& paint,
115 const SkIRect& bounds,
116 const SkMatrix& ctm,
117 SkBaseDevice* device) {
118 SkRasterClip rc(bounds);
119 SkDraw draw;
120 draw.fDst = SkPixmap(SkImageInfo::MakeUnknown(bounds.right(), bounds.bottom()), nullptr, 0);
121 draw.fMatrix = &ctm;
122 draw.fRC = &rc;
123 draw.drawPoints(mode, count, points, paint, device);
124}
125
reed374772b2016-10-05 17:33:02 -0700126// If the paint will definitely draw opaquely, replace kSrc with
127// kSrcOver. http://crbug.com/473572
halcanarya6814332015-05-27 08:53:36 -0700128static void replace_srcmode_on_opaque_paint(SkPaint* paint) {
reed374772b2016-10-05 17:33:02 -0700129 if (kSrcOver_SkXfermodeInterpretation == SkInterpretXfermode(*paint, false)) {
130 paint->setBlendMode(SkBlendMode::kSrcOver);
halcanarya6814332015-05-27 08:53:36 -0700131 }
132}
133
Hal Canaryd12a6762017-05-26 17:01:16 -0400134// A shader's matrix is: CTMM x LocalMatrix x WrappingLocalMatrix. We want to
135// switch to device space, where CTM = I, while keeping the original behavior.
136//
137// I * LocalMatrix * NewWrappingMatrix = CTM * LocalMatrix
138// LocalMatrix * NewWrappingMatrix = CTM * LocalMatrix
139// InvLocalMatrix * LocalMatrix * NewWrappingMatrix = InvLocalMatrix * CTM * LocalMatrix
140// NewWrappingMatrix = InvLocalMatrix * CTM * LocalMatrix
141//
142static void transform_shader(SkPaint* paint, const SkMatrix& ctm) {
143 SkMatrix lm = SkPDFUtils::GetShaderLocalMatrix(paint->getShader());
144 SkMatrix lmInv;
145 if (lm.invert(&lmInv)) {
146 SkMatrix m = SkMatrix::Concat(SkMatrix::Concat(lmInv, ctm), lm);
147 paint->setShader(paint->getShader()->makeWithLocalMatrix(m));
148 }
149}
150
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000151static void emit_pdf_color(SkColor color, SkWStream* result) {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000152 SkASSERT(SkColorGetA(color) == 0xFF); // We handle alpha elsewhere.
halcanaryeb92cb32016-07-15 13:41:27 -0700153 SkPDFUtils::AppendColorComponent(SkColorGetR(color), result);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000154 result->writeText(" ");
halcanaryeb92cb32016-07-15 13:41:27 -0700155 SkPDFUtils::AppendColorComponent(SkColorGetG(color), result);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000156 result->writeText(" ");
halcanaryeb92cb32016-07-15 13:41:27 -0700157 SkPDFUtils::AppendColorComponent(SkColorGetB(color), result);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000158 result->writeText(" ");
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000159}
160
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000161static SkPaint calculate_text_paint(const SkPaint& paint) {
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000162 SkPaint result = paint;
163 if (result.isFakeBoldText()) {
164 SkScalar fakeBoldScale = SkScalarInterpFunc(result.getTextSize(),
165 kStdFakeBoldInterpKeys,
166 kStdFakeBoldInterpValues,
167 kStdFakeBoldInterpLength);
Mike Reed8be952a2017-02-13 20:44:33 -0500168 SkScalar width = result.getTextSize() * fakeBoldScale;
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000169 if (result.getStyle() == SkPaint::kFill_Style) {
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000170 result.setStyle(SkPaint::kStrokeAndFill_Style);
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000171 } else {
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000172 width += result.getStrokeWidth();
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000173 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000174 result.setStrokeWidth(width);
175 }
176 return result;
177}
178
Hal Canaryf50ff392016-09-30 10:25:39 -0400179
Hal Canaryd12a6762017-05-26 17:01:16 -0400180// If the paint has a color filter, apply the color filter to the shader or the
181// paint color. Remove the color filter.
182void remove_color_filter(SkPaint* paint) {
183 if (SkColorFilter* cf = paint->getColorFilter()) {
184 if (SkShader* shader = paint->getShader()) {
185 paint->setShader(shader->makeWithColorFilter(paint->refColorFilter()));
186 } else {
187 paint->setColor(cf->filterColor(paint->getColor()));
188 }
189 paint->setColorFilter(nullptr);
190 }
191}
192
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000193
Hal Canaryb400d4d2018-09-26 16:33:52 -0400194void SkPDFDevice::GraphicStackState::drainStack() {
195 if (fContentStream) {
196 while (fStackDepth) {
Hal Canaryb5b72792018-09-28 08:49:33 -0400197 this->pop();
Hal Canaryb400d4d2018-09-26 16:33:52 -0400198 }
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000199 }
Hal Canaryb400d4d2018-09-26 16:33:52 -0400200 SkASSERT(fStackDepth == 0);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000201}
202
Hal Canaryb400d4d2018-09-26 16:33:52 -0400203void SkPDFDevice::GraphicStackState::push() {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000204 SkASSERT(fStackDepth < kMaxStackDepth);
205 fContentStream->writeText("q\n");
206 fStackDepth++;
207 fEntries[fStackDepth] = fEntries[fStackDepth - 1];
208}
209
Hal Canaryb400d4d2018-09-26 16:33:52 -0400210void SkPDFDevice::GraphicStackState::pop() {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000211 SkASSERT(fStackDepth > 0);
212 fContentStream->writeText("Q\n");
Hal Canaryb400d4d2018-09-26 16:33:52 -0400213 fEntries[fStackDepth] = SkPDFDevice::GraphicStateEntry();
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000214 fStackDepth--;
215}
216
commit-bot@chromium.orgd2623a12013-08-08 02:52:05 +0000217/* Calculate an inverted path's equivalent non-inverted path, given the
218 * canvas bounds.
219 * outPath may alias with invPath (since this is supported by PathOps).
220 */
221static bool calculate_inverse_path(const SkRect& bounds, const SkPath& invPath,
222 SkPath* outPath) {
223 SkASSERT(invPath.isInverseFillType());
Hal Canaryd791a6f2018-09-28 12:14:08 -0400224 return Op(to_path(bounds), invPath, kIntersect_SkPathOp, outPath);
commit-bot@chromium.orgd2623a12013-08-08 02:52:05 +0000225}
226
Hal Canaryd00ef062018-06-05 11:53:58 -0400227static SkRect rect_intersect(SkRect u, SkRect v) {
228 if (u.isEmpty() || v.isEmpty()) { return {0, 0, 0, 0}; }
229 return u.intersect(v) ? u : SkRect{0, 0, 0, 0};
230}
231
232// Test to see if the clipstack is a simple rect, If so, we can avoid all PathOps code
233// and speed thing up.
234static bool is_rect(const SkClipStack& clipStack, const SkRect& bounds, SkRect* dst) {
235 SkRect currentClip = bounds;
236 SkClipStack::Iter iter(clipStack, SkClipStack::Iter::kBottom_IterStart);
237 while (const SkClipStack::Element* element = iter.next()) {
238 SkRect elementRect{0, 0, 0, 0};
239 switch (element->getDeviceSpaceType()) {
240 case SkClipStack::Element::DeviceSpaceType::kEmpty:
241 break;
242 case SkClipStack::Element::DeviceSpaceType::kRect:
243 elementRect = element->getDeviceSpaceRect();
244 break;
245 default:
246 return false;
247 }
248 switch (element->getOp()) {
249 case kReplace_SkClipOp:
250 currentClip = rect_intersect(bounds, elementRect);
251 break;
252 case SkClipOp::kIntersect:
253 currentClip = rect_intersect(currentClip, elementRect);
254 break;
255 default:
256 return false;
257 }
258 }
259 *dst = currentClip;
260 return true;
261}
262
263static void append_clip(const SkClipStack& clipStack,
264 const SkIRect& bounds,
265 SkWStream* wStream) {
266 // The bounds are slightly outset to ensure this is correct in the
267 // face of floating-point accuracy and possible SkRegion bitmap
268 // approximations.
269 SkRect outsetBounds = SkRect::Make(bounds.makeOutset(1, 1));
270
271 SkRect clipStackRect;
272 if (is_rect(clipStack, outsetBounds, &clipStackRect)) {
273 SkPDFUtils::AppendRectangle(clipStackRect, wStream);
274 wStream->writeText("W* n\n");
275 return;
276 }
277
278 SkPath clipPath;
279 (void)clipStack.asPath(&clipPath);
280
Hal Canaryd791a6f2018-09-28 12:14:08 -0400281 if (Op(clipPath, to_path(outsetBounds), kIntersect_SkPathOp, &clipPath)) {
Hal Canaryd00ef062018-06-05 11:53:58 -0400282 SkPDFUtils::EmitPath(clipPath, SkPaint::kFill_Style, wStream);
283 SkPath::FillType clipFill = clipPath.getFillType();
284 NOT_IMPLEMENTED(clipFill == SkPath::kInverseEvenOdd_FillType, false);
285 NOT_IMPLEMENTED(clipFill == SkPath::kInverseWinding_FillType, false);
286 if (clipFill == SkPath::kEvenOdd_FillType) {
287 wStream->writeText("W* n\n");
288 } else {
289 wStream->writeText("W n\n");
290 }
291 }
292 // If Op() fails (pathological case; e.g. input values are
293 // extremely large or NaN), emit no clip at all.
294}
295
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000296// TODO(vandebo): Take advantage of SkClipStack::getSaveCount(), the PDF
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000297// graphic state stack, and the fact that we can know all the clips used
298// on the page to optimize this.
Hal Canaryb400d4d2018-09-26 16:33:52 -0400299void SkPDFDevice::GraphicStackState::updateClip(const SkClipStack* clipStack,
300 const SkIRect& bounds) {
301 uint32_t clipStackGenID = clipStack ? clipStack->getTopmostGenID()
302 : SkClipStack::kWideOpenGenID;
303 if (clipStackGenID == currentEntry()->fClipStackGenID) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000304 return;
305 }
306
307 while (fStackDepth > 0) {
Hal Canaryb5b72792018-09-28 08:49:33 -0400308 this->pop();
Hal Canaryb400d4d2018-09-26 16:33:52 -0400309 if (clipStackGenID == currentEntry()->fClipStackGenID) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000310 return;
311 }
312 }
Hal Canaryb5b72792018-09-28 08:49:33 -0400313 SkASSERT(currentEntry()->fClipStackGenID == SkClipStack::kWideOpenGenID);
Hal Canaryb400d4d2018-09-26 16:33:52 -0400314 if (clipStackGenID != SkClipStack::kWideOpenGenID) {
315 SkASSERT(clipStack);
Hal Canaryb5b72792018-09-28 08:49:33 -0400316 this->push();
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000317
Hal Canaryb400d4d2018-09-26 16:33:52 -0400318 currentEntry()->fClipStackGenID = clipStackGenID;
319 append_clip(*clipStack, bounds, fContentStream);
320 }
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000321}
322
Hal Canarye650b852018-09-12 09:12:36 -0400323static void append_transform(const SkMatrix& matrix, SkWStream* content) {
324 SkScalar values[6];
325 if (!matrix.asAffine(values)) {
326 SkMatrix::SetAffineIdentity(values);
327 }
328 for (SkScalar v : values) {
329 SkPDFUtils::AppendScalar(v, content);
330 content->writeText(" ");
331 }
332 content->writeText("cm\n");
333}
334
Hal Canaryb400d4d2018-09-26 16:33:52 -0400335void SkPDFDevice::GraphicStackState::updateMatrix(const SkMatrix& matrix) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000336 if (matrix == currentEntry()->fMatrix) {
337 return;
338 }
339
340 if (currentEntry()->fMatrix.getType() != SkMatrix::kIdentity_Mask) {
341 SkASSERT(fStackDepth > 0);
Hal Canaryb400d4d2018-09-26 16:33:52 -0400342 SkASSERT(fEntries[fStackDepth].fClipStackGenID ==
343 fEntries[fStackDepth -1].fClipStackGenID);
Hal Canaryb5b72792018-09-28 08:49:33 -0400344 this->pop();
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000345
346 SkASSERT(currentEntry()->fMatrix.getType() == SkMatrix::kIdentity_Mask);
347 }
348 if (matrix.getType() == SkMatrix::kIdentity_Mask) {
349 return;
350 }
351
Hal Canaryb5b72792018-09-28 08:49:33 -0400352 this->push();
Hal Canarye650b852018-09-12 09:12:36 -0400353 append_transform(matrix, fContentStream);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000354 currentEntry()->fMatrix = matrix;
355}
356
Hal Canaryb400d4d2018-09-26 16:33:52 -0400357void SkPDFDevice::GraphicStackState::updateDrawingState(const SkPDFDevice::GraphicStateEntry& state) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000358 // PDF treats a shader as a color, so we only set one or the other.
359 if (state.fShaderIndex >= 0) {
360 if (state.fShaderIndex != currentEntry()->fShaderIndex) {
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000361 SkPDFUtils::ApplyPattern(state.fShaderIndex, fContentStream);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000362 currentEntry()->fShaderIndex = state.fShaderIndex;
363 }
364 } else {
365 if (state.fColor != currentEntry()->fColor ||
366 currentEntry()->fShaderIndex >= 0) {
367 emit_pdf_color(state.fColor, fContentStream);
368 fContentStream->writeText("RG ");
369 emit_pdf_color(state.fColor, fContentStream);
370 fContentStream->writeText("rg\n");
371 currentEntry()->fColor = state.fColor;
372 currentEntry()->fShaderIndex = -1;
373 }
374 }
375
376 if (state.fGraphicStateIndex != currentEntry()->fGraphicStateIndex) {
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000377 SkPDFUtils::ApplyGraphicState(state.fGraphicStateIndex, fContentStream);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000378 currentEntry()->fGraphicStateIndex = state.fGraphicStateIndex;
379 }
380
381 if (state.fTextScaleX) {
382 if (state.fTextScaleX != currentEntry()->fTextScaleX) {
Hal Canary9b491b22017-11-28 15:10:13 -0500383 SkScalar pdfScale = state.fTextScaleX * 100;
halcanarybc4696b2015-05-06 10:56:04 -0700384 SkPDFUtils::AppendScalar(pdfScale, fContentStream);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000385 fContentStream->writeText(" Tz\n");
386 currentEntry()->fTextScaleX = state.fTextScaleX;
387 }
388 if (state.fTextFill != currentEntry()->fTextFill) {
bungeman99fe8222015-08-20 07:57:51 -0700389 static_assert(SkPaint::kFill_Style == 0, "enum_must_match_value");
390 static_assert(SkPaint::kStroke_Style == 1, "enum_must_match_value");
391 static_assert(SkPaint::kStrokeAndFill_Style == 2, "enum_must_match_value");
Cary Clark60ebf142018-09-06 12:22:33 +0000392 fContentStream->writeDecAsText(state.fTextFill);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000393 fContentStream->writeText(" Tr\n");
394 currentEntry()->fTextFill = state.fTextFill;
395 }
396 }
397}
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000398
reed76033be2015-03-14 10:54:31 -0700399static bool not_supported_for_layers(const SkPaint& layerPaint) {
senorblancob0e89dc2014-10-20 14:03:12 -0700400 // PDF does not support image filters, so render them on CPU.
401 // Note that this rendering is done at "screen" resolution (100dpi), not
402 // printer resolution.
halcanary7a14b312015-10-01 07:28:13 -0700403 // TODO: It may be possible to express some filters natively using PDF
halcanary6950de62015-11-07 05:29:00 -0800404 // to improve quality and file size (https://bug.skia.org/3043)
reed76033be2015-03-14 10:54:31 -0700405
406 // TODO: should we return true if there is a colorfilter?
halcanary96fcdcc2015-08-27 07:41:13 -0700407 return layerPaint.getImageFilter() != nullptr;
reed76033be2015-03-14 10:54:31 -0700408}
409
410SkBaseDevice* SkPDFDevice::onCreateDevice(const CreateInfo& cinfo, const SkPaint* layerPaint) {
reedcd4051e2016-07-15 09:41:26 -0700411 if (layerPaint && not_supported_for_layers(*layerPaint)) {
reed7503d602016-07-15 14:23:29 -0700412 // need to return a raster device, which we will detect in drawDevice()
413 return SkBitmapDevice::Create(cinfo.fInfo, SkSurfaceProps(0, kUnknown_SkPixelGeometry));
senorblancob0e89dc2014-10-20 14:03:12 -0700414 }
Hal Canary22b2d8c2017-07-19 14:46:12 -0400415 return new SkPDFDevice(cinfo.fInfo.dimensions(), fDocument);
bsalomon@google.come97f0852011-06-17 13:10:25 +0000416}
417
halcanary989da4a2016-03-21 14:33:17 -0700418SkPDFCanon* SkPDFDevice::getCanon() const { return fDocument->canon(); }
419
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000420// A helper class to automatically finish a ContentEntry at the end of a
421// drawing method and maintain the state needed between set up and finish.
vandebo@chromium.org13d14a92011-05-24 23:12:41 +0000422class ScopedContentEntry {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000423public:
Mike Reeda1361362017-03-07 09:37:29 -0500424 ScopedContentEntry(SkPDFDevice* device,
Hal Canaryd892a9d2018-09-24 21:20:47 -0400425 const SkClipStack* clipStack,
Mike Reed27d07f02017-03-04 21:47:47 +0000426 const SkMatrix& matrix,
Mike Reeda1361362017-03-07 09:37:29 -0500427 const SkPaint& paint,
428 bool hasText = false)
429 : fDevice(device)
430 , fContentEntry(nullptr)
431 , fBlendMode(SkBlendMode::kSrcOver)
432 , fDstFormXObject(nullptr)
Hal Canaryb400d4d2018-09-26 16:33:52 -0400433 , fClipStack(clipStack)
Mike Reeda1361362017-03-07 09:37:29 -0500434 {
435 if (matrix.hasPerspective()) {
436 NOT_IMPLEMENTED(!matrix.hasPerspective(), false);
437 return;
438 }
439 fBlendMode = paint.getBlendMode();
440 fContentEntry =
441 fDevice->setUpContentEntry(clipStack, matrix, paint, hasText, &fDstFormXObject);
Mike Reed27d07f02017-03-04 21:47:47 +0000442 }
Mike Reeda1361362017-03-07 09:37:29 -0500443 ScopedContentEntry(SkPDFDevice* dev, const SkPaint& paint, bool hasText = false)
Hal Canaryd892a9d2018-09-24 21:20:47 -0400444 : ScopedContentEntry(dev, &dev->cs(), dev->ctm(), paint, hasText) {}
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000445
vandebo@chromium.org13d14a92011-05-24 23:12:41 +0000446 ~ScopedContentEntry() {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000447 if (fContentEntry) {
vandebo@chromium.org3b416212013-10-30 20:48:05 +0000448 SkPath* shape = &fShape;
449 if (shape->isEmpty()) {
halcanary96fcdcc2015-08-27 07:41:13 -0700450 shape = nullptr;
vandebo@chromium.org3b416212013-10-30 20:48:05 +0000451 }
Hal Canaryb400d4d2018-09-26 16:33:52 -0400452 fDevice->finishContentEntry(fClipStack, fBlendMode, std::move(fDstFormXObject), shape);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000453 }
454 }
455
Hal Canaryb400d4d2018-09-26 16:33:52 -0400456 SkDynamicMemoryWStream* entry() { return fContentEntry; }
457 SkDynamicMemoryWStream* stream() { return fContentEntry; }
vandebo@chromium.org3b416212013-10-30 20:48:05 +0000458
459 /* Returns true when we explicitly need the shape of the drawing. */
460 bool needShape() {
reed374772b2016-10-05 17:33:02 -0700461 switch (fBlendMode) {
462 case SkBlendMode::kClear:
463 case SkBlendMode::kSrc:
464 case SkBlendMode::kSrcIn:
465 case SkBlendMode::kSrcOut:
466 case SkBlendMode::kDstIn:
467 case SkBlendMode::kDstOut:
468 case SkBlendMode::kSrcATop:
469 case SkBlendMode::kDstATop:
470 case SkBlendMode::kModulate:
vandebo@chromium.org3b416212013-10-30 20:48:05 +0000471 return true;
472 default:
473 return false;
474 }
475 }
476
477 /* Returns true unless we only need the shape of the drawing. */
478 bool needSource() {
reed374772b2016-10-05 17:33:02 -0700479 if (fBlendMode == SkBlendMode::kClear) {
vandebo@chromium.org3b416212013-10-30 20:48:05 +0000480 return false;
481 }
482 return true;
483 }
484
485 /* If the shape is different than the alpha component of the content, then
486 * setShape should be called with the shape. In particular, images and
487 * devices have rectangular shape.
488 */
489 void setShape(const SkPath& shape) {
490 fShape = shape;
491 }
492
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000493private:
494 SkPDFDevice* fDevice;
Hal Canaryb400d4d2018-09-26 16:33:52 -0400495 SkDynamicMemoryWStream* fContentEntry;
reed374772b2016-10-05 17:33:02 -0700496 SkBlendMode fBlendMode;
halcanarydabd4f02016-08-03 11:16:56 -0700497 sk_sp<SkPDFObject> fDstFormXObject;
vandebo@chromium.org3b416212013-10-30 20:48:05 +0000498 SkPath fShape;
Hal Canaryb400d4d2018-09-26 16:33:52 -0400499 const SkClipStack* fClipStack;
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000500};
501
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000502////////////////////////////////////////////////////////////////////////////////
503
Hal Canary7d06ab22018-09-10 14:39:13 -0400504SkPDFDevice::SkPDFDevice(SkISize pageSize, SkPDFDocument* doc, const SkMatrix& transform)
reed589a39e2016-08-20 07:59:19 -0700505 : INHERITED(SkImageInfo::MakeUnknown(pageSize.width(), pageSize.height()),
506 SkSurfaceProps(0, kUnknown_SkPixelGeometry))
Hal Canary7d06ab22018-09-10 14:39:13 -0400507 , fInitialTransform(transform)
Dominic Mazzoni656cefe2018-09-25 20:29:15 -0700508 , fNodeId(0)
Hal Canarya0622582017-06-29 18:51:35 -0400509 , fDocument(doc)
510{
Hal Canary22b2d8c2017-07-19 14:46:12 -0400511 SkASSERT(!pageSize.isEmpty());
Hal Canarya0622582017-06-29 18:51:35 -0400512}
robertphillips1f3923e2016-07-21 07:17:54 -0700513
Hal Canary9e41c212018-09-03 12:00:23 -0400514SkPDFDevice::~SkPDFDevice() = default;
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000515
Hal Canary9e41c212018-09-03 12:00:23 -0400516void SkPDFDevice::reset() {
Hal Canary9e41c212018-09-03 12:00:23 -0400517 fLinkToURLs = std::vector<RectWithData>();
518 fLinkToDestinations = std::vector<RectWithData>();
519 fNamedDestinations = std::vector<NamedDestination>();
520 fGraphicStateResources = std::vector<sk_sp<SkPDFObject>>();
521 fXObjectResources = std::vector<sk_sp<SkPDFObject>>();
522 fShaderResources = std::vector<sk_sp<SkPDFObject>>();
523 fFontResources = std::vector<sk_sp<SkPDFFont>>();
mtklein852f15d2016-03-17 10:51:27 -0700524 fContentEntries.reset();
Hal Canary813b5ac2018-09-28 12:30:37 -0400525 fActiveStackState = GraphicStackState();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000526}
527
Mike Reeda1361362017-03-07 09:37:29 -0500528void SkPDFDevice::drawAnnotation(const SkRect& rect, const char key[], SkData* value) {
Hal Canary9cd21682017-02-22 15:55:06 -0500529 if (!value) {
530 return;
531 }
532 if (rect.isEmpty()) {
Dominic Mazzoni656cefe2018-09-25 20:29:15 -0700533 if (!strcmp(key, SkPDFGetNodeIdKey())) {
534 int nodeID;
535 if (value->size() != sizeof(nodeID)) { return; }
536 memcpy(&nodeID, value->data(), sizeof(nodeID));
537 fNodeId = nodeID;
538 return;
539 }
Mike Reeda1361362017-03-07 09:37:29 -0500540 if (!strcmp(SkAnnotationKeys::Define_Named_Dest_Key(), key)) {
541 SkPoint transformedPoint;
542 this->ctm().mapXY(rect.x(), rect.y(), &transformedPoint);
Hal Canary5c1b3602017-04-17 16:30:06 -0400543 fNamedDestinations.emplace_back(NamedDestination{sk_ref_sp(value), transformedPoint});
Mike Reeda1361362017-03-07 09:37:29 -0500544 }
545 return;
546 }
547 // Convert to path to handle non-90-degree rotations.
Hal Canaryd791a6f2018-09-28 12:14:08 -0400548 SkPath path = to_path(rect);
Mike Reeda1361362017-03-07 09:37:29 -0500549 path.transform(this->ctm(), &path);
550 SkPath clip;
551 (void)this->cs().asPath(&clip);
552 Op(clip, path, kIntersect_SkPathOp, &path);
553 // PDF wants a rectangle only.
554 SkRect transformedRect = path.getBounds();
555 if (transformedRect.isEmpty()) {
556 return;
557 }
558 if (!strcmp(SkAnnotationKeys::URL_Key(), key)) {
Hal Canary5c1b3602017-04-17 16:30:06 -0400559 fLinkToURLs.emplace_back(RectWithData{transformedRect, sk_ref_sp(value)});
Mike Reeda1361362017-03-07 09:37:29 -0500560 } else if (!strcmp(SkAnnotationKeys::Link_Named_Dest_Key(), key)) {
Hal Canary5c1b3602017-04-17 16:30:06 -0400561 fLinkToDestinations.emplace_back(RectWithData{transformedRect, sk_ref_sp(value)});
reedf70b5312016-03-04 16:36:20 -0800562 }
563}
564
Hal Canaryd12a6762017-05-26 17:01:16 -0400565void SkPDFDevice::drawPaint(const SkPaint& srcPaint) {
Hal Canaryabf8e412018-09-24 11:37:23 -0400566 SkMatrix inverse;
567 if (!this->ctm().invert(&inverse)) {
568 return;
569 }
570 SkRect bbox = this->cs().bounds(this->bounds());
571 inverse.mapRect(&bbox);
572 bbox.roundOut(&bbox);
Hal Canaryb4e528d2018-03-09 16:02:15 -0500573 if (this->hasEmptyClip()) {
574 return;
575 }
Hal Canaryd12a6762017-05-26 17:01:16 -0400576 SkPaint newPaint = srcPaint;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000577 newPaint.setStyle(SkPaint::kFill_Style);
Hal Canaryabf8e412018-09-24 11:37:23 -0400578 this->drawRect(bbox, newPaint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000579}
580
Mike Reeda1361362017-03-07 09:37:29 -0500581void SkPDFDevice::drawPoints(SkCanvas::PointMode mode,
halcanarya6814332015-05-27 08:53:36 -0700582 size_t count,
583 const SkPoint* points,
584 const SkPaint& srcPaint) {
Hal Canaryb4e528d2018-03-09 16:02:15 -0500585 if (this->hasEmptyClip()) {
586 return;
587 }
halcanarya6814332015-05-27 08:53:36 -0700588 SkPaint passedPaint = srcPaint;
Hal Canaryd12a6762017-05-26 17:01:16 -0400589 remove_color_filter(&passedPaint);
halcanarya6814332015-05-27 08:53:36 -0700590 replace_srcmode_on_opaque_paint(&passedPaint);
Hal Canary80fa7ce2017-06-28 16:04:20 -0400591 if (SkCanvas::kPoints_PointMode != mode) {
592 passedPaint.setStyle(SkPaint::kStroke_Style);
593 }
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000594 if (count == 0) {
595 return;
596 }
597
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000598 // SkDraw::drawPoints converts to multiple calls to fDevice->drawPath.
599 // We only use this when there's a path effect because of the overhead
600 // of multiple calls to setUpContentEntry it causes.
601 if (passedPaint.getPathEffect()) {
Mike Reeda1361362017-03-07 09:37:29 -0500602 draw_points(mode, count, points, passedPaint,
603 this->devClipBounds(), this->ctm(), this);
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000604 return;
605 }
606
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000607 const SkPaint* paint = &passedPaint;
608 SkPaint modifiedPaint;
609
610 if (mode == SkCanvas::kPoints_PointMode &&
611 paint->getStrokeCap() != SkPaint::kRound_Cap) {
612 modifiedPaint = *paint;
613 paint = &modifiedPaint;
614 if (paint->getStrokeWidth()) {
615 // PDF won't draw a single point with square/butt caps because the
616 // orientation is ambiguous. Draw a rectangle instead.
617 modifiedPaint.setStyle(SkPaint::kFill_Style);
618 SkScalar strokeWidth = paint->getStrokeWidth();
619 SkScalar halfStroke = SkScalarHalf(strokeWidth);
620 for (size_t i = 0; i < count; i++) {
621 SkRect r = SkRect::MakeXYWH(points[i].fX, points[i].fY, 0, 0);
622 r.inset(-halfStroke, -halfStroke);
Mike Reeda1361362017-03-07 09:37:29 -0500623 this->drawRect(r, modifiedPaint);
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000624 }
625 return;
626 } else {
627 modifiedPaint.setStrokeCap(SkPaint::kRound_Cap);
628 }
629 }
630
Mike Reeda1361362017-03-07 09:37:29 -0500631 ScopedContentEntry content(this, *paint);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000632 if (!content.entry()) {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000633 return;
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000634 }
Hal Canary51329c92017-06-27 14:28:37 -0400635 SkDynamicMemoryWStream* contentStream = content.stream();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000636 switch (mode) {
637 case SkCanvas::kPolygon_PointMode:
Hal Canary51329c92017-06-27 14:28:37 -0400638 SkPDFUtils::MoveTo(points[0].fX, points[0].fY, contentStream);
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +0000639 for (size_t i = 1; i < count; i++) {
Hal Canary51329c92017-06-27 14:28:37 -0400640 SkPDFUtils::AppendLine(points[i].fX, points[i].fY, contentStream);
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +0000641 }
Hal Canary51329c92017-06-27 14:28:37 -0400642 SkPDFUtils::StrokePath(contentStream);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000643 break;
644 case SkCanvas::kLines_PointMode:
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000645 for (size_t i = 0; i < count/2; i++) {
Hal Canary51329c92017-06-27 14:28:37 -0400646 SkPDFUtils::MoveTo(points[i * 2].fX, points[i * 2].fY, contentStream);
647 SkPDFUtils::AppendLine(points[i * 2 + 1].fX, points[i * 2 + 1].fY, contentStream);
648 SkPDFUtils::StrokePath(contentStream);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000649 }
650 break;
651 case SkCanvas::kPoints_PointMode:
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000652 SkASSERT(paint->getStrokeCap() == SkPaint::kRound_Cap);
653 for (size_t i = 0; i < count; i++) {
Hal Canary51329c92017-06-27 14:28:37 -0400654 SkPDFUtils::MoveTo(points[i].fX, points[i].fY, contentStream);
655 SkPDFUtils::ClosePath(contentStream);
656 SkPDFUtils::StrokePath(contentStream);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000657 }
658 break;
659 default:
660 SkASSERT(false);
661 }
662}
663
halcanary8103a342016-03-08 15:10:16 -0800664static sk_sp<SkPDFDict> create_link_annotation(const SkRect& translatedRect) {
halcanaryece83922016-03-08 08:32:12 -0800665 auto annotation = sk_make_sp<SkPDFDict>("Annot");
wangxianzhud76665d2015-07-17 17:23:15 -0700666 annotation->insertName("Subtype", "Link");
halcanary488165e2016-04-22 06:10:21 -0700667 annotation->insertInt("F", 4); // required by ISO 19005
Hal Canarye650b852018-09-12 09:12:36 -0400668 // Border: 0 = Horizontal corner radius.
669 // 0 = Vertical corner radius.
670 // 0 = Width, 0 = no border.
671 annotation->insertObject("Border", SkPDFMakeArray(0, 0, 0));
wangxianzhud76665d2015-07-17 17:23:15 -0700672
Hal Canarye650b852018-09-12 09:12:36 -0400673 annotation->insertObject("Rect", SkPDFMakeArray(translatedRect.fLeft,
674 translatedRect.fTop,
675 translatedRect.fRight,
676 translatedRect.fBottom));
halcanary8103a342016-03-08 15:10:16 -0800677 return annotation;
wangxianzhud76665d2015-07-17 17:23:15 -0700678}
679
halcanary8103a342016-03-08 15:10:16 -0800680static sk_sp<SkPDFDict> create_link_to_url(const SkData* urlData, const SkRect& r) {
halcanary4b1e17e2016-07-27 14:49:46 -0700681 sk_sp<SkPDFDict> annotation = create_link_annotation(r);
wangxianzhud76665d2015-07-17 17:23:15 -0700682 SkString url(static_cast<const char *>(urlData->data()),
683 urlData->size() - 1);
halcanaryece83922016-03-08 08:32:12 -0800684 auto action = sk_make_sp<SkPDFDict>("Action");
wangxianzhud76665d2015-07-17 17:23:15 -0700685 action->insertName("S", "URI");
686 action->insertString("URI", url);
halcanary8103a342016-03-08 15:10:16 -0800687 annotation->insertObject("A", std::move(action));
688 return annotation;
wangxianzhud76665d2015-07-17 17:23:15 -0700689}
690
halcanary8103a342016-03-08 15:10:16 -0800691static sk_sp<SkPDFDict> create_link_named_dest(const SkData* nameData,
692 const SkRect& r) {
halcanary4b1e17e2016-07-27 14:49:46 -0700693 sk_sp<SkPDFDict> annotation = create_link_annotation(r);
wangxianzhud76665d2015-07-17 17:23:15 -0700694 SkString name(static_cast<const char *>(nameData->data()),
695 nameData->size() - 1);
696 annotation->insertName("Dest", name);
halcanary8103a342016-03-08 15:10:16 -0800697 return annotation;
wangxianzhud76665d2015-07-17 17:23:15 -0700698}
699
Mike Reeda1361362017-03-07 09:37:29 -0500700void SkPDFDevice::drawRect(const SkRect& rect,
halcanarya6814332015-05-27 08:53:36 -0700701 const SkPaint& srcPaint) {
Hal Canaryb4e528d2018-03-09 16:02:15 -0500702 if (this->hasEmptyClip()) {
703 return;
704 }
halcanarya6814332015-05-27 08:53:36 -0700705 SkPaint paint = srcPaint;
Hal Canaryd12a6762017-05-26 17:01:16 -0400706 remove_color_filter(&paint);
halcanarya6814332015-05-27 08:53:36 -0700707 replace_srcmode_on_opaque_paint(&paint);
commit-bot@chromium.org969fd6a2013-05-14 18:16:40 +0000708 SkRect r = rect;
709 r.sort();
710
Hal Canary8b354dc2018-09-20 12:51:22 -0400711 if (paint.getPathEffect() || paint.getMaskFilter() || this->ctm().hasPerspective()) {
Hal Canaryd791a6f2018-09-28 12:14:08 -0400712 this->drawPath(to_path(r), paint, true);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000713 return;
714 }
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000715
Mike Reeda1361362017-03-07 09:37:29 -0500716 ScopedContentEntry content(this, paint);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000717 if (!content.entry()) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000718 return;
719 }
Hal Canary51329c92017-06-27 14:28:37 -0400720 SkPDFUtils::AppendRectangle(r, content.stream());
721 SkPDFUtils::PaintPath(paint.getStyle(), SkPath::kWinding_FillType, content.stream());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000722}
723
Mike Reeda1361362017-03-07 09:37:29 -0500724void SkPDFDevice::drawRRect(const SkRRect& rrect,
halcanarya6814332015-05-27 08:53:36 -0700725 const SkPaint& srcPaint) {
Hal Canaryb4e528d2018-03-09 16:02:15 -0500726 if (this->hasEmptyClip()) {
727 return;
728 }
halcanarya6814332015-05-27 08:53:36 -0700729 SkPaint paint = srcPaint;
Hal Canaryd12a6762017-05-26 17:01:16 -0400730 remove_color_filter(&paint);
halcanarya6814332015-05-27 08:53:36 -0700731 replace_srcmode_on_opaque_paint(&paint);
scroggo@google.coma8e33a92013-11-08 18:02:53 +0000732 SkPath path;
733 path.addRRect(rrect);
Robert Phillips137ca522018-08-15 10:14:33 -0400734 this->drawPath(path, paint, true);
scroggo@google.coma8e33a92013-11-08 18:02:53 +0000735}
736
Mike Reeda1361362017-03-07 09:37:29 -0500737void SkPDFDevice::drawOval(const SkRect& oval,
halcanarya6814332015-05-27 08:53:36 -0700738 const SkPaint& srcPaint) {
Hal Canaryb4e528d2018-03-09 16:02:15 -0500739 if (this->hasEmptyClip()) {
740 return;
741 }
halcanarya6814332015-05-27 08:53:36 -0700742 SkPaint paint = srcPaint;
Hal Canaryd12a6762017-05-26 17:01:16 -0400743 remove_color_filter(&paint);
halcanarya6814332015-05-27 08:53:36 -0700744 replace_srcmode_on_opaque_paint(&paint);
reed89443ab2014-06-27 11:34:19 -0700745 SkPath path;
746 path.addOval(oval);
Robert Phillips137ca522018-08-15 10:14:33 -0400747 this->drawPath(path, paint, true);
reed89443ab2014-06-27 11:34:19 -0700748}
749
Mike Reeda1361362017-03-07 09:37:29 -0500750void SkPDFDevice::drawPath(const SkPath& origPath,
halcanarya6814332015-05-27 08:53:36 -0700751 const SkPaint& srcPaint,
vandebo@chromium.org02cc5aa2011-01-25 22:06:29 +0000752 bool pathIsMutable) {
Robert Phillips137ca522018-08-15 10:14:33 -0400753 this->internalDrawPath(this->cs(), this->ctm(), origPath, srcPaint, pathIsMutable);
Mike Reeda1361362017-03-07 09:37:29 -0500754}
755
Hal Canaryd12a6762017-05-26 17:01:16 -0400756void SkPDFDevice::internalDrawPathWithFilter(const SkClipStack& clipStack,
757 const SkMatrix& ctm,
758 const SkPath& origPath,
Robert Phillips137ca522018-08-15 10:14:33 -0400759 const SkPaint& origPaint) {
Hal Canaryd12a6762017-05-26 17:01:16 -0400760 SkASSERT(origPaint.getMaskFilter());
761 SkPath path(origPath);
762 SkTCopyOnFirstWrite<SkPaint> paint(origPaint);
Robert Phillips137ca522018-08-15 10:14:33 -0400763
Hal Canaryd12a6762017-05-26 17:01:16 -0400764 SkStrokeRec::InitStyle initStyle = paint->getFillPath(path, &path)
765 ? SkStrokeRec::kFill_InitStyle
766 : SkStrokeRec::kHairline_InitStyle;
767 path.transform(ctm, &path);
768
Hal Canary22b2d8c2017-07-19 14:46:12 -0400769 SkIRect bounds = clipStack.bounds(this->bounds()).roundOut();
Hal Canaryd12a6762017-05-26 17:01:16 -0400770 SkMask sourceMask;
771 if (!SkDraw::DrawToMask(path, &bounds, paint->getMaskFilter(), &SkMatrix::I(),
772 &sourceMask, SkMask::kComputeBoundsAndRenderImage_CreateMode,
773 initStyle)) {
774 return;
775 }
776 SkAutoMaskFreeImage srcAutoMaskFreeImage(sourceMask.fImage);
777 SkMask dstMask;
778 SkIPoint margin;
Mike Reed80747ef2018-01-23 15:29:32 -0500779 if (!as_MFB(paint->getMaskFilter())->filterMask(&dstMask, sourceMask, ctm, &margin)) {
Hal Canaryd12a6762017-05-26 17:01:16 -0400780 return;
781 }
Hal Canary51329c92017-06-27 14:28:37 -0400782 SkIRect dstMaskBounds = dstMask.fBounds;
783 sk_sp<SkImage> mask = mask_to_greyscale_image(&dstMask);
Hal Canaryd12a6762017-05-26 17:01:16 -0400784 // PDF doesn't seem to allow masking vector graphics with an Image XObject.
785 // Must mask with a Form XObject.
Hal Canary51329c92017-06-27 14:28:37 -0400786 sk_sp<SkPDFDevice> maskDevice = this->makeCongruentDevice();
Hal Canaryd12a6762017-05-26 17:01:16 -0400787 {
Herb Derbyefe39bc2018-05-01 17:06:20 -0400788 SkCanvas canvas(maskDevice);
Hal Canary51329c92017-06-27 14:28:37 -0400789 canvas.drawImage(mask, dstMaskBounds.x(), dstMaskBounds.y());
Hal Canaryd12a6762017-05-26 17:01:16 -0400790 }
Hal Canaryd12a6762017-05-26 17:01:16 -0400791 if (!ctm.isIdentity() && paint->getShader()) {
792 transform_shader(paint.writable(), ctm); // Since we are using identity matrix.
793 }
Hal Canaryd892a9d2018-09-24 21:20:47 -0400794 ScopedContentEntry content(this, &clipStack, SkMatrix::I(), *paint);
Hal Canaryd12a6762017-05-26 17:01:16 -0400795 if (!content.entry()) {
796 return;
797 }
Hal Canary51329c92017-06-27 14:28:37 -0400798 this->addSMaskGraphicState(std::move(maskDevice), content.stream());
799 SkPDFUtils::AppendRectangle(SkRect::Make(dstMaskBounds), content.stream());
800 SkPDFUtils::PaintPath(SkPaint::kFill_Style, path.getFillType(), content.stream());
801 this->clearMaskOnGraphicState(content.stream());
802}
Hal Canaryd12a6762017-05-26 17:01:16 -0400803
Hal Canary9e41c212018-09-03 12:00:23 -0400804template <typename T,
805 typename U,
806 typename = typename std::enable_if<std::is_convertible<U*, T*>::value>::type>
807static int find_or_add(std::vector<sk_sp<T>>* vec, sk_sp<U> object) {
808 SkASSERT(vec);
809 SkASSERT(object);
810 for (size_t i = 0; i < vec->size(); ++i) {
811 if ((*vec)[i].get() == object.get()) {
812 return SkToInt(i);
813 }
814 }
815 int index = SkToInt(vec->size());
816 vec->push_back(sk_sp<T>(std::move(object)));
817 return index;
818}
819
Hal Canary51329c92017-06-27 14:28:37 -0400820void SkPDFDevice::addSMaskGraphicState(sk_sp<SkPDFDevice> maskDevice,
821 SkDynamicMemoryWStream* contentStream) {
Hal Canary9e41c212018-09-03 12:00:23 -0400822 sk_sp<SkPDFObject> sMaskGS = SkPDFGraphicState::GetSMaskGraphicState(
Hal Canaryb4bd5ef2017-07-26 09:16:01 -0400823 maskDevice->makeFormXObjectFromDevice(true), false,
Hal Canary51329c92017-06-27 14:28:37 -0400824 SkPDFGraphicState::kLuminosity_SMaskMode, this->getCanon());
Hal Canary9e41c212018-09-03 12:00:23 -0400825 int gStateResourceIndex = find_or_add(&fGraphicStateResources, std::move(sMaskGS));
826 SkPDFUtils::ApplyGraphicState(gStateResourceIndex, contentStream);
Hal Canary51329c92017-06-27 14:28:37 -0400827}
Hal Canaryd12a6762017-05-26 17:01:16 -0400828
Hal Canary51329c92017-06-27 14:28:37 -0400829void SkPDFDevice::clearMaskOnGraphicState(SkDynamicMemoryWStream* contentStream) {
Hal Canaryd12a6762017-05-26 17:01:16 -0400830 // The no-softmask graphic state is used to "turn off" the mask for later draw calls.
Hal Canaryc02de0b2017-06-28 13:14:03 -0400831 sk_sp<SkPDFDict>& noSMaskGS = this->getCanon()->fNoSmaskGraphicState;
832 if (!noSMaskGS) {
833 noSMaskGS = sk_make_sp<SkPDFDict>("ExtGState");
834 noSMaskGS->insertName("SMask", "None");
835 }
Hal Canary9e41c212018-09-03 12:00:23 -0400836 int gStateResourceIndex = find_or_add(&fGraphicStateResources, noSMaskGS);
837 SkPDFUtils::ApplyGraphicState(gStateResourceIndex, contentStream);
Hal Canaryd12a6762017-05-26 17:01:16 -0400838}
839
Mike Reeda1361362017-03-07 09:37:29 -0500840void SkPDFDevice::internalDrawPath(const SkClipStack& clipStack,
841 const SkMatrix& ctm,
842 const SkPath& origPath,
843 const SkPaint& srcPaint,
Mike Reeda1361362017-03-07 09:37:29 -0500844 bool pathIsMutable) {
Hal Canaryb4e528d2018-03-09 16:02:15 -0500845 if (clipStack.isEmpty(this->bounds())) {
846 return;
847 }
halcanarya6814332015-05-27 08:53:36 -0700848 SkPaint paint = srcPaint;
Hal Canaryd12a6762017-05-26 17:01:16 -0400849 remove_color_filter(&paint);
halcanarya6814332015-05-27 08:53:36 -0700850 replace_srcmode_on_opaque_paint(&paint);
halcanary682ee012016-01-28 10:59:34 -0800851 SkPath modifiedPath;
852 SkPath* pathPtr = const_cast<SkPath*>(&origPath);
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000853
Hal Canaryd12a6762017-05-26 17:01:16 -0400854 if (paint.getMaskFilter()) {
Robert Phillips137ca522018-08-15 10:14:33 -0400855 this->internalDrawPathWithFilter(clipStack, ctm, origPath, paint);
Hal Canaryd12a6762017-05-26 17:01:16 -0400856 return;
857 }
858
Mike Reeda1361362017-03-07 09:37:29 -0500859 SkMatrix matrix = ctm;
vandebo@chromium.org02cc5aa2011-01-25 22:06:29 +0000860
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000861 if (paint.getPathEffect()) {
Hal Canary22b2d8c2017-07-19 14:46:12 -0400862 if (clipStack.isEmpty(this->bounds())) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000863 return;
864 }
halcanary682ee012016-01-28 10:59:34 -0800865 if (!pathIsMutable) {
Hal Canaryd12a6762017-05-26 17:01:16 -0400866 modifiedPath = origPath;
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000867 pathPtr = &modifiedPath;
868 pathIsMutable = true;
869 }
Hal Canaryd12a6762017-05-26 17:01:16 -0400870 if (paint.getFillPath(*pathPtr, pathPtr)) {
871 paint.setStyle(SkPaint::kFill_Style);
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000872 } else {
Hal Canaryd12a6762017-05-26 17:01:16 -0400873 paint.setStyle(SkPaint::kStroke_Style);
874 paint.setStrokeWidth(0);
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000875 }
Hal Canaryd12a6762017-05-26 17:01:16 -0400876 paint.setPathEffect(nullptr);
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000877 }
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000878
Robert Phillips137ca522018-08-15 10:14:33 -0400879 if (this->handleInversePath(*pathPtr, paint, pathIsMutable)) {
commit-bot@chromium.org92ffe7d2013-07-31 22:54:31 +0000880 return;
881 }
Hal Canaryd12a6762017-05-26 17:01:16 -0400882 if (matrix.getType() & SkMatrix::kPerspective_Mask) {
883 if (!pathIsMutable) {
884 modifiedPath = origPath;
885 pathPtr = &modifiedPath;
886 pathIsMutable = true;
887 }
888 pathPtr->transform(matrix);
889 if (paint.getShader()) {
890 transform_shader(&paint, matrix);
891 }
892 matrix = SkMatrix::I();
893 }
commit-bot@chromium.org92ffe7d2013-07-31 22:54:31 +0000894
Hal Canaryd892a9d2018-09-24 21:20:47 -0400895 ScopedContentEntry content(this, &clipStack, matrix, paint);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000896 if (!content.entry()) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000897 return;
898 }
Hal Canarydfaa0572017-11-27 09:33:44 -0500899 constexpr SkScalar kToleranceScale = 0.0625f; // smaller = better conics (circles).
Hal Canary385468f2017-02-13 11:03:23 -0500900 SkScalar matrixScale = matrix.mapRadius(1.0f);
Hal Canarydfaa0572017-11-27 09:33:44 -0500901 SkScalar tolerance = matrixScale > 0.0f ? kToleranceScale / matrixScale : kToleranceScale;
halcanary8b2bc252015-10-06 09:41:47 -0700902 bool consumeDegeratePathSegments =
903 paint.getStyle() == SkPaint::kFill_Style ||
904 (paint.getStrokeCap() != SkPaint::kRound_Cap &&
905 paint.getStrokeCap() != SkPaint::kSquare_Cap);
Hal Canary51329c92017-06-27 14:28:37 -0400906 SkPDFUtils::EmitPath(*pathPtr, paint.getStyle(), consumeDegeratePathSegments, content.stream(),
Hal Canary385468f2017-02-13 11:03:23 -0500907 tolerance);
Hal Canary51329c92017-06-27 14:28:37 -0400908 SkPDFUtils::PaintPath(paint.getStyle(), pathPtr->getFillType(), content.stream());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000909}
910
Hal Canary7cbf5e32017-07-12 13:10:23 -0400911////////////////////////////////////////////////////////////////////////////////
Hal Canaryf50ff392016-09-30 10:25:39 -0400912
Mike Reeda1361362017-03-07 09:37:29 -0500913void SkPDFDevice::drawImageRect(const SkImage* image,
Hal Canaryf50ff392016-09-30 10:25:39 -0400914 const SkRect* src,
915 const SkRect& dst,
Hal Canary7cbf5e32017-07-12 13:10:23 -0400916 const SkPaint& paint,
Hal Canaryf50ff392016-09-30 10:25:39 -0400917 SkCanvas::SrcRectConstraint) {
Hal Canary7cbf5e32017-07-12 13:10:23 -0400918 SkASSERT(image);
919 this->internalDrawImageRect(SkKeyedImage(sk_ref_sp(const_cast<SkImage*>(image))),
920 src, dst, paint, this->ctm());
Hal Canaryf50ff392016-09-30 10:25:39 -0400921}
922
Hal Canary7cbf5e32017-07-12 13:10:23 -0400923void SkPDFDevice::drawBitmapRect(const SkBitmap& bm,
halcanary7a14b312015-10-01 07:28:13 -0700924 const SkRect* src,
925 const SkRect& dst,
Hal Canary7cbf5e32017-07-12 13:10:23 -0400926 const SkPaint& paint,
Mike Reeda1361362017-03-07 09:37:29 -0500927 SkCanvas::SrcRectConstraint) {
Hal Canary7cbf5e32017-07-12 13:10:23 -0400928 SkASSERT(!bm.drawsNothing());
929 this->internalDrawImageRect(SkKeyedImage(bm), src, dst, paint, this->ctm());
halcanary7a14b312015-10-01 07:28:13 -0700930}
931
Hal Canary7cbf5e32017-07-12 13:10:23 -0400932void SkPDFDevice::drawBitmap(const SkBitmap& bm, SkScalar x, SkScalar y, const SkPaint& paint) {
933 SkASSERT(!bm.drawsNothing());
934 auto r = SkRect::MakeXYWH(x, y, bm.width(), bm.height());
935 this->internalDrawImageRect(SkKeyedImage(bm), nullptr, r, paint, this->ctm());
halcanary7a14b312015-10-01 07:28:13 -0700936}
937
Hal Canary7cbf5e32017-07-12 13:10:23 -0400938void SkPDFDevice::drawSprite(const SkBitmap& bm, int x, int y, const SkPaint& paint) {
939 SkASSERT(!bm.drawsNothing());
940 auto r = SkRect::MakeXYWH(x, y, bm.width(), bm.height());
941 this->internalDrawImageRect(SkKeyedImage(bm), nullptr, r, paint, SkMatrix::I());
halcanary7a14b312015-10-01 07:28:13 -0700942}
943
Hal Canary7cbf5e32017-07-12 13:10:23 -0400944void SkPDFDevice::drawImage(const SkImage* image, SkScalar x, SkScalar y, const SkPaint& paint) {
945 SkASSERT(image);
946 auto r = SkRect::MakeXYWH(x, y, image->width(), image->height());
947 this->internalDrawImageRect(SkKeyedImage(sk_ref_sp(const_cast<SkImage*>(image))),
948 nullptr, r, paint, this->ctm());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000949}
950
Hal Canary7cbf5e32017-07-12 13:10:23 -0400951////////////////////////////////////////////////////////////////////////////////
952
halcanaryf0c30f52016-07-15 13:35:45 -0700953namespace {
954class GlyphPositioner {
955public:
956 GlyphPositioner(SkDynamicMemoryWStream* content,
957 SkScalar textSkewX,
halcanary4ed2f012016-08-15 18:40:07 -0700958 bool wideChars,
halcanary4ed2f012016-08-15 18:40:07 -0700959 SkPoint origin)
halcanaryf0c30f52016-07-15 13:35:45 -0700960 : fContent(content)
halcanaryc2f9ec12016-09-12 08:55:29 -0700961 , fCurrentMatrixOrigin(origin)
962 , fTextSkewX(textSkewX)
Hal Canary98caedd2018-07-23 10:50:49 -0400963 , fWideChars(wideChars) {
halcanaryf0c30f52016-07-15 13:35:45 -0700964 }
halcanary4871f222016-08-26 13:17:44 -0700965 ~GlyphPositioner() { this->flush(); }
halcanaryf0c30f52016-07-15 13:35:45 -0700966 void flush() {
967 if (fInText) {
968 fContent->writeText("> Tj\n");
969 fInText = false;
970 }
971 }
halcanary4871f222016-08-26 13:17:44 -0700972 void writeGlyph(SkPoint xy,
halcanaryf0c30f52016-07-15 13:35:45 -0700973 SkScalar advanceWidth,
974 uint16_t glyph) {
halcanaryc2f9ec12016-09-12 08:55:29 -0700975 if (!fInitialized) {
976 // Flip the text about the x-axis to account for origin swap and include
977 // the passed parameters.
978 fContent->writeText("1 0 ");
979 SkPDFUtils::AppendScalar(-fTextSkewX, fContent);
980 fContent->writeText(" -1 ");
981 SkPDFUtils::AppendScalar(fCurrentMatrixOrigin.x(), fContent);
982 fContent->writeText(" ");
983 SkPDFUtils::AppendScalar(fCurrentMatrixOrigin.y(), fContent);
984 fContent->writeText(" Tm\n");
985 fCurrentMatrixOrigin.set(0.0f, 0.0f);
986 fInitialized = true;
987 }
Hal Canary98caedd2018-07-23 10:50:49 -0400988 SkPoint position = xy - fCurrentMatrixOrigin;
989 if (position != SkPoint{fXAdvance, 0}) {
990 this->flush();
991 SkPDFUtils::AppendScalar(position.x() - position.y() * fTextSkewX, fContent);
992 fContent->writeText(" ");
993 SkPDFUtils::AppendScalar(-position.y(), fContent);
994 fContent->writeText(" Td ");
995 fCurrentMatrixOrigin = xy;
996 fXAdvance = 0;
halcanaryf0c30f52016-07-15 13:35:45 -0700997 }
Hal Canary98caedd2018-07-23 10:50:49 -0400998 fXAdvance += advanceWidth;
halcanaryf0c30f52016-07-15 13:35:45 -0700999 if (!fInText) {
1000 fContent->writeText("<");
1001 fInText = true;
1002 }
1003 if (fWideChars) {
1004 SkPDFUtils::WriteUInt16BE(fContent, glyph);
1005 } else {
1006 SkASSERT(0 == glyph >> 8);
1007 SkPDFUtils::WriteUInt8(fContent, static_cast<uint8_t>(glyph));
1008 }
halcanaryf0c30f52016-07-15 13:35:45 -07001009 }
1010
1011private:
1012 SkDynamicMemoryWStream* fContent;
halcanary4871f222016-08-26 13:17:44 -07001013 SkPoint fCurrentMatrixOrigin;
halcanaryc2f9ec12016-09-12 08:55:29 -07001014 SkScalar fXAdvance = 0.0f;
1015 SkScalar fTextSkewX;
halcanaryf0c30f52016-07-15 13:35:45 -07001016 bool fWideChars;
halcanaryc2f9ec12016-09-12 08:55:29 -07001017 bool fInText = false;
1018 bool fInitialized = false;
halcanaryf0c30f52016-07-15 13:35:45 -07001019};
1020} // namespace
1021
Hal Canary46cc3da2018-05-09 11:50:34 -04001022static SkUnichar map_glyph(const std::vector<SkUnichar>& glyphToUnicode, SkGlyphID glyph) {
1023 return glyph < glyphToUnicode.size() ? glyphToUnicode[SkToInt(glyph)] : -1;
halcanaryf59d18a2016-09-16 14:44:57 -07001024}
1025
Hal Canary98caedd2018-07-23 10:50:49 -04001026static void draw_glyph_run_as_path(SkPDFDevice* dev, const SkGlyphRun& glyphRun, SkPoint offset) {
Hal Canaryd12a6762017-05-26 17:01:16 -04001027 SkPath path;
Hal Canary98caedd2018-07-23 10:50:49 -04001028 SkASSERT(glyphRun.paint().getTextEncoding() == SkPaint::kGlyphID_TextEncoding);
1029 glyphRun.paint().getPosTextPath(glyphRun.shuntGlyphsIDs().data(),
1030 glyphRun.shuntGlyphsIDs().size() * sizeof(SkGlyphID),
1031 glyphRun.positions().data(),
1032 &path);
1033 path.offset(offset.x(), offset.y());
Robert Phillips137ca522018-08-15 10:14:33 -04001034 dev->drawPath(path, glyphRun.paint(), true);
Hal Canaryd12a6762017-05-26 17:01:16 -04001035}
1036
Hal Canary9b9510a2017-07-18 09:39:00 -04001037static bool has_outline_glyph(SkGlyphID gid, SkGlyphCache* cache) {
1038 const SkGlyph& glyph = cache->getGlyphIDMetrics(gid);
1039 const SkPath* path = cache->findPath(glyph);
1040 return (path && !path->isEmpty()) || (glyph.fWidth == 0 && glyph.fHeight == 0);
1041}
1042
Hal Canary699b8732017-06-13 12:13:29 -04001043static SkRect get_glyph_bounds_device_space(SkGlyphID gid, SkGlyphCache* cache,
1044 SkScalar xScale, SkScalar yScale,
1045 SkPoint xy, const SkMatrix& ctm) {
1046 const SkGlyph& glyph = cache->getGlyphIDMetrics(gid);
1047 SkRect glyphBounds = {glyph.fLeft * xScale,
1048 glyph.fTop * yScale,
1049 (glyph.fLeft + glyph.fWidth) * xScale,
1050 (glyph.fTop + glyph.fHeight) * yScale};
1051 glyphBounds.offset(xy);
1052 ctm.mapRect(&glyphBounds); // now in dev space.
1053 return glyphBounds;
1054}
1055
1056static bool contains(const SkRect& r, SkPoint p) {
1057 return r.left() <= p.x() && p.x() <= r.right() &&
1058 r.top() <= p.y() && p.y() <= r.bottom();
1059}
1060
Hal Canary9b9510a2017-07-18 09:39:00 -04001061static sk_sp<SkImage> image_from_mask(const SkMask& mask) {
1062 if (!mask.fImage) {
1063 return nullptr;
1064 }
1065 SkIRect bounds = mask.fBounds;
1066 SkBitmap bm;
1067 switch (mask.fFormat) {
1068 case SkMask::kBW_Format:
1069 bm.allocPixels(SkImageInfo::MakeA8(bounds.width(), bounds.height()));
1070 for (int y = 0; y < bm.height(); ++y) {
1071 for (int x8 = 0; x8 < bm.width(); x8 += 8) {
1072 uint8_t v = *mask.getAddr1(x8 + bounds.x(), y + bounds.y());
1073 int e = SkTMin(x8 + 8, bm.width());
1074 for (int x = x8; x < e; ++x) {
1075 *bm.getAddr8(x, y) = (v >> (x & 0x7)) & 0x1 ? 0xFF : 0x00;
1076 }
1077 }
1078 }
1079 bm.setImmutable();
1080 return SkImage::MakeFromBitmap(bm);
1081 case SkMask::kA8_Format:
1082 bm.installPixels(SkImageInfo::MakeA8(bounds.width(), bounds.height()),
1083 mask.fImage, mask.fRowBytes);
1084 return SkMakeImageFromRasterBitmap(bm, kAlways_SkCopyPixelsMode);
1085 case SkMask::kARGB32_Format:
1086 bm.installPixels(SkImageInfo::MakeN32Premul(bounds.width(), bounds.height()),
1087 mask.fImage, mask.fRowBytes);
1088 return SkMakeImageFromRasterBitmap(bm, kAlways_SkCopyPixelsMode);
1089 case SkMask::k3D_Format:
1090 SkASSERT(false);
1091 return nullptr;
1092 case SkMask::kLCD16_Format:
1093 SkASSERT(false);
1094 return nullptr;
1095 default:
1096 SkASSERT(false);
1097 return nullptr;
1098 }
1099}
1100
Hal Canary98caedd2018-07-23 10:50:49 -04001101void SkPDFDevice::internalDrawGlyphRun(const SkGlyphRun& glyphRun, SkPoint offset) {
1102
1103 const SkGlyphID* glyphs = glyphRun.shuntGlyphsIDs().data();
1104 uint32_t glyphCount = SkToU32(glyphRun.shuntGlyphsIDs().size());
1105 const SkPaint& srcPaint = glyphRun.paint();
1106 if (!glyphCount || !glyphs || srcPaint.getTextSize() <= 0 || this->hasEmptyClip()) {
Hal Canaryd12a6762017-05-26 17:01:16 -04001107 return;
1108 }
Hal Canaryd12a6762017-05-26 17:01:16 -04001109 if (srcPaint.getPathEffect()
Hal Canary98caedd2018-07-23 10:50:49 -04001110 || srcPaint.getMaskFilter()
1111 || srcPaint.isVerticalText()
Hal Canary92c500b2018-09-19 16:19:45 -04001112 || this->ctm().hasPerspective()
Hal Canary98caedd2018-07-23 10:50:49 -04001113 || SkPaint::kFill_Style != srcPaint.getStyle()) {
Hal Canaryd12a6762017-05-26 17:01:16 -04001114 // Stroked Text doesn't work well with Type3 fonts.
Hal Canary98caedd2018-07-23 10:50:49 -04001115 return draw_glyph_run_as_path(this, glyphRun, offset);
halcanarye06ca962016-09-09 05:34:55 -07001116 }
halcanary4ed2f012016-08-15 18:40:07 -07001117 SkPaint paint = calculate_text_paint(srcPaint);
Hal Canaryd12a6762017-05-26 17:01:16 -04001118 remove_color_filter(&paint);
halcanary4ed2f012016-08-15 18:40:07 -07001119 replace_srcmode_on_opaque_paint(&paint);
Hal Canary98caedd2018-07-23 10:50:49 -04001120 paint.setHinting(SkPaint::kNo_Hinting);
halcanary4ed2f012016-08-15 18:40:07 -07001121 if (!paint.getTypeface()) {
1122 paint.setTypeface(SkTypeface::MakeDefault());
1123 }
1124 SkTypeface* typeface = paint.getTypeface();
1125 if (!typeface) {
1126 SkDebugf("SkPDF: SkTypeface::MakeDefault() returned nullptr.\n");
1127 return;
1128 }
Hal Canary98caedd2018-07-23 10:50:49 -04001129 const SkAdvancedTypefaceMetrics* metrics = SkPDFFont::GetMetrics(typeface, fDocument->canon());
halcanary4871f222016-08-26 13:17:44 -07001130 if (!metrics) {
halcanary4ed2f012016-08-15 18:40:07 -07001131 return;
1132 }
Hal Canary46cc3da2018-05-09 11:50:34 -04001133 const std::vector<SkUnichar>& glyphToUnicode = SkPDFFont::GetUnicodeMap(
1134 typeface, fDocument->canon());
1135
Hal Canary98caedd2018-07-23 10:50:49 -04001136 SkClusterator clusterator(glyphRun);
Hal Canaryaa3af7b2017-03-06 16:18:49 -05001137
1138 int emSize;
Herb Derby1a605cd2018-03-22 11:16:25 -04001139 auto glyphCache = SkPDFFont::MakeVectorCache(typeface, &emSize);
Hal Canaryaa3af7b2017-03-06 16:18:49 -05001140
1141 SkScalar textSize = paint.getTextSize();
1142 SkScalar advanceScale = textSize * paint.getTextScaleX() / emSize;
halcanary4ed2f012016-08-15 18:40:07 -07001143
Hal Canary699b8732017-06-13 12:13:29 -04001144 // textScaleX and textScaleY are used to get a conservative bounding box for glyphs.
1145 SkScalar textScaleY = textSize / emSize;
1146 SkScalar textScaleX = advanceScale + paint.getTextSkewX() * textScaleY;
1147
Hal Canary98caedd2018-07-23 10:50:49 -04001148 SkASSERT(paint.getTextAlign() == SkPaint::kLeft_Align);
Hal Canary22b2d8c2017-07-19 14:46:12 -04001149 SkRect clipStackBounds = this->cs().bounds(this->bounds());
Hal Canary9b9510a2017-07-18 09:39:00 -04001150 struct PositionedGlyph {
1151 SkPoint fPos;
1152 SkGlyphID fGlyph;
1153 };
Hal Canary9e41c212018-09-03 12:00:23 -04001154 std::vector<PositionedGlyph> missingGlyphs;
Hal Canary9b9510a2017-07-18 09:39:00 -04001155 {
1156 ScopedContentEntry content(this, paint, true);
1157 if (!content.entry()) {
1158 return;
robertphillips5ba165e2016-08-15 15:36:58 -07001159 }
Hal Canary9b9510a2017-07-18 09:39:00 -04001160 SkDynamicMemoryWStream* out = content.stream();
Hal Canary9b9510a2017-07-18 09:39:00 -04001161
1162 out->writeText("BT\n");
Dominic Mazzoni656cefe2018-09-25 20:29:15 -07001163
1164 int markId = -1;
1165 if (fNodeId) {
1166 markId = fDocument->getMarkIdForNodeId(fNodeId);
1167 }
1168
1169 if (markId != -1) {
1170 out->writeText("/P <</MCID ");
1171 out->writeDecAsText(markId);
1172 out->writeText(" >>BDC\n");
1173 }
1174 SK_AT_SCOPE_EXIT(if (markId != -1) out->writeText("EMC\n"));
1175
Hal Canary9b9510a2017-07-18 09:39:00 -04001176 SK_AT_SCOPE_EXIT(out->writeText("ET\n"));
1177
1178 const SkGlyphID maxGlyphID = SkToU16(typeface->countGlyphs() - 1);
1179
1180 bool multiByteGlyphs = SkPDFFont::IsMultiByte(SkPDFFont::FontType(*metrics));
1181 if (clusterator.reversedChars()) {
1182 out->writeText("/ReversedChars BMC\n");
1183 }
1184 SK_AT_SCOPE_EXIT(if (clusterator.reversedChars()) { out->writeText("EMC\n"); } );
1185 GlyphPositioner glyphPositioner(out,
1186 paint.getTextSkewX(),
1187 multiByteGlyphs,
Hal Canary9b9510a2017-07-18 09:39:00 -04001188 offset);
1189 SkPDFFont* font = nullptr;
1190
Hal Canary1521c8a2018-03-28 09:51:00 -07001191 while (SkClusterator::Cluster c = clusterator.next()) {
Hal Canary9b9510a2017-07-18 09:39:00 -04001192 int index = c.fGlyphIndex;
1193 int glyphLimit = index + c.fGlyphCount;
1194
1195 bool actualText = false;
1196 SK_AT_SCOPE_EXIT(if (actualText) {
1197 glyphPositioner.flush();
1198 out->writeText("EMC\n");
1199 });
1200 if (c.fUtf8Text) { // real cluster
1201 // Check if `/ActualText` needed.
1202 const char* textPtr = c.fUtf8Text;
1203 const char* textEnd = c.fUtf8Text + c.fTextByteLength;
Hal Canaryf107a2f2018-07-25 16:52:48 -04001204 SkUnichar unichar = SkUTF::NextUTF8(&textPtr, textEnd);
Hal Canary9b9510a2017-07-18 09:39:00 -04001205 if (unichar < 0) {
halcanaryf59d18a2016-09-16 14:44:57 -07001206 return;
1207 }
Hal Canary9b9510a2017-07-18 09:39:00 -04001208 if (textPtr < textEnd || // more characters left
1209 glyphLimit > index + 1 || // toUnicode wouldn't work
1210 unichar != map_glyph(glyphToUnicode, glyphs[index])) // test single Unichar map
1211 {
1212 glyphPositioner.flush();
1213 out->writeText("/Span<</ActualText <");
1214 SkPDFUtils::WriteUTF16beHex(out, 0xFEFF); // U+FEFF = BYTE ORDER MARK
1215 // the BOM marks this text as UTF-16BE, not PDFDocEncoding.
1216 SkPDFUtils::WriteUTF16beHex(out, unichar); // first char
1217 while (textPtr < textEnd) {
Hal Canaryf107a2f2018-07-25 16:52:48 -04001218 unichar = SkUTF::NextUTF8(&textPtr, textEnd);
Hal Canary9b9510a2017-07-18 09:39:00 -04001219 if (unichar < 0) {
1220 break;
1221 }
1222 SkPDFUtils::WriteUTF16beHex(out, unichar);
1223 }
1224 out->writeText("> >> BDC\n"); // begin marked-content sequence
1225 // with an associated property list.
1226 actualText = true;
halcanaryf59d18a2016-09-16 14:44:57 -07001227 }
halcanaryf59d18a2016-09-16 14:44:57 -07001228 }
Hal Canary9b9510a2017-07-18 09:39:00 -04001229 for (; index < glyphLimit; ++index) {
1230 SkGlyphID gid = glyphs[index];
1231 if (gid > maxGlyphID) {
1232 continue;
halcanaryf59d18a2016-09-16 14:44:57 -07001233 }
Hal Canary9b9510a2017-07-18 09:39:00 -04001234 if (!font || !font->hasGlyph(gid)) {
1235 // Not yet specified font or need to switch font.
Hal Canary9e41c212018-09-03 12:00:23 -04001236 sk_sp<SkPDFFont> newFont =
1237 SkPDFFont::GetFontResource(fDocument->canon(), typeface, gid);
1238 SkASSERT(newFont); // All preconditions for SkPDFFont::GetFontResource are met.
1239 if (!newFont) {
Hal Canary9b9510a2017-07-18 09:39:00 -04001240 return;
1241 }
Hal Canary9e41c212018-09-03 12:00:23 -04001242 font = newFont.get();
1243 fDocument->registerFont(font);
1244 int fontIndex = find_or_add(&fFontResources, std::move(newFont));
1245
Hal Canary9b9510a2017-07-18 09:39:00 -04001246 glyphPositioner.flush();
Hal Canarye650b852018-09-12 09:12:36 -04001247
1248 SkPDFWriteResourceName(out, SkPDFResourceType::kFont, fontIndex);
1249 out->writeText(" ");
1250 SkPDFUtils::AppendScalar(textSize, out);
1251 out->writeText(" Tf\n");
1252
Hal Canary9b9510a2017-07-18 09:39:00 -04001253 SkASSERT(font->multiByteGlyphs() == multiByteGlyphs);
1254 }
Hal Canary98caedd2018-07-23 10:50:49 -04001255 SkPoint xy = glyphRun.positions()[index];
1256 // Do a glyph-by-glyph bounds-reject if positions are absolute.
1257 SkRect glyphBounds = get_glyph_bounds_device_space(
1258 gid, glyphCache.get(), textScaleX, textScaleY,
1259 xy + offset, this->ctm());
1260 if (glyphBounds.isEmpty()) {
1261 if (!contains(clipStackBounds, {glyphBounds.x(), glyphBounds.y()})) {
1262 continue;
Hal Canary699b8732017-06-13 12:13:29 -04001263 }
1264 } else {
Hal Canary98caedd2018-07-23 10:50:49 -04001265 if (!clipStackBounds.intersects(glyphBounds)) {
1266 continue; // reject glyphs as out of bounds
Hal Canary699b8732017-06-13 12:13:29 -04001267 }
1268 }
Hal Canary98caedd2018-07-23 10:50:49 -04001269 if (!has_outline_glyph(gid, glyphCache.get())) {
1270 missingGlyphs.push_back({xy + offset, gid});
1271 }
1272
Hal Canary9b9510a2017-07-18 09:39:00 -04001273 font->noteGlyphUsage(gid);
1274
1275 SkGlyphID encodedGlyph = multiByteGlyphs ? gid : font->glyphToPDFFontEncoding(gid);
Hal Canary98caedd2018-07-23 10:50:49 -04001276 SkScalar advance = advanceScale * glyphCache->getGlyphIDAdvance(gid).fAdvanceX;
Hal Canary9b9510a2017-07-18 09:39:00 -04001277 glyphPositioner.writeGlyph(xy, advance, encodedGlyph);
halcanaryf59d18a2016-09-16 14:44:57 -07001278 }
Hal Canary9b9510a2017-07-18 09:39:00 -04001279 }
1280 }
Hal Canary9e41c212018-09-03 12:00:23 -04001281 if (missingGlyphs.size() > 0) {
Hal Canary9b9510a2017-07-18 09:39:00 -04001282 // Fall back on images.
1283 SkPaint scaledGlyphCachePaint;
1284 scaledGlyphCachePaint.setTextSize(paint.getTextSize());
1285 scaledGlyphCachePaint.setTextScaleX(paint.getTextScaleX());
1286 scaledGlyphCachePaint.setTextSkewX(paint.getTextSkewX());
1287 scaledGlyphCachePaint.setTypeface(sk_ref_sp(typeface));
Herb Derbyfa996902018-04-18 11:36:12 -04001288 auto scaledGlyphCache = SkStrikeCache::FindOrCreateStrikeExclusive(scaledGlyphCachePaint);
Hal Canary9b9510a2017-07-18 09:39:00 -04001289 SkTHashMap<SkPDFCanon::BitmapGlyphKey, SkPDFCanon::BitmapGlyph>* map =
1290 &this->getCanon()->fBitmapGlyphImages;
Hal Canary98caedd2018-07-23 10:50:49 -04001291 for (PositionedGlyph positionedGlyph : missingGlyphs) {
Hal Canary9b9510a2017-07-18 09:39:00 -04001292 SkPDFCanon::BitmapGlyphKey key = {typeface->uniqueID(),
1293 paint.getTextSize(),
1294 paint.getTextScaleX(),
1295 paint.getTextSkewX(),
1296 positionedGlyph.fGlyph,
1297 0};
1298 SkImage* img = nullptr;
1299 SkIPoint imgOffset = {0, 0};
1300 if (SkPDFCanon::BitmapGlyph* ptr = map->find(key)) {
1301 img = ptr->fImage.get();
1302 imgOffset = ptr->fOffset;
1303 } else {
1304 (void)scaledGlyphCache->findImage(
1305 scaledGlyphCache->getGlyphIDMetrics(positionedGlyph.fGlyph));
1306 SkMask mask;
1307 scaledGlyphCache->getGlyphIDMetrics(positionedGlyph.fGlyph).toMask(&mask);
1308 imgOffset = {mask.fBounds.x(), mask.fBounds.y()};
1309 img = map->set(key, {image_from_mask(mask), imgOffset})->fImage.get();
1310 }
1311 if (img) {
1312 SkPoint pt = positionedGlyph.fPos +
1313 SkPoint{(SkScalar)imgOffset.x(), (SkScalar)imgOffset.y()};
1314 this->drawImage(img, pt.x(), pt.y(), srcPaint);
1315 }
halcanaryf59d18a2016-09-16 14:44:57 -07001316 }
robertphillips5ba165e2016-08-15 15:36:58 -07001317 }
halcanary4ed2f012016-08-15 18:40:07 -07001318}
1319
Herb Derbyb935cf82018-07-26 16:54:18 -04001320void SkPDFDevice::drawGlyphRunList(const SkGlyphRunList& glyphRunList) {
1321 for (const SkGlyphRun& glyphRun : glyphRunList) {
1322 this->internalDrawGlyphRun(glyphRun, glyphRunList.origin());
halcanarye06ca962016-09-09 05:34:55 -07001323 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001324}
1325
Ruiqi Maoc97a3392018-08-15 10:44:19 -04001326void SkPDFDevice::drawVertices(const SkVertices*, const SkVertices::Bone[], int, SkBlendMode,
Ruiqi Maof5101492018-06-29 14:32:21 -04001327 const SkPaint&) {
Hal Canaryb4e528d2018-03-09 16:02:15 -05001328 if (this->hasEmptyClip()) {
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +00001329 return;
1330 }
reed@google.com85e143c2013-12-30 15:51:25 +00001331 // TODO: implement drawVertices
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001332}
1333
Hal Canarye650b852018-09-12 09:12:36 -04001334static void draw_form_xobject(int objectIndex, SkWStream* content) {
1335 SkPDFWriteResourceName(content, SkPDFResourceType::kXObject, objectIndex);
1336 content->writeText(" Do\n");
1337}
1338
Mike Reeda1361362017-03-07 09:37:29 -05001339void SkPDFDevice::drawDevice(SkBaseDevice* device, int x, int y, const SkPaint& paint) {
reedcf5c8462016-07-20 12:28:40 -07001340 SkASSERT(!paint.getImageFilter());
1341
reed7503d602016-07-15 14:23:29 -07001342 // Check if the source device is really a bitmapdevice (because that's what we returned
1343 // from createDevice (likely due to an imagefilter)
1344 SkPixmap pmap;
1345 if (device->peekPixels(&pmap)) {
1346 SkBitmap bitmap;
1347 bitmap.installPixels(pmap);
Mike Reeda1361362017-03-07 09:37:29 -05001348 this->drawSprite(bitmap, x, y, paint);
reed7503d602016-07-15 14:23:29 -07001349 return;
1350 }
1351
fmalita6987dca2014-11-13 08:33:37 -08001352 // our onCreateCompatibleDevice() always creates SkPDFDevice subclasses.
vandebo@chromium.orgee7a9562011-05-24 17:38:01 +00001353 SkPDFDevice* pdfDevice = static_cast<SkPDFDevice*>(device);
wangxianzhuef6c50a2015-09-17 20:38:02 -07001354
1355 SkScalar scalarX = SkIntToScalar(x);
1356 SkScalar scalarY = SkIntToScalar(y);
halcanary91fcb3e2016-03-04 13:53:22 -08001357 for (const RectWithData& l : pdfDevice->fLinkToURLs) {
1358 SkRect r = l.rect.makeOffset(scalarX, scalarY);
Hal Canary5c1b3602017-04-17 16:30:06 -04001359 fLinkToURLs.emplace_back(RectWithData{r, l.data});
wangxianzhuef6c50a2015-09-17 20:38:02 -07001360 }
halcanary91fcb3e2016-03-04 13:53:22 -08001361 for (const RectWithData& l : pdfDevice->fLinkToDestinations) {
1362 SkRect r = l.rect.makeOffset(scalarX, scalarY);
Hal Canary5c1b3602017-04-17 16:30:06 -04001363 fLinkToDestinations.emplace_back(RectWithData{r, l.data});
wangxianzhuef6c50a2015-09-17 20:38:02 -07001364 }
halcanary91fcb3e2016-03-04 13:53:22 -08001365 for (const NamedDestination& d : pdfDevice->fNamedDestinations) {
1366 SkPoint p = d.point + SkPoint::Make(scalarX, scalarY);
Hal Canary5c1b3602017-04-17 16:30:06 -04001367 fNamedDestinations.emplace_back(NamedDestination{d.nameData, p});
wangxianzhuef6c50a2015-09-17 20:38:02 -07001368 }
1369
ctguil@chromium.orgf4ff39c2011-05-24 19:55:05 +00001370 if (pdfDevice->isContentEmpty()) {
vandebo@chromium.orgee7a9562011-05-24 17:38:01 +00001371 return;
1372 }
1373
Mike Reeda1361362017-03-07 09:37:29 -05001374 SkMatrix matrix = SkMatrix::MakeTrans(SkIntToScalar(x), SkIntToScalar(y));
Hal Canaryd892a9d2018-09-24 21:20:47 -04001375 ScopedContentEntry content(this, &this->cs(), matrix, paint);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001376 if (!content.entry()) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001377 return;
1378 }
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001379 if (content.needShape()) {
Hal Canaryd791a6f2018-09-28 12:14:08 -04001380 SkISize dim = device->imageInfo().dimensions();
1381 content.setShape(to_path(SkRect::Make(SkIRect::MakeXYWH(x, y, dim.width(), dim.height()))));
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001382 }
1383 if (!content.needSource()) {
1384 return;
1385 }
vandebo@chromium.org1aef2ed2011-02-03 21:46:10 +00001386
Hal Canary9e41c212018-09-03 12:00:23 -04001387 int xObjectResourceIndex = find_or_add(&fXObjectResources,
1388 pdfDevice->makeFormXObjectFromDevice());
Hal Canarye650b852018-09-12 09:12:36 -04001389 draw_form_xobject(xObjectResourceIndex, content.stream());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001390}
1391
reede8f30622016-03-23 18:59:25 -07001392sk_sp<SkSurface> SkPDFDevice::makeSurface(const SkImageInfo& info, const SkSurfaceProps& props) {
1393 return SkSurface::MakeRaster(info, &props);
reed89443ab2014-06-27 11:34:19 -07001394}
1395
ctguil@chromium.org8dcf74f2011-07-12 21:56:27 +00001396
Hal Canary9e41c212018-09-03 12:00:23 -04001397sk_sp<SkPDFDict> SkPDFDevice::makeResourceDict() {
Hal Canarye650b852018-09-12 09:12:36 -04001398 return SkPDFMakeResourceDict(std::move(fGraphicStateResources),
Hal Canary9e41c212018-09-03 12:00:23 -04001399 std::move(fShaderResources),
1400 std::move(fXObjectResources),
1401 std::move(fFontResources));
vandebo@chromium.orgfc166672013-07-22 18:31:24 +00001402}
1403
Hal Canaryb400d4d2018-09-26 16:33:52 -04001404std::unique_ptr<SkStreamAsset> SkPDFDevice::content() {
1405 if (fActiveStackState.fContentStream) {
1406 fActiveStackState.drainStack();
1407 fActiveStackState = GraphicStackState();
1408 }
1409
halcanary334fcbc2015-02-24 12:56:16 -08001410 SkDynamicMemoryWStream buffer;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001411 if (fInitialTransform.getType() != SkMatrix::kIdentity_Mask) {
Hal Canarye650b852018-09-12 09:12:36 -04001412 append_transform(fInitialTransform, &buffer);
vandebo@chromium.orgc2a9b7f2011-02-24 23:22:30 +00001413 }
Hal Canaryb400d4d2018-09-26 16:33:52 -04001414 if (fContentEntries.back() && fContentEntries.back() == fContentEntries.front()) {
1415 fContentEntries.front()->writeToAndReset(&buffer);
1416 } else {
1417 for (SkDynamicMemoryWStream& entry : fContentEntries) {
1418 buffer.writeText("q\n");
1419 entry.writeToAndReset(&buffer);
1420 buffer.writeText("Q\n");
1421 }
halcanary2be7e012016-03-28 11:58:08 -07001422 }
Hal Canaryb400d4d2018-09-26 16:33:52 -04001423 fContentEntries.reset();
halcanary022c2bd2016-09-02 11:29:46 -07001424 if (buffer.bytesWritten() > 0) {
1425 return std::unique_ptr<SkStreamAsset>(buffer.detachAsStream());
1426 } else {
1427 return skstd::make_unique<SkMemoryStream>();
1428 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001429}
1430
commit-bot@chromium.org92ffe7d2013-07-31 22:54:31 +00001431/* Draws an inverse filled path by using Path Ops to compute the positive
1432 * inverse using the current clip as the inverse bounds.
1433 * Return true if this was an inverse path and was properly handled,
1434 * otherwise returns false and the normal drawing routine should continue,
1435 * either as a (incorrect) fallback or because the path was not inverse
1436 * in the first place.
1437 */
Mike Reeda1361362017-03-07 09:37:29 -05001438bool SkPDFDevice::handleInversePath(const SkPath& origPath,
Robert Phillips137ca522018-08-15 10:14:33 -04001439 const SkPaint& paint,
1440 bool pathIsMutable) {
commit-bot@chromium.org92ffe7d2013-07-31 22:54:31 +00001441 if (!origPath.isInverseFillType()) {
1442 return false;
1443 }
1444
Hal Canaryb4e528d2018-03-09 16:02:15 -05001445 if (this->hasEmptyClip()) {
commit-bot@chromium.org92ffe7d2013-07-31 22:54:31 +00001446 return false;
1447 }
1448
1449 SkPath modifiedPath;
1450 SkPath* pathPtr = const_cast<SkPath*>(&origPath);
1451 SkPaint noInversePaint(paint);
1452
1453 // Merge stroking operations into final path.
1454 if (SkPaint::kStroke_Style == paint.getStyle() ||
1455 SkPaint::kStrokeAndFill_Style == paint.getStyle()) {
1456 bool doFillPath = paint.getFillPath(origPath, &modifiedPath);
1457 if (doFillPath) {
1458 noInversePaint.setStyle(SkPaint::kFill_Style);
1459 noInversePaint.setStrokeWidth(0);
1460 pathPtr = &modifiedPath;
1461 } else {
1462 // To be consistent with the raster output, hairline strokes
1463 // are rendered as non-inverted.
1464 modifiedPath.toggleInverseFillType();
Robert Phillips137ca522018-08-15 10:14:33 -04001465 this->drawPath(modifiedPath, paint, true);
commit-bot@chromium.org92ffe7d2013-07-31 22:54:31 +00001466 return true;
1467 }
1468 }
1469
1470 // Get bounds of clip in current transform space
1471 // (clip bounds are given in device space).
commit-bot@chromium.org92ffe7d2013-07-31 22:54:31 +00001472 SkMatrix transformInverse;
Mike Reeda1361362017-03-07 09:37:29 -05001473 SkMatrix totalMatrix = this->ctm();
Robert Phillips137ca522018-08-15 10:14:33 -04001474
edisonn@google.coma9ebd162013-10-07 13:22:21 +00001475 if (!totalMatrix.invert(&transformInverse)) {
commit-bot@chromium.org92ffe7d2013-07-31 22:54:31 +00001476 return false;
1477 }
Hal Canary22b2d8c2017-07-19 14:46:12 -04001478 SkRect bounds = this->cs().bounds(this->bounds());
commit-bot@chromium.org92ffe7d2013-07-31 22:54:31 +00001479 transformInverse.mapRect(&bounds);
1480
1481 // Extend the bounds by the line width (plus some padding)
1482 // so the edge doesn't cause a visible stroke.
1483 bounds.outset(paint.getStrokeWidth() + SK_Scalar1,
1484 paint.getStrokeWidth() + SK_Scalar1);
1485
1486 if (!calculate_inverse_path(bounds, *pathPtr, &modifiedPath)) {
1487 return false;
1488 }
1489
Robert Phillips137ca522018-08-15 10:14:33 -04001490 this->drawPath(modifiedPath, noInversePaint, true);
commit-bot@chromium.org92ffe7d2013-07-31 22:54:31 +00001491 return true;
1492}
1493
Hal Canary9e41c212018-09-03 12:00:23 -04001494sk_sp<SkPDFArray> SkPDFDevice::getAnnotations() {
1495 sk_sp<SkPDFArray> array;
1496 size_t count = fLinkToURLs.size() + fLinkToDestinations.size();
1497 if (0 == count) {
1498 return array;
1499 }
1500 array = sk_make_sp<SkPDFArray>();
1501 array->reserve(count);
halcanary91fcb3e2016-03-04 13:53:22 -08001502 for (const RectWithData& rectWithURL : fLinkToURLs) {
wangxianzhuef6c50a2015-09-17 20:38:02 -07001503 SkRect r;
halcanary91fcb3e2016-03-04 13:53:22 -08001504 fInitialTransform.mapRect(&r, rectWithURL.rect);
Hal Canary7675b362018-06-15 15:32:14 -04001505 array->appendObjRef(create_link_to_url(rectWithURL.data.get(), r));
wangxianzhuef6c50a2015-09-17 20:38:02 -07001506 }
halcanary91fcb3e2016-03-04 13:53:22 -08001507 for (const RectWithData& linkToDestination : fLinkToDestinations) {
wangxianzhuef6c50a2015-09-17 20:38:02 -07001508 SkRect r;
halcanary91fcb3e2016-03-04 13:53:22 -08001509 fInitialTransform.mapRect(&r, linkToDestination.rect);
Hal Canary7675b362018-06-15 15:32:14 -04001510 array->appendObjRef(
halcanaryd7b28852016-03-07 12:39:14 -08001511 create_link_named_dest(linkToDestination.data.get(), r));
wangxianzhuef6c50a2015-09-17 20:38:02 -07001512 }
Hal Canary9e41c212018-09-03 12:00:23 -04001513 return array;
wangxianzhuef6c50a2015-09-17 20:38:02 -07001514}
epoger@google.comb58772f2013-03-08 09:09:10 +00001515
halcanary6d622702015-03-25 08:45:42 -07001516void SkPDFDevice::appendDestinations(SkPDFDict* dict, SkPDFObject* page) const {
halcanary91fcb3e2016-03-04 13:53:22 -08001517 for (const NamedDestination& dest : fNamedDestinations) {
halcanaryece83922016-03-08 08:32:12 -08001518 auto pdfDest = sk_make_sp<SkPDFArray>();
epoger@google.comb58772f2013-03-08 09:09:10 +00001519 pdfDest->reserve(5);
halcanarye94ea622016-03-09 07:52:09 -08001520 pdfDest->appendObjRef(sk_ref_sp(page));
epoger@google.comb58772f2013-03-08 09:09:10 +00001521 pdfDest->appendName("XYZ");
halcanary91fcb3e2016-03-04 13:53:22 -08001522 SkPoint p = fInitialTransform.mapXY(dest.point.x(), dest.point.y());
wangxianzhuef6c50a2015-09-17 20:38:02 -07001523 pdfDest->appendScalar(p.x());
1524 pdfDest->appendScalar(p.y());
epoger@google.comb58772f2013-03-08 09:09:10 +00001525 pdfDest->appendInt(0); // Leave zoom unchanged
halcanary91fcb3e2016-03-04 13:53:22 -08001526 SkString name(static_cast<const char*>(dest.nameData->data()));
halcanary8103a342016-03-08 15:10:16 -08001527 dict->insertObject(name, std::move(pdfDest));
epoger@google.comb58772f2013-03-08 09:09:10 +00001528 }
vandebo@chromium.org238be8c2012-07-13 20:06:02 +00001529}
1530
Hal Canaryb4bd5ef2017-07-26 09:16:01 -04001531sk_sp<SkPDFObject> SkPDFDevice::makeFormXObjectFromDevice(bool alpha) {
halcanary5abbb442016-07-29 08:41:33 -07001532 SkMatrix inverseTransform = SkMatrix::I();
halcanaryafdc1772016-08-23 09:02:12 -07001533 if (!fInitialTransform.isIdentity()) {
1534 if (!fInitialTransform.invert(&inverseTransform)) {
halcanary5abbb442016-07-29 08:41:33 -07001535 SkDEBUGFAIL("Layer initial transform should be invertible.");
1536 inverseTransform.reset();
1537 }
1538 }
Hal Canaryb4bd5ef2017-07-26 09:16:01 -04001539 const char* colorSpace = alpha ? "DeviceGray" : nullptr;
Hal Canarye650b852018-09-12 09:12:36 -04001540
halcanary4b1e17e2016-07-27 14:49:46 -07001541 sk_sp<SkPDFObject> xobject =
Hal Canarye650b852018-09-12 09:12:36 -04001542 SkPDFMakeFormXObject(this->content(),
1543 SkPDFMakeArray(0, 0, this->width(), this->height()),
Hal Canaryb4bd5ef2017-07-26 09:16:01 -04001544 this->makeResourceDict(), inverseTransform, colorSpace);
vandebo@chromium.org98594282011-07-25 22:34:12 +00001545 // We always draw the form xobjects that we create back into the device, so
1546 // we simply preserve the font usage instead of pulling it out and merging
1547 // it back in later.
Hal Canary9e41c212018-09-03 12:00:23 -04001548 this->reset();
reed@google.comfc641d02012-09-20 17:52:20 +00001549 return xobject;
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001550}
1551
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001552void SkPDFDevice::drawFormXObjectWithMask(int xObjectIndex,
halcanarydabd4f02016-08-03 11:16:56 -07001553 sk_sp<SkPDFObject> mask,
reed374772b2016-10-05 17:33:02 -07001554 SkBlendMode mode,
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001555 bool invertClip) {
halcanary4b1e17e2016-07-27 14:49:46 -07001556 sk_sp<SkPDFDict> sMaskGS = SkPDFGraphicState::GetSMaskGraphicState(
halcanarydabd4f02016-08-03 11:16:56 -07001557 std::move(mask), invertClip,
1558 SkPDFGraphicState::kAlpha_SMaskMode, fDocument->canon());
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001559
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001560 SkPaint paint;
reed374772b2016-10-05 17:33:02 -07001561 paint.setBlendMode(mode);
Hal Canaryd892a9d2018-09-24 21:20:47 -04001562 ScopedContentEntry content(this, nullptr, SkMatrix::I(), paint);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001563 if (!content.entry()) {
1564 return;
1565 }
Hal Canary9e41c212018-09-03 12:00:23 -04001566 int gStateResourceIndex = find_or_add(&fGraphicStateResources, std::move(sMaskGS));
1567 SkPDFUtils::ApplyGraphicState(gStateResourceIndex, content.stream());
Hal Canarye650b852018-09-12 09:12:36 -04001568 draw_form_xobject(xObjectIndex, content.stream());
Hal Canaryc02de0b2017-06-28 13:14:03 -04001569 this->clearMaskOnGraphicState(content.stream());
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001570}
1571
Hal Canaryb400d4d2018-09-26 16:33:52 -04001572
1573static bool treat_as_regular_pdf_blend_mode(SkBlendMode blendMode) {
1574 return nullptr != SkPDFUtils::BlendModeName(blendMode);
1575}
1576
1577SkDynamicMemoryWStream* SkPDFDevice::setUpContentEntry(const SkClipStack* clipStack,
1578 const SkMatrix& matrix,
1579 const SkPaint& paint,
1580 bool hasText,
1581 sk_sp<SkPDFObject>* dst) {
halcanary96fcdcc2015-08-27 07:41:13 -07001582 *dst = nullptr;
reed374772b2016-10-05 17:33:02 -07001583 SkBlendMode blendMode = paint.getBlendMode();
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001584
Hal Canaryb400d4d2018-09-26 16:33:52 -04001585 // Dst xfer mode doesn't draw source at all.
1586 if (blendMode == SkBlendMode::kDst) {
1587 return nullptr;
1588 }
1589
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001590 // For the following modes, we want to handle source and destination
1591 // separately, so make an object of what's already there.
Hal Canaryb400d4d2018-09-26 16:33:52 -04001592 if (!treat_as_regular_pdf_blend_mode(blendMode) && blendMode != SkBlendMode::kDstOver) {
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001593 if (!isContentEmpty()) {
halcanarydabd4f02016-08-03 11:16:56 -07001594 *dst = this->makeFormXObjectFromDevice();
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001595 SkASSERT(isContentEmpty());
reed374772b2016-10-05 17:33:02 -07001596 } else if (blendMode != SkBlendMode::kSrc &&
1597 blendMode != SkBlendMode::kSrcOut) {
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001598 // Except for Src and SrcOut, if there isn't anything already there,
1599 // then we're done.
halcanary96fcdcc2015-08-27 07:41:13 -07001600 return nullptr;
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001601 }
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001602 }
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +00001603 // TODO(vandebo): Figure out how/if we can handle the following modes:
Hal Canaryb400d4d2018-09-26 16:33:52 -04001604 // Xor, Plus. For now, we treat them as SrcOver/Normal.
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001605
Hal Canaryb400d4d2018-09-26 16:33:52 -04001606 if (treat_as_regular_pdf_blend_mode(blendMode)) {
1607 if (!fActiveStackState.fContentStream) {
1608 fActiveStackState = GraphicStackState(fContentEntries.emplace_back());
1609 }
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001610 } else {
Hal Canaryb400d4d2018-09-26 16:33:52 -04001611 fActiveStackState.drainStack();
1612 if (blendMode != SkBlendMode::kDstOver) {
1613 fActiveStackState = GraphicStackState(fContentEntries.emplace_back());
1614 } else {
1615 fActiveStackState = GraphicStackState(fContentEntries.emplace_front());
1616 }
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001617 }
Hal Canaryb400d4d2018-09-26 16:33:52 -04001618 SkASSERT(fActiveStackState.fContentStream);
1619 GraphicStateEntry entry;
1620 this->populateGraphicStateEntryFromPaint(matrix, clipStack, paint, hasText, &entry);
1621 fActiveStackState.updateClip(clipStack, this->bounds());
1622 fActiveStackState.updateMatrix(entry.fMatrix);
1623 fActiveStackState.updateDrawingState(entry);
1624
1625 return fActiveStackState.fContentStream;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001626}
1627
Hal Canaryb400d4d2018-09-26 16:33:52 -04001628void SkPDFDevice::finishContentEntry(const SkClipStack* clipStack,
1629 SkBlendMode blendMode,
halcanarydabd4f02016-08-03 11:16:56 -07001630 sk_sp<SkPDFObject> dst,
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001631 SkPath* shape) {
Hal Canaryb400d4d2018-09-26 16:33:52 -04001632 SkASSERT(blendMode != SkBlendMode::kDst);
1633 if (treat_as_regular_pdf_blend_mode(blendMode)) {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001634 SkASSERT(!dst);
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001635 return;
1636 }
Hal Canaryb400d4d2018-09-26 16:33:52 -04001637
1638 SkASSERT(fActiveStackState.fContentStream);
1639
1640 fActiveStackState.drainStack();
1641 fActiveStackState = GraphicStackState();
1642
reed374772b2016-10-05 17:33:02 -07001643 if (blendMode == SkBlendMode::kDstOver) {
commit-bot@chromium.org7542dc82013-12-03 21:08:46 +00001644 SkASSERT(!dst);
Hal Canaryb400d4d2018-09-26 16:33:52 -04001645 if (fContentEntries.front()->bytesWritten() == 0) {
commit-bot@chromium.org7542dc82013-12-03 21:08:46 +00001646 // For DstOver, an empty content entry was inserted before the rest
1647 // of the content entries. If nothing was drawn, it needs to be
1648 // removed.
halcanary2be7e012016-03-28 11:58:08 -07001649 fContentEntries.pop_front();
commit-bot@chromium.org7542dc82013-12-03 21:08:46 +00001650 }
1651 return;
1652 }
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001653 if (!dst) {
reed374772b2016-10-05 17:33:02 -07001654 SkASSERT(blendMode == SkBlendMode::kSrc ||
1655 blendMode == SkBlendMode::kSrcOut);
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001656 return;
1657 }
ctguil@chromium.org9510ccc2011-07-27 00:10:51 +00001658
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001659 SkASSERT(dst);
halcanary2be7e012016-03-28 11:58:08 -07001660 SkASSERT(fContentEntries.count() == 1);
commit-bot@chromium.org4e8f1e52013-12-17 23:38:28 +00001661 // Changing the current content into a form-xobject will destroy the clip
1662 // objects which is fine since the xobject will already be clipped. However
1663 // if source has shape, we need to clip it too, so a copy of the clip is
1664 // saved.
halcanary2be7e012016-03-28 11:58:08 -07001665
commit-bot@chromium.org7542dc82013-12-03 21:08:46 +00001666 SkPaint stockPaint;
1667
halcanary4b1e17e2016-07-27 14:49:46 -07001668 sk_sp<SkPDFObject> srcFormXObject;
Hal Canary9e41c212018-09-03 12:00:23 -04001669 if (this->isContentEmpty()) {
commit-bot@chromium.org7542dc82013-12-03 21:08:46 +00001670 // If nothing was drawn and there's no shape, then the draw was a
1671 // no-op, but dst needs to be restored for that to be true.
1672 // If there is shape, then an empty source with Src, SrcIn, SrcOut,
1673 // DstIn, DstAtop or Modulate reduces to Clear and DstOut or SrcAtop
1674 // reduces to Dst.
reed374772b2016-10-05 17:33:02 -07001675 if (shape == nullptr || blendMode == SkBlendMode::kDstOut ||
1676 blendMode == SkBlendMode::kSrcATop) {
Hal Canaryd892a9d2018-09-24 21:20:47 -04001677 ScopedContentEntry content(this, nullptr, SkMatrix::I(), stockPaint);
Hal Canarye650b852018-09-12 09:12:36 -04001678 draw_form_xobject(find_or_add(&fXObjectResources, std::move(dst)), content.stream());
commit-bot@chromium.org7542dc82013-12-03 21:08:46 +00001679 return;
1680 } else {
reed374772b2016-10-05 17:33:02 -07001681 blendMode = SkBlendMode::kClear;
commit-bot@chromium.org7542dc82013-12-03 21:08:46 +00001682 }
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001683 } else {
halcanary2be7e012016-03-28 11:58:08 -07001684 SkASSERT(fContentEntries.count() == 1);
halcanary4b1e17e2016-07-27 14:49:46 -07001685 srcFormXObject = this->makeFormXObjectFromDevice();
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001686 }
1687
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001688 // TODO(vandebo) srcFormXObject may contain alpha, but here we want it
1689 // without alpha.
reed374772b2016-10-05 17:33:02 -07001690 if (blendMode == SkBlendMode::kSrcATop) {
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001691 // TODO(vandebo): In order to properly support SrcATop we have to track
1692 // the shape of what's been drawn at all times. It's the intersection of
1693 // the non-transparent parts of the device and the outlines (shape) of
1694 // all images and devices drawn.
Hal Canary9e41c212018-09-03 12:00:23 -04001695 this->drawFormXObjectWithMask(find_or_add(&fXObjectResources, srcFormXObject), dst,
Hal Canaryd892a9d2018-09-24 21:20:47 -04001696 SkBlendMode::kSrcOver, true);
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001697 } else {
halcanary96fcdcc2015-08-27 07:41:13 -07001698 if (shape != nullptr) {
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001699 // Draw shape into a form-xobject.
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001700 SkPaint filledPaint;
1701 filledPaint.setColor(SK_ColorBLACK);
1702 filledPaint.setStyle(SkPaint::kFill_Style);
Hal Canaryb400d4d2018-09-26 16:33:52 -04001703 SkClipStack empty;
Hal Canary813b5ac2018-09-28 12:30:37 -04001704 SkPDFDevice shapeDev(this->size(), fDocument, fInitialTransform);
1705 shapeDev.internalDrawPath(clipStack ? *clipStack : empty,
1706 SkMatrix::I(), *shape, filledPaint, true);
Hal Canary9e41c212018-09-03 12:00:23 -04001707 this->drawFormXObjectWithMask(find_or_add(&fXObjectResources, dst),
Hal Canary813b5ac2018-09-28 12:30:37 -04001708 shapeDev.makeFormXObjectFromDevice(),
Mike Reeda1361362017-03-07 09:37:29 -05001709 SkBlendMode::kSrcOver, true);
halcanarydabd4f02016-08-03 11:16:56 -07001710 } else {
Hal Canary9e41c212018-09-03 12:00:23 -04001711 this->drawFormXObjectWithMask(find_or_add(&fXObjectResources, dst),
Hal Canaryd892a9d2018-09-24 21:20:47 -04001712 srcFormXObject, SkBlendMode::kSrcOver, true);
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001713 }
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001714 }
1715
reed374772b2016-10-05 17:33:02 -07001716 if (blendMode == SkBlendMode::kClear) {
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001717 return;
reed374772b2016-10-05 17:33:02 -07001718 } else if (blendMode == SkBlendMode::kSrc ||
1719 blendMode == SkBlendMode::kDstATop) {
Hal Canaryd892a9d2018-09-24 21:20:47 -04001720 ScopedContentEntry content(this, nullptr, SkMatrix::I(), stockPaint);
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001721 if (content.entry()) {
Hal Canarye650b852018-09-12 09:12:36 -04001722 draw_form_xobject(find_or_add(&fXObjectResources, srcFormXObject), content.stream());
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001723 }
reed374772b2016-10-05 17:33:02 -07001724 if (blendMode == SkBlendMode::kSrc) {
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001725 return;
1726 }
reed374772b2016-10-05 17:33:02 -07001727 } else if (blendMode == SkBlendMode::kSrcATop) {
Hal Canaryd892a9d2018-09-24 21:20:47 -04001728 ScopedContentEntry content(this, nullptr, SkMatrix::I(), stockPaint);
commit-bot@chromium.org7542dc82013-12-03 21:08:46 +00001729 if (content.entry()) {
Hal Canarye650b852018-09-12 09:12:36 -04001730 draw_form_xobject(find_or_add(&fXObjectResources, dst), content.stream());
commit-bot@chromium.org7542dc82013-12-03 21:08:46 +00001731 }
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001732 }
1733
reed374772b2016-10-05 17:33:02 -07001734 SkASSERT(blendMode == SkBlendMode::kSrcIn ||
1735 blendMode == SkBlendMode::kDstIn ||
1736 blendMode == SkBlendMode::kSrcOut ||
1737 blendMode == SkBlendMode::kDstOut ||
1738 blendMode == SkBlendMode::kSrcATop ||
1739 blendMode == SkBlendMode::kDstATop ||
1740 blendMode == SkBlendMode::kModulate);
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001741
reed374772b2016-10-05 17:33:02 -07001742 if (blendMode == SkBlendMode::kSrcIn ||
1743 blendMode == SkBlendMode::kSrcOut ||
1744 blendMode == SkBlendMode::kSrcATop) {
Hal Canaryd892a9d2018-09-24 21:20:47 -04001745 this->drawFormXObjectWithMask(find_or_add(&fXObjectResources, std::move(srcFormXObject)),
1746 std::move(dst), SkBlendMode::kSrcOver,
1747 blendMode == SkBlendMode::kSrcOut);
halcanarydabd4f02016-08-03 11:16:56 -07001748 return;
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001749 } else {
reed374772b2016-10-05 17:33:02 -07001750 SkBlendMode mode = SkBlendMode::kSrcOver;
Hal Canary9e41c212018-09-03 12:00:23 -04001751 int resourceID = find_or_add(&fXObjectResources, dst);
reed374772b2016-10-05 17:33:02 -07001752 if (blendMode == SkBlendMode::kModulate) {
Hal Canaryd892a9d2018-09-24 21:20:47 -04001753 this->drawFormXObjectWithMask(find_or_add(&fXObjectResources, srcFormXObject),
1754 std::move(dst), SkBlendMode::kSrcOver, false);
reed374772b2016-10-05 17:33:02 -07001755 mode = SkBlendMode::kMultiply;
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001756 }
Hal Canary9e41c212018-09-03 12:00:23 -04001757 this->drawFormXObjectWithMask(resourceID, std::move(srcFormXObject),
Hal Canaryd892a9d2018-09-24 21:20:47 -04001758 mode, blendMode == SkBlendMode::kDstOut);
halcanarydabd4f02016-08-03 11:16:56 -07001759 return;
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001760 }
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001761}
1762
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001763bool SkPDFDevice::isContentEmpty() {
Hal Canaryb400d4d2018-09-26 16:33:52 -04001764 if (!fContentEntries.front() || fContentEntries.front()->bytesWritten() == 0) {
halcanary2be7e012016-03-28 11:58:08 -07001765 SkASSERT(fContentEntries.count() <= 1);
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001766 return true;
1767 }
1768 return false;
1769}
1770
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001771void SkPDFDevice::populateGraphicStateEntryFromPaint(
1772 const SkMatrix& matrix,
Hal Canaryd892a9d2018-09-24 21:20:47 -04001773 const SkClipStack* clipStack,
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001774 const SkPaint& paint,
1775 bool hasText,
halcanary2be7e012016-03-28 11:58:08 -07001776 SkPDFDevice::GraphicStateEntry* entry) {
halcanary96fcdcc2015-08-27 07:41:13 -07001777 NOT_IMPLEMENTED(paint.getPathEffect() != nullptr, false);
1778 NOT_IMPLEMENTED(paint.getMaskFilter() != nullptr, false);
1779 NOT_IMPLEMENTED(paint.getColorFilter() != nullptr, false);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001780
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001781 entry->fMatrix = matrix;
Hal Canaryb400d4d2018-09-26 16:33:52 -04001782 entry->fClipStackGenID = clipStack ? clipStack->getTopmostGenID()
1783 : SkClipStack::kWideOpenGenID;
vandebo@chromium.orgda6c5692012-06-28 21:37:20 +00001784 entry->fColor = SkColorSetA(paint.getColor(), 0xFF);
1785 entry->fShaderIndex = -1;
vandebo@chromium.org48543272011-02-08 19:28:07 +00001786
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001787 // PDF treats a shader as a color, so we only set one or the other.
halcanary48810a02016-03-07 14:57:50 -08001788 sk_sp<SkPDFObject> pdfShader;
reedfe630452016-03-25 09:08:00 -07001789 SkShader* shader = paint.getShader();
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001790 SkColor color = paint.getColor();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001791 if (shader) {
Hal Canaryc8f91802017-02-12 20:29:12 -05001792 if (SkShader::kColor_GradientType == shader->asAGradient(nullptr)) {
1793 // We don't have to set a shader just for a color.
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001794 SkShader::GradientInfo gradientInfo;
Hal Canaryc8f91802017-02-12 20:29:12 -05001795 SkColor gradientColor = SK_ColorBLACK;
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001796 gradientInfo.fColors = &gradientColor;
halcanary96fcdcc2015-08-27 07:41:13 -07001797 gradientInfo.fColorOffsets = nullptr;
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001798 gradientInfo.fColorCount = 1;
Hal Canaryc8f91802017-02-12 20:29:12 -05001799 SkAssertResult(shader->asAGradient(&gradientInfo) == SkShader::kColor_GradientType);
1800 entry->fColor = SkColorSetA(gradientColor, 0xFF);
1801 color = gradientColor;
1802 } else {
1803 // PDF positions patterns relative to the initial transform, so
1804 // we need to apply the current transform to the shader parameters.
1805 SkMatrix transform = matrix;
1806 transform.postConcat(fInitialTransform);
1807
1808 // PDF doesn't support kClamp_TileMode, so we simulate it by making
1809 // a pattern the size of the current clip.
Hal Canaryb400d4d2018-09-26 16:33:52 -04001810 SkRect clipStackBounds = clipStack ? clipStack->bounds(this->bounds())
1811 : SkRect::Make(this->bounds());
Hal Canaryc8f91802017-02-12 20:29:12 -05001812
1813 // We need to apply the initial transform to bounds in order to get
1814 // bounds in a consistent coordinate system.
Hal Canarya41c2aa2017-02-22 16:32:34 -05001815 fInitialTransform.mapRect(&clipStackBounds);
1816 SkIRect bounds;
1817 clipStackBounds.roundOut(&bounds);
Hal Canaryc8f91802017-02-12 20:29:12 -05001818
Hal Canary7e872ca2017-07-19 15:51:18 -04001819 pdfShader = SkPDFMakeShader(fDocument, shader, transform, bounds, paint.getColor());
Hal Canaryc8f91802017-02-12 20:29:12 -05001820
Hal Canary9e41c212018-09-03 12:00:23 -04001821 if (pdfShader) {
1822 // pdfShader has been canonicalized so we can directly compare pointers.
1823 entry->fShaderIndex = find_or_add(&fShaderResources, std::move(pdfShader));
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001824 }
1825 }
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001826 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001827
Hal Canary80fa7ce2017-06-28 16:04:20 -04001828 sk_sp<SkPDFDict> newGraphicState;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001829 if (color == paint.getColor()) {
Hal Canary5c1b3602017-04-17 16:30:06 -04001830 newGraphicState = SkPDFGraphicState::GetGraphicStateForPaint(fDocument->canon(), paint);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001831 } else {
1832 SkPaint newPaint = paint;
1833 newPaint.setColor(color);
Hal Canary5c1b3602017-04-17 16:30:06 -04001834 newGraphicState = SkPDFGraphicState::GetGraphicStateForPaint(fDocument->canon(), newPaint);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001835 }
Hal Canary9e41c212018-09-03 12:00:23 -04001836 entry->fGraphicStateIndex = find_or_add(&fGraphicStateResources, std::move(newGraphicState));
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001837
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001838 if (hasText) {
1839 entry->fTextScaleX = paint.getTextScaleX();
1840 entry->fTextFill = paint.getStyle();
1841 } else {
1842 entry->fTextScaleX = 0;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001843 }
1844}
1845
Hal Canaryfafe1352017-04-11 12:12:02 -04001846static SkSize rect_to_size(const SkRect& r) { return {r.width(), r.height()}; }
halcanary7a14b312015-10-01 07:28:13 -07001847
Hal Canary7cbf5e32017-07-12 13:10:23 -04001848static sk_sp<SkImage> color_filter(const SkImage* image,
halcanarya50151d2016-03-25 11:57:49 -07001849 SkColorFilter* colorFilter) {
halcanary9d524f22016-03-29 09:03:52 -07001850 auto surface =
Hal Canary7cbf5e32017-07-12 13:10:23 -04001851 SkSurface::MakeRaster(SkImageInfo::MakeN32Premul(image->dimensions()));
halcanarya50151d2016-03-25 11:57:49 -07001852 SkASSERT(surface);
halcanary7a14b312015-10-01 07:28:13 -07001853 SkCanvas* canvas = surface->getCanvas();
1854 canvas->clear(SK_ColorTRANSPARENT);
1855 SkPaint paint;
reedd053ce92016-03-22 10:17:23 -07001856 paint.setColorFilter(sk_ref_sp(colorFilter));
Hal Canary7cbf5e32017-07-12 13:10:23 -04001857 canvas->drawImage(image, 0, 0, &paint);
halcanarya50151d2016-03-25 11:57:49 -07001858 return surface->makeImageSnapshot();
halcanary7a14b312015-10-01 07:28:13 -07001859}
1860
1861////////////////////////////////////////////////////////////////////////////////
Hal Canary7cbf5e32017-07-12 13:10:23 -04001862
1863static bool is_integer(SkScalar x) {
1864 return x == SkScalarTruncToScalar(x);
1865}
1866
1867static bool is_integral(const SkRect& r) {
1868 return is_integer(r.left()) &&
1869 is_integer(r.top()) &&
1870 is_integer(r.right()) &&
1871 is_integer(r.bottom());
1872}
1873
1874void SkPDFDevice::internalDrawImageRect(SkKeyedImage imageSubset,
1875 const SkRect* src,
1876 const SkRect& dst,
1877 const SkPaint& srcPaint,
1878 const SkMatrix& ctm) {
Hal Canaryb4e528d2018-03-09 16:02:15 -05001879 if (this->hasEmptyClip()) {
1880 return;
1881 }
Hal Canary7cbf5e32017-07-12 13:10:23 -04001882 if (!imageSubset) {
halcanarya50151d2016-03-25 11:57:49 -07001883 return;
1884 }
Hal Canary7cbf5e32017-07-12 13:10:23 -04001885
Hal Canaryf0f4c0c2017-07-19 15:48:38 -04001886 // First, figure out the src->dst transform and subset the image if needed.
Hal Canary7cbf5e32017-07-12 13:10:23 -04001887 SkIRect bounds = imageSubset.image()->bounds();
Hal Canary7cbf5e32017-07-12 13:10:23 -04001888 SkRect srcRect = src ? *src : SkRect::Make(bounds);
1889 SkMatrix transform;
1890 transform.setRectToRect(srcRect, dst, SkMatrix::kFill_ScaleToFit);
1891 if (src && *src != SkRect::Make(bounds)) {
1892 if (!srcRect.intersect(SkRect::Make(bounds))) {
1893 return;
1894 }
1895 srcRect.roundOut(&bounds);
1896 transform.preTranslate(SkIntToScalar(bounds.x()),
1897 SkIntToScalar(bounds.y()));
1898 if (bounds != imageSubset.image()->bounds()) {
1899 imageSubset = imageSubset.subset(bounds);
1900 }
1901 if (!imageSubset) {
1902 return;
1903 }
1904 }
1905
Hal Canaryf0f4c0c2017-07-19 15:48:38 -04001906 // If the image is opaque and the paint's alpha is too, replace
1907 // kSrc blendmode with kSrcOver.
1908 SkPaint paint = srcPaint;
1909 if (imageSubset.image()->isOpaque()) {
1910 replace_srcmode_on_opaque_paint(&paint);
Hal Canaryd425a1d2017-07-12 13:13:51 -04001911 }
Hal Canaryf0f4c0c2017-07-19 15:48:38 -04001912
1913 // Alpha-only images need to get their color from the shader, before
1914 // applying the colorfilter.
1915 if (imageSubset.image()->isAlphaOnly() && paint.getColorFilter()) {
1916 // must blend alpha image and shader before applying colorfilter.
1917 auto surface =
1918 SkSurface::MakeRaster(SkImageInfo::MakeN32Premul(imageSubset.image()->dimensions()));
1919 SkCanvas* canvas = surface->getCanvas();
1920 SkPaint tmpPaint;
1921 // In the case of alpha images with shaders, the shader's coordinate
1922 // system is the image's coordiantes.
1923 tmpPaint.setShader(sk_ref_sp(paint.getShader()));
1924 tmpPaint.setColor(paint.getColor());
1925 canvas->clear(0x00000000);
1926 canvas->drawImage(imageSubset.image().get(), 0, 0, &tmpPaint);
1927 paint.setShader(nullptr);
1928 imageSubset = SkKeyedImage(surface->makeImageSnapshot());
1929 SkASSERT(!imageSubset.image()->isAlphaOnly());
1930 }
1931
1932 if (imageSubset.image()->isAlphaOnly()) {
1933 // The ColorFilter applies to the paint color/shader, not the alpha layer.
1934 SkASSERT(nullptr == paint.getColorFilter());
1935
Hal Canaryd425a1d2017-07-12 13:13:51 -04001936 sk_sp<SkImage> mask = alpha_image_to_greyscale_image(imageSubset.image().get());
1937 if (!mask) {
1938 return;
1939 }
1940 // PDF doesn't seem to allow masking vector graphics with an Image XObject.
1941 // Must mask with a Form XObject.
1942 sk_sp<SkPDFDevice> maskDevice = this->makeCongruentDevice();
1943 {
Herb Derbyefe39bc2018-05-01 17:06:20 -04001944 SkCanvas canvas(maskDevice);
Hal Canaryf0f4c0c2017-07-19 15:48:38 -04001945 if (paint.getMaskFilter()) {
1946 // This clip prevents the mask image shader from covering
1947 // entire device if unnecessary.
1948 canvas.clipRect(this->cs().bounds(this->bounds()));
1949 canvas.concat(ctm);
1950 SkPaint tmpPaint;
1951 tmpPaint.setShader(mask->makeShader(&transform));
1952 tmpPaint.setMaskFilter(sk_ref_sp(paint.getMaskFilter()));
1953 canvas.drawRect(dst, tmpPaint);
1954 } else {
1955 canvas.concat(ctm);
1956 if (src && !is_integral(*src)) {
1957 canvas.clipRect(dst);
1958 }
1959 canvas.concat(transform);
1960 canvas.drawImage(mask, 0, 0);
1961 }
Hal Canaryd425a1d2017-07-12 13:13:51 -04001962 }
Hal Canaryd425a1d2017-07-12 13:13:51 -04001963 if (!ctm.isIdentity() && paint.getShader()) {
1964 transform_shader(&paint, ctm); // Since we are using identity matrix.
1965 }
Hal Canaryd892a9d2018-09-24 21:20:47 -04001966 ScopedContentEntry content(this, &this->cs(), SkMatrix::I(), paint);
Hal Canaryd425a1d2017-07-12 13:13:51 -04001967 if (!content.entry()) {
1968 return;
1969 }
1970 this->addSMaskGraphicState(std::move(maskDevice), content.stream());
Hal Canary813b5ac2018-09-28 12:30:37 -04001971 SkPDFUtils::AppendRectangle(SkRect::Make(this->size()), content.stream());
Hal Canaryd425a1d2017-07-12 13:13:51 -04001972 SkPDFUtils::PaintPath(SkPaint::kFill_Style, SkPath::kWinding_FillType, content.stream());
1973 this->clearMaskOnGraphicState(content.stream());
1974 return;
1975 }
Hal Canary7cbf5e32017-07-12 13:10:23 -04001976 if (paint.getMaskFilter()) {
1977 paint.setShader(imageSubset.image()->makeShader(&transform));
Hal Canaryd791a6f2018-09-28 12:14:08 -04001978 SkPath path = to_path(dst); // handles non-integral clipping.
Robert Phillips137ca522018-08-15 10:14:33 -04001979 this->internalDrawPath(this->cs(), this->ctm(), path, paint, true);
Hal Canary7cbf5e32017-07-12 13:10:23 -04001980 return;
1981 }
1982 transform.postConcat(ctm);
1983
1984 bool needToRestore = false;
1985 if (src && !is_integral(*src)) {
1986 // Need sub-pixel clipping to fix https://bug.skia.org/4374
1987 this->cs().save();
1988 this->cs().clipRect(dst, ctm, SkClipOp::kIntersect, true);
1989 needToRestore = true;
1990 }
1991 SK_AT_SCOPE_EXIT(if (needToRestore) { this->cs().restore(); });
1992
halcanary7a14b312015-10-01 07:28:13 -07001993 #ifdef SK_PDF_IMAGE_STATS
1994 gDrawImageCalls.fetch_add(1);
1995 #endif
Hal Canary7cbf5e32017-07-12 13:10:23 -04001996 SkMatrix matrix = transform;
edisonn@google.com9cf0cb12013-10-16 18:32:35 +00001997
1998 // Rasterize the bitmap using perspective in a new bitmap.
Hal Canary7cbf5e32017-07-12 13:10:23 -04001999 if (transform.hasPerspective()) {
edisonn@google.com73a7ea32013-11-11 20:55:15 +00002000 // Transform the bitmap in the new space, without taking into
2001 // account the initial transform.
Hal Canary7cbf5e32017-07-12 13:10:23 -04002002 SkRect imageBounds = SkRect::Make(imageSubset.image()->bounds());
Hal Canaryd791a6f2018-09-28 12:14:08 -04002003 SkPath perspectiveOutline = to_path(imageBounds);
Hal Canary7cbf5e32017-07-12 13:10:23 -04002004 perspectiveOutline.transform(transform);
edisonn@google.com9cf0cb12013-10-16 18:32:35 +00002005
2006 // TODO(edisonn): perf - use current clip too.
2007 // Retrieve the bounds of the new shape.
2008 SkRect bounds = perspectiveOutline.getBounds();
2009
edisonn@google.com73a7ea32013-11-11 20:55:15 +00002010 // Transform the bitmap in the new space, taking into
2011 // account the initial transform.
Hal Canary7cbf5e32017-07-12 13:10:23 -04002012 SkMatrix total = transform;
edisonn@google.com73a7ea32013-11-11 20:55:15 +00002013 total.postConcat(fInitialTransform);
halcanary7a14b312015-10-01 07:28:13 -07002014
Hal Canaryd791a6f2018-09-28 12:14:08 -04002015 SkPath physicalPerspectiveOutline = to_path(imageBounds);
edisonn@google.com73a7ea32013-11-11 20:55:15 +00002016 physicalPerspectiveOutline.transform(total);
2017
halcanary7a14b312015-10-01 07:28:13 -07002018 SkRect physicalPerspectiveBounds =
2019 physicalPerspectiveOutline.getBounds();
2020 SkScalar scaleX = physicalPerspectiveBounds.width() / bounds.width();
2021 SkScalar scaleY = physicalPerspectiveBounds.height() / bounds.height();
edisonn@google.com9cf0cb12013-10-16 18:32:35 +00002022
2023 // TODO(edisonn): A better approach would be to use a bitmap shader
2024 // (in clamp mode) and draw a rect over the entire bounding box. Then
2025 // intersect perspectiveOutline to the clip. That will avoid introducing
2026 // alpha to the image while still giving good behavior at the edge of
2027 // the image. Avoiding alpha will reduce the pdf size and generation
2028 // CPU time some.
2029
halcanary7a14b312015-10-01 07:28:13 -07002030 SkISize wh = rect_to_size(physicalPerspectiveBounds).toCeil();
2031
Hal Canaryf50ff392016-09-30 10:25:39 -04002032 auto surface = SkSurface::MakeRaster(SkImageInfo::MakeN32Premul(wh));
halcanary7a14b312015-10-01 07:28:13 -07002033 if (!surface) {
reed@google.com9ebcac52014-01-24 18:53:42 +00002034 return;
2035 }
halcanary7a14b312015-10-01 07:28:13 -07002036 SkCanvas* canvas = surface->getCanvas();
2037 canvas->clear(SK_ColorTRANSPARENT);
edisonn@google.com9cf0cb12013-10-16 18:32:35 +00002038
2039 SkScalar deltaX = bounds.left();
2040 SkScalar deltaY = bounds.top();
2041
Hal Canary7cbf5e32017-07-12 13:10:23 -04002042 SkMatrix offsetMatrix = transform;
edisonn@google.com9cf0cb12013-10-16 18:32:35 +00002043 offsetMatrix.postTranslate(-deltaX, -deltaY);
edisonn@google.com73a7ea32013-11-11 20:55:15 +00002044 offsetMatrix.postScale(scaleX, scaleY);
edisonn@google.com9cf0cb12013-10-16 18:32:35 +00002045
2046 // Translate the draw in the new canvas, so we perfectly fit the
2047 // shape in the bitmap.
halcanary7a14b312015-10-01 07:28:13 -07002048 canvas->setMatrix(offsetMatrix);
Hal Canary7cbf5e32017-07-12 13:10:23 -04002049 canvas->drawImage(imageSubset.image(), 0, 0);
edisonn@google.com9cf0cb12013-10-16 18:32:35 +00002050 // Make sure the final bits are in the bitmap.
halcanary7a14b312015-10-01 07:28:13 -07002051 canvas->flush();
edisonn@google.com9cf0cb12013-10-16 18:32:35 +00002052
edisonn@google.com73a7ea32013-11-11 20:55:15 +00002053 // In the new space, we use the identity matrix translated
2054 // and scaled to reflect DPI.
2055 matrix.setScale(1 / scaleX, 1 / scaleY);
2056 matrix.postTranslate(deltaX, deltaY);
2057
Hal Canary7cbf5e32017-07-12 13:10:23 -04002058 imageSubset = SkKeyedImage(surface->makeImageSnapshot());
2059 if (!imageSubset) {
2060 return;
2061 }
edisonn@google.com9cf0cb12013-10-16 18:32:35 +00002062 }
2063
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +00002064 SkMatrix scaled;
2065 // Adjust for origin flip.
vandebo@chromium.org663515b2012-01-05 18:45:27 +00002066 scaled.setScale(SK_Scalar1, -SK_Scalar1);
2067 scaled.postTranslate(0, SK_Scalar1);
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +00002068 // Scale the image up from 1x1 to WxH.
Hal Canary7cbf5e32017-07-12 13:10:23 -04002069 SkIRect subset = imageSubset.image()->bounds();
2070 scaled.postScale(SkIntToScalar(subset.width()),
2071 SkIntToScalar(subset.height()));
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +00002072 scaled.postConcat(matrix);
Hal Canaryd892a9d2018-09-24 21:20:47 -04002073 ScopedContentEntry content(this, &this->cs(), scaled, paint);
halcanarya50151d2016-03-25 11:57:49 -07002074 if (!content.entry()) {
vandebo@chromium.org3b416212013-10-30 20:48:05 +00002075 return;
2076 }
2077 if (content.needShape()) {
Hal Canaryd791a6f2018-09-28 12:14:08 -04002078 SkPath shape = to_path(SkRect::Make(subset));
vandebo@chromium.org3b416212013-10-30 20:48:05 +00002079 shape.transform(matrix);
2080 content.setShape(shape);
2081 }
2082 if (!content.needSource()) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +00002083 return;
2084 }
2085
halcanary287d22d2015-09-24 10:20:05 -07002086 if (SkColorFilter* colorFilter = paint.getColorFilter()) {
Hal Canary7cbf5e32017-07-12 13:10:23 -04002087 sk_sp<SkImage> img = color_filter(imageSubset.image().get(), colorFilter);
2088 imageSubset = SkKeyedImage(std::move(img));
2089 if (!imageSubset) {
2090 return;
2091 }
halcanary7a14b312015-10-01 07:28:13 -07002092 // TODO(halcanary): de-dupe this by caching filtered images.
2093 // (maybe in the resource cache?)
2094 }
halcanarya50151d2016-03-25 11:57:49 -07002095
Hal Canary7cbf5e32017-07-12 13:10:23 -04002096 SkBitmapKey key = imageSubset.key();
Hal Canary5c1b3602017-04-17 16:30:06 -04002097 sk_sp<SkPDFObject>* pdfimagePtr = fDocument->canon()->fPDFBitmapMap.find(key);
2098 sk_sp<SkPDFObject> pdfimage = pdfimagePtr ? *pdfimagePtr : nullptr;
halcanary7a14b312015-10-01 07:28:13 -07002099 if (!pdfimage) {
Hal Canary7cbf5e32017-07-12 13:10:23 -04002100 SkASSERT(imageSubset);
2101 pdfimage = SkPDFCreateBitmapObject(imageSubset.release(),
Mike Reeda4daf192017-12-14 13:25:04 -05002102 fDocument->metadata().fEncodingQuality);
halcanary7a14b312015-10-01 07:28:13 -07002103 if (!pdfimage) {
2104 return;
halcanary287d22d2015-09-24 10:20:05 -07002105 }
halcanarya50151d2016-03-25 11:57:49 -07002106 fDocument->serialize(pdfimage); // serialize images early.
Hal Canary7cbf5e32017-07-12 13:10:23 -04002107 SkASSERT((key != SkBitmapKey{{0, 0, 0, 0}, 0}));
Hal Canary5c1b3602017-04-17 16:30:06 -04002108 fDocument->canon()->fPDFBitmapMap.set(key, pdfimage);
halcanary287d22d2015-09-24 10:20:05 -07002109 }
Hal Canary9e41c212018-09-03 12:00:23 -04002110 int xObjectResourceIndex = find_or_add(&fXObjectResources, std::move(pdfimage));
Hal Canarye650b852018-09-12 09:12:36 -04002111 draw_form_xobject(xObjectResourceIndex, content.stream());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00002112}
reede51c3562016-07-19 14:33:20 -07002113
2114///////////////////////////////////////////////////////////////////////////////////////////////////
2115
2116#include "SkSpecialImage.h"
2117#include "SkImageFilter.h"
2118
Florin Malita53f77bd2017-04-28 13:48:37 -04002119void SkPDFDevice::drawSpecial(SkSpecialImage* srcImg, int x, int y, const SkPaint& paint,
2120 SkImage* clipImage, const SkMatrix& clipMatrix) {
Hal Canaryb4e528d2018-03-09 16:02:15 -05002121 if (this->hasEmptyClip()) {
2122 return;
2123 }
reede51c3562016-07-19 14:33:20 -07002124 SkASSERT(!srcImg->isTextureBacked());
2125
Florin Malita53f77bd2017-04-28 13:48:37 -04002126 //TODO: clipImage support
2127
reede51c3562016-07-19 14:33:20 -07002128 SkBitmap resultBM;
2129
2130 SkImageFilter* filter = paint.getImageFilter();
2131 if (filter) {
2132 SkIPoint offset = SkIPoint::Make(0, 0);
Mike Reeda1361362017-03-07 09:37:29 -05002133 SkMatrix matrix = this->ctm();
reede51c3562016-07-19 14:33:20 -07002134 matrix.postTranslate(SkIntToScalar(-x), SkIntToScalar(-y));
Hal Canaryf3ee34f2017-02-07 16:58:28 -05002135 const SkIRect clipBounds =
Hal Canary22b2d8c2017-07-19 14:46:12 -04002136 this->cs().bounds(this->bounds()).roundOut().makeOffset(-x, -y);
Hal Canary67b39de2016-11-07 11:47:44 -05002137 sk_sp<SkImageFilterCache> cache(this->getImageFilterCache());
Brian Osmana50205f2018-07-06 13:57:01 -04002138 // TODO: Should PDF be operating in a specified color type/space? For now, run the filter
brianosman2a75e5d2016-09-22 07:15:37 -07002139 // in the same color space as the source (this is different from all other backends).
Brian Osmana50205f2018-07-06 13:57:01 -04002140 SkImageFilter::OutputProperties outputProperties(kN32_SkColorType, srcImg->getColorSpace());
brianosman2a75e5d2016-09-22 07:15:37 -07002141 SkImageFilter::Context ctx(matrix, clipBounds, cache.get(), outputProperties);
reede51c3562016-07-19 14:33:20 -07002142
2143 sk_sp<SkSpecialImage> resultImg(filter->filterImage(srcImg, ctx, &offset));
2144 if (resultImg) {
2145 SkPaint tmpUnfiltered(paint);
2146 tmpUnfiltered.setImageFilter(nullptr);
2147 if (resultImg->getROPixels(&resultBM)) {
Mike Reeda1361362017-03-07 09:37:29 -05002148 this->drawSprite(resultBM, x + offset.x(), y + offset.y(), tmpUnfiltered);
reede51c3562016-07-19 14:33:20 -07002149 }
2150 }
2151 } else {
2152 if (srcImg->getROPixels(&resultBM)) {
Mike Reeda1361362017-03-07 09:37:29 -05002153 this->drawSprite(resultBM, x, y, paint);
reede51c3562016-07-19 14:33:20 -07002154 }
2155 }
2156}
2157
2158sk_sp<SkSpecialImage> SkPDFDevice::makeSpecial(const SkBitmap& bitmap) {
2159 return SkSpecialImage::MakeFromRaster(bitmap.bounds(), bitmap);
2160}
2161
2162sk_sp<SkSpecialImage> SkPDFDevice::makeSpecial(const SkImage* image) {
Brian Osman7992da32016-11-18 11:28:24 -05002163 // TODO: See comment above in drawSpecial. The color mode we use for decode should be driven
2164 // by the destination where we're going to draw thing thing (ie this device). But we don't have
2165 // a color space, so we always decode in legacy mode for now.
Brian Osman61624f02016-12-09 14:51:59 -05002166 SkColorSpace* legacyColorSpace = nullptr;
Hal Canary22b2d8c2017-07-19 14:46:12 -04002167 return SkSpecialImage::MakeFromImage(image->bounds(),
Brian Osman61624f02016-12-09 14:51:59 -05002168 image->makeNonTextureImage(), legacyColorSpace);
reede51c3562016-07-19 14:33:20 -07002169}
2170
2171sk_sp<SkSpecialImage> SkPDFDevice::snapSpecial() {
reede51c3562016-07-19 14:33:20 -07002172 return nullptr;
2173}
brianosman04a44d02016-09-21 09:46:57 -07002174
2175SkImageFilterCache* SkPDFDevice::getImageFilterCache() {
2176 // We always return a transient cache, so it is freed after each
2177 // filter traversal.
2178 return SkImageFilterCache::Create(SkImageFilterCache::kDefaultTransientSize);
2179}