blob: 00c05e40804068b544867c949bfb635dd7aea84a [file] [log] [blame]
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +00001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
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"
20#include "SkPaint.h"
21#include "SkPDFImage.h"
22#include "SkPDFGraphicState.h"
23#include "SkPDFTypes.h"
24#include "SkPDFStream.h"
25#include "SkRect.h"
26#include "SkString.h"
27
28// Utility functions
29
30namespace {
31
32SkString toPDFColor(SkColor color) {
33 SkASSERT(SkColorGetA(color) == 0xFF); // We handle alpha elsewhere.
34 SkScalar colorMax = SkIntToScalar(0xFF);
35 SkString result;
36 result.appendScalar(SkIntToScalar(SkColorGetR(color))/colorMax);
37 result.append(" ");
38 result.appendScalar(SkIntToScalar(SkColorGetG(color))/colorMax);
39 result.append(" ");
40 result.appendScalar(SkIntToScalar(SkColorGetB(color))/colorMax);
41 result.append(" ");
42 return result;
43}
44
45SkString StyleAndFillToPaintOperator(SkPaint::Style style,
46 SkPath::FillType fillType) {
47 SkString result;
48 if (style == SkPaint::kFill_Style)
49 result.append("f");
50 else if (style == SkPaint::kStrokeAndFill_Style)
51 result.append("B");
52 else if (style == SkPaint::kStroke_Style)
53 return SkString("S\n");
54
55 // Not supported yet.
56 SkASSERT(fillType != SkPath::kInverseEvenOdd_FillType);
57 SkASSERT(fillType != SkPath::kInverseWinding_FillType);
58 if (fillType == SkPath::kEvenOdd_FillType)
59 result.append("*");
60 result.append("\n");
61 return result;
62}
63
64} // namespace
65
66////////////////////////////////////////////////////////////////////////////////
67
68SkDevice* SkPDFDeviceFactory::newDevice(SkBitmap::Config config,
69 int width, int height,
70 bool isOpaque, bool isForLayer) {
71 return SkNEW_ARGS(SkPDFDevice, (width, height));
72}
73
74SkPDFDevice::SkPDFDevice(int width, int height)
75 : fWidth(width),
76 fHeight(height),
77 fCurrentColor(0),
78 fCurrentTextScaleX(SK_Scalar1) {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000079 fContent.append("q\n");
80 fCurTransform.reset();
81 fActiveTransform.reset();
82}
83
84SkPDFDevice::~SkPDFDevice() {
85 fGraphicStateResources.unrefAll();
86 fXObjectResources.unrefAll();
87}
88
89void SkPDFDevice::setMatrixClip(const SkMatrix& matrix,
90 const SkRegion& region) {
91 // TODO(vandebo) handle clipping
92 setTransform(matrix);
93 fCurTransform = matrix;
94}
95
96void SkPDFDevice::drawPaint(const SkDraw& d, const SkPaint& paint) {
97 setNoTransform();
98
99 SkPaint newPaint = paint;
100 newPaint.setStyle(SkPaint::kFill_Style);
101 updateGSFromPaint(newPaint, NULL);
102
103 SkRect all = SkRect::MakeWH(width() + 1, height() + 1);
104 drawRect(d, all, newPaint);
105 setTransform(fCurTransform);
106}
107
108void SkPDFDevice::drawPoints(const SkDraw& d, SkCanvas::PointMode mode,
109 size_t count, const SkPoint* points,
110 const SkPaint& paint) {
111 if (count == 0)
112 return;
113
114 switch (mode) {
115 case SkCanvas::kPolygon_PointMode:
116 updateGSFromPaint(paint, NULL);
117 moveTo(points[0].fX, points[0].fY);
118 for (size_t i = 1; i < count; i++)
119 appendLine(points[i].fX, points[i].fY);
120 strokePath();
121 break;
122 case SkCanvas::kLines_PointMode:
123 updateGSFromPaint(paint, NULL);
124 for (size_t i = 0; i < count/2; i++) {
125 moveTo(points[i * 2].fX, points[i * 2].fY);
126 appendLine(points[i * 2 + 1].fX, points[i * 2 + 1].fY);
127 strokePath();
128 }
129 break;
130 case SkCanvas::kPoints_PointMode:
131 if (paint.getStrokeCap() == SkPaint::kRound_Cap) {
132 updateGSFromPaint(paint, NULL);
133 for (size_t i = 0; i < count; i++) {
134 moveTo(points[i].fX, points[i].fY);
135 strokePath();
136 }
137 } else {
138 // PDF won't draw a single point with square/butt caps because
139 // the orientation is ambiguous. Draw a rectangle instead.
140 SkPaint newPaint = paint;
141 newPaint.setStyle(SkPaint::kFill_Style);
142 SkScalar strokeWidth = paint.getStrokeWidth();
143 SkScalar halfStroke = strokeWidth * SK_ScalarHalf;
144 for (size_t i = 0; i < count; i++) {
145 SkRect r = SkRect::MakeXYWH(points[i].fX, points[i].fY,
146 0, 0);
147 r.inset(-halfStroke, -halfStroke);
148 drawRect(d, r, newPaint);
149 }
150 }
151 break;
152 default:
153 SkASSERT(false);
154 }
155}
156
157void SkPDFDevice::drawRect(const SkDraw& d, const SkRect& r,
158 const SkPaint& paint) {
159 if (paint.getPathEffect()) {
160 // Draw a path instead.
161 SkPath path;
162 path.addRect(r);
163 paint.getFillPath(path, &path);
164
165 SkPaint no_effect_paint(paint);
166 SkSafeUnref(no_effect_paint.setPathEffect(NULL));
167 drawPath(d, path, no_effect_paint);
168 return;
169 }
170 updateGSFromPaint(paint, NULL);
171
172 // Skia has 0,0 at top left, pdf at bottom left. Do the right thing.
173 SkScalar bottom = r.fBottom < r.fTop ? r.fBottom : r.fTop;
174 appendRectangle(r.fLeft, bottom, r.width(), r.height());
175 fContent.append(StyleAndFillToPaintOperator(paint.getStyle(),
176 SkPath::kWinding_FillType));
177}
178
179void SkPDFDevice::drawPath(const SkDraw&, const SkPath& path,
180 const SkPaint& paint) {
181 SkASSERT(false);
182}
183
184void SkPDFDevice::drawBitmap(const SkDraw&, const SkBitmap& bitmap,
185 const SkMatrix& matrix, const SkPaint& paint) {
186 SkMatrix scaled;
187 // Adjust for origin flip.
188 scaled.setScale(1, -1);
189 scaled.postTranslate(0, 1);
190 scaled.postConcat(fCurTransform);
191 // Scale the image up from 1x1 to WxH.
192 scaled.postScale(bitmap.width(), bitmap.height());
193 scaled.postConcat(matrix);
194 internalDrawBitmap(scaled, bitmap, paint);
195}
196
197void SkPDFDevice::drawSprite(const SkDraw&, const SkBitmap& bitmap,
198 int x, int y, const SkPaint& paint) {
199 SkMatrix scaled;
200 // Adjust for origin flip.
201 scaled.setScale(1, -1);
202 scaled.postTranslate(0, 1);
203 // Scale the image up from 1x1 to WxH.
204 scaled.postScale(bitmap.width(), -bitmap.height());
205 scaled.postTranslate(x, y);
206 internalDrawBitmap(scaled, bitmap, paint);
207}
208
209void SkPDFDevice::drawText(const SkDraw&, const void* text, size_t len,
210 SkScalar x, SkScalar y, const SkPaint& paint) {
211 SkASSERT(false);
212}
213
214void SkPDFDevice::drawPosText(const SkDraw&, const void* text, size_t len,
215 const SkScalar pos[], SkScalar constY,
216 int scalarsPerPos, const SkPaint& paint) {
217 SkASSERT(false);
218}
219
220void SkPDFDevice::drawTextOnPath(const SkDraw&, const void* text, size_t len,
221 const SkPath& path, const SkMatrix* matrix,
222 const SkPaint& paint) {
223 SkASSERT(false);
224}
225
226void SkPDFDevice::drawVertices(const SkDraw&, SkCanvas::VertexMode,
227 int vertexCount, const SkPoint verts[],
228 const SkPoint texs[], const SkColor colors[],
229 SkXfermode* xmode, const uint16_t indices[],
230 int indexCount, const SkPaint& paint) {
231 SkASSERT(false);
232}
233
234void SkPDFDevice::drawDevice(const SkDraw&, SkDevice*, int x, int y,
235 const SkPaint&) {
236 SkASSERT(false);
237}
238
239const SkRefPtr<SkPDFDict>& SkPDFDevice::getResourceDict() {
240 if (fResourceDict.get() == NULL) {
241 fResourceDict = new SkPDFDict;
242 fResourceDict->unref(); // SkRefPtr and new both took a reference.
243
244 if (fGraphicStateResources.count()) {
245 SkRefPtr<SkPDFDict> extGState = new SkPDFDict();
246 extGState->unref(); // SkRefPtr and new both took a reference.
247 for (int i = 0; i < fGraphicStateResources.count(); i++) {
248 SkString nameString("G");
249 nameString.appendS32(i);
250 SkRefPtr<SkPDFName> name = new SkPDFName(nameString);
251 name->unref(); // SkRefPtr and new both took a reference.
252 SkRefPtr<SkPDFObjRef> gsRef =
253 new SkPDFObjRef(fGraphicStateResources[i]);
254 gsRef->unref(); // SkRefPtr and new both took a reference.
255 extGState->insert(name.get(), gsRef.get());
256 }
257 fResourceDict->insert("ExtGState", extGState.get());
258 }
259
260 if (fXObjectResources.count()) {
261 SkRefPtr<SkPDFDict> xObjects = new SkPDFDict();
262 xObjects->unref(); // SkRefPtr and new both took a reference.
263 for (int i = 0; i < fXObjectResources.count(); i++) {
264 SkString nameString("X");
265 nameString.appendS32(i);
266 SkRefPtr<SkPDFName> name = new SkPDFName(nameString);
267 name->unref(); // SkRefPtr and new both took a reference.
268 SkRefPtr<SkPDFObjRef> xObjRef =
269 new SkPDFObjRef(fXObjectResources[i]);
270 xObjRef->unref(); // SkRefPtr and new both took a reference.
271 xObjects->insert(name.get(), xObjRef.get());
272 }
273 fResourceDict->insert("XObject", xObjects.get());
274 }
275 }
276 return fResourceDict;
277}
278
279void SkPDFDevice::getResouces(SkTDArray<SkPDFObject*>* resouceList) {
280 resouceList->setReserve(resouceList->count() +
281 fGraphicStateResources.count() +
282 fXObjectResources.count());
283 for (int i = 0; i < fGraphicStateResources.count(); i++) {
284 resouceList->push(fGraphicStateResources[i]);
285 fGraphicStateResources[i]->ref();
286 }
287 for (int i = 0; i < fXObjectResources.count(); i++) {
288 resouceList->push(fXObjectResources[i]);
289 fXObjectResources[i]->ref();
290 }
291}
292
293SkRefPtr<SkPDFArray> SkPDFDevice::getMediaBox() {
294 SkRefPtr<SkPDFInt> zero = new SkPDFInt(0);
295 zero->unref(); // SkRefPtr and new both took a reference.
296 SkRefPtr<SkPDFInt> width = new SkPDFInt(fWidth);
297 width->unref(); // SkRefPtr and new both took a reference.
298 SkRefPtr<SkPDFInt> height = new SkPDFInt(fHeight);
299 height->unref(); // SkRefPtr and new both took a reference.
300 SkRefPtr<SkPDFArray> mediaBox = new SkPDFArray();
301 mediaBox->unref(); // SkRefPtr and new both took a reference.
302 mediaBox->reserve(4);
303 mediaBox->append(zero.get());
304 mediaBox->append(zero.get());
305 mediaBox->append(width.get());
306 mediaBox->append(height.get());
307 return mediaBox;
308}
309
vandebo@chromium.orgddbbd802010-10-26 19:45:06 +0000310SkString SkPDFDevice::content(bool flipOrigin) const {
311 SkString result;
312 // Scale and translate to move the origin from the lower left to the
313 // upper left.
314 if (flipOrigin)
315 result.printf("1 0 0 -1 0 %d cm\n", fHeight);
316 result.append(fContent);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000317 result.append("Q");
318 return result;
319}
320
321// Private
322
323// TODO(vandebo) handle these cases.
324#define PAINTCHECK(x,y) do { \
325 if(newPaint.x() y) { \
326 printf("!!" #x #y "\n"); \
327 SkASSERT(false); \
328 } \
329 } while(0)
330
331void SkPDFDevice::updateGSFromPaint(const SkPaint& newPaint,
332 SkString* textStateUpdate) {
333 PAINTCHECK(getXfermode, != NULL);
334 PAINTCHECK(getPathEffect, != NULL);
335 PAINTCHECK(getMaskFilter, != NULL);
336 PAINTCHECK(getShader, != NULL);
337 PAINTCHECK(getColorFilter, != NULL);
338 PAINTCHECK(isFakeBoldText, == true);
339 PAINTCHECK(isUnderlineText, == true);
340 PAINTCHECK(isStrikeThruText, == true);
341 PAINTCHECK(getTextSkewX, != 0);
342
343 SkRefPtr<SkPDFGraphicState> newGraphicState =
344 SkPDFGraphicState::getGraphicStateForPaint(newPaint);
345 newGraphicState->unref(); // getGraphicState and SkRefPtr both took a ref.
346 // newGraphicState has been canonicalized so we can directly compare
347 // pointers.
348 if (fCurrentGraphicState.get() != newGraphicState.get()) {
349 int resourceIndex = fGraphicStateResources.find(newGraphicState.get());
350 if (resourceIndex < 0) {
351 resourceIndex = fGraphicStateResources.count();
352 fGraphicStateResources.push(newGraphicState.get());
353 newGraphicState->ref();
354 }
355 fContent.append("/G");
356 fContent.appendS32(resourceIndex);
357 fContent.append(" gs\n");
358 fCurrentGraphicState = newGraphicState;
359 }
360
361 SkColor newColor = newPaint.getColor();
362 newColor = SkColorSetA(newColor, 0xFF);
363 if (fCurrentColor != newColor) {
364 SkString colorString = toPDFColor(newColor);
365 fContent.append(colorString);
366 fContent.append("RG ");
367 fContent.append(colorString);
368 fContent.append("rg\n");
369 fCurrentColor = newColor;
370 }
371
372 if (textStateUpdate != NULL &&
373 fCurrentTextScaleX != newPaint.getTextScaleX()) {
374 SkScalar scale = newPaint.getTextScaleX();
375 SkScalar pdfScale = scale * 100;
376 textStateUpdate->appendScalar(pdfScale);
377 textStateUpdate->append(" Tz\n");
378 fCurrentTextScaleX = scale;
379 }
380}
381
382void SkPDFDevice::moveTo(SkScalar x, SkScalar y) {
383 fContent.appendScalar(x);
384 fContent.append(" ");
385 fContent.appendScalar(y);
386 fContent.append(" m\n");
387}
388
389void SkPDFDevice::appendLine(SkScalar x, SkScalar y) {
390 fContent.appendScalar(x);
391 fContent.append(" ");
392 fContent.appendScalar(y);
393 fContent.append(" l\n");
394}
395
396void SkPDFDevice::appendCubic(SkScalar ctl1X, SkScalar ctl1Y,
397 SkScalar ctl2X, SkScalar ctl2Y,
398 SkScalar dstX, SkScalar dstY) {
399 SkString cmd("y\n");
400 fContent.appendScalar(ctl1X);
401 fContent.append(" ");
402 fContent.appendScalar(ctl1Y);
403 fContent.append(" ");
404 if (ctl2X != dstX || ctl2Y != dstY) {
405 cmd.set("c\n");
406 fContent.appendScalar(ctl2X);
407 fContent.append(" ");
408 fContent.appendScalar(ctl2Y);
409 fContent.append(" ");
410 }
411 fContent.appendScalar(dstX);
412 fContent.append(" ");
413 fContent.appendScalar(dstY);
414 fContent.append(cmd);
415}
416
417void SkPDFDevice::appendRectangle(SkScalar x, SkScalar y,
418 SkScalar w, SkScalar h) {
419 fContent.appendScalar(x);
420 fContent.append(" ");
421 fContent.appendScalar(y);
422 fContent.append(" ");
423 fContent.appendScalar(w);
424 fContent.append(" ");
425 fContent.appendScalar(h);
426 fContent.append(" re\n");
427}
428
429void SkPDFDevice::closePath() {
430 fContent.append("h\n");
431}
432
433void SkPDFDevice::strokePath() {
434 fContent.append(StyleAndFillToPaintOperator(SkPaint::kStroke_Style,
435 SkPath::kWinding_FillType));
436}
437
438void SkPDFDevice::internalDrawBitmap(const SkMatrix& matrix,
439 const SkBitmap& bitmap,
440 const SkPaint& paint) {
441 setTransform(matrix);
442 SkPDFImage* image = new SkPDFImage(bitmap, paint);
443 fXObjectResources.push(image); // Transfer reference.
444 fContent.append("/X");
445 fContent.appendS32(fXObjectResources.count() - 1);
446 fContent.append(" Do\n");
447 setTransform(fCurTransform);
448}
449
450void SkPDFDevice::setTransform(const SkMatrix& m) {
451 setNoTransform();
452 applyTransform(m);
453}
454
455void SkPDFDevice::setNoTransform() {
456 if (fActiveTransform.getType() == SkMatrix::kIdentity_Mask)
457 return;
458 fContent.append("Q q "); // Restore the default transform and save it.
459 fCurrentGraphicState = NULL;
460 fActiveTransform.reset();
461}
462
463void SkPDFDevice::applyTempTransform(const SkMatrix& m) {
464 fContent.append("q ");
465 applyTransform(m);
466}
467
468void SkPDFDevice::removeTempTransform() {
469 fContent.append("Q\n");
470 fActiveTransform = fCurTransform;
471}
472
473void SkPDFDevice::applyTransform(const SkMatrix& m) {
474 if (m == fActiveTransform)
475 return;
vandebo@chromium.orgddbbd802010-10-26 19:45:06 +0000476 SkScalar transform[6];
477 SkAssertResult(m.pdfTransform(transform));
478 for (size_t i = 0; i < SK_ARRAY_COUNT(transform); i++) {
479 fContent.appendScalar(transform[i]);
480 fContent.append(" ");
481 }
482 fContent.append("cm\n");
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000483 fActiveTransform = m;
484}