blob: 0da237d3d2c4b4545ec359dcfa749f646c71c3e6 [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"
reed@google.com8a85d0c2011-06-24 19:12:12 +000021#include "SkData.h"
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +000022#include "SkDraw.h"
vandebo@chromium.org28be72b2010-11-11 21:37:00 +000023#include "SkGlyphCache.h"
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000024#include "SkPaint.h"
vandebo@chromium.orga5180862010-10-26 19:48:49 +000025#include "SkPath.h"
vandebo@chromium.org28be72b2010-11-11 21:37:00 +000026#include "SkPDFFont.h"
vandebo@chromium.orgeb6c7592010-10-26 19:54:45 +000027#include "SkPDFFormXObject.h"
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +000028#include "SkPDFGraphicState.h"
29#include "SkPDFImage.h"
vandebo@chromium.orgda912d62011-03-08 18:31:02 +000030#include "SkPDFShader.h"
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000031#include "SkPDFStream.h"
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +000032#include "SkPDFTypes.h"
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +000033#include "SkPDFUtils.h"
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000034#include "SkRect.h"
35#include "SkString.h"
vandebo@chromium.org28be72b2010-11-11 21:37:00 +000036#include "SkTextFormatParams.h"
37#include "SkTypeface.h"
38#include "SkTypes.h"
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000039
40// Utility functions
41
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +000042static void emit_pdf_color(SkColor color, SkWStream* result) {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000043 SkASSERT(SkColorGetA(color) == 0xFF); // We handle alpha elsewhere.
44 SkScalar colorMax = SkIntToScalar(0xFF);
vandebo@chromium.org094316b2011-03-04 03:15:13 +000045 SkPDFScalar::Append(
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000046 SkScalarDiv(SkIntToScalar(SkColorGetR(color)), colorMax), result);
47 result->writeText(" ");
vandebo@chromium.org094316b2011-03-04 03:15:13 +000048 SkPDFScalar::Append(
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000049 SkScalarDiv(SkIntToScalar(SkColorGetG(color)), colorMax), result);
50 result->writeText(" ");
vandebo@chromium.org094316b2011-03-04 03:15:13 +000051 SkPDFScalar::Append(
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000052 SkScalarDiv(SkIntToScalar(SkColorGetB(color)), colorMax), result);
53 result->writeText(" ");
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000054}
55
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +000056static SkPaint calculate_text_paint(const SkPaint& paint) {
vandebo@chromium.org28be72b2010-11-11 21:37:00 +000057 SkPaint result = paint;
58 if (result.isFakeBoldText()) {
59 SkScalar fakeBoldScale = SkScalarInterpFunc(result.getTextSize(),
60 kStdFakeBoldInterpKeys,
61 kStdFakeBoldInterpValues,
62 kStdFakeBoldInterpLength);
63 SkScalar width = SkScalarMul(result.getTextSize(), fakeBoldScale);
64 if (result.getStyle() == SkPaint::kFill_Style)
65 result.setStyle(SkPaint::kStrokeAndFill_Style);
66 else
67 width += result.getStrokeWidth();
68 result.setStrokeWidth(width);
69 }
70 return result;
71}
72
73// Stolen from measure_text in SkDraw.cpp and then tweaked.
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +000074static void align_text(SkDrawCacheProc glyphCacheProc, const SkPaint& paint,
75 const uint16_t* glyphs, size_t len, SkScalar* x,
76 SkScalar* y, SkScalar* width) {
vandebo@chromium.org28be72b2010-11-11 21:37:00 +000077 if (paint.getTextAlign() == SkPaint::kLeft_Align && width == NULL)
78 return;
79
80 SkMatrix ident;
81 ident.reset();
82 SkAutoGlyphCache autoCache(paint, &ident);
83 SkGlyphCache* cache = autoCache.getCache();
84
85 const char* start = (char*)glyphs;
86 const char* stop = (char*)(glyphs + len);
87 SkFixed xAdv = 0, yAdv = 0;
88
89 // TODO(vandebo) This probably needs to take kerning into account.
90 while (start < stop) {
91 const SkGlyph& glyph = glyphCacheProc(cache, &start, 0, 0);
92 xAdv += glyph.fAdvanceX;
93 yAdv += glyph.fAdvanceY;
94 };
95 if (width)
96 *width = SkFixedToScalar(xAdv);
97 if (paint.getTextAlign() == SkPaint::kLeft_Align)
98 return;
99
100 SkScalar xAdj = SkFixedToScalar(xAdv);
101 SkScalar yAdj = SkFixedToScalar(yAdv);
102 if (paint.getTextAlign() == SkPaint::kCenter_Align) {
103 xAdj = SkScalarHalf(xAdj);
104 yAdj = SkScalarHalf(yAdj);
105 }
106 *x = *x - xAdj;
107 *y = *y - yAdj;
108}
109
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000110static void set_text_transform(SkScalar x, SkScalar y, SkScalar textSkewX,
111 SkWStream* content) {
112 // Flip the text about the x-axis to account for origin swap and include
113 // the passed parameters.
114 content->writeText("1 0 ");
115 SkPDFScalar::Append(0 - textSkewX, content);
116 content->writeText(" -1 ");
117 SkPDFScalar::Append(x, content);
118 content->writeText(" ");
119 SkPDFScalar::Append(y, content);
120 content->writeText(" Tm\n");
121}
122
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000123// It is important to not confuse GraphicStateEntry with SkPDFGraphicState, the
124// later being our representation of an object in the PDF file.
125struct GraphicStateEntry {
126 GraphicStateEntry();
127
128 // Compare the fields we care about when setting up a new content entry.
129 bool compareInitialState(const GraphicStateEntry& b);
130
131 SkMatrix fMatrix;
132 // We can't do set operations on Paths, though PDF natively supports
133 // intersect. If the clip stack does anything other than intersect,
134 // we have to fall back to the region. Treat fClipStack as authoritative.
135 // See http://code.google.com/p/skia/issues/detail?id=221
136 SkClipStack fClipStack;
137 SkRegion fClipRegion;
138
139 // When emitting the content entry, we will ensure the graphic state
140 // is set to these values first.
141 SkColor fColor;
142 SkScalar fTextScaleX; // Zero means we don't care what the value is.
143 SkPaint::Style fTextFill; // Only if TextScaleX is non-zero.
144 int fShaderIndex;
145 int fGraphicStateIndex;
146
147 // We may change the font (i.e. for Type1 support) within a
148 // ContentEntry. This is the one currently in effect, or NULL if none.
149 SkPDFFont* fFont;
150 // In PDF, text size has no default value. It is only valid if fFont is
151 // not NULL.
152 SkScalar fTextSize;
153};
154
155GraphicStateEntry::GraphicStateEntry() : fColor(SK_ColorBLACK),
156 fTextScaleX(SK_Scalar1),
157 fTextFill(SkPaint::kFill_Style),
158 fShaderIndex(-1),
159 fGraphicStateIndex(-1),
160 fFont(NULL),
161 fTextSize(SK_ScalarNaN) {
162 fMatrix.reset();
163}
164
165bool GraphicStateEntry::compareInitialState(const GraphicStateEntry& b) {
166 return fColor == b.fColor &&
167 fShaderIndex == b.fShaderIndex &&
168 fGraphicStateIndex == b.fGraphicStateIndex &&
169 fMatrix == b.fMatrix &&
170 fClipStack == b.fClipStack &&
171 (fTextScaleX == 0 ||
172 b.fTextScaleX == 0 ||
173 (fTextScaleX == b.fTextScaleX && fTextFill == b.fTextFill));
174}
175
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000176class GraphicStackState {
177public:
178 GraphicStackState(const SkClipStack& existingClipStack,
179 const SkRegion& existingClipRegion,
180 SkWStream* contentStream)
181 : fStackDepth(0),
182 fContentStream(contentStream) {
183 fEntries[0].fClipStack = existingClipStack;
184 fEntries[0].fClipRegion = existingClipRegion;
185 }
186
187 void updateClip(const SkClipStack& clipStack, const SkRegion& clipRegion,
188 const SkIPoint& translation);
189 void updateMatrix(const SkMatrix& matrix);
190 void updateDrawingState(const GraphicStateEntry& state);
191
192 void drainStack();
193
194private:
195 void push();
196 void pop();
197 GraphicStateEntry* currentEntry() { return &fEntries[fStackDepth]; }
198
199 // Conservative limit on save depth, see impl. notes in PDF 1.4 spec.
200 static const int kMaxStackDepth = 12;
201 GraphicStateEntry fEntries[kMaxStackDepth + 1];
202 int fStackDepth;
203 SkWStream* fContentStream;
204};
205
206void GraphicStackState::drainStack() {
207 while (fStackDepth) {
208 pop();
209 }
210}
211
212void GraphicStackState::push() {
213 SkASSERT(fStackDepth < kMaxStackDepth);
214 fContentStream->writeText("q\n");
215 fStackDepth++;
216 fEntries[fStackDepth] = fEntries[fStackDepth - 1];
217}
218
219void GraphicStackState::pop() {
220 SkASSERT(fStackDepth > 0);
221 fContentStream->writeText("Q\n");
222 fStackDepth--;
223}
224
225// This function initializes iter to be an interator on the "stack" argument
226// and then skips over the leading entries as specified in prefix. It requires
227// and asserts that "prefix" will be a prefix to "stack."
228static void skip_clip_stack_prefix(const SkClipStack& prefix,
229 const SkClipStack& stack,
230 SkClipStack::B2FIter* iter) {
231 SkClipStack::B2FIter prefixIter(prefix);
232 iter->reset(stack);
233
234 const SkClipStack::B2FIter::Clip* prefixEntry;
235 const SkClipStack::B2FIter::Clip* iterEntry;
236
vandebo@chromium.org8887ede2011-05-25 01:27:52 +0000237 int count = 0;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000238 for (prefixEntry = prefixIter.next(); prefixEntry;
vandebo@chromium.org8887ede2011-05-25 01:27:52 +0000239 prefixEntry = prefixIter.next(), count++) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000240 iterEntry = iter->next();
241 SkASSERT(iterEntry);
vandebo@chromium.org8887ede2011-05-25 01:27:52 +0000242 // Because of SkClipStack does internal intersection, the last clip
243 // entry may differ.
244 if(*prefixEntry != *iterEntry) {
245 SkASSERT(prefixEntry->fOp == SkRegion::kIntersect_Op);
246 SkASSERT(iterEntry->fOp == SkRegion::kIntersect_Op);
247 SkASSERT((iterEntry->fRect == NULL) ==
248 (prefixEntry->fRect == NULL));
249 SkASSERT((iterEntry->fPath == NULL) ==
250 (prefixEntry->fPath == NULL));
251 // We need to back up the iterator by one but don't have that
252 // function, so reset and go forward by one less.
253 iter->reset(stack);
254 for (int i = 0; i < count; i++) {
255 iter->next();
256 }
257 prefixEntry = prefixIter.next();
258 break;
259 }
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000260 }
261
262 SkASSERT(prefixEntry == NULL);
263}
264
265static void emit_clip(SkPath* clipPath, SkRect* clipRect,
266 SkWStream* contentStream) {
267 SkASSERT(clipPath || clipRect);
268
269 SkPath::FillType clipFill;
270 if (clipPath) {
271 SkPDFUtils::EmitPath(*clipPath, contentStream);
272 clipFill = clipPath->getFillType();
vandebo@chromium.org3e7b2802011-06-27 18:12:31 +0000273 } else {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000274 SkPDFUtils::AppendRectangle(*clipRect, contentStream);
275 clipFill = SkPath::kWinding_FillType;
276 }
277
278 NOT_IMPLEMENTED(clipFill == SkPath::kInverseEvenOdd_FillType, false);
279 NOT_IMPLEMENTED(clipFill == SkPath::kInverseWinding_FillType, false);
280 if (clipFill == SkPath::kEvenOdd_FillType) {
281 contentStream->writeText("W* n\n");
282 } else {
283 contentStream->writeText("W n\n");
284 }
285}
286
287// TODO(vandebo) Take advantage of SkClipStack::getSaveCount(), the PDF
288// graphic state stack, and the fact that we can know all the clips used
289// on the page to optimize this.
290void GraphicStackState::updateClip(const SkClipStack& clipStack,
291 const SkRegion& clipRegion,
292 const SkIPoint& translation) {
293 if (clipStack == currentEntry()->fClipStack) {
294 return;
295 }
296
297 while (fStackDepth > 0) {
298 pop();
299 if (clipStack == currentEntry()->fClipStack) {
300 return;
301 }
302 }
303 push();
304
305 // gsState->initialEntry()->fClipStack/Region specifies the clip that has
306 // already been applied. (If this is a top level device, then it specifies
307 // a clip to the content area. If this is a layer, then it specifies
308 // the clip in effect when the layer was created.) There's no need to
309 // reapply that clip; SKCanvas's SkDrawIter will draw anything outside the
310 // initial clip on the parent layer. (This means there's a bug if the user
311 // expands the clip and then uses any xfer mode that uses dst:
312 // http://code.google.com/p/skia/issues/detail?id=228 )
313 SkClipStack::B2FIter iter;
314 skip_clip_stack_prefix(fEntries[0].fClipStack, clipStack, &iter);
315
316 // If the clip stack does anything other than intersect or if it uses
317 // an inverse fill type, we have to fall back to the clip region.
318 bool needRegion = false;
319 const SkClipStack::B2FIter::Clip* clipEntry;
320 for (clipEntry = iter.next(); clipEntry; clipEntry = iter.next()) {
321 if (clipEntry->fOp != SkRegion::kIntersect_Op ||
322 (clipEntry->fPath && clipEntry->fPath->isInverseFillType())) {
323 needRegion = true;
324 break;
325 }
326 }
327
328 if (needRegion) {
329 SkPath clipPath;
330 SkAssertResult(clipRegion.getBoundaryPath(&clipPath));
331 emit_clip(&clipPath, NULL, fContentStream);
332 } else {
333 skip_clip_stack_prefix(fEntries[0].fClipStack, clipStack, &iter);
334 SkMatrix transform;
335 transform.setTranslate(translation.fX, translation.fY);
336 const SkClipStack::B2FIter::Clip* clipEntry;
337 for (clipEntry = iter.next(); clipEntry; clipEntry = iter.next()) {
338 SkASSERT(clipEntry->fOp == SkRegion::kIntersect_Op);
339 if (clipEntry->fRect) {
340 SkRect translatedClip;
341 transform.mapRect(&translatedClip, *clipEntry->fRect);
342 emit_clip(NULL, &translatedClip, fContentStream);
343 } else if (clipEntry->fPath) {
344 SkPath translatedPath;
345 clipEntry->fPath->transform(transform, &translatedPath);
346 emit_clip(&translatedPath, NULL, fContentStream);
347 } else {
348 SkASSERT(false);
349 }
350 }
351 }
352 currentEntry()->fClipStack = clipStack;
353 currentEntry()->fClipRegion = clipRegion;
354}
355
356void GraphicStackState::updateMatrix(const SkMatrix& matrix) {
357 if (matrix == currentEntry()->fMatrix) {
358 return;
359 }
360
361 if (currentEntry()->fMatrix.getType() != SkMatrix::kIdentity_Mask) {
362 SkASSERT(fStackDepth > 0);
363 SkASSERT(fEntries[fStackDepth].fClipStack ==
364 fEntries[fStackDepth -1].fClipStack);
365 pop();
366
367 SkASSERT(currentEntry()->fMatrix.getType() == SkMatrix::kIdentity_Mask);
368 }
369 if (matrix.getType() == SkMatrix::kIdentity_Mask) {
370 return;
371 }
372
373 push();
374 SkPDFUtils::AppendTransform(matrix, fContentStream);
375 currentEntry()->fMatrix = matrix;
376}
377
378void GraphicStackState::updateDrawingState(const GraphicStateEntry& state) {
379 // PDF treats a shader as a color, so we only set one or the other.
380 if (state.fShaderIndex >= 0) {
381 if (state.fShaderIndex != currentEntry()->fShaderIndex) {
382 fContentStream->writeText("/Pattern CS /Pattern cs /P");
383 fContentStream->writeDecAsText(state.fShaderIndex);
384 fContentStream->writeText(" SCN /P");
385 fContentStream->writeDecAsText(state.fShaderIndex);
386 fContentStream->writeText(" scn\n");
387 currentEntry()->fShaderIndex = state.fShaderIndex;
388 }
389 } else {
390 if (state.fColor != currentEntry()->fColor ||
391 currentEntry()->fShaderIndex >= 0) {
392 emit_pdf_color(state.fColor, fContentStream);
393 fContentStream->writeText("RG ");
394 emit_pdf_color(state.fColor, fContentStream);
395 fContentStream->writeText("rg\n");
396 currentEntry()->fColor = state.fColor;
397 currentEntry()->fShaderIndex = -1;
398 }
399 }
400
401 if (state.fGraphicStateIndex != currentEntry()->fGraphicStateIndex) {
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000402 SkPDFUtils::ApplyGraphicState(state.fGraphicStateIndex, fContentStream);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000403 currentEntry()->fGraphicStateIndex = state.fGraphicStateIndex;
404 }
405
406 if (state.fTextScaleX) {
407 if (state.fTextScaleX != currentEntry()->fTextScaleX) {
408 SkScalar pdfScale = SkScalarMul(state.fTextScaleX,
409 SkIntToScalar(100));
410 SkPDFScalar::Append(pdfScale, fContentStream);
411 fContentStream->writeText(" Tz\n");
412 currentEntry()->fTextScaleX = state.fTextScaleX;
413 }
414 if (state.fTextFill != currentEntry()->fTextFill) {
415 SK_COMPILE_ASSERT(SkPaint::kFill_Style == 0, enum_must_match_value);
416 SK_COMPILE_ASSERT(SkPaint::kStroke_Style == 1,
417 enum_must_match_value);
418 SK_COMPILE_ASSERT(SkPaint::kStrokeAndFill_Style == 2,
419 enum_must_match_value);
420 fContentStream->writeDecAsText(state.fTextFill);
421 fContentStream->writeText(" Tr\n");
422 currentEntry()->fTextFill = state.fTextFill;
423 }
424 }
425}
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000426
bsalomon@google.come97f0852011-06-17 13:10:25 +0000427SkDevice* SkPDFDevice::onCreateCompatibleDevice(SkBitmap::Config config,
428 int width, int height,
429 bool isOpaque,
430 Usage usage) {
431 SkMatrix initialTransform;
432 initialTransform.reset();
433 SkISize size = SkISize::Make(width, height);
434 return SkNEW_ARGS(SkPDFDevice, (size, size, initialTransform));
435}
436
437
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000438struct ContentEntry {
439 GraphicStateEntry fState;
440 SkDynamicMemoryWStream fContent;
441 SkTScopedPtr<ContentEntry> fNext;
442};
443
444// A helper class to automatically finish a ContentEntry at the end of a
445// drawing method and maintain the state needed between set up and finish.
vandebo@chromium.org13d14a92011-05-24 23:12:41 +0000446class ScopedContentEntry {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000447public:
vandebo@chromium.org13d14a92011-05-24 23:12:41 +0000448 ScopedContentEntry(SkPDFDevice* device, const SkDraw& draw,
449 const SkPaint& paint, bool hasText = false)
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000450 : fDevice(device),
451 fContentEntry(NULL),
452 fXfermode(SkXfermode::kSrcOver_Mode) {
453 init(draw.fClipStack, *draw.fClip, *draw.fMatrix, paint, hasText);
454 }
vandebo@chromium.org13d14a92011-05-24 23:12:41 +0000455 ScopedContentEntry(SkPDFDevice* device, const SkClipStack* clipStack,
456 const SkRegion& clipRegion, const SkMatrix& matrix,
457 const SkPaint& paint, bool hasText = false)
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000458 : fDevice(device),
459 fContentEntry(NULL),
460 fXfermode(SkXfermode::kSrcOver_Mode) {
461 init(clipStack, clipRegion, matrix, paint, hasText);
462 }
463
vandebo@chromium.org13d14a92011-05-24 23:12:41 +0000464 ~ScopedContentEntry() {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000465 if (fContentEntry) {
466 fDevice->finishContentEntry(fXfermode, fDstFormXObject.get());
467 }
468 }
469
470 ContentEntry* entry() { return fContentEntry; }
471private:
472 SkPDFDevice* fDevice;
473 ContentEntry* fContentEntry;
474 SkXfermode::Mode fXfermode;
475 SkRefPtr<SkPDFFormXObject> fDstFormXObject;
476
477 void init(const SkClipStack* clipStack, const SkRegion& clipRegion,
478 const SkMatrix& matrix, const SkPaint& paint, bool hasText) {
479 if (paint.getXfermode()) {
480 paint.getXfermode()->asMode(&fXfermode);
481 }
482 fContentEntry = fDevice->setUpContentEntry(clipStack, clipRegion,
483 matrix, paint, hasText,
484 &fDstFormXObject);
485 }
486};
487
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000488////////////////////////////////////////////////////////////////////////////////
489
vandebo@chromium.orga0c7edb2011-05-09 07:58:08 +0000490SkDevice* SkPDFDeviceFactory::newDevice(SkCanvas* c, SkBitmap::Config config,
reed@android.comf2b98d62010-12-20 18:26:13 +0000491 int width, int height, bool isOpaque,
vandebo@chromium.orgf60a0012011-02-24 23:14:04 +0000492 bool isForLayer) {
vandebo@chromium.org75f97e42011-04-11 23:24:18 +0000493 SkMatrix initialTransform;
494 initialTransform.reset();
ctguil@chromium.org15261292011-04-29 17:54:16 +0000495 SkISize size = SkISize::Make(width, height);
vandebo@chromium.orga0c7edb2011-05-09 07:58:08 +0000496 if (isForLayer) {
497 return SkNEW_ARGS(SkPDFDevice, (size, c->getTotalClipStack(),
498 c->getTotalClip()));
499 } else {
500 return SkNEW_ARGS(SkPDFDevice, (size, size, initialTransform));
501 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000502}
503
ctguil@chromium.org15261292011-04-29 17:54:16 +0000504static inline SkBitmap makeContentBitmap(const SkISize& contentSize,
vandebo@chromium.orga0c7edb2011-05-09 07:58:08 +0000505 const SkMatrix* initialTransform) {
reed@android.comf2b98d62010-12-20 18:26:13 +0000506 SkBitmap bitmap;
vandebo@chromium.orga0c7edb2011-05-09 07:58:08 +0000507 if (initialTransform) {
508 // Compute the size of the drawing area.
509 SkVector drawingSize;
510 SkMatrix inverse;
511 drawingSize.set(contentSize.fWidth, contentSize.fHeight);
512 initialTransform->invert(&inverse);
513 inverse.mapVectors(&drawingSize, 1);
514 SkISize size = SkSize::Make(drawingSize.fX, drawingSize.fY).toRound();
515 bitmap.setConfig(SkBitmap::kNo_Config, abs(size.fWidth),
516 abs(size.fHeight));
517 } else {
518 bitmap.setConfig(SkBitmap::kNo_Config, abs(contentSize.fWidth),
519 abs(contentSize.fHeight));
520 }
521
reed@android.comf2b98d62010-12-20 18:26:13 +0000522 return bitmap;
523}
524
ctguil@chromium.org15261292011-04-29 17:54:16 +0000525SkPDFDevice::SkPDFDevice(const SkISize& pageSize, const SkISize& contentSize,
vandebo@chromium.org75f97e42011-04-11 23:24:18 +0000526 const SkMatrix& initialTransform)
reed@google.comaf951c92011-06-16 19:10:39 +0000527 : SkDevice(makeContentBitmap(contentSize, &initialTransform)),
ctguil@chromium.org15261292011-04-29 17:54:16 +0000528 fPageSize(pageSize),
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000529 fContentSize(contentSize),
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000530 fLastContentEntry(NULL) {
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000531 // Skia generally uses the top left as the origin but PDF natively has the
vandebo@chromium.orga0c7edb2011-05-09 07:58:08 +0000532 // origin at the bottom left. This matrix corrects for that. But that only
533 // needs to be done once, we don't do it when layering.
ctguil@chromium.org15261292011-04-29 17:54:16 +0000534 fInitialTransform.setTranslate(0, pageSize.fHeight);
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000535 fInitialTransform.preScale(1, -1);
536 fInitialTransform.preConcat(initialTransform);
537
vandebo@chromium.orga0c7edb2011-05-09 07:58:08 +0000538 SkIRect existingClip = SkIRect::MakeWH(this->width(), this->height());
539 fExistingClipStack.clipDevRect(existingClip);
540 fExistingClipRegion.setRect(existingClip);
541
542 this->init();
543}
544
545SkPDFDevice::SkPDFDevice(const SkISize& layerSize,
546 const SkClipStack& existingClipStack,
547 const SkRegion& existingClipRegion)
reed@google.comaf951c92011-06-16 19:10:39 +0000548 : SkDevice(makeContentBitmap(layerSize, NULL)),
vandebo@chromium.orga0c7edb2011-05-09 07:58:08 +0000549 fPageSize(layerSize),
550 fContentSize(layerSize),
551 fExistingClipStack(existingClipStack),
552 fExistingClipRegion(existingClipRegion),
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000553 fLastContentEntry(NULL) {
vandebo@chromium.orga0c7edb2011-05-09 07:58:08 +0000554 fInitialTransform.reset();
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000555 this->init();
556}
557
558SkPDFDevice::~SkPDFDevice() {
559 this->cleanUp();
560}
561
562void SkPDFDevice::init() {
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000563 fResourceDict = NULL;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000564 fContentEntries.reset();
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000565 fLastContentEntry = NULL;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000566}
567
mike@reedtribe.orgea4ac972011-04-26 11:48:33 +0000568SkDeviceFactory* SkPDFDevice::onNewDeviceFactory() {
569 return SkNEW(SkPDFDeviceFactory);
570}
571
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000572void SkPDFDevice::cleanUp() {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000573 fGraphicStateResources.unrefAll();
574 fXObjectResources.unrefAll();
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000575 fFontResources.unrefAll();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000576 fShaderResources.unrefAll();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000577}
578
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000579void SkPDFDevice::clear(SkColor color) {
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000580 this->cleanUp();
581 this->init();
582
583 SkPaint paint;
584 paint.setColor(color);
585 paint.setStyle(SkPaint::kFill_Style);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000586 SkMatrix identity;
587 identity.reset();
vandebo@chromium.org13d14a92011-05-24 23:12:41 +0000588 ScopedContentEntry content(this, &fExistingClipStack, fExistingClipRegion,
589 identity, paint);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000590 internalDrawPaint(paint, content.entry());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000591}
592
593void SkPDFDevice::drawPaint(const SkDraw& d, const SkPaint& paint) {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000594 SkPaint newPaint = paint;
595 newPaint.setStyle(SkPaint::kFill_Style);
vandebo@chromium.org13d14a92011-05-24 23:12:41 +0000596 ScopedContentEntry content(this, d, newPaint);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000597 internalDrawPaint(newPaint, content.entry());
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000598}
599
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000600void SkPDFDevice::internalDrawPaint(const SkPaint& paint,
601 ContentEntry* contentEntry) {
602 if (!contentEntry) {
603 return;
604 }
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000605 SkRect bbox = SkRect::MakeWH(SkIntToScalar(this->width()),
606 SkIntToScalar(this->height()));
607 SkMatrix totalTransform = fInitialTransform;
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000608 totalTransform.preConcat(contentEntry->fState.fMatrix);
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000609 SkMatrix inverse;
610 inverse.reset();
611 totalTransform.invert(&inverse);
612 inverse.mapRect(&bbox);
613
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000614 SkPDFUtils::AppendRectangle(bbox, &contentEntry->fContent);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000615 SkPDFUtils::PaintPath(paint.getStyle(), SkPath::kWinding_FillType,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000616 &contentEntry->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000617}
618
619void SkPDFDevice::drawPoints(const SkDraw& d, SkCanvas::PointMode mode,
620 size_t count, const SkPoint* points,
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000621 const SkPaint& passedPaint) {
622 if (count == 0) {
623 return;
624 }
625
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000626 // SkDraw::drawPoints converts to multiple calls to fDevice->drawPath.
627 // We only use this when there's a path effect because of the overhead
628 // of multiple calls to setUpContentEntry it causes.
629 if (passedPaint.getPathEffect()) {
630 if (d.fClip->isEmpty()) {
631 return;
632 }
633 SkDraw pointDraw(d);
634 pointDraw.fDevice = this;
635 pointDraw.drawPoints(mode, count, points, passedPaint, true);
636 return;
637 }
638
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000639 const SkPaint* paint = &passedPaint;
640 SkPaint modifiedPaint;
641
642 if (mode == SkCanvas::kPoints_PointMode &&
643 paint->getStrokeCap() != SkPaint::kRound_Cap) {
644 modifiedPaint = *paint;
645 paint = &modifiedPaint;
646 if (paint->getStrokeWidth()) {
647 // PDF won't draw a single point with square/butt caps because the
648 // orientation is ambiguous. Draw a rectangle instead.
649 modifiedPaint.setStyle(SkPaint::kFill_Style);
650 SkScalar strokeWidth = paint->getStrokeWidth();
651 SkScalar halfStroke = SkScalarHalf(strokeWidth);
652 for (size_t i = 0; i < count; i++) {
653 SkRect r = SkRect::MakeXYWH(points[i].fX, points[i].fY, 0, 0);
654 r.inset(-halfStroke, -halfStroke);
655 drawRect(d, r, modifiedPaint);
656 }
657 return;
658 } else {
659 modifiedPaint.setStrokeCap(SkPaint::kRound_Cap);
660 }
661 }
662
vandebo@chromium.org13d14a92011-05-24 23:12:41 +0000663 ScopedContentEntry content(this, d, *paint);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000664 if (!content.entry()) {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000665 return;
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000666 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000667
668 switch (mode) {
669 case SkCanvas::kPolygon_PointMode:
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000670 SkPDFUtils::MoveTo(points[0].fX, points[0].fY,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000671 &content.entry()->fContent);
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +0000672 for (size_t i = 1; i < count; i++) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000673 SkPDFUtils::AppendLine(points[i].fX, points[i].fY,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000674 &content.entry()->fContent);
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +0000675 }
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000676 SkPDFUtils::StrokePath(&content.entry()->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000677 break;
678 case SkCanvas::kLines_PointMode:
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000679 for (size_t i = 0; i < count/2; i++) {
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +0000680 SkPDFUtils::MoveTo(points[i * 2].fX, points[i * 2].fY,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000681 &content.entry()->fContent);
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +0000682 SkPDFUtils::AppendLine(points[i * 2 + 1].fX,
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000683 points[i * 2 + 1].fY,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000684 &content.entry()->fContent);
685 SkPDFUtils::StrokePath(&content.entry()->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000686 }
687 break;
688 case SkCanvas::kPoints_PointMode:
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000689 SkASSERT(paint->getStrokeCap() == SkPaint::kRound_Cap);
690 for (size_t i = 0; i < count; i++) {
691 SkPDFUtils::MoveTo(points[i].fX, points[i].fY,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000692 &content.entry()->fContent);
693 SkPDFUtils::ClosePath(&content.entry()->fContent);
694 SkPDFUtils::StrokePath(&content.entry()->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000695 }
696 break;
697 default:
698 SkASSERT(false);
699 }
700}
701
702void SkPDFDevice::drawRect(const SkDraw& d, const SkRect& r,
703 const SkPaint& paint) {
704 if (paint.getPathEffect()) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000705 if (d.fClip->isEmpty()) {
706 return;
707 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000708 SkPath path;
709 path.addRect(r);
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000710 drawPath(d, path, paint, NULL, true);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000711 return;
712 }
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000713
vandebo@chromium.org13d14a92011-05-24 23:12:41 +0000714 ScopedContentEntry content(this, d, paint);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000715 if (!content.entry()) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000716 return;
717 }
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000718 SkPDFUtils::AppendRectangle(r, &content.entry()->fContent);
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +0000719 SkPDFUtils::PaintPath(paint.getStyle(), SkPath::kWinding_FillType,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000720 &content.entry()->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000721}
722
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000723void SkPDFDevice::drawPath(const SkDraw& d, const SkPath& origPath,
vandebo@chromium.org02cc5aa2011-01-25 22:06:29 +0000724 const SkPaint& paint, const SkMatrix* prePathMatrix,
725 bool pathIsMutable) {
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000726 SkPath modifiedPath;
727 SkPath* pathPtr = const_cast<SkPath*>(&origPath);
728
729 SkMatrix matrix = *d.fMatrix;
730 if (prePathMatrix) {
731 if (paint.getPathEffect() || paint.getStyle() != SkPaint::kFill_Style) {
732 if (!pathIsMutable) {
733 pathPtr = &modifiedPath;
734 pathIsMutable = true;
735 }
736 origPath.transform(*prePathMatrix, pathPtr);
737 } else {
738 if (!matrix.preConcat(*prePathMatrix)) {
739 return;
740 }
741 }
742 }
vandebo@chromium.org02cc5aa2011-01-25 22:06:29 +0000743
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000744 if (paint.getPathEffect()) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000745 if (d.fClip->isEmpty()) {
746 return;
747 }
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000748 if (!pathIsMutable) {
749 pathPtr = &modifiedPath;
750 pathIsMutable = true;
751 }
752 bool fill = paint.getFillPath(origPath, pathPtr);
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000753
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000754 SkPaint noEffectPaint(paint);
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000755 noEffectPaint.setPathEffect(NULL);
756 if (fill) {
757 noEffectPaint.setStyle(SkPaint::kFill_Style);
758 } else {
759 noEffectPaint.setStyle(SkPaint::kStroke_Style);
760 noEffectPaint.setStrokeWidth(0);
761 }
762 drawPath(d, *pathPtr, noEffectPaint, NULL, true);
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000763 return;
764 }
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000765
vandebo@chromium.org13d14a92011-05-24 23:12:41 +0000766 ScopedContentEntry content(this, d, paint);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000767 if (!content.entry()) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000768 return;
769 }
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000770 SkPDFUtils::EmitPath(*pathPtr, &content.entry()->fContent);
vandebo@chromium.orgff390322011-05-17 18:58:44 +0000771 SkPDFUtils::PaintPath(paint.getStyle(), pathPtr->getFillType(),
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000772 &content.entry()->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000773}
774
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000775void SkPDFDevice::drawBitmap(const SkDraw& d, const SkBitmap& bitmap,
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000776 const SkIRect* srcRect, const SkMatrix& matrix,
777 const SkPaint& paint) {
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000778 if (d.fClip->isEmpty()) {
779 return;
780 }
781
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000782 SkMatrix transform = matrix;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000783 transform.postConcat(*d.fMatrix);
vandebo@chromium.org78dad542011-05-11 18:46:03 +0000784 internalDrawBitmap(transform, d.fClipStack, *d.fClip, bitmap, srcRect,
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000785 paint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000786}
787
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000788void SkPDFDevice::drawSprite(const SkDraw& d, const SkBitmap& bitmap,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000789 int x, int y, const SkPaint& paint) {
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000790 if (d.fClip->isEmpty()) {
791 return;
792 }
793
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000794 SkMatrix matrix;
reed@google.coma6d59f62011-03-07 21:29:21 +0000795 matrix.setTranslate(SkIntToScalar(x), SkIntToScalar(y));
vandebo@chromium.org78dad542011-05-11 18:46:03 +0000796 internalDrawBitmap(matrix, d.fClipStack, *d.fClip, bitmap, NULL, paint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000797}
798
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000799void SkPDFDevice::drawText(const SkDraw& d, const void* text, size_t len,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000800 SkScalar x, SkScalar y, const SkPaint& paint) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000801 SkPaint textPaint = calculate_text_paint(paint);
vandebo@chromium.org13d14a92011-05-24 23:12:41 +0000802 ScopedContentEntry content(this, d, textPaint, true);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000803 if (!content.entry()) {
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000804 return;
805 }
806
vandebo@chromium.org01294102011-02-28 19:52:18 +0000807 // We want the text in glyph id encoding and a writable buffer, so we end
808 // up making a copy either way.
809 size_t numGlyphs = paint.textToGlyphs(text, len, NULL);
810 uint16_t* glyphIDs =
811 (uint16_t*)sk_malloc_flags(numGlyphs * 2,
812 SK_MALLOC_TEMP | SK_MALLOC_THROW);
813 SkAutoFree autoFreeGlyphIDs(glyphIDs);
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000814 if (paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding) {
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000815 paint.textToGlyphs(text, len, glyphIDs);
816 textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
817 } else {
818 SkASSERT((len & 1) == 0);
vandebo@chromium.org01294102011-02-28 19:52:18 +0000819 SkASSERT(len / 2 == numGlyphs);
820 memcpy(glyphIDs, text, len);
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000821 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000822
823 SkScalar width;
824 SkScalar* widthPtr = NULL;
825 if (textPaint.isUnderlineText() || textPaint.isStrikeThruText())
826 widthPtr = &width;
827
828 SkDrawCacheProc glyphCacheProc = textPaint.getDrawCacheProc();
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000829 align_text(glyphCacheProc, textPaint, glyphIDs, numGlyphs, &x, &y,
830 widthPtr);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000831 content.entry()->fContent.writeText("BT\n");
832 set_text_transform(x, y, textPaint.getTextSkewX(),
833 &content.entry()->fContent);
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000834 size_t consumedGlyphCount = 0;
835 while (numGlyphs > consumedGlyphCount) {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000836 updateFont(textPaint, glyphIDs[consumedGlyphCount], content.entry());
837 SkPDFFont* font = content.entry()->fState.fFont;
vandebo@chromium.org01294102011-02-28 19:52:18 +0000838 size_t availableGlyphs =
839 font->glyphsToPDFFontEncoding(glyphIDs + consumedGlyphCount,
840 numGlyphs - consumedGlyphCount);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000841 SkString encodedString =
842 SkPDFString::formatString(glyphIDs + consumedGlyphCount,
843 availableGlyphs, font->multiByteGlyphs());
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000844 content.entry()->fContent.writeText(encodedString.c_str());
vandebo@chromium.org01294102011-02-28 19:52:18 +0000845 consumedGlyphCount += availableGlyphs;
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000846 content.entry()->fContent.writeText(" Tj\n");
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000847 }
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000848 content.entry()->fContent.writeText("ET\n");
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000849
850 // Draw underline and/or strikethrough if the paint has them.
851 // drawPosText() and drawTextOnPath() don't draw underline or strikethrough
852 // because the raster versions don't. Use paint instead of textPaint
853 // because we may have changed strokeWidth to do fakeBold text.
854 if (paint.isUnderlineText() || paint.isStrikeThruText()) {
855 SkScalar textSize = paint.getTextSize();
856 SkScalar height = SkScalarMul(textSize, kStdUnderline_Thickness);
857
858 if (paint.isUnderlineText()) {
859 SkScalar top = SkScalarMulAdd(textSize, kStdUnderline_Offset, y);
860 SkRect r = SkRect::MakeXYWH(x, top - height, width, height);
861 drawRect(d, r, paint);
862 }
863 if (paint.isStrikeThruText()) {
864 SkScalar top = SkScalarMulAdd(textSize, kStdStrikeThru_Offset, y);
865 SkRect r = SkRect::MakeXYWH(x, top - height, width, height);
866 drawRect(d, r, paint);
867 }
868 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000869}
870
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000871void SkPDFDevice::drawPosText(const SkDraw& d, const void* text, size_t len,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000872 const SkScalar pos[], SkScalar constY,
873 int scalarsPerPos, const SkPaint& paint) {
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000874 SkASSERT(1 == scalarsPerPos || 2 == scalarsPerPos);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000875 SkPaint textPaint = calculate_text_paint(paint);
vandebo@chromium.org13d14a92011-05-24 23:12:41 +0000876 ScopedContentEntry content(this, d, textPaint, true);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000877 if (!content.entry()) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000878 return;
879 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000880
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000881 // Make sure we have a glyph id encoding.
882 SkAutoFree glyphStorage;
883 uint16_t* glyphIDs;
884 size_t numGlyphs;
885 if (paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding) {
886 numGlyphs = paint.textToGlyphs(text, len, NULL);
887 glyphIDs = (uint16_t*)sk_malloc_flags(numGlyphs * 2,
888 SK_MALLOC_TEMP | SK_MALLOC_THROW);
889 glyphStorage.set(glyphIDs);
890 paint.textToGlyphs(text, len, glyphIDs);
891 textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
892 } else {
893 SkASSERT((len & 1) == 0);
894 numGlyphs = len / 2;
895 glyphIDs = (uint16_t*)text;
896 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000897
898 SkDrawCacheProc glyphCacheProc = textPaint.getDrawCacheProc();
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000899 content.entry()->fContent.writeText("BT\n");
900 updateFont(textPaint, glyphIDs[0], content.entry());
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000901 for (size_t i = 0; i < numGlyphs; i++) {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000902 SkPDFFont* font = content.entry()->fState.fFont;
vandebo@chromium.org01294102011-02-28 19:52:18 +0000903 uint16_t encodedValue = glyphIDs[i];
904 if (font->glyphsToPDFFontEncoding(&encodedValue, 1) != 1) {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000905 updateFont(textPaint, glyphIDs[i], content.entry());
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000906 i--;
907 continue;
908 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000909 SkScalar x = pos[i * scalarsPerPos];
910 SkScalar y = scalarsPerPos == 1 ? constY : pos[i * scalarsPerPos + 1];
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000911 align_text(glyphCacheProc, textPaint, glyphIDs + i, 1, &x, &y, NULL);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000912 set_text_transform(x, y, textPaint.getTextSkewX(),
913 &content.entry()->fContent);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000914 SkString encodedString =
915 SkPDFString::formatString(&encodedValue, 1,
916 font->multiByteGlyphs());
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000917 content.entry()->fContent.writeText(encodedString.c_str());
918 content.entry()->fContent.writeText(" Tj\n");
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000919 }
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000920 content.entry()->fContent.writeText("ET\n");
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000921}
922
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000923void SkPDFDevice::drawTextOnPath(const SkDraw& d, const void* text, size_t len,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000924 const SkPath& path, const SkMatrix* matrix,
925 const SkPaint& paint) {
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000926 if (d.fClip->isEmpty()) {
927 return;
928 }
vandebo@chromium.org0f1c95c2011-06-24 23:13:47 +0000929 NOT_IMPLEMENTED("drawTextOnPath", false);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000930}
931
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000932void SkPDFDevice::drawVertices(const SkDraw& d, SkCanvas::VertexMode,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000933 int vertexCount, const SkPoint verts[],
934 const SkPoint texs[], const SkColor colors[],
935 SkXfermode* xmode, const uint16_t indices[],
936 int indexCount, const SkPaint& paint) {
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000937 if (d.fClip->isEmpty()) {
938 return;
939 }
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000940 NOT_IMPLEMENTED("drawVerticies", true);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000941}
942
vandebo@chromium.orgeb6c7592010-10-26 19:54:45 +0000943void SkPDFDevice::drawDevice(const SkDraw& d, SkDevice* device, int x, int y,
944 const SkPaint& paint) {
945 if ((device->getDeviceCapabilities() & kVector_Capability) == 0) {
946 // If we somehow get a raster device, do what our parent would do.
947 SkDevice::drawDevice(d, device, x, y, paint);
948 return;
949 }
vandebo@chromium.orgeb6c7592010-10-26 19:54:45 +0000950
vandebo@chromium.orgee7a9562011-05-24 17:38:01 +0000951 // Assume that a vector capable device means that it's a PDF Device.
952 SkPDFDevice* pdfDevice = static_cast<SkPDFDevice*>(device);
ctguil@chromium.orgf4ff39c2011-05-24 19:55:05 +0000953 if (pdfDevice->isContentEmpty()) {
vandebo@chromium.orgee7a9562011-05-24 17:38:01 +0000954 return;
955 }
956
vandebo@chromium.org1aef2ed2011-02-03 21:46:10 +0000957 SkMatrix matrix;
reed@google.coma6d59f62011-03-07 21:29:21 +0000958 matrix.setTranslate(SkIntToScalar(x), SkIntToScalar(y));
vandebo@chromium.org13d14a92011-05-24 23:12:41 +0000959 ScopedContentEntry content(this, d.fClipStack, *d.fClip, matrix, paint);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000960 if (!content.entry()) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +0000961 return;
962 }
vandebo@chromium.org1aef2ed2011-02-03 21:46:10 +0000963
964 SkPDFFormXObject* xobject = new SkPDFFormXObject(pdfDevice);
vandebo@chromium.orgeb6c7592010-10-26 19:54:45 +0000965 fXObjectResources.push(xobject); // Transfer reference.
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000966 SkPDFUtils::DrawFormXObject(fXObjectResources.count() - 1,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +0000967 &content.entry()->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000968}
969
970const SkRefPtr<SkPDFDict>& SkPDFDevice::getResourceDict() {
971 if (fResourceDict.get() == NULL) {
972 fResourceDict = new SkPDFDict;
973 fResourceDict->unref(); // SkRefPtr and new both took a reference.
974
975 if (fGraphicStateResources.count()) {
976 SkRefPtr<SkPDFDict> extGState = new SkPDFDict();
977 extGState->unref(); // SkRefPtr and new both took a reference.
978 for (int i = 0; i < fGraphicStateResources.count(); i++) {
979 SkString nameString("G");
980 nameString.appendS32(i);
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000981 extGState->insert(
982 nameString.c_str(),
983 new SkPDFObjRef(fGraphicStateResources[i]))->unref();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000984 }
985 fResourceDict->insert("ExtGState", extGState.get());
986 }
987
988 if (fXObjectResources.count()) {
989 SkRefPtr<SkPDFDict> xObjects = new SkPDFDict();
990 xObjects->unref(); // SkRefPtr and new both took a reference.
991 for (int i = 0; i < fXObjectResources.count(); i++) {
992 SkString nameString("X");
993 nameString.appendS32(i);
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000994 xObjects->insert(
995 nameString.c_str(),
996 new SkPDFObjRef(fXObjectResources[i]))->unref();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000997 }
998 fResourceDict->insert("XObject", xObjects.get());
999 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00001000
1001 if (fFontResources.count()) {
1002 SkRefPtr<SkPDFDict> fonts = new SkPDFDict();
1003 fonts->unref(); // SkRefPtr and new both took a reference.
1004 for (int i = 0; i < fFontResources.count(); i++) {
1005 SkString nameString("F");
1006 nameString.appendS32(i);
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +00001007 fonts->insert(nameString.c_str(),
1008 new SkPDFObjRef(fFontResources[i]))->unref();
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00001009 }
1010 fResourceDict->insert("Font", fonts.get());
1011 }
1012
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001013 if (fShaderResources.count()) {
1014 SkRefPtr<SkPDFDict> patterns = new SkPDFDict();
1015 patterns->unref(); // SkRefPtr and new both took a reference.
1016 for (int i = 0; i < fShaderResources.count(); i++) {
1017 SkString nameString("P");
1018 nameString.appendS32(i);
1019 patterns->insert(nameString.c_str(),
1020 new SkPDFObjRef(fShaderResources[i]))->unref();
1021 }
1022 fResourceDict->insert("Pattern", patterns.get());
1023 }
1024
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00001025 // For compatibility, add all proc sets (only used for output to PS
1026 // devices).
1027 const char procs[][7] = {"PDF", "Text", "ImageB", "ImageC", "ImageI"};
1028 SkRefPtr<SkPDFArray> procSets = new SkPDFArray();
1029 procSets->unref(); // SkRefPtr and new both took a reference.
1030 procSets->reserve(SK_ARRAY_COUNT(procs));
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +00001031 for (size_t i = 0; i < SK_ARRAY_COUNT(procs); i++)
1032 procSets->append(new SkPDFName(procs[i]))->unref();
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00001033 fResourceDict->insert("ProcSet", procSets.get());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001034 }
1035 return fResourceDict;
1036}
1037
vandebo@chromium.orga5180862010-10-26 19:48:49 +00001038void SkPDFDevice::getResources(SkTDArray<SkPDFObject*>* resourceList) const {
1039 resourceList->setReserve(resourceList->count() +
1040 fGraphicStateResources.count() +
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00001041 fXObjectResources.count() +
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001042 fFontResources.count() +
1043 fShaderResources.count());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001044 for (int i = 0; i < fGraphicStateResources.count(); i++) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +00001045 resourceList->push(fGraphicStateResources[i]);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001046 fGraphicStateResources[i]->ref();
vandebo@chromium.orga5180862010-10-26 19:48:49 +00001047 fGraphicStateResources[i]->getResources(resourceList);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001048 }
1049 for (int i = 0; i < fXObjectResources.count(); i++) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +00001050 resourceList->push(fXObjectResources[i]);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001051 fXObjectResources[i]->ref();
vandebo@chromium.orga5180862010-10-26 19:48:49 +00001052 fXObjectResources[i]->getResources(resourceList);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001053 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00001054 for (int i = 0; i < fFontResources.count(); i++) {
1055 resourceList->push(fFontResources[i]);
1056 fFontResources[i]->ref();
1057 fFontResources[i]->getResources(resourceList);
1058 }
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001059 for (int i = 0; i < fShaderResources.count(); i++) {
1060 resourceList->push(fShaderResources[i]);
1061 fShaderResources[i]->ref();
1062 fShaderResources[i]->getResources(resourceList);
1063 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001064}
1065
vandebo@chromium.orgf0ec2662011-05-29 05:55:42 +00001066const SkTDArray<SkPDFFont*>& SkPDFDevice::getFontResources() const {
1067 return fFontResources;
1068}
1069
vandebo@chromium.orga5180862010-10-26 19:48:49 +00001070SkRefPtr<SkPDFArray> SkPDFDevice::getMediaBox() const {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001071 SkRefPtr<SkPDFInt> zero = new SkPDFInt(0);
1072 zero->unref(); // SkRefPtr and new both took a reference.
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +00001073
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001074 SkRefPtr<SkPDFArray> mediaBox = new SkPDFArray();
1075 mediaBox->unref(); // SkRefPtr and new both took a reference.
1076 mediaBox->reserve(4);
1077 mediaBox->append(zero.get());
1078 mediaBox->append(zero.get());
ctguil@chromium.org15261292011-04-29 17:54:16 +00001079 mediaBox->append(new SkPDFInt(fPageSize.fWidth))->unref();
1080 mediaBox->append(new SkPDFInt(fPageSize.fHeight))->unref();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001081 return mediaBox;
1082}
1083
vandebo@chromium.orgc2a9b7f2011-02-24 23:22:30 +00001084SkStream* SkPDFDevice::content() const {
reed@google.com5667afc2011-06-27 14:42:15 +00001085 SkMemoryStream* result = new SkMemoryStream;
1086 result->setData(this->copyContentToData())->unref();
1087 return result;
1088}
1089
1090SkData* SkPDFDevice::copyContentToData() const {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001091 SkDynamicMemoryWStream data;
1092 if (fInitialTransform.getType() != SkMatrix::kIdentity_Mask) {
1093 SkPDFUtils::AppendTransform(fInitialTransform, &data);
vandebo@chromium.orgc2a9b7f2011-02-24 23:22:30 +00001094 }
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001095 // If the content area is the entire page, then we don't need to clip
1096 // the content area (PDF area clips to the page size). Otherwise,
1097 // we have to clip to the content area; we've already applied the
1098 // initial transform, so just clip to the device size.
1099 if (fPageSize != fContentSize) {
1100 SkRect r = SkRect::MakeWH(this->width(), this->height());
1101 emit_clip(NULL, &r, &data);
1102 }
reed@google.com5667afc2011-06-27 14:42:15 +00001103
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001104 GraphicStackState gsState(fExistingClipStack, fExistingClipRegion, &data);
1105 for (ContentEntry* entry = fContentEntries.get();
reed@google.com5667afc2011-06-27 14:42:15 +00001106 entry != NULL;
1107 entry = entry->fNext.get()) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001108 SkIPoint translation = this->getOrigin();
1109 translation.negate();
1110 gsState.updateClip(entry->fState.fClipStack, entry->fState.fClipRegion,
1111 translation);
1112 gsState.updateMatrix(entry->fState.fMatrix);
1113 gsState.updateDrawingState(entry->fState);
reed@google.com8a85d0c2011-06-24 19:12:12 +00001114
1115 SkAutoDataUnref copy(entry->fContent.copyToData());
1116 data.write(copy.data(), copy.size());
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001117 }
1118 gsState.drainStack();
1119
reed@google.com5667afc2011-06-27 14:42:15 +00001120 // potentially we could cache this SkData, and only rebuild it if we
1121 // see that our state has changed.
1122 return data.copyToData();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001123}
1124
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001125void SkPDFDevice::createFormXObjectFromDevice(
1126 SkRefPtr<SkPDFFormXObject>* xobject) {
1127 *xobject = new SkPDFFormXObject(this);
1128 (*xobject)->unref(); // SkRefPtr and new both took a reference.
1129 cleanUp(); // Reset this device to have no content.
1130 init();
1131}
1132
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001133void SkPDFDevice::clearClipFromContent(const SkClipStack* clipStack,
1134 const SkRegion& clipRegion) {
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001135 if (clipRegion.isEmpty() || isContentEmpty()) {
1136 return;
1137 }
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001138 SkRefPtr<SkPDFFormXObject> curContent;
1139 createFormXObjectFromDevice(&curContent);
1140
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001141 // Redraw what we already had, but with the clip as a mask.
1142 drawFormXObjectWithClip(curContent.get(), clipStack, clipRegion, true);
1143}
1144
1145void SkPDFDevice::drawFormXObjectWithClip(SkPDFFormXObject* xobject,
1146 const SkClipStack* clipStack,
1147 const SkRegion& clipRegion,
1148 bool invertClip) {
1149 if (clipRegion.isEmpty() && !invertClip) {
1150 return;
1151 }
1152
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001153 // Create the mask.
1154 SkMatrix identity;
1155 identity.reset();
1156 SkDraw draw;
1157 draw.fMatrix = &identity;
1158 draw.fClip = &clipRegion;
1159 draw.fClipStack = clipStack;
1160 SkPaint stockPaint;
1161 this->drawPaint(draw, stockPaint);
1162 SkRefPtr<SkPDFFormXObject> maskFormXObject;
1163 createFormXObjectFromDevice(&maskFormXObject);
1164 SkRefPtr<SkPDFGraphicState> sMaskGS =
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001165 SkPDFGraphicState::getSMaskGraphicState(maskFormXObject.get(),
1166 invertClip);
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001167 sMaskGS->unref(); // SkRefPtr and getSMaskGraphicState both took a ref.
1168
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001169 // Draw the xobject with the clip as a mask.
vandebo@chromium.org13d14a92011-05-24 23:12:41 +00001170 ScopedContentEntry content(this, &fExistingClipStack, fExistingClipRegion,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001171 identity, stockPaint);
1172 if (!content.entry()) {
1173 return;
1174 }
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001175 SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()),
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001176 &content.entry()->fContent);
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001177 SkPDFUtils::DrawFormXObject(fXObjectResources.count(),
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001178 &content.entry()->fContent);
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001179 fXObjectResources.push(xobject);
1180 xobject->ref();
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001181
1182 sMaskGS = SkPDFGraphicState::getNoSMaskGraphicState();
1183 sMaskGS->unref(); // SkRefPtr and getSMaskGraphicState both took a ref.
1184 SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()),
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001185 &content.entry()->fContent);
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001186}
1187
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001188ContentEntry* SkPDFDevice::setUpContentEntry(const SkClipStack* clipStack,
1189 const SkRegion& clipRegion,
1190 const SkMatrix& matrix,
1191 const SkPaint& paint,
1192 bool hasText,
1193 SkRefPtr<SkPDFFormXObject>* dst) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001194 if (clipRegion.isEmpty()) {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001195 return NULL;
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001196 }
1197
vandebo@chromium.org78dad542011-05-11 18:46:03 +00001198 // The clip stack can come from an SkDraw where it is technically optional.
1199 SkClipStack synthesizedClipStack;
1200 if (clipStack == NULL) {
1201 if (clipRegion == fExistingClipRegion) {
1202 clipStack = &fExistingClipStack;
1203 } else {
1204 // GraphicStackState::updateClip expects the clip stack to have
1205 // fExistingClip as a prefix, so start there, then set the clip
1206 // to the passed region.
1207 synthesizedClipStack = fExistingClipStack;
1208 SkPath clipPath;
1209 clipRegion.getBoundaryPath(&clipPath);
1210 synthesizedClipStack.clipDevPath(clipPath, SkRegion::kReplace_Op);
1211 clipStack = &synthesizedClipStack;
1212 }
1213 }
1214
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001215 SkXfermode::Mode xfermode = SkXfermode::kSrcOver_Mode;
1216 if (paint.getXfermode()) {
1217 paint.getXfermode()->asMode(&xfermode);
1218 }
1219
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001220 if (xfermode == SkXfermode::kClear_Mode ||
1221 xfermode == SkXfermode::kSrc_Mode) {
vandebo@chromium.org466f3d62011-05-18 23:06:29 +00001222 this->clearClipFromContent(clipStack, clipRegion);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001223 } else if (xfermode == SkXfermode::kSrcIn_Mode ||
1224 xfermode == SkXfermode::kDstIn_Mode ||
1225 xfermode == SkXfermode::kSrcOut_Mode ||
1226 xfermode == SkXfermode::kDstOut_Mode) {
1227 // For the following modes, we use both source and destination, but
1228 // we use one as a smask for the other, so we have to make form xobjects
1229 // out of both of them: SrcIn, DstIn, SrcOut, DstOut.
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001230 if (isContentEmpty()) {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001231 return NULL;
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001232 } else {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001233 createFormXObjectFromDevice(dst);
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001234 }
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001235 }
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001236 // TODO(vandebo) Figure out how/if we can handle the following modes:
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001237 // SrcAtop, DestAtop, Xor, Plus.
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001238
1239 // These xfer modes don't draw source at all.
1240 if (xfermode == SkXfermode::kClear_Mode ||
1241 xfermode == SkXfermode::kDst_Mode) {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001242 return NULL;
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001243 }
1244
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001245 ContentEntry* entry;
1246 SkTScopedPtr<ContentEntry> newEntry;
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001247 if (fLastContentEntry && fLastContentEntry->fContent.getOffset() == 0) {
1248 entry = fLastContentEntry;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001249 } else {
1250 newEntry.reset(new ContentEntry);
1251 entry = newEntry.get();
1252 }
1253
vandebo@chromium.org78dad542011-05-11 18:46:03 +00001254 populateGraphicStateEntryFromPaint(matrix, *clipStack, clipRegion, paint,
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001255 hasText, &entry->fState);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001256 if (fLastContentEntry && xfermode != SkXfermode::kDstOver_Mode &&
1257 entry->fState.compareInitialState(fLastContentEntry->fState)) {
1258 return fLastContentEntry;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001259 }
1260
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001261 if (!fLastContentEntry) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001262 fContentEntries.reset(entry);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001263 fLastContentEntry = entry;
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001264 } else if (xfermode == SkXfermode::kDstOver_Mode) {
1265 entry->fNext.reset(fContentEntries.release());
1266 fContentEntries.reset(entry);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001267 } else {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001268 fLastContentEntry->fNext.reset(entry);
1269 fLastContentEntry = entry;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001270 }
1271 newEntry.release();
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001272 return entry;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001273}
1274
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001275void SkPDFDevice::finishContentEntry(const SkXfermode::Mode xfermode,
1276 SkPDFFormXObject* dst) {
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001277 if (xfermode != SkXfermode::kSrcIn_Mode &&
1278 xfermode != SkXfermode::kDstIn_Mode &&
1279 xfermode != SkXfermode::kSrcOut_Mode &&
1280 xfermode != SkXfermode::kDstOut_Mode) {
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001281 SkASSERT(!dst);
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001282 return;
1283 }
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001284 SkASSERT(dst);
1285 SkASSERT(!fContentEntries->fNext.get());
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001286
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001287 // We have to make a copy of these here because changing the current
1288 // content into a form xobject will destroy them.
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001289 SkClipStack clipStack = fContentEntries->fState.fClipStack;
1290 SkRegion clipRegion = fContentEntries->fState.fClipRegion;
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001291
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001292 SkRefPtr<SkPDFFormXObject> srcFormXObject;
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001293 if (!isContentEmpty()) {
1294 createFormXObjectFromDevice(&srcFormXObject);
1295 }
1296
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001297 drawFormXObjectWithClip(dst, &clipStack, clipRegion, true);
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001298
1299 // We've redrawn dst minus the clip area, if there's no src, we're done.
1300 if (!srcFormXObject.get()) {
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001301 return;
1302 }
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001303
1304 SkMatrix identity;
1305 identity.reset();
1306 SkPaint stockPaint;
vandebo@chromium.org13d14a92011-05-24 23:12:41 +00001307 ScopedContentEntry inClipContentEntry(this, &fExistingClipStack,
1308 fExistingClipRegion, identity,
1309 stockPaint);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001310 if (!inClipContentEntry.entry()) {
1311 return;
1312 }
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001313
1314 SkRefPtr<SkPDFGraphicState> sMaskGS;
1315 if (xfermode == SkXfermode::kSrcIn_Mode ||
1316 xfermode == SkXfermode::kSrcOut_Mode) {
1317 sMaskGS = SkPDFGraphicState::getSMaskGraphicState(
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001318 dst, xfermode == SkXfermode::kSrcOut_Mode);
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001319 fXObjectResources.push(srcFormXObject.get());
1320 srcFormXObject->ref();
1321 } else {
1322 sMaskGS = SkPDFGraphicState::getSMaskGraphicState(
1323 srcFormXObject.get(), xfermode == SkXfermode::kDstOut_Mode);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001324 // dst already added to fXObjectResources in drawFormXObjectWithClip.
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001325 }
1326 sMaskGS->unref(); // SkRefPtr and getSMaskGraphicState both took a ref.
1327 SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()),
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001328 &inClipContentEntry.entry()->fContent);
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001329
1330 SkPDFUtils::DrawFormXObject(fXObjectResources.count() - 1,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001331 &inClipContentEntry.entry()->fContent);
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001332
1333 sMaskGS = SkPDFGraphicState::getNoSMaskGraphicState();
1334 sMaskGS->unref(); // SkRefPtr and getSMaskGraphicState both took a ref.
1335 SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()),
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001336 &inClipContentEntry.entry()->fContent);
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001337}
1338
vandebo@chromium.org481aef62011-05-24 16:39:05 +00001339bool SkPDFDevice::isContentEmpty() {
1340 if (!fContentEntries.get() || fContentEntries->fContent.getOffset() == 0) {
1341 SkASSERT(!fContentEntries.get() || !fContentEntries->fNext.get());
1342 return true;
1343 }
1344 return false;
1345}
1346
1347
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001348void SkPDFDevice::populateGraphicStateEntryFromPaint(
1349 const SkMatrix& matrix,
1350 const SkClipStack& clipStack,
1351 const SkRegion& clipRegion,
1352 const SkPaint& paint,
1353 bool hasText,
1354 GraphicStateEntry* entry) {
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001355 SkASSERT(paint.getPathEffect() == NULL);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001356
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001357 NOT_IMPLEMENTED(paint.getMaskFilter() != NULL, false);
1358 NOT_IMPLEMENTED(paint.getColorFilter() != NULL, false);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001359
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001360 entry->fMatrix = matrix;
1361 entry->fClipStack = clipStack;
1362 entry->fClipRegion = clipRegion;
vandebo@chromium.org48543272011-02-08 19:28:07 +00001363
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001364 // PDF treats a shader as a color, so we only set one or the other.
1365 SkRefPtr<SkPDFShader> pdfShader;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001366 const SkShader* shader = paint.getShader();
1367 SkColor color = paint.getColor();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001368 if (shader) {
1369 // PDF positions patterns relative to the initial transform, so
1370 // we need to apply the current transform to the shader parameters.
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001371 SkMatrix transform = matrix;
vandebo@chromium.org75f97e42011-04-11 23:24:18 +00001372 transform.postConcat(fInitialTransform);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001373
1374 // PDF doesn't support kClamp_TileMode, so we simulate it by making
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001375 // a pattern the size of the current clip.
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001376 SkIRect bounds = clipRegion.getBounds();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001377 pdfShader = SkPDFShader::getPDFShader(*shader, transform, bounds);
1378 SkSafeUnref(pdfShader.get()); // getShader and SkRefPtr both took a ref
1379
1380 // A color shader is treated as an invalid shader so we don't have
1381 // to set a shader just for a color.
1382 if (pdfShader.get() == NULL) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001383 entry->fColor = 0;
1384 color = 0;
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001385
1386 // Check for a color shader.
1387 SkShader::GradientInfo gradientInfo;
1388 SkColor gradientColor;
1389 gradientInfo.fColors = &gradientColor;
1390 gradientInfo.fColorOffsets = NULL;
1391 gradientInfo.fColorCount = 1;
1392 if (shader->asAGradient(&gradientInfo) ==
1393 SkShader::kColor_GradientType) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001394 entry->fColor = SkColorSetA(gradientColor, 0xFF);
1395 color = gradientColor;
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001396 }
1397 }
1398 }
1399
1400 if (pdfShader) {
1401 // pdfShader has been canonicalized so we can directly compare
1402 // pointers.
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001403 int resourceIndex = fShaderResources.find(pdfShader.get());
1404 if (resourceIndex < 0) {
1405 resourceIndex = fShaderResources.count();
1406 fShaderResources.push(pdfShader.get());
1407 pdfShader->ref();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001408 }
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001409 entry->fShaderIndex = resourceIndex;
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001410 } else {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001411 entry->fShaderIndex = -1;
1412 entry->fColor = SkColorSetA(paint.getColor(), 0xFF);
1413 color = paint.getColor();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001414 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001415
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001416 SkRefPtr<SkPDFGraphicState> newGraphicState;
1417 if (color == paint.getColor()) {
1418 newGraphicState = SkPDFGraphicState::getGraphicStateForPaint(paint);
1419 } else {
1420 SkPaint newPaint = paint;
1421 newPaint.setColor(color);
1422 newGraphicState = SkPDFGraphicState::getGraphicStateForPaint(newPaint);
1423 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001424 newGraphicState->unref(); // getGraphicState and SkRefPtr both took a ref.
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001425 int resourceIndex = addGraphicStateResource(newGraphicState.get());
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001426 entry->fGraphicStateIndex = resourceIndex;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001427
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001428 if (hasText) {
1429 entry->fTextScaleX = paint.getTextScaleX();
1430 entry->fTextFill = paint.getStyle();
1431 } else {
1432 entry->fTextScaleX = 0;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001433 }
1434}
1435
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001436int SkPDFDevice::addGraphicStateResource(SkPDFGraphicState* gs) {
1437 // Assumes that gs has been canonicalized (so we can directly compare
1438 // pointers).
1439 int result = fGraphicStateResources.find(gs);
1440 if (result < 0) {
1441 result = fGraphicStateResources.count();
1442 fGraphicStateResources.push(gs);
1443 gs->ref();
1444 }
1445 return result;
1446}
1447
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001448void SkPDFDevice::updateFont(const SkPaint& paint, uint16_t glyphID,
1449 ContentEntry* contentEntry) {
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +00001450 SkTypeface* typeface = paint.getTypeface();
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001451 if (contentEntry->fState.fFont == NULL ||
1452 contentEntry->fState.fTextSize != paint.getTextSize() ||
1453 !contentEntry->fState.fFont->hasGlyph(glyphID)) {
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +00001454 int fontIndex = getFontResourceIndex(typeface, glyphID);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001455 contentEntry->fContent.writeText("/F");
1456 contentEntry->fContent.writeDecAsText(fontIndex);
1457 contentEntry->fContent.writeText(" ");
1458 SkPDFScalar::Append(paint.getTextSize(), &contentEntry->fContent);
1459 contentEntry->fContent.writeText(" Tf\n");
1460 contentEntry->fState.fFont = fFontResources[fontIndex];
vandebo@chromium.org2a22e102011-01-25 21:01:34 +00001461 }
1462}
1463
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +00001464int SkPDFDevice::getFontResourceIndex(SkTypeface* typeface, uint16_t glyphID) {
1465 SkRefPtr<SkPDFFont> newFont = SkPDFFont::getFontResource(typeface, glyphID);
vandebo@chromium.org2a22e102011-01-25 21:01:34 +00001466 newFont->unref(); // getFontResource and SkRefPtr both took a ref.
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00001467 int resourceIndex = fFontResources.find(newFont.get());
1468 if (resourceIndex < 0) {
1469 resourceIndex = fFontResources.count();
1470 fFontResources.push(newFont.get());
1471 newFont->ref();
1472 }
1473 return resourceIndex;
1474}
1475
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001476void SkPDFDevice::internalDrawBitmap(const SkMatrix& matrix,
vandebo@chromium.org78dad542011-05-11 18:46:03 +00001477 const SkClipStack* clipStack,
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001478 const SkRegion& clipRegion,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001479 const SkBitmap& bitmap,
vandebo@chromium.orgbefebb82011-01-29 01:38:50 +00001480 const SkIRect* srcRect,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001481 const SkPaint& paint) {
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +00001482 SkMatrix scaled;
1483 // Adjust for origin flip.
1484 scaled.setScale(1, -1);
1485 scaled.postTranslate(0, 1);
1486 // Scale the image up from 1x1 to WxH.
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001487 SkIRect subset = SkIRect::MakeWH(bitmap.width(), bitmap.height());
reed@google.coma6d59f62011-03-07 21:29:21 +00001488 scaled.postScale(SkIntToScalar(subset.width()),
1489 SkIntToScalar(subset.height()));
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +00001490 scaled.postConcat(matrix);
vandebo@chromium.org13d14a92011-05-24 23:12:41 +00001491 ScopedContentEntry content(this, clipStack, clipRegion, scaled, paint);
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001492 if (!content.entry()) {
vandebo@chromium.org25adce82011-05-09 08:05:01 +00001493 return;
1494 }
1495
1496 if (srcRect && !subset.intersect(*srcRect)) {
1497 return;
1498 }
1499
1500 SkPDFImage* image = SkPDFImage::CreateImage(bitmap, subset, paint);
1501 if (!image) {
1502 return;
1503 }
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +00001504
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001505 fXObjectResources.push(image); // Transfer reference.
vandebo@chromium.org6112c212011-05-13 03:50:38 +00001506 SkPDFUtils::DrawFormXObject(fXObjectResources.count() - 1,
vandebo@chromium.orgb069c8c2011-05-24 17:19:38 +00001507 &content.entry()->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001508}