blob: 012caf6d7de7b07759148bb67643ecf1596e3a29 [file] [log] [blame]
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001/*
epoger@google.comec3ed6a2011-07-28 14:26:00 +00002 * Copyright 2011 Google Inc.
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00003 *
epoger@google.comec3ed6a2011-07-28 14:26:00 +00004 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00006 */
7
halcanary05b48e22015-11-16 10:51:21 -08008#include <tuple>
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00009#include "SkPDFDevice.h"
10
vandebo@chromium.org238be8c2012-07-13 20:06:02 +000011#include "SkAnnotation.h"
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000012#include "SkColor.h"
halcanary287d22d2015-09-24 10:20:05 -070013#include "SkColorFilter.h"
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +000014#include "SkClipStack.h"
reed@google.com8a85d0c2011-06-24 19:12:12 +000015#include "SkData.h"
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +000016#include "SkDraw.h"
vandebo@chromium.org28be72b2010-11-11 21:37:00 +000017#include "SkGlyphCache.h"
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000018#include "SkPaint.h"
vandebo@chromium.orga5180862010-10-26 19:48:49 +000019#include "SkPath.h"
commit-bot@chromium.org92ffe7d2013-07-31 22:54:31 +000020#include "SkPathOps.h"
halcanarydb0dcc72015-03-20 12:31:52 -070021#include "SkPDFBitmap.h"
halcanary7a14b312015-10-01 07:28:13 -070022#include "SkPDFCanon.h"
vandebo@chromium.org28be72b2010-11-11 21:37:00 +000023#include "SkPDFFont.h"
vandebo@chromium.orgeb6c7592010-10-26 19:54:45 +000024#include "SkPDFFormXObject.h"
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +000025#include "SkPDFGraphicState.h"
commit-bot@chromium.org47401352013-07-23 21:49:29 +000026#include "SkPDFResourceDict.h"
vandebo@chromium.orgda912d62011-03-08 18:31:02 +000027#include "SkPDFShader.h"
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000028#include "SkPDFStream.h"
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +000029#include "SkPDFTypes.h"
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +000030#include "SkPDFUtils.h"
wangxianzhuef6c50a2015-09-17 20:38:02 -070031#include "SkRasterClip.h"
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000032#include "SkRect.h"
scroggo@google.coma8e33a92013-11-08 18:02:53 +000033#include "SkRRect.h"
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000034#include "SkString.h"
reed89443ab2014-06-27 11:34:19 -070035#include "SkSurface.h"
vandebo@chromium.org28be72b2010-11-11 21:37:00 +000036#include "SkTextFormatParams.h"
vandebo@chromium.org4e1cc6a2013-01-25 19:27:23 +000037#include "SkTemplates.h"
reed@google.comfed86bd2013-03-14 15:04:57 +000038#include "SkTypefacePriv.h"
halcanarya6814332015-05-27 08:53:36 -070039#include "SkXfermodeInterpretation.h"
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000040
edisonn@google.com73a7ea32013-11-11 20:55:15 +000041#define DPI_FOR_RASTER_SCALE_ONE 72
42
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000043// Utility functions
44
halcanary05b48e22015-11-16 10:51:21 -080045static bool excessive_translation(const SkMatrix& m) {
46 const SkScalar kExcessiveTranslation = 8192.0f;
47 return SkScalarAbs(m.getTranslateX()) > kExcessiveTranslation
48 || SkScalarAbs(m.getTranslateY()) > kExcessiveTranslation;
49}
50
51static std::tuple<SkMatrix, SkVector> untranslate(const SkDraw& d) {
52 // https://bug.skia.org/257 If the translation is too large,
53 // PDF can't exactly represent the float values as numbers.
54 SkScalar translateX = d.fMatrix->getTranslateX() / d.fMatrix->getScaleX();
55 SkScalar translateY = d.fMatrix->getTranslateY() / d.fMatrix->getScaleY();
56 SkMatrix mat = *d.fMatrix;
57 mat.preTranslate(-translateX, -translateY);
58 SkASSERT(SkScalarAbs(mat.getTranslateX()) <= 1.0);
59 return std::make_tuple(mat, SkVector::Make(translateX, translateY));
60}
61
halcanarya6814332015-05-27 08:53:36 -070062// If the paint will definitely draw opaquely, replace kSrc_Mode with
63// kSrcOver_Mode. http://crbug.com/473572
64static void replace_srcmode_on_opaque_paint(SkPaint* paint) {
65 if (kSrcOver_SkXfermodeInterpretation
66 == SkInterpretXfermode(*paint, false)) {
halcanary96fcdcc2015-08-27 07:41:13 -070067 paint->setXfermode(nullptr);
halcanarya6814332015-05-27 08:53:36 -070068 }
69}
70
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +000071static void emit_pdf_color(SkColor color, SkWStream* result) {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000072 SkASSERT(SkColorGetA(color) == 0xFF); // We handle alpha elsewhere.
reed80ea19c2015-05-12 10:37:34 -070073 SkScalar colorScale = SkScalarInvert(0xFF);
74 SkPDFUtils::AppendScalar(SkColorGetR(color) * colorScale, result);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000075 result->writeText(" ");
reed80ea19c2015-05-12 10:37:34 -070076 SkPDFUtils::AppendScalar(SkColorGetG(color) * colorScale, result);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000077 result->writeText(" ");
reed80ea19c2015-05-12 10:37:34 -070078 SkPDFUtils::AppendScalar(SkColorGetB(color) * colorScale, result);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000079 result->writeText(" ");
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000080}
81
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +000082static SkPaint calculate_text_paint(const SkPaint& paint) {
vandebo@chromium.org28be72b2010-11-11 21:37:00 +000083 SkPaint result = paint;
84 if (result.isFakeBoldText()) {
85 SkScalar fakeBoldScale = SkScalarInterpFunc(result.getTextSize(),
86 kStdFakeBoldInterpKeys,
87 kStdFakeBoldInterpValues,
88 kStdFakeBoldInterpLength);
89 SkScalar width = SkScalarMul(result.getTextSize(), fakeBoldScale);
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +000090 if (result.getStyle() == SkPaint::kFill_Style) {
vandebo@chromium.org28be72b2010-11-11 21:37:00 +000091 result.setStyle(SkPaint::kStrokeAndFill_Style);
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +000092 } else {
vandebo@chromium.org28be72b2010-11-11 21:37:00 +000093 width += result.getStrokeWidth();
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +000094 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +000095 result.setStrokeWidth(width);
96 }
97 return result;
98}
99
100// Stolen from measure_text in SkDraw.cpp and then tweaked.
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000101static void align_text(SkDrawCacheProc glyphCacheProc, const SkPaint& paint,
bungeman@google.com9a87cee2011-08-23 17:02:18 +0000102 const uint16_t* glyphs, size_t len,
103 SkScalar* x, SkScalar* y) {
104 if (paint.getTextAlign() == SkPaint::kLeft_Align) {
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000105 return;
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000106 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000107
108 SkMatrix ident;
109 ident.reset();
halcanary96fcdcc2015-08-27 07:41:13 -0700110 SkAutoGlyphCache autoCache(paint, nullptr, &ident);
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000111 SkGlyphCache* cache = autoCache.getCache();
112
ctguil@chromium.org9510ccc2011-07-27 00:10:51 +0000113 const char* start = reinterpret_cast<const char*>(glyphs);
114 const char* stop = reinterpret_cast<const char*>(glyphs + len);
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000115 SkFixed xAdv = 0, yAdv = 0;
116
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000117 // TODO(vandebo): This probably needs to take kerning into account.
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000118 while (start < stop) {
119 const SkGlyph& glyph = glyphCacheProc(cache, &start, 0, 0);
120 xAdv += glyph.fAdvanceX;
121 yAdv += glyph.fAdvanceY;
122 };
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000123 if (paint.getTextAlign() == SkPaint::kLeft_Align) {
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000124 return;
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000125 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000126
127 SkScalar xAdj = SkFixedToScalar(xAdv);
128 SkScalar yAdj = SkFixedToScalar(yAdv);
129 if (paint.getTextAlign() == SkPaint::kCenter_Align) {
130 xAdj = SkScalarHalf(xAdj);
131 yAdj = SkScalarHalf(yAdj);
132 }
133 *x = *x - xAdj;
134 *y = *y - yAdj;
135}
136
robertphillips@google.coma4662862013-11-21 14:24:16 +0000137static int max_glyphid_for_typeface(SkTypeface* typeface) {
reed@google.comfed86bd2013-03-14 15:04:57 +0000138 SkAutoResolveDefaultTypeface autoResolve(typeface);
139 typeface = autoResolve.get();
commit-bot@chromium.org6a4ba5b2013-07-17 21:55:08 +0000140 return typeface->countGlyphs() - 1;
vandebo@chromium.org4e1cc6a2013-01-25 19:27:23 +0000141}
142
143typedef SkAutoSTMalloc<128, uint16_t> SkGlyphStorage;
144
reed@google.comaec40662014-04-18 19:29:07 +0000145static int force_glyph_encoding(const SkPaint& paint, const void* text,
146 size_t len, SkGlyphStorage* storage,
bungeman22edc832014-10-03 07:55:58 -0700147 const uint16_t** glyphIDs) {
vandebo@chromium.org4e1cc6a2013-01-25 19:27:23 +0000148 // Make sure we have a glyph id encoding.
149 if (paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding) {
halcanary96fcdcc2015-08-27 07:41:13 -0700150 int numGlyphs = paint.textToGlyphs(text, len, nullptr);
vandebo@chromium.org4e1cc6a2013-01-25 19:27:23 +0000151 storage->reset(numGlyphs);
152 paint.textToGlyphs(text, len, storage->get());
153 *glyphIDs = storage->get();
154 return numGlyphs;
155 }
156
157 // For user supplied glyph ids we need to validate them.
158 SkASSERT((len & 1) == 0);
reed@google.comaec40662014-04-18 19:29:07 +0000159 int numGlyphs = SkToInt(len / 2);
bungeman22edc832014-10-03 07:55:58 -0700160 const uint16_t* input = static_cast<const uint16_t*>(text);
vandebo@chromium.org4e1cc6a2013-01-25 19:27:23 +0000161
162 int maxGlyphID = max_glyphid_for_typeface(paint.getTypeface());
reed@google.comaec40662014-04-18 19:29:07 +0000163 int validated;
vandebo@chromium.org4e1cc6a2013-01-25 19:27:23 +0000164 for (validated = 0; validated < numGlyphs; ++validated) {
165 if (input[validated] > maxGlyphID) {
166 break;
167 }
168 }
169 if (validated >= numGlyphs) {
bungeman22edc832014-10-03 07:55:58 -0700170 *glyphIDs = static_cast<const uint16_t*>(text);
vandebo@chromium.org4e1cc6a2013-01-25 19:27:23 +0000171 return numGlyphs;
172 }
173
174 // Silently drop anything out of range.
175 storage->reset(numGlyphs);
176 if (validated > 0) {
177 memcpy(storage->get(), input, validated * sizeof(uint16_t));
178 }
179
reed@google.comaec40662014-04-18 19:29:07 +0000180 for (int i = validated; i < numGlyphs; ++i) {
vandebo@chromium.org4e1cc6a2013-01-25 19:27:23 +0000181 storage->get()[i] = input[i];
182 if (input[i] > maxGlyphID) {
183 storage->get()[i] = 0;
184 }
185 }
186 *glyphIDs = storage->get();
187 return numGlyphs;
188}
189
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000190static void set_text_transform(SkScalar x, SkScalar y, SkScalar textSkewX,
191 SkWStream* content) {
192 // Flip the text about the x-axis to account for origin swap and include
193 // the passed parameters.
194 content->writeText("1 0 ");
halcanarybc4696b2015-05-06 10:56:04 -0700195 SkPDFUtils::AppendScalar(0 - textSkewX, content);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000196 content->writeText(" -1 ");
halcanarybc4696b2015-05-06 10:56:04 -0700197 SkPDFUtils::AppendScalar(x, content);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000198 content->writeText(" ");
halcanarybc4696b2015-05-06 10:56:04 -0700199 SkPDFUtils::AppendScalar(y, content);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000200 content->writeText(" Tm\n");
201}
202
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000203// It is important to not confuse GraphicStateEntry with SkPDFGraphicState, the
204// later being our representation of an object in the PDF file.
205struct GraphicStateEntry {
206 GraphicStateEntry();
207
208 // Compare the fields we care about when setting up a new content entry.
209 bool compareInitialState(const GraphicStateEntry& b);
210
211 SkMatrix fMatrix;
212 // We can't do set operations on Paths, though PDF natively supports
213 // intersect. If the clip stack does anything other than intersect,
214 // we have to fall back to the region. Treat fClipStack as authoritative.
215 // See http://code.google.com/p/skia/issues/detail?id=221
216 SkClipStack fClipStack;
217 SkRegion fClipRegion;
218
219 // When emitting the content entry, we will ensure the graphic state
220 // is set to these values first.
221 SkColor fColor;
222 SkScalar fTextScaleX; // Zero means we don't care what the value is.
223 SkPaint::Style fTextFill; // Only if TextScaleX is non-zero.
224 int fShaderIndex;
225 int fGraphicStateIndex;
226
227 // We may change the font (i.e. for Type1 support) within a
halcanary96fcdcc2015-08-27 07:41:13 -0700228 // ContentEntry. This is the one currently in effect, or nullptr if none.
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000229 SkPDFFont* fFont;
230 // In PDF, text size has no default value. It is only valid if fFont is
halcanary96fcdcc2015-08-27 07:41:13 -0700231 // not nullptr.
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000232 SkScalar fTextSize;
233};
234
235GraphicStateEntry::GraphicStateEntry() : fColor(SK_ColorBLACK),
236 fTextScaleX(SK_Scalar1),
237 fTextFill(SkPaint::kFill_Style),
238 fShaderIndex(-1),
239 fGraphicStateIndex(-1),
halcanary96fcdcc2015-08-27 07:41:13 -0700240 fFont(nullptr),
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000241 fTextSize(SK_ScalarNaN) {
242 fMatrix.reset();
243}
244
commit-bot@chromium.orgb000d762014-02-07 19:39:57 +0000245bool GraphicStateEntry::compareInitialState(const GraphicStateEntry& cur) {
246 return fColor == cur.fColor &&
247 fShaderIndex == cur.fShaderIndex &&
248 fGraphicStateIndex == cur.fGraphicStateIndex &&
249 fMatrix == cur.fMatrix &&
250 fClipStack == cur.fClipStack &&
251 (fTextScaleX == 0 ||
252 (fTextScaleX == cur.fTextScaleX && fTextFill == cur.fTextFill));
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000253}
254
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000255class GraphicStackState {
256public:
257 GraphicStackState(const SkClipStack& existingClipStack,
258 const SkRegion& existingClipRegion,
259 SkWStream* contentStream)
260 : fStackDepth(0),
261 fContentStream(contentStream) {
262 fEntries[0].fClipStack = existingClipStack;
263 fEntries[0].fClipRegion = existingClipRegion;
264 }
265
266 void updateClip(const SkClipStack& clipStack, const SkRegion& clipRegion,
vandebo@chromium.org663515b2012-01-05 18:45:27 +0000267 const SkPoint& translation);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000268 void updateMatrix(const SkMatrix& matrix);
269 void updateDrawingState(const GraphicStateEntry& state);
270
271 void drainStack();
272
273private:
274 void push();
275 void pop();
276 GraphicStateEntry* currentEntry() { return &fEntries[fStackDepth]; }
277
278 // Conservative limit on save depth, see impl. notes in PDF 1.4 spec.
279 static const int kMaxStackDepth = 12;
280 GraphicStateEntry fEntries[kMaxStackDepth + 1];
281 int fStackDepth;
282 SkWStream* fContentStream;
283};
284
285void GraphicStackState::drainStack() {
286 while (fStackDepth) {
287 pop();
288 }
289}
290
291void GraphicStackState::push() {
292 SkASSERT(fStackDepth < kMaxStackDepth);
293 fContentStream->writeText("q\n");
294 fStackDepth++;
295 fEntries[fStackDepth] = fEntries[fStackDepth - 1];
296}
297
298void GraphicStackState::pop() {
299 SkASSERT(fStackDepth > 0);
300 fContentStream->writeText("Q\n");
301 fStackDepth--;
302}
303
robertphillips@google.com80214e22012-07-20 15:33:18 +0000304// This function initializes iter to be an iterator on the "stack" argument
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000305// and then skips over the leading entries as specified in prefix. It requires
306// and asserts that "prefix" will be a prefix to "stack."
307static void skip_clip_stack_prefix(const SkClipStack& prefix,
308 const SkClipStack& stack,
robertphillips@google.comc0290622012-07-16 21:20:03 +0000309 SkClipStack::Iter* iter) {
robertphillips@google.com80214e22012-07-20 15:33:18 +0000310 SkClipStack::B2TIter prefixIter(prefix);
311 iter->reset(stack, SkClipStack::Iter::kBottom_IterStart);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000312
bsalomon@google.com8182fa02012-12-04 14:06:06 +0000313 const SkClipStack::Element* prefixEntry;
314 const SkClipStack::Element* iterEntry;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000315
316 for (prefixEntry = prefixIter.next(); prefixEntry;
robertphillips@google.comc0290622012-07-16 21:20:03 +0000317 prefixEntry = prefixIter.next()) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000318 iterEntry = iter->next();
319 SkASSERT(iterEntry);
vandebo@chromium.org8887ede2011-05-25 01:27:52 +0000320 // Because of SkClipStack does internal intersection, the last clip
321 // entry may differ.
ctguil@chromium.org9510ccc2011-07-27 00:10:51 +0000322 if (*prefixEntry != *iterEntry) {
bsalomon@google.com8182fa02012-12-04 14:06:06 +0000323 SkASSERT(prefixEntry->getOp() == SkRegion::kIntersect_Op);
324 SkASSERT(iterEntry->getOp() == SkRegion::kIntersect_Op);
325 SkASSERT(iterEntry->getType() == prefixEntry->getType());
robertphillips@google.comc0290622012-07-16 21:20:03 +0000326 // back up the iterator by one
327 iter->prev();
vandebo@chromium.org8887ede2011-05-25 01:27:52 +0000328 prefixEntry = prefixIter.next();
329 break;
330 }
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000331 }
332
halcanary96fcdcc2015-08-27 07:41:13 -0700333 SkASSERT(prefixEntry == nullptr);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000334}
335
336static void emit_clip(SkPath* clipPath, SkRect* clipRect,
337 SkWStream* contentStream) {
338 SkASSERT(clipPath || clipRect);
339
340 SkPath::FillType clipFill;
341 if (clipPath) {
vandebo@chromium.org683001c2012-05-09 17:17:51 +0000342 SkPDFUtils::EmitPath(*clipPath, SkPaint::kFill_Style, contentStream);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000343 clipFill = clipPath->getFillType();
vandebo@chromium.org3e7b2802011-06-27 18:12:31 +0000344 } else {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000345 SkPDFUtils::AppendRectangle(*clipRect, contentStream);
346 clipFill = SkPath::kWinding_FillType;
347 }
348
349 NOT_IMPLEMENTED(clipFill == SkPath::kInverseEvenOdd_FillType, false);
350 NOT_IMPLEMENTED(clipFill == SkPath::kInverseWinding_FillType, false);
351 if (clipFill == SkPath::kEvenOdd_FillType) {
352 contentStream->writeText("W* n\n");
353 } else {
354 contentStream->writeText("W n\n");
355 }
356}
357
commit-bot@chromium.orgd2623a12013-08-08 02:52:05 +0000358/* Calculate an inverted path's equivalent non-inverted path, given the
359 * canvas bounds.
360 * outPath may alias with invPath (since this is supported by PathOps).
361 */
362static bool calculate_inverse_path(const SkRect& bounds, const SkPath& invPath,
363 SkPath* outPath) {
364 SkASSERT(invPath.isInverseFillType());
365
366 SkPath clipPath;
367 clipPath.addRect(bounds);
368
reedcdb42bb2015-06-26 10:23:07 -0700369 return Op(clipPath, invPath, kIntersect_SkPathOp, outPath);
commit-bot@chromium.orgd2623a12013-08-08 02:52:05 +0000370}
371
fmalita632e92f2015-04-22 15:02:03 -0700372#ifdef SK_PDF_USE_PATHOPS_CLIPPING
commit-bot@chromium.orgd2623a12013-08-08 02:52:05 +0000373// Sanity check the numerical values of the SkRegion ops and PathOps ops
374// enums so region_op_to_pathops_op can do a straight passthrough cast.
375// If these are failing, it may be necessary to make region_op_to_pathops_op
376// do more.
bungeman99fe8222015-08-20 07:57:51 -0700377static_assert(SkRegion::kDifference_Op == (int)kDifference_SkPathOp, "region_pathop_mismatch");
378static_assert(SkRegion::kIntersect_Op == (int)kIntersect_SkPathOp, "region_pathop_mismatch");
379static_assert(SkRegion::kUnion_Op == (int)kUnion_SkPathOp, "region_pathop_mismatch");
380static_assert(SkRegion::kXOR_Op == (int)kXOR_SkPathOp, "region_pathop_mismatch");
381static_assert(SkRegion::kReverseDifference_Op == (int)kReverseDifference_SkPathOp,
382 "region_pathop_mismatch");
commit-bot@chromium.orgd2623a12013-08-08 02:52:05 +0000383
384static SkPathOp region_op_to_pathops_op(SkRegion::Op op) {
385 SkASSERT(op >= 0);
386 SkASSERT(op <= SkRegion::kReverseDifference_Op);
387 return (SkPathOp)op;
388}
389
390/* Uses Path Ops to calculate a vector SkPath clip from a clip stack.
391 * Returns true if successful, or false if not successful.
392 * If successful, the resulting clip is stored in outClipPath.
393 * If not successful, outClipPath is undefined, and a fallback method
394 * should be used.
395 */
396static bool get_clip_stack_path(const SkMatrix& transform,
397 const SkClipStack& clipStack,
398 const SkRegion& clipRegion,
399 SkPath* outClipPath) {
400 outClipPath->reset();
401 outClipPath->setFillType(SkPath::kInverseWinding_FillType);
402
403 const SkClipStack::Element* clipEntry;
404 SkClipStack::Iter iter;
405 iter.reset(clipStack, SkClipStack::Iter::kBottom_IterStart);
406 for (clipEntry = iter.next(); clipEntry; clipEntry = iter.next()) {
407 SkPath entryPath;
408 if (SkClipStack::Element::kEmpty_Type == clipEntry->getType()) {
409 outClipPath->reset();
410 outClipPath->setFillType(SkPath::kInverseWinding_FillType);
411 continue;
commit-bot@chromium.orge5b2af92014-02-16 13:25:24 +0000412 } else {
413 clipEntry->asPath(&entryPath);
commit-bot@chromium.orgd2623a12013-08-08 02:52:05 +0000414 }
415 entryPath.transform(transform);
416
417 if (SkRegion::kReplace_Op == clipEntry->getOp()) {
418 *outClipPath = entryPath;
419 } else {
420 SkPathOp op = region_op_to_pathops_op(clipEntry->getOp());
421 if (!Op(*outClipPath, entryPath, op, outClipPath)) {
422 return false;
423 }
424 }
425 }
426
427 if (outClipPath->isInverseFillType()) {
428 // The bounds are slightly outset to ensure this is correct in the
429 // face of floating-point accuracy and possible SkRegion bitmap
430 // approximations.
431 SkRect clipBounds = SkRect::Make(clipRegion.getBounds());
432 clipBounds.outset(SK_Scalar1, SK_Scalar1);
433 if (!calculate_inverse_path(clipBounds, *outClipPath, outClipPath)) {
434 return false;
435 }
436 }
437 return true;
438}
439#endif
440
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000441// TODO(vandebo): Take advantage of SkClipStack::getSaveCount(), the PDF
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000442// graphic state stack, and the fact that we can know all the clips used
443// on the page to optimize this.
444void GraphicStackState::updateClip(const SkClipStack& clipStack,
445 const SkRegion& clipRegion,
vandebo@chromium.org663515b2012-01-05 18:45:27 +0000446 const SkPoint& translation) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000447 if (clipStack == currentEntry()->fClipStack) {
448 return;
449 }
450
451 while (fStackDepth > 0) {
452 pop();
453 if (clipStack == currentEntry()->fClipStack) {
454 return;
455 }
456 }
457 push();
458
commit-bot@chromium.orgd2623a12013-08-08 02:52:05 +0000459 currentEntry()->fClipStack = clipStack;
460 currentEntry()->fClipRegion = clipRegion;
461
462 SkMatrix transform;
463 transform.setTranslate(translation.fX, translation.fY);
464
fmalita632e92f2015-04-22 15:02:03 -0700465#ifdef SK_PDF_USE_PATHOPS_CLIPPING
commit-bot@chromium.orgd2623a12013-08-08 02:52:05 +0000466 SkPath clipPath;
467 if (get_clip_stack_path(transform, clipStack, clipRegion, &clipPath)) {
halcanary96fcdcc2015-08-27 07:41:13 -0700468 emit_clip(&clipPath, nullptr, fContentStream);
commit-bot@chromium.orgd2623a12013-08-08 02:52:05 +0000469 return;
470 }
471#endif
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000472 // gsState->initialEntry()->fClipStack/Region specifies the clip that has
473 // already been applied. (If this is a top level device, then it specifies
474 // a clip to the content area. If this is a layer, then it specifies
475 // the clip in effect when the layer was created.) There's no need to
476 // reapply that clip; SKCanvas's SkDrawIter will draw anything outside the
477 // initial clip on the parent layer. (This means there's a bug if the user
478 // expands the clip and then uses any xfer mode that uses dst:
479 // http://code.google.com/p/skia/issues/detail?id=228 )
robertphillips@google.comc0290622012-07-16 21:20:03 +0000480 SkClipStack::Iter iter;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000481 skip_clip_stack_prefix(fEntries[0].fClipStack, clipStack, &iter);
482
483 // If the clip stack does anything other than intersect or if it uses
484 // an inverse fill type, we have to fall back to the clip region.
485 bool needRegion = false;
bsalomon@google.com8182fa02012-12-04 14:06:06 +0000486 const SkClipStack::Element* clipEntry;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000487 for (clipEntry = iter.next(); clipEntry; clipEntry = iter.next()) {
vandebo@chromium.org3b416212013-10-30 20:48:05 +0000488 if (clipEntry->getOp() != SkRegion::kIntersect_Op ||
489 clipEntry->isInverseFilled()) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000490 needRegion = true;
491 break;
492 }
493 }
494
495 if (needRegion) {
496 SkPath clipPath;
497 SkAssertResult(clipRegion.getBoundaryPath(&clipPath));
halcanary96fcdcc2015-08-27 07:41:13 -0700498 emit_clip(&clipPath, nullptr, fContentStream);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000499 } else {
500 skip_clip_stack_prefix(fEntries[0].fClipStack, clipStack, &iter);
bsalomon@google.com8182fa02012-12-04 14:06:06 +0000501 const SkClipStack::Element* clipEntry;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000502 for (clipEntry = iter.next(); clipEntry; clipEntry = iter.next()) {
bsalomon@google.com8182fa02012-12-04 14:06:06 +0000503 SkASSERT(clipEntry->getOp() == SkRegion::kIntersect_Op);
504 switch (clipEntry->getType()) {
505 case SkClipStack::Element::kRect_Type: {
506 SkRect translatedClip;
507 transform.mapRect(&translatedClip, clipEntry->getRect());
halcanary96fcdcc2015-08-27 07:41:13 -0700508 emit_clip(nullptr, &translatedClip, fContentStream);
bsalomon@google.com8182fa02012-12-04 14:06:06 +0000509 break;
510 }
commit-bot@chromium.org5a346a82014-02-16 16:01:14 +0000511 default: {
bsalomon@google.com8182fa02012-12-04 14:06:06 +0000512 SkPath translatedPath;
commit-bot@chromium.org5a346a82014-02-16 16:01:14 +0000513 clipEntry->asPath(&translatedPath);
514 translatedPath.transform(transform, &translatedPath);
halcanary96fcdcc2015-08-27 07:41:13 -0700515 emit_clip(&translatedPath, nullptr, fContentStream);
bsalomon@google.com8182fa02012-12-04 14:06:06 +0000516 break;
517 }
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000518 }
519 }
520 }
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000521}
522
523void GraphicStackState::updateMatrix(const SkMatrix& matrix) {
524 if (matrix == currentEntry()->fMatrix) {
525 return;
526 }
527
528 if (currentEntry()->fMatrix.getType() != SkMatrix::kIdentity_Mask) {
529 SkASSERT(fStackDepth > 0);
530 SkASSERT(fEntries[fStackDepth].fClipStack ==
531 fEntries[fStackDepth -1].fClipStack);
532 pop();
533
534 SkASSERT(currentEntry()->fMatrix.getType() == SkMatrix::kIdentity_Mask);
535 }
536 if (matrix.getType() == SkMatrix::kIdentity_Mask) {
537 return;
538 }
539
540 push();
541 SkPDFUtils::AppendTransform(matrix, fContentStream);
542 currentEntry()->fMatrix = matrix;
543}
544
545void GraphicStackState::updateDrawingState(const GraphicStateEntry& state) {
546 // PDF treats a shader as a color, so we only set one or the other.
547 if (state.fShaderIndex >= 0) {
548 if (state.fShaderIndex != currentEntry()->fShaderIndex) {
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000549 SkPDFUtils::ApplyPattern(state.fShaderIndex, fContentStream);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000550 currentEntry()->fShaderIndex = state.fShaderIndex;
551 }
552 } else {
553 if (state.fColor != currentEntry()->fColor ||
554 currentEntry()->fShaderIndex >= 0) {
555 emit_pdf_color(state.fColor, fContentStream);
556 fContentStream->writeText("RG ");
557 emit_pdf_color(state.fColor, fContentStream);
558 fContentStream->writeText("rg\n");
559 currentEntry()->fColor = state.fColor;
560 currentEntry()->fShaderIndex = -1;
561 }
562 }
563
564 if (state.fGraphicStateIndex != currentEntry()->fGraphicStateIndex) {
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000565 SkPDFUtils::ApplyGraphicState(state.fGraphicStateIndex, fContentStream);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000566 currentEntry()->fGraphicStateIndex = state.fGraphicStateIndex;
567 }
568
569 if (state.fTextScaleX) {
570 if (state.fTextScaleX != currentEntry()->fTextScaleX) {
571 SkScalar pdfScale = SkScalarMul(state.fTextScaleX,
572 SkIntToScalar(100));
halcanarybc4696b2015-05-06 10:56:04 -0700573 SkPDFUtils::AppendScalar(pdfScale, fContentStream);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000574 fContentStream->writeText(" Tz\n");
575 currentEntry()->fTextScaleX = state.fTextScaleX;
576 }
577 if (state.fTextFill != currentEntry()->fTextFill) {
bungeman99fe8222015-08-20 07:57:51 -0700578 static_assert(SkPaint::kFill_Style == 0, "enum_must_match_value");
579 static_assert(SkPaint::kStroke_Style == 1, "enum_must_match_value");
580 static_assert(SkPaint::kStrokeAndFill_Style == 2, "enum_must_match_value");
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000581 fContentStream->writeDecAsText(state.fTextFill);
582 fContentStream->writeText(" Tr\n");
583 currentEntry()->fTextFill = state.fTextFill;
584 }
585 }
586}
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000587
reed76033be2015-03-14 10:54:31 -0700588static bool not_supported_for_layers(const SkPaint& layerPaint) {
senorblancob0e89dc2014-10-20 14:03:12 -0700589 // PDF does not support image filters, so render them on CPU.
590 // Note that this rendering is done at "screen" resolution (100dpi), not
591 // printer resolution.
halcanary7a14b312015-10-01 07:28:13 -0700592 // TODO: It may be possible to express some filters natively using PDF
halcanary6950de62015-11-07 05:29:00 -0800593 // to improve quality and file size (https://bug.skia.org/3043)
reed76033be2015-03-14 10:54:31 -0700594
595 // TODO: should we return true if there is a colorfilter?
halcanary96fcdcc2015-08-27 07:41:13 -0700596 return layerPaint.getImageFilter() != nullptr;
reed76033be2015-03-14 10:54:31 -0700597}
598
599SkBaseDevice* SkPDFDevice::onCreateDevice(const CreateInfo& cinfo, const SkPaint* layerPaint) {
halcanary00b7e5e2015-04-15 13:05:18 -0700600 if (cinfo.fForImageFilter ||
601 (layerPaint && not_supported_for_layers(*layerPaint))) {
halcanary96fcdcc2015-08-27 07:41:13 -0700602 return nullptr;
senorblancob0e89dc2014-10-20 14:03:12 -0700603 }
fmalita6987dca2014-11-13 08:33:37 -0800604 SkISize size = SkISize::Make(cinfo.fInfo.width(), cinfo.fInfo.height());
halcanarya1f1ee92015-02-20 06:17:26 -0800605 return SkPDFDevice::Create(size, fRasterDpi, fCanon);
bsalomon@google.come97f0852011-06-17 13:10:25 +0000606}
607
608
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000609struct ContentEntry {
610 GraphicStateEntry fState;
611 SkDynamicMemoryWStream fContent;
commit-bot@chromium.orge0294402013-08-29 22:14:04 +0000612 SkAutoTDelete<ContentEntry> fNext;
edisonn@google.com2e6a69b2013-02-05 23:13:39 +0000613
614 // If the stack is too deep we could get Stack Overflow.
615 // So we manually destruct the object.
616 ~ContentEntry() {
commit-bot@chromium.orge0294402013-08-29 22:14:04 +0000617 ContentEntry* val = fNext.detach();
halcanary96fcdcc2015-08-27 07:41:13 -0700618 while (val != nullptr) {
commit-bot@chromium.orge0294402013-08-29 22:14:04 +0000619 ContentEntry* valNext = val->fNext.detach();
halcanary96fcdcc2015-08-27 07:41:13 -0700620 // When the destructor is called, fNext is nullptr and exits.
edisonn@google.com2e6a69b2013-02-05 23:13:39 +0000621 delete val;
622 val = valNext;
623 }
624 }
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000625};
626
627// A helper class to automatically finish a ContentEntry at the end of a
628// drawing method and maintain the state needed between set up and finish.
vandebo@chromium.org13d14a92011-05-24 23:12:41 +0000629class ScopedContentEntry {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000630public:
vandebo@chromium.org13d14a92011-05-24 23:12:41 +0000631 ScopedContentEntry(SkPDFDevice* device, const SkDraw& draw,
632 const SkPaint& paint, bool hasText = false)
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000633 : fDevice(device),
halcanary96fcdcc2015-08-27 07:41:13 -0700634 fContentEntry(nullptr),
vandebo@chromium.org3b416212013-10-30 20:48:05 +0000635 fXfermode(SkXfermode::kSrcOver_Mode),
halcanary96fcdcc2015-08-27 07:41:13 -0700636 fDstFormXObject(nullptr) {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000637 init(draw.fClipStack, *draw.fClip, *draw.fMatrix, paint, hasText);
638 }
vandebo@chromium.org13d14a92011-05-24 23:12:41 +0000639 ScopedContentEntry(SkPDFDevice* device, const SkClipStack* clipStack,
640 const SkRegion& clipRegion, const SkMatrix& matrix,
641 const SkPaint& paint, bool hasText = false)
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000642 : fDevice(device),
halcanary96fcdcc2015-08-27 07:41:13 -0700643 fContentEntry(nullptr),
vandebo@chromium.org3b416212013-10-30 20:48:05 +0000644 fXfermode(SkXfermode::kSrcOver_Mode),
halcanary96fcdcc2015-08-27 07:41:13 -0700645 fDstFormXObject(nullptr) {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000646 init(clipStack, clipRegion, matrix, paint, hasText);
647 }
648
vandebo@chromium.org13d14a92011-05-24 23:12:41 +0000649 ~ScopedContentEntry() {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000650 if (fContentEntry) {
vandebo@chromium.org3b416212013-10-30 20:48:05 +0000651 SkPath* shape = &fShape;
652 if (shape->isEmpty()) {
halcanary96fcdcc2015-08-27 07:41:13 -0700653 shape = nullptr;
vandebo@chromium.org3b416212013-10-30 20:48:05 +0000654 }
655 fDevice->finishContentEntry(fXfermode, fDstFormXObject, shape);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000656 }
reed@google.comfc641d02012-09-20 17:52:20 +0000657 SkSafeUnref(fDstFormXObject);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000658 }
659
660 ContentEntry* entry() { return fContentEntry; }
vandebo@chromium.org3b416212013-10-30 20:48:05 +0000661
662 /* Returns true when we explicitly need the shape of the drawing. */
663 bool needShape() {
664 switch (fXfermode) {
665 case SkXfermode::kClear_Mode:
666 case SkXfermode::kSrc_Mode:
667 case SkXfermode::kSrcIn_Mode:
668 case SkXfermode::kSrcOut_Mode:
669 case SkXfermode::kDstIn_Mode:
670 case SkXfermode::kDstOut_Mode:
671 case SkXfermode::kSrcATop_Mode:
672 case SkXfermode::kDstATop_Mode:
673 case SkXfermode::kModulate_Mode:
674 return true;
675 default:
676 return false;
677 }
678 }
679
680 /* Returns true unless we only need the shape of the drawing. */
681 bool needSource() {
682 if (fXfermode == SkXfermode::kClear_Mode) {
683 return false;
684 }
685 return true;
686 }
687
688 /* If the shape is different than the alpha component of the content, then
689 * setShape should be called with the shape. In particular, images and
690 * devices have rectangular shape.
691 */
692 void setShape(const SkPath& shape) {
693 fShape = shape;
694 }
695
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000696private:
697 SkPDFDevice* fDevice;
698 ContentEntry* fContentEntry;
699 SkXfermode::Mode fXfermode;
reed@google.comfc641d02012-09-20 17:52:20 +0000700 SkPDFFormXObject* fDstFormXObject;
vandebo@chromium.org3b416212013-10-30 20:48:05 +0000701 SkPath fShape;
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000702
703 void init(const SkClipStack* clipStack, const SkRegion& clipRegion,
704 const SkMatrix& matrix, const SkPaint& paint, bool hasText) {
edisonn@google.com83d8eda2013-10-24 13:19:28 +0000705 // Shape has to be flatten before we get here.
706 if (matrix.hasPerspective()) {
707 NOT_IMPLEMENTED(!matrix.hasPerspective(), false);
vandebo@chromium.orgdc37e202013-10-18 20:16:34 +0000708 return;
709 }
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000710 if (paint.getXfermode()) {
711 paint.getXfermode()->asMode(&fXfermode);
712 }
713 fContentEntry = fDevice->setUpContentEntry(clipStack, clipRegion,
714 matrix, paint, hasText,
715 &fDstFormXObject);
716 }
717};
718
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000719////////////////////////////////////////////////////////////////////////////////
720
halcanary385fe4d2015-08-26 13:07:48 -0700721SkPDFDevice::SkPDFDevice(SkISize pageSize, SkScalar rasterDpi, SkPDFCanon* canon, bool flip)
robertphillips9a53fd72015-06-22 09:46:59 -0700722 : INHERITED(SkSurfaceProps(0, kUnknown_SkPixelGeometry))
723 , fPageSize(pageSize)
halcanarya1f1ee92015-02-20 06:17:26 -0800724 , fContentSize(pageSize)
725 , fExistingClipRegion(SkIRect::MakeSize(pageSize))
halcanary96fcdcc2015-08-27 07:41:13 -0700726 , fLastContentEntry(nullptr)
727 , fLastMarginContentEntry(nullptr)
halcanarya1f1ee92015-02-20 06:17:26 -0800728 , fDrawingArea(kContent_DrawingArea)
halcanary96fcdcc2015-08-27 07:41:13 -0700729 , fClipStack(nullptr)
halcanary385fe4d2015-08-26 13:07:48 -0700730 , fFontGlyphUsage(new SkPDFGlyphSetMap)
halcanarya1f1ee92015-02-20 06:17:26 -0800731 , fRasterDpi(rasterDpi)
732 , fCanon(canon) {
733 SkASSERT(pageSize.width() > 0);
734 SkASSERT(pageSize.height() > 0);
735 fLegacyBitmap.setInfo(
736 SkImageInfo::MakeUnknown(pageSize.width(), pageSize.height()));
737 if (flip) {
738 // Skia generally uses the top left as the origin but PDF
739 // natively has the origin at the bottom left. This matrix
740 // corrects for that. But that only needs to be done once, we
741 // don't do it when layering.
742 fInitialTransform.setTranslate(0, SkIntToScalar(pageSize.fHeight));
743 fInitialTransform.preScale(SK_Scalar1, -SK_Scalar1);
744 } else {
745 fInitialTransform.setIdentity();
746 }
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000747}
748
749SkPDFDevice::~SkPDFDevice() {
vandebo@chromium.org98594282011-07-25 22:34:12 +0000750 this->cleanUp(true);
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000751}
752
753void SkPDFDevice::init() {
commit-bot@chromium.orge0294402013-08-29 22:14:04 +0000754 fContentEntries.free();
halcanary96fcdcc2015-08-27 07:41:13 -0700755 fLastContentEntry = nullptr;
commit-bot@chromium.orge0294402013-08-29 22:14:04 +0000756 fMarginContentEntries.free();
halcanary96fcdcc2015-08-27 07:41:13 -0700757 fLastMarginContentEntry = nullptr;
vandebo@chromium.org98594282011-07-25 22:34:12 +0000758 fDrawingArea = kContent_DrawingArea;
halcanary96fcdcc2015-08-27 07:41:13 -0700759 if (fFontGlyphUsage.get() == nullptr) {
halcanary385fe4d2015-08-26 13:07:48 -0700760 fFontGlyphUsage.reset(new SkPDFGlyphSetMap);
vandebo@chromium.org98594282011-07-25 22:34:12 +0000761 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000762}
763
vandebo@chromium.org98594282011-07-25 22:34:12 +0000764void SkPDFDevice::cleanUp(bool clearFontUsage) {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000765 fGraphicStateResources.unrefAll();
766 fXObjectResources.unrefAll();
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000767 fFontResources.unrefAll();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000768 fShaderResources.unrefAll();
wangxianzhuef6c50a2015-09-17 20:38:02 -0700769 fLinkToURLs.deleteAll();
770 fLinkToDestinations.deleteAll();
epoger@google.comb58772f2013-03-08 09:09:10 +0000771 fNamedDestinations.deleteAll();
reed@google.com2a006c12012-09-19 17:05:55 +0000772
vandebo@chromium.org98594282011-07-25 22:34:12 +0000773 if (clearFontUsage) {
774 fFontGlyphUsage->reset();
775 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000776}
777
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000778void SkPDFDevice::drawPaint(const SkDraw& d, const SkPaint& paint) {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000779 SkPaint newPaint = paint;
halcanarya6814332015-05-27 08:53:36 -0700780 replace_srcmode_on_opaque_paint(&newPaint);
781
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000782 newPaint.setStyle(SkPaint::kFill_Style);
vandebo@chromium.org13d14a92011-05-24 23:12:41 +0000783 ScopedContentEntry content(this, d, newPaint);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000784 internalDrawPaint(newPaint, content.entry());
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000785}
786
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000787void SkPDFDevice::internalDrawPaint(const SkPaint& paint,
788 ContentEntry* contentEntry) {
789 if (!contentEntry) {
790 return;
791 }
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000792 SkRect bbox = SkRect::MakeWH(SkIntToScalar(this->width()),
793 SkIntToScalar(this->height()));
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000794 SkMatrix inverse;
commit-bot@chromium.orgd2cfa742013-09-20 18:58:30 +0000795 if (!contentEntry->fState.fMatrix.invert(&inverse)) {
vandebo@chromium.org386dfc02012-04-17 22:31:52 +0000796 return;
vandebo@chromium.orgb0549902012-04-13 20:45:46 +0000797 }
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000798 inverse.mapRect(&bbox);
799
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000800 SkPDFUtils::AppendRectangle(bbox, &contentEntry->fContent);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000801 SkPDFUtils::PaintPath(paint.getStyle(), SkPath::kWinding_FillType,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000802 &contentEntry->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000803}
804
halcanarya6814332015-05-27 08:53:36 -0700805void SkPDFDevice::drawPoints(const SkDraw& d,
806 SkCanvas::PointMode mode,
807 size_t count,
808 const SkPoint* points,
809 const SkPaint& srcPaint) {
810 SkPaint passedPaint = srcPaint;
811 replace_srcmode_on_opaque_paint(&passedPaint);
812
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000813 if (count == 0) {
814 return;
815 }
816
wangxianzhud76665d2015-07-17 17:23:15 -0700817 if (SkAnnotation* annotation = passedPaint.getAnnotation()) {
818 if (handlePointAnnotation(points, count, *d.fMatrix, annotation)) {
819 return;
820 }
epoger@google.comb58772f2013-03-08 09:09:10 +0000821 }
halcanary05b48e22015-11-16 10:51:21 -0800822 if (excessive_translation(*d.fMatrix)) {
823 SkVector translate; SkMatrix translateMatrix;
824 std::tie(translateMatrix, translate) = untranslate(d);
825 SkDraw drawCopy(d);
826 drawCopy.fMatrix = &translateMatrix;
827 SkTArray<SkPoint> pointsCopy(points, SkToInt(count));
828 SkPoint::Offset(&pointsCopy[0], SkToInt(count), translate);
829 this->drawPoints(drawCopy, mode, count, &pointsCopy[0], srcPaint);
830 return; // NOTE: shader behavior will be off.
831 }
epoger@google.comb58772f2013-03-08 09:09:10 +0000832
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000833 // SkDraw::drawPoints converts to multiple calls to fDevice->drawPath.
834 // We only use this when there's a path effect because of the overhead
835 // of multiple calls to setUpContentEntry it causes.
836 if (passedPaint.getPathEffect()) {
837 if (d.fClip->isEmpty()) {
838 return;
839 }
840 SkDraw pointDraw(d);
841 pointDraw.fDevice = this;
842 pointDraw.drawPoints(mode, count, points, passedPaint, true);
843 return;
844 }
845
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000846 const SkPaint* paint = &passedPaint;
847 SkPaint modifiedPaint;
848
849 if (mode == SkCanvas::kPoints_PointMode &&
850 paint->getStrokeCap() != SkPaint::kRound_Cap) {
851 modifiedPaint = *paint;
852 paint = &modifiedPaint;
853 if (paint->getStrokeWidth()) {
854 // PDF won't draw a single point with square/butt caps because the
855 // orientation is ambiguous. Draw a rectangle instead.
856 modifiedPaint.setStyle(SkPaint::kFill_Style);
857 SkScalar strokeWidth = paint->getStrokeWidth();
858 SkScalar halfStroke = SkScalarHalf(strokeWidth);
859 for (size_t i = 0; i < count; i++) {
860 SkRect r = SkRect::MakeXYWH(points[i].fX, points[i].fY, 0, 0);
861 r.inset(-halfStroke, -halfStroke);
862 drawRect(d, r, modifiedPaint);
863 }
864 return;
865 } else {
866 modifiedPaint.setStrokeCap(SkPaint::kRound_Cap);
867 }
868 }
869
vandebo@chromium.org13d14a92011-05-24 23:12:41 +0000870 ScopedContentEntry content(this, d, *paint);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000871 if (!content.entry()) {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000872 return;
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000873 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000874
875 switch (mode) {
876 case SkCanvas::kPolygon_PointMode:
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000877 SkPDFUtils::MoveTo(points[0].fX, points[0].fY,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000878 &content.entry()->fContent);
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +0000879 for (size_t i = 1; i < count; i++) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000880 SkPDFUtils::AppendLine(points[i].fX, points[i].fY,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000881 &content.entry()->fContent);
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +0000882 }
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000883 SkPDFUtils::StrokePath(&content.entry()->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000884 break;
885 case SkCanvas::kLines_PointMode:
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000886 for (size_t i = 0; i < count/2; i++) {
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +0000887 SkPDFUtils::MoveTo(points[i * 2].fX, points[i * 2].fY,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000888 &content.entry()->fContent);
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +0000889 SkPDFUtils::AppendLine(points[i * 2 + 1].fX,
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000890 points[i * 2 + 1].fY,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000891 &content.entry()->fContent);
892 SkPDFUtils::StrokePath(&content.entry()->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000893 }
894 break;
895 case SkCanvas::kPoints_PointMode:
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000896 SkASSERT(paint->getStrokeCap() == SkPaint::kRound_Cap);
897 for (size_t i = 0; i < count; i++) {
898 SkPDFUtils::MoveTo(points[i].fX, points[i].fY,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000899 &content.entry()->fContent);
900 SkPDFUtils::ClosePath(&content.entry()->fContent);
901 SkPDFUtils::StrokePath(&content.entry()->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000902 }
903 break;
904 default:
905 SkASSERT(false);
906 }
907}
908
wangxianzhud76665d2015-07-17 17:23:15 -0700909static SkPDFDict* create_link_annotation(const SkRect& translatedRect) {
halcanary385fe4d2015-08-26 13:07:48 -0700910 SkAutoTUnref<SkPDFDict> annotation(new SkPDFDict("Annot"));
wangxianzhud76665d2015-07-17 17:23:15 -0700911 annotation->insertName("Subtype", "Link");
912
halcanary385fe4d2015-08-26 13:07:48 -0700913 SkAutoTUnref<SkPDFArray> border(new SkPDFArray);
wangxianzhud76665d2015-07-17 17:23:15 -0700914 border->reserve(3);
915 border->appendInt(0); // Horizontal corner radius.
916 border->appendInt(0); // Vertical corner radius.
917 border->appendInt(0); // Width, 0 = no border.
918 annotation->insertObject("Border", border.detach());
919
halcanary385fe4d2015-08-26 13:07:48 -0700920 SkAutoTUnref<SkPDFArray> rect(new SkPDFArray);
wangxianzhud76665d2015-07-17 17:23:15 -0700921 rect->reserve(4);
922 rect->appendScalar(translatedRect.fLeft);
923 rect->appendScalar(translatedRect.fTop);
924 rect->appendScalar(translatedRect.fRight);
925 rect->appendScalar(translatedRect.fBottom);
926 annotation->insertObject("Rect", rect.detach());
927
928 return annotation.detach();
929}
930
wangxianzhuef6c50a2015-09-17 20:38:02 -0700931static SkPDFDict* create_link_to_url(const SkData* urlData, const SkRect& r) {
wangxianzhud76665d2015-07-17 17:23:15 -0700932 SkAutoTUnref<SkPDFDict> annotation(create_link_annotation(r));
933
934 SkString url(static_cast<const char *>(urlData->data()),
935 urlData->size() - 1);
halcanary385fe4d2015-08-26 13:07:48 -0700936 SkAutoTUnref<SkPDFDict> action(new SkPDFDict("Action"));
wangxianzhud76665d2015-07-17 17:23:15 -0700937 action->insertName("S", "URI");
938 action->insertString("URI", url);
939 annotation->insertObject("A", action.detach());
940 return annotation.detach();
941}
942
wangxianzhuef6c50a2015-09-17 20:38:02 -0700943static SkPDFDict* create_link_named_dest(const SkData* nameData,
944 const SkRect& r) {
wangxianzhud76665d2015-07-17 17:23:15 -0700945 SkAutoTUnref<SkPDFDict> annotation(create_link_annotation(r));
946 SkString name(static_cast<const char *>(nameData->data()),
947 nameData->size() - 1);
948 annotation->insertName("Dest", name);
949 return annotation.detach();
950}
951
halcanarya6814332015-05-27 08:53:36 -0700952void SkPDFDevice::drawRect(const SkDraw& d,
953 const SkRect& rect,
954 const SkPaint& srcPaint) {
955 SkPaint paint = srcPaint;
956 replace_srcmode_on_opaque_paint(&paint);
commit-bot@chromium.org969fd6a2013-05-14 18:16:40 +0000957 SkRect r = rect;
958 r.sort();
959
halcanary05b48e22015-11-16 10:51:21 -0800960 if (excessive_translation(*d.fMatrix)) {
961 SkVector translate; SkMatrix translateMatrix;
962 std::tie(translateMatrix, translate) = untranslate(d);
963 SkDraw drawCopy(d);
964 drawCopy.fMatrix = &translateMatrix;
965 SkRect rectCopy = rect;
966 rectCopy.offset(translate.x(), translate.y());
967 this->drawRect(drawCopy, rectCopy, srcPaint);
968 return; // NOTE: shader behavior will be off.
969 }
970
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000971 if (paint.getPathEffect()) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000972 if (d.fClip->isEmpty()) {
973 return;
974 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000975 SkPath path;
976 path.addRect(r);
halcanary96fcdcc2015-08-27 07:41:13 -0700977 drawPath(d, path, paint, nullptr, true);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000978 return;
979 }
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000980
wangxianzhud76665d2015-07-17 17:23:15 -0700981 if (SkAnnotation* annotation = paint.getAnnotation()) {
982 SkPath path;
983 path.addRect(rect);
wangxianzhuef6c50a2015-09-17 20:38:02 -0700984 if (handlePathAnnotation(path, d, annotation)) {
wangxianzhud76665d2015-07-17 17:23:15 -0700985 return;
986 }
vandebo@chromium.org238be8c2012-07-13 20:06:02 +0000987 }
988
vandebo@chromium.org13d14a92011-05-24 23:12:41 +0000989 ScopedContentEntry content(this, d, paint);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000990 if (!content.entry()) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000991 return;
992 }
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000993 SkPDFUtils::AppendRectangle(r, &content.entry()->fContent);
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +0000994 SkPDFUtils::PaintPath(paint.getStyle(), SkPath::kWinding_FillType,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000995 &content.entry()->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000996}
997
halcanarya6814332015-05-27 08:53:36 -0700998void SkPDFDevice::drawRRect(const SkDraw& draw,
999 const SkRRect& rrect,
1000 const SkPaint& srcPaint) {
1001 SkPaint paint = srcPaint;
1002 replace_srcmode_on_opaque_paint(&paint);
scroggo@google.coma8e33a92013-11-08 18:02:53 +00001003 SkPath path;
1004 path.addRRect(rrect);
halcanary96fcdcc2015-08-27 07:41:13 -07001005 this->drawPath(draw, path, paint, nullptr, true);
scroggo@google.coma8e33a92013-11-08 18:02:53 +00001006}
1007
halcanarya6814332015-05-27 08:53:36 -07001008void SkPDFDevice::drawOval(const SkDraw& draw,
1009 const SkRect& oval,
1010 const SkPaint& srcPaint) {
1011 SkPaint paint = srcPaint;
1012 replace_srcmode_on_opaque_paint(&paint);
reed89443ab2014-06-27 11:34:19 -07001013 SkPath path;
1014 path.addOval(oval);
halcanary96fcdcc2015-08-27 07:41:13 -07001015 this->drawPath(draw, path, paint, nullptr, true);
reed89443ab2014-06-27 11:34:19 -07001016}
1017
halcanarya6814332015-05-27 08:53:36 -07001018void SkPDFDevice::drawPath(const SkDraw& d,
1019 const SkPath& origPath,
1020 const SkPaint& srcPaint,
1021 const SkMatrix* prePathMatrix,
vandebo@chromium.org02cc5aa2011-01-25 22:06:29 +00001022 bool pathIsMutable) {
halcanary05b48e22015-11-16 10:51:21 -08001023 if (excessive_translation(*d.fMatrix)) {
1024 SkVector translate; SkMatrix translateMatrix;
1025 std::tie(translateMatrix, translate) = untranslate(d);
1026 SkDraw drawCopy(d);
1027 drawCopy.fMatrix = &translateMatrix;
1028 SkPath pathCopy(origPath);
1029 pathCopy.offset(translate.x(), translate.y());
1030 this->drawPath(drawCopy, pathCopy, srcPaint, prePathMatrix, true);
1031 return; // NOTE: shader behavior will be off.
1032 }
1033
halcanarya6814332015-05-27 08:53:36 -07001034 SkPaint paint = srcPaint;
1035 replace_srcmode_on_opaque_paint(&paint);
vandebo@chromium.orgff390322011-05-17 18:58:44 +00001036 SkPath modifiedPath;
1037 SkPath* pathPtr = const_cast<SkPath*>(&origPath);
1038
1039 SkMatrix matrix = *d.fMatrix;
1040 if (prePathMatrix) {
1041 if (paint.getPathEffect() || paint.getStyle() != SkPaint::kFill_Style) {
1042 if (!pathIsMutable) {
1043 pathPtr = &modifiedPath;
1044 pathIsMutable = true;
1045 }
1046 origPath.transform(*prePathMatrix, pathPtr);
1047 } else {
commit-bot@chromium.org92362382014-03-18 12:51:48 +00001048 matrix.preConcat(*prePathMatrix);
vandebo@chromium.orgff390322011-05-17 18:58:44 +00001049 }
1050 }
vandebo@chromium.org02cc5aa2011-01-25 22:06:29 +00001051
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +00001052 if (paint.getPathEffect()) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001053 if (d.fClip->isEmpty()) {
1054 return;
1055 }
vandebo@chromium.orgff390322011-05-17 18:58:44 +00001056 if (!pathIsMutable) {
1057 pathPtr = &modifiedPath;
1058 pathIsMutable = true;
1059 }
1060 bool fill = paint.getFillPath(origPath, pathPtr);
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +00001061
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +00001062 SkPaint noEffectPaint(paint);
halcanary96fcdcc2015-08-27 07:41:13 -07001063 noEffectPaint.setPathEffect(nullptr);
vandebo@chromium.orgff390322011-05-17 18:58:44 +00001064 if (fill) {
1065 noEffectPaint.setStyle(SkPaint::kFill_Style);
1066 } else {
1067 noEffectPaint.setStyle(SkPaint::kStroke_Style);
1068 noEffectPaint.setStrokeWidth(0);
1069 }
halcanary96fcdcc2015-08-27 07:41:13 -07001070 drawPath(d, *pathPtr, noEffectPaint, nullptr, true);
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +00001071 return;
1072 }
vandebo@chromium.orgff390322011-05-17 18:58:44 +00001073
edisonn@google.coma9ebd162013-10-07 13:22:21 +00001074 if (handleInversePath(d, origPath, paint, pathIsMutable, prePathMatrix)) {
commit-bot@chromium.org92ffe7d2013-07-31 22:54:31 +00001075 return;
1076 }
commit-bot@chromium.org92ffe7d2013-07-31 22:54:31 +00001077
wangxianzhud76665d2015-07-17 17:23:15 -07001078 if (SkAnnotation* annotation = paint.getAnnotation()) {
wangxianzhuef6c50a2015-09-17 20:38:02 -07001079 if (handlePathAnnotation(*pathPtr, d, annotation)) {
wangxianzhud76665d2015-07-17 17:23:15 -07001080 return;
1081 }
vandebo@chromium.org238be8c2012-07-13 20:06:02 +00001082 }
1083
edisonn@google.coma9ebd162013-10-07 13:22:21 +00001084 ScopedContentEntry content(this, d.fClipStack, *d.fClip, matrix, paint);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001085 if (!content.entry()) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001086 return;
1087 }
halcanary8b2bc252015-10-06 09:41:47 -07001088 bool consumeDegeratePathSegments =
1089 paint.getStyle() == SkPaint::kFill_Style ||
1090 (paint.getStrokeCap() != SkPaint::kRound_Cap &&
1091 paint.getStrokeCap() != SkPaint::kSquare_Cap);
vandebo@chromium.org683001c2012-05-09 17:17:51 +00001092 SkPDFUtils::EmitPath(*pathPtr, paint.getStyle(),
halcanary8b2bc252015-10-06 09:41:47 -07001093 consumeDegeratePathSegments,
vandebo@chromium.org683001c2012-05-09 17:17:51 +00001094 &content.entry()->fContent);
vandebo@chromium.orgff390322011-05-17 18:58:44 +00001095 SkPDFUtils::PaintPath(paint.getStyle(), pathPtr->getFillType(),
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001096 &content.entry()->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001097}
1098
halcanary7a14b312015-10-01 07:28:13 -07001099void SkPDFDevice::drawBitmapRect(const SkDraw& draw,
1100 const SkBitmap& bitmap,
1101 const SkRect* src,
1102 const SkRect& dst,
1103 const SkPaint& srcPaint,
1104 SkCanvas::SrcRectConstraint constraint) {
1105 const SkImage* image = fCanon->bitmapToImage(bitmap);
1106 if (!image) {
1107 return;
1108 }
1109 // ownership of this image is retained by the canon.
1110 this->drawImageRect(draw, image, src, dst, srcPaint, constraint);
1111}
1112
1113void SkPDFDevice::drawBitmap(const SkDraw& d,
1114 const SkBitmap& bitmap,
1115 const SkMatrix& matrix,
1116 const SkPaint& srcPaint) {
halcanarya6814332015-05-27 08:53:36 -07001117 SkPaint paint = srcPaint;
1118 if (bitmap.isOpaque()) {
1119 replace_srcmode_on_opaque_paint(&paint);
1120 }
1121
halcanary7a14b312015-10-01 07:28:13 -07001122 if (d.fClip->isEmpty()) {
1123 return;
1124 }
edisonn@google.com2ae67e72013-02-12 01:06:38 +00001125
halcanary7a14b312015-10-01 07:28:13 -07001126 SkMatrix transform = matrix;
1127 transform.postConcat(*d.fMatrix);
1128 const SkImage* image = fCanon->bitmapToImage(bitmap);
1129 if (!image) {
1130 return;
1131 }
1132 this->internalDrawImage(transform, d.fClipStack, *d.fClip, image, nullptr,
1133 paint);
1134}
1135
1136void SkPDFDevice::drawSprite(const SkDraw& d,
1137 const SkBitmap& bitmap,
1138 int x,
1139 int y,
1140 const SkPaint& srcPaint) {
1141 SkPaint paint = srcPaint;
1142 if (bitmap.isOpaque()) {
1143 replace_srcmode_on_opaque_paint(&paint);
1144 }
1145
1146 if (d.fClip->isEmpty()) {
1147 return;
1148 }
1149
1150 SkMatrix matrix;
1151 matrix.setTranslate(SkIntToScalar(x), SkIntToScalar(y));
1152 const SkImage* image = fCanon->bitmapToImage(bitmap);
1153 if (!image) {
1154 return;
1155 }
1156 this->internalDrawImage(matrix, d.fClipStack, *d.fClip, image, nullptr,
1157 paint);
1158}
1159
1160void SkPDFDevice::drawImage(const SkDraw& draw,
1161 const SkImage* image,
1162 SkScalar x,
1163 SkScalar y,
1164 const SkPaint& srcPaint) {
1165 SkPaint paint = srcPaint;
1166 if (!image) {
1167 return;
1168 }
1169 if (image->isOpaque()) {
1170 replace_srcmode_on_opaque_paint(&paint);
1171 }
1172 if (draw.fClip->isEmpty()) {
1173 return;
1174 }
1175 SkMatrix transform = SkMatrix::MakeTrans(x, y);
1176 transform.postConcat(*draw.fMatrix);
1177 this->internalDrawImage(transform, draw.fClipStack, *draw.fClip, image,
1178 nullptr, paint);
1179}
1180
1181void SkPDFDevice::drawImageRect(const SkDraw& draw,
1182 const SkImage* image,
1183 const SkRect* src,
1184 const SkRect& dst,
1185 const SkPaint& srcPaint,
1186 SkCanvas::SrcRectConstraint constraint) {
1187 if (!image) {
1188 return;
1189 }
1190 if (draw.fClip->isEmpty()) {
1191 return;
1192 }
1193 SkPaint paint = srcPaint;
1194 if (image->isOpaque()) {
1195 replace_srcmode_on_opaque_paint(&paint);
1196 }
1197 // TODO: this code path must be updated to respect the flags parameter
1198 SkMatrix matrix;
1199 SkRect tmpSrc, tmpDst;
1200 SkRect imageBounds = SkRect::Make(image->bounds());
edisonn@google.com2ae67e72013-02-12 01:06:38 +00001201
1202 // Compute matrix from the two rectangles
1203 if (src) {
1204 tmpSrc = *src;
1205 } else {
halcanary7a14b312015-10-01 07:28:13 -07001206 tmpSrc = imageBounds;
edisonn@google.com2ae67e72013-02-12 01:06:38 +00001207 }
1208 matrix.setRectToRect(tmpSrc, dst, SkMatrix::kFill_ScaleToFit);
1209
edisonn@google.com2ae67e72013-02-12 01:06:38 +00001210 // clip the tmpSrc to the bounds of the bitmap, and recompute dstRect if
1211 // needed (if the src was clipped). No check needed if src==null.
halcanary7a14b312015-10-01 07:28:13 -07001212 SkAutoTUnref<const SkImage> autoImageUnref;
edisonn@google.com2ae67e72013-02-12 01:06:38 +00001213 if (src) {
halcanary7a14b312015-10-01 07:28:13 -07001214 if (!imageBounds.contains(*src)) {
1215 if (!tmpSrc.intersect(imageBounds)) {
edisonn@google.com2ae67e72013-02-12 01:06:38 +00001216 return; // nothing to draw
1217 }
1218 // recompute dst, based on the smaller tmpSrc
1219 matrix.mapRect(&tmpDst, tmpSrc);
1220 }
1221
1222 // since we may need to clamp to the borders of the src rect within
1223 // the bitmap, we extract a subset.
edisonn@google.com2ae67e72013-02-12 01:06:38 +00001224 SkIRect srcIR;
1225 tmpSrc.roundOut(&srcIR);
halcanary7a14b312015-10-01 07:28:13 -07001226
1227 autoImageUnref.reset(image->newSubset(srcIR));
1228 if (!autoImageUnref) {
edisonn@google.com2ae67e72013-02-12 01:06:38 +00001229 return;
1230 }
halcanary7a14b312015-10-01 07:28:13 -07001231 image = autoImageUnref;
edisonn@google.com2ae67e72013-02-12 01:06:38 +00001232 // Since we did an extract, we need to adjust the matrix accordingly
1233 SkScalar dx = 0, dy = 0;
1234 if (srcIR.fLeft > 0) {
1235 dx = SkIntToScalar(srcIR.fLeft);
1236 }
1237 if (srcIR.fTop > 0) {
1238 dy = SkIntToScalar(srcIR.fTop);
1239 }
1240 if (dx || dy) {
1241 matrix.preTranslate(dx, dy);
1242 }
1243 }
halcanary7a14b312015-10-01 07:28:13 -07001244 matrix.postConcat(*draw.fMatrix);
1245 this->internalDrawImage(matrix, draw.fClipStack, *draw.fClip, image,
1246 nullptr, paint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001247}
1248
halcanarybb264b72015-04-07 10:40:03 -07001249// Create a PDF string. Maximum length (in bytes) is 65,535.
1250// @param input A string value.
1251// @param len The length of the input array.
1252// @param wideChars True iff the upper byte in each uint16_t is
1253// significant and should be encoded and not
1254// discarded. If true, the upper byte is encoded
1255// first. Otherwise, we assert the upper byte is
1256// zero.
1257static SkString format_wide_string(const uint16_t* input,
1258 size_t len,
1259 bool wideChars) {
1260 if (wideChars) {
1261 SkASSERT(2 * len < 65535);
1262 static const char gHex[] = "0123456789ABCDEF";
1263 SkString result(4 * len + 2);
1264 result[0] = '<';
1265 for (size_t i = 0; i < len; i++) {
1266 result[4 * i + 1] = gHex[(input[i] >> 12) & 0xF];
1267 result[4 * i + 2] = gHex[(input[i] >> 8) & 0xF];
1268 result[4 * i + 3] = gHex[(input[i] >> 4) & 0xF];
1269 result[4 * i + 4] = gHex[(input[i] ) & 0xF];
1270 }
1271 result[4 * len + 1] = '>';
1272 return result;
1273 } else {
1274 SkASSERT(len <= 65535);
1275 SkString tmp(len);
1276 for (size_t i = 0; i < len; i++) {
1277 SkASSERT(0 == input[i] >> 8);
1278 tmp[i] = static_cast<uint8_t>(input[i]);
1279 }
halcanarybc4696b2015-05-06 10:56:04 -07001280 return SkPDFUtils::FormatString(tmp.c_str(), tmp.size());
halcanarybb264b72015-04-07 10:40:03 -07001281 }
1282}
1283
halcanary66a82f32015-10-12 13:05:04 -07001284static void draw_transparent_text(SkPDFDevice* device,
1285 const SkDraw& d,
1286 const void* text, size_t len,
1287 SkScalar x, SkScalar y,
1288 const SkPaint& srcPaint) {
1289
1290 SkPaint transparent;
1291 if (!SkPDFFont::CanEmbedTypeface(transparent.getTypeface(),
1292 device->getCanon())) {
1293 SkDEBUGFAIL("default typeface should be embeddable");
1294 return; // Avoid infinite loop in release.
1295 }
1296 transparent.setTextSize(srcPaint.getTextSize());
1297 transparent.setColor(SK_ColorTRANSPARENT);
1298 switch (srcPaint.getTextEncoding()) {
1299 case SkPaint::kGlyphID_TextEncoding: {
1300 // Since a glyphId<->Unicode mapping is typeface-specific,
1301 // map back to Unicode first.
1302 size_t glyphCount = len / 2;
1303 SkAutoTMalloc<SkUnichar> unichars(glyphCount);
1304 srcPaint.glyphsToUnichars(
1305 (const uint16_t*)text, SkToInt(glyphCount), &unichars[0]);
1306 transparent.setTextEncoding(SkPaint::kUTF32_TextEncoding);
1307 device->drawText(d, &unichars[0],
1308 glyphCount * sizeof(SkUnichar),
1309 x, y, transparent);
1310 break;
1311 }
1312 case SkPaint::kUTF8_TextEncoding:
1313 case SkPaint::kUTF16_TextEncoding:
1314 case SkPaint::kUTF32_TextEncoding:
1315 transparent.setTextEncoding(srcPaint.getTextEncoding());
1316 device->drawText(d, text, len, x, y, transparent);
1317 break;
1318 default:
1319 SkFAIL("unknown text encoding");
1320 }
1321}
1322
1323
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00001324void SkPDFDevice::drawText(const SkDraw& d, const void* text, size_t len,
halcanarya6814332015-05-27 08:53:36 -07001325 SkScalar x, SkScalar y, const SkPaint& srcPaint) {
halcanary05b48e22015-11-16 10:51:21 -08001326 if (excessive_translation(*d.fMatrix)) {
1327 SkVector translate; SkMatrix translateMatrix;
1328 std::tie(translateMatrix, translate) = untranslate(d);
1329 SkDraw drawCopy(d);
1330 drawCopy.fMatrix = &translateMatrix;
1331 this->drawText(drawCopy, text, len, x + translate.x(),
1332 y + translate.y(), srcPaint);
1333 return; // NOTE: shader behavior will be off.
1334 }
1335
halcanary66a82f32015-10-12 13:05:04 -07001336 if (!SkPDFFont::CanEmbedTypeface(srcPaint.getTypeface(), fCanon)) {
halcanary6950de62015-11-07 05:29:00 -08001337 // https://bug.skia.org/3866
halcanary66a82f32015-10-12 13:05:04 -07001338 SkPath path;
1339 srcPaint.getTextPath(text, len, x, y, &path);
1340 this->drawPath(d, path, srcPaint, &SkMatrix::I(), true);
1341 // Draw text transparently to make it copyable/searchable/accessable.
1342 draw_transparent_text(this, d, text, len, x, y, srcPaint);
1343 return;
1344 }
halcanarya6814332015-05-27 08:53:36 -07001345 SkPaint paint = srcPaint;
1346 replace_srcmode_on_opaque_paint(&paint);
1347
halcanary96fcdcc2015-08-27 07:41:13 -07001348 NOT_IMPLEMENTED(paint.getMaskFilter() != nullptr, false);
1349 if (paint.getMaskFilter() != nullptr) {
edisonn@google.comb62f93f2013-03-24 18:05:10 +00001350 // Don't pretend we support drawing MaskFilters, it makes for artifacts
1351 // making text unreadable (e.g. same text twice when using CSS shadows).
1352 return;
1353 }
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001354 SkPaint textPaint = calculate_text_paint(paint);
vandebo@chromium.org13d14a92011-05-24 23:12:41 +00001355 ScopedContentEntry content(this, d, textPaint, true);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001356 if (!content.entry()) {
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +00001357 return;
1358 }
1359
vandebo@chromium.org4e1cc6a2013-01-25 19:27:23 +00001360 SkGlyphStorage storage(0);
halcanary96fcdcc2015-08-27 07:41:13 -07001361 const uint16_t* glyphIDs = nullptr;
reed@google.comaec40662014-04-18 19:29:07 +00001362 int numGlyphs = force_glyph_encoding(paint, text, len, &storage, &glyphIDs);
vandebo@chromium.org4e1cc6a2013-01-25 19:27:23 +00001363 textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00001364
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00001365 SkDrawCacheProc glyphCacheProc = textPaint.getDrawCacheProc();
bungeman@google.com9a87cee2011-08-23 17:02:18 +00001366 align_text(glyphCacheProc, textPaint, glyphIDs, numGlyphs, &x, &y);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001367 content.entry()->fContent.writeText("BT\n");
1368 set_text_transform(x, y, textPaint.getTextSkewX(),
1369 &content.entry()->fContent);
reed@google.comaec40662014-04-18 19:29:07 +00001370 int consumedGlyphCount = 0;
halcanary2f912f32014-10-16 09:53:20 -07001371
1372 SkTDArray<uint16_t> glyphIDsCopy(glyphIDs, numGlyphs);
1373
vandebo@chromium.org2a22e102011-01-25 21:01:34 +00001374 while (numGlyphs > consumedGlyphCount) {
robertphillips8e0c1502015-07-07 10:28:43 -07001375 this->updateFont(textPaint, glyphIDs[consumedGlyphCount], content.entry());
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001376 SkPDFFont* font = content.entry()->fState.fFont;
halcanary2f912f32014-10-16 09:53:20 -07001377
1378 int availableGlyphs = font->glyphsToPDFFontEncoding(
1379 glyphIDsCopy.begin() + consumedGlyphCount,
1380 numGlyphs - consumedGlyphCount);
1381 fFontGlyphUsage->noteGlyphUsage(
1382 font, glyphIDsCopy.begin() + consumedGlyphCount,
1383 availableGlyphs);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +00001384 SkString encodedString =
halcanarybb264b72015-04-07 10:40:03 -07001385 format_wide_string(glyphIDsCopy.begin() + consumedGlyphCount,
1386 availableGlyphs, font->multiByteGlyphs());
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001387 content.entry()->fContent.writeText(encodedString.c_str());
vandebo@chromium.org01294102011-02-28 19:52:18 +00001388 consumedGlyphCount += availableGlyphs;
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001389 content.entry()->fContent.writeText(" Tj\n");
vandebo@chromium.org2a22e102011-01-25 21:01:34 +00001390 }
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001391 content.entry()->fContent.writeText("ET\n");
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001392}
1393
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +00001394void SkPDFDevice::drawPosText(const SkDraw& d, const void* text, size_t len,
fmalita05c4a432014-09-29 06:29:53 -07001395 const SkScalar pos[], int scalarsPerPos,
halcanarya6814332015-05-27 08:53:36 -07001396 const SkPoint& offset, const SkPaint& srcPaint) {
halcanary05b48e22015-11-16 10:51:21 -08001397 if (excessive_translation(*d.fMatrix)) {
1398 SkVector translate; SkMatrix translateMatrix;
1399 std::tie(translateMatrix, translate) = untranslate(d);
1400 SkDraw drawCopy(d);
1401 drawCopy.fMatrix = &translateMatrix;
1402 SkPoint offsetCopy = offset;
1403 SkPoint::Offset(&offsetCopy, 1, translate.x(), translate.y());
1404 this->drawPosText(drawCopy, text, len, pos, scalarsPerPos, offsetCopy,
1405 srcPaint);
1406 return; // NOTE: shader behavior will be off.
1407 }
1408
halcanary66a82f32015-10-12 13:05:04 -07001409 if (!SkPDFFont::CanEmbedTypeface(srcPaint.getTypeface(), fCanon)) {
1410 const SkPoint* positions = reinterpret_cast<const SkPoint*>(pos);
1411 SkAutoTMalloc<SkPoint> positionsBuffer;
1412 if (2 != scalarsPerPos) {
1413 int glyphCount = srcPaint.textToGlyphs(text, len, NULL);
1414 positionsBuffer.reset(glyphCount);
1415 for (int i = 0; i < glyphCount; ++i) {
1416 positionsBuffer[i].set(pos[i], 0.0f);
1417 }
1418 positions = &positionsBuffer[0];
1419 }
1420 SkPath path;
1421 srcPaint.getPosTextPath(text, len, positions, &path);
1422 SkMatrix matrix;
1423 matrix.setTranslate(offset);
1424 this->drawPath(d, path, srcPaint, &matrix, true);
1425 // Draw text transparently to make it copyable/searchable/accessable.
1426 draw_transparent_text(
1427 this, d, text, len, offset.x() + positions[0].x(),
1428 offset.y() + positions[0].y(), srcPaint);
1429 return;
1430 }
1431
halcanarya6814332015-05-27 08:53:36 -07001432 SkPaint paint = srcPaint;
1433 replace_srcmode_on_opaque_paint(&paint);
1434
halcanary96fcdcc2015-08-27 07:41:13 -07001435 NOT_IMPLEMENTED(paint.getMaskFilter() != nullptr, false);
1436 if (paint.getMaskFilter() != nullptr) {
edisonn@google.comb62f93f2013-03-24 18:05:10 +00001437 // Don't pretend we support drawing MaskFilters, it makes for artifacts
1438 // making text unreadable (e.g. same text twice when using CSS shadows).
1439 return;
1440 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00001441 SkASSERT(1 == scalarsPerPos || 2 == scalarsPerPos);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001442 SkPaint textPaint = calculate_text_paint(paint);
vandebo@chromium.org13d14a92011-05-24 23:12:41 +00001443 ScopedContentEntry content(this, d, textPaint, true);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001444 if (!content.entry()) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001445 return;
1446 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00001447
vandebo@chromium.org4e1cc6a2013-01-25 19:27:23 +00001448 SkGlyphStorage storage(0);
halcanary96fcdcc2015-08-27 07:41:13 -07001449 const uint16_t* glyphIDs = nullptr;
bungeman22edc832014-10-03 07:55:58 -07001450 size_t numGlyphs = force_glyph_encoding(paint, text, len, &storage, &glyphIDs);
vandebo@chromium.org4e1cc6a2013-01-25 19:27:23 +00001451 textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00001452
1453 SkDrawCacheProc glyphCacheProc = textPaint.getDrawCacheProc();
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001454 content.entry()->fContent.writeText("BT\n");
robertphillips8e0c1502015-07-07 10:28:43 -07001455 this->updateFont(textPaint, glyphIDs[0], content.entry());
vandebo@chromium.org2a22e102011-01-25 21:01:34 +00001456 for (size_t i = 0; i < numGlyphs; i++) {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001457 SkPDFFont* font = content.entry()->fState.fFont;
vandebo@chromium.org01294102011-02-28 19:52:18 +00001458 uint16_t encodedValue = glyphIDs[i];
1459 if (font->glyphsToPDFFontEncoding(&encodedValue, 1) != 1) {
bungeman22edc832014-10-03 07:55:58 -07001460 // The current pdf font cannot encode the current glyph.
1461 // Try to get a pdf font which can encode the current glyph.
robertphillips8e0c1502015-07-07 10:28:43 -07001462 this->updateFont(textPaint, glyphIDs[i], content.entry());
bungeman22edc832014-10-03 07:55:58 -07001463 font = content.entry()->fState.fFont;
1464 if (font->glyphsToPDFFontEncoding(&encodedValue, 1) != 1) {
1465 SkDEBUGFAIL("PDF could not encode glyph.");
1466 continue;
1467 }
vandebo@chromium.org2a22e102011-01-25 21:01:34 +00001468 }
bungeman22edc832014-10-03 07:55:58 -07001469
vandebo@chromium.org98594282011-07-25 22:34:12 +00001470 fFontGlyphUsage->noteGlyphUsage(font, &encodedValue, 1);
fmalita05c4a432014-09-29 06:29:53 -07001471 SkScalar x = offset.x() + pos[i * scalarsPerPos];
1472 SkScalar y = offset.y() + (2 == scalarsPerPos ? pos[i * scalarsPerPos + 1] : 0);
1473
bungeman@google.com9a87cee2011-08-23 17:02:18 +00001474 align_text(glyphCacheProc, textPaint, glyphIDs + i, 1, &x, &y);
bungeman22edc832014-10-03 07:55:58 -07001475 set_text_transform(x, y, textPaint.getTextSkewX(), &content.entry()->fContent);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +00001476 SkString encodedString =
halcanarybb264b72015-04-07 10:40:03 -07001477 format_wide_string(&encodedValue, 1, font->multiByteGlyphs());
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001478 content.entry()->fContent.writeText(encodedString.c_str());
1479 content.entry()->fContent.writeText(" Tj\n");
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00001480 }
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001481 content.entry()->fContent.writeText("ET\n");
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001482}
1483
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +00001484void SkPDFDevice::drawVertices(const SkDraw& d, SkCanvas::VertexMode,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001485 int vertexCount, const SkPoint verts[],
1486 const SkPoint texs[], const SkColor colors[],
1487 SkXfermode* xmode, const uint16_t indices[],
1488 int indexCount, const SkPaint& paint) {
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +00001489 if (d.fClip->isEmpty()) {
1490 return;
1491 }
reed@google.com85e143c2013-12-30 15:51:25 +00001492 // TODO: implement drawVertices
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001493}
1494
wangxianzhuef6c50a2015-09-17 20:38:02 -07001495struct RectWithData {
1496 SkRect rect;
1497 SkAutoTUnref<const SkData> data;
1498
1499 RectWithData(const SkRect& rect, const SkData* data)
1500 : rect(rect), data(SkRef(data)) {}
1501};
1502
1503struct NamedDestination {
1504 SkAutoTUnref<const SkData> nameData;
1505 SkPoint point;
1506
1507 NamedDestination(const SkData* nameData, const SkPoint& point)
1508 : nameData(SkRef(nameData)), point(point) {}
1509};
1510
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001511void SkPDFDevice::drawDevice(const SkDraw& d, SkBaseDevice* device,
1512 int x, int y, const SkPaint& paint) {
fmalita6987dca2014-11-13 08:33:37 -08001513 // our onCreateCompatibleDevice() always creates SkPDFDevice subclasses.
vandebo@chromium.orgee7a9562011-05-24 17:38:01 +00001514 SkPDFDevice* pdfDevice = static_cast<SkPDFDevice*>(device);
wangxianzhuef6c50a2015-09-17 20:38:02 -07001515
1516 SkScalar scalarX = SkIntToScalar(x);
1517 SkScalar scalarY = SkIntToScalar(y);
1518 for (RectWithData* link : pdfDevice->fLinkToURLs) {
1519 fLinkToURLs.push(new RectWithData(
1520 link->rect.makeOffset(scalarX, scalarY), link->data));
1521 }
1522 for (RectWithData* link : pdfDevice->fLinkToDestinations) {
1523 fLinkToDestinations.push(new RectWithData(
1524 link->rect.makeOffset(scalarX, scalarY), link->data));
1525 }
1526 for (NamedDestination* d : pdfDevice->fNamedDestinations) {
1527 fNamedDestinations.push(new NamedDestination(
1528 d->nameData, d->point + SkPoint::Make(scalarX, scalarY)));
1529 }
1530
ctguil@chromium.orgf4ff39c2011-05-24 19:55:05 +00001531 if (pdfDevice->isContentEmpty()) {
vandebo@chromium.orgee7a9562011-05-24 17:38:01 +00001532 return;
1533 }
1534
vandebo@chromium.org1aef2ed2011-02-03 21:46:10 +00001535 SkMatrix matrix;
reed@google.coma6d59f62011-03-07 21:29:21 +00001536 matrix.setTranslate(SkIntToScalar(x), SkIntToScalar(y));
vandebo@chromium.org13d14a92011-05-24 23:12:41 +00001537 ScopedContentEntry content(this, d.fClipStack, *d.fClip, matrix, paint);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001538 if (!content.entry()) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001539 return;
1540 }
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001541 if (content.needShape()) {
1542 SkPath shape;
1543 shape.addRect(SkRect::MakeXYWH(SkIntToScalar(x), SkIntToScalar(y),
vandebo@chromium.orgfd3c8c22013-10-30 21:00:47 +00001544 SkIntToScalar(device->width()),
1545 SkIntToScalar(device->height())));
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001546 content.setShape(shape);
1547 }
1548 if (!content.needSource()) {
1549 return;
1550 }
vandebo@chromium.org1aef2ed2011-02-03 21:46:10 +00001551
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001552 SkAutoTUnref<SkPDFFormXObject> xObject(new SkPDFFormXObject(pdfDevice));
1553 SkPDFUtils::DrawFormXObject(this->addXObjectResource(xObject.get()),
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001554 &content.entry()->fContent);
vandebo@chromium.org98594282011-07-25 22:34:12 +00001555
1556 // Merge glyph sets from the drawn device.
1557 fFontGlyphUsage->merge(pdfDevice->getFontGlyphUsage());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001558}
1559
reed89443ab2014-06-27 11:34:19 -07001560SkImageInfo SkPDFDevice::imageInfo() const {
1561 return fLegacyBitmap.info();
1562}
1563
robertphillips@google.com40a1ae42012-07-13 15:36:15 +00001564void SkPDFDevice::onAttachToCanvas(SkCanvas* canvas) {
1565 INHERITED::onAttachToCanvas(canvas);
1566
1567 // Canvas promises that this ptr is valid until onDetachFromCanvas is called
1568 fClipStack = canvas->getClipStack();
1569}
1570
1571void SkPDFDevice::onDetachFromCanvas() {
1572 INHERITED::onDetachFromCanvas();
1573
halcanary96fcdcc2015-08-27 07:41:13 -07001574 fClipStack = nullptr;
robertphillips@google.com40a1ae42012-07-13 15:36:15 +00001575}
1576
reed4a8126e2014-09-22 07:29:03 -07001577SkSurface* SkPDFDevice::newSurface(const SkImageInfo& info, const SkSurfaceProps& props) {
1578 return SkSurface::NewRaster(info, &props);
reed89443ab2014-06-27 11:34:19 -07001579}
1580
ctguil@chromium.org8dcf74f2011-07-12 21:56:27 +00001581ContentEntry* SkPDFDevice::getLastContentEntry() {
1582 if (fDrawingArea == kContent_DrawingArea) {
1583 return fLastContentEntry;
1584 } else {
1585 return fLastMarginContentEntry;
1586 }
1587}
1588
commit-bot@chromium.orge0294402013-08-29 22:14:04 +00001589SkAutoTDelete<ContentEntry>* SkPDFDevice::getContentEntries() {
ctguil@chromium.org8dcf74f2011-07-12 21:56:27 +00001590 if (fDrawingArea == kContent_DrawingArea) {
ctguil@chromium.org9510ccc2011-07-27 00:10:51 +00001591 return &fContentEntries;
ctguil@chromium.org8dcf74f2011-07-12 21:56:27 +00001592 } else {
ctguil@chromium.org9510ccc2011-07-27 00:10:51 +00001593 return &fMarginContentEntries;
ctguil@chromium.org8dcf74f2011-07-12 21:56:27 +00001594 }
1595}
1596
1597void SkPDFDevice::setLastContentEntry(ContentEntry* contentEntry) {
1598 if (fDrawingArea == kContent_DrawingArea) {
1599 fLastContentEntry = contentEntry;
1600 } else {
1601 fLastMarginContentEntry = contentEntry;
1602 }
1603}
1604
1605void SkPDFDevice::setDrawingArea(DrawingArea drawingArea) {
ctguil@chromium.org9510ccc2011-07-27 00:10:51 +00001606 // A ScopedContentEntry only exists during the course of a draw call, so
1607 // this can't be called while a ScopedContentEntry exists.
ctguil@chromium.org8dcf74f2011-07-12 21:56:27 +00001608 fDrawingArea = drawingArea;
1609}
1610
halcanary2b861552015-04-09 13:27:40 -07001611SkPDFDict* SkPDFDevice::createResourceDict() const {
1612 SkTDArray<SkPDFObject*> fonts;
1613 fonts.setReserve(fFontResources.count());
1614 for (SkPDFFont* font : fFontResources) {
1615 fonts.push(font);
vandebo@chromium.orgfc166672013-07-22 18:31:24 +00001616 }
halcanary2b861552015-04-09 13:27:40 -07001617 return SkPDFResourceDict::Create(
1618 &fGraphicStateResources,
1619 &fShaderResources,
1620 &fXObjectResources,
1621 &fonts);
vandebo@chromium.orgfc166672013-07-22 18:31:24 +00001622}
1623
vandebo@chromium.orgf0ec2662011-05-29 05:55:42 +00001624const SkTDArray<SkPDFFont*>& SkPDFDevice::getFontResources() const {
1625 return fFontResources;
1626}
1627
reed@google.com2a006c12012-09-19 17:05:55 +00001628SkPDFArray* SkPDFDevice::copyMediaBox() const {
1629 // should this be a singleton?
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +00001630
halcanary385fe4d2015-08-26 13:07:48 -07001631 SkAutoTUnref<SkPDFArray> mediaBox(new SkPDFArray);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001632 mediaBox->reserve(4);
halcanary130444f2015-04-25 06:45:07 -07001633 mediaBox->appendInt(0);
1634 mediaBox->appendInt(0);
reed@google.comc789cf12011-07-20 12:14:33 +00001635 mediaBox->appendInt(fPageSize.fWidth);
1636 mediaBox->appendInt(fPageSize.fHeight);
halcanary130444f2015-04-25 06:45:07 -07001637 return mediaBox.detach();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001638}
1639
halcanary334fcbc2015-02-24 12:56:16 -08001640SkStreamAsset* SkPDFDevice::content() const {
1641 SkDynamicMemoryWStream buffer;
1642 this->writeContent(&buffer);
halcanary7a14b312015-10-01 07:28:13 -07001643 return buffer.bytesWritten() > 0
1644 ? buffer.detachAsStream()
1645 : new SkMemoryStream;
reed@google.com5667afc2011-06-27 14:42:15 +00001646}
1647
ctguil@chromium.org8dcf74f2011-07-12 21:56:27 +00001648void SkPDFDevice::copyContentEntriesToData(ContentEntry* entry,
1649 SkWStream* data) const {
ctguil@chromium.org9510ccc2011-07-27 00:10:51 +00001650 // TODO(ctguil): For margins, I'm not sure fExistingClipStack/Region is the
1651 // right thing to pass here.
ctguil@chromium.org8dcf74f2011-07-12 21:56:27 +00001652 GraphicStackState gsState(fExistingClipStack, fExistingClipRegion, data);
halcanary96fcdcc2015-08-27 07:41:13 -07001653 while (entry != nullptr) {
vandebo@chromium.org663515b2012-01-05 18:45:27 +00001654 SkPoint translation;
1655 translation.iset(this->getOrigin());
ctguil@chromium.org8dcf74f2011-07-12 21:56:27 +00001656 translation.negate();
1657 gsState.updateClip(entry->fState.fClipStack, entry->fState.fClipRegion,
1658 translation);
1659 gsState.updateMatrix(entry->fState.fMatrix);
1660 gsState.updateDrawingState(entry->fState);
ctguil@chromium.org9510ccc2011-07-27 00:10:51 +00001661
halcanary7af21502015-02-23 12:17:59 -08001662 entry->fContent.writeToStream(data);
ctguil@chromium.org8dcf74f2011-07-12 21:56:27 +00001663 entry = entry->fNext.get();
1664 }
1665 gsState.drainStack();
1666}
1667
halcanary334fcbc2015-02-24 12:56:16 -08001668void SkPDFDevice::writeContent(SkWStream* out) const {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001669 if (fInitialTransform.getType() != SkMatrix::kIdentity_Mask) {
halcanary334fcbc2015-02-24 12:56:16 -08001670 SkPDFUtils::AppendTransform(fInitialTransform, out);
vandebo@chromium.orgc2a9b7f2011-02-24 23:22:30 +00001671 }
ctguil@chromium.org9510ccc2011-07-27 00:10:51 +00001672
ctguil@chromium.org8dcf74f2011-07-12 21:56:27 +00001673 // TODO(aayushkumar): Apply clip along the margins. Currently, webkit
1674 // colors the contentArea white before it starts drawing into it and
1675 // that currently acts as our clip.
1676 // Also, think about adding a transform here (or assume that the values
1677 // sent across account for that)
halcanary334fcbc2015-02-24 12:56:16 -08001678 SkPDFDevice::copyContentEntriesToData(fMarginContentEntries.get(), out);
ctguil@chromium.org9510ccc2011-07-27 00:10:51 +00001679
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001680 // If the content area is the entire page, then we don't need to clip
1681 // the content area (PDF area clips to the page size). Otherwise,
1682 // we have to clip to the content area; we've already applied the
1683 // initial transform, so just clip to the device size.
1684 if (fPageSize != fContentSize) {
robertphillips@google.com8637a362012-04-10 18:32:35 +00001685 SkRect r = SkRect::MakeWH(SkIntToScalar(this->width()),
1686 SkIntToScalar(this->height()));
halcanary96fcdcc2015-08-27 07:41:13 -07001687 emit_clip(nullptr, &r, out);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001688 }
vandebo@chromium.org98594282011-07-25 22:34:12 +00001689
halcanary334fcbc2015-02-24 12:56:16 -08001690 SkPDFDevice::copyContentEntriesToData(fContentEntries.get(), out);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001691}
1692
commit-bot@chromium.org92ffe7d2013-07-31 22:54:31 +00001693/* Draws an inverse filled path by using Path Ops to compute the positive
1694 * inverse using the current clip as the inverse bounds.
1695 * Return true if this was an inverse path and was properly handled,
1696 * otherwise returns false and the normal drawing routine should continue,
1697 * either as a (incorrect) fallback or because the path was not inverse
1698 * in the first place.
1699 */
1700bool SkPDFDevice::handleInversePath(const SkDraw& d, const SkPath& origPath,
edisonn@google.coma9ebd162013-10-07 13:22:21 +00001701 const SkPaint& paint, bool pathIsMutable,
1702 const SkMatrix* prePathMatrix) {
commit-bot@chromium.org92ffe7d2013-07-31 22:54:31 +00001703 if (!origPath.isInverseFillType()) {
1704 return false;
1705 }
1706
1707 if (d.fClip->isEmpty()) {
1708 return false;
1709 }
1710
1711 SkPath modifiedPath;
1712 SkPath* pathPtr = const_cast<SkPath*>(&origPath);
1713 SkPaint noInversePaint(paint);
1714
1715 // Merge stroking operations into final path.
1716 if (SkPaint::kStroke_Style == paint.getStyle() ||
1717 SkPaint::kStrokeAndFill_Style == paint.getStyle()) {
1718 bool doFillPath = paint.getFillPath(origPath, &modifiedPath);
1719 if (doFillPath) {
1720 noInversePaint.setStyle(SkPaint::kFill_Style);
1721 noInversePaint.setStrokeWidth(0);
1722 pathPtr = &modifiedPath;
1723 } else {
1724 // To be consistent with the raster output, hairline strokes
1725 // are rendered as non-inverted.
1726 modifiedPath.toggleInverseFillType();
halcanary96fcdcc2015-08-27 07:41:13 -07001727 drawPath(d, modifiedPath, paint, nullptr, true);
commit-bot@chromium.org92ffe7d2013-07-31 22:54:31 +00001728 return true;
1729 }
1730 }
1731
1732 // Get bounds of clip in current transform space
1733 // (clip bounds are given in device space).
1734 SkRect bounds;
1735 SkMatrix transformInverse;
edisonn@google.coma9ebd162013-10-07 13:22:21 +00001736 SkMatrix totalMatrix = *d.fMatrix;
1737 if (prePathMatrix) {
1738 totalMatrix.preConcat(*prePathMatrix);
1739 }
1740 if (!totalMatrix.invert(&transformInverse)) {
commit-bot@chromium.org92ffe7d2013-07-31 22:54:31 +00001741 return false;
1742 }
1743 bounds.set(d.fClip->getBounds());
1744 transformInverse.mapRect(&bounds);
1745
1746 // Extend the bounds by the line width (plus some padding)
1747 // so the edge doesn't cause a visible stroke.
1748 bounds.outset(paint.getStrokeWidth() + SK_Scalar1,
1749 paint.getStrokeWidth() + SK_Scalar1);
1750
1751 if (!calculate_inverse_path(bounds, *pathPtr, &modifiedPath)) {
1752 return false;
1753 }
1754
edisonn@google.coma9ebd162013-10-07 13:22:21 +00001755 drawPath(d, modifiedPath, noInversePaint, prePathMatrix, true);
commit-bot@chromium.org92ffe7d2013-07-31 22:54:31 +00001756 return true;
1757}
1758
epoger@google.comb58772f2013-03-08 09:09:10 +00001759bool SkPDFDevice::handlePointAnnotation(const SkPoint* points, size_t count,
1760 const SkMatrix& matrix,
wangxianzhud76665d2015-07-17 17:23:15 -07001761 SkAnnotation* annotationInfo) {
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001762 SkData* nameData = annotationInfo->find(
1763 SkAnnotationKeys::Define_Named_Dest_Key());
epoger@google.comb58772f2013-03-08 09:09:10 +00001764 if (nameData) {
1765 for (size_t i = 0; i < count; i++) {
wangxianzhuef6c50a2015-09-17 20:38:02 -07001766 SkPoint transformedPoint;
1767 matrix.mapXY(points[i].x(), points[i].y(), &transformedPoint);
1768 fNamedDestinations.push(new NamedDestination(nameData, transformedPoint));
epoger@google.comb58772f2013-03-08 09:09:10 +00001769 }
wangxianzhud76665d2015-07-17 17:23:15 -07001770 return true;
epoger@google.comb58772f2013-03-08 09:09:10 +00001771 }
1772 return false;
1773}
vandebo@chromium.org238be8c2012-07-13 20:06:02 +00001774
wangxianzhuef6c50a2015-09-17 20:38:02 -07001775bool SkPDFDevice::handlePathAnnotation(const SkPath& path,
1776 const SkDraw& d,
1777 SkAnnotation* annotation) {
1778 SkASSERT(annotation);
1779
1780 SkPath transformedPath = path;
1781 transformedPath.transform(*d.fMatrix);
1782 SkRasterClip clip = *d.fRC;
1783 clip.op(transformedPath, SkISize::Make(width(), height()), SkRegion::kIntersect_Op, false);
1784 SkRect transformedRect = SkRect::Make(clip.getBounds());
1785
1786 SkData* urlData = annotation->find(SkAnnotationKeys::URL_Key());
1787 if (urlData) {
1788 if (!transformedRect.isEmpty()) {
1789 fLinkToURLs.push(new RectWithData(transformedRect, urlData));
1790 }
1791 return true;
vandebo@chromium.org238be8c2012-07-13 20:06:02 +00001792 }
wangxianzhuef6c50a2015-09-17 20:38:02 -07001793
1794 SkData* linkToDestination =
1795 annotation->find(SkAnnotationKeys::Link_Named_Dest_Key());
1796 if (linkToDestination) {
1797 if (!transformedRect.isEmpty()) {
1798 fLinkToDestinations.push(new RectWithData(transformedRect, linkToDestination));
1799 }
1800 return true;
1801 }
1802
1803 return false;
halcanary438de492015-04-28 06:21:01 -07001804}
1805
wangxianzhuef6c50a2015-09-17 20:38:02 -07001806void SkPDFDevice::appendAnnotations(SkPDFArray* array) const {
1807 array->reserve(fLinkToURLs.count() + fLinkToDestinations.count());
1808 for (RectWithData* rectWithURL : fLinkToURLs) {
1809 SkRect r;
1810 fInitialTransform.mapRect(&r, rectWithURL->rect);
1811 array->appendObject(create_link_to_url(rectWithURL->data, r));
1812 }
1813 for (RectWithData* linkToDestination : fLinkToDestinations) {
1814 SkRect r;
1815 fInitialTransform.mapRect(&r, linkToDestination->rect);
1816 array->appendObject(create_link_named_dest(linkToDestination->data, r));
1817 }
1818}
epoger@google.comb58772f2013-03-08 09:09:10 +00001819
halcanary6d622702015-03-25 08:45:42 -07001820void SkPDFDevice::appendDestinations(SkPDFDict* dict, SkPDFObject* page) const {
wangxianzhuef6c50a2015-09-17 20:38:02 -07001821 for (NamedDestination* dest : fNamedDestinations) {
halcanary385fe4d2015-08-26 13:07:48 -07001822 SkAutoTUnref<SkPDFArray> pdfDest(new SkPDFArray);
epoger@google.comb58772f2013-03-08 09:09:10 +00001823 pdfDest->reserve(5);
halcanary130444f2015-04-25 06:45:07 -07001824 pdfDest->appendObjRef(SkRef(page));
epoger@google.comb58772f2013-03-08 09:09:10 +00001825 pdfDest->appendName("XYZ");
wangxianzhuef6c50a2015-09-17 20:38:02 -07001826 SkPoint p = fInitialTransform.mapXY(dest->point.x(), dest->point.y());
1827 pdfDest->appendScalar(p.x());
1828 pdfDest->appendScalar(p.y());
epoger@google.comb58772f2013-03-08 09:09:10 +00001829 pdfDest->appendInt(0); // Leave zoom unchanged
halcanary130444f2015-04-25 06:45:07 -07001830 SkString name(static_cast<const char*>(dest->nameData->data()));
1831 dict->insertObject(name, pdfDest.detach());
epoger@google.comb58772f2013-03-08 09:09:10 +00001832 }
vandebo@chromium.org238be8c2012-07-13 20:06:02 +00001833}
1834
reed@google.comfc641d02012-09-20 17:52:20 +00001835SkPDFFormXObject* SkPDFDevice::createFormXObjectFromDevice() {
halcanary385fe4d2015-08-26 13:07:48 -07001836 SkPDFFormXObject* xobject = new SkPDFFormXObject(this);
vandebo@chromium.org98594282011-07-25 22:34:12 +00001837 // We always draw the form xobjects that we create back into the device, so
1838 // we simply preserve the font usage instead of pulling it out and merging
1839 // it back in later.
1840 cleanUp(false); // Reset this device to have no content.
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001841 init();
reed@google.comfc641d02012-09-20 17:52:20 +00001842 return xobject;
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001843}
1844
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001845void SkPDFDevice::drawFormXObjectWithMask(int xObjectIndex,
1846 SkPDFFormXObject* mask,
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001847 const SkClipStack* clipStack,
1848 const SkRegion& clipRegion,
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001849 SkXfermode::Mode mode,
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001850 bool invertClip) {
1851 if (clipRegion.isEmpty() && !invertClip) {
1852 return;
1853 }
1854
halcanarybe27a112015-04-01 13:31:19 -07001855 SkAutoTUnref<SkPDFObject> sMaskGS(SkPDFGraphicState::GetSMaskGraphicState(
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001856 mask, invertClip, SkPDFGraphicState::kAlpha_SMaskMode));
1857
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001858 SkMatrix identity;
1859 identity.reset();
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001860 SkPaint paint;
1861 paint.setXfermodeMode(mode);
1862 ScopedContentEntry content(this, clipStack, clipRegion, identity, paint);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001863 if (!content.entry()) {
1864 return;
1865 }
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001866 SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()),
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001867 &content.entry()->fContent);
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001868 SkPDFUtils::DrawFormXObject(xObjectIndex, &content.entry()->fContent);
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001869
vandebo@chromium.orgd96d17b2013-01-04 19:31:24 +00001870 sMaskGS.reset(SkPDFGraphicState::GetNoSMaskGraphicState());
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001871 SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()),
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001872 &content.entry()->fContent);
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001873}
1874
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001875ContentEntry* SkPDFDevice::setUpContentEntry(const SkClipStack* clipStack,
1876 const SkRegion& clipRegion,
1877 const SkMatrix& matrix,
1878 const SkPaint& paint,
1879 bool hasText,
reed@google.comfc641d02012-09-20 17:52:20 +00001880 SkPDFFormXObject** dst) {
halcanary96fcdcc2015-08-27 07:41:13 -07001881 *dst = nullptr;
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001882 if (clipRegion.isEmpty()) {
halcanary96fcdcc2015-08-27 07:41:13 -07001883 return nullptr;
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001884 }
1885
vandebo@chromium.org78dad542011-05-11 18:46:03 +00001886 // The clip stack can come from an SkDraw where it is technically optional.
1887 SkClipStack synthesizedClipStack;
halcanary96fcdcc2015-08-27 07:41:13 -07001888 if (clipStack == nullptr) {
vandebo@chromium.org78dad542011-05-11 18:46:03 +00001889 if (clipRegion == fExistingClipRegion) {
1890 clipStack = &fExistingClipStack;
1891 } else {
1892 // GraphicStackState::updateClip expects the clip stack to have
1893 // fExistingClip as a prefix, so start there, then set the clip
1894 // to the passed region.
1895 synthesizedClipStack = fExistingClipStack;
1896 SkPath clipPath;
1897 clipRegion.getBoundaryPath(&clipPath);
reed@google.com00177082011-10-12 14:34:30 +00001898 synthesizedClipStack.clipDevPath(clipPath, SkRegion::kReplace_Op,
1899 false);
vandebo@chromium.org78dad542011-05-11 18:46:03 +00001900 clipStack = &synthesizedClipStack;
1901 }
1902 }
1903
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001904 SkXfermode::Mode xfermode = SkXfermode::kSrcOver_Mode;
1905 if (paint.getXfermode()) {
1906 paint.getXfermode()->asMode(&xfermode);
1907 }
1908
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001909 // For the following modes, we want to handle source and destination
1910 // separately, so make an object of what's already there.
1911 if (xfermode == SkXfermode::kClear_Mode ||
1912 xfermode == SkXfermode::kSrc_Mode ||
1913 xfermode == SkXfermode::kSrcIn_Mode ||
1914 xfermode == SkXfermode::kDstIn_Mode ||
1915 xfermode == SkXfermode::kSrcOut_Mode ||
1916 xfermode == SkXfermode::kDstOut_Mode ||
1917 xfermode == SkXfermode::kSrcATop_Mode ||
1918 xfermode == SkXfermode::kDstATop_Mode ||
1919 xfermode == SkXfermode::kModulate_Mode) {
1920 if (!isContentEmpty()) {
reed@google.comfc641d02012-09-20 17:52:20 +00001921 *dst = createFormXObjectFromDevice();
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001922 SkASSERT(isContentEmpty());
1923 } else if (xfermode != SkXfermode::kSrc_Mode &&
1924 xfermode != SkXfermode::kSrcOut_Mode) {
1925 // Except for Src and SrcOut, if there isn't anything already there,
1926 // then we're done.
halcanary96fcdcc2015-08-27 07:41:13 -07001927 return nullptr;
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001928 }
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001929 }
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +00001930 // TODO(vandebo): Figure out how/if we can handle the following modes:
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001931 // Xor, Plus.
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001932
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001933 // Dst xfer mode doesn't draw source at all.
1934 if (xfermode == SkXfermode::kDst_Mode) {
halcanary96fcdcc2015-08-27 07:41:13 -07001935 return nullptr;
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001936 }
1937
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001938 ContentEntry* entry;
commit-bot@chromium.orge0294402013-08-29 22:14:04 +00001939 SkAutoTDelete<ContentEntry> newEntry;
ctguil@chromium.org8dcf74f2011-07-12 21:56:27 +00001940
1941 ContentEntry* lastContentEntry = getLastContentEntry();
1942 if (lastContentEntry && lastContentEntry->fContent.getOffset() == 0) {
1943 entry = lastContentEntry;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001944 } else {
1945 newEntry.reset(new ContentEntry);
1946 entry = newEntry.get();
1947 }
1948
vandebo@chromium.org78dad542011-05-11 18:46:03 +00001949 populateGraphicStateEntryFromPaint(matrix, *clipStack, clipRegion, paint,
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001950 hasText, &entry->fState);
ctguil@chromium.org8dcf74f2011-07-12 21:56:27 +00001951 if (lastContentEntry && xfermode != SkXfermode::kDstOver_Mode &&
1952 entry->fState.compareInitialState(lastContentEntry->fState)) {
1953 return lastContentEntry;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001954 }
1955
commit-bot@chromium.orge0294402013-08-29 22:14:04 +00001956 SkAutoTDelete<ContentEntry>* contentEntries = getContentEntries();
ctguil@chromium.org8dcf74f2011-07-12 21:56:27 +00001957 if (!lastContentEntry) {
ctguil@chromium.org9510ccc2011-07-27 00:10:51 +00001958 contentEntries->reset(entry);
ctguil@chromium.org8dcf74f2011-07-12 21:56:27 +00001959 setLastContentEntry(entry);
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001960 } else if (xfermode == SkXfermode::kDstOver_Mode) {
commit-bot@chromium.orge0294402013-08-29 22:14:04 +00001961 entry->fNext.reset(contentEntries->detach());
ctguil@chromium.org9510ccc2011-07-27 00:10:51 +00001962 contentEntries->reset(entry);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001963 } else {
ctguil@chromium.org8dcf74f2011-07-12 21:56:27 +00001964 lastContentEntry->fNext.reset(entry);
1965 setLastContentEntry(entry);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001966 }
commit-bot@chromium.orge0294402013-08-29 22:14:04 +00001967 newEntry.detach();
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001968 return entry;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001969}
1970
commit-bot@chromium.org7542dc82013-12-03 21:08:46 +00001971void SkPDFDevice::finishContentEntry(SkXfermode::Mode xfermode,
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001972 SkPDFFormXObject* dst,
1973 SkPath* shape) {
1974 if (xfermode != SkXfermode::kClear_Mode &&
1975 xfermode != SkXfermode::kSrc_Mode &&
commit-bot@chromium.org7542dc82013-12-03 21:08:46 +00001976 xfermode != SkXfermode::kDstOver_Mode &&
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001977 xfermode != SkXfermode::kSrcIn_Mode &&
1978 xfermode != SkXfermode::kDstIn_Mode &&
1979 xfermode != SkXfermode::kSrcOut_Mode &&
1980 xfermode != SkXfermode::kDstOut_Mode &&
1981 xfermode != SkXfermode::kSrcATop_Mode &&
1982 xfermode != SkXfermode::kDstATop_Mode &&
1983 xfermode != SkXfermode::kModulate_Mode) {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001984 SkASSERT(!dst);
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001985 return;
1986 }
commit-bot@chromium.org7542dc82013-12-03 21:08:46 +00001987 if (xfermode == SkXfermode::kDstOver_Mode) {
1988 SkASSERT(!dst);
1989 ContentEntry* firstContentEntry = getContentEntries()->get();
1990 if (firstContentEntry->fContent.getOffset() == 0) {
1991 // For DstOver, an empty content entry was inserted before the rest
1992 // of the content entries. If nothing was drawn, it needs to be
1993 // removed.
1994 SkAutoTDelete<ContentEntry>* contentEntries = getContentEntries();
1995 contentEntries->reset(firstContentEntry->fNext.detach());
1996 }
1997 return;
1998 }
vandebo@chromium.org3b416212013-10-30 20:48:05 +00001999 if (!dst) {
2000 SkASSERT(xfermode == SkXfermode::kSrc_Mode ||
2001 xfermode == SkXfermode::kSrcOut_Mode);
2002 return;
2003 }
ctguil@chromium.org9510ccc2011-07-27 00:10:51 +00002004
2005 ContentEntry* contentEntries = getContentEntries()->get();
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00002006 SkASSERT(dst);
ctguil@chromium.org8dcf74f2011-07-12 21:56:27 +00002007 SkASSERT(!contentEntries->fNext.get());
commit-bot@chromium.org4e8f1e52013-12-17 23:38:28 +00002008 // Changing the current content into a form-xobject will destroy the clip
2009 // objects which is fine since the xobject will already be clipped. However
2010 // if source has shape, we need to clip it too, so a copy of the clip is
2011 // saved.
ctguil@chromium.org8dcf74f2011-07-12 21:56:27 +00002012 SkClipStack clipStack = contentEntries->fState.fClipStack;
2013 SkRegion clipRegion = contentEntries->fState.fClipRegion;
vandebo@chromium.org481aef62011-05-24 16:39:05 +00002014
commit-bot@chromium.org7542dc82013-12-03 21:08:46 +00002015 SkMatrix identity;
2016 identity.reset();
2017 SkPaint stockPaint;
2018
reed@google.comfc641d02012-09-20 17:52:20 +00002019 SkAutoTUnref<SkPDFFormXObject> srcFormXObject;
vandebo@chromium.org3b416212013-10-30 20:48:05 +00002020 if (isContentEmpty()) {
commit-bot@chromium.org7542dc82013-12-03 21:08:46 +00002021 // If nothing was drawn and there's no shape, then the draw was a
2022 // no-op, but dst needs to be restored for that to be true.
2023 // If there is shape, then an empty source with Src, SrcIn, SrcOut,
2024 // DstIn, DstAtop or Modulate reduces to Clear and DstOut or SrcAtop
2025 // reduces to Dst.
halcanary96fcdcc2015-08-27 07:41:13 -07002026 if (shape == nullptr || xfermode == SkXfermode::kDstOut_Mode ||
commit-bot@chromium.org7542dc82013-12-03 21:08:46 +00002027 xfermode == SkXfermode::kSrcATop_Mode) {
commit-bot@chromium.org4e8f1e52013-12-17 23:38:28 +00002028 ScopedContentEntry content(this, &fExistingClipStack,
2029 fExistingClipRegion, identity,
commit-bot@chromium.org7542dc82013-12-03 21:08:46 +00002030 stockPaint);
2031 SkPDFUtils::DrawFormXObject(this->addXObjectResource(dst),
2032 &content.entry()->fContent);
2033 return;
2034 } else {
2035 xfermode = SkXfermode::kClear_Mode;
2036 }
vandebo@chromium.org3b416212013-10-30 20:48:05 +00002037 } else {
2038 SkASSERT(!fContentEntries->fNext.get());
reed@google.comfc641d02012-09-20 17:52:20 +00002039 srcFormXObject.reset(createFormXObjectFromDevice());
vandebo@chromium.org481aef62011-05-24 16:39:05 +00002040 }
2041
vandebo@chromium.org3b416212013-10-30 20:48:05 +00002042 // TODO(vandebo) srcFormXObject may contain alpha, but here we want it
2043 // without alpha.
2044 if (xfermode == SkXfermode::kSrcATop_Mode) {
2045 // TODO(vandebo): In order to properly support SrcATop we have to track
2046 // the shape of what's been drawn at all times. It's the intersection of
2047 // the non-transparent parts of the device and the outlines (shape) of
2048 // all images and devices drawn.
2049 drawFormXObjectWithMask(addXObjectResource(srcFormXObject.get()), dst,
commit-bot@chromium.org4e8f1e52013-12-17 23:38:28 +00002050 &fExistingClipStack, fExistingClipRegion,
vandebo@chromium.org3b416212013-10-30 20:48:05 +00002051 SkXfermode::kSrcOver_Mode, true);
2052 } else {
2053 SkAutoTUnref<SkPDFFormXObject> dstMaskStorage;
2054 SkPDFFormXObject* dstMask = srcFormXObject.get();
halcanary96fcdcc2015-08-27 07:41:13 -07002055 if (shape != nullptr) {
vandebo@chromium.org3b416212013-10-30 20:48:05 +00002056 // Draw shape into a form-xobject.
2057 SkDraw d;
2058 d.fMatrix = &identity;
2059 d.fClip = &clipRegion;
2060 d.fClipStack = &clipStack;
2061 SkPaint filledPaint;
2062 filledPaint.setColor(SK_ColorBLACK);
2063 filledPaint.setStyle(SkPaint::kFill_Style);
halcanary96fcdcc2015-08-27 07:41:13 -07002064 this->drawPath(d, *shape, filledPaint, nullptr, true);
vandebo@chromium.org3b416212013-10-30 20:48:05 +00002065
2066 dstMaskStorage.reset(createFormXObjectFromDevice());
2067 dstMask = dstMaskStorage.get();
2068 }
commit-bot@chromium.org4e8f1e52013-12-17 23:38:28 +00002069 drawFormXObjectWithMask(addXObjectResource(dst), dstMask,
2070 &fExistingClipStack, fExistingClipRegion,
2071 SkXfermode::kSrcOver_Mode, true);
vandebo@chromium.org3b416212013-10-30 20:48:05 +00002072 }
2073
commit-bot@chromium.org7542dc82013-12-03 21:08:46 +00002074 if (xfermode == SkXfermode::kClear_Mode) {
vandebo@chromium.org3b416212013-10-30 20:48:05 +00002075 return;
2076 } else if (xfermode == SkXfermode::kSrc_Mode ||
2077 xfermode == SkXfermode::kDstATop_Mode) {
commit-bot@chromium.org4e8f1e52013-12-17 23:38:28 +00002078 ScopedContentEntry content(this, &fExistingClipStack,
2079 fExistingClipRegion, identity, stockPaint);
vandebo@chromium.org3b416212013-10-30 20:48:05 +00002080 if (content.entry()) {
2081 SkPDFUtils::DrawFormXObject(
2082 this->addXObjectResource(srcFormXObject.get()),
2083 &content.entry()->fContent);
2084 }
2085 if (xfermode == SkXfermode::kSrc_Mode) {
2086 return;
2087 }
commit-bot@chromium.org7542dc82013-12-03 21:08:46 +00002088 } else if (xfermode == SkXfermode::kSrcATop_Mode) {
commit-bot@chromium.org4e8f1e52013-12-17 23:38:28 +00002089 ScopedContentEntry content(this, &fExistingClipStack,
2090 fExistingClipRegion, identity, stockPaint);
commit-bot@chromium.org7542dc82013-12-03 21:08:46 +00002091 if (content.entry()) {
2092 SkPDFUtils::DrawFormXObject(this->addXObjectResource(dst),
2093 &content.entry()->fContent);
2094 }
vandebo@chromium.org3b416212013-10-30 20:48:05 +00002095 }
2096
2097 SkASSERT(xfermode == SkXfermode::kSrcIn_Mode ||
2098 xfermode == SkXfermode::kDstIn_Mode ||
2099 xfermode == SkXfermode::kSrcOut_Mode ||
2100 xfermode == SkXfermode::kDstOut_Mode ||
2101 xfermode == SkXfermode::kSrcATop_Mode ||
2102 xfermode == SkXfermode::kDstATop_Mode ||
2103 xfermode == SkXfermode::kModulate_Mode);
2104
vandebo@chromium.org6112c212011-05-13 03:50:38 +00002105 if (xfermode == SkXfermode::kSrcIn_Mode ||
vandebo@chromium.org3b416212013-10-30 20:48:05 +00002106 xfermode == SkXfermode::kSrcOut_Mode ||
2107 xfermode == SkXfermode::kSrcATop_Mode) {
2108 drawFormXObjectWithMask(addXObjectResource(srcFormXObject.get()), dst,
commit-bot@chromium.org4e8f1e52013-12-17 23:38:28 +00002109 &fExistingClipStack, fExistingClipRegion,
vandebo@chromium.org3b416212013-10-30 20:48:05 +00002110 SkXfermode::kSrcOver_Mode,
2111 xfermode == SkXfermode::kSrcOut_Mode);
vandebo@chromium.org6112c212011-05-13 03:50:38 +00002112 } else {
vandebo@chromium.org3b416212013-10-30 20:48:05 +00002113 SkXfermode::Mode mode = SkXfermode::kSrcOver_Mode;
2114 if (xfermode == SkXfermode::kModulate_Mode) {
2115 drawFormXObjectWithMask(addXObjectResource(srcFormXObject.get()),
commit-bot@chromium.org4e8f1e52013-12-17 23:38:28 +00002116 dst, &fExistingClipStack,
2117 fExistingClipRegion,
vandebo@chromium.org3b416212013-10-30 20:48:05 +00002118 SkXfermode::kSrcOver_Mode, false);
2119 mode = SkXfermode::kMultiply_Mode;
2120 }
2121 drawFormXObjectWithMask(addXObjectResource(dst), srcFormXObject.get(),
commit-bot@chromium.org4e8f1e52013-12-17 23:38:28 +00002122 &fExistingClipStack, fExistingClipRegion, mode,
vandebo@chromium.org3b416212013-10-30 20:48:05 +00002123 xfermode == SkXfermode::kDstOut_Mode);
vandebo@chromium.org6112c212011-05-13 03:50:38 +00002124 }
vandebo@chromium.org6112c212011-05-13 03:50:38 +00002125}
2126
vandebo@chromium.org481aef62011-05-24 16:39:05 +00002127bool SkPDFDevice::isContentEmpty() {
ctguil@chromium.org9510ccc2011-07-27 00:10:51 +00002128 ContentEntry* contentEntries = getContentEntries()->get();
2129 if (!contentEntries || contentEntries->fContent.getOffset() == 0) {
2130 SkASSERT(!contentEntries || !contentEntries->fNext.get());
vandebo@chromium.org481aef62011-05-24 16:39:05 +00002131 return true;
2132 }
2133 return false;
2134}
2135
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00002136void SkPDFDevice::populateGraphicStateEntryFromPaint(
2137 const SkMatrix& matrix,
2138 const SkClipStack& clipStack,
2139 const SkRegion& clipRegion,
2140 const SkPaint& paint,
2141 bool hasText,
2142 GraphicStateEntry* entry) {
halcanary96fcdcc2015-08-27 07:41:13 -07002143 NOT_IMPLEMENTED(paint.getPathEffect() != nullptr, false);
2144 NOT_IMPLEMENTED(paint.getMaskFilter() != nullptr, false);
2145 NOT_IMPLEMENTED(paint.getColorFilter() != nullptr, false);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00002146
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00002147 entry->fMatrix = matrix;
2148 entry->fClipStack = clipStack;
2149 entry->fClipRegion = clipRegion;
vandebo@chromium.orgda6c5692012-06-28 21:37:20 +00002150 entry->fColor = SkColorSetA(paint.getColor(), 0xFF);
2151 entry->fShaderIndex = -1;
vandebo@chromium.org48543272011-02-08 19:28:07 +00002152
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00002153 // PDF treats a shader as a color, so we only set one or the other.
vandebo@chromium.orgd96d17b2013-01-04 19:31:24 +00002154 SkAutoTUnref<SkPDFObject> pdfShader;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00002155 const SkShader* shader = paint.getShader();
2156 SkColor color = paint.getColor();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00002157 if (shader) {
2158 // PDF positions patterns relative to the initial transform, so
2159 // we need to apply the current transform to the shader parameters.
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00002160 SkMatrix transform = matrix;
vandebo@chromium.org75f97e42011-04-11 23:24:18 +00002161 transform.postConcat(fInitialTransform);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00002162
2163 // PDF doesn't support kClamp_TileMode, so we simulate it by making
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00002164 // a pattern the size of the current clip.
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00002165 SkIRect bounds = clipRegion.getBounds();
vandebo@chromium.org293a7582012-03-16 19:50:37 +00002166
2167 // We need to apply the initial transform to bounds in order to get
2168 // bounds in a consistent coordinate system.
2169 SkRect boundsTemp;
2170 boundsTemp.set(bounds);
2171 fInitialTransform.mapRect(&boundsTemp);
2172 boundsTemp.roundOut(&bounds);
2173
halcanary792c80f2015-02-20 07:21:05 -08002174 SkScalar rasterScale =
2175 SkIntToScalar(fRasterDpi) / DPI_FOR_RASTER_SCALE_ONE;
2176 pdfShader.reset(SkPDFShader::GetPDFShader(
2177 fCanon, fRasterDpi, *shader, transform, bounds, rasterScale));
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00002178
vandebo@chromium.orgb88cfe52011-07-18 18:40:32 +00002179 if (pdfShader.get()) {
2180 // pdfShader has been canonicalized so we can directly compare
2181 // pointers.
2182 int resourceIndex = fShaderResources.find(pdfShader.get());
2183 if (resourceIndex < 0) {
2184 resourceIndex = fShaderResources.count();
2185 fShaderResources.push(pdfShader.get());
vandebo@chromium.orgd96d17b2013-01-04 19:31:24 +00002186 pdfShader.get()->ref();
vandebo@chromium.orgb88cfe52011-07-18 18:40:32 +00002187 }
2188 entry->fShaderIndex = resourceIndex;
2189 } else {
2190 // A color shader is treated as an invalid shader so we don't have
2191 // to set a shader just for a color.
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00002192 SkShader::GradientInfo gradientInfo;
2193 SkColor gradientColor;
2194 gradientInfo.fColors = &gradientColor;
halcanary96fcdcc2015-08-27 07:41:13 -07002195 gradientInfo.fColorOffsets = nullptr;
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00002196 gradientInfo.fColorCount = 1;
2197 if (shader->asAGradient(&gradientInfo) ==
2198 SkShader::kColor_GradientType) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00002199 entry->fColor = SkColorSetA(gradientColor, 0xFF);
2200 color = gradientColor;
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00002201 }
2202 }
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00002203 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00002204
vandebo@chromium.orgd96d17b2013-01-04 19:31:24 +00002205 SkAutoTUnref<SkPDFGraphicState> newGraphicState;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00002206 if (color == paint.getColor()) {
vandebo@chromium.orgd96d17b2013-01-04 19:31:24 +00002207 newGraphicState.reset(
halcanary792c80f2015-02-20 07:21:05 -08002208 SkPDFGraphicState::GetGraphicStateForPaint(fCanon, paint));
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00002209 } else {
2210 SkPaint newPaint = paint;
2211 newPaint.setColor(color);
vandebo@chromium.orgd96d17b2013-01-04 19:31:24 +00002212 newGraphicState.reset(
halcanary792c80f2015-02-20 07:21:05 -08002213 SkPDFGraphicState::GetGraphicStateForPaint(fCanon, newPaint));
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00002214 }
vandebo@chromium.org6112c212011-05-13 03:50:38 +00002215 int resourceIndex = addGraphicStateResource(newGraphicState.get());
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00002216 entry->fGraphicStateIndex = resourceIndex;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00002217
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00002218 if (hasText) {
2219 entry->fTextScaleX = paint.getTextScaleX();
2220 entry->fTextFill = paint.getStyle();
2221 } else {
2222 entry->fTextScaleX = 0;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00002223 }
2224}
2225
halcanarybe27a112015-04-01 13:31:19 -07002226int SkPDFDevice::addGraphicStateResource(SkPDFObject* gs) {
vandebo@chromium.org6112c212011-05-13 03:50:38 +00002227 // Assumes that gs has been canonicalized (so we can directly compare
2228 // pointers).
2229 int result = fGraphicStateResources.find(gs);
2230 if (result < 0) {
2231 result = fGraphicStateResources.count();
2232 fGraphicStateResources.push(gs);
2233 gs->ref();
2234 }
2235 return result;
2236}
2237
vandebo@chromium.org3b416212013-10-30 20:48:05 +00002238int SkPDFDevice::addXObjectResource(SkPDFObject* xObject) {
2239 // Assumes that xobject has been canonicalized (so we can directly compare
2240 // pointers).
2241 int result = fXObjectResources.find(xObject);
2242 if (result < 0) {
2243 result = fXObjectResources.count();
2244 fXObjectResources.push(xObject);
2245 xObject->ref();
2246 }
2247 return result;
2248}
2249
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00002250void SkPDFDevice::updateFont(const SkPaint& paint, uint16_t glyphID,
2251 ContentEntry* contentEntry) {
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +00002252 SkTypeface* typeface = paint.getTypeface();
halcanary96fcdcc2015-08-27 07:41:13 -07002253 if (contentEntry->fState.fFont == nullptr ||
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00002254 contentEntry->fState.fTextSize != paint.getTextSize() ||
2255 !contentEntry->fState.fFont->hasGlyph(glyphID)) {
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +00002256 int fontIndex = getFontResourceIndex(typeface, glyphID);
commit-bot@chromium.org47401352013-07-23 21:49:29 +00002257 contentEntry->fContent.writeText("/");
2258 contentEntry->fContent.writeText(SkPDFResourceDict::getResourceName(
2259 SkPDFResourceDict::kFont_ResourceType,
2260 fontIndex).c_str());
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00002261 contentEntry->fContent.writeText(" ");
halcanarybc4696b2015-05-06 10:56:04 -07002262 SkPDFUtils::AppendScalar(paint.getTextSize(), &contentEntry->fContent);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00002263 contentEntry->fContent.writeText(" Tf\n");
2264 contentEntry->fState.fFont = fFontResources[fontIndex];
vandebo@chromium.org2a22e102011-01-25 21:01:34 +00002265 }
2266}
2267
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +00002268int SkPDFDevice::getFontResourceIndex(SkTypeface* typeface, uint16_t glyphID) {
halcanary792c80f2015-02-20 07:21:05 -08002269 SkAutoTUnref<SkPDFFont> newFont(
2270 SkPDFFont::GetFontResource(fCanon, typeface, glyphID));
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00002271 int resourceIndex = fFontResources.find(newFont.get());
2272 if (resourceIndex < 0) {
2273 resourceIndex = fFontResources.count();
2274 fFontResources.push(newFont.get());
vandebo@chromium.orgd96d17b2013-01-04 19:31:24 +00002275 newFont.get()->ref();
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00002276 }
2277 return resourceIndex;
2278}
2279
halcanary7a14b312015-10-01 07:28:13 -07002280static SkSize rect_to_size(const SkRect& r) {
2281 return SkSize::Make(r.width(), r.height());
2282}
2283
2284static const SkImage* color_filter(const SkImage* image,
2285 SkColorFilter* colorFilter) {
2286 SkAutoTUnref<SkSurface> surface(SkSurface::NewRaster(
2287 SkImageInfo::MakeN32Premul(image->dimensions())));
2288 if (!surface) {
2289 return image;
2290 }
2291 SkCanvas* canvas = surface->getCanvas();
2292 canvas->clear(SK_ColorTRANSPARENT);
2293 SkPaint paint;
2294 paint.setColorFilter(colorFilter);
2295 canvas->drawImage(image, 0, 0, &paint);
2296 canvas->flush();
2297 return surface->newImageSnapshot();
2298}
2299
2300////////////////////////////////////////////////////////////////////////////////
2301void SkPDFDevice::internalDrawImage(const SkMatrix& origMatrix,
2302 const SkClipStack* clipStack,
2303 const SkRegion& origClipRegion,
2304 const SkImage* image,
2305 const SkIRect* srcRect,
2306 const SkPaint& paint) {
2307 SkASSERT(image);
2308 #ifdef SK_PDF_IMAGE_STATS
2309 gDrawImageCalls.fetch_add(1);
2310 #endif
edisonn@google.com9cf0cb12013-10-16 18:32:35 +00002311 SkMatrix matrix = origMatrix;
2312 SkRegion perspectiveBounds;
2313 const SkRegion* clipRegion = &origClipRegion;
halcanary7a14b312015-10-01 07:28:13 -07002314 SkAutoTUnref<const SkImage> autoImageUnref;
edisonn@google.com9cf0cb12013-10-16 18:32:35 +00002315
halcanary7a14b312015-10-01 07:28:13 -07002316 if (srcRect) {
2317 autoImageUnref.reset(image->newSubset(*srcRect));
2318 if (!autoImageUnref) {
2319 return;
2320 }
2321 image = autoImageUnref;
2322 }
edisonn@google.com9cf0cb12013-10-16 18:32:35 +00002323 // Rasterize the bitmap using perspective in a new bitmap.
2324 if (origMatrix.hasPerspective()) {
edisonn@google.com73a7ea32013-11-11 20:55:15 +00002325 if (fRasterDpi == 0) {
2326 return;
2327 }
edisonn@google.com73a7ea32013-11-11 20:55:15 +00002328 // Transform the bitmap in the new space, without taking into
2329 // account the initial transform.
edisonn@google.com9cf0cb12013-10-16 18:32:35 +00002330 SkPath perspectiveOutline;
halcanary7a14b312015-10-01 07:28:13 -07002331 SkRect imageBounds = SkRect::Make(image->bounds());
2332 perspectiveOutline.addRect(imageBounds);
edisonn@google.com9cf0cb12013-10-16 18:32:35 +00002333 perspectiveOutline.transform(origMatrix);
2334
2335 // TODO(edisonn): perf - use current clip too.
2336 // Retrieve the bounds of the new shape.
2337 SkRect bounds = perspectiveOutline.getBounds();
2338
edisonn@google.com73a7ea32013-11-11 20:55:15 +00002339 // Transform the bitmap in the new space, taking into
2340 // account the initial transform.
2341 SkMatrix total = origMatrix;
2342 total.postConcat(fInitialTransform);
halcanary7a14b312015-10-01 07:28:13 -07002343 SkScalar dpiScale = SkIntToScalar(fRasterDpi) /
2344 SkIntToScalar(DPI_FOR_RASTER_SCALE_ONE);
2345 total.postScale(dpiScale, dpiScale);
2346
edisonn@google.com73a7ea32013-11-11 20:55:15 +00002347 SkPath physicalPerspectiveOutline;
halcanary7a14b312015-10-01 07:28:13 -07002348 physicalPerspectiveOutline.addRect(imageBounds);
edisonn@google.com73a7ea32013-11-11 20:55:15 +00002349 physicalPerspectiveOutline.transform(total);
2350
halcanary7a14b312015-10-01 07:28:13 -07002351 SkRect physicalPerspectiveBounds =
2352 physicalPerspectiveOutline.getBounds();
2353 SkScalar scaleX = physicalPerspectiveBounds.width() / bounds.width();
2354 SkScalar scaleY = physicalPerspectiveBounds.height() / bounds.height();
edisonn@google.com9cf0cb12013-10-16 18:32:35 +00002355
2356 // TODO(edisonn): A better approach would be to use a bitmap shader
2357 // (in clamp mode) and draw a rect over the entire bounding box. Then
2358 // intersect perspectiveOutline to the clip. That will avoid introducing
2359 // alpha to the image while still giving good behavior at the edge of
2360 // the image. Avoiding alpha will reduce the pdf size and generation
2361 // CPU time some.
2362
halcanary7a14b312015-10-01 07:28:13 -07002363 SkISize wh = rect_to_size(physicalPerspectiveBounds).toCeil();
2364
2365 SkAutoTUnref<SkSurface> surface(
2366 SkSurface::NewRaster(SkImageInfo::MakeN32Premul(wh)));
2367 if (!surface) {
reed@google.com9ebcac52014-01-24 18:53:42 +00002368 return;
2369 }
halcanary7a14b312015-10-01 07:28:13 -07002370 SkCanvas* canvas = surface->getCanvas();
2371 canvas->clear(SK_ColorTRANSPARENT);
edisonn@google.com9cf0cb12013-10-16 18:32:35 +00002372
2373 SkScalar deltaX = bounds.left();
2374 SkScalar deltaY = bounds.top();
2375
2376 SkMatrix offsetMatrix = origMatrix;
2377 offsetMatrix.postTranslate(-deltaX, -deltaY);
edisonn@google.com73a7ea32013-11-11 20:55:15 +00002378 offsetMatrix.postScale(scaleX, scaleY);
edisonn@google.com9cf0cb12013-10-16 18:32:35 +00002379
2380 // Translate the draw in the new canvas, so we perfectly fit the
2381 // shape in the bitmap.
halcanary7a14b312015-10-01 07:28:13 -07002382 canvas->setMatrix(offsetMatrix);
2383 canvas->drawImage(image, 0, 0, nullptr);
edisonn@google.com9cf0cb12013-10-16 18:32:35 +00002384 // Make sure the final bits are in the bitmap.
halcanary7a14b312015-10-01 07:28:13 -07002385 canvas->flush();
edisonn@google.com9cf0cb12013-10-16 18:32:35 +00002386
edisonn@google.com73a7ea32013-11-11 20:55:15 +00002387 // In the new space, we use the identity matrix translated
2388 // and scaled to reflect DPI.
2389 matrix.setScale(1 / scaleX, 1 / scaleY);
2390 matrix.postTranslate(deltaX, deltaY);
2391
halcanary7a14b312015-10-01 07:28:13 -07002392 perspectiveBounds.setRect(bounds.roundOut());
edisonn@google.com9cf0cb12013-10-16 18:32:35 +00002393 clipRegion = &perspectiveBounds;
halcanary96fcdcc2015-08-27 07:41:13 -07002394 srcRect = nullptr;
halcanary7a14b312015-10-01 07:28:13 -07002395
2396 autoImageUnref.reset(surface->newImageSnapshot());
2397 image = autoImageUnref;
edisonn@google.com9cf0cb12013-10-16 18:32:35 +00002398 }
2399
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +00002400 SkMatrix scaled;
2401 // Adjust for origin flip.
vandebo@chromium.org663515b2012-01-05 18:45:27 +00002402 scaled.setScale(SK_Scalar1, -SK_Scalar1);
2403 scaled.postTranslate(0, SK_Scalar1);
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +00002404 // Scale the image up from 1x1 to WxH.
halcanary7a14b312015-10-01 07:28:13 -07002405 SkIRect subset = image->bounds();
2406 scaled.postScale(SkIntToScalar(image->width()),
2407 SkIntToScalar(image->height()));
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +00002408 scaled.postConcat(matrix);
edisonn@google.com9cf0cb12013-10-16 18:32:35 +00002409 ScopedContentEntry content(this, clipStack, *clipRegion, scaled, paint);
vandebo@chromium.org3b416212013-10-30 20:48:05 +00002410 if (!content.entry() || (srcRect && !subset.intersect(*srcRect))) {
2411 return;
2412 }
2413 if (content.needShape()) {
2414 SkPath shape;
halcanary7a14b312015-10-01 07:28:13 -07002415 shape.addRect(SkRect::Make(subset));
vandebo@chromium.org3b416212013-10-30 20:48:05 +00002416 shape.transform(matrix);
2417 content.setShape(shape);
2418 }
2419 if (!content.needSource()) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +00002420 return;
2421 }
2422
halcanary287d22d2015-09-24 10:20:05 -07002423 if (SkColorFilter* colorFilter = paint.getColorFilter()) {
halcanary6950de62015-11-07 05:29:00 -08002424 // TODO(https://bug.skia.org/4378): implement colorfilter on other
halcanary7a14b312015-10-01 07:28:13 -07002425 // draw calls. This code here works for all
2426 // drawBitmap*()/drawImage*() calls amd ImageFilters (which
2427 // rasterize a layer on this backend). Fortuanely, this seems
2428 // to be how Chromium impements most color-filters.
2429 autoImageUnref.reset(color_filter(image, colorFilter));
2430 image = autoImageUnref;
2431 // TODO(halcanary): de-dupe this by caching filtered images.
2432 // (maybe in the resource cache?)
2433 }
halcanary3d8c33c2015-10-01 11:06:22 -07002434 SkAutoTUnref<SkPDFObject> pdfimage(SkSafeRef(fCanon->findPDFBitmap(image)));
halcanary7a14b312015-10-01 07:28:13 -07002435 if (!pdfimage) {
halcanary712fdf72015-12-10 08:59:43 -08002436 pdfimage.reset(SkPDFCreateBitmapObject(
2437 image, fCanon->fPixelSerializer));
halcanary7a14b312015-10-01 07:28:13 -07002438 if (!pdfimage) {
2439 return;
halcanary287d22d2015-09-24 10:20:05 -07002440 }
halcanary7a14b312015-10-01 07:28:13 -07002441 fCanon->addPDFBitmap(image->uniqueID(), pdfimage);
halcanary287d22d2015-09-24 10:20:05 -07002442 }
halcanary3d8c33c2015-10-01 11:06:22 -07002443 SkPDFUtils::DrawFormXObject(this->addXObjectResource(pdfimage.get()),
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00002444 &content.entry()->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00002445}