blob: fd458e0ea9a1659883fac3dfc612d103497b8f27 [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.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)
reed@google.com07700442010-12-20 19:46:07 +0000134 : SkDevice(NULL, makeABitmap(width, height), false),
135 fWidth(width),
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000136 fHeight(height),
reed@google.com07700442010-12-20 19:46:07 +0000137 fGraphicStackIndex(0) {
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.org2a22e102011-01-25 21:01:34 +0000142 fGraphicStack[0].fFont = NULL;
143 fGraphicStack[0].fGraphicState = NULL;
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000144 fGraphicStack[0].fClip.setRect(0,0, width, height);
145 fGraphicStack[0].fTransform.reset();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000146}
147
148SkPDFDevice::~SkPDFDevice() {
149 fGraphicStateResources.unrefAll();
150 fXObjectResources.unrefAll();
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000151 fFontResources.unrefAll();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000152}
153
154void SkPDFDevice::setMatrixClip(const SkMatrix& matrix,
155 const SkRegion& region) {
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000156 // See the comment in the header file above GraphicStackEntry.
157 if (region != fGraphicStack[fGraphicStackIndex].fClip) {
158 while (fGraphicStackIndex > 0)
159 popGS();
160 pushGS();
161
162 SkPath clipPath;
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000163 if (region.getBoundaryPath(&clipPath)) {
164 emitPath(clipPath);
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000165
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000166 SkPath::FillType clipFill = clipPath.getFillType();
167 NOT_IMPLEMENTED(clipFill == SkPath::kInverseEvenOdd_FillType,
168 false);
169 NOT_IMPLEMENTED(clipFill == SkPath::kInverseWinding_FillType,
170 false);
171 if (clipFill == SkPath::kEvenOdd_FillType)
172 fContent.append("W* n ");
173 else
174 fContent.append("W n ");
175 }
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000176
177 fGraphicStack[fGraphicStackIndex].fClip = region;
178 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000179 setTransform(matrix);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000180}
181
182void SkPDFDevice::drawPaint(const SkDraw& d, const SkPaint& paint) {
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000183 SkMatrix identityTransform;
184 identityTransform.reset();
185 SkMatrix curTransform = setTransform(identityTransform);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000186
187 SkPaint newPaint = paint;
188 newPaint.setStyle(SkPaint::kFill_Style);
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000189 updateGSFromPaint(newPaint, false);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000190
191 SkRect all = SkRect::MakeWH(width() + 1, height() + 1);
192 drawRect(d, all, newPaint);
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000193 setTransform(curTransform);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000194}
195
196void SkPDFDevice::drawPoints(const SkDraw& d, SkCanvas::PointMode mode,
197 size_t count, const SkPoint* points,
198 const SkPaint& paint) {
199 if (count == 0)
200 return;
201
202 switch (mode) {
203 case SkCanvas::kPolygon_PointMode:
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000204 updateGSFromPaint(paint, false);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000205 moveTo(points[0].fX, points[0].fY);
206 for (size_t i = 1; i < count; i++)
207 appendLine(points[i].fX, points[i].fY);
208 strokePath();
209 break;
210 case SkCanvas::kLines_PointMode:
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000211 updateGSFromPaint(paint, false);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000212 for (size_t i = 0; i < count/2; i++) {
213 moveTo(points[i * 2].fX, points[i * 2].fY);
214 appendLine(points[i * 2 + 1].fX, points[i * 2 + 1].fY);
215 strokePath();
216 }
217 break;
218 case SkCanvas::kPoints_PointMode:
219 if (paint.getStrokeCap() == SkPaint::kRound_Cap) {
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000220 updateGSFromPaint(paint, false);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000221 for (size_t i = 0; i < count; i++) {
222 moveTo(points[i].fX, points[i].fY);
223 strokePath();
224 }
225 } else {
226 // PDF won't draw a single point with square/butt caps because
227 // the orientation is ambiguous. Draw a rectangle instead.
228 SkPaint newPaint = paint;
229 newPaint.setStyle(SkPaint::kFill_Style);
230 SkScalar strokeWidth = paint.getStrokeWidth();
231 SkScalar halfStroke = strokeWidth * SK_ScalarHalf;
232 for (size_t i = 0; i < count; i++) {
233 SkRect r = SkRect::MakeXYWH(points[i].fX, points[i].fY,
234 0, 0);
235 r.inset(-halfStroke, -halfStroke);
236 drawRect(d, r, newPaint);
237 }
238 }
239 break;
240 default:
241 SkASSERT(false);
242 }
243}
244
245void SkPDFDevice::drawRect(const SkDraw& d, const SkRect& r,
246 const SkPaint& paint) {
247 if (paint.getPathEffect()) {
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000248 // Create a path for the rectangle and apply the path effect to it.
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000249 SkPath path;
250 path.addRect(r);
251 paint.getFillPath(path, &path);
252
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000253 SkPaint noEffectPaint(paint);
254 SkSafeUnref(noEffectPaint.setPathEffect(NULL));
255 drawPath(d, path, noEffectPaint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000256 return;
257 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000258 updateGSFromPaint(paint, false);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000259
260 // Skia has 0,0 at top left, pdf at bottom left. Do the right thing.
261 SkScalar bottom = r.fBottom < r.fTop ? r.fBottom : r.fTop;
262 appendRectangle(r.fLeft, bottom, r.width(), r.height());
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000263 paintPath(paint.getStyle(), SkPath::kWinding_FillType);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000264}
265
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000266void SkPDFDevice::drawPath(const SkDraw& d, const SkPath& path,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000267 const SkPaint& paint) {
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000268 if (paint.getPathEffect()) {
269 // Apply the path effect to path and draw it that way.
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000270 SkPath noEffectPath;
271 paint.getFillPath(path, &noEffectPath);
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000272
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000273 SkPaint noEffectPaint(paint);
274 SkSafeUnref(noEffectPaint.setPathEffect(NULL));
275 drawPath(d, noEffectPath, noEffectPaint);
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000276 return;
277 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000278 updateGSFromPaint(paint, false);
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000279
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000280 emitPath(path);
vandebo@chromium.org7d71f7f2010-10-26 19:51:44 +0000281 paintPath(paint.getStyle(), path.getFillType());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000282}
283
284void SkPDFDevice::drawBitmap(const SkDraw&, const SkBitmap& bitmap,
reed@android.comf2b98d62010-12-20 18:26:13 +0000285 const SkIRect* srcRect,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000286 const SkMatrix& matrix, const SkPaint& paint) {
reed@android.comf2b98d62010-12-20 18:26:13 +0000287 // TODO: respect srcRect if present
288
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000289 SkMatrix transform = matrix;
290 transform.postConcat(fGraphicStack[fGraphicStackIndex].fTransform);
291 internalDrawBitmap(transform, bitmap, paint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000292}
293
294void SkPDFDevice::drawSprite(const SkDraw&, const SkBitmap& bitmap,
295 int x, int y, const SkPaint& paint) {
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000296 SkMatrix matrix;
297 matrix.setTranslate(x, y);
298 internalDrawBitmap(matrix, bitmap, paint);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000299}
300
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000301void SkPDFDevice::drawText(const SkDraw& d, const void* text, size_t len,
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000302 SkScalar x, SkScalar y, const SkPaint& paint) {
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000303 SkPaint textPaint = calculateTextPaint(paint);
304 updateGSFromPaint(textPaint, true);
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000305
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000306 // Make sure we have a glyph id encoding.
307 SkAutoFree glyphStorage;
308 uint16_t* glyphIDs;
309 size_t numGlyphs;
310 if (paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding) {
311 numGlyphs = paint.textToGlyphs(text, len, NULL);
312 glyphIDs = (uint16_t*)sk_malloc_flags(numGlyphs * 2,
313 SK_MALLOC_TEMP | SK_MALLOC_THROW);
314 glyphStorage.set(glyphIDs);
315 paint.textToGlyphs(text, len, glyphIDs);
316 textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
317 } else {
318 SkASSERT((len & 1) == 0);
319 numGlyphs = len / 2;
320 glyphIDs = (uint16_t*)text;
321 }
322 SkAutoFree encodedStorage(
323 sk_malloc_flags(numGlyphs * 2, SK_MALLOC_TEMP | SK_MALLOC_THROW));
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000324
325 SkScalar width;
326 SkScalar* widthPtr = NULL;
327 if (textPaint.isUnderlineText() || textPaint.isStrikeThruText())
328 widthPtr = &width;
329
330 SkDrawCacheProc glyphCacheProc = textPaint.getDrawCacheProc();
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000331 alignText(glyphCacheProc, textPaint, glyphIDs, numGlyphs, &x, &y, widthPtr);
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000332 fContent.append("BT\n");
333 setTextTransform(x, y, textPaint.getTextSkewX());
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000334 size_t consumedGlyphCount = 0;
335 while (numGlyphs > consumedGlyphCount) {
336 updateFont(textPaint, glyphIDs[consumedGlyphCount]);
337 SkPDFFont* font = fGraphicStack[fGraphicStackIndex].fFont;
338 size_t encodedLength = numGlyphs * 2;
339 consumedGlyphCount += font->glyphsToPDFFontEncoding(
340 glyphIDs + consumedGlyphCount, numGlyphs - consumedGlyphCount,
341 encodedStorage.get(), &encodedLength);
342 if (font->multiByteGlyphs())
343 encodedLength /= 2;
344 fContent.append(
345 SkPDFString::formatString((const uint16_t*)encodedStorage.get(),
346 encodedLength,
347 font->multiByteGlyphs()));
348 fContent.append(" Tj\n");
349 }
350 fContent.append("ET\n");
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000351
352 // Draw underline and/or strikethrough if the paint has them.
353 // drawPosText() and drawTextOnPath() don't draw underline or strikethrough
354 // because the raster versions don't. Use paint instead of textPaint
355 // because we may have changed strokeWidth to do fakeBold text.
356 if (paint.isUnderlineText() || paint.isStrikeThruText()) {
357 SkScalar textSize = paint.getTextSize();
358 SkScalar height = SkScalarMul(textSize, kStdUnderline_Thickness);
359
360 if (paint.isUnderlineText()) {
361 SkScalar top = SkScalarMulAdd(textSize, kStdUnderline_Offset, y);
362 SkRect r = SkRect::MakeXYWH(x, top - height, width, height);
363 drawRect(d, r, paint);
364 }
365 if (paint.isStrikeThruText()) {
366 SkScalar top = SkScalarMulAdd(textSize, kStdStrikeThru_Offset, y);
367 SkRect r = SkRect::MakeXYWH(x, top - height, width, height);
368 drawRect(d, r, paint);
369 }
370 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000371}
372
373void SkPDFDevice::drawPosText(const SkDraw&, const void* text, size_t len,
374 const SkScalar pos[], SkScalar constY,
375 int scalarsPerPos, const SkPaint& paint) {
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000376 SkASSERT(1 == scalarsPerPos || 2 == scalarsPerPos);
377 SkPaint textPaint = calculateTextPaint(paint);
378 updateGSFromPaint(textPaint, true);
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000379
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000380 // Make sure we have a glyph id encoding.
381 SkAutoFree glyphStorage;
382 uint16_t* glyphIDs;
383 size_t numGlyphs;
384 if (paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding) {
385 numGlyphs = paint.textToGlyphs(text, len, NULL);
386 glyphIDs = (uint16_t*)sk_malloc_flags(numGlyphs * 2,
387 SK_MALLOC_TEMP | SK_MALLOC_THROW);
388 glyphStorage.set(glyphIDs);
389 paint.textToGlyphs(text, len, glyphIDs);
390 textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
391 } else {
392 SkASSERT((len & 1) == 0);
393 numGlyphs = len / 2;
394 glyphIDs = (uint16_t*)text;
395 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000396
397 SkDrawCacheProc glyphCacheProc = textPaint.getDrawCacheProc();
398 fContent.append("BT\n");
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000399 updateFont(textPaint, glyphIDs[0]);
400 for (size_t i = 0; i < numGlyphs; i++) {
401 SkPDFFont* font = fGraphicStack[fGraphicStackIndex].fFont;
402 uint16_t encodedValue;
403 size_t encodedLength = 2;
404 if (font->glyphsToPDFFontEncoding(glyphIDs + i, 1, &encodedValue,
405 &encodedLength) == 0) {
406 updateFont(textPaint, glyphIDs[i]);
407 i--;
408 continue;
409 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000410 SkScalar x = pos[i * scalarsPerPos];
411 SkScalar y = scalarsPerPos == 1 ? constY : pos[i * scalarsPerPos + 1];
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000412 alignText(glyphCacheProc, textPaint, glyphIDs + i, 1, &x, &y, NULL);
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000413 setTextTransform(x, y, textPaint.getTextSkewX());
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000414 fContent.append(SkPDFString::formatString(&encodedValue, 1,
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000415 font->multiByteGlyphs()));
416 fContent.append(" Tj\n");
417 }
418 fContent.append("ET\n");
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000419}
420
421void SkPDFDevice::drawTextOnPath(const SkDraw&, const void* text, size_t len,
422 const SkPath& path, const SkMatrix* matrix,
423 const SkPaint& paint) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000424 NOT_IMPLEMENTED("drawTextOnPath", true);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000425}
426
427void SkPDFDevice::drawVertices(const SkDraw&, SkCanvas::VertexMode,
428 int vertexCount, const SkPoint verts[],
429 const SkPoint texs[], const SkColor colors[],
430 SkXfermode* xmode, const uint16_t indices[],
431 int indexCount, const SkPaint& paint) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000432 NOT_IMPLEMENTED("drawVerticies", true);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000433}
434
vandebo@chromium.orgeb6c7592010-10-26 19:54:45 +0000435void SkPDFDevice::drawDevice(const SkDraw& d, SkDevice* device, int x, int y,
436 const SkPaint& paint) {
437 if ((device->getDeviceCapabilities() & kVector_Capability) == 0) {
438 // If we somehow get a raster device, do what our parent would do.
439 SkDevice::drawDevice(d, device, x, y, paint);
440 return;
441 }
442
443 // Assume that a vector capable device means that it's a PDF Device.
444 // TODO(vandebo) handle the paint (alpha and compositing mode).
445 SkMatrix matrix;
446 matrix.setTranslate(x, y);
447 SkPDFDevice* pdfDevice = static_cast<SkPDFDevice*>(device);
448
449 SkPDFFormXObject* xobject = new SkPDFFormXObject(pdfDevice, matrix);
450 fXObjectResources.push(xobject); // Transfer reference.
451 fContent.append("/X");
452 fContent.appendS32(fXObjectResources.count() - 1);
453 fContent.append(" Do\n");
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000454}
455
456const SkRefPtr<SkPDFDict>& SkPDFDevice::getResourceDict() {
457 if (fResourceDict.get() == NULL) {
458 fResourceDict = new SkPDFDict;
459 fResourceDict->unref(); // SkRefPtr and new both took a reference.
460
461 if (fGraphicStateResources.count()) {
462 SkRefPtr<SkPDFDict> extGState = new SkPDFDict();
463 extGState->unref(); // SkRefPtr and new both took a reference.
464 for (int i = 0; i < fGraphicStateResources.count(); i++) {
465 SkString nameString("G");
466 nameString.appendS32(i);
467 SkRefPtr<SkPDFName> name = new SkPDFName(nameString);
468 name->unref(); // SkRefPtr and new both took a reference.
469 SkRefPtr<SkPDFObjRef> gsRef =
470 new SkPDFObjRef(fGraphicStateResources[i]);
471 gsRef->unref(); // SkRefPtr and new both took a reference.
472 extGState->insert(name.get(), gsRef.get());
473 }
474 fResourceDict->insert("ExtGState", extGState.get());
475 }
476
477 if (fXObjectResources.count()) {
478 SkRefPtr<SkPDFDict> xObjects = new SkPDFDict();
479 xObjects->unref(); // SkRefPtr and new both took a reference.
480 for (int i = 0; i < fXObjectResources.count(); i++) {
481 SkString nameString("X");
482 nameString.appendS32(i);
483 SkRefPtr<SkPDFName> name = new SkPDFName(nameString);
484 name->unref(); // SkRefPtr and new both took a reference.
485 SkRefPtr<SkPDFObjRef> xObjRef =
486 new SkPDFObjRef(fXObjectResources[i]);
487 xObjRef->unref(); // SkRefPtr and new both took a reference.
488 xObjects->insert(name.get(), xObjRef.get());
489 }
490 fResourceDict->insert("XObject", xObjects.get());
491 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000492
493 if (fFontResources.count()) {
494 SkRefPtr<SkPDFDict> fonts = new SkPDFDict();
495 fonts->unref(); // SkRefPtr and new both took a reference.
496 for (int i = 0; i < fFontResources.count(); i++) {
497 SkString nameString("F");
498 nameString.appendS32(i);
499 SkRefPtr<SkPDFName> name = new SkPDFName(nameString);
500 name->unref(); // SkRefPtr and new both took a reference.
501 SkRefPtr<SkPDFObjRef> fontRef =
502 new SkPDFObjRef(fFontResources[i]);
503 fontRef->unref(); // SkRefPtr and new both took a reference.
504 fonts->insert(name.get(), fontRef.get());
505 }
506 fResourceDict->insert("Font", fonts.get());
507 }
508
509 // For compatibility, add all proc sets (only used for output to PS
510 // devices).
511 const char procs[][7] = {"PDF", "Text", "ImageB", "ImageC", "ImageI"};
512 SkRefPtr<SkPDFArray> procSets = new SkPDFArray();
513 procSets->unref(); // SkRefPtr and new both took a reference.
514 procSets->reserve(SK_ARRAY_COUNT(procs));
515 for (size_t i = 0; i < SK_ARRAY_COUNT(procs); i++) {
516 SkRefPtr<SkPDFName> entry = new SkPDFName(procs[i]);
517 entry->unref(); // SkRefPtr and new both took a reference.
518 procSets->append(entry.get());
519 }
520 fResourceDict->insert("ProcSet", procSets.get());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000521 }
522 return fResourceDict;
523}
524
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000525void SkPDFDevice::getResources(SkTDArray<SkPDFObject*>* resourceList) const {
526 resourceList->setReserve(resourceList->count() +
527 fGraphicStateResources.count() +
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000528 fXObjectResources.count() +
529 fFontResources.count());
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000530 for (int i = 0; i < fGraphicStateResources.count(); i++) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000531 resourceList->push(fGraphicStateResources[i]);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000532 fGraphicStateResources[i]->ref();
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000533 fGraphicStateResources[i]->getResources(resourceList);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000534 }
535 for (int i = 0; i < fXObjectResources.count(); i++) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000536 resourceList->push(fXObjectResources[i]);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000537 fXObjectResources[i]->ref();
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000538 fXObjectResources[i]->getResources(resourceList);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000539 }
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000540 for (int i = 0; i < fFontResources.count(); i++) {
541 resourceList->push(fFontResources[i]);
542 fFontResources[i]->ref();
543 fFontResources[i]->getResources(resourceList);
544 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000545}
546
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000547SkRefPtr<SkPDFArray> SkPDFDevice::getMediaBox() const {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000548 SkRefPtr<SkPDFInt> zero = new SkPDFInt(0);
549 zero->unref(); // SkRefPtr and new both took a reference.
550 SkRefPtr<SkPDFInt> width = new SkPDFInt(fWidth);
551 width->unref(); // SkRefPtr and new both took a reference.
552 SkRefPtr<SkPDFInt> height = new SkPDFInt(fHeight);
553 height->unref(); // SkRefPtr and new both took a reference.
554 SkRefPtr<SkPDFArray> mediaBox = new SkPDFArray();
555 mediaBox->unref(); // SkRefPtr and new both took a reference.
556 mediaBox->reserve(4);
557 mediaBox->append(zero.get());
558 mediaBox->append(zero.get());
559 mediaBox->append(width.get());
560 mediaBox->append(height.get());
561 return mediaBox;
562}
563
vandebo@chromium.orgddbbd802010-10-26 19:45:06 +0000564SkString SkPDFDevice::content(bool flipOrigin) const {
565 SkString result;
566 // Scale and translate to move the origin from the lower left to the
567 // upper left.
568 if (flipOrigin)
569 result.printf("1 0 0 -1 0 %d cm\n", fHeight);
570 result.append(fContent);
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000571 for (int i = 0; i < fGraphicStackIndex; i++)
572 result.append("Q\n");
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000573 return result;
574}
575
576// Private
577
578// TODO(vandebo) handle these cases.
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000579#define PAINTCHECK(x,y) NOT_IMPLEMENTED(newPaint.x() y, false)
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000580
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000581void SkPDFDevice::updateGSFromPaint(const SkPaint& newPaint, bool forText) {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000582 PAINTCHECK(getXfermode, != NULL);
583 PAINTCHECK(getPathEffect, != NULL);
584 PAINTCHECK(getMaskFilter, != NULL);
585 PAINTCHECK(getShader, != NULL);
586 PAINTCHECK(getColorFilter, != NULL);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000587
588 SkRefPtr<SkPDFGraphicState> newGraphicState =
589 SkPDFGraphicState::getGraphicStateForPaint(newPaint);
590 newGraphicState->unref(); // getGraphicState and SkRefPtr both took a ref.
591 // newGraphicState has been canonicalized so we can directly compare
592 // pointers.
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000593 if (fGraphicStack[fGraphicStackIndex].fGraphicState !=
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000594 newGraphicState.get()) {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000595 int resourceIndex = fGraphicStateResources.find(newGraphicState.get());
596 if (resourceIndex < 0) {
597 resourceIndex = fGraphicStateResources.count();
598 fGraphicStateResources.push(newGraphicState.get());
599 newGraphicState->ref();
600 }
601 fContent.append("/G");
602 fContent.appendS32(resourceIndex);
603 fContent.append(" gs\n");
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000604 fGraphicStack[fGraphicStackIndex].fGraphicState = newGraphicState.get();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000605 }
606
607 SkColor newColor = newPaint.getColor();
608 newColor = SkColorSetA(newColor, 0xFF);
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000609 if (fGraphicStack[fGraphicStackIndex].fColor != newColor) {
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000610 SkString colorString = toPDFColor(newColor);
611 fContent.append(colorString);
612 fContent.append("RG ");
613 fContent.append(colorString);
614 fContent.append("rg\n");
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000615 fGraphicStack[fGraphicStackIndex].fColor = newColor;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000616 }
617
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000618 if (forText) {
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000619 if (fGraphicStack[fGraphicStackIndex].fTextScaleX !=
620 newPaint.getTextScaleX()) {
621 SkScalar scale = newPaint.getTextScaleX();
622 SkScalar pdfScale = SkScalarMul(scale, SkIntToScalar(100));
623 fContent.appendScalar(pdfScale);
624 fContent.append(" Tz\n");
625 fGraphicStack[fGraphicStackIndex].fTextScaleX = scale;
626 }
627
628 if (fGraphicStack[fGraphicStackIndex].fTextFill !=
629 newPaint.getStyle()) {
630 SK_COMPILE_ASSERT(SkPaint::kFill_Style == 0, enum_must_match_value);
631 SK_COMPILE_ASSERT(SkPaint::kStroke_Style == 1,
632 enum_must_match_value);
633 SK_COMPILE_ASSERT(SkPaint::kStrokeAndFill_Style == 2,
634 enum_must_match_value);
635 fContent.appendS32(newPaint.getStyle());
636 fContent.append(" Tr\n");
637 fGraphicStack[fGraphicStackIndex].fTextFill = newPaint.getStyle();
638 }
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000639 }
640}
641
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000642void SkPDFDevice::updateFont(const SkPaint& paint, uint16_t glyphID) {
643 uint32_t fontID = SkTypeface::UniqueID(paint.getTypeface());
644 if (fGraphicStack[fGraphicStackIndex].fTextSize != paint.getTextSize() ||
645 fGraphicStack[fGraphicStackIndex].fFont == NULL ||
646 fGraphicStack[fGraphicStackIndex].fFont->fontID() != fontID ||
647 !fGraphicStack[fGraphicStackIndex].fFont->hasGlyph(glyphID)) {
648 int fontIndex = getFontResourceIndex(fontID, glyphID);
649 fContent.append("/F");
650 fContent.appendS32(fontIndex);
651 fContent.append(" ");
652 fContent.appendScalar(paint.getTextSize());
653 fContent.append(" Tf\n");
654 fGraphicStack[fGraphicStackIndex].fTextSize = paint.getTextSize();
655 fGraphicStack[fGraphicStackIndex].fFont = fFontResources[fontIndex];
656 }
657}
658
659
660int SkPDFDevice::getFontResourceIndex(uint32_t fontID, uint16_t glyphID) {
661 SkRefPtr<SkPDFFont> newFont = SkPDFFont::getFontResource(fontID, glyphID);
662 newFont->unref(); // getFontResource and SkRefPtr both took a ref.
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000663 int resourceIndex = fFontResources.find(newFont.get());
664 if (resourceIndex < 0) {
665 resourceIndex = fFontResources.count();
666 fFontResources.push(newFont.get());
667 newFont->ref();
668 }
669 return resourceIndex;
670}
671
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000672void SkPDFDevice::moveTo(SkScalar x, SkScalar y) {
673 fContent.appendScalar(x);
674 fContent.append(" ");
675 fContent.appendScalar(y);
676 fContent.append(" m\n");
677}
678
679void SkPDFDevice::appendLine(SkScalar x, SkScalar y) {
680 fContent.appendScalar(x);
681 fContent.append(" ");
682 fContent.appendScalar(y);
683 fContent.append(" l\n");
684}
685
686void SkPDFDevice::appendCubic(SkScalar ctl1X, SkScalar ctl1Y,
687 SkScalar ctl2X, SkScalar ctl2Y,
688 SkScalar dstX, SkScalar dstY) {
689 SkString cmd("y\n");
690 fContent.appendScalar(ctl1X);
691 fContent.append(" ");
692 fContent.appendScalar(ctl1Y);
693 fContent.append(" ");
694 if (ctl2X != dstX || ctl2Y != dstY) {
695 cmd.set("c\n");
696 fContent.appendScalar(ctl2X);
697 fContent.append(" ");
698 fContent.appendScalar(ctl2Y);
699 fContent.append(" ");
700 }
701 fContent.appendScalar(dstX);
702 fContent.append(" ");
703 fContent.appendScalar(dstY);
704 fContent.append(cmd);
705}
706
707void SkPDFDevice::appendRectangle(SkScalar x, SkScalar y,
708 SkScalar w, SkScalar h) {
709 fContent.appendScalar(x);
710 fContent.append(" ");
711 fContent.appendScalar(y);
712 fContent.append(" ");
713 fContent.appendScalar(w);
714 fContent.append(" ");
715 fContent.appendScalar(h);
716 fContent.append(" re\n");
717}
718
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000719void SkPDFDevice::emitPath(const SkPath& path) {
720 SkPoint args[4];
721 SkPath::Iter iter(path, false);
722 for (SkPath::Verb verb = iter.next(args);
723 verb != SkPath::kDone_Verb;
724 verb = iter.next(args)) {
725 // args gets all the points, even the implicit first point.
726 switch (verb) {
727 case SkPath::kMove_Verb:
728 moveTo(args[0].fX, args[0].fY);
729 break;
730 case SkPath::kLine_Verb:
731 appendLine(args[1].fX, args[1].fY);
732 break;
733 case SkPath::kQuad_Verb: {
734 // Convert quad to cubic (degree elevation). http://goo.gl/vS4i
735 const SkScalar three = SkIntToScalar(3);
736 args[1].scale(SkIntToScalar(2));
737 SkScalar ctl1X = SkScalarDiv(args[0].fX + args[1].fX, three);
738 SkScalar ctl1Y = SkScalarDiv(args[0].fY + args[1].fY, three);
739 SkScalar ctl2X = SkScalarDiv(args[2].fX + args[1].fX, three);
740 SkScalar ctl2Y = SkScalarDiv(args[2].fY + args[1].fY, three);
741 appendCubic(ctl1X, ctl1Y, ctl2X, ctl2Y, args[2].fX, args[2].fY);
742 break;
743 }
744 case SkPath::kCubic_Verb:
745 appendCubic(args[1].fX, args[1].fY, args[2].fX, args[2].fY,
746 args[3].fX, args[3].fY);
747 break;
748 case SkPath::kClose_Verb:
749 closePath();
750 break;
751 case SkPath::kDone_Verb:
752 break;
753 default:
754 SkASSERT(false);
755 break;
756 }
757 }
758}
759
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000760void SkPDFDevice::closePath() {
761 fContent.append("h\n");
762}
763
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000764void SkPDFDevice::paintPath(SkPaint::Style style, SkPath::FillType fill) {
765 if (style == SkPaint::kFill_Style)
766 fContent.append("f");
767 else if (style == SkPaint::kStrokeAndFill_Style)
768 fContent.append("B");
769 else if (style == SkPaint::kStroke_Style)
770 fContent.append("S");
771
772 if (style != SkPaint::kStroke_Style) {
773 // Not supported yet.
774 NOT_IMPLEMENTED(fill == SkPath::kInverseEvenOdd_FillType, false);
775 NOT_IMPLEMENTED(fill == SkPath::kInverseWinding_FillType, false);
776 if (fill == SkPath::kEvenOdd_FillType)
777 fContent.append("*");
778 }
779 fContent.append("\n");
780}
781
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000782void SkPDFDevice::strokePath() {
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000783 paintPath(SkPaint::kStroke_Style, SkPath::kWinding_FillType);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000784}
785
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000786void SkPDFDevice::pushGS() {
787 SkASSERT(fGraphicStackIndex < 2);
788 fContent.append("q\n");
789 fGraphicStackIndex++;
790 fGraphicStack[fGraphicStackIndex] = fGraphicStack[fGraphicStackIndex - 1];
791}
792
793void SkPDFDevice::popGS() {
794 SkASSERT(fGraphicStackIndex > 0);
795 fContent.append("Q\n");
796 fGraphicStackIndex--;
797}
798
vandebo@chromium.org28be72b2010-11-11 21:37:00 +0000799void SkPDFDevice::setTextTransform(SkScalar x, SkScalar y, SkScalar textSkewX) {
800 // Flip the text about the x-axis to account for origin swap and include
801 // the passed parameters.
802 fContent.append("1 0 ");
803 fContent.appendScalar(0 - textSkewX);
804 fContent.append(" -1 ");
805 fContent.appendScalar(x);
806 fContent.append(" ");
807 fContent.appendScalar(y);
808 fContent.append(" Tm\n");
809}
810
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000811void SkPDFDevice::internalDrawBitmap(const SkMatrix& matrix,
812 const SkBitmap& bitmap,
813 const SkPaint& paint) {
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000814 SkMatrix scaled;
815 // Adjust for origin flip.
816 scaled.setScale(1, -1);
817 scaled.postTranslate(0, 1);
818 // Scale the image up from 1x1 to WxH.
819 scaled.postScale(bitmap.width(), bitmap.height());
820 scaled.postConcat(matrix);
821 SkMatrix curTransform = setTransform(scaled);
822
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000823 SkPDFImage* image = new SkPDFImage(bitmap, paint);
824 fXObjectResources.push(image); // Transfer reference.
825 fContent.append("/X");
826 fContent.appendS32(fXObjectResources.count() - 1);
827 fContent.append(" Do\n");
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000828 setTransform(curTransform);
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000829}
830
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000831SkMatrix SkPDFDevice::setTransform(const SkMatrix& m) {
832 SkMatrix old = fGraphicStack[fGraphicStackIndex].fTransform;
833 if (old == m)
834 return old;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000835
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000836 if (old.getType() != SkMatrix::kIdentity_Mask) {
837 SkASSERT(fGraphicStackIndex > 0);
838 SkASSERT(fGraphicStack[fGraphicStackIndex - 1].fTransform.getType() ==
839 SkMatrix::kIdentity_Mask);
840 SkASSERT(fGraphicStack[fGraphicStackIndex].fClip ==
841 fGraphicStack[fGraphicStackIndex - 1].fClip);
842 popGS();
843 }
844 if (m.getType() == SkMatrix::kIdentity_Mask)
845 return old;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000846
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000847 if (fGraphicStackIndex == 0 || fGraphicStack[fGraphicStackIndex].fClip !=
848 fGraphicStack[fGraphicStackIndex - 1].fClip)
849 pushGS();
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000850
vandebo@chromium.orgddbbd802010-10-26 19:45:06 +0000851 SkScalar transform[6];
852 SkAssertResult(m.pdfTransform(transform));
853 for (size_t i = 0; i < SK_ARRAY_COUNT(transform); i++) {
854 fContent.appendScalar(transform[i]);
855 fContent.append(" ");
856 }
857 fContent.append("cm\n");
vandebo@chromium.org7e2ff7c2010-11-03 23:55:28 +0000858 fGraphicStack[fGraphicStackIndex].fTransform = m;
859
860 return old;
vandebo@chromium.org9b49dc02010-10-20 22:23:29 +0000861}