blob: 316cd7c896f72b161e23521685797d58ac025189 [file] [log] [blame]
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001/*
vandebo@chromium.org2a22e102011-01-25 21:01:34 +00002 * Copyright (C) 2011 Google Inc.
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "SkPDFDevice.h"
18
19#include "SkColor.h"
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +000020#include "SkClipStack.h"
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +000021#include "SkDraw.h"
vandebo@chromium.org28be72b2010-11-11 21:37:00 +000022#include "SkGlyphCache.h"
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000023#include "SkPaint.h"
vandebo@chromium.orga5180862010-10-26 19:48:49 +000024#include "SkPath.h"
vandebo@chromium.org28be72b2010-11-11 21:37:00 +000025#include "SkPDFFont.h"
vandebo@chromium.orgeb6c7592010-10-26 19:54:45 +000026#include "SkPDFFormXObject.h"
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +000027#include "SkPDFGraphicState.h"
28#include "SkPDFImage.h"
vandebo@chromium.orgda912d62011-03-08 18:31:02 +000029#include "SkPDFShader.h"
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000030#include "SkPDFStream.h"
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +000031#include "SkPDFTypes.h"
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +000032#include "SkPDFUtils.h"
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000033#include "SkRect.h"
34#include "SkString.h"
vandebo@chromium.org28be72b2010-11-11 21:37:00 +000035#include "SkTextFormatParams.h"
36#include "SkTypeface.h"
37#include "SkTypes.h"
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000038
39// Utility functions
40
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +000041static void emit_pdf_color(SkColor color, SkWStream* result) {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000042 SkASSERT(SkColorGetA(color) == 0xFF); // We handle alpha elsewhere.
43 SkScalar colorMax = SkIntToScalar(0xFF);
vandebo@chromium.org094316b2011-03-04 03:15:13 +000044 SkPDFScalar::Append(
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000045 SkScalarDiv(SkIntToScalar(SkColorGetR(color)), colorMax), result);
46 result->writeText(" ");
vandebo@chromium.org094316b2011-03-04 03:15:13 +000047 SkPDFScalar::Append(
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000048 SkScalarDiv(SkIntToScalar(SkColorGetG(color)), colorMax), result);
49 result->writeText(" ");
vandebo@chromium.org094316b2011-03-04 03:15:13 +000050 SkPDFScalar::Append(
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000051 SkScalarDiv(SkIntToScalar(SkColorGetB(color)), colorMax), result);
52 result->writeText(" ");
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000053}
54
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +000055static SkPaint calculate_text_paint(const SkPaint& paint) {
vandebo@chromium.org28be72b2010-11-11 21:37:00 +000056 SkPaint result = paint;
57 if (result.isFakeBoldText()) {
58 SkScalar fakeBoldScale = SkScalarInterpFunc(result.getTextSize(),
59 kStdFakeBoldInterpKeys,
60 kStdFakeBoldInterpValues,
61 kStdFakeBoldInterpLength);
62 SkScalar width = SkScalarMul(result.getTextSize(), fakeBoldScale);
63 if (result.getStyle() == SkPaint::kFill_Style)
64 result.setStyle(SkPaint::kStrokeAndFill_Style);
65 else
66 width += result.getStrokeWidth();
67 result.setStrokeWidth(width);
68 }
69 return result;
70}
71
72// Stolen from measure_text in SkDraw.cpp and then tweaked.
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +000073static void align_text(SkDrawCacheProc glyphCacheProc, const SkPaint& paint,
74 const uint16_t* glyphs, size_t len, SkScalar* x,
75 SkScalar* y, SkScalar* width) {
vandebo@chromium.org28be72b2010-11-11 21:37:00 +000076 if (paint.getTextAlign() == SkPaint::kLeft_Align && width == NULL)
77 return;
78
79 SkMatrix ident;
80 ident.reset();
81 SkAutoGlyphCache autoCache(paint, &ident);
82 SkGlyphCache* cache = autoCache.getCache();
83
84 const char* start = (char*)glyphs;
85 const char* stop = (char*)(glyphs + len);
86 SkFixed xAdv = 0, yAdv = 0;
87
88 // TODO(vandebo) This probably needs to take kerning into account.
89 while (start < stop) {
90 const SkGlyph& glyph = glyphCacheProc(cache, &start, 0, 0);
91 xAdv += glyph.fAdvanceX;
92 yAdv += glyph.fAdvanceY;
93 };
94 if (width)
95 *width = SkFixedToScalar(xAdv);
96 if (paint.getTextAlign() == SkPaint::kLeft_Align)
97 return;
98
99 SkScalar xAdj = SkFixedToScalar(xAdv);
100 SkScalar yAdj = SkFixedToScalar(yAdv);
101 if (paint.getTextAlign() == SkPaint::kCenter_Align) {
102 xAdj = SkScalarHalf(xAdj);
103 yAdj = SkScalarHalf(yAdj);
104 }
105 *x = *x - xAdj;
106 *y = *y - yAdj;
107}
108
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000109static void set_text_transform(SkScalar x, SkScalar y, SkScalar textSkewX,
110 SkWStream* content) {
111 // Flip the text about the x-axis to account for origin swap and include
112 // the passed parameters.
113 content->writeText("1 0 ");
114 SkPDFScalar::Append(0 - textSkewX, content);
115 content->writeText(" -1 ");
116 SkPDFScalar::Append(x, content);
117 content->writeText(" ");
118 SkPDFScalar::Append(y, content);
119 content->writeText(" Tm\n");
120}
121
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000122// It is important to not confuse GraphicStateEntry with SkPDFGraphicState, the
123// later being our representation of an object in the PDF file.
124struct GraphicStateEntry {
125 GraphicStateEntry();
126
127 // Compare the fields we care about when setting up a new content entry.
128 bool compareInitialState(const GraphicStateEntry& b);
129
130 SkMatrix fMatrix;
131 // We can't do set operations on Paths, though PDF natively supports
132 // intersect. If the clip stack does anything other than intersect,
133 // we have to fall back to the region. Treat fClipStack as authoritative.
134 // See http://code.google.com/p/skia/issues/detail?id=221
135 SkClipStack fClipStack;
136 SkRegion fClipRegion;
137
138 // When emitting the content entry, we will ensure the graphic state
139 // is set to these values first.
140 SkColor fColor;
141 SkScalar fTextScaleX; // Zero means we don't care what the value is.
142 SkPaint::Style fTextFill; // Only if TextScaleX is non-zero.
143 int fShaderIndex;
144 int fGraphicStateIndex;
145
146 // We may change the font (i.e. for Type1 support) within a
147 // ContentEntry. This is the one currently in effect, or NULL if none.
148 SkPDFFont* fFont;
149 // In PDF, text size has no default value. It is only valid if fFont is
150 // not NULL.
151 SkScalar fTextSize;
152};
153
154GraphicStateEntry::GraphicStateEntry() : fColor(SK_ColorBLACK),
155 fTextScaleX(SK_Scalar1),
156 fTextFill(SkPaint::kFill_Style),
157 fShaderIndex(-1),
158 fGraphicStateIndex(-1),
159 fFont(NULL),
160 fTextSize(SK_ScalarNaN) {
161 fMatrix.reset();
162}
163
164bool GraphicStateEntry::compareInitialState(const GraphicStateEntry& b) {
165 return fColor == b.fColor &&
166 fShaderIndex == b.fShaderIndex &&
167 fGraphicStateIndex == b.fGraphicStateIndex &&
168 fMatrix == b.fMatrix &&
169 fClipStack == b.fClipStack &&
170 (fTextScaleX == 0 ||
171 b.fTextScaleX == 0 ||
172 (fTextScaleX == b.fTextScaleX && fTextFill == b.fTextFill));
173}
174
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000175class GraphicStackState {
176public:
177 GraphicStackState(const SkClipStack& existingClipStack,
178 const SkRegion& existingClipRegion,
179 SkWStream* contentStream)
180 : fStackDepth(0),
181 fContentStream(contentStream) {
182 fEntries[0].fClipStack = existingClipStack;
183 fEntries[0].fClipRegion = existingClipRegion;
184 }
185
186 void updateClip(const SkClipStack& clipStack, const SkRegion& clipRegion,
187 const SkIPoint& translation);
188 void updateMatrix(const SkMatrix& matrix);
189 void updateDrawingState(const GraphicStateEntry& state);
190
191 void drainStack();
192
193private:
194 void push();
195 void pop();
196 GraphicStateEntry* currentEntry() { return &fEntries[fStackDepth]; }
197
198 // Conservative limit on save depth, see impl. notes in PDF 1.4 spec.
199 static const int kMaxStackDepth = 12;
200 GraphicStateEntry fEntries[kMaxStackDepth + 1];
201 int fStackDepth;
202 SkWStream* fContentStream;
203};
204
205void GraphicStackState::drainStack() {
206 while (fStackDepth) {
207 pop();
208 }
209}
210
211void GraphicStackState::push() {
212 SkASSERT(fStackDepth < kMaxStackDepth);
213 fContentStream->writeText("q\n");
214 fStackDepth++;
215 fEntries[fStackDepth] = fEntries[fStackDepth - 1];
216}
217
218void GraphicStackState::pop() {
219 SkASSERT(fStackDepth > 0);
220 fContentStream->writeText("Q\n");
221 fStackDepth--;
222}
223
224// This function initializes iter to be an interator on the "stack" argument
225// and then skips over the leading entries as specified in prefix. It requires
226// and asserts that "prefix" will be a prefix to "stack."
227static void skip_clip_stack_prefix(const SkClipStack& prefix,
228 const SkClipStack& stack,
229 SkClipStack::B2FIter* iter) {
230 SkClipStack::B2FIter prefixIter(prefix);
231 iter->reset(stack);
232
233 const SkClipStack::B2FIter::Clip* prefixEntry;
234 const SkClipStack::B2FIter::Clip* iterEntry;
235
236 for (prefixEntry = prefixIter.next(); prefixEntry;
237 prefixEntry = prefixIter.next()) {
238 iterEntry = iter->next();
239 SkASSERT(iterEntry);
240 SkASSERT(*prefixEntry == *iterEntry);
241 }
242
243 SkASSERT(prefixEntry == NULL);
244}
245
246static void emit_clip(SkPath* clipPath, SkRect* clipRect,
247 SkWStream* contentStream) {
248 SkASSERT(clipPath || clipRect);
249
250 SkPath::FillType clipFill;
251 if (clipPath) {
252 SkPDFUtils::EmitPath(*clipPath, contentStream);
253 clipFill = clipPath->getFillType();
254 } else if (clipRect) {
255 SkPDFUtils::AppendRectangle(*clipRect, contentStream);
256 clipFill = SkPath::kWinding_FillType;
257 }
258
259 NOT_IMPLEMENTED(clipFill == SkPath::kInverseEvenOdd_FillType, false);
260 NOT_IMPLEMENTED(clipFill == SkPath::kInverseWinding_FillType, false);
261 if (clipFill == SkPath::kEvenOdd_FillType) {
262 contentStream->writeText("W* n\n");
263 } else {
264 contentStream->writeText("W n\n");
265 }
266}
267
268// TODO(vandebo) Take advantage of SkClipStack::getSaveCount(), the PDF
269// graphic state stack, and the fact that we can know all the clips used
270// on the page to optimize this.
271void GraphicStackState::updateClip(const SkClipStack& clipStack,
272 const SkRegion& clipRegion,
273 const SkIPoint& translation) {
274 if (clipStack == currentEntry()->fClipStack) {
275 return;
276 }
277
278 while (fStackDepth > 0) {
279 pop();
280 if (clipStack == currentEntry()->fClipStack) {
281 return;
282 }
283 }
284 push();
285
286 // gsState->initialEntry()->fClipStack/Region specifies the clip that has
287 // already been applied. (If this is a top level device, then it specifies
288 // a clip to the content area. If this is a layer, then it specifies
289 // the clip in effect when the layer was created.) There's no need to
290 // reapply that clip; SKCanvas's SkDrawIter will draw anything outside the
291 // initial clip on the parent layer. (This means there's a bug if the user
292 // expands the clip and then uses any xfer mode that uses dst:
293 // http://code.google.com/p/skia/issues/detail?id=228 )
294 SkClipStack::B2FIter iter;
295 skip_clip_stack_prefix(fEntries[0].fClipStack, clipStack, &iter);
296
297 // If the clip stack does anything other than intersect or if it uses
298 // an inverse fill type, we have to fall back to the clip region.
299 bool needRegion = false;
300 const SkClipStack::B2FIter::Clip* clipEntry;
301 for (clipEntry = iter.next(); clipEntry; clipEntry = iter.next()) {
302 if (clipEntry->fOp != SkRegion::kIntersect_Op ||
303 (clipEntry->fPath && clipEntry->fPath->isInverseFillType())) {
304 needRegion = true;
305 break;
306 }
307 }
308
309 if (needRegion) {
310 SkPath clipPath;
311 SkAssertResult(clipRegion.getBoundaryPath(&clipPath));
312 emit_clip(&clipPath, NULL, fContentStream);
313 } else {
314 skip_clip_stack_prefix(fEntries[0].fClipStack, clipStack, &iter);
315 SkMatrix transform;
316 transform.setTranslate(translation.fX, translation.fY);
317 const SkClipStack::B2FIter::Clip* clipEntry;
318 for (clipEntry = iter.next(); clipEntry; clipEntry = iter.next()) {
319 SkASSERT(clipEntry->fOp == SkRegion::kIntersect_Op);
320 if (clipEntry->fRect) {
321 SkRect translatedClip;
322 transform.mapRect(&translatedClip, *clipEntry->fRect);
323 emit_clip(NULL, &translatedClip, fContentStream);
324 } else if (clipEntry->fPath) {
325 SkPath translatedPath;
326 clipEntry->fPath->transform(transform, &translatedPath);
327 emit_clip(&translatedPath, NULL, fContentStream);
328 } else {
329 SkASSERT(false);
330 }
331 }
332 }
333 currentEntry()->fClipStack = clipStack;
334 currentEntry()->fClipRegion = clipRegion;
335}
336
337void GraphicStackState::updateMatrix(const SkMatrix& matrix) {
338 if (matrix == currentEntry()->fMatrix) {
339 return;
340 }
341
342 if (currentEntry()->fMatrix.getType() != SkMatrix::kIdentity_Mask) {
343 SkASSERT(fStackDepth > 0);
344 SkASSERT(fEntries[fStackDepth].fClipStack ==
345 fEntries[fStackDepth -1].fClipStack);
346 pop();
347
348 SkASSERT(currentEntry()->fMatrix.getType() == SkMatrix::kIdentity_Mask);
349 }
350 if (matrix.getType() == SkMatrix::kIdentity_Mask) {
351 return;
352 }
353
354 push();
355 SkPDFUtils::AppendTransform(matrix, fContentStream);
356 currentEntry()->fMatrix = matrix;
357}
358
359void GraphicStackState::updateDrawingState(const GraphicStateEntry& state) {
360 // PDF treats a shader as a color, so we only set one or the other.
361 if (state.fShaderIndex >= 0) {
362 if (state.fShaderIndex != currentEntry()->fShaderIndex) {
363 fContentStream->writeText("/Pattern CS /Pattern cs /P");
364 fContentStream->writeDecAsText(state.fShaderIndex);
365 fContentStream->writeText(" SCN /P");
366 fContentStream->writeDecAsText(state.fShaderIndex);
367 fContentStream->writeText(" scn\n");
368 currentEntry()->fShaderIndex = state.fShaderIndex;
369 }
370 } else {
371 if (state.fColor != currentEntry()->fColor ||
372 currentEntry()->fShaderIndex >= 0) {
373 emit_pdf_color(state.fColor, fContentStream);
374 fContentStream->writeText("RG ");
375 emit_pdf_color(state.fColor, fContentStream);
376 fContentStream->writeText("rg\n");
377 currentEntry()->fColor = state.fColor;
378 currentEntry()->fShaderIndex = -1;
379 }
380 }
381
382 if (state.fGraphicStateIndex != currentEntry()->fGraphicStateIndex) {
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000383 SkPDFUtils::ApplyGraphicState(state.fGraphicStateIndex, fContentStream);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000384 currentEntry()->fGraphicStateIndex = state.fGraphicStateIndex;
385 }
386
387 if (state.fTextScaleX) {
388 if (state.fTextScaleX != currentEntry()->fTextScaleX) {
389 SkScalar pdfScale = SkScalarMul(state.fTextScaleX,
390 SkIntToScalar(100));
391 SkPDFScalar::Append(pdfScale, fContentStream);
392 fContentStream->writeText(" Tz\n");
393 currentEntry()->fTextScaleX = state.fTextScaleX;
394 }
395 if (state.fTextFill != currentEntry()->fTextFill) {
396 SK_COMPILE_ASSERT(SkPaint::kFill_Style == 0, enum_must_match_value);
397 SK_COMPILE_ASSERT(SkPaint::kStroke_Style == 1,
398 enum_must_match_value);
399 SK_COMPILE_ASSERT(SkPaint::kStrokeAndFill_Style == 2,
400 enum_must_match_value);
401 fContentStream->writeDecAsText(state.fTextFill);
402 fContentStream->writeText(" Tr\n");
403 currentEntry()->fTextFill = state.fTextFill;
404 }
405 }
406}
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000407
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000408struct ContentEntry {
409 GraphicStateEntry fState;
410 SkDynamicMemoryWStream fContent;
411 SkTScopedPtr<ContentEntry> fNext;
412};
413
414// A helper class to automatically finish a ContentEntry at the end of a
415// drawing method and maintain the state needed between set up and finish.
416class ContentEntryAccessor {
417public:
418 ContentEntryAccessor(SkPDFDevice* device, const SkDraw& draw,
419 const SkPaint& paint, bool hasText = false)
420 : fDevice(device),
421 fContentEntry(NULL),
422 fXfermode(SkXfermode::kSrcOver_Mode) {
423 init(draw.fClipStack, *draw.fClip, *draw.fMatrix, paint, hasText);
424 }
425 ContentEntryAccessor(SkPDFDevice* device, const SkClipStack* clipStack,
426 const SkRegion& clipRegion, const SkMatrix& matrix,
427 const SkPaint& paint, bool hasText = false)
428 : fDevice(device),
429 fContentEntry(NULL),
430 fXfermode(SkXfermode::kSrcOver_Mode) {
431 init(clipStack, clipRegion, matrix, paint, hasText);
432 }
433
434 ~ContentEntryAccessor() {
435 if (fContentEntry) {
436 fDevice->finishContentEntry(fXfermode, fDstFormXObject.get());
437 }
438 }
439
440 ContentEntry* entry() { return fContentEntry; }
441private:
442 SkPDFDevice* fDevice;
443 ContentEntry* fContentEntry;
444 SkXfermode::Mode fXfermode;
445 SkRefPtr<SkPDFFormXObject> fDstFormXObject;
446
447 void init(const SkClipStack* clipStack, const SkRegion& clipRegion,
448 const SkMatrix& matrix, const SkPaint& paint, bool hasText) {
449 if (paint.getXfermode()) {
450 paint.getXfermode()->asMode(&fXfermode);
451 }
452 fContentEntry = fDevice->setUpContentEntry(clipStack, clipRegion,
453 matrix, paint, hasText,
454 &fDstFormXObject);
455 }
456};
457
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000458////////////////////////////////////////////////////////////////////////////////
459
vandebo@chromium.orga0c7edb2011-05-09 07:58:08 +0000460SkDevice* SkPDFDeviceFactory::newDevice(SkCanvas* c, SkBitmap::Config config,
reed@android.comf2b98d62010-12-20 18:26:13 +0000461 int width, int height, bool isOpaque,
vandebo@chromium.orgf60a0012011-02-24 23:14:04 +0000462 bool isForLayer) {
vandebo@chromium.org75f97e42011-04-11 23:24:18 +0000463 SkMatrix initialTransform;
464 initialTransform.reset();
ctguil@chromium.org15261292011-04-29 17:54:16 +0000465 SkISize size = SkISize::Make(width, height);
vandebo@chromium.orga0c7edb2011-05-09 07:58:08 +0000466 if (isForLayer) {
467 return SkNEW_ARGS(SkPDFDevice, (size, c->getTotalClipStack(),
468 c->getTotalClip()));
469 } else {
470 return SkNEW_ARGS(SkPDFDevice, (size, size, initialTransform));
471 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000472}
473
ctguil@chromium.org15261292011-04-29 17:54:16 +0000474static inline SkBitmap makeContentBitmap(const SkISize& contentSize,
vandebo@chromium.orga0c7edb2011-05-09 07:58:08 +0000475 const SkMatrix* initialTransform) {
reed@android.comf2b98d62010-12-20 18:26:13 +0000476 SkBitmap bitmap;
vandebo@chromium.orga0c7edb2011-05-09 07:58:08 +0000477 if (initialTransform) {
478 // Compute the size of the drawing area.
479 SkVector drawingSize;
480 SkMatrix inverse;
481 drawingSize.set(contentSize.fWidth, contentSize.fHeight);
482 initialTransform->invert(&inverse);
483 inverse.mapVectors(&drawingSize, 1);
484 SkISize size = SkSize::Make(drawingSize.fX, drawingSize.fY).toRound();
485 bitmap.setConfig(SkBitmap::kNo_Config, abs(size.fWidth),
486 abs(size.fHeight));
487 } else {
488 bitmap.setConfig(SkBitmap::kNo_Config, abs(contentSize.fWidth),
489 abs(contentSize.fHeight));
490 }
491
reed@android.comf2b98d62010-12-20 18:26:13 +0000492 return bitmap;
493}
494
ctguil@chromium.org15261292011-04-29 17:54:16 +0000495SkPDFDevice::SkPDFDevice(const SkISize& pageSize, const SkISize& contentSize,
vandebo@chromium.org75f97e42011-04-11 23:24:18 +0000496 const SkMatrix& initialTransform)
vandebo@chromium.orga0c7edb2011-05-09 07:58:08 +0000497 : SkDevice(NULL, makeContentBitmap(contentSize, &initialTransform), false),
ctguil@chromium.org15261292011-04-29 17:54:16 +0000498 fPageSize(pageSize),
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000499 fContentSize(contentSize),
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000500 fLastContentEntry(NULL) {
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000501 // Skia generally uses the top left as the origin but PDF natively has the
vandebo@chromium.orga0c7edb2011-05-09 07:58:08 +0000502 // origin at the bottom left. This matrix corrects for that. But that only
503 // needs to be done once, we don't do it when layering.
ctguil@chromium.org15261292011-04-29 17:54:16 +0000504 fInitialTransform.setTranslate(0, pageSize.fHeight);
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000505 fInitialTransform.preScale(1, -1);
506 fInitialTransform.preConcat(initialTransform);
507
vandebo@chromium.orga0c7edb2011-05-09 07:58:08 +0000508 SkIRect existingClip = SkIRect::MakeWH(this->width(), this->height());
509 fExistingClipStack.clipDevRect(existingClip);
510 fExistingClipRegion.setRect(existingClip);
511
512 this->init();
513}
514
515SkPDFDevice::SkPDFDevice(const SkISize& layerSize,
516 const SkClipStack& existingClipStack,
517 const SkRegion& existingClipRegion)
518 : SkDevice(NULL, makeContentBitmap(layerSize, NULL), false),
519 fPageSize(layerSize),
520 fContentSize(layerSize),
521 fExistingClipStack(existingClipStack),
522 fExistingClipRegion(existingClipRegion),
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000523 fLastContentEntry(NULL) {
vandebo@chromium.orga0c7edb2011-05-09 07:58:08 +0000524 fInitialTransform.reset();
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000525 this->init();
526}
527
528SkPDFDevice::~SkPDFDevice() {
529 this->cleanUp();
530}
531
532void SkPDFDevice::init() {
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000533 fResourceDict = NULL;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000534 fContentEntries.reset();
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000535 fLastContentEntry = NULL;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000536}
537
mike@reedtribe.orgea4ac972011-04-26 11:48:33 +0000538SkDeviceFactory* SkPDFDevice::onNewDeviceFactory() {
539 return SkNEW(SkPDFDeviceFactory);
540}
541
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000542void SkPDFDevice::cleanUp() {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000543 fGraphicStateResources.unrefAll();
544 fXObjectResources.unrefAll();
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000545 fFontResources.unrefAll();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000546 fShaderResources.unrefAll();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000547}
548
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000549void SkPDFDevice::clear(SkColor color) {
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000550 this->cleanUp();
551 this->init();
552
553 SkPaint paint;
554 paint.setColor(color);
555 paint.setStyle(SkPaint::kFill_Style);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000556 SkMatrix identity;
557 identity.reset();
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000558 ContentEntryAccessor content(this, &fExistingClipStack, fExistingClipRegion,
559 identity, paint);
560 internalDrawPaint(paint, content.entry());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000561}
562
563void SkPDFDevice::drawPaint(const SkDraw& d, const SkPaint& paint) {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000564 SkPaint newPaint = paint;
565 newPaint.setStyle(SkPaint::kFill_Style);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000566 ContentEntryAccessor content(this, d, newPaint);
567 internalDrawPaint(newPaint, content.entry());
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000568}
569
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000570void SkPDFDevice::internalDrawPaint(const SkPaint& paint,
571 ContentEntry* contentEntry) {
572 if (!contentEntry) {
573 return;
574 }
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000575 SkRect bbox = SkRect::MakeWH(SkIntToScalar(this->width()),
576 SkIntToScalar(this->height()));
577 SkMatrix totalTransform = fInitialTransform;
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000578 totalTransform.preConcat(contentEntry->fState.fMatrix);
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000579 SkMatrix inverse;
580 inverse.reset();
581 totalTransform.invert(&inverse);
582 inverse.mapRect(&bbox);
583
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000584 SkPDFUtils::AppendRectangle(bbox, &contentEntry->fContent);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000585 SkPDFUtils::PaintPath(paint.getStyle(), SkPath::kWinding_FillType,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000586 &contentEntry->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000587}
588
589void SkPDFDevice::drawPoints(const SkDraw& d, SkCanvas::PointMode mode,
590 size_t count, const SkPoint* points,
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000591 const SkPaint& passedPaint) {
592 if (count == 0) {
593 return;
594 }
595
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000596 // SkDraw::drawPoints converts to multiple calls to fDevice->drawPath.
597 // We only use this when there's a path effect because of the overhead
598 // of multiple calls to setUpContentEntry it causes.
599 if (passedPaint.getPathEffect()) {
600 if (d.fClip->isEmpty()) {
601 return;
602 }
603 SkDraw pointDraw(d);
604 pointDraw.fDevice = this;
605 pointDraw.drawPoints(mode, count, points, passedPaint, true);
606 return;
607 }
608
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000609 const SkPaint* paint = &passedPaint;
610 SkPaint modifiedPaint;
611
612 if (mode == SkCanvas::kPoints_PointMode &&
613 paint->getStrokeCap() != SkPaint::kRound_Cap) {
614 modifiedPaint = *paint;
615 paint = &modifiedPaint;
616 if (paint->getStrokeWidth()) {
617 // PDF won't draw a single point with square/butt caps because the
618 // orientation is ambiguous. Draw a rectangle instead.
619 modifiedPaint.setStyle(SkPaint::kFill_Style);
620 SkScalar strokeWidth = paint->getStrokeWidth();
621 SkScalar halfStroke = SkScalarHalf(strokeWidth);
622 for (size_t i = 0; i < count; i++) {
623 SkRect r = SkRect::MakeXYWH(points[i].fX, points[i].fY, 0, 0);
624 r.inset(-halfStroke, -halfStroke);
625 drawRect(d, r, modifiedPaint);
626 }
627 return;
628 } else {
629 modifiedPaint.setStrokeCap(SkPaint::kRound_Cap);
630 }
631 }
632
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000633 ContentEntryAccessor content(this, d, *paint);
634 if (!content.entry()) {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000635 return;
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000636 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000637
638 switch (mode) {
639 case SkCanvas::kPolygon_PointMode:
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000640 SkPDFUtils::MoveTo(points[0].fX, points[0].fY,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000641 &content.entry()->fContent);
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +0000642 for (size_t i = 1; i < count; i++) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000643 SkPDFUtils::AppendLine(points[i].fX, points[i].fY,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000644 &content.entry()->fContent);
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +0000645 }
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000646 SkPDFUtils::StrokePath(&content.entry()->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000647 break;
648 case SkCanvas::kLines_PointMode:
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000649 for (size_t i = 0; i < count/2; i++) {
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +0000650 SkPDFUtils::MoveTo(points[i * 2].fX, points[i * 2].fY,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000651 &content.entry()->fContent);
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +0000652 SkPDFUtils::AppendLine(points[i * 2 + 1].fX,
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000653 points[i * 2 + 1].fY,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000654 &content.entry()->fContent);
655 SkPDFUtils::StrokePath(&content.entry()->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000656 }
657 break;
658 case SkCanvas::kPoints_PointMode:
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000659 SkASSERT(paint->getStrokeCap() == SkPaint::kRound_Cap);
660 for (size_t i = 0; i < count; i++) {
661 SkPDFUtils::MoveTo(points[i].fX, points[i].fY,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000662 &content.entry()->fContent);
663 SkPDFUtils::ClosePath(&content.entry()->fContent);
664 SkPDFUtils::StrokePath(&content.entry()->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000665 }
666 break;
667 default:
668 SkASSERT(false);
669 }
670}
671
672void SkPDFDevice::drawRect(const SkDraw& d, const SkRect& r,
673 const SkPaint& paint) {
674 if (paint.getPathEffect()) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000675 if (d.fClip->isEmpty()) {
676 return;
677 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000678 SkPath path;
679 path.addRect(r);
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000680 drawPath(d, path, paint, NULL, true);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000681 return;
682 }
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000683
684 ContentEntryAccessor content(this, d, paint);
685 if (!content.entry()) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000686 return;
687 }
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000688 SkPDFUtils::AppendRectangle(r, &content.entry()->fContent);
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +0000689 SkPDFUtils::PaintPath(paint.getStyle(), SkPath::kWinding_FillType,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000690 &content.entry()->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000691}
692
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000693void SkPDFDevice::drawPath(const SkDraw& d, const SkPath& origPath,
vandebo@chromium.org02cc5aa2011-01-25 22:06:29 +0000694 const SkPaint& paint, const SkMatrix* prePathMatrix,
695 bool pathIsMutable) {
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000696 SkPath modifiedPath;
697 SkPath* pathPtr = const_cast<SkPath*>(&origPath);
698
699 SkMatrix matrix = *d.fMatrix;
700 if (prePathMatrix) {
701 if (paint.getPathEffect() || paint.getStyle() != SkPaint::kFill_Style) {
702 if (!pathIsMutable) {
703 pathPtr = &modifiedPath;
704 pathIsMutable = true;
705 }
706 origPath.transform(*prePathMatrix, pathPtr);
707 } else {
708 if (!matrix.preConcat(*prePathMatrix)) {
709 return;
710 }
711 }
712 }
vandebo@chromium.org02cc5aa2011-01-25 22:06:29 +0000713
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000714 if (paint.getPathEffect()) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000715 if (d.fClip->isEmpty()) {
716 return;
717 }
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000718 if (!pathIsMutable) {
719 pathPtr = &modifiedPath;
720 pathIsMutable = true;
721 }
722 bool fill = paint.getFillPath(origPath, pathPtr);
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000723
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000724 SkPaint noEffectPaint(paint);
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000725 noEffectPaint.setPathEffect(NULL);
726 if (fill) {
727 noEffectPaint.setStyle(SkPaint::kFill_Style);
728 } else {
729 noEffectPaint.setStyle(SkPaint::kStroke_Style);
730 noEffectPaint.setStrokeWidth(0);
731 }
732 drawPath(d, *pathPtr, noEffectPaint, NULL, true);
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000733 return;
734 }
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000735
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000736 ContentEntryAccessor content(this, d, paint);
737 if (!content.entry()) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000738 return;
739 }
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000740 SkPDFUtils::EmitPath(*pathPtr, &content.entry()->fContent);
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000741 SkPDFUtils::PaintPath(paint.getStyle(), pathPtr->getFillType(),
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000742 &content.entry()->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000743}
744
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000745void SkPDFDevice::drawBitmap(const SkDraw& d, const SkBitmap& bitmap,
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000746 const SkIRect* srcRect, const SkMatrix& matrix,
747 const SkPaint& paint) {
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000748 if (d.fClip->isEmpty()) {
749 return;
750 }
751
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000752 SkMatrix transform = matrix;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000753 transform.postConcat(*d.fMatrix);
vandebo@chromium.org78dad542011-05-11 18:46:03 +0000754 internalDrawBitmap(transform, d.fClipStack, *d.fClip, bitmap, srcRect,
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000755 paint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000756}
757
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000758void SkPDFDevice::drawSprite(const SkDraw& d, const SkBitmap& bitmap,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000759 int x, int y, const SkPaint& paint) {
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000760 if (d.fClip->isEmpty()) {
761 return;
762 }
763
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000764 SkMatrix matrix;
reed@google.coma6d59f62011-03-07 21:29:21 +0000765 matrix.setTranslate(SkIntToScalar(x), SkIntToScalar(y));
vandebo@chromium.org78dad542011-05-11 18:46:03 +0000766 internalDrawBitmap(matrix, d.fClipStack, *d.fClip, bitmap, NULL, paint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000767}
768
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000769void SkPDFDevice::drawText(const SkDraw& d, const void* text, size_t len,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000770 SkScalar x, SkScalar y, const SkPaint& paint) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000771 SkPaint textPaint = calculate_text_paint(paint);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000772 ContentEntryAccessor content(this, d, textPaint, true);
773 if (!content.entry()) {
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000774 return;
775 }
776
vandebo@chromium.org01294102011-02-28 19:52:18 +0000777 // We want the text in glyph id encoding and a writable buffer, so we end
778 // up making a copy either way.
779 size_t numGlyphs = paint.textToGlyphs(text, len, NULL);
780 uint16_t* glyphIDs =
781 (uint16_t*)sk_malloc_flags(numGlyphs * 2,
782 SK_MALLOC_TEMP | SK_MALLOC_THROW);
783 SkAutoFree autoFreeGlyphIDs(glyphIDs);
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000784 if (paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding) {
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000785 paint.textToGlyphs(text, len, glyphIDs);
786 textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
787 } else {
788 SkASSERT((len & 1) == 0);
vandebo@chromium.org01294102011-02-28 19:52:18 +0000789 SkASSERT(len / 2 == numGlyphs);
790 memcpy(glyphIDs, text, len);
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000791 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000792
793 SkScalar width;
794 SkScalar* widthPtr = NULL;
795 if (textPaint.isUnderlineText() || textPaint.isStrikeThruText())
796 widthPtr = &width;
797
798 SkDrawCacheProc glyphCacheProc = textPaint.getDrawCacheProc();
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000799 align_text(glyphCacheProc, textPaint, glyphIDs, numGlyphs, &x, &y,
800 widthPtr);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000801 content.entry()->fContent.writeText("BT\n");
802 set_text_transform(x, y, textPaint.getTextSkewX(),
803 &content.entry()->fContent);
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000804 size_t consumedGlyphCount = 0;
805 while (numGlyphs > consumedGlyphCount) {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000806 updateFont(textPaint, glyphIDs[consumedGlyphCount], content.entry());
807 SkPDFFont* font = content.entry()->fState.fFont;
vandebo@chromium.org01294102011-02-28 19:52:18 +0000808 size_t availableGlyphs =
809 font->glyphsToPDFFontEncoding(glyphIDs + consumedGlyphCount,
810 numGlyphs - consumedGlyphCount);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000811 SkString encodedString =
812 SkPDFString::formatString(glyphIDs + consumedGlyphCount,
813 availableGlyphs, font->multiByteGlyphs());
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000814 content.entry()->fContent.writeText(encodedString.c_str());
vandebo@chromium.org01294102011-02-28 19:52:18 +0000815 consumedGlyphCount += availableGlyphs;
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000816 content.entry()->fContent.writeText(" Tj\n");
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000817 }
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000818 content.entry()->fContent.writeText("ET\n");
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000819
820 // Draw underline and/or strikethrough if the paint has them.
821 // drawPosText() and drawTextOnPath() don't draw underline or strikethrough
822 // because the raster versions don't. Use paint instead of textPaint
823 // because we may have changed strokeWidth to do fakeBold text.
824 if (paint.isUnderlineText() || paint.isStrikeThruText()) {
825 SkScalar textSize = paint.getTextSize();
826 SkScalar height = SkScalarMul(textSize, kStdUnderline_Thickness);
827
828 if (paint.isUnderlineText()) {
829 SkScalar top = SkScalarMulAdd(textSize, kStdUnderline_Offset, y);
830 SkRect r = SkRect::MakeXYWH(x, top - height, width, height);
831 drawRect(d, r, paint);
832 }
833 if (paint.isStrikeThruText()) {
834 SkScalar top = SkScalarMulAdd(textSize, kStdStrikeThru_Offset, y);
835 SkRect r = SkRect::MakeXYWH(x, top - height, width, height);
836 drawRect(d, r, paint);
837 }
838 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000839}
840
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000841void SkPDFDevice::drawPosText(const SkDraw& d, const void* text, size_t len,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000842 const SkScalar pos[], SkScalar constY,
843 int scalarsPerPos, const SkPaint& paint) {
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000844 SkASSERT(1 == scalarsPerPos || 2 == scalarsPerPos);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000845 SkPaint textPaint = calculate_text_paint(paint);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000846 ContentEntryAccessor content(this, d, textPaint, true);
847 if (!content.entry()) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000848 return;
849 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000850
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000851 // Make sure we have a glyph id encoding.
852 SkAutoFree glyphStorage;
853 uint16_t* glyphIDs;
854 size_t numGlyphs;
855 if (paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding) {
856 numGlyphs = paint.textToGlyphs(text, len, NULL);
857 glyphIDs = (uint16_t*)sk_malloc_flags(numGlyphs * 2,
858 SK_MALLOC_TEMP | SK_MALLOC_THROW);
859 glyphStorage.set(glyphIDs);
860 paint.textToGlyphs(text, len, glyphIDs);
861 textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
862 } else {
863 SkASSERT((len & 1) == 0);
864 numGlyphs = len / 2;
865 glyphIDs = (uint16_t*)text;
866 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000867
868 SkDrawCacheProc glyphCacheProc = textPaint.getDrawCacheProc();
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000869 content.entry()->fContent.writeText("BT\n");
870 updateFont(textPaint, glyphIDs[0], content.entry());
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000871 for (size_t i = 0; i < numGlyphs; i++) {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000872 SkPDFFont* font = content.entry()->fState.fFont;
vandebo@chromium.org01294102011-02-28 19:52:18 +0000873 uint16_t encodedValue = glyphIDs[i];
874 if (font->glyphsToPDFFontEncoding(&encodedValue, 1) != 1) {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000875 updateFont(textPaint, glyphIDs[i], content.entry());
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000876 i--;
877 continue;
878 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000879 SkScalar x = pos[i * scalarsPerPos];
880 SkScalar y = scalarsPerPos == 1 ? constY : pos[i * scalarsPerPos + 1];
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000881 align_text(glyphCacheProc, textPaint, glyphIDs + i, 1, &x, &y, NULL);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000882 set_text_transform(x, y, textPaint.getTextSkewX(),
883 &content.entry()->fContent);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000884 SkString encodedString =
885 SkPDFString::formatString(&encodedValue, 1,
886 font->multiByteGlyphs());
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000887 content.entry()->fContent.writeText(encodedString.c_str());
888 content.entry()->fContent.writeText(" Tj\n");
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000889 }
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000890 content.entry()->fContent.writeText("ET\n");
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000891}
892
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000893void SkPDFDevice::drawTextOnPath(const SkDraw& d, const void* text, size_t len,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000894 const SkPath& path, const SkMatrix* matrix,
895 const SkPaint& paint) {
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000896 if (d.fClip->isEmpty()) {
897 return;
898 }
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000899 NOT_IMPLEMENTED("drawTextOnPath", true);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000900}
901
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000902void SkPDFDevice::drawVertices(const SkDraw& d, SkCanvas::VertexMode,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000903 int vertexCount, const SkPoint verts[],
904 const SkPoint texs[], const SkColor colors[],
905 SkXfermode* xmode, const uint16_t indices[],
906 int indexCount, const SkPaint& paint) {
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000907 if (d.fClip->isEmpty()) {
908 return;
909 }
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000910 NOT_IMPLEMENTED("drawVerticies", true);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000911}
912
vandebo@chromium.orgeb6c7592010-10-26 19:54:45 +0000913void SkPDFDevice::drawDevice(const SkDraw& d, SkDevice* device, int x, int y,
914 const SkPaint& paint) {
915 if ((device->getDeviceCapabilities() & kVector_Capability) == 0) {
916 // If we somehow get a raster device, do what our parent would do.
917 SkDevice::drawDevice(d, device, x, y, paint);
918 return;
919 }
vandebo@chromium.orgeb6c7592010-10-26 19:54:45 +0000920
vandebo@chromium.org1aef2ed2011-02-03 21:46:10 +0000921 SkMatrix matrix;
reed@google.coma6d59f62011-03-07 21:29:21 +0000922 matrix.setTranslate(SkIntToScalar(x), SkIntToScalar(y));
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000923 ContentEntryAccessor content(this, d.fClipStack, *d.fClip, matrix, paint);
924 if (!content.entry()) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000925 return;
926 }
vandebo@chromium.org1aef2ed2011-02-03 21:46:10 +0000927
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000928 // Assume that a vector capable device means that it's a PDF Device.
929 SkPDFDevice* pdfDevice = static_cast<SkPDFDevice*>(device);
vandebo@chromium.org1aef2ed2011-02-03 21:46:10 +0000930 SkPDFFormXObject* xobject = new SkPDFFormXObject(pdfDevice);
vandebo@chromium.orgeb6c7592010-10-26 19:54:45 +0000931 fXObjectResources.push(xobject); // Transfer reference.
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000932 SkPDFUtils::DrawFormXObject(fXObjectResources.count() - 1,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000933 &content.entry()->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000934}
935
936const SkRefPtr<SkPDFDict>& SkPDFDevice::getResourceDict() {
937 if (fResourceDict.get() == NULL) {
938 fResourceDict = new SkPDFDict;
939 fResourceDict->unref(); // SkRefPtr and new both took a reference.
940
941 if (fGraphicStateResources.count()) {
942 SkRefPtr<SkPDFDict> extGState = new SkPDFDict();
943 extGState->unref(); // SkRefPtr and new both took a reference.
944 for (int i = 0; i < fGraphicStateResources.count(); i++) {
945 SkString nameString("G");
946 nameString.appendS32(i);
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000947 extGState->insert(
948 nameString.c_str(),
949 new SkPDFObjRef(fGraphicStateResources[i]))->unref();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000950 }
951 fResourceDict->insert("ExtGState", extGState.get());
952 }
953
954 if (fXObjectResources.count()) {
955 SkRefPtr<SkPDFDict> xObjects = new SkPDFDict();
956 xObjects->unref(); // SkRefPtr and new both took a reference.
957 for (int i = 0; i < fXObjectResources.count(); i++) {
958 SkString nameString("X");
959 nameString.appendS32(i);
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000960 xObjects->insert(
961 nameString.c_str(),
962 new SkPDFObjRef(fXObjectResources[i]))->unref();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000963 }
964 fResourceDict->insert("XObject", xObjects.get());
965 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000966
967 if (fFontResources.count()) {
968 SkRefPtr<SkPDFDict> fonts = new SkPDFDict();
969 fonts->unref(); // SkRefPtr and new both took a reference.
970 for (int i = 0; i < fFontResources.count(); i++) {
971 SkString nameString("F");
972 nameString.appendS32(i);
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000973 fonts->insert(nameString.c_str(),
974 new SkPDFObjRef(fFontResources[i]))->unref();
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000975 }
976 fResourceDict->insert("Font", fonts.get());
977 }
978
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000979 if (fShaderResources.count()) {
980 SkRefPtr<SkPDFDict> patterns = new SkPDFDict();
981 patterns->unref(); // SkRefPtr and new both took a reference.
982 for (int i = 0; i < fShaderResources.count(); i++) {
983 SkString nameString("P");
984 nameString.appendS32(i);
985 patterns->insert(nameString.c_str(),
986 new SkPDFObjRef(fShaderResources[i]))->unref();
987 }
988 fResourceDict->insert("Pattern", patterns.get());
989 }
990
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000991 // For compatibility, add all proc sets (only used for output to PS
992 // devices).
993 const char procs[][7] = {"PDF", "Text", "ImageB", "ImageC", "ImageI"};
994 SkRefPtr<SkPDFArray> procSets = new SkPDFArray();
995 procSets->unref(); // SkRefPtr and new both took a reference.
996 procSets->reserve(SK_ARRAY_COUNT(procs));
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000997 for (size_t i = 0; i < SK_ARRAY_COUNT(procs); i++)
998 procSets->append(new SkPDFName(procs[i]))->unref();
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000999 fResourceDict->insert("ProcSet", procSets.get());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001000 }
1001 return fResourceDict;
1002}
1003
vandebo@chromium.orga5180862010-10-26 19:48:49 +00001004void SkPDFDevice::getResources(SkTDArray<SkPDFObject*>* resourceList) const {
1005 resourceList->setReserve(resourceList->count() +
1006 fGraphicStateResources.count() +
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00001007 fXObjectResources.count() +
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001008 fFontResources.count() +
1009 fShaderResources.count());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001010 for (int i = 0; i < fGraphicStateResources.count(); i++) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +00001011 resourceList->push(fGraphicStateResources[i]);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001012 fGraphicStateResources[i]->ref();
vandebo@chromium.orga5180862010-10-26 19:48:49 +00001013 fGraphicStateResources[i]->getResources(resourceList);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001014 }
1015 for (int i = 0; i < fXObjectResources.count(); i++) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +00001016 resourceList->push(fXObjectResources[i]);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001017 fXObjectResources[i]->ref();
vandebo@chromium.orga5180862010-10-26 19:48:49 +00001018 fXObjectResources[i]->getResources(resourceList);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001019 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00001020 for (int i = 0; i < fFontResources.count(); i++) {
1021 resourceList->push(fFontResources[i]);
1022 fFontResources[i]->ref();
1023 fFontResources[i]->getResources(resourceList);
1024 }
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001025 for (int i = 0; i < fShaderResources.count(); i++) {
1026 resourceList->push(fShaderResources[i]);
1027 fShaderResources[i]->ref();
1028 fShaderResources[i]->getResources(resourceList);
1029 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001030}
1031
vandebo@chromium.orga5180862010-10-26 19:48:49 +00001032SkRefPtr<SkPDFArray> SkPDFDevice::getMediaBox() const {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001033 SkRefPtr<SkPDFInt> zero = new SkPDFInt(0);
1034 zero->unref(); // SkRefPtr and new both took a reference.
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +00001035
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001036 SkRefPtr<SkPDFArray> mediaBox = new SkPDFArray();
1037 mediaBox->unref(); // SkRefPtr and new both took a reference.
1038 mediaBox->reserve(4);
1039 mediaBox->append(zero.get());
1040 mediaBox->append(zero.get());
ctguil@chromium.org15261292011-04-29 17:54:16 +00001041 mediaBox->append(new SkPDFInt(fPageSize.fWidth))->unref();
1042 mediaBox->append(new SkPDFInt(fPageSize.fHeight))->unref();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001043 return mediaBox;
1044}
1045
vandebo@chromium.orgc2a9b7f2011-02-24 23:22:30 +00001046SkStream* SkPDFDevice::content() const {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001047 SkDynamicMemoryWStream data;
1048 if (fInitialTransform.getType() != SkMatrix::kIdentity_Mask) {
1049 SkPDFUtils::AppendTransform(fInitialTransform, &data);
vandebo@chromium.orgc2a9b7f2011-02-24 23:22:30 +00001050 }
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001051 // If the content area is the entire page, then we don't need to clip
1052 // the content area (PDF area clips to the page size). Otherwise,
1053 // we have to clip to the content area; we've already applied the
1054 // initial transform, so just clip to the device size.
1055 if (fPageSize != fContentSize) {
1056 SkRect r = SkRect::MakeWH(this->width(), this->height());
1057 emit_clip(NULL, &r, &data);
1058 }
1059
1060 GraphicStackState gsState(fExistingClipStack, fExistingClipRegion, &data);
1061 for (ContentEntry* entry = fContentEntries.get();
1062 entry != NULL;
1063 entry = entry->fNext.get()) {
1064 SkIPoint translation = this->getOrigin();
1065 translation.negate();
1066 gsState.updateClip(entry->fState.fClipStack, entry->fState.fClipRegion,
1067 translation);
1068 gsState.updateMatrix(entry->fState.fMatrix);
1069 gsState.updateDrawingState(entry->fState);
1070 data.write(entry->fContent.getStream(), entry->fContent.getOffset());
1071 }
1072 gsState.drainStack();
1073
vandebo@chromium.orgc2a9b7f2011-02-24 23:22:30 +00001074 SkMemoryStream* result = new SkMemoryStream;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001075 result->setMemoryOwned(data.detach(), data.getOffset());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001076 return result;
1077}
1078
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001079void SkPDFDevice::createFormXObjectFromDevice(
1080 SkRefPtr<SkPDFFormXObject>* xobject) {
1081 *xobject = new SkPDFFormXObject(this);
1082 (*xobject)->unref(); // SkRefPtr and new both took a reference.
1083 cleanUp(); // Reset this device to have no content.
1084 init();
1085}
1086
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001087void SkPDFDevice::clearClipFromContent(const SkClipStack* clipStack,
1088 const SkRegion& clipRegion) {
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001089 if (clipRegion.isEmpty() || isContentEmpty()) {
1090 return;
1091 }
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001092 SkRefPtr<SkPDFFormXObject> curContent;
1093 createFormXObjectFromDevice(&curContent);
1094
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001095 // Redraw what we already had, but with the clip as a mask.
1096 drawFormXObjectWithClip(curContent.get(), clipStack, clipRegion, true);
1097}
1098
1099void SkPDFDevice::drawFormXObjectWithClip(SkPDFFormXObject* xobject,
1100 const SkClipStack* clipStack,
1101 const SkRegion& clipRegion,
1102 bool invertClip) {
1103 if (clipRegion.isEmpty() && !invertClip) {
1104 return;
1105 }
1106
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001107 // Create the mask.
1108 SkMatrix identity;
1109 identity.reset();
1110 SkDraw draw;
1111 draw.fMatrix = &identity;
1112 draw.fClip = &clipRegion;
1113 draw.fClipStack = clipStack;
1114 SkPaint stockPaint;
1115 this->drawPaint(draw, stockPaint);
1116 SkRefPtr<SkPDFFormXObject> maskFormXObject;
1117 createFormXObjectFromDevice(&maskFormXObject);
1118 SkRefPtr<SkPDFGraphicState> sMaskGS =
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001119 SkPDFGraphicState::getSMaskGraphicState(maskFormXObject.get(),
1120 invertClip);
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001121 sMaskGS->unref(); // SkRefPtr and getSMaskGraphicState both took a ref.
1122
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001123 // Draw the xobject with the clip as a mask.
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001124 ContentEntryAccessor content(this, &fExistingClipStack, fExistingClipRegion,
1125 identity, stockPaint);
1126 if (!content.entry()) {
1127 return;
1128 }
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001129 SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()),
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001130 &content.entry()->fContent);
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001131 SkPDFUtils::DrawFormXObject(fXObjectResources.count(),
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001132 &content.entry()->fContent);
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001133 fXObjectResources.push(xobject);
1134 xobject->ref();
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001135
1136 sMaskGS = SkPDFGraphicState::getNoSMaskGraphicState();
1137 sMaskGS->unref(); // SkRefPtr and getSMaskGraphicState both took a ref.
1138 SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()),
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001139 &content.entry()->fContent);
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001140}
1141
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001142ContentEntry* SkPDFDevice::setUpContentEntry(const SkClipStack* clipStack,
1143 const SkRegion& clipRegion,
1144 const SkMatrix& matrix,
1145 const SkPaint& paint,
1146 bool hasText,
1147 SkRefPtr<SkPDFFormXObject>* dst) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001148 if (clipRegion.isEmpty()) {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001149 return NULL;
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001150 }
1151
vandebo@chromium.org78dad542011-05-11 18:46:03 +00001152 // The clip stack can come from an SkDraw where it is technically optional.
1153 SkClipStack synthesizedClipStack;
1154 if (clipStack == NULL) {
1155 if (clipRegion == fExistingClipRegion) {
1156 clipStack = &fExistingClipStack;
1157 } else {
1158 // GraphicStackState::updateClip expects the clip stack to have
1159 // fExistingClip as a prefix, so start there, then set the clip
1160 // to the passed region.
1161 synthesizedClipStack = fExistingClipStack;
1162 SkPath clipPath;
1163 clipRegion.getBoundaryPath(&clipPath);
1164 synthesizedClipStack.clipDevPath(clipPath, SkRegion::kReplace_Op);
1165 clipStack = &synthesizedClipStack;
1166 }
1167 }
1168
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001169 SkXfermode::Mode xfermode = SkXfermode::kSrcOver_Mode;
1170 if (paint.getXfermode()) {
1171 paint.getXfermode()->asMode(&xfermode);
1172 }
1173
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001174 if (xfermode == SkXfermode::kClear_Mode ||
1175 xfermode == SkXfermode::kSrc_Mode) {
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001176 this->clearClipFromContent(clipStack, clipRegion);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001177 } else if (xfermode == SkXfermode::kSrcIn_Mode ||
1178 xfermode == SkXfermode::kDstIn_Mode ||
1179 xfermode == SkXfermode::kSrcOut_Mode ||
1180 xfermode == SkXfermode::kDstOut_Mode) {
1181 // For the following modes, we use both source and destination, but
1182 // we use one as a smask for the other, so we have to make form xobjects
1183 // out of both of them: SrcIn, DstIn, SrcOut, DstOut.
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001184 if (isContentEmpty()) {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001185 return NULL;
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001186 } else {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001187 createFormXObjectFromDevice(dst);
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001188 }
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001189 }
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001190 // TODO(vandebo) Figure out how/if we can handle the following modes:
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001191 // SrcAtop, DestAtop, Xor, Plus.
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001192
1193 // These xfer modes don't draw source at all.
1194 if (xfermode == SkXfermode::kClear_Mode ||
1195 xfermode == SkXfermode::kDst_Mode) {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001196 return NULL;
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001197 }
1198
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001199 ContentEntry* entry;
1200 SkTScopedPtr<ContentEntry> newEntry;
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001201 if (fLastContentEntry && fLastContentEntry->fContent.getOffset() == 0) {
1202 entry = fLastContentEntry;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001203 } else {
1204 newEntry.reset(new ContentEntry);
1205 entry = newEntry.get();
1206 }
1207
vandebo@chromium.org78dad542011-05-11 18:46:03 +00001208 populateGraphicStateEntryFromPaint(matrix, *clipStack, clipRegion, paint,
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001209 hasText, &entry->fState);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001210 if (fLastContentEntry && xfermode != SkXfermode::kDstOver_Mode &&
1211 entry->fState.compareInitialState(fLastContentEntry->fState)) {
1212 return fLastContentEntry;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001213 }
1214
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001215 if (!fLastContentEntry) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001216 fContentEntries.reset(entry);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001217 fLastContentEntry = entry;
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001218 } else if (xfermode == SkXfermode::kDstOver_Mode) {
1219 entry->fNext.reset(fContentEntries.release());
1220 fContentEntries.reset(entry);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001221 } else {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001222 fLastContentEntry->fNext.reset(entry);
1223 fLastContentEntry = entry;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001224 }
1225 newEntry.release();
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001226 return entry;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001227}
1228
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001229void SkPDFDevice::finishContentEntry(const SkXfermode::Mode xfermode,
1230 SkPDFFormXObject* dst) {
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001231 if (xfermode != SkXfermode::kSrcIn_Mode &&
1232 xfermode != SkXfermode::kDstIn_Mode &&
1233 xfermode != SkXfermode::kSrcOut_Mode &&
1234 xfermode != SkXfermode::kDstOut_Mode) {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001235 SkASSERT(!dst);
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001236 return;
1237 }
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001238 SkASSERT(dst);
1239 SkASSERT(!fContentEntries->fNext.get());
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001240
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001241 // We have to make a copy of these here because changing the current
1242 // content into a form xobject will destroy them.
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001243 SkClipStack clipStack = fContentEntries->fState.fClipStack;
1244 SkRegion clipRegion = fContentEntries->fState.fClipRegion;
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001245
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001246 SkRefPtr<SkPDFFormXObject> srcFormXObject;
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001247 if (!isContentEmpty()) {
1248 createFormXObjectFromDevice(&srcFormXObject);
1249 }
1250
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001251 drawFormXObjectWithClip(dst, &clipStack, clipRegion, true);
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001252
1253 // We've redrawn dst minus the clip area, if there's no src, we're done.
1254 if (!srcFormXObject.get()) {
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001255 return;
1256 }
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001257
1258 SkMatrix identity;
1259 identity.reset();
1260 SkPaint stockPaint;
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001261 ContentEntryAccessor inClipContentEntry(this, &fExistingClipStack,
1262 fExistingClipRegion, identity,
1263 stockPaint);
1264 if (!inClipContentEntry.entry()) {
1265 return;
1266 }
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001267
1268 SkRefPtr<SkPDFGraphicState> sMaskGS;
1269 if (xfermode == SkXfermode::kSrcIn_Mode ||
1270 xfermode == SkXfermode::kSrcOut_Mode) {
1271 sMaskGS = SkPDFGraphicState::getSMaskGraphicState(
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001272 dst, xfermode == SkXfermode::kSrcOut_Mode);
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001273 fXObjectResources.push(srcFormXObject.get());
1274 srcFormXObject->ref();
1275 } else {
1276 sMaskGS = SkPDFGraphicState::getSMaskGraphicState(
1277 srcFormXObject.get(), xfermode == SkXfermode::kDstOut_Mode);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001278 // dst already added to fXObjectResources in drawFormXObjectWithClip.
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001279 }
1280 sMaskGS->unref(); // SkRefPtr and getSMaskGraphicState both took a ref.
1281 SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()),
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001282 &inClipContentEntry.entry()->fContent);
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001283
1284 SkPDFUtils::DrawFormXObject(fXObjectResources.count() - 1,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001285 &inClipContentEntry.entry()->fContent);
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001286
1287 sMaskGS = SkPDFGraphicState::getNoSMaskGraphicState();
1288 sMaskGS->unref(); // SkRefPtr and getSMaskGraphicState both took a ref.
1289 SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()),
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001290 &inClipContentEntry.entry()->fContent);
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001291}
1292
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001293bool SkPDFDevice::isContentEmpty() {
1294 if (!fContentEntries.get() || fContentEntries->fContent.getOffset() == 0) {
1295 SkASSERT(!fContentEntries.get() || !fContentEntries->fNext.get());
1296 return true;
1297 }
1298 return false;
1299}
1300
1301
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001302void SkPDFDevice::populateGraphicStateEntryFromPaint(
1303 const SkMatrix& matrix,
1304 const SkClipStack& clipStack,
1305 const SkRegion& clipRegion,
1306 const SkPaint& paint,
1307 bool hasText,
1308 GraphicStateEntry* entry) {
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001309 SkASSERT(paint.getPathEffect() == NULL);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001310
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001311 NOT_IMPLEMENTED(paint.getMaskFilter() != NULL, false);
1312 NOT_IMPLEMENTED(paint.getColorFilter() != NULL, false);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001313
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001314 entry->fMatrix = matrix;
1315 entry->fClipStack = clipStack;
1316 entry->fClipRegion = clipRegion;
vandebo@chromium.org48543272011-02-08 19:28:07 +00001317
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001318 // PDF treats a shader as a color, so we only set one or the other.
1319 SkRefPtr<SkPDFShader> pdfShader;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001320 const SkShader* shader = paint.getShader();
1321 SkColor color = paint.getColor();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001322 if (shader) {
1323 // PDF positions patterns relative to the initial transform, so
1324 // we need to apply the current transform to the shader parameters.
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001325 SkMatrix transform = matrix;
vandebo@chromium.org75f97e42011-04-11 23:24:18 +00001326 transform.postConcat(fInitialTransform);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001327
1328 // PDF doesn't support kClamp_TileMode, so we simulate it by making
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001329 // a pattern the size of the current clip.
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001330 SkIRect bounds = clipRegion.getBounds();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001331 pdfShader = SkPDFShader::getPDFShader(*shader, transform, bounds);
1332 SkSafeUnref(pdfShader.get()); // getShader and SkRefPtr both took a ref
1333
1334 // A color shader is treated as an invalid shader so we don't have
1335 // to set a shader just for a color.
1336 if (pdfShader.get() == NULL) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001337 entry->fColor = 0;
1338 color = 0;
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001339
1340 // Check for a color shader.
1341 SkShader::GradientInfo gradientInfo;
1342 SkColor gradientColor;
1343 gradientInfo.fColors = &gradientColor;
1344 gradientInfo.fColorOffsets = NULL;
1345 gradientInfo.fColorCount = 1;
1346 if (shader->asAGradient(&gradientInfo) ==
1347 SkShader::kColor_GradientType) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001348 entry->fColor = SkColorSetA(gradientColor, 0xFF);
1349 color = gradientColor;
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001350 }
1351 }
1352 }
1353
1354 if (pdfShader) {
1355 // pdfShader has been canonicalized so we can directly compare
1356 // pointers.
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001357 int resourceIndex = fShaderResources.find(pdfShader.get());
1358 if (resourceIndex < 0) {
1359 resourceIndex = fShaderResources.count();
1360 fShaderResources.push(pdfShader.get());
1361 pdfShader->ref();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001362 }
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001363 entry->fShaderIndex = resourceIndex;
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001364 } else {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001365 entry->fShaderIndex = -1;
1366 entry->fColor = SkColorSetA(paint.getColor(), 0xFF);
1367 color = paint.getColor();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001368 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001369
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001370 SkRefPtr<SkPDFGraphicState> newGraphicState;
1371 if (color == paint.getColor()) {
1372 newGraphicState = SkPDFGraphicState::getGraphicStateForPaint(paint);
1373 } else {
1374 SkPaint newPaint = paint;
1375 newPaint.setColor(color);
1376 newGraphicState = SkPDFGraphicState::getGraphicStateForPaint(newPaint);
1377 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001378 newGraphicState->unref(); // getGraphicState and SkRefPtr both took a ref.
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001379 int resourceIndex = addGraphicStateResource(newGraphicState.get());
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001380 entry->fGraphicStateIndex = resourceIndex;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001381
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001382 if (hasText) {
1383 entry->fTextScaleX = paint.getTextScaleX();
1384 entry->fTextFill = paint.getStyle();
1385 } else {
1386 entry->fTextScaleX = 0;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001387 }
1388}
1389
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001390int SkPDFDevice::addGraphicStateResource(SkPDFGraphicState* gs) {
1391 // Assumes that gs has been canonicalized (so we can directly compare
1392 // pointers).
1393 int result = fGraphicStateResources.find(gs);
1394 if (result < 0) {
1395 result = fGraphicStateResources.count();
1396 fGraphicStateResources.push(gs);
1397 gs->ref();
1398 }
1399 return result;
1400}
1401
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001402void SkPDFDevice::updateFont(const SkPaint& paint, uint16_t glyphID,
1403 ContentEntry* contentEntry) {
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +00001404 SkTypeface* typeface = paint.getTypeface();
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001405 if (contentEntry->fState.fFont == NULL ||
1406 contentEntry->fState.fTextSize != paint.getTextSize() ||
1407 !contentEntry->fState.fFont->hasGlyph(glyphID)) {
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +00001408 int fontIndex = getFontResourceIndex(typeface, glyphID);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001409 contentEntry->fContent.writeText("/F");
1410 contentEntry->fContent.writeDecAsText(fontIndex);
1411 contentEntry->fContent.writeText(" ");
1412 SkPDFScalar::Append(paint.getTextSize(), &contentEntry->fContent);
1413 contentEntry->fContent.writeText(" Tf\n");
1414 contentEntry->fState.fFont = fFontResources[fontIndex];
vandebo@chromium.org2a22e102011-01-25 21:01:34 +00001415 }
1416}
1417
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +00001418int SkPDFDevice::getFontResourceIndex(SkTypeface* typeface, uint16_t glyphID) {
1419 SkRefPtr<SkPDFFont> newFont = SkPDFFont::getFontResource(typeface, glyphID);
vandebo@chromium.org2a22e102011-01-25 21:01:34 +00001420 newFont->unref(); // getFontResource and SkRefPtr both took a ref.
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00001421 int resourceIndex = fFontResources.find(newFont.get());
1422 if (resourceIndex < 0) {
1423 resourceIndex = fFontResources.count();
1424 fFontResources.push(newFont.get());
1425 newFont->ref();
1426 }
1427 return resourceIndex;
1428}
1429
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001430void SkPDFDevice::internalDrawBitmap(const SkMatrix& matrix,
vandebo@chromium.org78dad542011-05-11 18:46:03 +00001431 const SkClipStack* clipStack,
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001432 const SkRegion& clipRegion,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001433 const SkBitmap& bitmap,
vandebo@chromium.orgbefebb82011-01-29 01:38:50 +00001434 const SkIRect* srcRect,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001435 const SkPaint& paint) {
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +00001436 SkMatrix scaled;
1437 // Adjust for origin flip.
1438 scaled.setScale(1, -1);
1439 scaled.postTranslate(0, 1);
1440 // Scale the image up from 1x1 to WxH.
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001441 SkIRect subset = SkIRect::MakeWH(bitmap.width(), bitmap.height());
reed@google.coma6d59f62011-03-07 21:29:21 +00001442 scaled.postScale(SkIntToScalar(subset.width()),
1443 SkIntToScalar(subset.height()));
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +00001444 scaled.postConcat(matrix);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001445 ContentEntryAccessor content(this, clipStack, clipRegion, scaled, paint);
1446 if (!content.entry()) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001447 return;
1448 }
1449
1450 if (srcRect && !subset.intersect(*srcRect)) {
1451 return;
1452 }
1453
1454 SkPDFImage* image = SkPDFImage::CreateImage(bitmap, subset, paint);
1455 if (!image) {
1456 return;
1457 }
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +00001458
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001459 fXObjectResources.push(image); // Transfer reference.
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001460 SkPDFUtils::DrawFormXObject(fXObjectResources.count() - 1,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001461 &content.entry()->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001462}