michael@0: /* michael@0: * Copyright 2011 Google Inc. michael@0: * michael@0: * Use of this source code is governed by a BSD-style license that can be michael@0: * found in the LICENSE file. michael@0: */ michael@0: michael@0: #include "SkPDFDevice.h" michael@0: michael@0: #include "SkAnnotation.h" michael@0: #include "SkColor.h" michael@0: #include "SkClipStack.h" michael@0: #include "SkData.h" michael@0: #include "SkDraw.h" michael@0: #include "SkFontHost.h" michael@0: #include "SkGlyphCache.h" michael@0: #include "SkPaint.h" michael@0: #include "SkPath.h" michael@0: #include "SkPathOps.h" michael@0: #include "SkPDFFont.h" michael@0: #include "SkPDFFormXObject.h" michael@0: #include "SkPDFGraphicState.h" michael@0: #include "SkPDFImage.h" michael@0: #include "SkPDFResourceDict.h" michael@0: #include "SkPDFShader.h" michael@0: #include "SkPDFStream.h" michael@0: #include "SkPDFTypes.h" michael@0: #include "SkPDFUtils.h" michael@0: #include "SkRect.h" michael@0: #include "SkRRect.h" michael@0: #include "SkString.h" michael@0: #include "SkTextFormatParams.h" michael@0: #include "SkTemplates.h" michael@0: #include "SkTypefacePriv.h" michael@0: #include "SkTSet.h" michael@0: michael@0: #ifdef SK_BUILD_FOR_ANDROID michael@0: #include "SkTypeface_android.h" michael@0: michael@0: struct TypefaceFallbackData { michael@0: SkTypeface* typeface; michael@0: int lowerBounds; michael@0: int upperBounds; michael@0: michael@0: bool operator==(const TypefaceFallbackData& b) const { michael@0: return typeface == b.typeface && michael@0: lowerBounds == b.lowerBounds && michael@0: upperBounds == b.upperBounds; michael@0: } michael@0: }; michael@0: #endif michael@0: michael@0: #define DPI_FOR_RASTER_SCALE_ONE 72 michael@0: michael@0: // Utility functions michael@0: michael@0: static void emit_pdf_color(SkColor color, SkWStream* result) { michael@0: SkASSERT(SkColorGetA(color) == 0xFF); // We handle alpha elsewhere. michael@0: SkScalar colorMax = SkIntToScalar(0xFF); michael@0: SkPDFScalar::Append( michael@0: SkScalarDiv(SkIntToScalar(SkColorGetR(color)), colorMax), result); michael@0: result->writeText(" "); michael@0: SkPDFScalar::Append( michael@0: SkScalarDiv(SkIntToScalar(SkColorGetG(color)), colorMax), result); michael@0: result->writeText(" "); michael@0: SkPDFScalar::Append( michael@0: SkScalarDiv(SkIntToScalar(SkColorGetB(color)), colorMax), result); michael@0: result->writeText(" "); michael@0: } michael@0: michael@0: static SkPaint calculate_text_paint(const SkPaint& paint) { michael@0: SkPaint result = paint; michael@0: if (result.isFakeBoldText()) { michael@0: SkScalar fakeBoldScale = SkScalarInterpFunc(result.getTextSize(), michael@0: kStdFakeBoldInterpKeys, michael@0: kStdFakeBoldInterpValues, michael@0: kStdFakeBoldInterpLength); michael@0: SkScalar width = SkScalarMul(result.getTextSize(), fakeBoldScale); michael@0: if (result.getStyle() == SkPaint::kFill_Style) { michael@0: result.setStyle(SkPaint::kStrokeAndFill_Style); michael@0: } else { michael@0: width += result.getStrokeWidth(); michael@0: } michael@0: result.setStrokeWidth(width); michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: // Stolen from measure_text in SkDraw.cpp and then tweaked. michael@0: static void align_text(SkDrawCacheProc glyphCacheProc, const SkPaint& paint, michael@0: const uint16_t* glyphs, size_t len, michael@0: SkScalar* x, SkScalar* y) { michael@0: if (paint.getTextAlign() == SkPaint::kLeft_Align) { michael@0: return; michael@0: } michael@0: michael@0: SkMatrix ident; michael@0: ident.reset(); michael@0: SkAutoGlyphCache autoCache(paint, NULL, &ident); michael@0: SkGlyphCache* cache = autoCache.getCache(); michael@0: michael@0: const char* start = reinterpret_cast(glyphs); michael@0: const char* stop = reinterpret_cast(glyphs + len); michael@0: SkFixed xAdv = 0, yAdv = 0; michael@0: michael@0: // TODO(vandebo): This probably needs to take kerning into account. michael@0: while (start < stop) { michael@0: const SkGlyph& glyph = glyphCacheProc(cache, &start, 0, 0); michael@0: xAdv += glyph.fAdvanceX; michael@0: yAdv += glyph.fAdvanceY; michael@0: }; michael@0: if (paint.getTextAlign() == SkPaint::kLeft_Align) { michael@0: return; michael@0: } michael@0: michael@0: SkScalar xAdj = SkFixedToScalar(xAdv); michael@0: SkScalar yAdj = SkFixedToScalar(yAdv); michael@0: if (paint.getTextAlign() == SkPaint::kCenter_Align) { michael@0: xAdj = SkScalarHalf(xAdj); michael@0: yAdj = SkScalarHalf(yAdj); michael@0: } michael@0: *x = *x - xAdj; michael@0: *y = *y - yAdj; michael@0: } michael@0: michael@0: static int max_glyphid_for_typeface(SkTypeface* typeface) { michael@0: SkAutoResolveDefaultTypeface autoResolve(typeface); michael@0: typeface = autoResolve.get(); michael@0: return typeface->countGlyphs() - 1; michael@0: } michael@0: michael@0: typedef SkAutoSTMalloc<128, uint16_t> SkGlyphStorage; michael@0: michael@0: static size_t force_glyph_encoding(const SkPaint& paint, const void* text, michael@0: size_t len, SkGlyphStorage* storage, michael@0: uint16_t** glyphIDs) { michael@0: // Make sure we have a glyph id encoding. michael@0: if (paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding) { michael@0: size_t numGlyphs = paint.textToGlyphs(text, len, NULL); michael@0: storage->reset(numGlyphs); michael@0: paint.textToGlyphs(text, len, storage->get()); michael@0: *glyphIDs = storage->get(); michael@0: return numGlyphs; michael@0: } michael@0: michael@0: // For user supplied glyph ids we need to validate them. michael@0: SkASSERT((len & 1) == 0); michael@0: size_t numGlyphs = len / 2; michael@0: const uint16_t* input = michael@0: reinterpret_cast(const_cast((text))); michael@0: michael@0: int maxGlyphID = max_glyphid_for_typeface(paint.getTypeface()); michael@0: size_t validated; michael@0: for (validated = 0; validated < numGlyphs; ++validated) { michael@0: if (input[validated] > maxGlyphID) { michael@0: break; michael@0: } michael@0: } michael@0: if (validated >= numGlyphs) { michael@0: *glyphIDs = reinterpret_cast(const_cast((text))); michael@0: return numGlyphs; michael@0: } michael@0: michael@0: // Silently drop anything out of range. michael@0: storage->reset(numGlyphs); michael@0: if (validated > 0) { michael@0: memcpy(storage->get(), input, validated * sizeof(uint16_t)); michael@0: } michael@0: michael@0: for (size_t i = validated; i < numGlyphs; ++i) { michael@0: storage->get()[i] = input[i]; michael@0: if (input[i] > maxGlyphID) { michael@0: storage->get()[i] = 0; michael@0: } michael@0: } michael@0: *glyphIDs = storage->get(); michael@0: return numGlyphs; michael@0: } michael@0: michael@0: static void set_text_transform(SkScalar x, SkScalar y, SkScalar textSkewX, michael@0: SkWStream* content) { michael@0: // Flip the text about the x-axis to account for origin swap and include michael@0: // the passed parameters. michael@0: content->writeText("1 0 "); michael@0: SkPDFScalar::Append(0 - textSkewX, content); michael@0: content->writeText(" -1 "); michael@0: SkPDFScalar::Append(x, content); michael@0: content->writeText(" "); michael@0: SkPDFScalar::Append(y, content); michael@0: content->writeText(" Tm\n"); michael@0: } michael@0: michael@0: // It is important to not confuse GraphicStateEntry with SkPDFGraphicState, the michael@0: // later being our representation of an object in the PDF file. michael@0: struct GraphicStateEntry { michael@0: GraphicStateEntry(); michael@0: michael@0: // Compare the fields we care about when setting up a new content entry. michael@0: bool compareInitialState(const GraphicStateEntry& b); michael@0: michael@0: SkMatrix fMatrix; michael@0: // We can't do set operations on Paths, though PDF natively supports michael@0: // intersect. If the clip stack does anything other than intersect, michael@0: // we have to fall back to the region. Treat fClipStack as authoritative. michael@0: // See http://code.google.com/p/skia/issues/detail?id=221 michael@0: SkClipStack fClipStack; michael@0: SkRegion fClipRegion; michael@0: michael@0: // When emitting the content entry, we will ensure the graphic state michael@0: // is set to these values first. michael@0: SkColor fColor; michael@0: SkScalar fTextScaleX; // Zero means we don't care what the value is. michael@0: SkPaint::Style fTextFill; // Only if TextScaleX is non-zero. michael@0: int fShaderIndex; michael@0: int fGraphicStateIndex; michael@0: michael@0: // We may change the font (i.e. for Type1 support) within a michael@0: // ContentEntry. This is the one currently in effect, or NULL if none. michael@0: SkPDFFont* fFont; michael@0: // In PDF, text size has no default value. It is only valid if fFont is michael@0: // not NULL. michael@0: SkScalar fTextSize; michael@0: }; michael@0: michael@0: GraphicStateEntry::GraphicStateEntry() : fColor(SK_ColorBLACK), michael@0: fTextScaleX(SK_Scalar1), michael@0: fTextFill(SkPaint::kFill_Style), michael@0: fShaderIndex(-1), michael@0: fGraphicStateIndex(-1), michael@0: fFont(NULL), michael@0: fTextSize(SK_ScalarNaN) { michael@0: fMatrix.reset(); michael@0: } michael@0: michael@0: bool GraphicStateEntry::compareInitialState(const GraphicStateEntry& cur) { michael@0: return fColor == cur.fColor && michael@0: fShaderIndex == cur.fShaderIndex && michael@0: fGraphicStateIndex == cur.fGraphicStateIndex && michael@0: fMatrix == cur.fMatrix && michael@0: fClipStack == cur.fClipStack && michael@0: (fTextScaleX == 0 || michael@0: (fTextScaleX == cur.fTextScaleX && fTextFill == cur.fTextFill)); michael@0: } michael@0: michael@0: class GraphicStackState { michael@0: public: michael@0: GraphicStackState(const SkClipStack& existingClipStack, michael@0: const SkRegion& existingClipRegion, michael@0: SkWStream* contentStream) michael@0: : fStackDepth(0), michael@0: fContentStream(contentStream) { michael@0: fEntries[0].fClipStack = existingClipStack; michael@0: fEntries[0].fClipRegion = existingClipRegion; michael@0: } michael@0: michael@0: void updateClip(const SkClipStack& clipStack, const SkRegion& clipRegion, michael@0: const SkPoint& translation); michael@0: void updateMatrix(const SkMatrix& matrix); michael@0: void updateDrawingState(const GraphicStateEntry& state); michael@0: michael@0: void drainStack(); michael@0: michael@0: private: michael@0: void push(); michael@0: void pop(); michael@0: GraphicStateEntry* currentEntry() { return &fEntries[fStackDepth]; } michael@0: michael@0: // Conservative limit on save depth, see impl. notes in PDF 1.4 spec. michael@0: static const int kMaxStackDepth = 12; michael@0: GraphicStateEntry fEntries[kMaxStackDepth + 1]; michael@0: int fStackDepth; michael@0: SkWStream* fContentStream; michael@0: }; michael@0: michael@0: void GraphicStackState::drainStack() { michael@0: while (fStackDepth) { michael@0: pop(); michael@0: } michael@0: } michael@0: michael@0: void GraphicStackState::push() { michael@0: SkASSERT(fStackDepth < kMaxStackDepth); michael@0: fContentStream->writeText("q\n"); michael@0: fStackDepth++; michael@0: fEntries[fStackDepth] = fEntries[fStackDepth - 1]; michael@0: } michael@0: michael@0: void GraphicStackState::pop() { michael@0: SkASSERT(fStackDepth > 0); michael@0: fContentStream->writeText("Q\n"); michael@0: fStackDepth--; michael@0: } michael@0: michael@0: // This function initializes iter to be an iterator on the "stack" argument michael@0: // and then skips over the leading entries as specified in prefix. It requires michael@0: // and asserts that "prefix" will be a prefix to "stack." michael@0: static void skip_clip_stack_prefix(const SkClipStack& prefix, michael@0: const SkClipStack& stack, michael@0: SkClipStack::Iter* iter) { michael@0: SkClipStack::B2TIter prefixIter(prefix); michael@0: iter->reset(stack, SkClipStack::Iter::kBottom_IterStart); michael@0: michael@0: const SkClipStack::Element* prefixEntry; michael@0: const SkClipStack::Element* iterEntry; michael@0: michael@0: for (prefixEntry = prefixIter.next(); prefixEntry; michael@0: prefixEntry = prefixIter.next()) { michael@0: iterEntry = iter->next(); michael@0: SkASSERT(iterEntry); michael@0: // Because of SkClipStack does internal intersection, the last clip michael@0: // entry may differ. michael@0: if (*prefixEntry != *iterEntry) { michael@0: SkASSERT(prefixEntry->getOp() == SkRegion::kIntersect_Op); michael@0: SkASSERT(iterEntry->getOp() == SkRegion::kIntersect_Op); michael@0: SkASSERT(iterEntry->getType() == prefixEntry->getType()); michael@0: // back up the iterator by one michael@0: iter->prev(); michael@0: prefixEntry = prefixIter.next(); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: SkASSERT(prefixEntry == NULL); michael@0: } michael@0: michael@0: static void emit_clip(SkPath* clipPath, SkRect* clipRect, michael@0: SkWStream* contentStream) { michael@0: SkASSERT(clipPath || clipRect); michael@0: michael@0: SkPath::FillType clipFill; michael@0: if (clipPath) { michael@0: SkPDFUtils::EmitPath(*clipPath, SkPaint::kFill_Style, contentStream); michael@0: clipFill = clipPath->getFillType(); michael@0: } else { michael@0: SkPDFUtils::AppendRectangle(*clipRect, contentStream); michael@0: clipFill = SkPath::kWinding_FillType; michael@0: } michael@0: michael@0: NOT_IMPLEMENTED(clipFill == SkPath::kInverseEvenOdd_FillType, false); michael@0: NOT_IMPLEMENTED(clipFill == SkPath::kInverseWinding_FillType, false); michael@0: if (clipFill == SkPath::kEvenOdd_FillType) { michael@0: contentStream->writeText("W* n\n"); michael@0: } else { michael@0: contentStream->writeText("W n\n"); michael@0: } michael@0: } michael@0: michael@0: #ifdef SK_PDF_USE_PATHOPS michael@0: /* Calculate an inverted path's equivalent non-inverted path, given the michael@0: * canvas bounds. michael@0: * outPath may alias with invPath (since this is supported by PathOps). michael@0: */ michael@0: static bool calculate_inverse_path(const SkRect& bounds, const SkPath& invPath, michael@0: SkPath* outPath) { michael@0: SkASSERT(invPath.isInverseFillType()); michael@0: michael@0: SkPath clipPath; michael@0: clipPath.addRect(bounds); michael@0: michael@0: return Op(clipPath, invPath, kIntersect_PathOp, outPath); michael@0: } michael@0: michael@0: // Sanity check the numerical values of the SkRegion ops and PathOps ops michael@0: // enums so region_op_to_pathops_op can do a straight passthrough cast. michael@0: // If these are failing, it may be necessary to make region_op_to_pathops_op michael@0: // do more. michael@0: SK_COMPILE_ASSERT(SkRegion::kDifference_Op == (int)kDifference_PathOp, michael@0: region_pathop_mismatch); michael@0: SK_COMPILE_ASSERT(SkRegion::kIntersect_Op == (int)kIntersect_PathOp, michael@0: region_pathop_mismatch); michael@0: SK_COMPILE_ASSERT(SkRegion::kUnion_Op == (int)kUnion_PathOp, michael@0: region_pathop_mismatch); michael@0: SK_COMPILE_ASSERT(SkRegion::kXOR_Op == (int)kXOR_PathOp, michael@0: region_pathop_mismatch); michael@0: SK_COMPILE_ASSERT(SkRegion::kReverseDifference_Op == michael@0: (int)kReverseDifference_PathOp, michael@0: region_pathop_mismatch); michael@0: michael@0: static SkPathOp region_op_to_pathops_op(SkRegion::Op op) { michael@0: SkASSERT(op >= 0); michael@0: SkASSERT(op <= SkRegion::kReverseDifference_Op); michael@0: return (SkPathOp)op; michael@0: } michael@0: michael@0: /* Uses Path Ops to calculate a vector SkPath clip from a clip stack. michael@0: * Returns true if successful, or false if not successful. michael@0: * If successful, the resulting clip is stored in outClipPath. michael@0: * If not successful, outClipPath is undefined, and a fallback method michael@0: * should be used. michael@0: */ michael@0: static bool get_clip_stack_path(const SkMatrix& transform, michael@0: const SkClipStack& clipStack, michael@0: const SkRegion& clipRegion, michael@0: SkPath* outClipPath) { michael@0: outClipPath->reset(); michael@0: outClipPath->setFillType(SkPath::kInverseWinding_FillType); michael@0: michael@0: const SkClipStack::Element* clipEntry; michael@0: SkClipStack::Iter iter; michael@0: iter.reset(clipStack, SkClipStack::Iter::kBottom_IterStart); michael@0: for (clipEntry = iter.next(); clipEntry; clipEntry = iter.next()) { michael@0: SkPath entryPath; michael@0: if (SkClipStack::Element::kEmpty_Type == clipEntry->getType()) { michael@0: outClipPath->reset(); michael@0: outClipPath->setFillType(SkPath::kInverseWinding_FillType); michael@0: continue; michael@0: } else { michael@0: clipEntry->asPath(&entryPath); michael@0: } michael@0: entryPath.transform(transform); michael@0: michael@0: if (SkRegion::kReplace_Op == clipEntry->getOp()) { michael@0: *outClipPath = entryPath; michael@0: } else { michael@0: SkPathOp op = region_op_to_pathops_op(clipEntry->getOp()); michael@0: if (!Op(*outClipPath, entryPath, op, outClipPath)) { michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (outClipPath->isInverseFillType()) { michael@0: // The bounds are slightly outset to ensure this is correct in the michael@0: // face of floating-point accuracy and possible SkRegion bitmap michael@0: // approximations. michael@0: SkRect clipBounds = SkRect::Make(clipRegion.getBounds()); michael@0: clipBounds.outset(SK_Scalar1, SK_Scalar1); michael@0: if (!calculate_inverse_path(clipBounds, *outClipPath, outClipPath)) { michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: #endif michael@0: michael@0: // TODO(vandebo): Take advantage of SkClipStack::getSaveCount(), the PDF michael@0: // graphic state stack, and the fact that we can know all the clips used michael@0: // on the page to optimize this. michael@0: void GraphicStackState::updateClip(const SkClipStack& clipStack, michael@0: const SkRegion& clipRegion, michael@0: const SkPoint& translation) { michael@0: if (clipStack == currentEntry()->fClipStack) { michael@0: return; michael@0: } michael@0: michael@0: while (fStackDepth > 0) { michael@0: pop(); michael@0: if (clipStack == currentEntry()->fClipStack) { michael@0: return; michael@0: } michael@0: } michael@0: push(); michael@0: michael@0: currentEntry()->fClipStack = clipStack; michael@0: currentEntry()->fClipRegion = clipRegion; michael@0: michael@0: SkMatrix transform; michael@0: transform.setTranslate(translation.fX, translation.fY); michael@0: michael@0: #ifdef SK_PDF_USE_PATHOPS michael@0: SkPath clipPath; michael@0: if (get_clip_stack_path(transform, clipStack, clipRegion, &clipPath)) { michael@0: emit_clip(&clipPath, NULL, fContentStream); michael@0: return; michael@0: } michael@0: #endif michael@0: // gsState->initialEntry()->fClipStack/Region specifies the clip that has michael@0: // already been applied. (If this is a top level device, then it specifies michael@0: // a clip to the content area. If this is a layer, then it specifies michael@0: // the clip in effect when the layer was created.) There's no need to michael@0: // reapply that clip; SKCanvas's SkDrawIter will draw anything outside the michael@0: // initial clip on the parent layer. (This means there's a bug if the user michael@0: // expands the clip and then uses any xfer mode that uses dst: michael@0: // http://code.google.com/p/skia/issues/detail?id=228 ) michael@0: SkClipStack::Iter iter; michael@0: skip_clip_stack_prefix(fEntries[0].fClipStack, clipStack, &iter); michael@0: michael@0: // If the clip stack does anything other than intersect or if it uses michael@0: // an inverse fill type, we have to fall back to the clip region. michael@0: bool needRegion = false; michael@0: const SkClipStack::Element* clipEntry; michael@0: for (clipEntry = iter.next(); clipEntry; clipEntry = iter.next()) { michael@0: if (clipEntry->getOp() != SkRegion::kIntersect_Op || michael@0: clipEntry->isInverseFilled()) { michael@0: needRegion = true; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (needRegion) { michael@0: SkPath clipPath; michael@0: SkAssertResult(clipRegion.getBoundaryPath(&clipPath)); michael@0: emit_clip(&clipPath, NULL, fContentStream); michael@0: } else { michael@0: skip_clip_stack_prefix(fEntries[0].fClipStack, clipStack, &iter); michael@0: const SkClipStack::Element* clipEntry; michael@0: for (clipEntry = iter.next(); clipEntry; clipEntry = iter.next()) { michael@0: SkASSERT(clipEntry->getOp() == SkRegion::kIntersect_Op); michael@0: switch (clipEntry->getType()) { michael@0: case SkClipStack::Element::kRect_Type: { michael@0: SkRect translatedClip; michael@0: transform.mapRect(&translatedClip, clipEntry->getRect()); michael@0: emit_clip(NULL, &translatedClip, fContentStream); michael@0: break; michael@0: } michael@0: default: { michael@0: SkPath translatedPath; michael@0: clipEntry->asPath(&translatedPath); michael@0: translatedPath.transform(transform, &translatedPath); michael@0: emit_clip(&translatedPath, NULL, fContentStream); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void GraphicStackState::updateMatrix(const SkMatrix& matrix) { michael@0: if (matrix == currentEntry()->fMatrix) { michael@0: return; michael@0: } michael@0: michael@0: if (currentEntry()->fMatrix.getType() != SkMatrix::kIdentity_Mask) { michael@0: SkASSERT(fStackDepth > 0); michael@0: SkASSERT(fEntries[fStackDepth].fClipStack == michael@0: fEntries[fStackDepth -1].fClipStack); michael@0: pop(); michael@0: michael@0: SkASSERT(currentEntry()->fMatrix.getType() == SkMatrix::kIdentity_Mask); michael@0: } michael@0: if (matrix.getType() == SkMatrix::kIdentity_Mask) { michael@0: return; michael@0: } michael@0: michael@0: push(); michael@0: SkPDFUtils::AppendTransform(matrix, fContentStream); michael@0: currentEntry()->fMatrix = matrix; michael@0: } michael@0: michael@0: void GraphicStackState::updateDrawingState(const GraphicStateEntry& state) { michael@0: // PDF treats a shader as a color, so we only set one or the other. michael@0: if (state.fShaderIndex >= 0) { michael@0: if (state.fShaderIndex != currentEntry()->fShaderIndex) { michael@0: SkPDFUtils::ApplyPattern(state.fShaderIndex, fContentStream); michael@0: currentEntry()->fShaderIndex = state.fShaderIndex; michael@0: } michael@0: } else { michael@0: if (state.fColor != currentEntry()->fColor || michael@0: currentEntry()->fShaderIndex >= 0) { michael@0: emit_pdf_color(state.fColor, fContentStream); michael@0: fContentStream->writeText("RG "); michael@0: emit_pdf_color(state.fColor, fContentStream); michael@0: fContentStream->writeText("rg\n"); michael@0: currentEntry()->fColor = state.fColor; michael@0: currentEntry()->fShaderIndex = -1; michael@0: } michael@0: } michael@0: michael@0: if (state.fGraphicStateIndex != currentEntry()->fGraphicStateIndex) { michael@0: SkPDFUtils::ApplyGraphicState(state.fGraphicStateIndex, fContentStream); michael@0: currentEntry()->fGraphicStateIndex = state.fGraphicStateIndex; michael@0: } michael@0: michael@0: if (state.fTextScaleX) { michael@0: if (state.fTextScaleX != currentEntry()->fTextScaleX) { michael@0: SkScalar pdfScale = SkScalarMul(state.fTextScaleX, michael@0: SkIntToScalar(100)); michael@0: SkPDFScalar::Append(pdfScale, fContentStream); michael@0: fContentStream->writeText(" Tz\n"); michael@0: currentEntry()->fTextScaleX = state.fTextScaleX; michael@0: } michael@0: if (state.fTextFill != currentEntry()->fTextFill) { michael@0: SK_COMPILE_ASSERT(SkPaint::kFill_Style == 0, enum_must_match_value); michael@0: SK_COMPILE_ASSERT(SkPaint::kStroke_Style == 1, michael@0: enum_must_match_value); michael@0: SK_COMPILE_ASSERT(SkPaint::kStrokeAndFill_Style == 2, michael@0: enum_must_match_value); michael@0: fContentStream->writeDecAsText(state.fTextFill); michael@0: fContentStream->writeText(" Tr\n"); michael@0: currentEntry()->fTextFill = state.fTextFill; michael@0: } michael@0: } michael@0: } michael@0: michael@0: SkBaseDevice* SkPDFDevice::onCreateDevice(const SkImageInfo& info, Usage usage) { michael@0: SkMatrix initialTransform; michael@0: initialTransform.reset(); michael@0: SkISize size = SkISize::Make(info.width(), info.height()); michael@0: return SkNEW_ARGS(SkPDFDevice, (size, size, initialTransform)); michael@0: } michael@0: michael@0: michael@0: struct ContentEntry { michael@0: GraphicStateEntry fState; michael@0: SkDynamicMemoryWStream fContent; michael@0: SkAutoTDelete fNext; michael@0: michael@0: // If the stack is too deep we could get Stack Overflow. michael@0: // So we manually destruct the object. michael@0: ~ContentEntry() { michael@0: ContentEntry* val = fNext.detach(); michael@0: while (val != NULL) { michael@0: ContentEntry* valNext = val->fNext.detach(); michael@0: // When the destructor is called, fNext is NULL and exits. michael@0: delete val; michael@0: val = valNext; michael@0: } michael@0: } michael@0: }; michael@0: michael@0: // A helper class to automatically finish a ContentEntry at the end of a michael@0: // drawing method and maintain the state needed between set up and finish. michael@0: class ScopedContentEntry { michael@0: public: michael@0: ScopedContentEntry(SkPDFDevice* device, const SkDraw& draw, michael@0: const SkPaint& paint, bool hasText = false) michael@0: : fDevice(device), michael@0: fContentEntry(NULL), michael@0: fXfermode(SkXfermode::kSrcOver_Mode), michael@0: fDstFormXObject(NULL) { michael@0: init(draw.fClipStack, *draw.fClip, *draw.fMatrix, paint, hasText); michael@0: } michael@0: ScopedContentEntry(SkPDFDevice* device, const SkClipStack* clipStack, michael@0: const SkRegion& clipRegion, const SkMatrix& matrix, michael@0: const SkPaint& paint, bool hasText = false) michael@0: : fDevice(device), michael@0: fContentEntry(NULL), michael@0: fXfermode(SkXfermode::kSrcOver_Mode), michael@0: fDstFormXObject(NULL) { michael@0: init(clipStack, clipRegion, matrix, paint, hasText); michael@0: } michael@0: michael@0: ~ScopedContentEntry() { michael@0: if (fContentEntry) { michael@0: SkPath* shape = &fShape; michael@0: if (shape->isEmpty()) { michael@0: shape = NULL; michael@0: } michael@0: fDevice->finishContentEntry(fXfermode, fDstFormXObject, shape); michael@0: } michael@0: SkSafeUnref(fDstFormXObject); michael@0: } michael@0: michael@0: ContentEntry* entry() { return fContentEntry; } michael@0: michael@0: /* Returns true when we explicitly need the shape of the drawing. */ michael@0: bool needShape() { michael@0: switch (fXfermode) { michael@0: case SkXfermode::kClear_Mode: michael@0: case SkXfermode::kSrc_Mode: michael@0: case SkXfermode::kSrcIn_Mode: michael@0: case SkXfermode::kSrcOut_Mode: michael@0: case SkXfermode::kDstIn_Mode: michael@0: case SkXfermode::kDstOut_Mode: michael@0: case SkXfermode::kSrcATop_Mode: michael@0: case SkXfermode::kDstATop_Mode: michael@0: case SkXfermode::kModulate_Mode: michael@0: return true; michael@0: default: michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: /* Returns true unless we only need the shape of the drawing. */ michael@0: bool needSource() { michael@0: if (fXfermode == SkXfermode::kClear_Mode) { michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: /* If the shape is different than the alpha component of the content, then michael@0: * setShape should be called with the shape. In particular, images and michael@0: * devices have rectangular shape. michael@0: */ michael@0: void setShape(const SkPath& shape) { michael@0: fShape = shape; michael@0: } michael@0: michael@0: private: michael@0: SkPDFDevice* fDevice; michael@0: ContentEntry* fContentEntry; michael@0: SkXfermode::Mode fXfermode; michael@0: SkPDFFormXObject* fDstFormXObject; michael@0: SkPath fShape; michael@0: michael@0: void init(const SkClipStack* clipStack, const SkRegion& clipRegion, michael@0: const SkMatrix& matrix, const SkPaint& paint, bool hasText) { michael@0: // Shape has to be flatten before we get here. michael@0: if (matrix.hasPerspective()) { michael@0: NOT_IMPLEMENTED(!matrix.hasPerspective(), false); michael@0: return; michael@0: } michael@0: if (paint.getXfermode()) { michael@0: paint.getXfermode()->asMode(&fXfermode); michael@0: } michael@0: fContentEntry = fDevice->setUpContentEntry(clipStack, clipRegion, michael@0: matrix, paint, hasText, michael@0: &fDstFormXObject); michael@0: } michael@0: }; michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: static inline SkBitmap makeContentBitmap(const SkISize& contentSize, michael@0: const SkMatrix* initialTransform) { michael@0: SkImageInfo info; michael@0: if (initialTransform) { michael@0: // Compute the size of the drawing area. michael@0: SkVector drawingSize; michael@0: SkMatrix inverse; michael@0: drawingSize.set(SkIntToScalar(contentSize.fWidth), michael@0: SkIntToScalar(contentSize.fHeight)); michael@0: if (!initialTransform->invert(&inverse)) { michael@0: // This shouldn't happen, initial transform should be invertible. michael@0: SkASSERT(false); michael@0: inverse.reset(); michael@0: } michael@0: inverse.mapVectors(&drawingSize, 1); michael@0: SkISize size = SkSize::Make(drawingSize.fX, drawingSize.fY).toRound(); michael@0: info = SkImageInfo::MakeUnknown(abs(size.fWidth), abs(size.fHeight)); michael@0: } else { michael@0: info = SkImageInfo::MakeUnknown(abs(contentSize.fWidth), michael@0: abs(contentSize.fHeight)); michael@0: } michael@0: michael@0: SkBitmap bitmap; michael@0: bitmap.setConfig(info); michael@0: return bitmap; michael@0: } michael@0: michael@0: // TODO(vandebo) change pageSize to SkSize. michael@0: // TODO: inherit from SkBaseDevice instead of SkBitmapDevice michael@0: SkPDFDevice::SkPDFDevice(const SkISize& pageSize, const SkISize& contentSize, michael@0: const SkMatrix& initialTransform) michael@0: : SkBitmapDevice(makeContentBitmap(contentSize, &initialTransform)), michael@0: fPageSize(pageSize), michael@0: fContentSize(contentSize), michael@0: fLastContentEntry(NULL), michael@0: fLastMarginContentEntry(NULL), michael@0: fClipStack(NULL), michael@0: fEncoder(NULL), michael@0: fRasterDpi(72.0f) { michael@0: // Just report that PDF does not supports perspective in the michael@0: // initial transform. michael@0: NOT_IMPLEMENTED(initialTransform.hasPerspective(), true); michael@0: michael@0: // Skia generally uses the top left as the origin but PDF natively has the michael@0: // origin at the bottom left. This matrix corrects for that. But that only michael@0: // needs to be done once, we don't do it when layering. michael@0: fInitialTransform.setTranslate(0, SkIntToScalar(pageSize.fHeight)); michael@0: fInitialTransform.preScale(SK_Scalar1, -SK_Scalar1); michael@0: fInitialTransform.preConcat(initialTransform); michael@0: michael@0: SkIRect existingClip = SkIRect::MakeWH(this->width(), this->height()); michael@0: fExistingClipRegion.setRect(existingClip); michael@0: michael@0: this->init(); michael@0: } michael@0: michael@0: // TODO(vandebo) change layerSize to SkSize. michael@0: SkPDFDevice::SkPDFDevice(const SkISize& layerSize, michael@0: const SkClipStack& existingClipStack, michael@0: const SkRegion& existingClipRegion) michael@0: : SkBitmapDevice(makeContentBitmap(layerSize, NULL)), michael@0: fPageSize(layerSize), michael@0: fContentSize(layerSize), michael@0: fExistingClipStack(existingClipStack), michael@0: fExistingClipRegion(existingClipRegion), michael@0: fLastContentEntry(NULL), michael@0: fLastMarginContentEntry(NULL), michael@0: fClipStack(NULL), michael@0: fEncoder(NULL), michael@0: fRasterDpi(72.0f) { michael@0: fInitialTransform.reset(); michael@0: this->init(); michael@0: } michael@0: michael@0: SkPDFDevice::~SkPDFDevice() { michael@0: this->cleanUp(true); michael@0: } michael@0: michael@0: void SkPDFDevice::init() { michael@0: fAnnotations = NULL; michael@0: fResourceDict = NULL; michael@0: fContentEntries.free(); michael@0: fLastContentEntry = NULL; michael@0: fMarginContentEntries.free(); michael@0: fLastMarginContentEntry = NULL; michael@0: fDrawingArea = kContent_DrawingArea; michael@0: if (fFontGlyphUsage.get() == NULL) { michael@0: fFontGlyphUsage.reset(new SkPDFGlyphSetMap()); michael@0: } michael@0: } michael@0: michael@0: void SkPDFDevice::cleanUp(bool clearFontUsage) { michael@0: fGraphicStateResources.unrefAll(); michael@0: fXObjectResources.unrefAll(); michael@0: fFontResources.unrefAll(); michael@0: fShaderResources.unrefAll(); michael@0: SkSafeUnref(fAnnotations); michael@0: SkSafeUnref(fResourceDict); michael@0: fNamedDestinations.deleteAll(); michael@0: michael@0: if (clearFontUsage) { michael@0: fFontGlyphUsage->reset(); michael@0: } michael@0: } michael@0: michael@0: void SkPDFDevice::clear(SkColor color) { michael@0: this->cleanUp(true); michael@0: this->init(); michael@0: michael@0: SkPaint paint; michael@0: paint.setColor(color); michael@0: paint.setStyle(SkPaint::kFill_Style); michael@0: SkMatrix identity; michael@0: identity.reset(); michael@0: ScopedContentEntry content(this, &fExistingClipStack, fExistingClipRegion, michael@0: identity, paint); michael@0: internalDrawPaint(paint, content.entry()); michael@0: } michael@0: michael@0: void SkPDFDevice::drawPaint(const SkDraw& d, const SkPaint& paint) { michael@0: SkPaint newPaint = paint; michael@0: newPaint.setStyle(SkPaint::kFill_Style); michael@0: ScopedContentEntry content(this, d, newPaint); michael@0: internalDrawPaint(newPaint, content.entry()); michael@0: } michael@0: michael@0: void SkPDFDevice::internalDrawPaint(const SkPaint& paint, michael@0: ContentEntry* contentEntry) { michael@0: if (!contentEntry) { michael@0: return; michael@0: } michael@0: SkRect bbox = SkRect::MakeWH(SkIntToScalar(this->width()), michael@0: SkIntToScalar(this->height())); michael@0: SkMatrix inverse; michael@0: if (!contentEntry->fState.fMatrix.invert(&inverse)) { michael@0: return; michael@0: } michael@0: inverse.mapRect(&bbox); michael@0: michael@0: SkPDFUtils::AppendRectangle(bbox, &contentEntry->fContent); michael@0: SkPDFUtils::PaintPath(paint.getStyle(), SkPath::kWinding_FillType, michael@0: &contentEntry->fContent); michael@0: } michael@0: michael@0: void SkPDFDevice::drawPoints(const SkDraw& d, SkCanvas::PointMode mode, michael@0: size_t count, const SkPoint* points, michael@0: const SkPaint& passedPaint) { michael@0: if (count == 0) { michael@0: return; michael@0: } michael@0: michael@0: if (handlePointAnnotation(points, count, *d.fMatrix, passedPaint)) { michael@0: return; michael@0: } michael@0: michael@0: // SkDraw::drawPoints converts to multiple calls to fDevice->drawPath. michael@0: // We only use this when there's a path effect because of the overhead michael@0: // of multiple calls to setUpContentEntry it causes. michael@0: if (passedPaint.getPathEffect()) { michael@0: if (d.fClip->isEmpty()) { michael@0: return; michael@0: } michael@0: SkDraw pointDraw(d); michael@0: pointDraw.fDevice = this; michael@0: pointDraw.drawPoints(mode, count, points, passedPaint, true); michael@0: return; michael@0: } michael@0: michael@0: const SkPaint* paint = &passedPaint; michael@0: SkPaint modifiedPaint; michael@0: michael@0: if (mode == SkCanvas::kPoints_PointMode && michael@0: paint->getStrokeCap() != SkPaint::kRound_Cap) { michael@0: modifiedPaint = *paint; michael@0: paint = &modifiedPaint; michael@0: if (paint->getStrokeWidth()) { michael@0: // PDF won't draw a single point with square/butt caps because the michael@0: // orientation is ambiguous. Draw a rectangle instead. michael@0: modifiedPaint.setStyle(SkPaint::kFill_Style); michael@0: SkScalar strokeWidth = paint->getStrokeWidth(); michael@0: SkScalar halfStroke = SkScalarHalf(strokeWidth); michael@0: for (size_t i = 0; i < count; i++) { michael@0: SkRect r = SkRect::MakeXYWH(points[i].fX, points[i].fY, 0, 0); michael@0: r.inset(-halfStroke, -halfStroke); michael@0: drawRect(d, r, modifiedPaint); michael@0: } michael@0: return; michael@0: } else { michael@0: modifiedPaint.setStrokeCap(SkPaint::kRound_Cap); michael@0: } michael@0: } michael@0: michael@0: ScopedContentEntry content(this, d, *paint); michael@0: if (!content.entry()) { michael@0: return; michael@0: } michael@0: michael@0: switch (mode) { michael@0: case SkCanvas::kPolygon_PointMode: michael@0: SkPDFUtils::MoveTo(points[0].fX, points[0].fY, michael@0: &content.entry()->fContent); michael@0: for (size_t i = 1; i < count; i++) { michael@0: SkPDFUtils::AppendLine(points[i].fX, points[i].fY, michael@0: &content.entry()->fContent); michael@0: } michael@0: SkPDFUtils::StrokePath(&content.entry()->fContent); michael@0: break; michael@0: case SkCanvas::kLines_PointMode: michael@0: for (size_t i = 0; i < count/2; i++) { michael@0: SkPDFUtils::MoveTo(points[i * 2].fX, points[i * 2].fY, michael@0: &content.entry()->fContent); michael@0: SkPDFUtils::AppendLine(points[i * 2 + 1].fX, michael@0: points[i * 2 + 1].fY, michael@0: &content.entry()->fContent); michael@0: SkPDFUtils::StrokePath(&content.entry()->fContent); michael@0: } michael@0: break; michael@0: case SkCanvas::kPoints_PointMode: michael@0: SkASSERT(paint->getStrokeCap() == SkPaint::kRound_Cap); michael@0: for (size_t i = 0; i < count; i++) { michael@0: SkPDFUtils::MoveTo(points[i].fX, points[i].fY, michael@0: &content.entry()->fContent); michael@0: SkPDFUtils::ClosePath(&content.entry()->fContent); michael@0: SkPDFUtils::StrokePath(&content.entry()->fContent); michael@0: } michael@0: break; michael@0: default: michael@0: SkASSERT(false); michael@0: } michael@0: } michael@0: michael@0: void SkPDFDevice::drawRect(const SkDraw& d, const SkRect& rect, michael@0: const SkPaint& paint) { michael@0: SkRect r = rect; michael@0: r.sort(); michael@0: michael@0: if (paint.getPathEffect()) { michael@0: if (d.fClip->isEmpty()) { michael@0: return; michael@0: } michael@0: SkPath path; michael@0: path.addRect(r); michael@0: drawPath(d, path, paint, NULL, true); michael@0: return; michael@0: } michael@0: michael@0: if (handleRectAnnotation(r, *d.fMatrix, paint)) { michael@0: return; michael@0: } michael@0: michael@0: ScopedContentEntry content(this, d, paint); michael@0: if (!content.entry()) { michael@0: return; michael@0: } michael@0: SkPDFUtils::AppendRectangle(r, &content.entry()->fContent); michael@0: SkPDFUtils::PaintPath(paint.getStyle(), SkPath::kWinding_FillType, michael@0: &content.entry()->fContent); michael@0: } michael@0: michael@0: void SkPDFDevice::drawRRect(const SkDraw& draw, const SkRRect& rrect, michael@0: const SkPaint& paint) { michael@0: SkPath path; michael@0: path.addRRect(rrect); michael@0: this->drawPath(draw, path, paint, NULL, true); michael@0: } michael@0: michael@0: void SkPDFDevice::drawPath(const SkDraw& d, const SkPath& origPath, michael@0: const SkPaint& paint, const SkMatrix* prePathMatrix, michael@0: bool pathIsMutable) { michael@0: SkPath modifiedPath; michael@0: SkPath* pathPtr = const_cast(&origPath); michael@0: michael@0: SkMatrix matrix = *d.fMatrix; michael@0: if (prePathMatrix) { michael@0: if (paint.getPathEffect() || paint.getStyle() != SkPaint::kFill_Style) { michael@0: if (!pathIsMutable) { michael@0: pathPtr = &modifiedPath; michael@0: pathIsMutable = true; michael@0: } michael@0: origPath.transform(*prePathMatrix, pathPtr); michael@0: } else { michael@0: if (!matrix.preConcat(*prePathMatrix)) { michael@0: // TODO(edisonn): report somehow why we failed? michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (paint.getPathEffect()) { michael@0: if (d.fClip->isEmpty()) { michael@0: return; michael@0: } michael@0: if (!pathIsMutable) { michael@0: pathPtr = &modifiedPath; michael@0: pathIsMutable = true; michael@0: } michael@0: bool fill = paint.getFillPath(origPath, pathPtr); michael@0: michael@0: SkPaint noEffectPaint(paint); michael@0: noEffectPaint.setPathEffect(NULL); michael@0: if (fill) { michael@0: noEffectPaint.setStyle(SkPaint::kFill_Style); michael@0: } else { michael@0: noEffectPaint.setStyle(SkPaint::kStroke_Style); michael@0: noEffectPaint.setStrokeWidth(0); michael@0: } michael@0: drawPath(d, *pathPtr, noEffectPaint, NULL, true); michael@0: return; michael@0: } michael@0: michael@0: #ifdef SK_PDF_USE_PATHOPS michael@0: if (handleInversePath(d, origPath, paint, pathIsMutable, prePathMatrix)) { michael@0: return; michael@0: } michael@0: #endif michael@0: michael@0: if (handleRectAnnotation(pathPtr->getBounds(), matrix, paint)) { michael@0: return; michael@0: } michael@0: michael@0: ScopedContentEntry content(this, d.fClipStack, *d.fClip, matrix, paint); michael@0: if (!content.entry()) { michael@0: return; michael@0: } michael@0: SkPDFUtils::EmitPath(*pathPtr, paint.getStyle(), michael@0: &content.entry()->fContent); michael@0: SkPDFUtils::PaintPath(paint.getStyle(), pathPtr->getFillType(), michael@0: &content.entry()->fContent); michael@0: } michael@0: michael@0: void SkPDFDevice::drawBitmapRect(const SkDraw& draw, const SkBitmap& bitmap, michael@0: const SkRect* src, const SkRect& dst, michael@0: const SkPaint& paint, michael@0: SkCanvas::DrawBitmapRectFlags flags) { michael@0: // TODO: this code path must be updated to respect the flags parameter michael@0: SkMatrix matrix; michael@0: SkRect bitmapBounds, tmpSrc, tmpDst; michael@0: SkBitmap tmpBitmap; michael@0: michael@0: bitmapBounds.isetWH(bitmap.width(), bitmap.height()); michael@0: michael@0: // Compute matrix from the two rectangles michael@0: if (src) { michael@0: tmpSrc = *src; michael@0: } else { michael@0: tmpSrc = bitmapBounds; michael@0: } michael@0: matrix.setRectToRect(tmpSrc, dst, SkMatrix::kFill_ScaleToFit); michael@0: michael@0: const SkBitmap* bitmapPtr = &bitmap; michael@0: michael@0: // clip the tmpSrc to the bounds of the bitmap, and recompute dstRect if michael@0: // needed (if the src was clipped). No check needed if src==null. michael@0: if (src) { michael@0: if (!bitmapBounds.contains(*src)) { michael@0: if (!tmpSrc.intersect(bitmapBounds)) { michael@0: return; // nothing to draw michael@0: } michael@0: // recompute dst, based on the smaller tmpSrc michael@0: matrix.mapRect(&tmpDst, tmpSrc); michael@0: } michael@0: michael@0: // since we may need to clamp to the borders of the src rect within michael@0: // the bitmap, we extract a subset. michael@0: // TODO: make sure this is handled in drawBitmap and remove from here. michael@0: SkIRect srcIR; michael@0: tmpSrc.roundOut(&srcIR); michael@0: if (!bitmap.extractSubset(&tmpBitmap, srcIR)) { michael@0: return; michael@0: } michael@0: bitmapPtr = &tmpBitmap; michael@0: michael@0: // Since we did an extract, we need to adjust the matrix accordingly michael@0: SkScalar dx = 0, dy = 0; michael@0: if (srcIR.fLeft > 0) { michael@0: dx = SkIntToScalar(srcIR.fLeft); michael@0: } michael@0: if (srcIR.fTop > 0) { michael@0: dy = SkIntToScalar(srcIR.fTop); michael@0: } michael@0: if (dx || dy) { michael@0: matrix.preTranslate(dx, dy); michael@0: } michael@0: } michael@0: this->drawBitmap(draw, *bitmapPtr, matrix, paint); michael@0: } michael@0: michael@0: void SkPDFDevice::drawBitmap(const SkDraw& d, const SkBitmap& bitmap, michael@0: const SkMatrix& matrix, const SkPaint& paint) { michael@0: if (d.fClip->isEmpty()) { michael@0: return; michael@0: } michael@0: michael@0: SkMatrix transform = matrix; michael@0: transform.postConcat(*d.fMatrix); michael@0: this->internalDrawBitmap(transform, d.fClipStack, *d.fClip, bitmap, NULL, michael@0: paint); michael@0: } michael@0: michael@0: void SkPDFDevice::drawSprite(const SkDraw& d, const SkBitmap& bitmap, michael@0: int x, int y, const SkPaint& paint) { michael@0: if (d.fClip->isEmpty()) { michael@0: return; michael@0: } michael@0: michael@0: SkMatrix matrix; michael@0: matrix.setTranslate(SkIntToScalar(x), SkIntToScalar(y)); michael@0: this->internalDrawBitmap(matrix, d.fClipStack, *d.fClip, bitmap, NULL, michael@0: paint); michael@0: } michael@0: michael@0: void SkPDFDevice::drawText(const SkDraw& d, const void* text, size_t len, michael@0: SkScalar x, SkScalar y, const SkPaint& paint) { michael@0: NOT_IMPLEMENTED(paint.getMaskFilter() != NULL, false); michael@0: if (paint.getMaskFilter() != NULL) { michael@0: // Don't pretend we support drawing MaskFilters, it makes for artifacts michael@0: // making text unreadable (e.g. same text twice when using CSS shadows). michael@0: return; michael@0: } michael@0: SkPaint textPaint = calculate_text_paint(paint); michael@0: ScopedContentEntry content(this, d, textPaint, true); michael@0: if (!content.entry()) { michael@0: return; michael@0: } michael@0: michael@0: SkGlyphStorage storage(0); michael@0: uint16_t* glyphIDs = NULL; michael@0: size_t numGlyphs = force_glyph_encoding(paint, text, len, &storage, michael@0: &glyphIDs); michael@0: textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); michael@0: michael@0: SkDrawCacheProc glyphCacheProc = textPaint.getDrawCacheProc(); michael@0: align_text(glyphCacheProc, textPaint, glyphIDs, numGlyphs, &x, &y); michael@0: content.entry()->fContent.writeText("BT\n"); michael@0: set_text_transform(x, y, textPaint.getTextSkewX(), michael@0: &content.entry()->fContent); michael@0: size_t consumedGlyphCount = 0; michael@0: while (numGlyphs > consumedGlyphCount) { michael@0: updateFont(textPaint, glyphIDs[consumedGlyphCount], content.entry()); michael@0: SkPDFFont* font = content.entry()->fState.fFont; michael@0: size_t availableGlyphs = michael@0: font->glyphsToPDFFontEncoding(glyphIDs + consumedGlyphCount, michael@0: numGlyphs - consumedGlyphCount); michael@0: fFontGlyphUsage->noteGlyphUsage(font, glyphIDs + consumedGlyphCount, michael@0: availableGlyphs); michael@0: SkString encodedString = michael@0: SkPDFString::FormatString(glyphIDs + consumedGlyphCount, michael@0: availableGlyphs, font->multiByteGlyphs()); michael@0: content.entry()->fContent.writeText(encodedString.c_str()); michael@0: consumedGlyphCount += availableGlyphs; michael@0: content.entry()->fContent.writeText(" Tj\n"); michael@0: } michael@0: content.entry()->fContent.writeText("ET\n"); michael@0: } michael@0: michael@0: void SkPDFDevice::drawPosText(const SkDraw& d, const void* text, size_t len, michael@0: const SkScalar pos[], SkScalar constY, michael@0: int scalarsPerPos, const SkPaint& paint) { michael@0: NOT_IMPLEMENTED(paint.getMaskFilter() != NULL, false); michael@0: if (paint.getMaskFilter() != NULL) { michael@0: // Don't pretend we support drawing MaskFilters, it makes for artifacts michael@0: // making text unreadable (e.g. same text twice when using CSS shadows). michael@0: return; michael@0: } michael@0: SkASSERT(1 == scalarsPerPos || 2 == scalarsPerPos); michael@0: SkPaint textPaint = calculate_text_paint(paint); michael@0: ScopedContentEntry content(this, d, textPaint, true); michael@0: if (!content.entry()) { michael@0: return; michael@0: } michael@0: michael@0: #ifdef SK_BUILD_FOR_ANDROID michael@0: /* michael@0: * In the case that we have enabled fallback fonts on Android we need to michael@0: * take the following steps to ensure that the PDF draws all characters, michael@0: * regardless of their underlying font file, correctly. michael@0: * michael@0: * 1. Convert input into GlyphID encoding if it currently is not michael@0: * 2. Iterate over the glyphIDs and identify the actual typeface that each michael@0: * glyph resolves to michael@0: * 3. Iterate over those typefaces and recursively call this function with michael@0: * only the glyphs (and their positions) that the typeface is capable of michael@0: * resolving. michael@0: */ michael@0: if (paint.getPaintOptionsAndroid().isUsingFontFallbacks()) { michael@0: uint16_t* glyphIDs = NULL; michael@0: SkGlyphStorage tmpStorage(0); michael@0: size_t numGlyphs = 0; michael@0: michael@0: // convert to glyphIDs michael@0: if (paint.getTextEncoding() == SkPaint::kGlyphID_TextEncoding) { michael@0: numGlyphs = len / 2; michael@0: glyphIDs = reinterpret_cast(const_cast(text)); michael@0: } else { michael@0: numGlyphs = paint.textToGlyphs(text, len, NULL); michael@0: tmpStorage.reset(numGlyphs); michael@0: paint.textToGlyphs(text, len, tmpStorage.get()); michael@0: glyphIDs = tmpStorage.get(); michael@0: } michael@0: michael@0: // if no typeface is provided in the paint get the default michael@0: SkAutoTUnref origFace(SkSafeRef(paint.getTypeface())); michael@0: if (NULL == origFace.get()) { michael@0: origFace.reset(SkTypeface::RefDefault()); michael@0: } michael@0: const uint16_t origGlyphCount = origFace->countGlyphs(); michael@0: michael@0: // keep a list of the already visited typefaces and some data about them michael@0: SkTDArray visitedTypefaces; michael@0: michael@0: // find all the typefaces needed to resolve this run of text michael@0: bool usesOriginalTypeface = false; michael@0: for (uint16_t x = 0; x < numGlyphs; ++x) { michael@0: // optimization that checks to see if original typeface can resolve michael@0: // the glyph michael@0: if (glyphIDs[x] < origGlyphCount) { michael@0: usesOriginalTypeface = true; michael@0: continue; michael@0: } michael@0: michael@0: // find the fallback typeface that supports this glyph michael@0: TypefaceFallbackData data; michael@0: data.typeface = michael@0: SkGetTypefaceForGlyphID(glyphIDs[x], origFace.get(), michael@0: paint.getPaintOptionsAndroid(), michael@0: &data.lowerBounds, michael@0: &data.upperBounds); michael@0: // add the typeface and its data if we don't have it michael@0: if (data.typeface && !visitedTypefaces.contains(data)) { michael@0: visitedTypefaces.push(data); michael@0: } michael@0: } michael@0: michael@0: // if the original font was used then add it to the list as well michael@0: if (usesOriginalTypeface) { michael@0: TypefaceFallbackData* data = visitedTypefaces.push(); michael@0: data->typeface = origFace.get(); michael@0: data->lowerBounds = 0; michael@0: data->upperBounds = origGlyphCount; michael@0: } michael@0: michael@0: // keep a scratch glyph and pos storage michael@0: SkAutoTMalloc posStorage(len * scalarsPerPos); michael@0: SkScalar* tmpPos = posStorage.get(); michael@0: SkGlyphStorage glyphStorage(numGlyphs); michael@0: uint16_t* tmpGlyphIDs = glyphStorage.get(); michael@0: michael@0: // loop through all the valid typefaces, trim the glyphs to only those michael@0: // resolved by the typeface, and then draw that run of glyphs michael@0: for (int x = 0; x < visitedTypefaces.count(); ++x) { michael@0: const TypefaceFallbackData& data = visitedTypefaces[x]; michael@0: michael@0: int tmpGlyphCount = 0; michael@0: for (uint16_t y = 0; y < numGlyphs; ++y) { michael@0: if (glyphIDs[y] >= data.lowerBounds && michael@0: glyphIDs[y] < data.upperBounds) { michael@0: tmpGlyphIDs[tmpGlyphCount] = glyphIDs[y] - data.lowerBounds; michael@0: memcpy(&(tmpPos[tmpGlyphCount * scalarsPerPos]), michael@0: &(pos[y * scalarsPerPos]), michael@0: scalarsPerPos * sizeof(SkScalar)); michael@0: tmpGlyphCount++; michael@0: } michael@0: } michael@0: michael@0: // recursively call this function with the right typeface michael@0: SkPaint tmpPaint = paint; michael@0: tmpPaint.setTypeface(data.typeface); michael@0: tmpPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); michael@0: michael@0: // turn off fallback chaining michael@0: SkPaintOptionsAndroid paintOpts = tmpPaint.getPaintOptionsAndroid(); michael@0: paintOpts.setUseFontFallbacks(false); michael@0: tmpPaint.setPaintOptionsAndroid(paintOpts); michael@0: michael@0: this->drawPosText(d, tmpGlyphIDs, tmpGlyphCount * 2, tmpPos, constY, michael@0: scalarsPerPos, tmpPaint); michael@0: } michael@0: return; michael@0: } michael@0: #endif michael@0: michael@0: SkGlyphStorage storage(0); michael@0: uint16_t* glyphIDs = NULL; michael@0: size_t numGlyphs = force_glyph_encoding(paint, text, len, &storage, michael@0: &glyphIDs); michael@0: textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); michael@0: michael@0: SkDrawCacheProc glyphCacheProc = textPaint.getDrawCacheProc(); michael@0: content.entry()->fContent.writeText("BT\n"); michael@0: updateFont(textPaint, glyphIDs[0], content.entry()); michael@0: for (size_t i = 0; i < numGlyphs; i++) { michael@0: SkPDFFont* font = content.entry()->fState.fFont; michael@0: uint16_t encodedValue = glyphIDs[i]; michael@0: if (font->glyphsToPDFFontEncoding(&encodedValue, 1) != 1) { michael@0: updateFont(textPaint, glyphIDs[i], content.entry()); michael@0: i--; michael@0: continue; michael@0: } michael@0: fFontGlyphUsage->noteGlyphUsage(font, &encodedValue, 1); michael@0: SkScalar x = pos[i * scalarsPerPos]; michael@0: SkScalar y = scalarsPerPos == 1 ? constY : pos[i * scalarsPerPos + 1]; michael@0: align_text(glyphCacheProc, textPaint, glyphIDs + i, 1, &x, &y); michael@0: set_text_transform(x, y, textPaint.getTextSkewX(), michael@0: &content.entry()->fContent); michael@0: SkString encodedString = michael@0: SkPDFString::FormatString(&encodedValue, 1, michael@0: font->multiByteGlyphs()); michael@0: content.entry()->fContent.writeText(encodedString.c_str()); michael@0: content.entry()->fContent.writeText(" Tj\n"); michael@0: } michael@0: content.entry()->fContent.writeText("ET\n"); michael@0: } michael@0: michael@0: void SkPDFDevice::drawTextOnPath(const SkDraw& d, const void* text, size_t len, michael@0: const SkPath& path, const SkMatrix* matrix, michael@0: const SkPaint& paint) { michael@0: if (d.fClip->isEmpty()) { michael@0: return; michael@0: } michael@0: d.drawTextOnPath((const char*)text, len, path, matrix, paint); michael@0: } michael@0: michael@0: void SkPDFDevice::drawVertices(const SkDraw& d, SkCanvas::VertexMode, michael@0: int vertexCount, const SkPoint verts[], michael@0: const SkPoint texs[], const SkColor colors[], michael@0: SkXfermode* xmode, const uint16_t indices[], michael@0: int indexCount, const SkPaint& paint) { michael@0: if (d.fClip->isEmpty()) { michael@0: return; michael@0: } michael@0: // TODO: implement drawVertices michael@0: } michael@0: michael@0: void SkPDFDevice::drawDevice(const SkDraw& d, SkBaseDevice* device, michael@0: int x, int y, const SkPaint& paint) { michael@0: // our onCreateDevice() always creates SkPDFDevice subclasses. michael@0: SkPDFDevice* pdfDevice = static_cast(device); michael@0: if (pdfDevice->isContentEmpty()) { michael@0: return; michael@0: } michael@0: michael@0: SkMatrix matrix; michael@0: matrix.setTranslate(SkIntToScalar(x), SkIntToScalar(y)); michael@0: ScopedContentEntry content(this, d.fClipStack, *d.fClip, matrix, paint); michael@0: if (!content.entry()) { michael@0: return; michael@0: } michael@0: if (content.needShape()) { michael@0: SkPath shape; michael@0: shape.addRect(SkRect::MakeXYWH(SkIntToScalar(x), SkIntToScalar(y), michael@0: SkIntToScalar(device->width()), michael@0: SkIntToScalar(device->height()))); michael@0: content.setShape(shape); michael@0: } michael@0: if (!content.needSource()) { michael@0: return; michael@0: } michael@0: michael@0: SkAutoTUnref xObject(new SkPDFFormXObject(pdfDevice)); michael@0: SkPDFUtils::DrawFormXObject(this->addXObjectResource(xObject.get()), michael@0: &content.entry()->fContent); michael@0: michael@0: // Merge glyph sets from the drawn device. michael@0: fFontGlyphUsage->merge(pdfDevice->getFontGlyphUsage()); michael@0: } michael@0: michael@0: void SkPDFDevice::onAttachToCanvas(SkCanvas* canvas) { michael@0: INHERITED::onAttachToCanvas(canvas); michael@0: michael@0: // Canvas promises that this ptr is valid until onDetachFromCanvas is called michael@0: fClipStack = canvas->getClipStack(); michael@0: } michael@0: michael@0: void SkPDFDevice::onDetachFromCanvas() { michael@0: INHERITED::onDetachFromCanvas(); michael@0: michael@0: fClipStack = NULL; michael@0: } michael@0: michael@0: ContentEntry* SkPDFDevice::getLastContentEntry() { michael@0: if (fDrawingArea == kContent_DrawingArea) { michael@0: return fLastContentEntry; michael@0: } else { michael@0: return fLastMarginContentEntry; michael@0: } michael@0: } michael@0: michael@0: SkAutoTDelete* SkPDFDevice::getContentEntries() { michael@0: if (fDrawingArea == kContent_DrawingArea) { michael@0: return &fContentEntries; michael@0: } else { michael@0: return &fMarginContentEntries; michael@0: } michael@0: } michael@0: michael@0: void SkPDFDevice::setLastContentEntry(ContentEntry* contentEntry) { michael@0: if (fDrawingArea == kContent_DrawingArea) { michael@0: fLastContentEntry = contentEntry; michael@0: } else { michael@0: fLastMarginContentEntry = contentEntry; michael@0: } michael@0: } michael@0: michael@0: void SkPDFDevice::setDrawingArea(DrawingArea drawingArea) { michael@0: // A ScopedContentEntry only exists during the course of a draw call, so michael@0: // this can't be called while a ScopedContentEntry exists. michael@0: fDrawingArea = drawingArea; michael@0: } michael@0: michael@0: SkPDFResourceDict* SkPDFDevice::getResourceDict() { michael@0: if (NULL == fResourceDict) { michael@0: fResourceDict = SkNEW(SkPDFResourceDict); michael@0: michael@0: if (fGraphicStateResources.count()) { michael@0: for (int i = 0; i < fGraphicStateResources.count(); i++) { michael@0: fResourceDict->insertResourceAsReference( michael@0: SkPDFResourceDict::kExtGState_ResourceType, michael@0: i, fGraphicStateResources[i]); michael@0: } michael@0: } michael@0: michael@0: if (fXObjectResources.count()) { michael@0: for (int i = 0; i < fXObjectResources.count(); i++) { michael@0: fResourceDict->insertResourceAsReference( michael@0: SkPDFResourceDict::kXObject_ResourceType, michael@0: i, fXObjectResources[i]); michael@0: } michael@0: } michael@0: michael@0: if (fFontResources.count()) { michael@0: for (int i = 0; i < fFontResources.count(); i++) { michael@0: fResourceDict->insertResourceAsReference( michael@0: SkPDFResourceDict::kFont_ResourceType, michael@0: i, fFontResources[i]); michael@0: } michael@0: } michael@0: michael@0: if (fShaderResources.count()) { michael@0: SkAutoTUnref patterns(new SkPDFDict()); michael@0: for (int i = 0; i < fShaderResources.count(); i++) { michael@0: fResourceDict->insertResourceAsReference( michael@0: SkPDFResourceDict::kPattern_ResourceType, michael@0: i, fShaderResources[i]); michael@0: } michael@0: } michael@0: } michael@0: return fResourceDict; michael@0: } michael@0: michael@0: const SkTDArray& SkPDFDevice::getFontResources() const { michael@0: return fFontResources; michael@0: } michael@0: michael@0: SkPDFArray* SkPDFDevice::copyMediaBox() const { michael@0: // should this be a singleton? michael@0: SkAutoTUnref zero(SkNEW_ARGS(SkPDFInt, (0))); michael@0: michael@0: SkPDFArray* mediaBox = SkNEW(SkPDFArray); michael@0: mediaBox->reserve(4); michael@0: mediaBox->append(zero.get()); michael@0: mediaBox->append(zero.get()); michael@0: mediaBox->appendInt(fPageSize.fWidth); michael@0: mediaBox->appendInt(fPageSize.fHeight); michael@0: return mediaBox; michael@0: } michael@0: michael@0: SkStream* SkPDFDevice::content() const { michael@0: SkMemoryStream* result = new SkMemoryStream; michael@0: result->setData(this->copyContentToData())->unref(); michael@0: return result; michael@0: } michael@0: michael@0: void SkPDFDevice::copyContentEntriesToData(ContentEntry* entry, michael@0: SkWStream* data) const { michael@0: // TODO(ctguil): For margins, I'm not sure fExistingClipStack/Region is the michael@0: // right thing to pass here. michael@0: GraphicStackState gsState(fExistingClipStack, fExistingClipRegion, data); michael@0: while (entry != NULL) { michael@0: SkPoint translation; michael@0: translation.iset(this->getOrigin()); michael@0: translation.negate(); michael@0: gsState.updateClip(entry->fState.fClipStack, entry->fState.fClipRegion, michael@0: translation); michael@0: gsState.updateMatrix(entry->fState.fMatrix); michael@0: gsState.updateDrawingState(entry->fState); michael@0: michael@0: SkAutoDataUnref copy(entry->fContent.copyToData()); michael@0: data->write(copy->data(), copy->size()); michael@0: entry = entry->fNext.get(); michael@0: } michael@0: gsState.drainStack(); michael@0: } michael@0: michael@0: SkData* SkPDFDevice::copyContentToData() const { michael@0: SkDynamicMemoryWStream data; michael@0: if (fInitialTransform.getType() != SkMatrix::kIdentity_Mask) { michael@0: SkPDFUtils::AppendTransform(fInitialTransform, &data); michael@0: } michael@0: michael@0: // TODO(aayushkumar): Apply clip along the margins. Currently, webkit michael@0: // colors the contentArea white before it starts drawing into it and michael@0: // that currently acts as our clip. michael@0: // Also, think about adding a transform here (or assume that the values michael@0: // sent across account for that) michael@0: SkPDFDevice::copyContentEntriesToData(fMarginContentEntries.get(), &data); michael@0: michael@0: // If the content area is the entire page, then we don't need to clip michael@0: // the content area (PDF area clips to the page size). Otherwise, michael@0: // we have to clip to the content area; we've already applied the michael@0: // initial transform, so just clip to the device size. michael@0: if (fPageSize != fContentSize) { michael@0: SkRect r = SkRect::MakeWH(SkIntToScalar(this->width()), michael@0: SkIntToScalar(this->height())); michael@0: emit_clip(NULL, &r, &data); michael@0: } michael@0: michael@0: SkPDFDevice::copyContentEntriesToData(fContentEntries.get(), &data); michael@0: michael@0: // potentially we could cache this SkData, and only rebuild it if we michael@0: // see that our state has changed. michael@0: return data.copyToData(); michael@0: } michael@0: michael@0: #ifdef SK_PDF_USE_PATHOPS michael@0: /* Draws an inverse filled path by using Path Ops to compute the positive michael@0: * inverse using the current clip as the inverse bounds. michael@0: * Return true if this was an inverse path and was properly handled, michael@0: * otherwise returns false and the normal drawing routine should continue, michael@0: * either as a (incorrect) fallback or because the path was not inverse michael@0: * in the first place. michael@0: */ michael@0: bool SkPDFDevice::handleInversePath(const SkDraw& d, const SkPath& origPath, michael@0: const SkPaint& paint, bool pathIsMutable, michael@0: const SkMatrix* prePathMatrix) { michael@0: if (!origPath.isInverseFillType()) { michael@0: return false; michael@0: } michael@0: michael@0: if (d.fClip->isEmpty()) { michael@0: return false; michael@0: } michael@0: michael@0: SkPath modifiedPath; michael@0: SkPath* pathPtr = const_cast(&origPath); michael@0: SkPaint noInversePaint(paint); michael@0: michael@0: // Merge stroking operations into final path. michael@0: if (SkPaint::kStroke_Style == paint.getStyle() || michael@0: SkPaint::kStrokeAndFill_Style == paint.getStyle()) { michael@0: bool doFillPath = paint.getFillPath(origPath, &modifiedPath); michael@0: if (doFillPath) { michael@0: noInversePaint.setStyle(SkPaint::kFill_Style); michael@0: noInversePaint.setStrokeWidth(0); michael@0: pathPtr = &modifiedPath; michael@0: } else { michael@0: // To be consistent with the raster output, hairline strokes michael@0: // are rendered as non-inverted. michael@0: modifiedPath.toggleInverseFillType(); michael@0: drawPath(d, modifiedPath, paint, NULL, true); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: // Get bounds of clip in current transform space michael@0: // (clip bounds are given in device space). michael@0: SkRect bounds; michael@0: SkMatrix transformInverse; michael@0: SkMatrix totalMatrix = *d.fMatrix; michael@0: if (prePathMatrix) { michael@0: totalMatrix.preConcat(*prePathMatrix); michael@0: } michael@0: if (!totalMatrix.invert(&transformInverse)) { michael@0: return false; michael@0: } michael@0: bounds.set(d.fClip->getBounds()); michael@0: transformInverse.mapRect(&bounds); michael@0: michael@0: // Extend the bounds by the line width (plus some padding) michael@0: // so the edge doesn't cause a visible stroke. michael@0: bounds.outset(paint.getStrokeWidth() + SK_Scalar1, michael@0: paint.getStrokeWidth() + SK_Scalar1); michael@0: michael@0: if (!calculate_inverse_path(bounds, *pathPtr, &modifiedPath)) { michael@0: return false; michael@0: } michael@0: michael@0: drawPath(d, modifiedPath, noInversePaint, prePathMatrix, true); michael@0: return true; michael@0: } michael@0: #endif michael@0: michael@0: bool SkPDFDevice::handleRectAnnotation(const SkRect& r, const SkMatrix& matrix, michael@0: const SkPaint& p) { michael@0: SkAnnotation* annotationInfo = p.getAnnotation(); michael@0: if (!annotationInfo) { michael@0: return false; michael@0: } michael@0: SkData* urlData = annotationInfo->find(SkAnnotationKeys::URL_Key()); michael@0: if (urlData) { michael@0: handleLinkToURL(urlData, r, matrix); michael@0: return p.getAnnotation() != NULL; michael@0: } michael@0: SkData* linkToName = annotationInfo->find( michael@0: SkAnnotationKeys::Link_Named_Dest_Key()); michael@0: if (linkToName) { michael@0: handleLinkToNamedDest(linkToName, r, matrix); michael@0: return p.getAnnotation() != NULL; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool SkPDFDevice::handlePointAnnotation(const SkPoint* points, size_t count, michael@0: const SkMatrix& matrix, michael@0: const SkPaint& paint) { michael@0: SkAnnotation* annotationInfo = paint.getAnnotation(); michael@0: if (!annotationInfo) { michael@0: return false; michael@0: } michael@0: SkData* nameData = annotationInfo->find( michael@0: SkAnnotationKeys::Define_Named_Dest_Key()); michael@0: if (nameData) { michael@0: for (size_t i = 0; i < count; i++) { michael@0: defineNamedDestination(nameData, points[i], matrix); michael@0: } michael@0: return paint.getAnnotation() != NULL; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: SkPDFDict* SkPDFDevice::createLinkAnnotation(const SkRect& r, michael@0: const SkMatrix& matrix) { michael@0: SkMatrix transform = matrix; michael@0: transform.postConcat(fInitialTransform); michael@0: SkRect translatedRect; michael@0: transform.mapRect(&translatedRect, r); michael@0: michael@0: if (NULL == fAnnotations) { michael@0: fAnnotations = SkNEW(SkPDFArray); michael@0: } michael@0: SkPDFDict* annotation(SkNEW_ARGS(SkPDFDict, ("Annot"))); michael@0: annotation->insertName("Subtype", "Link"); michael@0: fAnnotations->append(annotation); michael@0: michael@0: SkAutoTUnref border(SkNEW(SkPDFArray)); michael@0: border->reserve(3); michael@0: border->appendInt(0); // Horizontal corner radius. michael@0: border->appendInt(0); // Vertical corner radius. michael@0: border->appendInt(0); // Width, 0 = no border. michael@0: annotation->insert("Border", border.get()); michael@0: michael@0: SkAutoTUnref rect(SkNEW(SkPDFArray)); michael@0: rect->reserve(4); michael@0: rect->appendScalar(translatedRect.fLeft); michael@0: rect->appendScalar(translatedRect.fTop); michael@0: rect->appendScalar(translatedRect.fRight); michael@0: rect->appendScalar(translatedRect.fBottom); michael@0: annotation->insert("Rect", rect.get()); michael@0: michael@0: return annotation; michael@0: } michael@0: michael@0: void SkPDFDevice::handleLinkToURL(SkData* urlData, const SkRect& r, michael@0: const SkMatrix& matrix) { michael@0: SkAutoTUnref annotation(createLinkAnnotation(r, matrix)); michael@0: michael@0: SkString url(static_cast(urlData->data()), michael@0: urlData->size() - 1); michael@0: SkAutoTUnref action(SkNEW_ARGS(SkPDFDict, ("Action"))); michael@0: action->insertName("S", "URI"); michael@0: action->insert("URI", SkNEW_ARGS(SkPDFString, (url)))->unref(); michael@0: annotation->insert("A", action.get()); michael@0: } michael@0: michael@0: void SkPDFDevice::handleLinkToNamedDest(SkData* nameData, const SkRect& r, michael@0: const SkMatrix& matrix) { michael@0: SkAutoTUnref annotation(createLinkAnnotation(r, matrix)); michael@0: SkString name(static_cast(nameData->data()), michael@0: nameData->size() - 1); michael@0: annotation->insert("Dest", SkNEW_ARGS(SkPDFName, (name)))->unref(); michael@0: } michael@0: michael@0: struct NamedDestination { michael@0: const SkData* nameData; michael@0: SkPoint point; michael@0: michael@0: NamedDestination(const SkData* nameData, const SkPoint& point) michael@0: : nameData(nameData), point(point) { michael@0: nameData->ref(); michael@0: } michael@0: michael@0: ~NamedDestination() { michael@0: nameData->unref(); michael@0: } michael@0: }; michael@0: michael@0: void SkPDFDevice::defineNamedDestination(SkData* nameData, const SkPoint& point, michael@0: const SkMatrix& matrix) { michael@0: SkMatrix transform = matrix; michael@0: transform.postConcat(fInitialTransform); michael@0: SkPoint translatedPoint; michael@0: transform.mapXY(point.x(), point.y(), &translatedPoint); michael@0: fNamedDestinations.push( michael@0: SkNEW_ARGS(NamedDestination, (nameData, translatedPoint))); michael@0: } michael@0: michael@0: void SkPDFDevice::appendDestinations(SkPDFDict* dict, SkPDFObject* page) { michael@0: int nDest = fNamedDestinations.count(); michael@0: for (int i = 0; i < nDest; i++) { michael@0: NamedDestination* dest = fNamedDestinations[i]; michael@0: SkAutoTUnref pdfDest(SkNEW(SkPDFArray)); michael@0: pdfDest->reserve(5); michael@0: pdfDest->append(SkNEW_ARGS(SkPDFObjRef, (page)))->unref(); michael@0: pdfDest->appendName("XYZ"); michael@0: pdfDest->appendScalar(dest->point.x()); michael@0: pdfDest->appendScalar(dest->point.y()); michael@0: pdfDest->appendInt(0); // Leave zoom unchanged michael@0: dict->insert(static_cast(dest->nameData->data()), michael@0: pdfDest); michael@0: } michael@0: } michael@0: michael@0: SkPDFFormXObject* SkPDFDevice::createFormXObjectFromDevice() { michael@0: SkPDFFormXObject* xobject = SkNEW_ARGS(SkPDFFormXObject, (this)); michael@0: // We always draw the form xobjects that we create back into the device, so michael@0: // we simply preserve the font usage instead of pulling it out and merging michael@0: // it back in later. michael@0: cleanUp(false); // Reset this device to have no content. michael@0: init(); michael@0: return xobject; michael@0: } michael@0: michael@0: void SkPDFDevice::drawFormXObjectWithMask(int xObjectIndex, michael@0: SkPDFFormXObject* mask, michael@0: const SkClipStack* clipStack, michael@0: const SkRegion& clipRegion, michael@0: SkXfermode::Mode mode, michael@0: bool invertClip) { michael@0: if (clipRegion.isEmpty() && !invertClip) { michael@0: return; michael@0: } michael@0: michael@0: SkAutoTUnref sMaskGS( michael@0: SkPDFGraphicState::GetSMaskGraphicState( michael@0: mask, invertClip, SkPDFGraphicState::kAlpha_SMaskMode)); michael@0: michael@0: SkMatrix identity; michael@0: identity.reset(); michael@0: SkPaint paint; michael@0: paint.setXfermodeMode(mode); michael@0: ScopedContentEntry content(this, clipStack, clipRegion, identity, paint); michael@0: if (!content.entry()) { michael@0: return; michael@0: } michael@0: SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()), michael@0: &content.entry()->fContent); michael@0: SkPDFUtils::DrawFormXObject(xObjectIndex, &content.entry()->fContent); michael@0: michael@0: sMaskGS.reset(SkPDFGraphicState::GetNoSMaskGraphicState()); michael@0: SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()), michael@0: &content.entry()->fContent); michael@0: } michael@0: michael@0: ContentEntry* SkPDFDevice::setUpContentEntry(const SkClipStack* clipStack, michael@0: const SkRegion& clipRegion, michael@0: const SkMatrix& matrix, michael@0: const SkPaint& paint, michael@0: bool hasText, michael@0: SkPDFFormXObject** dst) { michael@0: *dst = NULL; michael@0: if (clipRegion.isEmpty()) { michael@0: return NULL; michael@0: } michael@0: michael@0: // The clip stack can come from an SkDraw where it is technically optional. michael@0: SkClipStack synthesizedClipStack; michael@0: if (clipStack == NULL) { michael@0: if (clipRegion == fExistingClipRegion) { michael@0: clipStack = &fExistingClipStack; michael@0: } else { michael@0: // GraphicStackState::updateClip expects the clip stack to have michael@0: // fExistingClip as a prefix, so start there, then set the clip michael@0: // to the passed region. michael@0: synthesizedClipStack = fExistingClipStack; michael@0: SkPath clipPath; michael@0: clipRegion.getBoundaryPath(&clipPath); michael@0: synthesizedClipStack.clipDevPath(clipPath, SkRegion::kReplace_Op, michael@0: false); michael@0: clipStack = &synthesizedClipStack; michael@0: } michael@0: } michael@0: michael@0: SkXfermode::Mode xfermode = SkXfermode::kSrcOver_Mode; michael@0: if (paint.getXfermode()) { michael@0: paint.getXfermode()->asMode(&xfermode); michael@0: } michael@0: michael@0: // For the following modes, we want to handle source and destination michael@0: // separately, so make an object of what's already there. michael@0: if (xfermode == SkXfermode::kClear_Mode || michael@0: xfermode == SkXfermode::kSrc_Mode || michael@0: xfermode == SkXfermode::kSrcIn_Mode || michael@0: xfermode == SkXfermode::kDstIn_Mode || michael@0: xfermode == SkXfermode::kSrcOut_Mode || michael@0: xfermode == SkXfermode::kDstOut_Mode || michael@0: xfermode == SkXfermode::kSrcATop_Mode || michael@0: xfermode == SkXfermode::kDstATop_Mode || michael@0: xfermode == SkXfermode::kModulate_Mode) { michael@0: if (!isContentEmpty()) { michael@0: *dst = createFormXObjectFromDevice(); michael@0: SkASSERT(isContentEmpty()); michael@0: } else if (xfermode != SkXfermode::kSrc_Mode && michael@0: xfermode != SkXfermode::kSrcOut_Mode) { michael@0: // Except for Src and SrcOut, if there isn't anything already there, michael@0: // then we're done. michael@0: return NULL; michael@0: } michael@0: } michael@0: // TODO(vandebo): Figure out how/if we can handle the following modes: michael@0: // Xor, Plus. michael@0: michael@0: // Dst xfer mode doesn't draw source at all. michael@0: if (xfermode == SkXfermode::kDst_Mode) { michael@0: return NULL; michael@0: } michael@0: michael@0: ContentEntry* entry; michael@0: SkAutoTDelete newEntry; michael@0: michael@0: ContentEntry* lastContentEntry = getLastContentEntry(); michael@0: if (lastContentEntry && lastContentEntry->fContent.getOffset() == 0) { michael@0: entry = lastContentEntry; michael@0: } else { michael@0: newEntry.reset(new ContentEntry); michael@0: entry = newEntry.get(); michael@0: } michael@0: michael@0: populateGraphicStateEntryFromPaint(matrix, *clipStack, clipRegion, paint, michael@0: hasText, &entry->fState); michael@0: if (lastContentEntry && xfermode != SkXfermode::kDstOver_Mode && michael@0: entry->fState.compareInitialState(lastContentEntry->fState)) { michael@0: return lastContentEntry; michael@0: } michael@0: michael@0: SkAutoTDelete* contentEntries = getContentEntries(); michael@0: if (!lastContentEntry) { michael@0: contentEntries->reset(entry); michael@0: setLastContentEntry(entry); michael@0: } else if (xfermode == SkXfermode::kDstOver_Mode) { michael@0: entry->fNext.reset(contentEntries->detach()); michael@0: contentEntries->reset(entry); michael@0: } else { michael@0: lastContentEntry->fNext.reset(entry); michael@0: setLastContentEntry(entry); michael@0: } michael@0: newEntry.detach(); michael@0: return entry; michael@0: } michael@0: michael@0: void SkPDFDevice::finishContentEntry(SkXfermode::Mode xfermode, michael@0: SkPDFFormXObject* dst, michael@0: SkPath* shape) { michael@0: if (xfermode != SkXfermode::kClear_Mode && michael@0: xfermode != SkXfermode::kSrc_Mode && michael@0: xfermode != SkXfermode::kDstOver_Mode && michael@0: xfermode != SkXfermode::kSrcIn_Mode && michael@0: xfermode != SkXfermode::kDstIn_Mode && michael@0: xfermode != SkXfermode::kSrcOut_Mode && michael@0: xfermode != SkXfermode::kDstOut_Mode && michael@0: xfermode != SkXfermode::kSrcATop_Mode && michael@0: xfermode != SkXfermode::kDstATop_Mode && michael@0: xfermode != SkXfermode::kModulate_Mode) { michael@0: SkASSERT(!dst); michael@0: return; michael@0: } michael@0: if (xfermode == SkXfermode::kDstOver_Mode) { michael@0: SkASSERT(!dst); michael@0: ContentEntry* firstContentEntry = getContentEntries()->get(); michael@0: if (firstContentEntry->fContent.getOffset() == 0) { michael@0: // For DstOver, an empty content entry was inserted before the rest michael@0: // of the content entries. If nothing was drawn, it needs to be michael@0: // removed. michael@0: SkAutoTDelete* contentEntries = getContentEntries(); michael@0: contentEntries->reset(firstContentEntry->fNext.detach()); michael@0: } michael@0: return; michael@0: } michael@0: if (!dst) { michael@0: SkASSERT(xfermode == SkXfermode::kSrc_Mode || michael@0: xfermode == SkXfermode::kSrcOut_Mode); michael@0: return; michael@0: } michael@0: michael@0: ContentEntry* contentEntries = getContentEntries()->get(); michael@0: SkASSERT(dst); michael@0: SkASSERT(!contentEntries->fNext.get()); michael@0: // Changing the current content into a form-xobject will destroy the clip michael@0: // objects which is fine since the xobject will already be clipped. However michael@0: // if source has shape, we need to clip it too, so a copy of the clip is michael@0: // saved. michael@0: SkClipStack clipStack = contentEntries->fState.fClipStack; michael@0: SkRegion clipRegion = contentEntries->fState.fClipRegion; michael@0: michael@0: SkMatrix identity; michael@0: identity.reset(); michael@0: SkPaint stockPaint; michael@0: michael@0: SkAutoTUnref srcFormXObject; michael@0: if (isContentEmpty()) { michael@0: // If nothing was drawn and there's no shape, then the draw was a michael@0: // no-op, but dst needs to be restored for that to be true. michael@0: // If there is shape, then an empty source with Src, SrcIn, SrcOut, michael@0: // DstIn, DstAtop or Modulate reduces to Clear and DstOut or SrcAtop michael@0: // reduces to Dst. michael@0: if (shape == NULL || xfermode == SkXfermode::kDstOut_Mode || michael@0: xfermode == SkXfermode::kSrcATop_Mode) { michael@0: ScopedContentEntry content(this, &fExistingClipStack, michael@0: fExistingClipRegion, identity, michael@0: stockPaint); michael@0: SkPDFUtils::DrawFormXObject(this->addXObjectResource(dst), michael@0: &content.entry()->fContent); michael@0: return; michael@0: } else { michael@0: xfermode = SkXfermode::kClear_Mode; michael@0: } michael@0: } else { michael@0: SkASSERT(!fContentEntries->fNext.get()); michael@0: srcFormXObject.reset(createFormXObjectFromDevice()); michael@0: } michael@0: michael@0: // TODO(vandebo) srcFormXObject may contain alpha, but here we want it michael@0: // without alpha. michael@0: if (xfermode == SkXfermode::kSrcATop_Mode) { michael@0: // TODO(vandebo): In order to properly support SrcATop we have to track michael@0: // the shape of what's been drawn at all times. It's the intersection of michael@0: // the non-transparent parts of the device and the outlines (shape) of michael@0: // all images and devices drawn. michael@0: drawFormXObjectWithMask(addXObjectResource(srcFormXObject.get()), dst, michael@0: &fExistingClipStack, fExistingClipRegion, michael@0: SkXfermode::kSrcOver_Mode, true); michael@0: } else { michael@0: SkAutoTUnref dstMaskStorage; michael@0: SkPDFFormXObject* dstMask = srcFormXObject.get(); michael@0: if (shape != NULL) { michael@0: // Draw shape into a form-xobject. michael@0: SkDraw d; michael@0: d.fMatrix = &identity; michael@0: d.fClip = &clipRegion; michael@0: d.fClipStack = &clipStack; michael@0: SkPaint filledPaint; michael@0: filledPaint.setColor(SK_ColorBLACK); michael@0: filledPaint.setStyle(SkPaint::kFill_Style); michael@0: this->drawPath(d, *shape, filledPaint, NULL, true); michael@0: michael@0: dstMaskStorage.reset(createFormXObjectFromDevice()); michael@0: dstMask = dstMaskStorage.get(); michael@0: } michael@0: drawFormXObjectWithMask(addXObjectResource(dst), dstMask, michael@0: &fExistingClipStack, fExistingClipRegion, michael@0: SkXfermode::kSrcOver_Mode, true); michael@0: } michael@0: michael@0: if (xfermode == SkXfermode::kClear_Mode) { michael@0: return; michael@0: } else if (xfermode == SkXfermode::kSrc_Mode || michael@0: xfermode == SkXfermode::kDstATop_Mode) { michael@0: ScopedContentEntry content(this, &fExistingClipStack, michael@0: fExistingClipRegion, identity, stockPaint); michael@0: if (content.entry()) { michael@0: SkPDFUtils::DrawFormXObject( michael@0: this->addXObjectResource(srcFormXObject.get()), michael@0: &content.entry()->fContent); michael@0: } michael@0: if (xfermode == SkXfermode::kSrc_Mode) { michael@0: return; michael@0: } michael@0: } else if (xfermode == SkXfermode::kSrcATop_Mode) { michael@0: ScopedContentEntry content(this, &fExistingClipStack, michael@0: fExistingClipRegion, identity, stockPaint); michael@0: if (content.entry()) { michael@0: SkPDFUtils::DrawFormXObject(this->addXObjectResource(dst), michael@0: &content.entry()->fContent); michael@0: } michael@0: } michael@0: michael@0: SkASSERT(xfermode == SkXfermode::kSrcIn_Mode || michael@0: xfermode == SkXfermode::kDstIn_Mode || michael@0: xfermode == SkXfermode::kSrcOut_Mode || michael@0: xfermode == SkXfermode::kDstOut_Mode || michael@0: xfermode == SkXfermode::kSrcATop_Mode || michael@0: xfermode == SkXfermode::kDstATop_Mode || michael@0: xfermode == SkXfermode::kModulate_Mode); michael@0: michael@0: if (xfermode == SkXfermode::kSrcIn_Mode || michael@0: xfermode == SkXfermode::kSrcOut_Mode || michael@0: xfermode == SkXfermode::kSrcATop_Mode) { michael@0: drawFormXObjectWithMask(addXObjectResource(srcFormXObject.get()), dst, michael@0: &fExistingClipStack, fExistingClipRegion, michael@0: SkXfermode::kSrcOver_Mode, michael@0: xfermode == SkXfermode::kSrcOut_Mode); michael@0: } else { michael@0: SkXfermode::Mode mode = SkXfermode::kSrcOver_Mode; michael@0: if (xfermode == SkXfermode::kModulate_Mode) { michael@0: drawFormXObjectWithMask(addXObjectResource(srcFormXObject.get()), michael@0: dst, &fExistingClipStack, michael@0: fExistingClipRegion, michael@0: SkXfermode::kSrcOver_Mode, false); michael@0: mode = SkXfermode::kMultiply_Mode; michael@0: } michael@0: drawFormXObjectWithMask(addXObjectResource(dst), srcFormXObject.get(), michael@0: &fExistingClipStack, fExistingClipRegion, mode, michael@0: xfermode == SkXfermode::kDstOut_Mode); michael@0: } michael@0: } michael@0: michael@0: bool SkPDFDevice::isContentEmpty() { michael@0: ContentEntry* contentEntries = getContentEntries()->get(); michael@0: if (!contentEntries || contentEntries->fContent.getOffset() == 0) { michael@0: SkASSERT(!contentEntries || !contentEntries->fNext.get()); michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: void SkPDFDevice::populateGraphicStateEntryFromPaint( michael@0: const SkMatrix& matrix, michael@0: const SkClipStack& clipStack, michael@0: const SkRegion& clipRegion, michael@0: const SkPaint& paint, michael@0: bool hasText, michael@0: GraphicStateEntry* entry) { michael@0: SkASSERT(paint.getPathEffect() == NULL); michael@0: michael@0: NOT_IMPLEMENTED(paint.getMaskFilter() != NULL, false); michael@0: NOT_IMPLEMENTED(paint.getColorFilter() != NULL, false); michael@0: michael@0: entry->fMatrix = matrix; michael@0: entry->fClipStack = clipStack; michael@0: entry->fClipRegion = clipRegion; michael@0: entry->fColor = SkColorSetA(paint.getColor(), 0xFF); michael@0: entry->fShaderIndex = -1; michael@0: michael@0: // PDF treats a shader as a color, so we only set one or the other. michael@0: SkAutoTUnref pdfShader; michael@0: const SkShader* shader = paint.getShader(); michael@0: SkColor color = paint.getColor(); michael@0: if (shader) { michael@0: // PDF positions patterns relative to the initial transform, so michael@0: // we need to apply the current transform to the shader parameters. michael@0: SkMatrix transform = matrix; michael@0: transform.postConcat(fInitialTransform); michael@0: michael@0: // PDF doesn't support kClamp_TileMode, so we simulate it by making michael@0: // a pattern the size of the current clip. michael@0: SkIRect bounds = clipRegion.getBounds(); michael@0: michael@0: // We need to apply the initial transform to bounds in order to get michael@0: // bounds in a consistent coordinate system. michael@0: SkRect boundsTemp; michael@0: boundsTemp.set(bounds); michael@0: fInitialTransform.mapRect(&boundsTemp); michael@0: boundsTemp.roundOut(&bounds); michael@0: michael@0: pdfShader.reset(SkPDFShader::GetPDFShader(*shader, transform, bounds)); michael@0: michael@0: if (pdfShader.get()) { michael@0: // pdfShader has been canonicalized so we can directly compare michael@0: // pointers. michael@0: int resourceIndex = fShaderResources.find(pdfShader.get()); michael@0: if (resourceIndex < 0) { michael@0: resourceIndex = fShaderResources.count(); michael@0: fShaderResources.push(pdfShader.get()); michael@0: pdfShader.get()->ref(); michael@0: } michael@0: entry->fShaderIndex = resourceIndex; michael@0: } else { michael@0: // A color shader is treated as an invalid shader so we don't have michael@0: // to set a shader just for a color. michael@0: SkShader::GradientInfo gradientInfo; michael@0: SkColor gradientColor; michael@0: gradientInfo.fColors = &gradientColor; michael@0: gradientInfo.fColorOffsets = NULL; michael@0: gradientInfo.fColorCount = 1; michael@0: if (shader->asAGradient(&gradientInfo) == michael@0: SkShader::kColor_GradientType) { michael@0: entry->fColor = SkColorSetA(gradientColor, 0xFF); michael@0: color = gradientColor; michael@0: } michael@0: } michael@0: } michael@0: michael@0: SkAutoTUnref newGraphicState; michael@0: if (color == paint.getColor()) { michael@0: newGraphicState.reset( michael@0: SkPDFGraphicState::GetGraphicStateForPaint(paint)); michael@0: } else { michael@0: SkPaint newPaint = paint; michael@0: newPaint.setColor(color); michael@0: newGraphicState.reset( michael@0: SkPDFGraphicState::GetGraphicStateForPaint(newPaint)); michael@0: } michael@0: int resourceIndex = addGraphicStateResource(newGraphicState.get()); michael@0: entry->fGraphicStateIndex = resourceIndex; michael@0: michael@0: if (hasText) { michael@0: entry->fTextScaleX = paint.getTextScaleX(); michael@0: entry->fTextFill = paint.getStyle(); michael@0: } else { michael@0: entry->fTextScaleX = 0; michael@0: } michael@0: } michael@0: michael@0: int SkPDFDevice::addGraphicStateResource(SkPDFGraphicState* gs) { michael@0: // Assumes that gs has been canonicalized (so we can directly compare michael@0: // pointers). michael@0: int result = fGraphicStateResources.find(gs); michael@0: if (result < 0) { michael@0: result = fGraphicStateResources.count(); michael@0: fGraphicStateResources.push(gs); michael@0: gs->ref(); michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: int SkPDFDevice::addXObjectResource(SkPDFObject* xObject) { michael@0: // Assumes that xobject has been canonicalized (so we can directly compare michael@0: // pointers). michael@0: int result = fXObjectResources.find(xObject); michael@0: if (result < 0) { michael@0: result = fXObjectResources.count(); michael@0: fXObjectResources.push(xObject); michael@0: xObject->ref(); michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: void SkPDFDevice::updateFont(const SkPaint& paint, uint16_t glyphID, michael@0: ContentEntry* contentEntry) { michael@0: SkTypeface* typeface = paint.getTypeface(); michael@0: if (contentEntry->fState.fFont == NULL || michael@0: contentEntry->fState.fTextSize != paint.getTextSize() || michael@0: !contentEntry->fState.fFont->hasGlyph(glyphID)) { michael@0: int fontIndex = getFontResourceIndex(typeface, glyphID); michael@0: contentEntry->fContent.writeText("/"); michael@0: contentEntry->fContent.writeText(SkPDFResourceDict::getResourceName( michael@0: SkPDFResourceDict::kFont_ResourceType, michael@0: fontIndex).c_str()); michael@0: contentEntry->fContent.writeText(" "); michael@0: SkPDFScalar::Append(paint.getTextSize(), &contentEntry->fContent); michael@0: contentEntry->fContent.writeText(" Tf\n"); michael@0: contentEntry->fState.fFont = fFontResources[fontIndex]; michael@0: } michael@0: } michael@0: michael@0: int SkPDFDevice::getFontResourceIndex(SkTypeface* typeface, uint16_t glyphID) { michael@0: SkAutoTUnref newFont(SkPDFFont::GetFontResource(typeface, michael@0: glyphID)); michael@0: int resourceIndex = fFontResources.find(newFont.get()); michael@0: if (resourceIndex < 0) { michael@0: resourceIndex = fFontResources.count(); michael@0: fFontResources.push(newFont.get()); michael@0: newFont.get()->ref(); michael@0: } michael@0: return resourceIndex; michael@0: } michael@0: michael@0: void SkPDFDevice::internalDrawBitmap(const SkMatrix& origMatrix, michael@0: const SkClipStack* clipStack, michael@0: const SkRegion& origClipRegion, michael@0: const SkBitmap& origBitmap, michael@0: const SkIRect* srcRect, michael@0: const SkPaint& paint) { michael@0: SkMatrix matrix = origMatrix; michael@0: SkRegion perspectiveBounds; michael@0: const SkRegion* clipRegion = &origClipRegion; michael@0: SkBitmap perspectiveBitmap; michael@0: const SkBitmap* bitmap = &origBitmap; michael@0: SkBitmap tmpSubsetBitmap; michael@0: michael@0: // Rasterize the bitmap using perspective in a new bitmap. michael@0: if (origMatrix.hasPerspective()) { michael@0: if (fRasterDpi == 0) { michael@0: return; michael@0: } michael@0: SkBitmap* subsetBitmap; michael@0: if (srcRect) { michael@0: if (!origBitmap.extractSubset(&tmpSubsetBitmap, *srcRect)) { michael@0: return; michael@0: } michael@0: subsetBitmap = &tmpSubsetBitmap; michael@0: } else { michael@0: subsetBitmap = &tmpSubsetBitmap; michael@0: *subsetBitmap = origBitmap; michael@0: } michael@0: srcRect = NULL; michael@0: michael@0: // Transform the bitmap in the new space, without taking into michael@0: // account the initial transform. michael@0: SkPath perspectiveOutline; michael@0: perspectiveOutline.addRect( michael@0: SkRect::MakeWH(SkIntToScalar(subsetBitmap->width()), michael@0: SkIntToScalar(subsetBitmap->height()))); michael@0: perspectiveOutline.transform(origMatrix); michael@0: michael@0: // TODO(edisonn): perf - use current clip too. michael@0: // Retrieve the bounds of the new shape. michael@0: SkRect bounds = perspectiveOutline.getBounds(); michael@0: michael@0: // Transform the bitmap in the new space, taking into michael@0: // account the initial transform. michael@0: SkMatrix total = origMatrix; michael@0: total.postConcat(fInitialTransform); michael@0: total.postScale(SkIntToScalar(fRasterDpi) / michael@0: SkIntToScalar(DPI_FOR_RASTER_SCALE_ONE), michael@0: SkIntToScalar(fRasterDpi) / michael@0: SkIntToScalar(DPI_FOR_RASTER_SCALE_ONE)); michael@0: SkPath physicalPerspectiveOutline; michael@0: physicalPerspectiveOutline.addRect( michael@0: SkRect::MakeWH(SkIntToScalar(subsetBitmap->width()), michael@0: SkIntToScalar(subsetBitmap->height()))); michael@0: physicalPerspectiveOutline.transform(total); michael@0: michael@0: SkScalar scaleX = physicalPerspectiveOutline.getBounds().width() / michael@0: bounds.width(); michael@0: SkScalar scaleY = physicalPerspectiveOutline.getBounds().height() / michael@0: bounds.height(); michael@0: michael@0: // TODO(edisonn): A better approach would be to use a bitmap shader michael@0: // (in clamp mode) and draw a rect over the entire bounding box. Then michael@0: // intersect perspectiveOutline to the clip. That will avoid introducing michael@0: // alpha to the image while still giving good behavior at the edge of michael@0: // the image. Avoiding alpha will reduce the pdf size and generation michael@0: // CPU time some. michael@0: michael@0: const int w = SkScalarCeilToInt(physicalPerspectiveOutline.getBounds().width()); michael@0: const int h = SkScalarCeilToInt(physicalPerspectiveOutline.getBounds().height()); michael@0: if (!perspectiveBitmap.allocPixels(SkImageInfo::MakeN32Premul(w, h))) { michael@0: return; michael@0: } michael@0: perspectiveBitmap.eraseColor(SK_ColorTRANSPARENT); michael@0: michael@0: SkBitmapDevice device(perspectiveBitmap); michael@0: SkCanvas canvas(&device); michael@0: michael@0: SkScalar deltaX = bounds.left(); michael@0: SkScalar deltaY = bounds.top(); michael@0: michael@0: SkMatrix offsetMatrix = origMatrix; michael@0: offsetMatrix.postTranslate(-deltaX, -deltaY); michael@0: offsetMatrix.postScale(scaleX, scaleY); michael@0: michael@0: // Translate the draw in the new canvas, so we perfectly fit the michael@0: // shape in the bitmap. michael@0: canvas.setMatrix(offsetMatrix); michael@0: michael@0: canvas.drawBitmap(*subsetBitmap, SkIntToScalar(0), SkIntToScalar(0)); michael@0: michael@0: // Make sure the final bits are in the bitmap. michael@0: canvas.flush(); michael@0: michael@0: // In the new space, we use the identity matrix translated michael@0: // and scaled to reflect DPI. michael@0: matrix.setScale(1 / scaleX, 1 / scaleY); michael@0: matrix.postTranslate(deltaX, deltaY); michael@0: michael@0: perspectiveBounds.setRect( michael@0: SkIRect::MakeXYWH(SkScalarFloorToInt(bounds.x()), michael@0: SkScalarFloorToInt(bounds.y()), michael@0: SkScalarCeilToInt(bounds.width()), michael@0: SkScalarCeilToInt(bounds.height()))); michael@0: clipRegion = &perspectiveBounds; michael@0: srcRect = NULL; michael@0: bitmap = &perspectiveBitmap; michael@0: } michael@0: michael@0: SkMatrix scaled; michael@0: // Adjust for origin flip. michael@0: scaled.setScale(SK_Scalar1, -SK_Scalar1); michael@0: scaled.postTranslate(0, SK_Scalar1); michael@0: // Scale the image up from 1x1 to WxH. michael@0: SkIRect subset = SkIRect::MakeWH(bitmap->width(), bitmap->height()); michael@0: scaled.postScale(SkIntToScalar(subset.width()), michael@0: SkIntToScalar(subset.height())); michael@0: scaled.postConcat(matrix); michael@0: ScopedContentEntry content(this, clipStack, *clipRegion, scaled, paint); michael@0: if (!content.entry() || (srcRect && !subset.intersect(*srcRect))) { michael@0: return; michael@0: } michael@0: if (content.needShape()) { michael@0: SkPath shape; michael@0: shape.addRect(SkRect::MakeWH(SkIntToScalar(subset.width()), michael@0: SkIntToScalar( subset.height()))); michael@0: shape.transform(matrix); michael@0: content.setShape(shape); michael@0: } michael@0: if (!content.needSource()) { michael@0: return; michael@0: } michael@0: michael@0: SkAutoTUnref image( michael@0: SkPDFImage::CreateImage(*bitmap, subset, fEncoder)); michael@0: if (!image) { michael@0: return; michael@0: } michael@0: michael@0: SkPDFUtils::DrawFormXObject(this->addXObjectResource(image.get()), michael@0: &content.entry()->fContent); michael@0: } michael@0: michael@0: bool SkPDFDevice::onReadPixels(const SkBitmap& bitmap, int x, int y, michael@0: SkCanvas::Config8888) { michael@0: return false; michael@0: } michael@0: michael@0: bool SkPDFDevice::allowImageFilter(const SkImageFilter*) { michael@0: return false; michael@0: }