1/*
2 * Copyright 2011 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "src/pdf/SkPDFDevice.h"
9
10#include "include/core/SkCanvas.h"
11#include "include/core/SkColor.h"
12#include "include/core/SkColorFilter.h"
13#include "include/core/SkPath.h"
14#include "include/core/SkPathEffect.h"
15#include "include/core/SkRRect.h"
16#include "include/core/SkString.h"
17#include "include/core/SkSurface.h"
18#include "include/core/SkTextBlob.h"
19#include "include/docs/SkPDFDocument.h"
20#include "include/encode/SkJpegEncoder.h"
21#include "include/pathops/SkPathOps.h"
22#include "include/private/SkTemplates.h"
23#include "include/private/SkTo.h"
24#include "src/core/SkAdvancedTypefaceMetrics.h"
25#include "src/core/SkAnnotationKeys.h"
26#include "src/core/SkBitmapDevice.h"
27#include "src/core/SkClipOpPriv.h"
28#include "src/core/SkColorSpacePriv.h"
29#include "src/core/SkDraw.h"
30#include "src/core/SkGlyphRun.h"
31#include "src/core/SkImageFilterCache.h"
32#include "src/core/SkImageFilter_Base.h"
33#include "src/core/SkMaskFilterBase.h"
34#include "src/core/SkRasterClip.h"
35#include "src/core/SkScalerCache.h"
36#include "src/core/SkScopeExit.h"
37#include "src/core/SkStrikeSpec.h"
38#include "src/core/SkTextFormatParams.h"
39#include "src/core/SkXfermodeInterpretation.h"
40#include "src/pdf/SkBitmapKey.h"
41#include "src/pdf/SkClusterator.h"
42#include "src/pdf/SkPDFBitmap.h"
43#include "src/pdf/SkPDFDocumentPriv.h"
44#include "src/pdf/SkPDFFont.h"
45#include "src/pdf/SkPDFFormXObject.h"
46#include "src/pdf/SkPDFGraphicState.h"
47#include "src/pdf/SkPDFResourceDict.h"
48#include "src/pdf/SkPDFShader.h"
49#include "src/pdf/SkPDFTypes.h"
50#include "src/pdf/SkPDFUtils.h"
51#include "src/utils/SkClipStackUtils.h"
52#include "src/utils/SkUTF.h"
53
54#include <vector>
55
56#ifndef SK_PDF_MASK_QUALITY
57 // If MASK_QUALITY is in [0,100], will be used for JpegEncoder.
58 // Otherwise, just encode masks losslessly.
59 #define SK_PDF_MASK_QUALITY 50
60 // Since these masks are used for blurry shadows, we shouldn't need
61 // high quality. Raise this value if your shadows have visible JPEG
62 // artifacts.
63 // If SkJpegEncoder::Encode fails, we will fall back to the lossless
64 // encoding.
65#endif
66
67namespace {
68
69// If nodeId is not zero, outputs the tags to begin a marked-content sequence
70// for the given node ID, and then closes those tags when this object goes
71// out of scope.
72class ScopedOutputMarkedContentTags {
73public:
74 ScopedOutputMarkedContentTags(int nodeId, SkPDFDocument* document, SkDynamicMemoryWStream* out)
75 : fOut(out)
76 , fMarkId(-1) {
77 if (nodeId) {
78 fMarkId = document->getMarkIdForNodeId(nodeId);
79 }
80
81 if (fMarkId != -1) {
82 fOut->writeText("/P <</MCID ");
83 fOut->writeDecAsText(fMarkId);
84 fOut->writeText(" >>BDC\n");
85 }
86 }
87
88 ~ScopedOutputMarkedContentTags() {
89 if (fMarkId != -1) {
90 fOut->writeText("EMC\n");
91 }
92 }
93
94private:
95 SkDynamicMemoryWStream* fOut;
96 int fMarkId;
97};
98
99}
100
101// Utility functions
102
103static SkPath to_path(const SkRect& r) {
104 SkPath p;
105 p.addRect(r);
106 return p;
107}
108
109// This function destroys the mask and either frees or takes the pixels.
110sk_sp<SkImage> mask_to_greyscale_image(SkMask* mask) {
111 sk_sp<SkImage> img;
112 SkPixmap pm(SkImageInfo::Make(mask->fBounds.width(), mask->fBounds.height(),
113 kGray_8_SkColorType, kOpaque_SkAlphaType),
114 mask->fImage, mask->fRowBytes);
115 const int imgQuality = SK_PDF_MASK_QUALITY;
116 if (imgQuality <= 100 && imgQuality >= 0) {
117 SkDynamicMemoryWStream buffer;
118 SkJpegEncoder::Options jpegOptions;
119 jpegOptions.fQuality = imgQuality;
120 if (SkJpegEncoder::Encode(&buffer, pm, jpegOptions)) {
121 img = SkImage::MakeFromEncoded(buffer.detachAsData());
122 SkASSERT(img);
123 if (img) {
124 SkMask::FreeImage(mask->fImage);
125 }
126 }
127 }
128 if (!img) {
129 img = SkImage::MakeFromRaster(pm, [](const void* p, void*) { SkMask::FreeImage((void*)p); },
130 nullptr);
131 }
132 *mask = SkMask(); // destructive;
133 return img;
134}
135
136sk_sp<SkImage> alpha_image_to_greyscale_image(const SkImage* mask) {
137 int w = mask->width(), h = mask->height();
138 SkBitmap greyBitmap;
139 greyBitmap.allocPixels(SkImageInfo::Make(w, h, kGray_8_SkColorType, kOpaque_SkAlphaType));
140 if (!mask->readPixels(SkImageInfo::MakeA8(w, h),
141 greyBitmap.getPixels(), greyBitmap.rowBytes(), 0, 0)) {
142 return nullptr;
143 }
144 return SkImage::MakeFromBitmap(greyBitmap);
145}
146
147static int add_resource(SkTHashSet<SkPDFIndirectReference>& resources, SkPDFIndirectReference ref) {
148 resources.add(ref);
149 return ref.fValue;
150}
151
152static void draw_points(SkCanvas::PointMode mode,
153 size_t count,
154 const SkPoint* points,
155 const SkPaint& paint,
156 const SkIRect& bounds,
157 const SkMatrix& ctm,
158 SkBaseDevice* device) {
159 SkRasterClip rc(bounds);
160 SkDraw draw;
161 draw.fDst = SkPixmap(SkImageInfo::MakeUnknown(bounds.right(), bounds.bottom()), nullptr, 0);
162 draw.fMatrix = &ctm;
163 draw.fRC = &rc;
164 draw.drawPoints(mode, count, points, paint, device);
165}
166
167// A shader's matrix is: CTMM x LocalMatrix x WrappingLocalMatrix. We want to
168// switch to device space, where CTM = I, while keeping the original behavior.
169//
170// I * LocalMatrix * NewWrappingMatrix = CTM * LocalMatrix
171// LocalMatrix * NewWrappingMatrix = CTM * LocalMatrix
172// InvLocalMatrix * LocalMatrix * NewWrappingMatrix = InvLocalMatrix * CTM * LocalMatrix
173// NewWrappingMatrix = InvLocalMatrix * CTM * LocalMatrix
174//
175static void transform_shader(SkPaint* paint, const SkMatrix& ctm) {
176 SkASSERT(!ctm.isIdentity());
177 SkMatrix lm = SkPDFUtils::GetShaderLocalMatrix(paint->getShader());
178 SkMatrix lmInv;
179 if (lm.invert(&lmInv)) {
180 SkMatrix m = SkMatrix::Concat(SkMatrix::Concat(lmInv, ctm), lm);
181 paint->setShader(paint->getShader()->makeWithLocalMatrix(m));
182 }
183}
184
185static SkTCopyOnFirstWrite<SkPaint> clean_paint(const SkPaint& srcPaint) {
186 SkTCopyOnFirstWrite<SkPaint> paint(srcPaint);
187 // If the paint will definitely draw opaquely, replace kSrc with
188 // kSrcOver. http://crbug.com/473572
189 if (SkBlendMode::kSrcOver != paint->getBlendMode() &&
190 kSrcOver_SkXfermodeInterpretation == SkInterpretXfermode(*paint, false))
191 {
192 paint.writable()->setBlendMode(SkBlendMode::kSrcOver);
193 }
194 if (paint->getColorFilter()) {
195 // We assume here that PDFs all draw in sRGB.
196 SkPaintPriv::RemoveColorFilter(paint.writable(), sk_srgb_singleton());
197 }
198 SkASSERT(!paint->getColorFilter());
199 return paint;
200}
201
202static void set_style(SkTCopyOnFirstWrite<SkPaint>* paint, SkPaint::Style style) {
203 if (paint->get()->getStyle() != style) {
204 paint->writable()->setStyle(style);
205 }
206}
207
208/* Calculate an inverted path's equivalent non-inverted path, given the
209 * canvas bounds.
210 * outPath may alias with invPath (since this is supported by PathOps).
211 */
212static bool calculate_inverse_path(const SkRect& bounds, const SkPath& invPath,
213 SkPath* outPath) {
214 SkASSERT(invPath.isInverseFillType());
215 return Op(to_path(bounds), invPath, kIntersect_SkPathOp, outPath);
216}
217
218SkBaseDevice* SkPDFDevice::onCreateDevice(const CreateInfo& cinfo, const SkPaint* layerPaint) {
219 // PDF does not support image filters, so render them on CPU.
220 // Note that this rendering is done at "screen" resolution (100dpi), not
221 // printer resolution.
222
223 // TODO: It may be possible to express some filters natively using PDF
224 // to improve quality and file size (https://bug.skia.org/3043)
225 if (layerPaint && (layerPaint->getImageFilter() || layerPaint->getColorFilter())) {
226 // need to return a raster device, which we will detect in drawDevice()
227 return SkBitmapDevice::Create(cinfo.fInfo, SkSurfaceProps(0, kUnknown_SkPixelGeometry));
228 }
229 return new SkPDFDevice(cinfo.fInfo.dimensions(), fDocument);
230}
231
232// A helper class to automatically finish a ContentEntry at the end of a
233// drawing method and maintain the state needed between set up and finish.
234class ScopedContentEntry {
235public:
236 ScopedContentEntry(SkPDFDevice* device,
237 const SkClipStack* clipStack,
238 const SkMatrix& matrix,
239 const SkPaint& paint,
240 SkScalar textScale = 0)
241 : fDevice(device)
242 , fBlendMode(SkBlendMode::kSrcOver)
243 , fClipStack(clipStack)
244 {
245 if (matrix.hasPerspective()) {
246 NOT_IMPLEMENTED(!matrix.hasPerspective(), false);
247 return;
248 }
249 fBlendMode = paint.getBlendMode();
250 fContentStream =
251 fDevice->setUpContentEntry(clipStack, matrix, paint, textScale, &fDstFormXObject);
252 }
253 ScopedContentEntry(SkPDFDevice* dev, const SkPaint& paint, SkScalar textScale = 0)
254 : ScopedContentEntry(dev, &dev->cs(), dev->localToDevice(), paint, textScale) {}
255
256 ~ScopedContentEntry() {
257 if (fContentStream) {
258 SkPath* shape = &fShape;
259 if (shape->isEmpty()) {
260 shape = nullptr;
261 }
262 fDevice->finishContentEntry(fClipStack, fBlendMode, fDstFormXObject, shape);
263 }
264 }
265
266 explicit operator bool() const { return fContentStream != nullptr; }
267 SkDynamicMemoryWStream* stream() { return fContentStream; }
268
269 /* Returns true when we explicitly need the shape of the drawing. */
270 bool needShape() {
271 switch (fBlendMode) {
272 case SkBlendMode::kClear:
273 case SkBlendMode::kSrc:
274 case SkBlendMode::kSrcIn:
275 case SkBlendMode::kSrcOut:
276 case SkBlendMode::kDstIn:
277 case SkBlendMode::kDstOut:
278 case SkBlendMode::kSrcATop:
279 case SkBlendMode::kDstATop:
280 case SkBlendMode::kModulate:
281 return true;
282 default:
283 return false;
284 }
285 }
286
287 /* Returns true unless we only need the shape of the drawing. */
288 bool needSource() {
289 if (fBlendMode == SkBlendMode::kClear) {
290 return false;
291 }
292 return true;
293 }
294
295 /* If the shape is different than the alpha component of the content, then
296 * setShape should be called with the shape. In particular, images and
297 * devices have rectangular shape.
298 */
299 void setShape(const SkPath& shape) {
300 fShape = shape;
301 }
302
303private:
304 SkPDFDevice* fDevice = nullptr;
305 SkDynamicMemoryWStream* fContentStream = nullptr;
306 SkBlendMode fBlendMode;
307 SkPDFIndirectReference fDstFormXObject;
308 SkPath fShape;
309 const SkClipStack* fClipStack;
310};
311
312////////////////////////////////////////////////////////////////////////////////
313
314SkPDFDevice::SkPDFDevice(SkISize pageSize, SkPDFDocument* doc, const SkMatrix& transform)
315 : INHERITED(SkImageInfo::MakeUnknown(pageSize.width(), pageSize.height()),
316 SkSurfaceProps(0, kUnknown_SkPixelGeometry))
317 , fInitialTransform(transform)
318 , fNodeId(0)
319 , fDocument(doc)
320{
321 SkASSERT(!pageSize.isEmpty());
322}
323
324SkPDFDevice::~SkPDFDevice() = default;
325
326void SkPDFDevice::reset() {
327 fGraphicStateResources.reset();
328 fXObjectResources.reset();
329 fShaderResources.reset();
330 fFontResources.reset();
331 fContent.reset();
332 fActiveStackState = SkPDFGraphicStackState();
333}
334
335void SkPDFDevice::drawAnnotation(const SkRect& rect, const char key[], SkData* value) {
336 if (!value) {
337 return;
338 }
339 // Annotations are specified in absolute coordinates, so the page xform maps from device space
340 // to the global space, and applies the document transform.
341 SkMatrix pageXform = this->deviceToGlobal();
342 pageXform.postConcat(fDocument->currentPageTransform());
343 if (rect.isEmpty()) {
344 if (!strcmp(key, SkPDFGetNodeIdKey())) {
345 int nodeID;
346 if (value->size() != sizeof(nodeID)) { return; }
347 memcpy(&nodeID, value->data(), sizeof(nodeID));
348 fNodeId = nodeID;
349 return;
350 }
351 if (!strcmp(SkAnnotationKeys::Define_Named_Dest_Key(), key)) {
352 SkPoint p = this->localToDevice().mapXY(rect.x(), rect.y());
353 pageXform.mapPoints(&p, 1);
354 auto pg = fDocument->currentPage();
355 fDocument->fNamedDestinations.push_back(SkPDFNamedDestination{sk_ref_sp(value), p, pg});
356 }
357 return;
358 }
359 // Convert to path to handle non-90-degree rotations.
360 SkPath path = to_path(rect);
361 path.transform(this->localToDevice(), &path);
362 SkPath clip;
363 SkClipStack_AsPath(this->cs(), &clip);
364 Op(clip, path, kIntersect_SkPathOp, &path);
365 // PDF wants a rectangle only.
366 SkRect transformedRect = pageXform.mapRect(path.getBounds());
367 if (transformedRect.isEmpty()) {
368 return;
369 }
370
371 SkPDFLink::Type linkType = SkPDFLink::Type::kNone;
372 if (!strcmp(SkAnnotationKeys::URL_Key(), key)) {
373 linkType = SkPDFLink::Type::kUrl;
374 } else if (!strcmp(SkAnnotationKeys::Link_Named_Dest_Key(), key)) {
375 linkType = SkPDFLink::Type::kNamedDestination;
376 }
377
378 if (linkType != SkPDFLink::Type::kNone) {
379 std::unique_ptr<SkPDFLink> link = std::make_unique<SkPDFLink>(
380 linkType, value, transformedRect, fNodeId);
381 fDocument->fCurrentPageLinks.push_back(std::move(link));
382 }
383}
384
385void SkPDFDevice::drawPaint(const SkPaint& srcPaint) {
386 SkMatrix inverse;
387 if (!this->localToDevice().invert(&inverse)) {
388 return;
389 }
390 SkRect bbox = this->cs().bounds(this->bounds());
391 inverse.mapRect(&bbox);
392 bbox.roundOut(&bbox);
393 if (this->hasEmptyClip()) {
394 return;
395 }
396 SkPaint newPaint = srcPaint;
397 newPaint.setStyle(SkPaint::kFill_Style);
398 this->drawRect(bbox, newPaint);
399}
400
401void SkPDFDevice::drawPoints(SkCanvas::PointMode mode,
402 size_t count,
403 const SkPoint* points,
404 const SkPaint& srcPaint) {
405 if (this->hasEmptyClip()) {
406 return;
407 }
408 if (count == 0) {
409 return;
410 }
411 SkTCopyOnFirstWrite<SkPaint> paint(clean_paint(srcPaint));
412
413
414
415 if (SkCanvas::kPoints_PointMode != mode) {
416 set_style(&paint, SkPaint::kStroke_Style);
417 }
418
419 // SkDraw::drawPoints converts to multiple calls to fDevice->drawPath.
420 // We only use this when there's a path effect because of the overhead
421 // of multiple calls to setUpContentEntry it causes.
422 if (paint->getPathEffect()) {
423 draw_points(mode, count, points, *paint,
424 this->devClipBounds(), this->localToDevice(), this);
425 return;
426 }
427
428
429 if (mode == SkCanvas::kPoints_PointMode && paint->getStrokeCap() != SkPaint::kRound_Cap) {
430 if (paint->getStrokeWidth()) {
431 // PDF won't draw a single point with square/butt caps because the
432 // orientation is ambiguous. Draw a rectangle instead.
433 set_style(&paint, SkPaint::kFill_Style);
434 SkScalar strokeWidth = paint->getStrokeWidth();
435 SkScalar halfStroke = SkScalarHalf(strokeWidth);
436 for (size_t i = 0; i < count; i++) {
437 SkRect r = SkRect::MakeXYWH(points[i].fX, points[i].fY, 0, 0);
438 r.inset(-halfStroke, -halfStroke);
439 this->drawRect(r, *paint);
440 }
441 return;
442 } else {
443 if (paint->getStrokeCap() != SkPaint::kRound_Cap) {
444 paint.writable()->setStrokeCap(SkPaint::kRound_Cap);
445 }
446 }
447 }
448
449 ScopedContentEntry content(this, *paint);
450 if (!content) {
451 return;
452 }
453 SkDynamicMemoryWStream* contentStream = content.stream();
454 switch (mode) {
455 case SkCanvas::kPolygon_PointMode:
456 SkPDFUtils::MoveTo(points[0].fX, points[0].fY, contentStream);
457 for (size_t i = 1; i < count; i++) {
458 SkPDFUtils::AppendLine(points[i].fX, points[i].fY, contentStream);
459 }
460 SkPDFUtils::StrokePath(contentStream);
461 break;
462 case SkCanvas::kLines_PointMode:
463 for (size_t i = 0; i < count/2; i++) {
464 SkPDFUtils::MoveTo(points[i * 2].fX, points[i * 2].fY, contentStream);
465 SkPDFUtils::AppendLine(points[i * 2 + 1].fX, points[i * 2 + 1].fY, contentStream);
466 SkPDFUtils::StrokePath(contentStream);
467 }
468 break;
469 case SkCanvas::kPoints_PointMode:
470 SkASSERT(paint->getStrokeCap() == SkPaint::kRound_Cap);
471 for (size_t i = 0; i < count; i++) {
472 SkPDFUtils::MoveTo(points[i].fX, points[i].fY, contentStream);
473 SkPDFUtils::ClosePath(contentStream);
474 SkPDFUtils::StrokePath(contentStream);
475 }
476 break;
477 default:
478 SkASSERT(false);
479 }
480}
481
482void SkPDFDevice::drawRect(const SkRect& rect, const SkPaint& paint) {
483 SkRect r = rect;
484 r.sort();
485 this->internalDrawPath(this->cs(), this->localToDevice(), to_path(r), paint, true);
486}
487
488void SkPDFDevice::drawRRect(const SkRRect& rrect, const SkPaint& paint) {
489 SkPath path;
490 path.addRRect(rrect);
491 this->internalDrawPath(this->cs(), this->localToDevice(), path, paint, true);
492}
493
494void SkPDFDevice::drawOval(const SkRect& oval, const SkPaint& paint) {
495 SkPath path;
496 path.addOval(oval);
497 this->internalDrawPath(this->cs(), this->localToDevice(), path, paint, true);
498}
499
500void SkPDFDevice::drawPath(const SkPath& path, const SkPaint& paint, bool pathIsMutable) {
501 this->internalDrawPath(this->cs(), this->localToDevice(), path, paint, pathIsMutable);
502}
503
504void SkPDFDevice::internalDrawPathWithFilter(const SkClipStack& clipStack,
505 const SkMatrix& ctm,
506 const SkPath& origPath,
507 const SkPaint& origPaint) {
508 SkASSERT(origPaint.getMaskFilter());
509 SkPath path(origPath);
510 SkTCopyOnFirstWrite<SkPaint> paint(origPaint);
511
512 SkStrokeRec::InitStyle initStyle = paint->getFillPath(path, &path)
513 ? SkStrokeRec::kFill_InitStyle
514 : SkStrokeRec::kHairline_InitStyle;
515 path.transform(ctm, &path);
516
517 SkIRect bounds = clipStack.bounds(this->bounds()).roundOut();
518 SkMask sourceMask;
519 if (!SkDraw::DrawToMask(path, &bounds, paint->getMaskFilter(), &SkMatrix::I(),
520 &sourceMask, SkMask::kComputeBoundsAndRenderImage_CreateMode,
521 initStyle)) {
522 return;
523 }
524 SkAutoMaskFreeImage srcAutoMaskFreeImage(sourceMask.fImage);
525 SkMask dstMask;
526 SkIPoint margin;
527 if (!as_MFB(paint->getMaskFilter())->filterMask(&dstMask, sourceMask, ctm, &margin)) {
528 return;
529 }
530 SkIRect dstMaskBounds = dstMask.fBounds;
531 sk_sp<SkImage> mask = mask_to_greyscale_image(&dstMask);
532 // PDF doesn't seem to allow masking vector graphics with an Image XObject.
533 // Must mask with a Form XObject.
534 sk_sp<SkPDFDevice> maskDevice = this->makeCongruentDevice();
535 {
536 SkCanvas canvas(maskDevice);
537 canvas.drawImage(mask, dstMaskBounds.x(), dstMaskBounds.y());
538 }
539 if (!ctm.isIdentity() && paint->getShader()) {
540 transform_shader(paint.writable(), ctm); // Since we are using identity matrix.
541 }
542 ScopedContentEntry content(this, &clipStack, SkMatrix::I(), *paint);
543 if (!content) {
544 return;
545 }
546 this->setGraphicState(SkPDFGraphicState::GetSMaskGraphicState(
547 maskDevice->makeFormXObjectFromDevice(dstMaskBounds, true), false,
548 SkPDFGraphicState::kLuminosity_SMaskMode, fDocument), content.stream());
549 SkPDFUtils::AppendRectangle(SkRect::Make(dstMaskBounds), content.stream());
550 SkPDFUtils::PaintPath(SkPaint::kFill_Style, path.getFillType(), content.stream());
551 this->clearMaskOnGraphicState(content.stream());
552}
553
554void SkPDFDevice::setGraphicState(SkPDFIndirectReference gs, SkDynamicMemoryWStream* content) {
555 SkPDFUtils::ApplyGraphicState(add_resource(fGraphicStateResources, gs), content);
556}
557
558void SkPDFDevice::clearMaskOnGraphicState(SkDynamicMemoryWStream* contentStream) {
559 // The no-softmask graphic state is used to "turn off" the mask for later draw calls.
560 SkPDFIndirectReference& noSMaskGS = fDocument->fNoSmaskGraphicState;
561 if (!noSMaskGS) {
562 SkPDFDict tmp("ExtGState");
563 tmp.insertName("SMask", "None");
564 noSMaskGS = fDocument->emit(tmp);
565 }
566 this->setGraphicState(noSMaskGS, contentStream);
567}
568
569void SkPDFDevice::internalDrawPath(const SkClipStack& clipStack,
570 const SkMatrix& ctm,
571 const SkPath& origPath,
572 const SkPaint& srcPaint,
573 bool pathIsMutable) {
574 if (clipStack.isEmpty(this->bounds())) {
575 return;
576 }
577 SkTCopyOnFirstWrite<SkPaint> paint(clean_paint(srcPaint));
578 SkPath modifiedPath;
579 SkPath* pathPtr = const_cast<SkPath*>(&origPath);
580
581 if (paint->getMaskFilter()) {
582 this->internalDrawPathWithFilter(clipStack, ctm, origPath, *paint);
583 return;
584 }
585
586 SkMatrix matrix = ctm;
587
588 if (paint->getPathEffect()) {
589 if (clipStack.isEmpty(this->bounds())) {
590 return;
591 }
592 if (!pathIsMutable) {
593 modifiedPath = origPath;
594 pathPtr = &modifiedPath;
595 pathIsMutable = true;
596 }
597 if (paint->getFillPath(*pathPtr, pathPtr)) {
598 set_style(&paint, SkPaint::kFill_Style);
599 } else {
600 set_style(&paint, SkPaint::kStroke_Style);
601 if (paint->getStrokeWidth() != 0) {
602 paint.writable()->setStrokeWidth(0);
603 }
604 }
605 paint.writable()->setPathEffect(nullptr);
606 }
607
608 if (this->handleInversePath(*pathPtr, *paint, pathIsMutable)) {
609 return;
610 }
611 if (matrix.getType() & SkMatrix::kPerspective_Mask) {
612 if (!pathIsMutable) {
613 modifiedPath = origPath;
614 pathPtr = &modifiedPath;
615 pathIsMutable = true;
616 }
617 pathPtr->transform(matrix);
618 if (paint->getShader()) {
619 transform_shader(paint.writable(), matrix);
620 }
621 matrix = SkMatrix::I();
622 }
623
624 ScopedContentEntry content(this, &clipStack, matrix, *paint);
625 if (!content) {
626 return;
627 }
628 constexpr SkScalar kToleranceScale = 0.0625f; // smaller = better conics (circles).
629 SkScalar matrixScale = matrix.mapRadius(1.0f);
630 SkScalar tolerance = matrixScale > 0.0f ? kToleranceScale / matrixScale : kToleranceScale;
631 bool consumeDegeratePathSegments =
632 paint->getStyle() == SkPaint::kFill_Style ||
633 (paint->getStrokeCap() != SkPaint::kRound_Cap &&
634 paint->getStrokeCap() != SkPaint::kSquare_Cap);
635 SkPDFUtils::EmitPath(*pathPtr, paint->getStyle(), consumeDegeratePathSegments, content.stream(),
636 tolerance);
637 SkPDFUtils::PaintPath(paint->getStyle(), pathPtr->getFillType(), content.stream());
638}
639
640////////////////////////////////////////////////////////////////////////////////
641
642void SkPDFDevice::drawImageRect(const SkImage* image,
643 const SkRect* src,
644 const SkRect& dst,
645 const SkPaint& paint,
646 SkCanvas::SrcRectConstraint) {
647 SkASSERT(image);
648 this->internalDrawImageRect(SkKeyedImage(sk_ref_sp(const_cast<SkImage*>(image))),
649 src, dst, paint, this->localToDevice());
650}
651
652void SkPDFDevice::drawSprite(const SkBitmap& bm, int x, int y, const SkPaint& paint) {
653 SkASSERT(!bm.drawsNothing());
654 auto r = SkRect::MakeXYWH(x, y, bm.width(), bm.height());
655 this->internalDrawImageRect(SkKeyedImage(bm), nullptr, r, paint, SkMatrix::I());
656}
657
658////////////////////////////////////////////////////////////////////////////////
659
660namespace {
661class GlyphPositioner {
662public:
663 GlyphPositioner(SkDynamicMemoryWStream* content,
664 SkScalar textSkewX,
665 SkPoint origin)
666 : fContent(content)
667 , fCurrentMatrixOrigin(origin)
668 , fTextSkewX(textSkewX) {
669 }
670 ~GlyphPositioner() { this->flush(); }
671 void flush() {
672 if (fInText) {
673 fContent->writeText("> Tj\n");
674 fInText = false;
675 }
676 }
677 void setWideChars(bool wide) {
678 this->flush();
679 fWideChars = wide;
680 }
681 void writeGlyph(SkPoint xy,
682 SkScalar advanceWidth,
683 uint16_t glyph) {
684 if (!fInitialized) {
685 // Flip the text about the x-axis to account for origin swap and include
686 // the passed parameters.
687 fContent->writeText("1 0 ");
688 SkPDFUtils::AppendScalar(-fTextSkewX, fContent);
689 fContent->writeText(" -1 ");
690 SkPDFUtils::AppendScalar(fCurrentMatrixOrigin.x(), fContent);
691 fContent->writeText(" ");
692 SkPDFUtils::AppendScalar(fCurrentMatrixOrigin.y(), fContent);
693 fContent->writeText(" Tm\n");
694 fCurrentMatrixOrigin.set(0.0f, 0.0f);
695 fInitialized = true;
696 }
697 SkPoint position = xy - fCurrentMatrixOrigin;
698 if (position != SkPoint{fXAdvance, 0}) {
699 this->flush();
700 SkPDFUtils::AppendScalar(position.x() - position.y() * fTextSkewX, fContent);
701 fContent->writeText(" ");
702 SkPDFUtils::AppendScalar(-position.y(), fContent);
703 fContent->writeText(" Td ");
704 fCurrentMatrixOrigin = xy;
705 fXAdvance = 0;
706 }
707 fXAdvance += advanceWidth;
708 if (!fInText) {
709 fContent->writeText("<");
710 fInText = true;
711 }
712 if (fWideChars) {
713 SkPDFUtils::WriteUInt16BE(fContent, glyph);
714 } else {
715 SkASSERT(0 == glyph >> 8);
716 SkPDFUtils::WriteUInt8(fContent, static_cast<uint8_t>(glyph));
717 }
718 }
719
720private:
721 SkDynamicMemoryWStream* fContent;
722 SkPoint fCurrentMatrixOrigin;
723 SkScalar fXAdvance = 0.0f;
724 SkScalar fTextSkewX;
725 bool fWideChars = true;
726 bool fInText = false;
727 bool fInitialized = false;
728};
729} // namespace
730
731static SkUnichar map_glyph(const std::vector<SkUnichar>& glyphToUnicode, SkGlyphID glyph) {
732 return glyph < glyphToUnicode.size() ? glyphToUnicode[SkToInt(glyph)] : -1;
733}
734
735namespace {
736struct PositionedGlyph {
737 SkPoint fPos;
738 SkGlyphID fGlyph;
739};
740}
741
742static SkRect get_glyph_bounds_device_space(const SkGlyph* glyph,
743 SkScalar xScale, SkScalar yScale,
744 SkPoint xy, const SkMatrix& ctm) {
745 SkRect glyphBounds = SkMatrix::MakeScale(xScale, yScale).mapRect(glyph->rect());
746 glyphBounds.offset(xy);
747 ctm.mapRect(&glyphBounds); // now in dev space.
748 return glyphBounds;
749}
750
751static bool contains(const SkRect& r, SkPoint p) {
752 return r.left() <= p.x() && p.x() <= r.right() &&
753 r.top() <= p.y() && p.y() <= r.bottom();
754}
755
756void SkPDFDevice::drawGlyphRunAsPath(
757 const SkGlyphRun& glyphRun, SkPoint offset, const SkPaint& runPaint) {
758 const SkFont& font = glyphRun.font();
759 SkPath path;
760
761 struct Rec {
762 SkPath* fPath;
763 SkPoint fOffset;
764 const SkPoint* fPos;
765 } rec = {&path, offset, glyphRun.positions().data()};
766
767 font.getPaths(glyphRun.glyphsIDs().data(), glyphRun.glyphsIDs().size(),
768 [](const SkPath* path, const SkMatrix& mx, void* ctx) {
769 Rec* rec = reinterpret_cast<Rec*>(ctx);
770 if (path) {
771 SkMatrix total = mx;
772 total.postTranslate(rec->fPos->fX + rec->fOffset.fX,
773 rec->fPos->fY + rec->fOffset.fY);
774 rec->fPath->addPath(*path, total);
775 }
776 rec->fPos += 1; // move to the next glyph's position
777 }, &rec);
778 this->internalDrawPath(this->cs(), this->localToDevice(), path, runPaint, true);
779
780 SkFont transparentFont = glyphRun.font();
781 transparentFont.setEmbolden(false); // Stop Recursion
782 SkGlyphRun tmpGlyphRun(glyphRun, transparentFont);
783
784 SkPaint transparent;
785 transparent.setColor(SK_ColorTRANSPARENT);
786
787 if (this->localToDevice().hasPerspective()) {
788 SkAutoDeviceTransformRestore adr(this, SkMatrix::I());
789 this->internalDrawGlyphRun(tmpGlyphRun, offset, transparent);
790 } else {
791 this->internalDrawGlyphRun(tmpGlyphRun, offset, transparent);
792 }
793}
794
795static bool needs_new_font(SkPDFFont* font, const SkGlyph* glyph,
796 SkAdvancedTypefaceMetrics::FontType fontType) {
797 if (!font || !font->hasGlyph(glyph->getGlyphID())) {
798 return true;
799 }
800 if (fontType == SkAdvancedTypefaceMetrics::kOther_Font) {
801 return false;
802 }
803 if (glyph->isEmpty()) {
804 return false;
805 }
806
807 bool bitmapOnly = nullptr == glyph->path();
808 bool convertedToType3 = (font->getType() == SkAdvancedTypefaceMetrics::kOther_Font);
809 return convertedToType3 != bitmapOnly;
810}
811
812void SkPDFDevice::internalDrawGlyphRun(
813 const SkGlyphRun& glyphRun, SkPoint offset, const SkPaint& runPaint) {
814
815 const SkGlyphID* glyphIDs = glyphRun.glyphsIDs().data();
816 uint32_t glyphCount = SkToU32(glyphRun.glyphsIDs().size());
817 const SkFont& glyphRunFont = glyphRun.font();
818
819 if (!glyphCount || !glyphIDs || glyphRunFont.getSize() <= 0 || this->hasEmptyClip()) {
820 return;
821 }
822 if (runPaint.getPathEffect()
823 || runPaint.getMaskFilter()
824 || glyphRunFont.isEmbolden()
825 || this->localToDevice().hasPerspective()
826 || SkPaint::kFill_Style != runPaint.getStyle()) {
827 // Stroked Text doesn't work well with Type3 fonts.
828 this->drawGlyphRunAsPath(glyphRun, offset, runPaint);
829 return;
830 }
831 SkTypeface* typeface = glyphRunFont.getTypefaceOrDefault();
832 if (!typeface) {
833 SkDebugf("SkPDF: SkTypeface::MakeDefault() returned nullptr.\n");
834 return;
835 }
836
837 const SkAdvancedTypefaceMetrics* metrics = SkPDFFont::GetMetrics(typeface, fDocument);
838 if (!metrics) {
839 return;
840 }
841 SkAdvancedTypefaceMetrics::FontType fontType = SkPDFFont::FontType(*metrics);
842
843 const std::vector<SkUnichar>& glyphToUnicode = SkPDFFont::GetUnicodeMap(typeface, fDocument);
844
845 SkClusterator clusterator(glyphRun);
846
847 int emSize;
848 SkStrikeSpec strikeSpec = SkStrikeSpec::MakePDFVector(*typeface, &emSize);
849
850 SkScalar textSize = glyphRunFont.getSize();
851 SkScalar advanceScale = textSize * glyphRunFont.getScaleX() / emSize;
852
853 // textScaleX and textScaleY are used to get a conservative bounding box for glyphs.
854 SkScalar textScaleY = textSize / emSize;
855 SkScalar textScaleX = advanceScale + glyphRunFont.getSkewX() * textScaleY;
856
857 SkRect clipStackBounds = this->cs().bounds(this->bounds());
858
859 SkTCopyOnFirstWrite<SkPaint> paint(clean_paint(runPaint));
860 ScopedContentEntry content(this, *paint, glyphRunFont.getScaleX());
861 if (!content) {
862 return;
863 }
864 SkDynamicMemoryWStream* out = content.stream();
865
866 out->writeText("BT\n");
867 SK_AT_SCOPE_EXIT(out->writeText("ET\n"));
868
869 ScopedOutputMarkedContentTags mark(fNodeId, fDocument, out);
870
871 const SkGlyphID maxGlyphID = SkToU16(typeface->countGlyphs() - 1);
872
873 if (clusterator.reversedChars()) {
874 out->writeText("/ReversedChars BMC\n");
875 }
876 SK_AT_SCOPE_EXIT(if (clusterator.reversedChars()) { out->writeText("EMC\n"); } );
877 GlyphPositioner glyphPositioner(out, glyphRunFont.getSkewX(), offset);
878 SkPDFFont* font = nullptr;
879
880 SkBulkGlyphMetricsAndPaths paths{strikeSpec};
881 auto glyphs = paths.glyphs(glyphRun.glyphsIDs());
882
883 while (SkClusterator::Cluster c = clusterator.next()) {
884 int index = c.fGlyphIndex;
885 int glyphLimit = index + c.fGlyphCount;
886
887 bool actualText = false;
888 SK_AT_SCOPE_EXIT(if (actualText) {
889 glyphPositioner.flush();
890 out->writeText("EMC\n");
891 });
892 if (c.fUtf8Text) { // real cluster
893 // Check if `/ActualText` needed.
894 const char* textPtr = c.fUtf8Text;
895 const char* textEnd = c.fUtf8Text + c.fTextByteLength;
896 SkUnichar unichar = SkUTF::NextUTF8(&textPtr, textEnd);
897 if (unichar < 0) {
898 return;
899 }
900 if (textPtr < textEnd || // more characters left
901 glyphLimit > index + 1 || // toUnicode wouldn't work
902 unichar != map_glyph(glyphToUnicode, glyphIDs[index])) // test single Unichar map
903 {
904 glyphPositioner.flush();
905 out->writeText("/Span<</ActualText <");
906 SkPDFUtils::WriteUTF16beHex(out, 0xFEFF); // U+FEFF = BYTE ORDER MARK
907 // the BOM marks this text as UTF-16BE, not PDFDocEncoding.
908 SkPDFUtils::WriteUTF16beHex(out, unichar); // first char
909 while (textPtr < textEnd) {
910 unichar = SkUTF::NextUTF8(&textPtr, textEnd);
911 if (unichar < 0) {
912 break;
913 }
914 SkPDFUtils::WriteUTF16beHex(out, unichar);
915 }
916 out->writeText("> >> BDC\n"); // begin marked-content sequence
917 // with an associated property list.
918 actualText = true;
919 }
920 }
921 for (; index < glyphLimit; ++index) {
922 SkGlyphID gid = glyphIDs[index];
923 if (gid > maxGlyphID) {
924 continue;
925 }
926 SkPoint xy = glyphRun.positions()[index];
927 // Do a glyph-by-glyph bounds-reject if positions are absolute.
928 SkRect glyphBounds = get_glyph_bounds_device_space(
929 glyphs[index], textScaleX, textScaleY,
930 xy + offset, this->localToDevice());
931 if (glyphBounds.isEmpty()) {
932 if (!contains(clipStackBounds, {glyphBounds.x(), glyphBounds.y()})) {
933 continue;
934 }
935 } else {
936 if (!clipStackBounds.intersects(glyphBounds)) {
937 continue; // reject glyphs as out of bounds
938 }
939 }
940 if (needs_new_font(font, glyphs[index], fontType)) {
941 // Not yet specified font or need to switch font.
942 font = SkPDFFont::GetFontResource(fDocument, glyphs[index], typeface);
943 SkASSERT(font); // All preconditions for SkPDFFont::GetFontResource are met.
944 glyphPositioner.flush();
945 glyphPositioner.setWideChars(font->multiByteGlyphs());
946 SkPDFWriteResourceName(out, SkPDFResourceType::kFont,
947 add_resource(fFontResources, font->indirectReference()));
948 out->writeText(" ");
949 SkPDFUtils::AppendScalar(textSize, out);
950 out->writeText(" Tf\n");
951
952 }
953 font->noteGlyphUsage(gid);
954 SkGlyphID encodedGlyph = font->multiByteGlyphs()
955 ? gid : font->glyphToPDFFontEncoding(gid);
956 SkScalar advance = advanceScale * glyphs[index]->advanceX();
957 glyphPositioner.writeGlyph(xy, advance, encodedGlyph);
958 }
959 }
960}
961
962void SkPDFDevice::drawGlyphRunList(const SkGlyphRunList& glyphRunList) {
963 for (const SkGlyphRun& glyphRun : glyphRunList) {
964 this->internalDrawGlyphRun(glyphRun, glyphRunList.origin(), glyphRunList.paint());
965 }
966}
967
968void SkPDFDevice::drawVertices(const SkVertices*, SkBlendMode, const SkPaint&) {
969 if (this->hasEmptyClip()) {
970 return;
971 }
972 // TODO: implement drawVertices
973}
974
975void SkPDFDevice::drawFormXObject(SkPDFIndirectReference xObject, SkDynamicMemoryWStream* content) {
976 ScopedOutputMarkedContentTags mark(fNodeId, fDocument, content);
977
978 SkASSERT(xObject);
979 SkPDFWriteResourceName(content, SkPDFResourceType::kXObject,
980 add_resource(fXObjectResources, xObject));
981 content->writeText(" Do\n");
982}
983
984void SkPDFDevice::drawDevice(SkBaseDevice* device, int x, int y, const SkPaint& paint) {
985 SkASSERT(!paint.getImageFilter());
986 SkASSERT(!paint.getMaskFilter());
987
988 // Check if the source device is really a bitmapdevice (because that's what we returned
989 // from createDevice (likely due to an imagefilter)
990 SkPixmap pmap;
991 if (device->peekPixels(&pmap)) {
992 SkBitmap bitmap;
993 bitmap.installPixels(pmap);
994 this->drawSprite(bitmap, x, y, paint);
995 return;
996 }
997
998 // our onCreateCompatibleDevice() always creates SkPDFDevice subclasses.
999 SkPDFDevice* pdfDevice = static_cast<SkPDFDevice*>(device);
1000
1001 if (pdfDevice->isContentEmpty()) {
1002 return;
1003 }
1004
1005 SkMatrix matrix = SkMatrix::MakeTrans(SkIntToScalar(x), SkIntToScalar(y));
1006 ScopedContentEntry content(this, &this->cs(), matrix, paint);
1007 if (!content) {
1008 return;
1009 }
1010 if (content.needShape()) {
1011 SkISize dim = device->imageInfo().dimensions();
1012 content.setShape(to_path(SkRect::Make(SkIRect::MakeXYWH(x, y, dim.width(), dim.height()))));
1013 }
1014 if (!content.needSource()) {
1015 return;
1016 }
1017 this->drawFormXObject(pdfDevice->makeFormXObjectFromDevice(), content.stream());
1018}
1019
1020sk_sp<SkSurface> SkPDFDevice::makeSurface(const SkImageInfo& info, const SkSurfaceProps& props) {
1021 return SkSurface::MakeRaster(info, &props);
1022}
1023
1024static std::vector<SkPDFIndirectReference> sort(const SkTHashSet<SkPDFIndirectReference>& src) {
1025 std::vector<SkPDFIndirectReference> dst;
1026 dst.reserve(src.count());
1027 src.foreach([&dst](SkPDFIndirectReference ref) { dst.push_back(ref); } );
1028 std::sort(dst.begin(), dst.end(),
1029 [](SkPDFIndirectReference a, SkPDFIndirectReference b) { return a.fValue < b.fValue; });
1030 return dst;
1031}
1032
1033std::unique_ptr<SkPDFDict> SkPDFDevice::makeResourceDict() {
1034 return SkPDFMakeResourceDict(sort(fGraphicStateResources),
1035 sort(fShaderResources),
1036 sort(fXObjectResources),
1037 sort(fFontResources));
1038}
1039
1040std::unique_ptr<SkStreamAsset> SkPDFDevice::content() {
1041 if (fActiveStackState.fContentStream) {
1042 fActiveStackState.drainStack();
1043 fActiveStackState = SkPDFGraphicStackState();
1044 }
1045 if (fContent.bytesWritten() == 0) {
1046 return std::make_unique<SkMemoryStream>();
1047 }
1048 SkDynamicMemoryWStream buffer;
1049 if (fInitialTransform.getType() != SkMatrix::kIdentity_Mask) {
1050 SkPDFUtils::AppendTransform(fInitialTransform, &buffer);
1051 }
1052 if (fNeedsExtraSave) {
1053 buffer.writeText("q\n");
1054 }
1055 fContent.writeToAndReset(&buffer);
1056 if (fNeedsExtraSave) {
1057 buffer.writeText("Q\n");
1058 }
1059 fNeedsExtraSave = false;
1060 return std::unique_ptr<SkStreamAsset>(buffer.detachAsStream());
1061}
1062
1063/* Draws an inverse filled path by using Path Ops to compute the positive
1064 * inverse using the current clip as the inverse bounds.
1065 * Return true if this was an inverse path and was properly handled,
1066 * otherwise returns false and the normal drawing routine should continue,
1067 * either as a (incorrect) fallback or because the path was not inverse
1068 * in the first place.
1069 */
1070bool SkPDFDevice::handleInversePath(const SkPath& origPath,
1071 const SkPaint& paint,
1072 bool pathIsMutable) {
1073 if (!origPath.isInverseFillType()) {
1074 return false;
1075 }
1076
1077 if (this->hasEmptyClip()) {
1078 return false;
1079 }
1080
1081 SkPath modifiedPath;
1082 SkPath* pathPtr = const_cast<SkPath*>(&origPath);
1083 SkPaint noInversePaint(paint);
1084
1085 // Merge stroking operations into final path.
1086 if (SkPaint::kStroke_Style == paint.getStyle() ||
1087 SkPaint::kStrokeAndFill_Style == paint.getStyle()) {
1088 bool doFillPath = paint.getFillPath(origPath, &modifiedPath);
1089 if (doFillPath) {
1090 noInversePaint.setStyle(SkPaint::kFill_Style);
1091 noInversePaint.setStrokeWidth(0);
1092 pathPtr = &modifiedPath;
1093 } else {
1094 // To be consistent with the raster output, hairline strokes
1095 // are rendered as non-inverted.
1096 modifiedPath.toggleInverseFillType();
1097 this->internalDrawPath(this->cs(), this->localToDevice(), modifiedPath, paint, true);
1098 return true;
1099 }
1100 }
1101
1102 // Get bounds of clip in current transform space
1103 // (clip bounds are given in device space).
1104 SkMatrix transformInverse;
1105 SkMatrix totalMatrix = this->localToDevice();
1106
1107 if (!totalMatrix.invert(&transformInverse)) {
1108 return false;
1109 }
1110 SkRect bounds = this->cs().bounds(this->bounds());
1111 transformInverse.mapRect(&bounds);
1112
1113 // Extend the bounds by the line width (plus some padding)
1114 // so the edge doesn't cause a visible stroke.
1115 bounds.outset(paint.getStrokeWidth() + SK_Scalar1,
1116 paint.getStrokeWidth() + SK_Scalar1);
1117
1118 if (!calculate_inverse_path(bounds, *pathPtr, &modifiedPath)) {
1119 return false;
1120 }
1121
1122 this->internalDrawPath(this->cs(), this->localToDevice(), modifiedPath, noInversePaint, true);
1123 return true;
1124}
1125
1126SkPDFIndirectReference SkPDFDevice::makeFormXObjectFromDevice(SkIRect bounds, bool alpha) {
1127 SkMatrix inverseTransform = SkMatrix::I();
1128 if (!fInitialTransform.isIdentity()) {
1129 if (!fInitialTransform.invert(&inverseTransform)) {
1130 SkDEBUGFAIL("Layer initial transform should be invertible.");
1131 inverseTransform.reset();
1132 }
1133 }
1134 const char* colorSpace = alpha ? "DeviceGray" : nullptr;
1135
1136 SkPDFIndirectReference xobject =
1137 SkPDFMakeFormXObject(fDocument, this->content(),
1138 SkPDFMakeArray(bounds.left(), bounds.top(),
1139 bounds.right(), bounds.bottom()),
1140 this->makeResourceDict(), inverseTransform, colorSpace);
1141 // We always draw the form xobjects that we create back into the device, so
1142 // we simply preserve the font usage instead of pulling it out and merging
1143 // it back in later.
1144 this->reset();
1145 return xobject;
1146}
1147
1148SkPDFIndirectReference SkPDFDevice::makeFormXObjectFromDevice(bool alpha) {
1149 return this->makeFormXObjectFromDevice(SkIRect{0, 0, this->width(), this->height()}, alpha);
1150}
1151
1152void SkPDFDevice::drawFormXObjectWithMask(SkPDFIndirectReference xObject,
1153 SkPDFIndirectReference sMask,
1154 SkBlendMode mode,
1155 bool invertClip) {
1156 SkASSERT(sMask);
1157 SkPaint paint;
1158 paint.setBlendMode(mode);
1159 ScopedContentEntry content(this, nullptr, SkMatrix::I(), paint);
1160 if (!content) {
1161 return;
1162 }
1163 this->setGraphicState(SkPDFGraphicState::GetSMaskGraphicState(
1164 sMask, invertClip, SkPDFGraphicState::kAlpha_SMaskMode,
1165 fDocument), content.stream());
1166 this->drawFormXObject(xObject, content.stream());
1167 this->clearMaskOnGraphicState(content.stream());
1168}
1169
1170
1171static bool treat_as_regular_pdf_blend_mode(SkBlendMode blendMode) {
1172 return nullptr != SkPDFUtils::BlendModeName(blendMode);
1173}
1174
1175static void populate_graphic_state_entry_from_paint(
1176 SkPDFDocument* doc,
1177 const SkMatrix& matrix,
1178 const SkClipStack* clipStack,
1179 SkIRect deviceBounds,
1180 const SkPaint& paint,
1181 const SkMatrix& initialTransform,
1182 SkScalar textScale,
1183 SkPDFGraphicStackState::Entry* entry,
1184 SkTHashSet<SkPDFIndirectReference>* shaderResources,
1185 SkTHashSet<SkPDFIndirectReference>* graphicStateResources) {
1186 NOT_IMPLEMENTED(paint.getPathEffect() != nullptr, false);
1187 NOT_IMPLEMENTED(paint.getMaskFilter() != nullptr, false);
1188 NOT_IMPLEMENTED(paint.getColorFilter() != nullptr, false);
1189
1190 entry->fMatrix = matrix;
1191 entry->fClipStackGenID = clipStack ? clipStack->getTopmostGenID()
1192 : SkClipStack::kWideOpenGenID;
1193 SkColor4f color = paint.getColor4f();
1194 entry->fColor = {color.fR, color.fG, color.fB, 1};
1195 entry->fShaderIndex = -1;
1196
1197 // PDF treats a shader as a color, so we only set one or the other.
1198 SkShader* shader = paint.getShader();
1199 if (shader) {
1200 if (SkShader::kColor_GradientType == shader->asAGradient(nullptr)) {
1201 // We don't have to set a shader just for a color.
1202 SkShader::GradientInfo gradientInfo;
1203 SkColor gradientColor = SK_ColorBLACK;
1204 gradientInfo.fColors = &gradientColor;
1205 gradientInfo.fColorOffsets = nullptr;
1206 gradientInfo.fColorCount = 1;
1207 SkAssertResult(shader->asAGradient(&gradientInfo) == SkShader::kColor_GradientType);
1208 color = SkColor4f::FromColor(gradientColor);
1209 entry->fColor ={color.fR, color.fG, color.fB, 1};
1210
1211 } else {
1212 // PDF positions patterns relative to the initial transform, so
1213 // we need to apply the current transform to the shader parameters.
1214 SkMatrix transform = matrix;
1215 transform.postConcat(initialTransform);
1216
1217 // PDF doesn't support kClamp_TileMode, so we simulate it by making
1218 // a pattern the size of the current clip.
1219 SkRect clipStackBounds = clipStack ? clipStack->bounds(deviceBounds)
1220 : SkRect::Make(deviceBounds);
1221
1222 // We need to apply the initial transform to bounds in order to get
1223 // bounds in a consistent coordinate system.
1224 initialTransform.mapRect(&clipStackBounds);
1225 SkIRect bounds;
1226 clipStackBounds.roundOut(&bounds);
1227
1228 SkPDFIndirectReference pdfShader
1229 = SkPDFMakeShader(doc, shader, transform, bounds, paint.getColor4f());
1230
1231 if (pdfShader) {
1232 // pdfShader has been canonicalized so we can directly compare pointers.
1233 entry->fShaderIndex = add_resource(*shaderResources, pdfShader);
1234 }
1235 }
1236 }
1237
1238 SkPDFIndirectReference newGraphicState;
1239 if (color == paint.getColor4f()) {
1240 newGraphicState = SkPDFGraphicState::GetGraphicStateForPaint(doc, paint);
1241 } else {
1242 SkPaint newPaint = paint;
1243 newPaint.setColor4f(color, nullptr);
1244 newGraphicState = SkPDFGraphicState::GetGraphicStateForPaint(doc, newPaint);
1245 }
1246 entry->fGraphicStateIndex = add_resource(*graphicStateResources, newGraphicState);
1247 entry->fTextScaleX = textScale;
1248}
1249
1250SkDynamicMemoryWStream* SkPDFDevice::setUpContentEntry(const SkClipStack* clipStack,
1251 const SkMatrix& matrix,
1252 const SkPaint& paint,
1253 SkScalar textScale,
1254 SkPDFIndirectReference* dst) {
1255 SkASSERT(!*dst);
1256 SkBlendMode blendMode = paint.getBlendMode();
1257
1258 // Dst xfer mode doesn't draw source at all.
1259 if (blendMode == SkBlendMode::kDst) {
1260 return nullptr;
1261 }
1262
1263 // For the following modes, we want to handle source and destination
1264 // separately, so make an object of what's already there.
1265 if (!treat_as_regular_pdf_blend_mode(blendMode) && blendMode != SkBlendMode::kDstOver) {
1266 if (!isContentEmpty()) {
1267 *dst = this->makeFormXObjectFromDevice();
1268 SkASSERT(isContentEmpty());
1269 } else if (blendMode != SkBlendMode::kSrc &&
1270 blendMode != SkBlendMode::kSrcOut) {
1271 // Except for Src and SrcOut, if there isn't anything already there,
1272 // then we're done.
1273 return nullptr;
1274 }
1275 }
1276 // TODO(vandebo): Figure out how/if we can handle the following modes:
1277 // Xor, Plus. For now, we treat them as SrcOver/Normal.
1278
1279 if (treat_as_regular_pdf_blend_mode(blendMode)) {
1280 if (!fActiveStackState.fContentStream) {
1281 if (fContent.bytesWritten() != 0) {
1282 fContent.writeText("Q\nq\n");
1283 fNeedsExtraSave = true;
1284 }
1285 fActiveStackState = SkPDFGraphicStackState(&fContent);
1286 } else {
1287 SkASSERT(fActiveStackState.fContentStream = &fContent);
1288 }
1289 } else {
1290 fActiveStackState.drainStack();
1291 fActiveStackState = SkPDFGraphicStackState(&fContentBuffer);
1292 }
1293 SkASSERT(fActiveStackState.fContentStream);
1294 SkPDFGraphicStackState::Entry entry;
1295 populate_graphic_state_entry_from_paint(
1296 fDocument,
1297 matrix,
1298 clipStack,
1299 this->bounds(),
1300 paint,
1301 fInitialTransform,
1302 textScale,
1303 &entry,
1304 &fShaderResources,
1305 &fGraphicStateResources);
1306 fActiveStackState.updateClip(clipStack, this->bounds());
1307 fActiveStackState.updateMatrix(entry.fMatrix);
1308 fActiveStackState.updateDrawingState(entry);
1309
1310 return fActiveStackState.fContentStream;
1311}
1312
1313void SkPDFDevice::finishContentEntry(const SkClipStack* clipStack,
1314 SkBlendMode blendMode,
1315 SkPDFIndirectReference dst,
1316 SkPath* shape) {
1317 SkASSERT(blendMode != SkBlendMode::kDst);
1318 if (treat_as_regular_pdf_blend_mode(blendMode)) {
1319 SkASSERT(!dst);
1320 return;
1321 }
1322
1323 SkASSERT(fActiveStackState.fContentStream);
1324
1325 fActiveStackState.drainStack();
1326 fActiveStackState = SkPDFGraphicStackState();
1327
1328 if (blendMode == SkBlendMode::kDstOver) {
1329 SkASSERT(!dst);
1330 if (fContentBuffer.bytesWritten() != 0) {
1331 if (fContent.bytesWritten() != 0) {
1332 fContentBuffer.writeText("Q\nq\n");
1333 fNeedsExtraSave = true;
1334 }
1335 fContentBuffer.prependToAndReset(&fContent);
1336 SkASSERT(fContentBuffer.bytesWritten() == 0);
1337 }
1338 return;
1339 }
1340 if (fContentBuffer.bytesWritten() != 0) {
1341 if (fContent.bytesWritten() != 0) {
1342 fContent.writeText("Q\nq\n");
1343 fNeedsExtraSave = true;
1344 }
1345 fContentBuffer.writeToAndReset(&fContent);
1346 SkASSERT(fContentBuffer.bytesWritten() == 0);
1347 }
1348
1349 if (!dst) {
1350 SkASSERT(blendMode == SkBlendMode::kSrc ||
1351 blendMode == SkBlendMode::kSrcOut);
1352 return;
1353 }
1354
1355 SkASSERT(dst);
1356 // Changing the current content into a form-xobject will destroy the clip
1357 // objects which is fine since the xobject will already be clipped. However
1358 // if source has shape, we need to clip it too, so a copy of the clip is
1359 // saved.
1360
1361 SkPaint stockPaint;
1362
1363 SkPDFIndirectReference srcFormXObject;
1364 if (this->isContentEmpty()) {
1365 // If nothing was drawn and there's no shape, then the draw was a
1366 // no-op, but dst needs to be restored for that to be true.
1367 // If there is shape, then an empty source with Src, SrcIn, SrcOut,
1368 // DstIn, DstAtop or Modulate reduces to Clear and DstOut or SrcAtop
1369 // reduces to Dst.
1370 if (shape == nullptr || blendMode == SkBlendMode::kDstOut ||
1371 blendMode == SkBlendMode::kSrcATop) {
1372 ScopedContentEntry content(this, nullptr, SkMatrix::I(), stockPaint);
1373 this->drawFormXObject(dst, content.stream());
1374 return;
1375 } else {
1376 blendMode = SkBlendMode::kClear;
1377 }
1378 } else {
1379 srcFormXObject = this->makeFormXObjectFromDevice();
1380 }
1381
1382 // TODO(vandebo) srcFormXObject may contain alpha, but here we want it
1383 // without alpha.
1384 if (blendMode == SkBlendMode::kSrcATop) {
1385 // TODO(vandebo): In order to properly support SrcATop we have to track
1386 // the shape of what's been drawn at all times. It's the intersection of
1387 // the non-transparent parts of the device and the outlines (shape) of
1388 // all images and devices drawn.
1389 this->drawFormXObjectWithMask(srcFormXObject, dst, SkBlendMode::kSrcOver, true);
1390 } else {
1391 if (shape != nullptr) {
1392 // Draw shape into a form-xobject.
1393 SkPaint filledPaint;
1394 filledPaint.setColor(SK_ColorBLACK);
1395 filledPaint.setStyle(SkPaint::kFill_Style);
1396 SkClipStack empty;
1397 SkPDFDevice shapeDev(this->size(), fDocument, fInitialTransform);
1398 shapeDev.internalDrawPath(clipStack ? *clipStack : empty,
1399 SkMatrix::I(), *shape, filledPaint, true);
1400 this->drawFormXObjectWithMask(dst, shapeDev.makeFormXObjectFromDevice(),
1401 SkBlendMode::kSrcOver, true);
1402 } else {
1403 this->drawFormXObjectWithMask(dst, srcFormXObject, SkBlendMode::kSrcOver, true);
1404 }
1405 }
1406
1407 if (blendMode == SkBlendMode::kClear) {
1408 return;
1409 } else if (blendMode == SkBlendMode::kSrc ||
1410 blendMode == SkBlendMode::kDstATop) {
1411 ScopedContentEntry content(this, nullptr, SkMatrix::I(), stockPaint);
1412 if (content) {
1413 this->drawFormXObject(srcFormXObject, content.stream());
1414 }
1415 if (blendMode == SkBlendMode::kSrc) {
1416 return;
1417 }
1418 } else if (blendMode == SkBlendMode::kSrcATop) {
1419 ScopedContentEntry content(this, nullptr, SkMatrix::I(), stockPaint);
1420 if (content) {
1421 this->drawFormXObject(dst, content.stream());
1422 }
1423 }
1424
1425 SkASSERT(blendMode == SkBlendMode::kSrcIn ||
1426 blendMode == SkBlendMode::kDstIn ||
1427 blendMode == SkBlendMode::kSrcOut ||
1428 blendMode == SkBlendMode::kDstOut ||
1429 blendMode == SkBlendMode::kSrcATop ||
1430 blendMode == SkBlendMode::kDstATop ||
1431 blendMode == SkBlendMode::kModulate);
1432
1433 if (blendMode == SkBlendMode::kSrcIn ||
1434 blendMode == SkBlendMode::kSrcOut ||
1435 blendMode == SkBlendMode::kSrcATop) {
1436 this->drawFormXObjectWithMask(srcFormXObject, dst, SkBlendMode::kSrcOver,
1437 blendMode == SkBlendMode::kSrcOut);
1438 return;
1439 } else {
1440 SkBlendMode mode = SkBlendMode::kSrcOver;
1441 if (blendMode == SkBlendMode::kModulate) {
1442 this->drawFormXObjectWithMask(srcFormXObject, dst, SkBlendMode::kSrcOver, false);
1443 mode = SkBlendMode::kMultiply;
1444 }
1445 this->drawFormXObjectWithMask(dst, srcFormXObject, mode, blendMode == SkBlendMode::kDstOut);
1446 return;
1447 }
1448}
1449
1450bool SkPDFDevice::isContentEmpty() {
1451 return fContent.bytesWritten() == 0 && fContentBuffer.bytesWritten() == 0;
1452}
1453
1454static SkSize rect_to_size(const SkRect& r) { return {r.width(), r.height()}; }
1455
1456static sk_sp<SkImage> color_filter(const SkImage* image,
1457 SkColorFilter* colorFilter) {
1458 auto surface =
1459 SkSurface::MakeRaster(SkImageInfo::MakeN32Premul(image->dimensions()));
1460 SkASSERT(surface);
1461 SkCanvas* canvas = surface->getCanvas();
1462 canvas->clear(SK_ColorTRANSPARENT);
1463 SkPaint paint;
1464 paint.setColorFilter(sk_ref_sp(colorFilter));
1465 canvas->drawImage(image, 0, 0, &paint);
1466 return surface->makeImageSnapshot();
1467}
1468
1469////////////////////////////////////////////////////////////////////////////////
1470
1471static bool is_integer(SkScalar x) {
1472 return x == SkScalarTruncToScalar(x);
1473}
1474
1475static bool is_integral(const SkRect& r) {
1476 return is_integer(r.left()) &&
1477 is_integer(r.top()) &&
1478 is_integer(r.right()) &&
1479 is_integer(r.bottom());
1480}
1481
1482void SkPDFDevice::internalDrawImageRect(SkKeyedImage imageSubset,
1483 const SkRect* src,
1484 const SkRect& dst,
1485 const SkPaint& srcPaint,
1486 const SkMatrix& ctm) {
1487 if (this->hasEmptyClip()) {
1488 return;
1489 }
1490 if (!imageSubset) {
1491 return;
1492 }
1493
1494 // First, figure out the src->dst transform and subset the image if needed.
1495 SkIRect bounds = imageSubset.image()->bounds();
1496 SkRect srcRect = src ? *src : SkRect::Make(bounds);
1497 SkMatrix transform;
1498 transform.setRectToRect(srcRect, dst, SkMatrix::kFill_ScaleToFit);
1499 if (src && *src != SkRect::Make(bounds)) {
1500 if (!srcRect.intersect(SkRect::Make(bounds))) {
1501 return;
1502 }
1503 srcRect.roundOut(&bounds);
1504 transform.preTranslate(SkIntToScalar(bounds.x()),
1505 SkIntToScalar(bounds.y()));
1506 if (bounds != imageSubset.image()->bounds()) {
1507 imageSubset = imageSubset.subset(bounds);
1508 }
1509 if (!imageSubset) {
1510 return;
1511 }
1512 }
1513
1514 // If the image is opaque and the paint's alpha is too, replace
1515 // kSrc blendmode with kSrcOver. http://crbug.com/473572
1516 SkTCopyOnFirstWrite<SkPaint> paint(srcPaint);
1517 if (SkBlendMode::kSrcOver != paint->getBlendMode() &&
1518 imageSubset.image()->isOpaque() &&
1519 kSrcOver_SkXfermodeInterpretation == SkInterpretXfermode(*paint, false))
1520 {
1521 paint.writable()->setBlendMode(SkBlendMode::kSrcOver);
1522 }
1523
1524 // Alpha-only images need to get their color from the shader, before
1525 // applying the colorfilter.
1526 if (imageSubset.image()->isAlphaOnly() && paint->getColorFilter()) {
1527 // must blend alpha image and shader before applying colorfilter.
1528 auto surface =
1529 SkSurface::MakeRaster(SkImageInfo::MakeN32Premul(imageSubset.image()->dimensions()));
1530 SkCanvas* canvas = surface->getCanvas();
1531 SkPaint tmpPaint;
1532 // In the case of alpha images with shaders, the shader's coordinate
1533 // system is the image's coordiantes.
1534 tmpPaint.setShader(sk_ref_sp(paint->getShader()));
1535 tmpPaint.setColor4f(paint->getColor4f(), nullptr);
1536 canvas->clear(0x00000000);
1537 canvas->drawImage(imageSubset.image().get(), 0, 0, &tmpPaint);
1538 if (paint->getShader() != nullptr) {
1539 paint.writable()->setShader(nullptr);
1540 }
1541 imageSubset = SkKeyedImage(surface->makeImageSnapshot());
1542 SkASSERT(!imageSubset.image()->isAlphaOnly());
1543 }
1544
1545 if (imageSubset.image()->isAlphaOnly()) {
1546 // The ColorFilter applies to the paint color/shader, not the alpha layer.
1547 SkASSERT(nullptr == paint->getColorFilter());
1548
1549 sk_sp<SkImage> mask = alpha_image_to_greyscale_image(imageSubset.image().get());
1550 if (!mask) {
1551 return;
1552 }
1553 // PDF doesn't seem to allow masking vector graphics with an Image XObject.
1554 // Must mask with a Form XObject.
1555 sk_sp<SkPDFDevice> maskDevice = this->makeCongruentDevice();
1556 {
1557 SkCanvas canvas(maskDevice);
1558 // This clip prevents the mask image shader from covering
1559 // entire device if unnecessary.
1560 canvas.clipRect(this->cs().bounds(this->bounds()));
1561 canvas.concat(ctm);
1562 if (paint->getMaskFilter()) {
1563 SkPaint tmpPaint;
1564 tmpPaint.setShader(mask->makeShader(&transform));
1565 tmpPaint.setMaskFilter(sk_ref_sp(paint->getMaskFilter()));
1566 canvas.drawRect(dst, tmpPaint);
1567 } else {
1568 if (src && !is_integral(*src)) {
1569 canvas.clipRect(dst);
1570 }
1571 canvas.concat(transform);
1572 canvas.drawImage(mask, 0, 0);
1573 }
1574 }
1575 SkIRect maskDeviceBounds = maskDevice->cs().bounds(maskDevice->bounds()).roundOut();
1576 if (!ctm.isIdentity() && paint->getShader()) {
1577 transform_shader(paint.writable(), ctm); // Since we are using identity matrix.
1578 }
1579 ScopedContentEntry content(this, &this->cs(), SkMatrix::I(), *paint);
1580 if (!content) {
1581 return;
1582 }
1583 this->setGraphicState(SkPDFGraphicState::GetSMaskGraphicState(
1584 maskDevice->makeFormXObjectFromDevice(maskDeviceBounds, true), false,
1585 SkPDFGraphicState::kLuminosity_SMaskMode, fDocument), content.stream());
1586 SkPDFUtils::AppendRectangle(SkRect::Make(this->size()), content.stream());
1587 SkPDFUtils::PaintPath(SkPaint::kFill_Style, SkPathFillType::kWinding, content.stream());
1588 this->clearMaskOnGraphicState(content.stream());
1589 return;
1590 }
1591 if (paint->getMaskFilter()) {
1592 paint.writable()->setShader(imageSubset.image()->makeShader(&transform));
1593 SkPath path = to_path(dst); // handles non-integral clipping.
1594 this->internalDrawPath(this->cs(), this->localToDevice(), path, *paint, true);
1595 return;
1596 }
1597 transform.postConcat(ctm);
1598
1599 bool needToRestore = false;
1600 if (src && !is_integral(*src)) {
1601 // Need sub-pixel clipping to fix https://bug.skia.org/4374
1602 this->cs().save();
1603 this->cs().clipRect(dst, ctm, SkClipOp::kIntersect, true);
1604 needToRestore = true;
1605 }
1606 SK_AT_SCOPE_EXIT(if (needToRestore) { this->cs().restore(); });
1607
1608 SkMatrix matrix = transform;
1609
1610 // Rasterize the bitmap using perspective in a new bitmap.
1611 if (transform.hasPerspective()) {
1612 // Transform the bitmap in the new space, without taking into
1613 // account the initial transform.
1614 SkRect imageBounds = SkRect::Make(imageSubset.image()->bounds());
1615 SkPath perspectiveOutline = to_path(imageBounds);
1616 perspectiveOutline.transform(transform);
1617
1618 // TODO(edisonn): perf - use current clip too.
1619 // Retrieve the bounds of the new shape.
1620 SkRect bounds = perspectiveOutline.getBounds();
1621
1622 // Transform the bitmap in the new space, taking into
1623 // account the initial transform.
1624 SkMatrix total = transform;
1625 total.postConcat(fInitialTransform);
1626
1627 SkPath physicalPerspectiveOutline = to_path(imageBounds);
1628 physicalPerspectiveOutline.transform(total);
1629
1630 SkRect physicalPerspectiveBounds =
1631 physicalPerspectiveOutline.getBounds();
1632 SkScalar scaleX = physicalPerspectiveBounds.width() / bounds.width();
1633 SkScalar scaleY = physicalPerspectiveBounds.height() / bounds.height();
1634
1635 // TODO(edisonn): A better approach would be to use a bitmap shader
1636 // (in clamp mode) and draw a rect over the entire bounding box. Then
1637 // intersect perspectiveOutline to the clip. That will avoid introducing
1638 // alpha to the image while still giving good behavior at the edge of
1639 // the image. Avoiding alpha will reduce the pdf size and generation
1640 // CPU time some.
1641
1642 SkISize wh = rect_to_size(physicalPerspectiveBounds).toCeil();
1643
1644 auto surface = SkSurface::MakeRaster(SkImageInfo::MakeN32Premul(wh));
1645 if (!surface) {
1646 return;
1647 }
1648 SkCanvas* canvas = surface->getCanvas();
1649 canvas->clear(SK_ColorTRANSPARENT);
1650
1651 SkScalar deltaX = bounds.left();
1652 SkScalar deltaY = bounds.top();
1653
1654 SkMatrix offsetMatrix = transform;
1655 offsetMatrix.postTranslate(-deltaX, -deltaY);
1656 offsetMatrix.postScale(scaleX, scaleY);
1657
1658 // Translate the draw in the new canvas, so we perfectly fit the
1659 // shape in the bitmap.
1660 canvas->setMatrix(offsetMatrix);
1661 canvas->drawImage(imageSubset.image(), 0, 0);
1662 // Make sure the final bits are in the bitmap.
1663 surface->flush();
1664
1665 // In the new space, we use the identity matrix translated
1666 // and scaled to reflect DPI.
1667 matrix.setScale(1 / scaleX, 1 / scaleY);
1668 matrix.postTranslate(deltaX, deltaY);
1669
1670 imageSubset = SkKeyedImage(surface->makeImageSnapshot());
1671 if (!imageSubset) {
1672 return;
1673 }
1674 }
1675
1676 SkMatrix scaled;
1677 // Adjust for origin flip.
1678 scaled.setScale(SK_Scalar1, -SK_Scalar1);
1679 scaled.postTranslate(0, SK_Scalar1);
1680 // Scale the image up from 1x1 to WxH.
1681 SkIRect subset = imageSubset.image()->bounds();
1682 scaled.postScale(SkIntToScalar(subset.width()),
1683 SkIntToScalar(subset.height()));
1684 scaled.postConcat(matrix);
1685 ScopedContentEntry content(this, &this->cs(), scaled, *paint);
1686 if (!content) {
1687 return;
1688 }
1689 if (content.needShape()) {
1690 SkPath shape = to_path(SkRect::Make(subset));
1691 shape.transform(matrix);
1692 content.setShape(shape);
1693 }
1694 if (!content.needSource()) {
1695 return;
1696 }
1697
1698 if (SkColorFilter* colorFilter = paint->getColorFilter()) {
1699 sk_sp<SkImage> img = color_filter(imageSubset.image().get(), colorFilter);
1700 imageSubset = SkKeyedImage(std::move(img));
1701 if (!imageSubset) {
1702 return;
1703 }
1704 // TODO(halcanary): de-dupe this by caching filtered images.
1705 // (maybe in the resource cache?)
1706 }
1707
1708 SkBitmapKey key = imageSubset.key();
1709 SkPDFIndirectReference* pdfimagePtr = fDocument->fPDFBitmapMap.find(key);
1710 SkPDFIndirectReference pdfimage = pdfimagePtr ? *pdfimagePtr : SkPDFIndirectReference();
1711 if (!pdfimagePtr) {
1712 SkASSERT(imageSubset);
1713 pdfimage = SkPDFSerializeImage(imageSubset.image().get(), fDocument,
1714 fDocument->metadata().fEncodingQuality);
1715 SkASSERT((key != SkBitmapKey{{0, 0, 0, 0}, 0}));
1716 fDocument->fPDFBitmapMap.set(key, pdfimage);
1717 }
1718 SkASSERT(pdfimage != SkPDFIndirectReference());
1719 this->drawFormXObject(pdfimage, content.stream());
1720}
1721
1722///////////////////////////////////////////////////////////////////////////////////////////////////
1723
1724#include "include/core/SkImageFilter.h"
1725#include "src/core/SkSpecialImage.h"
1726
1727void SkPDFDevice::drawSpecial(SkSpecialImage* srcImg, int x, int y, const SkPaint& paint,
1728 SkImage* clipImage, const SkMatrix& clipMatrix) {
1729 if (this->hasEmptyClip()) {
1730 return;
1731 }
1732 SkASSERT(!srcImg->isTextureBacked());
1733 SkASSERT(!paint.getMaskFilter());
1734
1735 //TODO: clipImage support
1736
1737 SkBitmap resultBM;
1738
1739 SkImageFilter* filter = paint.getImageFilter();
1740 if (filter) {
1741 SkIPoint offset = SkIPoint::Make(0, 0);
1742 SkMatrix matrix = this->localToDevice();
1743 matrix.postTranslate(SkIntToScalar(-x), SkIntToScalar(-y));
1744 const SkIRect clipBounds =
1745 this->cs().bounds(this->bounds()).roundOut().makeOffset(-x, -y);
1746 sk_sp<SkImageFilterCache> cache(this->getImageFilterCache());
1747 // TODO: Should PDF be operating in a specified color type/space? For now, run the filter
1748 // in the same color space as the source (this is different from all other backends).
1749 SkImageFilter_Base::Context ctx(matrix, clipBounds, cache.get(), kN32_SkColorType,
1750 srcImg->getColorSpace(), srcImg);
1751
1752 sk_sp<SkSpecialImage> resultImg(as_IFB(filter)->filterImage(ctx).imageAndOffset(&offset));
1753 if (resultImg) {
1754 SkPaint tmpUnfiltered(paint);
1755 tmpUnfiltered.setImageFilter(nullptr);
1756 if (resultImg->getROPixels(&resultBM)) {
1757 this->drawSprite(resultBM, x + offset.x(), y + offset.y(), tmpUnfiltered);
1758 }
1759 }
1760 } else {
1761 if (srcImg->getROPixels(&resultBM)) {
1762 this->drawSprite(resultBM, x, y, paint);
1763 }
1764 }
1765}
1766
1767sk_sp<SkSpecialImage> SkPDFDevice::makeSpecial(const SkBitmap& bitmap) {
1768 return SkSpecialImage::MakeFromRaster(bitmap.bounds(), bitmap);
1769}
1770
1771sk_sp<SkSpecialImage> SkPDFDevice::makeSpecial(const SkImage* image) {
1772 return SkSpecialImage::MakeFromImage(nullptr, image->bounds(), image->makeNonTextureImage());
1773}
1774
1775SkImageFilterCache* SkPDFDevice::getImageFilterCache() {
1776 // We always return a transient cache, so it is freed after each
1777 // filter traversal.
1778 return SkImageFilterCache::Create(SkImageFilterCache::kDefaultTransientSize);
1779}
1780