Line data Source code
1 : /*
2 : * Copyright 2011 Google Inc.
3 : *
4 : * Use of this source code is governed by a BSD-style license that can be
5 : * found in the LICENSE file.
6 : */
7 :
8 : #include "SkPDFDevice.h"
9 :
10 : #include "SkAdvancedTypefaceMetrics.h"
11 : #include "SkAnnotationKeys.h"
12 : #include "SkBitmapDevice.h"
13 : #include "SkBitmapKey.h"
14 : #include "SkColor.h"
15 : #include "SkColorFilter.h"
16 : #include "SkDraw.h"
17 : #include "SkDrawFilter.h"
18 : #include "SkGlyphCache.h"
19 : #include "SkImageFilterCache.h"
20 : #include "SkMakeUnique.h"
21 : #include "SkPath.h"
22 : #include "SkPathEffect.h"
23 : #include "SkPathOps.h"
24 : #include "SkPDFBitmap.h"
25 : #include "SkPDFCanon.h"
26 : #include "SkPDFDocument.h"
27 : #include "SkPDFFont.h"
28 : #include "SkPDFFormXObject.h"
29 : #include "SkPDFGraphicState.h"
30 : #include "SkPDFResourceDict.h"
31 : #include "SkPDFShader.h"
32 : #include "SkPDFTypes.h"
33 : #include "SkPDFUtils.h"
34 : #include "SkPixelRef.h"
35 : #include "SkRasterClip.h"
36 : #include "SkRRect.h"
37 : #include "SkScopeExit.h"
38 : #include "SkString.h"
39 : #include "SkSurface.h"
40 : #include "SkTemplates.h"
41 : #include "SkTextBlobRunIterator.h"
42 : #include "SkTextFormatParams.h"
43 : #include "SkUtils.h"
44 : #include "SkXfermodeInterpretation.h"
45 : #include "SkClipOpPriv.h"
46 :
47 : #define DPI_FOR_RASTER_SCALE_ONE 72
48 :
49 : // Utility functions
50 :
51 0 : static void draw_points(SkCanvas::PointMode mode,
52 : size_t count,
53 : const SkPoint* points,
54 : const SkPaint& paint,
55 : const SkIRect& bounds,
56 : const SkMatrix& ctm,
57 : SkBaseDevice* device) {
58 0 : SkRasterClip rc(bounds);
59 0 : SkDraw draw;
60 0 : draw.fDst = SkPixmap(SkImageInfo::MakeUnknown(bounds.right(), bounds.bottom()), nullptr, 0);
61 0 : draw.fMatrix = &ctm;
62 0 : draw.fRC = &rc;
63 0 : draw.drawPoints(mode, count, points, paint, device);
64 0 : }
65 :
66 0 : static SkIRect size(const SkBaseDevice& dev) { return {0, 0, dev.width(), dev.height()}; }
67 :
68 : // If the paint will definitely draw opaquely, replace kSrc with
69 : // kSrcOver. http://crbug.com/473572
70 0 : static void replace_srcmode_on_opaque_paint(SkPaint* paint) {
71 0 : if (kSrcOver_SkXfermodeInterpretation == SkInterpretXfermode(*paint, false)) {
72 0 : paint->setBlendMode(SkBlendMode::kSrcOver);
73 : }
74 0 : }
75 :
76 0 : static void emit_pdf_color(SkColor color, SkWStream* result) {
77 0 : SkASSERT(SkColorGetA(color) == 0xFF); // We handle alpha elsewhere.
78 0 : SkPDFUtils::AppendColorComponent(SkColorGetR(color), result);
79 0 : result->writeText(" ");
80 0 : SkPDFUtils::AppendColorComponent(SkColorGetG(color), result);
81 0 : result->writeText(" ");
82 0 : SkPDFUtils::AppendColorComponent(SkColorGetB(color), result);
83 0 : result->writeText(" ");
84 0 : }
85 :
86 0 : static SkPaint calculate_text_paint(const SkPaint& paint) {
87 0 : SkPaint result = paint;
88 0 : if (result.isFakeBoldText()) {
89 0 : SkScalar fakeBoldScale = SkScalarInterpFunc(result.getTextSize(),
90 : kStdFakeBoldInterpKeys,
91 : kStdFakeBoldInterpValues,
92 0 : kStdFakeBoldInterpLength);
93 0 : SkScalar width = result.getTextSize() * fakeBoldScale;
94 0 : if (result.getStyle() == SkPaint::kFill_Style) {
95 0 : result.setStyle(SkPaint::kStrokeAndFill_Style);
96 : } else {
97 0 : width += result.getStrokeWidth();
98 : }
99 0 : result.setStrokeWidth(width);
100 : }
101 0 : return result;
102 : }
103 :
104 0 : static SkImageSubset make_image_subset(const SkBitmap& bitmap) {
105 0 : SkASSERT(!bitmap.drawsNothing());
106 0 : SkIRect subset = bitmap.getSubset();
107 0 : SkAutoLockPixels autoLockPixels(bitmap);
108 0 : SkASSERT(bitmap.pixelRef());
109 0 : SkBitmap tmp;
110 0 : tmp.setInfo(bitmap.pixelRef()->info(), bitmap.rowBytes());
111 0 : tmp.setPixelRef(sk_ref_sp(bitmap.pixelRef()), 0, 0);
112 0 : tmp.lockPixels();
113 0 : auto img = SkImage::MakeFromBitmap(tmp);
114 0 : if (img) {
115 0 : SkASSERT(!bitmap.isImmutable() || img->uniqueID() == bitmap.getGenerationID());
116 0 : SkASSERT(img->bounds().contains(subset));
117 : }
118 0 : SkImageSubset imageSubset(std::move(img), subset);
119 : // SkImage::MakeFromBitmap only preserves genID for immutable
120 : // bitmaps. Use the bitmap's original ID for de-duping.
121 0 : imageSubset.setID(bitmap.getGenerationID());
122 0 : return imageSubset;
123 : }
124 :
125 0 : SkPDFDevice::GraphicStateEntry::GraphicStateEntry()
126 : : fColor(SK_ColorBLACK)
127 : , fTextScaleX(SK_Scalar1)
128 : , fTextFill(SkPaint::kFill_Style)
129 : , fShaderIndex(-1)
130 0 : , fGraphicStateIndex(-1) {
131 0 : fMatrix.reset();
132 0 : }
133 :
134 0 : bool SkPDFDevice::GraphicStateEntry::compareInitialState(
135 : const GraphicStateEntry& cur) {
136 0 : return fColor == cur.fColor &&
137 0 : fShaderIndex == cur.fShaderIndex &&
138 0 : fGraphicStateIndex == cur.fGraphicStateIndex &&
139 0 : fMatrix == cur.fMatrix &&
140 0 : fClipStack == cur.fClipStack &&
141 0 : (fTextScaleX == 0 ||
142 0 : (fTextScaleX == cur.fTextScaleX && fTextFill == cur.fTextFill));
143 : }
144 :
145 0 : class GraphicStackState {
146 : public:
147 0 : GraphicStackState(const SkClipStack& existingClipStack,
148 : SkWStream* contentStream)
149 0 : : fStackDepth(0),
150 0 : fContentStream(contentStream) {
151 0 : fEntries[0].fClipStack = existingClipStack;
152 0 : }
153 :
154 : void updateClip(const SkClipStack& clipStack,
155 : const SkPoint& translation, const SkRect& bounds);
156 : void updateMatrix(const SkMatrix& matrix);
157 : void updateDrawingState(const SkPDFDevice::GraphicStateEntry& state);
158 :
159 : void drainStack();
160 :
161 : private:
162 : void push();
163 : void pop();
164 0 : SkPDFDevice::GraphicStateEntry* currentEntry() { return &fEntries[fStackDepth]; }
165 :
166 : // Conservative limit on save depth, see impl. notes in PDF 1.4 spec.
167 : static const int kMaxStackDepth = 12;
168 : SkPDFDevice::GraphicStateEntry fEntries[kMaxStackDepth + 1];
169 : int fStackDepth;
170 : SkWStream* fContentStream;
171 : };
172 :
173 0 : void GraphicStackState::drainStack() {
174 0 : while (fStackDepth) {
175 0 : pop();
176 : }
177 0 : }
178 :
179 0 : void GraphicStackState::push() {
180 0 : SkASSERT(fStackDepth < kMaxStackDepth);
181 0 : fContentStream->writeText("q\n");
182 0 : fStackDepth++;
183 0 : fEntries[fStackDepth] = fEntries[fStackDepth - 1];
184 0 : }
185 :
186 0 : void GraphicStackState::pop() {
187 0 : SkASSERT(fStackDepth > 0);
188 0 : fContentStream->writeText("Q\n");
189 0 : fStackDepth--;
190 0 : }
191 :
192 : /* Calculate an inverted path's equivalent non-inverted path, given the
193 : * canvas bounds.
194 : * outPath may alias with invPath (since this is supported by PathOps).
195 : */
196 0 : static bool calculate_inverse_path(const SkRect& bounds, const SkPath& invPath,
197 : SkPath* outPath) {
198 0 : SkASSERT(invPath.isInverseFillType());
199 :
200 0 : SkPath clipPath;
201 0 : clipPath.addRect(bounds);
202 :
203 0 : return Op(clipPath, invPath, kIntersect_SkPathOp, outPath);
204 : }
205 :
206 0 : bool apply_clip(SkClipOp op, const SkPath& u, const SkPath& v, SkPath* r) {
207 0 : switch (op) {
208 : case SkClipOp::kDifference:
209 0 : return Op(u, v, kDifference_SkPathOp, r);
210 : case SkClipOp::kIntersect:
211 0 : return Op(u, v, kIntersect_SkPathOp, r);
212 : case SkClipOp::kUnion_deprecated:
213 0 : return Op(u, v, kUnion_SkPathOp, r);
214 : case SkClipOp::kXOR_deprecated:
215 0 : return Op(u, v, kXOR_SkPathOp, r);
216 : case SkClipOp::kReverseDifference_deprecated:
217 0 : return Op(u, v, kReverseDifference_SkPathOp, r);
218 : case SkClipOp::kReplace_deprecated:
219 0 : *r = v;
220 0 : return true;
221 : default:
222 0 : return false;
223 : }
224 : }
225 :
226 : /* Uses Path Ops to calculate a vector SkPath clip from a clip stack.
227 : * Returns true if successful, or false if not successful.
228 : * If successful, the resulting clip is stored in outClipPath.
229 : * If not successful, outClipPath is undefined, and a fallback method
230 : * should be used.
231 : */
232 0 : static bool get_clip_stack_path(const SkMatrix& transform,
233 : const SkClipStack& clipStack,
234 : const SkRect& bounds,
235 : SkPath* outClipPath) {
236 0 : outClipPath->reset();
237 0 : outClipPath->setFillType(SkPath::kInverseWinding_FillType);
238 :
239 : const SkClipStack::Element* clipEntry;
240 0 : SkClipStack::Iter iter;
241 0 : iter.reset(clipStack, SkClipStack::Iter::kBottom_IterStart);
242 0 : for (clipEntry = iter.next(); clipEntry; clipEntry = iter.next()) {
243 0 : SkPath entryPath;
244 0 : if (SkClipStack::Element::kEmpty_Type == clipEntry->getType()) {
245 0 : outClipPath->reset();
246 0 : outClipPath->setFillType(SkPath::kInverseWinding_FillType);
247 0 : continue;
248 : } else {
249 0 : clipEntry->asPath(&entryPath);
250 : }
251 0 : entryPath.transform(transform);
252 0 : if (!apply_clip(clipEntry->getOp(), *outClipPath, entryPath, outClipPath)) {
253 0 : return false;
254 : }
255 : }
256 :
257 0 : if (outClipPath->isInverseFillType()) {
258 : // The bounds are slightly outset to ensure this is correct in the
259 : // face of floating-point accuracy and possible SkRegion bitmap
260 : // approximations.
261 0 : SkRect clipBounds = bounds;
262 0 : clipBounds.outset(SK_Scalar1, SK_Scalar1);
263 0 : if (!calculate_inverse_path(clipBounds, *outClipPath, outClipPath)) {
264 0 : return false;
265 : }
266 : }
267 0 : return true;
268 : }
269 :
270 : // TODO(vandebo): Take advantage of SkClipStack::getSaveCount(), the PDF
271 : // graphic state stack, and the fact that we can know all the clips used
272 : // on the page to optimize this.
273 0 : void GraphicStackState::updateClip(const SkClipStack& clipStack,
274 : const SkPoint& translation,
275 : const SkRect& bounds) {
276 0 : if (clipStack == currentEntry()->fClipStack) {
277 0 : return;
278 : }
279 :
280 0 : while (fStackDepth > 0) {
281 0 : pop();
282 0 : if (clipStack == currentEntry()->fClipStack) {
283 0 : return;
284 : }
285 : }
286 0 : push();
287 :
288 0 : currentEntry()->fClipStack = clipStack;
289 :
290 : SkMatrix transform;
291 0 : transform.setTranslate(translation.fX, translation.fY);
292 :
293 0 : SkPath clipPath;
294 0 : if (get_clip_stack_path(transform, clipStack, bounds, &clipPath)) {
295 0 : SkPDFUtils::EmitPath(clipPath, SkPaint::kFill_Style, fContentStream);
296 0 : SkPath::FillType clipFill = clipPath.getFillType();
297 : NOT_IMPLEMENTED(clipFill == SkPath::kInverseEvenOdd_FillType, false);
298 : NOT_IMPLEMENTED(clipFill == SkPath::kInverseWinding_FillType, false);
299 0 : if (clipFill == SkPath::kEvenOdd_FillType) {
300 0 : fContentStream->writeText("W* n\n");
301 : } else {
302 0 : fContentStream->writeText("W n\n");
303 : }
304 : }
305 : // If Op() fails (pathological case; e.g. input values are
306 : // extremely large or NaN), emit no clip at all.
307 : }
308 :
309 0 : void GraphicStackState::updateMatrix(const SkMatrix& matrix) {
310 0 : if (matrix == currentEntry()->fMatrix) {
311 0 : return;
312 : }
313 :
314 0 : if (currentEntry()->fMatrix.getType() != SkMatrix::kIdentity_Mask) {
315 0 : SkASSERT(fStackDepth > 0);
316 0 : SkASSERT(fEntries[fStackDepth].fClipStack ==
317 : fEntries[fStackDepth -1].fClipStack);
318 0 : pop();
319 :
320 0 : SkASSERT(currentEntry()->fMatrix.getType() == SkMatrix::kIdentity_Mask);
321 : }
322 0 : if (matrix.getType() == SkMatrix::kIdentity_Mask) {
323 0 : return;
324 : }
325 :
326 0 : push();
327 0 : SkPDFUtils::AppendTransform(matrix, fContentStream);
328 0 : currentEntry()->fMatrix = matrix;
329 : }
330 :
331 0 : void GraphicStackState::updateDrawingState(const SkPDFDevice::GraphicStateEntry& state) {
332 : // PDF treats a shader as a color, so we only set one or the other.
333 0 : if (state.fShaderIndex >= 0) {
334 0 : if (state.fShaderIndex != currentEntry()->fShaderIndex) {
335 0 : SkPDFUtils::ApplyPattern(state.fShaderIndex, fContentStream);
336 0 : currentEntry()->fShaderIndex = state.fShaderIndex;
337 : }
338 : } else {
339 0 : if (state.fColor != currentEntry()->fColor ||
340 0 : currentEntry()->fShaderIndex >= 0) {
341 0 : emit_pdf_color(state.fColor, fContentStream);
342 0 : fContentStream->writeText("RG ");
343 0 : emit_pdf_color(state.fColor, fContentStream);
344 0 : fContentStream->writeText("rg\n");
345 0 : currentEntry()->fColor = state.fColor;
346 0 : currentEntry()->fShaderIndex = -1;
347 : }
348 : }
349 :
350 0 : if (state.fGraphicStateIndex != currentEntry()->fGraphicStateIndex) {
351 0 : SkPDFUtils::ApplyGraphicState(state.fGraphicStateIndex, fContentStream);
352 0 : currentEntry()->fGraphicStateIndex = state.fGraphicStateIndex;
353 : }
354 :
355 0 : if (state.fTextScaleX) {
356 0 : if (state.fTextScaleX != currentEntry()->fTextScaleX) {
357 0 : SkScalar pdfScale = state.fTextScaleX * 1000;
358 0 : SkPDFUtils::AppendScalar(pdfScale, fContentStream);
359 0 : fContentStream->writeText(" Tz\n");
360 0 : currentEntry()->fTextScaleX = state.fTextScaleX;
361 : }
362 0 : if (state.fTextFill != currentEntry()->fTextFill) {
363 : static_assert(SkPaint::kFill_Style == 0, "enum_must_match_value");
364 : static_assert(SkPaint::kStroke_Style == 1, "enum_must_match_value");
365 : static_assert(SkPaint::kStrokeAndFill_Style == 2, "enum_must_match_value");
366 0 : fContentStream->writeDecAsText(state.fTextFill);
367 0 : fContentStream->writeText(" Tr\n");
368 0 : currentEntry()->fTextFill = state.fTextFill;
369 : }
370 : }
371 0 : }
372 :
373 0 : static bool not_supported_for_layers(const SkPaint& layerPaint) {
374 : // PDF does not support image filters, so render them on CPU.
375 : // Note that this rendering is done at "screen" resolution (100dpi), not
376 : // printer resolution.
377 : // TODO: It may be possible to express some filters natively using PDF
378 : // to improve quality and file size (https://bug.skia.org/3043)
379 :
380 : // TODO: should we return true if there is a colorfilter?
381 0 : return layerPaint.getImageFilter() != nullptr;
382 : }
383 :
384 0 : SkBaseDevice* SkPDFDevice::onCreateDevice(const CreateInfo& cinfo, const SkPaint* layerPaint) {
385 0 : if (layerPaint && not_supported_for_layers(*layerPaint)) {
386 : // need to return a raster device, which we will detect in drawDevice()
387 0 : return SkBitmapDevice::Create(cinfo.fInfo, SkSurfaceProps(0, kUnknown_SkPixelGeometry));
388 : }
389 0 : SkISize size = SkISize::Make(cinfo.fInfo.width(), cinfo.fInfo.height());
390 0 : return SkPDFDevice::Create(size, fRasterDpi, fDocument);
391 : }
392 :
393 0 : SkPDFCanon* SkPDFDevice::getCanon() const { return fDocument->canon(); }
394 :
395 :
396 :
397 : // A helper class to automatically finish a ContentEntry at the end of a
398 : // drawing method and maintain the state needed between set up and finish.
399 : class ScopedContentEntry {
400 : public:
401 0 : ScopedContentEntry(SkPDFDevice* device,
402 : const SkClipStack& clipStack,
403 : const SkMatrix& matrix,
404 : const SkPaint& paint,
405 : bool hasText = false)
406 0 : : fDevice(device)
407 : , fContentEntry(nullptr)
408 : , fBlendMode(SkBlendMode::kSrcOver)
409 0 : , fDstFormXObject(nullptr)
410 : {
411 0 : if (matrix.hasPerspective()) {
412 0 : NOT_IMPLEMENTED(!matrix.hasPerspective(), false);
413 0 : return;
414 : }
415 0 : fBlendMode = paint.getBlendMode();
416 0 : fContentEntry =
417 0 : fDevice->setUpContentEntry(clipStack, matrix, paint, hasText, &fDstFormXObject);
418 : }
419 0 : ScopedContentEntry(SkPDFDevice* dev, const SkPaint& paint, bool hasText = false)
420 0 : : ScopedContentEntry(dev, dev->cs(), dev->ctm(), paint, hasText) {}
421 :
422 0 : ~ScopedContentEntry() {
423 0 : if (fContentEntry) {
424 0 : SkPath* shape = &fShape;
425 0 : if (shape->isEmpty()) {
426 0 : shape = nullptr;
427 : }
428 0 : fDevice->finishContentEntry(fBlendMode, std::move(fDstFormXObject), shape);
429 : }
430 0 : }
431 :
432 0 : SkPDFDevice::ContentEntry* entry() { return fContentEntry; }
433 :
434 : /* Returns true when we explicitly need the shape of the drawing. */
435 0 : bool needShape() {
436 0 : switch (fBlendMode) {
437 : case SkBlendMode::kClear:
438 : case SkBlendMode::kSrc:
439 : case SkBlendMode::kSrcIn:
440 : case SkBlendMode::kSrcOut:
441 : case SkBlendMode::kDstIn:
442 : case SkBlendMode::kDstOut:
443 : case SkBlendMode::kSrcATop:
444 : case SkBlendMode::kDstATop:
445 : case SkBlendMode::kModulate:
446 0 : return true;
447 : default:
448 0 : return false;
449 : }
450 : }
451 :
452 : /* Returns true unless we only need the shape of the drawing. */
453 0 : bool needSource() {
454 0 : if (fBlendMode == SkBlendMode::kClear) {
455 0 : return false;
456 : }
457 0 : return true;
458 : }
459 :
460 : /* If the shape is different than the alpha component of the content, then
461 : * setShape should be called with the shape. In particular, images and
462 : * devices have rectangular shape.
463 : */
464 0 : void setShape(const SkPath& shape) {
465 0 : fShape = shape;
466 0 : }
467 :
468 : private:
469 : SkPDFDevice* fDevice;
470 : SkPDFDevice::ContentEntry* fContentEntry;
471 : SkBlendMode fBlendMode;
472 : sk_sp<SkPDFObject> fDstFormXObject;
473 : SkPath fShape;
474 : };
475 :
476 : ////////////////////////////////////////////////////////////////////////////////
477 :
478 0 : SkPDFDevice::SkPDFDevice(SkISize pageSize, SkScalar rasterDpi, SkPDFDocument* doc, bool flip)
479 0 : : INHERITED(SkImageInfo::MakeUnknown(pageSize.width(), pageSize.height()),
480 0 : SkSurfaceProps(0, kUnknown_SkPixelGeometry))
481 : , fPageSize(pageSize)
482 : , fRasterDpi(rasterDpi)
483 0 : , fDocument(doc) {
484 0 : SkASSERT(pageSize.width() > 0);
485 0 : SkASSERT(pageSize.height() > 0);
486 :
487 0 : if (flip) {
488 : // Skia generally uses the top left as the origin but PDF
489 : // natively has the origin at the bottom left. This matrix
490 : // corrects for that. But that only needs to be done once, we
491 : // don't do it when layering.
492 0 : fInitialTransform.setTranslate(0, SkIntToScalar(pageSize.fHeight));
493 0 : fInitialTransform.preScale(SK_Scalar1, -SK_Scalar1);
494 : } else {
495 0 : fInitialTransform.setIdentity();
496 : }
497 0 : }
498 :
499 0 : SkPDFDevice::~SkPDFDevice() {
500 0 : this->cleanUp();
501 0 : }
502 :
503 0 : void SkPDFDevice::init() {
504 0 : fContentEntries.reset();
505 0 : }
506 :
507 0 : void SkPDFDevice::cleanUp() {
508 0 : fGraphicStateResources.unrefAll();
509 0 : fXObjectResources.unrefAll();
510 0 : fFontResources.unrefAll();
511 0 : fShaderResources.unrefAll();
512 0 : }
513 :
514 0 : void SkPDFDevice::drawAnnotation(const SkRect& rect, const char key[], SkData* value) {
515 0 : if (!value) {
516 0 : return;
517 : }
518 0 : if (rect.isEmpty()) {
519 0 : if (!strcmp(SkAnnotationKeys::Define_Named_Dest_Key(), key)) {
520 : SkPoint transformedPoint;
521 0 : this->ctm().mapXY(rect.x(), rect.y(), &transformedPoint);
522 0 : fNamedDestinations.emplace_back(value, transformedPoint);
523 : }
524 0 : return;
525 : }
526 : // Convert to path to handle non-90-degree rotations.
527 0 : SkPath path;
528 0 : path.addRect(rect);
529 0 : path.transform(this->ctm(), &path);
530 0 : SkPath clip;
531 0 : (void)this->cs().asPath(&clip);
532 0 : Op(clip, path, kIntersect_SkPathOp, &path);
533 : // PDF wants a rectangle only.
534 0 : SkRect transformedRect = path.getBounds();
535 0 : if (transformedRect.isEmpty()) {
536 0 : return;
537 : }
538 0 : if (!strcmp(SkAnnotationKeys::URL_Key(), key)) {
539 0 : fLinkToURLs.emplace_back(transformedRect, value);
540 0 : } else if (!strcmp(SkAnnotationKeys::Link_Named_Dest_Key(), key)) {
541 0 : fLinkToDestinations.emplace_back(transformedRect, value);
542 : }
543 : }
544 :
545 0 : void SkPDFDevice::drawPaint(const SkPaint& paint) {
546 0 : SkPaint newPaint = paint;
547 0 : replace_srcmode_on_opaque_paint(&newPaint);
548 :
549 0 : newPaint.setStyle(SkPaint::kFill_Style);
550 0 : ScopedContentEntry content(this, newPaint);
551 0 : this->internalDrawPaint(newPaint, content.entry());
552 0 : }
553 :
554 0 : void SkPDFDevice::internalDrawPaint(const SkPaint& paint,
555 : SkPDFDevice::ContentEntry* contentEntry) {
556 0 : if (!contentEntry) {
557 0 : return;
558 : }
559 0 : SkRect bbox = SkRect::MakeWH(SkIntToScalar(this->width()),
560 0 : SkIntToScalar(this->height()));
561 : SkMatrix inverse;
562 0 : if (!contentEntry->fState.fMatrix.invert(&inverse)) {
563 0 : return;
564 : }
565 0 : inverse.mapRect(&bbox);
566 :
567 0 : SkPDFUtils::AppendRectangle(bbox, &contentEntry->fContent);
568 0 : SkPDFUtils::PaintPath(paint.getStyle(), SkPath::kWinding_FillType,
569 0 : &contentEntry->fContent);
570 : }
571 :
572 0 : void SkPDFDevice::drawPoints(SkCanvas::PointMode mode,
573 : size_t count,
574 : const SkPoint* points,
575 : const SkPaint& srcPaint) {
576 0 : SkPaint passedPaint = srcPaint;
577 0 : replace_srcmode_on_opaque_paint(&passedPaint);
578 :
579 0 : if (count == 0) {
580 0 : return;
581 : }
582 :
583 : // SkDraw::drawPoints converts to multiple calls to fDevice->drawPath.
584 : // We only use this when there's a path effect because of the overhead
585 : // of multiple calls to setUpContentEntry it causes.
586 0 : if (passedPaint.getPathEffect()) {
587 0 : if (this->cs().isEmpty(size(*this))) {
588 0 : return;
589 : }
590 0 : draw_points(mode, count, points, passedPaint,
591 0 : this->devClipBounds(), this->ctm(), this);
592 0 : return;
593 : }
594 :
595 0 : const SkPaint* paint = &passedPaint;
596 0 : SkPaint modifiedPaint;
597 :
598 0 : if (mode == SkCanvas::kPoints_PointMode &&
599 0 : paint->getStrokeCap() != SkPaint::kRound_Cap) {
600 0 : modifiedPaint = *paint;
601 0 : paint = &modifiedPaint;
602 0 : if (paint->getStrokeWidth()) {
603 : // PDF won't draw a single point with square/butt caps because the
604 : // orientation is ambiguous. Draw a rectangle instead.
605 0 : modifiedPaint.setStyle(SkPaint::kFill_Style);
606 0 : SkScalar strokeWidth = paint->getStrokeWidth();
607 0 : SkScalar halfStroke = SkScalarHalf(strokeWidth);
608 0 : for (size_t i = 0; i < count; i++) {
609 0 : SkRect r = SkRect::MakeXYWH(points[i].fX, points[i].fY, 0, 0);
610 0 : r.inset(-halfStroke, -halfStroke);
611 0 : this->drawRect(r, modifiedPaint);
612 : }
613 0 : return;
614 : } else {
615 0 : modifiedPaint.setStrokeCap(SkPaint::kRound_Cap);
616 : }
617 : }
618 :
619 0 : ScopedContentEntry content(this, *paint);
620 0 : if (!content.entry()) {
621 0 : return;
622 : }
623 :
624 0 : switch (mode) {
625 : case SkCanvas::kPolygon_PointMode:
626 0 : SkPDFUtils::MoveTo(points[0].fX, points[0].fY,
627 0 : &content.entry()->fContent);
628 0 : for (size_t i = 1; i < count; i++) {
629 0 : SkPDFUtils::AppendLine(points[i].fX, points[i].fY,
630 0 : &content.entry()->fContent);
631 : }
632 0 : SkPDFUtils::StrokePath(&content.entry()->fContent);
633 0 : break;
634 : case SkCanvas::kLines_PointMode:
635 0 : for (size_t i = 0; i < count/2; i++) {
636 0 : SkPDFUtils::MoveTo(points[i * 2].fX, points[i * 2].fY,
637 0 : &content.entry()->fContent);
638 0 : SkPDFUtils::AppendLine(points[i * 2 + 1].fX,
639 0 : points[i * 2 + 1].fY,
640 0 : &content.entry()->fContent);
641 0 : SkPDFUtils::StrokePath(&content.entry()->fContent);
642 : }
643 0 : break;
644 : case SkCanvas::kPoints_PointMode:
645 0 : SkASSERT(paint->getStrokeCap() == SkPaint::kRound_Cap);
646 0 : for (size_t i = 0; i < count; i++) {
647 0 : SkPDFUtils::MoveTo(points[i].fX, points[i].fY,
648 0 : &content.entry()->fContent);
649 0 : SkPDFUtils::ClosePath(&content.entry()->fContent);
650 0 : SkPDFUtils::StrokePath(&content.entry()->fContent);
651 : }
652 0 : break;
653 : default:
654 0 : SkASSERT(false);
655 : }
656 : }
657 :
658 0 : static sk_sp<SkPDFDict> create_link_annotation(const SkRect& translatedRect) {
659 0 : auto annotation = sk_make_sp<SkPDFDict>("Annot");
660 0 : annotation->insertName("Subtype", "Link");
661 0 : annotation->insertInt("F", 4); // required by ISO 19005
662 :
663 0 : auto border = sk_make_sp<SkPDFArray>();
664 0 : border->reserve(3);
665 0 : border->appendInt(0); // Horizontal corner radius.
666 0 : border->appendInt(0); // Vertical corner radius.
667 0 : border->appendInt(0); // Width, 0 = no border.
668 0 : annotation->insertObject("Border", std::move(border));
669 :
670 0 : auto rect = sk_make_sp<SkPDFArray>();
671 0 : rect->reserve(4);
672 0 : rect->appendScalar(translatedRect.fLeft);
673 0 : rect->appendScalar(translatedRect.fTop);
674 0 : rect->appendScalar(translatedRect.fRight);
675 0 : rect->appendScalar(translatedRect.fBottom);
676 0 : annotation->insertObject("Rect", std::move(rect));
677 :
678 0 : return annotation;
679 : }
680 :
681 0 : static sk_sp<SkPDFDict> create_link_to_url(const SkData* urlData, const SkRect& r) {
682 0 : sk_sp<SkPDFDict> annotation = create_link_annotation(r);
683 0 : SkString url(static_cast<const char *>(urlData->data()),
684 0 : urlData->size() - 1);
685 0 : auto action = sk_make_sp<SkPDFDict>("Action");
686 0 : action->insertName("S", "URI");
687 0 : action->insertString("URI", url);
688 0 : annotation->insertObject("A", std::move(action));
689 0 : return annotation;
690 : }
691 :
692 0 : static sk_sp<SkPDFDict> create_link_named_dest(const SkData* nameData,
693 : const SkRect& r) {
694 0 : sk_sp<SkPDFDict> annotation = create_link_annotation(r);
695 0 : SkString name(static_cast<const char *>(nameData->data()),
696 0 : nameData->size() - 1);
697 0 : annotation->insertName("Dest", name);
698 0 : return annotation;
699 : }
700 :
701 0 : void SkPDFDevice::drawRect(const SkRect& rect,
702 : const SkPaint& srcPaint) {
703 0 : SkPaint paint = srcPaint;
704 0 : replace_srcmode_on_opaque_paint(&paint);
705 0 : SkRect r = rect;
706 0 : r.sort();
707 :
708 0 : if (paint.getPathEffect()) {
709 0 : if (this->cs().isEmpty(size(*this))) {
710 0 : return;
711 : }
712 0 : SkPath path;
713 0 : path.addRect(r);
714 0 : this->drawPath(path, paint, nullptr, true);
715 0 : return;
716 : }
717 :
718 0 : ScopedContentEntry content(this, paint);
719 0 : if (!content.entry()) {
720 0 : return;
721 : }
722 0 : SkPDFUtils::AppendRectangle(r, &content.entry()->fContent);
723 0 : SkPDFUtils::PaintPath(paint.getStyle(), SkPath::kWinding_FillType,
724 0 : &content.entry()->fContent);
725 : }
726 :
727 0 : void SkPDFDevice::drawRRect(const SkRRect& rrect,
728 : const SkPaint& srcPaint) {
729 0 : SkPaint paint = srcPaint;
730 0 : replace_srcmode_on_opaque_paint(&paint);
731 0 : SkPath path;
732 0 : path.addRRect(rrect);
733 0 : this->drawPath(path, paint, nullptr, true);
734 0 : }
735 :
736 0 : void SkPDFDevice::drawOval(const SkRect& oval,
737 : const SkPaint& srcPaint) {
738 0 : SkPaint paint = srcPaint;
739 0 : replace_srcmode_on_opaque_paint(&paint);
740 0 : SkPath path;
741 0 : path.addOval(oval);
742 0 : this->drawPath(path, paint, nullptr, true);
743 0 : }
744 :
745 0 : void SkPDFDevice::drawPath(const SkPath& origPath,
746 : const SkPaint& srcPaint,
747 : const SkMatrix* prePathMatrix,
748 : bool pathIsMutable) {
749 0 : this->internalDrawPath(
750 0 : this->cs(), this->ctm(), origPath, srcPaint, prePathMatrix, pathIsMutable);
751 0 : }
752 :
753 0 : void SkPDFDevice::internalDrawPath(const SkClipStack& clipStack,
754 : const SkMatrix& ctm,
755 : const SkPath& origPath,
756 : const SkPaint& srcPaint,
757 : const SkMatrix* prePathMatrix,
758 : bool pathIsMutable) {
759 0 : SkPaint paint = srcPaint;
760 0 : replace_srcmode_on_opaque_paint(&paint);
761 0 : SkPath modifiedPath;
762 0 : SkPath* pathPtr = const_cast<SkPath*>(&origPath);
763 :
764 0 : SkMatrix matrix = ctm;
765 0 : if (prePathMatrix) {
766 0 : if (paint.getPathEffect() || paint.getStyle() != SkPaint::kFill_Style) {
767 0 : if (!pathIsMutable) {
768 0 : pathPtr = &modifiedPath;
769 0 : pathIsMutable = true;
770 : }
771 0 : origPath.transform(*prePathMatrix, pathPtr);
772 : } else {
773 0 : matrix.preConcat(*prePathMatrix);
774 : }
775 : }
776 :
777 0 : if (paint.getPathEffect()) {
778 0 : if (clipStack.isEmpty(size(*this))) {
779 0 : return;
780 : }
781 0 : if (!pathIsMutable) {
782 0 : pathPtr = &modifiedPath;
783 0 : pathIsMutable = true;
784 : }
785 0 : bool fill = paint.getFillPath(origPath, pathPtr);
786 :
787 0 : SkPaint noEffectPaint(paint);
788 0 : noEffectPaint.setPathEffect(nullptr);
789 0 : if (fill) {
790 0 : noEffectPaint.setStyle(SkPaint::kFill_Style);
791 : } else {
792 0 : noEffectPaint.setStyle(SkPaint::kStroke_Style);
793 0 : noEffectPaint.setStrokeWidth(0);
794 : }
795 0 : this->internalDrawPath(clipStack, ctm, *pathPtr, noEffectPaint, nullptr, true);
796 0 : return;
797 : }
798 :
799 0 : if (this->handleInversePath(origPath, paint, pathIsMutable, prePathMatrix)) {
800 0 : return;
801 : }
802 :
803 0 : ScopedContentEntry content(this, clipStack, matrix, paint);
804 0 : if (!content.entry()) {
805 0 : return;
806 : }
807 0 : SkScalar matrixScale = matrix.mapRadius(1.0f);
808 0 : SkScalar tolerance = matrixScale > 0.0f ? 0.25f / matrixScale : 0.25f;
809 : bool consumeDegeratePathSegments =
810 0 : paint.getStyle() == SkPaint::kFill_Style ||
811 0 : (paint.getStrokeCap() != SkPaint::kRound_Cap &&
812 0 : paint.getStrokeCap() != SkPaint::kSquare_Cap);
813 0 : SkPDFUtils::EmitPath(*pathPtr, paint.getStyle(),
814 : consumeDegeratePathSegments,
815 0 : &content.entry()->fContent,
816 0 : tolerance);
817 0 : SkPDFUtils::PaintPath(paint.getStyle(), pathPtr->getFillType(),
818 0 : &content.entry()->fContent);
819 : }
820 :
821 :
822 0 : void SkPDFDevice::drawImageRect(const SkImage* image,
823 : const SkRect* src,
824 : const SkRect& dst,
825 : const SkPaint& srcPaint,
826 : SkCanvas::SrcRectConstraint) {
827 0 : if (!image) {
828 0 : return;
829 : }
830 0 : SkIRect bounds = image->bounds();
831 0 : SkPaint paint = srcPaint;
832 0 : if (image->isOpaque()) {
833 0 : replace_srcmode_on_opaque_paint(&paint);
834 : }
835 0 : SkRect srcRect = src ? *src : SkRect::Make(bounds);
836 : SkMatrix transform;
837 0 : transform.setRectToRect(srcRect, dst, SkMatrix::kFill_ScaleToFit);
838 0 : if (src) {
839 0 : if (!srcRect.intersect(SkRect::Make(bounds))) {
840 0 : return;
841 : }
842 0 : srcRect.roundOut(&bounds);
843 0 : transform.preTranslate(SkIntToScalar(bounds.x()),
844 0 : SkIntToScalar(bounds.y()));
845 : }
846 0 : SkImageSubset imageSubset(sk_ref_sp(const_cast<SkImage*>(image)), bounds);
847 0 : if (!imageSubset.isValid()) {
848 0 : return;
849 : }
850 0 : transform.postConcat(this->ctm());
851 0 : this->internalDrawImage(transform, this->cs(), std::move(imageSubset), paint);
852 : }
853 :
854 0 : void SkPDFDevice::drawBitmapRect(const SkBitmap& bitmap,
855 : const SkRect* src,
856 : const SkRect& dst,
857 : const SkPaint& srcPaint,
858 : SkCanvas::SrcRectConstraint) {
859 0 : if (bitmap.drawsNothing()) {
860 0 : return;
861 : }
862 0 : SkIRect bounds = bitmap.bounds();
863 0 : SkPaint paint = srcPaint;
864 0 : if (bitmap.isOpaque()) {
865 0 : replace_srcmode_on_opaque_paint(&paint);
866 : }
867 0 : SkRect srcRect = src ? *src : SkRect::Make(bounds);
868 : SkMatrix transform;
869 0 : transform.setRectToRect(srcRect, dst, SkMatrix::kFill_ScaleToFit);
870 0 : if (src) {
871 0 : if (!srcRect.intersect(SkRect::Make(bounds))) {
872 0 : return;
873 : }
874 0 : srcRect.roundOut(&bounds);
875 0 : transform.preTranslate(SkIntToScalar(bounds.x()),
876 0 : SkIntToScalar(bounds.y()));
877 : }
878 0 : SkBitmap bitmapSubset;
879 0 : if (!bitmap.extractSubset(&bitmapSubset, bounds)) {
880 0 : return;
881 : }
882 0 : SkImageSubset imageSubset = make_image_subset(bitmapSubset);
883 0 : if (!imageSubset.isValid()) {
884 0 : return;
885 : }
886 0 : transform.postConcat(this->ctm());
887 0 : this->internalDrawImage(transform, this->cs(), std::move(imageSubset), paint);
888 : }
889 :
890 0 : void SkPDFDevice::drawBitmap(const SkBitmap& bitmap,
891 : const SkMatrix& matrix,
892 : const SkPaint& srcPaint) {
893 0 : if (bitmap.drawsNothing() || this->cs().isEmpty(size(*this))) {
894 0 : return;
895 : }
896 0 : SkPaint paint = srcPaint;
897 0 : if (bitmap.isOpaque()) {
898 0 : replace_srcmode_on_opaque_paint(&paint);
899 : }
900 0 : SkImageSubset imageSubset = make_image_subset(bitmap);
901 0 : if (!imageSubset.isValid()) {
902 0 : return;
903 : }
904 0 : SkMatrix transform = matrix;
905 0 : transform.postConcat(this->ctm());
906 0 : this->internalDrawImage(transform, this->cs(), std::move(imageSubset), paint);
907 : }
908 :
909 0 : void SkPDFDevice::drawSprite(const SkBitmap& bitmap,
910 : int x,
911 : int y,
912 : const SkPaint& srcPaint) {
913 0 : if (bitmap.drawsNothing() || this->cs().isEmpty(size(*this))) {
914 0 : return;
915 : }
916 0 : SkPaint paint = srcPaint;
917 0 : if (bitmap.isOpaque()) {
918 0 : replace_srcmode_on_opaque_paint(&paint);
919 : }
920 0 : SkImageSubset imageSubset = make_image_subset(bitmap);
921 0 : if (!imageSubset.isValid()) {
922 0 : return;
923 : }
924 0 : SkMatrix transform = SkMatrix::MakeTrans(SkIntToScalar(x), SkIntToScalar(y));
925 0 : this->internalDrawImage(transform, this->cs(), std::move(imageSubset), paint);
926 : }
927 :
928 0 : void SkPDFDevice::drawImage(const SkImage* image,
929 : SkScalar x,
930 : SkScalar y,
931 : const SkPaint& srcPaint) {
932 0 : SkPaint paint = srcPaint;
933 0 : if (!image) {
934 0 : return;
935 : }
936 0 : if (image->isOpaque()) {
937 0 : replace_srcmode_on_opaque_paint(&paint);
938 : }
939 0 : SkImageSubset imageSubset(sk_ref_sp(const_cast<SkImage*>(image)));
940 0 : if (!imageSubset.isValid()) {
941 0 : return;
942 : }
943 0 : SkMatrix transform = SkMatrix::MakeTrans(x, y);
944 0 : transform.postConcat(this->ctm());
945 0 : this->internalDrawImage(transform, this->cs(), std::move(imageSubset), paint);
946 : }
947 :
948 : namespace {
949 : class GlyphPositioner {
950 : public:
951 0 : GlyphPositioner(SkDynamicMemoryWStream* content,
952 : SkScalar textSkewX,
953 : bool wideChars,
954 : bool defaultPositioning,
955 : SkPoint origin)
956 0 : : fContent(content)
957 : , fCurrentMatrixOrigin(origin)
958 : , fTextSkewX(textSkewX)
959 : , fWideChars(wideChars)
960 0 : , fDefaultPositioning(defaultPositioning) {
961 0 : }
962 0 : ~GlyphPositioner() { this->flush(); }
963 0 : void flush() {
964 0 : if (fInText) {
965 0 : fContent->writeText("> Tj\n");
966 0 : fInText = false;
967 : }
968 0 : }
969 0 : void writeGlyph(SkPoint xy,
970 : SkScalar advanceWidth,
971 : uint16_t glyph) {
972 0 : if (!fInitialized) {
973 : // Flip the text about the x-axis to account for origin swap and include
974 : // the passed parameters.
975 0 : fContent->writeText("1 0 ");
976 0 : SkPDFUtils::AppendScalar(-fTextSkewX, fContent);
977 0 : fContent->writeText(" -1 ");
978 0 : SkPDFUtils::AppendScalar(fCurrentMatrixOrigin.x(), fContent);
979 0 : fContent->writeText(" ");
980 0 : SkPDFUtils::AppendScalar(fCurrentMatrixOrigin.y(), fContent);
981 0 : fContent->writeText(" Tm\n");
982 0 : fCurrentMatrixOrigin.set(0.0f, 0.0f);
983 0 : fInitialized = true;
984 : }
985 : #ifdef SK_BUILD_FOR_WIN
986 : const bool kAlwaysPosition = true;
987 : #else
988 0 : const bool kAlwaysPosition = false;
989 : #endif
990 0 : if (!fDefaultPositioning) {
991 0 : SkPoint position = xy - fCurrentMatrixOrigin;
992 0 : if (kAlwaysPosition || position != SkPoint{fXAdvance, 0}) {
993 0 : this->flush();
994 0 : SkPDFUtils::AppendScalar(position.x(), fContent);
995 0 : fContent->writeText(" ");
996 0 : SkPDFUtils::AppendScalar(-position.y(), fContent);
997 0 : fContent->writeText(" Td ");
998 0 : fCurrentMatrixOrigin = xy;
999 0 : fXAdvance = 0;
1000 : }
1001 0 : fXAdvance += advanceWidth;
1002 : }
1003 0 : if (!fInText) {
1004 0 : fContent->writeText("<");
1005 0 : fInText = true;
1006 : }
1007 0 : if (fWideChars) {
1008 0 : SkPDFUtils::WriteUInt16BE(fContent, glyph);
1009 : } else {
1010 0 : SkASSERT(0 == glyph >> 8);
1011 0 : SkPDFUtils::WriteUInt8(fContent, static_cast<uint8_t>(glyph));
1012 : }
1013 0 : }
1014 :
1015 : private:
1016 : SkDynamicMemoryWStream* fContent;
1017 : SkPoint fCurrentMatrixOrigin;
1018 : SkScalar fXAdvance = 0.0f;
1019 : SkScalar fTextSkewX;
1020 : bool fWideChars;
1021 : bool fInText = false;
1022 : bool fInitialized = false;
1023 : const bool fDefaultPositioning;
1024 : };
1025 :
1026 : /** Given the m-to-n glyph-to-character mapping data (as returned by
1027 : harfbuzz), iterate over the clusters. */
1028 : class Clusterator {
1029 : public:
1030 0 : Clusterator() : fClusters(nullptr), fUtf8Text(nullptr), fGlyphCount(0), fTextByteLength(0) {}
1031 0 : explicit Clusterator(uint32_t glyphCount)
1032 0 : : fClusters(nullptr)
1033 : , fUtf8Text(nullptr)
1034 : , fGlyphCount(glyphCount)
1035 0 : , fTextByteLength(0) {}
1036 : // The clusters[] array is an array of offsets into utf8Text[],
1037 : // one offset for each glyph. See SkTextBlobBuilder for more info.
1038 0 : Clusterator(const uint32_t* clusters,
1039 : const char* utf8Text,
1040 : uint32_t glyphCount,
1041 : uint32_t textByteLength)
1042 0 : : fClusters(clusters)
1043 : , fUtf8Text(utf8Text)
1044 : , fGlyphCount(glyphCount)
1045 0 : , fTextByteLength(textByteLength) {
1046 : // This is a cheap heuristic for /ReversedChars which seems to
1047 : // work for clusters produced by HarfBuzz, which either
1048 : // increase from zero (LTR) or decrease to zero (RTL).
1049 : // "ReversedChars" is how PDF deals with RTL text.
1050 0 : fReversedChars =
1051 0 : fUtf8Text && fClusters && fGlyphCount && fClusters[0] != 0;
1052 0 : }
1053 : struct Cluster {
1054 : const char* fUtf8Text;
1055 : uint32_t fTextByteLength;
1056 : uint32_t fGlyphIndex;
1057 : uint32_t fGlyphCount;
1058 0 : explicit operator bool() const { return fGlyphCount != 0; }
1059 : };
1060 : // True if this looks like right-to-left text.
1061 0 : bool reversedChars() const { return fReversedChars; }
1062 0 : Cluster next() {
1063 0 : if ((!fUtf8Text || !fClusters) && fGlyphCount) {
1064 : // These glyphs have no text. Treat as one "cluster".
1065 0 : uint32_t glyphCount = fGlyphCount;
1066 0 : fGlyphCount = 0;
1067 0 : return Cluster{nullptr, 0, 0, glyphCount};
1068 : }
1069 0 : if (fGlyphCount == 0 || fTextByteLength == 0) {
1070 0 : return Cluster{nullptr, 0, 0, 0}; // empty
1071 : }
1072 0 : SkASSERT(fUtf8Text);
1073 0 : SkASSERT(fClusters);
1074 0 : uint32_t cluster = fClusters[0];
1075 0 : if (cluster >= fTextByteLength) {
1076 0 : return Cluster{nullptr, 0, 0, 0}; // bad input.
1077 : }
1078 0 : uint32_t glyphsInCluster = 1;
1079 0 : while (glyphsInCluster < fGlyphCount &&
1080 0 : fClusters[glyphsInCluster] == cluster) {
1081 0 : ++glyphsInCluster;
1082 : }
1083 0 : SkASSERT(glyphsInCluster <= fGlyphCount);
1084 0 : uint32_t textLength = 0;
1085 0 : if (glyphsInCluster == fGlyphCount) {
1086 : // consumes rest of glyphs and rest of text
1087 0 : if (kInvalidCluster == fPreviousCluster) { // LTR text or single cluster
1088 0 : textLength = fTextByteLength - cluster;
1089 : } else { // RTL text; last cluster.
1090 0 : SkASSERT(fPreviousCluster < fTextByteLength);
1091 0 : if (fPreviousCluster <= cluster) { // bad input.
1092 0 : return Cluster{nullptr, 0, 0, 0};
1093 : }
1094 0 : textLength = fPreviousCluster - cluster;
1095 : }
1096 0 : fGlyphCount = 0;
1097 0 : return Cluster{fUtf8Text + cluster,
1098 : textLength,
1099 0 : fGlyphIndex,
1100 0 : glyphsInCluster};
1101 : }
1102 0 : SkASSERT(glyphsInCluster < fGlyphCount);
1103 0 : uint32_t nextCluster = fClusters[glyphsInCluster];
1104 0 : if (nextCluster >= fTextByteLength) {
1105 0 : return Cluster{nullptr, 0, 0, 0}; // bad input.
1106 : }
1107 0 : if (nextCluster > cluster) { // LTR text
1108 0 : if (kInvalidCluster != fPreviousCluster) {
1109 0 : return Cluster{nullptr, 0, 0, 0}; // bad input.
1110 : }
1111 0 : textLength = nextCluster - cluster;
1112 : } else { // RTL text
1113 0 : SkASSERT(nextCluster < cluster);
1114 0 : if (kInvalidCluster == fPreviousCluster) { // first cluster
1115 0 : textLength = fTextByteLength - cluster;
1116 : } else { // later cluster
1117 0 : if (fPreviousCluster <= cluster) {
1118 0 : return Cluster{nullptr, 0, 0, 0}; // bad input.
1119 : }
1120 0 : textLength = fPreviousCluster - cluster;
1121 : }
1122 0 : fPreviousCluster = cluster;
1123 : }
1124 0 : uint32_t glyphIndex = fGlyphIndex;
1125 0 : fGlyphCount -= glyphsInCluster;
1126 0 : fGlyphIndex += glyphsInCluster;
1127 0 : fClusters += glyphsInCluster;
1128 0 : return Cluster{fUtf8Text + cluster,
1129 : textLength,
1130 : glyphIndex,
1131 0 : glyphsInCluster};
1132 : }
1133 :
1134 : private:
1135 : static constexpr uint32_t kInvalidCluster = 0xFFFFFFFF;
1136 : const uint32_t* fClusters;
1137 : const char* fUtf8Text;
1138 : uint32_t fGlyphCount;
1139 : uint32_t fTextByteLength;
1140 : uint32_t fGlyphIndex = 0;
1141 : uint32_t fPreviousCluster = kInvalidCluster;
1142 : bool fReversedChars = false;
1143 : };
1144 :
1145 0 : struct TextStorage {
1146 : SkAutoTMalloc<char> fUtf8textStorage;
1147 : SkAutoTMalloc<uint32_t> fClusterStorage;
1148 : SkAutoTMalloc<SkGlyphID> fGlyphStorage;
1149 : };
1150 : } // namespace
1151 :
1152 : /** Given some unicode text (as passed to drawText(), convert to
1153 : glyphs (via primitive shaping), while preserving
1154 : glyph-to-character mapping information. */
1155 0 : static Clusterator make_clusterator(
1156 : const void* sourceText,
1157 : size_t sourceByteCount,
1158 : const SkPaint& paint,
1159 : TextStorage* storage,
1160 : int glyphCount) {
1161 0 : SkASSERT(SkPaint::kGlyphID_TextEncoding != paint.getTextEncoding());
1162 0 : SkASSERT(glyphCount == paint.textToGlyphs(sourceText, sourceByteCount, nullptr));
1163 0 : SkASSERT(glyphCount > 0);
1164 0 : storage->fGlyphStorage.reset(SkToSizeT(glyphCount));
1165 0 : (void)paint.textToGlyphs(sourceText, sourceByteCount, storage->fGlyphStorage.get());
1166 0 : storage->fClusterStorage.reset(SkToSizeT(glyphCount));
1167 0 : uint32_t* clusters = storage->fClusterStorage.get();
1168 0 : uint32_t utf8ByteCount = 0;
1169 0 : const char* utf8Text = nullptr;
1170 0 : switch (paint.getTextEncoding()) {
1171 : case SkPaint::kUTF8_TextEncoding: {
1172 0 : const char* txtPtr = (const char*)sourceText;
1173 0 : for (int i = 0; i < glyphCount; ++i) {
1174 0 : clusters[i] = SkToU32(txtPtr - (const char*)sourceText);
1175 0 : txtPtr += SkUTF8_LeadByteToCount(*(const unsigned char*)txtPtr);
1176 0 : SkASSERT(txtPtr <= (const char*)sourceText + sourceByteCount);
1177 : }
1178 0 : SkASSERT(txtPtr == (const char*)sourceText + sourceByteCount);
1179 0 : utf8ByteCount = SkToU32(sourceByteCount);
1180 0 : utf8Text = (const char*)sourceText;
1181 0 : break;
1182 : }
1183 : case SkPaint::kUTF16_TextEncoding: {
1184 0 : const uint16_t* utf16ptr = (const uint16_t*)sourceText;
1185 0 : int utf16count = SkToInt(sourceByteCount / sizeof(uint16_t));
1186 0 : utf8ByteCount = SkToU32(SkUTF16_ToUTF8(utf16ptr, utf16count));
1187 0 : storage->fUtf8textStorage.reset(utf8ByteCount);
1188 0 : char* txtPtr = storage->fUtf8textStorage.get();
1189 0 : utf8Text = txtPtr;
1190 0 : int clusterIndex = 0;
1191 0 : while (utf16ptr < (const uint16_t*)sourceText + utf16count) {
1192 0 : clusters[clusterIndex++] = SkToU32(txtPtr - utf8Text);
1193 0 : SkUnichar uni = SkUTF16_NextUnichar(&utf16ptr);
1194 0 : txtPtr += SkUTF8_FromUnichar(uni, txtPtr);
1195 : }
1196 0 : SkASSERT(clusterIndex == glyphCount);
1197 0 : SkASSERT(txtPtr == storage->fUtf8textStorage.get() + utf8ByteCount);
1198 0 : SkASSERT(utf16ptr == (const uint16_t*)sourceText + utf16count);
1199 0 : break;
1200 : }
1201 : case SkPaint::kUTF32_TextEncoding: {
1202 0 : const SkUnichar* utf32 = (const SkUnichar*)sourceText;
1203 0 : int utf32count = SkToInt(sourceByteCount / sizeof(SkUnichar));
1204 0 : SkASSERT(glyphCount == utf32count);
1205 0 : for (int i = 0; i < utf32count; ++i) {
1206 0 : utf8ByteCount += SkToU32(SkUTF8_FromUnichar(utf32[i]));
1207 : }
1208 0 : storage->fUtf8textStorage.reset(SkToSizeT(utf8ByteCount));
1209 0 : char* txtPtr = storage->fUtf8textStorage.get();
1210 0 : utf8Text = txtPtr;
1211 0 : for (int i = 0; i < utf32count; ++i) {
1212 0 : clusters[i] = SkToU32(txtPtr - utf8Text);
1213 0 : txtPtr += SkUTF8_FromUnichar(utf32[i], txtPtr);
1214 : }
1215 0 : break;
1216 : }
1217 : default:
1218 0 : SkDEBUGFAIL("");
1219 0 : break;
1220 : }
1221 0 : return Clusterator(clusters, utf8Text, SkToU32(glyphCount), utf8ByteCount);
1222 : }
1223 :
1224 0 : static SkUnichar map_glyph(const SkTDArray<SkUnichar>& glyphToUnicode, SkGlyphID glyph) {
1225 0 : return SkToInt(glyph) < glyphToUnicode.count() ? glyphToUnicode[SkToInt(glyph)] : -1;
1226 : }
1227 :
1228 0 : static void update_font(SkWStream* wStream, int fontIndex, SkScalar textSize) {
1229 0 : wStream->writeText("/");
1230 0 : char prefix = SkPDFResourceDict::GetResourceTypePrefix(SkPDFResourceDict::kFont_ResourceType);
1231 0 : wStream->write(&prefix, 1);
1232 0 : wStream->writeDecAsText(fontIndex);
1233 0 : wStream->writeText(" ");
1234 0 : SkPDFUtils::AppendScalar(textSize, wStream);
1235 0 : wStream->writeText(" Tf\n");
1236 0 : }
1237 :
1238 0 : void SkPDFDevice::internalDrawText(
1239 : const void* sourceText, size_t sourceByteCount,
1240 : const SkScalar pos[], SkTextBlob::GlyphPositioning positioning,
1241 : SkPoint offset, const SkPaint& srcPaint, const uint32_t* clusters,
1242 : uint32_t textByteLength, const char* utf8Text) {
1243 0 : NOT_IMPLEMENTED(srcPaint.getMaskFilter() != nullptr, false);
1244 0 : if (srcPaint.getMaskFilter() != nullptr) {
1245 : // Don't pretend we support drawing MaskFilters, it makes for artifacts
1246 : // making text unreadable (e.g. same text twice when using CSS shadows).
1247 0 : return;
1248 : }
1249 0 : NOT_IMPLEMENTED(srcPaint.isVerticalText(), false);
1250 0 : if (srcPaint.isVerticalText()) {
1251 : // Don't pretend we support drawing vertical text. It is not
1252 : // clear to me how to switch to "vertical writing" mode in PDF.
1253 : // Currently neither Chromium or Android set this flag.
1254 : // https://bug.skia.org/5665
1255 0 : return;
1256 : }
1257 0 : if (0 == sourceByteCount || !sourceText) {
1258 0 : return;
1259 : }
1260 0 : SkPaint paint = calculate_text_paint(srcPaint);
1261 0 : replace_srcmode_on_opaque_paint(&paint);
1262 0 : if (!paint.getTypeface()) {
1263 0 : paint.setTypeface(SkTypeface::MakeDefault());
1264 : }
1265 0 : SkTypeface* typeface = paint.getTypeface();
1266 0 : if (!typeface) {
1267 0 : SkDebugf("SkPDF: SkTypeface::MakeDefault() returned nullptr.\n");
1268 0 : return;
1269 : }
1270 :
1271 : const SkAdvancedTypefaceMetrics* metrics =
1272 0 : SkPDFFont::GetMetrics(typeface, fDocument->canon());
1273 0 : if (!metrics) {
1274 0 : return;
1275 : }
1276 0 : int glyphCount = paint.textToGlyphs(sourceText, sourceByteCount, nullptr);
1277 0 : if (glyphCount <= 0) {
1278 0 : return;
1279 : }
1280 :
1281 : // These three heap buffers are only used in the case where no glyphs
1282 : // are passed to drawText() (most clients pass glyphs or a textblob).
1283 0 : TextStorage storage;
1284 0 : const SkGlyphID* glyphs = nullptr;
1285 0 : Clusterator clusterator;
1286 0 : if (textByteLength > 0) {
1287 0 : SkASSERT(glyphCount == SkToInt(sourceByteCount / sizeof(SkGlyphID)));
1288 0 : glyphs = (const SkGlyphID*)sourceText;
1289 0 : clusterator = Clusterator(clusters, utf8Text, SkToU32(glyphCount), textByteLength);
1290 0 : SkASSERT(clusters);
1291 0 : SkASSERT(utf8Text);
1292 0 : SkASSERT(srcPaint.getTextEncoding() == SkPaint::kGlyphID_TextEncoding);
1293 0 : SkASSERT(glyphCount == paint.textToGlyphs(sourceText, sourceByteCount, nullptr));
1294 0 : } else if (SkPaint::kGlyphID_TextEncoding == srcPaint.getTextEncoding()) {
1295 0 : SkASSERT(glyphCount == SkToInt(sourceByteCount / sizeof(SkGlyphID)));
1296 0 : glyphs = (const SkGlyphID*)sourceText;
1297 0 : clusterator = Clusterator(SkToU32(glyphCount));
1298 0 : SkASSERT(glyphCount == paint.textToGlyphs(sourceText, sourceByteCount, nullptr));
1299 0 : SkASSERT(nullptr == clusters);
1300 0 : SkASSERT(nullptr == utf8Text);
1301 : } else {
1302 0 : SkASSERT(nullptr == clusters);
1303 0 : SkASSERT(nullptr == utf8Text);
1304 0 : clusterator = make_clusterator(sourceText, sourceByteCount, srcPaint,
1305 0 : &storage, glyphCount);
1306 0 : glyphs = storage.fGlyphStorage;
1307 : }
1308 0 : bool defaultPositioning = (positioning == SkTextBlob::kDefault_Positioning);
1309 0 : paint.setHinting(SkPaint::kNo_Hinting);
1310 :
1311 : int emSize;
1312 0 : SkAutoGlyphCache glyphCache = SkPDFFont::MakeVectorCache(typeface, &emSize);
1313 :
1314 0 : SkScalar textSize = paint.getTextSize();
1315 0 : SkScalar advanceScale = textSize * paint.getTextScaleX() / emSize;
1316 :
1317 0 : SkPaint::Align alignment = paint.getTextAlign();
1318 0 : float alignmentFactor = SkPaint::kLeft_Align == alignment ? 0.0f :
1319 0 : SkPaint::kCenter_Align == alignment ? -0.5f :
1320 0 : /* SkPaint::kRight_Align */ -1.0f;
1321 0 : if (defaultPositioning && alignment != SkPaint::kLeft_Align) {
1322 0 : SkScalar advance = 0;
1323 0 : for (int i = 0; i < glyphCount; ++i) {
1324 0 : advance += advanceScale * glyphCache->getGlyphIDAdvance(glyphs[i]).fAdvanceX;
1325 : }
1326 0 : offset.offset(alignmentFactor * advance, 0);
1327 : }
1328 0 : ScopedContentEntry content(this, paint, true);
1329 0 : if (!content.entry()) {
1330 0 : return;
1331 : }
1332 0 : SkDynamicMemoryWStream* out = &content.entry()->fContent;
1333 0 : const SkTDArray<SkUnichar>& glyphToUnicode = metrics->fGlyphToUnicode;
1334 :
1335 0 : out->writeText("BT\n");
1336 0 : SK_AT_SCOPE_EXIT(out->writeText("ET\n"));
1337 :
1338 0 : const SkGlyphID maxGlyphID = SkToU16(typeface->countGlyphs() - 1);
1339 :
1340 0 : bool multiByteGlyphs = SkPDFFont::IsMultiByte(SkPDFFont::FontType(*metrics));
1341 0 : if (clusterator.reversedChars()) {
1342 0 : out->writeText("/ReversedChars BMC\n");
1343 : }
1344 0 : SK_AT_SCOPE_EXIT(if (clusterator.reversedChars()) { out->writeText("EMC\n"); } );
1345 : GlyphPositioner glyphPositioner(out,
1346 : paint.getTextSkewX(),
1347 : multiByteGlyphs,
1348 : defaultPositioning,
1349 0 : offset);
1350 0 : SkPDFFont* font = nullptr;
1351 :
1352 0 : while (Clusterator::Cluster c = clusterator.next()) {
1353 0 : int index = c.fGlyphIndex;
1354 0 : int glyphLimit = index + c.fGlyphCount;
1355 :
1356 0 : bool actualText = false;
1357 0 : SK_AT_SCOPE_EXIT(if (actualText) { glyphPositioner.flush(); out->writeText("EMC\n"); } );
1358 0 : if (c.fUtf8Text) { // real cluster
1359 : // Check if `/ActualText` needed.
1360 0 : const char* textPtr = c.fUtf8Text;
1361 0 : const char* textEnd = c.fUtf8Text + c.fTextByteLength;
1362 0 : SkUnichar unichar = SkUTF8_NextUnicharWithError(&textPtr, textEnd);
1363 0 : if (unichar < 0) {
1364 0 : return;
1365 : }
1366 0 : if (textPtr < textEnd || // more characters left
1367 0 : glyphLimit > index + 1 || // toUnicode wouldn't work
1368 0 : unichar != map_glyph(glyphToUnicode, glyphs[index])) // test single Unichar map
1369 : {
1370 0 : glyphPositioner.flush();
1371 0 : out->writeText("/Span<</ActualText <");
1372 0 : SkPDFUtils::WriteUTF16beHex(out, 0xFEFF); // U+FEFF = BYTE ORDER MARK
1373 : // the BOM marks this text as UTF-16BE, not PDFDocEncoding.
1374 0 : SkPDFUtils::WriteUTF16beHex(out, unichar); // first char
1375 0 : while (textPtr < textEnd) {
1376 0 : unichar = SkUTF8_NextUnicharWithError(&textPtr, textEnd);
1377 0 : if (unichar < 0) {
1378 0 : break;
1379 : }
1380 0 : SkPDFUtils::WriteUTF16beHex(out, unichar);
1381 : }
1382 0 : out->writeText("> >> BDC\n"); // begin marked-content sequence
1383 : // with an associated property list.
1384 0 : actualText = true;
1385 : }
1386 : }
1387 0 : for (; index < glyphLimit; ++index) {
1388 0 : SkGlyphID gid = glyphs[index];
1389 0 : if (gid > maxGlyphID) {
1390 0 : continue;
1391 : }
1392 0 : if (!font || !font->hasGlyph(gid)) {
1393 : // Not yet specified font or need to switch font.
1394 0 : int fontIndex = this->getFontResourceIndex(typeface, gid);
1395 : // All preconditions for SkPDFFont::GetFontResource are met.
1396 0 : SkASSERT(fontIndex >= 0);
1397 0 : if (fontIndex < 0) {
1398 0 : return;
1399 : }
1400 0 : glyphPositioner.flush();
1401 0 : update_font(out, fontIndex, textSize);
1402 0 : font = fFontResources[fontIndex];
1403 0 : SkASSERT(font); // All preconditions for SkPDFFont::GetFontResource are met.
1404 0 : if (!font) {
1405 0 : return;
1406 : }
1407 0 : SkASSERT(font->multiByteGlyphs() == multiByteGlyphs);
1408 : }
1409 0 : SkPoint xy{0, 0};
1410 0 : SkScalar advance{0};
1411 0 : if (!defaultPositioning) {
1412 0 : advance = advanceScale * glyphCache->getGlyphIDAdvance(gid).fAdvanceX;
1413 : xy = SkTextBlob::kFull_Positioning == positioning
1414 0 : ? SkPoint{pos[2 * index], pos[2 * index + 1]}
1415 0 : : SkPoint{pos[index], 0};
1416 0 : if (alignment != SkPaint::kLeft_Align) {
1417 0 : xy.offset(alignmentFactor * advance, 0);
1418 : }
1419 : }
1420 0 : font->noteGlyphUsage(gid);
1421 0 : SkGlyphID encodedGlyph = multiByteGlyphs ? gid : font->glyphToPDFFontEncoding(gid);
1422 0 : glyphPositioner.writeGlyph(xy, advance, encodedGlyph);
1423 : }
1424 0 : }
1425 : }
1426 :
1427 0 : void SkPDFDevice::drawText(const void* text, size_t len,
1428 : SkScalar x, SkScalar y, const SkPaint& paint) {
1429 : this->internalDrawText(text, len, nullptr, SkTextBlob::kDefault_Positioning,
1430 0 : SkPoint{x, y}, paint, nullptr, 0, nullptr);
1431 0 : }
1432 :
1433 0 : void SkPDFDevice::drawPosText(const void* text, size_t len,
1434 : const SkScalar pos[], int scalarsPerPos,
1435 : const SkPoint& offset, const SkPaint& paint) {
1436 0 : this->internalDrawText(text, len, pos, (SkTextBlob::GlyphPositioning)scalarsPerPos,
1437 0 : offset, paint, nullptr, 0, nullptr);
1438 0 : }
1439 :
1440 0 : void SkPDFDevice::drawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
1441 : const SkPaint &paint, SkDrawFilter* drawFilter) {
1442 0 : for (SkTextBlobRunIterator it(blob); !it.done(); it.next()) {
1443 0 : SkPaint runPaint(paint);
1444 0 : it.applyFontToPaint(&runPaint);
1445 0 : if (drawFilter && !drawFilter->filter(&runPaint, SkDrawFilter::kText_Type)) {
1446 0 : continue;
1447 : }
1448 0 : runPaint.setFlags(this->filterTextFlags(runPaint));
1449 0 : SkPoint offset = it.offset() + SkPoint{x, y};
1450 0 : this->internalDrawText(it.glyphs(), sizeof(SkGlyphID) * it.glyphCount(),
1451 0 : it.pos(), it.positioning(), offset, runPaint,
1452 0 : it.clusters(), it.textSize(), it.text());
1453 : }
1454 0 : }
1455 :
1456 0 : void SkPDFDevice::drawVertices(const SkVertices*, SkBlendMode, const SkPaint&) {
1457 0 : if (this->cs().isEmpty(size(*this))) {
1458 0 : return;
1459 : }
1460 : // TODO: implement drawVertices
1461 : }
1462 :
1463 0 : void SkPDFDevice::drawDevice(SkBaseDevice* device, int x, int y, const SkPaint& paint) {
1464 0 : SkASSERT(!paint.getImageFilter());
1465 :
1466 : // Check if the source device is really a bitmapdevice (because that's what we returned
1467 : // from createDevice (likely due to an imagefilter)
1468 0 : SkPixmap pmap;
1469 0 : if (device->peekPixels(&pmap)) {
1470 0 : SkBitmap bitmap;
1471 0 : bitmap.installPixels(pmap);
1472 0 : this->drawSprite(bitmap, x, y, paint);
1473 0 : return;
1474 : }
1475 :
1476 : // our onCreateCompatibleDevice() always creates SkPDFDevice subclasses.
1477 0 : SkPDFDevice* pdfDevice = static_cast<SkPDFDevice*>(device);
1478 :
1479 0 : SkScalar scalarX = SkIntToScalar(x);
1480 0 : SkScalar scalarY = SkIntToScalar(y);
1481 0 : for (const RectWithData& l : pdfDevice->fLinkToURLs) {
1482 0 : SkRect r = l.rect.makeOffset(scalarX, scalarY);
1483 0 : fLinkToURLs.emplace_back(r, l.data.get());
1484 : }
1485 0 : for (const RectWithData& l : pdfDevice->fLinkToDestinations) {
1486 0 : SkRect r = l.rect.makeOffset(scalarX, scalarY);
1487 0 : fLinkToDestinations.emplace_back(r, l.data.get());
1488 : }
1489 0 : for (const NamedDestination& d : pdfDevice->fNamedDestinations) {
1490 0 : SkPoint p = d.point + SkPoint::Make(scalarX, scalarY);
1491 0 : fNamedDestinations.emplace_back(d.nameData.get(), p);
1492 : }
1493 :
1494 0 : if (pdfDevice->isContentEmpty()) {
1495 0 : return;
1496 : }
1497 :
1498 0 : SkMatrix matrix = SkMatrix::MakeTrans(SkIntToScalar(x), SkIntToScalar(y));
1499 0 : ScopedContentEntry content(this, this->cs(), matrix, paint);
1500 0 : if (!content.entry()) {
1501 0 : return;
1502 : }
1503 0 : if (content.needShape()) {
1504 0 : SkPath shape;
1505 0 : shape.addRect(SkRect::MakeXYWH(SkIntToScalar(x), SkIntToScalar(y),
1506 0 : SkIntToScalar(device->width()),
1507 0 : SkIntToScalar(device->height())));
1508 0 : content.setShape(shape);
1509 : }
1510 0 : if (!content.needSource()) {
1511 0 : return;
1512 : }
1513 :
1514 0 : sk_sp<SkPDFObject> xObject = pdfDevice->makeFormXObjectFromDevice();
1515 0 : SkPDFUtils::DrawFormXObject(this->addXObjectResource(xObject.get()),
1516 0 : &content.entry()->fContent);
1517 : }
1518 :
1519 0 : sk_sp<SkSurface> SkPDFDevice::makeSurface(const SkImageInfo& info, const SkSurfaceProps& props) {
1520 0 : return SkSurface::MakeRaster(info, &props);
1521 : }
1522 :
1523 :
1524 0 : sk_sp<SkPDFDict> SkPDFDevice::makeResourceDict() const {
1525 0 : SkTDArray<SkPDFObject*> fonts;
1526 0 : fonts.setReserve(fFontResources.count());
1527 0 : for (SkPDFFont* font : fFontResources) {
1528 0 : fonts.push(font);
1529 : }
1530 : return SkPDFResourceDict::Make(
1531 : &fGraphicStateResources,
1532 : &fShaderResources,
1533 : &fXObjectResources,
1534 0 : &fonts);
1535 : }
1536 :
1537 0 : sk_sp<SkPDFArray> SkPDFDevice::copyMediaBox() const {
1538 0 : auto mediaBox = sk_make_sp<SkPDFArray>();
1539 0 : mediaBox->reserve(4);
1540 0 : mediaBox->appendInt(0);
1541 0 : mediaBox->appendInt(0);
1542 0 : mediaBox->appendInt(fPageSize.width());
1543 0 : mediaBox->appendInt(fPageSize.height());
1544 0 : return mediaBox;
1545 : }
1546 :
1547 0 : std::unique_ptr<SkStreamAsset> SkPDFDevice::content() const {
1548 0 : SkDynamicMemoryWStream buffer;
1549 0 : if (fInitialTransform.getType() != SkMatrix::kIdentity_Mask) {
1550 0 : SkPDFUtils::AppendTransform(fInitialTransform, &buffer);
1551 : }
1552 :
1553 0 : GraphicStackState gsState(fExistingClipStack, &buffer);
1554 0 : for (const auto& entry : fContentEntries) {
1555 0 : gsState.updateClip(entry.fState.fClipStack,
1556 0 : {0, 0}, SkRect::Make(size(*this)));
1557 0 : gsState.updateMatrix(entry.fState.fMatrix);
1558 0 : gsState.updateDrawingState(entry.fState);
1559 :
1560 0 : entry.fContent.writeToStream(&buffer);
1561 : }
1562 0 : gsState.drainStack();
1563 0 : if (buffer.bytesWritten() > 0) {
1564 0 : return std::unique_ptr<SkStreamAsset>(buffer.detachAsStream());
1565 : } else {
1566 0 : return skstd::make_unique<SkMemoryStream>();
1567 : }
1568 : }
1569 :
1570 : /* Draws an inverse filled path by using Path Ops to compute the positive
1571 : * inverse using the current clip as the inverse bounds.
1572 : * Return true if this was an inverse path and was properly handled,
1573 : * otherwise returns false and the normal drawing routine should continue,
1574 : * either as a (incorrect) fallback or because the path was not inverse
1575 : * in the first place.
1576 : */
1577 0 : bool SkPDFDevice::handleInversePath(const SkPath& origPath,
1578 : const SkPaint& paint, bool pathIsMutable,
1579 : const SkMatrix* prePathMatrix) {
1580 0 : if (!origPath.isInverseFillType()) {
1581 0 : return false;
1582 : }
1583 :
1584 0 : if (this->cs().isEmpty(size(*this))) {
1585 0 : return false;
1586 : }
1587 :
1588 0 : SkPath modifiedPath;
1589 0 : SkPath* pathPtr = const_cast<SkPath*>(&origPath);
1590 0 : SkPaint noInversePaint(paint);
1591 :
1592 : // Merge stroking operations into final path.
1593 0 : if (SkPaint::kStroke_Style == paint.getStyle() ||
1594 0 : SkPaint::kStrokeAndFill_Style == paint.getStyle()) {
1595 0 : bool doFillPath = paint.getFillPath(origPath, &modifiedPath);
1596 0 : if (doFillPath) {
1597 0 : noInversePaint.setStyle(SkPaint::kFill_Style);
1598 0 : noInversePaint.setStrokeWidth(0);
1599 0 : pathPtr = &modifiedPath;
1600 : } else {
1601 : // To be consistent with the raster output, hairline strokes
1602 : // are rendered as non-inverted.
1603 0 : modifiedPath.toggleInverseFillType();
1604 0 : this->drawPath(modifiedPath, paint, nullptr, true);
1605 0 : return true;
1606 : }
1607 : }
1608 :
1609 : // Get bounds of clip in current transform space
1610 : // (clip bounds are given in device space).
1611 : SkMatrix transformInverse;
1612 0 : SkMatrix totalMatrix = this->ctm();
1613 0 : if (prePathMatrix) {
1614 0 : totalMatrix.preConcat(*prePathMatrix);
1615 : }
1616 0 : if (!totalMatrix.invert(&transformInverse)) {
1617 0 : return false;
1618 : }
1619 0 : SkRect bounds = this->cs().bounds(size(*this));
1620 0 : transformInverse.mapRect(&bounds);
1621 :
1622 : // Extend the bounds by the line width (plus some padding)
1623 : // so the edge doesn't cause a visible stroke.
1624 0 : bounds.outset(paint.getStrokeWidth() + SK_Scalar1,
1625 0 : paint.getStrokeWidth() + SK_Scalar1);
1626 :
1627 0 : if (!calculate_inverse_path(bounds, *pathPtr, &modifiedPath)) {
1628 0 : return false;
1629 : }
1630 :
1631 0 : this->drawPath(modifiedPath, noInversePaint, prePathMatrix, true);
1632 0 : return true;
1633 : }
1634 :
1635 0 : void SkPDFDevice::appendAnnotations(SkPDFArray* array) const {
1636 0 : array->reserve(fLinkToURLs.count() + fLinkToDestinations.count());
1637 0 : for (const RectWithData& rectWithURL : fLinkToURLs) {
1638 : SkRect r;
1639 0 : fInitialTransform.mapRect(&r, rectWithURL.rect);
1640 0 : array->appendObject(create_link_to_url(rectWithURL.data.get(), r));
1641 : }
1642 0 : for (const RectWithData& linkToDestination : fLinkToDestinations) {
1643 : SkRect r;
1644 0 : fInitialTransform.mapRect(&r, linkToDestination.rect);
1645 0 : array->appendObject(
1646 0 : create_link_named_dest(linkToDestination.data.get(), r));
1647 : }
1648 0 : }
1649 :
1650 0 : void SkPDFDevice::appendDestinations(SkPDFDict* dict, SkPDFObject* page) const {
1651 0 : for (const NamedDestination& dest : fNamedDestinations) {
1652 0 : auto pdfDest = sk_make_sp<SkPDFArray>();
1653 0 : pdfDest->reserve(5);
1654 0 : pdfDest->appendObjRef(sk_ref_sp(page));
1655 0 : pdfDest->appendName("XYZ");
1656 0 : SkPoint p = fInitialTransform.mapXY(dest.point.x(), dest.point.y());
1657 0 : pdfDest->appendScalar(p.x());
1658 0 : pdfDest->appendScalar(p.y());
1659 0 : pdfDest->appendInt(0); // Leave zoom unchanged
1660 0 : SkString name(static_cast<const char*>(dest.nameData->data()));
1661 0 : dict->insertObject(name, std::move(pdfDest));
1662 : }
1663 0 : }
1664 :
1665 0 : sk_sp<SkPDFObject> SkPDFDevice::makeFormXObjectFromDevice() {
1666 0 : SkMatrix inverseTransform = SkMatrix::I();
1667 0 : if (!fInitialTransform.isIdentity()) {
1668 0 : if (!fInitialTransform.invert(&inverseTransform)) {
1669 0 : SkDEBUGFAIL("Layer initial transform should be invertible.");
1670 0 : inverseTransform.reset();
1671 : }
1672 : }
1673 : sk_sp<SkPDFObject> xobject =
1674 0 : SkPDFMakeFormXObject(this->content(), this->copyMediaBox(),
1675 0 : this->makeResourceDict(), inverseTransform, nullptr);
1676 : // We always draw the form xobjects that we create back into the device, so
1677 : // we simply preserve the font usage instead of pulling it out and merging
1678 : // it back in later.
1679 0 : this->cleanUp(); // Reset this device to have no content.
1680 0 : this->init();
1681 0 : return xobject;
1682 : }
1683 :
1684 0 : void SkPDFDevice::drawFormXObjectWithMask(int xObjectIndex,
1685 : sk_sp<SkPDFObject> mask,
1686 : const SkClipStack& clipStack,
1687 : SkBlendMode mode,
1688 : bool invertClip) {
1689 0 : if (!invertClip && clipStack.isEmpty(size(*this))) {
1690 0 : return;
1691 : }
1692 :
1693 : sk_sp<SkPDFDict> sMaskGS = SkPDFGraphicState::GetSMaskGraphicState(
1694 0 : std::move(mask), invertClip,
1695 0 : SkPDFGraphicState::kAlpha_SMaskMode, fDocument->canon());
1696 :
1697 0 : SkPaint paint;
1698 0 : paint.setBlendMode(mode);
1699 0 : ScopedContentEntry content(this, clipStack, SkMatrix::I(), paint);
1700 0 : if (!content.entry()) {
1701 0 : return;
1702 : }
1703 0 : SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()),
1704 0 : &content.entry()->fContent);
1705 0 : SkPDFUtils::DrawFormXObject(xObjectIndex, &content.entry()->fContent);
1706 :
1707 : // Call makeNoSmaskGraphicState() instead of
1708 : // SkPDFGraphicState::MakeNoSmaskGraphicState so that the canon
1709 : // can deduplicate.
1710 0 : sMaskGS = fDocument->canon()->makeNoSmaskGraphicState();
1711 0 : SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()),
1712 0 : &content.entry()->fContent);
1713 : }
1714 :
1715 0 : SkPDFDevice::ContentEntry* SkPDFDevice::setUpContentEntry(const SkClipStack& clipStack,
1716 : const SkMatrix& matrix,
1717 : const SkPaint& paint,
1718 : bool hasText,
1719 : sk_sp<SkPDFObject>* dst) {
1720 0 : *dst = nullptr;
1721 0 : SkBlendMode blendMode = paint.getBlendMode();
1722 :
1723 : // For the following modes, we want to handle source and destination
1724 : // separately, so make an object of what's already there.
1725 0 : if (blendMode == SkBlendMode::kClear ||
1726 0 : blendMode == SkBlendMode::kSrc ||
1727 0 : blendMode == SkBlendMode::kSrcIn ||
1728 0 : blendMode == SkBlendMode::kDstIn ||
1729 0 : blendMode == SkBlendMode::kSrcOut ||
1730 0 : blendMode == SkBlendMode::kDstOut ||
1731 0 : blendMode == SkBlendMode::kSrcATop ||
1732 0 : blendMode == SkBlendMode::kDstATop ||
1733 : blendMode == SkBlendMode::kModulate) {
1734 0 : if (!isContentEmpty()) {
1735 0 : *dst = this->makeFormXObjectFromDevice();
1736 0 : SkASSERT(isContentEmpty());
1737 0 : } else if (blendMode != SkBlendMode::kSrc &&
1738 : blendMode != SkBlendMode::kSrcOut) {
1739 : // Except for Src and SrcOut, if there isn't anything already there,
1740 : // then we're done.
1741 0 : return nullptr;
1742 : }
1743 : }
1744 : // TODO(vandebo): Figure out how/if we can handle the following modes:
1745 : // Xor, Plus.
1746 :
1747 : // Dst xfer mode doesn't draw source at all.
1748 0 : if (blendMode == SkBlendMode::kDst) {
1749 0 : return nullptr;
1750 : }
1751 :
1752 : SkPDFDevice::ContentEntry* entry;
1753 0 : if (fContentEntries.back() && fContentEntries.back()->fContent.bytesWritten() == 0) {
1754 0 : entry = fContentEntries.back();
1755 0 : } else if (blendMode != SkBlendMode::kDstOver) {
1756 0 : entry = fContentEntries.emplace_back();
1757 : } else {
1758 0 : entry = fContentEntries.emplace_front();
1759 : }
1760 0 : populateGraphicStateEntryFromPaint(matrix, clipStack, paint, hasText, &entry->fState);
1761 0 : return entry;
1762 : }
1763 :
1764 0 : void SkPDFDevice::finishContentEntry(SkBlendMode blendMode,
1765 : sk_sp<SkPDFObject> dst,
1766 : SkPath* shape) {
1767 0 : if (blendMode != SkBlendMode::kClear &&
1768 0 : blendMode != SkBlendMode::kSrc &&
1769 0 : blendMode != SkBlendMode::kDstOver &&
1770 0 : blendMode != SkBlendMode::kSrcIn &&
1771 0 : blendMode != SkBlendMode::kDstIn &&
1772 0 : blendMode != SkBlendMode::kSrcOut &&
1773 0 : blendMode != SkBlendMode::kDstOut &&
1774 0 : blendMode != SkBlendMode::kSrcATop &&
1775 0 : blendMode != SkBlendMode::kDstATop &&
1776 : blendMode != SkBlendMode::kModulate) {
1777 0 : SkASSERT(!dst);
1778 0 : return;
1779 : }
1780 0 : if (blendMode == SkBlendMode::kDstOver) {
1781 0 : SkASSERT(!dst);
1782 0 : if (fContentEntries.front()->fContent.bytesWritten() == 0) {
1783 : // For DstOver, an empty content entry was inserted before the rest
1784 : // of the content entries. If nothing was drawn, it needs to be
1785 : // removed.
1786 0 : fContentEntries.pop_front();
1787 : }
1788 0 : return;
1789 : }
1790 0 : if (!dst) {
1791 0 : SkASSERT(blendMode == SkBlendMode::kSrc ||
1792 : blendMode == SkBlendMode::kSrcOut);
1793 0 : return;
1794 : }
1795 :
1796 0 : SkASSERT(dst);
1797 0 : SkASSERT(fContentEntries.count() == 1);
1798 : // Changing the current content into a form-xobject will destroy the clip
1799 : // objects which is fine since the xobject will already be clipped. However
1800 : // if source has shape, we need to clip it too, so a copy of the clip is
1801 : // saved.
1802 :
1803 0 : SkClipStack clipStack = fContentEntries.front()->fState.fClipStack;
1804 :
1805 0 : SkPaint stockPaint;
1806 :
1807 0 : sk_sp<SkPDFObject> srcFormXObject;
1808 0 : if (isContentEmpty()) {
1809 : // If nothing was drawn and there's no shape, then the draw was a
1810 : // no-op, but dst needs to be restored for that to be true.
1811 : // If there is shape, then an empty source with Src, SrcIn, SrcOut,
1812 : // DstIn, DstAtop or Modulate reduces to Clear and DstOut or SrcAtop
1813 : // reduces to Dst.
1814 0 : if (shape == nullptr || blendMode == SkBlendMode::kDstOut ||
1815 : blendMode == SkBlendMode::kSrcATop) {
1816 0 : ScopedContentEntry content(this, fExistingClipStack, SkMatrix::I(), stockPaint);
1817 : // TODO: addXObjectResource take sk_sp
1818 0 : SkPDFUtils::DrawFormXObject(this->addXObjectResource(dst.get()),
1819 0 : &content.entry()->fContent);
1820 0 : return;
1821 : } else {
1822 0 : blendMode = SkBlendMode::kClear;
1823 : }
1824 : } else {
1825 0 : SkASSERT(fContentEntries.count() == 1);
1826 0 : srcFormXObject = this->makeFormXObjectFromDevice();
1827 : }
1828 :
1829 : // TODO(vandebo) srcFormXObject may contain alpha, but here we want it
1830 : // without alpha.
1831 0 : if (blendMode == SkBlendMode::kSrcATop) {
1832 : // TODO(vandebo): In order to properly support SrcATop we have to track
1833 : // the shape of what's been drawn at all times. It's the intersection of
1834 : // the non-transparent parts of the device and the outlines (shape) of
1835 : // all images and devices drawn.
1836 0 : drawFormXObjectWithMask(addXObjectResource(srcFormXObject.get()), dst,
1837 0 : fExistingClipStack, SkBlendMode::kSrcOver, true);
1838 : } else {
1839 0 : if (shape != nullptr) {
1840 : // Draw shape into a form-xobject.
1841 0 : SkPaint filledPaint;
1842 0 : filledPaint.setColor(SK_ColorBLACK);
1843 0 : filledPaint.setStyle(SkPaint::kFill_Style);
1844 0 : this->internalDrawPath(clipStack, SkMatrix::I(), *shape, filledPaint, nullptr, true);
1845 0 : this->drawFormXObjectWithMask(this->addXObjectResource(dst.get()),
1846 0 : this->makeFormXObjectFromDevice(),
1847 : fExistingClipStack,
1848 0 : SkBlendMode::kSrcOver, true);
1849 : } else {
1850 0 : this->drawFormXObjectWithMask(this->addXObjectResource(dst.get()),
1851 : srcFormXObject,
1852 : fExistingClipStack,
1853 0 : SkBlendMode::kSrcOver, true);
1854 : }
1855 : }
1856 :
1857 0 : if (blendMode == SkBlendMode::kClear) {
1858 0 : return;
1859 0 : } else if (blendMode == SkBlendMode::kSrc ||
1860 : blendMode == SkBlendMode::kDstATop) {
1861 0 : ScopedContentEntry content(this, fExistingClipStack, SkMatrix::I(), stockPaint);
1862 0 : if (content.entry()) {
1863 0 : SkPDFUtils::DrawFormXObject(
1864 : this->addXObjectResource(srcFormXObject.get()),
1865 0 : &content.entry()->fContent);
1866 : }
1867 0 : if (blendMode == SkBlendMode::kSrc) {
1868 0 : return;
1869 0 : }
1870 0 : } else if (blendMode == SkBlendMode::kSrcATop) {
1871 : ScopedContentEntry content(this, fExistingClipStack,
1872 0 : SkMatrix::I(), stockPaint);
1873 0 : if (content.entry()) {
1874 0 : SkPDFUtils::DrawFormXObject(this->addXObjectResource(dst.get()),
1875 0 : &content.entry()->fContent);
1876 : }
1877 : }
1878 :
1879 0 : SkASSERT(blendMode == SkBlendMode::kSrcIn ||
1880 : blendMode == SkBlendMode::kDstIn ||
1881 : blendMode == SkBlendMode::kSrcOut ||
1882 : blendMode == SkBlendMode::kDstOut ||
1883 : blendMode == SkBlendMode::kSrcATop ||
1884 : blendMode == SkBlendMode::kDstATop ||
1885 : blendMode == SkBlendMode::kModulate);
1886 :
1887 0 : if (blendMode == SkBlendMode::kSrcIn ||
1888 0 : blendMode == SkBlendMode::kSrcOut ||
1889 : blendMode == SkBlendMode::kSrcATop) {
1890 0 : drawFormXObjectWithMask(addXObjectResource(srcFormXObject.get()),
1891 0 : std::move(dst),
1892 : fExistingClipStack,
1893 : SkBlendMode::kSrcOver,
1894 0 : blendMode == SkBlendMode::kSrcOut);
1895 0 : return;
1896 : } else {
1897 0 : SkBlendMode mode = SkBlendMode::kSrcOver;
1898 0 : int resourceID = addXObjectResource(dst.get());
1899 0 : if (blendMode == SkBlendMode::kModulate) {
1900 0 : drawFormXObjectWithMask(addXObjectResource(srcFormXObject.get()),
1901 0 : std::move(dst), fExistingClipStack,
1902 0 : SkBlendMode::kSrcOver, false);
1903 0 : mode = SkBlendMode::kMultiply;
1904 : }
1905 0 : drawFormXObjectWithMask(resourceID, std::move(srcFormXObject),
1906 : fExistingClipStack, mode,
1907 0 : blendMode == SkBlendMode::kDstOut);
1908 0 : return;
1909 : }
1910 : }
1911 :
1912 0 : bool SkPDFDevice::isContentEmpty() {
1913 0 : if (!fContentEntries.front() || fContentEntries.front()->fContent.bytesWritten() == 0) {
1914 0 : SkASSERT(fContentEntries.count() <= 1);
1915 0 : return true;
1916 : }
1917 0 : return false;
1918 : }
1919 :
1920 0 : void SkPDFDevice::populateGraphicStateEntryFromPaint(
1921 : const SkMatrix& matrix,
1922 : const SkClipStack& clipStack,
1923 : const SkPaint& paint,
1924 : bool hasText,
1925 : SkPDFDevice::GraphicStateEntry* entry) {
1926 0 : NOT_IMPLEMENTED(paint.getPathEffect() != nullptr, false);
1927 0 : NOT_IMPLEMENTED(paint.getMaskFilter() != nullptr, false);
1928 0 : NOT_IMPLEMENTED(paint.getColorFilter() != nullptr, false);
1929 :
1930 0 : entry->fMatrix = matrix;
1931 0 : entry->fClipStack = clipStack;
1932 0 : entry->fColor = SkColorSetA(paint.getColor(), 0xFF);
1933 0 : entry->fShaderIndex = -1;
1934 :
1935 : // PDF treats a shader as a color, so we only set one or the other.
1936 0 : sk_sp<SkPDFObject> pdfShader;
1937 0 : SkShader* shader = paint.getShader();
1938 0 : SkColor color = paint.getColor();
1939 0 : if (shader) {
1940 0 : if (SkShader::kColor_GradientType == shader->asAGradient(nullptr)) {
1941 : // We don't have to set a shader just for a color.
1942 : SkShader::GradientInfo gradientInfo;
1943 0 : SkColor gradientColor = SK_ColorBLACK;
1944 0 : gradientInfo.fColors = &gradientColor;
1945 0 : gradientInfo.fColorOffsets = nullptr;
1946 0 : gradientInfo.fColorCount = 1;
1947 0 : SkAssertResult(shader->asAGradient(&gradientInfo) == SkShader::kColor_GradientType);
1948 0 : entry->fColor = SkColorSetA(gradientColor, 0xFF);
1949 0 : color = gradientColor;
1950 : } else {
1951 : // PDF positions patterns relative to the initial transform, so
1952 : // we need to apply the current transform to the shader parameters.
1953 0 : SkMatrix transform = matrix;
1954 0 : transform.postConcat(fInitialTransform);
1955 :
1956 : // PDF doesn't support kClamp_TileMode, so we simulate it by making
1957 : // a pattern the size of the current clip.
1958 0 : SkRect clipStackBounds = clipStack.bounds(size(*this));
1959 :
1960 : // We need to apply the initial transform to bounds in order to get
1961 : // bounds in a consistent coordinate system.
1962 0 : fInitialTransform.mapRect(&clipStackBounds);
1963 : SkIRect bounds;
1964 0 : clipStackBounds.roundOut(&bounds);
1965 :
1966 : SkScalar rasterScale =
1967 0 : SkIntToScalar(fRasterDpi) / DPI_FOR_RASTER_SCALE_ONE;
1968 0 : pdfShader = SkPDFShader::GetPDFShader(
1969 0 : fDocument, fRasterDpi, shader, transform, bounds, rasterScale);
1970 :
1971 0 : if (pdfShader.get()) {
1972 : // pdfShader has been canonicalized so we can directly compare
1973 : // pointers.
1974 0 : int resourceIndex = fShaderResources.find(pdfShader.get());
1975 0 : if (resourceIndex < 0) {
1976 0 : resourceIndex = fShaderResources.count();
1977 0 : fShaderResources.push(pdfShader.get());
1978 0 : pdfShader.get()->ref();
1979 : }
1980 0 : entry->fShaderIndex = resourceIndex;
1981 : }
1982 : }
1983 : }
1984 :
1985 0 : sk_sp<SkPDFGraphicState> newGraphicState;
1986 0 : if (color == paint.getColor()) {
1987 0 : newGraphicState.reset(
1988 0 : SkPDFGraphicState::GetGraphicStateForPaint(fDocument->canon(), paint));
1989 : } else {
1990 0 : SkPaint newPaint = paint;
1991 0 : newPaint.setColor(color);
1992 0 : newGraphicState.reset(
1993 0 : SkPDFGraphicState::GetGraphicStateForPaint(fDocument->canon(), newPaint));
1994 : }
1995 0 : int resourceIndex = addGraphicStateResource(newGraphicState.get());
1996 0 : entry->fGraphicStateIndex = resourceIndex;
1997 :
1998 0 : if (hasText) {
1999 0 : entry->fTextScaleX = paint.getTextScaleX();
2000 0 : entry->fTextFill = paint.getStyle();
2001 : } else {
2002 0 : entry->fTextScaleX = 0;
2003 : }
2004 0 : }
2005 :
2006 0 : int SkPDFDevice::addGraphicStateResource(SkPDFObject* gs) {
2007 : // Assumes that gs has been canonicalized (so we can directly compare
2008 : // pointers).
2009 0 : int result = fGraphicStateResources.find(gs);
2010 0 : if (result < 0) {
2011 0 : result = fGraphicStateResources.count();
2012 0 : fGraphicStateResources.push(gs);
2013 0 : gs->ref();
2014 : }
2015 0 : return result;
2016 : }
2017 :
2018 0 : int SkPDFDevice::addXObjectResource(SkPDFObject* xObject) {
2019 : // TODO(halcanary): make this take a sk_sp<SkPDFObject>
2020 : // Assumes that xobject has been canonicalized (so we can directly compare
2021 : // pointers).
2022 0 : int result = fXObjectResources.find(xObject);
2023 0 : if (result < 0) {
2024 0 : result = fXObjectResources.count();
2025 0 : fXObjectResources.push(SkRef(xObject));
2026 : }
2027 0 : return result;
2028 : }
2029 :
2030 0 : int SkPDFDevice::getFontResourceIndex(SkTypeface* typeface, uint16_t glyphID) {
2031 : sk_sp<SkPDFFont> newFont(
2032 0 : SkPDFFont::GetFontResource(fDocument->canon(), typeface, glyphID));
2033 0 : if (!newFont) {
2034 0 : return -1;
2035 : }
2036 0 : int resourceIndex = fFontResources.find(newFont.get());
2037 0 : if (resourceIndex < 0) {
2038 0 : fDocument->registerFont(newFont.get());
2039 0 : resourceIndex = fFontResources.count();
2040 0 : fFontResources.push(newFont.release());
2041 : }
2042 0 : return resourceIndex;
2043 : }
2044 :
2045 0 : static SkSize rect_to_size(const SkRect& r) { return {r.width(), r.height()}; }
2046 :
2047 0 : static sk_sp<SkImage> color_filter(const SkImageSubset& imageSubset,
2048 : SkColorFilter* colorFilter) {
2049 : auto surface =
2050 0 : SkSurface::MakeRaster(SkImageInfo::MakeN32Premul(imageSubset.dimensions()));
2051 0 : SkASSERT(surface);
2052 0 : SkCanvas* canvas = surface->getCanvas();
2053 0 : canvas->clear(SK_ColorTRANSPARENT);
2054 0 : SkPaint paint;
2055 0 : paint.setColorFilter(sk_ref_sp(colorFilter));
2056 0 : imageSubset.draw(canvas, &paint);
2057 0 : canvas->flush();
2058 0 : return surface->makeImageSnapshot();
2059 : }
2060 :
2061 : ////////////////////////////////////////////////////////////////////////////////
2062 0 : void SkPDFDevice::internalDrawImage(const SkMatrix& origMatrix,
2063 : const SkClipStack& clipStack,
2064 : SkImageSubset imageSubset,
2065 : const SkPaint& paint) {
2066 0 : if (imageSubset.dimensions().isZero()) {
2067 0 : return;
2068 : }
2069 : #ifdef SK_PDF_IMAGE_STATS
2070 : gDrawImageCalls.fetch_add(1);
2071 : #endif
2072 0 : SkMatrix matrix = origMatrix;
2073 :
2074 : // Rasterize the bitmap using perspective in a new bitmap.
2075 0 : if (origMatrix.hasPerspective()) {
2076 0 : if (fRasterDpi == 0) {
2077 0 : return;
2078 : }
2079 : // Transform the bitmap in the new space, without taking into
2080 : // account the initial transform.
2081 0 : SkPath perspectiveOutline;
2082 0 : SkRect imageBounds = SkRect::Make(imageSubset.bounds());
2083 0 : perspectiveOutline.addRect(imageBounds);
2084 0 : perspectiveOutline.transform(origMatrix);
2085 :
2086 : // TODO(edisonn): perf - use current clip too.
2087 : // Retrieve the bounds of the new shape.
2088 0 : SkRect bounds = perspectiveOutline.getBounds();
2089 :
2090 : // Transform the bitmap in the new space, taking into
2091 : // account the initial transform.
2092 0 : SkMatrix total = origMatrix;
2093 0 : total.postConcat(fInitialTransform);
2094 0 : SkScalar dpiScale = SkIntToScalar(fRasterDpi) /
2095 0 : SkIntToScalar(DPI_FOR_RASTER_SCALE_ONE);
2096 0 : total.postScale(dpiScale, dpiScale);
2097 :
2098 0 : SkPath physicalPerspectiveOutline;
2099 0 : physicalPerspectiveOutline.addRect(imageBounds);
2100 0 : physicalPerspectiveOutline.transform(total);
2101 :
2102 : SkRect physicalPerspectiveBounds =
2103 0 : physicalPerspectiveOutline.getBounds();
2104 0 : SkScalar scaleX = physicalPerspectiveBounds.width() / bounds.width();
2105 0 : SkScalar scaleY = physicalPerspectiveBounds.height() / bounds.height();
2106 :
2107 : // TODO(edisonn): A better approach would be to use a bitmap shader
2108 : // (in clamp mode) and draw a rect over the entire bounding box. Then
2109 : // intersect perspectiveOutline to the clip. That will avoid introducing
2110 : // alpha to the image while still giving good behavior at the edge of
2111 : // the image. Avoiding alpha will reduce the pdf size and generation
2112 : // CPU time some.
2113 :
2114 0 : SkISize wh = rect_to_size(physicalPerspectiveBounds).toCeil();
2115 :
2116 0 : auto surface = SkSurface::MakeRaster(SkImageInfo::MakeN32Premul(wh));
2117 0 : if (!surface) {
2118 0 : return;
2119 : }
2120 0 : SkCanvas* canvas = surface->getCanvas();
2121 0 : canvas->clear(SK_ColorTRANSPARENT);
2122 :
2123 0 : SkScalar deltaX = bounds.left();
2124 0 : SkScalar deltaY = bounds.top();
2125 :
2126 0 : SkMatrix offsetMatrix = origMatrix;
2127 0 : offsetMatrix.postTranslate(-deltaX, -deltaY);
2128 0 : offsetMatrix.postScale(scaleX, scaleY);
2129 :
2130 : // Translate the draw in the new canvas, so we perfectly fit the
2131 : // shape in the bitmap.
2132 0 : canvas->setMatrix(offsetMatrix);
2133 0 : imageSubset.draw(canvas, nullptr);
2134 : // Make sure the final bits are in the bitmap.
2135 0 : canvas->flush();
2136 :
2137 : // In the new space, we use the identity matrix translated
2138 : // and scaled to reflect DPI.
2139 0 : matrix.setScale(1 / scaleX, 1 / scaleY);
2140 0 : matrix.postTranslate(deltaX, deltaY);
2141 :
2142 0 : imageSubset = SkImageSubset(surface->makeImageSnapshot());
2143 : }
2144 :
2145 : SkMatrix scaled;
2146 : // Adjust for origin flip.
2147 0 : scaled.setScale(SK_Scalar1, -SK_Scalar1);
2148 0 : scaled.postTranslate(0, SK_Scalar1);
2149 : // Scale the image up from 1x1 to WxH.
2150 0 : SkIRect subset = imageSubset.bounds();
2151 0 : scaled.postScale(SkIntToScalar(imageSubset.dimensions().width()),
2152 0 : SkIntToScalar(imageSubset.dimensions().height()));
2153 0 : scaled.postConcat(matrix);
2154 0 : ScopedContentEntry content(this, clipStack, scaled, paint);
2155 0 : if (!content.entry()) {
2156 0 : return;
2157 : }
2158 0 : if (content.needShape()) {
2159 0 : SkPath shape;
2160 0 : shape.addRect(SkRect::Make(subset));
2161 0 : shape.transform(matrix);
2162 0 : content.setShape(shape);
2163 : }
2164 0 : if (!content.needSource()) {
2165 0 : return;
2166 : }
2167 :
2168 0 : if (SkColorFilter* colorFilter = paint.getColorFilter()) {
2169 : // TODO(https://bug.skia.org/4378): implement colorfilter on other
2170 : // draw calls. This code here works for all
2171 : // drawBitmap*()/drawImage*() calls amd ImageFilters (which
2172 : // rasterize a layer on this backend). Fortuanely, this seems
2173 : // to be how Chromium impements most color-filters.
2174 0 : sk_sp<SkImage> img = color_filter(imageSubset, colorFilter);
2175 0 : imageSubset = SkImageSubset(std::move(img));
2176 : // TODO(halcanary): de-dupe this by caching filtered images.
2177 : // (maybe in the resource cache?)
2178 : }
2179 :
2180 0 : SkBitmapKey key = imageSubset.getKey();
2181 0 : sk_sp<SkPDFObject> pdfimage = fDocument->canon()->findPDFBitmap(key);
2182 0 : if (!pdfimage) {
2183 0 : sk_sp<SkImage> img = imageSubset.makeImage();
2184 0 : if (!img) {
2185 0 : return;
2186 : }
2187 0 : pdfimage = SkPDFCreateBitmapObject(
2188 0 : std::move(img), fDocument->canon()->getPixelSerializer());
2189 0 : if (!pdfimage) {
2190 0 : return;
2191 : }
2192 0 : fDocument->serialize(pdfimage); // serialize images early.
2193 0 : fDocument->canon()->addPDFBitmap(key, pdfimage);
2194 : }
2195 : // TODO(halcanary): addXObjectResource() should take a sk_sp<SkPDFObject>
2196 0 : SkPDFUtils::DrawFormXObject(this->addXObjectResource(pdfimage.get()),
2197 0 : &content.entry()->fContent);
2198 : }
2199 :
2200 : ///////////////////////////////////////////////////////////////////////////////////////////////////
2201 :
2202 : #include "SkSpecialImage.h"
2203 : #include "SkImageFilter.h"
2204 :
2205 0 : void SkPDFDevice::drawSpecial(SkSpecialImage* srcImg, int x, int y,
2206 : const SkPaint& paint) {
2207 0 : SkASSERT(!srcImg->isTextureBacked());
2208 :
2209 0 : SkBitmap resultBM;
2210 :
2211 0 : SkImageFilter* filter = paint.getImageFilter();
2212 0 : if (filter) {
2213 0 : SkIPoint offset = SkIPoint::Make(0, 0);
2214 0 : SkMatrix matrix = this->ctm();
2215 0 : matrix.postTranslate(SkIntToScalar(-x), SkIntToScalar(-y));
2216 : const SkIRect clipBounds =
2217 0 : this->cs().bounds(this->imageInfo().bounds()).roundOut().makeOffset(-x, -y);
2218 0 : sk_sp<SkImageFilterCache> cache(this->getImageFilterCache());
2219 : // TODO: Should PDF be operating in a specified color space? For now, run the filter
2220 : // in the same color space as the source (this is different from all other backends).
2221 0 : SkImageFilter::OutputProperties outputProperties(srcImg->getColorSpace());
2222 0 : SkImageFilter::Context ctx(matrix, clipBounds, cache.get(), outputProperties);
2223 :
2224 0 : sk_sp<SkSpecialImage> resultImg(filter->filterImage(srcImg, ctx, &offset));
2225 0 : if (resultImg) {
2226 0 : SkPaint tmpUnfiltered(paint);
2227 0 : tmpUnfiltered.setImageFilter(nullptr);
2228 0 : if (resultImg->getROPixels(&resultBM)) {
2229 0 : this->drawSprite(resultBM, x + offset.x(), y + offset.y(), tmpUnfiltered);
2230 : }
2231 : }
2232 : } else {
2233 0 : if (srcImg->getROPixels(&resultBM)) {
2234 0 : this->drawSprite(resultBM, x, y, paint);
2235 : }
2236 : }
2237 0 : }
2238 :
2239 0 : sk_sp<SkSpecialImage> SkPDFDevice::makeSpecial(const SkBitmap& bitmap) {
2240 0 : return SkSpecialImage::MakeFromRaster(bitmap.bounds(), bitmap);
2241 : }
2242 :
2243 0 : sk_sp<SkSpecialImage> SkPDFDevice::makeSpecial(const SkImage* image) {
2244 : // TODO: See comment above in drawSpecial. The color mode we use for decode should be driven
2245 : // by the destination where we're going to draw thing thing (ie this device). But we don't have
2246 : // a color space, so we always decode in legacy mode for now.
2247 0 : SkColorSpace* legacyColorSpace = nullptr;
2248 0 : return SkSpecialImage::MakeFromImage(SkIRect::MakeWH(image->width(), image->height()),
2249 0 : image->makeNonTextureImage(), legacyColorSpace);
2250 : }
2251 :
2252 0 : sk_sp<SkSpecialImage> SkPDFDevice::snapSpecial() {
2253 0 : return nullptr;
2254 : }
2255 :
2256 0 : SkImageFilterCache* SkPDFDevice::getImageFilterCache() {
2257 : // We always return a transient cache, so it is freed after each
2258 : // filter traversal.
2259 0 : return SkImageFilterCache::Create(SkImageFilterCache::kDefaultTransientSize);
2260 : }
|