blob: cc65413328f6e1a4a75bddec4e49163aa371842d [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
121SkDevice* SkPDFDeviceFactory::newDevice(SkBitmap::Config config,
122 int width, int height,
123 bool isOpaque, bool isForLayer) {
124 return SkNEW_ARGS(SkPDFDevice, (width, height));
125}
126
127SkPDFDevice::SkPDFDevice(int width, int height)
128 : fWidth(width),
129 fHeight(height),
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000130 fGraphicStackIndex(0) {
131 fGraphicStack[0].fColor = SK_ColorBLACK;
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000132 fGraphicStack[0].fTextSize = SK_ScalarNaN; // This has no default value.
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000133 fGraphicStack[0].fTextScaleX = SK_Scalar1;
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000134 fGraphicStack[0].fTextFill = SkPaint::kFill_Style;
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000135 fGraphicStack[0].fClip.setRect(0,0, width, height);
136 fGraphicStack[0].fTransform.reset();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000137}
138
139SkPDFDevice::~SkPDFDevice() {
140 fGraphicStateResources.unrefAll();
141 fXObjectResources.unrefAll();
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000142 fFontResources.unrefAll();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000143}
144
145void SkPDFDevice::setMatrixClip(const SkMatrix& matrix,
146 const SkRegion& region) {
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000147 // See the comment in the header file above GraphicStackEntry.
148 if (region != fGraphicStack[fGraphicStackIndex].fClip) {
149 while (fGraphicStackIndex > 0)
150 popGS();
151 pushGS();
152
153 SkPath clipPath;
154 if (!region.getBoundaryPath(&clipPath))
155 clipPath.moveTo(SkIntToScalar(-1), SkIntToScalar(-1));
156 emitPath(clipPath);
157
158 SkPath::FillType clipFill = clipPath.getFillType();
159 NOT_IMPLEMENTED(clipFill == SkPath::kInverseEvenOdd_FillType, false);
160 NOT_IMPLEMENTED(clipFill == SkPath::kInverseWinding_FillType, false);
161 if (clipFill == SkPath::kEvenOdd_FillType)
162 fContent.append("W* n ");
163 else
164 fContent.append("W n ");
165
166 fGraphicStack[fGraphicStackIndex].fClip = region;
167 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000168 setTransform(matrix);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000169}
170
171void SkPDFDevice::drawPaint(const SkDraw& d, const SkPaint& paint) {
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000172 SkMatrix identityTransform;
173 identityTransform.reset();
174 SkMatrix curTransform = setTransform(identityTransform);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000175
176 SkPaint newPaint = paint;
177 newPaint.setStyle(SkPaint::kFill_Style);
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000178 updateGSFromPaint(newPaint, false);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000179
180 SkRect all = SkRect::MakeWH(width() + 1, height() + 1);
181 drawRect(d, all, newPaint);
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000182 setTransform(curTransform);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000183}
184
185void SkPDFDevice::drawPoints(const SkDraw& d, SkCanvas::PointMode mode,
186 size_t count, const SkPoint* points,
187 const SkPaint& paint) {
188 if (count == 0)
189 return;
190
191 switch (mode) {
192 case SkCanvas::kPolygon_PointMode:
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000193 updateGSFromPaint(paint, false);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000194 moveTo(points[0].fX, points[0].fY);
195 for (size_t i = 1; i < count; i++)
196 appendLine(points[i].fX, points[i].fY);
197 strokePath();
198 break;
199 case SkCanvas::kLines_PointMode:
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000200 updateGSFromPaint(paint, false);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000201 for (size_t i = 0; i < count/2; i++) {
202 moveTo(points[i * 2].fX, points[i * 2].fY);
203 appendLine(points[i * 2 + 1].fX, points[i * 2 + 1].fY);
204 strokePath();
205 }
206 break;
207 case SkCanvas::kPoints_PointMode:
208 if (paint.getStrokeCap() == SkPaint::kRound_Cap) {
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000209 updateGSFromPaint(paint, false);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000210 for (size_t i = 0; i < count; i++) {
211 moveTo(points[i].fX, points[i].fY);
212 strokePath();
213 }
214 } else {
215 // PDF won't draw a single point with square/butt caps because
216 // the orientation is ambiguous. Draw a rectangle instead.
217 SkPaint newPaint = paint;
218 newPaint.setStyle(SkPaint::kFill_Style);
219 SkScalar strokeWidth = paint.getStrokeWidth();
220 SkScalar halfStroke = strokeWidth * SK_ScalarHalf;
221 for (size_t i = 0; i < count; i++) {
222 SkRect r = SkRect::MakeXYWH(points[i].fX, points[i].fY,
223 0, 0);
224 r.inset(-halfStroke, -halfStroke);
225 drawRect(d, r, newPaint);
226 }
227 }
228 break;
229 default:
230 SkASSERT(false);
231 }
232}
233
234void SkPDFDevice::drawRect(const SkDraw& d, const SkRect& r,
235 const SkPaint& paint) {
236 if (paint.getPathEffect()) {
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000237 // Create a path for the rectangle and apply the path effect to it.
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000238 SkPath path;
239 path.addRect(r);
240 paint.getFillPath(path, &path);
241
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000242 SkPaint noEffectPaint(paint);
243 SkSafeUnref(noEffectPaint.setPathEffect(NULL));
244 drawPath(d, path, noEffectPaint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000245 return;
246 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000247 updateGSFromPaint(paint, false);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000248
249 // Skia has 0,0 at top left, pdf at bottom left. Do the right thing.
250 SkScalar bottom = r.fBottom < r.fTop ? r.fBottom : r.fTop;
251 appendRectangle(r.fLeft, bottom, r.width(), r.height());
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000252 paintPath(paint.getStyle(), SkPath::kWinding_FillType);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000253}
254
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000255void SkPDFDevice::drawPath(const SkDraw& d, const SkPath& path,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000256 const SkPaint& paint) {
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000257 if (paint.getPathEffect()) {
258 // Apply the path effect to path and draw it that way.
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000259 SkPath noEffectPath;
260 paint.getFillPath(path, &noEffectPath);
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000261
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000262 SkPaint noEffectPaint(paint);
263 SkSafeUnref(noEffectPaint.setPathEffect(NULL));
264 drawPath(d, noEffectPath, noEffectPaint);
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000265 return;
266 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000267 updateGSFromPaint(paint, false);
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000268
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000269 emitPath(path);
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000270 paintPath(paint.getStyle(), path.getFillType());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000271}
272
273void SkPDFDevice::drawBitmap(const SkDraw&, const SkBitmap& bitmap,
274 const SkMatrix& matrix, const SkPaint& paint) {
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000275 SkMatrix transform = matrix;
276 transform.postConcat(fGraphicStack[fGraphicStackIndex].fTransform);
277 internalDrawBitmap(transform, bitmap, paint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000278}
279
280void SkPDFDevice::drawSprite(const SkDraw&, const SkBitmap& bitmap,
281 int x, int y, const SkPaint& paint) {
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000282 SkMatrix matrix;
283 matrix.setTranslate(x, y);
284 internalDrawBitmap(matrix, bitmap, paint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000285}
286
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000287void SkPDFDevice::drawText(const SkDraw& d, const void* text, size_t len,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000288 SkScalar x, SkScalar y, const SkPaint& paint) {
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000289 SkPaint textPaint = calculateTextPaint(paint);
290 updateGSFromPaint(textPaint, true);
291 SkPDFFont* font = fGraphicStack[fGraphicStackIndex].fFont.get();
292
293 uint16_t glyphs[len];
294 size_t glyphsLength;
295 glyphsLength = font->textToPDFGlyphs(text, len, textPaint, glyphs, len);
296 textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
297
298 SkScalar width;
299 SkScalar* widthPtr = NULL;
300 if (textPaint.isUnderlineText() || textPaint.isStrikeThruText())
301 widthPtr = &width;
302
303 SkDrawCacheProc glyphCacheProc = textPaint.getDrawCacheProc();
304 alignText(glyphCacheProc, textPaint, glyphs, glyphsLength, &x, &y,
305 widthPtr);
306 fContent.append("BT\n");
307 setTextTransform(x, y, textPaint.getTextSkewX());
308 fContent.append(SkPDFString::formatString(glyphs, glyphsLength,
309 font->multiByteGlyphs()));
310 fContent.append(" Tj\nET\n");
311
312 // Draw underline and/or strikethrough if the paint has them.
313 // drawPosText() and drawTextOnPath() don't draw underline or strikethrough
314 // because the raster versions don't. Use paint instead of textPaint
315 // because we may have changed strokeWidth to do fakeBold text.
316 if (paint.isUnderlineText() || paint.isStrikeThruText()) {
317 SkScalar textSize = paint.getTextSize();
318 SkScalar height = SkScalarMul(textSize, kStdUnderline_Thickness);
319
320 if (paint.isUnderlineText()) {
321 SkScalar top = SkScalarMulAdd(textSize, kStdUnderline_Offset, y);
322 SkRect r = SkRect::MakeXYWH(x, top - height, width, height);
323 drawRect(d, r, paint);
324 }
325 if (paint.isStrikeThruText()) {
326 SkScalar top = SkScalarMulAdd(textSize, kStdStrikeThru_Offset, y);
327 SkRect r = SkRect::MakeXYWH(x, top - height, width, height);
328 drawRect(d, r, paint);
329 }
330 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000331}
332
333void SkPDFDevice::drawPosText(const SkDraw&, const void* text, size_t len,
334 const SkScalar pos[], SkScalar constY,
335 int scalarsPerPos, const SkPaint& paint) {
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000336 SkASSERT(1 == scalarsPerPos || 2 == scalarsPerPos);
337 SkPaint textPaint = calculateTextPaint(paint);
338 updateGSFromPaint(textPaint, true);
339 SkPDFFont* font = fGraphicStack[fGraphicStackIndex].fFont.get();
340
341 uint16_t glyphs[len];
342 size_t glyphsLength;
343 glyphsLength = font->textToPDFGlyphs(text, len, textPaint, glyphs, len);
344 textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
345
346 SkDrawCacheProc glyphCacheProc = textPaint.getDrawCacheProc();
347 fContent.append("BT\n");
348 for (size_t i = 0; i < glyphsLength; i++) {
349 SkScalar x = pos[i * scalarsPerPos];
350 SkScalar y = scalarsPerPos == 1 ? constY : pos[i * scalarsPerPos + 1];
351 alignText(glyphCacheProc, textPaint, glyphs + i, 1, &x, &y, NULL);
352 setTextTransform(x, y, textPaint.getTextSkewX());
353 fContent.append(SkPDFString::formatString(glyphs + i, 1,
354 font->multiByteGlyphs()));
355 fContent.append(" Tj\n");
356 }
357 fContent.append("ET\n");
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000358}
359
360void SkPDFDevice::drawTextOnPath(const SkDraw&, const void* text, size_t len,
361 const SkPath& path, const SkMatrix* matrix,
362 const SkPaint& paint) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000363 NOT_IMPLEMENTED("drawTextOnPath", true);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000364}
365
366void SkPDFDevice::drawVertices(const SkDraw&, SkCanvas::VertexMode,
367 int vertexCount, const SkPoint verts[],
368 const SkPoint texs[], const SkColor colors[],
369 SkXfermode* xmode, const uint16_t indices[],
370 int indexCount, const SkPaint& paint) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000371 NOT_IMPLEMENTED("drawVerticies", true);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000372}
373
vandebo@chromium.orgeb6c7592010-10-26 19:54:45 +0000374void SkPDFDevice::drawDevice(const SkDraw& d, SkDevice* device, int x, int y,
375 const SkPaint& paint) {
376 if ((device->getDeviceCapabilities() & kVector_Capability) == 0) {
377 // If we somehow get a raster device, do what our parent would do.
378 SkDevice::drawDevice(d, device, x, y, paint);
379 return;
380 }
381
382 // Assume that a vector capable device means that it's a PDF Device.
383 // TODO(vandebo) handle the paint (alpha and compositing mode).
384 SkMatrix matrix;
385 matrix.setTranslate(x, y);
386 SkPDFDevice* pdfDevice = static_cast<SkPDFDevice*>(device);
387
388 SkPDFFormXObject* xobject = new SkPDFFormXObject(pdfDevice, matrix);
389 fXObjectResources.push(xobject); // Transfer reference.
390 fContent.append("/X");
391 fContent.appendS32(fXObjectResources.count() - 1);
392 fContent.append(" Do\n");
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000393}
394
395const SkRefPtr<SkPDFDict>& SkPDFDevice::getResourceDict() {
396 if (fResourceDict.get() == NULL) {
397 fResourceDict = new SkPDFDict;
398 fResourceDict->unref(); // SkRefPtr and new both took a reference.
399
400 if (fGraphicStateResources.count()) {
401 SkRefPtr<SkPDFDict> extGState = new SkPDFDict();
402 extGState->unref(); // SkRefPtr and new both took a reference.
403 for (int i = 0; i < fGraphicStateResources.count(); i++) {
404 SkString nameString("G");
405 nameString.appendS32(i);
406 SkRefPtr<SkPDFName> name = new SkPDFName(nameString);
407 name->unref(); // SkRefPtr and new both took a reference.
408 SkRefPtr<SkPDFObjRef> gsRef =
409 new SkPDFObjRef(fGraphicStateResources[i]);
410 gsRef->unref(); // SkRefPtr and new both took a reference.
411 extGState->insert(name.get(), gsRef.get());
412 }
413 fResourceDict->insert("ExtGState", extGState.get());
414 }
415
416 if (fXObjectResources.count()) {
417 SkRefPtr<SkPDFDict> xObjects = new SkPDFDict();
418 xObjects->unref(); // SkRefPtr and new both took a reference.
419 for (int i = 0; i < fXObjectResources.count(); i++) {
420 SkString nameString("X");
421 nameString.appendS32(i);
422 SkRefPtr<SkPDFName> name = new SkPDFName(nameString);
423 name->unref(); // SkRefPtr and new both took a reference.
424 SkRefPtr<SkPDFObjRef> xObjRef =
425 new SkPDFObjRef(fXObjectResources[i]);
426 xObjRef->unref(); // SkRefPtr and new both took a reference.
427 xObjects->insert(name.get(), xObjRef.get());
428 }
429 fResourceDict->insert("XObject", xObjects.get());
430 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000431
432 if (fFontResources.count()) {
433 SkRefPtr<SkPDFDict> fonts = new SkPDFDict();
434 fonts->unref(); // SkRefPtr and new both took a reference.
435 for (int i = 0; i < fFontResources.count(); i++) {
436 SkString nameString("F");
437 nameString.appendS32(i);
438 SkRefPtr<SkPDFName> name = new SkPDFName(nameString);
439 name->unref(); // SkRefPtr and new both took a reference.
440 SkRefPtr<SkPDFObjRef> fontRef =
441 new SkPDFObjRef(fFontResources[i]);
442 fontRef->unref(); // SkRefPtr and new both took a reference.
443 fonts->insert(name.get(), fontRef.get());
444 }
445 fResourceDict->insert("Font", fonts.get());
446 }
447
448 // For compatibility, add all proc sets (only used for output to PS
449 // devices).
450 const char procs[][7] = {"PDF", "Text", "ImageB", "ImageC", "ImageI"};
451 SkRefPtr<SkPDFArray> procSets = new SkPDFArray();
452 procSets->unref(); // SkRefPtr and new both took a reference.
453 procSets->reserve(SK_ARRAY_COUNT(procs));
454 for (size_t i = 0; i < SK_ARRAY_COUNT(procs); i++) {
455 SkRefPtr<SkPDFName> entry = new SkPDFName(procs[i]);
456 entry->unref(); // SkRefPtr and new both took a reference.
457 procSets->append(entry.get());
458 }
459 fResourceDict->insert("ProcSet", procSets.get());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000460 }
461 return fResourceDict;
462}
463
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000464void SkPDFDevice::getResources(SkTDArray<SkPDFObject*>* resourceList) const {
465 resourceList->setReserve(resourceList->count() +
466 fGraphicStateResources.count() +
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000467 fXObjectResources.count() +
468 fFontResources.count());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000469 for (int i = 0; i < fGraphicStateResources.count(); i++) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000470 resourceList->push(fGraphicStateResources[i]);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000471 fGraphicStateResources[i]->ref();
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000472 fGraphicStateResources[i]->getResources(resourceList);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000473 }
474 for (int i = 0; i < fXObjectResources.count(); i++) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000475 resourceList->push(fXObjectResources[i]);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000476 fXObjectResources[i]->ref();
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000477 fXObjectResources[i]->getResources(resourceList);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000478 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000479 for (int i = 0; i < fFontResources.count(); i++) {
480 resourceList->push(fFontResources[i]);
481 fFontResources[i]->ref();
482 fFontResources[i]->getResources(resourceList);
483 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000484}
485
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000486SkRefPtr<SkPDFArray> SkPDFDevice::getMediaBox() const {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000487 SkRefPtr<SkPDFInt> zero = new SkPDFInt(0);
488 zero->unref(); // SkRefPtr and new both took a reference.
489 SkRefPtr<SkPDFInt> width = new SkPDFInt(fWidth);
490 width->unref(); // SkRefPtr and new both took a reference.
491 SkRefPtr<SkPDFInt> height = new SkPDFInt(fHeight);
492 height->unref(); // SkRefPtr and new both took a reference.
493 SkRefPtr<SkPDFArray> mediaBox = new SkPDFArray();
494 mediaBox->unref(); // SkRefPtr and new both took a reference.
495 mediaBox->reserve(4);
496 mediaBox->append(zero.get());
497 mediaBox->append(zero.get());
498 mediaBox->append(width.get());
499 mediaBox->append(height.get());
500 return mediaBox;
501}
502
vandebo@chromium.orgddbbd802010-10-26 19:45:06 +0000503SkString SkPDFDevice::content(bool flipOrigin) const {
504 SkString result;
505 // Scale and translate to move the origin from the lower left to the
506 // upper left.
507 if (flipOrigin)
508 result.printf("1 0 0 -1 0 %d cm\n", fHeight);
509 result.append(fContent);
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000510 for (int i = 0; i < fGraphicStackIndex; i++)
511 result.append("Q\n");
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000512 return result;
513}
514
515// Private
516
517// TODO(vandebo) handle these cases.
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000518#define PAINTCHECK(x,y) NOT_IMPLEMENTED(newPaint.x() y, false)
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000519
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000520void SkPDFDevice::updateGSFromPaint(const SkPaint& newPaint, bool forText) {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000521 PAINTCHECK(getXfermode, != NULL);
522 PAINTCHECK(getPathEffect, != NULL);
523 PAINTCHECK(getMaskFilter, != NULL);
524 PAINTCHECK(getShader, != NULL);
525 PAINTCHECK(getColorFilter, != NULL);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000526
527 SkRefPtr<SkPDFGraphicState> newGraphicState =
528 SkPDFGraphicState::getGraphicStateForPaint(newPaint);
529 newGraphicState->unref(); // getGraphicState and SkRefPtr both took a ref.
530 // newGraphicState has been canonicalized so we can directly compare
531 // pointers.
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000532 if (fGraphicStack[fGraphicStackIndex].fGraphicState.get() !=
533 newGraphicState.get()) {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000534 int resourceIndex = fGraphicStateResources.find(newGraphicState.get());
535 if (resourceIndex < 0) {
536 resourceIndex = fGraphicStateResources.count();
537 fGraphicStateResources.push(newGraphicState.get());
538 newGraphicState->ref();
539 }
540 fContent.append("/G");
541 fContent.appendS32(resourceIndex);
542 fContent.append(" gs\n");
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000543 fGraphicStack[fGraphicStackIndex].fGraphicState = newGraphicState;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000544 }
545
546 SkColor newColor = newPaint.getColor();
547 newColor = SkColorSetA(newColor, 0xFF);
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000548 if (fGraphicStack[fGraphicStackIndex].fColor != newColor) {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000549 SkString colorString = toPDFColor(newColor);
550 fContent.append(colorString);
551 fContent.append("RG ");
552 fContent.append(colorString);
553 fContent.append("rg\n");
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000554 fGraphicStack[fGraphicStackIndex].fColor = newColor;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000555 }
556
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000557 if (forText) {
558 uint32_t fontID = SkTypeface::UniqueID(newPaint.getTypeface());
559 if (fGraphicStack[fGraphicStackIndex].fTextSize !=
560 newPaint.getTextSize() ||
561 fGraphicStack[fGraphicStackIndex].fFont.get() == NULL ||
562 fGraphicStack[fGraphicStackIndex].fFont->fontID() != fontID) {
563 int fontIndex = getFontResourceIndex(fontID);
564 fContent.append("/F");
565 fContent.appendS32(fontIndex);
566 fContent.append(" ");
567 fContent.appendScalar(newPaint.getTextSize());
568 fContent.append(" Tf\n");
569 fGraphicStack[fGraphicStackIndex].fTextSize =
570 newPaint.getTextSize();
571 fGraphicStack[fGraphicStackIndex].fFont = fFontResources[fontIndex];
572 }
573
574 if (fGraphicStack[fGraphicStackIndex].fTextScaleX !=
575 newPaint.getTextScaleX()) {
576 SkScalar scale = newPaint.getTextScaleX();
577 SkScalar pdfScale = SkScalarMul(scale, SkIntToScalar(100));
578 fContent.appendScalar(pdfScale);
579 fContent.append(" Tz\n");
580 fGraphicStack[fGraphicStackIndex].fTextScaleX = scale;
581 }
582
583 if (fGraphicStack[fGraphicStackIndex].fTextFill !=
584 newPaint.getStyle()) {
585 SK_COMPILE_ASSERT(SkPaint::kFill_Style == 0, enum_must_match_value);
586 SK_COMPILE_ASSERT(SkPaint::kStroke_Style == 1,
587 enum_must_match_value);
588 SK_COMPILE_ASSERT(SkPaint::kStrokeAndFill_Style == 2,
589 enum_must_match_value);
590 fContent.appendS32(newPaint.getStyle());
591 fContent.append(" Tr\n");
592 fGraphicStack[fGraphicStackIndex].fTextFill = newPaint.getStyle();
593 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000594 }
595}
596
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000597int SkPDFDevice::getFontResourceIndex(uint32_t fontID) {
598 SkRefPtr<SkPDFFont> newFont = SkPDFFont::getFontResouceByID(fontID);
599 newFont->unref(); // getFontResourceByID and SkRefPtr both took a ref.
600 int resourceIndex = fFontResources.find(newFont.get());
601 if (resourceIndex < 0) {
602 resourceIndex = fFontResources.count();
603 fFontResources.push(newFont.get());
604 newFont->ref();
605 }
606 return resourceIndex;
607}
608
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000609void SkPDFDevice::moveTo(SkScalar x, SkScalar y) {
610 fContent.appendScalar(x);
611 fContent.append(" ");
612 fContent.appendScalar(y);
613 fContent.append(" m\n");
614}
615
616void SkPDFDevice::appendLine(SkScalar x, SkScalar y) {
617 fContent.appendScalar(x);
618 fContent.append(" ");
619 fContent.appendScalar(y);
620 fContent.append(" l\n");
621}
622
623void SkPDFDevice::appendCubic(SkScalar ctl1X, SkScalar ctl1Y,
624 SkScalar ctl2X, SkScalar ctl2Y,
625 SkScalar dstX, SkScalar dstY) {
626 SkString cmd("y\n");
627 fContent.appendScalar(ctl1X);
628 fContent.append(" ");
629 fContent.appendScalar(ctl1Y);
630 fContent.append(" ");
631 if (ctl2X != dstX || ctl2Y != dstY) {
632 cmd.set("c\n");
633 fContent.appendScalar(ctl2X);
634 fContent.append(" ");
635 fContent.appendScalar(ctl2Y);
636 fContent.append(" ");
637 }
638 fContent.appendScalar(dstX);
639 fContent.append(" ");
640 fContent.appendScalar(dstY);
641 fContent.append(cmd);
642}
643
644void SkPDFDevice::appendRectangle(SkScalar x, SkScalar y,
645 SkScalar w, SkScalar h) {
646 fContent.appendScalar(x);
647 fContent.append(" ");
648 fContent.appendScalar(y);
649 fContent.append(" ");
650 fContent.appendScalar(w);
651 fContent.append(" ");
652 fContent.appendScalar(h);
653 fContent.append(" re\n");
654}
655
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000656void SkPDFDevice::emitPath(const SkPath& path) {
657 SkPoint args[4];
658 SkPath::Iter iter(path, false);
659 for (SkPath::Verb verb = iter.next(args);
660 verb != SkPath::kDone_Verb;
661 verb = iter.next(args)) {
662 // args gets all the points, even the implicit first point.
663 switch (verb) {
664 case SkPath::kMove_Verb:
665 moveTo(args[0].fX, args[0].fY);
666 break;
667 case SkPath::kLine_Verb:
668 appendLine(args[1].fX, args[1].fY);
669 break;
670 case SkPath::kQuad_Verb: {
671 // Convert quad to cubic (degree elevation). http://goo.gl/vS4i
672 const SkScalar three = SkIntToScalar(3);
673 args[1].scale(SkIntToScalar(2));
674 SkScalar ctl1X = SkScalarDiv(args[0].fX + args[1].fX, three);
675 SkScalar ctl1Y = SkScalarDiv(args[0].fY + args[1].fY, three);
676 SkScalar ctl2X = SkScalarDiv(args[2].fX + args[1].fX, three);
677 SkScalar ctl2Y = SkScalarDiv(args[2].fY + args[1].fY, three);
678 appendCubic(ctl1X, ctl1Y, ctl2X, ctl2Y, args[2].fX, args[2].fY);
679 break;
680 }
681 case SkPath::kCubic_Verb:
682 appendCubic(args[1].fX, args[1].fY, args[2].fX, args[2].fY,
683 args[3].fX, args[3].fY);
684 break;
685 case SkPath::kClose_Verb:
686 closePath();
687 break;
688 case SkPath::kDone_Verb:
689 break;
690 default:
691 SkASSERT(false);
692 break;
693 }
694 }
695}
696
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000697void SkPDFDevice::closePath() {
698 fContent.append("h\n");
699}
700
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000701void SkPDFDevice::paintPath(SkPaint::Style style, SkPath::FillType fill) {
702 if (style == SkPaint::kFill_Style)
703 fContent.append("f");
704 else if (style == SkPaint::kStrokeAndFill_Style)
705 fContent.append("B");
706 else if (style == SkPaint::kStroke_Style)
707 fContent.append("S");
708
709 if (style != SkPaint::kStroke_Style) {
710 // Not supported yet.
711 NOT_IMPLEMENTED(fill == SkPath::kInverseEvenOdd_FillType, false);
712 NOT_IMPLEMENTED(fill == SkPath::kInverseWinding_FillType, false);
713 if (fill == SkPath::kEvenOdd_FillType)
714 fContent.append("*");
715 }
716 fContent.append("\n");
717}
718
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000719void SkPDFDevice::strokePath() {
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000720 paintPath(SkPaint::kStroke_Style, SkPath::kWinding_FillType);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000721}
722
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000723void SkPDFDevice::pushGS() {
724 SkASSERT(fGraphicStackIndex < 2);
725 fContent.append("q\n");
726 fGraphicStackIndex++;
727 fGraphicStack[fGraphicStackIndex] = fGraphicStack[fGraphicStackIndex - 1];
728}
729
730void SkPDFDevice::popGS() {
731 SkASSERT(fGraphicStackIndex > 0);
732 fContent.append("Q\n");
733 fGraphicStackIndex--;
734}
735
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000736void SkPDFDevice::setTextTransform(SkScalar x, SkScalar y, SkScalar textSkewX) {
737 // Flip the text about the x-axis to account for origin swap and include
738 // the passed parameters.
739 fContent.append("1 0 ");
740 fContent.appendScalar(0 - textSkewX);
741 fContent.append(" -1 ");
742 fContent.appendScalar(x);
743 fContent.append(" ");
744 fContent.appendScalar(y);
745 fContent.append(" Tm\n");
746}
747
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000748void SkPDFDevice::internalDrawBitmap(const SkMatrix& matrix,
749 const SkBitmap& bitmap,
750 const SkPaint& paint) {
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000751 SkMatrix scaled;
752 // Adjust for origin flip.
753 scaled.setScale(1, -1);
754 scaled.postTranslate(0, 1);
755 // Scale the image up from 1x1 to WxH.
756 scaled.postScale(bitmap.width(), bitmap.height());
757 scaled.postConcat(matrix);
758 SkMatrix curTransform = setTransform(scaled);
759
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000760 SkPDFImage* image = new SkPDFImage(bitmap, paint);
761 fXObjectResources.push(image); // Transfer reference.
762 fContent.append("/X");
763 fContent.appendS32(fXObjectResources.count() - 1);
764 fContent.append(" Do\n");
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000765 setTransform(curTransform);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000766}
767
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000768SkMatrix SkPDFDevice::setTransform(const SkMatrix& m) {
769 SkMatrix old = fGraphicStack[fGraphicStackIndex].fTransform;
770 if (old == m)
771 return old;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000772
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000773 if (old.getType() != SkMatrix::kIdentity_Mask) {
774 SkASSERT(fGraphicStackIndex > 0);
775 SkASSERT(fGraphicStack[fGraphicStackIndex - 1].fTransform.getType() ==
776 SkMatrix::kIdentity_Mask);
777 SkASSERT(fGraphicStack[fGraphicStackIndex].fClip ==
778 fGraphicStack[fGraphicStackIndex - 1].fClip);
779 popGS();
780 }
781 if (m.getType() == SkMatrix::kIdentity_Mask)
782 return old;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000783
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000784 if (fGraphicStackIndex == 0 || fGraphicStack[fGraphicStackIndex].fClip !=
785 fGraphicStack[fGraphicStackIndex - 1].fClip)
786 pushGS();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000787
vandebo@chromium.orgddbbd802010-10-26 19:45:06 +0000788 SkScalar transform[6];
789 SkAssertResult(m.pdfTransform(transform));
790 for (size_t i = 0; i < SK_ARRAY_COUNT(transform); i++) {
791 fContent.appendScalar(transform[i]);
792 fContent.append(" ");
793 }
794 fContent.append("cm\n");
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000795 fGraphicStack[fGraphicStackIndex].fTransform = m;
796
797 return old;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000798}