blob: 9aefd0a2586941a6097992e58a2155ebababd8a1 [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"
vandebo@chromium.org28be72b2010-11-11 21:37:00 +000020#include "SkGlyphCache.h"
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000021#include "SkPaint.h"
vandebo@chromium.orga5180862010-10-26 19:48:49 +000022#include "SkPath.h"
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000023#include "SkPDFImage.h"
24#include "SkPDFGraphicState.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.org9b49dc02010-10-20 22:23:29 +000027#include "SkPDFTypes.h"
28#include "SkPDFStream.h"
29#include "SkRect.h"
30#include "SkString.h"
vandebo@chromium.org28be72b2010-11-11 21:37:00 +000031#include "SkTextFormatParams.h"
32#include "SkTypeface.h"
33#include "SkTypes.h"
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000034
vandebo@chromium.orga5180862010-10-26 19:48:49 +000035#define NOT_IMPLEMENTED(condition, assert) \
36 do { \
37 if (condition) { \
38 fprintf(stderr, "NOT_IMPLEMENTED: " #condition "\n"); \
39 SkASSERT(!assert); \
40 } \
41 } while(0)
42
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000043// Utility functions
44
45namespace {
46
47SkString toPDFColor(SkColor color) {
48 SkASSERT(SkColorGetA(color) == 0xFF); // We handle alpha elsewhere.
49 SkScalar colorMax = SkIntToScalar(0xFF);
50 SkString result;
vandebo@chromium.orga5180862010-10-26 19:48:49 +000051 result.appendScalar(SkScalarDiv(SkIntToScalar(SkColorGetR(color)),
52 colorMax));
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000053 result.append(" ");
vandebo@chromium.orga5180862010-10-26 19:48:49 +000054 result.appendScalar(SkScalarDiv(SkIntToScalar(SkColorGetG(color)),
55 colorMax));
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000056 result.append(" ");
vandebo@chromium.orga5180862010-10-26 19:48:49 +000057 result.appendScalar(SkScalarDiv(SkIntToScalar(SkColorGetB(color)),
58 colorMax));
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +000059 result.append(" ");
60 return result;
61}
62
vandebo@chromium.org28be72b2010-11-11 21:37:00 +000063SkPaint calculateTextPaint(const SkPaint& paint) {
64 SkPaint result = paint;
65 if (result.isFakeBoldText()) {
66 SkScalar fakeBoldScale = SkScalarInterpFunc(result.getTextSize(),
67 kStdFakeBoldInterpKeys,
68 kStdFakeBoldInterpValues,
69 kStdFakeBoldInterpLength);
70 SkScalar width = SkScalarMul(result.getTextSize(), fakeBoldScale);
71 if (result.getStyle() == SkPaint::kFill_Style)
72 result.setStyle(SkPaint::kStrokeAndFill_Style);
73 else
74 width += result.getStrokeWidth();
75 result.setStrokeWidth(width);
76 }
77 return result;
78}
79
80// Stolen from measure_text in SkDraw.cpp and then tweaked.
81void alignText(SkDrawCacheProc glyphCacheProc, const SkPaint& paint,
82 const uint16_t* glyphs, size_t len, SkScalar* x, SkScalar* y,
83 SkScalar* width) {
84 if (paint.getTextAlign() == SkPaint::kLeft_Align && width == NULL)
85 return;
86
87 SkMatrix ident;
88 ident.reset();
89 SkAutoGlyphCache autoCache(paint, &ident);
90 SkGlyphCache* cache = autoCache.getCache();
91
92 const char* start = (char*)glyphs;
93 const char* stop = (char*)(glyphs + len);
94 SkFixed xAdv = 0, yAdv = 0;
95
96 // TODO(vandebo) This probably needs to take kerning into account.
97 while (start < stop) {
98 const SkGlyph& glyph = glyphCacheProc(cache, &start, 0, 0);
99 xAdv += glyph.fAdvanceX;
100 yAdv += glyph.fAdvanceY;
101 };
102 if (width)
103 *width = SkFixedToScalar(xAdv);
104 if (paint.getTextAlign() == SkPaint::kLeft_Align)
105 return;
106
107 SkScalar xAdj = SkFixedToScalar(xAdv);
108 SkScalar yAdj = SkFixedToScalar(yAdv);
109 if (paint.getTextAlign() == SkPaint::kCenter_Align) {
110 xAdj = SkScalarHalf(xAdj);
111 yAdj = SkScalarHalf(yAdj);
112 }
113 *x = *x - xAdj;
114 *y = *y - yAdj;
115}
116
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000117} // namespace
118
119////////////////////////////////////////////////////////////////////////////////
120
reed@android.comf2b98d62010-12-20 18:26:13 +0000121SkDevice* SkPDFDeviceFactory::newDevice(SkCanvas*, SkBitmap::Config config,
122 int width, int height, bool isOpaque,
123 bool /*isForLayer*/) {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000124 return SkNEW_ARGS(SkPDFDevice, (width, height));
125}
126
reed@android.comf2b98d62010-12-20 18:26:13 +0000127static inline SkBitmap makeABitmap(int width, int height) {
128 SkBitmap bitmap;
129 bitmap.setConfig(SkBitmap::kNo_Config, width, height);
130 return bitmap;
131}
132
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000133SkPDFDevice::SkPDFDevice(int width, int height)
134 : fWidth(width),
135 fHeight(height),
reed@android.comf2b98d62010-12-20 18:26:13 +0000136 fGraphicStackIndex(0),
137 SkDevice(NULL, makeABitmap(width, height), false) {
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000138 fGraphicStack[0].fColor = SK_ColorBLACK;
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000139 fGraphicStack[0].fTextSize = SK_ScalarNaN; // This has no default value.
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000140 fGraphicStack[0].fTextScaleX = SK_Scalar1;
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000141 fGraphicStack[0].fTextFill = SkPaint::kFill_Style;
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000142 fGraphicStack[0].fClip.setRect(0,0, width, height);
143 fGraphicStack[0].fTransform.reset();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000144}
145
146SkPDFDevice::~SkPDFDevice() {
147 fGraphicStateResources.unrefAll();
148 fXObjectResources.unrefAll();
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000149 fFontResources.unrefAll();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000150}
151
152void SkPDFDevice::setMatrixClip(const SkMatrix& matrix,
153 const SkRegion& region) {
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000154 // See the comment in the header file above GraphicStackEntry.
155 if (region != fGraphicStack[fGraphicStackIndex].fClip) {
156 while (fGraphicStackIndex > 0)
157 popGS();
158 pushGS();
159
160 SkPath clipPath;
161 if (!region.getBoundaryPath(&clipPath))
162 clipPath.moveTo(SkIntToScalar(-1), SkIntToScalar(-1));
163 emitPath(clipPath);
164
165 SkPath::FillType clipFill = clipPath.getFillType();
166 NOT_IMPLEMENTED(clipFill == SkPath::kInverseEvenOdd_FillType, false);
167 NOT_IMPLEMENTED(clipFill == SkPath::kInverseWinding_FillType, false);
168 if (clipFill == SkPath::kEvenOdd_FillType)
169 fContent.append("W* n ");
170 else
171 fContent.append("W n ");
172
173 fGraphicStack[fGraphicStackIndex].fClip = region;
174 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000175 setTransform(matrix);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000176}
177
178void SkPDFDevice::drawPaint(const SkDraw& d, const SkPaint& paint) {
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000179 SkMatrix identityTransform;
180 identityTransform.reset();
181 SkMatrix curTransform = setTransform(identityTransform);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000182
183 SkPaint newPaint = paint;
184 newPaint.setStyle(SkPaint::kFill_Style);
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000185 updateGSFromPaint(newPaint, false);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000186
187 SkRect all = SkRect::MakeWH(width() + 1, height() + 1);
188 drawRect(d, all, newPaint);
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000189 setTransform(curTransform);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000190}
191
192void SkPDFDevice::drawPoints(const SkDraw& d, SkCanvas::PointMode mode,
193 size_t count, const SkPoint* points,
194 const SkPaint& paint) {
195 if (count == 0)
196 return;
197
198 switch (mode) {
199 case SkCanvas::kPolygon_PointMode:
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000200 updateGSFromPaint(paint, false);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000201 moveTo(points[0].fX, points[0].fY);
202 for (size_t i = 1; i < count; i++)
203 appendLine(points[i].fX, points[i].fY);
204 strokePath();
205 break;
206 case SkCanvas::kLines_PointMode:
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000207 updateGSFromPaint(paint, false);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000208 for (size_t i = 0; i < count/2; i++) {
209 moveTo(points[i * 2].fX, points[i * 2].fY);
210 appendLine(points[i * 2 + 1].fX, points[i * 2 + 1].fY);
211 strokePath();
212 }
213 break;
214 case SkCanvas::kPoints_PointMode:
215 if (paint.getStrokeCap() == SkPaint::kRound_Cap) {
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000216 updateGSFromPaint(paint, false);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000217 for (size_t i = 0; i < count; i++) {
218 moveTo(points[i].fX, points[i].fY);
219 strokePath();
220 }
221 } else {
222 // PDF won't draw a single point with square/butt caps because
223 // the orientation is ambiguous. Draw a rectangle instead.
224 SkPaint newPaint = paint;
225 newPaint.setStyle(SkPaint::kFill_Style);
226 SkScalar strokeWidth = paint.getStrokeWidth();
227 SkScalar halfStroke = strokeWidth * SK_ScalarHalf;
228 for (size_t i = 0; i < count; i++) {
229 SkRect r = SkRect::MakeXYWH(points[i].fX, points[i].fY,
230 0, 0);
231 r.inset(-halfStroke, -halfStroke);
232 drawRect(d, r, newPaint);
233 }
234 }
235 break;
236 default:
237 SkASSERT(false);
238 }
239}
240
241void SkPDFDevice::drawRect(const SkDraw& d, const SkRect& r,
242 const SkPaint& paint) {
243 if (paint.getPathEffect()) {
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000244 // Create a path for the rectangle and apply the path effect to it.
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000245 SkPath path;
246 path.addRect(r);
247 paint.getFillPath(path, &path);
248
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000249 SkPaint noEffectPaint(paint);
250 SkSafeUnref(noEffectPaint.setPathEffect(NULL));
251 drawPath(d, path, noEffectPaint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000252 return;
253 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000254 updateGSFromPaint(paint, false);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000255
256 // Skia has 0,0 at top left, pdf at bottom left. Do the right thing.
257 SkScalar bottom = r.fBottom < r.fTop ? r.fBottom : r.fTop;
258 appendRectangle(r.fLeft, bottom, r.width(), r.height());
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000259 paintPath(paint.getStyle(), SkPath::kWinding_FillType);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000260}
261
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000262void SkPDFDevice::drawPath(const SkDraw& d, const SkPath& path,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000263 const SkPaint& paint) {
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000264 if (paint.getPathEffect()) {
265 // Apply the path effect to path and draw it that way.
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000266 SkPath noEffectPath;
267 paint.getFillPath(path, &noEffectPath);
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000268
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000269 SkPaint noEffectPaint(paint);
270 SkSafeUnref(noEffectPaint.setPathEffect(NULL));
271 drawPath(d, noEffectPath, noEffectPaint);
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000272 return;
273 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000274 updateGSFromPaint(paint, false);
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000275
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000276 emitPath(path);
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000277 paintPath(paint.getStyle(), path.getFillType());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000278}
279
280void SkPDFDevice::drawBitmap(const SkDraw&, const SkBitmap& bitmap,
reed@android.comf2b98d62010-12-20 18:26:13 +0000281 const SkIRect* srcRect,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000282 const SkMatrix& matrix, const SkPaint& paint) {
reed@android.comf2b98d62010-12-20 18:26:13 +0000283 // TODO: respect srcRect if present
284
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000285 SkMatrix transform = matrix;
286 transform.postConcat(fGraphicStack[fGraphicStackIndex].fTransform);
287 internalDrawBitmap(transform, bitmap, paint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000288}
289
290void SkPDFDevice::drawSprite(const SkDraw&, const SkBitmap& bitmap,
291 int x, int y, const SkPaint& paint) {
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000292 SkMatrix matrix;
293 matrix.setTranslate(x, y);
294 internalDrawBitmap(matrix, bitmap, paint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000295}
296
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000297void SkPDFDevice::drawText(const SkDraw& d, const void* text, size_t len,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000298 SkScalar x, SkScalar y, const SkPaint& paint) {
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000299 SkPaint textPaint = calculateTextPaint(paint);
300 updateGSFromPaint(textPaint, true);
301 SkPDFFont* font = fGraphicStack[fGraphicStackIndex].fFont.get();
302
303 uint16_t glyphs[len];
304 size_t glyphsLength;
305 glyphsLength = font->textToPDFGlyphs(text, len, textPaint, glyphs, len);
306 textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
307
308 SkScalar width;
309 SkScalar* widthPtr = NULL;
310 if (textPaint.isUnderlineText() || textPaint.isStrikeThruText())
311 widthPtr = &width;
312
313 SkDrawCacheProc glyphCacheProc = textPaint.getDrawCacheProc();
314 alignText(glyphCacheProc, textPaint, glyphs, glyphsLength, &x, &y,
315 widthPtr);
316 fContent.append("BT\n");
317 setTextTransform(x, y, textPaint.getTextSkewX());
318 fContent.append(SkPDFString::formatString(glyphs, glyphsLength,
319 font->multiByteGlyphs()));
320 fContent.append(" Tj\nET\n");
321
322 // Draw underline and/or strikethrough if the paint has them.
323 // drawPosText() and drawTextOnPath() don't draw underline or strikethrough
324 // because the raster versions don't. Use paint instead of textPaint
325 // because we may have changed strokeWidth to do fakeBold text.
326 if (paint.isUnderlineText() || paint.isStrikeThruText()) {
327 SkScalar textSize = paint.getTextSize();
328 SkScalar height = SkScalarMul(textSize, kStdUnderline_Thickness);
329
330 if (paint.isUnderlineText()) {
331 SkScalar top = SkScalarMulAdd(textSize, kStdUnderline_Offset, y);
332 SkRect r = SkRect::MakeXYWH(x, top - height, width, height);
333 drawRect(d, r, paint);
334 }
335 if (paint.isStrikeThruText()) {
336 SkScalar top = SkScalarMulAdd(textSize, kStdStrikeThru_Offset, y);
337 SkRect r = SkRect::MakeXYWH(x, top - height, width, height);
338 drawRect(d, r, paint);
339 }
340 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000341}
342
343void SkPDFDevice::drawPosText(const SkDraw&, const void* text, size_t len,
344 const SkScalar pos[], SkScalar constY,
345 int scalarsPerPos, const SkPaint& paint) {
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000346 SkASSERT(1 == scalarsPerPos || 2 == scalarsPerPos);
347 SkPaint textPaint = calculateTextPaint(paint);
348 updateGSFromPaint(textPaint, true);
349 SkPDFFont* font = fGraphicStack[fGraphicStackIndex].fFont.get();
350
351 uint16_t glyphs[len];
352 size_t glyphsLength;
353 glyphsLength = font->textToPDFGlyphs(text, len, textPaint, glyphs, len);
354 textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
355
356 SkDrawCacheProc glyphCacheProc = textPaint.getDrawCacheProc();
357 fContent.append("BT\n");
358 for (size_t i = 0; i < glyphsLength; i++) {
359 SkScalar x = pos[i * scalarsPerPos];
360 SkScalar y = scalarsPerPos == 1 ? constY : pos[i * scalarsPerPos + 1];
361 alignText(glyphCacheProc, textPaint, glyphs + i, 1, &x, &y, NULL);
362 setTextTransform(x, y, textPaint.getTextSkewX());
363 fContent.append(SkPDFString::formatString(glyphs + i, 1,
364 font->multiByteGlyphs()));
365 fContent.append(" Tj\n");
366 }
367 fContent.append("ET\n");
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000368}
369
370void SkPDFDevice::drawTextOnPath(const SkDraw&, const void* text, size_t len,
371 const SkPath& path, const SkMatrix* matrix,
372 const SkPaint& paint) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000373 NOT_IMPLEMENTED("drawTextOnPath", true);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000374}
375
376void SkPDFDevice::drawVertices(const SkDraw&, SkCanvas::VertexMode,
377 int vertexCount, const SkPoint verts[],
378 const SkPoint texs[], const SkColor colors[],
379 SkXfermode* xmode, const uint16_t indices[],
380 int indexCount, const SkPaint& paint) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000381 NOT_IMPLEMENTED("drawVerticies", true);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000382}
383
vandebo@chromium.orgeb6c7592010-10-26 19:54:45 +0000384void SkPDFDevice::drawDevice(const SkDraw& d, SkDevice* device, int x, int y,
385 const SkPaint& paint) {
386 if ((device->getDeviceCapabilities() & kVector_Capability) == 0) {
387 // If we somehow get a raster device, do what our parent would do.
388 SkDevice::drawDevice(d, device, x, y, paint);
389 return;
390 }
391
392 // Assume that a vector capable device means that it's a PDF Device.
393 // TODO(vandebo) handle the paint (alpha and compositing mode).
394 SkMatrix matrix;
395 matrix.setTranslate(x, y);
396 SkPDFDevice* pdfDevice = static_cast<SkPDFDevice*>(device);
397
398 SkPDFFormXObject* xobject = new SkPDFFormXObject(pdfDevice, matrix);
399 fXObjectResources.push(xobject); // Transfer reference.
400 fContent.append("/X");
401 fContent.appendS32(fXObjectResources.count() - 1);
402 fContent.append(" Do\n");
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000403}
404
405const SkRefPtr<SkPDFDict>& SkPDFDevice::getResourceDict() {
406 if (fResourceDict.get() == NULL) {
407 fResourceDict = new SkPDFDict;
408 fResourceDict->unref(); // SkRefPtr and new both took a reference.
409
410 if (fGraphicStateResources.count()) {
411 SkRefPtr<SkPDFDict> extGState = new SkPDFDict();
412 extGState->unref(); // SkRefPtr and new both took a reference.
413 for (int i = 0; i < fGraphicStateResources.count(); i++) {
414 SkString nameString("G");
415 nameString.appendS32(i);
416 SkRefPtr<SkPDFName> name = new SkPDFName(nameString);
417 name->unref(); // SkRefPtr and new both took a reference.
418 SkRefPtr<SkPDFObjRef> gsRef =
419 new SkPDFObjRef(fGraphicStateResources[i]);
420 gsRef->unref(); // SkRefPtr and new both took a reference.
421 extGState->insert(name.get(), gsRef.get());
422 }
423 fResourceDict->insert("ExtGState", extGState.get());
424 }
425
426 if (fXObjectResources.count()) {
427 SkRefPtr<SkPDFDict> xObjects = new SkPDFDict();
428 xObjects->unref(); // SkRefPtr and new both took a reference.
429 for (int i = 0; i < fXObjectResources.count(); i++) {
430 SkString nameString("X");
431 nameString.appendS32(i);
432 SkRefPtr<SkPDFName> name = new SkPDFName(nameString);
433 name->unref(); // SkRefPtr and new both took a reference.
434 SkRefPtr<SkPDFObjRef> xObjRef =
435 new SkPDFObjRef(fXObjectResources[i]);
436 xObjRef->unref(); // SkRefPtr and new both took a reference.
437 xObjects->insert(name.get(), xObjRef.get());
438 }
439 fResourceDict->insert("XObject", xObjects.get());
440 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000441
442 if (fFontResources.count()) {
443 SkRefPtr<SkPDFDict> fonts = new SkPDFDict();
444 fonts->unref(); // SkRefPtr and new both took a reference.
445 for (int i = 0; i < fFontResources.count(); i++) {
446 SkString nameString("F");
447 nameString.appendS32(i);
448 SkRefPtr<SkPDFName> name = new SkPDFName(nameString);
449 name->unref(); // SkRefPtr and new both took a reference.
450 SkRefPtr<SkPDFObjRef> fontRef =
451 new SkPDFObjRef(fFontResources[i]);
452 fontRef->unref(); // SkRefPtr and new both took a reference.
453 fonts->insert(name.get(), fontRef.get());
454 }
455 fResourceDict->insert("Font", fonts.get());
456 }
457
458 // For compatibility, add all proc sets (only used for output to PS
459 // devices).
460 const char procs[][7] = {"PDF", "Text", "ImageB", "ImageC", "ImageI"};
461 SkRefPtr<SkPDFArray> procSets = new SkPDFArray();
462 procSets->unref(); // SkRefPtr and new both took a reference.
463 procSets->reserve(SK_ARRAY_COUNT(procs));
464 for (size_t i = 0; i < SK_ARRAY_COUNT(procs); i++) {
465 SkRefPtr<SkPDFName> entry = new SkPDFName(procs[i]);
466 entry->unref(); // SkRefPtr and new both took a reference.
467 procSets->append(entry.get());
468 }
469 fResourceDict->insert("ProcSet", procSets.get());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000470 }
471 return fResourceDict;
472}
473
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000474void SkPDFDevice::getResources(SkTDArray<SkPDFObject*>* resourceList) const {
475 resourceList->setReserve(resourceList->count() +
476 fGraphicStateResources.count() +
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000477 fXObjectResources.count() +
478 fFontResources.count());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000479 for (int i = 0; i < fGraphicStateResources.count(); i++) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000480 resourceList->push(fGraphicStateResources[i]);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000481 fGraphicStateResources[i]->ref();
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000482 fGraphicStateResources[i]->getResources(resourceList);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000483 }
484 for (int i = 0; i < fXObjectResources.count(); i++) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000485 resourceList->push(fXObjectResources[i]);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000486 fXObjectResources[i]->ref();
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000487 fXObjectResources[i]->getResources(resourceList);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000488 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000489 for (int i = 0; i < fFontResources.count(); i++) {
490 resourceList->push(fFontResources[i]);
491 fFontResources[i]->ref();
492 fFontResources[i]->getResources(resourceList);
493 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000494}
495
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000496SkRefPtr<SkPDFArray> SkPDFDevice::getMediaBox() const {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000497 SkRefPtr<SkPDFInt> zero = new SkPDFInt(0);
498 zero->unref(); // SkRefPtr and new both took a reference.
499 SkRefPtr<SkPDFInt> width = new SkPDFInt(fWidth);
500 width->unref(); // SkRefPtr and new both took a reference.
501 SkRefPtr<SkPDFInt> height = new SkPDFInt(fHeight);
502 height->unref(); // SkRefPtr and new both took a reference.
503 SkRefPtr<SkPDFArray> mediaBox = new SkPDFArray();
504 mediaBox->unref(); // SkRefPtr and new both took a reference.
505 mediaBox->reserve(4);
506 mediaBox->append(zero.get());
507 mediaBox->append(zero.get());
508 mediaBox->append(width.get());
509 mediaBox->append(height.get());
510 return mediaBox;
511}
512
vandebo@chromium.orgddbbd802010-10-26 19:45:06 +0000513SkString SkPDFDevice::content(bool flipOrigin) const {
514 SkString result;
515 // Scale and translate to move the origin from the lower left to the
516 // upper left.
517 if (flipOrigin)
518 result.printf("1 0 0 -1 0 %d cm\n", fHeight);
519 result.append(fContent);
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000520 for (int i = 0; i < fGraphicStackIndex; i++)
521 result.append("Q\n");
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000522 return result;
523}
524
525// Private
526
527// TODO(vandebo) handle these cases.
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000528#define PAINTCHECK(x,y) NOT_IMPLEMENTED(newPaint.x() y, false)
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000529
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000530void SkPDFDevice::updateGSFromPaint(const SkPaint& newPaint, bool forText) {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000531 PAINTCHECK(getXfermode, != NULL);
532 PAINTCHECK(getPathEffect, != NULL);
533 PAINTCHECK(getMaskFilter, != NULL);
534 PAINTCHECK(getShader, != NULL);
535 PAINTCHECK(getColorFilter, != NULL);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000536
537 SkRefPtr<SkPDFGraphicState> newGraphicState =
538 SkPDFGraphicState::getGraphicStateForPaint(newPaint);
539 newGraphicState->unref(); // getGraphicState and SkRefPtr both took a ref.
540 // newGraphicState has been canonicalized so we can directly compare
541 // pointers.
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000542 if (fGraphicStack[fGraphicStackIndex].fGraphicState.get() !=
543 newGraphicState.get()) {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000544 int resourceIndex = fGraphicStateResources.find(newGraphicState.get());
545 if (resourceIndex < 0) {
546 resourceIndex = fGraphicStateResources.count();
547 fGraphicStateResources.push(newGraphicState.get());
548 newGraphicState->ref();
549 }
550 fContent.append("/G");
551 fContent.appendS32(resourceIndex);
552 fContent.append(" gs\n");
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000553 fGraphicStack[fGraphicStackIndex].fGraphicState = newGraphicState;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000554 }
555
556 SkColor newColor = newPaint.getColor();
557 newColor = SkColorSetA(newColor, 0xFF);
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000558 if (fGraphicStack[fGraphicStackIndex].fColor != newColor) {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000559 SkString colorString = toPDFColor(newColor);
560 fContent.append(colorString);
561 fContent.append("RG ");
562 fContent.append(colorString);
563 fContent.append("rg\n");
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000564 fGraphicStack[fGraphicStackIndex].fColor = newColor;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000565 }
566
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000567 if (forText) {
568 uint32_t fontID = SkTypeface::UniqueID(newPaint.getTypeface());
569 if (fGraphicStack[fGraphicStackIndex].fTextSize !=
570 newPaint.getTextSize() ||
571 fGraphicStack[fGraphicStackIndex].fFont.get() == NULL ||
572 fGraphicStack[fGraphicStackIndex].fFont->fontID() != fontID) {
573 int fontIndex = getFontResourceIndex(fontID);
574 fContent.append("/F");
575 fContent.appendS32(fontIndex);
576 fContent.append(" ");
577 fContent.appendScalar(newPaint.getTextSize());
578 fContent.append(" Tf\n");
579 fGraphicStack[fGraphicStackIndex].fTextSize =
580 newPaint.getTextSize();
581 fGraphicStack[fGraphicStackIndex].fFont = fFontResources[fontIndex];
582 }
583
584 if (fGraphicStack[fGraphicStackIndex].fTextScaleX !=
585 newPaint.getTextScaleX()) {
586 SkScalar scale = newPaint.getTextScaleX();
587 SkScalar pdfScale = SkScalarMul(scale, SkIntToScalar(100));
588 fContent.appendScalar(pdfScale);
589 fContent.append(" Tz\n");
590 fGraphicStack[fGraphicStackIndex].fTextScaleX = scale;
591 }
592
593 if (fGraphicStack[fGraphicStackIndex].fTextFill !=
594 newPaint.getStyle()) {
595 SK_COMPILE_ASSERT(SkPaint::kFill_Style == 0, enum_must_match_value);
596 SK_COMPILE_ASSERT(SkPaint::kStroke_Style == 1,
597 enum_must_match_value);
598 SK_COMPILE_ASSERT(SkPaint::kStrokeAndFill_Style == 2,
599 enum_must_match_value);
600 fContent.appendS32(newPaint.getStyle());
601 fContent.append(" Tr\n");
602 fGraphicStack[fGraphicStackIndex].fTextFill = newPaint.getStyle();
603 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000604 }
605}
606
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000607int SkPDFDevice::getFontResourceIndex(uint32_t fontID) {
608 SkRefPtr<SkPDFFont> newFont = SkPDFFont::getFontResouceByID(fontID);
609 newFont->unref(); // getFontResourceByID and SkRefPtr both took a ref.
610 int resourceIndex = fFontResources.find(newFont.get());
611 if (resourceIndex < 0) {
612 resourceIndex = fFontResources.count();
613 fFontResources.push(newFont.get());
614 newFont->ref();
615 }
616 return resourceIndex;
617}
618
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000619void SkPDFDevice::moveTo(SkScalar x, SkScalar y) {
620 fContent.appendScalar(x);
621 fContent.append(" ");
622 fContent.appendScalar(y);
623 fContent.append(" m\n");
624}
625
626void SkPDFDevice::appendLine(SkScalar x, SkScalar y) {
627 fContent.appendScalar(x);
628 fContent.append(" ");
629 fContent.appendScalar(y);
630 fContent.append(" l\n");
631}
632
633void SkPDFDevice::appendCubic(SkScalar ctl1X, SkScalar ctl1Y,
634 SkScalar ctl2X, SkScalar ctl2Y,
635 SkScalar dstX, SkScalar dstY) {
636 SkString cmd("y\n");
637 fContent.appendScalar(ctl1X);
638 fContent.append(" ");
639 fContent.appendScalar(ctl1Y);
640 fContent.append(" ");
641 if (ctl2X != dstX || ctl2Y != dstY) {
642 cmd.set("c\n");
643 fContent.appendScalar(ctl2X);
644 fContent.append(" ");
645 fContent.appendScalar(ctl2Y);
646 fContent.append(" ");
647 }
648 fContent.appendScalar(dstX);
649 fContent.append(" ");
650 fContent.appendScalar(dstY);
651 fContent.append(cmd);
652}
653
654void SkPDFDevice::appendRectangle(SkScalar x, SkScalar y,
655 SkScalar w, SkScalar h) {
656 fContent.appendScalar(x);
657 fContent.append(" ");
658 fContent.appendScalar(y);
659 fContent.append(" ");
660 fContent.appendScalar(w);
661 fContent.append(" ");
662 fContent.appendScalar(h);
663 fContent.append(" re\n");
664}
665
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000666void SkPDFDevice::emitPath(const SkPath& path) {
667 SkPoint args[4];
668 SkPath::Iter iter(path, false);
669 for (SkPath::Verb verb = iter.next(args);
670 verb != SkPath::kDone_Verb;
671 verb = iter.next(args)) {
672 // args gets all the points, even the implicit first point.
673 switch (verb) {
674 case SkPath::kMove_Verb:
675 moveTo(args[0].fX, args[0].fY);
676 break;
677 case SkPath::kLine_Verb:
678 appendLine(args[1].fX, args[1].fY);
679 break;
680 case SkPath::kQuad_Verb: {
681 // Convert quad to cubic (degree elevation). http://goo.gl/vS4i
682 const SkScalar three = SkIntToScalar(3);
683 args[1].scale(SkIntToScalar(2));
684 SkScalar ctl1X = SkScalarDiv(args[0].fX + args[1].fX, three);
685 SkScalar ctl1Y = SkScalarDiv(args[0].fY + args[1].fY, three);
686 SkScalar ctl2X = SkScalarDiv(args[2].fX + args[1].fX, three);
687 SkScalar ctl2Y = SkScalarDiv(args[2].fY + args[1].fY, three);
688 appendCubic(ctl1X, ctl1Y, ctl2X, ctl2Y, args[2].fX, args[2].fY);
689 break;
690 }
691 case SkPath::kCubic_Verb:
692 appendCubic(args[1].fX, args[1].fY, args[2].fX, args[2].fY,
693 args[3].fX, args[3].fY);
694 break;
695 case SkPath::kClose_Verb:
696 closePath();
697 break;
698 case SkPath::kDone_Verb:
699 break;
700 default:
701 SkASSERT(false);
702 break;
703 }
704 }
705}
706
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000707void SkPDFDevice::closePath() {
708 fContent.append("h\n");
709}
710
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000711void SkPDFDevice::paintPath(SkPaint::Style style, SkPath::FillType fill) {
712 if (style == SkPaint::kFill_Style)
713 fContent.append("f");
714 else if (style == SkPaint::kStrokeAndFill_Style)
715 fContent.append("B");
716 else if (style == SkPaint::kStroke_Style)
717 fContent.append("S");
718
719 if (style != SkPaint::kStroke_Style) {
720 // Not supported yet.
721 NOT_IMPLEMENTED(fill == SkPath::kInverseEvenOdd_FillType, false);
722 NOT_IMPLEMENTED(fill == SkPath::kInverseWinding_FillType, false);
723 if (fill == SkPath::kEvenOdd_FillType)
724 fContent.append("*");
725 }
726 fContent.append("\n");
727}
728
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000729void SkPDFDevice::strokePath() {
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000730 paintPath(SkPaint::kStroke_Style, SkPath::kWinding_FillType);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000731}
732
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000733void SkPDFDevice::pushGS() {
734 SkASSERT(fGraphicStackIndex < 2);
735 fContent.append("q\n");
736 fGraphicStackIndex++;
737 fGraphicStack[fGraphicStackIndex] = fGraphicStack[fGraphicStackIndex - 1];
738}
739
740void SkPDFDevice::popGS() {
741 SkASSERT(fGraphicStackIndex > 0);
742 fContent.append("Q\n");
743 fGraphicStackIndex--;
744}
745
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000746void SkPDFDevice::setTextTransform(SkScalar x, SkScalar y, SkScalar textSkewX) {
747 // Flip the text about the x-axis to account for origin swap and include
748 // the passed parameters.
749 fContent.append("1 0 ");
750 fContent.appendScalar(0 - textSkewX);
751 fContent.append(" -1 ");
752 fContent.appendScalar(x);
753 fContent.append(" ");
754 fContent.appendScalar(y);
755 fContent.append(" Tm\n");
756}
757
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000758void SkPDFDevice::internalDrawBitmap(const SkMatrix& matrix,
759 const SkBitmap& bitmap,
760 const SkPaint& paint) {
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000761 SkMatrix scaled;
762 // Adjust for origin flip.
763 scaled.setScale(1, -1);
764 scaled.postTranslate(0, 1);
765 // Scale the image up from 1x1 to WxH.
766 scaled.postScale(bitmap.width(), bitmap.height());
767 scaled.postConcat(matrix);
768 SkMatrix curTransform = setTransform(scaled);
769
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000770 SkPDFImage* image = new SkPDFImage(bitmap, paint);
771 fXObjectResources.push(image); // Transfer reference.
772 fContent.append("/X");
773 fContent.appendS32(fXObjectResources.count() - 1);
774 fContent.append(" Do\n");
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000775 setTransform(curTransform);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000776}
777
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000778SkMatrix SkPDFDevice::setTransform(const SkMatrix& m) {
779 SkMatrix old = fGraphicStack[fGraphicStackIndex].fTransform;
780 if (old == m)
781 return old;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000782
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000783 if (old.getType() != SkMatrix::kIdentity_Mask) {
784 SkASSERT(fGraphicStackIndex > 0);
785 SkASSERT(fGraphicStack[fGraphicStackIndex - 1].fTransform.getType() ==
786 SkMatrix::kIdentity_Mask);
787 SkASSERT(fGraphicStack[fGraphicStackIndex].fClip ==
788 fGraphicStack[fGraphicStackIndex - 1].fClip);
789 popGS();
790 }
791 if (m.getType() == SkMatrix::kIdentity_Mask)
792 return old;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000793
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000794 if (fGraphicStackIndex == 0 || fGraphicStack[fGraphicStackIndex].fClip !=
795 fGraphicStack[fGraphicStackIndex - 1].fClip)
796 pushGS();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000797
vandebo@chromium.orgddbbd802010-10-26 19:45:06 +0000798 SkScalar transform[6];
799 SkAssertResult(m.pdfTransform(transform));
800 for (size_t i = 0; i < SK_ARRAY_COUNT(transform); i++) {
801 fContent.appendScalar(transform[i]);
802 fContent.append(" ");
803 }
804 fContent.append("cm\n");
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000805 fGraphicStack[fGraphicStackIndex].fTransform = m;
806
807 return old;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000808}