blob: d55deca83d5f9d3111055cd4af7b7cb997488f25 [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.org9fbdf872011-05-09 07:55:58 +0000109// It is important to not confuse GraphicStateEntry with SkPDFGraphicState, the
110// later being our representation of an object in the PDF file.
111struct GraphicStateEntry {
112 GraphicStateEntry();
113
114 // Compare the fields we care about when setting up a new content entry.
115 bool compareInitialState(const GraphicStateEntry& b);
116
117 SkMatrix fMatrix;
118 // We can't do set operations on Paths, though PDF natively supports
119 // intersect. If the clip stack does anything other than intersect,
120 // we have to fall back to the region. Treat fClipStack as authoritative.
121 // See http://code.google.com/p/skia/issues/detail?id=221
122 SkClipStack fClipStack;
123 SkRegion fClipRegion;
124
125 // When emitting the content entry, we will ensure the graphic state
126 // is set to these values first.
127 SkColor fColor;
128 SkScalar fTextScaleX; // Zero means we don't care what the value is.
129 SkPaint::Style fTextFill; // Only if TextScaleX is non-zero.
130 int fShaderIndex;
131 int fGraphicStateIndex;
132
133 // We may change the font (i.e. for Type1 support) within a
134 // ContentEntry. This is the one currently in effect, or NULL if none.
135 SkPDFFont* fFont;
136 // In PDF, text size has no default value. It is only valid if fFont is
137 // not NULL.
138 SkScalar fTextSize;
139};
140
141GraphicStateEntry::GraphicStateEntry() : fColor(SK_ColorBLACK),
142 fTextScaleX(SK_Scalar1),
143 fTextFill(SkPaint::kFill_Style),
144 fShaderIndex(-1),
145 fGraphicStateIndex(-1),
146 fFont(NULL),
147 fTextSize(SK_ScalarNaN) {
148 fMatrix.reset();
149}
150
151bool GraphicStateEntry::compareInitialState(const GraphicStateEntry& b) {
152 return fColor == b.fColor &&
153 fShaderIndex == b.fShaderIndex &&
154 fGraphicStateIndex == b.fGraphicStateIndex &&
155 fMatrix == b.fMatrix &&
156 fClipStack == b.fClipStack &&
157 (fTextScaleX == 0 ||
158 b.fTextScaleX == 0 ||
159 (fTextScaleX == b.fTextScaleX && fTextFill == b.fTextFill));
160}
161
162struct ContentEntry {
163 GraphicStateEntry fState;
164 SkDynamicMemoryWStream fContent;
165 SkTScopedPtr<ContentEntry> fNext;
166};
167
168class GraphicStackState {
169public:
170 GraphicStackState(const SkClipStack& existingClipStack,
171 const SkRegion& existingClipRegion,
172 SkWStream* contentStream)
173 : fStackDepth(0),
174 fContentStream(contentStream) {
175 fEntries[0].fClipStack = existingClipStack;
176 fEntries[0].fClipRegion = existingClipRegion;
177 }
178
179 void updateClip(const SkClipStack& clipStack, const SkRegion& clipRegion,
180 const SkIPoint& translation);
181 void updateMatrix(const SkMatrix& matrix);
182 void updateDrawingState(const GraphicStateEntry& state);
183
184 void drainStack();
185
186private:
187 void push();
188 void pop();
189 GraphicStateEntry* currentEntry() { return &fEntries[fStackDepth]; }
190
191 // Conservative limit on save depth, see impl. notes in PDF 1.4 spec.
192 static const int kMaxStackDepth = 12;
193 GraphicStateEntry fEntries[kMaxStackDepth + 1];
194 int fStackDepth;
195 SkWStream* fContentStream;
196};
197
198void GraphicStackState::drainStack() {
199 while (fStackDepth) {
200 pop();
201 }
202}
203
204void GraphicStackState::push() {
205 SkASSERT(fStackDepth < kMaxStackDepth);
206 fContentStream->writeText("q\n");
207 fStackDepth++;
208 fEntries[fStackDepth] = fEntries[fStackDepth - 1];
209}
210
211void GraphicStackState::pop() {
212 SkASSERT(fStackDepth > 0);
213 fContentStream->writeText("Q\n");
214 fStackDepth--;
215}
216
217// This function initializes iter to be an interator on the "stack" argument
218// and then skips over the leading entries as specified in prefix. It requires
219// and asserts that "prefix" will be a prefix to "stack."
220static void skip_clip_stack_prefix(const SkClipStack& prefix,
221 const SkClipStack& stack,
222 SkClipStack::B2FIter* iter) {
223 SkClipStack::B2FIter prefixIter(prefix);
224 iter->reset(stack);
225
226 const SkClipStack::B2FIter::Clip* prefixEntry;
227 const SkClipStack::B2FIter::Clip* iterEntry;
228
229 for (prefixEntry = prefixIter.next(); prefixEntry;
230 prefixEntry = prefixIter.next()) {
231 iterEntry = iter->next();
232 SkASSERT(iterEntry);
233 SkASSERT(*prefixEntry == *iterEntry);
234 }
235
236 SkASSERT(prefixEntry == NULL);
237}
238
239static void emit_clip(SkPath* clipPath, SkRect* clipRect,
240 SkWStream* contentStream) {
241 SkASSERT(clipPath || clipRect);
242
243 SkPath::FillType clipFill;
244 if (clipPath) {
245 SkPDFUtils::EmitPath(*clipPath, contentStream);
246 clipFill = clipPath->getFillType();
247 } else if (clipRect) {
248 SkPDFUtils::AppendRectangle(*clipRect, contentStream);
249 clipFill = SkPath::kWinding_FillType;
250 }
251
252 NOT_IMPLEMENTED(clipFill == SkPath::kInverseEvenOdd_FillType, false);
253 NOT_IMPLEMENTED(clipFill == SkPath::kInverseWinding_FillType, false);
254 if (clipFill == SkPath::kEvenOdd_FillType) {
255 contentStream->writeText("W* n\n");
256 } else {
257 contentStream->writeText("W n\n");
258 }
259}
260
261// TODO(vandebo) Take advantage of SkClipStack::getSaveCount(), the PDF
262// graphic state stack, and the fact that we can know all the clips used
263// on the page to optimize this.
264void GraphicStackState::updateClip(const SkClipStack& clipStack,
265 const SkRegion& clipRegion,
266 const SkIPoint& translation) {
267 if (clipStack == currentEntry()->fClipStack) {
268 return;
269 }
270
271 while (fStackDepth > 0) {
272 pop();
273 if (clipStack == currentEntry()->fClipStack) {
274 return;
275 }
276 }
277 push();
278
279 // gsState->initialEntry()->fClipStack/Region specifies the clip that has
280 // already been applied. (If this is a top level device, then it specifies
281 // a clip to the content area. If this is a layer, then it specifies
282 // the clip in effect when the layer was created.) There's no need to
283 // reapply that clip; SKCanvas's SkDrawIter will draw anything outside the
284 // initial clip on the parent layer. (This means there's a bug if the user
285 // expands the clip and then uses any xfer mode that uses dst:
286 // http://code.google.com/p/skia/issues/detail?id=228 )
287 SkClipStack::B2FIter iter;
288 skip_clip_stack_prefix(fEntries[0].fClipStack, clipStack, &iter);
289
290 // If the clip stack does anything other than intersect or if it uses
291 // an inverse fill type, we have to fall back to the clip region.
292 bool needRegion = false;
293 const SkClipStack::B2FIter::Clip* clipEntry;
294 for (clipEntry = iter.next(); clipEntry; clipEntry = iter.next()) {
295 if (clipEntry->fOp != SkRegion::kIntersect_Op ||
296 (clipEntry->fPath && clipEntry->fPath->isInverseFillType())) {
297 needRegion = true;
298 break;
299 }
300 }
301
302 if (needRegion) {
303 SkPath clipPath;
304 SkAssertResult(clipRegion.getBoundaryPath(&clipPath));
305 emit_clip(&clipPath, NULL, fContentStream);
306 } else {
307 skip_clip_stack_prefix(fEntries[0].fClipStack, clipStack, &iter);
308 SkMatrix transform;
309 transform.setTranslate(translation.fX, translation.fY);
310 const SkClipStack::B2FIter::Clip* clipEntry;
311 for (clipEntry = iter.next(); clipEntry; clipEntry = iter.next()) {
312 SkASSERT(clipEntry->fOp == SkRegion::kIntersect_Op);
313 if (clipEntry->fRect) {
314 SkRect translatedClip;
315 transform.mapRect(&translatedClip, *clipEntry->fRect);
316 emit_clip(NULL, &translatedClip, fContentStream);
317 } else if (clipEntry->fPath) {
318 SkPath translatedPath;
319 clipEntry->fPath->transform(transform, &translatedPath);
320 emit_clip(&translatedPath, NULL, fContentStream);
321 } else {
322 SkASSERT(false);
323 }
324 }
325 }
326 currentEntry()->fClipStack = clipStack;
327 currentEntry()->fClipRegion = clipRegion;
328}
329
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000330static void append_clip_stack(const SkClipStack& src, SkClipStack* dst) {
331 SkClipStack::B2FIter iter(src);
332 const SkClipStack::B2FIter::Clip* clipEntry;
333 for (clipEntry = iter.next(); clipEntry; clipEntry = iter.next()) {
334 if (clipEntry->fRect) {
335 dst->clipDevRect(*clipEntry->fRect, clipEntry->fOp);
336 } else if (clipEntry->fPath) {
337 dst->clipDevPath(*clipEntry->fPath, clipEntry->fOp);
338 } else {
339 // There's not an easy way to add an empty clip with an arbitrary
340 // op, but an empty path (we've already used an empty rect) will
341 // work just fine for our purposes.
342 SkPath empty;
343 dst->clipDevPath(empty, clipEntry->fOp);
344 }
345 }
346}
347
348static void apply_inverse_clip_to_content_entries(
349 const SkClipStack& clipStack,
350 const SkRegion& clipRegion,
351 SkTScopedPtr<ContentEntry>* entries) {
352 // I don't see how to find the inverse of a clip stack and apply it to
353 // another clip stack. So until we can do polygon boolean operations,
354 // we combine the two clip stacks plus a sentinal with a non-intersect
355 // operator to annotate the intent and force use of the clip region while
356 // keeping the stacks comparable.
357 // Our sentinal is an empty SkRect with kReplace_Op. This sentinal should
358 // not appear in any reasonable real clip stack.
359 SkRect empty = SkRect::MakeEmpty();
360 SkTScopedPtr<ContentEntry>* entry = entries;
361 while (entry->get()) {
362 entry->get()->fState.fClipRegion.op(clipRegion,
363 SkRegion::kDifference_Op);
364 if (entry->get()->fState.fClipRegion.isEmpty()) {
365 // TODO(vandebo) The content entry we are removing may have been
366 // the only one referencing specific resources, we should remove
367 // those from our resource lists.
368 entry->reset(entry->get()->fNext.release());
369 continue;
370 }
371 entry->get()->fState.fClipStack.save();
372 entry->get()->fState.fClipStack.clipDevRect(empty,
373 SkRegion::kReplace_Op);
374 append_clip_stack(clipStack, &entry->get()->fState.fClipStack);
375
376 entry = &entry->get()->fNext;
377 }
378}
379
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000380void GraphicStackState::updateMatrix(const SkMatrix& matrix) {
381 if (matrix == currentEntry()->fMatrix) {
382 return;
383 }
384
385 if (currentEntry()->fMatrix.getType() != SkMatrix::kIdentity_Mask) {
386 SkASSERT(fStackDepth > 0);
387 SkASSERT(fEntries[fStackDepth].fClipStack ==
388 fEntries[fStackDepth -1].fClipStack);
389 pop();
390
391 SkASSERT(currentEntry()->fMatrix.getType() == SkMatrix::kIdentity_Mask);
392 }
393 if (matrix.getType() == SkMatrix::kIdentity_Mask) {
394 return;
395 }
396
397 push();
398 SkPDFUtils::AppendTransform(matrix, fContentStream);
399 currentEntry()->fMatrix = matrix;
400}
401
402void GraphicStackState::updateDrawingState(const GraphicStateEntry& state) {
403 // PDF treats a shader as a color, so we only set one or the other.
404 if (state.fShaderIndex >= 0) {
405 if (state.fShaderIndex != currentEntry()->fShaderIndex) {
406 fContentStream->writeText("/Pattern CS /Pattern cs /P");
407 fContentStream->writeDecAsText(state.fShaderIndex);
408 fContentStream->writeText(" SCN /P");
409 fContentStream->writeDecAsText(state.fShaderIndex);
410 fContentStream->writeText(" scn\n");
411 currentEntry()->fShaderIndex = state.fShaderIndex;
412 }
413 } else {
414 if (state.fColor != currentEntry()->fColor ||
415 currentEntry()->fShaderIndex >= 0) {
416 emit_pdf_color(state.fColor, fContentStream);
417 fContentStream->writeText("RG ");
418 emit_pdf_color(state.fColor, fContentStream);
419 fContentStream->writeText("rg\n");
420 currentEntry()->fColor = state.fColor;
421 currentEntry()->fShaderIndex = -1;
422 }
423 }
424
425 if (state.fGraphicStateIndex != currentEntry()->fGraphicStateIndex) {
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000426 SkPDFUtils::ApplyGraphicState(state.fGraphicStateIndex, fContentStream);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000427 currentEntry()->fGraphicStateIndex = state.fGraphicStateIndex;
428 }
429
430 if (state.fTextScaleX) {
431 if (state.fTextScaleX != currentEntry()->fTextScaleX) {
432 SkScalar pdfScale = SkScalarMul(state.fTextScaleX,
433 SkIntToScalar(100));
434 SkPDFScalar::Append(pdfScale, fContentStream);
435 fContentStream->writeText(" Tz\n");
436 currentEntry()->fTextScaleX = state.fTextScaleX;
437 }
438 if (state.fTextFill != currentEntry()->fTextFill) {
439 SK_COMPILE_ASSERT(SkPaint::kFill_Style == 0, enum_must_match_value);
440 SK_COMPILE_ASSERT(SkPaint::kStroke_Style == 1,
441 enum_must_match_value);
442 SK_COMPILE_ASSERT(SkPaint::kStrokeAndFill_Style == 2,
443 enum_must_match_value);
444 fContentStream->writeDecAsText(state.fTextFill);
445 fContentStream->writeText(" Tr\n");
446 currentEntry()->fTextFill = state.fTextFill;
447 }
448 }
449}
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000450
451////////////////////////////////////////////////////////////////////////////////
452
vandebo@chromium.orga0c7edb2011-05-09 07:58:08 +0000453SkDevice* SkPDFDeviceFactory::newDevice(SkCanvas* c, SkBitmap::Config config,
reed@android.comf2b98d62010-12-20 18:26:13 +0000454 int width, int height, bool isOpaque,
vandebo@chromium.orgf60a0012011-02-24 23:14:04 +0000455 bool isForLayer) {
vandebo@chromium.org75f97e42011-04-11 23:24:18 +0000456 SkMatrix initialTransform;
457 initialTransform.reset();
ctguil@chromium.org15261292011-04-29 17:54:16 +0000458 SkISize size = SkISize::Make(width, height);
vandebo@chromium.orga0c7edb2011-05-09 07:58:08 +0000459 if (isForLayer) {
460 return SkNEW_ARGS(SkPDFDevice, (size, c->getTotalClipStack(),
461 c->getTotalClip()));
462 } else {
463 return SkNEW_ARGS(SkPDFDevice, (size, size, initialTransform));
464 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000465}
466
ctguil@chromium.org15261292011-04-29 17:54:16 +0000467static inline SkBitmap makeContentBitmap(const SkISize& contentSize,
vandebo@chromium.orga0c7edb2011-05-09 07:58:08 +0000468 const SkMatrix* initialTransform) {
reed@android.comf2b98d62010-12-20 18:26:13 +0000469 SkBitmap bitmap;
vandebo@chromium.orga0c7edb2011-05-09 07:58:08 +0000470 if (initialTransform) {
471 // Compute the size of the drawing area.
472 SkVector drawingSize;
473 SkMatrix inverse;
474 drawingSize.set(contentSize.fWidth, contentSize.fHeight);
475 initialTransform->invert(&inverse);
476 inverse.mapVectors(&drawingSize, 1);
477 SkISize size = SkSize::Make(drawingSize.fX, drawingSize.fY).toRound();
478 bitmap.setConfig(SkBitmap::kNo_Config, abs(size.fWidth),
479 abs(size.fHeight));
480 } else {
481 bitmap.setConfig(SkBitmap::kNo_Config, abs(contentSize.fWidth),
482 abs(contentSize.fHeight));
483 }
484
reed@android.comf2b98d62010-12-20 18:26:13 +0000485 return bitmap;
486}
487
ctguil@chromium.org15261292011-04-29 17:54:16 +0000488SkPDFDevice::SkPDFDevice(const SkISize& pageSize, const SkISize& contentSize,
vandebo@chromium.org75f97e42011-04-11 23:24:18 +0000489 const SkMatrix& initialTransform)
vandebo@chromium.orga0c7edb2011-05-09 07:58:08 +0000490 : SkDevice(NULL, makeContentBitmap(contentSize, &initialTransform), false),
ctguil@chromium.org15261292011-04-29 17:54:16 +0000491 fPageSize(pageSize),
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000492 fContentSize(contentSize),
493 fCurrentContentEntry(NULL) {
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000494 // Skia generally uses the top left as the origin but PDF natively has the
vandebo@chromium.orga0c7edb2011-05-09 07:58:08 +0000495 // origin at the bottom left. This matrix corrects for that. But that only
496 // needs to be done once, we don't do it when layering.
ctguil@chromium.org15261292011-04-29 17:54:16 +0000497 fInitialTransform.setTranslate(0, pageSize.fHeight);
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000498 fInitialTransform.preScale(1, -1);
499 fInitialTransform.preConcat(initialTransform);
500
vandebo@chromium.orga0c7edb2011-05-09 07:58:08 +0000501 SkIRect existingClip = SkIRect::MakeWH(this->width(), this->height());
502 fExistingClipStack.clipDevRect(existingClip);
503 fExistingClipRegion.setRect(existingClip);
504
505 this->init();
506}
507
508SkPDFDevice::SkPDFDevice(const SkISize& layerSize,
509 const SkClipStack& existingClipStack,
510 const SkRegion& existingClipRegion)
511 : SkDevice(NULL, makeContentBitmap(layerSize, NULL), false),
512 fPageSize(layerSize),
513 fContentSize(layerSize),
514 fExistingClipStack(existingClipStack),
515 fExistingClipRegion(existingClipRegion),
516 fCurrentContentEntry(NULL) {
517 fInitialTransform.reset();
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000518 this->init();
519}
520
521SkPDFDevice::~SkPDFDevice() {
522 this->cleanUp();
523}
524
525void SkPDFDevice::init() {
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000526 fResourceDict = NULL;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000527 fContentEntries.reset();
528 fCurrentContentEntry = NULL;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000529}
530
mike@reedtribe.orgea4ac972011-04-26 11:48:33 +0000531SkDeviceFactory* SkPDFDevice::onNewDeviceFactory() {
532 return SkNEW(SkPDFDeviceFactory);
533}
534
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000535void SkPDFDevice::cleanUp() {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000536 fGraphicStateResources.unrefAll();
537 fXObjectResources.unrefAll();
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000538 fFontResources.unrefAll();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000539 fShaderResources.unrefAll();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000540}
541
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000542void SkPDFDevice::clear(SkColor color) {
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000543 this->cleanUp();
544 this->init();
545
546 SkPaint paint;
547 paint.setColor(color);
548 paint.setStyle(SkPaint::kFill_Style);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000549 SkMatrix identity;
550 identity.reset();
vandebo@chromium.org78dad542011-05-11 18:46:03 +0000551 if (!setUpContentEntry(&fExistingClipStack, fExistingClipRegion, identity,
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000552 paint)) {
553 return;
554 }
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000555
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000556 internalDrawPaint(paint);
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000557 finishContentEntry(paint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000558}
559
560void SkPDFDevice::drawPaint(const SkDraw& d, const SkPaint& paint) {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000561 SkPaint newPaint = paint;
562 newPaint.setStyle(SkPaint::kFill_Style);
vandebo@chromium.org78dad542011-05-11 18:46:03 +0000563 if (!setUpContentEntry(d.fClipStack, *d.fClip, *d.fMatrix, newPaint)) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000564 return;
565 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000566
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000567 internalDrawPaint(newPaint);
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000568 finishContentEntry(newPaint);
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000569}
570
571void SkPDFDevice::internalDrawPaint(const SkPaint& paint) {
572 SkRect bbox = SkRect::MakeWH(SkIntToScalar(this->width()),
573 SkIntToScalar(this->height()));
574 SkMatrix totalTransform = fInitialTransform;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000575 totalTransform.preConcat(fCurrentContentEntry->fState.fMatrix);
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000576 SkMatrix inverse;
577 inverse.reset();
578 totalTransform.invert(&inverse);
579 inverse.mapRect(&bbox);
580
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000581 SkPDFUtils::AppendRectangle(bbox, &fCurrentContentEntry->fContent);
582 SkPDFUtils::PaintPath(paint.getStyle(), SkPath::kWinding_FillType,
583 &fCurrentContentEntry->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000584}
585
586void SkPDFDevice::drawPoints(const SkDraw& d, SkCanvas::PointMode mode,
587 size_t count, const SkPoint* points,
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000588 const SkPaint& passedPaint) {
589 if (count == 0) {
590 return;
591 }
592
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000593 // SkDraw::drawPoints converts to multiple calls to fDevice->drawPath.
594 // We only use this when there's a path effect because of the overhead
595 // of multiple calls to setUpContentEntry it causes.
596 if (passedPaint.getPathEffect()) {
597 if (d.fClip->isEmpty()) {
598 return;
599 }
600 SkDraw pointDraw(d);
601 pointDraw.fDevice = this;
602 pointDraw.drawPoints(mode, count, points, passedPaint, true);
603 return;
604 }
605
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000606 const SkPaint* paint = &passedPaint;
607 SkPaint modifiedPaint;
608
609 if (mode == SkCanvas::kPoints_PointMode &&
610 paint->getStrokeCap() != SkPaint::kRound_Cap) {
611 modifiedPaint = *paint;
612 paint = &modifiedPaint;
613 if (paint->getStrokeWidth()) {
614 // PDF won't draw a single point with square/butt caps because the
615 // orientation is ambiguous. Draw a rectangle instead.
616 modifiedPaint.setStyle(SkPaint::kFill_Style);
617 SkScalar strokeWidth = paint->getStrokeWidth();
618 SkScalar halfStroke = SkScalarHalf(strokeWidth);
619 for (size_t i = 0; i < count; i++) {
620 SkRect r = SkRect::MakeXYWH(points[i].fX, points[i].fY, 0, 0);
621 r.inset(-halfStroke, -halfStroke);
622 drawRect(d, r, modifiedPaint);
623 }
624 return;
625 } else {
626 modifiedPaint.setStrokeCap(SkPaint::kRound_Cap);
627 }
628 }
629
630
vandebo@chromium.org78dad542011-05-11 18:46:03 +0000631 if (!setUpContentEntry(d.fClipStack, *d.fClip, *d.fMatrix, *paint)) {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000632 return;
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000633 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000634
635 switch (mode) {
636 case SkCanvas::kPolygon_PointMode:
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000637 SkPDFUtils::MoveTo(points[0].fX, points[0].fY,
638 &fCurrentContentEntry->fContent);
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +0000639 for (size_t i = 1; i < count; i++) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000640 SkPDFUtils::AppendLine(points[i].fX, points[i].fY,
641 &fCurrentContentEntry->fContent);
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +0000642 }
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000643 SkPDFUtils::StrokePath(&fCurrentContentEntry->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000644 break;
645 case SkCanvas::kLines_PointMode:
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000646 for (size_t i = 0; i < count/2; i++) {
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +0000647 SkPDFUtils::MoveTo(points[i * 2].fX, points[i * 2].fY,
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000648 &fCurrentContentEntry->fContent);
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +0000649 SkPDFUtils::AppendLine(points[i * 2 + 1].fX,
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000650 points[i * 2 + 1].fY,
651 &fCurrentContentEntry->fContent);
652 SkPDFUtils::StrokePath(&fCurrentContentEntry->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000653 }
654 break;
655 case SkCanvas::kPoints_PointMode:
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000656 SkASSERT(paint->getStrokeCap() == SkPaint::kRound_Cap);
657 for (size_t i = 0; i < count; i++) {
658 SkPDFUtils::MoveTo(points[i].fX, points[i].fY,
659 &fCurrentContentEntry->fContent);
660 SkPDFUtils::ClosePath(&fCurrentContentEntry->fContent);
661 SkPDFUtils::StrokePath(&fCurrentContentEntry->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000662 }
663 break;
664 default:
665 SkASSERT(false);
666 }
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000667 finishContentEntry(*paint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000668}
669
670void SkPDFDevice::drawRect(const SkDraw& d, const SkRect& r,
671 const SkPaint& paint) {
672 if (paint.getPathEffect()) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000673 if (d.fClip->isEmpty()) {
674 return;
675 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000676 SkPath path;
677 path.addRect(r);
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000678 drawPath(d, path, paint, NULL, true);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000679 return;
680 }
vandebo@chromium.org78dad542011-05-11 18:46:03 +0000681 if (!setUpContentEntry(d.fClipStack, *d.fClip, *d.fMatrix, paint)) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000682 return;
683 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000684
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000685 SkPDFUtils::AppendRectangle(r, &fCurrentContentEntry->fContent);
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +0000686 SkPDFUtils::PaintPath(paint.getStyle(), SkPath::kWinding_FillType,
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000687 &fCurrentContentEntry->fContent);
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000688 finishContentEntry(paint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000689}
690
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000691void SkPDFDevice::drawPath(const SkDraw& d, const SkPath& origPath,
vandebo@chromium.org02cc5aa2011-01-25 22:06:29 +0000692 const SkPaint& paint, const SkMatrix* prePathMatrix,
693 bool pathIsMutable) {
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000694 SkPath modifiedPath;
695 SkPath* pathPtr = const_cast<SkPath*>(&origPath);
696
697 SkMatrix matrix = *d.fMatrix;
698 if (prePathMatrix) {
699 if (paint.getPathEffect() || paint.getStyle() != SkPaint::kFill_Style) {
700 if (!pathIsMutable) {
701 pathPtr = &modifiedPath;
702 pathIsMutable = true;
703 }
704 origPath.transform(*prePathMatrix, pathPtr);
705 } else {
706 if (!matrix.preConcat(*prePathMatrix)) {
707 return;
708 }
709 }
710 }
vandebo@chromium.org02cc5aa2011-01-25 22:06:29 +0000711
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000712 if (paint.getPathEffect()) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000713 if (d.fClip->isEmpty()) {
714 return;
715 }
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000716 if (!pathIsMutable) {
717 pathPtr = &modifiedPath;
718 pathIsMutable = true;
719 }
720 bool fill = paint.getFillPath(origPath, pathPtr);
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000721
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000722 SkPaint noEffectPaint(paint);
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000723 noEffectPaint.setPathEffect(NULL);
724 if (fill) {
725 noEffectPaint.setStyle(SkPaint::kFill_Style);
726 } else {
727 noEffectPaint.setStyle(SkPaint::kStroke_Style);
728 noEffectPaint.setStrokeWidth(0);
729 }
730 drawPath(d, *pathPtr, noEffectPaint, NULL, true);
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000731 return;
732 }
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000733
vandebo@chromium.org78dad542011-05-11 18:46:03 +0000734 if (!setUpContentEntry(d.fClipStack, *d.fClip, *d.fMatrix, paint)) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000735 return;
736 }
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000737 SkPDFUtils::EmitPath(*pathPtr, &fCurrentContentEntry->fContent);
738 SkPDFUtils::PaintPath(paint.getStyle(), pathPtr->getFillType(),
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000739 &fCurrentContentEntry->fContent);
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000740 finishContentEntry(paint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000741}
742
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000743void SkPDFDevice::drawBitmap(const SkDraw& d, const SkBitmap& bitmap,
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000744 const SkIRect* srcRect, const SkMatrix& matrix,
745 const SkPaint& paint) {
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000746 if (d.fClip->isEmpty()) {
747 return;
748 }
749
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000750 SkMatrix transform = matrix;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000751 transform.postConcat(*d.fMatrix);
vandebo@chromium.org78dad542011-05-11 18:46:03 +0000752 internalDrawBitmap(transform, d.fClipStack, *d.fClip, bitmap, srcRect,
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000753 paint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000754}
755
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000756void SkPDFDevice::drawSprite(const SkDraw& d, const SkBitmap& bitmap,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000757 int x, int y, const SkPaint& paint) {
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000758 if (d.fClip->isEmpty()) {
759 return;
760 }
761
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000762 SkMatrix matrix;
reed@google.coma6d59f62011-03-07 21:29:21 +0000763 matrix.setTranslate(SkIntToScalar(x), SkIntToScalar(y));
vandebo@chromium.org78dad542011-05-11 18:46:03 +0000764 internalDrawBitmap(matrix, d.fClipStack, *d.fClip, bitmap, NULL, paint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000765}
766
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000767void SkPDFDevice::drawText(const SkDraw& d, const void* text, size_t len,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000768 SkScalar x, SkScalar y, const SkPaint& paint) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000769 SkPaint textPaint = calculate_text_paint(paint);
vandebo@chromium.org78dad542011-05-11 18:46:03 +0000770 if (!setUpContentEntryForText(d.fClipStack, *d.fClip, *d.fMatrix,
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000771 textPaint)) {
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000772 return;
773 }
774
vandebo@chromium.org01294102011-02-28 19:52:18 +0000775 // We want the text in glyph id encoding and a writable buffer, so we end
776 // up making a copy either way.
777 size_t numGlyphs = paint.textToGlyphs(text, len, NULL);
778 uint16_t* glyphIDs =
779 (uint16_t*)sk_malloc_flags(numGlyphs * 2,
780 SK_MALLOC_TEMP | SK_MALLOC_THROW);
781 SkAutoFree autoFreeGlyphIDs(glyphIDs);
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000782 if (paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding) {
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000783 paint.textToGlyphs(text, len, glyphIDs);
784 textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
785 } else {
786 SkASSERT((len & 1) == 0);
vandebo@chromium.org01294102011-02-28 19:52:18 +0000787 SkASSERT(len / 2 == numGlyphs);
788 memcpy(glyphIDs, text, len);
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000789 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000790
791 SkScalar width;
792 SkScalar* widthPtr = NULL;
793 if (textPaint.isUnderlineText() || textPaint.isStrikeThruText())
794 widthPtr = &width;
795
796 SkDrawCacheProc glyphCacheProc = textPaint.getDrawCacheProc();
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000797 align_text(glyphCacheProc, textPaint, glyphIDs, numGlyphs, &x, &y,
798 widthPtr);
799 fCurrentContentEntry->fContent.writeText("BT\n");
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000800 setTextTransform(x, y, textPaint.getTextSkewX());
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000801 size_t consumedGlyphCount = 0;
802 while (numGlyphs > consumedGlyphCount) {
803 updateFont(textPaint, glyphIDs[consumedGlyphCount]);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000804 SkPDFFont* font = fCurrentContentEntry->fState.fFont;
vandebo@chromium.org01294102011-02-28 19:52:18 +0000805 size_t availableGlyphs =
806 font->glyphsToPDFFontEncoding(glyphIDs + consumedGlyphCount,
807 numGlyphs - consumedGlyphCount);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000808 SkString encodedString =
809 SkPDFString::formatString(glyphIDs + consumedGlyphCount,
810 availableGlyphs, font->multiByteGlyphs());
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000811 fCurrentContentEntry->fContent.writeText(encodedString.c_str());
vandebo@chromium.org01294102011-02-28 19:52:18 +0000812 consumedGlyphCount += availableGlyphs;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000813 fCurrentContentEntry->fContent.writeText(" Tj\n");
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000814 }
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000815 fCurrentContentEntry->fContent.writeText("ET\n");
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000816
817 // Draw underline and/or strikethrough if the paint has them.
818 // drawPosText() and drawTextOnPath() don't draw underline or strikethrough
819 // because the raster versions don't. Use paint instead of textPaint
820 // because we may have changed strokeWidth to do fakeBold text.
821 if (paint.isUnderlineText() || paint.isStrikeThruText()) {
822 SkScalar textSize = paint.getTextSize();
823 SkScalar height = SkScalarMul(textSize, kStdUnderline_Thickness);
824
825 if (paint.isUnderlineText()) {
826 SkScalar top = SkScalarMulAdd(textSize, kStdUnderline_Offset, y);
827 SkRect r = SkRect::MakeXYWH(x, top - height, width, height);
828 drawRect(d, r, paint);
829 }
830 if (paint.isStrikeThruText()) {
831 SkScalar top = SkScalarMulAdd(textSize, kStdStrikeThru_Offset, y);
832 SkRect r = SkRect::MakeXYWH(x, top - height, width, height);
833 drawRect(d, r, paint);
834 }
835 }
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000836 finishContentEntry(textPaint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000837}
838
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000839void SkPDFDevice::drawPosText(const SkDraw& d, const void* text, size_t len,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000840 const SkScalar pos[], SkScalar constY,
841 int scalarsPerPos, const SkPaint& paint) {
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000842 SkASSERT(1 == scalarsPerPos || 2 == scalarsPerPos);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000843 SkPaint textPaint = calculate_text_paint(paint);
vandebo@chromium.org78dad542011-05-11 18:46:03 +0000844 if (!setUpContentEntryForText(d.fClipStack, *d.fClip, *d.fMatrix,
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000845 textPaint)) {
846 return;
847 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000848
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000849 // Make sure we have a glyph id encoding.
850 SkAutoFree glyphStorage;
851 uint16_t* glyphIDs;
852 size_t numGlyphs;
853 if (paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding) {
854 numGlyphs = paint.textToGlyphs(text, len, NULL);
855 glyphIDs = (uint16_t*)sk_malloc_flags(numGlyphs * 2,
856 SK_MALLOC_TEMP | SK_MALLOC_THROW);
857 glyphStorage.set(glyphIDs);
858 paint.textToGlyphs(text, len, glyphIDs);
859 textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
860 } else {
861 SkASSERT((len & 1) == 0);
862 numGlyphs = len / 2;
863 glyphIDs = (uint16_t*)text;
864 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000865
866 SkDrawCacheProc glyphCacheProc = textPaint.getDrawCacheProc();
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000867 fCurrentContentEntry->fContent.writeText("BT\n");
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000868 updateFont(textPaint, glyphIDs[0]);
869 for (size_t i = 0; i < numGlyphs; i++) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000870 SkPDFFont* font = fCurrentContentEntry->fState.fFont;
vandebo@chromium.org01294102011-02-28 19:52:18 +0000871 uint16_t encodedValue = glyphIDs[i];
872 if (font->glyphsToPDFFontEncoding(&encodedValue, 1) != 1) {
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000873 updateFont(textPaint, glyphIDs[i]);
874 i--;
875 continue;
876 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000877 SkScalar x = pos[i * scalarsPerPos];
878 SkScalar y = scalarsPerPos == 1 ? constY : pos[i * scalarsPerPos + 1];
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000879 align_text(glyphCacheProc, textPaint, glyphIDs + i, 1, &x, &y, NULL);
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000880 setTextTransform(x, y, textPaint.getTextSkewX());
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000881 SkString encodedString =
882 SkPDFString::formatString(&encodedValue, 1,
883 font->multiByteGlyphs());
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000884 fCurrentContentEntry->fContent.writeText(encodedString.c_str());
885 fCurrentContentEntry->fContent.writeText(" Tj\n");
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000886 }
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000887 fCurrentContentEntry->fContent.writeText("ET\n");
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000888 finishContentEntry(textPaint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000889}
890
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000891void SkPDFDevice::drawTextOnPath(const SkDraw& d, const void* text, size_t len,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000892 const SkPath& path, const SkMatrix* matrix,
893 const SkPaint& paint) {
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000894 if (d.fClip->isEmpty()) {
895 return;
896 }
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000897 NOT_IMPLEMENTED("drawTextOnPath", true);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000898}
899
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000900void SkPDFDevice::drawVertices(const SkDraw& d, SkCanvas::VertexMode,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000901 int vertexCount, const SkPoint verts[],
902 const SkPoint texs[], const SkColor colors[],
903 SkXfermode* xmode, const uint16_t indices[],
904 int indexCount, const SkPaint& paint) {
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000905 if (d.fClip->isEmpty()) {
906 return;
907 }
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000908 NOT_IMPLEMENTED("drawVerticies", true);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000909}
910
vandebo@chromium.orgeb6c7592010-10-26 19:54:45 +0000911void SkPDFDevice::drawDevice(const SkDraw& d, SkDevice* device, int x, int y,
912 const SkPaint& paint) {
913 if ((device->getDeviceCapabilities() & kVector_Capability) == 0) {
914 // If we somehow get a raster device, do what our parent would do.
915 SkDevice::drawDevice(d, device, x, y, paint);
916 return;
917 }
vandebo@chromium.orgeb6c7592010-10-26 19:54:45 +0000918
vandebo@chromium.org1aef2ed2011-02-03 21:46:10 +0000919 SkMatrix matrix;
reed@google.coma6d59f62011-03-07 21:29:21 +0000920 matrix.setTranslate(SkIntToScalar(x), SkIntToScalar(y));
vandebo@chromium.org78dad542011-05-11 18:46:03 +0000921 if (!setUpContentEntry(d.fClipStack, *d.fClip, matrix, paint)) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000922 return;
923 }
vandebo@chromium.org1aef2ed2011-02-03 21:46:10 +0000924
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000925 // Assume that a vector capable device means that it's a PDF Device.
926 SkPDFDevice* pdfDevice = static_cast<SkPDFDevice*>(device);
vandebo@chromium.org1aef2ed2011-02-03 21:46:10 +0000927 SkPDFFormXObject* xobject = new SkPDFFormXObject(pdfDevice);
vandebo@chromium.orgeb6c7592010-10-26 19:54:45 +0000928 fXObjectResources.push(xobject); // Transfer reference.
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000929 SkPDFUtils::DrawFormXObject(fXObjectResources.count() - 1,
930 &fCurrentContentEntry->fContent);
931 finishContentEntry(paint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000932}
933
934const SkRefPtr<SkPDFDict>& SkPDFDevice::getResourceDict() {
935 if (fResourceDict.get() == NULL) {
936 fResourceDict = new SkPDFDict;
937 fResourceDict->unref(); // SkRefPtr and new both took a reference.
938
939 if (fGraphicStateResources.count()) {
940 SkRefPtr<SkPDFDict> extGState = new SkPDFDict();
941 extGState->unref(); // SkRefPtr and new both took a reference.
942 for (int i = 0; i < fGraphicStateResources.count(); i++) {
943 SkString nameString("G");
944 nameString.appendS32(i);
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000945 extGState->insert(
946 nameString.c_str(),
947 new SkPDFObjRef(fGraphicStateResources[i]))->unref();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000948 }
949 fResourceDict->insert("ExtGState", extGState.get());
950 }
951
952 if (fXObjectResources.count()) {
953 SkRefPtr<SkPDFDict> xObjects = new SkPDFDict();
954 xObjects->unref(); // SkRefPtr and new both took a reference.
955 for (int i = 0; i < fXObjectResources.count(); i++) {
956 SkString nameString("X");
957 nameString.appendS32(i);
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000958 xObjects->insert(
959 nameString.c_str(),
960 new SkPDFObjRef(fXObjectResources[i]))->unref();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000961 }
962 fResourceDict->insert("XObject", xObjects.get());
963 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000964
965 if (fFontResources.count()) {
966 SkRefPtr<SkPDFDict> fonts = new SkPDFDict();
967 fonts->unref(); // SkRefPtr and new both took a reference.
968 for (int i = 0; i < fFontResources.count(); i++) {
969 SkString nameString("F");
970 nameString.appendS32(i);
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000971 fonts->insert(nameString.c_str(),
972 new SkPDFObjRef(fFontResources[i]))->unref();
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000973 }
974 fResourceDict->insert("Font", fonts.get());
975 }
976
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000977 if (fShaderResources.count()) {
978 SkRefPtr<SkPDFDict> patterns = new SkPDFDict();
979 patterns->unref(); // SkRefPtr and new both took a reference.
980 for (int i = 0; i < fShaderResources.count(); i++) {
981 SkString nameString("P");
982 nameString.appendS32(i);
983 patterns->insert(nameString.c_str(),
984 new SkPDFObjRef(fShaderResources[i]))->unref();
985 }
986 fResourceDict->insert("Pattern", patterns.get());
987 }
988
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000989 // For compatibility, add all proc sets (only used for output to PS
990 // devices).
991 const char procs[][7] = {"PDF", "Text", "ImageB", "ImageC", "ImageI"};
992 SkRefPtr<SkPDFArray> procSets = new SkPDFArray();
993 procSets->unref(); // SkRefPtr and new both took a reference.
994 procSets->reserve(SK_ARRAY_COUNT(procs));
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000995 for (size_t i = 0; i < SK_ARRAY_COUNT(procs); i++)
996 procSets->append(new SkPDFName(procs[i]))->unref();
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000997 fResourceDict->insert("ProcSet", procSets.get());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000998 }
999 return fResourceDict;
1000}
1001
vandebo@chromium.orga5180862010-10-26 19:48:49 +00001002void SkPDFDevice::getResources(SkTDArray<SkPDFObject*>* resourceList) const {
1003 resourceList->setReserve(resourceList->count() +
1004 fGraphicStateResources.count() +
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00001005 fXObjectResources.count() +
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001006 fFontResources.count() +
1007 fShaderResources.count());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001008 for (int i = 0; i < fGraphicStateResources.count(); i++) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +00001009 resourceList->push(fGraphicStateResources[i]);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001010 fGraphicStateResources[i]->ref();
vandebo@chromium.orga5180862010-10-26 19:48:49 +00001011 fGraphicStateResources[i]->getResources(resourceList);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001012 }
1013 for (int i = 0; i < fXObjectResources.count(); i++) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +00001014 resourceList->push(fXObjectResources[i]);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001015 fXObjectResources[i]->ref();
vandebo@chromium.orga5180862010-10-26 19:48:49 +00001016 fXObjectResources[i]->getResources(resourceList);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001017 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00001018 for (int i = 0; i < fFontResources.count(); i++) {
1019 resourceList->push(fFontResources[i]);
1020 fFontResources[i]->ref();
1021 fFontResources[i]->getResources(resourceList);
1022 }
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001023 for (int i = 0; i < fShaderResources.count(); i++) {
1024 resourceList->push(fShaderResources[i]);
1025 fShaderResources[i]->ref();
1026 fShaderResources[i]->getResources(resourceList);
1027 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001028}
1029
vandebo@chromium.orga5180862010-10-26 19:48:49 +00001030SkRefPtr<SkPDFArray> SkPDFDevice::getMediaBox() const {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001031 SkRefPtr<SkPDFInt> zero = new SkPDFInt(0);
1032 zero->unref(); // SkRefPtr and new both took a reference.
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +00001033
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001034 SkRefPtr<SkPDFArray> mediaBox = new SkPDFArray();
1035 mediaBox->unref(); // SkRefPtr and new both took a reference.
1036 mediaBox->reserve(4);
1037 mediaBox->append(zero.get());
1038 mediaBox->append(zero.get());
ctguil@chromium.org15261292011-04-29 17:54:16 +00001039 mediaBox->append(new SkPDFInt(fPageSize.fWidth))->unref();
1040 mediaBox->append(new SkPDFInt(fPageSize.fHeight))->unref();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001041 return mediaBox;
1042}
1043
vandebo@chromium.orgc2a9b7f2011-02-24 23:22:30 +00001044SkStream* SkPDFDevice::content() const {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001045 SkDynamicMemoryWStream data;
1046 if (fInitialTransform.getType() != SkMatrix::kIdentity_Mask) {
1047 SkPDFUtils::AppendTransform(fInitialTransform, &data);
vandebo@chromium.orgc2a9b7f2011-02-24 23:22:30 +00001048 }
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001049 // If the content area is the entire page, then we don't need to clip
1050 // the content area (PDF area clips to the page size). Otherwise,
1051 // we have to clip to the content area; we've already applied the
1052 // initial transform, so just clip to the device size.
1053 if (fPageSize != fContentSize) {
1054 SkRect r = SkRect::MakeWH(this->width(), this->height());
1055 emit_clip(NULL, &r, &data);
1056 }
1057
1058 GraphicStackState gsState(fExistingClipStack, fExistingClipRegion, &data);
1059 for (ContentEntry* entry = fContentEntries.get();
1060 entry != NULL;
1061 entry = entry->fNext.get()) {
1062 SkIPoint translation = this->getOrigin();
1063 translation.negate();
1064 gsState.updateClip(entry->fState.fClipStack, entry->fState.fClipRegion,
1065 translation);
1066 gsState.updateMatrix(entry->fState.fMatrix);
1067 gsState.updateDrawingState(entry->fState);
1068 data.write(entry->fContent.getStream(), entry->fContent.getOffset());
1069 }
1070 gsState.drainStack();
1071
vandebo@chromium.orgc2a9b7f2011-02-24 23:22:30 +00001072 SkMemoryStream* result = new SkMemoryStream;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001073 result->setMemoryOwned(data.detach(), data.getOffset());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001074 return result;
1075}
1076
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001077void SkPDFDevice::createFormXObjectFromDevice(
1078 SkRefPtr<SkPDFFormXObject>* xobject) {
1079 *xobject = new SkPDFFormXObject(this);
1080 (*xobject)->unref(); // SkRefPtr and new both took a reference.
1081 cleanUp(); // Reset this device to have no content.
1082 init();
1083}
1084
vandebo@chromium.org78dad542011-05-11 18:46:03 +00001085bool SkPDFDevice::setUpContentEntry(const SkClipStack* clipStack,
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001086 const SkRegion& clipRegion,
1087 const SkMatrix& matrix,
1088 const SkPaint& paint,
1089 bool hasText) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001090 if (clipRegion.isEmpty()) {
1091 return false;
1092 }
1093
vandebo@chromium.org78dad542011-05-11 18:46:03 +00001094 // The clip stack can come from an SkDraw where it is technically optional.
1095 SkClipStack synthesizedClipStack;
1096 if (clipStack == NULL) {
1097 if (clipRegion == fExistingClipRegion) {
1098 clipStack = &fExistingClipStack;
1099 } else {
1100 // GraphicStackState::updateClip expects the clip stack to have
1101 // fExistingClip as a prefix, so start there, then set the clip
1102 // to the passed region.
1103 synthesizedClipStack = fExistingClipStack;
1104 SkPath clipPath;
1105 clipRegion.getBoundaryPath(&clipPath);
1106 synthesizedClipStack.clipDevPath(clipPath, SkRegion::kReplace_Op);
1107 clipStack = &synthesizedClipStack;
1108 }
1109 }
1110
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001111 SkXfermode::Mode xfermode = SkXfermode::kSrcOver_Mode;
1112 if (paint.getXfermode()) {
1113 paint.getXfermode()->asMode(&xfermode);
1114 }
1115
1116 // For Clear and Src modes, we need to push down the inverse of the
1117 // current clip.
1118 if (xfermode == SkXfermode::kClear_Mode ||
1119 xfermode == SkXfermode::kSrc_Mode) {
vandebo@chromium.org78dad542011-05-11 18:46:03 +00001120 apply_inverse_clip_to_content_entries(*clipStack, clipRegion,
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001121 &fContentEntries);
1122 // apply_inverse_clip_to_content_entries may have removed entries
1123 // from fContentEntries and this may have invalidated
1124 // fCurrentContentEntry. Set it to a known good value.
1125 fCurrentContentEntry = fContentEntries.get();
1126 }
1127
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001128 // For the following modes, we use both source and destination, but
1129 // we use one as a smask for the other, so we have to make form xobjects
1130 // out of both of them: SrcIn, DstIn, SrcOut, DstOut.
1131 if (xfermode == SkXfermode::kSrcIn_Mode ||
1132 xfermode == SkXfermode::kDstIn_Mode ||
1133 xfermode == SkXfermode::kSrcOut_Mode ||
1134 xfermode == SkXfermode::kDstOut_Mode) {
1135 SkASSERT(fDstFormXObject.get() == NULL);
1136 createFormXObjectFromDevice(&fDstFormXObject);
1137 }
1138
1139 // TODO(vandebo) Figure out how/if we can handle the following modes:
1140 // SrcAtop, DestAtop, Xor.
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001141
1142 // These xfer modes don't draw source at all.
1143 if (xfermode == SkXfermode::kClear_Mode ||
1144 xfermode == SkXfermode::kDst_Mode) {
1145 return false;
1146 }
1147
1148 // If the previous content entry was for DstOver reset fCurrentContentEntry.
1149 if (fCurrentContentEntry && xfermode != SkXfermode::kDstOver_Mode) {
1150 while (fCurrentContentEntry->fNext.get()) {
1151 fCurrentContentEntry = fCurrentContentEntry->fNext.get();
1152 }
1153 }
1154
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001155 ContentEntry* entry;
1156 SkTScopedPtr<ContentEntry> newEntry;
1157 if (fCurrentContentEntry &&
1158 fCurrentContentEntry->fContent.getOffset() == 0) {
1159 entry = fCurrentContentEntry;
1160 } else {
1161 newEntry.reset(new ContentEntry);
1162 entry = newEntry.get();
1163 }
1164
vandebo@chromium.org78dad542011-05-11 18:46:03 +00001165 populateGraphicStateEntryFromPaint(matrix, *clipStack, clipRegion, paint,
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001166 hasText, &entry->fState);
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001167 if (fCurrentContentEntry && xfermode != SkXfermode::kDstOver_Mode &&
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001168 entry->fState.compareInitialState(fCurrentContentEntry->fState)) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001169 return true;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001170 }
1171
1172 if (!fCurrentContentEntry) {
1173 fContentEntries.reset(entry);
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001174 } else if (xfermode == SkXfermode::kDstOver_Mode) {
1175 entry->fNext.reset(fContentEntries.release());
1176 fContentEntries.reset(entry);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001177 } else {
1178 fCurrentContentEntry->fNext.reset(entry);
1179 }
1180 newEntry.release();
1181 fCurrentContentEntry = entry;
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001182 return true;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001183}
1184
vandebo@chromium.org78dad542011-05-11 18:46:03 +00001185bool SkPDFDevice::setUpContentEntryForText(const SkClipStack* clipStack,
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001186 const SkRegion& clipRegion,
1187 const SkMatrix& matrix,
1188 const SkPaint& paint) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001189 return setUpContentEntry(clipStack, clipRegion, matrix, paint, true);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001190}
1191
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001192void SkPDFDevice::finishContentEntry(const SkPaint& paint) {
1193 SkXfermode::Mode xfermode = SkXfermode::kSrcOver_Mode;
1194 if (paint.getXfermode()) {
1195 paint.getXfermode()->asMode(&xfermode);
1196 }
1197 if (xfermode != SkXfermode::kSrcIn_Mode &&
1198 xfermode != SkXfermode::kDstIn_Mode &&
1199 xfermode != SkXfermode::kSrcOut_Mode &&
1200 xfermode != SkXfermode::kDstOut_Mode) {
1201 SkASSERT(fDstFormXObject.get() == NULL);
1202 return;
1203 }
1204
1205 SkRefPtr<SkPDFFormXObject> srcFormXObject;
1206 createFormXObjectFromDevice(&srcFormXObject);
1207
1208 SkMatrix identity;
1209 identity.reset();
1210 SkPaint stockPaint;
1211 setUpContentEntry(&fExistingClipStack, fExistingClipRegion, identity,
1212 stockPaint);
1213
1214 SkRefPtr<SkPDFGraphicState> sMaskGS;
1215 if (xfermode == SkXfermode::kSrcIn_Mode ||
1216 xfermode == SkXfermode::kSrcOut_Mode) {
1217 sMaskGS = SkPDFGraphicState::getSMaskGraphicState(
1218 fDstFormXObject.get(), xfermode == SkXfermode::kSrcOut_Mode);
1219 fXObjectResources.push(srcFormXObject.get());
1220 srcFormXObject->ref();
1221 } else {
1222 sMaskGS = SkPDFGraphicState::getSMaskGraphicState(
1223 srcFormXObject.get(), xfermode == SkXfermode::kDstOut_Mode);
1224 fXObjectResources.push(fDstFormXObject.get());
1225 fDstFormXObject->ref();
1226 }
1227 sMaskGS->unref(); // SkRefPtr and getSMaskGraphicState both took a ref.
1228 SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()),
1229 &fCurrentContentEntry->fContent);
1230
1231 SkPDFUtils::DrawFormXObject(fXObjectResources.count() - 1,
1232 &fCurrentContentEntry->fContent);
1233
1234 sMaskGS = SkPDFGraphicState::getNoSMaskGraphicState();
1235 sMaskGS->unref(); // SkRefPtr and getSMaskGraphicState both took a ref.
1236 SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()),
1237 &fCurrentContentEntry->fContent);
1238
1239 fDstFormXObject = NULL;
1240 finishContentEntry(stockPaint);
1241}
1242
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001243void SkPDFDevice::populateGraphicStateEntryFromPaint(
1244 const SkMatrix& matrix,
1245 const SkClipStack& clipStack,
1246 const SkRegion& clipRegion,
1247 const SkPaint& paint,
1248 bool hasText,
1249 GraphicStateEntry* entry) {
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001250 SkASSERT(paint.getPathEffect() == NULL);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001251
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001252 NOT_IMPLEMENTED(paint.getMaskFilter() != NULL, false);
1253 NOT_IMPLEMENTED(paint.getColorFilter() != NULL, false);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001254
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001255 entry->fMatrix = matrix;
1256 entry->fClipStack = clipStack;
1257 entry->fClipRegion = clipRegion;
vandebo@chromium.org48543272011-02-08 19:28:07 +00001258
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001259 // PDF treats a shader as a color, so we only set one or the other.
1260 SkRefPtr<SkPDFShader> pdfShader;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001261 const SkShader* shader = paint.getShader();
1262 SkColor color = paint.getColor();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001263 if (shader) {
1264 // PDF positions patterns relative to the initial transform, so
1265 // we need to apply the current transform to the shader parameters.
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001266 SkMatrix transform = matrix;
vandebo@chromium.org75f97e42011-04-11 23:24:18 +00001267 transform.postConcat(fInitialTransform);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001268
1269 // PDF doesn't support kClamp_TileMode, so we simulate it by making
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001270 // a pattern the size of the current clip.
1271 SkIRect bounds = fCurrentContentEntry->fState.fClipRegion.getBounds();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001272 pdfShader = SkPDFShader::getPDFShader(*shader, transform, bounds);
1273 SkSafeUnref(pdfShader.get()); // getShader and SkRefPtr both took a ref
1274
1275 // A color shader is treated as an invalid shader so we don't have
1276 // to set a shader just for a color.
1277 if (pdfShader.get() == NULL) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001278 entry->fColor = 0;
1279 color = 0;
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001280
1281 // Check for a color shader.
1282 SkShader::GradientInfo gradientInfo;
1283 SkColor gradientColor;
1284 gradientInfo.fColors = &gradientColor;
1285 gradientInfo.fColorOffsets = NULL;
1286 gradientInfo.fColorCount = 1;
1287 if (shader->asAGradient(&gradientInfo) ==
1288 SkShader::kColor_GradientType) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001289 entry->fColor = SkColorSetA(gradientColor, 0xFF);
1290 color = gradientColor;
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001291 }
1292 }
1293 }
1294
1295 if (pdfShader) {
1296 // pdfShader has been canonicalized so we can directly compare
1297 // pointers.
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001298 int resourceIndex = fShaderResources.find(pdfShader.get());
1299 if (resourceIndex < 0) {
1300 resourceIndex = fShaderResources.count();
1301 fShaderResources.push(pdfShader.get());
1302 pdfShader->ref();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001303 }
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001304 entry->fShaderIndex = resourceIndex;
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001305 } else {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001306 entry->fShaderIndex = -1;
1307 entry->fColor = SkColorSetA(paint.getColor(), 0xFF);
1308 color = paint.getColor();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001309 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001310
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001311 SkRefPtr<SkPDFGraphicState> newGraphicState;
1312 if (color == paint.getColor()) {
1313 newGraphicState = SkPDFGraphicState::getGraphicStateForPaint(paint);
1314 } else {
1315 SkPaint newPaint = paint;
1316 newPaint.setColor(color);
1317 newGraphicState = SkPDFGraphicState::getGraphicStateForPaint(newPaint);
1318 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001319 newGraphicState->unref(); // getGraphicState and SkRefPtr both took a ref.
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001320 int resourceIndex = addGraphicStateResource(newGraphicState.get());
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001321 entry->fGraphicStateIndex = resourceIndex;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001322
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001323 if (hasText) {
1324 entry->fTextScaleX = paint.getTextScaleX();
1325 entry->fTextFill = paint.getStyle();
1326 } else {
1327 entry->fTextScaleX = 0;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001328 }
1329}
1330
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001331int SkPDFDevice::addGraphicStateResource(SkPDFGraphicState* gs) {
1332 // Assumes that gs has been canonicalized (so we can directly compare
1333 // pointers).
1334 int result = fGraphicStateResources.find(gs);
1335 if (result < 0) {
1336 result = fGraphicStateResources.count();
1337 fGraphicStateResources.push(gs);
1338 gs->ref();
1339 }
1340 return result;
1341}
1342
vandebo@chromium.org2a22e102011-01-25 21:01:34 +00001343void SkPDFDevice::updateFont(const SkPaint& paint, uint16_t glyphID) {
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +00001344 SkTypeface* typeface = paint.getTypeface();
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001345 if (fCurrentContentEntry->fState.fFont == NULL ||
vandebo@chromium.org339ac3d2011-05-09 17:36:36 +00001346 fCurrentContentEntry->fState.fTextSize != paint.getTextSize() ||
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001347 !fCurrentContentEntry->fState.fFont->hasGlyph(glyphID)) {
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +00001348 int fontIndex = getFontResourceIndex(typeface, glyphID);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001349 fCurrentContentEntry->fContent.writeText("/F");
1350 fCurrentContentEntry->fContent.writeDecAsText(fontIndex);
1351 fCurrentContentEntry->fContent.writeText(" ");
1352 SkPDFScalar::Append(paint.getTextSize(),
1353 &fCurrentContentEntry->fContent);
1354 fCurrentContentEntry->fContent.writeText(" Tf\n");
1355 fCurrentContentEntry->fState.fFont = fFontResources[fontIndex];
vandebo@chromium.org2a22e102011-01-25 21:01:34 +00001356 }
1357}
1358
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +00001359int SkPDFDevice::getFontResourceIndex(SkTypeface* typeface, uint16_t glyphID) {
1360 SkRefPtr<SkPDFFont> newFont = SkPDFFont::getFontResource(typeface, glyphID);
vandebo@chromium.org2a22e102011-01-25 21:01:34 +00001361 newFont->unref(); // getFontResource and SkRefPtr both took a ref.
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00001362 int resourceIndex = fFontResources.find(newFont.get());
1363 if (resourceIndex < 0) {
1364 resourceIndex = fFontResources.count();
1365 fFontResources.push(newFont.get());
1366 newFont->ref();
1367 }
1368 return resourceIndex;
1369}
1370
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00001371void SkPDFDevice::setTextTransform(SkScalar x, SkScalar y, SkScalar textSkewX) {
1372 // Flip the text about the x-axis to account for origin swap and include
1373 // the passed parameters.
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001374 fCurrentContentEntry->fContent.writeText("1 0 ");
1375 SkPDFScalar::Append(0 - textSkewX, &fCurrentContentEntry->fContent);
1376 fCurrentContentEntry->fContent.writeText(" -1 ");
1377 SkPDFScalar::Append(x, &fCurrentContentEntry->fContent);
1378 fCurrentContentEntry->fContent.writeText(" ");
1379 SkPDFScalar::Append(y, &fCurrentContentEntry->fContent);
1380 fCurrentContentEntry->fContent.writeText(" Tm\n");
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00001381}
1382
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001383void SkPDFDevice::internalDrawBitmap(const SkMatrix& matrix,
vandebo@chromium.org78dad542011-05-11 18:46:03 +00001384 const SkClipStack* clipStack,
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001385 const SkRegion& clipRegion,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001386 const SkBitmap& bitmap,
vandebo@chromium.orgbefebb82011-01-29 01:38:50 +00001387 const SkIRect* srcRect,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001388 const SkPaint& paint) {
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +00001389 SkMatrix scaled;
1390 // Adjust for origin flip.
1391 scaled.setScale(1, -1);
1392 scaled.postTranslate(0, 1);
1393 // Scale the image up from 1x1 to WxH.
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001394 SkIRect subset = SkIRect::MakeWH(bitmap.width(), bitmap.height());
reed@google.coma6d59f62011-03-07 21:29:21 +00001395 scaled.postScale(SkIntToScalar(subset.width()),
1396 SkIntToScalar(subset.height()));
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +00001397 scaled.postConcat(matrix);
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001398 if (!setUpContentEntry(clipStack, clipRegion, scaled, paint)) {
1399 return;
1400 }
1401
1402 if (srcRect && !subset.intersect(*srcRect)) {
1403 return;
1404 }
1405
1406 SkPDFImage* image = SkPDFImage::CreateImage(bitmap, subset, paint);
1407 if (!image) {
1408 return;
1409 }
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +00001410
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001411 fXObjectResources.push(image); // Transfer reference.
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001412 SkPDFUtils::DrawFormXObject(fXObjectResources.count() - 1,
1413 &fCurrentContentEntry->fContent);
1414 finishContentEntry(paint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001415}