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