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