blob: 17ea5ddb5cc5933ec7e32e82c3988d31d7a2159e [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.orgee7a9562011-05-24 17:38:01 +0000921 // Assume that a vector capable device means that it's a PDF Device.
922 SkPDFDevice* pdfDevice = static_cast<SkPDFDevice*>(device);
ctguil@chromium.orgf4ff39c2011-05-24 19:55:05 +0000923 if (pdfDevice->isContentEmpty()) {
vandebo@chromium.orgee7a9562011-05-24 17:38:01 +0000924 return;
925 }
926
vandebo@chromium.org1aef2ed2011-02-03 21:46:10 +0000927 SkMatrix matrix;
reed@google.coma6d59f62011-03-07 21:29:21 +0000928 matrix.setTranslate(SkIntToScalar(x), SkIntToScalar(y));
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000929 ContentEntryAccessor content(this, d.fClipStack, *d.fClip, matrix, paint);
930 if (!content.entry()) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000931 return;
932 }
vandebo@chromium.org1aef2ed2011-02-03 21:46:10 +0000933
934 SkPDFFormXObject* xobject = new SkPDFFormXObject(pdfDevice);
vandebo@chromium.orgeb6c7592010-10-26 19:54:45 +0000935 fXObjectResources.push(xobject); // Transfer reference.
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000936 SkPDFUtils::DrawFormXObject(fXObjectResources.count() - 1,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000937 &content.entry()->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000938}
939
940const SkRefPtr<SkPDFDict>& SkPDFDevice::getResourceDict() {
941 if (fResourceDict.get() == NULL) {
942 fResourceDict = new SkPDFDict;
943 fResourceDict->unref(); // SkRefPtr and new both took a reference.
944
945 if (fGraphicStateResources.count()) {
946 SkRefPtr<SkPDFDict> extGState = new SkPDFDict();
947 extGState->unref(); // SkRefPtr and new both took a reference.
948 for (int i = 0; i < fGraphicStateResources.count(); i++) {
949 SkString nameString("G");
950 nameString.appendS32(i);
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000951 extGState->insert(
952 nameString.c_str(),
953 new SkPDFObjRef(fGraphicStateResources[i]))->unref();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000954 }
955 fResourceDict->insert("ExtGState", extGState.get());
956 }
957
958 if (fXObjectResources.count()) {
959 SkRefPtr<SkPDFDict> xObjects = new SkPDFDict();
960 xObjects->unref(); // SkRefPtr and new both took a reference.
961 for (int i = 0; i < fXObjectResources.count(); i++) {
962 SkString nameString("X");
963 nameString.appendS32(i);
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000964 xObjects->insert(
965 nameString.c_str(),
966 new SkPDFObjRef(fXObjectResources[i]))->unref();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000967 }
968 fResourceDict->insert("XObject", xObjects.get());
969 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000970
971 if (fFontResources.count()) {
972 SkRefPtr<SkPDFDict> fonts = new SkPDFDict();
973 fonts->unref(); // SkRefPtr and new both took a reference.
974 for (int i = 0; i < fFontResources.count(); i++) {
975 SkString nameString("F");
976 nameString.appendS32(i);
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000977 fonts->insert(nameString.c_str(),
978 new SkPDFObjRef(fFontResources[i]))->unref();
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000979 }
980 fResourceDict->insert("Font", fonts.get());
981 }
982
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000983 if (fShaderResources.count()) {
984 SkRefPtr<SkPDFDict> patterns = new SkPDFDict();
985 patterns->unref(); // SkRefPtr and new both took a reference.
986 for (int i = 0; i < fShaderResources.count(); i++) {
987 SkString nameString("P");
988 nameString.appendS32(i);
989 patterns->insert(nameString.c_str(),
990 new SkPDFObjRef(fShaderResources[i]))->unref();
991 }
992 fResourceDict->insert("Pattern", patterns.get());
993 }
994
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000995 // For compatibility, add all proc sets (only used for output to PS
996 // devices).
997 const char procs[][7] = {"PDF", "Text", "ImageB", "ImageC", "ImageI"};
998 SkRefPtr<SkPDFArray> procSets = new SkPDFArray();
999 procSets->unref(); // SkRefPtr and new both took a reference.
1000 procSets->reserve(SK_ARRAY_COUNT(procs));
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +00001001 for (size_t i = 0; i < SK_ARRAY_COUNT(procs); i++)
1002 procSets->append(new SkPDFName(procs[i]))->unref();
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00001003 fResourceDict->insert("ProcSet", procSets.get());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001004 }
1005 return fResourceDict;
1006}
1007
vandebo@chromium.orga5180862010-10-26 19:48:49 +00001008void SkPDFDevice::getResources(SkTDArray<SkPDFObject*>* resourceList) const {
1009 resourceList->setReserve(resourceList->count() +
1010 fGraphicStateResources.count() +
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00001011 fXObjectResources.count() +
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001012 fFontResources.count() +
1013 fShaderResources.count());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001014 for (int i = 0; i < fGraphicStateResources.count(); i++) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +00001015 resourceList->push(fGraphicStateResources[i]);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001016 fGraphicStateResources[i]->ref();
vandebo@chromium.orga5180862010-10-26 19:48:49 +00001017 fGraphicStateResources[i]->getResources(resourceList);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001018 }
1019 for (int i = 0; i < fXObjectResources.count(); i++) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +00001020 resourceList->push(fXObjectResources[i]);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001021 fXObjectResources[i]->ref();
vandebo@chromium.orga5180862010-10-26 19:48:49 +00001022 fXObjectResources[i]->getResources(resourceList);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001023 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00001024 for (int i = 0; i < fFontResources.count(); i++) {
1025 resourceList->push(fFontResources[i]);
1026 fFontResources[i]->ref();
1027 fFontResources[i]->getResources(resourceList);
1028 }
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001029 for (int i = 0; i < fShaderResources.count(); i++) {
1030 resourceList->push(fShaderResources[i]);
1031 fShaderResources[i]->ref();
1032 fShaderResources[i]->getResources(resourceList);
1033 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001034}
1035
vandebo@chromium.orga5180862010-10-26 19:48:49 +00001036SkRefPtr<SkPDFArray> SkPDFDevice::getMediaBox() const {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001037 SkRefPtr<SkPDFInt> zero = new SkPDFInt(0);
1038 zero->unref(); // SkRefPtr and new both took a reference.
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +00001039
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001040 SkRefPtr<SkPDFArray> mediaBox = new SkPDFArray();
1041 mediaBox->unref(); // SkRefPtr and new both took a reference.
1042 mediaBox->reserve(4);
1043 mediaBox->append(zero.get());
1044 mediaBox->append(zero.get());
ctguil@chromium.org15261292011-04-29 17:54:16 +00001045 mediaBox->append(new SkPDFInt(fPageSize.fWidth))->unref();
1046 mediaBox->append(new SkPDFInt(fPageSize.fHeight))->unref();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001047 return mediaBox;
1048}
1049
vandebo@chromium.orgc2a9b7f2011-02-24 23:22:30 +00001050SkStream* SkPDFDevice::content() const {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001051 SkDynamicMemoryWStream data;
1052 if (fInitialTransform.getType() != SkMatrix::kIdentity_Mask) {
1053 SkPDFUtils::AppendTransform(fInitialTransform, &data);
vandebo@chromium.orgc2a9b7f2011-02-24 23:22:30 +00001054 }
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001055 // If the content area is the entire page, then we don't need to clip
1056 // the content area (PDF area clips to the page size). Otherwise,
1057 // we have to clip to the content area; we've already applied the
1058 // initial transform, so just clip to the device size.
1059 if (fPageSize != fContentSize) {
1060 SkRect r = SkRect::MakeWH(this->width(), this->height());
1061 emit_clip(NULL, &r, &data);
1062 }
1063
1064 GraphicStackState gsState(fExistingClipStack, fExistingClipRegion, &data);
1065 for (ContentEntry* entry = fContentEntries.get();
1066 entry != NULL;
1067 entry = entry->fNext.get()) {
1068 SkIPoint translation = this->getOrigin();
1069 translation.negate();
1070 gsState.updateClip(entry->fState.fClipStack, entry->fState.fClipRegion,
1071 translation);
1072 gsState.updateMatrix(entry->fState.fMatrix);
1073 gsState.updateDrawingState(entry->fState);
1074 data.write(entry->fContent.getStream(), entry->fContent.getOffset());
1075 }
1076 gsState.drainStack();
1077
vandebo@chromium.orgc2a9b7f2011-02-24 23:22:30 +00001078 SkMemoryStream* result = new SkMemoryStream;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001079 result->setMemoryOwned(data.detach(), data.getOffset());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001080 return result;
1081}
1082
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001083void SkPDFDevice::createFormXObjectFromDevice(
1084 SkRefPtr<SkPDFFormXObject>* xobject) {
1085 *xobject = new SkPDFFormXObject(this);
1086 (*xobject)->unref(); // SkRefPtr and new both took a reference.
1087 cleanUp(); // Reset this device to have no content.
1088 init();
1089}
1090
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001091void SkPDFDevice::clearClipFromContent(const SkClipStack* clipStack,
1092 const SkRegion& clipRegion) {
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001093 if (clipRegion.isEmpty() || isContentEmpty()) {
1094 return;
1095 }
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001096 SkRefPtr<SkPDFFormXObject> curContent;
1097 createFormXObjectFromDevice(&curContent);
1098
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001099 // Redraw what we already had, but with the clip as a mask.
1100 drawFormXObjectWithClip(curContent.get(), clipStack, clipRegion, true);
1101}
1102
1103void SkPDFDevice::drawFormXObjectWithClip(SkPDFFormXObject* xobject,
1104 const SkClipStack* clipStack,
1105 const SkRegion& clipRegion,
1106 bool invertClip) {
1107 if (clipRegion.isEmpty() && !invertClip) {
1108 return;
1109 }
1110
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001111 // Create the mask.
1112 SkMatrix identity;
1113 identity.reset();
1114 SkDraw draw;
1115 draw.fMatrix = &identity;
1116 draw.fClip = &clipRegion;
1117 draw.fClipStack = clipStack;
1118 SkPaint stockPaint;
1119 this->drawPaint(draw, stockPaint);
1120 SkRefPtr<SkPDFFormXObject> maskFormXObject;
1121 createFormXObjectFromDevice(&maskFormXObject);
1122 SkRefPtr<SkPDFGraphicState> sMaskGS =
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001123 SkPDFGraphicState::getSMaskGraphicState(maskFormXObject.get(),
1124 invertClip);
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001125 sMaskGS->unref(); // SkRefPtr and getSMaskGraphicState both took a ref.
1126
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001127 // Draw the xobject with the clip as a mask.
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001128 ContentEntryAccessor content(this, &fExistingClipStack, fExistingClipRegion,
1129 identity, stockPaint);
1130 if (!content.entry()) {
1131 return;
1132 }
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001133 SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()),
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001134 &content.entry()->fContent);
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001135 SkPDFUtils::DrawFormXObject(fXObjectResources.count(),
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001136 &content.entry()->fContent);
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001137 fXObjectResources.push(xobject);
1138 xobject->ref();
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001139
1140 sMaskGS = SkPDFGraphicState::getNoSMaskGraphicState();
1141 sMaskGS->unref(); // SkRefPtr and getSMaskGraphicState both took a ref.
1142 SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()),
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001143 &content.entry()->fContent);
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001144}
1145
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001146ContentEntry* SkPDFDevice::setUpContentEntry(const SkClipStack* clipStack,
1147 const SkRegion& clipRegion,
1148 const SkMatrix& matrix,
1149 const SkPaint& paint,
1150 bool hasText,
1151 SkRefPtr<SkPDFFormXObject>* dst) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001152 if (clipRegion.isEmpty()) {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001153 return NULL;
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001154 }
1155
vandebo@chromium.org78dad542011-05-11 18:46:03 +00001156 // The clip stack can come from an SkDraw where it is technically optional.
1157 SkClipStack synthesizedClipStack;
1158 if (clipStack == NULL) {
1159 if (clipRegion == fExistingClipRegion) {
1160 clipStack = &fExistingClipStack;
1161 } else {
1162 // GraphicStackState::updateClip expects the clip stack to have
1163 // fExistingClip as a prefix, so start there, then set the clip
1164 // to the passed region.
1165 synthesizedClipStack = fExistingClipStack;
1166 SkPath clipPath;
1167 clipRegion.getBoundaryPath(&clipPath);
1168 synthesizedClipStack.clipDevPath(clipPath, SkRegion::kReplace_Op);
1169 clipStack = &synthesizedClipStack;
1170 }
1171 }
1172
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001173 SkXfermode::Mode xfermode = SkXfermode::kSrcOver_Mode;
1174 if (paint.getXfermode()) {
1175 paint.getXfermode()->asMode(&xfermode);
1176 }
1177
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001178 if (xfermode == SkXfermode::kClear_Mode ||
1179 xfermode == SkXfermode::kSrc_Mode) {
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001180 this->clearClipFromContent(clipStack, clipRegion);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001181 } else if (xfermode == SkXfermode::kSrcIn_Mode ||
1182 xfermode == SkXfermode::kDstIn_Mode ||
1183 xfermode == SkXfermode::kSrcOut_Mode ||
1184 xfermode == SkXfermode::kDstOut_Mode) {
1185 // For the following modes, we use both source and destination, but
1186 // we use one as a smask for the other, so we have to make form xobjects
1187 // out of both of them: SrcIn, DstIn, SrcOut, DstOut.
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001188 if (isContentEmpty()) {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001189 return NULL;
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001190 } else {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001191 createFormXObjectFromDevice(dst);
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001192 }
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001193 }
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001194 // TODO(vandebo) Figure out how/if we can handle the following modes:
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001195 // SrcAtop, DestAtop, Xor, Plus.
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001196
1197 // These xfer modes don't draw source at all.
1198 if (xfermode == SkXfermode::kClear_Mode ||
1199 xfermode == SkXfermode::kDst_Mode) {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001200 return NULL;
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001201 }
1202
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001203 ContentEntry* entry;
1204 SkTScopedPtr<ContentEntry> newEntry;
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001205 if (fLastContentEntry && fLastContentEntry->fContent.getOffset() == 0) {
1206 entry = fLastContentEntry;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001207 } else {
1208 newEntry.reset(new ContentEntry);
1209 entry = newEntry.get();
1210 }
1211
vandebo@chromium.org78dad542011-05-11 18:46:03 +00001212 populateGraphicStateEntryFromPaint(matrix, *clipStack, clipRegion, paint,
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001213 hasText, &entry->fState);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001214 if (fLastContentEntry && xfermode != SkXfermode::kDstOver_Mode &&
1215 entry->fState.compareInitialState(fLastContentEntry->fState)) {
1216 return fLastContentEntry;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001217 }
1218
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001219 if (!fLastContentEntry) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001220 fContentEntries.reset(entry);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001221 fLastContentEntry = entry;
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001222 } else if (xfermode == SkXfermode::kDstOver_Mode) {
1223 entry->fNext.reset(fContentEntries.release());
1224 fContentEntries.reset(entry);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001225 } else {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001226 fLastContentEntry->fNext.reset(entry);
1227 fLastContentEntry = entry;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001228 }
1229 newEntry.release();
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001230 return entry;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001231}
1232
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001233void SkPDFDevice::finishContentEntry(const SkXfermode::Mode xfermode,
1234 SkPDFFormXObject* dst) {
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001235 if (xfermode != SkXfermode::kSrcIn_Mode &&
1236 xfermode != SkXfermode::kDstIn_Mode &&
1237 xfermode != SkXfermode::kSrcOut_Mode &&
1238 xfermode != SkXfermode::kDstOut_Mode) {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001239 SkASSERT(!dst);
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001240 return;
1241 }
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001242 SkASSERT(dst);
1243 SkASSERT(!fContentEntries->fNext.get());
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001244
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001245 // We have to make a copy of these here because changing the current
1246 // content into a form xobject will destroy them.
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001247 SkClipStack clipStack = fContentEntries->fState.fClipStack;
1248 SkRegion clipRegion = fContentEntries->fState.fClipRegion;
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001249
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001250 SkRefPtr<SkPDFFormXObject> srcFormXObject;
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001251 if (!isContentEmpty()) {
1252 createFormXObjectFromDevice(&srcFormXObject);
1253 }
1254
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001255 drawFormXObjectWithClip(dst, &clipStack, clipRegion, true);
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001256
1257 // We've redrawn dst minus the clip area, if there's no src, we're done.
1258 if (!srcFormXObject.get()) {
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001259 return;
1260 }
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001261
1262 SkMatrix identity;
1263 identity.reset();
1264 SkPaint stockPaint;
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001265 ContentEntryAccessor inClipContentEntry(this, &fExistingClipStack,
1266 fExistingClipRegion, identity,
1267 stockPaint);
1268 if (!inClipContentEntry.entry()) {
1269 return;
1270 }
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001271
1272 SkRefPtr<SkPDFGraphicState> sMaskGS;
1273 if (xfermode == SkXfermode::kSrcIn_Mode ||
1274 xfermode == SkXfermode::kSrcOut_Mode) {
1275 sMaskGS = SkPDFGraphicState::getSMaskGraphicState(
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001276 dst, xfermode == SkXfermode::kSrcOut_Mode);
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001277 fXObjectResources.push(srcFormXObject.get());
1278 srcFormXObject->ref();
1279 } else {
1280 sMaskGS = SkPDFGraphicState::getSMaskGraphicState(
1281 srcFormXObject.get(), xfermode == SkXfermode::kDstOut_Mode);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001282 // dst already added to fXObjectResources in drawFormXObjectWithClip.
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001283 }
1284 sMaskGS->unref(); // SkRefPtr and getSMaskGraphicState both took a ref.
1285 SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()),
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001286 &inClipContentEntry.entry()->fContent);
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001287
1288 SkPDFUtils::DrawFormXObject(fXObjectResources.count() - 1,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001289 &inClipContentEntry.entry()->fContent);
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001290
1291 sMaskGS = SkPDFGraphicState::getNoSMaskGraphicState();
1292 sMaskGS->unref(); // SkRefPtr and getSMaskGraphicState both took a ref.
1293 SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()),
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001294 &inClipContentEntry.entry()->fContent);
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001295}
1296
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001297bool SkPDFDevice::isContentEmpty() {
1298 if (!fContentEntries.get() || fContentEntries->fContent.getOffset() == 0) {
1299 SkASSERT(!fContentEntries.get() || !fContentEntries->fNext.get());
1300 return true;
1301 }
1302 return false;
1303}
1304
1305
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001306void SkPDFDevice::populateGraphicStateEntryFromPaint(
1307 const SkMatrix& matrix,
1308 const SkClipStack& clipStack,
1309 const SkRegion& clipRegion,
1310 const SkPaint& paint,
1311 bool hasText,
1312 GraphicStateEntry* entry) {
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001313 SkASSERT(paint.getPathEffect() == NULL);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001314
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001315 NOT_IMPLEMENTED(paint.getMaskFilter() != NULL, false);
1316 NOT_IMPLEMENTED(paint.getColorFilter() != NULL, false);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001317
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001318 entry->fMatrix = matrix;
1319 entry->fClipStack = clipStack;
1320 entry->fClipRegion = clipRegion;
vandebo@chromium.org48543272011-02-08 19:28:07 +00001321
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001322 // PDF treats a shader as a color, so we only set one or the other.
1323 SkRefPtr<SkPDFShader> pdfShader;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001324 const SkShader* shader = paint.getShader();
1325 SkColor color = paint.getColor();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001326 if (shader) {
1327 // PDF positions patterns relative to the initial transform, so
1328 // we need to apply the current transform to the shader parameters.
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001329 SkMatrix transform = matrix;
vandebo@chromium.org75f97e42011-04-11 23:24:18 +00001330 transform.postConcat(fInitialTransform);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001331
1332 // PDF doesn't support kClamp_TileMode, so we simulate it by making
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001333 // a pattern the size of the current clip.
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001334 SkIRect bounds = clipRegion.getBounds();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001335 pdfShader = SkPDFShader::getPDFShader(*shader, transform, bounds);
1336 SkSafeUnref(pdfShader.get()); // getShader and SkRefPtr both took a ref
1337
1338 // A color shader is treated as an invalid shader so we don't have
1339 // to set a shader just for a color.
1340 if (pdfShader.get() == NULL) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001341 entry->fColor = 0;
1342 color = 0;
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001343
1344 // Check for a color shader.
1345 SkShader::GradientInfo gradientInfo;
1346 SkColor gradientColor;
1347 gradientInfo.fColors = &gradientColor;
1348 gradientInfo.fColorOffsets = NULL;
1349 gradientInfo.fColorCount = 1;
1350 if (shader->asAGradient(&gradientInfo) ==
1351 SkShader::kColor_GradientType) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001352 entry->fColor = SkColorSetA(gradientColor, 0xFF);
1353 color = gradientColor;
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001354 }
1355 }
1356 }
1357
1358 if (pdfShader) {
1359 // pdfShader has been canonicalized so we can directly compare
1360 // pointers.
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001361 int resourceIndex = fShaderResources.find(pdfShader.get());
1362 if (resourceIndex < 0) {
1363 resourceIndex = fShaderResources.count();
1364 fShaderResources.push(pdfShader.get());
1365 pdfShader->ref();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001366 }
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001367 entry->fShaderIndex = resourceIndex;
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001368 } else {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001369 entry->fShaderIndex = -1;
1370 entry->fColor = SkColorSetA(paint.getColor(), 0xFF);
1371 color = paint.getColor();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001372 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001373
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001374 SkRefPtr<SkPDFGraphicState> newGraphicState;
1375 if (color == paint.getColor()) {
1376 newGraphicState = SkPDFGraphicState::getGraphicStateForPaint(paint);
1377 } else {
1378 SkPaint newPaint = paint;
1379 newPaint.setColor(color);
1380 newGraphicState = SkPDFGraphicState::getGraphicStateForPaint(newPaint);
1381 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001382 newGraphicState->unref(); // getGraphicState and SkRefPtr both took a ref.
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001383 int resourceIndex = addGraphicStateResource(newGraphicState.get());
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001384 entry->fGraphicStateIndex = resourceIndex;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001385
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001386 if (hasText) {
1387 entry->fTextScaleX = paint.getTextScaleX();
1388 entry->fTextFill = paint.getStyle();
1389 } else {
1390 entry->fTextScaleX = 0;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001391 }
1392}
1393
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001394int SkPDFDevice::addGraphicStateResource(SkPDFGraphicState* gs) {
1395 // Assumes that gs has been canonicalized (so we can directly compare
1396 // pointers).
1397 int result = fGraphicStateResources.find(gs);
1398 if (result < 0) {
1399 result = fGraphicStateResources.count();
1400 fGraphicStateResources.push(gs);
1401 gs->ref();
1402 }
1403 return result;
1404}
1405
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001406void SkPDFDevice::updateFont(const SkPaint& paint, uint16_t glyphID,
1407 ContentEntry* contentEntry) {
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +00001408 SkTypeface* typeface = paint.getTypeface();
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001409 if (contentEntry->fState.fFont == NULL ||
1410 contentEntry->fState.fTextSize != paint.getTextSize() ||
1411 !contentEntry->fState.fFont->hasGlyph(glyphID)) {
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +00001412 int fontIndex = getFontResourceIndex(typeface, glyphID);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001413 contentEntry->fContent.writeText("/F");
1414 contentEntry->fContent.writeDecAsText(fontIndex);
1415 contentEntry->fContent.writeText(" ");
1416 SkPDFScalar::Append(paint.getTextSize(), &contentEntry->fContent);
1417 contentEntry->fContent.writeText(" Tf\n");
1418 contentEntry->fState.fFont = fFontResources[fontIndex];
vandebo@chromium.org2a22e102011-01-25 21:01:34 +00001419 }
1420}
1421
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +00001422int SkPDFDevice::getFontResourceIndex(SkTypeface* typeface, uint16_t glyphID) {
1423 SkRefPtr<SkPDFFont> newFont = SkPDFFont::getFontResource(typeface, glyphID);
vandebo@chromium.org2a22e102011-01-25 21:01:34 +00001424 newFont->unref(); // getFontResource and SkRefPtr both took a ref.
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00001425 int resourceIndex = fFontResources.find(newFont.get());
1426 if (resourceIndex < 0) {
1427 resourceIndex = fFontResources.count();
1428 fFontResources.push(newFont.get());
1429 newFont->ref();
1430 }
1431 return resourceIndex;
1432}
1433
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001434void SkPDFDevice::internalDrawBitmap(const SkMatrix& matrix,
vandebo@chromium.org78dad542011-05-11 18:46:03 +00001435 const SkClipStack* clipStack,
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001436 const SkRegion& clipRegion,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001437 const SkBitmap& bitmap,
vandebo@chromium.orgbefebb82011-01-29 01:38:50 +00001438 const SkIRect* srcRect,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001439 const SkPaint& paint) {
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +00001440 SkMatrix scaled;
1441 // Adjust for origin flip.
1442 scaled.setScale(1, -1);
1443 scaled.postTranslate(0, 1);
1444 // Scale the image up from 1x1 to WxH.
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001445 SkIRect subset = SkIRect::MakeWH(bitmap.width(), bitmap.height());
reed@google.coma6d59f62011-03-07 21:29:21 +00001446 scaled.postScale(SkIntToScalar(subset.width()),
1447 SkIntToScalar(subset.height()));
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +00001448 scaled.postConcat(matrix);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001449 ContentEntryAccessor content(this, clipStack, clipRegion, scaled, paint);
1450 if (!content.entry()) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001451 return;
1452 }
1453
1454 if (srcRect && !subset.intersect(*srcRect)) {
1455 return;
1456 }
1457
1458 SkPDFImage* image = SkPDFImage::CreateImage(bitmap, subset, paint);
1459 if (!image) {
1460 return;
1461 }
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +00001462
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001463 fXObjectResources.push(image); // Transfer reference.
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001464 SkPDFUtils::DrawFormXObject(fXObjectResources.count() - 1,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001465 &content.entry()->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001466}