blob: 7c10c7c35a4ead7729f819c33c63e78d269aa616 [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
330void GraphicStackState::updateMatrix(const SkMatrix& matrix) {
331 if (matrix == currentEntry()->fMatrix) {
332 return;
333 }
334
335 if (currentEntry()->fMatrix.getType() != SkMatrix::kIdentity_Mask) {
336 SkASSERT(fStackDepth > 0);
337 SkASSERT(fEntries[fStackDepth].fClipStack ==
338 fEntries[fStackDepth -1].fClipStack);
339 pop();
340
341 SkASSERT(currentEntry()->fMatrix.getType() == SkMatrix::kIdentity_Mask);
342 }
343 if (matrix.getType() == SkMatrix::kIdentity_Mask) {
344 return;
345 }
346
347 push();
348 SkPDFUtils::AppendTransform(matrix, fContentStream);
349 currentEntry()->fMatrix = matrix;
350}
351
352void GraphicStackState::updateDrawingState(const GraphicStateEntry& state) {
353 // PDF treats a shader as a color, so we only set one or the other.
354 if (state.fShaderIndex >= 0) {
355 if (state.fShaderIndex != currentEntry()->fShaderIndex) {
356 fContentStream->writeText("/Pattern CS /Pattern cs /P");
357 fContentStream->writeDecAsText(state.fShaderIndex);
358 fContentStream->writeText(" SCN /P");
359 fContentStream->writeDecAsText(state.fShaderIndex);
360 fContentStream->writeText(" scn\n");
361 currentEntry()->fShaderIndex = state.fShaderIndex;
362 }
363 } else {
364 if (state.fColor != currentEntry()->fColor ||
365 currentEntry()->fShaderIndex >= 0) {
366 emit_pdf_color(state.fColor, fContentStream);
367 fContentStream->writeText("RG ");
368 emit_pdf_color(state.fColor, fContentStream);
369 fContentStream->writeText("rg\n");
370 currentEntry()->fColor = state.fColor;
371 currentEntry()->fShaderIndex = -1;
372 }
373 }
374
375 if (state.fGraphicStateIndex != currentEntry()->fGraphicStateIndex) {
376 fContentStream->writeText("/G");
377 fContentStream->writeDecAsText(state.fGraphicStateIndex);
378 fContentStream->writeText(" gs\n");
379 currentEntry()->fGraphicStateIndex = state.fGraphicStateIndex;
380 }
381
382 if (state.fTextScaleX) {
383 if (state.fTextScaleX != currentEntry()->fTextScaleX) {
384 SkScalar pdfScale = SkScalarMul(state.fTextScaleX,
385 SkIntToScalar(100));
386 SkPDFScalar::Append(pdfScale, fContentStream);
387 fContentStream->writeText(" Tz\n");
388 currentEntry()->fTextScaleX = state.fTextScaleX;
389 }
390 if (state.fTextFill != currentEntry()->fTextFill) {
391 SK_COMPILE_ASSERT(SkPaint::kFill_Style == 0, enum_must_match_value);
392 SK_COMPILE_ASSERT(SkPaint::kStroke_Style == 1,
393 enum_must_match_value);
394 SK_COMPILE_ASSERT(SkPaint::kStrokeAndFill_Style == 2,
395 enum_must_match_value);
396 fContentStream->writeDecAsText(state.fTextFill);
397 fContentStream->writeText(" Tr\n");
398 currentEntry()->fTextFill = state.fTextFill;
399 }
400 }
401}
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000402
403////////////////////////////////////////////////////////////////////////////////
404
reed@android.comf2b98d62010-12-20 18:26:13 +0000405SkDevice* SkPDFDeviceFactory::newDevice(SkCanvas*, SkBitmap::Config config,
406 int width, int height, bool isOpaque,
vandebo@chromium.orgf60a0012011-02-24 23:14:04 +0000407 bool isForLayer) {
vandebo@chromium.org75f97e42011-04-11 23:24:18 +0000408 SkMatrix initialTransform;
409 initialTransform.reset();
vandebo@chromium.orgf60a0012011-02-24 23:14:04 +0000410 if (isForLayer) {
vandebo@chromium.org75f97e42011-04-11 23:24:18 +0000411 initialTransform.setTranslate(0, height);
412 initialTransform.preScale(1, -1);
vandebo@chromium.orgf60a0012011-02-24 23:14:04 +0000413 }
ctguil@chromium.org15261292011-04-29 17:54:16 +0000414 SkISize size = SkISize::Make(width, height);
415 return SkNEW_ARGS(SkPDFDevice, (size, size, initialTransform));
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000416}
417
ctguil@chromium.org15261292011-04-29 17:54:16 +0000418static inline SkBitmap makeContentBitmap(const SkISize& contentSize,
419 const SkMatrix& initialTransform) {
420 // Compute the size of the drawing area.
421 SkVector drawingSize;
422 SkMatrix inverse;
423 drawingSize.set(contentSize.fWidth, contentSize.fHeight);
424 initialTransform.invert(&inverse);
425 inverse.mapVectors(&drawingSize, 1);
426 SkISize size = SkSize::Make(drawingSize.fX, drawingSize.fY).toRound();
427
reed@android.comf2b98d62010-12-20 18:26:13 +0000428 SkBitmap bitmap;
vandebo@chromium.orgbe2048a2011-05-02 15:24:01 +0000429 bitmap.setConfig(SkBitmap::kNo_Config, abs(size.fWidth), abs(size.fHeight));
reed@android.comf2b98d62010-12-20 18:26:13 +0000430 return bitmap;
431}
432
ctguil@chromium.org15261292011-04-29 17:54:16 +0000433SkPDFDevice::SkPDFDevice(const SkISize& pageSize, const SkISize& contentSize,
vandebo@chromium.org75f97e42011-04-11 23:24:18 +0000434 const SkMatrix& initialTransform)
ctguil@chromium.org15261292011-04-29 17:54:16 +0000435 : SkDevice(NULL, makeContentBitmap(contentSize, initialTransform), false),
436 fPageSize(pageSize),
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000437 fContentSize(contentSize),
438 fCurrentContentEntry(NULL) {
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000439 // Skia generally uses the top left as the origin but PDF natively has the
440 // origin at the bottom left. This matrix corrects for that. When layering,
441 // we specify an inverse correction to cancel this out.
ctguil@chromium.org15261292011-04-29 17:54:16 +0000442 fInitialTransform.setTranslate(0, pageSize.fHeight);
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000443 fInitialTransform.preScale(1, -1);
444 fInitialTransform.preConcat(initialTransform);
445
446 this->init();
447}
448
449SkPDFDevice::~SkPDFDevice() {
450 this->cleanUp();
451}
452
453void SkPDFDevice::init() {
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000454 fResourceDict = NULL;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000455 fContentEntries.reset();
456 fCurrentContentEntry = NULL;
vandebo@chromium.orgf60a0012011-02-24 23:14:04 +0000457
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000458 SkIRect existingClip = SkIRect::MakeWH(this->width(), this->height());
459 fExistingClipStack.clipDevRect(existingClip);
460 fExistingClipRegion.setRect(existingClip);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000461}
462
mike@reedtribe.orgea4ac972011-04-26 11:48:33 +0000463SkDeviceFactory* SkPDFDevice::onNewDeviceFactory() {
464 return SkNEW(SkPDFDeviceFactory);
465}
466
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000467void SkPDFDevice::cleanUp() {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000468 fGraphicStateResources.unrefAll();
469 fXObjectResources.unrefAll();
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000470 fFontResources.unrefAll();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000471 fShaderResources.unrefAll();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000472}
473
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000474void SkPDFDevice::setExistingClip(const SkClipStack& clipStack,
475 const SkRegion& clipRegion) {
476 this->fExistingClipStack = clipStack;
477 this->fExistingClipRegion = clipRegion;
478}
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000479
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000480void SkPDFDevice::clear(SkColor color) {
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000481 this->cleanUp();
482 this->init();
483
484 SkPaint paint;
485 paint.setColor(color);
486 paint.setStyle(SkPaint::kFill_Style);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000487 SkMatrix identity;
488 identity.reset();
489 setUpContentEntry(fExistingClipStack, fExistingClipRegion, identity, paint);
490
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000491 internalDrawPaint(paint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000492}
493
494void SkPDFDevice::drawPaint(const SkDraw& d, const SkPaint& paint) {
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000495 if (d.fClip->isEmpty()) {
496 return;
497 }
498
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000499 SkPaint newPaint = paint;
500 newPaint.setStyle(SkPaint::kFill_Style);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000501 setUpContentEntry(*d.fClipStack, *d.fClip, *d.fMatrix, newPaint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000502
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000503 internalDrawPaint(newPaint);
504}
505
506void SkPDFDevice::internalDrawPaint(const SkPaint& paint) {
507 SkRect bbox = SkRect::MakeWH(SkIntToScalar(this->width()),
508 SkIntToScalar(this->height()));
509 SkMatrix totalTransform = fInitialTransform;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000510 totalTransform.preConcat(fCurrentContentEntry->fState.fMatrix);
vandebo@chromium.org77bcaa32011-04-15 20:57:37 +0000511 SkMatrix inverse;
512 inverse.reset();
513 totalTransform.invert(&inverse);
514 inverse.mapRect(&bbox);
515
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000516 SkPDFUtils::AppendRectangle(bbox, &fCurrentContentEntry->fContent);
517 SkPDFUtils::PaintPath(paint.getStyle(), SkPath::kWinding_FillType,
518 &fCurrentContentEntry->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000519}
520
521void SkPDFDevice::drawPoints(const SkDraw& d, SkCanvas::PointMode mode,
522 size_t count, const SkPoint* points,
523 const SkPaint& paint) {
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000524 if (count == 0 || d.fClip->isEmpty()) {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000525 return;
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000526 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000527
528 switch (mode) {
529 case SkCanvas::kPolygon_PointMode:
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000530 setUpContentEntry(*d.fClipStack, *d.fClip, *d.fMatrix, paint);
531 SkPDFUtils::MoveTo(points[0].fX, points[0].fY,
532 &fCurrentContentEntry->fContent);
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +0000533 for (size_t i = 1; i < count; i++) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000534 SkPDFUtils::AppendLine(points[i].fX, points[i].fY,
535 &fCurrentContentEntry->fContent);
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +0000536 }
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000537 SkPDFUtils::StrokePath(&fCurrentContentEntry->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000538 break;
539 case SkCanvas::kLines_PointMode:
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000540 setUpContentEntry(*d.fClipStack, *d.fClip, *d.fMatrix, paint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000541 for (size_t i = 0; i < count/2; i++) {
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +0000542 SkPDFUtils::MoveTo(points[i * 2].fX, points[i * 2].fY,
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000543 &fCurrentContentEntry->fContent);
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +0000544 SkPDFUtils::AppendLine(points[i * 2 + 1].fX,
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000545 points[i * 2 + 1].fY,
546 &fCurrentContentEntry->fContent);
547 SkPDFUtils::StrokePath(&fCurrentContentEntry->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000548 }
549 break;
550 case SkCanvas::kPoints_PointMode:
551 if (paint.getStrokeCap() == SkPaint::kRound_Cap) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000552 setUpContentEntry(*d.fClipStack, *d.fClip, *d.fMatrix, paint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000553 for (size_t i = 0; i < count; i++) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000554 SkPDFUtils::MoveTo(points[i].fX, points[i].fY,
555 &fCurrentContentEntry->fContent);
556 SkPDFUtils::StrokePath(&fCurrentContentEntry->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000557 }
558 } else {
559 // PDF won't draw a single point with square/butt caps because
560 // the orientation is ambiguous. Draw a rectangle instead.
561 SkPaint newPaint = paint;
562 newPaint.setStyle(SkPaint::kFill_Style);
563 SkScalar strokeWidth = paint.getStrokeWidth();
564 SkScalar halfStroke = strokeWidth * SK_ScalarHalf;
565 for (size_t i = 0; i < count; i++) {
566 SkRect r = SkRect::MakeXYWH(points[i].fX, points[i].fY,
567 0, 0);
568 r.inset(-halfStroke, -halfStroke);
569 drawRect(d, r, newPaint);
570 }
571 }
572 break;
573 default:
574 SkASSERT(false);
575 }
576}
577
578void SkPDFDevice::drawRect(const SkDraw& d, const SkRect& r,
579 const SkPaint& paint) {
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000580 if (d.fClip->isEmpty()) {
581 return;
582 }
583
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000584 if (paint.getPathEffect()) {
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000585 // Create a path for the rectangle and apply the path effect to it.
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000586 SkPath path;
587 path.addRect(r);
588 paint.getFillPath(path, &path);
589
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000590 SkPaint noEffectPaint(paint);
591 SkSafeUnref(noEffectPaint.setPathEffect(NULL));
vandebo@chromium.org02cc5aa2011-01-25 22:06:29 +0000592 drawPath(d, path, noEffectPaint, NULL, true);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000593 return;
594 }
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000595 setUpContentEntry(*d.fClipStack, *d.fClip, *d.fMatrix, paint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000596
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000597 SkPDFUtils::AppendRectangle(r, &fCurrentContentEntry->fContent);
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +0000598 SkPDFUtils::PaintPath(paint.getStyle(), SkPath::kWinding_FillType,
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000599 &fCurrentContentEntry->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000600}
601
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000602void SkPDFDevice::drawPath(const SkDraw& d, const SkPath& path,
vandebo@chromium.org02cc5aa2011-01-25 22:06:29 +0000603 const SkPaint& paint, const SkMatrix* prePathMatrix,
604 bool pathIsMutable) {
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000605 if (d.fClip->isEmpty()) {
606 return;
607 }
vandebo@chromium.orgbefebb82011-01-29 01:38:50 +0000608 NOT_IMPLEMENTED(prePathMatrix != NULL, true);
vandebo@chromium.org02cc5aa2011-01-25 22:06:29 +0000609
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000610 if (paint.getPathEffect()) {
611 // Apply the path effect to path and draw it that way.
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000612 SkPath noEffectPath;
613 paint.getFillPath(path, &noEffectPath);
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000614
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000615 SkPaint noEffectPaint(paint);
616 SkSafeUnref(noEffectPaint.setPathEffect(NULL));
vandebo@chromium.org02cc5aa2011-01-25 22:06:29 +0000617 drawPath(d, noEffectPath, noEffectPaint, NULL, true);
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000618 return;
619 }
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000620 setUpContentEntry(*d.fClipStack, *d.fClip, *d.fMatrix, paint);
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000621
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000622 SkPDFUtils::EmitPath(path, &fCurrentContentEntry->fContent);
623 SkPDFUtils::PaintPath(paint.getStyle(), path.getFillType(),
624 &fCurrentContentEntry->fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000625}
626
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000627void SkPDFDevice::drawBitmap(const SkDraw& d, const SkBitmap& bitmap,
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000628 const SkIRect* srcRect, const SkMatrix& matrix,
629 const SkPaint& paint) {
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000630 if (d.fClip->isEmpty()) {
631 return;
632 }
633
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000634 SkMatrix transform = matrix;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000635 transform.postConcat(*d.fMatrix);
636 internalDrawBitmap(transform, *d.fClipStack, *d.fClip, bitmap, srcRect,
637 paint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000638}
639
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000640void SkPDFDevice::drawSprite(const SkDraw& d, const SkBitmap& bitmap,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000641 int x, int y, const SkPaint& paint) {
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000642 if (d.fClip->isEmpty()) {
643 return;
644 }
645
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000646 SkMatrix matrix;
reed@google.coma6d59f62011-03-07 21:29:21 +0000647 matrix.setTranslate(SkIntToScalar(x), SkIntToScalar(y));
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000648 internalDrawBitmap(matrix, *d.fClipStack, *d.fClip, bitmap, NULL, paint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000649}
650
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000651void SkPDFDevice::drawText(const SkDraw& d, const void* text, size_t len,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000652 SkScalar x, SkScalar y, const SkPaint& paint) {
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000653 if (d.fClip->isEmpty()) {
654 return;
655 }
656
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000657 SkPaint textPaint = calculate_text_paint(paint);
658 setUpContentEntryForText(*d.fClipStack, *d.fClip, *d.fMatrix, textPaint);
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000659
vandebo@chromium.org01294102011-02-28 19:52:18 +0000660 // We want the text in glyph id encoding and a writable buffer, so we end
661 // up making a copy either way.
662 size_t numGlyphs = paint.textToGlyphs(text, len, NULL);
663 uint16_t* glyphIDs =
664 (uint16_t*)sk_malloc_flags(numGlyphs * 2,
665 SK_MALLOC_TEMP | SK_MALLOC_THROW);
666 SkAutoFree autoFreeGlyphIDs(glyphIDs);
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000667 if (paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding) {
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000668 paint.textToGlyphs(text, len, glyphIDs);
669 textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
670 } else {
671 SkASSERT((len & 1) == 0);
vandebo@chromium.org01294102011-02-28 19:52:18 +0000672 SkASSERT(len / 2 == numGlyphs);
673 memcpy(glyphIDs, text, len);
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000674 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000675
676 SkScalar width;
677 SkScalar* widthPtr = NULL;
678 if (textPaint.isUnderlineText() || textPaint.isStrikeThruText())
679 widthPtr = &width;
680
681 SkDrawCacheProc glyphCacheProc = textPaint.getDrawCacheProc();
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000682 align_text(glyphCacheProc, textPaint, glyphIDs, numGlyphs, &x, &y,
683 widthPtr);
684 fCurrentContentEntry->fContent.writeText("BT\n");
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000685 setTextTransform(x, y, textPaint.getTextSkewX());
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000686 size_t consumedGlyphCount = 0;
687 while (numGlyphs > consumedGlyphCount) {
688 updateFont(textPaint, glyphIDs[consumedGlyphCount]);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000689 SkPDFFont* font = fCurrentContentEntry->fState.fFont;
vandebo@chromium.org01294102011-02-28 19:52:18 +0000690 size_t availableGlyphs =
691 font->glyphsToPDFFontEncoding(glyphIDs + consumedGlyphCount,
692 numGlyphs - consumedGlyphCount);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000693 SkString encodedString =
694 SkPDFString::formatString(glyphIDs + consumedGlyphCount,
695 availableGlyphs, font->multiByteGlyphs());
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000696 fCurrentContentEntry->fContent.writeText(encodedString.c_str());
vandebo@chromium.org01294102011-02-28 19:52:18 +0000697 consumedGlyphCount += availableGlyphs;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000698 fCurrentContentEntry->fContent.writeText(" Tj\n");
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000699 }
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000700 fCurrentContentEntry->fContent.writeText("ET\n");
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000701
702 // Draw underline and/or strikethrough if the paint has them.
703 // drawPosText() and drawTextOnPath() don't draw underline or strikethrough
704 // because the raster versions don't. Use paint instead of textPaint
705 // because we may have changed strokeWidth to do fakeBold text.
706 if (paint.isUnderlineText() || paint.isStrikeThruText()) {
707 SkScalar textSize = paint.getTextSize();
708 SkScalar height = SkScalarMul(textSize, kStdUnderline_Thickness);
709
710 if (paint.isUnderlineText()) {
711 SkScalar top = SkScalarMulAdd(textSize, kStdUnderline_Offset, y);
712 SkRect r = SkRect::MakeXYWH(x, top - height, width, height);
713 drawRect(d, r, paint);
714 }
715 if (paint.isStrikeThruText()) {
716 SkScalar top = SkScalarMulAdd(textSize, kStdStrikeThru_Offset, y);
717 SkRect r = SkRect::MakeXYWH(x, top - height, width, height);
718 drawRect(d, r, paint);
719 }
720 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000721}
722
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000723void SkPDFDevice::drawPosText(const SkDraw& d, const void* text, size_t len,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000724 const SkScalar pos[], SkScalar constY,
725 int scalarsPerPos, const SkPaint& paint) {
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000726 if (d.fClip->isEmpty()) {
727 return;
728 }
729
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000730 SkASSERT(1 == scalarsPerPos || 2 == scalarsPerPos);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000731 SkPaint textPaint = calculate_text_paint(paint);
732 setUpContentEntryForText(*d.fClipStack, *d.fClip, *d.fMatrix, textPaint);
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000733
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000734 // Make sure we have a glyph id encoding.
735 SkAutoFree glyphStorage;
736 uint16_t* glyphIDs;
737 size_t numGlyphs;
738 if (paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding) {
739 numGlyphs = paint.textToGlyphs(text, len, NULL);
740 glyphIDs = (uint16_t*)sk_malloc_flags(numGlyphs * 2,
741 SK_MALLOC_TEMP | SK_MALLOC_THROW);
742 glyphStorage.set(glyphIDs);
743 paint.textToGlyphs(text, len, glyphIDs);
744 textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
745 } else {
746 SkASSERT((len & 1) == 0);
747 numGlyphs = len / 2;
748 glyphIDs = (uint16_t*)text;
749 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000750
751 SkDrawCacheProc glyphCacheProc = textPaint.getDrawCacheProc();
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000752 fCurrentContentEntry->fContent.writeText("BT\n");
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000753 updateFont(textPaint, glyphIDs[0]);
754 for (size_t i = 0; i < numGlyphs; i++) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000755 SkPDFFont* font = fCurrentContentEntry->fState.fFont;
vandebo@chromium.org01294102011-02-28 19:52:18 +0000756 uint16_t encodedValue = glyphIDs[i];
757 if (font->glyphsToPDFFontEncoding(&encodedValue, 1) != 1) {
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000758 updateFont(textPaint, glyphIDs[i]);
759 i--;
760 continue;
761 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000762 SkScalar x = pos[i * scalarsPerPos];
763 SkScalar y = scalarsPerPos == 1 ? constY : pos[i * scalarsPerPos + 1];
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000764 align_text(glyphCacheProc, textPaint, glyphIDs + i, 1, &x, &y, NULL);
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000765 setTextTransform(x, y, textPaint.getTextSkewX());
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000766 SkString encodedString =
767 SkPDFString::formatString(&encodedValue, 1,
768 font->multiByteGlyphs());
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000769 fCurrentContentEntry->fContent.writeText(encodedString.c_str());
770 fCurrentContentEntry->fContent.writeText(" Tj\n");
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000771 }
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000772 fCurrentContentEntry->fContent.writeText("ET\n");
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000773}
774
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000775void SkPDFDevice::drawTextOnPath(const SkDraw& d, const void* text, size_t len,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000776 const SkPath& path, const SkMatrix* matrix,
777 const SkPaint& paint) {
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000778 if (d.fClip->isEmpty()) {
779 return;
780 }
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000781 NOT_IMPLEMENTED("drawTextOnPath", true);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000782}
783
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000784void SkPDFDevice::drawVertices(const SkDraw& d, SkCanvas::VertexMode,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000785 int vertexCount, const SkPoint verts[],
786 const SkPoint texs[], const SkColor colors[],
787 SkXfermode* xmode, const uint16_t indices[],
788 int indexCount, const SkPaint& paint) {
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000789 if (d.fClip->isEmpty()) {
790 return;
791 }
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000792 NOT_IMPLEMENTED("drawVerticies", true);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000793}
794
vandebo@chromium.orgeb6c7592010-10-26 19:54:45 +0000795void SkPDFDevice::drawDevice(const SkDraw& d, SkDevice* device, int x, int y,
796 const SkPaint& paint) {
vandebo@chromium.orgfb0b0ed2011-04-15 20:01:17 +0000797 if (d.fClip->isEmpty()) {
798 return;
799 }
800
vandebo@chromium.orgeb6c7592010-10-26 19:54:45 +0000801 if ((device->getDeviceCapabilities() & kVector_Capability) == 0) {
802 // If we somehow get a raster device, do what our parent would do.
803 SkDevice::drawDevice(d, device, x, y, paint);
804 return;
805 }
vandebo@chromium.orgeb6c7592010-10-26 19:54:45 +0000806 // Assume that a vector capable device means that it's a PDF Device.
vandebo@chromium.orgeb6c7592010-10-26 19:54:45 +0000807 SkPDFDevice* pdfDevice = static_cast<SkPDFDevice*>(device);
808
vandebo@chromium.org1aef2ed2011-02-03 21:46:10 +0000809 SkMatrix matrix;
reed@google.coma6d59f62011-03-07 21:29:21 +0000810 matrix.setTranslate(SkIntToScalar(x), SkIntToScalar(y));
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000811 setUpContentEntry(*d.fClipStack, *d.fClip, matrix, paint);
812 pdfDevice->setExistingClip(*d.fClipStack, *d.fClip);
vandebo@chromium.org1aef2ed2011-02-03 21:46:10 +0000813
814 SkPDFFormXObject* xobject = new SkPDFFormXObject(pdfDevice);
vandebo@chromium.orgeb6c7592010-10-26 19:54:45 +0000815 fXObjectResources.push(xobject); // Transfer reference.
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000816 fCurrentContentEntry->fContent.writeText("/X");
817 fCurrentContentEntry->fContent.writeDecAsText(
818 fXObjectResources.count() - 1);
819 fCurrentContentEntry->fContent.writeText(" Do\n");
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000820}
821
822const SkRefPtr<SkPDFDict>& SkPDFDevice::getResourceDict() {
823 if (fResourceDict.get() == NULL) {
824 fResourceDict = new SkPDFDict;
825 fResourceDict->unref(); // SkRefPtr and new both took a reference.
826
827 if (fGraphicStateResources.count()) {
828 SkRefPtr<SkPDFDict> extGState = new SkPDFDict();
829 extGState->unref(); // SkRefPtr and new both took a reference.
830 for (int i = 0; i < fGraphicStateResources.count(); i++) {
831 SkString nameString("G");
832 nameString.appendS32(i);
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000833 extGState->insert(
834 nameString.c_str(),
835 new SkPDFObjRef(fGraphicStateResources[i]))->unref();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000836 }
837 fResourceDict->insert("ExtGState", extGState.get());
838 }
839
840 if (fXObjectResources.count()) {
841 SkRefPtr<SkPDFDict> xObjects = new SkPDFDict();
842 xObjects->unref(); // SkRefPtr and new both took a reference.
843 for (int i = 0; i < fXObjectResources.count(); i++) {
844 SkString nameString("X");
845 nameString.appendS32(i);
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000846 xObjects->insert(
847 nameString.c_str(),
848 new SkPDFObjRef(fXObjectResources[i]))->unref();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000849 }
850 fResourceDict->insert("XObject", xObjects.get());
851 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000852
853 if (fFontResources.count()) {
854 SkRefPtr<SkPDFDict> fonts = new SkPDFDict();
855 fonts->unref(); // SkRefPtr and new both took a reference.
856 for (int i = 0; i < fFontResources.count(); i++) {
857 SkString nameString("F");
858 nameString.appendS32(i);
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000859 fonts->insert(nameString.c_str(),
860 new SkPDFObjRef(fFontResources[i]))->unref();
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000861 }
862 fResourceDict->insert("Font", fonts.get());
863 }
864
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000865 if (fShaderResources.count()) {
866 SkRefPtr<SkPDFDict> patterns = new SkPDFDict();
867 patterns->unref(); // SkRefPtr and new both took a reference.
868 for (int i = 0; i < fShaderResources.count(); i++) {
869 SkString nameString("P");
870 nameString.appendS32(i);
871 patterns->insert(nameString.c_str(),
872 new SkPDFObjRef(fShaderResources[i]))->unref();
873 }
874 fResourceDict->insert("Pattern", patterns.get());
875 }
876
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000877 // For compatibility, add all proc sets (only used for output to PS
878 // devices).
879 const char procs[][7] = {"PDF", "Text", "ImageB", "ImageC", "ImageI"};
880 SkRefPtr<SkPDFArray> procSets = new SkPDFArray();
881 procSets->unref(); // SkRefPtr and new both took a reference.
882 procSets->reserve(SK_ARRAY_COUNT(procs));
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000883 for (size_t i = 0; i < SK_ARRAY_COUNT(procs); i++)
884 procSets->append(new SkPDFName(procs[i]))->unref();
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000885 fResourceDict->insert("ProcSet", procSets.get());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000886 }
887 return fResourceDict;
888}
889
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000890void SkPDFDevice::getResources(SkTDArray<SkPDFObject*>* resourceList) const {
891 resourceList->setReserve(resourceList->count() +
892 fGraphicStateResources.count() +
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000893 fXObjectResources.count() +
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000894 fFontResources.count() +
895 fShaderResources.count());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000896 for (int i = 0; i < fGraphicStateResources.count(); i++) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000897 resourceList->push(fGraphicStateResources[i]);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000898 fGraphicStateResources[i]->ref();
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000899 fGraphicStateResources[i]->getResources(resourceList);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000900 }
901 for (int i = 0; i < fXObjectResources.count(); i++) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000902 resourceList->push(fXObjectResources[i]);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000903 fXObjectResources[i]->ref();
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000904 fXObjectResources[i]->getResources(resourceList);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000905 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000906 for (int i = 0; i < fFontResources.count(); i++) {
907 resourceList->push(fFontResources[i]);
908 fFontResources[i]->ref();
909 fFontResources[i]->getResources(resourceList);
910 }
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000911 for (int i = 0; i < fShaderResources.count(); i++) {
912 resourceList->push(fShaderResources[i]);
913 fShaderResources[i]->ref();
914 fShaderResources[i]->getResources(resourceList);
915 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000916}
917
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000918SkRefPtr<SkPDFArray> SkPDFDevice::getMediaBox() const {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000919 SkRefPtr<SkPDFInt> zero = new SkPDFInt(0);
920 zero->unref(); // SkRefPtr and new both took a reference.
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000921
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000922 SkRefPtr<SkPDFArray> mediaBox = new SkPDFArray();
923 mediaBox->unref(); // SkRefPtr and new both took a reference.
924 mediaBox->reserve(4);
925 mediaBox->append(zero.get());
926 mediaBox->append(zero.get());
ctguil@chromium.org15261292011-04-29 17:54:16 +0000927 mediaBox->append(new SkPDFInt(fPageSize.fWidth))->unref();
928 mediaBox->append(new SkPDFInt(fPageSize.fHeight))->unref();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000929 return mediaBox;
930}
931
vandebo@chromium.orgc2a9b7f2011-02-24 23:22:30 +0000932SkStream* SkPDFDevice::content() const {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000933 SkDynamicMemoryWStream data;
934 if (fInitialTransform.getType() != SkMatrix::kIdentity_Mask) {
935 SkPDFUtils::AppendTransform(fInitialTransform, &data);
vandebo@chromium.orgc2a9b7f2011-02-24 23:22:30 +0000936 }
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000937 // If the content area is the entire page, then we don't need to clip
938 // the content area (PDF area clips to the page size). Otherwise,
939 // we have to clip to the content area; we've already applied the
940 // initial transform, so just clip to the device size.
941 if (fPageSize != fContentSize) {
942 SkRect r = SkRect::MakeWH(this->width(), this->height());
943 emit_clip(NULL, &r, &data);
944 }
945
946 GraphicStackState gsState(fExistingClipStack, fExistingClipRegion, &data);
947 for (ContentEntry* entry = fContentEntries.get();
948 entry != NULL;
949 entry = entry->fNext.get()) {
950 SkIPoint translation = this->getOrigin();
951 translation.negate();
952 gsState.updateClip(entry->fState.fClipStack, entry->fState.fClipRegion,
953 translation);
954 gsState.updateMatrix(entry->fState.fMatrix);
955 gsState.updateDrawingState(entry->fState);
956 data.write(entry->fContent.getStream(), entry->fContent.getOffset());
957 }
958 gsState.drainStack();
959
vandebo@chromium.orgc2a9b7f2011-02-24 23:22:30 +0000960 SkMemoryStream* result = new SkMemoryStream;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000961 result->setMemoryOwned(data.detach(), data.getOffset());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000962 return result;
963}
964
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000965void SkPDFDevice::setUpContentEntry(const SkClipStack& clipStack,
966 const SkRegion& clipRegion,
967 const SkMatrix& matrix,
968 const SkPaint& paint,
969 bool hasText) {
970 ContentEntry* entry;
971 SkTScopedPtr<ContentEntry> newEntry;
972 if (fCurrentContentEntry &&
973 fCurrentContentEntry->fContent.getOffset() == 0) {
974 entry = fCurrentContentEntry;
975 } else {
976 newEntry.reset(new ContentEntry);
977 entry = newEntry.get();
978 }
979
980 populateGraphicStateEntryFromPaint(matrix, clipStack, clipRegion, paint,
981 hasText, &entry->fState);
982 if (fCurrentContentEntry &&
983 entry->fState.compareInitialState(fCurrentContentEntry->fState)) {
984 return;
985 }
986
987 if (!fCurrentContentEntry) {
988 fContentEntries.reset(entry);
989 } else {
990 fCurrentContentEntry->fNext.reset(entry);
991 }
992 newEntry.release();
993 fCurrentContentEntry = entry;
994}
995
996void SkPDFDevice::setUpContentEntryForText(const SkClipStack& clipStack,
997 const SkRegion& clipRegion,
998 const SkMatrix& matrix,
999 const SkPaint& paint) {
1000 setUpContentEntry(clipStack, clipRegion, matrix, paint, true);
1001}
1002
1003void SkPDFDevice::populateGraphicStateEntryFromPaint(
1004 const SkMatrix& matrix,
1005 const SkClipStack& clipStack,
1006 const SkRegion& clipRegion,
1007 const SkPaint& paint,
1008 bool hasText,
1009 GraphicStateEntry* entry) {
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001010 SkASSERT(paint.getPathEffect() == NULL);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001011
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001012 NOT_IMPLEMENTED(paint.getMaskFilter() != NULL, false);
1013 NOT_IMPLEMENTED(paint.getColorFilter() != NULL, false);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001014
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001015 entry->fMatrix = matrix;
1016 entry->fClipStack = clipStack;
1017 entry->fClipRegion = clipRegion;
vandebo@chromium.org48543272011-02-08 19:28:07 +00001018
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001019 // PDF treats a shader as a color, so we only set one or the other.
1020 SkRefPtr<SkPDFShader> pdfShader;
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001021 const SkShader* shader = paint.getShader();
1022 SkColor color = paint.getColor();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001023 if (shader) {
1024 // PDF positions patterns relative to the initial transform, so
1025 // we need to apply the current transform to the shader parameters.
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001026 SkMatrix transform = matrix;
vandebo@chromium.org75f97e42011-04-11 23:24:18 +00001027 transform.postConcat(fInitialTransform);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001028
1029 // PDF doesn't support kClamp_TileMode, so we simulate it by making
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001030 // a pattern the size of the current clip.
1031 SkIRect bounds = fCurrentContentEntry->fState.fClipRegion.getBounds();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001032 pdfShader = SkPDFShader::getPDFShader(*shader, transform, bounds);
1033 SkSafeUnref(pdfShader.get()); // getShader and SkRefPtr both took a ref
1034
1035 // A color shader is treated as an invalid shader so we don't have
1036 // to set a shader just for a color.
1037 if (pdfShader.get() == NULL) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001038 entry->fColor = 0;
1039 color = 0;
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001040
1041 // Check for a color shader.
1042 SkShader::GradientInfo gradientInfo;
1043 SkColor gradientColor;
1044 gradientInfo.fColors = &gradientColor;
1045 gradientInfo.fColorOffsets = NULL;
1046 gradientInfo.fColorCount = 1;
1047 if (shader->asAGradient(&gradientInfo) ==
1048 SkShader::kColor_GradientType) {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001049 entry->fColor = SkColorSetA(gradientColor, 0xFF);
1050 color = gradientColor;
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001051 }
1052 }
1053 }
1054
1055 if (pdfShader) {
1056 // pdfShader has been canonicalized so we can directly compare
1057 // pointers.
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001058 int resourceIndex = fShaderResources.find(pdfShader.get());
1059 if (resourceIndex < 0) {
1060 resourceIndex = fShaderResources.count();
1061 fShaderResources.push(pdfShader.get());
1062 pdfShader->ref();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001063 }
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001064 entry->fShaderIndex = resourceIndex;
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001065 } else {
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001066 entry->fShaderIndex = -1;
1067 entry->fColor = SkColorSetA(paint.getColor(), 0xFF);
1068 color = paint.getColor();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001069 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001070
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001071 SkRefPtr<SkPDFGraphicState> newGraphicState;
1072 if (color == paint.getColor()) {
1073 newGraphicState = SkPDFGraphicState::getGraphicStateForPaint(paint);
1074 } else {
1075 SkPaint newPaint = paint;
1076 newPaint.setColor(color);
1077 newGraphicState = SkPDFGraphicState::getGraphicStateForPaint(newPaint);
1078 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001079 newGraphicState->unref(); // getGraphicState and SkRefPtr both took a ref.
1080 // newGraphicState has been canonicalized so we can directly compare
1081 // pointers.
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001082 int resourceIndex = fGraphicStateResources.find(newGraphicState.get());
1083 if (resourceIndex < 0) {
1084 resourceIndex = fGraphicStateResources.count();
1085 fGraphicStateResources.push(newGraphicState.get());
1086 newGraphicState->ref();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001087 }
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001088 entry->fGraphicStateIndex = resourceIndex;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001089
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001090 if (hasText) {
1091 entry->fTextScaleX = paint.getTextScaleX();
1092 entry->fTextFill = paint.getStyle();
1093 } else {
1094 entry->fTextScaleX = 0;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001095 }
1096}
1097
vandebo@chromium.org2a22e102011-01-25 21:01:34 +00001098void SkPDFDevice::updateFont(const SkPaint& paint, uint16_t glyphID) {
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +00001099 SkTypeface* typeface = paint.getTypeface();
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001100 if (fCurrentContentEntry->fState.fFont == NULL ||
1101 !fCurrentContentEntry->fState.fFont->hasGlyph(glyphID)) {
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +00001102 int fontIndex = getFontResourceIndex(typeface, glyphID);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001103 fCurrentContentEntry->fContent.writeText("/F");
1104 fCurrentContentEntry->fContent.writeDecAsText(fontIndex);
1105 fCurrentContentEntry->fContent.writeText(" ");
1106 SkPDFScalar::Append(paint.getTextSize(),
1107 &fCurrentContentEntry->fContent);
1108 fCurrentContentEntry->fContent.writeText(" Tf\n");
1109 fCurrentContentEntry->fState.fFont = fFontResources[fontIndex];
vandebo@chromium.org2a22e102011-01-25 21:01:34 +00001110 }
1111}
1112
ctguil@chromium.org9db86bb2011-03-04 21:43:27 +00001113int SkPDFDevice::getFontResourceIndex(SkTypeface* typeface, uint16_t glyphID) {
1114 SkRefPtr<SkPDFFont> newFont = SkPDFFont::getFontResource(typeface, glyphID);
vandebo@chromium.org2a22e102011-01-25 21:01:34 +00001115 newFont->unref(); // getFontResource and SkRefPtr both took a ref.
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00001116 int resourceIndex = fFontResources.find(newFont.get());
1117 if (resourceIndex < 0) {
1118 resourceIndex = fFontResources.count();
1119 fFontResources.push(newFont.get());
1120 newFont->ref();
1121 }
1122 return resourceIndex;
1123}
1124
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00001125void SkPDFDevice::setTextTransform(SkScalar x, SkScalar y, SkScalar textSkewX) {
1126 // Flip the text about the x-axis to account for origin swap and include
1127 // the passed parameters.
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001128 fCurrentContentEntry->fContent.writeText("1 0 ");
1129 SkPDFScalar::Append(0 - textSkewX, &fCurrentContentEntry->fContent);
1130 fCurrentContentEntry->fContent.writeText(" -1 ");
1131 SkPDFScalar::Append(x, &fCurrentContentEntry->fContent);
1132 fCurrentContentEntry->fContent.writeText(" ");
1133 SkPDFScalar::Append(y, &fCurrentContentEntry->fContent);
1134 fCurrentContentEntry->fContent.writeText(" Tm\n");
vandebo@chromium.org28be72b2010-11-11 21:37:00 +00001135}
1136
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001137void SkPDFDevice::internalDrawBitmap(const SkMatrix& matrix,
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001138 const SkClipStack& clipStack,
1139 const SkRegion& clipRegion,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001140 const SkBitmap& bitmap,
vandebo@chromium.orgbefebb82011-01-29 01:38:50 +00001141 const SkIRect* srcRect,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001142 const SkPaint& paint) {
vandebo@chromium.orgbefebb82011-01-29 01:38:50 +00001143 SkIRect subset = SkIRect::MakeWH(bitmap.width(), bitmap.height());
1144 if (srcRect && !subset.intersect(*srcRect))
1145 return;
1146
vandebo@chromium.org1cfa2c42011-01-31 19:35:43 +00001147 SkPDFImage* image = SkPDFImage::CreateImage(bitmap, subset, paint);
1148 if (!image)
1149 return;
1150
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +00001151 SkMatrix scaled;
1152 // Adjust for origin flip.
1153 scaled.setScale(1, -1);
1154 scaled.postTranslate(0, 1);
1155 // Scale the image up from 1x1 to WxH.
reed@google.coma6d59f62011-03-07 21:29:21 +00001156 scaled.postScale(SkIntToScalar(subset.width()),
1157 SkIntToScalar(subset.height()));
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +00001158 scaled.postConcat(matrix);
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001159 setUpContentEntry(clipStack, clipRegion, scaled, paint);
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +00001160
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001161 fXObjectResources.push(image); // Transfer reference.
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +00001162 fCurrentContentEntry->fContent.writeText("/X");
1163 fCurrentContentEntry->fContent.writeDecAsText(
1164 fXObjectResources.count() - 1);
1165 fCurrentContentEntry->fContent.writeText(" Do\n");
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001166}