gfx/skia/trunk/src/pdf/SkPDFDevice.cpp

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 /*
michael@0 2 * Copyright 2011 Google Inc.
michael@0 3 *
michael@0 4 * Use of this source code is governed by a BSD-style license that can be
michael@0 5 * found in the LICENSE file.
michael@0 6 */
michael@0 7
michael@0 8 #include "SkPDFDevice.h"
michael@0 9
michael@0 10 #include "SkAnnotation.h"
michael@0 11 #include "SkColor.h"
michael@0 12 #include "SkClipStack.h"
michael@0 13 #include "SkData.h"
michael@0 14 #include "SkDraw.h"
michael@0 15 #include "SkFontHost.h"
michael@0 16 #include "SkGlyphCache.h"
michael@0 17 #include "SkPaint.h"
michael@0 18 #include "SkPath.h"
michael@0 19 #include "SkPathOps.h"
michael@0 20 #include "SkPDFFont.h"
michael@0 21 #include "SkPDFFormXObject.h"
michael@0 22 #include "SkPDFGraphicState.h"
michael@0 23 #include "SkPDFImage.h"
michael@0 24 #include "SkPDFResourceDict.h"
michael@0 25 #include "SkPDFShader.h"
michael@0 26 #include "SkPDFStream.h"
michael@0 27 #include "SkPDFTypes.h"
michael@0 28 #include "SkPDFUtils.h"
michael@0 29 #include "SkRect.h"
michael@0 30 #include "SkRRect.h"
michael@0 31 #include "SkString.h"
michael@0 32 #include "SkTextFormatParams.h"
michael@0 33 #include "SkTemplates.h"
michael@0 34 #include "SkTypefacePriv.h"
michael@0 35 #include "SkTSet.h"
michael@0 36
michael@0 37 #ifdef SK_BUILD_FOR_ANDROID
michael@0 38 #include "SkTypeface_android.h"
michael@0 39
michael@0 40 struct TypefaceFallbackData {
michael@0 41 SkTypeface* typeface;
michael@0 42 int lowerBounds;
michael@0 43 int upperBounds;
michael@0 44
michael@0 45 bool operator==(const TypefaceFallbackData& b) const {
michael@0 46 return typeface == b.typeface &&
michael@0 47 lowerBounds == b.lowerBounds &&
michael@0 48 upperBounds == b.upperBounds;
michael@0 49 }
michael@0 50 };
michael@0 51 #endif
michael@0 52
michael@0 53 #define DPI_FOR_RASTER_SCALE_ONE 72
michael@0 54
michael@0 55 // Utility functions
michael@0 56
michael@0 57 static void emit_pdf_color(SkColor color, SkWStream* result) {
michael@0 58 SkASSERT(SkColorGetA(color) == 0xFF); // We handle alpha elsewhere.
michael@0 59 SkScalar colorMax = SkIntToScalar(0xFF);
michael@0 60 SkPDFScalar::Append(
michael@0 61 SkScalarDiv(SkIntToScalar(SkColorGetR(color)), colorMax), result);
michael@0 62 result->writeText(" ");
michael@0 63 SkPDFScalar::Append(
michael@0 64 SkScalarDiv(SkIntToScalar(SkColorGetG(color)), colorMax), result);
michael@0 65 result->writeText(" ");
michael@0 66 SkPDFScalar::Append(
michael@0 67 SkScalarDiv(SkIntToScalar(SkColorGetB(color)), colorMax), result);
michael@0 68 result->writeText(" ");
michael@0 69 }
michael@0 70
michael@0 71 static SkPaint calculate_text_paint(const SkPaint& paint) {
michael@0 72 SkPaint result = paint;
michael@0 73 if (result.isFakeBoldText()) {
michael@0 74 SkScalar fakeBoldScale = SkScalarInterpFunc(result.getTextSize(),
michael@0 75 kStdFakeBoldInterpKeys,
michael@0 76 kStdFakeBoldInterpValues,
michael@0 77 kStdFakeBoldInterpLength);
michael@0 78 SkScalar width = SkScalarMul(result.getTextSize(), fakeBoldScale);
michael@0 79 if (result.getStyle() == SkPaint::kFill_Style) {
michael@0 80 result.setStyle(SkPaint::kStrokeAndFill_Style);
michael@0 81 } else {
michael@0 82 width += result.getStrokeWidth();
michael@0 83 }
michael@0 84 result.setStrokeWidth(width);
michael@0 85 }
michael@0 86 return result;
michael@0 87 }
michael@0 88
michael@0 89 // Stolen from measure_text in SkDraw.cpp and then tweaked.
michael@0 90 static void align_text(SkDrawCacheProc glyphCacheProc, const SkPaint& paint,
michael@0 91 const uint16_t* glyphs, size_t len,
michael@0 92 SkScalar* x, SkScalar* y) {
michael@0 93 if (paint.getTextAlign() == SkPaint::kLeft_Align) {
michael@0 94 return;
michael@0 95 }
michael@0 96
michael@0 97 SkMatrix ident;
michael@0 98 ident.reset();
michael@0 99 SkAutoGlyphCache autoCache(paint, NULL, &ident);
michael@0 100 SkGlyphCache* cache = autoCache.getCache();
michael@0 101
michael@0 102 const char* start = reinterpret_cast<const char*>(glyphs);
michael@0 103 const char* stop = reinterpret_cast<const char*>(glyphs + len);
michael@0 104 SkFixed xAdv = 0, yAdv = 0;
michael@0 105
michael@0 106 // TODO(vandebo): This probably needs to take kerning into account.
michael@0 107 while (start < stop) {
michael@0 108 const SkGlyph& glyph = glyphCacheProc(cache, &start, 0, 0);
michael@0 109 xAdv += glyph.fAdvanceX;
michael@0 110 yAdv += glyph.fAdvanceY;
michael@0 111 };
michael@0 112 if (paint.getTextAlign() == SkPaint::kLeft_Align) {
michael@0 113 return;
michael@0 114 }
michael@0 115
michael@0 116 SkScalar xAdj = SkFixedToScalar(xAdv);
michael@0 117 SkScalar yAdj = SkFixedToScalar(yAdv);
michael@0 118 if (paint.getTextAlign() == SkPaint::kCenter_Align) {
michael@0 119 xAdj = SkScalarHalf(xAdj);
michael@0 120 yAdj = SkScalarHalf(yAdj);
michael@0 121 }
michael@0 122 *x = *x - xAdj;
michael@0 123 *y = *y - yAdj;
michael@0 124 }
michael@0 125
michael@0 126 static int max_glyphid_for_typeface(SkTypeface* typeface) {
michael@0 127 SkAutoResolveDefaultTypeface autoResolve(typeface);
michael@0 128 typeface = autoResolve.get();
michael@0 129 return typeface->countGlyphs() - 1;
michael@0 130 }
michael@0 131
michael@0 132 typedef SkAutoSTMalloc<128, uint16_t> SkGlyphStorage;
michael@0 133
michael@0 134 static size_t force_glyph_encoding(const SkPaint& paint, const void* text,
michael@0 135 size_t len, SkGlyphStorage* storage,
michael@0 136 uint16_t** glyphIDs) {
michael@0 137 // Make sure we have a glyph id encoding.
michael@0 138 if (paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding) {
michael@0 139 size_t numGlyphs = paint.textToGlyphs(text, len, NULL);
michael@0 140 storage->reset(numGlyphs);
michael@0 141 paint.textToGlyphs(text, len, storage->get());
michael@0 142 *glyphIDs = storage->get();
michael@0 143 return numGlyphs;
michael@0 144 }
michael@0 145
michael@0 146 // For user supplied glyph ids we need to validate them.
michael@0 147 SkASSERT((len & 1) == 0);
michael@0 148 size_t numGlyphs = len / 2;
michael@0 149 const uint16_t* input =
michael@0 150 reinterpret_cast<uint16_t*>(const_cast<void*>((text)));
michael@0 151
michael@0 152 int maxGlyphID = max_glyphid_for_typeface(paint.getTypeface());
michael@0 153 size_t validated;
michael@0 154 for (validated = 0; validated < numGlyphs; ++validated) {
michael@0 155 if (input[validated] > maxGlyphID) {
michael@0 156 break;
michael@0 157 }
michael@0 158 }
michael@0 159 if (validated >= numGlyphs) {
michael@0 160 *glyphIDs = reinterpret_cast<uint16_t*>(const_cast<void*>((text)));
michael@0 161 return numGlyphs;
michael@0 162 }
michael@0 163
michael@0 164 // Silently drop anything out of range.
michael@0 165 storage->reset(numGlyphs);
michael@0 166 if (validated > 0) {
michael@0 167 memcpy(storage->get(), input, validated * sizeof(uint16_t));
michael@0 168 }
michael@0 169
michael@0 170 for (size_t i = validated; i < numGlyphs; ++i) {
michael@0 171 storage->get()[i] = input[i];
michael@0 172 if (input[i] > maxGlyphID) {
michael@0 173 storage->get()[i] = 0;
michael@0 174 }
michael@0 175 }
michael@0 176 *glyphIDs = storage->get();
michael@0 177 return numGlyphs;
michael@0 178 }
michael@0 179
michael@0 180 static void set_text_transform(SkScalar x, SkScalar y, SkScalar textSkewX,
michael@0 181 SkWStream* content) {
michael@0 182 // Flip the text about the x-axis to account for origin swap and include
michael@0 183 // the passed parameters.
michael@0 184 content->writeText("1 0 ");
michael@0 185 SkPDFScalar::Append(0 - textSkewX, content);
michael@0 186 content->writeText(" -1 ");
michael@0 187 SkPDFScalar::Append(x, content);
michael@0 188 content->writeText(" ");
michael@0 189 SkPDFScalar::Append(y, content);
michael@0 190 content->writeText(" Tm\n");
michael@0 191 }
michael@0 192
michael@0 193 // It is important to not confuse GraphicStateEntry with SkPDFGraphicState, the
michael@0 194 // later being our representation of an object in the PDF file.
michael@0 195 struct GraphicStateEntry {
michael@0 196 GraphicStateEntry();
michael@0 197
michael@0 198 // Compare the fields we care about when setting up a new content entry.
michael@0 199 bool compareInitialState(const GraphicStateEntry& b);
michael@0 200
michael@0 201 SkMatrix fMatrix;
michael@0 202 // We can't do set operations on Paths, though PDF natively supports
michael@0 203 // intersect. If the clip stack does anything other than intersect,
michael@0 204 // we have to fall back to the region. Treat fClipStack as authoritative.
michael@0 205 // See http://code.google.com/p/skia/issues/detail?id=221
michael@0 206 SkClipStack fClipStack;
michael@0 207 SkRegion fClipRegion;
michael@0 208
michael@0 209 // When emitting the content entry, we will ensure the graphic state
michael@0 210 // is set to these values first.
michael@0 211 SkColor fColor;
michael@0 212 SkScalar fTextScaleX; // Zero means we don't care what the value is.
michael@0 213 SkPaint::Style fTextFill; // Only if TextScaleX is non-zero.
michael@0 214 int fShaderIndex;
michael@0 215 int fGraphicStateIndex;
michael@0 216
michael@0 217 // We may change the font (i.e. for Type1 support) within a
michael@0 218 // ContentEntry. This is the one currently in effect, or NULL if none.
michael@0 219 SkPDFFont* fFont;
michael@0 220 // In PDF, text size has no default value. It is only valid if fFont is
michael@0 221 // not NULL.
michael@0 222 SkScalar fTextSize;
michael@0 223 };
michael@0 224
michael@0 225 GraphicStateEntry::GraphicStateEntry() : fColor(SK_ColorBLACK),
michael@0 226 fTextScaleX(SK_Scalar1),
michael@0 227 fTextFill(SkPaint::kFill_Style),
michael@0 228 fShaderIndex(-1),
michael@0 229 fGraphicStateIndex(-1),
michael@0 230 fFont(NULL),
michael@0 231 fTextSize(SK_ScalarNaN) {
michael@0 232 fMatrix.reset();
michael@0 233 }
michael@0 234
michael@0 235 bool GraphicStateEntry::compareInitialState(const GraphicStateEntry& cur) {
michael@0 236 return fColor == cur.fColor &&
michael@0 237 fShaderIndex == cur.fShaderIndex &&
michael@0 238 fGraphicStateIndex == cur.fGraphicStateIndex &&
michael@0 239 fMatrix == cur.fMatrix &&
michael@0 240 fClipStack == cur.fClipStack &&
michael@0 241 (fTextScaleX == 0 ||
michael@0 242 (fTextScaleX == cur.fTextScaleX && fTextFill == cur.fTextFill));
michael@0 243 }
michael@0 244
michael@0 245 class GraphicStackState {
michael@0 246 public:
michael@0 247 GraphicStackState(const SkClipStack& existingClipStack,
michael@0 248 const SkRegion& existingClipRegion,
michael@0 249 SkWStream* contentStream)
michael@0 250 : fStackDepth(0),
michael@0 251 fContentStream(contentStream) {
michael@0 252 fEntries[0].fClipStack = existingClipStack;
michael@0 253 fEntries[0].fClipRegion = existingClipRegion;
michael@0 254 }
michael@0 255
michael@0 256 void updateClip(const SkClipStack& clipStack, const SkRegion& clipRegion,
michael@0 257 const SkPoint& translation);
michael@0 258 void updateMatrix(const SkMatrix& matrix);
michael@0 259 void updateDrawingState(const GraphicStateEntry& state);
michael@0 260
michael@0 261 void drainStack();
michael@0 262
michael@0 263 private:
michael@0 264 void push();
michael@0 265 void pop();
michael@0 266 GraphicStateEntry* currentEntry() { return &fEntries[fStackDepth]; }
michael@0 267
michael@0 268 // Conservative limit on save depth, see impl. notes in PDF 1.4 spec.
michael@0 269 static const int kMaxStackDepth = 12;
michael@0 270 GraphicStateEntry fEntries[kMaxStackDepth + 1];
michael@0 271 int fStackDepth;
michael@0 272 SkWStream* fContentStream;
michael@0 273 };
michael@0 274
michael@0 275 void GraphicStackState::drainStack() {
michael@0 276 while (fStackDepth) {
michael@0 277 pop();
michael@0 278 }
michael@0 279 }
michael@0 280
michael@0 281 void GraphicStackState::push() {
michael@0 282 SkASSERT(fStackDepth < kMaxStackDepth);
michael@0 283 fContentStream->writeText("q\n");
michael@0 284 fStackDepth++;
michael@0 285 fEntries[fStackDepth] = fEntries[fStackDepth - 1];
michael@0 286 }
michael@0 287
michael@0 288 void GraphicStackState::pop() {
michael@0 289 SkASSERT(fStackDepth > 0);
michael@0 290 fContentStream->writeText("Q\n");
michael@0 291 fStackDepth--;
michael@0 292 }
michael@0 293
michael@0 294 // This function initializes iter to be an iterator on the "stack" argument
michael@0 295 // and then skips over the leading entries as specified in prefix. It requires
michael@0 296 // and asserts that "prefix" will be a prefix to "stack."
michael@0 297 static void skip_clip_stack_prefix(const SkClipStack& prefix,
michael@0 298 const SkClipStack& stack,
michael@0 299 SkClipStack::Iter* iter) {
michael@0 300 SkClipStack::B2TIter prefixIter(prefix);
michael@0 301 iter->reset(stack, SkClipStack::Iter::kBottom_IterStart);
michael@0 302
michael@0 303 const SkClipStack::Element* prefixEntry;
michael@0 304 const SkClipStack::Element* iterEntry;
michael@0 305
michael@0 306 for (prefixEntry = prefixIter.next(); prefixEntry;
michael@0 307 prefixEntry = prefixIter.next()) {
michael@0 308 iterEntry = iter->next();
michael@0 309 SkASSERT(iterEntry);
michael@0 310 // Because of SkClipStack does internal intersection, the last clip
michael@0 311 // entry may differ.
michael@0 312 if (*prefixEntry != *iterEntry) {
michael@0 313 SkASSERT(prefixEntry->getOp() == SkRegion::kIntersect_Op);
michael@0 314 SkASSERT(iterEntry->getOp() == SkRegion::kIntersect_Op);
michael@0 315 SkASSERT(iterEntry->getType() == prefixEntry->getType());
michael@0 316 // back up the iterator by one
michael@0 317 iter->prev();
michael@0 318 prefixEntry = prefixIter.next();
michael@0 319 break;
michael@0 320 }
michael@0 321 }
michael@0 322
michael@0 323 SkASSERT(prefixEntry == NULL);
michael@0 324 }
michael@0 325
michael@0 326 static void emit_clip(SkPath* clipPath, SkRect* clipRect,
michael@0 327 SkWStream* contentStream) {
michael@0 328 SkASSERT(clipPath || clipRect);
michael@0 329
michael@0 330 SkPath::FillType clipFill;
michael@0 331 if (clipPath) {
michael@0 332 SkPDFUtils::EmitPath(*clipPath, SkPaint::kFill_Style, contentStream);
michael@0 333 clipFill = clipPath->getFillType();
michael@0 334 } else {
michael@0 335 SkPDFUtils::AppendRectangle(*clipRect, contentStream);
michael@0 336 clipFill = SkPath::kWinding_FillType;
michael@0 337 }
michael@0 338
michael@0 339 NOT_IMPLEMENTED(clipFill == SkPath::kInverseEvenOdd_FillType, false);
michael@0 340 NOT_IMPLEMENTED(clipFill == SkPath::kInverseWinding_FillType, false);
michael@0 341 if (clipFill == SkPath::kEvenOdd_FillType) {
michael@0 342 contentStream->writeText("W* n\n");
michael@0 343 } else {
michael@0 344 contentStream->writeText("W n\n");
michael@0 345 }
michael@0 346 }
michael@0 347
michael@0 348 #ifdef SK_PDF_USE_PATHOPS
michael@0 349 /* Calculate an inverted path's equivalent non-inverted path, given the
michael@0 350 * canvas bounds.
michael@0 351 * outPath may alias with invPath (since this is supported by PathOps).
michael@0 352 */
michael@0 353 static bool calculate_inverse_path(const SkRect& bounds, const SkPath& invPath,
michael@0 354 SkPath* outPath) {
michael@0 355 SkASSERT(invPath.isInverseFillType());
michael@0 356
michael@0 357 SkPath clipPath;
michael@0 358 clipPath.addRect(bounds);
michael@0 359
michael@0 360 return Op(clipPath, invPath, kIntersect_PathOp, outPath);
michael@0 361 }
michael@0 362
michael@0 363 // Sanity check the numerical values of the SkRegion ops and PathOps ops
michael@0 364 // enums so region_op_to_pathops_op can do a straight passthrough cast.
michael@0 365 // If these are failing, it may be necessary to make region_op_to_pathops_op
michael@0 366 // do more.
michael@0 367 SK_COMPILE_ASSERT(SkRegion::kDifference_Op == (int)kDifference_PathOp,
michael@0 368 region_pathop_mismatch);
michael@0 369 SK_COMPILE_ASSERT(SkRegion::kIntersect_Op == (int)kIntersect_PathOp,
michael@0 370 region_pathop_mismatch);
michael@0 371 SK_COMPILE_ASSERT(SkRegion::kUnion_Op == (int)kUnion_PathOp,
michael@0 372 region_pathop_mismatch);
michael@0 373 SK_COMPILE_ASSERT(SkRegion::kXOR_Op == (int)kXOR_PathOp,
michael@0 374 region_pathop_mismatch);
michael@0 375 SK_COMPILE_ASSERT(SkRegion::kReverseDifference_Op ==
michael@0 376 (int)kReverseDifference_PathOp,
michael@0 377 region_pathop_mismatch);
michael@0 378
michael@0 379 static SkPathOp region_op_to_pathops_op(SkRegion::Op op) {
michael@0 380 SkASSERT(op >= 0);
michael@0 381 SkASSERT(op <= SkRegion::kReverseDifference_Op);
michael@0 382 return (SkPathOp)op;
michael@0 383 }
michael@0 384
michael@0 385 /* Uses Path Ops to calculate a vector SkPath clip from a clip stack.
michael@0 386 * Returns true if successful, or false if not successful.
michael@0 387 * If successful, the resulting clip is stored in outClipPath.
michael@0 388 * If not successful, outClipPath is undefined, and a fallback method
michael@0 389 * should be used.
michael@0 390 */
michael@0 391 static bool get_clip_stack_path(const SkMatrix& transform,
michael@0 392 const SkClipStack& clipStack,
michael@0 393 const SkRegion& clipRegion,
michael@0 394 SkPath* outClipPath) {
michael@0 395 outClipPath->reset();
michael@0 396 outClipPath->setFillType(SkPath::kInverseWinding_FillType);
michael@0 397
michael@0 398 const SkClipStack::Element* clipEntry;
michael@0 399 SkClipStack::Iter iter;
michael@0 400 iter.reset(clipStack, SkClipStack::Iter::kBottom_IterStart);
michael@0 401 for (clipEntry = iter.next(); clipEntry; clipEntry = iter.next()) {
michael@0 402 SkPath entryPath;
michael@0 403 if (SkClipStack::Element::kEmpty_Type == clipEntry->getType()) {
michael@0 404 outClipPath->reset();
michael@0 405 outClipPath->setFillType(SkPath::kInverseWinding_FillType);
michael@0 406 continue;
michael@0 407 } else {
michael@0 408 clipEntry->asPath(&entryPath);
michael@0 409 }
michael@0 410 entryPath.transform(transform);
michael@0 411
michael@0 412 if (SkRegion::kReplace_Op == clipEntry->getOp()) {
michael@0 413 *outClipPath = entryPath;
michael@0 414 } else {
michael@0 415 SkPathOp op = region_op_to_pathops_op(clipEntry->getOp());
michael@0 416 if (!Op(*outClipPath, entryPath, op, outClipPath)) {
michael@0 417 return false;
michael@0 418 }
michael@0 419 }
michael@0 420 }
michael@0 421
michael@0 422 if (outClipPath->isInverseFillType()) {
michael@0 423 // The bounds are slightly outset to ensure this is correct in the
michael@0 424 // face of floating-point accuracy and possible SkRegion bitmap
michael@0 425 // approximations.
michael@0 426 SkRect clipBounds = SkRect::Make(clipRegion.getBounds());
michael@0 427 clipBounds.outset(SK_Scalar1, SK_Scalar1);
michael@0 428 if (!calculate_inverse_path(clipBounds, *outClipPath, outClipPath)) {
michael@0 429 return false;
michael@0 430 }
michael@0 431 }
michael@0 432 return true;
michael@0 433 }
michael@0 434 #endif
michael@0 435
michael@0 436 // TODO(vandebo): Take advantage of SkClipStack::getSaveCount(), the PDF
michael@0 437 // graphic state stack, and the fact that we can know all the clips used
michael@0 438 // on the page to optimize this.
michael@0 439 void GraphicStackState::updateClip(const SkClipStack& clipStack,
michael@0 440 const SkRegion& clipRegion,
michael@0 441 const SkPoint& translation) {
michael@0 442 if (clipStack == currentEntry()->fClipStack) {
michael@0 443 return;
michael@0 444 }
michael@0 445
michael@0 446 while (fStackDepth > 0) {
michael@0 447 pop();
michael@0 448 if (clipStack == currentEntry()->fClipStack) {
michael@0 449 return;
michael@0 450 }
michael@0 451 }
michael@0 452 push();
michael@0 453
michael@0 454 currentEntry()->fClipStack = clipStack;
michael@0 455 currentEntry()->fClipRegion = clipRegion;
michael@0 456
michael@0 457 SkMatrix transform;
michael@0 458 transform.setTranslate(translation.fX, translation.fY);
michael@0 459
michael@0 460 #ifdef SK_PDF_USE_PATHOPS
michael@0 461 SkPath clipPath;
michael@0 462 if (get_clip_stack_path(transform, clipStack, clipRegion, &clipPath)) {
michael@0 463 emit_clip(&clipPath, NULL, fContentStream);
michael@0 464 return;
michael@0 465 }
michael@0 466 #endif
michael@0 467 // gsState->initialEntry()->fClipStack/Region specifies the clip that has
michael@0 468 // already been applied. (If this is a top level device, then it specifies
michael@0 469 // a clip to the content area. If this is a layer, then it specifies
michael@0 470 // the clip in effect when the layer was created.) There's no need to
michael@0 471 // reapply that clip; SKCanvas's SkDrawIter will draw anything outside the
michael@0 472 // initial clip on the parent layer. (This means there's a bug if the user
michael@0 473 // expands the clip and then uses any xfer mode that uses dst:
michael@0 474 // http://code.google.com/p/skia/issues/detail?id=228 )
michael@0 475 SkClipStack::Iter iter;
michael@0 476 skip_clip_stack_prefix(fEntries[0].fClipStack, clipStack, &iter);
michael@0 477
michael@0 478 // If the clip stack does anything other than intersect or if it uses
michael@0 479 // an inverse fill type, we have to fall back to the clip region.
michael@0 480 bool needRegion = false;
michael@0 481 const SkClipStack::Element* clipEntry;
michael@0 482 for (clipEntry = iter.next(); clipEntry; clipEntry = iter.next()) {
michael@0 483 if (clipEntry->getOp() != SkRegion::kIntersect_Op ||
michael@0 484 clipEntry->isInverseFilled()) {
michael@0 485 needRegion = true;
michael@0 486 break;
michael@0 487 }
michael@0 488 }
michael@0 489
michael@0 490 if (needRegion) {
michael@0 491 SkPath clipPath;
michael@0 492 SkAssertResult(clipRegion.getBoundaryPath(&clipPath));
michael@0 493 emit_clip(&clipPath, NULL, fContentStream);
michael@0 494 } else {
michael@0 495 skip_clip_stack_prefix(fEntries[0].fClipStack, clipStack, &iter);
michael@0 496 const SkClipStack::Element* clipEntry;
michael@0 497 for (clipEntry = iter.next(); clipEntry; clipEntry = iter.next()) {
michael@0 498 SkASSERT(clipEntry->getOp() == SkRegion::kIntersect_Op);
michael@0 499 switch (clipEntry->getType()) {
michael@0 500 case SkClipStack::Element::kRect_Type: {
michael@0 501 SkRect translatedClip;
michael@0 502 transform.mapRect(&translatedClip, clipEntry->getRect());
michael@0 503 emit_clip(NULL, &translatedClip, fContentStream);
michael@0 504 break;
michael@0 505 }
michael@0 506 default: {
michael@0 507 SkPath translatedPath;
michael@0 508 clipEntry->asPath(&translatedPath);
michael@0 509 translatedPath.transform(transform, &translatedPath);
michael@0 510 emit_clip(&translatedPath, NULL, fContentStream);
michael@0 511 break;
michael@0 512 }
michael@0 513 }
michael@0 514 }
michael@0 515 }
michael@0 516 }
michael@0 517
michael@0 518 void GraphicStackState::updateMatrix(const SkMatrix& matrix) {
michael@0 519 if (matrix == currentEntry()->fMatrix) {
michael@0 520 return;
michael@0 521 }
michael@0 522
michael@0 523 if (currentEntry()->fMatrix.getType() != SkMatrix::kIdentity_Mask) {
michael@0 524 SkASSERT(fStackDepth > 0);
michael@0 525 SkASSERT(fEntries[fStackDepth].fClipStack ==
michael@0 526 fEntries[fStackDepth -1].fClipStack);
michael@0 527 pop();
michael@0 528
michael@0 529 SkASSERT(currentEntry()->fMatrix.getType() == SkMatrix::kIdentity_Mask);
michael@0 530 }
michael@0 531 if (matrix.getType() == SkMatrix::kIdentity_Mask) {
michael@0 532 return;
michael@0 533 }
michael@0 534
michael@0 535 push();
michael@0 536 SkPDFUtils::AppendTransform(matrix, fContentStream);
michael@0 537 currentEntry()->fMatrix = matrix;
michael@0 538 }
michael@0 539
michael@0 540 void GraphicStackState::updateDrawingState(const GraphicStateEntry& state) {
michael@0 541 // PDF treats a shader as a color, so we only set one or the other.
michael@0 542 if (state.fShaderIndex >= 0) {
michael@0 543 if (state.fShaderIndex != currentEntry()->fShaderIndex) {
michael@0 544 SkPDFUtils::ApplyPattern(state.fShaderIndex, fContentStream);
michael@0 545 currentEntry()->fShaderIndex = state.fShaderIndex;
michael@0 546 }
michael@0 547 } else {
michael@0 548 if (state.fColor != currentEntry()->fColor ||
michael@0 549 currentEntry()->fShaderIndex >= 0) {
michael@0 550 emit_pdf_color(state.fColor, fContentStream);
michael@0 551 fContentStream->writeText("RG ");
michael@0 552 emit_pdf_color(state.fColor, fContentStream);
michael@0 553 fContentStream->writeText("rg\n");
michael@0 554 currentEntry()->fColor = state.fColor;
michael@0 555 currentEntry()->fShaderIndex = -1;
michael@0 556 }
michael@0 557 }
michael@0 558
michael@0 559 if (state.fGraphicStateIndex != currentEntry()->fGraphicStateIndex) {
michael@0 560 SkPDFUtils::ApplyGraphicState(state.fGraphicStateIndex, fContentStream);
michael@0 561 currentEntry()->fGraphicStateIndex = state.fGraphicStateIndex;
michael@0 562 }
michael@0 563
michael@0 564 if (state.fTextScaleX) {
michael@0 565 if (state.fTextScaleX != currentEntry()->fTextScaleX) {
michael@0 566 SkScalar pdfScale = SkScalarMul(state.fTextScaleX,
michael@0 567 SkIntToScalar(100));
michael@0 568 SkPDFScalar::Append(pdfScale, fContentStream);
michael@0 569 fContentStream->writeText(" Tz\n");
michael@0 570 currentEntry()->fTextScaleX = state.fTextScaleX;
michael@0 571 }
michael@0 572 if (state.fTextFill != currentEntry()->fTextFill) {
michael@0 573 SK_COMPILE_ASSERT(SkPaint::kFill_Style == 0, enum_must_match_value);
michael@0 574 SK_COMPILE_ASSERT(SkPaint::kStroke_Style == 1,
michael@0 575 enum_must_match_value);
michael@0 576 SK_COMPILE_ASSERT(SkPaint::kStrokeAndFill_Style == 2,
michael@0 577 enum_must_match_value);
michael@0 578 fContentStream->writeDecAsText(state.fTextFill);
michael@0 579 fContentStream->writeText(" Tr\n");
michael@0 580 currentEntry()->fTextFill = state.fTextFill;
michael@0 581 }
michael@0 582 }
michael@0 583 }
michael@0 584
michael@0 585 SkBaseDevice* SkPDFDevice::onCreateDevice(const SkImageInfo& info, Usage usage) {
michael@0 586 SkMatrix initialTransform;
michael@0 587 initialTransform.reset();
michael@0 588 SkISize size = SkISize::Make(info.width(), info.height());
michael@0 589 return SkNEW_ARGS(SkPDFDevice, (size, size, initialTransform));
michael@0 590 }
michael@0 591
michael@0 592
michael@0 593 struct ContentEntry {
michael@0 594 GraphicStateEntry fState;
michael@0 595 SkDynamicMemoryWStream fContent;
michael@0 596 SkAutoTDelete<ContentEntry> fNext;
michael@0 597
michael@0 598 // If the stack is too deep we could get Stack Overflow.
michael@0 599 // So we manually destruct the object.
michael@0 600 ~ContentEntry() {
michael@0 601 ContentEntry* val = fNext.detach();
michael@0 602 while (val != NULL) {
michael@0 603 ContentEntry* valNext = val->fNext.detach();
michael@0 604 // When the destructor is called, fNext is NULL and exits.
michael@0 605 delete val;
michael@0 606 val = valNext;
michael@0 607 }
michael@0 608 }
michael@0 609 };
michael@0 610
michael@0 611 // A helper class to automatically finish a ContentEntry at the end of a
michael@0 612 // drawing method and maintain the state needed between set up and finish.
michael@0 613 class ScopedContentEntry {
michael@0 614 public:
michael@0 615 ScopedContentEntry(SkPDFDevice* device, const SkDraw& draw,
michael@0 616 const SkPaint& paint, bool hasText = false)
michael@0 617 : fDevice(device),
michael@0 618 fContentEntry(NULL),
michael@0 619 fXfermode(SkXfermode::kSrcOver_Mode),
michael@0 620 fDstFormXObject(NULL) {
michael@0 621 init(draw.fClipStack, *draw.fClip, *draw.fMatrix, paint, hasText);
michael@0 622 }
michael@0 623 ScopedContentEntry(SkPDFDevice* device, const SkClipStack* clipStack,
michael@0 624 const SkRegion& clipRegion, const SkMatrix& matrix,
michael@0 625 const SkPaint& paint, bool hasText = false)
michael@0 626 : fDevice(device),
michael@0 627 fContentEntry(NULL),
michael@0 628 fXfermode(SkXfermode::kSrcOver_Mode),
michael@0 629 fDstFormXObject(NULL) {
michael@0 630 init(clipStack, clipRegion, matrix, paint, hasText);
michael@0 631 }
michael@0 632
michael@0 633 ~ScopedContentEntry() {
michael@0 634 if (fContentEntry) {
michael@0 635 SkPath* shape = &fShape;
michael@0 636 if (shape->isEmpty()) {
michael@0 637 shape = NULL;
michael@0 638 }
michael@0 639 fDevice->finishContentEntry(fXfermode, fDstFormXObject, shape);
michael@0 640 }
michael@0 641 SkSafeUnref(fDstFormXObject);
michael@0 642 }
michael@0 643
michael@0 644 ContentEntry* entry() { return fContentEntry; }
michael@0 645
michael@0 646 /* Returns true when we explicitly need the shape of the drawing. */
michael@0 647 bool needShape() {
michael@0 648 switch (fXfermode) {
michael@0 649 case SkXfermode::kClear_Mode:
michael@0 650 case SkXfermode::kSrc_Mode:
michael@0 651 case SkXfermode::kSrcIn_Mode:
michael@0 652 case SkXfermode::kSrcOut_Mode:
michael@0 653 case SkXfermode::kDstIn_Mode:
michael@0 654 case SkXfermode::kDstOut_Mode:
michael@0 655 case SkXfermode::kSrcATop_Mode:
michael@0 656 case SkXfermode::kDstATop_Mode:
michael@0 657 case SkXfermode::kModulate_Mode:
michael@0 658 return true;
michael@0 659 default:
michael@0 660 return false;
michael@0 661 }
michael@0 662 }
michael@0 663
michael@0 664 /* Returns true unless we only need the shape of the drawing. */
michael@0 665 bool needSource() {
michael@0 666 if (fXfermode == SkXfermode::kClear_Mode) {
michael@0 667 return false;
michael@0 668 }
michael@0 669 return true;
michael@0 670 }
michael@0 671
michael@0 672 /* If the shape is different than the alpha component of the content, then
michael@0 673 * setShape should be called with the shape. In particular, images and
michael@0 674 * devices have rectangular shape.
michael@0 675 */
michael@0 676 void setShape(const SkPath& shape) {
michael@0 677 fShape = shape;
michael@0 678 }
michael@0 679
michael@0 680 private:
michael@0 681 SkPDFDevice* fDevice;
michael@0 682 ContentEntry* fContentEntry;
michael@0 683 SkXfermode::Mode fXfermode;
michael@0 684 SkPDFFormXObject* fDstFormXObject;
michael@0 685 SkPath fShape;
michael@0 686
michael@0 687 void init(const SkClipStack* clipStack, const SkRegion& clipRegion,
michael@0 688 const SkMatrix& matrix, const SkPaint& paint, bool hasText) {
michael@0 689 // Shape has to be flatten before we get here.
michael@0 690 if (matrix.hasPerspective()) {
michael@0 691 NOT_IMPLEMENTED(!matrix.hasPerspective(), false);
michael@0 692 return;
michael@0 693 }
michael@0 694 if (paint.getXfermode()) {
michael@0 695 paint.getXfermode()->asMode(&fXfermode);
michael@0 696 }
michael@0 697 fContentEntry = fDevice->setUpContentEntry(clipStack, clipRegion,
michael@0 698 matrix, paint, hasText,
michael@0 699 &fDstFormXObject);
michael@0 700 }
michael@0 701 };
michael@0 702
michael@0 703 ////////////////////////////////////////////////////////////////////////////////
michael@0 704
michael@0 705 static inline SkBitmap makeContentBitmap(const SkISize& contentSize,
michael@0 706 const SkMatrix* initialTransform) {
michael@0 707 SkImageInfo info;
michael@0 708 if (initialTransform) {
michael@0 709 // Compute the size of the drawing area.
michael@0 710 SkVector drawingSize;
michael@0 711 SkMatrix inverse;
michael@0 712 drawingSize.set(SkIntToScalar(contentSize.fWidth),
michael@0 713 SkIntToScalar(contentSize.fHeight));
michael@0 714 if (!initialTransform->invert(&inverse)) {
michael@0 715 // This shouldn't happen, initial transform should be invertible.
michael@0 716 SkASSERT(false);
michael@0 717 inverse.reset();
michael@0 718 }
michael@0 719 inverse.mapVectors(&drawingSize, 1);
michael@0 720 SkISize size = SkSize::Make(drawingSize.fX, drawingSize.fY).toRound();
michael@0 721 info = SkImageInfo::MakeUnknown(abs(size.fWidth), abs(size.fHeight));
michael@0 722 } else {
michael@0 723 info = SkImageInfo::MakeUnknown(abs(contentSize.fWidth),
michael@0 724 abs(contentSize.fHeight));
michael@0 725 }
michael@0 726
michael@0 727 SkBitmap bitmap;
michael@0 728 bitmap.setConfig(info);
michael@0 729 return bitmap;
michael@0 730 }
michael@0 731
michael@0 732 // TODO(vandebo) change pageSize to SkSize.
michael@0 733 // TODO: inherit from SkBaseDevice instead of SkBitmapDevice
michael@0 734 SkPDFDevice::SkPDFDevice(const SkISize& pageSize, const SkISize& contentSize,
michael@0 735 const SkMatrix& initialTransform)
michael@0 736 : SkBitmapDevice(makeContentBitmap(contentSize, &initialTransform)),
michael@0 737 fPageSize(pageSize),
michael@0 738 fContentSize(contentSize),
michael@0 739 fLastContentEntry(NULL),
michael@0 740 fLastMarginContentEntry(NULL),
michael@0 741 fClipStack(NULL),
michael@0 742 fEncoder(NULL),
michael@0 743 fRasterDpi(72.0f) {
michael@0 744 // Just report that PDF does not supports perspective in the
michael@0 745 // initial transform.
michael@0 746 NOT_IMPLEMENTED(initialTransform.hasPerspective(), true);
michael@0 747
michael@0 748 // Skia generally uses the top left as the origin but PDF natively has the
michael@0 749 // origin at the bottom left. This matrix corrects for that. But that only
michael@0 750 // needs to be done once, we don't do it when layering.
michael@0 751 fInitialTransform.setTranslate(0, SkIntToScalar(pageSize.fHeight));
michael@0 752 fInitialTransform.preScale(SK_Scalar1, -SK_Scalar1);
michael@0 753 fInitialTransform.preConcat(initialTransform);
michael@0 754
michael@0 755 SkIRect existingClip = SkIRect::MakeWH(this->width(), this->height());
michael@0 756 fExistingClipRegion.setRect(existingClip);
michael@0 757
michael@0 758 this->init();
michael@0 759 }
michael@0 760
michael@0 761 // TODO(vandebo) change layerSize to SkSize.
michael@0 762 SkPDFDevice::SkPDFDevice(const SkISize& layerSize,
michael@0 763 const SkClipStack& existingClipStack,
michael@0 764 const SkRegion& existingClipRegion)
michael@0 765 : SkBitmapDevice(makeContentBitmap(layerSize, NULL)),
michael@0 766 fPageSize(layerSize),
michael@0 767 fContentSize(layerSize),
michael@0 768 fExistingClipStack(existingClipStack),
michael@0 769 fExistingClipRegion(existingClipRegion),
michael@0 770 fLastContentEntry(NULL),
michael@0 771 fLastMarginContentEntry(NULL),
michael@0 772 fClipStack(NULL),
michael@0 773 fEncoder(NULL),
michael@0 774 fRasterDpi(72.0f) {
michael@0 775 fInitialTransform.reset();
michael@0 776 this->init();
michael@0 777 }
michael@0 778
michael@0 779 SkPDFDevice::~SkPDFDevice() {
michael@0 780 this->cleanUp(true);
michael@0 781 }
michael@0 782
michael@0 783 void SkPDFDevice::init() {
michael@0 784 fAnnotations = NULL;
michael@0 785 fResourceDict = NULL;
michael@0 786 fContentEntries.free();
michael@0 787 fLastContentEntry = NULL;
michael@0 788 fMarginContentEntries.free();
michael@0 789 fLastMarginContentEntry = NULL;
michael@0 790 fDrawingArea = kContent_DrawingArea;
michael@0 791 if (fFontGlyphUsage.get() == NULL) {
michael@0 792 fFontGlyphUsage.reset(new SkPDFGlyphSetMap());
michael@0 793 }
michael@0 794 }
michael@0 795
michael@0 796 void SkPDFDevice::cleanUp(bool clearFontUsage) {
michael@0 797 fGraphicStateResources.unrefAll();
michael@0 798 fXObjectResources.unrefAll();
michael@0 799 fFontResources.unrefAll();
michael@0 800 fShaderResources.unrefAll();
michael@0 801 SkSafeUnref(fAnnotations);
michael@0 802 SkSafeUnref(fResourceDict);
michael@0 803 fNamedDestinations.deleteAll();
michael@0 804
michael@0 805 if (clearFontUsage) {
michael@0 806 fFontGlyphUsage->reset();
michael@0 807 }
michael@0 808 }
michael@0 809
michael@0 810 void SkPDFDevice::clear(SkColor color) {
michael@0 811 this->cleanUp(true);
michael@0 812 this->init();
michael@0 813
michael@0 814 SkPaint paint;
michael@0 815 paint.setColor(color);
michael@0 816 paint.setStyle(SkPaint::kFill_Style);
michael@0 817 SkMatrix identity;
michael@0 818 identity.reset();
michael@0 819 ScopedContentEntry content(this, &fExistingClipStack, fExistingClipRegion,
michael@0 820 identity, paint);
michael@0 821 internalDrawPaint(paint, content.entry());
michael@0 822 }
michael@0 823
michael@0 824 void SkPDFDevice::drawPaint(const SkDraw& d, const SkPaint& paint) {
michael@0 825 SkPaint newPaint = paint;
michael@0 826 newPaint.setStyle(SkPaint::kFill_Style);
michael@0 827 ScopedContentEntry content(this, d, newPaint);
michael@0 828 internalDrawPaint(newPaint, content.entry());
michael@0 829 }
michael@0 830
michael@0 831 void SkPDFDevice::internalDrawPaint(const SkPaint& paint,
michael@0 832 ContentEntry* contentEntry) {
michael@0 833 if (!contentEntry) {
michael@0 834 return;
michael@0 835 }
michael@0 836 SkRect bbox = SkRect::MakeWH(SkIntToScalar(this->width()),
michael@0 837 SkIntToScalar(this->height()));
michael@0 838 SkMatrix inverse;
michael@0 839 if (!contentEntry->fState.fMatrix.invert(&inverse)) {
michael@0 840 return;
michael@0 841 }
michael@0 842 inverse.mapRect(&bbox);
michael@0 843
michael@0 844 SkPDFUtils::AppendRectangle(bbox, &contentEntry->fContent);
michael@0 845 SkPDFUtils::PaintPath(paint.getStyle(), SkPath::kWinding_FillType,
michael@0 846 &contentEntry->fContent);
michael@0 847 }
michael@0 848
michael@0 849 void SkPDFDevice::drawPoints(const SkDraw& d, SkCanvas::PointMode mode,
michael@0 850 size_t count, const SkPoint* points,
michael@0 851 const SkPaint& passedPaint) {
michael@0 852 if (count == 0) {
michael@0 853 return;
michael@0 854 }
michael@0 855
michael@0 856 if (handlePointAnnotation(points, count, *d.fMatrix, passedPaint)) {
michael@0 857 return;
michael@0 858 }
michael@0 859
michael@0 860 // SkDraw::drawPoints converts to multiple calls to fDevice->drawPath.
michael@0 861 // We only use this when there's a path effect because of the overhead
michael@0 862 // of multiple calls to setUpContentEntry it causes.
michael@0 863 if (passedPaint.getPathEffect()) {
michael@0 864 if (d.fClip->isEmpty()) {
michael@0 865 return;
michael@0 866 }
michael@0 867 SkDraw pointDraw(d);
michael@0 868 pointDraw.fDevice = this;
michael@0 869 pointDraw.drawPoints(mode, count, points, passedPaint, true);
michael@0 870 return;
michael@0 871 }
michael@0 872
michael@0 873 const SkPaint* paint = &passedPaint;
michael@0 874 SkPaint modifiedPaint;
michael@0 875
michael@0 876 if (mode == SkCanvas::kPoints_PointMode &&
michael@0 877 paint->getStrokeCap() != SkPaint::kRound_Cap) {
michael@0 878 modifiedPaint = *paint;
michael@0 879 paint = &modifiedPaint;
michael@0 880 if (paint->getStrokeWidth()) {
michael@0 881 // PDF won't draw a single point with square/butt caps because the
michael@0 882 // orientation is ambiguous. Draw a rectangle instead.
michael@0 883 modifiedPaint.setStyle(SkPaint::kFill_Style);
michael@0 884 SkScalar strokeWidth = paint->getStrokeWidth();
michael@0 885 SkScalar halfStroke = SkScalarHalf(strokeWidth);
michael@0 886 for (size_t i = 0; i < count; i++) {
michael@0 887 SkRect r = SkRect::MakeXYWH(points[i].fX, points[i].fY, 0, 0);
michael@0 888 r.inset(-halfStroke, -halfStroke);
michael@0 889 drawRect(d, r, modifiedPaint);
michael@0 890 }
michael@0 891 return;
michael@0 892 } else {
michael@0 893 modifiedPaint.setStrokeCap(SkPaint::kRound_Cap);
michael@0 894 }
michael@0 895 }
michael@0 896
michael@0 897 ScopedContentEntry content(this, d, *paint);
michael@0 898 if (!content.entry()) {
michael@0 899 return;
michael@0 900 }
michael@0 901
michael@0 902 switch (mode) {
michael@0 903 case SkCanvas::kPolygon_PointMode:
michael@0 904 SkPDFUtils::MoveTo(points[0].fX, points[0].fY,
michael@0 905 &content.entry()->fContent);
michael@0 906 for (size_t i = 1; i < count; i++) {
michael@0 907 SkPDFUtils::AppendLine(points[i].fX, points[i].fY,
michael@0 908 &content.entry()->fContent);
michael@0 909 }
michael@0 910 SkPDFUtils::StrokePath(&content.entry()->fContent);
michael@0 911 break;
michael@0 912 case SkCanvas::kLines_PointMode:
michael@0 913 for (size_t i = 0; i < count/2; i++) {
michael@0 914 SkPDFUtils::MoveTo(points[i * 2].fX, points[i * 2].fY,
michael@0 915 &content.entry()->fContent);
michael@0 916 SkPDFUtils::AppendLine(points[i * 2 + 1].fX,
michael@0 917 points[i * 2 + 1].fY,
michael@0 918 &content.entry()->fContent);
michael@0 919 SkPDFUtils::StrokePath(&content.entry()->fContent);
michael@0 920 }
michael@0 921 break;
michael@0 922 case SkCanvas::kPoints_PointMode:
michael@0 923 SkASSERT(paint->getStrokeCap() == SkPaint::kRound_Cap);
michael@0 924 for (size_t i = 0; i < count; i++) {
michael@0 925 SkPDFUtils::MoveTo(points[i].fX, points[i].fY,
michael@0 926 &content.entry()->fContent);
michael@0 927 SkPDFUtils::ClosePath(&content.entry()->fContent);
michael@0 928 SkPDFUtils::StrokePath(&content.entry()->fContent);
michael@0 929 }
michael@0 930 break;
michael@0 931 default:
michael@0 932 SkASSERT(false);
michael@0 933 }
michael@0 934 }
michael@0 935
michael@0 936 void SkPDFDevice::drawRect(const SkDraw& d, const SkRect& rect,
michael@0 937 const SkPaint& paint) {
michael@0 938 SkRect r = rect;
michael@0 939 r.sort();
michael@0 940
michael@0 941 if (paint.getPathEffect()) {
michael@0 942 if (d.fClip->isEmpty()) {
michael@0 943 return;
michael@0 944 }
michael@0 945 SkPath path;
michael@0 946 path.addRect(r);
michael@0 947 drawPath(d, path, paint, NULL, true);
michael@0 948 return;
michael@0 949 }
michael@0 950
michael@0 951 if (handleRectAnnotation(r, *d.fMatrix, paint)) {
michael@0 952 return;
michael@0 953 }
michael@0 954
michael@0 955 ScopedContentEntry content(this, d, paint);
michael@0 956 if (!content.entry()) {
michael@0 957 return;
michael@0 958 }
michael@0 959 SkPDFUtils::AppendRectangle(r, &content.entry()->fContent);
michael@0 960 SkPDFUtils::PaintPath(paint.getStyle(), SkPath::kWinding_FillType,
michael@0 961 &content.entry()->fContent);
michael@0 962 }
michael@0 963
michael@0 964 void SkPDFDevice::drawRRect(const SkDraw& draw, const SkRRect& rrect,
michael@0 965 const SkPaint& paint) {
michael@0 966 SkPath path;
michael@0 967 path.addRRect(rrect);
michael@0 968 this->drawPath(draw, path, paint, NULL, true);
michael@0 969 }
michael@0 970
michael@0 971 void SkPDFDevice::drawPath(const SkDraw& d, const SkPath& origPath,
michael@0 972 const SkPaint& paint, const SkMatrix* prePathMatrix,
michael@0 973 bool pathIsMutable) {
michael@0 974 SkPath modifiedPath;
michael@0 975 SkPath* pathPtr = const_cast<SkPath*>(&origPath);
michael@0 976
michael@0 977 SkMatrix matrix = *d.fMatrix;
michael@0 978 if (prePathMatrix) {
michael@0 979 if (paint.getPathEffect() || paint.getStyle() != SkPaint::kFill_Style) {
michael@0 980 if (!pathIsMutable) {
michael@0 981 pathPtr = &modifiedPath;
michael@0 982 pathIsMutable = true;
michael@0 983 }
michael@0 984 origPath.transform(*prePathMatrix, pathPtr);
michael@0 985 } else {
michael@0 986 if (!matrix.preConcat(*prePathMatrix)) {
michael@0 987 // TODO(edisonn): report somehow why we failed?
michael@0 988 return;
michael@0 989 }
michael@0 990 }
michael@0 991 }
michael@0 992
michael@0 993 if (paint.getPathEffect()) {
michael@0 994 if (d.fClip->isEmpty()) {
michael@0 995 return;
michael@0 996 }
michael@0 997 if (!pathIsMutable) {
michael@0 998 pathPtr = &modifiedPath;
michael@0 999 pathIsMutable = true;
michael@0 1000 }
michael@0 1001 bool fill = paint.getFillPath(origPath, pathPtr);
michael@0 1002
michael@0 1003 SkPaint noEffectPaint(paint);
michael@0 1004 noEffectPaint.setPathEffect(NULL);
michael@0 1005 if (fill) {
michael@0 1006 noEffectPaint.setStyle(SkPaint::kFill_Style);
michael@0 1007 } else {
michael@0 1008 noEffectPaint.setStyle(SkPaint::kStroke_Style);
michael@0 1009 noEffectPaint.setStrokeWidth(0);
michael@0 1010 }
michael@0 1011 drawPath(d, *pathPtr, noEffectPaint, NULL, true);
michael@0 1012 return;
michael@0 1013 }
michael@0 1014
michael@0 1015 #ifdef SK_PDF_USE_PATHOPS
michael@0 1016 if (handleInversePath(d, origPath, paint, pathIsMutable, prePathMatrix)) {
michael@0 1017 return;
michael@0 1018 }
michael@0 1019 #endif
michael@0 1020
michael@0 1021 if (handleRectAnnotation(pathPtr->getBounds(), matrix, paint)) {
michael@0 1022 return;
michael@0 1023 }
michael@0 1024
michael@0 1025 ScopedContentEntry content(this, d.fClipStack, *d.fClip, matrix, paint);
michael@0 1026 if (!content.entry()) {
michael@0 1027 return;
michael@0 1028 }
michael@0 1029 SkPDFUtils::EmitPath(*pathPtr, paint.getStyle(),
michael@0 1030 &content.entry()->fContent);
michael@0 1031 SkPDFUtils::PaintPath(paint.getStyle(), pathPtr->getFillType(),
michael@0 1032 &content.entry()->fContent);
michael@0 1033 }
michael@0 1034
michael@0 1035 void SkPDFDevice::drawBitmapRect(const SkDraw& draw, const SkBitmap& bitmap,
michael@0 1036 const SkRect* src, const SkRect& dst,
michael@0 1037 const SkPaint& paint,
michael@0 1038 SkCanvas::DrawBitmapRectFlags flags) {
michael@0 1039 // TODO: this code path must be updated to respect the flags parameter
michael@0 1040 SkMatrix matrix;
michael@0 1041 SkRect bitmapBounds, tmpSrc, tmpDst;
michael@0 1042 SkBitmap tmpBitmap;
michael@0 1043
michael@0 1044 bitmapBounds.isetWH(bitmap.width(), bitmap.height());
michael@0 1045
michael@0 1046 // Compute matrix from the two rectangles
michael@0 1047 if (src) {
michael@0 1048 tmpSrc = *src;
michael@0 1049 } else {
michael@0 1050 tmpSrc = bitmapBounds;
michael@0 1051 }
michael@0 1052 matrix.setRectToRect(tmpSrc, dst, SkMatrix::kFill_ScaleToFit);
michael@0 1053
michael@0 1054 const SkBitmap* bitmapPtr = &bitmap;
michael@0 1055
michael@0 1056 // clip the tmpSrc to the bounds of the bitmap, and recompute dstRect if
michael@0 1057 // needed (if the src was clipped). No check needed if src==null.
michael@0 1058 if (src) {
michael@0 1059 if (!bitmapBounds.contains(*src)) {
michael@0 1060 if (!tmpSrc.intersect(bitmapBounds)) {
michael@0 1061 return; // nothing to draw
michael@0 1062 }
michael@0 1063 // recompute dst, based on the smaller tmpSrc
michael@0 1064 matrix.mapRect(&tmpDst, tmpSrc);
michael@0 1065 }
michael@0 1066
michael@0 1067 // since we may need to clamp to the borders of the src rect within
michael@0 1068 // the bitmap, we extract a subset.
michael@0 1069 // TODO: make sure this is handled in drawBitmap and remove from here.
michael@0 1070 SkIRect srcIR;
michael@0 1071 tmpSrc.roundOut(&srcIR);
michael@0 1072 if (!bitmap.extractSubset(&tmpBitmap, srcIR)) {
michael@0 1073 return;
michael@0 1074 }
michael@0 1075 bitmapPtr = &tmpBitmap;
michael@0 1076
michael@0 1077 // Since we did an extract, we need to adjust the matrix accordingly
michael@0 1078 SkScalar dx = 0, dy = 0;
michael@0 1079 if (srcIR.fLeft > 0) {
michael@0 1080 dx = SkIntToScalar(srcIR.fLeft);
michael@0 1081 }
michael@0 1082 if (srcIR.fTop > 0) {
michael@0 1083 dy = SkIntToScalar(srcIR.fTop);
michael@0 1084 }
michael@0 1085 if (dx || dy) {
michael@0 1086 matrix.preTranslate(dx, dy);
michael@0 1087 }
michael@0 1088 }
michael@0 1089 this->drawBitmap(draw, *bitmapPtr, matrix, paint);
michael@0 1090 }
michael@0 1091
michael@0 1092 void SkPDFDevice::drawBitmap(const SkDraw& d, const SkBitmap& bitmap,
michael@0 1093 const SkMatrix& matrix, const SkPaint& paint) {
michael@0 1094 if (d.fClip->isEmpty()) {
michael@0 1095 return;
michael@0 1096 }
michael@0 1097
michael@0 1098 SkMatrix transform = matrix;
michael@0 1099 transform.postConcat(*d.fMatrix);
michael@0 1100 this->internalDrawBitmap(transform, d.fClipStack, *d.fClip, bitmap, NULL,
michael@0 1101 paint);
michael@0 1102 }
michael@0 1103
michael@0 1104 void SkPDFDevice::drawSprite(const SkDraw& d, const SkBitmap& bitmap,
michael@0 1105 int x, int y, const SkPaint& paint) {
michael@0 1106 if (d.fClip->isEmpty()) {
michael@0 1107 return;
michael@0 1108 }
michael@0 1109
michael@0 1110 SkMatrix matrix;
michael@0 1111 matrix.setTranslate(SkIntToScalar(x), SkIntToScalar(y));
michael@0 1112 this->internalDrawBitmap(matrix, d.fClipStack, *d.fClip, bitmap, NULL,
michael@0 1113 paint);
michael@0 1114 }
michael@0 1115
michael@0 1116 void SkPDFDevice::drawText(const SkDraw& d, const void* text, size_t len,
michael@0 1117 SkScalar x, SkScalar y, const SkPaint& paint) {
michael@0 1118 NOT_IMPLEMENTED(paint.getMaskFilter() != NULL, false);
michael@0 1119 if (paint.getMaskFilter() != NULL) {
michael@0 1120 // Don't pretend we support drawing MaskFilters, it makes for artifacts
michael@0 1121 // making text unreadable (e.g. same text twice when using CSS shadows).
michael@0 1122 return;
michael@0 1123 }
michael@0 1124 SkPaint textPaint = calculate_text_paint(paint);
michael@0 1125 ScopedContentEntry content(this, d, textPaint, true);
michael@0 1126 if (!content.entry()) {
michael@0 1127 return;
michael@0 1128 }
michael@0 1129
michael@0 1130 SkGlyphStorage storage(0);
michael@0 1131 uint16_t* glyphIDs = NULL;
michael@0 1132 size_t numGlyphs = force_glyph_encoding(paint, text, len, &storage,
michael@0 1133 &glyphIDs);
michael@0 1134 textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
michael@0 1135
michael@0 1136 SkDrawCacheProc glyphCacheProc = textPaint.getDrawCacheProc();
michael@0 1137 align_text(glyphCacheProc, textPaint, glyphIDs, numGlyphs, &x, &y);
michael@0 1138 content.entry()->fContent.writeText("BT\n");
michael@0 1139 set_text_transform(x, y, textPaint.getTextSkewX(),
michael@0 1140 &content.entry()->fContent);
michael@0 1141 size_t consumedGlyphCount = 0;
michael@0 1142 while (numGlyphs > consumedGlyphCount) {
michael@0 1143 updateFont(textPaint, glyphIDs[consumedGlyphCount], content.entry());
michael@0 1144 SkPDFFont* font = content.entry()->fState.fFont;
michael@0 1145 size_t availableGlyphs =
michael@0 1146 font->glyphsToPDFFontEncoding(glyphIDs + consumedGlyphCount,
michael@0 1147 numGlyphs - consumedGlyphCount);
michael@0 1148 fFontGlyphUsage->noteGlyphUsage(font, glyphIDs + consumedGlyphCount,
michael@0 1149 availableGlyphs);
michael@0 1150 SkString encodedString =
michael@0 1151 SkPDFString::FormatString(glyphIDs + consumedGlyphCount,
michael@0 1152 availableGlyphs, font->multiByteGlyphs());
michael@0 1153 content.entry()->fContent.writeText(encodedString.c_str());
michael@0 1154 consumedGlyphCount += availableGlyphs;
michael@0 1155 content.entry()->fContent.writeText(" Tj\n");
michael@0 1156 }
michael@0 1157 content.entry()->fContent.writeText("ET\n");
michael@0 1158 }
michael@0 1159
michael@0 1160 void SkPDFDevice::drawPosText(const SkDraw& d, const void* text, size_t len,
michael@0 1161 const SkScalar pos[], SkScalar constY,
michael@0 1162 int scalarsPerPos, const SkPaint& paint) {
michael@0 1163 NOT_IMPLEMENTED(paint.getMaskFilter() != NULL, false);
michael@0 1164 if (paint.getMaskFilter() != NULL) {
michael@0 1165 // Don't pretend we support drawing MaskFilters, it makes for artifacts
michael@0 1166 // making text unreadable (e.g. same text twice when using CSS shadows).
michael@0 1167 return;
michael@0 1168 }
michael@0 1169 SkASSERT(1 == scalarsPerPos || 2 == scalarsPerPos);
michael@0 1170 SkPaint textPaint = calculate_text_paint(paint);
michael@0 1171 ScopedContentEntry content(this, d, textPaint, true);
michael@0 1172 if (!content.entry()) {
michael@0 1173 return;
michael@0 1174 }
michael@0 1175
michael@0 1176 #ifdef SK_BUILD_FOR_ANDROID
michael@0 1177 /*
michael@0 1178 * In the case that we have enabled fallback fonts on Android we need to
michael@0 1179 * take the following steps to ensure that the PDF draws all characters,
michael@0 1180 * regardless of their underlying font file, correctly.
michael@0 1181 *
michael@0 1182 * 1. Convert input into GlyphID encoding if it currently is not
michael@0 1183 * 2. Iterate over the glyphIDs and identify the actual typeface that each
michael@0 1184 * glyph resolves to
michael@0 1185 * 3. Iterate over those typefaces and recursively call this function with
michael@0 1186 * only the glyphs (and their positions) that the typeface is capable of
michael@0 1187 * resolving.
michael@0 1188 */
michael@0 1189 if (paint.getPaintOptionsAndroid().isUsingFontFallbacks()) {
michael@0 1190 uint16_t* glyphIDs = NULL;
michael@0 1191 SkGlyphStorage tmpStorage(0);
michael@0 1192 size_t numGlyphs = 0;
michael@0 1193
michael@0 1194 // convert to glyphIDs
michael@0 1195 if (paint.getTextEncoding() == SkPaint::kGlyphID_TextEncoding) {
michael@0 1196 numGlyphs = len / 2;
michael@0 1197 glyphIDs = reinterpret_cast<uint16_t*>(const_cast<void*>(text));
michael@0 1198 } else {
michael@0 1199 numGlyphs = paint.textToGlyphs(text, len, NULL);
michael@0 1200 tmpStorage.reset(numGlyphs);
michael@0 1201 paint.textToGlyphs(text, len, tmpStorage.get());
michael@0 1202 glyphIDs = tmpStorage.get();
michael@0 1203 }
michael@0 1204
michael@0 1205 // if no typeface is provided in the paint get the default
michael@0 1206 SkAutoTUnref<SkTypeface> origFace(SkSafeRef(paint.getTypeface()));
michael@0 1207 if (NULL == origFace.get()) {
michael@0 1208 origFace.reset(SkTypeface::RefDefault());
michael@0 1209 }
michael@0 1210 const uint16_t origGlyphCount = origFace->countGlyphs();
michael@0 1211
michael@0 1212 // keep a list of the already visited typefaces and some data about them
michael@0 1213 SkTDArray<TypefaceFallbackData> visitedTypefaces;
michael@0 1214
michael@0 1215 // find all the typefaces needed to resolve this run of text
michael@0 1216 bool usesOriginalTypeface = false;
michael@0 1217 for (uint16_t x = 0; x < numGlyphs; ++x) {
michael@0 1218 // optimization that checks to see if original typeface can resolve
michael@0 1219 // the glyph
michael@0 1220 if (glyphIDs[x] < origGlyphCount) {
michael@0 1221 usesOriginalTypeface = true;
michael@0 1222 continue;
michael@0 1223 }
michael@0 1224
michael@0 1225 // find the fallback typeface that supports this glyph
michael@0 1226 TypefaceFallbackData data;
michael@0 1227 data.typeface =
michael@0 1228 SkGetTypefaceForGlyphID(glyphIDs[x], origFace.get(),
michael@0 1229 paint.getPaintOptionsAndroid(),
michael@0 1230 &data.lowerBounds,
michael@0 1231 &data.upperBounds);
michael@0 1232 // add the typeface and its data if we don't have it
michael@0 1233 if (data.typeface && !visitedTypefaces.contains(data)) {
michael@0 1234 visitedTypefaces.push(data);
michael@0 1235 }
michael@0 1236 }
michael@0 1237
michael@0 1238 // if the original font was used then add it to the list as well
michael@0 1239 if (usesOriginalTypeface) {
michael@0 1240 TypefaceFallbackData* data = visitedTypefaces.push();
michael@0 1241 data->typeface = origFace.get();
michael@0 1242 data->lowerBounds = 0;
michael@0 1243 data->upperBounds = origGlyphCount;
michael@0 1244 }
michael@0 1245
michael@0 1246 // keep a scratch glyph and pos storage
michael@0 1247 SkAutoTMalloc<SkScalar> posStorage(len * scalarsPerPos);
michael@0 1248 SkScalar* tmpPos = posStorage.get();
michael@0 1249 SkGlyphStorage glyphStorage(numGlyphs);
michael@0 1250 uint16_t* tmpGlyphIDs = glyphStorage.get();
michael@0 1251
michael@0 1252 // loop through all the valid typefaces, trim the glyphs to only those
michael@0 1253 // resolved by the typeface, and then draw that run of glyphs
michael@0 1254 for (int x = 0; x < visitedTypefaces.count(); ++x) {
michael@0 1255 const TypefaceFallbackData& data = visitedTypefaces[x];
michael@0 1256
michael@0 1257 int tmpGlyphCount = 0;
michael@0 1258 for (uint16_t y = 0; y < numGlyphs; ++y) {
michael@0 1259 if (glyphIDs[y] >= data.lowerBounds &&
michael@0 1260 glyphIDs[y] < data.upperBounds) {
michael@0 1261 tmpGlyphIDs[tmpGlyphCount] = glyphIDs[y] - data.lowerBounds;
michael@0 1262 memcpy(&(tmpPos[tmpGlyphCount * scalarsPerPos]),
michael@0 1263 &(pos[y * scalarsPerPos]),
michael@0 1264 scalarsPerPos * sizeof(SkScalar));
michael@0 1265 tmpGlyphCount++;
michael@0 1266 }
michael@0 1267 }
michael@0 1268
michael@0 1269 // recursively call this function with the right typeface
michael@0 1270 SkPaint tmpPaint = paint;
michael@0 1271 tmpPaint.setTypeface(data.typeface);
michael@0 1272 tmpPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
michael@0 1273
michael@0 1274 // turn off fallback chaining
michael@0 1275 SkPaintOptionsAndroid paintOpts = tmpPaint.getPaintOptionsAndroid();
michael@0 1276 paintOpts.setUseFontFallbacks(false);
michael@0 1277 tmpPaint.setPaintOptionsAndroid(paintOpts);
michael@0 1278
michael@0 1279 this->drawPosText(d, tmpGlyphIDs, tmpGlyphCount * 2, tmpPos, constY,
michael@0 1280 scalarsPerPos, tmpPaint);
michael@0 1281 }
michael@0 1282 return;
michael@0 1283 }
michael@0 1284 #endif
michael@0 1285
michael@0 1286 SkGlyphStorage storage(0);
michael@0 1287 uint16_t* glyphIDs = NULL;
michael@0 1288 size_t numGlyphs = force_glyph_encoding(paint, text, len, &storage,
michael@0 1289 &glyphIDs);
michael@0 1290 textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
michael@0 1291
michael@0 1292 SkDrawCacheProc glyphCacheProc = textPaint.getDrawCacheProc();
michael@0 1293 content.entry()->fContent.writeText("BT\n");
michael@0 1294 updateFont(textPaint, glyphIDs[0], content.entry());
michael@0 1295 for (size_t i = 0; i < numGlyphs; i++) {
michael@0 1296 SkPDFFont* font = content.entry()->fState.fFont;
michael@0 1297 uint16_t encodedValue = glyphIDs[i];
michael@0 1298 if (font->glyphsToPDFFontEncoding(&encodedValue, 1) != 1) {
michael@0 1299 updateFont(textPaint, glyphIDs[i], content.entry());
michael@0 1300 i--;
michael@0 1301 continue;
michael@0 1302 }
michael@0 1303 fFontGlyphUsage->noteGlyphUsage(font, &encodedValue, 1);
michael@0 1304 SkScalar x = pos[i * scalarsPerPos];
michael@0 1305 SkScalar y = scalarsPerPos == 1 ? constY : pos[i * scalarsPerPos + 1];
michael@0 1306 align_text(glyphCacheProc, textPaint, glyphIDs + i, 1, &x, &y);
michael@0 1307 set_text_transform(x, y, textPaint.getTextSkewX(),
michael@0 1308 &content.entry()->fContent);
michael@0 1309 SkString encodedString =
michael@0 1310 SkPDFString::FormatString(&encodedValue, 1,
michael@0 1311 font->multiByteGlyphs());
michael@0 1312 content.entry()->fContent.writeText(encodedString.c_str());
michael@0 1313 content.entry()->fContent.writeText(" Tj\n");
michael@0 1314 }
michael@0 1315 content.entry()->fContent.writeText("ET\n");
michael@0 1316 }
michael@0 1317
michael@0 1318 void SkPDFDevice::drawTextOnPath(const SkDraw& d, const void* text, size_t len,
michael@0 1319 const SkPath& path, const SkMatrix* matrix,
michael@0 1320 const SkPaint& paint) {
michael@0 1321 if (d.fClip->isEmpty()) {
michael@0 1322 return;
michael@0 1323 }
michael@0 1324 d.drawTextOnPath((const char*)text, len, path, matrix, paint);
michael@0 1325 }
michael@0 1326
michael@0 1327 void SkPDFDevice::drawVertices(const SkDraw& d, SkCanvas::VertexMode,
michael@0 1328 int vertexCount, const SkPoint verts[],
michael@0 1329 const SkPoint texs[], const SkColor colors[],
michael@0 1330 SkXfermode* xmode, const uint16_t indices[],
michael@0 1331 int indexCount, const SkPaint& paint) {
michael@0 1332 if (d.fClip->isEmpty()) {
michael@0 1333 return;
michael@0 1334 }
michael@0 1335 // TODO: implement drawVertices
michael@0 1336 }
michael@0 1337
michael@0 1338 void SkPDFDevice::drawDevice(const SkDraw& d, SkBaseDevice* device,
michael@0 1339 int x, int y, const SkPaint& paint) {
michael@0 1340 // our onCreateDevice() always creates SkPDFDevice subclasses.
michael@0 1341 SkPDFDevice* pdfDevice = static_cast<SkPDFDevice*>(device);
michael@0 1342 if (pdfDevice->isContentEmpty()) {
michael@0 1343 return;
michael@0 1344 }
michael@0 1345
michael@0 1346 SkMatrix matrix;
michael@0 1347 matrix.setTranslate(SkIntToScalar(x), SkIntToScalar(y));
michael@0 1348 ScopedContentEntry content(this, d.fClipStack, *d.fClip, matrix, paint);
michael@0 1349 if (!content.entry()) {
michael@0 1350 return;
michael@0 1351 }
michael@0 1352 if (content.needShape()) {
michael@0 1353 SkPath shape;
michael@0 1354 shape.addRect(SkRect::MakeXYWH(SkIntToScalar(x), SkIntToScalar(y),
michael@0 1355 SkIntToScalar(device->width()),
michael@0 1356 SkIntToScalar(device->height())));
michael@0 1357 content.setShape(shape);
michael@0 1358 }
michael@0 1359 if (!content.needSource()) {
michael@0 1360 return;
michael@0 1361 }
michael@0 1362
michael@0 1363 SkAutoTUnref<SkPDFFormXObject> xObject(new SkPDFFormXObject(pdfDevice));
michael@0 1364 SkPDFUtils::DrawFormXObject(this->addXObjectResource(xObject.get()),
michael@0 1365 &content.entry()->fContent);
michael@0 1366
michael@0 1367 // Merge glyph sets from the drawn device.
michael@0 1368 fFontGlyphUsage->merge(pdfDevice->getFontGlyphUsage());
michael@0 1369 }
michael@0 1370
michael@0 1371 void SkPDFDevice::onAttachToCanvas(SkCanvas* canvas) {
michael@0 1372 INHERITED::onAttachToCanvas(canvas);
michael@0 1373
michael@0 1374 // Canvas promises that this ptr is valid until onDetachFromCanvas is called
michael@0 1375 fClipStack = canvas->getClipStack();
michael@0 1376 }
michael@0 1377
michael@0 1378 void SkPDFDevice::onDetachFromCanvas() {
michael@0 1379 INHERITED::onDetachFromCanvas();
michael@0 1380
michael@0 1381 fClipStack = NULL;
michael@0 1382 }
michael@0 1383
michael@0 1384 ContentEntry* SkPDFDevice::getLastContentEntry() {
michael@0 1385 if (fDrawingArea == kContent_DrawingArea) {
michael@0 1386 return fLastContentEntry;
michael@0 1387 } else {
michael@0 1388 return fLastMarginContentEntry;
michael@0 1389 }
michael@0 1390 }
michael@0 1391
michael@0 1392 SkAutoTDelete<ContentEntry>* SkPDFDevice::getContentEntries() {
michael@0 1393 if (fDrawingArea == kContent_DrawingArea) {
michael@0 1394 return &fContentEntries;
michael@0 1395 } else {
michael@0 1396 return &fMarginContentEntries;
michael@0 1397 }
michael@0 1398 }
michael@0 1399
michael@0 1400 void SkPDFDevice::setLastContentEntry(ContentEntry* contentEntry) {
michael@0 1401 if (fDrawingArea == kContent_DrawingArea) {
michael@0 1402 fLastContentEntry = contentEntry;
michael@0 1403 } else {
michael@0 1404 fLastMarginContentEntry = contentEntry;
michael@0 1405 }
michael@0 1406 }
michael@0 1407
michael@0 1408 void SkPDFDevice::setDrawingArea(DrawingArea drawingArea) {
michael@0 1409 // A ScopedContentEntry only exists during the course of a draw call, so
michael@0 1410 // this can't be called while a ScopedContentEntry exists.
michael@0 1411 fDrawingArea = drawingArea;
michael@0 1412 }
michael@0 1413
michael@0 1414 SkPDFResourceDict* SkPDFDevice::getResourceDict() {
michael@0 1415 if (NULL == fResourceDict) {
michael@0 1416 fResourceDict = SkNEW(SkPDFResourceDict);
michael@0 1417
michael@0 1418 if (fGraphicStateResources.count()) {
michael@0 1419 for (int i = 0; i < fGraphicStateResources.count(); i++) {
michael@0 1420 fResourceDict->insertResourceAsReference(
michael@0 1421 SkPDFResourceDict::kExtGState_ResourceType,
michael@0 1422 i, fGraphicStateResources[i]);
michael@0 1423 }
michael@0 1424 }
michael@0 1425
michael@0 1426 if (fXObjectResources.count()) {
michael@0 1427 for (int i = 0; i < fXObjectResources.count(); i++) {
michael@0 1428 fResourceDict->insertResourceAsReference(
michael@0 1429 SkPDFResourceDict::kXObject_ResourceType,
michael@0 1430 i, fXObjectResources[i]);
michael@0 1431 }
michael@0 1432 }
michael@0 1433
michael@0 1434 if (fFontResources.count()) {
michael@0 1435 for (int i = 0; i < fFontResources.count(); i++) {
michael@0 1436 fResourceDict->insertResourceAsReference(
michael@0 1437 SkPDFResourceDict::kFont_ResourceType,
michael@0 1438 i, fFontResources[i]);
michael@0 1439 }
michael@0 1440 }
michael@0 1441
michael@0 1442 if (fShaderResources.count()) {
michael@0 1443 SkAutoTUnref<SkPDFDict> patterns(new SkPDFDict());
michael@0 1444 for (int i = 0; i < fShaderResources.count(); i++) {
michael@0 1445 fResourceDict->insertResourceAsReference(
michael@0 1446 SkPDFResourceDict::kPattern_ResourceType,
michael@0 1447 i, fShaderResources[i]);
michael@0 1448 }
michael@0 1449 }
michael@0 1450 }
michael@0 1451 return fResourceDict;
michael@0 1452 }
michael@0 1453
michael@0 1454 const SkTDArray<SkPDFFont*>& SkPDFDevice::getFontResources() const {
michael@0 1455 return fFontResources;
michael@0 1456 }
michael@0 1457
michael@0 1458 SkPDFArray* SkPDFDevice::copyMediaBox() const {
michael@0 1459 // should this be a singleton?
michael@0 1460 SkAutoTUnref<SkPDFInt> zero(SkNEW_ARGS(SkPDFInt, (0)));
michael@0 1461
michael@0 1462 SkPDFArray* mediaBox = SkNEW(SkPDFArray);
michael@0 1463 mediaBox->reserve(4);
michael@0 1464 mediaBox->append(zero.get());
michael@0 1465 mediaBox->append(zero.get());
michael@0 1466 mediaBox->appendInt(fPageSize.fWidth);
michael@0 1467 mediaBox->appendInt(fPageSize.fHeight);
michael@0 1468 return mediaBox;
michael@0 1469 }
michael@0 1470
michael@0 1471 SkStream* SkPDFDevice::content() const {
michael@0 1472 SkMemoryStream* result = new SkMemoryStream;
michael@0 1473 result->setData(this->copyContentToData())->unref();
michael@0 1474 return result;
michael@0 1475 }
michael@0 1476
michael@0 1477 void SkPDFDevice::copyContentEntriesToData(ContentEntry* entry,
michael@0 1478 SkWStream* data) const {
michael@0 1479 // TODO(ctguil): For margins, I'm not sure fExistingClipStack/Region is the
michael@0 1480 // right thing to pass here.
michael@0 1481 GraphicStackState gsState(fExistingClipStack, fExistingClipRegion, data);
michael@0 1482 while (entry != NULL) {
michael@0 1483 SkPoint translation;
michael@0 1484 translation.iset(this->getOrigin());
michael@0 1485 translation.negate();
michael@0 1486 gsState.updateClip(entry->fState.fClipStack, entry->fState.fClipRegion,
michael@0 1487 translation);
michael@0 1488 gsState.updateMatrix(entry->fState.fMatrix);
michael@0 1489 gsState.updateDrawingState(entry->fState);
michael@0 1490
michael@0 1491 SkAutoDataUnref copy(entry->fContent.copyToData());
michael@0 1492 data->write(copy->data(), copy->size());
michael@0 1493 entry = entry->fNext.get();
michael@0 1494 }
michael@0 1495 gsState.drainStack();
michael@0 1496 }
michael@0 1497
michael@0 1498 SkData* SkPDFDevice::copyContentToData() const {
michael@0 1499 SkDynamicMemoryWStream data;
michael@0 1500 if (fInitialTransform.getType() != SkMatrix::kIdentity_Mask) {
michael@0 1501 SkPDFUtils::AppendTransform(fInitialTransform, &data);
michael@0 1502 }
michael@0 1503
michael@0 1504 // TODO(aayushkumar): Apply clip along the margins. Currently, webkit
michael@0 1505 // colors the contentArea white before it starts drawing into it and
michael@0 1506 // that currently acts as our clip.
michael@0 1507 // Also, think about adding a transform here (or assume that the values
michael@0 1508 // sent across account for that)
michael@0 1509 SkPDFDevice::copyContentEntriesToData(fMarginContentEntries.get(), &data);
michael@0 1510
michael@0 1511 // If the content area is the entire page, then we don't need to clip
michael@0 1512 // the content area (PDF area clips to the page size). Otherwise,
michael@0 1513 // we have to clip to the content area; we've already applied the
michael@0 1514 // initial transform, so just clip to the device size.
michael@0 1515 if (fPageSize != fContentSize) {
michael@0 1516 SkRect r = SkRect::MakeWH(SkIntToScalar(this->width()),
michael@0 1517 SkIntToScalar(this->height()));
michael@0 1518 emit_clip(NULL, &r, &data);
michael@0 1519 }
michael@0 1520
michael@0 1521 SkPDFDevice::copyContentEntriesToData(fContentEntries.get(), &data);
michael@0 1522
michael@0 1523 // potentially we could cache this SkData, and only rebuild it if we
michael@0 1524 // see that our state has changed.
michael@0 1525 return data.copyToData();
michael@0 1526 }
michael@0 1527
michael@0 1528 #ifdef SK_PDF_USE_PATHOPS
michael@0 1529 /* Draws an inverse filled path by using Path Ops to compute the positive
michael@0 1530 * inverse using the current clip as the inverse bounds.
michael@0 1531 * Return true if this was an inverse path and was properly handled,
michael@0 1532 * otherwise returns false and the normal drawing routine should continue,
michael@0 1533 * either as a (incorrect) fallback or because the path was not inverse
michael@0 1534 * in the first place.
michael@0 1535 */
michael@0 1536 bool SkPDFDevice::handleInversePath(const SkDraw& d, const SkPath& origPath,
michael@0 1537 const SkPaint& paint, bool pathIsMutable,
michael@0 1538 const SkMatrix* prePathMatrix) {
michael@0 1539 if (!origPath.isInverseFillType()) {
michael@0 1540 return false;
michael@0 1541 }
michael@0 1542
michael@0 1543 if (d.fClip->isEmpty()) {
michael@0 1544 return false;
michael@0 1545 }
michael@0 1546
michael@0 1547 SkPath modifiedPath;
michael@0 1548 SkPath* pathPtr = const_cast<SkPath*>(&origPath);
michael@0 1549 SkPaint noInversePaint(paint);
michael@0 1550
michael@0 1551 // Merge stroking operations into final path.
michael@0 1552 if (SkPaint::kStroke_Style == paint.getStyle() ||
michael@0 1553 SkPaint::kStrokeAndFill_Style == paint.getStyle()) {
michael@0 1554 bool doFillPath = paint.getFillPath(origPath, &modifiedPath);
michael@0 1555 if (doFillPath) {
michael@0 1556 noInversePaint.setStyle(SkPaint::kFill_Style);
michael@0 1557 noInversePaint.setStrokeWidth(0);
michael@0 1558 pathPtr = &modifiedPath;
michael@0 1559 } else {
michael@0 1560 // To be consistent with the raster output, hairline strokes
michael@0 1561 // are rendered as non-inverted.
michael@0 1562 modifiedPath.toggleInverseFillType();
michael@0 1563 drawPath(d, modifiedPath, paint, NULL, true);
michael@0 1564 return true;
michael@0 1565 }
michael@0 1566 }
michael@0 1567
michael@0 1568 // Get bounds of clip in current transform space
michael@0 1569 // (clip bounds are given in device space).
michael@0 1570 SkRect bounds;
michael@0 1571 SkMatrix transformInverse;
michael@0 1572 SkMatrix totalMatrix = *d.fMatrix;
michael@0 1573 if (prePathMatrix) {
michael@0 1574 totalMatrix.preConcat(*prePathMatrix);
michael@0 1575 }
michael@0 1576 if (!totalMatrix.invert(&transformInverse)) {
michael@0 1577 return false;
michael@0 1578 }
michael@0 1579 bounds.set(d.fClip->getBounds());
michael@0 1580 transformInverse.mapRect(&bounds);
michael@0 1581
michael@0 1582 // Extend the bounds by the line width (plus some padding)
michael@0 1583 // so the edge doesn't cause a visible stroke.
michael@0 1584 bounds.outset(paint.getStrokeWidth() + SK_Scalar1,
michael@0 1585 paint.getStrokeWidth() + SK_Scalar1);
michael@0 1586
michael@0 1587 if (!calculate_inverse_path(bounds, *pathPtr, &modifiedPath)) {
michael@0 1588 return false;
michael@0 1589 }
michael@0 1590
michael@0 1591 drawPath(d, modifiedPath, noInversePaint, prePathMatrix, true);
michael@0 1592 return true;
michael@0 1593 }
michael@0 1594 #endif
michael@0 1595
michael@0 1596 bool SkPDFDevice::handleRectAnnotation(const SkRect& r, const SkMatrix& matrix,
michael@0 1597 const SkPaint& p) {
michael@0 1598 SkAnnotation* annotationInfo = p.getAnnotation();
michael@0 1599 if (!annotationInfo) {
michael@0 1600 return false;
michael@0 1601 }
michael@0 1602 SkData* urlData = annotationInfo->find(SkAnnotationKeys::URL_Key());
michael@0 1603 if (urlData) {
michael@0 1604 handleLinkToURL(urlData, r, matrix);
michael@0 1605 return p.getAnnotation() != NULL;
michael@0 1606 }
michael@0 1607 SkData* linkToName = annotationInfo->find(
michael@0 1608 SkAnnotationKeys::Link_Named_Dest_Key());
michael@0 1609 if (linkToName) {
michael@0 1610 handleLinkToNamedDest(linkToName, r, matrix);
michael@0 1611 return p.getAnnotation() != NULL;
michael@0 1612 }
michael@0 1613 return false;
michael@0 1614 }
michael@0 1615
michael@0 1616 bool SkPDFDevice::handlePointAnnotation(const SkPoint* points, size_t count,
michael@0 1617 const SkMatrix& matrix,
michael@0 1618 const SkPaint& paint) {
michael@0 1619 SkAnnotation* annotationInfo = paint.getAnnotation();
michael@0 1620 if (!annotationInfo) {
michael@0 1621 return false;
michael@0 1622 }
michael@0 1623 SkData* nameData = annotationInfo->find(
michael@0 1624 SkAnnotationKeys::Define_Named_Dest_Key());
michael@0 1625 if (nameData) {
michael@0 1626 for (size_t i = 0; i < count; i++) {
michael@0 1627 defineNamedDestination(nameData, points[i], matrix);
michael@0 1628 }
michael@0 1629 return paint.getAnnotation() != NULL;
michael@0 1630 }
michael@0 1631 return false;
michael@0 1632 }
michael@0 1633
michael@0 1634 SkPDFDict* SkPDFDevice::createLinkAnnotation(const SkRect& r,
michael@0 1635 const SkMatrix& matrix) {
michael@0 1636 SkMatrix transform = matrix;
michael@0 1637 transform.postConcat(fInitialTransform);
michael@0 1638 SkRect translatedRect;
michael@0 1639 transform.mapRect(&translatedRect, r);
michael@0 1640
michael@0 1641 if (NULL == fAnnotations) {
michael@0 1642 fAnnotations = SkNEW(SkPDFArray);
michael@0 1643 }
michael@0 1644 SkPDFDict* annotation(SkNEW_ARGS(SkPDFDict, ("Annot")));
michael@0 1645 annotation->insertName("Subtype", "Link");
michael@0 1646 fAnnotations->append(annotation);
michael@0 1647
michael@0 1648 SkAutoTUnref<SkPDFArray> border(SkNEW(SkPDFArray));
michael@0 1649 border->reserve(3);
michael@0 1650 border->appendInt(0); // Horizontal corner radius.
michael@0 1651 border->appendInt(0); // Vertical corner radius.
michael@0 1652 border->appendInt(0); // Width, 0 = no border.
michael@0 1653 annotation->insert("Border", border.get());
michael@0 1654
michael@0 1655 SkAutoTUnref<SkPDFArray> rect(SkNEW(SkPDFArray));
michael@0 1656 rect->reserve(4);
michael@0 1657 rect->appendScalar(translatedRect.fLeft);
michael@0 1658 rect->appendScalar(translatedRect.fTop);
michael@0 1659 rect->appendScalar(translatedRect.fRight);
michael@0 1660 rect->appendScalar(translatedRect.fBottom);
michael@0 1661 annotation->insert("Rect", rect.get());
michael@0 1662
michael@0 1663 return annotation;
michael@0 1664 }
michael@0 1665
michael@0 1666 void SkPDFDevice::handleLinkToURL(SkData* urlData, const SkRect& r,
michael@0 1667 const SkMatrix& matrix) {
michael@0 1668 SkAutoTUnref<SkPDFDict> annotation(createLinkAnnotation(r, matrix));
michael@0 1669
michael@0 1670 SkString url(static_cast<const char *>(urlData->data()),
michael@0 1671 urlData->size() - 1);
michael@0 1672 SkAutoTUnref<SkPDFDict> action(SkNEW_ARGS(SkPDFDict, ("Action")));
michael@0 1673 action->insertName("S", "URI");
michael@0 1674 action->insert("URI", SkNEW_ARGS(SkPDFString, (url)))->unref();
michael@0 1675 annotation->insert("A", action.get());
michael@0 1676 }
michael@0 1677
michael@0 1678 void SkPDFDevice::handleLinkToNamedDest(SkData* nameData, const SkRect& r,
michael@0 1679 const SkMatrix& matrix) {
michael@0 1680 SkAutoTUnref<SkPDFDict> annotation(createLinkAnnotation(r, matrix));
michael@0 1681 SkString name(static_cast<const char *>(nameData->data()),
michael@0 1682 nameData->size() - 1);
michael@0 1683 annotation->insert("Dest", SkNEW_ARGS(SkPDFName, (name)))->unref();
michael@0 1684 }
michael@0 1685
michael@0 1686 struct NamedDestination {
michael@0 1687 const SkData* nameData;
michael@0 1688 SkPoint point;
michael@0 1689
michael@0 1690 NamedDestination(const SkData* nameData, const SkPoint& point)
michael@0 1691 : nameData(nameData), point(point) {
michael@0 1692 nameData->ref();
michael@0 1693 }
michael@0 1694
michael@0 1695 ~NamedDestination() {
michael@0 1696 nameData->unref();
michael@0 1697 }
michael@0 1698 };
michael@0 1699
michael@0 1700 void SkPDFDevice::defineNamedDestination(SkData* nameData, const SkPoint& point,
michael@0 1701 const SkMatrix& matrix) {
michael@0 1702 SkMatrix transform = matrix;
michael@0 1703 transform.postConcat(fInitialTransform);
michael@0 1704 SkPoint translatedPoint;
michael@0 1705 transform.mapXY(point.x(), point.y(), &translatedPoint);
michael@0 1706 fNamedDestinations.push(
michael@0 1707 SkNEW_ARGS(NamedDestination, (nameData, translatedPoint)));
michael@0 1708 }
michael@0 1709
michael@0 1710 void SkPDFDevice::appendDestinations(SkPDFDict* dict, SkPDFObject* page) {
michael@0 1711 int nDest = fNamedDestinations.count();
michael@0 1712 for (int i = 0; i < nDest; i++) {
michael@0 1713 NamedDestination* dest = fNamedDestinations[i];
michael@0 1714 SkAutoTUnref<SkPDFArray> pdfDest(SkNEW(SkPDFArray));
michael@0 1715 pdfDest->reserve(5);
michael@0 1716 pdfDest->append(SkNEW_ARGS(SkPDFObjRef, (page)))->unref();
michael@0 1717 pdfDest->appendName("XYZ");
michael@0 1718 pdfDest->appendScalar(dest->point.x());
michael@0 1719 pdfDest->appendScalar(dest->point.y());
michael@0 1720 pdfDest->appendInt(0); // Leave zoom unchanged
michael@0 1721 dict->insert(static_cast<const char *>(dest->nameData->data()),
michael@0 1722 pdfDest);
michael@0 1723 }
michael@0 1724 }
michael@0 1725
michael@0 1726 SkPDFFormXObject* SkPDFDevice::createFormXObjectFromDevice() {
michael@0 1727 SkPDFFormXObject* xobject = SkNEW_ARGS(SkPDFFormXObject, (this));
michael@0 1728 // We always draw the form xobjects that we create back into the device, so
michael@0 1729 // we simply preserve the font usage instead of pulling it out and merging
michael@0 1730 // it back in later.
michael@0 1731 cleanUp(false); // Reset this device to have no content.
michael@0 1732 init();
michael@0 1733 return xobject;
michael@0 1734 }
michael@0 1735
michael@0 1736 void SkPDFDevice::drawFormXObjectWithMask(int xObjectIndex,
michael@0 1737 SkPDFFormXObject* mask,
michael@0 1738 const SkClipStack* clipStack,
michael@0 1739 const SkRegion& clipRegion,
michael@0 1740 SkXfermode::Mode mode,
michael@0 1741 bool invertClip) {
michael@0 1742 if (clipRegion.isEmpty() && !invertClip) {
michael@0 1743 return;
michael@0 1744 }
michael@0 1745
michael@0 1746 SkAutoTUnref<SkPDFGraphicState> sMaskGS(
michael@0 1747 SkPDFGraphicState::GetSMaskGraphicState(
michael@0 1748 mask, invertClip, SkPDFGraphicState::kAlpha_SMaskMode));
michael@0 1749
michael@0 1750 SkMatrix identity;
michael@0 1751 identity.reset();
michael@0 1752 SkPaint paint;
michael@0 1753 paint.setXfermodeMode(mode);
michael@0 1754 ScopedContentEntry content(this, clipStack, clipRegion, identity, paint);
michael@0 1755 if (!content.entry()) {
michael@0 1756 return;
michael@0 1757 }
michael@0 1758 SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()),
michael@0 1759 &content.entry()->fContent);
michael@0 1760 SkPDFUtils::DrawFormXObject(xObjectIndex, &content.entry()->fContent);
michael@0 1761
michael@0 1762 sMaskGS.reset(SkPDFGraphicState::GetNoSMaskGraphicState());
michael@0 1763 SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()),
michael@0 1764 &content.entry()->fContent);
michael@0 1765 }
michael@0 1766
michael@0 1767 ContentEntry* SkPDFDevice::setUpContentEntry(const SkClipStack* clipStack,
michael@0 1768 const SkRegion& clipRegion,
michael@0 1769 const SkMatrix& matrix,
michael@0 1770 const SkPaint& paint,
michael@0 1771 bool hasText,
michael@0 1772 SkPDFFormXObject** dst) {
michael@0 1773 *dst = NULL;
michael@0 1774 if (clipRegion.isEmpty()) {
michael@0 1775 return NULL;
michael@0 1776 }
michael@0 1777
michael@0 1778 // The clip stack can come from an SkDraw where it is technically optional.
michael@0 1779 SkClipStack synthesizedClipStack;
michael@0 1780 if (clipStack == NULL) {
michael@0 1781 if (clipRegion == fExistingClipRegion) {
michael@0 1782 clipStack = &fExistingClipStack;
michael@0 1783 } else {
michael@0 1784 // GraphicStackState::updateClip expects the clip stack to have
michael@0 1785 // fExistingClip as a prefix, so start there, then set the clip
michael@0 1786 // to the passed region.
michael@0 1787 synthesizedClipStack = fExistingClipStack;
michael@0 1788 SkPath clipPath;
michael@0 1789 clipRegion.getBoundaryPath(&clipPath);
michael@0 1790 synthesizedClipStack.clipDevPath(clipPath, SkRegion::kReplace_Op,
michael@0 1791 false);
michael@0 1792 clipStack = &synthesizedClipStack;
michael@0 1793 }
michael@0 1794 }
michael@0 1795
michael@0 1796 SkXfermode::Mode xfermode = SkXfermode::kSrcOver_Mode;
michael@0 1797 if (paint.getXfermode()) {
michael@0 1798 paint.getXfermode()->asMode(&xfermode);
michael@0 1799 }
michael@0 1800
michael@0 1801 // For the following modes, we want to handle source and destination
michael@0 1802 // separately, so make an object of what's already there.
michael@0 1803 if (xfermode == SkXfermode::kClear_Mode ||
michael@0 1804 xfermode == SkXfermode::kSrc_Mode ||
michael@0 1805 xfermode == SkXfermode::kSrcIn_Mode ||
michael@0 1806 xfermode == SkXfermode::kDstIn_Mode ||
michael@0 1807 xfermode == SkXfermode::kSrcOut_Mode ||
michael@0 1808 xfermode == SkXfermode::kDstOut_Mode ||
michael@0 1809 xfermode == SkXfermode::kSrcATop_Mode ||
michael@0 1810 xfermode == SkXfermode::kDstATop_Mode ||
michael@0 1811 xfermode == SkXfermode::kModulate_Mode) {
michael@0 1812 if (!isContentEmpty()) {
michael@0 1813 *dst = createFormXObjectFromDevice();
michael@0 1814 SkASSERT(isContentEmpty());
michael@0 1815 } else if (xfermode != SkXfermode::kSrc_Mode &&
michael@0 1816 xfermode != SkXfermode::kSrcOut_Mode) {
michael@0 1817 // Except for Src and SrcOut, if there isn't anything already there,
michael@0 1818 // then we're done.
michael@0 1819 return NULL;
michael@0 1820 }
michael@0 1821 }
michael@0 1822 // TODO(vandebo): Figure out how/if we can handle the following modes:
michael@0 1823 // Xor, Plus.
michael@0 1824
michael@0 1825 // Dst xfer mode doesn't draw source at all.
michael@0 1826 if (xfermode == SkXfermode::kDst_Mode) {
michael@0 1827 return NULL;
michael@0 1828 }
michael@0 1829
michael@0 1830 ContentEntry* entry;
michael@0 1831 SkAutoTDelete<ContentEntry> newEntry;
michael@0 1832
michael@0 1833 ContentEntry* lastContentEntry = getLastContentEntry();
michael@0 1834 if (lastContentEntry && lastContentEntry->fContent.getOffset() == 0) {
michael@0 1835 entry = lastContentEntry;
michael@0 1836 } else {
michael@0 1837 newEntry.reset(new ContentEntry);
michael@0 1838 entry = newEntry.get();
michael@0 1839 }
michael@0 1840
michael@0 1841 populateGraphicStateEntryFromPaint(matrix, *clipStack, clipRegion, paint,
michael@0 1842 hasText, &entry->fState);
michael@0 1843 if (lastContentEntry && xfermode != SkXfermode::kDstOver_Mode &&
michael@0 1844 entry->fState.compareInitialState(lastContentEntry->fState)) {
michael@0 1845 return lastContentEntry;
michael@0 1846 }
michael@0 1847
michael@0 1848 SkAutoTDelete<ContentEntry>* contentEntries = getContentEntries();
michael@0 1849 if (!lastContentEntry) {
michael@0 1850 contentEntries->reset(entry);
michael@0 1851 setLastContentEntry(entry);
michael@0 1852 } else if (xfermode == SkXfermode::kDstOver_Mode) {
michael@0 1853 entry->fNext.reset(contentEntries->detach());
michael@0 1854 contentEntries->reset(entry);
michael@0 1855 } else {
michael@0 1856 lastContentEntry->fNext.reset(entry);
michael@0 1857 setLastContentEntry(entry);
michael@0 1858 }
michael@0 1859 newEntry.detach();
michael@0 1860 return entry;
michael@0 1861 }
michael@0 1862
michael@0 1863 void SkPDFDevice::finishContentEntry(SkXfermode::Mode xfermode,
michael@0 1864 SkPDFFormXObject* dst,
michael@0 1865 SkPath* shape) {
michael@0 1866 if (xfermode != SkXfermode::kClear_Mode &&
michael@0 1867 xfermode != SkXfermode::kSrc_Mode &&
michael@0 1868 xfermode != SkXfermode::kDstOver_Mode &&
michael@0 1869 xfermode != SkXfermode::kSrcIn_Mode &&
michael@0 1870 xfermode != SkXfermode::kDstIn_Mode &&
michael@0 1871 xfermode != SkXfermode::kSrcOut_Mode &&
michael@0 1872 xfermode != SkXfermode::kDstOut_Mode &&
michael@0 1873 xfermode != SkXfermode::kSrcATop_Mode &&
michael@0 1874 xfermode != SkXfermode::kDstATop_Mode &&
michael@0 1875 xfermode != SkXfermode::kModulate_Mode) {
michael@0 1876 SkASSERT(!dst);
michael@0 1877 return;
michael@0 1878 }
michael@0 1879 if (xfermode == SkXfermode::kDstOver_Mode) {
michael@0 1880 SkASSERT(!dst);
michael@0 1881 ContentEntry* firstContentEntry = getContentEntries()->get();
michael@0 1882 if (firstContentEntry->fContent.getOffset() == 0) {
michael@0 1883 // For DstOver, an empty content entry was inserted before the rest
michael@0 1884 // of the content entries. If nothing was drawn, it needs to be
michael@0 1885 // removed.
michael@0 1886 SkAutoTDelete<ContentEntry>* contentEntries = getContentEntries();
michael@0 1887 contentEntries->reset(firstContentEntry->fNext.detach());
michael@0 1888 }
michael@0 1889 return;
michael@0 1890 }
michael@0 1891 if (!dst) {
michael@0 1892 SkASSERT(xfermode == SkXfermode::kSrc_Mode ||
michael@0 1893 xfermode == SkXfermode::kSrcOut_Mode);
michael@0 1894 return;
michael@0 1895 }
michael@0 1896
michael@0 1897 ContentEntry* contentEntries = getContentEntries()->get();
michael@0 1898 SkASSERT(dst);
michael@0 1899 SkASSERT(!contentEntries->fNext.get());
michael@0 1900 // Changing the current content into a form-xobject will destroy the clip
michael@0 1901 // objects which is fine since the xobject will already be clipped. However
michael@0 1902 // if source has shape, we need to clip it too, so a copy of the clip is
michael@0 1903 // saved.
michael@0 1904 SkClipStack clipStack = contentEntries->fState.fClipStack;
michael@0 1905 SkRegion clipRegion = contentEntries->fState.fClipRegion;
michael@0 1906
michael@0 1907 SkMatrix identity;
michael@0 1908 identity.reset();
michael@0 1909 SkPaint stockPaint;
michael@0 1910
michael@0 1911 SkAutoTUnref<SkPDFFormXObject> srcFormXObject;
michael@0 1912 if (isContentEmpty()) {
michael@0 1913 // If nothing was drawn and there's no shape, then the draw was a
michael@0 1914 // no-op, but dst needs to be restored for that to be true.
michael@0 1915 // If there is shape, then an empty source with Src, SrcIn, SrcOut,
michael@0 1916 // DstIn, DstAtop or Modulate reduces to Clear and DstOut or SrcAtop
michael@0 1917 // reduces to Dst.
michael@0 1918 if (shape == NULL || xfermode == SkXfermode::kDstOut_Mode ||
michael@0 1919 xfermode == SkXfermode::kSrcATop_Mode) {
michael@0 1920 ScopedContentEntry content(this, &fExistingClipStack,
michael@0 1921 fExistingClipRegion, identity,
michael@0 1922 stockPaint);
michael@0 1923 SkPDFUtils::DrawFormXObject(this->addXObjectResource(dst),
michael@0 1924 &content.entry()->fContent);
michael@0 1925 return;
michael@0 1926 } else {
michael@0 1927 xfermode = SkXfermode::kClear_Mode;
michael@0 1928 }
michael@0 1929 } else {
michael@0 1930 SkASSERT(!fContentEntries->fNext.get());
michael@0 1931 srcFormXObject.reset(createFormXObjectFromDevice());
michael@0 1932 }
michael@0 1933
michael@0 1934 // TODO(vandebo) srcFormXObject may contain alpha, but here we want it
michael@0 1935 // without alpha.
michael@0 1936 if (xfermode == SkXfermode::kSrcATop_Mode) {
michael@0 1937 // TODO(vandebo): In order to properly support SrcATop we have to track
michael@0 1938 // the shape of what's been drawn at all times. It's the intersection of
michael@0 1939 // the non-transparent parts of the device and the outlines (shape) of
michael@0 1940 // all images and devices drawn.
michael@0 1941 drawFormXObjectWithMask(addXObjectResource(srcFormXObject.get()), dst,
michael@0 1942 &fExistingClipStack, fExistingClipRegion,
michael@0 1943 SkXfermode::kSrcOver_Mode, true);
michael@0 1944 } else {
michael@0 1945 SkAutoTUnref<SkPDFFormXObject> dstMaskStorage;
michael@0 1946 SkPDFFormXObject* dstMask = srcFormXObject.get();
michael@0 1947 if (shape != NULL) {
michael@0 1948 // Draw shape into a form-xobject.
michael@0 1949 SkDraw d;
michael@0 1950 d.fMatrix = &identity;
michael@0 1951 d.fClip = &clipRegion;
michael@0 1952 d.fClipStack = &clipStack;
michael@0 1953 SkPaint filledPaint;
michael@0 1954 filledPaint.setColor(SK_ColorBLACK);
michael@0 1955 filledPaint.setStyle(SkPaint::kFill_Style);
michael@0 1956 this->drawPath(d, *shape, filledPaint, NULL, true);
michael@0 1957
michael@0 1958 dstMaskStorage.reset(createFormXObjectFromDevice());
michael@0 1959 dstMask = dstMaskStorage.get();
michael@0 1960 }
michael@0 1961 drawFormXObjectWithMask(addXObjectResource(dst), dstMask,
michael@0 1962 &fExistingClipStack, fExistingClipRegion,
michael@0 1963 SkXfermode::kSrcOver_Mode, true);
michael@0 1964 }
michael@0 1965
michael@0 1966 if (xfermode == SkXfermode::kClear_Mode) {
michael@0 1967 return;
michael@0 1968 } else if (xfermode == SkXfermode::kSrc_Mode ||
michael@0 1969 xfermode == SkXfermode::kDstATop_Mode) {
michael@0 1970 ScopedContentEntry content(this, &fExistingClipStack,
michael@0 1971 fExistingClipRegion, identity, stockPaint);
michael@0 1972 if (content.entry()) {
michael@0 1973 SkPDFUtils::DrawFormXObject(
michael@0 1974 this->addXObjectResource(srcFormXObject.get()),
michael@0 1975 &content.entry()->fContent);
michael@0 1976 }
michael@0 1977 if (xfermode == SkXfermode::kSrc_Mode) {
michael@0 1978 return;
michael@0 1979 }
michael@0 1980 } else if (xfermode == SkXfermode::kSrcATop_Mode) {
michael@0 1981 ScopedContentEntry content(this, &fExistingClipStack,
michael@0 1982 fExistingClipRegion, identity, stockPaint);
michael@0 1983 if (content.entry()) {
michael@0 1984 SkPDFUtils::DrawFormXObject(this->addXObjectResource(dst),
michael@0 1985 &content.entry()->fContent);
michael@0 1986 }
michael@0 1987 }
michael@0 1988
michael@0 1989 SkASSERT(xfermode == SkXfermode::kSrcIn_Mode ||
michael@0 1990 xfermode == SkXfermode::kDstIn_Mode ||
michael@0 1991 xfermode == SkXfermode::kSrcOut_Mode ||
michael@0 1992 xfermode == SkXfermode::kDstOut_Mode ||
michael@0 1993 xfermode == SkXfermode::kSrcATop_Mode ||
michael@0 1994 xfermode == SkXfermode::kDstATop_Mode ||
michael@0 1995 xfermode == SkXfermode::kModulate_Mode);
michael@0 1996
michael@0 1997 if (xfermode == SkXfermode::kSrcIn_Mode ||
michael@0 1998 xfermode == SkXfermode::kSrcOut_Mode ||
michael@0 1999 xfermode == SkXfermode::kSrcATop_Mode) {
michael@0 2000 drawFormXObjectWithMask(addXObjectResource(srcFormXObject.get()), dst,
michael@0 2001 &fExistingClipStack, fExistingClipRegion,
michael@0 2002 SkXfermode::kSrcOver_Mode,
michael@0 2003 xfermode == SkXfermode::kSrcOut_Mode);
michael@0 2004 } else {
michael@0 2005 SkXfermode::Mode mode = SkXfermode::kSrcOver_Mode;
michael@0 2006 if (xfermode == SkXfermode::kModulate_Mode) {
michael@0 2007 drawFormXObjectWithMask(addXObjectResource(srcFormXObject.get()),
michael@0 2008 dst, &fExistingClipStack,
michael@0 2009 fExistingClipRegion,
michael@0 2010 SkXfermode::kSrcOver_Mode, false);
michael@0 2011 mode = SkXfermode::kMultiply_Mode;
michael@0 2012 }
michael@0 2013 drawFormXObjectWithMask(addXObjectResource(dst), srcFormXObject.get(),
michael@0 2014 &fExistingClipStack, fExistingClipRegion, mode,
michael@0 2015 xfermode == SkXfermode::kDstOut_Mode);
michael@0 2016 }
michael@0 2017 }
michael@0 2018
michael@0 2019 bool SkPDFDevice::isContentEmpty() {
michael@0 2020 ContentEntry* contentEntries = getContentEntries()->get();
michael@0 2021 if (!contentEntries || contentEntries->fContent.getOffset() == 0) {
michael@0 2022 SkASSERT(!contentEntries || !contentEntries->fNext.get());
michael@0 2023 return true;
michael@0 2024 }
michael@0 2025 return false;
michael@0 2026 }
michael@0 2027
michael@0 2028 void SkPDFDevice::populateGraphicStateEntryFromPaint(
michael@0 2029 const SkMatrix& matrix,
michael@0 2030 const SkClipStack& clipStack,
michael@0 2031 const SkRegion& clipRegion,
michael@0 2032 const SkPaint& paint,
michael@0 2033 bool hasText,
michael@0 2034 GraphicStateEntry* entry) {
michael@0 2035 SkASSERT(paint.getPathEffect() == NULL);
michael@0 2036
michael@0 2037 NOT_IMPLEMENTED(paint.getMaskFilter() != NULL, false);
michael@0 2038 NOT_IMPLEMENTED(paint.getColorFilter() != NULL, false);
michael@0 2039
michael@0 2040 entry->fMatrix = matrix;
michael@0 2041 entry->fClipStack = clipStack;
michael@0 2042 entry->fClipRegion = clipRegion;
michael@0 2043 entry->fColor = SkColorSetA(paint.getColor(), 0xFF);
michael@0 2044 entry->fShaderIndex = -1;
michael@0 2045
michael@0 2046 // PDF treats a shader as a color, so we only set one or the other.
michael@0 2047 SkAutoTUnref<SkPDFObject> pdfShader;
michael@0 2048 const SkShader* shader = paint.getShader();
michael@0 2049 SkColor color = paint.getColor();
michael@0 2050 if (shader) {
michael@0 2051 // PDF positions patterns relative to the initial transform, so
michael@0 2052 // we need to apply the current transform to the shader parameters.
michael@0 2053 SkMatrix transform = matrix;
michael@0 2054 transform.postConcat(fInitialTransform);
michael@0 2055
michael@0 2056 // PDF doesn't support kClamp_TileMode, so we simulate it by making
michael@0 2057 // a pattern the size of the current clip.
michael@0 2058 SkIRect bounds = clipRegion.getBounds();
michael@0 2059
michael@0 2060 // We need to apply the initial transform to bounds in order to get
michael@0 2061 // bounds in a consistent coordinate system.
michael@0 2062 SkRect boundsTemp;
michael@0 2063 boundsTemp.set(bounds);
michael@0 2064 fInitialTransform.mapRect(&boundsTemp);
michael@0 2065 boundsTemp.roundOut(&bounds);
michael@0 2066
michael@0 2067 pdfShader.reset(SkPDFShader::GetPDFShader(*shader, transform, bounds));
michael@0 2068
michael@0 2069 if (pdfShader.get()) {
michael@0 2070 // pdfShader has been canonicalized so we can directly compare
michael@0 2071 // pointers.
michael@0 2072 int resourceIndex = fShaderResources.find(pdfShader.get());
michael@0 2073 if (resourceIndex < 0) {
michael@0 2074 resourceIndex = fShaderResources.count();
michael@0 2075 fShaderResources.push(pdfShader.get());
michael@0 2076 pdfShader.get()->ref();
michael@0 2077 }
michael@0 2078 entry->fShaderIndex = resourceIndex;
michael@0 2079 } else {
michael@0 2080 // A color shader is treated as an invalid shader so we don't have
michael@0 2081 // to set a shader just for a color.
michael@0 2082 SkShader::GradientInfo gradientInfo;
michael@0 2083 SkColor gradientColor;
michael@0 2084 gradientInfo.fColors = &gradientColor;
michael@0 2085 gradientInfo.fColorOffsets = NULL;
michael@0 2086 gradientInfo.fColorCount = 1;
michael@0 2087 if (shader->asAGradient(&gradientInfo) ==
michael@0 2088 SkShader::kColor_GradientType) {
michael@0 2089 entry->fColor = SkColorSetA(gradientColor, 0xFF);
michael@0 2090 color = gradientColor;
michael@0 2091 }
michael@0 2092 }
michael@0 2093 }
michael@0 2094
michael@0 2095 SkAutoTUnref<SkPDFGraphicState> newGraphicState;
michael@0 2096 if (color == paint.getColor()) {
michael@0 2097 newGraphicState.reset(
michael@0 2098 SkPDFGraphicState::GetGraphicStateForPaint(paint));
michael@0 2099 } else {
michael@0 2100 SkPaint newPaint = paint;
michael@0 2101 newPaint.setColor(color);
michael@0 2102 newGraphicState.reset(
michael@0 2103 SkPDFGraphicState::GetGraphicStateForPaint(newPaint));
michael@0 2104 }
michael@0 2105 int resourceIndex = addGraphicStateResource(newGraphicState.get());
michael@0 2106 entry->fGraphicStateIndex = resourceIndex;
michael@0 2107
michael@0 2108 if (hasText) {
michael@0 2109 entry->fTextScaleX = paint.getTextScaleX();
michael@0 2110 entry->fTextFill = paint.getStyle();
michael@0 2111 } else {
michael@0 2112 entry->fTextScaleX = 0;
michael@0 2113 }
michael@0 2114 }
michael@0 2115
michael@0 2116 int SkPDFDevice::addGraphicStateResource(SkPDFGraphicState* gs) {
michael@0 2117 // Assumes that gs has been canonicalized (so we can directly compare
michael@0 2118 // pointers).
michael@0 2119 int result = fGraphicStateResources.find(gs);
michael@0 2120 if (result < 0) {
michael@0 2121 result = fGraphicStateResources.count();
michael@0 2122 fGraphicStateResources.push(gs);
michael@0 2123 gs->ref();
michael@0 2124 }
michael@0 2125 return result;
michael@0 2126 }
michael@0 2127
michael@0 2128 int SkPDFDevice::addXObjectResource(SkPDFObject* xObject) {
michael@0 2129 // Assumes that xobject has been canonicalized (so we can directly compare
michael@0 2130 // pointers).
michael@0 2131 int result = fXObjectResources.find(xObject);
michael@0 2132 if (result < 0) {
michael@0 2133 result = fXObjectResources.count();
michael@0 2134 fXObjectResources.push(xObject);
michael@0 2135 xObject->ref();
michael@0 2136 }
michael@0 2137 return result;
michael@0 2138 }
michael@0 2139
michael@0 2140 void SkPDFDevice::updateFont(const SkPaint& paint, uint16_t glyphID,
michael@0 2141 ContentEntry* contentEntry) {
michael@0 2142 SkTypeface* typeface = paint.getTypeface();
michael@0 2143 if (contentEntry->fState.fFont == NULL ||
michael@0 2144 contentEntry->fState.fTextSize != paint.getTextSize() ||
michael@0 2145 !contentEntry->fState.fFont->hasGlyph(glyphID)) {
michael@0 2146 int fontIndex = getFontResourceIndex(typeface, glyphID);
michael@0 2147 contentEntry->fContent.writeText("/");
michael@0 2148 contentEntry->fContent.writeText(SkPDFResourceDict::getResourceName(
michael@0 2149 SkPDFResourceDict::kFont_ResourceType,
michael@0 2150 fontIndex).c_str());
michael@0 2151 contentEntry->fContent.writeText(" ");
michael@0 2152 SkPDFScalar::Append(paint.getTextSize(), &contentEntry->fContent);
michael@0 2153 contentEntry->fContent.writeText(" Tf\n");
michael@0 2154 contentEntry->fState.fFont = fFontResources[fontIndex];
michael@0 2155 }
michael@0 2156 }
michael@0 2157
michael@0 2158 int SkPDFDevice::getFontResourceIndex(SkTypeface* typeface, uint16_t glyphID) {
michael@0 2159 SkAutoTUnref<SkPDFFont> newFont(SkPDFFont::GetFontResource(typeface,
michael@0 2160 glyphID));
michael@0 2161 int resourceIndex = fFontResources.find(newFont.get());
michael@0 2162 if (resourceIndex < 0) {
michael@0 2163 resourceIndex = fFontResources.count();
michael@0 2164 fFontResources.push(newFont.get());
michael@0 2165 newFont.get()->ref();
michael@0 2166 }
michael@0 2167 return resourceIndex;
michael@0 2168 }
michael@0 2169
michael@0 2170 void SkPDFDevice::internalDrawBitmap(const SkMatrix& origMatrix,
michael@0 2171 const SkClipStack* clipStack,
michael@0 2172 const SkRegion& origClipRegion,
michael@0 2173 const SkBitmap& origBitmap,
michael@0 2174 const SkIRect* srcRect,
michael@0 2175 const SkPaint& paint) {
michael@0 2176 SkMatrix matrix = origMatrix;
michael@0 2177 SkRegion perspectiveBounds;
michael@0 2178 const SkRegion* clipRegion = &origClipRegion;
michael@0 2179 SkBitmap perspectiveBitmap;
michael@0 2180 const SkBitmap* bitmap = &origBitmap;
michael@0 2181 SkBitmap tmpSubsetBitmap;
michael@0 2182
michael@0 2183 // Rasterize the bitmap using perspective in a new bitmap.
michael@0 2184 if (origMatrix.hasPerspective()) {
michael@0 2185 if (fRasterDpi == 0) {
michael@0 2186 return;
michael@0 2187 }
michael@0 2188 SkBitmap* subsetBitmap;
michael@0 2189 if (srcRect) {
michael@0 2190 if (!origBitmap.extractSubset(&tmpSubsetBitmap, *srcRect)) {
michael@0 2191 return;
michael@0 2192 }
michael@0 2193 subsetBitmap = &tmpSubsetBitmap;
michael@0 2194 } else {
michael@0 2195 subsetBitmap = &tmpSubsetBitmap;
michael@0 2196 *subsetBitmap = origBitmap;
michael@0 2197 }
michael@0 2198 srcRect = NULL;
michael@0 2199
michael@0 2200 // Transform the bitmap in the new space, without taking into
michael@0 2201 // account the initial transform.
michael@0 2202 SkPath perspectiveOutline;
michael@0 2203 perspectiveOutline.addRect(
michael@0 2204 SkRect::MakeWH(SkIntToScalar(subsetBitmap->width()),
michael@0 2205 SkIntToScalar(subsetBitmap->height())));
michael@0 2206 perspectiveOutline.transform(origMatrix);
michael@0 2207
michael@0 2208 // TODO(edisonn): perf - use current clip too.
michael@0 2209 // Retrieve the bounds of the new shape.
michael@0 2210 SkRect bounds = perspectiveOutline.getBounds();
michael@0 2211
michael@0 2212 // Transform the bitmap in the new space, taking into
michael@0 2213 // account the initial transform.
michael@0 2214 SkMatrix total = origMatrix;
michael@0 2215 total.postConcat(fInitialTransform);
michael@0 2216 total.postScale(SkIntToScalar(fRasterDpi) /
michael@0 2217 SkIntToScalar(DPI_FOR_RASTER_SCALE_ONE),
michael@0 2218 SkIntToScalar(fRasterDpi) /
michael@0 2219 SkIntToScalar(DPI_FOR_RASTER_SCALE_ONE));
michael@0 2220 SkPath physicalPerspectiveOutline;
michael@0 2221 physicalPerspectiveOutline.addRect(
michael@0 2222 SkRect::MakeWH(SkIntToScalar(subsetBitmap->width()),
michael@0 2223 SkIntToScalar(subsetBitmap->height())));
michael@0 2224 physicalPerspectiveOutline.transform(total);
michael@0 2225
michael@0 2226 SkScalar scaleX = physicalPerspectiveOutline.getBounds().width() /
michael@0 2227 bounds.width();
michael@0 2228 SkScalar scaleY = physicalPerspectiveOutline.getBounds().height() /
michael@0 2229 bounds.height();
michael@0 2230
michael@0 2231 // TODO(edisonn): A better approach would be to use a bitmap shader
michael@0 2232 // (in clamp mode) and draw a rect over the entire bounding box. Then
michael@0 2233 // intersect perspectiveOutline to the clip. That will avoid introducing
michael@0 2234 // alpha to the image while still giving good behavior at the edge of
michael@0 2235 // the image. Avoiding alpha will reduce the pdf size and generation
michael@0 2236 // CPU time some.
michael@0 2237
michael@0 2238 const int w = SkScalarCeilToInt(physicalPerspectiveOutline.getBounds().width());
michael@0 2239 const int h = SkScalarCeilToInt(physicalPerspectiveOutline.getBounds().height());
michael@0 2240 if (!perspectiveBitmap.allocPixels(SkImageInfo::MakeN32Premul(w, h))) {
michael@0 2241 return;
michael@0 2242 }
michael@0 2243 perspectiveBitmap.eraseColor(SK_ColorTRANSPARENT);
michael@0 2244
michael@0 2245 SkBitmapDevice device(perspectiveBitmap);
michael@0 2246 SkCanvas canvas(&device);
michael@0 2247
michael@0 2248 SkScalar deltaX = bounds.left();
michael@0 2249 SkScalar deltaY = bounds.top();
michael@0 2250
michael@0 2251 SkMatrix offsetMatrix = origMatrix;
michael@0 2252 offsetMatrix.postTranslate(-deltaX, -deltaY);
michael@0 2253 offsetMatrix.postScale(scaleX, scaleY);
michael@0 2254
michael@0 2255 // Translate the draw in the new canvas, so we perfectly fit the
michael@0 2256 // shape in the bitmap.
michael@0 2257 canvas.setMatrix(offsetMatrix);
michael@0 2258
michael@0 2259 canvas.drawBitmap(*subsetBitmap, SkIntToScalar(0), SkIntToScalar(0));
michael@0 2260
michael@0 2261 // Make sure the final bits are in the bitmap.
michael@0 2262 canvas.flush();
michael@0 2263
michael@0 2264 // In the new space, we use the identity matrix translated
michael@0 2265 // and scaled to reflect DPI.
michael@0 2266 matrix.setScale(1 / scaleX, 1 / scaleY);
michael@0 2267 matrix.postTranslate(deltaX, deltaY);
michael@0 2268
michael@0 2269 perspectiveBounds.setRect(
michael@0 2270 SkIRect::MakeXYWH(SkScalarFloorToInt(bounds.x()),
michael@0 2271 SkScalarFloorToInt(bounds.y()),
michael@0 2272 SkScalarCeilToInt(bounds.width()),
michael@0 2273 SkScalarCeilToInt(bounds.height())));
michael@0 2274 clipRegion = &perspectiveBounds;
michael@0 2275 srcRect = NULL;
michael@0 2276 bitmap = &perspectiveBitmap;
michael@0 2277 }
michael@0 2278
michael@0 2279 SkMatrix scaled;
michael@0 2280 // Adjust for origin flip.
michael@0 2281 scaled.setScale(SK_Scalar1, -SK_Scalar1);
michael@0 2282 scaled.postTranslate(0, SK_Scalar1);
michael@0 2283 // Scale the image up from 1x1 to WxH.
michael@0 2284 SkIRect subset = SkIRect::MakeWH(bitmap->width(), bitmap->height());
michael@0 2285 scaled.postScale(SkIntToScalar(subset.width()),
michael@0 2286 SkIntToScalar(subset.height()));
michael@0 2287 scaled.postConcat(matrix);
michael@0 2288 ScopedContentEntry content(this, clipStack, *clipRegion, scaled, paint);
michael@0 2289 if (!content.entry() || (srcRect && !subset.intersect(*srcRect))) {
michael@0 2290 return;
michael@0 2291 }
michael@0 2292 if (content.needShape()) {
michael@0 2293 SkPath shape;
michael@0 2294 shape.addRect(SkRect::MakeWH(SkIntToScalar(subset.width()),
michael@0 2295 SkIntToScalar( subset.height())));
michael@0 2296 shape.transform(matrix);
michael@0 2297 content.setShape(shape);
michael@0 2298 }
michael@0 2299 if (!content.needSource()) {
michael@0 2300 return;
michael@0 2301 }
michael@0 2302
michael@0 2303 SkAutoTUnref<SkPDFImage> image(
michael@0 2304 SkPDFImage::CreateImage(*bitmap, subset, fEncoder));
michael@0 2305 if (!image) {
michael@0 2306 return;
michael@0 2307 }
michael@0 2308
michael@0 2309 SkPDFUtils::DrawFormXObject(this->addXObjectResource(image.get()),
michael@0 2310 &content.entry()->fContent);
michael@0 2311 }
michael@0 2312
michael@0 2313 bool SkPDFDevice::onReadPixels(const SkBitmap& bitmap, int x, int y,
michael@0 2314 SkCanvas::Config8888) {
michael@0 2315 return false;
michael@0 2316 }
michael@0 2317
michael@0 2318 bool SkPDFDevice::allowImageFilter(const SkImageFilter*) {
michael@0 2319 return false;
michael@0 2320 }

mercurial