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